@chelone/core 0.5.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 +28 -0
- package/dist/assistant/assistant-session.js +208 -0
- package/dist/assistant/echo-transport.d.ts +7 -0
- package/dist/assistant/echo-transport.js +28 -0
- package/dist/assistant/http-transport.d.ts +10 -0
- package/dist/assistant/http-transport.js +98 -0
- package/dist/assistant/types.d.ts +54 -0
- 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.d.ts +25 -0
- package/dist/chart/chart-data.js +61 -0
- 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 +220 -190
- package/dist/controller/inspector.d.ts +49 -0
- package/dist/controller/inspector.js +64 -0
- package/dist/controller/job-monitor.d.ts +41 -0
- package/dist/controller/job-monitor.js +49 -0
- 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.d.ts +36 -0
- package/dist/controller/pivot-grid.js +99 -0
- 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.d.ts +35 -0
- package/dist/controller/record-timeline.js +48 -0
- 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 -190
- 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 +8 -2
- package/dist/i18n/i18n.js +47 -48
- 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.d.ts +19 -0
- package/dist/i18n/missing-keys.js +68 -0
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +96 -22
- package/dist/index.js +52 -257
- 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.d.ts +34 -0
- package/dist/onboarding-tour/placement.js +67 -0
- package/dist/onboarding-tour/tour-controller.d.ts +25 -0
- package/dist/onboarding-tour/tour-controller.js +50 -0
- package/dist/realtime/add-events-for-mutations.js +75 -81
- package/dist/realtime/announce-lock.d.ts +7 -0
- package/dist/realtime/announce-lock.js +18 -0
- package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
- package/dist/realtime/broadcast-channel-transport.js +69 -0
- package/dist/realtime/copy-members.js +22 -14
- package/dist/realtime/in-memory-lock-provider.js +41 -60
- 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 +26 -67
- 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.d.ts +15 -0
- 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.d.ts +24 -0
- package/dist/realtime/topic-hub.js +66 -0
- 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 +62 -0
- package/dist/rich-text/rich-text.js +142 -0
- 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
|
@@ -1,201 +1,256 @@
|
|
|
1
1
|
import { createCreateController, createEditController } from "../controller/record-controllers.js";
|
|
2
|
+
import { isPlainObject } from "../guards.js";
|
|
3
|
+
import { toFieldName } from "./field-name.js";
|
|
2
4
|
import { createFormController } from "./form-controller.js";
|
|
5
|
+
import { toResourceFormHandle } from "./form-handle.js";
|
|
6
|
+
// src/form/edit-create-form.ts
|
|
7
|
+
/** The registry schema for `resource`, widened to the form's Partial data type. */
|
|
3
8
|
function registrySchema(ctx, resource) {
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
function isPlainObject(value) {
|
|
7
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
const proto = Object.getPrototypeOf(value);
|
|
11
|
-
return proto === Object.prototype || proto === null;
|
|
9
|
+
return ctx.resources?.[resource]?.schema;
|
|
12
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Parity merge (§0b defaultValues precedence): `override` (the record) WINS,
|
|
13
|
+
* including `null`; explicit `undefined` falls back to the base (an absent
|
|
14
|
+
* server field never erases a default); arrays replace wholesale. Fresh
|
|
15
|
+
* objects on every merged level.
|
|
16
|
+
*/
|
|
13
17
|
function deepMerge(base, override) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
return result;
|
|
18
|
+
if (override === void 0) return base;
|
|
19
|
+
if (!isPlainObject(base) || !isPlainObject(override)) return override;
|
|
20
|
+
const result = { ...base };
|
|
21
|
+
for (const [key, value] of Object.entries(override)) result[key] = key in base ? deepMerge(base[key], value) : value;
|
|
22
|
+
return result;
|
|
21
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The record-hydration decision (§0b: record identity drives reset):
|
|
26
|
+
* - a record with a DIFFERENT id (loose comparison) -> hard reset, dropping
|
|
27
|
+
* dirty values. Unreachable through `createEditFormController` today (the
|
|
28
|
+
* P4 fetch guard errors on id mismatches before a record lands), kept as
|
|
29
|
+
* the pinned semantic for any composition that can swap records.
|
|
30
|
+
* - untouched form -> rebaseline (apply the merged record as new defaults —
|
|
31
|
+
* `form.update` auto-applies defaults to untouched forms without dirtying).
|
|
32
|
+
* - FIRST record arrival into a touched form -> partial apply (§0b
|
|
33
|
+
* keepDirtyValues): the form has never seen the record, so clean fields
|
|
34
|
+
* still hold construction defaults — keeping them would make the next save
|
|
35
|
+
* send those defaults over the real record fields (a data wipe). The
|
|
36
|
+
* record hydrates every clean field; dirty values (and their dirty meta)
|
|
37
|
+
* survive.
|
|
38
|
+
* - touched form, same record (refetch) -> keep ALL current values (soft):
|
|
39
|
+
* clean fields already hold the previous record's values — stale at worst,
|
|
40
|
+
* never destructive.
|
|
41
|
+
*/
|
|
22
42
|
function resolveHydrationAction(previousId, nextId, isTouched) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
if (previousId !== void 0 && String(nextId) !== String(previousId)) return "hard-reset";
|
|
44
|
+
if (!isTouched) return "rebaseline";
|
|
45
|
+
return previousId === void 0 ? "partial-apply" : "keep-dirty";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Wires the shared composed lifecycle. `onAttach` runs on the 0 -> 1
|
|
49
|
+
* subscriber transition BEFORE the form mounts (the edit hydration listener
|
|
50
|
+
* — so a cached record hydrates before mount validators see the form) and
|
|
51
|
+
* its teardown runs on the last unsubscribe.
|
|
52
|
+
*/
|
|
53
|
+
function composeControllers(formController, recordStore, submit, onAttach) {
|
|
54
|
+
let destroyed = false;
|
|
55
|
+
let attachTeardown;
|
|
56
|
+
const active = /* @__PURE__ */ new Set();
|
|
57
|
+
let lastFormState;
|
|
58
|
+
let lastRecordState;
|
|
59
|
+
let cachedSnapshot;
|
|
60
|
+
return {
|
|
61
|
+
subscribe(listener) {
|
|
62
|
+
if (destroyed) return () => {};
|
|
63
|
+
const token = Symbol("composed-subscription");
|
|
64
|
+
const isFirst = active.size === 0;
|
|
65
|
+
active.add(token);
|
|
66
|
+
if (isFirst && onAttach) attachTeardown = onAttach();
|
|
67
|
+
const offForm = formController.subscribe(listener);
|
|
68
|
+
const offRecord = recordStore.subscribe(listener);
|
|
69
|
+
return () => {
|
|
70
|
+
if (!active.delete(token)) return;
|
|
71
|
+
offForm();
|
|
72
|
+
offRecord();
|
|
73
|
+
if (active.size === 0) {
|
|
74
|
+
const teardown = attachTeardown;
|
|
75
|
+
attachTeardown = void 0;
|
|
76
|
+
teardown?.();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
getSnapshot() {
|
|
81
|
+
const formState = formController.getSnapshot();
|
|
82
|
+
const recordState = recordStore.getSnapshot();
|
|
83
|
+
if (cachedSnapshot === void 0 || lastFormState !== formState || lastRecordState !== recordState) {
|
|
84
|
+
lastFormState = formState;
|
|
85
|
+
lastRecordState = recordState;
|
|
86
|
+
cachedSnapshot = {
|
|
87
|
+
...recordState,
|
|
88
|
+
form: formState
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return cachedSnapshot;
|
|
92
|
+
},
|
|
93
|
+
submit,
|
|
94
|
+
destroy() {
|
|
95
|
+
if (destroyed) return;
|
|
96
|
+
destroyed = true;
|
|
97
|
+
const teardown = attachTeardown;
|
|
98
|
+
attachTeardown = void 0;
|
|
99
|
+
teardown?.();
|
|
100
|
+
formController.destroy();
|
|
101
|
+
recordStore.destroy();
|
|
102
|
+
active.clear();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
28
105
|
}
|
|
29
|
-
|
|
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
|
-
if (cachedSnapshot === void 0 || lastFormState !== formState || lastRecordState !== recordState) {
|
|
61
|
-
lastFormState = formState;
|
|
62
|
-
lastRecordState = recordState;
|
|
63
|
-
cachedSnapshot = { ...recordState, form: formState };
|
|
64
|
-
}
|
|
65
|
-
return cachedSnapshot;
|
|
66
|
-
},
|
|
67
|
-
submit: () => formController.submit(),
|
|
68
|
-
destroy() {
|
|
69
|
-
if (destroyed) return;
|
|
70
|
-
destroyed = true;
|
|
71
|
-
const teardown = attachTeardown;
|
|
72
|
-
attachTeardown = void 0;
|
|
73
|
-
teardown?.();
|
|
74
|
-
formController.destroy();
|
|
75
|
-
recordStore.destroy();
|
|
76
|
-
active.clear();
|
|
77
|
-
}
|
|
78
|
-
};
|
|
106
|
+
/**
|
|
107
|
+
* The per-submit save options. The form's `onSubmit` takes no arguments, so
|
|
108
|
+
* `submit(options)` parks `options` here for the length of that one submit and
|
|
109
|
+
* the form's `save` reads them back through `saveOptions`. Two overlapping
|
|
110
|
+
* submits share the slot.
|
|
111
|
+
*/
|
|
112
|
+
function createSubmitOptions() {
|
|
113
|
+
let current = {};
|
|
114
|
+
return {
|
|
115
|
+
/** The form's `save`: the call's options, with `reconcile` run before the caller's own `onSaved`. */
|
|
116
|
+
save(recordController, reconcile) {
|
|
117
|
+
return (data) => {
|
|
118
|
+
const call = current;
|
|
119
|
+
return recordController.save(data, {
|
|
120
|
+
...call,
|
|
121
|
+
onSaved: async (saved) => {
|
|
122
|
+
await reconcile(saved);
|
|
123
|
+
await call.onSaved?.(saved);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
async submit(formController, options = {}) {
|
|
129
|
+
current = options;
|
|
130
|
+
try {
|
|
131
|
+
await formController.submit();
|
|
132
|
+
} finally {
|
|
133
|
+
current = {};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
79
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* The money path (D10): P4 `createEditController` + the form controller,
|
|
140
|
+
* wired — record hydration into form defaults, `save` flowing server
|
|
141
|
+
* `fieldErrors` onto the form, success reconciling with SERVER truth
|
|
142
|
+
* (`form.reset(saved)` — D5), one subscription lifecycle spanning both.
|
|
143
|
+
*/
|
|
80
144
|
function createEditFormController(ctx, resource, options) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const lifecycle = composeControllers(formController, recordController, () => {
|
|
152
|
-
const off = recordController.subscribe(hydrate);
|
|
153
|
-
hydrate();
|
|
154
|
-
return off;
|
|
155
|
-
});
|
|
156
|
-
return { form: formController.form, recordController, ...lifecycle };
|
|
145
|
+
const recordController = createEditController(ctx, resource, {
|
|
146
|
+
id: options.id,
|
|
147
|
+
enabled: options.enabled,
|
|
148
|
+
mutationMode: options.mutationMode,
|
|
149
|
+
transform: options.transform,
|
|
150
|
+
onRedirect: options.onRedirect
|
|
151
|
+
});
|
|
152
|
+
const baseDefaults = options.defaultValues ?? {};
|
|
153
|
+
const resetOnSuccess = options.resetOnSuccess !== false;
|
|
154
|
+
const mergedWith = (record) => deepMerge(baseDefaults, record);
|
|
155
|
+
/** The last hydrated record REFERENCE — unrelated record-store writes (saving flips) skip hydration. */
|
|
156
|
+
let lastHydrated;
|
|
157
|
+
const submitOptions = createSubmitOptions();
|
|
158
|
+
const formController = createFormController({
|
|
159
|
+
defaultValues: baseDefaults,
|
|
160
|
+
schema: options.schema ?? registrySchema(ctx, resource),
|
|
161
|
+
validate: options.validate,
|
|
162
|
+
sanitizeEmptyValues: options.sanitizeEmptyValues,
|
|
163
|
+
getRecord: () => recordController.getSnapshot().record,
|
|
164
|
+
notifications: ctx.notifications,
|
|
165
|
+
translate: ctx.translate,
|
|
166
|
+
resetOnSuccess: false,
|
|
167
|
+
save: submitOptions.save(recordController, async (saved) => {
|
|
168
|
+
if (resetOnSuccess) {
|
|
169
|
+
formController.form.reset(mergedWith(saved));
|
|
170
|
+
lastHydrated = saved;
|
|
171
|
+
}
|
|
172
|
+
await options.onSuccess?.(saved);
|
|
173
|
+
}),
|
|
174
|
+
formOptions: options.formOptions
|
|
175
|
+
});
|
|
176
|
+
const hydrate = () => {
|
|
177
|
+
const { record } = recordController.getSnapshot();
|
|
178
|
+
if (record === void 0 || record === lastHydrated) return;
|
|
179
|
+
const form = formController.form;
|
|
180
|
+
const action = resolveHydrationAction(lastHydrated === void 0 ? void 0 : lastHydrated.id, record.id, form.state.isTouched);
|
|
181
|
+
lastHydrated = record;
|
|
182
|
+
if (action === "keep-dirty") return;
|
|
183
|
+
const merged = mergedWith(record);
|
|
184
|
+
if (action === "hard-reset") {
|
|
185
|
+
form.reset(merged);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (action === "partial-apply") {
|
|
189
|
+
const dirtyValues = [];
|
|
190
|
+
for (const [path, meta] of Object.entries(form.state.fieldMeta)) {
|
|
191
|
+
if (!meta?.isDirty) continue;
|
|
192
|
+
const name = toFieldName(path);
|
|
193
|
+
dirtyValues.push([name, form.getFieldValue(name)]);
|
|
194
|
+
}
|
|
195
|
+
form.reset(merged);
|
|
196
|
+
for (const [path, value] of dirtyValues) form.setFieldValue(path, value, { dontRunListeners: true });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
form.update({
|
|
200
|
+
...form.options,
|
|
201
|
+
defaultValues: merged
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
const lifecycle = composeControllers(formController, recordController, (saveOptions) => submitOptions.submit(formController, saveOptions), () => {
|
|
205
|
+
const off = recordController.subscribe(hydrate);
|
|
206
|
+
hydrate();
|
|
207
|
+
return off;
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
form: formController.form,
|
|
211
|
+
handle: toResourceFormHandle(formController.handle, resource),
|
|
212
|
+
recordController,
|
|
213
|
+
...lifecycle
|
|
214
|
+
};
|
|
157
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* D10's create arm: P4 `createCreateController` + the form controller. No
|
|
218
|
+
* hydration machinery — the seed record and defaults are both known at
|
|
219
|
+
* construction (seed wins, §0b precedence); success reconciles with the
|
|
220
|
+
* CREATED record (server-assigned id included).
|
|
221
|
+
*/
|
|
158
222
|
function createCreateFormController(ctx, resource, options = {}) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}),
|
|
192
|
-
formOptions: options.formOptions
|
|
193
|
-
});
|
|
194
|
-
const lifecycle = composeControllers(formController, recordController);
|
|
195
|
-
return { form: formController.form, recordController, ...lifecycle };
|
|
223
|
+
const recordController = createCreateController(ctx, resource, {
|
|
224
|
+
record: options.record,
|
|
225
|
+
transform: options.transform,
|
|
226
|
+
onRedirect: options.onRedirect,
|
|
227
|
+
mutationMode: options.mutationMode
|
|
228
|
+
});
|
|
229
|
+
const seed = options.record ?? {};
|
|
230
|
+
const baseDefaults = options.defaultValues ?? {};
|
|
231
|
+
const resetOnSuccess = options.resetOnSuccess !== false;
|
|
232
|
+
const submitOptions = createSubmitOptions();
|
|
233
|
+
const formController = createFormController({
|
|
234
|
+
defaultValues: deepMerge(baseDefaults, seed),
|
|
235
|
+
schema: options.schema ?? registrySchema(ctx, resource),
|
|
236
|
+
validate: options.validate,
|
|
237
|
+
sanitizeEmptyValues: options.sanitizeEmptyValues,
|
|
238
|
+
getRecord: () => seed,
|
|
239
|
+
notifications: ctx.notifications,
|
|
240
|
+
translate: ctx.translate,
|
|
241
|
+
resetOnSuccess: false,
|
|
242
|
+
save: submitOptions.save(recordController, async (saved) => {
|
|
243
|
+
if (resetOnSuccess) formController.form.reset(deepMerge(baseDefaults, saved));
|
|
244
|
+
await options.onSuccess?.(saved);
|
|
245
|
+
}),
|
|
246
|
+
formOptions: options.formOptions
|
|
247
|
+
});
|
|
248
|
+
const lifecycle = composeControllers(formController, recordController, (saveOptions) => submitOptions.submit(formController, saveOptions));
|
|
249
|
+
return {
|
|
250
|
+
form: formController.form,
|
|
251
|
+
handle: toResourceFormHandle(formController.handle, resource),
|
|
252
|
+
recordController,
|
|
253
|
+
...lifecycle
|
|
254
|
+
};
|
|
196
255
|
}
|
|
197
|
-
export {
|
|
198
|
-
createCreateFormController,
|
|
199
|
-
createEditFormController,
|
|
200
|
-
resolveHydrationAction
|
|
201
|
-
};
|
|
256
|
+
export { createCreateFormController, createEditFormController, resolveHydrationAction };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RHF-compatible event extraction: DOM/synthetic change events yield
|
|
3
|
+
* `target.checked` for checkboxes and `target.value` otherwise; anything
|
|
4
|
+
* else (a raw value, a custom picker's payload) passes through untouched.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractEventValue(input: unknown): unknown;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { hasProperty } from "../guards.js";
|
|
2
|
+
// src/form/event-value.ts
|
|
3
|
+
/**
|
|
4
|
+
* RHF-compatible event extraction: DOM/synthetic change events yield
|
|
5
|
+
* `target.checked` for checkboxes and `target.value` otherwise; anything
|
|
6
|
+
* else (a raw value, a custom picker's payload) passes through untouched.
|
|
7
|
+
*/
|
|
8
|
+
function extractEventValue(input) {
|
|
9
|
+
if (!hasProperty(input, "target")) return input;
|
|
10
|
+
const { target } = input;
|
|
11
|
+
const hasValue = hasProperty(target, "value");
|
|
12
|
+
const hasChecked = hasProperty(target, "checked");
|
|
13
|
+
if (!hasValue && !hasChecked) return input;
|
|
14
|
+
if (hasProperty(target, "type") && target.type === "checkbox") return hasChecked ? target.checked : void 0;
|
|
15
|
+
return hasValue ? target.value : void 0;
|
|
16
|
+
}
|
|
17
|
+
export { extractEventValue };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type TranslatableMessage } from "./form-controller.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error-map scan order: server/submit errors first, then validator causes.
|
|
4
|
+
* Internal to core — `firstFieldError` and the form-level scan both read it, so
|
|
5
|
+
* the order cannot drift between a field's error and a notification's.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ERROR_MAP_PRIORITY: readonly ["onSubmit", "onDynamic", "onChange", "onBlur", "onMount", "onServer"];
|
|
8
|
+
/**
|
|
9
|
+
* The first displayable error across a field's error-map causes, normalized.
|
|
10
|
+
* Takes any error-map shaped object (form-core's `ValidationErrorMap`, its
|
|
11
|
+
* form-level `FormValidationErrorMap`, a field meta's `errorMap`) and reads
|
|
12
|
+
* each cause through `Reflect.get`, so no call site needs a cast.
|
|
13
|
+
*/
|
|
14
|
+
export declare function firstFieldError(errorMap: object): string | TranslatableMessage | undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { normalizeFormError } from "./form-controller.js";
|
|
2
|
+
// src/form/field-error.ts
|
|
3
|
+
/**
|
|
4
|
+
* Error-map scan order: server/submit errors first, then validator causes.
|
|
5
|
+
* Internal to core — `firstFieldError` and the form-level scan both read it, so
|
|
6
|
+
* the order cannot drift between a field's error and a notification's.
|
|
7
|
+
*/
|
|
8
|
+
var ERROR_MAP_PRIORITY = [
|
|
9
|
+
"onSubmit",
|
|
10
|
+
"onDynamic",
|
|
11
|
+
"onChange",
|
|
12
|
+
"onBlur",
|
|
13
|
+
"onMount",
|
|
14
|
+
"onServer"
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* The first displayable error across a field's error-map causes, normalized.
|
|
18
|
+
* Takes any error-map shaped object (form-core's `ValidationErrorMap`, its
|
|
19
|
+
* form-level `FormValidationErrorMap`, a field meta's `errorMap`) and reads
|
|
20
|
+
* each cause through `Reflect.get`, so no call site needs a cast.
|
|
21
|
+
*/
|
|
22
|
+
function firstFieldError(errorMap) {
|
|
23
|
+
for (const cause of ERROR_MAP_PRIORITY) {
|
|
24
|
+
const normalized = normalizeFormError(Reflect.get(errorMap, cause));
|
|
25
|
+
if (normalized !== void 0) return normalized;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export { ERROR_MAP_PRIORITY, firstFieldError };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DeepKeys, DeepValue } from "@tanstack/form-core";
|
|
2
|
+
/**
|
|
3
|
+
* The one place chelone speaks form-core's path types. `FieldApi` and `FormApi`
|
|
4
|
+
* constrain their generics on `DeepKeys`/`DeepValue`, while chelone's own paths
|
|
5
|
+
* are dot.paths' `SourcePath`/`SourceValue`. The two agree on a concrete record
|
|
6
|
+
* (pinned in `field-name.test-d.ts`), but TS cannot relate them while the record
|
|
7
|
+
* is a type parameter, so a path crosses over through {@link toFieldName}.
|
|
8
|
+
*
|
|
9
|
+
* These are renames, not aliases: a new alias of a conditional type is a
|
|
10
|
+
* distinct type to TS, which form-core's own signatures then fail to match.
|
|
11
|
+
*/
|
|
12
|
+
export type { DeepKeys as FieldName, DeepValue as FieldValue };
|
|
13
|
+
/**
|
|
14
|
+
* The widest value a runtime-composed path can address. An alias, so it is for
|
|
15
|
+
* chelone's own signatures: spell `FieldValue<T, FieldName<T>>` to meet a
|
|
16
|
+
* value form-core returned.
|
|
17
|
+
*/
|
|
18
|
+
export type FieldData<TRecord> = DeepValue<TRecord, DeepKeys<TRecord>>;
|
|
19
|
+
/** A runtime-composed path, as form-core's name for it. */
|
|
20
|
+
export declare function toFieldName<TRecord>(path: string): DeepKeys<TRecord>;
|