preact-sigma 2.2.2 → 2.3.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/dist/index.mjs CHANGED
@@ -198,7 +198,7 @@ function getSigmaInternals(context) {
198
198
  function isAutoFreeze() {
199
199
  return autoFreezeEnabled;
200
200
  }
201
- /** Controls whether sigma deep-freezes published public state. Auto-freezing starts enabled. */
201
+ /** Controls whether sigma deep-freezes published public state. Auto-freezing starts enabled and the setting is shared across instances. */
202
202
  function setAutoFreeze(autoFreeze) {
203
203
  autoFreezeEnabled = autoFreeze;
204
204
  immer.setAutoFreeze(autoFreeze);
@@ -446,8 +446,9 @@ function publishState(instance, finalized) {
446
446
  * Returns a shallow snapshot of an instance's committed public state.
447
447
  *
448
448
  * The snapshot includes one own property for each top-level state key and reads
449
- * the current committed value for that key. Its type is inferred from the
450
- * instance's sigma-state definition.
449
+ * the current committed value for that key. Nested sigma states remain as
450
+ * referenced values. Its type is inferred from the instance's sigma-state
451
+ * definition.
451
452
  */
452
453
  function snapshot(publicInstance) {
453
454
  return snapshotState(getSigmaInternals(publicInstance));
@@ -456,8 +457,9 @@ function snapshot(publicInstance) {
456
457
  * Replaces an instance's committed public state from a snapshot object.
457
458
  *
458
459
  * The replacement snapshot must be a plain object with exactly the instance's
459
- * top-level state keys. Its type is inferred from the instance's sigma-state
460
- * definition.
460
+ * top-level state keys. It updates committed state outside action semantics and
461
+ * notifies observers when the committed state changes. Its type is inferred
462
+ * from the instance's sigma-state definition.
461
463
  */
462
464
  function replaceState(publicInstance, nextState) {
463
465
  const instance = getSigmaInternals(publicInstance);
@@ -537,17 +539,26 @@ var Sigma = class extends EventTarget {
537
539
  Object.defineProperty(Sigma.prototype, sigmaStateBrand, { value: true });
538
540
  //#endregion
539
541
  //#region src/framework.ts
540
- /** Checks whether a value is a sigma-state instance. */
542
+ /** Checks whether a value is an instance created by a configured sigma type. */
541
543
  function isSigmaState(value) {
542
- return Boolean(value && typeof value === "object" && value[sigmaStateBrand]);
544
+ return Boolean(value[sigmaStateBrand]);
543
545
  }
544
- /** Creates a standalone tracked query function with the same signature as `fn`. */
546
+ /**
547
+ * Creates a standalone tracked query helper with the same signature as `fn`.
548
+ *
549
+ * Each call is reactive at the call site and does not memoize results across
550
+ * invocations, which makes `query(fn)` a good fit for local tracked helpers
551
+ * that do not need to live on the sigma-state instance.
552
+ */
545
553
  function query(fn) {
546
554
  return ((...args) => computed$1(() => fn(...args)).value);
547
555
  }
548
556
  /**
549
- * Builds sigma-state constructors by accumulating default state, computeds, queries,
550
- * observers, actions, and setup handlers.
557
+ * Builds sigma-state constructors by accumulating default state, computeds,
558
+ * queries, observers, actions, and setup handlers.
559
+ *
560
+ * State and event inference starts from `new SigmaType<TState, TEvents>()`.
561
+ * Later builder methods infer names and types from the objects you pass to them.
551
562
  */
552
563
  var SigmaType = class extends Function {
553
564
  constructor(name = "Sigma") {
@@ -618,17 +629,51 @@ var SigmaType = class extends Function {
618
629
  };
619
630
  //#endregion
620
631
  //#region src/listener.ts
621
- /** Adds an event listener and returns a cleanup function that removes it. */
622
- function listen(target, name, fn) {
623
- const listener = isSigmaState(target) ? (event) => fn(event.detail) : fn;
624
- target.addEventListener(name, listener);
632
+ /**
633
+ * A standalone typed event hub with `emit(...)` and `on(...)` methods and full
634
+ * `EventTarget`, `listen(...)`, and `useListener(...)` compatibility.
635
+ */
636
+ var SigmaTarget = class extends EventTarget {
637
+ /**
638
+ * Emits a typed event from the hub.
639
+ *
640
+ * Void events dispatch a plain `Event`. Payload events dispatch a
641
+ * `CustomEvent` whose `detail` holds the payload.
642
+ */
643
+ emit(name, ...args) {
644
+ this.dispatchEvent(args.length === 0 ? new Event(name) : new CustomEvent(name, { detail: args[0] }));
645
+ }
646
+ /**
647
+ * Registers a typed event listener and returns an unsubscribe function.
648
+ *
649
+ * Payload events pass their payload directly to the listener. Void events
650
+ * call the listener with no arguments.
651
+ */
652
+ on(name, listener) {
653
+ const adapter = (event) => listener(event.detail);
654
+ this.addEventListener(name, adapter);
655
+ return () => {
656
+ this.removeEventListener(name, adapter);
657
+ };
658
+ }
659
+ };
660
+ /** Adds a listener to a sigma state or DOM target and returns a cleanup function that removes it. */
661
+ function listen(target, name, listener) {
662
+ const adapter = isSigmaState(target) || target instanceof SigmaTarget ? (event) => listener(event.detail) : listener;
663
+ target.addEventListener(name, adapter);
625
664
  return () => {
626
- target.removeEventListener(name, listener);
665
+ target.removeEventListener(name, adapter);
627
666
  };
628
667
  }
629
668
  //#endregion
630
669
  //#region src/hooks.ts
631
- /** Creates one sigma-state instance for a component and manages its setup cleanup. */
670
+ /**
671
+ * Creates one sigma-state instance for a component.
672
+ *
673
+ * `create()` runs once per mounted component instance. When the sigma state
674
+ * defines setup, `useSigma(...)` also runs `setup(...setupArgs)` in an effect
675
+ * and cleans it up when the setup arguments change or the component unmounts.
676
+ */
632
677
  function useSigma(create, setupArgs) {
633
678
  const sigmaState = useState(create)[0];
634
679
  if (shouldSetup(sigmaState)) {
@@ -637,7 +682,12 @@ function useSigma(create, setupArgs) {
637
682
  }
638
683
  return sigmaState;
639
684
  }
640
- /** Attaches an event listener in a component and cleans it up when dependencies change. */
685
+ /**
686
+ * Attaches an event listener in a component and cleans it up automatically.
687
+ *
688
+ * Passing `null` disables the listener. The latest callback is used without
689
+ * forcing the effect to resubscribe on every render.
690
+ */
641
691
  function useListener(target, name, listener) {
642
692
  const listenerRef = useRef(listener);
643
693
  listenerRef.current = listener;
@@ -647,4 +697,4 @@ function useListener(target, name, listener) {
647
697
  }, [target, name]);
648
698
  }
649
699
  //#endregion
650
- export { SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
700
+ export { SigmaTarget, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
@@ -0,0 +1,104 @@
1
+ # Overview
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. For event-only flows, `SigmaTarget` provides a standalone typed event hub with no managed state.
4
+
5
+ # When to Use
6
+
7
+ - State, derived reads, mutations, and lifecycle need to stay together.
8
+ - You need multiple instances of the same model.
9
+ - Public reads should stay reactive and readonly while writes stay explicit.
10
+ - A model needs to own timers, subscriptions, listeners, nested setup, or other cleanup-aware resources.
11
+ - Components should consume the same model shape used outside Preact.
12
+
13
+ # When Not to Use
14
+
15
+ - A few plain signals already cover the state without extra coordination.
16
+ - You want side effects to start implicitly during construction.
17
+ - The main problem is remote caching, normalization, or cross-app store tooling rather than local state behavior.
18
+ - You need ad hoc mutable objects with no benefit from typed actions, setup, or signal-backed reads.
19
+
20
+ # Core Abstractions
21
+
22
+ - Sigma type: the builder returned by `new SigmaType<TState, TEvents>()`. After configuration, it is also the constructor for instances.
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.
25
+ - State property: a top-level key from `TState`. Each one becomes a readonly reactive public property and gets its own signal.
26
+ - Computed: an argument-free derived getter declared with `.computed(...)`.
27
+ - Query: a reactive read that accepts arguments, declared with `.queries(...)` or built locally with `query(fn)`.
28
+ - Action: a method declared with `.actions(...)` that reads and writes through sigma's draft and commit semantics.
29
+ - Setup handler: a function declared with `.setup(...)` that owns side effects and cleanup resources explicitly.
30
+ - Event: a typed notification emitted through `this.emit(...)` and observed through `.on(...)`, `listen(...)`, or `useListener(...)`.
31
+
32
+ # Data Flow / Lifecycle
33
+
34
+ 1. Define a sigma type with `new SigmaType<TState, TEvents>()`. Let later builder methods infer names and types from the objects you pass to them.
35
+ 2. Add `defaultState(...)` for top-level public state and optional per-instance initializers.
36
+ 3. Add `computed(...)`, `queries(...)`, and `actions(...)` for derived reads and writes.
37
+ 4. Instantiate the configured type. Constructor input shallowly overrides `defaultState(...)`.
38
+ 5. Read state, computeds, and queries reactively from the public instance.
39
+ 6. Mutate state inside actions. Sync nested actions on the same instance share one draft. Boundaries like `await`, `emit(...)`, or separate action invocations may require `this.commit()` before the boundary.
40
+ 7. Run `setup(...)` explicitly when the instance should start owning side effects. `useSigma(...)` does this automatically for component-owned instances that define setup.
41
+ 8. Dispose the cleanup returned from `setup(...)` when the owned resources should stop.
42
+
43
+ # Common Tasks -> Recommended APIs
44
+
45
+ - Define reusable model state: `new SigmaType<TState, TEvents>().defaultState(...)`
46
+ - Derive an argument-free value: `.computed(...)`
47
+ - Derive a reactive read with arguments: `.queries(...)`
48
+ - Keep a tracked helper local to one consumer module: `query(fn)`
49
+ - Mutate state and emit typed notifications: `.actions(...)`
50
+ - Publish before `await`, `emit(...)`, or another action boundary: `this.commit()`
51
+ - React to committed state changes: `.observe(...)`
52
+ - Own timers, listeners, subscriptions, or nested setup: `.setup(...)`
53
+ - Use a sigma state inside a component: `useSigma(...)`
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(...)`
56
+ - Subscribe outside components: `.on(...)` or `listen(...)`
57
+ - Read or restore committed top-level state: `snapshot(...)` and `replaceState(...)`
58
+
59
+ # Practical Guidelines
60
+
61
+ - Put explicit type arguments on `new SigmaType<TState, TEvents>()` and let later builder methods infer from the objects you pass.
62
+ - Keep frequently read values as separate top-level state properties. Each top-level key gets its own signal.
63
+ - Use `.computed(...)` for argument-free derived reads.
64
+ - Use `.queries(...)` for tracked reads with arguments.
65
+ - Keep one-off calculations local until they become reusable model behavior.
66
+ - Reach for `instance.get(key)` only when code specifically needs the underlying `ReadonlySignal`.
67
+ - Treat `emit(...)`, `await`, and any action call other than a same-instance synchronous nested action call as draft boundaries. Call `this.commit()` only when pending changes need to become public before one of those boundaries.
68
+ - Use ordinary actions for routine writes. Reserve `snapshot(...)` and `replaceState(...)` for replay, reset, or undo-like flows on committed top-level state.
69
+ - Put owned side effects in `.setup(...)`.
70
+ - Use `this.act(function () { ... })` for setup-owned callbacks that need action semantics.
71
+ - Call Immer's `enablePatches()` before relying on `.observe(..., { patches: true })`.
72
+
73
+ # Invariants and Constraints
74
+
75
+ - Sigma only tracks top-level state properties. Each top-level key gets its own signal.
76
+ - Public state is readonly outside actions and `this.act(...)` inside setup.
77
+ - Duplicate names across state properties, computeds, queries, and actions are rejected at runtime. Reserved public names include `act`, `emit`, `get`, `on`, and `setup`.
78
+ - Query calls are reactive at the call site but do not memoize across invocations.
79
+ - Setup handlers return arrays of cleanup resources, and cleanup runs in reverse order.
80
+ - `replaceState(...)` works on committed top-level state and requires the exact state-key shape.
81
+ - Published draftable public state is deep-frozen by default. `setAutoFreeze(false)` disables that behavior globally.
82
+
83
+ # Error Model
84
+
85
+ - Crossing an action boundary with unpublished changes throws until `this.commit()` publishes them. Async actions also reject when they finish with unpublished changes.
86
+ - If another invocation crosses a boundary while unpublished changes still exist, sigma warns and discards those changes before continuing.
87
+ - Calling `setup(...)` on a sigma state without registered setup handlers throws.
88
+ - Cleanup rethrows an `AggregateError` when more than one cleanup resource fails.
89
+ - `replaceState(...)` throws when the replacement value is not a plain object, has the wrong top-level keys, or runs while an action still owns unpublished changes.
90
+
91
+ # Terminology
92
+
93
+ - Draft boundary: a point where sigma cannot keep reusing the current unpublished draft.
94
+ - Committed state: the published top-level public state visible outside the current action draft.
95
+ - Signal access: reading the underlying `ReadonlySignal` for a top-level state key or computed through `instance.get(key)`.
96
+ - Cleanup resource: a cleanup function, `AbortController`, object with `dispose()`, or object with `[Symbol.dispose]()`.
97
+ - Nested sigma state: a sigma-state instance stored in top-level state as a value; it stays usable as a value rather than exposing its internals through parent actions.
98
+
99
+ # Non-Goals
100
+
101
+ - Replacing every plain-signal use case with a builder abstraction.
102
+ - Hiding lifecycle behind implicit setup or constructor side effects.
103
+ - Memoizing every query call or turning queries into a global cache.
104
+ - Acting as a large tutorial framework or hand-maintained API reference. Exact signatures come from declaration output, and factual behavior lives beside source.
@@ -0,0 +1,42 @@
1
+ import { SigmaType } from "preact-sigma";
2
+
3
+ const SaveIndicator = new SigmaType<
4
+ {
5
+ savedCount: number;
6
+ saving: boolean;
7
+ },
8
+ {
9
+ saved: {
10
+ count: number;
11
+ };
12
+ }
13
+ >("SaveIndicator")
14
+ .defaultState({
15
+ savedCount: 0,
16
+ saving: false,
17
+ })
18
+ .actions({
19
+ async save() {
20
+ this.saving = true;
21
+ this.commit(); // Publish before the async boundary.
22
+
23
+ await Promise.resolve();
24
+
25
+ this.savedCount += 1;
26
+ this.saving = false;
27
+ this.commit(); // Publish before emitting the event boundary.
28
+
29
+ this.emit("saved", { count: this.savedCount });
30
+ },
31
+ });
32
+
33
+ const indicator = new SaveIndicator();
34
+
35
+ indicator.on("saved", ({ count }) => {
36
+ console.log(`Saved ${count} times`);
37
+ });
38
+
39
+ await indicator.save();
40
+
41
+ console.log(indicator.saving); // false
42
+ console.log(indicator.savedCount); // 1
@@ -0,0 +1,23 @@
1
+ import { SigmaType } from "preact-sigma";
2
+
3
+ const Counter = new SigmaType<{ count: number }>("Counter")
4
+ .defaultState({
5
+ count: 0,
6
+ })
7
+ .computed({
8
+ doubled() {
9
+ return this.count * 2;
10
+ },
11
+ })
12
+ .actions({
13
+ increment() {
14
+ this.count += 1;
15
+ },
16
+ });
17
+
18
+ const counter = new Counter();
19
+
20
+ counter.increment();
21
+
22
+ console.log(counter.count); // 1
23
+ console.log(counter.doubled); // 2
@@ -0,0 +1,211 @@
1
+ import { useState } from "preact/hooks";
2
+
3
+ import { listen, query, SigmaType, useListener, useSigma } from "preact-sigma";
4
+
5
+ type Command = {
6
+ id: string;
7
+ title: string;
8
+ keywords: readonly string[];
9
+ };
10
+
11
+ class UsageLedger {
12
+ counts = new Map<string, number>();
13
+
14
+ get(id: string) {
15
+ return this.counts.get(id) ?? 0;
16
+ }
17
+
18
+ increment(id: string) {
19
+ this.counts.set(id, this.get(id) + 1);
20
+ }
21
+ }
22
+
23
+ const matchesText = query((command: Command, draft: string) => {
24
+ const needle = draft.trim().toLowerCase();
25
+ if (!needle) {
26
+ return true;
27
+ }
28
+
29
+ return (
30
+ command.title.toLowerCase().includes(needle) ||
31
+ command.keywords.some((keyword) => keyword.toLowerCase().includes(needle))
32
+ );
33
+ });
34
+
35
+ const SearchHistory = new SigmaType<{
36
+ items: string[];
37
+ }>("SearchHistory")
38
+ .defaultState({
39
+ items: [],
40
+ })
41
+ .actions({
42
+ remember(query: string) {
43
+ const value = query.trim();
44
+ if (!value) {
45
+ return;
46
+ }
47
+
48
+ this.items = [value, ...this.items.filter((item) => item !== value)].slice(0, 5);
49
+ },
50
+ });
51
+
52
+ interface SearchHistory extends InstanceType<typeof SearchHistory> {}
53
+
54
+ const CommandPalette = new SigmaType<
55
+ {
56
+ commands: Command[];
57
+ cursor: number;
58
+ draft: string;
59
+ history: SearchHistory;
60
+ usage: UsageLedger;
61
+ },
62
+ {
63
+ ran: Command;
64
+ }
65
+ >("CommandPalette")
66
+ .defaultState({
67
+ commands: [
68
+ { id: "inbox", title: "Open inbox", keywords: ["mail", "messages", "triage"] },
69
+ { id: "capture", title: "Capture note", keywords: ["write", "quick", "idea"] },
70
+ { id: "focus", title: "Start focus timer", keywords: ["pomodoro", "deep work"] },
71
+ { id: "theme", title: "Toggle theme", keywords: ["appearance", "dark", "light"] },
72
+ ],
73
+ cursor: 0,
74
+ draft: "",
75
+ history: () => new SearchHistory(),
76
+ usage: () => new UsageLedger(),
77
+ })
78
+ .computed({
79
+ visibleCommands() {
80
+ return this.commands.filter((command) => matchesText(command, this.draft));
81
+ },
82
+ activeCommand() {
83
+ return this.visibleCommands[this.cursor] ?? null;
84
+ },
85
+ })
86
+ .queries({
87
+ canRun() {
88
+ return this.activeCommand !== null;
89
+ },
90
+ usageCount(id: string) {
91
+ return this.usage.get(id);
92
+ },
93
+ })
94
+ .actions({
95
+ setDraft(draft: string) {
96
+ this.draft = draft;
97
+ this.cursor = 0;
98
+ },
99
+ move(step: number) {
100
+ if (this.visibleCommands.length === 0) {
101
+ this.cursor = 0;
102
+ return;
103
+ }
104
+
105
+ const lastIndex = this.visibleCommands.length - 1;
106
+ this.cursor = Math.max(0, Math.min(lastIndex, this.cursor + step));
107
+ },
108
+ seedDraftFromHistory() {
109
+ const latest = this.history.items[0];
110
+ if (latest) {
111
+ this.setDraft(latest);
112
+ }
113
+ },
114
+ runActive() {
115
+ const command = this.activeCommand;
116
+ if (!command || !this.canRun()) {
117
+ return;
118
+ }
119
+
120
+ this.history.remember(this.draft || command.title);
121
+ this.usage.increment(command.id);
122
+ this.emit("ran", command);
123
+ this.draft = "";
124
+ this.cursor = 0;
125
+ },
126
+ })
127
+ .setup(function () {
128
+ return [
129
+ listen(window, "keydown", (event) => {
130
+ if ((event.metaKey || event.ctrlKey) && event.key === "k") {
131
+ event.preventDefault();
132
+ this.seedDraftFromHistory();
133
+ return;
134
+ }
135
+
136
+ if (event.key === "ArrowDown") {
137
+ event.preventDefault();
138
+ this.move(1);
139
+ } else if (event.key === "ArrowUp") {
140
+ event.preventDefault();
141
+ this.move(-1);
142
+ } else if (event.key === "Enter") {
143
+ this.runActive();
144
+ }
145
+ }),
146
+ ];
147
+ });
148
+
149
+ interface CommandPalette extends InstanceType<typeof CommandPalette> {}
150
+
151
+ export function CommandPaletteExample() {
152
+ const palette = useSigma(() => new CommandPalette());
153
+ const [lastRun, setLastRun] = useState<string>("Nothing yet");
154
+
155
+ useListener(palette, "ran", (command) => {
156
+ setLastRun(`${command.title} (${palette.usageCount(command.id)} runs)`);
157
+ });
158
+
159
+ return (
160
+ <section>
161
+ <p>
162
+ <strong>Command palette</strong>: setup-owned keyboard shortcuts, computed getters, tracked
163
+ queries with args, typed events, nested sigma state, and a mutable custom class instance.
164
+ </p>
165
+
166
+ <label>
167
+ Search
168
+ <input
169
+ value={palette.draft}
170
+ onInput={(event) => palette.setDraft((event.currentTarget as HTMLInputElement).value)}
171
+ placeholder="Try: note, timer, inbox"
172
+ />
173
+ </label>
174
+
175
+ <div>
176
+ <button type="button" onClick={() => palette.move(-1)}>
177
+ Up
178
+ </button>
179
+ <button type="button" onClick={() => palette.move(1)}>
180
+ Down
181
+ </button>
182
+ <button type="button" onClick={() => palette.runActive()} disabled={!palette.canRun()}>
183
+ Run
184
+ </button>
185
+ </div>
186
+
187
+ <p>Last run: {lastRun}</p>
188
+
189
+ <ul>
190
+ {palette.visibleCommands.map((command, index) => (
191
+ <li key={command.id}>
192
+ <button
193
+ type="button"
194
+ onClick={() => {
195
+ palette.setDraft(command.title);
196
+ palette.runActive();
197
+ }}
198
+ style={{
199
+ fontWeight: index === palette.cursor ? "700" : "400",
200
+ }}
201
+ >
202
+ {command.title} · used {palette.usageCount(command.id)} times
203
+ </button>
204
+ </li>
205
+ ))}
206
+ </ul>
207
+
208
+ <p>History: {palette.history.items.join(" / ") || "empty"}</p>
209
+ </section>
210
+ );
211
+ }
@@ -0,0 +1,27 @@
1
+ import { replaceState, SigmaType, snapshot } from "preact-sigma";
2
+
3
+ const TodoList = new SigmaType<{
4
+ todos: string[];
5
+ }>("TodoList")
6
+ .defaultState({
7
+ todos: [],
8
+ })
9
+ .observe(function (change) {
10
+ console.log(`${change.oldState.todos.length} -> ${change.newState.todos.length}`);
11
+ })
12
+ .actions({
13
+ add(title: string) {
14
+ this.todos.push(title);
15
+ },
16
+ });
17
+
18
+ const todoList = new TodoList();
19
+
20
+ todoList.add("Write docs");
21
+
22
+ const saved = snapshot(todoList);
23
+
24
+ todoList.add("Ship release");
25
+ replaceState(todoList, saved);
26
+
27
+ console.log(snapshot(todoList).todos); // ["Write docs"]
@@ -0,0 +1,34 @@
1
+ import { listen, SigmaType } from "preact-sigma";
2
+
3
+ const ClickTracker = new SigmaType<{
4
+ clicks: number;
5
+ status: "idle" | "ready";
6
+ }>("ClickTracker")
7
+ .defaultState({
8
+ clicks: 0,
9
+ status: "idle",
10
+ })
11
+ .setup(function (target: EventTarget) {
12
+ this.act(function () {
13
+ this.status = "ready";
14
+ });
15
+
16
+ return [
17
+ listen(target, "click", () => {
18
+ this.act(function () {
19
+ this.clicks += 1;
20
+ });
21
+ }),
22
+ ];
23
+ });
24
+
25
+ const target = new EventTarget();
26
+ const tracker = new ClickTracker();
27
+ const cleanup = tracker.setup(target);
28
+
29
+ target.dispatchEvent(new Event("click"));
30
+ target.dispatchEvent(new Event("click"));
31
+
32
+ console.log(tracker.status, tracker.clicks); // ready 2
33
+
34
+ cleanup();
@@ -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();
@@ -0,0 +1,28 @@
1
+ import { effect, SigmaType } from "preact-sigma";
2
+
3
+ const Counter = new SigmaType<{
4
+ count: number;
5
+ }>("Counter")
6
+ .defaultState({
7
+ count: 0,
8
+ })
9
+ .computed({
10
+ doubled() {
11
+ return this.count * 2;
12
+ },
13
+ })
14
+ .actions({
15
+ increment() {
16
+ this.count += 1;
17
+ },
18
+ });
19
+
20
+ const counter = new Counter();
21
+
22
+ const stop = effect(() => {
23
+ console.log(counter.get("count").value, counter.get("doubled").value);
24
+ });
25
+
26
+ counter.increment();
27
+
28
+ stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "preact-sigma",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "keywords": [],
5
5
  "license": "MIT",
6
6
  "author": "Alec Larson",
@@ -10,7 +10,8 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
- "llms.txt"
13
+ "docs",
14
+ "examples"
14
15
  ],
15
16
  "type": "module",
16
17
  "exports": {