Skip to content

Commit

Permalink
feat(core): add signal.get() and signal.set() (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
ocavue authored Jun 16, 2024
1 parent e448c8b commit eec595a
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 93 deletions.
7 changes: 7 additions & 0 deletions .changeset/swift-toes-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@aria-ui/core": patch
---

Signals now use `.get()` and `.set()` instead of `.value`.

This matches the design of the stage-1 JavaScript Signals standard proposal.
88 changes: 63 additions & 25 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,68 @@ Shares signals easily across widely nested HTML elements through context.

A comprehensive collection of utilities for DOM interactions, enabling declarative management of attributes, styles, and event listeners.

## Interfaces

### ReadonlySignal\<T\>

A read-only signal that holds a reactive value.

#### Accessors

##### value

#### Methods

##### get()

```ts
get(): T
```

Get the signal's current value.

##### peek()

```ts
peek(): T
```

Get the signal's current value without subscribing.

### Signal\<T\>

A mutable signal that can be used to manage reactive state changes.

#### Accessors

##### value

#### Methods

##### get()

```ts
get(): T
```

Get the signal's current value.

##### peek()

```ts
peek(): T
```

Get the signal's current value without subscribing.

##### set()

```ts
set(value: T): void
```

Set the value of the signal.

## Functions

### ElementMixin()
Expand Down Expand Up @@ -51,7 +113,7 @@ Receives the signal from a parent element.
##### provide()

```ts
provide(element: ConnectableElement, signal: Signal<T>): void
provide(element: ConnectableElement, signal: Signal<T> | ReadonlySignal<T>): void
```

Provides a signal to all children of the element.
Expand Down Expand Up @@ -222,26 +284,6 @@ Maps every signal in the given object to its current value.
## Signals
### ReadonlySignal\<T\>
```ts
type ReadonlySignal<T>: _ReadonlySignal<T>;
```
A read-only signal, providing a way to observe state changes without the ability to modify the state.
This is a re-export of `ReadonlySignal` type from `@preact/signals-core`.
### Signal\<T\>
```ts
type Signal<T>: _Signal<T>;
```
A mutable signal that can be used to manage reactive state changes.
This is a re-export of `Signal` type from `@preact/signals-core`.
### SignalValue\<S\>
```ts
Expand All @@ -268,8 +310,6 @@ function createComputed<T>(fn: () => T): ReadonlySignal<T>;
Creates a computed signal that automatically updates its value based on the reactive dependencies it uses. Computed signals are read-only and are used to derive state from other signals, recalculating their value when dependencies change.
This is an alias for `computed` from `@preact/signals-core`.
### createSignal()
```ts
Expand All @@ -278,8 +318,6 @@ function createSignal<T>(value: T): Signal<T>;
Creates and returns a new signal with the given initial value. Signals are reactive data sources that can be read and written to, allowing components to reactively update when their values change.
This is an alias for `signal` from `@preact/signals-core`.
### untracked()
```ts
Expand Down
69 changes: 41 additions & 28 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ConnectableElement } from "./connectable-element"
import { createSignal, type Signal } from "./signals"
import type { ReadonlySignal, Signal } from "./signals"

class ContextRequestEvent<T> extends Event {
public constructor(
Expand All @@ -21,11 +21,15 @@ export interface Context<T> {
* @param element The element to provide the signal to.
* @param signal The signal to provide.
*/
provide(element: ConnectableElement, signal: Signal<T>): void
provide(
element: ConnectableElement,
signal: Signal<T> | ReadonlySignal<T>,
): void

/**
* Receives the signal from a parent element.
* @param element The element to consume the signal from.
* @returns A signal that is double bound to the provided signal.
* @returns A signal that is double-bound to the provided signal.
*/
consume(element: ConnectableElement): Signal<T>
}
Expand Down Expand Up @@ -55,44 +59,53 @@ class ContextImpl<T> implements Context<T> {
}

public consume(element: ConnectableElement): Signal<T> {
const consumer = createSignal<T>(this.defaultValue)
let dispose: VoidFunction | undefined = undefined
let getter: (() => T) | null = null
let peeker: (() => T) | null = null
let setter: ((value: T) => void) | null = null

element.addConnectedCallback(() => {
element.dispatchEvent(
new ContextRequestEvent<T>(this.key, (provider) => {
dispose?.()
dispose = bind(provider, consumer)
getter = () => provider.get()
peeker = () => provider.peek()
setter = (value: T) => provider.set(value)
}),
)
return () => {
dispose?.()
dispose = undefined
}
})

return consumer
}
}

function bind<T>(provider: Signal<T>, consumer: Signal<T>): VoidFunction {
consumer.value = provider.peek()
const get = () => {
return getter ? getter() : this.defaultValue
}

const unsubscribeProvider = provider.subscribe((value) => {
if (consumer.peek() !== value) {
consumer.value = value
const set = (value: T) => {
setter?.(value)
}
})

const unsubscribeConsumer = consumer.subscribe((value) => {
if (provider.peek() !== value) {
provider.value = value
const peek = () => {
return peeker ? peeker() : this.defaultValue
}
})

return () => {
unsubscribeProvider()
unsubscribeConsumer()
return {
get,

/**
* @deprecated
*/
get value() {
return get()
},

set,

/**
* @deprecated
*/
set value(value: T) {
set(value)
},

peek,
}
}
}

Expand Down
7 changes: 2 additions & 5 deletions packages/core/src/signal-state.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import type { Signal } from "@preact/signals-core"
import { signal } from "@preact/signals-core"

import { createSignal, type Signal } from "./signals"
import { getObjectEntries } from "./types"

// TODO: correct typo
/**
* A plain object containing signals.
*
Expand Down Expand Up @@ -34,7 +31,7 @@ export function mapValues<T extends object>(signals: SignalState<T>): T {
export function mapSignals<T extends object>(values: T): SignalState<T> {
const signals = {} as SignalState<T>
for (const [key, value] of getObjectEntries(values)) {
signals[key] = signal(value)
signals[key] = createSignal(value)
}
return signals
}
Loading

0 comments on commit eec595a

Please sign in to comment.