preact-sigma 2.2.1 → 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 +10 -159
- 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
|
@@ -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": {
|