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/README.md +420 -414
- package/dist/atom.d.ts +4 -7
- package/dist/atom.js +8 -6
- package/dist/form.d.ts +6 -6
- package/dist/form.js +1 -1
- package/dist/impl.js +31 -19
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/mixed_state.d.ts +2 -2
- package/dist/mixed_state.js +5 -11
- package/dist/node.js +2 -11
- package/dist/path.d.ts +1 -0
- package/dist/root.js +17 -37
- package/dist/stable_keys.js +1 -1
- package/dist/types.d.ts +8 -44
- package/dist/utils.d.ts +51 -0
- package/dist/utils.js +57 -0
- package/package.json +13 -30
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:
|
|
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
|
|
33
|
+
* <Render state={stateA}>
|
|
37
34
|
* {(value, setValue) => (
|
|
38
35
|
* <button onClick={() => setValue(!value)}>{value ? 'Hide' : 'Show'}</button>
|
|
39
36
|
* )}
|
|
40
|
-
* </
|
|
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
|
|
16
|
+
* <Render state={stateA}>
|
|
17
17
|
* {(value, setValue) => (
|
|
18
18
|
* <button onClick={() => setValue(!value)}>{value ? 'Hide' : 'Show'}</button>
|
|
19
19
|
* )}
|
|
20
|
-
* </
|
|
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
|
-
|
|
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,
|
|
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,
|
|
16
|
-
type FormReadOnlyState<T> = Prettify<Pick<FormValueState<Readonly<Required<T>>>, 'value' | 'use' | 'useCompute' | '
|
|
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<
|
|
48
|
-
type FormObjectState<
|
|
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.
|
|
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
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
|
|
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
|
|
96
|
+
const [first, ...parts] = key.split('.');
|
|
97
|
+
if (parts.length === 0)
|
|
98
98
|
return [];
|
|
99
99
|
const prefixes = [];
|
|
100
|
-
let current =
|
|
101
|
-
for (let i =
|
|
102
|
-
current +=
|
|
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(
|
|
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.
|
|
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 =>
|
|
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 =>
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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 =>
|
|
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 =>
|
|
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)
|
|
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)
|
|
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.
|
|
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.
|
|
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 {
|
|
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,
|
|
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
package/dist/mixed_state.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ export { createMixedState };
|
|
|
11
11
|
* @example
|
|
12
12
|
* const mixedState = createMixedState(states.addLoading, states.copyLoading, state.agent)
|
|
13
13
|
*
|
|
14
|
-
* <mixedState
|
|
14
|
+
* <Render state={mixedState}>
|
|
15
15
|
* {[addLoading, copyLoading, agent] => <SomeComponent />}
|
|
16
|
-
* </
|
|
16
|
+
* </Render>
|
|
17
17
|
*/
|
|
18
18
|
declare function createMixedState<T extends readonly unknown[]>(...states: {
|
|
19
19
|
[K in keyof T]-?: ValueState<T[K]>;
|
package/dist/mixed_state.js
CHANGED
|
@@ -12,9 +12,9 @@ export { createMixedState };
|
|
|
12
12
|
* @example
|
|
13
13
|
* const mixedState = createMixedState(states.addLoading, states.copyLoading, state.agent)
|
|
14
14
|
*
|
|
15
|
-
* <mixedState
|
|
15
|
+
* <Render state={mixedState}>
|
|
16
16
|
* {[addLoading, copyLoading, agent] => <SomeComponent />}
|
|
17
|
-
* </
|
|
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 =>
|
|
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
|
-
|
|
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
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 &&
|
|
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 = {
|
|
75
|
+
cacheRef.current = { storeValue, computed: computedNext };
|
|
70
76
|
return computedNext;
|
|
71
|
-
}, [fullPath
|
|
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
|
-
|
|
86
|
-
|
|
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;
|
package/dist/stable_keys.js
CHANGED
|
@@ -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.
|
|
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,
|
|
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'
|
|
155
|
-
type
|
|
156
|
-
type
|
|
157
|
-
type
|
|
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;
|
package/dist/utils.d.ts
ADDED
|
@@ -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
|
+
}
|