@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
|
@@ -1,56 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { isFilterBranch } from "../data/filter-branch.js";
|
|
2
|
+
import { isRecord } from "../guards.js";
|
|
3
|
+
// src/form/filter-form-values.ts
|
|
4
|
+
/**
|
|
5
|
+
* Pure helpers a live filter form uses to keep its values and the list filter
|
|
6
|
+
* in sync. Shared by the React and Vue adapters so the merge and reflect rules
|
|
7
|
+
* live once.
|
|
8
|
+
*/
|
|
9
|
+
var resolveFilterValue = (formValues, key, filter) => {
|
|
10
|
+
const formValue = formValues[key];
|
|
11
|
+
if (formValue === void 0 || formValue === null || Array.isArray(formValue) || formValue instanceof Date) return key in filter ? filter[key] : "";
|
|
12
|
+
if (isRecord(formValue)) {
|
|
13
|
+
const heldFilter = filter[key];
|
|
14
|
+
const nestedFilter = isRecord(heldFilter) ? heldFilter : {};
|
|
15
|
+
const rebuilt = {};
|
|
16
|
+
for (const innerKey of Object.keys(formValue)) rebuilt[innerKey] = resolveFilterValue(formValue, innerKey, nestedFilter);
|
|
17
|
+
if (Object.keys(rebuilt).length === 0) return "";
|
|
18
|
+
return rebuilt;
|
|
19
|
+
}
|
|
20
|
+
return key in filter ? filter[key] : "";
|
|
17
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Rebuild form values from the incoming filter so EVERY current form key is
|
|
24
|
+
* present with a defined value (an empty string when the filter lacks it).
|
|
25
|
+
*
|
|
26
|
+
* form-core (like RHF) re-applies a field's CURRENT value when `reset(values)`
|
|
27
|
+
* omits that key — which would silently ignore an external filter clear. So a
|
|
28
|
+
* reset must carry a value for every mounted input. This never produces
|
|
29
|
+
* `undefined`/`null` for a present form key.
|
|
30
|
+
*/
|
|
18
31
|
function getFilterFormValues(formValues, filter) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
32
|
+
const result = {};
|
|
33
|
+
for (const key of Object.keys(formValues)) result[key] = resolveFilterValue(formValues, key, filter);
|
|
34
|
+
return result;
|
|
24
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The form shape the external-reflect must cover: every key that is either a
|
|
38
|
+
* current form value, a REGISTERED field (form-core tracks these in `fieldMeta`
|
|
39
|
+
* even when unset), or present on the incoming filter. Existing values are
|
|
40
|
+
* carried through; registered-but-unset / filter-only keys seed to `undefined`
|
|
41
|
+
* so {@link getFilterFormValues} resolves them from the filter (fallback `''`).
|
|
42
|
+
*/
|
|
25
43
|
function filterFormReflectShape(values, fieldMeta, filter) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
return shape;
|
|
44
|
+
const shape = { ...values };
|
|
45
|
+
for (const key of Object.keys(fieldMeta)) {
|
|
46
|
+
const top = key.split(/[.[]/, 1)[0];
|
|
47
|
+
if (top && !(top in shape)) shape[top] = void 0;
|
|
48
|
+
}
|
|
49
|
+
for (const key of Object.keys(filter)) if (!(key in shape)) shape[key] = void 0;
|
|
50
|
+
return shape;
|
|
35
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Merge form values OVER the existing filter, one level deep, with arrays
|
|
54
|
+
* REPLACED (never element-merged) — matching ra's mergeObjNotArray. The result
|
|
55
|
+
* is a fresh object (no mutation of the inputs).
|
|
56
|
+
*/
|
|
36
57
|
function mergeFilterValues(filter, values) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return merged;
|
|
58
|
+
const merged = { ...filter };
|
|
59
|
+
for (const key of Object.keys(values)) {
|
|
60
|
+
const next = values[key];
|
|
61
|
+
const prev = merged[key];
|
|
62
|
+
if (isFilterBranch(next) && isFilterBranch(prev)) merged[key] = {
|
|
63
|
+
...prev,
|
|
64
|
+
...next
|
|
65
|
+
};
|
|
66
|
+
else merged[key] = next;
|
|
67
|
+
}
|
|
68
|
+
return merged;
|
|
51
69
|
}
|
|
52
|
-
export {
|
|
53
|
-
filterFormReflectShape,
|
|
54
|
-
getFilterFormValues,
|
|
55
|
-
mergeFilterValues
|
|
56
|
-
};
|
|
70
|
+
export { filterFormReflectShape, getFilterFormValues, mergeFilterValues };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { FormApi, type FormAsyncValidateOrFn, type FormOptions, type FormValidateOrFn, type StandardSchemaV1 } from "@tanstack/form-core";
|
|
2
|
+
import { type TranslateParamValues } from "../i18n/messages.js";
|
|
2
3
|
import type { NotificationStore } from "../notification/notification-store.js";
|
|
4
|
+
import { type FormHandle } from "./form-handle.js";
|
|
3
5
|
/** A sync validator slot of the wrapped FormApi: fn, Standard Schema, or absent. */
|
|
4
6
|
export type SyncValidator<TRecord> = FormValidateOrFn<TRecord> | undefined;
|
|
5
7
|
/** An async validator slot of the wrapped FormApi. */
|
|
@@ -39,7 +41,7 @@ export type FieldErrors = Record<string, string | string[]>;
|
|
|
39
41
|
*/
|
|
40
42
|
export interface TranslatableMessage {
|
|
41
43
|
message: string;
|
|
42
|
-
args?:
|
|
44
|
+
args?: TranslateParamValues;
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
45
47
|
* Form-level validate fn (ra contract): receives ALL values, returns a map of
|
|
@@ -115,6 +117,12 @@ export interface FormController<TRecord> {
|
|
|
115
117
|
* and advanced imperative use.
|
|
116
118
|
*/
|
|
117
119
|
form: CheloneFormApi<TRecord>;
|
|
120
|
+
/**
|
|
121
|
+
* The same form as a {@link FormHandle}: `source` paths typed by `TRecord`,
|
|
122
|
+
* values typed at each path, and the record-erased `ambient` form a context
|
|
123
|
+
* can carry.
|
|
124
|
+
*/
|
|
125
|
+
handle: FormHandle<TRecord>;
|
|
118
126
|
/**
|
|
119
127
|
* Refcounted subscription (uSES-compatible): registers `listener` with
|
|
120
128
|
* `form.store` and mounts the form on the 0 -> 1 transition. Returns an
|
|
@@ -1,168 +1,173 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { hasProperty, isThenable } from "../guards.js";
|
|
2
|
+
import { pickTranslateParamValues } from "../i18n/messages.js";
|
|
3
|
+
import { ERROR_MAP_PRIORITY } from "./field-error.js";
|
|
4
|
+
import { createFormHandle } from "./form-handle.js";
|
|
5
|
+
import { composeFormValidator, sanitizeEmptyValues, toFormErrorMapValue } from "./save-pipeline.js";
|
|
6
|
+
import { FormApi, revalidateLogic } from "@tanstack/form-core";
|
|
7
|
+
// src/form/form-controller.ts
|
|
3
8
|
function createFormController(options) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
9
|
+
const userFormOptions = options.formOptions;
|
|
10
|
+
/**
|
|
11
|
+
* True while the `onSubmit` error-map key holds errors WE wrote (server
|
|
12
|
+
* fieldErrors / async validate results). They make `canSubmit` false, and
|
|
13
|
+
* `handleSubmit` dead-ends on that gate BEFORE any validator could clear
|
|
14
|
+
* them (probed on form-core 1.33) — so `submit()` clears the channel first,
|
|
15
|
+
* letting an unchanged resubmit re-reach the server.
|
|
16
|
+
*/
|
|
17
|
+
let serverErrorsApplied = false;
|
|
18
|
+
/** Emits the invalid-form notification, preferring a displayable root message. */
|
|
19
|
+
const notifyInvalid = (root) => {
|
|
20
|
+
const display = displayableRoot(root);
|
|
21
|
+
if (display === void 0) options.notifications?.notify("chelone.message.invalid_form", { type: "error" });
|
|
22
|
+
else if (typeof display === "string") options.notifications?.notify(display, { type: "error" });
|
|
23
|
+
else options.notifications?.notify(display.message, {
|
|
24
|
+
type: "error",
|
|
25
|
+
messageArgs: display.args
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const dynamicValidator = composeFormValidator({
|
|
29
|
+
schema: options.schema,
|
|
30
|
+
validate: options.validate,
|
|
31
|
+
userValidator: userFormOptions?.validators?.onDynamic
|
|
32
|
+
});
|
|
33
|
+
const onSubmit = async ({ value }) => {
|
|
34
|
+
if (options.validate) {
|
|
35
|
+
const pending = options.validate(value);
|
|
36
|
+
const result = isThenable(pending) ? await pending : pending;
|
|
37
|
+
const mapped = toFormErrorMapValue(result);
|
|
38
|
+
if (mapped) {
|
|
39
|
+
form.setErrorMap({ onSubmit: mapped });
|
|
40
|
+
serverErrorsApplied = true;
|
|
41
|
+
notifyInvalid(mapped.form);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const data = options.sanitizeEmptyValues ? sanitizeEmptyValues(value, options.getRecord?.()) : value;
|
|
46
|
+
const fieldErrors = await options.save(data);
|
|
47
|
+
const mapped = toFormErrorMapValue(fieldErrors);
|
|
48
|
+
if (mapped) {
|
|
49
|
+
form.setErrorMap({ onSubmit: mapped });
|
|
50
|
+
serverErrorsApplied = true;
|
|
51
|
+
notifyInvalid(mapped.form);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (options.resetOnSuccess !== false) form.reset(value);
|
|
55
|
+
await options.onSuccess?.(data);
|
|
56
|
+
};
|
|
57
|
+
const onSubmitInvalid = (props) => {
|
|
58
|
+
notifyInvalid(firstFormLevelError(form.store.state.errorMap));
|
|
59
|
+
userFormOptions?.onSubmitInvalid?.(props);
|
|
60
|
+
};
|
|
61
|
+
const formOptions = {
|
|
62
|
+
...userFormOptions,
|
|
63
|
+
defaultValues: options.defaultValues,
|
|
64
|
+
validators: dynamicValidator ? {
|
|
65
|
+
...userFormOptions?.validators,
|
|
66
|
+
onDynamic: dynamicValidator
|
|
67
|
+
} : userFormOptions?.validators,
|
|
68
|
+
validationLogic: userFormOptions?.validationLogic ?? revalidateLogic(),
|
|
69
|
+
onSubmit,
|
|
70
|
+
onSubmitInvalid
|
|
71
|
+
};
|
|
72
|
+
const form = new FormApi(formOptions);
|
|
73
|
+
let destroyed = false;
|
|
74
|
+
/** Non-undefined exactly while attached (mount cleanup). */
|
|
75
|
+
let unmountForm;
|
|
76
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
77
|
+
/** Runs the mount cleanup at most once per attach cycle. */
|
|
78
|
+
const detach = () => {
|
|
79
|
+
const unmount = unmountForm;
|
|
80
|
+
unmountForm = void 0;
|
|
81
|
+
unmount?.();
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
form,
|
|
85
|
+
handle: createFormHandle(form),
|
|
86
|
+
subscribe(listener) {
|
|
87
|
+
if (destroyed) return () => {};
|
|
88
|
+
const storeSubscription = form.store.subscribe(listener);
|
|
89
|
+
const subscription = { off: () => storeSubscription.unsubscribe() };
|
|
90
|
+
const isFirst = subscriptions.size === 0;
|
|
91
|
+
subscriptions.add(subscription);
|
|
92
|
+
if (isFirst) unmountForm = form.mount();
|
|
93
|
+
return () => {
|
|
94
|
+
if (!subscriptions.delete(subscription)) return;
|
|
95
|
+
subscription.off();
|
|
96
|
+
if (subscriptions.size === 0 && !destroyed) detach();
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
getSnapshot: () => form.store.state,
|
|
100
|
+
async submit() {
|
|
101
|
+
if (destroyed || form.state.isSubmitting) return;
|
|
102
|
+
if (serverErrorsApplied) {
|
|
103
|
+
form.setErrorMap({ onSubmit: {
|
|
104
|
+
form: void 0,
|
|
105
|
+
fields: {}
|
|
106
|
+
} });
|
|
107
|
+
serverErrorsApplied = false;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await form.handleSubmit();
|
|
111
|
+
} catch (error) {
|
|
112
|
+
options.notifications?.notify("chelone.notification.http_error", {
|
|
113
|
+
type: "error",
|
|
114
|
+
messageArgs: { _: String(error) }
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
destroy() {
|
|
119
|
+
if (destroyed) return;
|
|
120
|
+
destroyed = true;
|
|
121
|
+
detach();
|
|
122
|
+
for (const subscription of subscriptions) subscription.off();
|
|
123
|
+
subscriptions.clear();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
121
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Filters an error down to something a NOTIFICATION can display: strings
|
|
129
|
+
* (and the first entry of arrays) pass, objects with a string `message`
|
|
130
|
+
* normalize to a translatable payload, everything else — schema path-record
|
|
131
|
+
* objects, issue lists addressed at fields — yields `undefined` (those are
|
|
132
|
+
* field concerns, the generic invalid_form key covers the notification).
|
|
133
|
+
*/
|
|
122
134
|
function displayableRoot(error) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return normalizeFormError(error);
|
|
127
|
-
}
|
|
128
|
-
return void 0;
|
|
135
|
+
if (typeof error === "string") return error || void 0;
|
|
136
|
+
if (Array.isArray(error)) return displayableRoot(error[0]);
|
|
137
|
+
if (hasProperty(error, "message") && typeof error.message === "string") return normalizeFormError(error);
|
|
129
138
|
}
|
|
139
|
+
/** First displayable form-level error across the error-map keys, submit-first. */
|
|
130
140
|
function firstFormLevelError(errorMap) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"onChange",
|
|
136
|
-
"onBlur",
|
|
137
|
-
"onMount",
|
|
138
|
-
"onServer"
|
|
139
|
-
]) {
|
|
140
|
-
const candidate = displayableRoot(map[key]);
|
|
141
|
-
if (candidate !== void 0) return candidate;
|
|
142
|
-
}
|
|
143
|
-
return void 0;
|
|
141
|
+
for (const key of ERROR_MAP_PRIORITY) {
|
|
142
|
+
const candidate = displayableRoot(errorMap[key]);
|
|
143
|
+
if (candidate !== void 0) return candidate;
|
|
144
|
+
}
|
|
144
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Boundary normalizer for form-core's `unknown`-typed errors: strings pass
|
|
148
|
+
* through, ra translatable payloads (`{message, args}`) are preserved,
|
|
149
|
+
* Standard Schema issues (and anything else with a string `message`,
|
|
150
|
+
* including `Error`s) collapse to their message, arrays surface their first
|
|
151
|
+
* entry (schema validators emit `StandardSchemaV1Issue[]`), nullish/empty
|
|
152
|
+
* input yields `undefined`, and everything else is `String()`-ed.
|
|
153
|
+
*/
|
|
145
154
|
function normalizeFormError(error) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return String(error);
|
|
155
|
+
if (error == null) return void 0;
|
|
156
|
+
if (typeof error === "string") return error;
|
|
157
|
+
if (Array.isArray(error)) return error.length > 0 ? normalizeFormError(error[0]) : void 0;
|
|
158
|
+
if (typeof error === "object" && "message" in error) {
|
|
159
|
+
const { message } = error;
|
|
160
|
+
if (typeof message === "string") {
|
|
161
|
+
if ("args" in error) {
|
|
162
|
+
const args = pickTranslateParamValues(error.args);
|
|
163
|
+
if (args) return {
|
|
164
|
+
message,
|
|
165
|
+
args
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return message;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return String(error);
|
|
164
172
|
}
|
|
165
|
-
export {
|
|
166
|
-
createFormController,
|
|
167
|
-
normalizeFormError
|
|
168
|
-
};
|
|
173
|
+
export { createFormController, normalizeFormError };
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* Vanilla + SSR-safe: no `window`, no React, no router/form library import at
|
|
14
14
|
* runtime — mirrors the resource-registry / MicroStore notification pattern.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import type { ValidationErrorMap } from "@tanstack/form-core";
|
|
17
|
+
import type { TranslatableMessage } from "./form-controller.js";
|
|
17
18
|
/** Called on every membership mutation (a member added or removed). */
|
|
18
19
|
export type FormGroupsListener = () => void;
|
|
19
20
|
/** The vanilla form-group membership registry contract. */
|
|
@@ -47,8 +48,8 @@ export interface GroupFieldMeta {
|
|
|
47
48
|
isDirty: boolean;
|
|
48
49
|
isTouched: boolean;
|
|
49
50
|
isValidating: boolean;
|
|
50
|
-
/**
|
|
51
|
-
errorMap:
|
|
51
|
+
/** form-core's cause-keyed validation errors (`onSubmit`/`onDynamic`/…). */
|
|
52
|
+
errorMap: ValidationErrorMap;
|
|
52
53
|
}
|
|
53
54
|
/** The aggregated group state a badge/toolbar renders. */
|
|
54
55
|
export interface GroupState {
|