preact-sigma 6.1.4 → 6.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 CHANGED
@@ -48,5 +48,8 @@ console.log(counter.doubled); // 2
48
48
  - Concepts, lifecycle, invariants, and API selection live in [`docs/context.md`](./docs/context.md).
49
49
  - Persistence-specific guidance lives in [`docs/persist.md`](./docs/persist.md).
50
50
  - Migration guidance from v5 lives in [`docs/migrations/v5-to-v6.md`](./docs/migrations/v5-to-v6.md).
51
- - Runnable usage patterns live in [`examples/`](./examples/), starting with [`examples/basic-counter.ts`](./examples/basic-counter.ts) and [`examples/command-palette.tsx`](./examples/command-palette.tsx).
51
+ - Runnable usage patterns live in [`examples/`](./examples/).
52
+ - [`examples/basic-counter.ts`](./examples/basic-counter.ts)
53
+ - [`examples/command-palette.tsx`](./examples/command-palette.tsx)
54
+ - [`examples/external-query-sync.tsx`](./examples/external-query-sync.tsx)
52
55
  - Exact exported signatures and public API comments live in `dist/index.d.mts` and `dist/persist.d.mts` after `pnpm build`.
package/dist/index.d.mts CHANGED
@@ -58,4 +58,18 @@ type UseSigmaArgs<T extends Sigma<any>> = T extends {
58
58
  */
59
59
  declare function useSigma<T extends Sigma<any>>(...args: UseSigmaArgs<T>): Protected<T>;
60
60
  //#endregion
61
- export { type Draft, type Immutable, InferEventType, InferListener, Listenable, Protected, ReadableSigma, Sigma, SigmaDefinition, SigmaListenable, SigmaRef, SigmaState, SigmaTarget, UseSigmaArgs, UseSigmaOptions, castProtected, listen, mergeDefaults, query, setAutoFreeze, sigma, useListener, useSigma };
61
+ //#region src/hooks/useSigmaSync.d.ts
62
+ type PlainObject = Record<string, unknown>;
63
+ /**
64
+ * Synchronizes changed external data into a sigma instance after the initial render.
65
+ *
66
+ * `input` must be a plain object with stable keys. Its values are shallow-compared with
67
+ * `Object.is(...)`, and `sync(...)` runs only after at least one value changes.
68
+ *
69
+ * A changed `instance` resets the baseline input, so newly created component-owned sigma
70
+ * instances can receive their initial external data through construction or setup before
71
+ * later renders synchronize changes through this hook.
72
+ */
73
+ declare function useSigmaSync<TInstance extends Sigma<any>, TInput extends PlainObject>(instance: TInstance | Protected<TInstance>, input: TInput, sync: (input: Readonly<TInput>) => void): void;
74
+ //#endregion
75
+ export { type Draft, type Immutable, InferEventType, InferListener, Listenable, Protected, ReadableSigma, Sigma, SigmaDefinition, SigmaListenable, SigmaRef, SigmaState, SigmaTarget, UseSigmaArgs, UseSigmaOptions, castProtected, listen, mergeDefaults, query, setAutoFreeze, sigma, useListener, useSigma, useSigmaSync };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as setAutoFreeze, c as listenersSymbol, i as query, n as SigmaTarget, o as sigma, r as castProtected, t as Sigma } from "./sigma-DEKK3bHd.mjs";
1
+ import { a as setAutoFreeze, c as listenersSymbol, i as query, n as SigmaTarget, o as sigma, r as castProtected, s as isPlainObject, t as Sigma } from "./sigma-DEKK3bHd.mjs";
2
2
  import { useEffect, useRef } from "preact/hooks";
3
3
  //#region src/defaults.ts
4
4
  /**
@@ -77,4 +77,42 @@ function useSigma(create, optionsOrDeps) {
77
77
  return castProtected(instance);
78
78
  }
79
79
  //#endregion
80
- export { Sigma, SigmaTarget, castProtected, listen, mergeDefaults, query, setAutoFreeze, sigma, useListener, useSigma };
80
+ //#region src/hooks/useSigmaSync.ts
81
+ function assertStableKeys(cachedKeys, nextKeys) {
82
+ if (cachedKeys.length !== nextKeys.length || nextKeys.some((key) => !cachedKeys.includes(key))) throw new Error("[preact-sigma] useSigmaSync() input keys must stay stable between renders.");
83
+ }
84
+ function hasChanged(previous, next, keys) {
85
+ return keys.some((key) => !Object.is(previous[key], next[key]));
86
+ }
87
+ /**
88
+ * Synchronizes changed external data into a sigma instance after the initial render.
89
+ *
90
+ * `input` must be a plain object with stable keys. Its values are shallow-compared with
91
+ * `Object.is(...)`, and `sync(...)` runs only after at least one value changes.
92
+ *
93
+ * A changed `instance` resets the baseline input, so newly created component-owned sigma
94
+ * instances can receive their initial external data through construction or setup before
95
+ * later renders synchronize changes through this hook.
96
+ */
97
+ function useSigmaSync(instance, input, sync) {
98
+ if (!isPlainObject(input)) throw new Error("[preact-sigma] useSigmaSync() input must be a plain object.");
99
+ const previousInput = useRef(void 0);
100
+ const previousKeys = useRef(void 0);
101
+ const previousInstance = useRef(void 0);
102
+ useEffect(() => {
103
+ const nextKeys = Object.keys(input);
104
+ if (previousInstance.current !== instance) {
105
+ previousInstance.current = instance;
106
+ previousInput.current = input;
107
+ previousKeys.current = nextKeys;
108
+ return;
109
+ }
110
+ assertStableKeys(previousKeys.current, nextKeys);
111
+ if (hasChanged(previousInput.current, input, nextKeys)) {
112
+ previousInput.current = input;
113
+ sync(input);
114
+ }
115
+ });
116
+ }
117
+ //#endregion
118
+ export { Sigma, SigmaTarget, castProtected, listen, mergeDefaults, query, setAutoFreeze, sigma, useListener, useSigma, useSigmaSync };
package/docs/context.md CHANGED
@@ -55,6 +55,7 @@
55
55
  - Read one top-level state property as a `ReadonlySignal`: `sigma.getSignal(instance, key)`.
56
56
  - Own timers, listeners, subscriptions, or nested setup: `onSetup(...)` plus `setup(...)`.
57
57
  - Use a sigma instance inside a component: `useSigma(...)`.
58
+ - Synchronize changed component data into a sigma instance after the initial render: `useSigmaSync(instance, input, sync)`.
58
59
  - Cast an instance to its readonly consumer view outside a component: `castProtected(instance)`.
59
60
  - Subscribe to sigma or DOM events in a component: `useListener(...)`.
60
61
  - Subscribe outside components: `listen(instance, ...)`.
@@ -72,6 +73,7 @@
72
73
  - Emit directly from standalone `SigmaTarget` instances. In subclasses, emit from actions that have no unpublished draft changes. After mutating state, publish first with `this.commit(); this.emit(...)`.
73
74
  - Prefer `listen(...)` for external event subscriptions. It works with sigma targets and DOM targets.
74
75
  - Put owned side effects in `onSetup(...)`.
76
+ - Use `useSigmaSync(...)` when a component-owned sigma instance is initialized from external props or hook data and needs to receive later changes through an action. Pass a plain object with stable keys; values are compared with `Object.is(...)`, and a recreated instance treats the current input as its new baseline.
75
77
  - Use `sigma.subscribe(this, ...)` inside `onSetup(...)` when a setup-owned side effect should react to future committed publishes. Return that cleanup so the subscription stops with setup.
76
78
  ```ts
77
79
  onSetup() {
@@ -115,6 +117,7 @@
115
117
  - Calling `emit(...)` outside an action on a subclass, or before committing the active draft, throws.
116
118
  - Calling an action from a computed or query throws.
117
119
  - Returning an active draft from an action throws.
120
+ - `useSigmaSync(...)` throws when its input is not a plain object or when the input keys change between renders for the same instance.
118
121
  - `sigma.replaceState(...)` throws when the replacement value is not a plain object or when an action still owns unpublished changes.
119
122
  - Starting an action on another sigma instance while the current instance has an active action context throws.
120
123
 
@@ -0,0 +1,51 @@
1
+ import type { FunctionComponent } from "preact";
2
+
3
+ import { Sigma, useSigma, useSigmaSync } from "preact-sigma";
4
+
5
+ type SearchResult = {
6
+ id: string;
7
+ title: string;
8
+ };
9
+
10
+ type SearchResultsState = {
11
+ query: string;
12
+ results: readonly SearchResult[];
13
+ };
14
+
15
+ class SearchResults extends Sigma<SearchResultsState> {
16
+ constructor(query: string, results: readonly SearchResult[]) {
17
+ super({
18
+ query,
19
+ results,
20
+ });
21
+ }
22
+
23
+ syncExternalQuery(query: string, results: readonly SearchResult[]) {
24
+ this.query = query;
25
+ this.results = results;
26
+ }
27
+ }
28
+
29
+ interface SearchResults extends SearchResultsState {}
30
+
31
+ export const ExternalQuerySyncExample: FunctionComponent<{
32
+ query: string;
33
+ results: readonly SearchResult[];
34
+ }> = ({ query, results }) => {
35
+ const search = useSigma(() => new SearchResults(query, results));
36
+
37
+ useSigmaSync(search, { query, results }, ({ query, results }) => {
38
+ search.syncExternalQuery(query, results);
39
+ });
40
+
41
+ return (
42
+ <section>
43
+ <h2>Results for {search.query || "everything"}</h2>
44
+ <ul>
45
+ {search.results.map((result) => (
46
+ <li key={result.id}>{result.title}</li>
47
+ ))}
48
+ </ul>
49
+ </section>
50
+ );
51
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "preact-sigma",
3
- "version": "6.1.4",
3
+ "version": "6.2.0",
4
4
  "keywords": [],
5
5
  "license": "MIT",
6
6
  "author": "Alec Larson",