@thefoxieflow/signalctx 0.1.1 → 1.0.1
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 +227 -131
- 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)`
|
|
114
|
+
|
|
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
|
+
```
|
|
100
131
|
|
|
101
|
-
|
|
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);
|
|
156
|
+
|
|
157
|
+
// update entire state
|
|
158
|
+
const prev = store();
|
|
159
|
+
set({ ...prev, count: prev.count + 1 });
|
|
123
160
|
|
|
124
|
-
|
|
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 = newSignal(() => ({
|
|
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.useSignal(options)`** - Hook to access raw signal underlying the context
|
|
216
|
+
|
|
164
217
|
---
|
|
165
218
|
|
|
166
219
|
## 🚀 Usage
|
|
@@ -168,33 +221,34 @@ 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
|
-
export function
|
|
177
|
-
|
|
178
|
-
return <Provider>{children}</Provider>
|
|
229
|
+
export function AppCtxProvider({ children }: Props) {
|
|
230
|
+
return <useAppCtx.Provider>{children}</useAppCtx.Provider>;
|
|
179
231
|
}
|
|
180
232
|
|
|
181
233
|
// overwrite value
|
|
182
|
-
export function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
234
|
+
export function AppCtxProvider({ children }: Props) {
|
|
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
|
|
|
194
248
|
```tsx
|
|
195
|
-
<
|
|
249
|
+
<AppCtxProvider>
|
|
196
250
|
<App />
|
|
197
|
-
</
|
|
251
|
+
</AppCtxProvider>
|
|
198
252
|
```
|
|
199
253
|
|
|
200
254
|
---
|
|
@@ -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,68 +347,75 @@ 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
|
---
|
|
271
360
|
|
|
272
361
|
## 🧩 Multiple Stores
|
|
273
362
|
|
|
274
|
-
You can create isolated stores using `
|
|
363
|
+
You can create isolated stores using `name`.
|
|
275
364
|
|
|
276
365
|
```tsx
|
|
277
366
|
type Props = {
|
|
278
|
-
children: React.ReactNode
|
|
279
|
-
|
|
280
|
-
initialValue?: { count: number; book: { title: string } }
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function StoreProvider({ children, storeName, initialValue }: Props) {
|
|
284
|
-
const Provider = useStore.provide({
|
|
285
|
-
storeName,
|
|
286
|
-
value: initialValue || { count: 0, book: { title: "1984" } }
|
|
287
|
-
})
|
|
367
|
+
children: React.ReactNode;
|
|
368
|
+
name?: string;
|
|
369
|
+
initialValue?: { count: number; book: { title: string } };
|
|
370
|
+
};
|
|
288
371
|
|
|
289
|
-
|
|
372
|
+
export function AppCtxProvider({ children, name, initialValue }: Props) {
|
|
373
|
+
return (
|
|
374
|
+
<useAppCtx.Provider value={initialValue} name={name}>
|
|
375
|
+
{children}
|
|
376
|
+
</useAppCtx.Provider>
|
|
377
|
+
);
|
|
290
378
|
}
|
|
291
379
|
```
|
|
292
380
|
|
|
293
381
|
### Usage
|
|
382
|
+
|
|
294
383
|
```tsx
|
|
295
|
-
<
|
|
384
|
+
<AppCtxProvider name="storeA" initialValue={{ count: 1, book: { title: "A" } }}>
|
|
385
|
+
{/* useAppCtx(s => s.book) is from storeA */}
|
|
296
386
|
<AppA />
|
|
297
|
-
<
|
|
387
|
+
<AppCtxProvider
|
|
388
|
+
name="storeB"
|
|
389
|
+
initialValue={{ count: 5, book: { title: "B" } }}
|
|
390
|
+
>
|
|
391
|
+
{/* useAppCtx(s => s.book) is from storeB */}
|
|
392
|
+
{/* useAppCtx(s => s.book, { name: "storeA" }) is from storeA */}
|
|
298
393
|
<AppB />
|
|
299
|
-
</
|
|
300
|
-
</
|
|
301
|
-
|
|
302
|
-
function AppB(){
|
|
303
|
-
//
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
// but i want a book from storeA; so it requires storeName
|
|
307
|
-
// book.title = "B"
|
|
308
|
-
const storeABook = useStore(s => s.book,{ storeName :"storeA" })
|
|
309
|
-
|
|
310
|
-
// same apply to setter
|
|
311
|
-
const setBookStoreA = useStore.useSet((s)=>s.book, {storeName : "storeA"})
|
|
312
|
-
|
|
313
|
-
const handleClick = (text)=>{
|
|
314
|
-
setCountStoreA((s) => {
|
|
315
|
-
s.title = "change A to AA"
|
|
316
|
-
return s
|
|
317
|
-
})
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
```
|
|
394
|
+
</AppCtxProvider>
|
|
395
|
+
</AppCtxProvider>;
|
|
396
|
+
|
|
397
|
+
function AppB() {
|
|
398
|
+
// Read from parent storeB, book.title = "B"
|
|
399
|
+
const currentBook = useAppCtx((s) => s.book); // or useAppCtx(s => s.book, { name: "storeB" })
|
|
321
400
|
|
|
401
|
+
const layerAbook = useAppCtx((s) => s.book, { name: "storeA" }); // book.title = "A"
|
|
322
402
|
|
|
403
|
+
// AppB want to change data in context StoreA layer
|
|
404
|
+
const setLayerAbook = useAppCtx.useSet((s) => s.book, {
|
|
405
|
+
name: "storeA",
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const handleSetLayerABook = (text) => {
|
|
409
|
+
setLayerAbook((b) => {
|
|
410
|
+
if (b.title !== "A") {
|
|
411
|
+
console.error("title in storeA should be A");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
b.title = text;
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
```
|
|
323
419
|
|
|
324
420
|
Each store is independent.
|
|
325
421
|
|
|
@@ -329,24 +425,24 @@ Each store is independent.
|
|
|
329
425
|
|
|
330
426
|
`Signal Ctx` is SSR-safe.
|
|
331
427
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
428
|
+
- Uses `useSyncExternalStore`
|
|
429
|
+
- Identical snapshot logic on server & client
|
|
430
|
+
- No shared global state between requests
|
|
335
431
|
|
|
336
432
|
---
|
|
337
433
|
|
|
338
434
|
## ⚠️ Caveats
|
|
339
435
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
436
|
+
- No middleware
|
|
437
|
+
- No devtools
|
|
438
|
+
- No persistence
|
|
439
|
+
- Mutation-based updates by design
|
|
344
440
|
|
|
345
441
|
Best suited for:
|
|
346
442
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
443
|
+
- UI state
|
|
444
|
+
- Lightweight global stores
|
|
445
|
+
- flexible shared state
|
|
350
446
|
|
|
351
447
|
---
|
|
352
448
|
|
|
@@ -355,7 +451,7 @@ Best suited for:
|
|
|
355
451
|
Fully typed with generics and inferred selectors.
|
|
356
452
|
|
|
357
453
|
```ts
|
|
358
|
-
const count =
|
|
454
|
+
const count = useAppCtx((s) => s.count); // number
|
|
359
455
|
```
|
|
360
456
|
|
|
361
457
|
---
|
|
@@ -372,8 +468,8 @@ MIT
|
|
|
372
468
|
|
|
373
469
|
It favors:
|
|
374
470
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
471
|
+
- Explicit ownership
|
|
472
|
+
- Predictable updates
|
|
473
|
+
- Minimal abstraction
|
|
378
474
|
|
|
379
475
|
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
|
};
|