@thefoxieflow/signalctx 0.1.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 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
- { count, book }
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
- * Moves state **outside React**
38
- * Uses **external subscriptions**
39
- * Allows **selector-based updates**
39
+ - Moves state **outside React**
40
+ - Uses **external subscriptions**
41
+ - Allows **selector-based updates**
40
42
 
41
- So only the components that *actually use* the changed data re-render.
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
- * ⚡ Signal-style state container
48
- * 🎯 Selector-based subscriptions
49
- * 🧵 React 18 concurrent-safe
50
- * 🧩 Context-backed but not context-driven
51
- * 📦 Very small bundle size
52
- * 🌳 Tree-shakable
53
- * 🧠 Explicit and predictable
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 Store
83
+ ## 🔹 Signal
82
84
 
83
85
  A signal is:
84
86
 
85
- * A function that returns state
86
- * Can be subscribed to
87
- * Can be updated imperatively
87
+ - A function that returns state
88
+ - Can be subscribed to
89
+ - Can be updated imperatively
88
90
 
89
91
  ```ts
90
- type Store<T> = {
91
- (): T
92
- subscribe(fn: () => void): () => void
93
- set(action: SetStateAction<T>): void
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 Hooks
111
+ ## 🔹 Low-Level Functions
112
+
113
+ ### `newSignal(init)`
100
114
 
101
- ### `createStore(()=>{})`
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
- * Uses `useSyncExternalStore`
112
- * Re-renders only when the selected value changes
113
- * Selector is optional
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
- set(prev => ({ ...prev, count: prev.count + 1 }))
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 = useSetSignal(store, s => s.book)
132
-
133
-
134
- // put: update an object
135
- setBook({
136
- title : "book"
137
- })
138
-
139
- // patch: update partially
140
- setBook((b)=>{
141
- b.title = "book"
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 "@your-scope/signal-ctx"
202
+ import { createCtx } from "@thefoxieflow/signalctx";
157
203
 
158
- export const useStore = createCtx(() => ({
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 useStore
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
- const Provider = useStore.provide()
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
- const Provider = useStore.provide({
184
- value: {
185
- count: 10,
186
- book: { title: "Brave New World" }
187
- }
188
- })
189
-
190
- return <Provider>{children}</Provider>
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 = useStore(s => s.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 = useStore(s => s.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 set = useStore.useSetter()
276
+ const setCount = useAppCtx.useSet((s) => s);
223
277
 
224
278
  return (
225
- <button onClick={() =>
226
- set(s => ({ ...s, count: s.count + 1 }))
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
- * Context value never changes
241
- * React does not re-render on context updates
242
- * `useSyncExternalStore` compares selected snapshots
243
- * Only changed selectors trigger re-renders
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
- * Redux `useSelector`
248
- * Zustand selectors
249
- * React’s official external store docs
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 state = useStore(s => s)
350
+ const { count } = useAppCtx((s) => s);
262
351
  ```
263
352
 
264
353
  ✅ Good:
265
354
 
266
355
  ```ts
267
- const count = useStore(s => s.count)
356
+ const count = useAppCtx((s) => s.count);
268
357
  ```
269
358
 
270
359
  ---
@@ -275,78 +364,88 @@ You can create isolated stores using `storeName`.
275
364
 
276
365
  ```tsx
277
366
  type Props = {
278
- children: React.ReactNode
279
- storeName: string
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
- const Provider = useStore.provide({
285
- storeName,
286
- value: initialValue || { count: 0, book: { title: "1984" } }
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 storeName="storeA" initialValue={{ count: 1, book: { title: "A" } }}>
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 storeName="storeB" initialValue={{ count: 5, book: { title: "B" } }}>
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
- function AppB(){
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
- // but i want a book from storeA; so it requires storeName
307
- // book.title = "B"
308
- const storeABook = useStore(s => s.book,{ storeName :"storeA" })
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
- // same apply to setter
311
- const setBookStoreA = useStore.useSetter((s)=>s.book, {storeName : "storeA"})
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
- const handleClick = (text)=>{
314
- setCountStoreA((s) => {
315
- s.title = "change A to AA"
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
  ---
327
426
 
328
427
  ## 🌐 Server-Side Rendering (SSR)
329
428
 
330
- `signalctx` is SSR-safe.
429
+ `Signal Ctx` is SSR-safe.
331
430
 
332
- * Uses `useSyncExternalStore`
333
- * Identical snapshot logic on server & client
334
- * No shared global state between requests
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
- * No middleware
341
- * No devtools
342
- * No persistence
343
- * Mutation-based updates by design
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
- * UI state
348
- * App-level shared state
349
- * Lightweight global stores
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 = useStore(s => s.count) // number
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
- * Explicit ownership
376
- * Predictable updates
377
- * Minimal abstraction
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
@@ -1,49 +1,29 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PropsWithChildren } from 'react';
3
3
 
4
- type SetStateAction<T> = T | ((prev: T) => T);
4
+ type SetAction<T> = T | ((prev: T) => void);
5
5
  type Subscriber = () => void;
6
- /** for controlling store internal state without notifying subscribers
7
- * @function calling the store internal state reference
8
- * @method on subscribe to state changes
9
- * @method set update the state without notifying subscribers
10
- * @method reset reset the state to initial value without notifying subscribers
11
- * @method notify manually notify all subscribers
12
- */
13
- type InnerStore<T> = {
6
+ type Signal<T> = {
14
7
  (): T;
15
8
  on: (sub: Subscriber) => () => void;
16
9
  notify: () => void;
17
10
  reset: () => void;
18
- set: (action: SetStateAction<T>) => void;
11
+ set: (action: SetAction<T>) => void;
19
12
  };
20
- /**
21
- * @function calling the store returns a copy of the internal state
22
- * @method on subscribe to state changes
23
- * @method set update the state with notifying subscribers
24
- * @method reset reset the state to initial value with notifying subscribers
25
- * @property $ access to non-notifying methods
26
- */
27
- type Store<T> = {
28
- (): T;
29
- on: (sub: Subscriber) => () => void;
30
- reset: () => void;
31
- set: (action: SetStateAction<T>) => void;
32
- $: InnerStore<T>;
33
- };
34
- declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
35
- declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetStateAction<Dest>) => Dest;
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;
36
16
  type StoreOption = {
37
- storeName?: string;
17
+ name?: string;
38
18
  };
39
- declare function createCtx<T extends object>(init: () => T): {
40
- <Dest = any>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
41
- provide: (opt?: {
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<{
42
24
  value?: T;
43
- storeName?: string;
44
- }) => ({ children }: PropsWithChildren) => react_jsx_runtime.JSX.Element;
45
- useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetStateAction<Dest>) => Dest;
46
- useStore: (opt?: StoreOption) => Store<T>;
25
+ name?: string;
26
+ }>): react_jsx_runtime.JSX.Element;
47
27
  };
48
28
 
49
- export { type InnerStore, type SetStateAction, type Store, type Subscriber, createCtx, useSet, useValue };
29
+ export { type SetAction, type Signal, type Subscriber, createCtx, newSignal, useSet, useValue };
package/dist/index.d.ts CHANGED
@@ -1,49 +1,29 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PropsWithChildren } from 'react';
3
3
 
4
- type SetStateAction<T> = T | ((prev: T) => T);
4
+ type SetAction<T> = T | ((prev: T) => void);
5
5
  type Subscriber = () => void;
6
- /** for controlling store internal state without notifying subscribers
7
- * @function calling the store internal state reference
8
- * @method on subscribe to state changes
9
- * @method set update the state without notifying subscribers
10
- * @method reset reset the state to initial value without notifying subscribers
11
- * @method notify manually notify all subscribers
12
- */
13
- type InnerStore<T> = {
6
+ type Signal<T> = {
14
7
  (): T;
15
8
  on: (sub: Subscriber) => () => void;
16
9
  notify: () => void;
17
10
  reset: () => void;
18
- set: (action: SetStateAction<T>) => void;
11
+ set: (action: SetAction<T>) => void;
19
12
  };
20
- /**
21
- * @function calling the store returns a copy of the internal state
22
- * @method on subscribe to state changes
23
- * @method set update the state with notifying subscribers
24
- * @method reset reset the state to initial value with notifying subscribers
25
- * @property $ access to non-notifying methods
26
- */
27
- type Store<T> = {
28
- (): T;
29
- on: (sub: Subscriber) => () => void;
30
- reset: () => void;
31
- set: (action: SetStateAction<T>) => void;
32
- $: InnerStore<T>;
33
- };
34
- declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
35
- declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetStateAction<Dest>) => Dest;
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;
36
16
  type StoreOption = {
37
- storeName?: string;
17
+ name?: string;
38
18
  };
39
- declare function createCtx<T extends object>(init: () => T): {
40
- <Dest = any>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
41
- provide: (opt?: {
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<{
42
24
  value?: T;
43
- storeName?: string;
44
- }) => ({ children }: PropsWithChildren) => react_jsx_runtime.JSX.Element;
45
- useSet: <Dest extends object = T>(selector?: (state: T) => Dest, opt?: StoreOption) => (action: SetStateAction<Dest>) => Dest;
46
- useStore: (opt?: StoreOption) => Store<T>;
25
+ name?: string;
26
+ }>): react_jsx_runtime.JSX.Element;
47
27
  };
48
28
 
49
- export { type InnerStore, type SetStateAction, type Store, type Subscriber, createCtx, useSet, useValue };
29
+ export { type SetAction, type Signal, type Subscriber, createCtx, newSignal, useSet, useValue };
package/dist/index.js CHANGED
@@ -21,115 +21,112 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  createCtx: () => createCtx,
24
+ newSignal: () => newSignal,
24
25
  useSet: () => useSet,
25
26
  useValue: () => useValue
26
27
  });
27
28
  module.exports = __toCommonJS(index_exports);
28
29
  var import_react = require("react");
29
30
  var import_jsx_runtime = require("react/jsx-runtime");
30
- var createInnerStore = (init) => {
31
+ var newSignal = (init) => {
31
32
  let ref;
32
33
  const subs = /* @__PURE__ */ new Set();
33
- const store = () => ref || (ref = init());
34
- store.set = (action) => {
35
- apply(store(), action);
34
+ const signal = () => ref || (ref = init());
35
+ signal.set = (action) => {
36
+ act(signal(), action);
36
37
  };
37
- store.reset = () => {
38
+ signal.reset = () => {
38
39
  ref = init();
39
40
  };
40
- store.notify = () => {
41
+ signal.notify = () => {
41
42
  subs.forEach((sub) => sub());
42
43
  };
43
- store.on = (sub) => {
44
+ signal.on = (sub) => {
44
45
  subs.add(sub);
45
46
  return () => subs.delete(sub);
46
47
  };
47
- return store;
48
+ return signal;
48
49
  };
49
- var isFunction = (val) => typeof val === "function";
50
- var setTargetValue = (target, newVal) => {
51
- if (Object.is(target, newVal)) return target;
50
+ var replaceIfDiff = (target, newVal) => {
51
+ if (Object.is(target, newVal)) return;
52
52
  Object.keys(target).forEach((k) => delete target[k]);
53
53
  Object.keys(newVal).forEach((k) => target[k] = newVal[k]);
54
- return target;
55
54
  };
56
- var apply = (target, action) => {
57
- const val = isFunction(action) ? action(target) : action;
58
- return setTargetValue(target, val);
55
+ var act = (target, action) => {
56
+ if (typeof action === "function") {
57
+ action(target);
58
+ } else {
59
+ replaceIfDiff(target, action);
60
+ }
59
61
  };
60
- function createStore(init) {
61
- const $ = createInnerStore(init);
62
- const store = () => {
63
- const ref = $();
64
- return { ...ref };
65
- };
66
- store.reset = () => {
67
- $.reset();
68
- $.notify();
69
- };
70
- store.set = (action) => {
71
- $.set(action);
72
- $.notify();
73
- };
74
- store.on = $.on;
75
- store.$ = $;
76
- return store;
77
- }
78
62
  var useValue = (store, selector) => {
79
63
  const get = () => selector ? selector(store()) : store();
80
64
  return (0, import_react.useSyncExternalStore)(store.on, get, get);
81
65
  };
82
- var useSet = (store, selector) => {
66
+ var useSet = ($, selector) => {
83
67
  const setter = (action) => {
84
- const current = store();
85
- const target = selector ? selector(current) : current;
86
- apply(target, action);
87
- store.set(current);
88
- return target;
68
+ const ref = $();
69
+ const targetRef = selector ? selector(ref) : ref;
70
+ act(targetRef, action);
71
+ $.notify();
89
72
  };
90
73
  return setter;
91
74
  };
92
- function createCtx(init) {
93
- const ctx = (0, import_react.createContext)(createStore(init));
94
- const stores = /* @__PURE__ */ new Map();
95
- const initStore = (name, init2) => {
96
- if (!stores.has(name)) stores.set(name, createStore(init2));
75
+ function createCtx(init, makeSignal = (init2) => newSignal(init2)) {
76
+ const ctx = (0, import_react.createContext)(null);
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));
97
82
  return stores.get(name);
98
83
  };
99
- const useStore = (opt) => {
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
+ }
100
90
  const store = (0, import_react.useContext)(ctx);
101
- if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
102
91
  if (!store) throw new Error("useCtx must be used within a Provider");
103
92
  return store;
104
93
  };
105
- const provide = (opt = {}) => {
106
- const storeInit = opt.value ? () => opt.value : init;
107
- const storeName = opt.storeName || "ctx__store";
108
- const Provider = ({ children }) => {
109
- const store = initStore(storeName, storeInit);
110
- (0, import_react.useEffect)(() => () => {
111
- stores.delete(storeName);
112
- }, []);
113
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ctx.Provider, { value: store, children });
114
- };
115
- return Provider;
116
- };
117
- const use = (selector, opt) => {
118
- const store = useStore(opt);
94
+ const useSelect = (selector, opt) => {
95
+ const store = useSignal(opt);
119
96
  return useValue(store, selector);
120
97
  };
121
- const useSetter = (selector, opt) => {
122
- const store = useStore(opt);
98
+ useSelect.useSet = (selector, opt) => {
99
+ const store = useSignal(opt);
123
100
  return useSet(store, selector);
124
101
  };
125
- use.provide = provide;
126
- use.useSet = useSetter;
127
- use.useStore = useStore;
128
- return use;
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]);
112
+ (0, import_react.useEffect)(
113
+ () => () => {
114
+ if (name) stores.delete(name);
115
+ },
116
+ [signal, name]
117
+ );
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ctx.Provider, { value: signal, children: [
119
+ " ",
120
+ props.children,
121
+ " "
122
+ ] });
123
+ };
124
+ return useSelect;
129
125
  }
130
126
  // Annotate the CommonJS export names for ESM import in node:
131
127
  0 && (module.exports = {
132
128
  createCtx,
129
+ newSignal,
133
130
  useSet,
134
131
  useValue
135
132
  });
package/dist/index.mjs CHANGED
@@ -3,111 +3,108 @@ import {
3
3
  createContext,
4
4
  useContext,
5
5
  useEffect,
6
+ useMemo,
6
7
  useSyncExternalStore
7
8
  } from "react";
8
- import { jsx } from "react/jsx-runtime";
9
- var createInnerStore = (init) => {
9
+ import { jsxs } from "react/jsx-runtime";
10
+ var newSignal = (init) => {
10
11
  let ref;
11
12
  const subs = /* @__PURE__ */ new Set();
12
- const store = () => ref || (ref = init());
13
- store.set = (action) => {
14
- apply(store(), action);
13
+ const signal = () => ref || (ref = init());
14
+ signal.set = (action) => {
15
+ act(signal(), action);
15
16
  };
16
- store.reset = () => {
17
+ signal.reset = () => {
17
18
  ref = init();
18
19
  };
19
- store.notify = () => {
20
+ signal.notify = () => {
20
21
  subs.forEach((sub) => sub());
21
22
  };
22
- store.on = (sub) => {
23
+ signal.on = (sub) => {
23
24
  subs.add(sub);
24
25
  return () => subs.delete(sub);
25
26
  };
26
- return store;
27
+ return signal;
27
28
  };
28
- var isFunction = (val) => typeof val === "function";
29
- var setTargetValue = (target, newVal) => {
30
- if (Object.is(target, newVal)) return target;
29
+ var replaceIfDiff = (target, newVal) => {
30
+ if (Object.is(target, newVal)) return;
31
31
  Object.keys(target).forEach((k) => delete target[k]);
32
32
  Object.keys(newVal).forEach((k) => target[k] = newVal[k]);
33
- return target;
34
33
  };
35
- var apply = (target, action) => {
36
- const val = isFunction(action) ? action(target) : action;
37
- return setTargetValue(target, val);
34
+ var act = (target, action) => {
35
+ if (typeof action === "function") {
36
+ action(target);
37
+ } else {
38
+ replaceIfDiff(target, action);
39
+ }
38
40
  };
39
- function createStore(init) {
40
- const $ = createInnerStore(init);
41
- const store = () => {
42
- const ref = $();
43
- return { ...ref };
44
- };
45
- store.reset = () => {
46
- $.reset();
47
- $.notify();
48
- };
49
- store.set = (action) => {
50
- $.set(action);
51
- $.notify();
52
- };
53
- store.on = $.on;
54
- store.$ = $;
55
- return store;
56
- }
57
41
  var useValue = (store, selector) => {
58
42
  const get = () => selector ? selector(store()) : store();
59
43
  return useSyncExternalStore(store.on, get, get);
60
44
  };
61
- var useSet = (store, selector) => {
45
+ var useSet = ($, selector) => {
62
46
  const setter = (action) => {
63
- const current = store();
64
- const target = selector ? selector(current) : current;
65
- apply(target, action);
66
- store.set(current);
67
- return target;
47
+ const ref = $();
48
+ const targetRef = selector ? selector(ref) : ref;
49
+ act(targetRef, action);
50
+ $.notify();
68
51
  };
69
52
  return setter;
70
53
  };
71
- function createCtx(init) {
72
- const ctx = createContext(createStore(init));
73
- const stores = /* @__PURE__ */ new Map();
74
- const initStore = (name, init2) => {
75
- if (!stores.has(name)) stores.set(name, createStore(init2));
54
+ function createCtx(init, makeSignal = (init2) => newSignal(init2)) {
55
+ const ctx = createContext(null);
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));
76
61
  return stores.get(name);
77
62
  };
78
- const useStore = (opt) => {
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
+ }
79
69
  const store = useContext(ctx);
80
- if (opt?.storeName && stores.has(opt.storeName)) return stores.get(opt.storeName);
81
70
  if (!store) throw new Error("useCtx must be used within a Provider");
82
71
  return store;
83
72
  };
84
- const provide = (opt = {}) => {
85
- const storeInit = opt.value ? () => opt.value : init;
86
- const storeName = opt.storeName || "ctx__store";
87
- const Provider = ({ children }) => {
88
- const store = initStore(storeName, storeInit);
89
- useEffect(() => () => {
90
- stores.delete(storeName);
91
- }, []);
92
- return /* @__PURE__ */ jsx(ctx.Provider, { value: store, children });
93
- };
94
- return Provider;
95
- };
96
- const use = (selector, opt) => {
97
- const store = useStore(opt);
73
+ const useSelect = (selector, opt) => {
74
+ const store = useSignal(opt);
98
75
  return useValue(store, selector);
99
76
  };
100
- const useSetter = (selector, opt) => {
101
- const store = useStore(opt);
77
+ useSelect.useSet = (selector, opt) => {
78
+ const store = useSignal(opt);
102
79
  return useSet(store, selector);
103
80
  };
104
- use.provide = provide;
105
- use.useSet = useSetter;
106
- use.useStore = useStore;
107
- return use;
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]);
91
+ useEffect(
92
+ () => () => {
93
+ if (name) stores.delete(name);
94
+ },
95
+ [signal, name]
96
+ );
97
+ return /* @__PURE__ */ jsxs(ctx.Provider, { value: signal, children: [
98
+ " ",
99
+ props.children,
100
+ " "
101
+ ] });
102
+ };
103
+ return useSelect;
108
104
  }
109
105
  export {
110
106
  createCtx,
107
+ newSignal,
111
108
  useSet,
112
109
  useValue
113
110
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thefoxieflow/signalctx",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Lightweight signal-based React context store",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",