synstate-preact-signals 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/dist/create-boolean-state.d.mts +39 -0
- package/dist/create-boolean-state.d.mts.map +1 -0
- package/dist/create-boolean-state.mjs +49 -0
- package/dist/create-boolean-state.mjs.map +1 -0
- package/dist/create-reducer.d.mts +39 -0
- package/dist/create-reducer.d.mts.map +1 -0
- package/dist/create-reducer.mjs +42 -0
- package/dist/create-reducer.mjs.map +1 -0
- package/dist/create-state.d.mts +45 -0
- package/dist/create-state.d.mts.map +1 -0
- package/dist/create-state.mjs +56 -0
- package/dist/create-state.mjs.map +1 -0
- package/dist/from-signal.d.mts +40 -0
- package/dist/from-signal.d.mts.map +1 -0
- package/dist/from-signal.mjs +51 -0
- package/dist/from-signal.mjs.map +1 -0
- package/dist/globals.d.mts +1 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +7 -0
- package/dist/index.mjs.map +1 -0
- package/dist/synstate.d.mts +2 -0
- package/dist/synstate.d.mts.map +1 -0
- package/dist/synstate.mjs +2 -0
- package/dist/synstate.mjs.map +1 -0
- package/dist/to-signal.d.mts +42 -0
- package/dist/to-signal.d.mts.map +1 -0
- package/dist/to-signal.mjs +15 -0
- package/dist/to-signal.mjs.map +1 -0
- package/dist/tsconfig.json +1 -0
- package/dist/types.d.mts +2 -0
- package/package.json +74 -0
- package/src/create-boolean-state.mts +78 -0
- package/src/create-reducer.mts +59 -0
- package/src/create-state.mts +72 -0
- package/src/from-signal.mts +54 -0
- package/src/globals.d.mts +1 -0
- package/src/index.mts +6 -0
- package/src/synstate.mts +58 -0
- package/src/to-signal.mts +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# synstate-preact-signals
|
|
2
|
+
|
|
3
|
+
[Preact Signals](https://preactjs.com/guide/v10/signals/) integration for [SynState](https://github.com/noshiro-pf/synstate).
|
|
4
|
+
|
|
5
|
+
Provides a bidirectional bridge between synstate observables and `@preact/signals`, enabling fine-grained DOM updates without component re-renders.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install synstate-preact-signals synstate preact @preact/signals
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
### `toSignal(observable$)` — Observable to ReadonlySignal
|
|
16
|
+
|
|
17
|
+
Converts a synstate observable into a Preact `ReadonlySignal`. The signal stays in sync with the observable and can be embedded directly in JSX.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import type * as Preact from 'preact';
|
|
21
|
+
import { createState } from 'synstate';
|
|
22
|
+
import { toSignal } from 'synstate-preact-signals';
|
|
23
|
+
|
|
24
|
+
const [count$, setCount] = createState(0);
|
|
25
|
+
|
|
26
|
+
const countSignal = toSignal<number>(count$);
|
|
27
|
+
|
|
28
|
+
const Counter = (): Preact.JSX.Element => {
|
|
29
|
+
// No hooks needed — only this text node updates, not the whole component
|
|
30
|
+
return (
|
|
31
|
+
<div>
|
|
32
|
+
<span>{countSignal}</span>
|
|
33
|
+
<button
|
|
34
|
+
onClick={() => {
|
|
35
|
+
setCount(countSignal.value + 1);
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
{'+1'}
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `fromSignal(signal)` — Signal to Observable
|
|
46
|
+
|
|
47
|
+
Converts a Preact Signal into a synstate observable, allowing you to apply synstate's operator chain.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { signal } from '@preact/signals';
|
|
51
|
+
import { debounce } from 'synstate';
|
|
52
|
+
import { fromSignal } from 'synstate-preact-signals';
|
|
53
|
+
|
|
54
|
+
const input = signal('');
|
|
55
|
+
|
|
56
|
+
const [input$, dispose] = fromSignal(input);
|
|
57
|
+
|
|
58
|
+
const debouncedInput$ = input$.pipe(debounce(300));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `createState` / `createBooleanState` / `createReducer`
|
|
62
|
+
|
|
63
|
+
Signal-returning wrappers for synstate's state creation utilities. Instead of returning a hook (like `synstate-preact-hooks`), these return a `ReadonlySignal`.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import type * as Preact from 'preact';
|
|
67
|
+
import { createState } from 'synstate-preact-signals';
|
|
68
|
+
|
|
69
|
+
const [countSignal, setCount, { updateState, resetState }] = createState(0);
|
|
70
|
+
|
|
71
|
+
const Counter = (): Preact.JSX.Element => (
|
|
72
|
+
<div>
|
|
73
|
+
<span>{countSignal}</span>
|
|
74
|
+
<button
|
|
75
|
+
onClick={() => {
|
|
76
|
+
updateState((n) => n + 1);
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{'+1'}
|
|
80
|
+
</button>
|
|
81
|
+
<button onClick={resetState}>{'Reset'}</button>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Comparison with `synstate-preact-hooks`
|
|
87
|
+
|
|
88
|
+
| Feature | `synstate-preact-hooks` | `synstate-preact-signals` |
|
|
89
|
+
| -------------------- | --------------------------------- | ---------------------------- |
|
|
90
|
+
| Preact dep | `preact` | `preact` + `@preact/signals` |
|
|
91
|
+
| Read value | `useObservableValue(obs$)` (hook) | `toSignal(obs$)` (signal) |
|
|
92
|
+
| Re-render scope | Entire component | Only affected DOM nodes |
|
|
93
|
+
| Hooks required | Yes | No |
|
|
94
|
+
| Operator integration | Via hooks | Via `fromSignal` |
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
Apache-2.0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import { type InitializedObservable } from 'synstate';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a synstate boolean state with a `ReadonlySignal` for reading the
|
|
5
|
+
* current value in Preact components.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // eslint-disable-next-line @typescript-eslint/no-shadow
|
|
10
|
+
* const [isOpenSignal, { setTrue: open, setFalse: close, toggle }] =
|
|
11
|
+
* createBooleanState(false);
|
|
12
|
+
*
|
|
13
|
+
* assert.strictEqual(isOpenSignal.value, false);
|
|
14
|
+
*
|
|
15
|
+
* open();
|
|
16
|
+
*
|
|
17
|
+
* assert.strictEqual(isOpenSignal.value, true);
|
|
18
|
+
*
|
|
19
|
+
* close();
|
|
20
|
+
*
|
|
21
|
+
* assert.strictEqual(isOpenSignal.value, false);
|
|
22
|
+
*
|
|
23
|
+
* toggle();
|
|
24
|
+
*
|
|
25
|
+
* assert.strictEqual(isOpenSignal.value, true);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare const createBooleanState: (initialState: boolean) => readonly [signal: ReadonlySignal<boolean>, Readonly<{
|
|
29
|
+
state: InitializedObservable<boolean>;
|
|
30
|
+
setTrue: () => void;
|
|
31
|
+
setFalse: () => void;
|
|
32
|
+
setState: (next: boolean) => boolean;
|
|
33
|
+
toggle: () => boolean;
|
|
34
|
+
updateState: (updateFn: (prev: boolean) => boolean) => boolean;
|
|
35
|
+
resetState: () => boolean;
|
|
36
|
+
getSnapshot: () => boolean;
|
|
37
|
+
initialState: boolean;
|
|
38
|
+
}>];
|
|
39
|
+
//# sourceMappingURL=create-boolean-state.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-boolean-state.d.mts","sourceRoot":"","sources":["../src/create-boolean-state.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,kBAAkB,GAC7B,cAAc,OAAO,KACpB,SAAS,CACV,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,EAC/B,QAAQ,CAAC;IACP,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IACrC,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,KAAK,OAAO,CAAC;IAC/D,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC,CA+BH,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createBooleanState as createBooleanState$1 } from 'synstate';
|
|
2
|
+
import { toSignal } from './to-signal.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a synstate boolean state with a `ReadonlySignal` for reading the
|
|
6
|
+
* current value in Preact components.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // eslint-disable-next-line @typescript-eslint/no-shadow
|
|
11
|
+
* const [isOpenSignal, { setTrue: open, setFalse: close, toggle }] =
|
|
12
|
+
* createBooleanState(false);
|
|
13
|
+
*
|
|
14
|
+
* assert.strictEqual(isOpenSignal.value, false);
|
|
15
|
+
*
|
|
16
|
+
* open();
|
|
17
|
+
*
|
|
18
|
+
* assert.strictEqual(isOpenSignal.value, true);
|
|
19
|
+
*
|
|
20
|
+
* close();
|
|
21
|
+
*
|
|
22
|
+
* assert.strictEqual(isOpenSignal.value, false);
|
|
23
|
+
*
|
|
24
|
+
* toggle();
|
|
25
|
+
*
|
|
26
|
+
* assert.strictEqual(isOpenSignal.value, true);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
const createBooleanState = (initialState) => {
|
|
30
|
+
const [state, { setTrue, setFalse, setState, toggle, updateState, resetState, getSnapshot, },] = createBooleanState$1(initialState);
|
|
31
|
+
const sig = toSignal(state);
|
|
32
|
+
return [
|
|
33
|
+
sig,
|
|
34
|
+
{
|
|
35
|
+
state,
|
|
36
|
+
setTrue,
|
|
37
|
+
setFalse,
|
|
38
|
+
setState,
|
|
39
|
+
toggle,
|
|
40
|
+
updateState,
|
|
41
|
+
resetState,
|
|
42
|
+
getSnapshot,
|
|
43
|
+
initialState,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export { createBooleanState };
|
|
49
|
+
//# sourceMappingURL=create-boolean-state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-boolean-state.mjs","sources":["../src/create-boolean-state.mts"],"sourcesContent":[null],"names":["createBooleanStateBase"],"mappings":";;;AAOA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;AACI,MAAM,kBAAkB,GAAG,CAChC,YAAqB,KAcnB;IACF,MAAM,CACJ,KAAK,EACL,EACE,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,WAAW,EACX,UAAU,EACV,WAAW,GACZ,EACF,GAAGA,oBAAsB,CAAC,YAAY,CAAC;AAExC,IAAA,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC;IAE3B,OAAO;QACL,GAAG;AACH,QAAA;YACE,KAAK;YACL,OAAO;YACP,QAAQ;YACR,QAAQ;YACR,MAAM;YACN,WAAW;YACX,UAAU;YACV,WAAW;YACX,YAAY;AACb,SAAA;KACO;AACZ;;;;"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import { type InitializedObservable } from 'synstate';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a synstate reducer with a `ReadonlySignal` for reading the current
|
|
5
|
+
* value in Preact components.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* type Action = Readonly<{ type: 'increment' } | { type: 'decrement' }>;
|
|
10
|
+
*
|
|
11
|
+
* const [countSignal, dispatch, { getSnapshot }] = createReducer(
|
|
12
|
+
* (state: number, action: Action) =>
|
|
13
|
+
* action.type === 'increment' ? state + 1 : state - 1,
|
|
14
|
+
* 0,
|
|
15
|
+
* );
|
|
16
|
+
*
|
|
17
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
18
|
+
*
|
|
19
|
+
* dispatch({ type: 'increment' });
|
|
20
|
+
*
|
|
21
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
22
|
+
*
|
|
23
|
+
* dispatch({ type: 'increment' });
|
|
24
|
+
*
|
|
25
|
+
* assert.strictEqual(countSignal.value, 2);
|
|
26
|
+
*
|
|
27
|
+
* dispatch({ type: 'decrement' });
|
|
28
|
+
*
|
|
29
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
30
|
+
*
|
|
31
|
+
* assert.strictEqual(getSnapshot(), 1);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare const createReducer: <S, A>(reducer: Reducer<S, A>, initialState: S) => readonly [signal: ReadonlySignal<S>, dispatch: (action: A) => S, Readonly<{
|
|
35
|
+
state: InitializedObservable<S>;
|
|
36
|
+
getSnapshot: () => S;
|
|
37
|
+
initialState: S;
|
|
38
|
+
}>];
|
|
39
|
+
//# sourceMappingURL=create-reducer.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-reducer.d.mts","sourceRoot":"","sources":["../src/create-reducer.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,EAChC,SAAS,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,cAAc,CAAC,KACd,SAAS,CACV,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAC1B,QAAQ,CAAC;IACP,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CAUH,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createReducer as createReducer$1 } from 'synstate';
|
|
2
|
+
import { toSignal } from './to-signal.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a synstate reducer with a `ReadonlySignal` for reading the current
|
|
6
|
+
* value in Preact components.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* type Action = Readonly<{ type: 'increment' } | { type: 'decrement' }>;
|
|
11
|
+
*
|
|
12
|
+
* const [countSignal, dispatch, { getSnapshot }] = createReducer(
|
|
13
|
+
* (state: number, action: Action) =>
|
|
14
|
+
* action.type === 'increment' ? state + 1 : state - 1,
|
|
15
|
+
* 0,
|
|
16
|
+
* );
|
|
17
|
+
*
|
|
18
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
19
|
+
*
|
|
20
|
+
* dispatch({ type: 'increment' });
|
|
21
|
+
*
|
|
22
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
23
|
+
*
|
|
24
|
+
* dispatch({ type: 'increment' });
|
|
25
|
+
*
|
|
26
|
+
* assert.strictEqual(countSignal.value, 2);
|
|
27
|
+
*
|
|
28
|
+
* dispatch({ type: 'decrement' });
|
|
29
|
+
*
|
|
30
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
31
|
+
*
|
|
32
|
+
* assert.strictEqual(getSnapshot(), 1);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
const createReducer = (reducer, initialState) => {
|
|
36
|
+
const [state, dispatch, { getSnapshot }] = createReducer$1(reducer, initialState);
|
|
37
|
+
const sig = toSignal(state);
|
|
38
|
+
return [sig, dispatch, { state, getSnapshot, initialState }];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { createReducer };
|
|
42
|
+
//# sourceMappingURL=create-reducer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-reducer.mjs","sources":["../src/create-reducer.mts"],"sourcesContent":[null],"names":["createReducerBase"],"mappings":";;;AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BG;MACU,aAAa,GAAG,CAC3B,OAAsB,EACtB,YAAe,KASb;AACF,IAAA,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,GAAGA,eAAiB,CAC1D,OAAO,EACP,YAAY,CACb;AAED,IAAA,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC;AAE3B,IAAA,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAC9D;;;;"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import { type InitializedObservable } from 'synstate';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a synstate state with a `ReadonlySignal` for reading the current
|
|
5
|
+
* value in Preact components.
|
|
6
|
+
*
|
|
7
|
+
* Unlike `synstate-preact-hooks`' `createState` which returns a hook, this
|
|
8
|
+
* returns a `ReadonlySignal` that can be embedded directly in JSX without
|
|
9
|
+
* hooks, enabling fine-grained DOM updates.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const [
|
|
14
|
+
* countSignal,
|
|
15
|
+
* setCount,
|
|
16
|
+
* { updateState, resetState, getSnapshot, initialState },
|
|
17
|
+
* ] = createState(0);
|
|
18
|
+
*
|
|
19
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
20
|
+
*
|
|
21
|
+
* assert.strictEqual(initialState, 0);
|
|
22
|
+
*
|
|
23
|
+
* setCount(5);
|
|
24
|
+
*
|
|
25
|
+
* assert.strictEqual(countSignal.value, 5);
|
|
26
|
+
*
|
|
27
|
+
* updateState((prev) => prev + 1);
|
|
28
|
+
*
|
|
29
|
+
* assert.strictEqual(countSignal.value, 6);
|
|
30
|
+
*
|
|
31
|
+
* resetState();
|
|
32
|
+
*
|
|
33
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
34
|
+
*
|
|
35
|
+
* assert.strictEqual(getSnapshot(), 0);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const createState: <S>(initialState: S) => readonly [signal: ReadonlySignal<S>, setState: (v: S) => S, Readonly<{
|
|
39
|
+
state: InitializedObservable<S>;
|
|
40
|
+
updateState: (updateFn: (prev: S) => S) => S;
|
|
41
|
+
resetState: () => S;
|
|
42
|
+
getSnapshot: () => S;
|
|
43
|
+
initialState: S;
|
|
44
|
+
}>];
|
|
45
|
+
//# sourceMappingURL=create-state.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-state.d.mts","sourceRoot":"","sources":["../src/create-state.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAC3B,cAAc,CAAC,KACd,SAAS,CACV,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACrB,QAAQ,CAAC;IACP,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAChC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC,CAkBH,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createState as createState$1 } from 'synstate';
|
|
2
|
+
import { toSignal } from './to-signal.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a synstate state with a `ReadonlySignal` for reading the current
|
|
6
|
+
* value in Preact components.
|
|
7
|
+
*
|
|
8
|
+
* Unlike `synstate-preact-hooks`' `createState` which returns a hook, this
|
|
9
|
+
* returns a `ReadonlySignal` that can be embedded directly in JSX without
|
|
10
|
+
* hooks, enabling fine-grained DOM updates.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const [
|
|
15
|
+
* countSignal,
|
|
16
|
+
* setCount,
|
|
17
|
+
* { updateState, resetState, getSnapshot, initialState },
|
|
18
|
+
* ] = createState(0);
|
|
19
|
+
*
|
|
20
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
21
|
+
*
|
|
22
|
+
* assert.strictEqual(initialState, 0);
|
|
23
|
+
*
|
|
24
|
+
* setCount(5);
|
|
25
|
+
*
|
|
26
|
+
* assert.strictEqual(countSignal.value, 5);
|
|
27
|
+
*
|
|
28
|
+
* updateState((prev) => prev + 1);
|
|
29
|
+
*
|
|
30
|
+
* assert.strictEqual(countSignal.value, 6);
|
|
31
|
+
*
|
|
32
|
+
* resetState();
|
|
33
|
+
*
|
|
34
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
35
|
+
*
|
|
36
|
+
* assert.strictEqual(getSnapshot(), 0);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
const createState = (initialState) => {
|
|
40
|
+
const [state, setState, { updateState, resetState, getSnapshot }] = createState$1(initialState);
|
|
41
|
+
const sig = toSignal(state);
|
|
42
|
+
return [
|
|
43
|
+
sig,
|
|
44
|
+
setState,
|
|
45
|
+
{
|
|
46
|
+
state,
|
|
47
|
+
updateState,
|
|
48
|
+
resetState,
|
|
49
|
+
getSnapshot,
|
|
50
|
+
initialState,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { createState };
|
|
56
|
+
//# sourceMappingURL=create-state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-state.mjs","sources":["../src/create-state.mts"],"sourcesContent":[null],"names":["createStateBase"],"mappings":";;;AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCG;AACI,MAAM,WAAW,GAAG,CACzB,YAAe,KAWb;AACF,IAAA,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,GAC/DA,aAAe,CAAC,YAAY,CAAC;AAE/B,IAAA,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC;IAE3B,OAAO;QACL,GAAG;QACH,QAAQ;AACR,QAAA;YACE,KAAK;YACL,WAAW;YACX,UAAU;YACV,WAAW;YACX,YAAY;AACb,SAAA;KACO;AACZ;;;;"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ReadonlySignal, type Signal } from '@preact/signals';
|
|
2
|
+
import { type InitializedSourceObservable } from 'synstate';
|
|
3
|
+
/**
|
|
4
|
+
* Converts a Preact `Signal` (or `ReadonlySignal`) into a synstate
|
|
5
|
+
* `InitializedSourceObservable`.
|
|
6
|
+
*
|
|
7
|
+
* This allows applying synstate's rich operator chain (e.g. `map`, `filter`,
|
|
8
|
+
* `debounce`, `switchMap`) to values originating from Preact Signals.
|
|
9
|
+
*
|
|
10
|
+
* The bridge uses `effect()` from `@preact/signals` to track signal changes
|
|
11
|
+
* and propagate them to the observable.
|
|
12
|
+
*
|
|
13
|
+
* @returns A tuple of `[observable, dispose]`.
|
|
14
|
+
* - `observable` — an `InitializedSourceObservable` that mirrors the signal.
|
|
15
|
+
* - `dispose` — a function to stop tracking the signal. Call this when the
|
|
16
|
+
* bridge is no longer needed to avoid memory leaks.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const input = signal('hello');
|
|
21
|
+
*
|
|
22
|
+
* const [input$, dispose] = fromSignal(input);
|
|
23
|
+
*
|
|
24
|
+
* const mut_valueHistory: string[] = [];
|
|
25
|
+
*
|
|
26
|
+
* input$.subscribe((value) => {
|
|
27
|
+
* mut_valueHistory.push(value);
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* assert.deepStrictEqual(mut_valueHistory, ['hello']);
|
|
31
|
+
*
|
|
32
|
+
* input.value = 'world';
|
|
33
|
+
*
|
|
34
|
+
* assert.deepStrictEqual(mut_valueHistory, ['hello', 'world']);
|
|
35
|
+
*
|
|
36
|
+
* dispose(); // stop tracking the signal
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare const fromSignal: <A>(sig: ReadonlySignal<A> | Signal<A>) => readonly [observable: InitializedSourceObservable<A>, dispose: () => void];
|
|
40
|
+
//# sourceMappingURL=from-signal.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-signal.d.mts","sourceRoot":"","sources":["../src/from-signal.mts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAU,KAAK,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAE1B,KAAK,cAAc,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,KACjC,SAAS,CACV,UAAU,EAAE,2BAA2B,CAAC,CAAC,CAAC,EAC1C,OAAO,EAAE,MAAM,IAAI,CASpB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { effect } from '@preact/signals';
|
|
2
|
+
import { source } from 'synstate';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts a Preact `Signal` (or `ReadonlySignal`) into a synstate
|
|
6
|
+
* `InitializedSourceObservable`.
|
|
7
|
+
*
|
|
8
|
+
* This allows applying synstate's rich operator chain (e.g. `map`, `filter`,
|
|
9
|
+
* `debounce`, `switchMap`) to values originating from Preact Signals.
|
|
10
|
+
*
|
|
11
|
+
* The bridge uses `effect()` from `@preact/signals` to track signal changes
|
|
12
|
+
* and propagate them to the observable.
|
|
13
|
+
*
|
|
14
|
+
* @returns A tuple of `[observable, dispose]`.
|
|
15
|
+
* - `observable` — an `InitializedSourceObservable` that mirrors the signal.
|
|
16
|
+
* - `dispose` — a function to stop tracking the signal. Call this when the
|
|
17
|
+
* bridge is no longer needed to avoid memory leaks.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const input = signal('hello');
|
|
22
|
+
*
|
|
23
|
+
* const [input$, dispose] = fromSignal(input);
|
|
24
|
+
*
|
|
25
|
+
* const mut_valueHistory: string[] = [];
|
|
26
|
+
*
|
|
27
|
+
* input$.subscribe((value) => {
|
|
28
|
+
* mut_valueHistory.push(value);
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* assert.deepStrictEqual(mut_valueHistory, ['hello']);
|
|
32
|
+
*
|
|
33
|
+
* input.value = 'world';
|
|
34
|
+
*
|
|
35
|
+
* assert.deepStrictEqual(mut_valueHistory, ['hello', 'world']);
|
|
36
|
+
*
|
|
37
|
+
* dispose(); // stop tracking the signal
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
const fromSignal = (
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
42
|
+
sig) => {
|
|
43
|
+
const src = source(sig.value);
|
|
44
|
+
const dispose = effect(() => {
|
|
45
|
+
src.next(sig.value);
|
|
46
|
+
});
|
|
47
|
+
return [src, dispose];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export { fromSignal };
|
|
51
|
+
//# sourceMappingURL=from-signal.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-signal.mjs","sources":["../src/from-signal.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCG;AACI,MAAM,UAAU,GAAG;AACxB;AACA,GAAkC,KAIhC;IACF,MAAM,GAAG,GAAG,MAAM,CAAI,GAAG,CAAC,KAAK,CAAC;AAEhC,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,MAAK;AAC1B,QAAA,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,IAAA,CAAC,CAAC;AAEF,IAAA,OAAO,CAAC,GAAG,EAAE,OAAO,CAAU;AAChC;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="ts-type-forge" />
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createBooleanState } from './create-boolean-state.mjs';
|
|
2
|
+
export { createReducer } from './create-reducer.mjs';
|
|
3
|
+
export { createState } from './create-state.mjs';
|
|
4
|
+
export { fromSignal } from './from-signal.mjs';
|
|
5
|
+
export { AsyncChildObservableClass, InitializedSyncChildObservableClass, ObservableBaseClass, RootObservableClass, SyncChildObservableClass, attachIndex, audit, collectToArray, combine, combineLatest, counter, createEventEmitter, createValueEmitter, debounce, distinctUntilChanged, filter, flatMap, fromAbortablePromise, fromPromise, fromSubscribable, getKey, isChildObservable, isManagerObservable, isRootObservable, just, map, mapOptional, mapResultErr, mapResultOk, mapTo, merge, mergeMap, pairwise, pluck, scan, skip, skipIfNoChange, skipUntil, skipWhile, source, switchMap, take, takeUntil, takeWhile, throttle, timer, unwrapOptional, unwrapResultErr, unwrapResultOk, withBuffered, withBufferedFrom, withCurrentValueFrom, withIndex, withInitialValue, withLatestFrom, zip } from 'synstate';
|
|
6
|
+
export { toSignal } from './to-signal.mjs';
|
|
7
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AsyncChildObservableClass, InitializedSyncChildObservableClass, ObservableBaseClass, RootObservableClass, SyncChildObservableClass, attachIndex, audit, collectToArray, combine, combineLatest, counter, createEventEmitter, createValueEmitter, debounce, distinctUntilChanged, filter, flatMap, fromAbortablePromise, fromPromise, fromSubscribable, getKey, isChildObservable, isManagerObservable, isRootObservable, just, map, mapOptional, mapResultErr, mapResultOk, mapTo, merge, mergeMap, pairwise, pluck, scan, skip, skipIfNoChange, skipUntil, skipWhile, source, switchMap, take, takeUntil, takeWhile, throttle, timer, unwrapOptional, unwrapResultErr, unwrapResultOk, withBuffered, withBufferedFrom, withCurrentValueFrom, withIndex, withInitialValue, withLatestFrom, zip, } from 'synstate';
|
|
2
|
+
//# sourceMappingURL=synstate.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synstate.d.mts","sourceRoot":"","sources":["../src/synstate.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,mCAAmC,EACnC,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,WAAW,EACX,KAAK,EACL,cAAc,EACd,OAAO,EACP,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,EACR,oBAAoB,EACpB,MAAM,EACN,OAAO,EACP,oBAAoB,EACpB,WAAW,EACX,gBAAgB,EAChB,MAAM,EACN,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,IAAI,EACJ,GAAG,EACH,WAAW,EACX,YAAY,EACZ,WAAW,EACX,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,cAAc,EACd,SAAS,EACT,SAAS,EACT,MAAM,EACN,SAAS,EACT,IAAI,EACJ,SAAS,EACT,SAAS,EACT,QAAQ,EACR,KAAK,EACL,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,GAAG,GACJ,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AsyncChildObservableClass, InitializedSyncChildObservableClass, ObservableBaseClass, RootObservableClass, SyncChildObservableClass, attachIndex, audit, collectToArray, combine, combineLatest, counter, createEventEmitter, createValueEmitter, debounce, distinctUntilChanged, filter, flatMap, fromAbortablePromise, fromPromise, fromSubscribable, getKey, isChildObservable, isManagerObservable, isRootObservable, just, map, mapOptional, mapResultErr, mapResultOk, mapTo, merge, mergeMap, pairwise, pluck, scan, skip, skipIfNoChange, skipUntil, skipWhile, source, switchMap, take, takeUntil, takeWhile, throttle, timer, unwrapOptional, unwrapResultErr, unwrapResultOk, withBuffered, withBufferedFrom, withCurrentValueFrom, withIndex, withInitialValue, withLatestFrom, zip } from 'synstate';
|
|
2
|
+
//# sourceMappingURL=synstate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synstate.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import { type InitializedObservable, type Observable } from 'synstate';
|
|
3
|
+
/**
|
|
4
|
+
* Converts a synstate `InitializedObservable` into a Preact `ReadonlySignal`.
|
|
5
|
+
*
|
|
6
|
+
* The signal automatically stays in sync with the observable. Since the
|
|
7
|
+
* observable has an initial value, the returned signal is guaranteed to hold a
|
|
8
|
+
* value of type `A`.
|
|
9
|
+
*
|
|
10
|
+
* The subscription lives for the lifetime of the observable (until it
|
|
11
|
+
* completes).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const count$ = source<number>(0);
|
|
16
|
+
*
|
|
17
|
+
* const countSignal = toSignal(count$);
|
|
18
|
+
*
|
|
19
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
20
|
+
*
|
|
21
|
+
* count$.next(1);
|
|
22
|
+
*
|
|
23
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
24
|
+
*
|
|
25
|
+
* count$.next(42);
|
|
26
|
+
*
|
|
27
|
+
* assert.strictEqual(countSignal.value, 42);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function toSignal<A>(observable$: InitializedObservable<A>): ReadonlySignal<A>;
|
|
31
|
+
/**
|
|
32
|
+
* Converts a synstate `Observable` (without guaranteed initial value) into a
|
|
33
|
+
* Preact `ReadonlySignal`. The signal value may be `undefined` until the
|
|
34
|
+
* observable emits.
|
|
35
|
+
*/
|
|
36
|
+
export declare function toSignal<A>(observable$: Observable<A>): ReadonlySignal<A | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* Converts a synstate `Observable` into a Preact `ReadonlySignal` with a
|
|
39
|
+
* fallback value used when the observable has not yet emitted.
|
|
40
|
+
*/
|
|
41
|
+
export declare function toSignal<A, B = A>(observable$: Observable<A>, fallback: B): ReadonlySignal<A | B>;
|
|
42
|
+
//# sourceMappingURL=to-signal.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-signal.d.mts","sourceRoot":"","sources":["../src/to-signal.mts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAGvE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,WAAW,EAAE,qBAAqB,CAAC,CAAC,CAAC,GACpC,cAAc,CAAC,CAAC,CAAC,CAAC;AAErB;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,GACzB,cAAc,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;AAEjC;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAC/B,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,EAC1B,QAAQ,EAAE,CAAC,GACV,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { signal } from '@preact/signals';
|
|
2
|
+
import { Optional } from 'ts-data-forge';
|
|
3
|
+
|
|
4
|
+
function toSignal(observable$, fallback) {
|
|
5
|
+
const initialValue = Optional.unwrapOr(observable$.getSnapshot(), fallback);
|
|
6
|
+
const sig = signal(initialValue);
|
|
7
|
+
observable$.subscribe((value) => {
|
|
8
|
+
// eslint-disable-next-line functional/immutable-data
|
|
9
|
+
sig.value = value;
|
|
10
|
+
});
|
|
11
|
+
return sig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { toSignal };
|
|
15
|
+
//# sourceMappingURL=to-signal.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-signal.mjs","sources":["../src/to-signal.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAqDM,SAAU,QAAQ,CACtB,WAA0B,EAC1B,QAAY,EAAA;AAEZ,IAAA,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC;AAE3E,IAAA,MAAM,GAAG,GAAG,MAAM,CAAoB,YAAY,CAAC;AAEnD,IAAA,WAAW,CAAC,SAAS,CAAC,CAAC,KAAK,KAAI;;AAE9B,QAAA,GAAG,CAAC,KAAK,GAAG,KAAK;AACnB,IAAA,CAAC,CAAC;AAEF,IAAA,OAAO,GAAG;AACZ;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"include":["."]}
|
package/dist/types.d.mts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "synstate-preact-signals",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Preact Signals integration for SynState - bridge between synstate observables and @preact/signals",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/noshiro-pf/synstate.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"author": "noshiro-pf <noshiro.pf@gmail.com>",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": {
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"default": "./dist/index.mjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsx ./scripts/cmd/build.mts",
|
|
31
|
+
"build:min": "tsx ./scripts/cmd/build.mts --skip-check",
|
|
32
|
+
"check:ext": "tsx ./scripts/cmd/check-ext.mts",
|
|
33
|
+
"check-all": "tsx ./scripts/cmd/check-all.mts",
|
|
34
|
+
"codemod": "convert-to-readonly '{src,samples,test}/**/*.{mts,tsx}'",
|
|
35
|
+
"doc": "run-s doc:embed doc:embed:jsdoc",
|
|
36
|
+
"doc:embed": "tsx ./scripts/cmd/embed-examples.mts",
|
|
37
|
+
"doc:embed:jsdoc": "tsx ./scripts/cmd/embed-examples-in-jsdoc.mts",
|
|
38
|
+
"fmt": "format-uncommitted",
|
|
39
|
+
"fmt:full": "pnpm run z:prettier .",
|
|
40
|
+
"gen:re-export": "run-s gen:re-export:script fmt",
|
|
41
|
+
"gen:re-export:script": "tsx ./scripts/cmd/generate-re-export-script.mts",
|
|
42
|
+
"gi": "run-s gi:src fmt",
|
|
43
|
+
"gi:src": "gen-index-ts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts",
|
|
44
|
+
"lint": "eslint .",
|
|
45
|
+
"lint:fix": "eslint . --fix",
|
|
46
|
+
"test": "pnpm run z:vitest:node run",
|
|
47
|
+
"test:browser": "pnpm run z:vitest --project='Browser' run",
|
|
48
|
+
"test:cov": "pnpm run z:vitest:node run --coverage",
|
|
49
|
+
"test:cov:ui": "vite preview --outDir ./coverage",
|
|
50
|
+
"test:ui": "pnpm run z:vitest:node --ui",
|
|
51
|
+
"testw": "pnpm run z:vitest:node watch",
|
|
52
|
+
"tsc": "tsc --noEmit",
|
|
53
|
+
"tscw": "tsc --noEmit --watch",
|
|
54
|
+
"type-check": "tsc --noEmit",
|
|
55
|
+
"update-packages": "pnpm update --latest",
|
|
56
|
+
"z:prettier": "prettier --config ../../.prettierrc --ignore-path ../../.prettierignore --ignore-path ../../.gitignore --ignore-unknown --no-error-on-unmatched-pattern --write",
|
|
57
|
+
"z:vitest": "vitest --config ./configs/vitest.config.mts",
|
|
58
|
+
"z:vitest:node": "pnpm run z:vitest --project='Node.js'"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"synstate": "workspace:*",
|
|
62
|
+
"ts-data-forge": "^6.8.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@preact/signals": "^2.8.2",
|
|
66
|
+
"@types/argparse": "^2.0.17",
|
|
67
|
+
"argparse": "^2.0.1",
|
|
68
|
+
"preact": "^10.29.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@preact/signals": ">=1.0.0",
|
|
72
|
+
"preact": ">=10.0.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import {
|
|
3
|
+
type InitializedObservable,
|
|
4
|
+
createBooleanState as createBooleanStateBase,
|
|
5
|
+
} from 'synstate';
|
|
6
|
+
import { toSignal } from './to-signal.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a synstate boolean state with a `ReadonlySignal` for reading the
|
|
10
|
+
* current value in Preact components.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // eslint-disable-next-line @typescript-eslint/no-shadow
|
|
15
|
+
* const [isOpenSignal, { setTrue: open, setFalse: close, toggle }] =
|
|
16
|
+
* createBooleanState(false);
|
|
17
|
+
*
|
|
18
|
+
* assert.strictEqual(isOpenSignal.value, false);
|
|
19
|
+
*
|
|
20
|
+
* open();
|
|
21
|
+
*
|
|
22
|
+
* assert.strictEqual(isOpenSignal.value, true);
|
|
23
|
+
*
|
|
24
|
+
* close();
|
|
25
|
+
*
|
|
26
|
+
* assert.strictEqual(isOpenSignal.value, false);
|
|
27
|
+
*
|
|
28
|
+
* toggle();
|
|
29
|
+
*
|
|
30
|
+
* assert.strictEqual(isOpenSignal.value, true);
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const createBooleanState = (
|
|
34
|
+
initialState: boolean,
|
|
35
|
+
): readonly [
|
|
36
|
+
signal: ReadonlySignal<boolean>,
|
|
37
|
+
Readonly<{
|
|
38
|
+
state: InitializedObservable<boolean>;
|
|
39
|
+
setTrue: () => void;
|
|
40
|
+
setFalse: () => void;
|
|
41
|
+
setState: (next: boolean) => boolean;
|
|
42
|
+
toggle: () => boolean;
|
|
43
|
+
updateState: (updateFn: (prev: boolean) => boolean) => boolean;
|
|
44
|
+
resetState: () => boolean;
|
|
45
|
+
getSnapshot: () => boolean;
|
|
46
|
+
initialState: boolean;
|
|
47
|
+
}>,
|
|
48
|
+
] => {
|
|
49
|
+
const [
|
|
50
|
+
state,
|
|
51
|
+
{
|
|
52
|
+
setTrue,
|
|
53
|
+
setFalse,
|
|
54
|
+
setState,
|
|
55
|
+
toggle,
|
|
56
|
+
updateState,
|
|
57
|
+
resetState,
|
|
58
|
+
getSnapshot,
|
|
59
|
+
},
|
|
60
|
+
] = createBooleanStateBase(initialState);
|
|
61
|
+
|
|
62
|
+
const sig = toSignal(state);
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
sig,
|
|
66
|
+
{
|
|
67
|
+
state,
|
|
68
|
+
setTrue,
|
|
69
|
+
setFalse,
|
|
70
|
+
setState,
|
|
71
|
+
toggle,
|
|
72
|
+
updateState,
|
|
73
|
+
resetState,
|
|
74
|
+
getSnapshot,
|
|
75
|
+
initialState,
|
|
76
|
+
},
|
|
77
|
+
] as const;
|
|
78
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import {
|
|
3
|
+
createReducer as createReducerBase,
|
|
4
|
+
type InitializedObservable,
|
|
5
|
+
} from 'synstate';
|
|
6
|
+
import { toSignal } from './to-signal.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a synstate reducer with a `ReadonlySignal` for reading the current
|
|
10
|
+
* value in Preact components.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* type Action = Readonly<{ type: 'increment' } | { type: 'decrement' }>;
|
|
15
|
+
*
|
|
16
|
+
* const [countSignal, dispatch, { getSnapshot }] = createReducer(
|
|
17
|
+
* (state: number, action: Action) =>
|
|
18
|
+
* action.type === 'increment' ? state + 1 : state - 1,
|
|
19
|
+
* 0,
|
|
20
|
+
* );
|
|
21
|
+
*
|
|
22
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
23
|
+
*
|
|
24
|
+
* dispatch({ type: 'increment' });
|
|
25
|
+
*
|
|
26
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
27
|
+
*
|
|
28
|
+
* dispatch({ type: 'increment' });
|
|
29
|
+
*
|
|
30
|
+
* assert.strictEqual(countSignal.value, 2);
|
|
31
|
+
*
|
|
32
|
+
* dispatch({ type: 'decrement' });
|
|
33
|
+
*
|
|
34
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
35
|
+
*
|
|
36
|
+
* assert.strictEqual(getSnapshot(), 1);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export const createReducer = <S, A>(
|
|
40
|
+
reducer: Reducer<S, A>,
|
|
41
|
+
initialState: S,
|
|
42
|
+
): readonly [
|
|
43
|
+
signal: ReadonlySignal<S>,
|
|
44
|
+
dispatch: (action: A) => S,
|
|
45
|
+
Readonly<{
|
|
46
|
+
state: InitializedObservable<S>;
|
|
47
|
+
getSnapshot: () => S;
|
|
48
|
+
initialState: S;
|
|
49
|
+
}>,
|
|
50
|
+
] => {
|
|
51
|
+
const [state, dispatch, { getSnapshot }] = createReducerBase(
|
|
52
|
+
reducer,
|
|
53
|
+
initialState,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const sig = toSignal(state);
|
|
57
|
+
|
|
58
|
+
return [sig, dispatch, { state, getSnapshot, initialState }];
|
|
59
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import {
|
|
3
|
+
createState as createStateBase,
|
|
4
|
+
type InitializedObservable,
|
|
5
|
+
} from 'synstate';
|
|
6
|
+
import { toSignal } from './to-signal.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a synstate state with a `ReadonlySignal` for reading the current
|
|
10
|
+
* value in Preact components.
|
|
11
|
+
*
|
|
12
|
+
* Unlike `synstate-preact-hooks`' `createState` which returns a hook, this
|
|
13
|
+
* returns a `ReadonlySignal` that can be embedded directly in JSX without
|
|
14
|
+
* hooks, enabling fine-grained DOM updates.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const [
|
|
19
|
+
* countSignal,
|
|
20
|
+
* setCount,
|
|
21
|
+
* { updateState, resetState, getSnapshot, initialState },
|
|
22
|
+
* ] = createState(0);
|
|
23
|
+
*
|
|
24
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
25
|
+
*
|
|
26
|
+
* assert.strictEqual(initialState, 0);
|
|
27
|
+
*
|
|
28
|
+
* setCount(5);
|
|
29
|
+
*
|
|
30
|
+
* assert.strictEqual(countSignal.value, 5);
|
|
31
|
+
*
|
|
32
|
+
* updateState((prev) => prev + 1);
|
|
33
|
+
*
|
|
34
|
+
* assert.strictEqual(countSignal.value, 6);
|
|
35
|
+
*
|
|
36
|
+
* resetState();
|
|
37
|
+
*
|
|
38
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
39
|
+
*
|
|
40
|
+
* assert.strictEqual(getSnapshot(), 0);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const createState = <S,>(
|
|
44
|
+
initialState: S,
|
|
45
|
+
): readonly [
|
|
46
|
+
signal: ReadonlySignal<S>,
|
|
47
|
+
setState: (v: S) => S,
|
|
48
|
+
Readonly<{
|
|
49
|
+
state: InitializedObservable<S>;
|
|
50
|
+
updateState: (updateFn: (prev: S) => S) => S;
|
|
51
|
+
resetState: () => S;
|
|
52
|
+
getSnapshot: () => S;
|
|
53
|
+
initialState: S;
|
|
54
|
+
}>,
|
|
55
|
+
] => {
|
|
56
|
+
const [state, setState, { updateState, resetState, getSnapshot }] =
|
|
57
|
+
createStateBase(initialState);
|
|
58
|
+
|
|
59
|
+
const sig = toSignal(state);
|
|
60
|
+
|
|
61
|
+
return [
|
|
62
|
+
sig,
|
|
63
|
+
setState,
|
|
64
|
+
{
|
|
65
|
+
state,
|
|
66
|
+
updateState,
|
|
67
|
+
resetState,
|
|
68
|
+
getSnapshot,
|
|
69
|
+
initialState,
|
|
70
|
+
},
|
|
71
|
+
] as const;
|
|
72
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { effect, type ReadonlySignal, type Signal } from '@preact/signals';
|
|
2
|
+
import { source, type InitializedSourceObservable } from 'synstate';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts a Preact `Signal` (or `ReadonlySignal`) into a synstate
|
|
6
|
+
* `InitializedSourceObservable`.
|
|
7
|
+
*
|
|
8
|
+
* This allows applying synstate's rich operator chain (e.g. `map`, `filter`,
|
|
9
|
+
* `debounce`, `switchMap`) to values originating from Preact Signals.
|
|
10
|
+
*
|
|
11
|
+
* The bridge uses `effect()` from `@preact/signals` to track signal changes
|
|
12
|
+
* and propagate them to the observable.
|
|
13
|
+
*
|
|
14
|
+
* @returns A tuple of `[observable, dispose]`.
|
|
15
|
+
* - `observable` — an `InitializedSourceObservable` that mirrors the signal.
|
|
16
|
+
* - `dispose` — a function to stop tracking the signal. Call this when the
|
|
17
|
+
* bridge is no longer needed to avoid memory leaks.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const input = signal('hello');
|
|
22
|
+
*
|
|
23
|
+
* const [input$, dispose] = fromSignal(input);
|
|
24
|
+
*
|
|
25
|
+
* const mut_valueHistory: string[] = [];
|
|
26
|
+
*
|
|
27
|
+
* input$.subscribe((value) => {
|
|
28
|
+
* mut_valueHistory.push(value);
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* assert.deepStrictEqual(mut_valueHistory, ['hello']);
|
|
32
|
+
*
|
|
33
|
+
* input.value = 'world';
|
|
34
|
+
*
|
|
35
|
+
* assert.deepStrictEqual(mut_valueHistory, ['hello', 'world']);
|
|
36
|
+
*
|
|
37
|
+
* dispose(); // stop tracking the signal
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export const fromSignal = <A,>(
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
42
|
+
sig: ReadonlySignal<A> | Signal<A>,
|
|
43
|
+
): readonly [
|
|
44
|
+
observable: InitializedSourceObservable<A>,
|
|
45
|
+
dispose: () => void,
|
|
46
|
+
] => {
|
|
47
|
+
const src = source<A>(sig.value);
|
|
48
|
+
|
|
49
|
+
const dispose = effect(() => {
|
|
50
|
+
src.next(sig.value);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return [src, dispose] as const;
|
|
54
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="ts-type-forge" />
|
package/src/index.mts
ADDED
package/src/synstate.mts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AsyncChildObservableClass,
|
|
3
|
+
InitializedSyncChildObservableClass,
|
|
4
|
+
ObservableBaseClass,
|
|
5
|
+
RootObservableClass,
|
|
6
|
+
SyncChildObservableClass,
|
|
7
|
+
attachIndex,
|
|
8
|
+
audit,
|
|
9
|
+
collectToArray,
|
|
10
|
+
combine,
|
|
11
|
+
combineLatest,
|
|
12
|
+
counter,
|
|
13
|
+
createEventEmitter,
|
|
14
|
+
createValueEmitter,
|
|
15
|
+
debounce,
|
|
16
|
+
distinctUntilChanged,
|
|
17
|
+
filter,
|
|
18
|
+
flatMap,
|
|
19
|
+
fromAbortablePromise,
|
|
20
|
+
fromPromise,
|
|
21
|
+
fromSubscribable,
|
|
22
|
+
getKey,
|
|
23
|
+
isChildObservable,
|
|
24
|
+
isManagerObservable,
|
|
25
|
+
isRootObservable,
|
|
26
|
+
just,
|
|
27
|
+
map,
|
|
28
|
+
mapOptional,
|
|
29
|
+
mapResultErr,
|
|
30
|
+
mapResultOk,
|
|
31
|
+
mapTo,
|
|
32
|
+
merge,
|
|
33
|
+
mergeMap,
|
|
34
|
+
pairwise,
|
|
35
|
+
pluck,
|
|
36
|
+
scan,
|
|
37
|
+
skip,
|
|
38
|
+
skipIfNoChange,
|
|
39
|
+
skipUntil,
|
|
40
|
+
skipWhile,
|
|
41
|
+
source,
|
|
42
|
+
switchMap,
|
|
43
|
+
take,
|
|
44
|
+
takeUntil,
|
|
45
|
+
takeWhile,
|
|
46
|
+
throttle,
|
|
47
|
+
timer,
|
|
48
|
+
unwrapOptional,
|
|
49
|
+
unwrapResultErr,
|
|
50
|
+
unwrapResultOk,
|
|
51
|
+
withBuffered,
|
|
52
|
+
withBufferedFrom,
|
|
53
|
+
withCurrentValueFrom,
|
|
54
|
+
withIndex,
|
|
55
|
+
withInitialValue,
|
|
56
|
+
withLatestFrom,
|
|
57
|
+
zip,
|
|
58
|
+
} from 'synstate';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { signal, type ReadonlySignal } from '@preact/signals';
|
|
2
|
+
import { type InitializedObservable, type Observable } from 'synstate';
|
|
3
|
+
import { Optional } from 'ts-data-forge';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts a synstate `InitializedObservable` into a Preact `ReadonlySignal`.
|
|
7
|
+
*
|
|
8
|
+
* The signal automatically stays in sync with the observable. Since the
|
|
9
|
+
* observable has an initial value, the returned signal is guaranteed to hold a
|
|
10
|
+
* value of type `A`.
|
|
11
|
+
*
|
|
12
|
+
* The subscription lives for the lifetime of the observable (until it
|
|
13
|
+
* completes).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const count$ = source<number>(0);
|
|
18
|
+
*
|
|
19
|
+
* const countSignal = toSignal(count$);
|
|
20
|
+
*
|
|
21
|
+
* assert.strictEqual(countSignal.value, 0);
|
|
22
|
+
*
|
|
23
|
+
* count$.next(1);
|
|
24
|
+
*
|
|
25
|
+
* assert.strictEqual(countSignal.value, 1);
|
|
26
|
+
*
|
|
27
|
+
* count$.next(42);
|
|
28
|
+
*
|
|
29
|
+
* assert.strictEqual(countSignal.value, 42);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function toSignal<A>(
|
|
33
|
+
observable$: InitializedObservable<A>,
|
|
34
|
+
): ReadonlySignal<A>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Converts a synstate `Observable` (without guaranteed initial value) into a
|
|
38
|
+
* Preact `ReadonlySignal`. The signal value may be `undefined` until the
|
|
39
|
+
* observable emits.
|
|
40
|
+
*/
|
|
41
|
+
export function toSignal<A>(
|
|
42
|
+
observable$: Observable<A>,
|
|
43
|
+
): ReadonlySignal<A | undefined>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Converts a synstate `Observable` into a Preact `ReadonlySignal` with a
|
|
47
|
+
* fallback value used when the observable has not yet emitted.
|
|
48
|
+
*/
|
|
49
|
+
export function toSignal<A, B = A>(
|
|
50
|
+
observable$: Observable<A>,
|
|
51
|
+
fallback: B,
|
|
52
|
+
): ReadonlySignal<A | B>;
|
|
53
|
+
|
|
54
|
+
export function toSignal<A, B = A>(
|
|
55
|
+
observable$: Observable<A>,
|
|
56
|
+
fallback?: B,
|
|
57
|
+
): ReadonlySignal<A | B | undefined> {
|
|
58
|
+
const initialValue = Optional.unwrapOr(observable$.getSnapshot(), fallback);
|
|
59
|
+
|
|
60
|
+
const sig = signal<A | B | undefined>(initialValue);
|
|
61
|
+
|
|
62
|
+
observable$.subscribe((value) => {
|
|
63
|
+
// eslint-disable-next-line functional/immutable-data
|
|
64
|
+
sig.value = value;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return sig;
|
|
68
|
+
}
|