preact-sigma 0.0.0 → 1.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 +509 -0
- package/dist/index.d.mts +215 -0
- package/dist/index.mjs +301 -0
- package/llms.txt +302 -0
- package/package.json +43 -9
- package/readme.md +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# `preact-sigma`
|
|
2
|
+
|
|
3
|
+
Managed UI state for Preact Signals, with Immer-powered updates and a small public API.
|
|
4
|
+
|
|
5
|
+
For naming and API design conventions, see [best-practices.md](./best-practices.md).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm add preact-sigma
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install preact-sigma
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Big Picture
|
|
18
|
+
|
|
19
|
+
Define state once, expose a few methods, and return reactive immutable data from the public instance.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { computed, defineManagedState, type StateHandle } from "preact-sigma";
|
|
23
|
+
|
|
24
|
+
type CounterEvents = {
|
|
25
|
+
thresholdReached: [{ count: number }];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type CounterState = number;
|
|
29
|
+
|
|
30
|
+
const Counter = defineManagedState(
|
|
31
|
+
(counter: StateHandle<CounterState, CounterEvents>, step: number) => {
|
|
32
|
+
const doubled = computed(() => counter.get() * 2);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
count: counter,
|
|
36
|
+
doubled,
|
|
37
|
+
increment() {
|
|
38
|
+
counter.set((value) => value + step);
|
|
39
|
+
|
|
40
|
+
if (counter.get() >= 10) {
|
|
41
|
+
counter.emit("thresholdReached", { count: counter.get() });
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
reset() {
|
|
45
|
+
counter.set(0);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
0,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const counter = new Counter(2);
|
|
53
|
+
const stopThreshold = counter.on("thresholdReached", (event) => {
|
|
54
|
+
console.log(event.count);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
counter.count;
|
|
58
|
+
counter.doubled;
|
|
59
|
+
counter.increment();
|
|
60
|
+
stopThreshold();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `count: counter` exposes the base state as a reactive immutable property.
|
|
64
|
+
- `doubled` is a memoized reactive value exposed through `computed()`.
|
|
65
|
+
- `increment()` is action-wrapped automatically, so the state update is batched and untracked.
|
|
66
|
+
- `counter.on(...)` returns `stopThreshold`, which unsubscribes the event listener.
|
|
67
|
+
|
|
68
|
+
## Define Reusable State
|
|
69
|
+
|
|
70
|
+
Use `defineManagedState()` when you want a reusable managed-state class.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
74
|
+
|
|
75
|
+
type CounterState = number;
|
|
76
|
+
|
|
77
|
+
const Counter = defineManagedState(
|
|
78
|
+
(counter: StateHandle<CounterState>) => ({
|
|
79
|
+
count: counter,
|
|
80
|
+
increment() {
|
|
81
|
+
counter.set((value) => value + 1);
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
0,
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Expose Base State
|
|
89
|
+
|
|
90
|
+
Return the constructor handle when you want the base state to appear as a reactive immutable property.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
94
|
+
|
|
95
|
+
type CounterState = number;
|
|
96
|
+
|
|
97
|
+
const Counter = defineManagedState(
|
|
98
|
+
(count: StateHandle<CounterState>) => ({
|
|
99
|
+
count,
|
|
100
|
+
}),
|
|
101
|
+
0,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
new Counter().count;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Memoize A Reactive Derivation
|
|
108
|
+
|
|
109
|
+
Use `computed()` when you want a memoized reactive value on the public instance.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { computed, defineManagedState, type StateHandle } from "preact-sigma";
|
|
113
|
+
|
|
114
|
+
type CounterState = number;
|
|
115
|
+
|
|
116
|
+
const Counter = defineManagedState(
|
|
117
|
+
(counter: StateHandle<CounterState>) => ({
|
|
118
|
+
doubled: computed(() => counter.get() * 2),
|
|
119
|
+
}),
|
|
120
|
+
0,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
new Counter().doubled;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Create A Tracked Query Method
|
|
127
|
+
|
|
128
|
+
Use `query()` when you want a public method whose reads stay tracked.
|
|
129
|
+
Query functions read from closed-over handles or signals and do not use
|
|
130
|
+
instance `this`.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { defineManagedState, query, type StateHandle } from "preact-sigma";
|
|
134
|
+
|
|
135
|
+
type CounterState = number;
|
|
136
|
+
|
|
137
|
+
const Counter = defineManagedState(
|
|
138
|
+
(counter: StateHandle<CounterState>) => ({
|
|
139
|
+
isPositive: query(() => counter.get() > 0),
|
|
140
|
+
}),
|
|
141
|
+
0,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
new Counter().isPositive();
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Read Base State Without Tracking
|
|
148
|
+
|
|
149
|
+
Use `handle.peek()` when you need the current base-state snapshot without creating a reactive dependency.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
153
|
+
|
|
154
|
+
type CounterState = number;
|
|
155
|
+
|
|
156
|
+
const Counter = defineManagedState(
|
|
157
|
+
(counter: StateHandle<CounterState>) => ({
|
|
158
|
+
logNow() {
|
|
159
|
+
console.log(counter.peek());
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
0,
|
|
163
|
+
);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Use Top-Level Lenses
|
|
167
|
+
|
|
168
|
+
When the base state is object-shaped, the constructor handle exposes a shallow lens for each top-level property, and you can return that lens directly.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { computed, defineManagedState, type StateHandle } from "preact-sigma";
|
|
172
|
+
|
|
173
|
+
type SearchState = {
|
|
174
|
+
query: string;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const Search = defineManagedState(
|
|
178
|
+
(search: StateHandle<SearchState>) => ({
|
|
179
|
+
query: search.query,
|
|
180
|
+
trimmedQuery: computed(() => search.query.get().trim()),
|
|
181
|
+
setQuery(query: string) {
|
|
182
|
+
search.query.set(query);
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
{ query: "" },
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
new Search().query;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Spread A Handle To Expose Top-Level Properties
|
|
192
|
+
|
|
193
|
+
When the base state is object-shaped, spread the handle into the returned object to expose its current top-level lenses at once.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
197
|
+
|
|
198
|
+
type SearchState = {
|
|
199
|
+
page: number;
|
|
200
|
+
query: string;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const Search = defineManagedState(
|
|
204
|
+
(search: StateHandle<SearchState>) => ({
|
|
205
|
+
...search,
|
|
206
|
+
nextPage() {
|
|
207
|
+
search.page.set((page) => page + 1);
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
{ page: 1, query: "" },
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const search = new Search();
|
|
214
|
+
|
|
215
|
+
search.query;
|
|
216
|
+
search.page;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Compose Managed States
|
|
220
|
+
|
|
221
|
+
Return another managed-state instance when you want to expose it unchanged as a property.
|
|
222
|
+
Composed managed states are available through direct property access and whole-state snapshots.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
226
|
+
|
|
227
|
+
type CounterState = number;
|
|
228
|
+
|
|
229
|
+
const Counter = defineManagedState(
|
|
230
|
+
(count: StateHandle<CounterState>) => ({
|
|
231
|
+
count,
|
|
232
|
+
increment() {
|
|
233
|
+
count.set((value) => value + 1);
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
0,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
type DashboardState = {
|
|
240
|
+
ready: boolean;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const Dashboard = defineManagedState(
|
|
244
|
+
(dashboard: StateHandle<DashboardState>) => ({
|
|
245
|
+
dashboard,
|
|
246
|
+
counter: new Counter(),
|
|
247
|
+
}),
|
|
248
|
+
{ ready: false },
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
new Dashboard().counter.increment();
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Own Resources And Dispose The Instance
|
|
255
|
+
|
|
256
|
+
Use `handle.own()` to register cleanup functions or disposables, and call `.dispose()` when the managed state should release them.
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
260
|
+
|
|
261
|
+
type PollerState = {
|
|
262
|
+
ticks: number;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const Poller = defineManagedState(
|
|
266
|
+
(poller: StateHandle<PollerState>) => ({
|
|
267
|
+
ticks: poller.ticks,
|
|
268
|
+
start() {
|
|
269
|
+
const interval = window.setInterval(() => {
|
|
270
|
+
poller.ticks.set((ticks) => ticks + 1);
|
|
271
|
+
}, 1000);
|
|
272
|
+
|
|
273
|
+
poller.own([() => window.clearInterval(interval)]);
|
|
274
|
+
},
|
|
275
|
+
}),
|
|
276
|
+
{ ticks: 0 },
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const poller = new Poller();
|
|
280
|
+
|
|
281
|
+
poller.start();
|
|
282
|
+
poller.dispose();
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Update State
|
|
286
|
+
|
|
287
|
+
Pass an Immer producer to `.set()` when your base state is object-shaped.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
291
|
+
|
|
292
|
+
type SearchState = {
|
|
293
|
+
query: string;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const Search = defineManagedState(
|
|
297
|
+
(search: StateHandle<SearchState>) => ({
|
|
298
|
+
setQuery(query: string) {
|
|
299
|
+
search.set((draft) => {
|
|
300
|
+
draft.query = query;
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
{ query: "" },
|
|
305
|
+
);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Emit Events
|
|
309
|
+
|
|
310
|
+
Use `.emit()` to publish a custom event with zero or one argument.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
314
|
+
|
|
315
|
+
type TodoEvents = {
|
|
316
|
+
saved: [];
|
|
317
|
+
selected: [{ id: string }];
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
type TodoState = {};
|
|
321
|
+
|
|
322
|
+
const Todo = defineManagedState(
|
|
323
|
+
(todo: StateHandle<TodoState, TodoEvents>) => ({
|
|
324
|
+
save() {
|
|
325
|
+
todo.emit("saved");
|
|
326
|
+
},
|
|
327
|
+
select(id: string) {
|
|
328
|
+
todo.emit("selected", { id });
|
|
329
|
+
},
|
|
330
|
+
}),
|
|
331
|
+
{},
|
|
332
|
+
);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Listen For Events
|
|
336
|
+
|
|
337
|
+
Use `.on()` to subscribe to custom events from a managed state instance.
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
const todo = new Todo();
|
|
341
|
+
|
|
342
|
+
const stopSaved = todo.on("saved", () => {
|
|
343
|
+
console.log("saved");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const stopSelected = todo.on("selected", (event) => {
|
|
347
|
+
console.log(event.id);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
stopSaved();
|
|
351
|
+
stopSelected();
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Read Signals From A Managed State
|
|
355
|
+
|
|
356
|
+
Use `.get(key)` for one exposed signal-backed property or `.get()` for the whole public state signal.
|
|
357
|
+
Keyed reads do not target composed managed-state properties.
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
const counter = new Counter();
|
|
361
|
+
|
|
362
|
+
const countSignal = counter.get("count");
|
|
363
|
+
const counterSignal = counter.get();
|
|
364
|
+
|
|
365
|
+
countSignal.value;
|
|
366
|
+
counterSignal.value.count;
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Peek At Public State
|
|
370
|
+
|
|
371
|
+
Use `.peek(key)` for one exposed signal-backed property or `.peek()` for the whole public snapshot.
|
|
372
|
+
Keyed peeks do not target composed managed-state properties.
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
const counter = new Counter();
|
|
376
|
+
|
|
377
|
+
counter.peek("count");
|
|
378
|
+
counter.peek();
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Subscribe To Public State
|
|
382
|
+
|
|
383
|
+
Use `.subscribe(key, listener)` for one exposed signal-backed property or `.subscribe(listener)` for the whole public state.
|
|
384
|
+
Listeners receive the current value immediately and then future updates.
|
|
385
|
+
Keyed subscriptions do not target composed managed-state properties.
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
const counter = new Counter();
|
|
389
|
+
|
|
390
|
+
const stopCount = counter.subscribe("count", (count) => {
|
|
391
|
+
console.log(count);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const stopState = counter.subscribe((value) => {
|
|
395
|
+
console.log(value.count);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
stopCount();
|
|
399
|
+
stopState();
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Use It Inside A Component
|
|
403
|
+
|
|
404
|
+
Use `useManagedState()` when you want the same pattern directly inside a component.
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import { useManagedState, type StateHandle } from "preact-sigma";
|
|
408
|
+
|
|
409
|
+
type SearchState = {
|
|
410
|
+
query: string;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
function SearchBox() {
|
|
414
|
+
const search = useManagedState(
|
|
415
|
+
(search: StateHandle<SearchState>) => ({
|
|
416
|
+
query: search.query,
|
|
417
|
+
setQuery(query: string) {
|
|
418
|
+
search.query.set(query);
|
|
419
|
+
},
|
|
420
|
+
}),
|
|
421
|
+
() => ({ query: "" }),
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<input value={search.query} onInput={(event) => search.setQuery(event.currentTarget.value)} />
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Subscribe In `useEffect`
|
|
431
|
+
|
|
432
|
+
Use `useSubscribe()` with any subscribable source, including managed state and Preact signals.
|
|
433
|
+
The listener receives the current value immediately and then future updates.
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import { useSubscribe } from "preact-sigma";
|
|
437
|
+
|
|
438
|
+
useSubscribe(counter, (value) => {
|
|
439
|
+
console.log(value.count);
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Listen To DOM Or Managed-State Events In `useEffect`
|
|
444
|
+
|
|
445
|
+
Use `useEventTarget()` for either DOM events or managed-state events.
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
import { useEventTarget } from "preact-sigma";
|
|
449
|
+
|
|
450
|
+
useEventTarget(window, "resize", () => {
|
|
451
|
+
console.log(window.innerWidth);
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
useEventTarget(counter, "thresholdReached", (event) => {
|
|
457
|
+
console.log(event.count);
|
|
458
|
+
});
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Reach For Signals Helpers
|
|
462
|
+
|
|
463
|
+
`batch` and `untracked` are re-exported from `@preact/signals`.
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
import { batch, untracked } from "preact-sigma";
|
|
467
|
+
|
|
468
|
+
batch(() => {
|
|
469
|
+
counter.increment();
|
|
470
|
+
counter.reset();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
untracked(() => {
|
|
474
|
+
console.log(counter.count);
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## Small Feature Model
|
|
479
|
+
|
|
480
|
+
This pattern works well when a component or UI feature needs a small state model with a few public methods and derived values.
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
import { defineManagedState, type StateHandle } from "preact-sigma";
|
|
484
|
+
|
|
485
|
+
type DialogState = boolean;
|
|
486
|
+
|
|
487
|
+
const Dialog = defineManagedState(
|
|
488
|
+
(dialog: StateHandle<DialogState>) => ({
|
|
489
|
+
open: dialog,
|
|
490
|
+
show() {
|
|
491
|
+
dialog.set(true);
|
|
492
|
+
},
|
|
493
|
+
hide() {
|
|
494
|
+
dialog.set(false);
|
|
495
|
+
},
|
|
496
|
+
}),
|
|
497
|
+
false,
|
|
498
|
+
);
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Keep using plain `useState()` when the state is trivial.
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
const [open, setOpen] = useState(false);
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## License
|
|
508
|
+
|
|
509
|
+
MIT. See [LICENSE-MIT](./LICENSE-MIT).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { ReadonlySignal, Signal, batch, computed, untracked } from "@preact/signals";
|
|
2
|
+
import { Immutable, Producer } from "immer";
|
|
3
|
+
|
|
4
|
+
//#region src/internal.d.ts
|
|
5
|
+
type Cleanup = () => void;
|
|
6
|
+
type Disposable = {
|
|
7
|
+
[Symbol.dispose](): void;
|
|
8
|
+
};
|
|
9
|
+
type OwnedResource = Cleanup | Disposable;
|
|
10
|
+
type OwnedResources = OwnedResource | readonly OwnedResource[];
|
|
11
|
+
declare class AnyStateHandle<TState = any, TEvents extends EventsDefinition = any> {
|
|
12
|
+
private readonly state;
|
|
13
|
+
/**
|
|
14
|
+
* Emit a custom event with zero or one argument.
|
|
15
|
+
*/
|
|
16
|
+
readonly emit: [TEvents] extends [{}] ? <TEvent extends string & keyof TEvents>(name: TEvent, ...args: TEvents[TEvent]) => void : never;
|
|
17
|
+
/**
|
|
18
|
+
* Attach cleanup functions or disposables to the managed state instance.
|
|
19
|
+
*/
|
|
20
|
+
readonly own: (resources: OwnedResources) => void;
|
|
21
|
+
constructor(state: Signal<Immutable<TState>>,
|
|
22
|
+
/**
|
|
23
|
+
* Emit a custom event with zero or one argument.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
emit: [TEvents] extends [{}] ? <TEvent extends string & keyof TEvents>(name: TEvent, ...args: TEvents[TEvent]) => void : never,
|
|
27
|
+
/**
|
|
28
|
+
* Attach cleanup functions or disposables to the managed state instance.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
own: (resources: OwnedResources) => void);
|
|
32
|
+
/** Read the current immutable base state. This read is tracked. */
|
|
33
|
+
get(): Immutable<TState>;
|
|
34
|
+
/** Read the current immutable base state snapshot without tracking. */
|
|
35
|
+
peek(): Immutable<TState>;
|
|
36
|
+
/** Replace the base state, or update it with an Immer producer. */
|
|
37
|
+
set(value: TState | Producer<TState>): void;
|
|
38
|
+
}
|
|
39
|
+
/** Check whether a value is a managed-state instance. */
|
|
40
|
+
declare function isManagedState(value: unknown): value is AnyManagedState;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/framework.d.ts
|
|
43
|
+
type EventsDefinition = Record<string, [any?]>;
|
|
44
|
+
type FilterProperties$1<T extends object, U> = {} & { [P in { [K in keyof T]: [T[K]] extends [never] ? never : T[K] extends U ? K : never }[keyof T]]: T[P] };
|
|
45
|
+
type InferActions$1<TProps extends object> = {} & FilterProperties$1<TProps, (...args: any[]) => any>;
|
|
46
|
+
type InferManagedStates$1<TProps extends object> = {} & FilterProperties$1<TProps, AnyManagedState>;
|
|
47
|
+
type InferPublicProps$1<TProps extends object> = InferActions$1<TProps> & InferManagedStates$1<TProps>;
|
|
48
|
+
type OmitManagedStates<TState> = TState extends object ? Omit<TState, keyof InferManagedStates$1<TState>> : TState;
|
|
49
|
+
type InferState$1<TProps extends object> = {} & Immutable<{ [K in keyof FilterProperties$1<TProps, ReadonlySignal | StateHandle<any, any> | Lens>]: TProps[K] extends ReadonlySignal<infer T> | StateHandle<infer T> | Lens<infer T> ? T : never }> & Readonly<InferManagedStates$1<TProps>>;
|
|
50
|
+
type AnyManagedState<TState = any, TEvents extends EventsDefinition = any> = {
|
|
51
|
+
/** Get the underlying signal for an exposed signal-backed public property. */get<K extends keyof OmitManagedStates<TState>>(key: K): ReadonlySignal<OmitManagedStates<TState>[K]>;
|
|
52
|
+
get(): ReadonlySignal<TState>; /** Read the current immutable public state snapshot without tracking. */
|
|
53
|
+
peek<K extends keyof OmitManagedStates<TState>>(key: K): OmitManagedStates<TState>[K];
|
|
54
|
+
peek(): TState;
|
|
55
|
+
/**
|
|
56
|
+
* Subscribe to the current and future immutable values of one signal-backed
|
|
57
|
+
* public property. Returns a function to unsubscribe.
|
|
58
|
+
*/
|
|
59
|
+
subscribe<K extends keyof OmitManagedStates<TState>>(key: K, listener: (value: OmitManagedStates<TState>[K]) => void): () => void; /** Subscribe to the current and future immutable public state snapshots. */
|
|
60
|
+
subscribe(listener: (value: TState) => void): () => void;
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to a custom event emitted by this managed state.
|
|
63
|
+
*
|
|
64
|
+
* Your listener receives the emitted argument directly, or no argument at all.
|
|
65
|
+
*/
|
|
66
|
+
on<TEvent extends string & keyof TEvents>(name: TEvent, listener: (...args: TEvents[TEvent]) => void): () => void; /** Dispose this managed state instance and its owned resources. */
|
|
67
|
+
dispose(): void;
|
|
68
|
+
[Symbol.dispose](): void;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Public instance shape produced by `defineManagedState()` and `useManagedState()`.
|
|
72
|
+
*
|
|
73
|
+
* Returned signals and top-level lenses are exposed as tracked getter
|
|
74
|
+
* properties. Returning the `StateHandle` itself exposes the base state
|
|
75
|
+
* directly as a reactive immutable property.
|
|
76
|
+
*/
|
|
77
|
+
type ManagedState<TState, TEvents extends EventsDefinition = EventsDefinition, TProps extends object = Record<string, unknown>> = AnyManagedState<TState, TEvents> & Immutable<TState> & TProps;
|
|
78
|
+
/**
|
|
79
|
+
* Mark a constructor-returned method as a tracked query.
|
|
80
|
+
*
|
|
81
|
+
* Query methods wrap their body in `computed()`, so reads inside the method
|
|
82
|
+
* participate in signal tracking even after the method is exposed publicly.
|
|
83
|
+
* Query functions read from closed-over handles or signals and do not use an
|
|
84
|
+
* instance receiver. Tagged query methods also skip the default `action()`
|
|
85
|
+
* wrapping step.
|
|
86
|
+
*/
|
|
87
|
+
declare function query<TFunction extends (this: void, ...args: any[]) => any>(fn: TFunction): TFunction;
|
|
88
|
+
/**
|
|
89
|
+
* Constructor-local access to one top-level property of an object-shaped base
|
|
90
|
+
* state.
|
|
91
|
+
*
|
|
92
|
+
* Lenses only exist on `StateHandle`, and `get()` reads are tracked in the
|
|
93
|
+
* same way as `StateHandle.get()`. `set()` accepts either a replacement value
|
|
94
|
+
* or an Immer producer for that property value.
|
|
95
|
+
*/
|
|
96
|
+
type Lens<TState = any> = {
|
|
97
|
+
/** Read the current immutable property value. This read is tracked. */get: () => Immutable<TState>;
|
|
98
|
+
/**
|
|
99
|
+
* Replace the property value, or update it with an Immer producer for that
|
|
100
|
+
* property value.
|
|
101
|
+
*/
|
|
102
|
+
set: (value: TState | Producer<TState>) => void;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Constructor-local access to the base state.
|
|
106
|
+
*
|
|
107
|
+
* `TState` may be any non-function value, including primitives.
|
|
108
|
+
*
|
|
109
|
+
* Return this handle from the constructor when you want to expose the base
|
|
110
|
+
* state directly as a reactive immutable property on the managed state.
|
|
111
|
+
*
|
|
112
|
+
* When the base state is object-shaped, the handle also exposes a shallow
|
|
113
|
+
* `Lens` for each top-level property key. Spreading an object-shaped handle
|
|
114
|
+
* into the returned constructor object exposes those top-level lenses as
|
|
115
|
+
* tracked public properties.
|
|
116
|
+
*
|
|
117
|
+
* For ordinary derived values, prefer external functions like
|
|
118
|
+
* `getVisibleTodos(state)` so unused helpers can be tree-shaken. Reach for
|
|
119
|
+
* `computed(() => derive(handle.get()))` only when you need memoized reactive
|
|
120
|
+
* reads as a performance optimization.
|
|
121
|
+
*/
|
|
122
|
+
type StateHandle<TState, TEvents extends EventsDefinition = never> = AnyStateHandle<TState, TEvents> & (TState extends object ? TState extends ((...args: any[]) => any) | readonly any[] | ReadonlyMap<any, any> | ReadonlySet<any> ? {} : { readonly [K in keyof TState]: Lens<TState[K]> } : {});
|
|
123
|
+
/**
|
|
124
|
+
* Pure constructor function for a managed state definition.
|
|
125
|
+
*
|
|
126
|
+
* The first parameter should be explicitly typed as `StateHandle<...>`. The
|
|
127
|
+
* library infers the internal state and event types from that parameter type.
|
|
128
|
+
*
|
|
129
|
+
* Return only methods, signals, top-level `Lens` values from the provided
|
|
130
|
+
* `StateHandle`, the provided `StateHandle`, or a managed state instance.
|
|
131
|
+
* Returned signals and lenses become tracked getter properties, returning the
|
|
132
|
+
* handle exposes the base state directly as a reactive immutable property, and
|
|
133
|
+
* managed state instances are passed through unchanged.
|
|
134
|
+
*/
|
|
135
|
+
type StateConstructor<TState, TEvents extends EventsDefinition, TParams extends any[], TProps extends object = {}> = (handle: StateHandle<TState, TEvents>, ...params: TParams) => TProps & {
|
|
136
|
+
[key: string]: ((...args: any[]) => any) | AnyManagedState | Lens | ReadonlySignal | StateHandle<any, any>;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Define a managed state class with a private mutable implementation and an
|
|
140
|
+
* immutable public surface.
|
|
141
|
+
*
|
|
142
|
+
* `TState` may be any non-function value, including primitives.
|
|
143
|
+
*
|
|
144
|
+
* The constructor function's explicitly typed `StateHandle` parameter is what
|
|
145
|
+
* the library uses to infer the internal state and event types.
|
|
146
|
+
*
|
|
147
|
+
* Methods are automatically wrapped with `action()` from `@preact/signals`, so
|
|
148
|
+
* they are untracked and batched unless you opt into tracked reads with
|
|
149
|
+
* `query()`.
|
|
150
|
+
*
|
|
151
|
+
* The state constructor must return an object with properties that are either:
|
|
152
|
+
* - A function (to expose methods)
|
|
153
|
+
* - A signal (to expose derived state)
|
|
154
|
+
* - A top-level lens from the state handle (to expose one reactive property)
|
|
155
|
+
* - The state handle (to expose the reactive immutable base state directly)
|
|
156
|
+
* - A managed state instance (to compose another managed state as a property)
|
|
157
|
+
*
|
|
158
|
+
* Returned signals and top-level lenses are turned into getter properties, so
|
|
159
|
+
* reads are tracked by the `@preact/signals` runtime. When the base state is
|
|
160
|
+
* object-shaped, spreading the `StateHandle` into the returned object exposes
|
|
161
|
+
* its current top-level lenses at once.
|
|
162
|
+
*
|
|
163
|
+
* Events can carry at most one argument.
|
|
164
|
+
*
|
|
165
|
+
* The state constructor should be side-effect free.
|
|
166
|
+
*/
|
|
167
|
+
declare function defineManagedState<TState, TEvents extends EventsDefinition, TParams extends any[], TProps extends object = {}, TInitialState extends TState = TState>(constructor: StateConstructor<TState, TEvents, TParams, TProps>, initialState: TInitialState): new (...params: TParams) => ManagedState<InferState$1<TProps>, TEvents, InferPublicProps$1<TProps>>;
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/hooks.d.ts
|
|
170
|
+
type FilterProperties<T extends object, U> = {} & { [P in { [K in keyof T]: [T[K]] extends [never] ? never : T[K] extends U ? K : never }[keyof T]]: T[P] };
|
|
171
|
+
type InferActions<TProps extends object> = {} & FilterProperties<TProps, (...args: any[]) => any>;
|
|
172
|
+
type InferManagedStates<TProps extends object> = {} & FilterProperties<TProps, AnyManagedState>;
|
|
173
|
+
type InferPublicProps<TProps extends object> = InferActions<TProps> & InferManagedStates<TProps>;
|
|
174
|
+
type InferState<TProps extends object> = {} & Immutable<{ [K in keyof FilterProperties<TProps, ReadonlySignal | StateHandle<any, any> | Lens>]: TProps[K] extends ReadonlySignal<infer T> | StateHandle<infer T> | Lens<infer T> ? T : never }> & Readonly<InferManagedStates<TProps>>;
|
|
175
|
+
/**
|
|
176
|
+
* Clean encapsulation of complex UI state.
|
|
177
|
+
*
|
|
178
|
+
* Use this when a component needs the same managed-state API without defining a
|
|
179
|
+
* separate class. The constructor follows the same rules as
|
|
180
|
+
* `defineManagedState()`, including explicit typing of the `StateHandle`
|
|
181
|
+
* parameter for state and event inference.
|
|
182
|
+
*/
|
|
183
|
+
declare function useManagedState<TState, TEvents extends EventsDefinition, TProps extends object = {}, TInitialState extends TState = TState>(constructor: StateConstructor<TState, TEvents, [], TProps>, initialState: TInitialState | (() => TInitialState)): ManagedState<InferState<TProps>, TEvents, InferPublicProps<TProps>>;
|
|
184
|
+
/**
|
|
185
|
+
* Any subscribable source, including a managed state or any Preact signal.
|
|
186
|
+
*/
|
|
187
|
+
type SubscribeTarget<T> = {
|
|
188
|
+
subscribe: (listener: (value: T) => void) => () => void;
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Subscribe to future values from a subscribable source inside `useEffect`.
|
|
192
|
+
*
|
|
193
|
+
* The listener is kept fresh automatically, so a dependency array is not part
|
|
194
|
+
* of this API. The listener receives the current value immediately and then
|
|
195
|
+
* future updates. Pass `null` to disable the subscription temporarily.
|
|
196
|
+
*/
|
|
197
|
+
declare function useSubscribe<T>(target: SubscribeTarget<T> | null, listener: (value: T) => void): void;
|
|
198
|
+
type InferEvent<T extends EventTarget | AnyManagedState> = T extends AnyManagedState<any, infer TEvents extends EventsDefinition> ? string & keyof TEvents : T extends {
|
|
199
|
+
addEventListener: (name: infer TEvent) => any;
|
|
200
|
+
} ? string & TEvent : string;
|
|
201
|
+
type InferEventListener<T extends EventTarget | AnyManagedState, TEvent extends string = any> = T extends AnyManagedState<any, infer TEvents extends EventsDefinition> ? TEvent extends string & keyof TEvents ? (...args: TEvents[TEvent]) => void : never : T extends {
|
|
202
|
+
addEventListener: (name: TEvent, listener: infer TListener extends (event: any) => any) => any;
|
|
203
|
+
} ? TListener : (event: Event) => void;
|
|
204
|
+
/**
|
|
205
|
+
* Subscribe to events from an `EventTarget` or managed state inside `useEffect`.
|
|
206
|
+
*
|
|
207
|
+
* The listener is kept fresh automatically, so a dependency array is not part
|
|
208
|
+
* of this API. Pass `null` to disable the subscription temporarily.
|
|
209
|
+
*
|
|
210
|
+
* For managed-state events, your listener receives the emitted argument
|
|
211
|
+
* directly, or no argument at all.
|
|
212
|
+
*/
|
|
213
|
+
declare function useEventTarget<T extends EventTarget | AnyManagedState, TEvent extends InferEvent<T>>(target: T | null, name: TEvent, listener: InferEventListener<T, TEvent>): void;
|
|
214
|
+
//#endregion
|
|
215
|
+
export { AnyManagedState, EventsDefinition, Lens, ManagedState, StateConstructor, StateHandle, SubscribeTarget, batch, computed, defineManagedState, isManagedState, query, untracked, useEventTarget, useManagedState, useSubscribe };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Signal, action, batch, computed, computed as computed$1, signal, untracked } from "@preact/signals";
|
|
2
|
+
import { castImmutable, freeze, produce } from "immer";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
|
4
|
+
//#region src/internal.ts
|
|
5
|
+
const lensKeys = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
const queryMethods = /* @__PURE__ */ new WeakSet();
|
|
7
|
+
var StateContainer = class extends EventTarget {
|
|
8
|
+
_signals = /* @__PURE__ */ new Map();
|
|
9
|
+
_view = computed$1(() => ({ ...this }));
|
|
10
|
+
constructor(state, handle, props, dispose) {
|
|
11
|
+
super();
|
|
12
|
+
this.dispose = dispose;
|
|
13
|
+
const propDescriptors = Object.getOwnPropertyDescriptors(props);
|
|
14
|
+
for (const key in propDescriptors) {
|
|
15
|
+
const propDescriptor = propDescriptors[key];
|
|
16
|
+
if ("value" in propDescriptor) {
|
|
17
|
+
let { value } = propDescriptor;
|
|
18
|
+
if (typeof value === "function") {
|
|
19
|
+
Object.defineProperty(this, key, { value: queryMethods.has(value) ? value : action(value) });
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const signal = getExposedSignal(value, state, handle);
|
|
23
|
+
if (signal) {
|
|
24
|
+
this._signals.set(key, signal);
|
|
25
|
+
Object.defineProperty(this, key, {
|
|
26
|
+
get: () => signal.value,
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
} else if (isManagedState(value)) Object.defineProperty(this, key, {
|
|
30
|
+
value,
|
|
31
|
+
enumerable: true
|
|
32
|
+
});
|
|
33
|
+
else throw new Error(`Invalid property: ${key}. Must be a function, a signal, a top-level lens, the state handle, or a managed state.`);
|
|
34
|
+
} else throw new Error(`\`get ${key}() {}\` syntax is forbidden`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
get(key) {
|
|
38
|
+
if (!key) return this._view;
|
|
39
|
+
return this._signals.get(key);
|
|
40
|
+
}
|
|
41
|
+
peek(key) {
|
|
42
|
+
const signal = this.get(key);
|
|
43
|
+
if (!signal) return;
|
|
44
|
+
return signal.peek();
|
|
45
|
+
}
|
|
46
|
+
subscribe(...args) {
|
|
47
|
+
if (args.length > 1) {
|
|
48
|
+
const [key, listener] = args;
|
|
49
|
+
const signal = this.get(key);
|
|
50
|
+
if (!signal) throw new Error(`Property ${key} is not a signal`);
|
|
51
|
+
return signal.subscribe(listener);
|
|
52
|
+
}
|
|
53
|
+
return this._view.subscribe(args[0]);
|
|
54
|
+
}
|
|
55
|
+
on(name, listener) {
|
|
56
|
+
const adapter = (event) => {
|
|
57
|
+
const detail = event.detail;
|
|
58
|
+
if (detail === void 0) listener();
|
|
59
|
+
else listener(detail);
|
|
60
|
+
};
|
|
61
|
+
this.addEventListener(name, adapter);
|
|
62
|
+
return () => {
|
|
63
|
+
this.removeEventListener(name, adapter);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
[Symbol.dispose]() {
|
|
67
|
+
this.dispose();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function isProducer(value) {
|
|
71
|
+
return typeof value === "function";
|
|
72
|
+
}
|
|
73
|
+
function makeNonEnumerable(object, keys) {
|
|
74
|
+
for (const key of keys) Object.defineProperty(object, key, {
|
|
75
|
+
...Object.getOwnPropertyDescriptor(object, key),
|
|
76
|
+
enumerable: false
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
var AnyStateHandle = class {
|
|
80
|
+
constructor(state, emit, own) {
|
|
81
|
+
this.state = state;
|
|
82
|
+
this.emit = emit;
|
|
83
|
+
this.own = own;
|
|
84
|
+
makeNonEnumerable(this, ["emit", "own"]);
|
|
85
|
+
}
|
|
86
|
+
/** Read the current immutable base state. This read is tracked. */
|
|
87
|
+
get() {
|
|
88
|
+
return this.state.value;
|
|
89
|
+
}
|
|
90
|
+
/** Read the current immutable base state snapshot without tracking. */
|
|
91
|
+
peek() {
|
|
92
|
+
return this.state.peek();
|
|
93
|
+
}
|
|
94
|
+
/** Replace the base state, or update it with an Immer producer. */
|
|
95
|
+
set(value) {
|
|
96
|
+
this.state.value = isProducer(value) ? produce(this.state.value, value) : castImmutable(value);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function createStateHandle(state, emit, own) {
|
|
100
|
+
const handle = new AnyStateHandle(state, emit, own);
|
|
101
|
+
let lenses;
|
|
102
|
+
const getLensDescriptor = (key) => {
|
|
103
|
+
const currentState = state.value;
|
|
104
|
+
if (!isLensableState(currentState)) return;
|
|
105
|
+
return Reflect.getOwnPropertyDescriptor(currentState, key);
|
|
106
|
+
};
|
|
107
|
+
const getLens = (key) => {
|
|
108
|
+
let lens = (lenses ||= /* @__PURE__ */ new Map()).get(key);
|
|
109
|
+
if (!lens) {
|
|
110
|
+
lens = {
|
|
111
|
+
get: () => handle.get()[key],
|
|
112
|
+
set: (update) => {
|
|
113
|
+
handle.set((draft) => {
|
|
114
|
+
draft[key] = isProducer(update) ? produce(draft[key], update) : update;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
lensKeys.set(lens, key);
|
|
119
|
+
lenses.set(key, lens);
|
|
120
|
+
}
|
|
121
|
+
return lens;
|
|
122
|
+
};
|
|
123
|
+
return new Proxy(handle, {
|
|
124
|
+
get(target, key, receiver) {
|
|
125
|
+
if (Reflect.has(target, key)) return Reflect.get(target, key, receiver);
|
|
126
|
+
if (!getLensDescriptor(key)) return;
|
|
127
|
+
return getLens(key);
|
|
128
|
+
},
|
|
129
|
+
ownKeys(_target) {
|
|
130
|
+
const currentState = state.value;
|
|
131
|
+
if (!isLensableState(currentState)) return [];
|
|
132
|
+
return Reflect.ownKeys(currentState);
|
|
133
|
+
},
|
|
134
|
+
getOwnPropertyDescriptor(_target, key) {
|
|
135
|
+
const lensDescriptor = getLensDescriptor(key);
|
|
136
|
+
if (!lensDescriptor) return;
|
|
137
|
+
return {
|
|
138
|
+
configurable: true,
|
|
139
|
+
enumerable: lensDescriptor.enumerable,
|
|
140
|
+
value: getLens(key),
|
|
141
|
+
writable: false
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function isLensableState(value) {
|
|
147
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
148
|
+
const prototype = Object.getPrototypeOf(value);
|
|
149
|
+
return prototype === Object.prototype || prototype === null;
|
|
150
|
+
}
|
|
151
|
+
function getExposedSignal(value, state, handle) {
|
|
152
|
+
if (value === handle) return state;
|
|
153
|
+
if (value instanceof Signal) return value;
|
|
154
|
+
const lensKey = getLensKey(value);
|
|
155
|
+
if (lensKey !== void 0) return computed$1(() => state.value[lensKey]);
|
|
156
|
+
}
|
|
157
|
+
function getLensKey(value) {
|
|
158
|
+
if (!value || typeof value !== "object") return;
|
|
159
|
+
return lensKeys.get(value);
|
|
160
|
+
}
|
|
161
|
+
function disposeOwnedResources(resources) {
|
|
162
|
+
let errors;
|
|
163
|
+
for (let index = resources.length - 1; index >= 0; index -= 1) try {
|
|
164
|
+
const resource = resources[index];
|
|
165
|
+
if (typeof resource === "function") resource();
|
|
166
|
+
else resource[Symbol.dispose]();
|
|
167
|
+
} catch (error) {
|
|
168
|
+
errors ||= [];
|
|
169
|
+
errors.push(error);
|
|
170
|
+
}
|
|
171
|
+
if (errors) throw new AggregateError(errors, "Failed to dispose one or more resources");
|
|
172
|
+
}
|
|
173
|
+
/** Check whether a value is a managed-state instance. */
|
|
174
|
+
function isManagedState(value) {
|
|
175
|
+
return value instanceof StateContainer;
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/framework.ts
|
|
179
|
+
/**
|
|
180
|
+
* Mark a constructor-returned method as a tracked query.
|
|
181
|
+
*
|
|
182
|
+
* Query methods wrap their body in `computed()`, so reads inside the method
|
|
183
|
+
* participate in signal tracking even after the method is exposed publicly.
|
|
184
|
+
* Query functions read from closed-over handles or signals and do not use an
|
|
185
|
+
* instance receiver. Tagged query methods also skip the default `action()`
|
|
186
|
+
* wrapping step.
|
|
187
|
+
*/
|
|
188
|
+
function query(fn) {
|
|
189
|
+
const wrapped = ((...args) => computed$1(() => fn(...args)).value);
|
|
190
|
+
queryMethods.add(wrapped);
|
|
191
|
+
return wrapped;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Define a managed state class with a private mutable implementation and an
|
|
195
|
+
* immutable public surface.
|
|
196
|
+
*
|
|
197
|
+
* `TState` may be any non-function value, including primitives.
|
|
198
|
+
*
|
|
199
|
+
* The constructor function's explicitly typed `StateHandle` parameter is what
|
|
200
|
+
* the library uses to infer the internal state and event types.
|
|
201
|
+
*
|
|
202
|
+
* Methods are automatically wrapped with `action()` from `@preact/signals`, so
|
|
203
|
+
* they are untracked and batched unless you opt into tracked reads with
|
|
204
|
+
* `query()`.
|
|
205
|
+
*
|
|
206
|
+
* The state constructor must return an object with properties that are either:
|
|
207
|
+
* - A function (to expose methods)
|
|
208
|
+
* - A signal (to expose derived state)
|
|
209
|
+
* - A top-level lens from the state handle (to expose one reactive property)
|
|
210
|
+
* - The state handle (to expose the reactive immutable base state directly)
|
|
211
|
+
* - A managed state instance (to compose another managed state as a property)
|
|
212
|
+
*
|
|
213
|
+
* Returned signals and top-level lenses are turned into getter properties, so
|
|
214
|
+
* reads are tracked by the `@preact/signals` runtime. When the base state is
|
|
215
|
+
* object-shaped, spreading the `StateHandle` into the returned object exposes
|
|
216
|
+
* its current top-level lenses at once.
|
|
217
|
+
*
|
|
218
|
+
* Events can carry at most one argument.
|
|
219
|
+
*
|
|
220
|
+
* The state constructor should be side-effect free.
|
|
221
|
+
*/
|
|
222
|
+
function defineManagedState(constructor, initialState) {
|
|
223
|
+
initialState = freeze(initialState);
|
|
224
|
+
return class extends StateContainer {
|
|
225
|
+
constructor(...params) {
|
|
226
|
+
const state = signal(initialState);
|
|
227
|
+
let owned;
|
|
228
|
+
let disposed = false;
|
|
229
|
+
const dispose = () => {
|
|
230
|
+
if (disposed) return;
|
|
231
|
+
disposed = true;
|
|
232
|
+
const current = owned;
|
|
233
|
+
owned = void 0;
|
|
234
|
+
if (!current) return;
|
|
235
|
+
disposeOwnedResources(current);
|
|
236
|
+
};
|
|
237
|
+
const handle = createStateHandle(state, (name, detail) => this.dispatchEvent(new CustomEvent(name, { detail })), (resources) => {
|
|
238
|
+
const nextResources = Array.isArray(resources) ? resources : [resources];
|
|
239
|
+
if (!nextResources.length) return;
|
|
240
|
+
if (disposed) disposeOwnedResources(nextResources);
|
|
241
|
+
else (owned ??= []).push(...nextResources);
|
|
242
|
+
});
|
|
243
|
+
const props = constructor(handle, ...params);
|
|
244
|
+
super(state, handle, props, dispose);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/hooks.ts
|
|
250
|
+
function isFunction(value) {
|
|
251
|
+
return typeof value === "function";
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Clean encapsulation of complex UI state.
|
|
255
|
+
*
|
|
256
|
+
* Use this when a component needs the same managed-state API without defining a
|
|
257
|
+
* separate class. The constructor follows the same rules as
|
|
258
|
+
* `defineManagedState()`, including explicit typing of the `StateHandle`
|
|
259
|
+
* parameter for state and event inference.
|
|
260
|
+
*/
|
|
261
|
+
function useManagedState(constructor, initialState) {
|
|
262
|
+
const managedState = useState(() => new (defineManagedState(constructor, isFunction(initialState) ? initialState() : initialState))())[0];
|
|
263
|
+
useEffect(() => () => managedState.dispose(), [managedState]);
|
|
264
|
+
return managedState;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Subscribe to future values from a subscribable source inside `useEffect`.
|
|
268
|
+
*
|
|
269
|
+
* The listener is kept fresh automatically, so a dependency array is not part
|
|
270
|
+
* of this API. The listener receives the current value immediately and then
|
|
271
|
+
* future updates. Pass `null` to disable the subscription temporarily.
|
|
272
|
+
*/
|
|
273
|
+
function useSubscribe(target, listener) {
|
|
274
|
+
listener = useStableCallback(listener);
|
|
275
|
+
useEffect(() => target?.subscribe(listener), [target]);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Subscribe to events from an `EventTarget` or managed state inside `useEffect`.
|
|
279
|
+
*
|
|
280
|
+
* The listener is kept fresh automatically, so a dependency array is not part
|
|
281
|
+
* of this API. Pass `null` to disable the subscription temporarily.
|
|
282
|
+
*
|
|
283
|
+
* For managed-state events, your listener receives the emitted argument
|
|
284
|
+
* directly, or no argument at all.
|
|
285
|
+
*/
|
|
286
|
+
function useEventTarget(target, name, listener) {
|
|
287
|
+
listener = useStableCallback(listener);
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (!target) return;
|
|
290
|
+
if (isManagedState(target)) return target.on(name, listener);
|
|
291
|
+
target.addEventListener(name, listener);
|
|
292
|
+
return () => target.removeEventListener(name, listener);
|
|
293
|
+
}, [target, name]);
|
|
294
|
+
}
|
|
295
|
+
function useStableCallback(callback) {
|
|
296
|
+
const ref = useRef(callback);
|
|
297
|
+
ref.current = callback;
|
|
298
|
+
return useCallback((...params) => (0, ref.current)(...params), []);
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
export { batch, computed, defineManagedState, isManagedState, query, untracked, useEventTarget, useManagedState, useSubscribe };
|
package/llms.txt
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# `preact-sigma` for LLMs
|
|
2
|
+
|
|
3
|
+
## Glossary
|
|
4
|
+
|
|
5
|
+
- `base state`: The private mutable state owned by one managed state definition. It may be any non-function value, including a primitive.
|
|
6
|
+
- `public state`: The immutable data exposed on a managed state instance. It is derived from returned signals, returned top-level lenses, returned handles, and returned nested managed states.
|
|
7
|
+
- `managed state`: An instance created by `defineManagedState()` or returned by `useManagedState()`. It exposes immutable public data, public methods, subscriptions, events, and disposal.
|
|
8
|
+
- `manager class`: The class returned by `defineManagedState()`. Best practice is to name it with a `Manager` suffix.
|
|
9
|
+
- `constructor`: The function passed to `defineManagedState()` or `useManagedState()`. It receives a typed `StateHandle`.
|
|
10
|
+
- `handle`: The first constructor parameter. Its type must be `StateHandle<...>`. It is the private control surface for reading, writing, owning resources, and emitting.
|
|
11
|
+
- `lens`: A constructor-local object for one top-level property of an object-shaped base state. A lens has `get()` and `set()`.
|
|
12
|
+
- `signal`: A `ReadonlySignal` from `@preact/signals`.
|
|
13
|
+
- `signal-backed public property`: A public property backed by a returned signal, returned lens, or returned handle. Keyed `get`, `peek`, and `subscribe` target only these properties.
|
|
14
|
+
- `derived value`: A value computed from state rather than stored directly.
|
|
15
|
+
- `public action`: A returned method intended to change state. Returned methods are action-wrapped by default.
|
|
16
|
+
- `query method`: A returned method wrapped with `query()`. Query methods are tracked reads and are not action-wrapped.
|
|
17
|
+
- `event map`: A type mapping event names to zero-or-one-argument tuples, for example `{ saved: []; selected: [{ id: string }] }`.
|
|
18
|
+
- `cleanup`: A function of type `() => void`.
|
|
19
|
+
- `disposable`: An object with a `[Symbol.dispose]()` method.
|
|
20
|
+
- `owned resource`: A cleanup function or disposable registered through `handle.own()`.
|
|
21
|
+
- `object-shaped state`: A base state that is a plain object. It is not a function, array, `Map`, or `Set`.
|
|
22
|
+
- `top-level property`: A direct property on an object-shaped base state, such as `query` in `{ query: string }`.
|
|
23
|
+
- `composition`: Returning one managed state instance from another managed state constructor so the nested instance is exposed unchanged as a public property.
|
|
24
|
+
- `tracked read`: A read that participates in Signals tracking.
|
|
25
|
+
- `untracked read`: A read that does not participate in Signals tracking.
|
|
26
|
+
- `unsubscribe`: The cleanup function returned by `.on()` and `.subscribe()`.
|
|
27
|
+
|
|
28
|
+
## Purpose
|
|
29
|
+
|
|
30
|
+
`preact-sigma` is a small state-management layer built on top of `@preact/signals` and `immer`.
|
|
31
|
+
|
|
32
|
+
It is designed to:
|
|
33
|
+
|
|
34
|
+
- keep mutable implementation details private
|
|
35
|
+
- expose immutable public data
|
|
36
|
+
- express state transitions through public methods
|
|
37
|
+
- support derived reactive values
|
|
38
|
+
- support typed custom events
|
|
39
|
+
- support composition of nested managed states
|
|
40
|
+
- support instance-owned resource cleanup
|
|
41
|
+
|
|
42
|
+
## Runtime Exports
|
|
43
|
+
|
|
44
|
+
- `defineManagedState`
|
|
45
|
+
- `useManagedState`
|
|
46
|
+
- `useSubscribe`
|
|
47
|
+
- `useEventTarget`
|
|
48
|
+
- `isManagedState`
|
|
49
|
+
- `query`
|
|
50
|
+
- `computed`
|
|
51
|
+
- `batch`
|
|
52
|
+
- `untracked`
|
|
53
|
+
|
|
54
|
+
## Public Type Exports
|
|
55
|
+
|
|
56
|
+
- `ManagedState`
|
|
57
|
+
- `StateConstructor`
|
|
58
|
+
- `StateHandle`
|
|
59
|
+
- `Lens`
|
|
60
|
+
- `SubscribeTarget`
|
|
61
|
+
|
|
62
|
+
## Core Rules
|
|
63
|
+
|
|
64
|
+
- The first constructor parameter must be explicitly typed as `StateHandle<...>`.
|
|
65
|
+
- The constructor should be side-effect free.
|
|
66
|
+
- The constructor may return only:
|
|
67
|
+
- methods
|
|
68
|
+
- signals
|
|
69
|
+
- top-level lenses from the provided handle
|
|
70
|
+
- the provided handle itself
|
|
71
|
+
- a managed state instance
|
|
72
|
+
- Event payloads may have zero or one argument only.
|
|
73
|
+
- Returned methods are action-wrapped automatically unless wrapped with `query()`.
|
|
74
|
+
- Returned signals and returned top-level lenses become tracked getter properties on the public instance.
|
|
75
|
+
- Returning the handle exposes the full base state as one reactive immutable public property.
|
|
76
|
+
- Returning a managed state instance exposes that nested managed state unchanged.
|
|
77
|
+
- Keyed `get`, `peek`, and `subscribe` apply only to signal-backed public properties, not to composed managed-state properties.
|
|
78
|
+
|
|
79
|
+
## `defineManagedState()`
|
|
80
|
+
|
|
81
|
+
Use `defineManagedState(constructor, initialState)` to create a reusable managed-state class.
|
|
82
|
+
|
|
83
|
+
Behavior:
|
|
84
|
+
|
|
85
|
+
- `initialState` is the initial base state
|
|
86
|
+
- constructor parameters after the handle become runtime constructor parameters for the class
|
|
87
|
+
- internal state and event types are inferred from the typed `StateHandle` parameter
|
|
88
|
+
- the resulting instance exposes immutable public data plus public methods
|
|
89
|
+
- the resulting instance owns any resources registered through `handle.own()`
|
|
90
|
+
|
|
91
|
+
## `useManagedState()`
|
|
92
|
+
|
|
93
|
+
Use `useManagedState(constructor, initialState)` to create a managed state directly inside a component.
|
|
94
|
+
|
|
95
|
+
Behavior:
|
|
96
|
+
|
|
97
|
+
- follows the same constructor rules as `defineManagedState()`
|
|
98
|
+
- accepts either a concrete initial state or a lazy initializer function
|
|
99
|
+
- returns one stable managed state instance for the component
|
|
100
|
+
|
|
101
|
+
## `StateHandle`
|
|
102
|
+
|
|
103
|
+
`StateHandle<TState, TEvents>` is the constructor-local control surface.
|
|
104
|
+
|
|
105
|
+
Methods:
|
|
106
|
+
|
|
107
|
+
- `get()`: tracked read of the current immutable base state
|
|
108
|
+
- `peek()`: untracked read of the current immutable base state
|
|
109
|
+
- `set(next)`: replace the base state or update it with an Immer producer
|
|
110
|
+
- `own(resources)`: attach cleanup functions or disposables to the managed state instance
|
|
111
|
+
- `emit(name, arg?)`: emit a typed custom event with zero or one argument
|
|
112
|
+
|
|
113
|
+
For object-shaped base state only:
|
|
114
|
+
|
|
115
|
+
- each top-level property is available as a `Lens`
|
|
116
|
+
- spreading the handle into the returned constructor object exposes all current top-level lenses as tracked public properties
|
|
117
|
+
|
|
118
|
+
## `Lens`
|
|
119
|
+
|
|
120
|
+
`Lens<T>` exists only on object-shaped `StateHandle`s.
|
|
121
|
+
|
|
122
|
+
Methods:
|
|
123
|
+
|
|
124
|
+
- `get()`: tracked read of that top-level property
|
|
125
|
+
- `set(next)`: replace that property or update it with an Immer producer for that property
|
|
126
|
+
|
|
127
|
+
Important:
|
|
128
|
+
|
|
129
|
+
- lenses are constructor-local unless returned
|
|
130
|
+
- returning one lens exposes one reactive public property
|
|
131
|
+
- spreading an object-shaped handle exposes all current top-level lenses at once
|
|
132
|
+
|
|
133
|
+
## `ManagedState`
|
|
134
|
+
|
|
135
|
+
A managed state instance exposes:
|
|
136
|
+
|
|
137
|
+
- immutable public data as normal properties
|
|
138
|
+
- public methods returned by the constructor
|
|
139
|
+
- subscription methods
|
|
140
|
+
- event subscription methods
|
|
141
|
+
- disposal methods
|
|
142
|
+
|
|
143
|
+
Read APIs:
|
|
144
|
+
|
|
145
|
+
- `get(key)`: return the underlying signal for one exposed signal-backed public property
|
|
146
|
+
- `get()`: return the underlying signal for the whole public state
|
|
147
|
+
- `peek(key)`: untracked read of one exposed signal-backed public property
|
|
148
|
+
- `peek()`: untracked read of the whole public state
|
|
149
|
+
|
|
150
|
+
Subscription APIs:
|
|
151
|
+
|
|
152
|
+
- `subscribe(key, listener)`: subscribe to the current and future values of one exposed signal-backed public property
|
|
153
|
+
- `subscribe(listener)`: subscribe to the current and future whole public state
|
|
154
|
+
- both forms return an unsubscribe function
|
|
155
|
+
|
|
156
|
+
Event API:
|
|
157
|
+
|
|
158
|
+
- `on(name, listener)`: subscribe to one custom event
|
|
159
|
+
- listener receives the emitted argument directly, or no argument at all
|
|
160
|
+
- returns an unsubscribe function
|
|
161
|
+
|
|
162
|
+
Disposal API:
|
|
163
|
+
|
|
164
|
+
- `dispose()`: dispose the managed state instance and its owned resources
|
|
165
|
+
- `[Symbol.dispose]()` does the same thing as `dispose()`
|
|
166
|
+
|
|
167
|
+
## `query()`
|
|
168
|
+
|
|
169
|
+
`query(fn)` marks a returned method as a tracked public read.
|
|
170
|
+
|
|
171
|
+
Behavior:
|
|
172
|
+
|
|
173
|
+
- wraps the method body in `computed()`
|
|
174
|
+
- lets reads inside the method participate in Signals tracking after the method is exposed publicly
|
|
175
|
+
- query functions read from closed-over handles or signals and do not use an instance receiver
|
|
176
|
+
- skips the default `action()` wrapping step
|
|
177
|
+
|
|
178
|
+
Use `query()` for public methods that conceptually answer a question.
|
|
179
|
+
|
|
180
|
+
Do not use `query()` for ordinary mutating actions.
|
|
181
|
+
|
|
182
|
+
## `computed`
|
|
183
|
+
|
|
184
|
+
`computed` is re-exported from `@preact/signals`.
|
|
185
|
+
|
|
186
|
+
Use it when a public derived value should be memoized and reactive.
|
|
187
|
+
|
|
188
|
+
## `batch`
|
|
189
|
+
|
|
190
|
+
`batch` is re-exported from `@preact/signals`.
|
|
191
|
+
|
|
192
|
+
Use it when multiple state updates should be grouped into one reactive batch.
|
|
193
|
+
|
|
194
|
+
## `untracked`
|
|
195
|
+
|
|
196
|
+
`untracked` is re-exported from `@preact/signals`.
|
|
197
|
+
|
|
198
|
+
Use it when code must read reactive values without subscribing to them.
|
|
199
|
+
|
|
200
|
+
## `useSubscribe()`
|
|
201
|
+
|
|
202
|
+
Use `useSubscribe(target, listener)` inside a component.
|
|
203
|
+
|
|
204
|
+
Behavior:
|
|
205
|
+
|
|
206
|
+
- accepts any subscribable target, including a managed state or a Preact signal
|
|
207
|
+
- keeps the listener fresh automatically
|
|
208
|
+
- calls the listener immediately with the current value, then with future updates
|
|
209
|
+
- does not take a dependency array
|
|
210
|
+
- accepts `null` to disable the subscription temporarily
|
|
211
|
+
|
|
212
|
+
## `useEventTarget()`
|
|
213
|
+
|
|
214
|
+
Use `useEventTarget(target, name, listener)` inside a component.
|
|
215
|
+
|
|
216
|
+
Behavior:
|
|
217
|
+
|
|
218
|
+
- accepts either a DOM-style `EventTarget` or a managed state
|
|
219
|
+
- keeps the listener fresh automatically
|
|
220
|
+
- does not take a dependency array
|
|
221
|
+
- accepts `null` to disable the subscription temporarily
|
|
222
|
+
- for managed-state events, the listener receives the emitted argument directly, or no argument at all
|
|
223
|
+
|
|
224
|
+
## `isManagedState()`
|
|
225
|
+
|
|
226
|
+
Use `isManagedState(value)` to check whether a value is a managed-state instance.
|
|
227
|
+
|
|
228
|
+
## Supported Patterns
|
|
229
|
+
|
|
230
|
+
- primitive base state
|
|
231
|
+
- plain-object base state
|
|
232
|
+
- Immer producer updates
|
|
233
|
+
- lifecycle-owned cleanup via `handle.own()`
|
|
234
|
+
- disposal via `dispose()` or `[Symbol.dispose]()`
|
|
235
|
+
- reactive derived properties via returned signals
|
|
236
|
+
- tracked read methods via `query()`
|
|
237
|
+
- public exposure of one top-level property via a returned lens
|
|
238
|
+
- public exposure of all top-level properties via `...handle`
|
|
239
|
+
- public exposure of the whole base state via the returned handle
|
|
240
|
+
- nested managed-state composition
|
|
241
|
+
- keyed and whole-state signal access
|
|
242
|
+
- keyed and whole-state snapshot access
|
|
243
|
+
- keyed and whole-state subscriptions
|
|
244
|
+
- typed custom events with zero or one argument
|
|
245
|
+
- component-local managed state via `useManagedState`
|
|
246
|
+
- hook-based subscriptions via `useSubscribe`
|
|
247
|
+
- hook-based event subscriptions via `useEventTarget`
|
|
248
|
+
|
|
249
|
+
## Unsupported Or Disallowed Patterns
|
|
250
|
+
|
|
251
|
+
- function-valued base state
|
|
252
|
+
- returning arbitrary plain objects from the constructor unless each returned property is one of the supported public shapes
|
|
253
|
+
- multi-argument event payloads outside a single object payload
|
|
254
|
+
- relying on untyped constructor parameters for state or event inference
|
|
255
|
+
- using arrays, `Map`, or `Set` as object-shaped state for lens generation
|
|
256
|
+
|
|
257
|
+
## Best Practices
|
|
258
|
+
|
|
259
|
+
### Inference And Naming
|
|
260
|
+
|
|
261
|
+
- Explicitly type the first constructor parameter as `StateHandle<...>`.
|
|
262
|
+
- Prefer a `${ModelName}State` alias near the constructor, even for simple state.
|
|
263
|
+
- Prefer a `${ModelName}Events` alias when events exist.
|
|
264
|
+
- Name the class returned by `defineManagedState()` with a `Manager` suffix.
|
|
265
|
+
- Name the handle like an instance of the state model, not a generic word like `state` or `value`.
|
|
266
|
+
|
|
267
|
+
### State Shape And Exposure
|
|
268
|
+
|
|
269
|
+
- Use top-level lenses for constructor-local reads and writes to top-level object fields.
|
|
270
|
+
- Return one top-level lens when one public field should stay reactive.
|
|
271
|
+
- Spread an object-shaped handle when the public managed state should mirror the base state's top-level shape.
|
|
272
|
+
- Return the full handle only when the whole base state should be one public property.
|
|
273
|
+
- Use `handle.own()` when the managed-state instance should control external cleanup.
|
|
274
|
+
|
|
275
|
+
### Derivations
|
|
276
|
+
|
|
277
|
+
- Prefer plain external derivation functions for ordinary derived values so unused helpers can be tree-shaken.
|
|
278
|
+
- Use `computed(() => derive(handle.get()))` only when a derived value needs memoized reactive reads for performance.
|
|
279
|
+
- Use `query()` for tracked public read methods.
|
|
280
|
+
|
|
281
|
+
### Public API Design
|
|
282
|
+
|
|
283
|
+
- Keep public actions domain-specific.
|
|
284
|
+
- Prefer verbs like `save`, `submit`, `open`, `close`, and `rename` over low-level mutation names.
|
|
285
|
+
- Avoid unnecessary binding. Public methods work through closure over the typed handle, not through `this`.
|
|
286
|
+
- Keep nested features as separate managed states when they already have a clean public API.
|
|
287
|
+
- Prefer explicit disposal when a managed state owns external resources.
|
|
288
|
+
|
|
289
|
+
### Events
|
|
290
|
+
|
|
291
|
+
- Keep events domain-specific.
|
|
292
|
+
- Do not use generic event names like `changed` or `updated` for ordinary state observation.
|
|
293
|
+
- When you need multiple event fields, wrap them in one object payload.
|
|
294
|
+
|
|
295
|
+
## Example Interpretation Rules For Agents
|
|
296
|
+
|
|
297
|
+
- If a snippet assigns `const stop = thing.on(...)`, `stop` is an unsubscribe function.
|
|
298
|
+
- If a snippet assigns `const stop = thing.subscribe(...)`, `stop` is an unsubscribe function.
|
|
299
|
+
- If a snippet calls `handle.own([...])`, those resources are released by `instance.dispose()` or `instance[Symbol.dispose]()`.
|
|
300
|
+
- If a returned method is wrapped with `query()`, treat it as a tracked read, not a mutating action.
|
|
301
|
+
- If a constructor returns `...handle`, treat each top-level object property as a public tracked property.
|
|
302
|
+
- If a constructor returns `field: handle.someField`, treat `field` as a public tracked property whose value is the lens's current value, not as the lens object itself.
|
package/package.json
CHANGED
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "preact-sigma",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
3
|
+
"version": "1.0.0",
|
|
9
4
|
"keywords": [],
|
|
10
|
-
"author": "",
|
|
11
5
|
"license": "MIT",
|
|
12
|
-
"
|
|
13
|
-
|
|
6
|
+
"author": "Alec Larson",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/alloc/preact-sigma.git"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"llms.txt"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@preact/signals": "^2.8.2",
|
|
24
|
+
"immer": "^11.1.4",
|
|
25
|
+
"preact": "11.0.0-beta.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@preact/preset-vite": "^2.10.5",
|
|
29
|
+
"@types/node": "^25.5.0",
|
|
30
|
+
"jsdom": "^29.0.1",
|
|
31
|
+
"lucide-react": "^1.6.0",
|
|
32
|
+
"oxfmt": "^0.42.0",
|
|
33
|
+
"oxlint": "^1.57.0",
|
|
34
|
+
"preact-sigma": "link:",
|
|
35
|
+
"tsdown": "^0.21.4",
|
|
36
|
+
"typescript": "^6.0.2",
|
|
37
|
+
"vite": "^8.0.2",
|
|
38
|
+
"vitest": "^4.1.1"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsdown",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"typecheck": "tsc --noEmit --allowImportingTsExtensions",
|
|
44
|
+
"fmt": "oxfmt",
|
|
45
|
+
"lint": "oxlint"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/readme.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Coming soon...
|