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.
@@ -1,6 +1,6 @@
1
1
  import { useState } from "preact/hooks";
2
2
 
3
- import { listen, query, SigmaType, useListener, useSigma } from "preact-sigma";
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.counts.get(id) ?? 0;
15
+ return this.#counts.get(id) ?? 0;
16
16
  }
17
17
 
18
18
  increment(id: string) {
19
- this.counts.set(id, this.get(id) + 1);
19
+ this.#counts.set(id, this.get(id) + 1);
20
20
  }
21
21
  }
22
22
 
23
- const matchesText = query((command: Command, draft: string) => {
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
- const SearchHistory = new SigmaType<{
35
+ type SearchHistoryState = {
36
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
- });
37
+ };
51
38
 
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;
39
+ class SearchHistory extends Sigma<SearchHistoryState> {
40
+ constructor() {
41
+ super({
42
+ items: [],
43
+ });
64
44
  }
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 = "";
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
- .setup(function () {
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 InstanceType<typeof CommandPalette> {}
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((event.currentTarget as HTMLInputElement).value)}
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 { replaceState, SigmaType, snapshot } from "preact-sigma";
1
+ import { sigma, Sigma } from "preact-sigma";
2
2
 
3
- const TodoList = new SigmaType<{
3
+ type TodoListState = {
4
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
- });
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 = snapshot(todoList);
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
- console.log(snapshot(todoList).todos); // ["Write docs"]
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();
@@ -1,14 +1,19 @@
1
- import { listen, SigmaType } from "preact-sigma";
1
+ import { listen, Sigma } from "preact-sigma";
2
2
 
3
- const ClickTracker = new SigmaType<{
3
+ type ClickTrackerState = {
4
4
  clicks: number;
5
5
  status: "idle" | "ready";
6
- }>("ClickTracker")
7
- .defaultState({
8
- clicks: 0,
9
- status: "idle",
10
- })
11
- .setup(function (target: EventTarget) {
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();
@@ -1,14 +1,26 @@
1
1
  import { listen, SigmaTarget } from "preact-sigma";
2
2
 
3
- const notifications = new SigmaTarget<{
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
- const stopSaved = notifications.on("saved", ({ id, title }) => {
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.emit("saved", {
20
- id: "note-1",
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": "4.0.0",
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();