preact-sigma 2.2.3 → 3.0.1
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 +1 -1
- package/dist/index.d.mts +130 -96
- package/dist/index.mjs +152 -83
- package/docs/context.md +3 -1
- package/examples/sigma-target.ts +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,5 +43,5 @@ console.log(counter.doubled); // 2
|
|
|
43
43
|
- Concepts, lifecycle, invariants, and API selection live in [`docs/context.md`](./docs/context.md).
|
|
44
44
|
- Quick-start usage lives in [`examples/basic-counter.ts`](./examples/basic-counter.ts).
|
|
45
45
|
- An advanced end-to-end example lives in [`examples/command-palette.tsx`](./examples/command-palette.tsx).
|
|
46
|
-
- Focused examples for non-obvious APIs live in [`examples/async-commit.ts`](./examples/async-commit.ts), [`examples/observe-and-restore.ts`](./examples/observe-and-restore.ts), [`examples/
|
|
46
|
+
- Focused examples for non-obvious APIs live in [`examples/async-commit.ts`](./examples/async-commit.ts), [`examples/observe-and-restore.ts`](./examples/observe-and-restore.ts), [`examples/setup-act.ts`](./examples/setup-act.ts), [`examples/sigma-target.ts`](./examples/sigma-target.ts), and [`examples/signal-access.ts`](./examples/signal-access.ts).
|
|
47
47
|
- Exact exported signatures live in `dist/index.d.mts` after `pnpm build`.
|
package/dist/index.d.mts
CHANGED
|
@@ -2,10 +2,54 @@ import { ReadonlySignal, action, batch, computed, effect, untracked } from "@pre
|
|
|
2
2
|
import { Patch, freeze, immerable } from "immer";
|
|
3
3
|
|
|
4
4
|
//#region src/internal/symbols.d.ts
|
|
5
|
+
declare const sigmaTypeBrand: unique symbol;
|
|
5
6
|
declare const sigmaStateBrand: unique symbol;
|
|
6
7
|
declare const sigmaEventsBrand: unique symbol;
|
|
7
8
|
declare const sigmaRefBrand: unique symbol;
|
|
8
9
|
//#endregion
|
|
10
|
+
//#region src/listener.d.ts
|
|
11
|
+
type InferEventMap<TTarget extends EventTarget> = TTarget extends {
|
|
12
|
+
[sigmaEventsBrand]: infer TEvents extends AnyEvents;
|
|
13
|
+
} ? TEvents : TTarget extends Window ? WindowEventMap : TTarget extends Document ? DocumentEventMap : TTarget extends HTMLBodyElement ? HTMLBodyElementEventMap : TTarget extends HTMLMediaElement ? HTMLMediaElementEventMap : TTarget extends HTMLElement ? HTMLElementEventMap : TTarget extends SVGSVGElement ? SVGSVGElementEventMap : TTarget extends SVGElement ? SVGElementEventMap : never;
|
|
14
|
+
type InferListenerArgs<TEvents extends object, TTarget extends EventTarget, TEvent extends string> = [(TEvent extends keyof TEvents ? TEvents[TEvent] : never) extends infer TPayload ? TTarget extends {
|
|
15
|
+
[sigmaEventsBrand]: TEvents;
|
|
16
|
+
} ? [TPayload] extends [never] ? never : [TPayload] extends [void] ? undefined : TPayload : ([TPayload] extends [never] ? CustomEvent : Extract<TPayload, Event>) & {
|
|
17
|
+
readonly currentTarget: TTarget;
|
|
18
|
+
} : never];
|
|
19
|
+
/** Infers the listener callback shape for a target and event name. Sigma states receive payloads directly, while DOM targets receive typed events. */
|
|
20
|
+
type InferListener<TTarget extends EventTarget, TEvent extends string = string> = InferEventMap<TTarget> extends infer TEvents extends object ? ((...args: InferListenerArgs<TEvents, TTarget, TEvent>) => void) & {
|
|
21
|
+
__eventType?: TEvent;
|
|
22
|
+
} : never;
|
|
23
|
+
/** Infers the event names accepted by `listen(...)` or `useListener(...)` for a target. */
|
|
24
|
+
type InferEventType<TTarget extends EventTarget> = (InferListener<TTarget> extends {
|
|
25
|
+
__eventType?: infer TEvent;
|
|
26
|
+
} ? string & TEvent : never) | (string & {});
|
|
27
|
+
/** Infers the detail parameter for a typed emit. */
|
|
28
|
+
type EventParameters<T> = [void] extends [T] ? [detail?: T extends void ? undefined : T] : [undefined] extends [T] ? [detail?: T] : [detail: T];
|
|
29
|
+
/**
|
|
30
|
+
* A standalone typed event hub with `emit(...)` and `on(...)` methods and full
|
|
31
|
+
* `EventTarget`, `listen(...)`, and `useListener(...)` compatibility.
|
|
32
|
+
*/
|
|
33
|
+
declare class SigmaTarget<TEvents extends AnyEvents = {}> extends EventTarget {
|
|
34
|
+
readonly [sigmaEventsBrand]: TEvents;
|
|
35
|
+
/**
|
|
36
|
+
* Emits a typed event from the hub.
|
|
37
|
+
*
|
|
38
|
+
* Void events dispatch a plain `Event`. Payload events dispatch a
|
|
39
|
+
* `CustomEvent` whose `detail` holds the payload.
|
|
40
|
+
*/
|
|
41
|
+
emit<TEvent extends string & keyof TEvents>(name: TEvent, ...[detail]: EventParameters<TEvents[TEvent]>): void;
|
|
42
|
+
/**
|
|
43
|
+
* Registers a typed event listener and returns an unsubscribe function.
|
|
44
|
+
*
|
|
45
|
+
* Payload events pass their payload directly to the listener. Void events
|
|
46
|
+
* call the listener with no arguments.
|
|
47
|
+
*/
|
|
48
|
+
on<TEvent extends string & keyof TEvents>(name: TEvent, listener: (...args: InferListenerArgs<TEvents, this, TEvent>) => void): () => void;
|
|
49
|
+
}
|
|
50
|
+
/** Adds a listener to a sigma state or DOM target and returns a cleanup function that removes it. */
|
|
51
|
+
declare function listen<TTarget extends EventTarget, TEvent extends InferEventType<TTarget>>(target: TTarget, name: TEvent, listener: InferListener<TTarget, TEvent>): () => void;
|
|
52
|
+
//#endregion
|
|
9
53
|
//#region src/immer.d.ts
|
|
10
54
|
type PrimitiveType = number | string | boolean;
|
|
11
55
|
/** Object types that should never be mapped */
|
|
@@ -40,6 +84,7 @@ type Draft<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : HasSi
|
|
|
40
84
|
type Immutable<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : HasSigmaRefBrand<T> extends true ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<Immutable<V>> : T extends WeakReferences ? T : T extends object ? { readonly [K in keyof T]: Immutable<T[K]> } : T;
|
|
41
85
|
//#endregion
|
|
42
86
|
//#region src/internal/types.d.ts
|
|
87
|
+
type Def = typeof sigmaTypeBrand;
|
|
43
88
|
type AnyFunction = (...args: any[]) => any;
|
|
44
89
|
type Cleanup = () => void;
|
|
45
90
|
type DefaultStateInitializer<TValue> = (this: void) => TValue;
|
|
@@ -60,30 +105,35 @@ type AnyEvents = Record<string, object | void>;
|
|
|
60
105
|
/** The top-level state object shape used by sigma types. */
|
|
61
106
|
type AnyState = Record<string, unknown>;
|
|
62
107
|
/** The object accepted by `.defaultState(...)`, where each property may be a value or a zero-argument initializer. */
|
|
63
|
-
type AnyDefaultState<TState extends
|
|
108
|
+
type AnyDefaultState<TState extends object> = { [K in keyof TState]?: DefaultStateValue<TState[K]> };
|
|
64
109
|
/** A cleanup resource supported by `.setup(...)`, including function, `dispose()`, and `Symbol.dispose` cleanup. */
|
|
65
110
|
type AnyResource = Cleanup | Disposable | DisposableLike | AbortController;
|
|
66
|
-
type ComputedValues<TComputeds
|
|
67
|
-
type
|
|
68
|
-
type
|
|
69
|
-
type
|
|
70
|
-
type
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
74
|
-
type
|
|
75
|
-
/** Runs every registered setup handler and returns one cleanup function for the active setup. */setup(...args: Extract<TSetupArgs, any[]>): Cleanup;
|
|
76
|
-
};
|
|
77
|
-
type ReadonlyContext<TState extends AnyState, TComputeds extends object, TQueries extends object> = Immutable<TState> & ComputedValues<TComputeds> & QueryMethods<TQueries>;
|
|
78
|
-
type Emit<TEvents extends AnyEvents> = <TEvent extends string & keyof TEvents>(name: TEvent, ...args: [TEvents[TEvent]] extends [void] ? [] : [payload: TEvents[TEvent]]) => void;
|
|
79
|
-
type ActionContext<TState extends AnyState, TEvents extends AnyEvents, TComputeds extends object, TQueries extends object, TActions extends object> = Draft<TState> & ComputedValues<TComputeds> & QueryMethods<TQueries> & ActionMethods<TActions> & {
|
|
111
|
+
type ComputedValues<TComputeds> = { readonly [K in keyof TComputeds]: TComputeds[K] extends AnyFunction ? Immutable<ReturnType<TComputeds[K]>> : never };
|
|
112
|
+
type QueryMethods<TQueries> = { [K in keyof TQueries]: TQueries[K] extends AnyFunction ? (...args: Parameters<TQueries[K]>) => ReturnType<TQueries[K]> : never };
|
|
113
|
+
type ActionMethods<TActions> = { [K in keyof TActions]: TActions[K] extends AnyFunction ? (...args: Parameters<TActions[K]>) => ReturnType<TActions[K]> : never };
|
|
114
|
+
type MergeObjects<TLeft, TRight, TConstraint extends object = object> = TRight extends TConstraint ? TLeft extends TConstraint ? Simplify<Omit<TLeft, keyof TRight> & TRight> : TRight : TLeft extends TConstraint ? TLeft : {};
|
|
115
|
+
type Simplify<T> = {} & { [K in keyof T]: T[K] };
|
|
116
|
+
type ComputedContext<T extends AnySigmaType, TOverrides extends Partial<SigmaDefinition> = {}> = Simplify<Immutable<MergeObjects<T[Def]["state"], TOverrides["state"]>> & ComputedValues<MergeObjects<T[Def]["computeds"], TOverrides["computeds"]>>>;
|
|
117
|
+
type ReadonlyContext<T extends AnySigmaType, TOverrides extends Partial<SigmaDefinition> = {}> = Simplify<Immutable<MergeObjects<T[Def]["state"], TOverrides["state"]>> & ComputedValues<MergeObjects<T[Def]["computeds"], TOverrides["computeds"]>> & QueryMethods<MergeObjects<T[Def]["queries"], TOverrides["queries"]>>>;
|
|
118
|
+
type Emit<T extends AnySigmaType, TOverrides extends Partial<SigmaDefinition> = {}> = MergeObjects<T[Def]["events"], TOverrides["events"], AnyEvents> extends infer TEvents ? [TEvents] extends [AnyEvents] ? <TEvent extends string & keyof TEvents>(name: TEvent, ...[detail]: EventParameters<TEvents[TEvent]>) => void : never : never;
|
|
119
|
+
type ActionContext<T extends AnySigmaType, TOverrides extends Partial<SigmaDefinition> = {}> = Simplify<Draft<MergeObjects<T[Def]["state"], TOverrides["state"]>> & ComputedValues<MergeObjects<T[Def]["computeds"], TOverrides["computeds"]>> & QueryMethods<MergeObjects<T[Def]["queries"], TOverrides["queries"]>> & ActionMethods<MergeObjects<T[Def]["actions"], TOverrides["actions"]>> & {
|
|
80
120
|
/** Publishes the current action draft immediately so later boundaries use committed state. */commit(): void; /** Emits a typed event from the current action. */
|
|
81
|
-
emit: Emit<
|
|
121
|
+
emit: Emit<T, TOverrides>;
|
|
122
|
+
}>;
|
|
123
|
+
type SetupContext<T extends AnySigmaType, TOverrides extends Partial<SigmaDefinition> = {}> = Simplify<SigmaState<T[Def] & TOverrides> & {
|
|
124
|
+
/** Runs a synchronous anonymous action from setup so reads and writes use normal action semantics. */act<TResult>(fn: (this: ActionContext<T>) => TResult): TResult; /** Emits a typed event from setup. */
|
|
125
|
+
emit: Emit<T, TOverrides>;
|
|
126
|
+
}>;
|
|
127
|
+
type AnySigmaType = SigmaType<any, any, any> & {
|
|
128
|
+
readonly [sigmaTypeBrand]: {
|
|
129
|
+
state: object;
|
|
130
|
+
events?: object;
|
|
131
|
+
computeds?: object;
|
|
132
|
+
queries?: object;
|
|
133
|
+
actions?: object;
|
|
134
|
+
setupArgs?: any[];
|
|
135
|
+
};
|
|
82
136
|
};
|
|
83
|
-
type DefinitionEvents<T extends SigmaDefinition> = T["events"] extends AnyEvents ? T["events"] : {};
|
|
84
|
-
type DefinitionComputeds<T extends SigmaDefinition> = T["computeds"] extends object ? T["computeds"] : {};
|
|
85
|
-
type DefinitionQueries<T extends SigmaDefinition> = T["queries"] extends object ? T["queries"] : {};
|
|
86
|
-
type DefinitionActions<T extends SigmaDefinition> = T["actions"] extends object ? T["actions"] : {};
|
|
87
137
|
/** The public shape shared by all sigma-state instances. */
|
|
88
138
|
interface AnySigmaState extends EventTarget {
|
|
89
139
|
readonly [sigmaStateBrand]: true;
|
|
@@ -116,21 +166,25 @@ interface SignalAccessors<T extends object> {
|
|
|
116
166
|
/** Returns the underlying signal for a top-level state property or computed. */
|
|
117
167
|
get<K extends keyof T>(key: K): ReadonlySignal<T[K]>;
|
|
118
168
|
}
|
|
119
|
-
type
|
|
120
|
-
|
|
169
|
+
type EventMethods<TEvents extends AnyEvents | undefined> = [undefined] extends [TEvents] ? never : {
|
|
170
|
+
readonly [sigmaEventsBrand]: TEvents; /** Registers a typed event listener and returns an unsubscribe function. */
|
|
171
|
+
on<TEvent extends string & keyof TEvents>(name: TEvent, listener: (...[detail]: EventParameters<TEvents[TEvent]>) => void): Cleanup;
|
|
172
|
+
};
|
|
173
|
+
type SetupMethods<TSetupArgs extends any[] | undefined> = [TSetupArgs] extends [undefined] ? never : {
|
|
174
|
+
/** Runs every registered setup handler and returns one cleanup function for the active setup. */setup(...args: Extract<TSetupArgs, any[]>): Cleanup;
|
|
175
|
+
};
|
|
121
176
|
type MapSigmaDefinition<T extends SigmaDefinition> = keyof T extends infer K ? K extends "state" ? Immutable<T[K]> & SignalAccessors<Immutable<T[K]>> : K extends "computeds" ? ComputedValues<T[K]> & SignalAccessors<ComputedValues<T[K]>> : K extends "queries" ? QueryMethods<T[K]> : K extends "actions" ? ActionMethods<T[K]> : K extends "events" ? EventMethods<T[K]> : K extends "setupArgs" ? SetupMethods<T[K]> : never : never;
|
|
177
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
122
178
|
/** The public instance shape produced by a configured sigma type, including signal access inferred from the definition. */
|
|
123
179
|
type SigmaState<T extends SigmaDefinition = SigmaDefinition> = AnySigmaState & Simplify<UnionToIntersection<MapSigmaDefinition<T>>>;
|
|
124
|
-
type SetupContext<T extends SigmaDefinition> = SigmaState<T> & {
|
|
125
|
-
/** Runs a synchronous anonymous action from setup so reads and writes use normal action semantics. */act<TResult>(fn: (this: ActionContext<T["state"], DefinitionEvents<T>, DefinitionComputeds<T>, DefinitionQueries<T>, DefinitionActions<T>>) => TResult): TResult; /** Emits a typed event from setup. */
|
|
126
|
-
emit: T["events"] extends object ? Emit<T["events"]> : never;
|
|
127
|
-
};
|
|
128
|
-
type MergeObjects<TLeft extends object, TRight> = [TRight] extends [object] ? Extract<Simplify<Omit<TLeft, keyof TRight> & TRight>, TLeft> : TLeft;
|
|
129
180
|
type RequiredKeys<TObject extends object> = { [K in keyof TObject]-?: {} extends Pick<TObject, K> ? never : K }[keyof TObject];
|
|
130
|
-
type MissingInitialKeys<TState extends AnyState, TDefaults extends
|
|
131
|
-
type InitialStateInput<TState extends AnyState, TDefaults extends
|
|
132
|
-
type
|
|
133
|
-
|
|
181
|
+
type MissingInitialKeys<TState extends AnyState, TDefaults extends object> = Exclude<RequiredKeys<TState>, keyof TDefaults>;
|
|
182
|
+
type InitialStateInput<TState extends AnyState, TDefaults extends object> = [MissingInitialKeys<TState, TDefaults>] extends [never] ? [initialState?: Partial<TState>] : [initialState: Pick<TState, MissingInitialKeys<TState, TDefaults>> & Partial<TState>];
|
|
183
|
+
type ExtendSigmaType<T extends SigmaType<any, any, any>, TExtension extends Partial<SigmaDefinition>> = T & {
|
|
184
|
+
readonly [sigmaTypeBrand]: T[Def] & TExtension;
|
|
185
|
+
};
|
|
186
|
+
type OmitEmpty<T extends object> = Omit<T, { [K in keyof T]: [undefined] extends [T[K]] ? K : [{}] extends [T[K]] ? K : never }[keyof T]>;
|
|
187
|
+
type InferSigmaDefinition<T extends SigmaType<any, any, any>> = Extract<Simplify<OmitEmpty<T[Def]>>, SigmaDefinition>;
|
|
134
188
|
/** Infers the `setup(...)` argument list for a sigma-state instance. */
|
|
135
189
|
type InferSetupArgs<T extends AnySigmaState> = T extends {
|
|
136
190
|
setup(...args: infer TArgs extends any[]): Cleanup;
|
|
@@ -160,7 +214,7 @@ declare function replaceState<T extends AnySigmaState>(publicInstance: T, nextSt
|
|
|
160
214
|
//#endregion
|
|
161
215
|
//#region src/framework.d.ts
|
|
162
216
|
/** Checks whether a value is an instance created by a configured sigma type. */
|
|
163
|
-
declare function isSigmaState(value:
|
|
217
|
+
declare function isSigmaState(value: object): value is AnySigmaState;
|
|
164
218
|
/**
|
|
165
219
|
* Creates a standalone tracked query helper with the same signature as `fn`.
|
|
166
220
|
*
|
|
@@ -176,53 +230,30 @@ declare function query<TArgs extends any[], TResult>(fn: (this: void, ...args: T
|
|
|
176
230
|
* State and event inference starts from `new SigmaType<TState, TEvents>()`.
|
|
177
231
|
* Later builder methods infer names and types from the objects you pass to them.
|
|
178
232
|
*/
|
|
179
|
-
declare class SigmaType<TState extends AnyState, TEvents extends AnyEvents = {}, TDefaults extends AnyDefaultState<TState> = {}
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
/** The constructor shape exposed by a configured sigma type. */
|
|
183
|
-
interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefaults extends AnyDefaultState<TState>, TComputeds extends object, TQueries extends object, TActions extends object, TSetupArgs extends any[]> {
|
|
184
|
-
/**
|
|
185
|
-
* Creates a sigma-state instance.
|
|
186
|
-
*
|
|
187
|
-
* Constructor input shallowly overrides `defaultState(...)`. Required keys are
|
|
188
|
-
* inferred from whichever state properties still do not have defaults.
|
|
189
|
-
*/
|
|
190
|
-
new (...args: InitialStateInput<TState, TDefaults>): SigmaState<Extract<OmitEmpty<{
|
|
191
|
-
state: TState;
|
|
192
|
-
events: TEvents;
|
|
193
|
-
computeds: TComputeds;
|
|
194
|
-
queries: TQueries;
|
|
195
|
-
actions: TActions;
|
|
196
|
-
setupArgs: TSetupArgs;
|
|
197
|
-
}>, SigmaDefinition>>;
|
|
198
|
-
/**
|
|
199
|
-
* Type-only access to the configured instance shape.
|
|
200
|
-
*
|
|
201
|
-
* This property does not exist at runtime. Its type is inferred from the
|
|
202
|
-
* generics on `new SigmaType<TState, TEvents>()` plus the later builder inputs.
|
|
203
|
-
*/
|
|
204
|
-
get Instance(): SigmaState<Extract<OmitEmpty<{
|
|
233
|
+
declare class SigmaType<TState extends AnyState, TEvents extends AnyEvents = {}, TDefaults extends AnyDefaultState<TState> = {}> extends Function {
|
|
234
|
+
readonly [sigmaTypeBrand]: {
|
|
205
235
|
state: TState;
|
|
206
236
|
events: TEvents;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
actions: TActions;
|
|
210
|
-
setupArgs: TSetupArgs;
|
|
211
|
-
}>, SigmaDefinition>>;
|
|
237
|
+
};
|
|
238
|
+
constructor(name?: string);
|
|
212
239
|
/**
|
|
213
240
|
* Adds top-level public state and default values to the builder.
|
|
214
241
|
*
|
|
215
242
|
* Each property becomes a reactive public state property on instances. Use a
|
|
216
243
|
* zero-argument function when each instance needs a fresh object or array.
|
|
217
244
|
*/
|
|
218
|
-
defaultState<
|
|
245
|
+
defaultState<TDefaults extends AnyDefaultState<TState>>(defaultState: TDefaults): SigmaType<TState, TEvents, TDefaults>;
|
|
219
246
|
/**
|
|
220
247
|
* Adds reactive getter properties for derived values that take no arguments.
|
|
221
248
|
*
|
|
222
249
|
* Computed names and return types are inferred from the object you pass.
|
|
223
250
|
* `this` exposes readonly state plus computeds that are already on the builder.
|
|
224
251
|
*/
|
|
225
|
-
computed<
|
|
252
|
+
computed<TComputeds extends object>(computeds: TComputeds & ThisType<ComputedContext<this, {
|
|
253
|
+
computeds: TComputeds;
|
|
254
|
+
}>>): ExtendSigmaType<this, {
|
|
255
|
+
computeds: TComputeds;
|
|
256
|
+
}>;
|
|
226
257
|
/**
|
|
227
258
|
* Adds reactive read methods that accept arguments.
|
|
228
259
|
*
|
|
@@ -230,17 +261,21 @@ interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefault
|
|
|
230
261
|
* pass. Each call tracks reactively at the call site and does not memoize
|
|
231
262
|
* results across invocations.
|
|
232
263
|
*/
|
|
233
|
-
queries<
|
|
264
|
+
queries<TQueries extends object>(queries: TQueries & ThisType<ReadonlyContext<this, {
|
|
265
|
+
queries: TQueries;
|
|
266
|
+
}>>): ExtendSigmaType<this, {
|
|
267
|
+
queries: TQueries;
|
|
268
|
+
}>;
|
|
234
269
|
/**
|
|
235
270
|
* Adds a committed-state observer.
|
|
236
271
|
*
|
|
237
272
|
* Observers run after successful publishes and can opt into Immer patches
|
|
238
273
|
* with `{ patches: true }`.
|
|
239
274
|
*/
|
|
240
|
-
observe(listener: (this: ReadonlyContext<
|
|
275
|
+
observe(listener: (this: ReadonlyContext<this>, change: SigmaObserveChange<TState>) => void, options?: SigmaObserveOptions & {
|
|
241
276
|
patches?: false | undefined;
|
|
242
277
|
}): this;
|
|
243
|
-
observe(listener: (this: ReadonlyContext<
|
|
278
|
+
observe(listener: (this: ReadonlyContext<this>, change: SigmaObserveChange<TState, true>) => void, options: SigmaObserveOptions & {
|
|
244
279
|
patches: true;
|
|
245
280
|
}): this;
|
|
246
281
|
/**
|
|
@@ -254,40 +289,39 @@ interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefault
|
|
|
254
289
|
* writes made after `await`. Non-async actions stay synchronous; if one
|
|
255
290
|
* returns a promise, sigma throws so async boundaries stay explicit.
|
|
256
291
|
*/
|
|
257
|
-
actions<
|
|
292
|
+
actions<TActions extends object>(actions: TActions & ThisType<ActionContext<this, {
|
|
293
|
+
actions: TActions;
|
|
294
|
+
}>>): ExtendSigmaType<this, {
|
|
295
|
+
actions: TActions;
|
|
296
|
+
}>;
|
|
258
297
|
/**
|
|
259
298
|
* Adds an explicit setup handler for side effects and owned resources.
|
|
260
299
|
*
|
|
261
300
|
* Every registered handler runs when `instance.setup(...)` is called, and the
|
|
262
301
|
* setup argument list is inferred from the first handler you add.
|
|
263
302
|
*/
|
|
264
|
-
setup<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
303
|
+
setup<TSetupArgs extends any[]>(setup: (this: SetupContext<this, {
|
|
304
|
+
setupArgs: TSetupArgs;
|
|
305
|
+
}>, ...args: TSetupArgs) => readonly AnyResource[]): ExtendSigmaType<this, {
|
|
306
|
+
setupArgs: TSetupArgs;
|
|
307
|
+
}>;
|
|
308
|
+
}
|
|
309
|
+
interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefaults extends AnyDefaultState<TState>> {
|
|
310
|
+
/**
|
|
311
|
+
* Type-only access to the configured instance shape.
|
|
312
|
+
*
|
|
313
|
+
* This property does not exist at runtime. Its type is inferred from the
|
|
314
|
+
* generics on `new SigmaType<TState, TEvents>()` plus the later builder inputs.
|
|
315
|
+
*/
|
|
316
|
+
get Instance(): SigmaState<InferSigmaDefinition<this>>;
|
|
317
|
+
/**
|
|
318
|
+
* Creates a sigma-state instance.
|
|
319
|
+
*
|
|
320
|
+
* Constructor input shallowly overrides `defaultState(...)`. Required keys are
|
|
321
|
+
* inferred from whichever state properties still do not have defaults.
|
|
322
|
+
*/
|
|
323
|
+
new (...args: InitialStateInput<TState, TDefaults>): this["Instance"];
|
|
272
324
|
}
|
|
273
|
-
//#endregion
|
|
274
|
-
//#region src/listener.d.ts
|
|
275
|
-
type TryGet<T, K extends PropertyKey, TCatch = never> = K extends keyof T ? T[K] : TCatch;
|
|
276
|
-
type TypedEventListener<TEventMap, TEvent extends string, TCurrentTarget extends EventTarget> = ((event: TryGet<TEventMap, string extends TEvent ? keyof TEventMap : TEvent, CustomEvent> & {
|
|
277
|
-
readonly currentTarget: TCurrentTarget;
|
|
278
|
-
}) => void) & {
|
|
279
|
-
__eventType?: string extends TEvent ? keyof TEventMap : TEvent;
|
|
280
|
-
};
|
|
281
|
-
/** Infers the event names accepted by `listen(...)` or `useListener(...)` for a target. */
|
|
282
|
-
type InferEventType<TTarget extends EventTarget> = (InferListener<TTarget> extends {
|
|
283
|
-
__eventType?: infer TEvent;
|
|
284
|
-
} ? string & TEvent : never) | (string & {});
|
|
285
|
-
/** Infers the listener callback shape for a target and event name. Sigma states receive payloads directly, while DOM targets receive typed events. */
|
|
286
|
-
type InferListener<TTarget extends EventTarget, TEvent extends string = string> = TTarget extends AnySigmaStateWithEvents<infer TEvents> ? TEvent extends keyof TEvents ? (event: TEvents[TEvent]) => void : never : TTarget extends Window ? TypedEventListener<WindowEventMap, TEvent, TTarget> : TTarget extends Document ? TypedEventListener<DocumentEventMap, TEvent, TTarget> : TTarget extends HTMLBodyElement ? TypedEventListener<HTMLBodyElementEventMap, TEvent, TTarget> : TTarget extends HTMLMediaElement ? TypedEventListener<HTMLMediaElementEventMap, TEvent, TTarget> : TTarget extends HTMLElement ? TypedEventListener<HTMLElementEventMap, TEvent, TTarget> : TTarget extends SVGSVGElement ? TypedEventListener<SVGSVGElementEventMap, TEvent, TTarget> : TTarget extends SVGElement ? TypedEventListener<SVGElementEventMap, TEvent, TTarget> : (event: Event & {
|
|
287
|
-
readonly currentTarget: TTarget;
|
|
288
|
-
}) => void;
|
|
289
|
-
/** Adds a listener to a sigma state or DOM target and returns a cleanup function that removes it. */
|
|
290
|
-
declare function listen<TTarget extends EventTarget, TEvent extends InferEventType<TTarget>>(target: TTarget, name: TEvent, fn: InferListener<TTarget, TEvent>): () => void;
|
|
291
325
|
//#endregion
|
|
292
326
|
//#region src/hooks.d.ts
|
|
293
327
|
/**
|
|
@@ -306,4 +340,4 @@ declare function useSigma<T extends AnySigmaState>(create: () => T, setupArgs?:
|
|
|
306
340
|
*/
|
|
307
341
|
declare function useListener<TTarget extends EventTarget | AnySigmaState, TEvent extends InferEventType<TTarget>>(target: TTarget | null, name: TEvent, listener: InferListener<TTarget, TEvent>): void;
|
|
308
342
|
//#endregion
|
|
309
|
-
export { type AnyDefaultState, type AnyEvents, type AnyResource, type AnySigmaState, type AnySigmaStateWithEvents, type AnyState, InferEventType, InferListener, type InferSetupArgs, type SigmaObserveChange, type SigmaObserveOptions, type SigmaRef, type SigmaState, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
|
|
343
|
+
export { type AnyDefaultState, type AnyEvents, type AnyResource, type AnySigmaState, type AnySigmaStateWithEvents, type AnyState, EventParameters, InferEventType, InferListener, type InferSetupArgs, type SigmaObserveChange, type SigmaObserveOptions, type SigmaRef, type SigmaState, SigmaTarget, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
|
package/dist/index.mjs
CHANGED
|
@@ -105,13 +105,13 @@ function createContext(instance, options, owner) {
|
|
|
105
105
|
const signal = getSignal(instance, key);
|
|
106
106
|
return options.reactiveReads ? signal.value : signal.peek();
|
|
107
107
|
}
|
|
108
|
-
if (key in instance.type.
|
|
108
|
+
if (key in instance.type._computeFunctions) {
|
|
109
109
|
if (owner && options.liveComputeds) return readActionComputedValue(owner, key);
|
|
110
110
|
const signal = getSignal(instance, key);
|
|
111
111
|
return options.reactiveReads ? signal.value : signal.peek();
|
|
112
112
|
}
|
|
113
|
-
if (options.allowQueries && key in instance.type.
|
|
114
|
-
if (options.allowActions && key in instance.type.
|
|
113
|
+
if (options.allowQueries && key in instance.type._queryFunctions) return Reflect.get(instance.publicInstance, key);
|
|
114
|
+
if (options.allowActions && key in instance.type._actionFunctions) return Reflect.get(instance.publicInstance, key);
|
|
115
115
|
if (Reflect.has(publicPrototype, key)) return Reflect.get(publicPrototype, key, owner?.actionContext ?? instance.publicInstance);
|
|
116
116
|
},
|
|
117
117
|
set(_target, key, value) {
|
|
@@ -172,7 +172,7 @@ function getPublicContext(instance, kind) {
|
|
|
172
172
|
}
|
|
173
173
|
//#endregion
|
|
174
174
|
//#region src/internal/symbols.ts
|
|
175
|
-
const sigmaStateBrand = Symbol("sigma.
|
|
175
|
+
const sigmaStateBrand = Symbol("sigma.state");
|
|
176
176
|
const reservedKeys = new Set([
|
|
177
177
|
"act",
|
|
178
178
|
"get",
|
|
@@ -212,7 +212,7 @@ function getSignal(instance, key) {
|
|
|
212
212
|
return instance.publicInstance["#" + key];
|
|
213
213
|
}
|
|
214
214
|
function initializeSigmaInstance(publicInstance, type, initialState) {
|
|
215
|
-
const stateKeys = new Set(type.
|
|
215
|
+
const stateKeys = new Set(type._defaultStateKeys);
|
|
216
216
|
if (initialState) for (const key in initialState) stateKeys.add(key);
|
|
217
217
|
const instance = {
|
|
218
218
|
currentSetupCleanup: void 0,
|
|
@@ -224,7 +224,7 @@ function initializeSigmaInstance(publicInstance, type, initialState) {
|
|
|
224
224
|
for (const key of stateKeys) {
|
|
225
225
|
if (reservedKeys.has(key)) throw new Error(`[preact-sigma] Reserved property name: ${key}`);
|
|
226
226
|
let value = initialState?.[key];
|
|
227
|
-
if (value === void 0) value = typeof type.
|
|
227
|
+
if (value === void 0) value = typeof type._defaultState[key] === "function" ? type._defaultState[key].call(void 0) : type._defaultState[key];
|
|
228
228
|
const container = signal(value);
|
|
229
229
|
Object.defineProperty(publicInstance, "#" + key, { value: container });
|
|
230
230
|
Object.defineProperty(publicInstance, key, {
|
|
@@ -232,7 +232,7 @@ function initializeSigmaInstance(publicInstance, type, initialState) {
|
|
|
232
232
|
enumerable: true
|
|
233
233
|
});
|
|
234
234
|
}
|
|
235
|
-
for (const key in type.
|
|
235
|
+
for (const key in type._computeFunctions) Object.defineProperty(publicInstance, "#" + key, { value: computed$1(() => type._computeFunctions[key].call(getContext(instance, "computedReadonly"))) });
|
|
236
236
|
registerSigmaInternals(publicInstance, instance);
|
|
237
237
|
}
|
|
238
238
|
function buildQueryMethod(queryFunction) {
|
|
@@ -259,7 +259,7 @@ function readActionStateValue(owner, key, options) {
|
|
|
259
259
|
return committedValue;
|
|
260
260
|
}
|
|
261
261
|
function readActionComputedValue(owner, key) {
|
|
262
|
-
return owner.instance.type.
|
|
262
|
+
return owner.instance.type._computeFunctions[key].call(getContext(owner, "computedDraftAware"));
|
|
263
263
|
}
|
|
264
264
|
function setActionStateValue(owner, key, value) {
|
|
265
265
|
ensureOwnerDraft(owner)[key] = value;
|
|
@@ -281,10 +281,10 @@ function handleActionBoundary(owner, boundary, actionName) {
|
|
|
281
281
|
}
|
|
282
282
|
function assertDefinitionKeyAvailable(builder, key, kind) {
|
|
283
283
|
if (reservedKeys.has(key)) throw new Error(`[preact-sigma] Reserved property name: ${key}`);
|
|
284
|
-
if (key in builder.
|
|
284
|
+
if (key in builder._computeFunctions || key in builder._queryFunctions || key in builder._actionFunctions) throw new Error(`[preact-sigma] Duplicate key for ${kind}: ${key}`);
|
|
285
285
|
}
|
|
286
286
|
function shouldSetup(publicInstance) {
|
|
287
|
-
return getSigmaInternals(publicInstance).type.
|
|
287
|
+
return getSigmaInternals(publicInstance).type._setupFunction !== null;
|
|
288
288
|
}
|
|
289
289
|
function clearCurrentDraft(owner) {
|
|
290
290
|
owner.currentDraft = void 0;
|
|
@@ -396,7 +396,7 @@ function finalizeOwnerDraft(owner) {
|
|
|
396
396
|
clearCurrentDraft(owner);
|
|
397
397
|
let patches;
|
|
398
398
|
let inversePatches;
|
|
399
|
-
const newState = owner.instance.type.
|
|
399
|
+
const newState = owner.instance.type._patchesEnabled ? immer.finishDraft(currentDraft, (nextPatches, nextInversePatches) => {
|
|
400
400
|
patches = nextPatches;
|
|
401
401
|
inversePatches = nextInversePatches;
|
|
402
402
|
}) : immer.finishDraft(currentDraft);
|
|
@@ -413,7 +413,7 @@ function finalizeReplacementState(instance, oldState, nextState) {
|
|
|
413
413
|
for (const key of instance.stateKeys) draft[key] = nextState[key];
|
|
414
414
|
let patches;
|
|
415
415
|
let inversePatches;
|
|
416
|
-
const newState = instance.type.
|
|
416
|
+
const newState = instance.type._patchesEnabled ? immer.finishDraft(draft, (nextPatches, nextInversePatches) => {
|
|
417
417
|
patches = nextPatches;
|
|
418
418
|
inversePatches = nextInversePatches;
|
|
419
419
|
}) : immer.finishDraft(draft);
|
|
@@ -440,7 +440,7 @@ function publishState(instance, finalized) {
|
|
|
440
440
|
signal.value = nextValue;
|
|
441
441
|
}
|
|
442
442
|
});
|
|
443
|
-
|
|
443
|
+
instance.type._observeFunction?.call(getContext(instance, "observe"), finalized);
|
|
444
444
|
}
|
|
445
445
|
/**
|
|
446
446
|
* Returns a shallow snapshot of an instance's committed public state.
|
|
@@ -492,15 +492,12 @@ async function resolveAsyncActionResult(owner, result) {
|
|
|
492
492
|
var Sigma = class extends EventTarget {
|
|
493
493
|
setup(...args) {
|
|
494
494
|
const instance = getSigmaInternals(this);
|
|
495
|
-
if (!instance.type.
|
|
495
|
+
if (!instance.type._setupFunction) throw new Error("[preact-sigma] Setup is undefined for this sigma state");
|
|
496
496
|
if (instance.disposed) throw new Error("[preact-sigma] Cannot set up a disposed sigma state");
|
|
497
497
|
instance.currentSetupCleanup?.();
|
|
498
498
|
instance.currentSetupCleanup = void 0;
|
|
499
|
-
const resources = instance.type.
|
|
500
|
-
|
|
501
|
-
if (!Array.isArray(result)) throw new Error("[preact-sigma] Sigma setup handlers must return an array");
|
|
502
|
-
return result;
|
|
503
|
-
});
|
|
499
|
+
const resources = instance.type._setupFunction.apply(getContext(instance, "setup"), args);
|
|
500
|
+
if (!Array.isArray(resources)) throw new Error("[preact-sigma] Sigma setup handlers must return an array");
|
|
504
501
|
let cleanup;
|
|
505
502
|
if (resources.length) {
|
|
506
503
|
let cleaned = false;
|
|
@@ -541,7 +538,7 @@ Object.defineProperty(Sigma.prototype, sigmaStateBrand, { value: true });
|
|
|
541
538
|
//#region src/framework.ts
|
|
542
539
|
/** Checks whether a value is an instance created by a configured sigma type. */
|
|
543
540
|
function isSigmaState(value) {
|
|
544
|
-
return Boolean(value
|
|
541
|
+
return Boolean(value[sigmaStateBrand]);
|
|
545
542
|
}
|
|
546
543
|
/**
|
|
547
544
|
* Creates a standalone tracked query helper with the same signature as `fn`.
|
|
@@ -560,81 +557,153 @@ function query(fn) {
|
|
|
560
557
|
* State and event inference starts from `new SigmaType<TState, TEvents>()`.
|
|
561
558
|
* Later builder methods infer names and types from the objects you pass to them.
|
|
562
559
|
*/
|
|
563
|
-
var SigmaType = class extends Function {
|
|
560
|
+
var SigmaType = class SigmaType extends Function {
|
|
564
561
|
constructor(name = "Sigma") {
|
|
565
562
|
super();
|
|
566
|
-
const type = {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
};
|
|
576
|
-
const { [name]: SigmaTypeBuilder } = { [name]: class extends Sigma {
|
|
563
|
+
const { [name]: type } = { [name]: class extends Sigma {
|
|
564
|
+
static _actionFunctions = Object.create(null);
|
|
565
|
+
static _computeFunctions = Object.create(null);
|
|
566
|
+
static _defaultState = Object.create(null);
|
|
567
|
+
static _defaultStateKeys = [];
|
|
568
|
+
static _observeFunction = null;
|
|
569
|
+
static _patchesEnabled = false;
|
|
570
|
+
static _queryFunctions = Object.create(null);
|
|
571
|
+
static _setupFunction = null;
|
|
577
572
|
constructor(initialState) {
|
|
578
573
|
super();
|
|
579
574
|
initializeSigmaInstance(this, type, initialState);
|
|
580
575
|
}
|
|
581
576
|
} };
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
type.
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
577
|
+
Object.defineProperties(type, Object.getOwnPropertyDescriptors(SigmaType.prototype));
|
|
578
|
+
return type;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Adds top-level public state and default values to the builder.
|
|
582
|
+
*
|
|
583
|
+
* Each property becomes a reactive public state property on instances. Use a
|
|
584
|
+
* zero-argument function when each instance needs a fresh object or array.
|
|
585
|
+
*/
|
|
586
|
+
defaultState(defaultState) {
|
|
587
|
+
const type = getTypeInternals(this);
|
|
588
|
+
for (const key in defaultState) {
|
|
589
|
+
if (defaultState[key] === void 0) continue;
|
|
590
|
+
type._defaultState[key] = defaultState[key];
|
|
591
|
+
type._defaultStateKeys.push(key);
|
|
592
|
+
}
|
|
593
|
+
return this;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Adds reactive getter properties for derived values that take no arguments.
|
|
597
|
+
*
|
|
598
|
+
* Computed names and return types are inferred from the object you pass.
|
|
599
|
+
* `this` exposes readonly state plus computeds that are already on the builder.
|
|
600
|
+
*/
|
|
601
|
+
computed(computeds) {
|
|
602
|
+
const type = getTypeInternals(this);
|
|
603
|
+
for (const key in computeds) {
|
|
604
|
+
assertDefinitionKeyAvailable(type, key, "computed");
|
|
605
|
+
type._computeFunctions[key] = computeds[key];
|
|
606
|
+
Object.defineProperty(this.prototype, key, { get: function() {
|
|
607
|
+
return this["#" + key].value;
|
|
608
|
+
} });
|
|
609
|
+
}
|
|
610
|
+
return this;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Adds reactive read methods that accept arguments.
|
|
614
|
+
*
|
|
615
|
+
* Query names, parameters, and return types are inferred from the object you
|
|
616
|
+
* pass. Each call tracks reactively at the call site and does not memoize
|
|
617
|
+
* results across invocations.
|
|
618
|
+
*/
|
|
619
|
+
queries(queries) {
|
|
620
|
+
const type = getTypeInternals(this);
|
|
621
|
+
for (const key in queries) {
|
|
622
|
+
assertDefinitionKeyAvailable(type, key, "query");
|
|
623
|
+
const queryFunction = queries[key];
|
|
624
|
+
type._queryFunctions[key] = queryFunction;
|
|
625
|
+
Object.defineProperty(this.prototype, key, { value: buildQueryMethod(queryFunction) });
|
|
626
|
+
}
|
|
627
|
+
return this;
|
|
628
|
+
}
|
|
629
|
+
observe(listener, options) {
|
|
630
|
+
const type = getTypeInternals(this);
|
|
631
|
+
type._observeFunction = listener;
|
|
632
|
+
if (options?.patches) type._patchesEnabled = true;
|
|
633
|
+
return this;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Adds action methods whose `this` receives draft state, typed events, `commit()`,
|
|
637
|
+
* and the computeds, queries, and actions already defined on the builder.
|
|
638
|
+
*
|
|
639
|
+
* Actions create drafts lazily as they need them. Sync actions on the same
|
|
640
|
+
* instance reuse the current draft, so they can compose and publish once when
|
|
641
|
+
* the outer action returns. Declared async actions publish their initial
|
|
642
|
+
* synchronous work on return, then require `this.commit()` to publish later
|
|
643
|
+
* writes made after `await`. Non-async actions stay synchronous; if one
|
|
644
|
+
* returns a promise, sigma throws so async boundaries stay explicit.
|
|
645
|
+
*/
|
|
646
|
+
actions(actions) {
|
|
647
|
+
const type = getTypeInternals(this);
|
|
648
|
+
for (const key in actions) {
|
|
649
|
+
assertDefinitionKeyAvailable(type, key, "action");
|
|
650
|
+
const actionFunction = actions[key];
|
|
651
|
+
type._actionFunctions[key] = actionFunction;
|
|
652
|
+
Object.defineProperty(this.prototype, key, { value: buildActionMethod(key, actionFunction) });
|
|
653
|
+
}
|
|
654
|
+
return this;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Adds an explicit setup handler for side effects and owned resources.
|
|
658
|
+
*
|
|
659
|
+
* Every registered handler runs when `instance.setup(...)` is called, and the
|
|
660
|
+
* setup argument list is inferred from the first handler you add.
|
|
661
|
+
*/
|
|
662
|
+
setup(setup) {
|
|
663
|
+
const type = getTypeInternals(this);
|
|
664
|
+
type._setupFunction = setup;
|
|
665
|
+
return this;
|
|
628
666
|
}
|
|
629
667
|
};
|
|
668
|
+
function getTypeInternals(type) {
|
|
669
|
+
return type;
|
|
670
|
+
}
|
|
630
671
|
//#endregion
|
|
631
672
|
//#region src/listener.ts
|
|
673
|
+
/**
|
|
674
|
+
* A standalone typed event hub with `emit(...)` and `on(...)` methods and full
|
|
675
|
+
* `EventTarget`, `listen(...)`, and `useListener(...)` compatibility.
|
|
676
|
+
*/
|
|
677
|
+
var SigmaTarget = class extends EventTarget {
|
|
678
|
+
/**
|
|
679
|
+
* Emits a typed event from the hub.
|
|
680
|
+
*
|
|
681
|
+
* Void events dispatch a plain `Event`. Payload events dispatch a
|
|
682
|
+
* `CustomEvent` whose `detail` holds the payload.
|
|
683
|
+
*/
|
|
684
|
+
emit(name, ...[detail]) {
|
|
685
|
+
this.dispatchEvent(detail === void 0 ? new Event(name) : new CustomEvent(name, { detail }));
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Registers a typed event listener and returns an unsubscribe function.
|
|
689
|
+
*
|
|
690
|
+
* Payload events pass their payload directly to the listener. Void events
|
|
691
|
+
* call the listener with no arguments.
|
|
692
|
+
*/
|
|
693
|
+
on(name, listener) {
|
|
694
|
+
const adapter = (event) => listener(event.detail);
|
|
695
|
+
this.addEventListener(name, adapter);
|
|
696
|
+
return () => {
|
|
697
|
+
this.removeEventListener(name, adapter);
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
};
|
|
632
701
|
/** Adds a listener to a sigma state or DOM target and returns a cleanup function that removes it. */
|
|
633
|
-
function listen(target, name,
|
|
634
|
-
const
|
|
635
|
-
target.addEventListener(name,
|
|
702
|
+
function listen(target, name, listener) {
|
|
703
|
+
const adapter = isSigmaState(target) || target instanceof SigmaTarget ? (event) => listener(event.detail) : listener;
|
|
704
|
+
target.addEventListener(name, adapter);
|
|
636
705
|
return () => {
|
|
637
|
-
target.removeEventListener(name,
|
|
706
|
+
target.removeEventListener(name, adapter);
|
|
638
707
|
};
|
|
639
708
|
}
|
|
640
709
|
//#endregion
|
|
@@ -669,4 +738,4 @@ function useListener(target, name, listener) {
|
|
|
669
738
|
}, [target, name]);
|
|
670
739
|
}
|
|
671
740
|
//#endregion
|
|
672
|
-
export { SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
|
|
741
|
+
export { SigmaTarget, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
|
package/docs/context.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Overview
|
|
2
2
|
|
|
3
|
-
`preact-sigma` builds reusable state models from one definition. A configured `SigmaType` owns top-level state, derived reads, writes, setup handlers, and typed events. Each top-level state property is exposed as a reactive public property backed by its own Preact signal, while actions use Immer-style mutation semantics to publish committed state.
|
|
3
|
+
`preact-sigma` builds reusable state models from one definition. A configured `SigmaType` owns top-level state, derived reads, writes, setup handlers, and typed events. Each top-level state property is exposed as a reactive public property backed by its own Preact signal, while actions use Immer-style mutation semantics to publish committed state. For event-only flows, `SigmaTarget` provides a standalone typed event hub with no managed state.
|
|
4
4
|
|
|
5
5
|
# When to Use
|
|
6
6
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
- Sigma type: the builder returned by `new SigmaType<TState, TEvents>()`. After configuration, it is also the constructor for instances.
|
|
23
23
|
- Sigma state: an instance created from a configured sigma type.
|
|
24
|
+
- Sigma target: a standalone typed event hub created with `new SigmaTarget<TEvents>()` when you need typed events without managed state.
|
|
24
25
|
- State property: a top-level key from `TState`. Each one becomes a readonly reactive public property and gets its own signal.
|
|
25
26
|
- Computed: an argument-free derived getter declared with `.computed(...)`.
|
|
26
27
|
- Query: a reactive read that accepts arguments, declared with `.queries(...)` or built locally with `query(fn)`.
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
- Own timers, listeners, subscriptions, or nested setup: `.setup(...)`
|
|
52
53
|
- Use a sigma state inside a component: `useSigma(...)`
|
|
53
54
|
- Subscribe to sigma or DOM events in a component: `useListener(...)`
|
|
55
|
+
- Create a standalone typed event hub with no managed state: `new SigmaTarget<TEvents>()`, `hub.emit(...)`, and `hub.on(...)`
|
|
54
56
|
- Subscribe outside components: `.on(...)` or `listen(...)`
|
|
55
57
|
- Read or restore committed top-level state: `snapshot(...)` and `replaceState(...)`
|
|
56
58
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { listen, SigmaTarget } from "preact-sigma";
|
|
2
|
+
|
|
3
|
+
const notifications = new SigmaTarget<{
|
|
4
|
+
saved: {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
};
|
|
8
|
+
reset: void;
|
|
9
|
+
}>();
|
|
10
|
+
|
|
11
|
+
const stopSaved = notifications.on("saved", ({ id, title }) => {
|
|
12
|
+
console.log(`Saved ${id}: ${title}`);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const stopReset = listen(notifications, "reset", () => {
|
|
16
|
+
console.log("Reset");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
notifications.emit("saved", {
|
|
20
|
+
id: "note-1",
|
|
21
|
+
title: "Draft post",
|
|
22
|
+
});
|
|
23
|
+
notifications.emit("reset");
|
|
24
|
+
|
|
25
|
+
stopSaved();
|
|
26
|
+
stopReset();
|