preact-sigma 2.2.2 → 2.2.3
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 +11 -291
- package/dist/index.d.mts +87 -22
- package/dist/index.mjs +34 -12
- package/docs/context.md +102 -0
- package/examples/async-commit.ts +42 -0
- package/examples/basic-counter.ts +23 -0
- package/examples/command-palette.tsx +211 -0
- package/examples/observe-and-restore.ts +27 -0
- package/examples/setup-act.ts +34 -0
- package/examples/signal-access.ts +28 -0
- package/package.json +3 -2
- package/llms.txt +0 -437
package/docs/context.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
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.
|
|
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
|
+
- State property: a top-level key from `TState`. Each one becomes a readonly reactive public property and gets its own signal.
|
|
25
|
+
- Computed: an argument-free derived getter declared with `.computed(...)`.
|
|
26
|
+
- Query: a reactive read that accepts arguments, declared with `.queries(...)` or built locally with `query(fn)`.
|
|
27
|
+
- Action: a method declared with `.actions(...)` that reads and writes through sigma's draft and commit semantics.
|
|
28
|
+
- Setup handler: a function declared with `.setup(...)` that owns side effects and cleanup resources explicitly.
|
|
29
|
+
- Event: a typed notification emitted through `this.emit(...)` and observed through `.on(...)`, `listen(...)`, or `useListener(...)`.
|
|
30
|
+
|
|
31
|
+
# Data Flow / Lifecycle
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
2. Add `defaultState(...)` for top-level public state and optional per-instance initializers.
|
|
35
|
+
3. Add `computed(...)`, `queries(...)`, and `actions(...)` for derived reads and writes.
|
|
36
|
+
4. Instantiate the configured type. Constructor input shallowly overrides `defaultState(...)`.
|
|
37
|
+
5. Read state, computeds, and queries reactively from the public instance.
|
|
38
|
+
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.
|
|
39
|
+
7. Run `setup(...)` explicitly when the instance should start owning side effects. `useSigma(...)` does this automatically for component-owned instances that define setup.
|
|
40
|
+
8. Dispose the cleanup returned from `setup(...)` when the owned resources should stop.
|
|
41
|
+
|
|
42
|
+
# Common Tasks -> Recommended APIs
|
|
43
|
+
|
|
44
|
+
- Define reusable model state: `new SigmaType<TState, TEvents>().defaultState(...)`
|
|
45
|
+
- Derive an argument-free value: `.computed(...)`
|
|
46
|
+
- Derive a reactive read with arguments: `.queries(...)`
|
|
47
|
+
- Keep a tracked helper local to one consumer module: `query(fn)`
|
|
48
|
+
- Mutate state and emit typed notifications: `.actions(...)`
|
|
49
|
+
- Publish before `await`, `emit(...)`, or another action boundary: `this.commit()`
|
|
50
|
+
- React to committed state changes: `.observe(...)`
|
|
51
|
+
- Own timers, listeners, subscriptions, or nested setup: `.setup(...)`
|
|
52
|
+
- Use a sigma state inside a component: `useSigma(...)`
|
|
53
|
+
- Subscribe to sigma or DOM events in a component: `useListener(...)`
|
|
54
|
+
- Subscribe outside components: `.on(...)` or `listen(...)`
|
|
55
|
+
- Read or restore committed top-level state: `snapshot(...)` and `replaceState(...)`
|
|
56
|
+
|
|
57
|
+
# Practical Guidelines
|
|
58
|
+
|
|
59
|
+
- Put explicit type arguments on `new SigmaType<TState, TEvents>()` and let later builder methods infer from the objects you pass.
|
|
60
|
+
- Keep frequently read values as separate top-level state properties. Each top-level key gets its own signal.
|
|
61
|
+
- Use `.computed(...)` for argument-free derived reads.
|
|
62
|
+
- Use `.queries(...)` for tracked reads with arguments.
|
|
63
|
+
- Keep one-off calculations local until they become reusable model behavior.
|
|
64
|
+
- Reach for `instance.get(key)` only when code specifically needs the underlying `ReadonlySignal`.
|
|
65
|
+
- 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.
|
|
66
|
+
- Use ordinary actions for routine writes. Reserve `snapshot(...)` and `replaceState(...)` for replay, reset, or undo-like flows on committed top-level state.
|
|
67
|
+
- Put owned side effects in `.setup(...)`.
|
|
68
|
+
- Use `this.act(function () { ... })` for setup-owned callbacks that need action semantics.
|
|
69
|
+
- Call Immer's `enablePatches()` before relying on `.observe(..., { patches: true })`.
|
|
70
|
+
|
|
71
|
+
# Invariants and Constraints
|
|
72
|
+
|
|
73
|
+
- Sigma only tracks top-level state properties. Each top-level key gets its own signal.
|
|
74
|
+
- Public state is readonly outside actions and `this.act(...)` inside setup.
|
|
75
|
+
- Duplicate names across state properties, computeds, queries, and actions are rejected at runtime. Reserved public names include `act`, `emit`, `get`, `on`, and `setup`.
|
|
76
|
+
- Query calls are reactive at the call site but do not memoize across invocations.
|
|
77
|
+
- Setup handlers return arrays of cleanup resources, and cleanup runs in reverse order.
|
|
78
|
+
- `replaceState(...)` works on committed top-level state and requires the exact state-key shape.
|
|
79
|
+
- Published draftable public state is deep-frozen by default. `setAutoFreeze(false)` disables that behavior globally.
|
|
80
|
+
|
|
81
|
+
# Error Model
|
|
82
|
+
|
|
83
|
+
- Crossing an action boundary with unpublished changes throws until `this.commit()` publishes them. Async actions also reject when they finish with unpublished changes.
|
|
84
|
+
- If another invocation crosses a boundary while unpublished changes still exist, sigma warns and discards those changes before continuing.
|
|
85
|
+
- Calling `setup(...)` on a sigma state without registered setup handlers throws.
|
|
86
|
+
- Cleanup rethrows an `AggregateError` when more than one cleanup resource fails.
|
|
87
|
+
- `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.
|
|
88
|
+
|
|
89
|
+
# Terminology
|
|
90
|
+
|
|
91
|
+
- Draft boundary: a point where sigma cannot keep reusing the current unpublished draft.
|
|
92
|
+
- Committed state: the published top-level public state visible outside the current action draft.
|
|
93
|
+
- Signal access: reading the underlying `ReadonlySignal` for a top-level state key or computed through `instance.get(key)`.
|
|
94
|
+
- Cleanup resource: a cleanup function, `AbortController`, object with `dispose()`, or object with `[Symbol.dispose]()`.
|
|
95
|
+
- 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.
|
|
96
|
+
|
|
97
|
+
# Non-Goals
|
|
98
|
+
|
|
99
|
+
- Replacing every plain-signal use case with a builder abstraction.
|
|
100
|
+
- Hiding lifecycle behind implicit setup or constructor side effects.
|
|
101
|
+
- Memoizing every query call or turning queries into a global cache.
|
|
102
|
+
- 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,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.
|
|
3
|
+
"version": "2.2.3",
|
|
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
|
-
"
|
|
13
|
+
"docs",
|
|
14
|
+
"examples"
|
|
14
15
|
],
|
|
15
16
|
"type": "module",
|
|
16
17
|
"exports": {
|