@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 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)`
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
- ### `createStore(()=>{})`
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);
156
+
157
+ // update entire state
158
+ const prev = store();
159
+ set({ ...prev, count: prev.count + 1 });
123
160
 
124
- set(prev => ({ ...prev, count: prev.count + 1 }))
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
- // 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.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 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
- export function StoreProvider({ children }: Props) {
177
- const Provider = useStore.provide()
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 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>
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
- <StoreProvider>
249
+ <AppCtxProvider>
196
250
  <App />
197
- </StoreProvider>
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 = 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.useSet()
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,68 +347,75 @@ 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
  ---
271
360
 
272
361
  ## 🧩 Multiple Stores
273
362
 
274
- You can create isolated stores using `storeName`.
363
+ You can create isolated stores using `name`.
275
364
 
276
365
  ```tsx
277
366
  type Props = {
278
- children: React.ReactNode
279
- storeName: string
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
- return <Provider>{children}</Provider>
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
- <StoreProvider storeName="storeA" initialValue={{ count: 1, book: { title: "A" } }}>
384
+ <AppCtxProvider name="storeA" initialValue={{ count: 1, book: { title: "A" } }}>
385
+ {/* useAppCtx(s => s.book) is from storeA */}
296
386
  <AppA />
297
- <StoreProvider storeName="storeB" initialValue={{ count: 5, book: { title: "B" } }}>
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
- </StoreProvider>
300
- </StoreProvider>
301
-
302
- function AppB(){
303
- // book from parent context; parent = "storeA" so book.title = "A"
304
- const book = useStore(s => s.book)
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
- * Uses `useSyncExternalStore`
333
- * Identical snapshot logic on server & client
334
- * No shared global state between requests
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
- * No middleware
341
- * No devtools
342
- * No persistence
343
- * Mutation-based updates by design
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
- * UI state
348
- * App-level shared state
349
- * Lightweight global stores
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 = useStore(s => s.count) // number
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
- * Explicit ownership
376
- * Predictable updates
377
- * Minimal abstraction
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
- type Store<T> = {
14
- (): T;
15
- on: (sub: Subscriber) => () => void;
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
- storeName?: string;
17
+ name?: string;
22
18
  };
23
- declare const newStore: <T extends Record<string, any>>(init: () => T) => Store<T>;
24
- declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
25
- declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetAction<Dest>) => void;
26
- declare function createCtx<T extends object>(init: () => T): {
27
- <Dest = any>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
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
- storeName?: string;
31
- }>) => react_jsx_runtime.JSX.Element;
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 Store, type Subscriber, createCtx, newStore, useSet, useValue };
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
- type Store<T> = {
14
- (): T;
15
- on: (sub: Subscriber) => () => void;
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
- storeName?: string;
17
+ name?: string;
22
18
  };
23
- declare const newStore: <T extends Record<string, any>>(init: () => T) => Store<T>;
24
- declare const useValue: <T extends object, Dest = T>(store: Store<T>, selector?: (state: T) => Dest) => Dest;
25
- declare const useSet: <T extends object = any, Dest extends object = T>(store: Store<T>, selector?: (state: T) => Dest) => (action: SetAction<Dest>) => void;
26
- declare function createCtx<T extends object>(init: () => T): {
27
- <Dest = any>(selector: (state: T) => Dest, opt?: StoreOption): Dest;
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
- storeName?: string;
31
- }>) => react_jsx_runtime.JSX.Element;
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 Store, type Subscriber, createCtx, newStore, useSet, useValue };
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
- newStore: () => newStore,
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 store = () => ref || (ref = init());
35
- store.set = (action) => {
36
- act(store(), action);
34
+ const signal = () => ref || (ref = init());
35
+ signal.set = (action) => {
36
+ act(signal(), action);
37
37
  };
38
- store.reset = () => {
38
+ signal.reset = () => {
39
39
  ref = init();
40
40
  };
41
- store.notify = () => {
41
+ signal.notify = () => {
42
42
  subs.forEach((sub) => sub());
43
43
  };
44
- store.on = (sub) => {
44
+ signal.on = (sub) => {
45
45
  subs.add(sub);
46
46
  return () => subs.delete(sub);
47
47
  };
48
- return store;
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 = (store, selector) => {
66
+ var useSet = ($, selector) => {
82
67
  const setter = (action) => {
83
- const ref = store.$();
68
+ const ref = $();
84
69
  const targetRef = selector ? selector(ref) : ref;
85
70
  act(targetRef, action);
86
- store.$.notify();
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
- const stores = /* @__PURE__ */ new Map();
93
- const initStore = (init2, name) => {
94
- if (!name) return newStore(init2);
95
- if (!stores.has(name)) {
96
- stores.set(name, newStore(init2));
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 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
+ }
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 Provider = ({
107
- children,
108
- value,
109
- storeName
110
- }) => {
111
- const [store, name] = (0, import_react.useMemo)(() => {
112
- const storeInit = value ? () => value : init;
113
- const store2 = initStore(storeInit, storeName);
114
- return [store2, storeName];
115
- }, [value, storeName]);
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
- [store, name]
116
+ [signal, name]
121
117
  );
122
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ctx.Provider, { value: store, children: [
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
- newStore,
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 store = () => ref || (ref = init());
14
- store.set = (action) => {
15
- act(store(), action);
13
+ const signal = () => ref || (ref = init());
14
+ signal.set = (action) => {
15
+ act(signal(), action);
16
16
  };
17
- store.reset = () => {
17
+ signal.reset = () => {
18
18
  ref = init();
19
19
  };
20
- store.notify = () => {
20
+ signal.notify = () => {
21
21
  subs.forEach((sub) => sub());
22
22
  };
23
- store.on = (sub) => {
23
+ signal.on = (sub) => {
24
24
  subs.add(sub);
25
25
  return () => subs.delete(sub);
26
26
  };
27
- return store;
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 = (store, selector) => {
45
+ var useSet = ($, selector) => {
61
46
  const setter = (action) => {
62
- const ref = store.$();
47
+ const ref = $();
63
48
  const targetRef = selector ? selector(ref) : ref;
64
49
  act(targetRef, action);
65
- store.$.notify();
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
- const stores = /* @__PURE__ */ new Map();
72
- const initStore = (init2, name) => {
73
- if (!name) return newStore(init2);
74
- if (!stores.has(name)) {
75
- stores.set(name, newStore(init2));
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 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
+ }
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 Provider = ({
86
- children,
87
- value,
88
- storeName
89
- }) => {
90
- const [store, name] = useMemo(() => {
91
- const storeInit = value ? () => value : init;
92
- const store2 = initStore(storeInit, storeName);
93
- return [store2, storeName];
94
- }, [value, storeName]);
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
- [store, name]
95
+ [signal, name]
100
96
  );
101
- return /* @__PURE__ */ jsxs(ctx.Provider, { value: store, children: [
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
- newStore,
107
+ newSignal,
123
108
  useSet,
124
109
  useValue
125
110
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thefoxieflow/signalctx",
3
- "version": "0.1.1",
3
+ "version": "1.0.1",
4
4
  "description": "Lightweight signal-based React context store",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",