vultus-core 0.4.0 → 0.5.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.
Files changed (120) hide show
  1. package/README.md +304 -6
  2. package/dist/ArgStoreContext.d.ts +10 -0
  3. package/dist/ArgStoreContext.d.ts.map +1 -0
  4. package/dist/ArgStoreContext.js +15 -0
  5. package/dist/ArgStoreContext.js.map +1 -0
  6. package/dist/FieldContext.d.ts +38 -0
  7. package/dist/FieldContext.d.ts.map +1 -0
  8. package/dist/FieldContext.js +82 -0
  9. package/dist/FieldContext.js.map +1 -0
  10. package/dist/argStore.d.ts +26 -0
  11. package/dist/argStore.d.ts.map +1 -0
  12. package/dist/argStore.js +27 -0
  13. package/dist/argStore.js.map +1 -0
  14. package/dist/choices.d.ts +43 -0
  15. package/dist/choices.d.ts.map +1 -0
  16. package/dist/choices.js +20 -0
  17. package/dist/choices.js.map +1 -0
  18. package/dist/field.d.ts +42 -15
  19. package/dist/field.d.ts.map +1 -1
  20. package/dist/field.js +20 -7
  21. package/dist/field.js.map +1 -1
  22. package/dist/index.d.ts +21 -4
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +20 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/kinds.d.ts +51 -0
  27. package/dist/kinds.d.ts.map +1 -0
  28. package/dist/kinds.js +143 -0
  29. package/dist/kinds.js.map +1 -0
  30. package/dist/localStorageStore.d.ts +16 -0
  31. package/dist/localStorageStore.d.ts.map +1 -0
  32. package/dist/localStorageStore.js +72 -0
  33. package/dist/localStorageStore.js.map +1 -0
  34. package/dist/normalizeText.d.ts +6 -0
  35. package/dist/normalizeText.d.ts.map +1 -0
  36. package/dist/normalizeText.js +8 -0
  37. package/dist/normalizeText.js.map +1 -0
  38. package/dist/numeric.d.ts +38 -0
  39. package/dist/numeric.d.ts.map +1 -0
  40. package/dist/numeric.js +43 -0
  41. package/dist/numeric.js.map +1 -0
  42. package/dist/resolve.d.ts +13 -0
  43. package/dist/resolve.d.ts.map +1 -1
  44. package/dist/resolve.js +25 -0
  45. package/dist/resolve.js.map +1 -1
  46. package/dist/tree.d.ts +69 -0
  47. package/dist/tree.d.ts.map +1 -0
  48. package/dist/tree.js +151 -0
  49. package/dist/tree.js.map +1 -0
  50. package/dist/types.d.ts +15 -7
  51. package/dist/types.d.ts.map +1 -1
  52. package/dist/useArg.d.ts +45 -0
  53. package/dist/useArg.d.ts.map +1 -0
  54. package/dist/useArg.js +83 -0
  55. package/dist/useArg.js.map +1 -0
  56. package/dist/useBoolField.d.ts +8 -3
  57. package/dist/useBoolField.d.ts.map +1 -1
  58. package/dist/useBoolField.js +9 -1
  59. package/dist/useBoolField.js.map +1 -1
  60. package/dist/useManyOfField.d.ts +35 -0
  61. package/dist/useManyOfField.d.ts.map +1 -0
  62. package/dist/useManyOfField.js +74 -0
  63. package/dist/useManyOfField.js.map +1 -0
  64. package/dist/useMixedBoolField.d.ts +8 -3
  65. package/dist/useMixedBoolField.d.ts.map +1 -1
  66. package/dist/useMixedBoolField.js +9 -1
  67. package/dist/useMixedBoolField.js.map +1 -1
  68. package/dist/useNumericField.d.ts +25 -0
  69. package/dist/useNumericField.d.ts.map +1 -0
  70. package/dist/useNumericField.js +52 -0
  71. package/dist/useNumericField.js.map +1 -0
  72. package/dist/useOneOfField.d.ts +18 -34
  73. package/dist/useOneOfField.d.ts.map +1 -1
  74. package/dist/useOneOfField.js +18 -18
  75. package/dist/useOneOfField.js.map +1 -1
  76. package/dist/useRangeField.d.ts +34 -0
  77. package/dist/useRangeField.d.ts.map +1 -0
  78. package/dist/useRangeField.js +63 -0
  79. package/dist/useRangeField.js.map +1 -0
  80. package/dist/useStoredField.d.ts +61 -0
  81. package/dist/useStoredField.d.ts.map +1 -0
  82. package/dist/useStoredField.js +151 -0
  83. package/dist/useStoredField.js.map +1 -0
  84. package/dist/useTextField.d.ts +22 -0
  85. package/dist/useTextField.d.ts.map +1 -0
  86. package/dist/useTextField.js +21 -0
  87. package/dist/useTextField.js.map +1 -0
  88. package/dist/useTreeField.d.ts +39 -0
  89. package/dist/useTreeField.d.ts.map +1 -0
  90. package/dist/useTreeField.js +66 -0
  91. package/dist/useTreeField.js.map +1 -0
  92. package/dist/valueTypes.d.ts +28 -0
  93. package/dist/valueTypes.d.ts.map +1 -0
  94. package/dist/valueTypes.js +65 -0
  95. package/dist/valueTypes.js.map +1 -0
  96. package/package.json +1 -1
  97. package/src/ArgStoreContext.tsx +16 -0
  98. package/src/FieldContext.tsx +98 -0
  99. package/src/argStore.ts +49 -0
  100. package/src/choices.ts +62 -0
  101. package/src/field.ts +64 -25
  102. package/src/index.ts +50 -4
  103. package/src/kinds.ts +197 -0
  104. package/src/localStorageStore.ts +68 -0
  105. package/src/normalizeText.ts +7 -0
  106. package/src/numeric.ts +68 -0
  107. package/src/resolve.ts +23 -0
  108. package/src/tree.ts +175 -0
  109. package/src/types.ts +28 -7
  110. package/src/useArg.ts +121 -0
  111. package/src/useBoolField.ts +19 -6
  112. package/src/useManyOfField.ts +107 -0
  113. package/src/useMixedBoolField.ts +19 -6
  114. package/src/useNumericField.ts +69 -0
  115. package/src/useOneOfField.ts +38 -58
  116. package/src/useRangeField.ts +86 -0
  117. package/src/useStoredField.ts +205 -0
  118. package/src/useTextField.ts +39 -0
  119. package/src/useTreeField.ts +108 -0
  120. package/src/valueTypes.ts +77 -0
@@ -0,0 +1,61 @@
1
+ import type { UpdateMethod } from './argStore.js';
2
+ import { type FieldCtx } from './FieldContext.js';
3
+ import { type FieldDefinition } from './kinds.js';
4
+ import { type ArgDefinition } from './useArg.js';
5
+ /**
6
+ * The argument a field's value is stored as: the definition's `id` as key, its kind's value
7
+ * type, and its initial value, resolved with the field context, as default — a value, so the
8
+ * argument layer compares against the same default the field starts at. A mixed-boolean field
9
+ * shows an aggregate of other data and cannot be stored: it throws, naming the field.
10
+ */
11
+ export declare function argOf(definition: FieldDefinition, ctx: FieldCtx): ArgDefinition<any>;
12
+ /**
13
+ * A field kept in the nearest ArgStore: its options for the kind's hook, and whether it shows
14
+ * its default. `options` follows `D` member by member, so options of a definition typed as the
15
+ * FieldDefinition union still narrow by `kind`.
16
+ */
17
+ export interface StoredField<D extends FieldDefinition = FieldDefinition> {
18
+ /** The definition in its outside form, for the kind's hook (`useManyOfField(stored.options)`). */
19
+ options: D extends any ? Omit<D, 'initialValue'> & {
20
+ value: any;
21
+ onChange(value: any): void;
22
+ } : never;
23
+ isDefault: boolean;
24
+ /** Removes the field's key from the store, so it shows its default again. */
25
+ reset(method?: UpdateMethod): void;
26
+ }
27
+ /**
28
+ * A field whose value lives in the nearest ArgStore (the URL, localStorage) under its `id`,
29
+ * with its initial value as the default (see argOf). `options.value` is the stored value made
30
+ * fit for the definition as it is now (sanitizeStored: choices, nodes and bounds follow the
31
+ * data); `onChange` writes with `'replace'`.
32
+ *
33
+ * A stored value that does not fit warns once per field and raw value: not again when only the
34
+ * definition or the field context is a new object (a provider given `value={{...}}` makes one
35
+ * on every render). With the key absent the default is the definer's, not stored input, and is
36
+ * sanitized silently.
37
+ *
38
+ * `options.value` keeps its identity while it stays the same element by element, so a list
39
+ * does not become a new array on every render (effects keyed on it would re-run), even when
40
+ * the field context is a new object each time. The default keeps its identity while it is
41
+ * `isEqual` to the freshly resolved one, so `onChange` and `options` keep theirs too.
42
+ */
43
+ export declare function useStoredField<D extends FieldDefinition>(definition: D): StoredField<D>;
44
+ /** Several stored fields at once: "more filters (3)" and "clear all". */
45
+ export interface StoredFields {
46
+ /** Whether the field with this id shows its default; an id not among the fields throws. */
47
+ isDefault(id: string): boolean;
48
+ /** How many of the fields do not show their defaults. */
49
+ changedCount: number;
50
+ /** Removes every field's key in one store write (one history entry). */
51
+ resetAll(method?: UpdateMethod): void;
52
+ }
53
+ /**
54
+ * Whether stored fields show their defaults, by the same rule as useStoredField, with one store
55
+ * subscription for all of them: its snapshot is their raw values joined into one string, which
56
+ * changes exactly when one of them does. It is a summary, so it never warns (the fields do).
57
+ * Each field is re-read only when its raw value, its definition or the field context changes,
58
+ * so typing into one field does not re-read its siblings.
59
+ */
60
+ export declare function useStoredFields(definitions: readonly FieldDefinition[]): StoredFields;
61
+ //# sourceMappingURL=useStoredField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStoredField.d.ts","sourceRoot":"","sources":["../src/useStoredField.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAA+C,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGzD;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,CASpF;AA4BD;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe;IACtE,kGAAkG;IAClG,OAAO,EAAE,CAAC,SAAS,GAAG,GAAG,IAAI,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG;QAAE,KAAK,EAAE,GAAG,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAAA;KAAE,GAAG,KAAK,CAAC;IACtG,SAAS,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,KAAK,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CACpC;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,eAAe,EAAE,UAAU,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAwCvF;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CACvC;AAsBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,GAAG,YAAY,CAwCrF"}
@@ -0,0 +1,151 @@
1
+ import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';
2
+ import { useArgStore } from './ArgStoreContext.js';
3
+ import { useFieldContext } from './FieldContext.js';
4
+ import { initialValueOf, sanitizeStored, valueTypeOf } from './kinds.js';
5
+ import { useArg } from './useArg.js';
6
+ /**
7
+ * The argument a field's value is stored as: the definition's `id` as key, its kind's value
8
+ * type, and its initial value, resolved with the field context, as default — a value, so the
9
+ * argument layer compares against the same default the field starts at. A mixed-boolean field
10
+ * shows an aggregate of other data and cannot be stored: it throws, naming the field.
11
+ */
12
+ export function argOf(definition, ctx) {
13
+ const { id, kind } = definition;
14
+ let valueType;
15
+ try {
16
+ valueType = valueTypeOf(kind);
17
+ }
18
+ catch (error) {
19
+ throw new Error(`Field '${id}': ${error.message}`);
20
+ }
21
+ return { key: id, valueType, default: initialValueOf(definition, ctx) };
22
+ }
23
+ function silently(_message) { }
24
+ function warnOnConsole(message) {
25
+ console.warn(message);
26
+ }
27
+ /**
28
+ * The same value element by element: a list in the same order, a range with the same ends, a
29
+ * scalar. Stricter than the value types' `isEqual` (lists as sets), because the order a field
30
+ * shows (choice order, tree order) matters to the identity kept below.
31
+ */
32
+ function sameValue(a, b) {
33
+ if (Array.isArray(a) && Array.isArray(b))
34
+ return a.length === b.length && a.every((v, i) => v === b[i]);
35
+ return a === b;
36
+ }
37
+ /**
38
+ * Whether a field shows its default: its stored value (parsed, or the default where absent or
39
+ * unreadable) equals it, or fits the definition only as the default (a one-of value no longer
40
+ * among the choices, a range clamped to bounds that are the default). `shown` is the sanitized
41
+ * value, asked for only when the first test fails.
42
+ */
43
+ function showsDefault(arg, stored, shown) {
44
+ return arg.valueType.isEqual(stored, arg.default) || arg.valueType.isEqual(shown(), arg.default);
45
+ }
46
+ /** A compile-time check: options of a FieldDefinition-typed definition narrow by `kind` (the Omit distributes over the union). */
47
+ function optionsNarrowByKind(stored) {
48
+ return stored.options.kind === 'manyOf' ? stored.options.choices : undefined;
49
+ }
50
+ /**
51
+ * A field whose value lives in the nearest ArgStore (the URL, localStorage) under its `id`,
52
+ * with its initial value as the default (see argOf). `options.value` is the stored value made
53
+ * fit for the definition as it is now (sanitizeStored: choices, nodes and bounds follow the
54
+ * data); `onChange` writes with `'replace'`.
55
+ *
56
+ * A stored value that does not fit warns once per field and raw value: not again when only the
57
+ * definition or the field context is a new object (a provider given `value={{...}}` makes one
58
+ * on every render). With the key absent the default is the definer's, not stored input, and is
59
+ * sanitized silently.
60
+ *
61
+ * `options.value` keeps its identity while it stays the same element by element, so a list
62
+ * does not become a new array on every render (effects keyed on it would re-run), even when
63
+ * the field context is a new object each time. The default keeps its identity while it is
64
+ * `isEqual` to the freshly resolved one, so `onChange` and `options` keep theirs too.
65
+ */
66
+ export function useStoredField(definition) {
67
+ const store = useArgStore();
68
+ const ctx = useFieldContext();
69
+ const fresh = argOf(definition, ctx);
70
+ const argRef = useRef(undefined);
71
+ const previousArg = argRef.current;
72
+ const arg = previousArg !== undefined && previousArg.key === fresh.key && previousArg.valueType === fresh.valueType
73
+ && fresh.valueType.isEqual(previousArg.default, fresh.default)
74
+ ? previousArg
75
+ : fresh;
76
+ argRef.current = arg;
77
+ const { value: stored, set, reset } = useArg(arg);
78
+ // Called on the store, not passed detached: a store written as a class uses `this`.
79
+ const subscribe = useCallback((listener) => store.subscribe(listener), [store]);
80
+ const read = useCallback(() => store.read(definition.id), [store, definition.id]);
81
+ const raw = useSyncExternalStore(subscribe, read);
82
+ const warnedRef = useRef(undefined);
83
+ const sanitized = useMemo(() => {
84
+ const input = raw === undefined ? undefined : JSON.stringify([definition.id, raw]);
85
+ const warn = input !== undefined && input !== warnedRef.current ? warnOnConsole : silently;
86
+ warnedRef.current = input;
87
+ return sanitizeStored(definition, stored, ctx, warn);
88
+ }, [definition, stored, ctx, raw]);
89
+ const previousRef = useRef(undefined);
90
+ const value = previousRef.current !== undefined && sameValue(previousRef.current.value, sanitized)
91
+ ? previousRef.current.value
92
+ : sanitized;
93
+ previousRef.current = { value };
94
+ const onChange = useCallback((next) => set(next, 'replace'), [set]);
95
+ const options = useMemo(() => {
96
+ const { initialValue: _inside, ...rest } = definition;
97
+ return { ...rest, value, onChange };
98
+ }, [definition, value, onChange]);
99
+ return { options, isDefault: showsDefault(arg, stored, () => value), reset };
100
+ }
101
+ /** A compile-time check: useStoredFields takes a `readonly` list of definitions (an `as const` module constant). */
102
+ function takesReadonlyDefinitions(definitions) {
103
+ return definitions;
104
+ }
105
+ /** Whether a field with this raw value shows its default, by useStoredField's rule; silent, as a summary does not warn. */
106
+ function readsAsDefault(definition, raw, ctx) {
107
+ const arg = argOf(definition, ctx);
108
+ const stored = (raw === null ? undefined : arg.valueType.parse(raw)) ?? arg.default;
109
+ return showsDefault(arg, stored, () => sanitizeStored(definition, stored, ctx, silently));
110
+ }
111
+ /**
112
+ * Whether stored fields show their defaults, by the same rule as useStoredField, with one store
113
+ * subscription for all of them: its snapshot is their raw values joined into one string, which
114
+ * changes exactly when one of them does. It is a summary, so it never warns (the fields do).
115
+ * Each field is re-read only when its raw value, its definition or the field context changes,
116
+ * so typing into one field does not re-read its siblings.
117
+ */
118
+ export function useStoredFields(definitions) {
119
+ const store = useArgStore();
120
+ const ctx = useFieldContext();
121
+ // Called on the store, not passed detached: a store written as a class uses `this`.
122
+ const subscribe = useCallback((listener) => store.subscribe(listener), [store]);
123
+ const readAll = useCallback(() => JSON.stringify(definitions.map((d) => store.read(d.id) ?? null)), [store, definitions]);
124
+ const snapshot = useSyncExternalStore(subscribe, readAll);
125
+ const readingsRef = useRef(new Map());
126
+ const changed = useMemo(() => {
127
+ const raws = JSON.parse(snapshot);
128
+ const previous = readingsRef.current;
129
+ const readings = new Map();
130
+ definitions.forEach((definition, i) => {
131
+ const raw = raws[i];
132
+ const before = previous.get(definition.id);
133
+ const reading = before !== undefined && before.definition === definition && before.raw === raw && before.ctx === ctx
134
+ ? before
135
+ : { definition, raw, ctx, isDefault: readsAsDefault(definition, raw, ctx) };
136
+ readings.set(definition.id, reading);
137
+ });
138
+ readingsRef.current = readings;
139
+ return new Set([...readings.values()].filter((r) => !r.isDefault).map((r) => r.definition.id));
140
+ }, [snapshot, definitions, ctx]);
141
+ const isDefault = useCallback((id) => {
142
+ if (!definitions.some((d) => d.id === id))
143
+ throw new Error(`Field '${id}': not among the fields given to useStoredFields`);
144
+ return !changed.has(id);
145
+ }, [definitions, changed]);
146
+ const resetAll = useCallback((method = 'replace') => {
147
+ store.write(definitions.map((d) => ({ key: d.id, raw: undefined })), method);
148
+ }, [store, definitions]);
149
+ return { isDefault, changedCount: changed.size, resetAll };
150
+ }
151
+ //# sourceMappingURL=useStoredField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStoredField.js","sourceRoot":"","sources":["../src/useStoredField.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type FieldControls, type FieldOptions } from './field.js';
2
+ import { type FieldCtx } from './FieldContext.js';
3
+ import type { ErrorRoutesRegistry, ValueOrFn } from './types.js';
4
+ export type TextOwnOptions = {
5
+ control?: 'input' | 'textarea';
6
+ };
7
+ export type TextFieldOptions<RouteId extends string = string, Ctx = any> = FieldOptions<string, string, RouteId, Ctx> & TextOwnOptions;
8
+ export interface TextFieldControls extends FieldControls<string, string> {
9
+ control: 'input' | 'textarea';
10
+ }
11
+ /** The implicit initial value for a text field: `initialValue` resolved, else the empty string. */
12
+ export declare function textInitialValue(opts: {
13
+ initialValue?: ValueOrFn<string>;
14
+ }, ctx: FieldCtx, id: string): string;
15
+ /**
16
+ * A field whose value is a string (moxb's Text, TextAnt, TextSearchAnt). `control` says whether
17
+ * vultus-antd renders an input or a textarea; defaults to `'input'`. A field inside the field or
18
+ * changed locally requests on every change; one that commits holds requests back until the
19
+ * widget decides (blur or Enter), as any commit-mode field does.
20
+ */
21
+ export declare function useTextField<RouteId extends string = string, Ctx = any>(opts: TextFieldOptions<RouteId, Ctx>, registry?: ErrorRoutesRegistry<RouteId>): TextFieldControls;
22
+ //# sourceMappingURL=useTextField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTextField.d.ts","sourceRoot":"","sources":["../src/useTextField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEjE,MAAM,MAAM,cAAc,GAAG;IAAE,OAAO,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;CAAE,CAAC;AAEhE,MAAM,MAAM,gBAAgB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,GAAG,GAAG,GAAG,IACrE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,cAAc,CAAC;AAE9D,MAAM,WAAW,iBAAkB,SAAQ,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC;IACtE,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC;CAC/B;AAED,mGAAmG;AACnG,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IAAE,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAA;CAAE,EAC1C,GAAG,EAAE,QAAQ,EACb,EAAE,EAAE,MAAM,GACT,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,GAAG,GAAG,GAAG,EACrE,IAAI,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,EACpC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,GACtC,iBAAiB,CAMnB"}
@@ -0,0 +1,21 @@
1
+ import { useFieldCore } from './field.js';
2
+ import { useFieldContext } from './FieldContext.js';
3
+ import { resolveIn } from './resolve.js';
4
+ /** The implicit initial value for a text field: `initialValue` resolved, else the empty string. */
5
+ export function textInitialValue(opts, ctx, id) {
6
+ return resolveIn(opts.initialValue, '', ctx, `Field '${id}': option 'initialValue'`);
7
+ }
8
+ /**
9
+ * A field whose value is a string (moxb's Text, TextAnt, TextSearchAnt). `control` says whether
10
+ * vultus-antd renders an input or a textarea; defaults to `'input'`. A field inside the field or
11
+ * changed locally requests on every change; one that commits holds requests back until the
12
+ * widget decides (blur or Enter), as any commit-mode field does.
13
+ */
14
+ export function useTextField(opts, registry) {
15
+ const ctx = useFieldContext();
16
+ const inside = !opts.onChange && !opts.commit;
17
+ const initialValue = () => textInitialValue(opts, ctx, opts.id);
18
+ const field = useFieldCore('text', inside ? { ...opts, initialValue } : opts, registry);
19
+ return { ...field, control: opts.control ?? 'input' };
20
+ }
21
+ //# sourceMappingURL=useTextField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTextField.js","sourceRoot":"","sources":["../src/useTextField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAyC,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,eAAe,EAAiB,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAYzC,mGAAmG;AACnG,MAAM,UAAU,gBAAgB,CAC9B,IAA0C,EAC1C,GAAa,EACb,EAAU;IAEV,OAAO,SAAS,CAAS,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,0BAA0B,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAoC,EACpC,QAAuC;IAEvC,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAC9C,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,YAAY,CAA+B,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACtH,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,OAAO,EAAE,CAAC;AACxD,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { type FieldControls, type FieldOptions } from './field.js';
2
+ import { type FieldCtx } from './FieldContext.js';
3
+ import { type TreeExpand, type TreeNode, type TreeNodeControls, type TreeSelection } from './tree.js';
4
+ import type { ErrorRoutesRegistry, ValueOrFn } from './types.js';
5
+ export type TreeOwnOptions<T extends string = string, Ctx = any> = {
6
+ /** The roots, each with its children; static or derived (a function, of the field context if it declares a parameter). */
7
+ nodes: ValueOrFn<TreeNode<T>[], Ctx>;
8
+ /** What the value lists; default `'topmost'` (see TreeSelection). */
9
+ selection?: TreeSelection;
10
+ /** Which nodes a widget opens at first; default `'top'` (see TreeExpand). */
11
+ expand?: TreeExpand;
12
+ };
13
+ export type TreeFieldOptions<T extends string = string, RouteId extends string = string, Ctx = any> = FieldOptions<T[], T[], RouteId, Ctx> & TreeOwnOptions<T, Ctx>;
14
+ export interface TreeFieldControls<T extends string = string> extends FieldControls<T[], T[]> {
15
+ /** The roots as the widgets get them, each with its children. */
16
+ nodes: TreeNodeControls<T>[];
17
+ selection: TreeSelection;
18
+ /** The nodes a widget opens at first (from `expand`). */
19
+ expandedKeys: T[];
20
+ /** Every node, by its value. */
21
+ byValue: Map<T, TreeNodeControls<T>>;
22
+ /** Checks or unchecks a node (with its subtree, in the cascading selections); ignored (with a warning) for an unknown or disabled node. */
23
+ toggle(value: T): void;
24
+ }
25
+ /** The implicit initial value for a tree field: `initialValue` resolved, else the empty list. */
26
+ export declare function treeInitialValue<T extends string>(opts: {
27
+ initialValue?: ValueOrFn<T[]>;
28
+ }, ctx: FieldCtx, id: string): T[];
29
+ /**
30
+ * A field whose value is a set of tree nodes (Praeceptor's category filter), with antd
31
+ * TreeSelect's semantics: in the cascading selections a checked node stands for its subtree,
32
+ * and `selection` says which nodes the value lists (see TreeSelection). The value is shown
33
+ * and requested normalized (normalizeTreeValue): in tree pre-order, values that name no
34
+ * node (a stored value naming a removed node) kept at the end. `setValue` does not police
35
+ * disabled nodes (the widgets disable them); `toggle` refuses a disabled or unknown node with
36
+ * a warning, as useManyOfField does.
37
+ */
38
+ export declare function useTreeField<T extends string = string, RouteId extends string = string, Ctx = any>(opts: TreeFieldOptions<T, RouteId, Ctx>, registry?: ErrorRoutesRegistry<RouteId>): TreeFieldControls<T>;
39
+ //# sourceMappingURL=useTreeField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTreeField.d.ts","sourceRoot":"","sources":["../src/useTreeField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjF,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAEL,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAC1E,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEjE,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,GAAG,GAAG,GAAG,IAAI;IACjE,0HAA0H;IAC1H,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACrC,qEAAqE;IACrE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,6EAA6E;IAC7E,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,GAAG,GAAG,GAAG,IAChG,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAEhE,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC3F,iEAAiE;IACjE,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,SAAS,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,YAAY,EAAE,CAAC,EAAE,CAAC;IAClB,gCAAgC;IAChC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,2IAA2I;IAC3I,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;CACxB;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAC/C,IAAI,EAAE;IAAE,YAAY,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAA;CAAE,EACvC,GAAG,EAAE,QAAQ,EACb,EAAE,EAAE,MAAM,GACT,CAAC,EAAE,CAEL;AAMD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,GAAG,GAAG,GAAG,EAChG,IAAI,EAAE,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EACvC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,GACtC,iBAAiB,CAAC,CAAC,CAAC,CAgDtB"}
@@ -0,0 +1,66 @@
1
+ import { useFieldCore } from './field.js';
2
+ import { resolveChoice } from './choices.js';
3
+ import { useFieldContext } from './FieldContext.js';
4
+ import { resolveIn } from './resolve.js';
5
+ import { coveredNodes, expandedTreeKeys, normalizeTreeValue, toggleTreeValue, } from './tree.js';
6
+ /** The implicit initial value for a tree field: `initialValue` resolved, else the empty list. */
7
+ export function treeInitialValue(opts, ctx, id) {
8
+ return resolveIn(opts.initialValue, [], ctx, `Field '${id}': option 'initialValue'`);
9
+ }
10
+ function sameList(a, b) {
11
+ return a.length === b.length && a.every((v, i) => v === b[i]);
12
+ }
13
+ /**
14
+ * A field whose value is a set of tree nodes (Praeceptor's category filter), with antd
15
+ * TreeSelect's semantics: in the cascading selections a checked node stands for its subtree,
16
+ * and `selection` says which nodes the value lists (see TreeSelection). The value is shown
17
+ * and requested normalized (normalizeTreeValue): in tree pre-order, values that name no
18
+ * node (a stored value naming a removed node) kept at the end. `setValue` does not police
19
+ * disabled nodes (the widgets disable them); `toggle` refuses a disabled or unknown node with
20
+ * a warning, as useManyOfField does.
21
+ */
22
+ export function useTreeField(opts, registry) {
23
+ const ctx = useFieldContext();
24
+ const nodes = resolveIn(opts.nodes, [], ctx, `Field '${opts.id}': option 'nodes'`);
25
+ const selection = opts.selection ?? 'topmost';
26
+ const inside = !opts.onChange && !opts.commit;
27
+ const initialValue = () => treeInitialValue(opts, ctx, opts.id);
28
+ const field = useFieldCore('tree', inside ? { ...opts, initialValue } : opts, registry);
29
+ // The same list object while it is already normalized, so the value is stable across renders.
30
+ const normalized = normalizeTreeValue(nodes, field.value, selection);
31
+ const value = sameList(normalized, field.value) ? field.value : normalized;
32
+ const checked = new Set(coveredNodes(nodes, value, selection));
33
+ const byValue = new Map();
34
+ const control = (node) => {
35
+ const controls = { ...resolveChoice(opts.id, node), children: undefined, checked: checked.has(node.value) };
36
+ byValue.set(node.value, controls);
37
+ if (node.children)
38
+ controls.children = node.children.map(control);
39
+ return controls;
40
+ };
41
+ const nodeControls = nodes.map(control);
42
+ const setValue = (next) => field.setValue(normalizeTreeValue(nodes, next, selection));
43
+ const toggle = (v) => {
44
+ const node = byValue.get(v);
45
+ if (!node) {
46
+ console.warn(`Field '${opts.id}': '${v}' is not one of its nodes`);
47
+ return;
48
+ }
49
+ if (!node.enabled) {
50
+ console.warn(`Field '${opts.id}': node '${v}' is disabled — ${node.whyDisabled ?? '(no reason)'}`);
51
+ return;
52
+ }
53
+ field.setValue(toggleTreeValue(nodes, value, v, selection));
54
+ };
55
+ return {
56
+ ...field,
57
+ value,
58
+ setValue,
59
+ nodes: nodeControls,
60
+ selection,
61
+ expandedKeys: expandedTreeKeys(nodes, value, opts.expand ?? 'top'),
62
+ byValue,
63
+ toggle,
64
+ };
65
+ }
66
+ //# sourceMappingURL=useTreeField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTreeField.js","sourceRoot":"","sources":["../src/useTreeField.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * How a value is kept in a store as a string (moxb's url-arg types): a URL query, localStorage.
3
+ * A stored string comes from outside (an edited or old URL), so `parse` never throws: a string
4
+ * that stands for no value gives `undefined`, and the caller falls back to its default.
5
+ * `isEqual` is the value's meaning (a list as a set, a range by both ends), used everywhere two
6
+ * values are compared, e.g. to leave a default out of the store.
7
+ */
8
+ export interface ValueType<T> {
9
+ parse(raw: string): T | undefined;
10
+ format(value: T): string;
11
+ isEqual(a: T, b: T): boolean;
12
+ }
13
+ /** A boolean as `1` / `0`; any other string is no value. */
14
+ export declare const booleanType: ValueType<boolean>;
15
+ /** A string as itself. */
16
+ export declare const stringType: ValueType<string>;
17
+ /**
18
+ * A set of strings as a sorted comma list; the empty string is the empty list (so a list
19
+ * present but empty differs from an absent one). Reading drops empty items. An item containing
20
+ * `,`, or an empty one (it would read back as no item), cannot be stored and throws: that is the
21
+ * definer's mistake, not outside input.
22
+ */
23
+ export declare const stringSetType: ValueType<string[]>;
24
+ /** A finite number as its decimal string; an empty or non-numeric string is no value. */
25
+ export declare const numberType: ValueType<number>;
26
+ /** A `[from, to]` range as `from..to`; ends that are not numbers, or out of order, are no value. */
27
+ export declare const rangeType: ValueType<[number, number]>;
28
+ //# sourceMappingURL=valueTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valueTypes.d.ts","sourceRoot":"","sources":["../src/valueTypes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;CAC9B;AAED,4DAA4D;AAC5D,eAAO,MAAM,WAAW,EAAE,SAAS,CAAC,OAAO,CAI1C,CAAC;AAEF,0BAA0B;AAC1B,eAAO,MAAM,UAAU,EAAE,SAAS,CAAC,MAAM,CAIxC,CAAC;AAOF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,MAAM,EAAE,CAa7C,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,UAAU,EAAE,SAAS,CAAC,MAAM,CAQxC,CAAC;AAEF,oGAAoG;AACpG,eAAO,MAAM,SAAS,EAAE,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAWjD,CAAC"}
@@ -0,0 +1,65 @@
1
+ /** A boolean as `1` / `0`; any other string is no value. */
2
+ export const booleanType = {
3
+ parse: (raw) => (raw === '1' ? true : raw === '0' ? false : undefined),
4
+ format: (value) => (value ? '1' : '0'),
5
+ isEqual: (a, b) => a === b,
6
+ };
7
+ /** A string as itself. */
8
+ export const stringType = {
9
+ parse: (raw) => raw,
10
+ format: (value) => value,
11
+ isEqual: (a, b) => a === b,
12
+ };
13
+ /** A set's canonical form: sorted, without duplicates. */
14
+ function canonicalSet(items) {
15
+ return [...new Set(items)].sort();
16
+ }
17
+ /**
18
+ * A set of strings as a sorted comma list; the empty string is the empty list (so a list
19
+ * present but empty differs from an absent one). Reading drops empty items. An item containing
20
+ * `,`, or an empty one (it would read back as no item), cannot be stored and throws: that is the
21
+ * definer's mistake, not outside input.
22
+ */
23
+ export const stringSetType = {
24
+ parse: (raw) => canonicalSet(raw.split(',').filter((item) => item !== '')),
25
+ format: (value) => {
26
+ if (value.includes(''))
27
+ throw new Error(`'' is empty and cannot be stored in a list`);
28
+ const bad = value.find((item) => item.includes(','));
29
+ if (bad !== undefined)
30
+ throw new Error(`'${bad}' contains ',' and cannot be stored in a list`);
31
+ return canonicalSet(value).join(',');
32
+ },
33
+ isEqual: (a, b) => {
34
+ const x = canonicalSet(a);
35
+ const y = canonicalSet(b);
36
+ return x.length === y.length && x.every((item, i) => item === y[i]);
37
+ },
38
+ };
39
+ /** A finite number as its decimal string; an empty or non-numeric string is no value. */
40
+ export const numberType = {
41
+ parse: (raw) => {
42
+ if (raw.trim() === '')
43
+ return undefined;
44
+ const n = Number(raw);
45
+ return Number.isFinite(n) ? n : undefined;
46
+ },
47
+ format: (value) => String(value),
48
+ isEqual: (a, b) => a === b,
49
+ };
50
+ /** A `[from, to]` range as `from..to`; ends that are not numbers, or out of order, are no value. */
51
+ export const rangeType = {
52
+ parse: (raw) => {
53
+ const ends = raw.split('..');
54
+ if (ends.length !== 2)
55
+ return undefined;
56
+ const from = numberType.parse(ends[0]);
57
+ const to = numberType.parse(ends[1]);
58
+ if (from === undefined || to === undefined || from > to)
59
+ return undefined;
60
+ return [from, to];
61
+ },
62
+ format: ([from, to]) => `${from}..${to}`,
63
+ isEqual: (a, b) => a[0] === b[0] && a[1] === b[1],
64
+ };
65
+ //# sourceMappingURL=valueTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valueTypes.js","sourceRoot":"","sources":["../src/valueTypes.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vultus-core",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Framework-neutral action model + hooks for vultus (antd-free by design)",
6
6
  "repository": {
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext, type ReactNode } from 'react';
2
+ import type { ArgStore } from './argStore.js';
3
+
4
+ const ArgStoreContext = createContext<ArgStore | undefined>(undefined);
5
+
6
+ /** Gives the subtree below an ArgStore (Praeceptor: the URL store around the module page, localStorage around the print dialog). */
7
+ export function ArgStoreProvider({ store, children }: { store: ArgStore; children?: ReactNode }) {
8
+ return <ArgStoreContext.Provider value={store}>{children}</ArgStoreContext.Provider>;
9
+ }
10
+
11
+ /** The nearest ArgStoreProvider's store; a hook with none above it fails loudly rather than reading nothing. */
12
+ export function useArgStore(): ArgStore {
13
+ const store = useContext(ArgStoreContext);
14
+ if (store === undefined) throw new Error('No ArgStoreProvider is above this component');
15
+ return store;
16
+ }
@@ -0,0 +1,98 @@
1
+ import { createContext, useContext, useMemo, type JSX, type ReactNode } from 'react';
2
+
3
+ /**
4
+ * What useFieldContext gives when no FieldContextProvider is above: a field's function that
5
+ * declares a parameter then has nothing to receive, and resolving it throws.
6
+ */
7
+ export const NO_FIELD_CONTEXT: unique symbol = Symbol('vultus.noFieldContext');
8
+
9
+ /** The field context as the hooks hold it: the guarded value, or the mark that there is none. */
10
+ export type FieldCtx = object | typeof NO_FIELD_CONTEXT;
11
+
12
+ /** The merged value of the providers above, and the same value guarded against missing keys. */
13
+ interface Provided {
14
+ raw: object;
15
+ guarded: object;
16
+ }
17
+
18
+ const FieldContext = createContext<Provided | undefined>(undefined);
19
+
20
+ /**
21
+ * Keys that libraries read to find out what an object is (a thenable, JSON, a test matcher, a
22
+ * React element, a DOM node or element, a mock function), and the `@@`-prefixed sentinels
23
+ * (Immutable's) that printers such as pretty-format test for. Reading them on the context is a
24
+ * probe, not a mistake.
25
+ */
26
+ const PROBED_KEYS = new Set([
27
+ 'then', 'toJSON', 'asymmetricMatch', '$$typeof', 'nodeType', 'tagName', 'hasAttribute', '_isMockFunction',
28
+ ]);
29
+
30
+ function isProbe(key: string): boolean {
31
+ return PROBED_KEYS.has(key) || key.startsWith('@@');
32
+ }
33
+
34
+ /** What the guard throws for a key no provider gave, so readingContext can tell it from the function's own errors. */
35
+ class MissingKeyError extends Error {}
36
+
37
+ /**
38
+ * The context value as the definitions' functions receive it: reading a key that no provider
39
+ * gave throws, naming it and the keys present, so a forgotten value fails loudly instead of
40
+ * passing `undefined` along. A key provided with the value `undefined` is fine.
41
+ */
42
+ function guard(raw: object): object {
43
+ return new Proxy(raw, {
44
+ get(target, key, receiver) {
45
+ if (typeof key === 'string' && !isProbe(key) && !(key in target)) {
46
+ throw new MissingKeyError(`Field context: '${key}' was never provided (provided: ${Object.keys(target).join(', ')})`);
47
+ }
48
+ return Reflect.get(target, key, receiver);
49
+ },
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Runs `read`, a call of a definition's function with the field context. A key it reads that
55
+ * no provider gave throws with `where` (`Field '<id>': option '<name>'`) in front of the
56
+ * guard's message, so the error names the field and the option as well as the key; any other
57
+ * error passes through unchanged.
58
+ */
59
+ export function readingContext<T>(where: string, read: () => T): T {
60
+ try {
61
+ return read();
62
+ } catch (error) {
63
+ if (error instanceof MissingKeyError) throw new Error(`${where}: ${error.message}`, { cause: error });
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Gives the fields below a context, the argument their definitions' functions receive. Nested
70
+ * providers merge, the inner value winning: an app can provide `t` near the root and the data
71
+ * a page shows further down.
72
+ */
73
+ export function FieldContextProvider({ value, children }: { value: object; children?: ReactNode }) {
74
+ const outer = useContext(FieldContext);
75
+ // Memoized on the outer context and `value`: a caller passing a fresh object literal gets a
76
+ // new context every render anyway; a stable `value` keeps the fields below from re-rendering.
77
+ const provided = useMemo(() => {
78
+ const raw = { ...outer?.raw, ...value };
79
+ return { raw, guarded: guard(raw) };
80
+ }, [outer, value]);
81
+ return <FieldContext.Provider value={provided}>{children}</FieldContext.Provider>;
82
+ }
83
+
84
+ /**
85
+ * A FieldContextProvider typed for the context `Ctx` an app's definitions are written against.
86
+ * It is the same provider (one React context), so typed providers nest and merge with each
87
+ * other and with the untyped one; each gives part of the context.
88
+ */
89
+ export function createFieldContext<Ctx extends object>(): {
90
+ Provider: (props: { value: Partial<Ctx>; children?: ReactNode }) => JSX.Element;
91
+ } {
92
+ return { Provider: FieldContextProvider };
93
+ }
94
+
95
+ /** The field context the hooks resolve functions with: the guarded value, or NO_FIELD_CONTEXT. */
96
+ export function useFieldContext(): FieldCtx {
97
+ return useContext(FieldContext)?.guarded ?? NO_FIELD_CONTEXT;
98
+ }