vultus-core 0.5.1 → 0.5.3
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 +96 -10
- package/dist/field.d.ts +12 -1
- package/dist/field.d.ts.map +1 -1
- package/dist/field.js +6 -1
- package/dist/field.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/kinds.d.ts +20 -15
- package/dist/kinds.d.ts.map +1 -1
- package/dist/kinds.js +46 -20
- package/dist/kinds.js.map +1 -1
- package/dist/parseApiError.d.ts +6 -0
- package/dist/parseApiError.d.ts.map +1 -1
- package/dist/parseApiError.js +7 -1
- package/dist/parseApiError.js.map +1 -1
- package/dist/tree.d.ts +38 -9
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +91 -21
- package/dist/tree.js.map +1 -1
- package/dist/{useTreeField.d.ts → useManyOfTreeField.d.ts} +10 -9
- package/dist/useManyOfTreeField.d.ts.map +1 -0
- package/dist/{useTreeField.js → useManyOfTreeField.js} +9 -8
- package/dist/useManyOfTreeField.js.map +1 -0
- package/dist/useOneOfTreeField.d.ts +79 -0
- package/dist/useOneOfTreeField.d.ts.map +1 -0
- package/dist/useOneOfTreeField.js +117 -0
- package/dist/useOneOfTreeField.js.map +1 -0
- package/dist/useStoredField.d.ts +1 -0
- package/dist/useStoredField.d.ts.map +1 -1
- package/dist/useStoredField.js +3 -3
- package/dist/useStoredField.js.map +1 -1
- package/package.json +3 -2
- package/src/field.ts +25 -5
- package/src/index.ts +9 -4
- package/src/kinds.ts +46 -22
- package/src/parseApiError.ts +7 -1
- package/src/tree.ts +105 -20
- package/src/{useTreeField.ts → useManyOfTreeField.ts} +14 -13
- package/src/useOneOfTreeField.ts +182 -0
- package/src/useStoredField.ts +6 -4
- package/dist/useTreeField.d.ts.map +0 -1
- package/dist/useTreeField.js.map +0 -1
|
@@ -0,0 +1,117 @@
|
|
|
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 { canChooseNode, choosableValues, expandedTreeKeys, treePath, } from './tree.js';
|
|
6
|
+
/**
|
|
7
|
+
* The implicit initial value for a one-of tree field: `initialValue` resolved through the field
|
|
8
|
+
* context, else '' when `optional`, else the first node `canChooseNode` accepts in tree
|
|
9
|
+
* pre-order, else '' (no node can be chosen; as oneOf's undefined choice).
|
|
10
|
+
*/
|
|
11
|
+
export function oneOfTreeInitialValue(opts, ctx, id, nodes) {
|
|
12
|
+
const resolved = resolveIn(opts.initialValue, undefined, ctx, `Field '${id}': option 'initialValue'`);
|
|
13
|
+
if (resolved !== undefined)
|
|
14
|
+
return resolved;
|
|
15
|
+
if (opts.optional)
|
|
16
|
+
return '';
|
|
17
|
+
return choosableValues(nodes, opts.choose ?? 'any')[0] ?? '';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A field whose value is a single tree node, or nothing (Praeceptor's category picker, a
|
|
21
|
+
* table of contents): `choosable` (a per-node flag, default true; a folder) and the field's
|
|
22
|
+
* `choose: 'any' | 'leaves'` together say which nodes `setValue` accepts (canChooseNode);
|
|
23
|
+
* `optional` says whether '' (nothing chosen) is one of them too. `setValue` refuses, with a
|
|
24
|
+
* warning (as useOneOfField refuses a disabled choice): a value naming no node, a node
|
|
25
|
+
* `canChooseNode` rejects, and '' unless `optional`. `clear()` requests ''. The field's current
|
|
26
|
+
* state is the chosen node's description, unless `valueDescriptions` words it differently (as
|
|
27
|
+
* oneOf). `previous` / `next` are the choosable nodes just before and after `current`'s own
|
|
28
|
+
* position in tree pre-order (so a `current` that cannot itself be chosen still gets its true
|
|
29
|
+
* neighbours, skipping only the nodes between them that cannot be chosen); with no `current`,
|
|
30
|
+
* `previous` is undefined and `next` is the first choosable node. `expandedKeys` (from `expand`,
|
|
31
|
+
* default `'selected'`) are the value's ancestors,
|
|
32
|
+
* so a table of contents opens where the reader is. Inside the field the value starts at
|
|
33
|
+
* `oneOfTreeInitialValue`.
|
|
34
|
+
*/
|
|
35
|
+
export function useOneOfTreeField(opts, registry) {
|
|
36
|
+
const ctx = useFieldContext();
|
|
37
|
+
const nodes = resolveIn(opts.nodes, [], ctx, `Field '${opts.id}': option 'nodes'`);
|
|
38
|
+
const choose = opts.choose ?? 'any';
|
|
39
|
+
const optional = opts.optional ?? false;
|
|
40
|
+
const inside = !opts.onChange && !opts.commit;
|
|
41
|
+
const initialValue = () => oneOfTreeInitialValue(opts, ctx, opts.id, nodes);
|
|
42
|
+
const field = useFieldCore('oneOfTree', inside ? { ...opts, initialValue } : opts, registry);
|
|
43
|
+
const valuePath = field.value ? treePath(nodes, field.value) : [];
|
|
44
|
+
const ancestors = new Set(valuePath);
|
|
45
|
+
const byValue = new Map();
|
|
46
|
+
const control = (node) => {
|
|
47
|
+
const controls = {
|
|
48
|
+
...resolveChoice(opts.id, node),
|
|
49
|
+
children: undefined,
|
|
50
|
+
choosable: canChooseNode(node, choose),
|
|
51
|
+
current: node.value === field.value,
|
|
52
|
+
onPath: ancestors.has(node.value) && node.value !== field.value,
|
|
53
|
+
};
|
|
54
|
+
byValue.set(node.value, controls);
|
|
55
|
+
if (node.children)
|
|
56
|
+
controls.children = node.children.map(control);
|
|
57
|
+
return controls;
|
|
58
|
+
};
|
|
59
|
+
const nodeControls = nodes.map(control);
|
|
60
|
+
const current = field.value ? byValue.get(field.value) : undefined;
|
|
61
|
+
const pathControls = valuePath.map((v) => byValue.get(v));
|
|
62
|
+
// byValue's insertion order is the full tree's pre-order (control() visits a node before its
|
|
63
|
+
// children), so it doubles as "every node, choosable or not" for finding current's neighbours.
|
|
64
|
+
const fullOrder = [...byValue.keys()];
|
|
65
|
+
const nearestChoosable = (from, step) => {
|
|
66
|
+
for (let i = from; i >= 0 && i < fullOrder.length; i += step) {
|
|
67
|
+
const node = byValue.get(fullOrder[i]);
|
|
68
|
+
if (node.choosable)
|
|
69
|
+
return node;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
};
|
|
73
|
+
const index = current ? fullOrder.indexOf(current.value) : -1;
|
|
74
|
+
const previous = nearestChoosable(index - 1, -1);
|
|
75
|
+
const next = nearestChoosable(index + 1, 1);
|
|
76
|
+
const setValue = (requested) => {
|
|
77
|
+
if (requested === '') {
|
|
78
|
+
if (!optional) {
|
|
79
|
+
console.warn(`Field '${opts.id}': '' is refused — the field is not optional`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
field.setValue('');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const node = byValue.get(requested);
|
|
86
|
+
if (!node) {
|
|
87
|
+
console.warn(`Field '${opts.id}': '${requested}' is not one of its nodes`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!node.choosable) {
|
|
91
|
+
if (!node.enabled) {
|
|
92
|
+
console.warn(`Field '${opts.id}': node '${requested}' is disabled — ${node.whyDisabled ?? '(no reason)'}`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.warn(`Field '${opts.id}': node '${requested}' cannot be chosen`);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
field.setValue(requested);
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
...field,
|
|
103
|
+
valueDescription: field.valueDescription ?? current?.description,
|
|
104
|
+
setValue,
|
|
105
|
+
nodes: nodeControls,
|
|
106
|
+
byValue,
|
|
107
|
+
current,
|
|
108
|
+
path: pathControls,
|
|
109
|
+
previous,
|
|
110
|
+
next,
|
|
111
|
+
optional,
|
|
112
|
+
choose,
|
|
113
|
+
expandedKeys: expandedTreeKeys(nodes, field.value ? [field.value] : [], opts.expand ?? 'selected'),
|
|
114
|
+
clear: () => setValue(''),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=useOneOfTreeField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOneOfTreeField.js","sourceRoot":"","sources":["../src/useOneOfTreeField.ts"],"names":[],"mappings":"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"}
|
package/dist/useStoredField.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface StoredField<D extends FieldDefinition = FieldDefinition> {
|
|
|
19
19
|
options: D extends any ? Omit<D, 'initialValue'> & {
|
|
20
20
|
value: any;
|
|
21
21
|
onChange(value: any): void;
|
|
22
|
+
hrefFor(value: any): string | undefined;
|
|
22
23
|
} : never;
|
|
23
24
|
isDefault: boolean;
|
|
24
25
|
/** Removes the field's key from the store, so it shows its default again. */
|
|
@@ -1 +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,
|
|
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,GAClB,IAAI,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG;QAAE,KAAK,EAAE,GAAG,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;QAAC,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GAC7G,KAAK,CAAC;IACV,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"}
|
package/dist/useStoredField.js
CHANGED
|
@@ -74,7 +74,7 @@ export function useStoredField(definition) {
|
|
|
74
74
|
? previousArg
|
|
75
75
|
: fresh;
|
|
76
76
|
argRef.current = arg;
|
|
77
|
-
const { value: stored, set, reset } = useArg(arg);
|
|
77
|
+
const { value: stored, set, reset, hrefFor } = useArg(arg);
|
|
78
78
|
// Called on the store, not passed detached: a store written as a class uses `this`.
|
|
79
79
|
const subscribe = useCallback((listener) => store.subscribe(listener), [store]);
|
|
80
80
|
const read = useCallback(() => store.read(definition.id), [store, definition.id]);
|
|
@@ -94,8 +94,8 @@ export function useStoredField(definition) {
|
|
|
94
94
|
const onChange = useCallback((next) => set(next, 'replace'), [set]);
|
|
95
95
|
const options = useMemo(() => {
|
|
96
96
|
const { initialValue: _inside, ...rest } = definition;
|
|
97
|
-
return { ...rest, value, onChange };
|
|
98
|
-
}, [definition, value, onChange]);
|
|
97
|
+
return { ...rest, value, onChange, hrefFor };
|
|
98
|
+
}, [definition, value, onChange, hrefFor]);
|
|
99
99
|
return { options, isDefault: showsDefault(arg, stored, () => value), reset };
|
|
100
100
|
}
|
|
101
101
|
/** A compile-time check: useStoredFields takes a `readonly` list of definitions (an `as const` module constant). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStoredField.js","sourceRoot":"","sources":["../src/useStoredField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE3E,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAiB,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAwB,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,UAA2B,EAAE,GAAa;IAC9D,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;IAChC,IAAI,SAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,UAAU,EAAE,MAAO,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC;AAC1E,CAAC;AAED,SAAS,QAAQ,CAAC,QAAgB,IAAS,CAAC;AAE5C,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,CAAU,EAAE,CAAU;IACvC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxG,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,GAAuB,EAAE,MAAe,EAAE,KAAoB;IAClF,OAAO,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;AACnG,CAAC;
|
|
1
|
+
{"version":3,"file":"useStoredField.js","sourceRoot":"","sources":["../src/useStoredField.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.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Framework-neutral action model + hooks for vultus (antd-free by design)",
|
|
6
6
|
"repository": {
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
56
|
-
"test": "vitest run"
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
57
58
|
},
|
|
58
59
|
"main": "./dist/index.js",
|
|
59
60
|
"types": "./dist/index.d.ts"
|
package/src/field.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
2
|
import { getReason, getVerdict, invertDecision } from './decision.js';
|
|
3
3
|
import { useFieldContext } from './FieldContext.js';
|
|
4
4
|
import { resolveIn } from './resolve.js';
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
/** The field kinds (FieldControls.type and a FieldDefinition's `kind`). */
|
|
26
|
-
export type FieldKind = 'boolean' | 'mixed-boolean' | 'oneOf' | 'manyOf' | 'numeric' | 'range' | '
|
|
26
|
+
export type FieldKind = 'boolean' | 'mixed-boolean' | 'oneOf' | 'manyOf' | 'numeric' | 'range' | 'manyOfTree' | 'oneOfTree' | 'text';
|
|
27
27
|
|
|
28
28
|
/** The widget to render a field with: a name, or a name with display options overriding its defaults. */
|
|
29
29
|
export type WidgetHint = string | { name: string; [option: string]: unknown };
|
|
@@ -78,11 +78,18 @@ export interface FieldBaseOptions<TValue, Ctx = any> {
|
|
|
78
78
|
*/
|
|
79
79
|
export type FieldStorage<TValue, TRequest = TValue, RouteId extends string = string, Ctx = any> =
|
|
80
80
|
/** Inside the field: its own state, starting at `initialValue` (resolved once, when the field mounts). */
|
|
81
|
-
| { initialValue?: ValueOrFn<TValue, Ctx>; value?: never; onChange?: never; commit?: never }
|
|
81
|
+
| { initialValue?: ValueOrFn<TValue, Ctx>; value?: never; onChange?: never; commit?: never; hrefFor?: never }
|
|
82
82
|
/** Outside, changed locally: the caller owns the value and applies `onChange`. */
|
|
83
|
-
| {
|
|
83
|
+
| {
|
|
84
|
+
value: TValue; onChange: (value: TRequest) => void; initialValue?: never; commit?: never;
|
|
85
|
+
/** The link that requesting `value` amounts to, where the caller has one (e.g. a URL store). */
|
|
86
|
+
hrefFor?: (value: TRequest) => string | undefined;
|
|
87
|
+
}
|
|
84
88
|
/** Outside, committed immediately: `commit.fire` stores the change (pending, messages, confirmation). */
|
|
85
|
-
| {
|
|
89
|
+
| {
|
|
90
|
+
value: TValue; commit: FieldCommit<TRequest, RouteId>; initialValue?: never; onChange?: never;
|
|
91
|
+
hrefFor?: (value: TRequest) => string | undefined;
|
|
92
|
+
};
|
|
86
93
|
|
|
87
94
|
export type FieldOptions<TValue, TRequest = TValue, RouteId extends string = string, Ctx = any> =
|
|
88
95
|
FieldBaseOptions<TValue, Ctx> & FieldStorage<TValue, TRequest, RouteId, Ctx>;
|
|
@@ -110,6 +117,13 @@ export interface FieldControls<TValue, TRequest = TValue> {
|
|
|
110
117
|
messages: Message[];
|
|
111
118
|
/** The confirmation a widget must obtain before requesting `next`, if any. */
|
|
112
119
|
confirmationFor(next: TRequest): ConfirmationSpec | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* The link that requesting `value` amounts to, where whoever keeps the value gave one (a URL
|
|
122
|
+
* store does); `undefined` otherwise, and always inside the field. Every kind's hook provides
|
|
123
|
+
* it; optional here so that controls built by hand (as before vultus-core 0.5.3) need none, so
|
|
124
|
+
* a widget calls it as `field.hrefFor?.(value)`.
|
|
125
|
+
*/
|
|
126
|
+
hrefFor?(value: TRequest): string | undefined;
|
|
113
127
|
widget: WidgetHint | undefined;
|
|
114
128
|
}
|
|
115
129
|
|
|
@@ -215,6 +229,11 @@ export function useFieldCore<TValue, TRequest extends TValue = TValue, RouteId e
|
|
|
215
229
|
return typeof spec === 'function' ? spec(next) : spec;
|
|
216
230
|
};
|
|
217
231
|
|
|
232
|
+
// Inside the field nothing outside keeps the value, so nothing gives a link: an `hrefFor` that
|
|
233
|
+
// came anyway (untyped options; the types forbid it) is ignored.
|
|
234
|
+
const hrefForOpt = mode === 'inside' ? undefined : (opts as { hrefFor?: (value: TRequest) => string | undefined }).hrefFor;
|
|
235
|
+
const hrefFor = useCallback((next: TRequest) => hrefForOpt?.(next), [hrefForOpt]);
|
|
236
|
+
|
|
218
237
|
return {
|
|
219
238
|
type,
|
|
220
239
|
id: opts.id,
|
|
@@ -231,6 +250,7 @@ export function useFieldCore<TValue, TRequest extends TValue = TValue, RouteId e
|
|
|
231
250
|
commits: mode === 'commit',
|
|
232
251
|
messages: mode === 'commit' ? (setter.messages ?? []) : [],
|
|
233
252
|
confirmationFor,
|
|
253
|
+
hrefFor,
|
|
234
254
|
widget: opts.widget,
|
|
235
255
|
};
|
|
236
256
|
}
|
package/src/index.ts
CHANGED
|
@@ -43,12 +43,17 @@ export {
|
|
|
43
43
|
} from './useRangeField.js';
|
|
44
44
|
export {
|
|
45
45
|
normalizeTreeValue, coveredNodes, toggleTreeValue, expandedTreeKeys, filterTree,
|
|
46
|
-
|
|
46
|
+
canChooseNode, choosableValues, treePath,
|
|
47
|
+
type TreeNode, type TreeNodeControls, type TreeSelection, type TreeExpand, type TreeChoose,
|
|
47
48
|
} from './tree.js';
|
|
48
49
|
export {
|
|
49
|
-
|
|
50
|
-
type
|
|
51
|
-
} from './
|
|
50
|
+
useManyOfTreeField, manyOfTreeInitialValue,
|
|
51
|
+
type ManyOfTreeFieldControls, type ManyOfTreeFieldOptions, type ManyOfTreeOwnOptions,
|
|
52
|
+
} from './useManyOfTreeField.js';
|
|
53
|
+
export {
|
|
54
|
+
useOneOfTreeField, oneOfTreeInitialValue,
|
|
55
|
+
type OneOfTreeFieldControls, type OneOfTreeFieldOptions, type OneOfTreeOwnOptions, type OneOfTreeNodeControls,
|
|
56
|
+
} from './useOneOfTreeField.js';
|
|
52
57
|
export {
|
|
53
58
|
useTextField, textInitialValue,
|
|
54
59
|
type TextFieldControls, type TextFieldOptions, type TextOwnOptions,
|
package/src/kinds.ts
CHANGED
|
@@ -3,16 +3,17 @@ import type { FieldBaseOptions, FieldKind } from './field.js';
|
|
|
3
3
|
import type { FieldCtx } from './FieldContext.js';
|
|
4
4
|
import { clamp, fit, resolveNumeric } from './numeric.js';
|
|
5
5
|
import { resolveIn } from './resolve.js';
|
|
6
|
-
import { normalizeTreeValue, type TreeNode } from './tree.js';
|
|
6
|
+
import { choosableValues, normalizeTreeValue, type TreeNode } from './tree.js';
|
|
7
7
|
import type { Fits, ValueOrFn } from './types.js';
|
|
8
8
|
import { boolInitialValue, type useBoolField } from './useBoolField.js';
|
|
9
9
|
import { manyOfInitialValue, type ManyOfOwnOptions, type useManyOfField } from './useManyOfField.js';
|
|
10
10
|
import { mixedBoolInitialValue, type MixedBool, type useMixedBoolField } from './useMixedBoolField.js';
|
|
11
11
|
import { numericInitialValue, type NumericOwnOptions, type useNumericField } from './useNumericField.js';
|
|
12
12
|
import { oneOfInitialValue, type OneOfOwnOptions, type useOneOfField } from './useOneOfField.js';
|
|
13
|
+
import { oneOfTreeInitialValue, type OneOfTreeOwnOptions, type useOneOfTreeField } from './useOneOfTreeField.js';
|
|
13
14
|
import { rangeInitialValue, type NumberRange, type RangeOwnOptions, type useRangeField } from './useRangeField.js';
|
|
14
15
|
import { textInitialValue, type TextOwnOptions, type useTextField } from './useTextField.js';
|
|
15
|
-
import {
|
|
16
|
+
import { manyOfTreeInitialValue, type ManyOfTreeOwnOptions, type useManyOfTreeField } from './useManyOfTreeField.js';
|
|
16
17
|
import { booleanType, numberType, rangeType, stringSetType, stringType, type ValueType } from './valueTypes.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -35,7 +36,8 @@ export type FieldDefinition<Ctx = any> =
|
|
|
35
36
|
| Definition<'manyOf', string[], ManyOfOwnOptions<string, Ctx>, Ctx>
|
|
36
37
|
| Definition<'numeric', number, NumericOwnOptions<Ctx>, Ctx>
|
|
37
38
|
| Definition<'range', NumberRange, RangeOwnOptions<Ctx>, Ctx>
|
|
38
|
-
| Definition<'
|
|
39
|
+
| Definition<'manyOfTree', string[], ManyOfTreeOwnOptions<string, Ctx>, Ctx>
|
|
40
|
+
| Definition<'oneOfTree', string, OneOfTreeOwnOptions<string, Ctx>, Ctx>
|
|
39
41
|
| Definition<'text', string, TextOwnOptions, Ctx>;
|
|
40
42
|
|
|
41
43
|
type DefinitionOf<K extends FieldKind> = Extract<FieldDefinition, { kind: K }>;
|
|
@@ -52,7 +54,8 @@ type DefinitionsFitTheirHooks = [
|
|
|
52
54
|
Fits<DefinitionOf<'manyOf'>, HookOptions<typeof useManyOfField>>,
|
|
53
55
|
Fits<DefinitionOf<'numeric'>, HookOptions<typeof useNumericField>>,
|
|
54
56
|
Fits<DefinitionOf<'range'>, HookOptions<typeof useRangeField>>,
|
|
55
|
-
Fits<DefinitionOf<'
|
|
57
|
+
Fits<DefinitionOf<'manyOfTree'>, HookOptions<typeof useManyOfTreeField>>,
|
|
58
|
+
Fits<DefinitionOf<'oneOfTree'>, HookOptions<typeof useOneOfTreeField>>,
|
|
56
59
|
Fits<DefinitionOf<'text'>, HookOptions<typeof useTextField>>,
|
|
57
60
|
];
|
|
58
61
|
|
|
@@ -79,7 +82,7 @@ function nodeValues(nodes: TreeNode[], into = new Set<string>()): Set<string> {
|
|
|
79
82
|
return into;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
/** A tree definition's nodes, resolved as
|
|
85
|
+
/** A tree definition's nodes, resolved as useManyOfTreeField resolves them. */
|
|
83
86
|
function nodesOf(definition: { id: string; nodes: ValueOrFn<TreeNode[]> }, ctx: FieldCtx): TreeNode[] {
|
|
84
87
|
return resolveIn<TreeNode[]>(definition.nodes, [], ctx, `Field '${definition.id}': option 'nodes'`);
|
|
85
88
|
}
|
|
@@ -87,10 +90,11 @@ function nodesOf(definition: { id: string; nodes: ValueOrFn<TreeNode[]> }, ctx:
|
|
|
87
90
|
/**
|
|
88
91
|
* A definition's default: `initialValue` resolved with the field context, else its kind's
|
|
89
92
|
* implicit initial value (false; the first enabled choice; the empty list; `min`, or 0, clamped
|
|
90
|
-
* into the bounds; `[min, max]`; the empty string). A tree's comes normalized under its
|
|
93
|
+
* into the bounds; `[min, max]`; the empty string). A many-of tree's comes normalized under its
|
|
91
94
|
* selection, as the hook shows it, so a value equal to what the field shows by default is equal
|
|
92
|
-
* to the default too
|
|
93
|
-
*
|
|
95
|
+
* to the default too; a one-of tree's is `''` when `optional`, else the first node it can choose
|
|
96
|
+
* in pre-order, else `''` (none can be chosen). It is what a store falls back to, what a reset
|
|
97
|
+
* restores, and what "is it set?" compares against; the kind's hook starts at the same value.
|
|
94
98
|
*/
|
|
95
99
|
export function initialValueOf(definition: FieldDefinition, ctx: FieldCtx): unknown {
|
|
96
100
|
const { id } = definition;
|
|
@@ -101,8 +105,9 @@ export function initialValueOf(definition: FieldDefinition, ctx: FieldCtx): unkn
|
|
|
101
105
|
case 'manyOf': return manyOfInitialValue(definition, ctx, id);
|
|
102
106
|
case 'numeric': return numericInitialValue(definition, ctx, id);
|
|
103
107
|
case 'range': return rangeInitialValue(definition, ctx, id);
|
|
104
|
-
case '
|
|
105
|
-
return normalizeTreeValue(nodesOf(definition, ctx),
|
|
108
|
+
case 'manyOfTree':
|
|
109
|
+
return normalizeTreeValue(nodesOf(definition, ctx), manyOfTreeInitialValue(definition, ctx, id), definition.selection ?? 'topmost');
|
|
110
|
+
case 'oneOfTree': return oneOfTreeInitialValue(definition, ctx, id, nodesOf(definition, ctx));
|
|
106
111
|
case 'text': return textInitialValue(definition, ctx, id);
|
|
107
112
|
default: return unknownKind(definition);
|
|
108
113
|
}
|
|
@@ -113,9 +118,10 @@ export function valueTypeOf(kind: FieldKind): ValueType<any> {
|
|
|
113
118
|
switch (kind) {
|
|
114
119
|
case 'boolean': return booleanType;
|
|
115
120
|
case 'oneOf':
|
|
121
|
+
case 'oneOfTree':
|
|
116
122
|
case 'text': return stringType;
|
|
117
123
|
case 'manyOf':
|
|
118
|
-
case '
|
|
124
|
+
case 'manyOfTree': return stringSetType;
|
|
119
125
|
case 'numeric': return numberType;
|
|
120
126
|
case 'range': return rangeType;
|
|
121
127
|
case 'mixed-boolean': throw new Error(NOT_STORABLE);
|
|
@@ -132,16 +138,19 @@ function warnDropped(id: string, among: 'choices' | 'nodes', dropped: string[],
|
|
|
132
138
|
/**
|
|
133
139
|
* A stored value (already parsed by the kind's value type) made fit for the definition as it is
|
|
134
140
|
* now: choices, nodes and bounds follow the data, and an old URL may not. A one-of value that is
|
|
135
|
-
* not a choice becomes the initial value; many-of
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
141
|
+
* not a choice becomes the initial value; many-of values that are not choices or nodes are
|
|
142
|
+
* dropped. A one-of tree value is replaced by the initial value too, when it names no node, when
|
|
143
|
+
* the node it names cannot be chosen (a folder, or one a context change disabled), or when it is
|
|
144
|
+
* `''` and the field is not optional. Numbers and ranges are clamped into the bounds, then
|
|
145
|
+
* rounded when the field is `integer` (so a stored `2.5` reads `3`; a whole number inside the
|
|
146
|
+
* bounds, see `fit`), with one warning for whichever of the two actually changed the value:
|
|
147
|
+
* "outside its bounds" when clamping did (whether or not rounding also did), "not a whole
|
|
148
|
+
* number" ("has ends that are not whole numbers" for a range) when only rounding did. Each such
|
|
149
|
+
* change warns, naming the field: the value came from outside, so it is not an error. Reordering
|
|
150
|
+
* is silent: lists come back in choice order or normalized under the tree's selection (a store
|
|
151
|
+
* keeps them sorted, so a warning would come on every read), and a range's ends in order. The
|
|
152
|
+
* warnings go to `warn`, console.warn unless a caller that only summarizes (useStoredFields)
|
|
153
|
+
* silences them.
|
|
145
154
|
*/
|
|
146
155
|
export function sanitizeStored(
|
|
147
156
|
definition: FieldDefinition,
|
|
@@ -169,13 +178,28 @@ export function sanitizeStored(
|
|
|
169
178
|
warnDropped(id, 'choices', stored.filter((v) => !known.includes(v)), warn);
|
|
170
179
|
return known.filter((v) => stored.includes(v));
|
|
171
180
|
}
|
|
172
|
-
case '
|
|
181
|
+
case 'manyOfTree': {
|
|
173
182
|
const stored = value as string[];
|
|
174
183
|
const nodes = nodesOf(definition, ctx);
|
|
175
184
|
const known = nodeValues(nodes);
|
|
176
185
|
warnDropped(id, 'nodes', stored.filter((v) => !known.has(v)), warn);
|
|
177
186
|
return normalizeTreeValue(nodes, stored.filter((v) => known.has(v)), definition.selection ?? 'topmost');
|
|
178
187
|
}
|
|
188
|
+
case 'oneOfTree': {
|
|
189
|
+
const stored = value as string;
|
|
190
|
+
const nodes = nodesOf(definition, ctx);
|
|
191
|
+
if (stored === '') {
|
|
192
|
+
if (definition.optional ?? false) return stored;
|
|
193
|
+
const fallback = oneOfTreeInitialValue(definition, ctx, id, nodes);
|
|
194
|
+
if (fallback !== stored) warn(`Field '${id}': stored value '' is empty but the field is not optional, using '${fallback}'`);
|
|
195
|
+
return fallback;
|
|
196
|
+
}
|
|
197
|
+
if (choosableValues(nodes, definition.choose ?? 'any').includes(stored)) return stored;
|
|
198
|
+
const fallback = oneOfTreeInitialValue(definition, ctx, id, nodes);
|
|
199
|
+
const reason = nodeValues(nodes).has(stored) ? 'cannot be chosen' : 'names no node';
|
|
200
|
+
if (fallback !== stored) warn(`Field '${id}': stored value '${stored}' ${reason}, using '${fallback}'`);
|
|
201
|
+
return fallback;
|
|
202
|
+
}
|
|
179
203
|
case 'numeric': {
|
|
180
204
|
const stored = value as number;
|
|
181
205
|
const { min, max, integer } = resolveNumeric(id, definition, ctx);
|
package/src/parseApiError.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { ParsedApiError } from './types.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* What a thrown error says, for an action's or a field commit's error message: an API error's
|
|
5
|
+
* status, reason and message (ts-rest's reject shape, `{ status, body: { message, reason } }`);
|
|
6
|
+
* without a body message, a thrown `Error`'s own message (`new Error('Printer offline')`, a
|
|
7
|
+
* failed fetch's TypeError), unless empty. `status` is 0 when the error carries none.
|
|
8
|
+
*/
|
|
3
9
|
export function parseApiError(err: unknown): ParsedApiError {
|
|
4
10
|
if (typeof err !== 'object' || err === null) return { status: 0 };
|
|
5
11
|
const e = err as { status?: number; body?: { message?: string; reason?: string } };
|
|
6
12
|
return {
|
|
7
13
|
status: e.status ?? 0,
|
|
8
14
|
reason: e.body?.reason,
|
|
9
|
-
message: e.body?.message,
|
|
15
|
+
message: e.body?.message ?? ((err instanceof Error && err.message) || undefined),
|
|
10
16
|
};
|
|
11
17
|
}
|