preact-sigma 6.1.3 → 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
@@ -1,4 +1,4 @@
1
- import { a as SigmaState, c as query, d as Draft, f as Immutable, i as SigmaRef, l as setAutoFreeze, m as Cleanup, n as Sigma, o as SigmaTarget, p as typeSymbol, r as SigmaDefinition, s as castProtected, t as Protected, u as sigma } from "./sigma-Dki3T7re.mjs";
1
+ import { a as SigmaRef, c as castProtected, d as sigma, f as Draft, h as Cleanup, i as SigmaDefinition, l as query, m as typeSymbol, n as ReadableSigma, o as SigmaState, p as Immutable, r as Sigma, s as SigmaTarget, t as Protected, u as setAutoFreeze } from "./sigma-BfaJUPWw.mjs";
2
2
 
3
3
  //#region src/defaults.d.ts
4
4
  /**
@@ -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, 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-Oviv0rzP.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 };
@@ -1,13 +1,7 @@
1
- import { f as Immutable, n as Sigma, t as Protected } from "./sigma-Dki3T7re.mjs";
1
+ import { n as ReadableSigma, p as Immutable, r as Sigma } from "./sigma-BfaJUPWw.mjs";
2
2
 
3
3
  //#region src/persist.d.ts
4
4
  type MaybePromise<T> = T | Promise<T>;
5
- /**
6
- * Sigma instance or protected consumer view accepted by persistence helpers.
7
- *
8
- * Restore and hydrate helpers may replace committed state through this trusted owner boundary.
9
- */
10
- type PersistInstance<TState extends object> = Sigma<TState> | Protected<Sigma<TState>>;
11
5
  /** Decode-time context passed to persistence codecs. */
12
6
  interface PersistDecodeContext<TState extends object> {
13
7
  /** Storage key used to read the record being decoded. */
@@ -114,19 +108,19 @@ interface SyncHydrationHandle extends PersistenceHandle {
114
108
  readonly restored: RestoreResult;
115
109
  }
116
110
  /** Restores committed state from a persisted record through an async store. */
117
- declare function restore<TState extends object, TKey extends keyof TState>(instance: PersistInstance<TState>, options: PickPersistOptions<TState, TKey>): Promise<RestoreResult>;
118
- declare function restore<TState extends object, TStored = Immutable<TState>>(instance: PersistInstance<TState>, options: PersistOptions<TState, TStored>): Promise<RestoreResult>;
111
+ declare function restore<TState extends object, TKey extends keyof TState>(instance: ReadableSigma<TState>, options: PickPersistOptions<TState, TKey>): Promise<RestoreResult>;
112
+ declare function restore<TState extends object, TStored = Immutable<TState>>(instance: ReadableSigma<TState>, options: PersistOptions<TState, TStored>): Promise<RestoreResult>;
119
113
  /** Restores committed state from a persisted record through a sync store. */
120
- declare function restoreSync<TState extends object, TKey extends keyof TState>(instance: PersistInstance<TState>, options: SyncPickPersistOptions<TState, TKey>): RestoreResult;
121
- declare function restoreSync<TState extends object, TStored = Immutable<TState>>(instance: PersistInstance<TState>, options: SyncPersistOptions<TState, TStored>): RestoreResult;
114
+ declare function restoreSync<TState extends object, TKey extends keyof TState>(instance: ReadableSigma<TState>, options: SyncPickPersistOptions<TState, TKey>): RestoreResult;
115
+ declare function restoreSync<TState extends object, TStored = Immutable<TState>>(instance: ReadableSigma<TState>, options: SyncPersistOptions<TState, TStored>): RestoreResult;
122
116
  /** Persists future committed state changes for one sigma instance. */
123
- declare function persist<TState extends object, TKey extends keyof TState>(instance: PersistInstance<TState>, options: PickPersistOptions<TState, TKey>): PersistenceHandle;
124
- declare function persist<TState extends object, TStored = Immutable<TState>>(instance: PersistInstance<TState>, options: PersistOptions<TState, TStored>): PersistenceHandle;
117
+ declare function persist<TState extends object, TKey extends keyof TState>(instance: ReadableSigma<TState>, options: PickPersistOptions<TState, TKey>): PersistenceHandle;
118
+ declare function persist<TState extends object, TStored = Immutable<TState>>(instance: ReadableSigma<TState>, options: PersistOptions<TState, TStored>): PersistenceHandle;
125
119
  /** Restores state, then begins persisting future committed changes. */
126
- declare function hydrate<TState extends object, TKey extends keyof TState>(instance: PersistInstance<TState>, options: PickPersistOptions<TState, TKey>): HydrationHandle;
127
- declare function hydrate<TState extends object, TStored = Immutable<TState>>(instance: PersistInstance<TState>, options: PersistOptions<TState, TStored>): HydrationHandle;
120
+ declare function hydrate<TState extends object, TKey extends keyof TState>(instance: ReadableSigma<TState>, options: PickPersistOptions<TState, TKey>): HydrationHandle;
121
+ declare function hydrate<TState extends object, TStored = Immutable<TState>>(instance: ReadableSigma<TState>, options: PersistOptions<TState, TStored>): HydrationHandle;
128
122
  /** Restores state synchronously, then begins persisting future committed changes. */
129
- declare function hydrateSync<TState extends object, TKey extends keyof TState>(instance: PersistInstance<TState>, options: SyncPickPersistOptions<TState, TKey>): SyncHydrationHandle;
130
- declare function hydrateSync<TState extends object, TStored = Immutable<TState>>(instance: PersistInstance<TState>, options: SyncPersistOptions<TState, TStored>): SyncHydrationHandle;
123
+ declare function hydrateSync<TState extends object, TKey extends keyof TState>(instance: ReadableSigma<TState>, options: SyncPickPersistOptions<TState, TKey>): SyncHydrationHandle;
124
+ declare function hydrateSync<TState extends object, TStored = Immutable<TState>>(instance: ReadableSigma<TState>, options: SyncPersistOptions<TState, TStored>): SyncHydrationHandle;
131
125
  //#endregion
132
126
  export { HydrationHandle, PersistCodec, PersistDecodeContext, PersistErrorContext, PersistOptions, PersistRecord, PersistSchedule, PersistStore, PersistenceHandle, PickPersistOptions, RestoreResult, SyncHydrationHandle, SyncPersistOptions, SyncPersistStore, SyncPickPersistOptions, hydrate, hydrateSync, persist, restore, restoreSync };
package/dist/persist.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { o as sigma, s as isPlainObject } from "./sigma-Oviv0rzP.mjs";
1
+ import { o as sigma, s as isPlainObject } from "./sigma-DEKK3bHd.mjs";
2
2
  //#region src/persist.ts
3
3
  function createIdentityCodec() {
4
4
  return {
@@ -75,6 +75,7 @@ declare function setAutoFreeze(autoFreeze: boolean): void;
75
75
  type SigmaRef<T extends object = {}> = T & {
76
76
  [refSymbol]?: true;
77
77
  };
78
+ type ReadableSigma<TState extends object> = Sigma<TState> | Protected<Sigma<TState>>;
78
79
  /** Definition shape used by helper types that need both state and event maps. */
79
80
  type SigmaDefinition = {
80
81
  state: object;
@@ -150,7 +151,7 @@ declare const sigma: Readonly<{
150
151
  <TState extends object, TKey extends Extract<keyof TState, string>>(instance: Sigma<TState>, key: TKey, listener: (value: Immutable<TState[TKey]>) => void): Cleanup;
151
152
  }; /** Returns the readonly signal backing one top-level state key. */
152
153
  getSignal<TState extends object, TKey extends Extract<keyof TState, string>>(instance: Sigma<TState>, key: TKey): ReadonlySignal<Immutable<TState[TKey]>>; /** Captures the current committed top-level state snapshot. */
153
- captureState<TState extends object>(instance: Sigma<TState>): Immutable<TState>; /** Publishes a plain-object snapshot, including readonly captured snapshots, as committed state. */
154
+ captureState<TState extends object>(instance: ReadableSigma<TState>): Immutable<TState>; /** Publishes a plain-object snapshot, including readonly captured snapshots, as committed state. */
154
155
  replaceState<TState extends object>(target: Sigma<TState>, nextState: TState | Immutable<TState>): void;
155
156
  }>;
156
157
  /**
@@ -171,4 +172,4 @@ type Protected<T extends Sigma<any>> = BrandProtected<T extends {
171
172
  };
172
173
  } ? { readonly [K in keyof T as K extends ProtectedKey ? never : K]: K extends typeof typeSymbol ? T[K] : K extends keyof TState ? Immutable<T[K]> : T[K] extends AnyFunction ? (...params: Parameters<T[K]>) => Immutable<ReturnType<T[K]>> : Immutable<T[K]> } : never>;
173
174
  //#endregion
174
- export { SigmaState as a, query as c, Draft as d, Immutable as f, SigmaRef as i, setAutoFreeze as l, Cleanup as m, Sigma as n, SigmaTarget as o, typeSymbol as p, SigmaDefinition as r, castProtected as s, Protected as t, sigma as u };
175
+ export { SigmaRef as a, castProtected as c, sigma as d, Draft as f, Cleanup as h, SigmaDefinition as i, query as l, typeSymbol as m, ReadableSigma as n, SigmaState as o, Immutable as p, Sigma as r, SigmaTarget as s, Protected as t, setAutoFreeze as u };
@@ -114,9 +114,11 @@ function getStateSignal(instance, key) {
114
114
  return instance[key + signalSuffix];
115
115
  }
116
116
  function createSnapshot(instance) {
117
- if (instance[snapshotSymbol]) return instance[snapshotSymbol];
117
+ const source = instance;
118
+ if (source[snapshotSymbol]) return source[snapshotSymbol];
118
119
  const state = {};
119
- for (const key in instance) if (isStateKey(instance, key)) state[key] = getStateSignal(instance, key).value;
120
+ for (const key in source) if (isStateKey(source, key)) state[key] = getStateSignal(source, key).value;
121
+ source[snapshotSymbol] = state;
120
122
  return state;
121
123
  }
122
124
  function createDraft(instance) {
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.3",
3
+ "version": "6.2.0",
4
4
  "keywords": [],
5
5
  "license": "MIT",
6
6
  "author": "Alec Larson",