juststore 0.4.3 → 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/dist/atom.d.ts CHANGED
@@ -11,16 +11,13 @@ type Atom<T> = {
11
11
  /** Subscribe to the value. */
12
12
  use: () => T;
13
13
  /** Set the value. */
14
- set: (value: T) => void;
14
+ set: AtomSetState<T>;
15
15
  /** Reset the value to the default value. */
16
16
  reset: () => void;
17
17
  /** Subscribe to the value.with a callback function. */
18
18
  subscribe: (listener: (value: T) => void) => () => void;
19
- /** Render the value. */
20
- Render: ({ children }: {
21
- children: (value: T, setValue: (value: T) => void) => React.ReactNode;
22
- }) => React.ReactNode;
23
19
  };
20
+ type AtomSetState<T> = (value: T | ((prev: T) => T)) => void;
24
21
  /**
25
22
  * Creates an atom with a given id and default value.
26
23
  *
@@ -33,11 +30,11 @@ type Atom<T> = {
33
30
  * <>
34
31
  * <ComponentA/>
35
32
  * <ComponentB/>
36
- * <stateA.Render>
33
+ * <Render state={stateA}>
37
34
  * {(value, setValue) => (
38
35
  * <button onClick={() => setValue(!value)}>{value ? 'Hide' : 'Show'}</button>
39
36
  * )}
40
- * </stateA.Render>
37
+ * </Render>
41
38
  * <ComponentC/>
42
39
  * <ComponentD/>
43
40
  * </>
package/dist/atom.js CHANGED
@@ -13,11 +13,11 @@ export { createAtom };
13
13
  * <>
14
14
  * <ComponentA/>
15
15
  * <ComponentB/>
16
- * <stateA.Render>
16
+ * <Render state={stateA}>
17
17
  * {(value, setValue) => (
18
18
  * <button onClick={() => setValue(!value)}>{value ? 'Hide' : 'Show'}</button>
19
19
  * )}
20
- * </stateA.Render>
20
+ * </Render>
21
21
  * <ComponentC/>
22
22
  * <ComponentD/>
23
23
  * </>
@@ -53,9 +53,6 @@ function createAtom(id, defaultValue, persistent = false) {
53
53
  if (prop === 'subscribe') {
54
54
  return (target._subscribe ??= (listener) => subscribeAtom(key, memoryOnly, listener));
55
55
  }
56
- if (prop === 'Render') {
57
- return (target._Render ??= ({ children }) => children(useAtom(key, memoryOnly), (value) => setAtom(key, value, memoryOnly)));
58
- }
59
56
  return undefined;
60
57
  }
61
58
  });
@@ -92,7 +89,12 @@ function getAtom(key, memoryOnly = true) {
92
89
  * @param memoryOnly - When true, skips localStorage persistence
93
90
  */
94
91
  function setAtom(key, value, memoryOnly = true) {
95
- updateSnapshot(key, value, memoryOnly);
92
+ if (typeof value !== 'function') {
93
+ updateSnapshot(key, value, memoryOnly);
94
+ }
95
+ else {
96
+ updateSnapshot(key, value(getAtom(key, memoryOnly)), memoryOnly);
97
+ }
96
98
  notifyAtom(key);
97
99
  }
98
100
  const listeners = new Map();
package/dist/form.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { FieldPath, FieldPathValue, FieldValues, IsEqual } from './path';
2
- import type { ArrayProxy, DerivedStateProps, IsNullable, MaybeNullable, ObjectMutationMethods, Prettify, ValueState } from './types';
2
+ import type { ArrayProxy, DerivedStateProps, ObjectMutationMethods, Prettify, ValueState } from './types';
3
3
  export { createForm, useForm, type CreateFormOptions, type DeepNonNullable, type FormArrayState, type FormObjectState, type FormState, type FormStore, type FormValueState };
4
4
  /**
5
5
  * Common form field methods available on every form state node.
@@ -12,8 +12,8 @@ type FormCommon = {
12
12
  /** Manually set a validation error. */
13
13
  setError: (error: string | undefined) => void;
14
14
  };
15
- type FormState<T> = IsEqual<T, unknown> extends true ? never : [NonNullable<T>] extends [readonly (infer U)[]] ? FormArrayState<U, IsNullable<T>> : [NonNullable<T>] extends [FieldValues] ? FormObjectState<NonNullable<T>, IsNullable<T>> : FormValueState<T>;
16
- type FormReadOnlyState<T> = Prettify<Pick<FormValueState<Readonly<Required<T>>>, 'value' | 'use' | 'useCompute' | 'Render' | 'Show' | 'error' | 'setError'>>;
15
+ type FormState<T> = IsEqual<T, unknown> extends true ? never : [NonNullable<T>] extends [readonly (infer U)[]] ? FormArrayState<U, T> : [NonNullable<T>] extends [FieldValues] ? FormObjectState<NonNullable<T>, T> : FormValueState<T>;
16
+ type FormReadOnlyState<T> = Prettify<Pick<FormValueState<Readonly<Required<T>>>, 'value' | 'use' | 'useCompute' | 'error' | 'setError'>>;
17
17
  interface FormValueState<T> extends Omit<ValueState<T>, 'ensureArray' | 'ensureObject' | 'withDefault' | 'derived'>, FormCommon {
18
18
  /** Ensure the value is an array. */
19
19
  ensureArray(): NonNullable<T> extends (infer U)[] ? FormArrayState<U> : never;
@@ -44,8 +44,8 @@ type FormObjectProxy<T extends FieldValues> = {
44
44
  } & {
45
45
  [K in keyof T]-?: FormState<T[K]>;
46
46
  };
47
- type FormArrayState<T, Nullable extends boolean = false> = IsEqual<T, unknown> extends true ? never : FormValueState<MaybeNullable<T[], Nullable>> & ArrayProxy<T, FormState<T>>;
48
- type FormObjectState<T extends FieldValues, Nullable extends boolean = false> = FormObjectProxy<T> & FormValueState<MaybeNullable<T, Nullable>> & ObjectMutationMethods;
47
+ type FormArrayState<TValue, TArray = TValue[]> = IsEqual<TValue, unknown> extends true ? never : FormValueState<TArray> & ArrayProxy<TValue, FormState<TValue>>;
48
+ type FormObjectState<TNonNullable extends FieldValues, TObject = TNonNullable> = FormObjectProxy<TNonNullable> & FormValueState<TObject> & ObjectMutationMethods;
49
49
  /** Type for nested objects with proxy methods */
50
50
  type DeepNonNullable<T> = [NonNullable<T>] extends [readonly (infer U)[]] ? U[] : [NonNullable<T>] extends [FieldValues] ? {
51
51
  [K in keyof NonNullable<T>]-?: DeepNonNullable<NonNullable<T>[K]>;
@@ -57,7 +57,7 @@ type FormStore<T extends FieldValues> = FormState<T> & {
57
57
  /** Clears all validation errors from the form. */
58
58
  clearErrors(): void;
59
59
  /** Returns a form submit handler that validates and calls onSubmit with form values. */
60
- handleSubmit(onSubmit: (values: T) => void): (e: React.FormEvent) => void;
60
+ handleSubmit(onSubmit: (values: T) => void): (e: React.SyntheticEvent) => void;
61
61
  };
62
62
  type NoEmptyValidator = 'not-empty';
63
63
  type RegexValidator = RegExp;
package/dist/form.js CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: intended */
2
2
  'use client';
3
3
  import { pascalCase } from 'change-case';
4
4
  import { useEffect, useId, useMemo } from 'react';
package/dist/impl.js CHANGED
@@ -84,7 +84,7 @@ function getNamespace(key) {
84
84
  function joinPath(namespace, path) {
85
85
  if (!path)
86
86
  return namespace;
87
- return namespace + '.' + path;
87
+ return `${namespace}.${path}`;
88
88
  }
89
89
  function joinChildKey(parent, child) {
90
90
  return parent ? `${parent}.${child}` : child;
@@ -93,16 +93,16 @@ function getKeyPrefixes(key) {
93
93
  const dot = key.indexOf('.');
94
94
  if (dot === -1)
95
95
  return [];
96
- const parts = key.split('.');
97
- if (parts.length <= 1)
96
+ const [first, ...parts] = key.split('.');
97
+ if (parts.length === 0)
98
98
  return [];
99
99
  const prefixes = [];
100
- let current = parts[0];
101
- for (let i = 1; i < parts.length - 1; i++) {
102
- current += '.' + parts[i];
100
+ let current = first;
101
+ for (let i = 0; i < parts.length - 1; i++) {
102
+ current += `.${parts[i]}`;
103
103
  prefixes.push(current);
104
104
  }
105
- prefixes.unshift(parts[0]);
105
+ prefixes.unshift(first);
106
106
  return prefixes;
107
107
  }
108
108
  /** Snapshot getter used by React's useSyncExternalStore. */
@@ -260,7 +260,7 @@ function setNestedValue(obj, path, value) {
260
260
  }
261
261
  else if (typeof current === 'object' && current !== null) {
262
262
  const currentObj = current;
263
- const hadKey = Object.prototype.hasOwnProperty.call(currentObj, lastSegment);
263
+ const hadKey = Object.hasOwn(currentObj, lastSegment);
264
264
  if (value === undefined) {
265
265
  delete currentObj[lastSegment];
266
266
  if (hadKey) {
@@ -316,21 +316,29 @@ function notifyListeners(key, oldValue, newValue, { skipRoot = false, skipChildr
316
316
  // exact match only
317
317
  const listenerSet = listeners.get(key);
318
318
  if (listenerSet) {
319
- listenerSet.forEach(listener => listener());
319
+ listenerSet.forEach(listener => {
320
+ listener();
321
+ });
320
322
  }
321
323
  return;
322
324
  }
323
325
  // Exact key match
324
326
  const exactSet = listeners.get(key);
325
327
  if (exactSet) {
326
- exactSet.forEach(listener => listener());
328
+ exactSet.forEach(listener => {
329
+ listener();
330
+ });
327
331
  }
328
332
  // Ancestor keys match (including namespace root)
329
333
  if (!skipRoot) {
330
334
  const namespace = getNamespace(key);
331
- const rootSet = listeners.get(namespace);
332
- if (rootSet) {
333
- rootSet.forEach(listener => listener());
335
+ if (namespace !== key) {
336
+ const rootSet = listeners.get(namespace);
337
+ if (rootSet) {
338
+ rootSet.forEach(listener => {
339
+ listener();
340
+ });
341
+ }
334
342
  }
335
343
  // Also notify intermediate ancestors
336
344
  const prefixes = getKeyPrefixes(key);
@@ -339,7 +347,9 @@ function notifyListeners(key, oldValue, newValue, { skipRoot = false, skipChildr
339
347
  continue; // Already handled
340
348
  const prefixSet = listeners.get(prefix);
341
349
  if (prefixSet) {
342
- prefixSet.forEach(listener => listener());
350
+ prefixSet.forEach(listener => {
351
+ listener();
352
+ });
343
353
  }
344
354
  }
345
355
  }
@@ -369,7 +379,9 @@ function notifyListeners(key, oldValue, newValue, { skipRoot = false, skipChildr
369
379
  if (forceNotify || !isEqual(oldChildValue, newChildValue)) {
370
380
  const childSet = listeners.get(childKey);
371
381
  if (childSet) {
372
- childSet.forEach(listener => listener());
382
+ childSet.forEach(listener => {
383
+ listener();
384
+ });
373
385
  }
374
386
  }
375
387
  }
@@ -395,13 +407,13 @@ function subscribe(key, listener) {
395
407
  if (!listeners.has(key)) {
396
408
  listeners.set(key, new Set());
397
409
  }
398
- listeners.get(key).add(listener);
410
+ listeners.get(key)?.add(listener);
399
411
  const prefixes = getKeyPrefixes(key);
400
412
  for (const prefix of prefixes) {
401
413
  if (!descendantListenerKeysByPrefix.has(prefix)) {
402
414
  descendantListenerKeysByPrefix.set(prefix, new Set());
403
415
  }
404
- descendantListenerKeysByPrefix.get(prefix).add(key);
416
+ descendantListenerKeysByPrefix.get(prefix)?.add(key);
405
417
  }
406
418
  return () => {
407
419
  const keyListeners = listeners.get(key);
@@ -471,12 +483,12 @@ function rename(path, oldKey, newKey, memoryOnly) {
471
483
  const obj = current;
472
484
  if (oldKey === newKey)
473
485
  return;
474
- if (!Object.prototype.hasOwnProperty.call(obj, oldKey))
486
+ if (!Object.hasOwn(obj, oldKey))
475
487
  return;
476
488
  const keyOrder = getStableKeys(obj);
477
489
  const entries = [];
478
490
  for (const key of keyOrder) {
479
- if (!Object.prototype.hasOwnProperty.call(obj, key))
491
+ if (!Object.hasOwn(obj, key))
480
492
  continue;
481
493
  if (key === oldKey) {
482
494
  entries.push([newKey, obj[oldKey]]);
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- export { createAtom, type Atom } from './atom';
1
+ export { type Atom, createAtom } from './atom';
2
2
  export type * from './form';
3
3
  export { useForm } from './form';
4
4
  export { isEqual } from './impl';
5
- export { createMemoryStore, useMemoryStore, type MemoryStore } from './memory';
5
+ export { createMemoryStore, type MemoryStore, useMemoryStore } from './memory';
6
6
  export { createMixedState } from './mixed_state';
7
7
  export type * from './path';
8
8
  export { createStore, type Store } from './store';
9
9
  export type * from './types';
10
+ export * from './utils';
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export { isEqual } from './impl';
4
4
  export { createMemoryStore, useMemoryStore } from './memory';
5
5
  export { createMixedState } from './mixed_state';
6
6
  export { createStore } from './store';
7
+ export * from './utils';
@@ -11,9 +11,9 @@ export { createMixedState };
11
11
  * @example
12
12
  * const mixedState = createMixedState(states.addLoading, states.copyLoading, state.agent)
13
13
  *
14
- * <mixedState.Render>
14
+ * <Render state={mixedState}>
15
15
  * {[addLoading, copyLoading, agent] => <SomeComponent />}
16
- * </mixedState.Render>
16
+ * </Render>
17
17
  */
18
18
  declare function createMixedState<T extends readonly unknown[]>(...states: {
19
19
  [K in keyof T]-?: ValueState<T[K]>;
@@ -12,9 +12,9 @@ export { createMixedState };
12
12
  * @example
13
13
  * const mixedState = createMixedState(states.addLoading, states.copyLoading, state.agent)
14
14
  *
15
- * <mixedState.Render>
15
+ * <Render state={mixedState}>
16
16
  * {[addLoading, copyLoading, agent] => <SomeComponent />}
17
- * </mixedState.Render>
17
+ * </Render>
18
18
  */
19
19
  function createMixedState(...states) {
20
20
  const use = () => states.map(state => state.use());
@@ -33,18 +33,12 @@ function createMixedState(...states) {
33
33
  useEffect(() => {
34
34
  const unsubscribeFns = states.map(state => state.subscribe(recompute));
35
35
  return () => {
36
- unsubscribeFns.forEach(unsubscribe => unsubscribe());
36
+ unsubscribeFns.forEach(unsubscribe => {
37
+ unsubscribe();
38
+ });
37
39
  };
38
40
  });
39
41
  return value;
40
- },
41
- Render({ children }) {
42
- const value = use();
43
- return children(value);
44
- },
45
- Show({ children, on }) {
46
- const value = use();
47
- return on(value) ? children : null;
48
42
  }
49
43
  };
50
44
  return mixedState;
package/dist/node.js CHANGED
@@ -1,4 +1,5 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: <T is not known at this point> */
2
+ /** biome-ignore-all lint/suspicious/noAssignInExpressions: <getter methods are cached> */
2
3
  import { getStableKeys } from './impl';
3
4
  export { createNode, createRootNode };
4
5
  /**
@@ -65,15 +66,6 @@ function createNode(storeApi, path, cache, extensions, from = unchanged, to = un
65
66
  if (prop === 'subscribe') {
66
67
  return (_target._subscribe ??= (listener) => storeApi.subscribe(path, value => listener(to(value))));
67
68
  }
68
- if (prop === 'Render') {
69
- return (_target._Render ??= ({ children }) => storeApi.Render({
70
- path,
71
- children: (value, update) => children(from(value), value => update(to(value)))
72
- }));
73
- }
74
- if (prop === 'Show') {
75
- return (_target._Show ??= ({ children, on }) => storeApi.Show({ path, children, on: value => on(from(value)) }));
76
- }
77
69
  if (prop === 'useCompute') {
78
70
  return (_target._useCompute ??= (fn) => {
79
71
  return storeApi.useCompute(path, value => fn(from(value)));
@@ -350,7 +342,6 @@ function createKeysNode(storeApi, path, getObjectValue) {
350
342
  return (_target._Render ??= ({ children }) => children(storeApi.useCompute(signalPath, computeKeys), () => { }));
351
343
  }
352
344
  if (prop === 'Show') {
353
- // eslint-disable-next-line react/display-name
354
345
  return (_target._Show ??= ({ children, on }) => {
355
346
  const show = storeApi.useCompute(signalPath, () => on(computeKeys()), [on]);
356
347
  return show ? children : null;
package/dist/path.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: <Intended> */
1
2
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
2
3
  export type BrowserNativeObject = Date | FileList | File;
3
4
  /**
package/dist/root.js CHANGED
@@ -15,6 +15,7 @@ export { createStoreRoot };
15
15
  * @returns A proxy object providing both path-based and dynamic property access to the store
16
16
  */
17
17
  function createStoreRoot(namespace, defaultValue, options = {}) {
18
+ 'use memo';
18
19
  const memoryOnly = options?.memoryOnly ?? false;
19
20
  // merge with default value and save in memory only
20
21
  produce(namespace, { ...defaultValue, ...(getSnapshot(namespace, memoryOnly) ?? {}) }, true, true);
@@ -45,16 +46,21 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
45
46
  const fnRef = useRef(fn);
46
47
  fnRef.current = fn;
47
48
  const cacheRef = useRef(null);
49
+ const depsRef = useRef(deps);
50
+ // Invalidate cached compute when hook inputs change.
51
+ if (!isEqual(depsRef.current, deps)) {
52
+ depsRef.current = deps;
53
+ cacheRef.current = null;
54
+ }
55
+ const pathRef = useRef(fullPath);
56
+ if (pathRef.current !== fullPath) {
57
+ pathRef.current = fullPath;
58
+ cacheRef.current = null;
59
+ }
48
60
  const subscribeToPath = useCallback((onStoreChange) => subscribe(fullPath, onStoreChange), [fullPath]);
49
61
  const getComputedSnapshot = useCallback(() => {
50
- if (cacheRef.current && cacheRef.current.path !== fullPath) {
51
- cacheRef.current = null;
52
- }
53
- if (cacheRef.current && !isEqual(cacheRef.current.deps, deps)) {
54
- cacheRef.current = null;
55
- }
56
62
  const storeValue = getSnapshot(fullPath, memoryOnly);
57
- if (cacheRef.current && isEqual(cacheRef.current.storeValue, storeValue)) {
63
+ if (cacheRef.current && Object.is(cacheRef.current.storeValue, storeValue)) {
58
64
  // same store value, return the same computed value
59
65
  return cacheRef.current.computed;
60
66
  }
@@ -66,9 +72,9 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
66
72
  cacheRef.current.storeValue = storeValue;
67
73
  return cacheRef.current.computed;
68
74
  }
69
- cacheRef.current = { path: fullPath, storeValue, computed: computedNext, deps };
75
+ cacheRef.current = { storeValue, computed: computedNext };
70
76
  return computedNext;
71
- }, [fullPath, deps]);
77
+ }, [fullPath]);
72
78
  return useSyncExternalStore(subscribeToPath, getComputedSnapshot, getComputedSnapshot);
73
79
  },
74
80
  notify: (path) => {
@@ -80,39 +86,13 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
80
86
  });
81
87
  },
82
88
  useState: (path) => {
83
- const fullPath = joinPath(namespace, path);
84
89
  const setValue = useCallback((value) => {
85
- if (typeof value === 'function') {
86
- const currentValue = getSnapshot(fullPath, memoryOnly);
87
- const newValue = value(currentValue);
88
- return setLeaf(namespace, path, newValue, false, memoryOnly);
89
- }
90
- return setLeaf(namespace, path, value, false, memoryOnly);
91
- }, [fullPath, path]);
90
+ storeApi.set(path, value, false);
91
+ }, [path]);
92
92
  return [
93
93
  useObject(namespace, path, memoryOnly),
94
94
  setValue
95
95
  ];
96
- },
97
- Render: ({ path, children }) => {
98
- const fullPath = joinPath(namespace, path);
99
- const value = useObject(namespace, path, memoryOnly);
100
- const update = useCallback((value) => {
101
- if (typeof value === 'function') {
102
- const currentValue = getSnapshot(fullPath, memoryOnly);
103
- const newValue = value(currentValue);
104
- return setLeaf(namespace, path, newValue, false, memoryOnly);
105
- }
106
- return setLeaf(namespace, path, value, false, memoryOnly);
107
- }, [fullPath, path]);
108
- return children(value, update);
109
- },
110
- Show: ({ path, children, on }) => {
111
- const value = useObject(namespace, path, memoryOnly);
112
- if (!on(value)) {
113
- return null;
114
- }
115
- return children;
116
96
  }
117
97
  };
118
98
  return storeApi;
@@ -13,7 +13,7 @@ function getStableKeys(value) {
13
13
  const target = value;
14
14
  const existing = externalKeyOrder.get(target);
15
15
  if (existing) {
16
- const next = existing.filter(k => Object.prototype.hasOwnProperty.call(target, k));
16
+ const next = existing.filter(k => Object.hasOwn(target, k));
17
17
  const nextSet = new Set(next);
18
18
  for (const k of Object.keys(target)) {
19
19
  if (nextSet.has(k))
package/dist/types.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { FieldPath, FieldPathValue, FieldValues, IsEqual } from './path';
2
- export type { AllowedKeys, ArrayProxy, ArrayState, DerivedStateProps, IsNullable, MaybeNullable, ObjectMutationMethods, ObjectState, Prettify, ReadOnlyState, State, StoreRenderProps, StoreRoot, StoreSetStateValue, StoreShowProps, StoreUseComputeFn, StoreUseState, ValueState };
2
+ export type { AllowedKeys, ArrayProxy, ArrayState, DerivedStateProps, ObjectMutationMethods, ObjectState, Prettify, ReadOnlyState, State, StoreRoot, StoreSetStateValue, StoreUseComputeFn, StoreUseState, ValueState };
3
3
  type Prettify<T> = {
4
4
  [K in keyof T]: T[K];
5
5
  } & {};
6
6
  type AllowedKeys<T> = Exclude<keyof T, keyof ValueState<unknown> | keyof ObjectMutationMethods>;
7
7
  type ArrayMutationMethods<T> = Prettify<Pick<Array<T>, 'push' | 'pop' | 'shift' | 'unshift' | 'splice' | 'reverse' | 'sort' | 'fill' | 'copyWithin'>>;
8
8
  /** Type for array proxy with index access */
9
- type ArrayProxy<T, ElementState = State<T>> = ArrayMutationMethods<T> & {
9
+ type ArrayProxy<T, ElementState = State<T>> = ArrayMutationMethods<NonNullable<T>> & {
10
10
  /** Read without subscribing. Returns array or undefined for missing paths. */
11
11
  readonly value: T[];
12
12
  /**
@@ -69,10 +69,6 @@ type StoreRoot<T extends FieldValues> = {
69
69
  useCompute: <P extends FieldPath<T>, R>(path: P, fn: StoreUseComputeFn<T, P, R>, deps?: readonly unknown[]) => R;
70
70
  /** Notify listeners at path. */
71
71
  notify: <P extends FieldPath<T>>(path: P) => void;
72
- /** Render-prop helper for inline usage. */
73
- Render: <P extends FieldPath<T>>(props: FieldPathValue<T, P> extends undefined ? never : StoreRenderProps<T, P>) => React.ReactNode;
74
- /** Show or hide children based on the value at the path. */
75
- Show: <P extends FieldPath<T>>(props: FieldPathValue<T, P> extends undefined ? never : StoreShowProps<T, P>) => React.ReactNode;
76
72
  };
77
73
  /** Common methods available on any deep proxy node */
78
74
  type ValueState<T> = {
@@ -80,6 +76,8 @@ type ValueState<T> = {
80
76
  readonly value: T;
81
77
  /** The field name for the proxy. */
82
78
  readonly field: string;
79
+ /** The path for the proxy. */
80
+ readonly path: string;
83
81
  /** Subscribe and read the value at path. Re-renders when the value changes. */
84
82
  use(): T;
85
83
  /** Subscribe and read the debounced value at path. Re-renders when the value changes. */
@@ -126,50 +124,16 @@ type ValueState<T> = {
126
124
  derived: <R>({ from, to }: DerivedStateProps<T, R>) => State<R>;
127
125
  /** Notify listener of current value. */
128
126
  notify(): void;
129
- /** Render-prop helper for inline usage.
130
- *
131
- * @example
132
- * <store.a.b.c.Render>
133
- * {(value, update) => <button onClick={() => update('new value')}>{value}</button>}
134
- * </store.a.b.c.Render>
135
- */
136
- Render: (props: {
137
- children: (value: T, update: (value: T) => void) => React.ReactNode;
138
- }) => React.ReactNode;
139
- /** Show or hide children based on the value at the path.
140
- *
141
- * @example
142
- * <store.a.b.c.Show on={value => value === 'show'}>
143
- * <div>Show</div>
144
- * </store.a.b.c.Show>
145
- */
146
- Show: (props: {
147
- children: React.ReactNode;
148
- on: (value: T) => boolean;
149
- }) => React.ReactNode;
150
127
  };
151
128
  /**
152
129
  * A read-only state that provides access to the value, use, Render, and Show methods.
153
130
  */
154
- type ReadOnlyState<T> = Prettify<Pick<ValueState<Readonly<Required<T>>>, 'value' | 'use' | 'useCompute' | 'Render' | 'Show'>>;
155
- type MaybeNullable<T, Nullable extends boolean = false> = Nullable extends true ? T | undefined : T;
156
- type IsNullable<T> = T extends undefined | null ? true : false;
157
- type State<T> = IsEqual<T, unknown> extends true ? never : [NonNullable<T>] extends [readonly (infer U)[]] ? ArrayState<U, IsNullable<T>> : [NonNullable<T>] extends [FieldValues] ? ObjectState<NonNullable<T>, IsNullable<T>> : ValueState<T>;
158
- type ArrayState<T, Nullable extends boolean = false> = IsEqual<T, unknown> extends true ? never : ValueState<MaybeNullable<T[], Nullable>> & ArrayProxy<T>;
159
- type ObjectState<T extends FieldValues, Nullable extends boolean = false> = ObjectProxy<T> & ValueState<MaybeNullable<T, Nullable>> & ObjectMutationMethods;
131
+ type ReadOnlyState<T> = Prettify<Pick<ValueState<Readonly<Required<T>>>, 'value' | 'use' | 'useCompute'>>;
132
+ type State<T> = IsEqual<T, unknown> extends true ? never : [NonNullable<T>] extends [readonly (infer U)[]] ? ArrayState<U, T> : [NonNullable<T>] extends [FieldValues] ? ObjectState<NonNullable<T>, T> : ValueState<T>;
133
+ type ArrayState<TValue, TArray = TValue[]> = IsEqual<TValue, unknown> extends true ? never : ValueState<TArray> & ArrayProxy<TValue>;
134
+ type ObjectState<TNonNullable extends FieldValues, TObject = TNonNullable> = ObjectProxy<TNonNullable> & ValueState<TObject> & ObjectMutationMethods;
160
135
  /** Type for useCompute function. */
161
136
  type StoreUseComputeFn<T extends FieldValues, P extends FieldPath<T>, R> = (value: FieldPathValue<T, P>) => R;
162
- /** Props for Store.Render helper. */
163
- type StoreRenderProps<T extends FieldValues, P extends FieldPath<T>> = {
164
- path: P;
165
- children: (value: FieldPathValue<T, P>, update: (value: StoreSetStateValue<FieldPathValue<T, P>>) => void) => React.ReactNode;
166
- };
167
- /** Props for Store.Show helper. */
168
- type StoreShowProps<T extends FieldValues, P extends FieldPath<T>> = {
169
- path: P;
170
- children: React.ReactNode;
171
- on: (value: FieldPathValue<T, P>) => boolean;
172
- };
173
137
  type DerivedStateProps<T, R> = {
174
138
  from?: (value: T) => R;
175
139
  to?: (value: R) => T;
@@ -0,0 +1,51 @@
1
+ import type { Atom } from './atom';
2
+ import type { StoreSetStateValue, ValueState } from './types';
3
+ export { Render, RenderWithUpdate, Conditional };
4
+ type AtomLike<T> = Pick<Atom<T> | ValueState<T>, 'use' | 'set' | 'value'>;
5
+ type ReadOnlyAtomLike<T> = Pick<Atom<T> | ValueState<T>, 'use' | 'value'>;
6
+ type RenderProps<State extends ReadOnlyAtomLike<unknown>> = {
7
+ state: State;
8
+ children: (value: ReturnType<State['use']>) => React.ReactNode;
9
+ };
10
+ type RenderWithUpdateProps<State extends AtomLike<unknown>> = {
11
+ state: State;
12
+ children: (value: ReturnType<State['use']>, update: (value: StoreSetStateValue<ReturnType<State['use']>>) => void) => React.ReactNode;
13
+ };
14
+ /**
15
+ * Renders the provided children function with the current value from the state.
16
+ *
17
+ * @template T The type of the state value.
18
+ * @param props - The props object.
19
+ * @param props.state - The ValueState whose value will be passed to children.
20
+ * @param props.children - A render prop that receives the current value.
21
+ * @returns The result of calling children with the current value.
22
+ */
23
+ declare function Render<State extends ReadOnlyAtomLike<unknown>>({ state, children }: RenderProps<State>): import("react").ReactNode;
24
+ /**
25
+ * Renders the provided children function with the current value and an update function.
26
+ *
27
+ * The update function can set the value directly or with an updater function.
28
+ *
29
+ * @template T The type of the state value.
30
+ * @template U The type allowed for updating the state (value or updater).
31
+ * @param props - The props object.
32
+ * @param props.state - The ValueState whose value will be passed to children.
33
+ * @param props.children - A render prop that receives the current value and update function.
34
+ * @returns The result of calling children with the current value and update function.
35
+ */
36
+ declare function RenderWithUpdate<State extends AtomLike<unknown>>({ state, children }: RenderWithUpdateProps<State>): import("react").ReactNode;
37
+ /**
38
+ * Conditionally renders the children function based on the result of the `on` predicate.
39
+ *
40
+ * @template T The type of the state value.
41
+ * @param props - The props object.
42
+ * @param props.state - The ValueState whose value will be used.
43
+ * @param props.on - A predicate that receives the value and returns whether to show children.
44
+ * @param props.children - A render prop that receives the current value for rendering if visible.
45
+ * @returns The result of children if the predicate returns true, otherwise null.
46
+ */
47
+ declare function Conditional<State extends ReadOnlyAtomLike<unknown>>({ state, on, children }: {
48
+ state: State;
49
+ on: (value: ReturnType<State['use']>) => boolean;
50
+ children: (value: ReturnType<State['use']>) => React.ReactNode;
51
+ }): import("react").ReactNode;
package/dist/utils.js ADDED
@@ -0,0 +1,57 @@
1
+ import { useCallback } from 'react';
2
+ export { Render, RenderWithUpdate, Conditional };
3
+ /**
4
+ * Renders the provided children function with the current value from the state.
5
+ *
6
+ * @template T The type of the state value.
7
+ * @param props - The props object.
8
+ * @param props.state - The ValueState whose value will be passed to children.
9
+ * @param props.children - A render prop that receives the current value.
10
+ * @returns The result of calling children with the current value.
11
+ */
12
+ function Render({ state, children }) {
13
+ const value = state.use();
14
+ return children(value);
15
+ }
16
+ /**
17
+ * Renders the provided children function with the current value and an update function.
18
+ *
19
+ * The update function can set the value directly or with an updater function.
20
+ *
21
+ * @template T The type of the state value.
22
+ * @template U The type allowed for updating the state (value or updater).
23
+ * @param props - The props object.
24
+ * @param props.state - The ValueState whose value will be passed to children.
25
+ * @param props.children - A render prop that receives the current value and update function.
26
+ * @returns The result of calling children with the current value and update function.
27
+ */
28
+ function RenderWithUpdate({ state, children }) {
29
+ const value = state.use();
30
+ const update = useCallback((value) => {
31
+ if (typeof value !== 'function') {
32
+ state.set(value);
33
+ }
34
+ else {
35
+ state.set(value(state.value));
36
+ }
37
+ }, [state]);
38
+ return children(value, update);
39
+ }
40
+ /**
41
+ * Conditionally renders the children function based on the result of the `on` predicate.
42
+ *
43
+ * @template T The type of the state value.
44
+ * @param props - The props object.
45
+ * @param props.state - The ValueState whose value will be used.
46
+ * @param props.on - A predicate that receives the value and returns whether to show children.
47
+ * @param props.children - A render prop that receives the current value for rendering if visible.
48
+ * @returns The result of children if the predicate returns true, otherwise null.
49
+ */
50
+ function Conditional({ state, on, children }) {
51
+ const value = state.use();
52
+ const show = on(value); // on should not be expensive, memorizing just adds overhead
53
+ if (!show) {
54
+ return null;
55
+ }
56
+ return children(value);
57
+ }