@chelone/core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/assistant-session.d.ts +6 -0
- package/dist/assistant/assistant-session.js +204 -181
- package/dist/assistant/echo-transport.js +26 -29
- package/dist/assistant/http-transport.d.ts +10 -0
- package/dist/assistant/http-transport.js +98 -0
- package/dist/assistant/types.d.ts +1 -7
- package/dist/auth/access-control.d.ts +13 -5
- package/dist/auth/access-control.js +25 -21
- package/dist/auth/auth-options.d.ts +21 -17
- package/dist/auth/auth-options.js +80 -48
- package/dist/auth/permission-matrix.d.ts +12 -0
- package/dist/auth/permission-matrix.js +67 -0
- package/dist/auth/types.d.ts +14 -4
- package/dist/block-editor/block-document.d.ts +60 -0
- package/dist/block-editor/block-document.js +143 -0
- package/dist/block-editor/data-blocks.d.ts +63 -0
- package/dist/block-editor/data-blocks.js +117 -0
- package/dist/chart/chart-data.js +46 -44
- package/dist/command-menu/hotkey.js +58 -47
- package/dist/command-menu/recents.js +8 -11
- package/dist/command-menu/registry.d.ts +9 -5
- package/dist/command-menu/registry.js +36 -46
- package/dist/controller/calendar-dates.js +82 -113
- package/dist/controller/calendar-events.js +93 -88
- package/dist/controller/client-list-controller.d.ts +2 -2
- package/dist/controller/client-list-controller.js +251 -254
- package/dist/controller/data-table-columns.d.ts +2 -0
- package/dist/controller/data-table-columns.js +50 -56
- package/dist/controller/deep-equal.d.ts +1 -0
- package/dist/controller/deep-equal.js +14 -0
- package/dist/controller/filter-toggle.js +36 -44
- package/dist/controller/in-place-editor.d.ts +42 -0
- package/dist/controller/in-place-editor.js +42 -0
- package/dist/controller/infinite-list-controller.js +219 -189
- package/dist/controller/inspector.d.ts +49 -0
- package/dist/controller/inspector.js +64 -0
- package/dist/controller/job-monitor.d.ts +3 -4
- package/dist/controller/job-monitor.js +37 -47
- package/dist/controller/kanban-board.d.ts +2 -3
- package/dist/controller/kanban-board.js +43 -24
- package/dist/controller/list-controller.d.ts +5 -5
- package/dist/controller/list-controller.js +444 -400
- package/dist/controller/list-params.js +96 -87
- package/dist/controller/pivot-grid.js +89 -95
- package/dist/controller/prev-next.d.ts +2 -2
- package/dist/controller/prev-next.js +74 -54
- package/dist/controller/record-controllers.d.ts +14 -6
- package/dist/controller/record-controllers.js +288 -257
- package/dist/controller/record-diff.d.ts +21 -0
- package/dist/controller/record-diff.js +50 -0
- package/dist/controller/record-timeline.js +42 -30
- package/dist/controller/record-tree.js +57 -47
- package/dist/controller/record-workflow.d.ts +38 -16
- package/dist/controller/record-workflow.js +85 -64
- package/dist/controller/reference-many-controller.js +198 -192
- package/dist/controller/reference-one-controller.d.ts +1 -1
- package/dist/controller/reference-one-controller.js +63 -56
- package/dist/controller/saved-queries.js +104 -91
- package/dist/controller/translate-fallback.js +10 -6
- package/dist/csv/export.d.ts +2 -1
- package/dist/csv/export.js +43 -40
- package/dist/csv/import-controller.js +185 -169
- package/dist/csv/match-headers.js +29 -22
- package/dist/csv/parse-with.js +60 -54
- package/dist/csv/run-import.js +69 -72
- package/dist/csv/validate-rows.d.ts +1 -1
- package/dist/csv/validate-rows.js +65 -52
- package/dist/data/cache-ops.d.ts +5 -4
- package/dist/data/cache-ops.js +208 -188
- package/dist/data/combine-data-providers.js +25 -23
- package/dist/data/data-provider-log.d.ts +45 -0
- package/dist/data/data-provider-log.js +93 -0
- package/dist/data/errors.js +14 -10
- package/dist/data/fake-provider.d.ts +6 -3
- package/dist/data/fake-provider.js +129 -114
- package/dist/data/fetch-utils.js +61 -44
- package/dist/data/filter-branch.d.ts +5 -0
- package/dist/data/filter-branch.js +10 -0
- package/dist/data/get-many-aggregate.js +84 -76
- package/dist/data/http-error.js +17 -12
- package/dist/data/keys.js +50 -36
- package/dist/data/mutation-options.d.ts +14 -9
- package/dist/data/mutation-options.js +93 -109
- package/dist/data/query-options.js +73 -68
- package/dist/data/remove-empty.d.ts +3 -0
- package/dist/data/remove-empty.js +11 -0
- package/dist/data/undoable.d.ts +2 -2
- package/dist/data/undoable.js +190 -174
- package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
- package/dist/data/with-lifecycle-callbacks.js +66 -52
- package/dist/environment/application-update-store.js +58 -55
- package/dist/environment/offline-store.js +21 -21
- package/dist/form/array-binding.d.ts +30 -11
- package/dist/form/array-binding.js +184 -166
- package/dist/form/choice-ids.d.ts +7 -0
- package/dist/form/choice-ids.js +11 -0
- package/dist/form/choices-controller.d.ts +3 -2
- package/dist/form/choices-controller.js +302 -275
- package/dist/form/date-format.d.ts +38 -0
- package/dist/form/date-format.js +89 -0
- package/dist/form/dirty-values.d.ts +2 -2
- package/dist/form/dirty-values.js +22 -11
- package/dist/form/duration.d.ts +22 -0
- package/dist/form/duration.js +49 -0
- package/dist/form/edit-create-form.d.ts +12 -5
- package/dist/form/edit-create-form.js +243 -188
- package/dist/form/event-value.d.ts +6 -0
- package/dist/form/event-value.js +17 -0
- package/dist/form/field-error.d.ts +14 -0
- package/dist/form/field-error.js +28 -0
- package/dist/form/field-name.d.ts +20 -0
- package/dist/form/field-name.js +6 -0
- package/dist/form/filter-form-values.js +63 -49
- package/dist/form/form-controller.d.ts +9 -1
- package/dist/form/form-controller.js +165 -160
- package/dist/form/form-groups.d.ts +4 -3
- package/dist/form/form-groups.js +79 -79
- package/dist/form/form-handle.d.ts +175 -0
- package/dist/form/form-handle.js +68 -0
- package/dist/form/input-binding.d.ts +56 -31
- package/dist/form/input-binding.js +169 -118
- package/dist/form/input-values.d.ts +130 -0
- package/dist/form/input-values.js +177 -0
- package/dist/form/json-text.js +51 -37
- package/dist/form/save-pipeline.d.ts +6 -8
- package/dist/form/save-pipeline.js +91 -82
- package/dist/form/set-form-values.d.ts +6 -0
- package/dist/form/set-form-values.js +10 -0
- package/dist/form/source.js +97 -65
- package/dist/form/text-of.d.ts +2 -0
- package/dist/form/text-of.js +7 -0
- package/dist/form/validators.d.ts +58 -19
- package/dist/form/validators.js +97 -71
- package/dist/geo/bbox.d.ts +5 -4
- package/dist/geo/bbox.js +69 -48
- package/dist/geo/bounds.d.ts +14 -3
- package/dist/geo/bounds.js +98 -68
- package/dist/geo/constraints.js +88 -93
- package/dist/geo/coordinates.js +31 -23
- package/dist/geo/editor.d.ts +13 -13
- package/dist/geo/editor.js +176 -153
- package/dist/geo/geocoding.d.ts +4 -4
- package/dist/geo/geocoding.js +38 -41
- package/dist/geo/geojson.d.ts +12 -74
- package/dist/geo/geojson.js +52 -40
- package/dist/geo/geometry-math.d.ts +4 -4
- package/dist/geo/geometry-math.js +100 -93
- package/dist/geo/index.d.ts +7 -6
- package/dist/geo/map-layers.d.ts +42 -7
- package/dist/geo/map-layers.js +88 -62
- package/dist/geo/map-style.js +31 -32
- package/dist/geo/messages.js +48 -45
- package/dist/geo/nominatim.js +88 -80
- package/dist/geo/osm-presets.d.ts +4 -3
- package/dist/geo/osm-presets.js +108 -86
- package/dist/geo/osm-tags.js +14 -12
- package/dist/geo/overpass.d.ts +14 -2
- package/dist/geo/overpass.js +83 -90
- package/dist/geo/shape-value.d.ts +9 -5
- package/dist/geo/shape-value.js +169 -146
- package/dist/geo/snap-to-roads.d.ts +3 -3
- package/dist/geo/snap-to-roads.js +55 -50
- package/dist/geo/throttle.js +36 -39
- package/dist/guards.d.ts +37 -0
- package/dist/guards.js +62 -0
- package/dist/i18n/default-messages.js +34 -29
- package/dist/i18n/english-messages.js +187 -189
- package/dist/i18n/i18n.d.ts +2 -2
- package/dist/i18n/i18n.js +47 -60
- package/dist/i18n/inflection.d.ts +34 -0
- package/dist/i18n/inflection.js +82 -0
- package/dist/i18n/messages.d.ts +10 -2
- package/dist/i18n/messages.js +17 -4
- package/dist/i18n/missing-keys.js +66 -62
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +71 -24
- package/dist/index.js +40 -288
- package/dist/json/deep-equal.d.ts +8 -0
- package/dist/json/deep-equal.js +24 -0
- package/dist/json/strip-json-comments.js +38 -42
- package/dist/noop.d.ts +2 -0
- package/dist/noop.js +4 -0
- package/dist/notification/error-message.d.ts +2 -0
- package/dist/notification/error-message.js +10 -0
- package/dist/notification/notification-store.d.ts +3 -2
- package/dist/notification/notification-store.js +41 -45
- package/dist/onboarding-tour/placement.js +63 -78
- package/dist/onboarding-tour/tour-controller.js +46 -54
- package/dist/realtime/add-events-for-mutations.js +75 -81
- package/dist/realtime/announce-lock.js +16 -13
- package/dist/realtime/broadcast-channel-transport.js +65 -59
- package/dist/realtime/copy-members.js +22 -14
- package/dist/realtime/in-memory-lock-provider.js +40 -48
- package/dist/realtime/live.js +95 -101
- package/dist/realtime/lock-options.js +16 -16
- package/dist/realtime/lock-session.js +91 -78
- package/dist/realtime/memory-transport.js +25 -25
- package/dist/realtime/presence.js +120 -122
- package/dist/realtime/realtime-data-provider.d.ts +10 -0
- package/dist/realtime/realtime-data-provider.js +45 -42
- package/dist/realtime/reconnect-backoff.js +17 -0
- package/dist/realtime/sse-transport.d.ts +31 -0
- package/dist/realtime/sse-transport.js +235 -0
- package/dist/realtime/status-store.js +23 -28
- package/dist/realtime/topic-hub.js +62 -71
- package/dist/realtime/topics.js +10 -12
- package/dist/realtime/types.js +10 -12
- package/dist/realtime/unread-count.js +21 -21
- package/dist/realtime/websocket-transport.d.ts +32 -0
- package/dist/realtime/websocket-transport.js +306 -0
- package/dist/resource/describe-resource.d.ts +2 -0
- package/dist/resource/describe-resource.js +232 -219
- package/dist/resource/get-at-path.js +29 -14
- package/dist/resource/identifier.d.ts +18 -0
- package/dist/resource/identifier.js +27 -0
- package/dist/resource/record.d.ts +4 -0
- package/dist/resource/record.js +10 -0
- package/dist/resource/register.d.ts +7 -0
- package/dist/resource/registry-store.d.ts +45 -15
- package/dist/resource/registry-store.js +67 -60
- package/dist/resource/registry.d.ts +10 -0
- package/dist/resource/registry.js +8 -4
- package/dist/resource/source-path.d.ts +16 -1
- package/dist/rich-text/rich-text-extras.d.ts +132 -0
- package/dist/rich-text/rich-text-extras.js +179 -0
- package/dist/rich-text/rich-text-image.d.ts +124 -0
- package/dist/rich-text/rich-text-image.js +163 -0
- package/dist/rich-text/rich-text.d.ts +6 -0
- package/dist/rich-text/rich-text.js +125 -88
- package/dist/router/location.d.ts +22 -4
- package/dist/router/location.js +41 -18
- package/dist/router/match.js +47 -39
- package/dist/router/paths.js +40 -34
- package/dist/router/record-from-location.d.ts +1 -1
- package/dist/router/record-from-location.js +25 -17
- package/dist/store/adapters.js +51 -55
- package/dist/store/micro-store.js +57 -59
- package/dist/store/shallow-equal.d.ts +12 -0
- package/dist/store/shallow-equal.js +20 -0
- package/dist/store/store.js +54 -57
- package/package.json +13 -11
package/dist/form/json-text.js
CHANGED
|
@@ -1,47 +1,61 @@
|
|
|
1
|
+
// src/form/json-text.ts
|
|
1
2
|
function detectJsonShape(value) {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
if (value === void 0) return null;
|
|
4
|
+
return typeof value === "string" ? "string" : "object";
|
|
4
5
|
}
|
|
5
6
|
function toJsonText(value, shape, indent = 2) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
if (shape === "string") return typeof value === "string" ? value : "";
|
|
8
|
+
if (value === void 0) return "";
|
|
9
|
+
try {
|
|
10
|
+
return JSON.stringify(value, null, indent) ?? "";
|
|
11
|
+
} catch {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
13
14
|
}
|
|
14
15
|
function fromJsonText(text, shape) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
if (shape === "string") return {
|
|
17
|
+
value: text,
|
|
18
|
+
parseError: null
|
|
19
|
+
};
|
|
20
|
+
if (text.trim() === "") return {
|
|
21
|
+
value: null,
|
|
22
|
+
parseError: null
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
return {
|
|
26
|
+
value: JSON.parse(text),
|
|
27
|
+
parseError: null
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
value: void 0,
|
|
32
|
+
parseError: error instanceof Error ? error : new Error(String(error))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
25
35
|
}
|
|
26
36
|
function formatJsonValue(value, indent = 2) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
try {
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
const parsed = parseOrKeep(value);
|
|
40
|
+
if (!parsed.ok) return value;
|
|
41
|
+
return JSON.stringify(parsed.value, null, indent) ?? value;
|
|
42
|
+
}
|
|
43
|
+
return JSON.stringify(value, null, indent) ?? String(value);
|
|
44
|
+
} catch {
|
|
45
|
+
return String(value);
|
|
46
|
+
}
|
|
34
47
|
}
|
|
35
48
|
function parseOrKeep(text) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
try {
|
|
50
|
+
return {
|
|
51
|
+
value: JSON.parse(text),
|
|
52
|
+
ok: true
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return {
|
|
56
|
+
value: text,
|
|
57
|
+
ok: false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
41
60
|
}
|
|
42
|
-
export {
|
|
43
|
-
detectJsonShape,
|
|
44
|
-
formatJsonValue,
|
|
45
|
-
fromJsonText,
|
|
46
|
-
toJsonText
|
|
47
|
-
};
|
|
61
|
+
export { detectJsonShape, formatJsonValue, fromJsonText, toJsonText };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StandardSchemaV1 } from "@tanstack/form-core";
|
|
1
|
+
import { type FormValidateFn, type FormValidateOrFn, type StandardSchemaV1 } from "@tanstack/form-core";
|
|
2
2
|
import type { FormLevelValidate } from "./form-controller.js";
|
|
3
3
|
/**
|
|
4
4
|
* ra parity (exact semantics, TOP-LEVEL keys only): `null`, `''` and
|
|
@@ -10,7 +10,7 @@ import type { FormLevelValidate } from "./form-controller.js";
|
|
|
10
10
|
* narrower than `TValues` (ra types it the same way; the DataProvider
|
|
11
11
|
* receives partials by contract).
|
|
12
12
|
*/
|
|
13
|
-
export declare function sanitizeEmptyValues<TValues
|
|
13
|
+
export declare function sanitizeEmptyValues<TValues>(values: TValues, record?: object): TValues;
|
|
14
14
|
/**
|
|
15
15
|
* form-core's "global form validation error" shape: `form` is the form-level
|
|
16
16
|
* error, `fields` maps field paths (dot/bracket, `items[0].sku`) to errors.
|
|
@@ -27,8 +27,6 @@ export interface FormErrorMapValue {
|
|
|
27
27
|
* Nullish entries are dropped; returns `undefined` when nothing remains.
|
|
28
28
|
*/
|
|
29
29
|
export declare function toFormErrorMapValue(errors: Record<string, unknown> | null | undefined): FormErrorMapValue | undefined;
|
|
30
|
-
/** Thenable detection — how the pipeline tells async validate fns from sync ones. */
|
|
31
|
-
export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
|
|
32
30
|
export interface ComposeFormValidatorConfig<TFormData> {
|
|
33
31
|
/** Resource schema (zod or any Standard Schema) — the D6 default validator. */
|
|
34
32
|
schema?: StandardSchemaV1<TFormData, unknown>;
|
|
@@ -38,8 +36,10 @@ export interface ComposeFormValidatorConfig<TFormData> {
|
|
|
38
36
|
* The user's own `formOptions.validators.onDynamic` escape hatch (fn or
|
|
39
37
|
* Standard Schema) — composed after the schema and validate fn.
|
|
40
38
|
*/
|
|
41
|
-
userValidator?:
|
|
39
|
+
userValidator?: FormValidateOrFn<TFormData>;
|
|
42
40
|
}
|
|
41
|
+
/** What form-core hands a form-level validator fn: `value` and `formApi`. */
|
|
42
|
+
export type FormValidatorProps<TFormData> = Parameters<FormValidateFn<TFormData>>[0];
|
|
43
43
|
/**
|
|
44
44
|
* Composes the D6 validator stack into ONE sync `onDynamic` validator:
|
|
45
45
|
* schema -> validate fn (sync results only) -> user onDynamic. Field errors
|
|
@@ -57,6 +57,4 @@ export interface ComposeFormValidatorConfig<TFormData> {
|
|
|
57
57
|
*
|
|
58
58
|
* Returns `undefined` when there is nothing to compose.
|
|
59
59
|
*/
|
|
60
|
-
export declare function composeFormValidator<TFormData>(config: ComposeFormValidatorConfig<TFormData>): ((props:
|
|
61
|
-
value: TFormData;
|
|
62
|
-
}) => unknown) | undefined;
|
|
60
|
+
export declare function composeFormValidator<TFormData>(config: ComposeFormValidatorConfig<TFormData>): ((props: FormValidatorProps<TFormData>) => FormErrorMapValue | undefined) | undefined;
|
|
@@ -1,88 +1,97 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isRecord, isThenable } from "../guards.js";
|
|
2
|
+
import { isStandardSchemaValidator, standardSchemaValidators } from "@tanstack/form-core";
|
|
3
|
+
// src/form/save-pipeline.ts
|
|
4
|
+
/**
|
|
5
|
+
* ra parity (exact semantics, TOP-LEVEL keys only): `null`, `''` and
|
|
6
|
+
* `undefined` values become `null` when the key exists on `record`, and are
|
|
7
|
+
* OMITTED otherwise; `0`, `false`, empty arrays and empty objects are NOT
|
|
8
|
+
* empty and pass through. Always returns a fresh object.
|
|
9
|
+
*
|
|
10
|
+
* NOTE the return type is nominal — omitted keys make the runtime value
|
|
11
|
+
* narrower than `TValues` (ra types it the same way; the DataProvider
|
|
12
|
+
* receives partials by contract).
|
|
13
|
+
*/
|
|
2
14
|
function sanitizeEmptyValues(values, record) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return result;
|
|
15
|
+
if (typeof values !== "object" || values === null) return values;
|
|
16
|
+
const result = {};
|
|
17
|
+
for (const [key, value] of Object.entries(values)) if (value === void 0 || value === null || value === "") {
|
|
18
|
+
if (record && key in record) result[key] = null;
|
|
19
|
+
} else result[key] = value;
|
|
20
|
+
return result;
|
|
12
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Converts an ra-shaped error record (field path -> error, `root` reserved
|
|
24
|
+
* for the form-level message) — the form-level validate fn contract AND the
|
|
25
|
+
* P4 save `FieldErrors` contract — into form-core's global error-map value.
|
|
26
|
+
* Nullish entries are dropped; returns `undefined` when nothing remains.
|
|
27
|
+
*/
|
|
13
28
|
function toFormErrorMapValue(errors) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
function isThenable(value) {
|
|
24
|
-
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
25
|
-
}
|
|
26
|
-
function isRecordObject(value) {
|
|
27
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
|
+
if (errors == null) return void 0;
|
|
30
|
+
const { root, ...rest } = errors;
|
|
31
|
+
const fields = {};
|
|
32
|
+
for (const [key, value] of Object.entries(rest)) if (value != null) fields[key] = value;
|
|
33
|
+
if (root == null && Object.keys(fields).length === 0) return void 0;
|
|
34
|
+
return {
|
|
35
|
+
form: root ?? void 0,
|
|
36
|
+
fields
|
|
37
|
+
};
|
|
28
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Composes the D6 validator stack into ONE sync `onDynamic` validator:
|
|
41
|
+
* schema -> validate fn (sync results only) -> user onDynamic. Field errors
|
|
42
|
+
* merge FIRST-WINS per path (ra's composeValidators short-circuit parity);
|
|
43
|
+
* the form-level slot also keeps the first non-nullish value.
|
|
44
|
+
*
|
|
45
|
+
* The composed validator runs on the SYNC slot deliberately: async
|
|
46
|
+
* form-level validators do not block a field-less `handleSubmit` (probed on
|
|
47
|
+
* form-core 1.33), so vanilla submit-only forms would save invalid data.
|
|
48
|
+
* An async validate fn is detected on its first call, skipped here from then
|
|
49
|
+
* on, and re-run by the submit pipeline instead (on-submit semantics). The
|
|
50
|
+
* pipeline absorbs sync AND async results from the latched fn — ra's
|
|
51
|
+
* contract legally mixes sync fast-path errors with async server checks, and
|
|
52
|
+
* a latched fn's later sync error can only surface through the pipeline.
|
|
53
|
+
*
|
|
54
|
+
* Returns `undefined` when there is nothing to compose.
|
|
55
|
+
*/
|
|
29
56
|
function composeFormValidator(config) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (userValidator) {
|
|
70
|
-
absorb(
|
|
71
|
-
isStandardSchemaValidator(userValidator) ? standardSchemaValidators.validate(
|
|
72
|
-
{ value: props.value, validationSource: "form" },
|
|
73
|
-
userValidator
|
|
74
|
-
) : userValidator(props)
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
if (formError === void 0 && Object.keys(fields).length === 0) {
|
|
78
|
-
return void 0;
|
|
79
|
-
}
|
|
80
|
-
return { form: formError, fields };
|
|
81
|
-
};
|
|
57
|
+
const { schema, validate, userValidator } = config;
|
|
58
|
+
if (!schema && !validate && !userValidator) return void 0;
|
|
59
|
+
/** Latched on the first thenable result — async fns never run in the sync slot again. */
|
|
60
|
+
let validateIsAsync = false;
|
|
61
|
+
return (props) => {
|
|
62
|
+
let formError;
|
|
63
|
+
const fields = {};
|
|
64
|
+
const absorb = (result) => {
|
|
65
|
+
if (result == null) return;
|
|
66
|
+
if (isRecord(result) && "fields" in result) {
|
|
67
|
+
if (formError === void 0 && result.form != null) formError = result.form;
|
|
68
|
+
if (isRecord(result.fields)) {
|
|
69
|
+
for (const [key, error] of Object.entries(result.fields)) if (error != null && !(key in fields)) fields[key] = error;
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (formError === void 0) formError = result;
|
|
74
|
+
};
|
|
75
|
+
if (schema) absorb(standardSchemaValidators.validate({
|
|
76
|
+
value: props.value,
|
|
77
|
+
validationSource: "form"
|
|
78
|
+
}, schema));
|
|
79
|
+
if (validate && !validateIsAsync) {
|
|
80
|
+
const result = validate(props.value);
|
|
81
|
+
if (isThenable(result)) {
|
|
82
|
+
validateIsAsync = true;
|
|
83
|
+
Promise.resolve(result).catch(() => void 0);
|
|
84
|
+
} else absorb(toFormErrorMapValue(result));
|
|
85
|
+
}
|
|
86
|
+
if (userValidator) absorb(isStandardSchemaValidator(userValidator) ? standardSchemaValidators.validate({
|
|
87
|
+
value: props.value,
|
|
88
|
+
validationSource: "form"
|
|
89
|
+
}, userValidator) : userValidator(props));
|
|
90
|
+
if (formError === void 0 && Object.keys(fields).length === 0) return;
|
|
91
|
+
return {
|
|
92
|
+
form: formError,
|
|
93
|
+
fields
|
|
94
|
+
};
|
|
95
|
+
};
|
|
82
96
|
}
|
|
83
|
-
export {
|
|
84
|
-
composeFormValidator,
|
|
85
|
-
isThenable,
|
|
86
|
-
sanitizeEmptyValues,
|
|
87
|
-
toFormErrorMapValue
|
|
88
|
-
};
|
|
97
|
+
export { composeFormValidator, sanitizeEmptyValues, toFormErrorMapValue };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CheloneFormApi } from "./form-controller.js";
|
|
2
|
+
/**
|
|
3
|
+
* Writes each key of `data` into the form, so a programmatic save sees the
|
|
4
|
+
* values it was handed. A key not in `data` keeps its value.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setFormValues<TValues>(form: CheloneFormApi<TValues>, data: Partial<NoInfer<TValues>>): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { toFieldName } from "./field-name.js";
|
|
2
|
+
// src/form/set-form-values.ts
|
|
3
|
+
/**
|
|
4
|
+
* Writes each key of `data` into the form, so a programmatic save sees the
|
|
5
|
+
* values it was handed. A key not in `data` keeps its value.
|
|
6
|
+
*/
|
|
7
|
+
function setFormValues(form, data) {
|
|
8
|
+
for (const [key, value] of Object.entries(data)) form.setFieldValue(toFieldName(key), value);
|
|
9
|
+
}
|
|
10
|
+
export { setFormValues };
|
package/dist/form/source.js
CHANGED
|
@@ -1,76 +1,108 @@
|
|
|
1
|
+
// src/form/source.ts
|
|
2
|
+
/**
|
|
3
|
+
* Joins two path fragments: bracket fragments (`[0]`, `[0].sku`) concatenate
|
|
4
|
+
* directly (form-core index syntax), everything else joins with a dot.
|
|
5
|
+
*/
|
|
1
6
|
function composeSource(parent, child) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
if (!child) return parent;
|
|
8
|
+
if (!parent) return child;
|
|
9
|
+
return child.startsWith("[") ? `${parent}${child}` : `${parent}.${child}`;
|
|
5
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Rewrite a dot-spelled array index as form-core's bracket spelling:
|
|
13
|
+
* `tags.0.label` -> `tags[0].label`. Without it an input written with a dot
|
|
14
|
+
* index binds a field named `tags.0.label`, which no validator, error or
|
|
15
|
+
* field group matches, while the same path read as data (a `DataTable.Col`)
|
|
16
|
+
* works — form-core's `getBy`/`setBy` accept both spellings, so only the
|
|
17
|
+
* FIELD NAME differs.
|
|
18
|
+
*
|
|
19
|
+
* Only a digits-only member that FOLLOWS another member is an index: a
|
|
20
|
+
* leading numeral (`0.a`) is a field name, and bracket spellings, non-numeric
|
|
21
|
+
* members and the empty string pass through untouched. Labels never go
|
|
22
|
+
* through this — translation keys keep the spelling as written.
|
|
23
|
+
*/
|
|
6
24
|
function normalizeSource(source) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
let out = "";
|
|
26
|
+
/** Bracket depth: inside a group the spelling is already form-core's. */
|
|
27
|
+
let depth = 0;
|
|
28
|
+
/** True once a member has been emitted — a dot before the next one is a join. */
|
|
29
|
+
let afterMember = false;
|
|
30
|
+
for (let i = 0; i < source.length; i++) {
|
|
31
|
+
const char = source[i];
|
|
32
|
+
if (char === "[") depth++;
|
|
33
|
+
if (char === "]") depth--;
|
|
34
|
+
if (char === "." && depth === 0 && afterMember) {
|
|
35
|
+
let end = i + 1;
|
|
36
|
+
while (end < source.length && source[end] !== "." && source[end] !== "[") end++;
|
|
37
|
+
const member = source.slice(i + 1, end);
|
|
38
|
+
if (/^\d+$/.test(member)) {
|
|
39
|
+
out += `[${member}]`;
|
|
40
|
+
i = end - 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
out += char;
|
|
45
|
+
if (char !== ".") afterMember = true;
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
30
48
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
/**
|
|
50
|
+
* What a top-level form provides: the identity scope for everything but a
|
|
51
|
+
* dot-spelled index, which it rewrites to form-core's bracket spelling.
|
|
52
|
+
*/
|
|
53
|
+
var rootSourceScope = {
|
|
54
|
+
getSource: (source) => normalizeSource(source),
|
|
55
|
+
getLabel: (source) => source
|
|
34
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* The scope an array input provides to its iterator: sources nest under the
|
|
59
|
+
* array's own (scoped) path, labels under its index-free label path.
|
|
60
|
+
*
|
|
61
|
+
* `getSource('')` resolves the array field itself — how an iterator finds
|
|
62
|
+
* the path it pushes/removes on.
|
|
63
|
+
*/
|
|
35
64
|
function createArraySourceScope(parent, arraySource) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// parent that normalizes it.
|
|
42
|
-
getSource: (source) => normalizeSource(composeSource(arrayPath, source)),
|
|
43
|
-
getLabel: (source) => parent.getLabel(composeSource(arraySource, source))
|
|
44
|
-
};
|
|
65
|
+
const arrayPath = parent.getSource(arraySource);
|
|
66
|
+
return {
|
|
67
|
+
getSource: (source) => normalizeSource(composeSource(arrayPath, source)),
|
|
68
|
+
getLabel: (source) => parent.getLabel(composeSource(arraySource, source))
|
|
69
|
+
};
|
|
45
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The scope an iterator row provides to its children: sources gain the row's
|
|
73
|
+
* `[index]` fragment; labels pass through UNCHANGED so translation keys stay
|
|
74
|
+
* index-free (`orders.date`, not `orders.0.date`).
|
|
75
|
+
*
|
|
76
|
+
* Scalar rows (`<ArrayInput source="tags">` with a sourceless child) call
|
|
77
|
+
* `getSource('')` and bind the row itself (`tags[2]`).
|
|
78
|
+
*/
|
|
46
79
|
function createRowSourceScope(parent, index) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
return {
|
|
81
|
+
getSource: (source) => parent.getSource(source ? `[${index}].${source}` : `[${index}]`),
|
|
82
|
+
getLabel: (source) => parent.getLabel(source)
|
|
83
|
+
};
|
|
51
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* The scope a translatable group provides for one locale: each child source
|
|
87
|
+
* gains the locale as a trailing member (`title` -> `title.fr`). Labels pass
|
|
88
|
+
* through so every locale shares one translation key.
|
|
89
|
+
*
|
|
90
|
+
* A translatable group has no value of its own, so a sourceless child has
|
|
91
|
+
* nothing to bind and throws.
|
|
92
|
+
*/
|
|
52
93
|
function createLocaleSourceScope(parent, locale) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
};
|
|
94
|
+
const requireSource = (source) => {
|
|
95
|
+
if (!source) throw new Error("Children of a translatable group must have a source");
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
getSource: (source) => {
|
|
99
|
+
requireSource(source);
|
|
100
|
+
return parent.getSource(`${source}.${locale}`);
|
|
101
|
+
},
|
|
102
|
+
getLabel: (source) => {
|
|
103
|
+
requireSource(source);
|
|
104
|
+
return parent.getLabel(source);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
68
107
|
}
|
|
69
|
-
export {
|
|
70
|
-
composeSource,
|
|
71
|
-
createArraySourceScope,
|
|
72
|
-
createLocaleSourceScope,
|
|
73
|
-
createRowSourceScope,
|
|
74
|
-
normalizeSource,
|
|
75
|
-
rootSourceScope
|
|
76
|
-
};
|
|
108
|
+
export { composeSource, createArraySourceScope, createLocaleSourceScope, createRowSourceScope, normalizeSource, rootSourceScope };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// src/form/text-of.ts
|
|
2
|
+
/** A form value as text: `""` for `null` and `undefined`, the string itself, `String(value)` for anything else. */
|
|
3
|
+
function textOf(value) {
|
|
4
|
+
if (value == null) return "";
|
|
5
|
+
return typeof value === "string" ? value : String(value);
|
|
6
|
+
}
|
|
7
|
+
export { textOf };
|