preact-sigma 4.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -21
- package/dist/index.d.mts +41 -325
- package/dist/index.mjs +52 -712
- package/dist/persist.d.mts +126 -0
- package/dist/persist.mjs +240 -0
- package/dist/sigma-CJibGQ6g.mjs +383 -0
- package/dist/sigma-DD7HfTvw.d.mts +162 -0
- package/docs/context.md +81 -55
- package/docs/migrations/v5-to-v6.md +273 -0
- package/docs/persist.md +67 -0
- package/examples/async-commit.ts +38 -31
- package/examples/basic-counter.ts +21 -16
- package/examples/command-palette.tsx +114 -104
- package/examples/observe-and-restore.ts +25 -17
- package/examples/persist-search-draft.ts +70 -0
- package/examples/setup-act.ts +17 -9
- package/examples/sigma-target.ts +17 -8
- package/package.json +5 -1
- package/examples/signal-access.ts +0 -28
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState } from "preact/hooks";
|
|
2
2
|
|
|
3
|
-
import { listen, query,
|
|
3
|
+
import { listen, query, Sigma, SigmaTarget, useListener, useSigma } from "preact-sigma";
|
|
4
4
|
|
|
5
5
|
type Command = {
|
|
6
6
|
id: string;
|
|
@@ -9,18 +9,18 @@ type Command = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
class UsageLedger {
|
|
12
|
-
counts = new Map<string, number>();
|
|
12
|
+
#counts = new Map<string, number>();
|
|
13
13
|
|
|
14
14
|
get(id: string) {
|
|
15
|
-
return this
|
|
15
|
+
return this.#counts.get(id) ?? 0;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
increment(id: string) {
|
|
19
|
-
this
|
|
19
|
+
this.#counts.set(id, this.get(id) + 1);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
function matchesText(command: Command, draft: string) {
|
|
24
24
|
const needle = draft.trim().toLowerCase();
|
|
25
25
|
if (!needle) {
|
|
26
26
|
return true;
|
|
@@ -30,101 +30,115 @@ const matchesText = query((command: Command, draft: string) => {
|
|
|
30
30
|
command.title.toLowerCase().includes(needle) ||
|
|
31
31
|
command.keywords.some((keyword) => keyword.toLowerCase().includes(needle))
|
|
32
32
|
);
|
|
33
|
-
}
|
|
33
|
+
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
type SearchHistoryState = {
|
|
36
36
|
items: string[];
|
|
37
|
-
}
|
|
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
|
-
});
|
|
37
|
+
};
|
|
51
38
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
cursor: number;
|
|
58
|
-
draft: string;
|
|
59
|
-
history: SearchHistory;
|
|
60
|
-
usage: UsageLedger;
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
ran: Command;
|
|
39
|
+
class SearchHistory extends Sigma<SearchHistoryState> {
|
|
40
|
+
constructor() {
|
|
41
|
+
super({
|
|
42
|
+
items: [],
|
|
43
|
+
});
|
|
64
44
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
],
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
45
|
+
|
|
46
|
+
remember(query: string) {
|
|
47
|
+
const value = query.trim();
|
|
48
|
+
if (!value) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.items = [value, ...this.items.filter((item) => item !== value)].slice(0, 5);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface SearchHistory extends SearchHistoryState {}
|
|
57
|
+
|
|
58
|
+
type CommandPaletteState = {
|
|
59
|
+
commands: Command[];
|
|
60
|
+
cursor: number;
|
|
61
|
+
draft: string;
|
|
62
|
+
history: SearchHistory;
|
|
63
|
+
usage: UsageLedger;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type CommandPaletteEvents = {
|
|
67
|
+
ran: Command;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
class CommandPalette extends SigmaTarget<CommandPaletteEvents, CommandPaletteState> {
|
|
71
|
+
constructor() {
|
|
72
|
+
super({
|
|
73
|
+
commands: [
|
|
74
|
+
{ id: "inbox", title: "Open inbox", keywords: ["mail", "messages", "triage"] },
|
|
75
|
+
{ id: "capture", title: "Capture note", keywords: ["write", "quick", "idea"] },
|
|
76
|
+
{ id: "focus", title: "Start focus timer", keywords: ["pomodoro", "deep work"] },
|
|
77
|
+
{ id: "theme", title: "Toggle theme", keywords: ["appearance", "dark", "light"] },
|
|
78
|
+
],
|
|
79
|
+
cursor: 0,
|
|
80
|
+
draft: "",
|
|
81
|
+
history: new SearchHistory(),
|
|
82
|
+
usage: new UsageLedger(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get visibleCommands() {
|
|
87
|
+
return this.commands.filter((command) => matchesText(command, this.draft));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get activeCommand() {
|
|
91
|
+
return this.visibleCommands[this.cursor] ?? null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get canRun() {
|
|
95
|
+
return this.activeCommand !== null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@query
|
|
99
|
+
usageCount(id: string) {
|
|
100
|
+
return this.usage.get(id);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setDraft(draft: string) {
|
|
104
|
+
this.draft = draft;
|
|
105
|
+
this.cursor = 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
move(step: number) {
|
|
109
|
+
if (this.visibleCommands.length === 0) {
|
|
124
110
|
this.cursor = 0;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lastIndex = this.visibleCommands.length - 1;
|
|
115
|
+
this.cursor = Math.max(0, Math.min(lastIndex, this.cursor + step));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
seedDraftFromHistory() {
|
|
119
|
+
const latest = this.history.items[0];
|
|
120
|
+
if (latest) {
|
|
121
|
+
this.setDraft(latest);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
runActive() {
|
|
126
|
+
const command = this.activeCommand;
|
|
127
|
+
if (!command) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const search = this.draft || command.title;
|
|
132
|
+
this.usage.increment(command.id);
|
|
133
|
+
this.draft = "";
|
|
134
|
+
this.cursor = 0;
|
|
135
|
+
this.commit();
|
|
136
|
+
|
|
137
|
+
this.history.remember(search);
|
|
138
|
+
this.emit("ran", command);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
onSetup() {
|
|
128
142
|
return [
|
|
129
143
|
listen(window, "keydown", (event) => {
|
|
130
144
|
if ((event.metaKey || event.ctrlKey) && event.key === "k") {
|
|
@@ -144,9 +158,10 @@ const CommandPalette = new SigmaType<
|
|
|
144
158
|
}
|
|
145
159
|
}),
|
|
146
160
|
];
|
|
147
|
-
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
148
163
|
|
|
149
|
-
interface CommandPalette extends
|
|
164
|
+
interface CommandPalette extends CommandPaletteState {}
|
|
150
165
|
|
|
151
166
|
export function CommandPaletteExample() {
|
|
152
167
|
const palette = useSigma(() => new CommandPalette());
|
|
@@ -158,16 +173,11 @@ export function CommandPaletteExample() {
|
|
|
158
173
|
|
|
159
174
|
return (
|
|
160
175
|
<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
176
|
<label>
|
|
167
177
|
Search
|
|
168
178
|
<input
|
|
169
179
|
value={palette.draft}
|
|
170
|
-
onInput={(event) => palette.setDraft(
|
|
180
|
+
onInput={(event) => palette.setDraft(event.currentTarget.value)}
|
|
171
181
|
placeholder="Try: note, timer, inbox"
|
|
172
182
|
/>
|
|
173
183
|
</label>
|
|
@@ -179,7 +189,7 @@ export function CommandPaletteExample() {
|
|
|
179
189
|
<button type="button" onClick={() => palette.move(1)}>
|
|
180
190
|
Down
|
|
181
191
|
</button>
|
|
182
|
-
<button type="button" onClick={() => palette.runActive()} disabled={!palette.canRun
|
|
192
|
+
<button type="button" onClick={() => palette.runActive()} disabled={!palette.canRun}>
|
|
183
193
|
Run
|
|
184
194
|
</button>
|
|
185
195
|
</div>
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sigma, Sigma } from "preact-sigma";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type TodoListState = {
|
|
4
4
|
todos: string[];
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
class TodoList extends Sigma<TodoListState> {
|
|
8
|
+
constructor() {
|
|
9
|
+
super({
|
|
10
|
+
todos: [],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
add(title: string) {
|
|
15
|
+
this.todos.push(title);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface TodoList extends TodoListState {}
|
|
17
20
|
|
|
18
21
|
const todoList = new TodoList();
|
|
22
|
+
const stop = sigma.subscribe(todoList, (nextState, baseState) => {
|
|
23
|
+
console.log(`${baseState.todos.length} -> ${nextState.todos.length}`);
|
|
24
|
+
});
|
|
19
25
|
|
|
20
26
|
todoList.add("Write docs");
|
|
21
27
|
|
|
22
|
-
const saved =
|
|
28
|
+
const saved = sigma.captureState(todoList);
|
|
23
29
|
|
|
24
30
|
todoList.add("Ship release");
|
|
25
|
-
replaceState(todoList, saved);
|
|
31
|
+
sigma.replaceState(todoList, saved);
|
|
32
|
+
|
|
33
|
+
console.log(sigma.captureState(todoList).todos); // ["Write docs"]
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
stop();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Sigma } from "preact-sigma";
|
|
2
|
+
import { hydrateSync, type PersistRecord, type SyncPersistStore } from "preact-sigma/persist";
|
|
3
|
+
|
|
4
|
+
type SearchState = {
|
|
5
|
+
draft: string;
|
|
6
|
+
page: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
class Search extends Sigma<SearchState> {
|
|
10
|
+
constructor(initialState: Partial<SearchState> = {}) {
|
|
11
|
+
super({
|
|
12
|
+
draft: "",
|
|
13
|
+
page: 1,
|
|
14
|
+
...initialState,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
nextPage() {
|
|
19
|
+
this.page += 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setDraft(draft: string) {
|
|
23
|
+
this.draft = draft;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Search extends SearchState {}
|
|
28
|
+
|
|
29
|
+
const records = new Map<string, PersistRecord<Pick<SearchState, "draft">>>([
|
|
30
|
+
[
|
|
31
|
+
"search",
|
|
32
|
+
{
|
|
33
|
+
savedAt: 100,
|
|
34
|
+
value: { draft: "restored" },
|
|
35
|
+
version: 1,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const store: SyncPersistStore<Pick<SearchState, "draft">> = {
|
|
41
|
+
get(key) {
|
|
42
|
+
return records.get(key);
|
|
43
|
+
},
|
|
44
|
+
set(key, record) {
|
|
45
|
+
return records.set(key, record);
|
|
46
|
+
},
|
|
47
|
+
delete(key) {
|
|
48
|
+
return records.delete(key);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const search = new Search({ page: 3 });
|
|
53
|
+
const persistence = hydrateSync(search, {
|
|
54
|
+
key: "search",
|
|
55
|
+
pick: ["draft"],
|
|
56
|
+
store,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log(persistence.restored); // { status: "restored", savedAt: 100, storedVersion: 1 }
|
|
60
|
+
console.log(search.draft); // "restored"
|
|
61
|
+
console.log(search.page); // 3
|
|
62
|
+
|
|
63
|
+
search.setDraft("signals");
|
|
64
|
+
search.nextPage();
|
|
65
|
+
|
|
66
|
+
await persistence.flush();
|
|
67
|
+
|
|
68
|
+
console.log(records.get("search")?.value); // { draft: "signals" }
|
|
69
|
+
|
|
70
|
+
await persistence.stop();
|
package/examples/setup-act.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { listen,
|
|
1
|
+
import { listen, Sigma } from "preact-sigma";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type ClickTrackerState = {
|
|
4
4
|
clicks: number;
|
|
5
5
|
status: "idle" | "ready";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
class ClickTracker extends Sigma<ClickTrackerState> {
|
|
9
|
+
constructor() {
|
|
10
|
+
super({
|
|
11
|
+
clicks: 0,
|
|
12
|
+
status: "idle",
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
onSetup(target: EventTarget) {
|
|
12
17
|
this.act(function () {
|
|
13
18
|
this.status = "ready";
|
|
14
19
|
});
|
|
@@ -20,7 +25,10 @@ const ClickTracker = new SigmaType<{
|
|
|
20
25
|
});
|
|
21
26
|
}),
|
|
22
27
|
];
|
|
23
|
-
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ClickTracker extends ClickTrackerState {}
|
|
24
32
|
|
|
25
33
|
const target = new EventTarget();
|
|
26
34
|
const tracker = new ClickTracker();
|
package/examples/sigma-target.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { listen, SigmaTarget } from "preact-sigma";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type NotificationEvents = {
|
|
4
4
|
saved: {
|
|
5
5
|
id: string;
|
|
6
6
|
title: string;
|
|
7
7
|
};
|
|
8
8
|
reset: void;
|
|
9
|
-
}
|
|
9
|
+
};
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
class Notifications extends SigmaTarget<NotificationEvents> {
|
|
12
|
+
saved(id: string, title: string) {
|
|
13
|
+
this.emit("saved", { id, title });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
reset() {
|
|
17
|
+
this.emit("reset");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const notifications = new Notifications();
|
|
22
|
+
|
|
23
|
+
const stopSaved = listen(notifications, "saved", ({ id, title }) => {
|
|
12
24
|
console.log(`Saved ${id}: ${title}`);
|
|
13
25
|
});
|
|
14
26
|
|
|
@@ -16,11 +28,8 @@ const stopReset = listen(notifications, "reset", () => {
|
|
|
16
28
|
console.log("Reset");
|
|
17
29
|
});
|
|
18
30
|
|
|
19
|
-
notifications.
|
|
20
|
-
|
|
21
|
-
title: "Draft post",
|
|
22
|
-
});
|
|
23
|
-
notifications.emit("reset");
|
|
31
|
+
notifications.saved("note-1", "Draft post");
|
|
32
|
+
notifications.reset();
|
|
24
33
|
|
|
25
34
|
stopSaved();
|
|
26
35
|
stopReset();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "preact-sigma",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alec Larson",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
".": {
|
|
19
19
|
"types": "./dist/index.d.mts",
|
|
20
20
|
"import": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./persist": {
|
|
23
|
+
"types": "./dist/persist.d.mts",
|
|
24
|
+
"import": "./dist/persist.mjs"
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"devDependencies": {
|
|
@@ -1,28 +0,0 @@
|
|
|
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();
|