@thefoxieflow/signalctx 0.1.1 → 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 +219 -120
- package/dist/index.d.mts +12 -19
- package/dist/index.d.ts +12 -19
- package/dist/index.js +45 -60
- package/dist/index.mjs +44 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ A tiny, signal-based state utility for React that **solves the `useContext` re-r
|
|
|
6
6
|
If you’ve ever had this issue:
|
|
7
7
|
|
|
8
8
|
```ts
|
|
9
|
-
{
|
|
9
|
+
{
|
|
10
|
+
count, book;
|
|
11
|
+
}
|
|
10
12
|
// updating count re-renders book components 😡
|
|
11
13
|
```
|
|
12
14
|
|
|
@@ -21,7 +23,7 @@ If you’ve ever had this issue:
|
|
|
21
23
|
React Context subscribes to the **entire value**.
|
|
22
24
|
|
|
23
25
|
```tsx
|
|
24
|
-
const { book } = useContext(StoreCtx)
|
|
26
|
+
const { book } = useContext(StoreCtx);
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
When **any property changes**, **every consumer re-renders** — even if they don’t use it.
|
|
@@ -34,23 +36,23 @@ This is not a bug. Context has **no selector mechanism**.
|
|
|
34
36
|
|
|
35
37
|
`signal-ctx`:
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
- Moves state **outside React**
|
|
40
|
+
- Uses **external subscriptions**
|
|
41
|
+
- Allows **selector-based updates**
|
|
40
42
|
|
|
41
|
-
So only the components that
|
|
43
|
+
So only the components that _actually use_ the changed data re-render.
|
|
42
44
|
|
|
43
45
|
---
|
|
44
46
|
|
|
45
47
|
## ✨ Features
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
- ⚡ Signal-style state container
|
|
50
|
+
- 🎯 Selector-based subscriptions
|
|
51
|
+
- 🧵 React 18 concurrent-safe
|
|
52
|
+
- 🧩 Context-backed but not context-driven
|
|
53
|
+
- 📦 Very small bundle size
|
|
54
|
+
- 🌳 Tree-shakable
|
|
55
|
+
- 🧠 Explicit and predictable
|
|
54
56
|
|
|
55
57
|
---
|
|
56
58
|
|
|
@@ -78,39 +80,70 @@ The state lives **outside React**, and components subscribe **directly to the si
|
|
|
78
80
|
|
|
79
81
|
---
|
|
80
82
|
|
|
81
|
-
## 🔹 Signal
|
|
83
|
+
## 🔹 Signal
|
|
82
84
|
|
|
83
85
|
A signal is:
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
- A function that returns state
|
|
88
|
+
- Can be subscribed to
|
|
89
|
+
- Can be updated imperatively
|
|
88
90
|
|
|
89
91
|
```ts
|
|
90
|
-
type
|
|
91
|
-
(): T
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
type Signal<T extends object> = {
|
|
93
|
+
(): T; // get state
|
|
94
|
+
|
|
95
|
+
// add listener
|
|
96
|
+
on(fn: Subscriber): () => void;
|
|
97
|
+
|
|
98
|
+
// notify all listeners
|
|
99
|
+
notify(): void;
|
|
100
|
+
|
|
101
|
+
// reset to initial value
|
|
102
|
+
reset(): void;
|
|
103
|
+
|
|
104
|
+
// update state
|
|
105
|
+
set(action: SetAction<T>): void;
|
|
106
|
+
};
|
|
95
107
|
```
|
|
96
108
|
|
|
97
109
|
---
|
|
98
110
|
|
|
99
|
-
## 🔹 Low-Level
|
|
111
|
+
## 🔹 Low-Level Functions
|
|
112
|
+
|
|
113
|
+
### `newSignal(init)`
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
Creates a low-level signal.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const signal = newSignal({ count: 0 });
|
|
119
|
+
const state = signal(); // get state { count: 0 }
|
|
120
|
+
|
|
121
|
+
signal.on(() => console.log("changed"));
|
|
122
|
+
|
|
123
|
+
setInterval(() => {
|
|
124
|
+
signal.set((s) => {
|
|
125
|
+
s.count++;
|
|
126
|
+
});
|
|
127
|
+
// will trigger signal.on listeners
|
|
128
|
+
signal.notify();
|
|
129
|
+
}, 2000);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🔹 React Hooks
|
|
102
135
|
|
|
103
136
|
### `useValue(store, selector?)`
|
|
104
137
|
|
|
105
138
|
Subscribe to a signal.
|
|
106
139
|
|
|
107
140
|
```tsx
|
|
108
|
-
const count = useValue(store, s => s.count)
|
|
141
|
+
const count = useValue(store, (s) => s.count);
|
|
109
142
|
```
|
|
110
143
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
144
|
+
- Uses `useSyncExternalStore`
|
|
145
|
+
- Re-renders only when the selected value changes
|
|
146
|
+
- Selector is optional
|
|
114
147
|
|
|
115
148
|
---
|
|
116
149
|
|
|
@@ -119,27 +152,40 @@ const count = useValue(store, s => s.count)
|
|
|
119
152
|
Returns a setter function.
|
|
120
153
|
|
|
121
154
|
```ts
|
|
122
|
-
const set = useSet(store)
|
|
155
|
+
const set = useSet(store);
|
|
123
156
|
|
|
124
|
-
|
|
157
|
+
// update entire state
|
|
158
|
+
const prev = store();
|
|
159
|
+
set({ ...prev, count: prev.count + 1 });
|
|
160
|
+
|
|
161
|
+
// or update partially
|
|
162
|
+
set((s) => {
|
|
163
|
+
s.count++;
|
|
164
|
+
});
|
|
125
165
|
```
|
|
126
166
|
|
|
127
167
|
Scoped update:
|
|
128
168
|
|
|
129
169
|
```ts
|
|
170
|
+
const store = newStore(() => ({
|
|
171
|
+
book: { title: "1984", page: 1 },
|
|
172
|
+
user: { name: "Alice" },
|
|
173
|
+
}));
|
|
130
174
|
// book must be object for selector
|
|
131
|
-
const setBook = useSet(store, s => s.book)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
setBook({
|
|
136
|
-
title
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
175
|
+
const setBook = useSet(store, (s) => s.book);
|
|
176
|
+
|
|
177
|
+
// update an object
|
|
178
|
+
const setBook = () => {
|
|
179
|
+
setBook({
|
|
180
|
+
title: "1999",
|
|
181
|
+
page: 10,
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// or update partially
|
|
186
|
+
setBook((b) => {
|
|
187
|
+
b.title = "1999";
|
|
188
|
+
});
|
|
143
189
|
```
|
|
144
190
|
|
|
145
191
|
⚠️ Updates are **mutation-based**. Spread manually if you want immutability.
|
|
@@ -153,14 +199,21 @@ setBook((b)=>{
|
|
|
153
199
|
Creates a **context-backed signal store hook**.
|
|
154
200
|
|
|
155
201
|
```ts
|
|
156
|
-
import { createCtx } from "@
|
|
202
|
+
import { createCtx } from "@thefoxieflow/signalctx";
|
|
157
203
|
|
|
158
|
-
export const
|
|
204
|
+
export const useAppCtx = createCtx(() => ({
|
|
159
205
|
count: 0,
|
|
160
|
-
book: { title: "1984" }
|
|
161
|
-
}))
|
|
206
|
+
book: { title: "1984" },
|
|
207
|
+
}));
|
|
162
208
|
```
|
|
163
209
|
|
|
210
|
+
The returned function has these properties:
|
|
211
|
+
|
|
212
|
+
- **`useAppCtx(selector, options)`** - Hook to select state
|
|
213
|
+
- **`useAppCtx.Provider`** - Context provider component
|
|
214
|
+
- **`useAppCtx.useSet(selector, options)`** - Hook to get setter function
|
|
215
|
+
- **`useAppCtx.useStore(options)`** - Hook to access raw store
|
|
216
|
+
|
|
164
217
|
---
|
|
165
218
|
|
|
166
219
|
## 🚀 Usage
|
|
@@ -168,26 +221,27 @@ export const useStore = createCtx(() => ({
|
|
|
168
221
|
### 1. Create a Provider
|
|
169
222
|
|
|
170
223
|
```tsx
|
|
171
|
-
// use default initial value from
|
|
224
|
+
// use default initial value from useAppCtx
|
|
172
225
|
type Props = {
|
|
173
|
-
children: React.ReactNode
|
|
174
|
-
}
|
|
226
|
+
children: React.ReactNode;
|
|
227
|
+
};
|
|
175
228
|
|
|
176
229
|
export function StoreProvider({ children }: Props) {
|
|
177
|
-
|
|
178
|
-
return <Provider>{children}</Provider>
|
|
230
|
+
return <useAppCtx.Provider>{children}</useAppCtx.Provider>;
|
|
179
231
|
}
|
|
180
232
|
|
|
181
233
|
// overwrite value
|
|
182
234
|
export function StoreProvider({ children }: Props) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
235
|
+
return (
|
|
236
|
+
<useAppCtx.Provider
|
|
237
|
+
value={{
|
|
238
|
+
count: 10,
|
|
239
|
+
book: { title: "Brave New World" },
|
|
240
|
+
}}
|
|
241
|
+
>
|
|
242
|
+
{children}
|
|
243
|
+
</useAppCtx.Provider>
|
|
244
|
+
);
|
|
191
245
|
}
|
|
192
246
|
```
|
|
193
247
|
|
|
@@ -203,13 +257,13 @@ export function StoreProvider({ children }: Props) {
|
|
|
203
257
|
|
|
204
258
|
```tsx
|
|
205
259
|
function Count() {
|
|
206
|
-
const count =
|
|
207
|
-
return <div>{count}</div
|
|
260
|
+
const count = useAppCtx((s) => s.count);
|
|
261
|
+
return <div>{count}</div>;
|
|
208
262
|
}
|
|
209
263
|
|
|
210
264
|
function Book() {
|
|
211
|
-
const book =
|
|
212
|
-
return <div>{book.title}</div
|
|
265
|
+
const book = useAppCtx((s) => s.book);
|
|
266
|
+
return <div>{book.title}</div>;
|
|
213
267
|
}
|
|
214
268
|
```
|
|
215
269
|
|
|
@@ -219,34 +273,69 @@ function Book() {
|
|
|
219
273
|
|
|
220
274
|
```tsx
|
|
221
275
|
function Increment() {
|
|
222
|
-
const
|
|
276
|
+
const setCount = useAppCtx.useSet((s) => s);
|
|
223
277
|
|
|
224
278
|
return (
|
|
225
|
-
<button
|
|
226
|
-
|
|
227
|
-
|
|
279
|
+
<button
|
|
280
|
+
onClick={() =>
|
|
281
|
+
setCount((s) => {
|
|
282
|
+
s.count++;
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
>
|
|
228
286
|
+
|
|
229
287
|
</button>
|
|
230
|
-
)
|
|
288
|
+
);
|
|
231
289
|
}
|
|
232
290
|
```
|
|
233
291
|
|
|
292
|
+
### 4. Custom signal for additional logic
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
const signalWithTraceSet = <T extends object & { traceSet: number }>(
|
|
296
|
+
init: () => T
|
|
297
|
+
) => {
|
|
298
|
+
const core = newSignal(init);
|
|
299
|
+
|
|
300
|
+
const signal: Signal<T> = () => core();
|
|
301
|
+
|
|
302
|
+
signal.reset = core.reset;
|
|
303
|
+
signal.notify = core.notify;
|
|
304
|
+
signal.on = core.on;
|
|
305
|
+
|
|
306
|
+
// set interceptor
|
|
307
|
+
signal.set = (action: SetAction<T>) => {
|
|
308
|
+
console.log("before set", core().traceSet);
|
|
309
|
+
core.set(action);
|
|
310
|
+
core().traceSet += 1;
|
|
311
|
+
console.log("after set", core().traceSet);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
return signal;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const useHelloCtx = createCtx(
|
|
318
|
+
() => ({ traceSet: 0, text: "hello" }),
|
|
319
|
+
signalWithTraceSet
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
234
323
|
✅ Updating `count` **does NOT re-render** `Book`.
|
|
235
324
|
|
|
236
325
|
---
|
|
237
326
|
|
|
238
327
|
## 🧩 Why This Works
|
|
239
328
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
329
|
+
- Context value never changes
|
|
330
|
+
- React does not re-render on context updates
|
|
331
|
+
- `useSyncExternalStore` compares selected snapshots
|
|
332
|
+
- Only changed selectors trigger re-renders
|
|
244
333
|
|
|
245
334
|
This is the **same model** used by:
|
|
246
335
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
336
|
+
- Redux `useSelector`
|
|
337
|
+
- Zustand selectors
|
|
338
|
+
- React’s official external store docs
|
|
250
339
|
|
|
251
340
|
---
|
|
252
341
|
|
|
@@ -258,13 +347,13 @@ This is the **same model** used by:
|
|
|
258
347
|
❌ Bad:
|
|
259
348
|
|
|
260
349
|
```ts
|
|
261
|
-
const
|
|
350
|
+
const { count } = useAppCtx((s) => s);
|
|
262
351
|
```
|
|
263
352
|
|
|
264
353
|
✅ Good:
|
|
265
354
|
|
|
266
355
|
```ts
|
|
267
|
-
const count =
|
|
356
|
+
const count = useAppCtx((s) => s.count);
|
|
268
357
|
```
|
|
269
358
|
|
|
270
359
|
---
|
|
@@ -275,52 +364,62 @@ You can create isolated stores using `storeName`.
|
|
|
275
364
|
|
|
276
365
|
```tsx
|
|
277
366
|
type Props = {
|
|
278
|
-
children: React.ReactNode
|
|
279
|
-
storeName
|
|
280
|
-
initialValue?: { count: number; book: { title: string } }
|
|
281
|
-
}
|
|
367
|
+
children: React.ReactNode;
|
|
368
|
+
storeName?: string;
|
|
369
|
+
initialValue?: { count: number; book: { title: string } };
|
|
370
|
+
};
|
|
282
371
|
|
|
283
372
|
export function StoreProvider({ children, storeName, initialValue }: Props) {
|
|
284
|
-
|
|
285
|
-
storeName
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return <Provider>{children}</Provider>
|
|
373
|
+
return (
|
|
374
|
+
<useAppCtx.Provider value={initialValue} storeName={storeName}>
|
|
375
|
+
{children}
|
|
376
|
+
</useAppCtx.Provider>
|
|
377
|
+
);
|
|
290
378
|
}
|
|
291
379
|
```
|
|
292
380
|
|
|
293
381
|
### Usage
|
|
382
|
+
|
|
294
383
|
```tsx
|
|
295
|
-
<StoreProvider
|
|
384
|
+
<StoreProvider
|
|
385
|
+
storeName="storeA"
|
|
386
|
+
initialValue={{ count: 1, book: { title: "A" } }}
|
|
387
|
+
>
|
|
388
|
+
{/* useAppCtx(s => s.book) is from storeA */}
|
|
296
389
|
<AppA />
|
|
297
|
-
<StoreProvider
|
|
390
|
+
<StoreProvider
|
|
391
|
+
storeName="storeB"
|
|
392
|
+
initialValue={{ count: 5, book: { title: "B" } }}
|
|
393
|
+
>
|
|
394
|
+
{/* useAppCtx(s => s.book) is from storeB */}
|
|
395
|
+
{/* useAppCtx(s => s.book, { storeName: "storeA" }) is from storeA */}
|
|
298
396
|
<AppB />
|
|
299
397
|
</StoreProvider>
|
|
300
|
-
</StoreProvider
|
|
398
|
+
</StoreProvider>;
|
|
399
|
+
|
|
400
|
+
function AppB() {
|
|
401
|
+
// Read from parent storeB, book.title = "B"
|
|
402
|
+
const currentBook = useAppCtx((s) => s.book); // or useAppCtx(s => s.book, { storeName: "storeB" })
|
|
301
403
|
|
|
302
|
-
|
|
303
|
-
// book from parent context; parent = "storeA" so book.title = "A"
|
|
304
|
-
const book = useStore(s => s.book)
|
|
404
|
+
const layerAbook = useAppCtx((s) => s.book, { storeName: "storeA" }); // book.title = "A"
|
|
305
405
|
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
406
|
+
// AppB want to change data in context StoreA layer
|
|
407
|
+
const setLayerAbook = useAppCtx.useSet((s) => s.book, {
|
|
408
|
+
storeName: "storeA",
|
|
409
|
+
});
|
|
309
410
|
|
|
310
|
-
|
|
311
|
-
|
|
411
|
+
const handleSetLayerABook = (text) => {
|
|
412
|
+
setLayerAbook((b) => {
|
|
413
|
+
if (b.title !== "A") {
|
|
414
|
+
console.error("title in storeA should be A");
|
|
415
|
+
}
|
|
312
416
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return s
|
|
317
|
-
})
|
|
318
|
-
}
|
|
417
|
+
b.title = text;
|
|
418
|
+
});
|
|
419
|
+
};
|
|
319
420
|
}
|
|
320
421
|
```
|
|
321
422
|
|
|
322
|
-
|
|
323
|
-
|
|
324
423
|
Each store is independent.
|
|
325
424
|
|
|
326
425
|
---
|
|
@@ -329,24 +428,24 @@ Each store is independent.
|
|
|
329
428
|
|
|
330
429
|
`Signal Ctx` is SSR-safe.
|
|
331
430
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
431
|
+
- Uses `useSyncExternalStore`
|
|
432
|
+
- Identical snapshot logic on server & client
|
|
433
|
+
- No shared global state between requests
|
|
335
434
|
|
|
336
435
|
---
|
|
337
436
|
|
|
338
437
|
## ⚠️ Caveats
|
|
339
438
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
439
|
+
- No middleware
|
|
440
|
+
- No devtools
|
|
441
|
+
- No persistence
|
|
442
|
+
- Mutation-based updates by design
|
|
344
443
|
|
|
345
444
|
Best suited for:
|
|
346
445
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
446
|
+
- UI state
|
|
447
|
+
- Lightweight global stores
|
|
448
|
+
- flexible shared state
|
|
350
449
|
|
|
351
450
|
---
|
|
352
451
|
|
|
@@ -355,7 +454,7 @@ Best suited for:
|
|
|
355
454
|
Fully typed with generics and inferred selectors.
|
|
356
455
|
|
|
357
456
|
```ts
|
|
358
|
-
const count =
|
|
457
|
+
const count = useAppCtx((s) => s.count); // number
|
|
359
458
|
```
|
|
360
459
|
|
|
361
460
|
---
|
|
@@ -372,8 +471,8 @@ MIT
|
|
|
372
471
|
|
|
373
472
|
It favors:
|
|
374
473
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
474
|
+
- Explicit ownership
|
|
475
|
+
- Predictable updates
|
|
476
|
+
- Minimal abstraction
|
|
378
477
|
|
|
379
478
|
If you understand React, you understand `signalctx`.
|
package/dist/index.d.mts
CHANGED
|
@@ -10,27 +10,20 @@ type Signal<T> = {
|
|
|
10
10
|
reset: () => void;
|
|
11
11
|
set: (action: SetAction<T>) => void;
|
|
12
12
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
reset: () => void;
|
|
17
|
-
set: (action: SetAction<T>) => void;
|
|
18
|
-
$: Signal<T>;
|
|
19
|
-
};
|
|
13
|
+
declare const newSignal: <T extends object>(init: () => T) => Signal<T>;
|
|
14
|
+
declare const useValue: <T extends object, Dest = T>(store: Signal<T>, selector?: (state: T) => Dest) => Dest;
|
|
15
|
+
declare const useSet: <T extends object = any, Dest extends object = T>($: Signal<T>, selector?: (state: T) => Dest) => (action: SetAction<Dest>) => void;
|
|
20
16
|
type StoreOption = {
|
|
21
|
-
|
|
17
|
+
name?: string;
|
|
22
18
|
};
|
|
23
|
-
declare
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Provider: ({ children, value, storeName, }: PropsWithChildren<{
|
|
19
|
+
declare function createCtx<T extends object>(init: () => T, makeSignal?: (init: () => T) => Signal<T>): {
|
|
20
|
+
<Dest extends object = T>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
|
|
21
|
+
useSet<Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption): (action: SetAction<Dest>) => void;
|
|
22
|
+
useSignal<Dest extends object = T>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
|
|
23
|
+
Provider(props: PropsWithChildren<{
|
|
29
24
|
value?: T;
|
|
30
|
-
|
|
31
|
-
}>)
|
|
32
|
-
useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetAction<Dest>) => void;
|
|
33
|
-
useStore: (opt?: StoreOption) => Store<T>;
|
|
25
|
+
name?: string;
|
|
26
|
+
}>): react_jsx_runtime.JSX.Element;
|
|
34
27
|
};
|
|
35
28
|
|
|
36
|
-
export { type SetAction, type Signal, type
|
|
29
|
+
export { type SetAction, type Signal, type Subscriber, createCtx, newSignal, useSet, useValue };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,27 +10,20 @@ type Signal<T> = {
|
|
|
10
10
|
reset: () => void;
|
|
11
11
|
set: (action: SetAction<T>) => void;
|
|
12
12
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
reset: () => void;
|
|
17
|
-
set: (action: SetAction<T>) => void;
|
|
18
|
-
$: Signal<T>;
|
|
19
|
-
};
|
|
13
|
+
declare const newSignal: <T extends object>(init: () => T) => Signal<T>;
|
|
14
|
+
declare const useValue: <T extends object, Dest = T>(store: Signal<T>, selector?: (state: T) => Dest) => Dest;
|
|
15
|
+
declare const useSet: <T extends object = any, Dest extends object = T>($: Signal<T>, selector?: (state: T) => Dest) => (action: SetAction<Dest>) => void;
|
|
20
16
|
type StoreOption = {
|
|
21
|
-
|
|
17
|
+
name?: string;
|
|
22
18
|
};
|
|
23
|
-
declare
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Provider: ({ children, value, storeName, }: PropsWithChildren<{
|
|
19
|
+
declare function createCtx<T extends object>(init: () => T, makeSignal?: (init: () => T) => Signal<T>): {
|
|
20
|
+
<Dest extends object = T>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
|
|
21
|
+
useSet<Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption): (action: SetAction<Dest>) => void;
|
|
22
|
+
useSignal<Dest extends object = T>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
|
|
23
|
+
Provider(props: PropsWithChildren<{
|
|
29
24
|
value?: T;
|
|
30
|
-
|
|
31
|
-
}>)
|
|
32
|
-
useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetAction<Dest>) => void;
|
|
33
|
-
useStore: (opt?: StoreOption) => Store<T>;
|
|
25
|
+
name?: string;
|
|
26
|
+
}>): react_jsx_runtime.JSX.Element;
|
|
34
27
|
};
|
|
35
28
|
|
|
36
|
-
export { type SetAction, type Signal, type
|
|
29
|
+
export { type SetAction, type Signal, type Subscriber, createCtx, newSignal, useSet, useValue };
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
createCtx: () => createCtx,
|
|
24
|
-
|
|
24
|
+
newSignal: () => newSignal,
|
|
25
25
|
useSet: () => useSet,
|
|
26
26
|
useValue: () => useValue
|
|
27
27
|
});
|
|
@@ -31,21 +31,21 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
31
31
|
var newSignal = (init) => {
|
|
32
32
|
let ref;
|
|
33
33
|
const subs = /* @__PURE__ */ new Set();
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
act(
|
|
34
|
+
const signal = () => ref || (ref = init());
|
|
35
|
+
signal.set = (action) => {
|
|
36
|
+
act(signal(), action);
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
signal.reset = () => {
|
|
39
39
|
ref = init();
|
|
40
40
|
};
|
|
41
|
-
|
|
41
|
+
signal.notify = () => {
|
|
42
42
|
subs.forEach((sub) => sub());
|
|
43
43
|
};
|
|
44
|
-
|
|
44
|
+
signal.on = (sub) => {
|
|
45
45
|
subs.add(sub);
|
|
46
46
|
return () => subs.delete(sub);
|
|
47
47
|
};
|
|
48
|
-
return
|
|
48
|
+
return signal;
|
|
49
49
|
};
|
|
50
50
|
var replaceIfDiff = (target, newVal) => {
|
|
51
51
|
if (Object.is(target, newVal)) return;
|
|
@@ -59,89 +59,74 @@ var act = (target, action) => {
|
|
|
59
59
|
replaceIfDiff(target, action);
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
|
-
var newStore = (init) => {
|
|
63
|
-
const $ = newSignal(init);
|
|
64
|
-
const store = () => $();
|
|
65
|
-
store.$ = $;
|
|
66
|
-
store.on = $.on;
|
|
67
|
-
store.reset = () => {
|
|
68
|
-
$.reset();
|
|
69
|
-
$.notify();
|
|
70
|
-
};
|
|
71
|
-
store.set = (action) => {
|
|
72
|
-
$.set(action);
|
|
73
|
-
$.notify();
|
|
74
|
-
};
|
|
75
|
-
return store;
|
|
76
|
-
};
|
|
77
62
|
var useValue = (store, selector) => {
|
|
78
63
|
const get = () => selector ? selector(store()) : store();
|
|
79
64
|
return (0, import_react.useSyncExternalStore)(store.on, get, get);
|
|
80
65
|
};
|
|
81
|
-
var useSet = (
|
|
66
|
+
var useSet = ($, selector) => {
|
|
82
67
|
const setter = (action) => {
|
|
83
|
-
const ref =
|
|
68
|
+
const ref = $();
|
|
84
69
|
const targetRef = selector ? selector(ref) : ref;
|
|
85
70
|
act(targetRef, action);
|
|
86
|
-
|
|
71
|
+
$.notify();
|
|
87
72
|
};
|
|
88
73
|
return setter;
|
|
89
74
|
};
|
|
90
|
-
function createCtx(init) {
|
|
75
|
+
function createCtx(init, makeSignal = (init2) => newSignal(init2)) {
|
|
91
76
|
const ctx = (0, import_react.createContext)(null);
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
if (!name) return
|
|
95
|
-
if (!stores
|
|
96
|
-
|
|
97
|
-
}
|
|
77
|
+
let stores;
|
|
78
|
+
const initSignal = (init2, name) => {
|
|
79
|
+
if (!name) return makeSignal(init2);
|
|
80
|
+
if (!stores) stores = /* @__PURE__ */ new Map();
|
|
81
|
+
if (!stores.has(name)) stores.set(name, makeSignal(init2));
|
|
98
82
|
return stores.get(name);
|
|
99
83
|
};
|
|
100
|
-
const
|
|
84
|
+
const useSignal = (opt) => {
|
|
85
|
+
if (opt?.name) {
|
|
86
|
+
if (!stores || !stores.has(opt.name))
|
|
87
|
+
throw new Error(`<Provider name="${opt.name}" ...> does not exist`);
|
|
88
|
+
return stores.get(opt.name);
|
|
89
|
+
}
|
|
101
90
|
const store = (0, import_react.useContext)(ctx);
|
|
102
|
-
if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
|
|
103
91
|
if (!store) throw new Error("useCtx must be used within a Provider");
|
|
104
92
|
return store;
|
|
105
93
|
};
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
94
|
+
const useSelect = (selector, opt) => {
|
|
95
|
+
const store = useSignal(opt);
|
|
96
|
+
return useValue(store, selector);
|
|
97
|
+
};
|
|
98
|
+
useSelect.useSet = (selector, opt) => {
|
|
99
|
+
const store = useSignal(opt);
|
|
100
|
+
return useSet(store, selector);
|
|
101
|
+
};
|
|
102
|
+
useSelect.useSignal = (selector, opt) => {
|
|
103
|
+
const store = useSignal(opt);
|
|
104
|
+
return useValue(store, selector);
|
|
105
|
+
};
|
|
106
|
+
useSelect.Provider = (props) => {
|
|
107
|
+
const [signal, name] = (0, import_react.useMemo)(() => {
|
|
108
|
+
const initor = props.value ? () => props.value : init;
|
|
109
|
+
const _signal = initSignal(initor, props.name);
|
|
110
|
+
return [_signal, props.name];
|
|
111
|
+
}, [props.value, props.name]);
|
|
116
112
|
(0, import_react.useEffect)(
|
|
117
113
|
() => () => {
|
|
118
114
|
if (name) stores.delete(name);
|
|
119
115
|
},
|
|
120
|
-
[
|
|
116
|
+
[signal, name]
|
|
121
117
|
);
|
|
122
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ctx.Provider, { value:
|
|
118
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ctx.Provider, { value: signal, children: [
|
|
123
119
|
" ",
|
|
124
|
-
children,
|
|
120
|
+
props.children,
|
|
125
121
|
" "
|
|
126
122
|
] });
|
|
127
123
|
};
|
|
128
|
-
const useSelect = (selector, opt) => {
|
|
129
|
-
const store = useStore(opt);
|
|
130
|
-
return useValue(store, selector);
|
|
131
|
-
};
|
|
132
|
-
const useSetter = (selector, opt) => {
|
|
133
|
-
const store = useStore(opt);
|
|
134
|
-
return useSet(store, selector);
|
|
135
|
-
};
|
|
136
|
-
useSelect.Provider = Provider;
|
|
137
|
-
useSelect.useSet = useSetter;
|
|
138
|
-
useSelect.useStore = useStore;
|
|
139
124
|
return useSelect;
|
|
140
125
|
}
|
|
141
126
|
// Annotate the CommonJS export names for ESM import in node:
|
|
142
127
|
0 && (module.exports = {
|
|
143
128
|
createCtx,
|
|
144
|
-
|
|
129
|
+
newSignal,
|
|
145
130
|
useSet,
|
|
146
131
|
useValue
|
|
147
132
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -10,21 +10,21 @@ import { jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
var newSignal = (init) => {
|
|
11
11
|
let ref;
|
|
12
12
|
const subs = /* @__PURE__ */ new Set();
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
act(
|
|
13
|
+
const signal = () => ref || (ref = init());
|
|
14
|
+
signal.set = (action) => {
|
|
15
|
+
act(signal(), action);
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
signal.reset = () => {
|
|
18
18
|
ref = init();
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
signal.notify = () => {
|
|
21
21
|
subs.forEach((sub) => sub());
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
signal.on = (sub) => {
|
|
24
24
|
subs.add(sub);
|
|
25
25
|
return () => subs.delete(sub);
|
|
26
26
|
};
|
|
27
|
-
return
|
|
27
|
+
return signal;
|
|
28
28
|
};
|
|
29
29
|
var replaceIfDiff = (target, newVal) => {
|
|
30
30
|
if (Object.is(target, newVal)) return;
|
|
@@ -38,88 +38,73 @@ var act = (target, action) => {
|
|
|
38
38
|
replaceIfDiff(target, action);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
var newStore = (init) => {
|
|
42
|
-
const $ = newSignal(init);
|
|
43
|
-
const store = () => $();
|
|
44
|
-
store.$ = $;
|
|
45
|
-
store.on = $.on;
|
|
46
|
-
store.reset = () => {
|
|
47
|
-
$.reset();
|
|
48
|
-
$.notify();
|
|
49
|
-
};
|
|
50
|
-
store.set = (action) => {
|
|
51
|
-
$.set(action);
|
|
52
|
-
$.notify();
|
|
53
|
-
};
|
|
54
|
-
return store;
|
|
55
|
-
};
|
|
56
41
|
var useValue = (store, selector) => {
|
|
57
42
|
const get = () => selector ? selector(store()) : store();
|
|
58
43
|
return useSyncExternalStore(store.on, get, get);
|
|
59
44
|
};
|
|
60
|
-
var useSet = (
|
|
45
|
+
var useSet = ($, selector) => {
|
|
61
46
|
const setter = (action) => {
|
|
62
|
-
const ref =
|
|
47
|
+
const ref = $();
|
|
63
48
|
const targetRef = selector ? selector(ref) : ref;
|
|
64
49
|
act(targetRef, action);
|
|
65
|
-
|
|
50
|
+
$.notify();
|
|
66
51
|
};
|
|
67
52
|
return setter;
|
|
68
53
|
};
|
|
69
|
-
function createCtx(init) {
|
|
54
|
+
function createCtx(init, makeSignal = (init2) => newSignal(init2)) {
|
|
70
55
|
const ctx = createContext(null);
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
if (!name) return
|
|
74
|
-
if (!stores
|
|
75
|
-
|
|
76
|
-
}
|
|
56
|
+
let stores;
|
|
57
|
+
const initSignal = (init2, name) => {
|
|
58
|
+
if (!name) return makeSignal(init2);
|
|
59
|
+
if (!stores) stores = /* @__PURE__ */ new Map();
|
|
60
|
+
if (!stores.has(name)) stores.set(name, makeSignal(init2));
|
|
77
61
|
return stores.get(name);
|
|
78
62
|
};
|
|
79
|
-
const
|
|
63
|
+
const useSignal = (opt) => {
|
|
64
|
+
if (opt?.name) {
|
|
65
|
+
if (!stores || !stores.has(opt.name))
|
|
66
|
+
throw new Error(`<Provider name="${opt.name}" ...> does not exist`);
|
|
67
|
+
return stores.get(opt.name);
|
|
68
|
+
}
|
|
80
69
|
const store = useContext(ctx);
|
|
81
|
-
if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
|
|
82
70
|
if (!store) throw new Error("useCtx must be used within a Provider");
|
|
83
71
|
return store;
|
|
84
72
|
};
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
const useSelect = (selector, opt) => {
|
|
74
|
+
const store = useSignal(opt);
|
|
75
|
+
return useValue(store, selector);
|
|
76
|
+
};
|
|
77
|
+
useSelect.useSet = (selector, opt) => {
|
|
78
|
+
const store = useSignal(opt);
|
|
79
|
+
return useSet(store, selector);
|
|
80
|
+
};
|
|
81
|
+
useSelect.useSignal = (selector, opt) => {
|
|
82
|
+
const store = useSignal(opt);
|
|
83
|
+
return useValue(store, selector);
|
|
84
|
+
};
|
|
85
|
+
useSelect.Provider = (props) => {
|
|
86
|
+
const [signal, name] = useMemo(() => {
|
|
87
|
+
const initor = props.value ? () => props.value : init;
|
|
88
|
+
const _signal = initSignal(initor, props.name);
|
|
89
|
+
return [_signal, props.name];
|
|
90
|
+
}, [props.value, props.name]);
|
|
95
91
|
useEffect(
|
|
96
92
|
() => () => {
|
|
97
93
|
if (name) stores.delete(name);
|
|
98
94
|
},
|
|
99
|
-
[
|
|
95
|
+
[signal, name]
|
|
100
96
|
);
|
|
101
|
-
return /* @__PURE__ */ jsxs(ctx.Provider, { value:
|
|
97
|
+
return /* @__PURE__ */ jsxs(ctx.Provider, { value: signal, children: [
|
|
102
98
|
" ",
|
|
103
|
-
children,
|
|
99
|
+
props.children,
|
|
104
100
|
" "
|
|
105
101
|
] });
|
|
106
102
|
};
|
|
107
|
-
const useSelect = (selector, opt) => {
|
|
108
|
-
const store = useStore(opt);
|
|
109
|
-
return useValue(store, selector);
|
|
110
|
-
};
|
|
111
|
-
const useSetter = (selector, opt) => {
|
|
112
|
-
const store = useStore(opt);
|
|
113
|
-
return useSet(store, selector);
|
|
114
|
-
};
|
|
115
|
-
useSelect.Provider = Provider;
|
|
116
|
-
useSelect.useSet = useSetter;
|
|
117
|
-
useSelect.useStore = useStore;
|
|
118
103
|
return useSelect;
|
|
119
104
|
}
|
|
120
105
|
export {
|
|
121
106
|
createCtx,
|
|
122
|
-
|
|
107
|
+
newSignal,
|
|
123
108
|
useSet,
|
|
124
109
|
useValue
|
|
125
110
|
};
|