@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/form-groups.js
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { firstFieldError } from "./field-error.js";
|
|
2
|
+
// src/form/form-groups.ts
|
|
3
|
+
/** Shared empty snapshot so unknown/empty groups return a stable reference. */
|
|
4
|
+
var EMPTY_MEMBERS = Object.freeze([]);
|
|
3
5
|
function createFormGroups() {
|
|
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
|
-
"onSubmit",
|
|
47
|
-
"onDynamic",
|
|
48
|
-
"onChange",
|
|
49
|
-
"onBlur",
|
|
50
|
-
"onMount",
|
|
51
|
-
"onServer"
|
|
52
|
-
];
|
|
53
|
-
function firstMemberError(errorMap) {
|
|
54
|
-
for (const cause of ERROR_MAP_PRIORITY) {
|
|
55
|
-
const normalized = normalizeFormError(errorMap[cause]);
|
|
56
|
-
if (normalized !== void 0) return normalized;
|
|
57
|
-
}
|
|
58
|
-
return void 0;
|
|
6
|
+
/** group → ordered set of member sources. */
|
|
7
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
9
|
+
/** Per-group cached snapshot; invalidated (deleted) on that group's mutation. */
|
|
10
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
11
|
+
const notify = (group) => {
|
|
12
|
+
snapshots.delete(group);
|
|
13
|
+
for (const listener of listeners) listener();
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
register(group, source) {
|
|
17
|
+
let members = groups.get(group);
|
|
18
|
+
if (members?.has(source)) return;
|
|
19
|
+
if (!members) {
|
|
20
|
+
members = /* @__PURE__ */ new Set();
|
|
21
|
+
groups.set(group, members);
|
|
22
|
+
}
|
|
23
|
+
members.add(source);
|
|
24
|
+
notify(group);
|
|
25
|
+
},
|
|
26
|
+
unregister(group, source) {
|
|
27
|
+
const members = groups.get(group);
|
|
28
|
+
if (!members?.delete(source)) return;
|
|
29
|
+
if (members.size === 0) groups.delete(group);
|
|
30
|
+
notify(group);
|
|
31
|
+
},
|
|
32
|
+
getMembers(group) {
|
|
33
|
+
const members = groups.get(group);
|
|
34
|
+
if (!members || members.size === 0) return EMPTY_MEMBERS;
|
|
35
|
+
const cached = snapshots.get(group);
|
|
36
|
+
if (cached) return cached;
|
|
37
|
+
const snapshot = Object.freeze([...members]);
|
|
38
|
+
snapshots.set(group, snapshot);
|
|
39
|
+
return snapshot;
|
|
40
|
+
},
|
|
41
|
+
subscribe(listener) {
|
|
42
|
+
listeners.add(listener);
|
|
43
|
+
return () => {
|
|
44
|
+
listeners.delete(listener);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
59
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Pure reducer: aggregate a group's member fields into a badge-ready
|
|
51
|
+
* {@link GroupState}. A member with NO `fieldMeta` entry (a registered field
|
|
52
|
+
* not yet mounted) contributes nothing — it is treated as valid + clean, so a
|
|
53
|
+
* pending mount never flips the group invalid. `isValid` is an AND (any invalid
|
|
54
|
+
* member fails the group); `isDirty`/`isTouched`/`isValidating` are ORs.
|
|
55
|
+
* `errors` collects the first displayable message per invalid member.
|
|
56
|
+
*/
|
|
60
57
|
function reduceGroupState(fieldMeta, members) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
let isValid = true;
|
|
59
|
+
let isDirty = false;
|
|
60
|
+
let isTouched = false;
|
|
61
|
+
let isValidating = false;
|
|
62
|
+
const errors = {};
|
|
63
|
+
for (const source of members) {
|
|
64
|
+
const meta = fieldMeta[source];
|
|
65
|
+
if (!meta) continue;
|
|
66
|
+
if (meta.isDirty) isDirty = true;
|
|
67
|
+
if (meta.isTouched) isTouched = true;
|
|
68
|
+
if (meta.isValidating) isValidating = true;
|
|
69
|
+
if (!meta.isValid) {
|
|
70
|
+
isValid = false;
|
|
71
|
+
const error = firstFieldError(meta.errorMap);
|
|
72
|
+
if (error !== void 0) errors[source] = error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
isValid,
|
|
77
|
+
isDirty,
|
|
78
|
+
isTouched,
|
|
79
|
+
isValidating,
|
|
80
|
+
errors
|
|
81
|
+
};
|
|
79
82
|
}
|
|
80
|
-
export {
|
|
81
|
-
createFormGroups,
|
|
82
|
-
reduceGroupState
|
|
83
|
-
};
|
|
83
|
+
export { createFormGroups, reduceGroupState };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { ValidationCause } from "@tanstack/form-core";
|
|
2
|
+
import type { RegisteredResources } from "../resource/register.js";
|
|
3
|
+
import type { RecordOf, ResourceMap } from "../resource/registry.js";
|
|
4
|
+
import type { SourcePath, SourceValue } from "../resource/source-path.js";
|
|
5
|
+
import { type ArrayBinding, type ArrayBindingOptions } from "./array-binding.js";
|
|
6
|
+
import type { CheloneFormApi, CheloneFormState } from "./form-controller.js";
|
|
7
|
+
import { type FieldBinding, type FieldBindingOptions } from "./input-binding.js";
|
|
8
|
+
/**
|
|
9
|
+
* A map of resource name -> record type — the shape {@link FormContextHandleOf}
|
|
10
|
+
* reads. Core names the records and never their fields.
|
|
11
|
+
*/
|
|
12
|
+
export type RecordMap = {
|
|
13
|
+
[name: string]: object;
|
|
14
|
+
};
|
|
15
|
+
/** The records of a resource-config map, keyed by resource name. */
|
|
16
|
+
type RecordsOf<TResources extends ResourceMap> = {
|
|
17
|
+
[TName in keyof TResources]: RecordOf<TResources, TName>;
|
|
18
|
+
};
|
|
19
|
+
/** form-core's derived store of a form's state, as the form itself keeps it. */
|
|
20
|
+
type FormStore<TRecord> = CheloneFormApi<TRecord>["store"];
|
|
21
|
+
/**
|
|
22
|
+
* The field-meta map of a form whose record type is erased: a form context
|
|
23
|
+
* spans every record below it and names none of them.
|
|
24
|
+
*/
|
|
25
|
+
export type AmbientFieldMeta = CheloneFormState<unknown>["fieldMeta"];
|
|
26
|
+
/**
|
|
27
|
+
* The form state a handle reads: its values plus the flags an adapter renders.
|
|
28
|
+
* Deliberately not the whole form-core state — `fieldMeta` and `errorMap` are
|
|
29
|
+
* keyed by the record's paths and by its validator slots, and a handle has to
|
|
30
|
+
* stay readable from a form context that knows neither.
|
|
31
|
+
*/
|
|
32
|
+
export interface FormSnapshot<TRecord> {
|
|
33
|
+
values: TRecord;
|
|
34
|
+
isSubmitting: boolean;
|
|
35
|
+
isDirty: boolean;
|
|
36
|
+
isValid: boolean;
|
|
37
|
+
isTouched: boolean;
|
|
38
|
+
isDefaultValue: boolean;
|
|
39
|
+
isSubmitSuccessful: boolean;
|
|
40
|
+
canSubmit: boolean;
|
|
41
|
+
}
|
|
42
|
+
type AmbientFieldOptions = FieldBindingOptions<unknown, string, unknown>;
|
|
43
|
+
type AmbientFieldBinding = FieldBinding<unknown, string, unknown>;
|
|
44
|
+
type AmbientArrayOptions = ArrayBindingOptions<unknown, string>;
|
|
45
|
+
type AmbientArrayBinding = ArrayBinding<unknown, string>;
|
|
46
|
+
/**
|
|
47
|
+
* A form with its record type erased: the shape a form context carries, since
|
|
48
|
+
* one context spans every input below it whatever their record. Every path is a
|
|
49
|
+
* `string` and every value unchecked; a {@link FormHandle} is the way back to a
|
|
50
|
+
* record, and {@link UnboundFormHandle} is this form as a context member.
|
|
51
|
+
*/
|
|
52
|
+
export interface AmbientForm {
|
|
53
|
+
/**
|
|
54
|
+
* form-core's derived store of the record-free state: `values`, `fieldMeta`,
|
|
55
|
+
* `errorMap` and the form flags.
|
|
56
|
+
*/
|
|
57
|
+
readonly store: FormStore<unknown>;
|
|
58
|
+
/** The value at a path. */
|
|
59
|
+
getFieldValue(path: string): unknown;
|
|
60
|
+
/** Writes a value at a path, marking the field touched and dirty. */
|
|
61
|
+
setFieldValue(path: string, value: unknown): void;
|
|
62
|
+
/** The field's meta (touched/dirty/errors/validators) at a path. */
|
|
63
|
+
getFieldMeta(path: string): AmbientFieldMeta[string];
|
|
64
|
+
/** Runs the validators of one field for `cause`, settling when they have. */
|
|
65
|
+
validateField(path: string, cause: ValidationCause): Promise<void>;
|
|
66
|
+
/** Runs the form's own submit: validate, then the save the form was built with. */
|
|
67
|
+
handleSubmit(): Promise<void>;
|
|
68
|
+
/** Rebaselines the form from `values`, or from its current defaults when omitted. */
|
|
69
|
+
reset(values?: unknown): void;
|
|
70
|
+
/**
|
|
71
|
+
* Binds one field of this form. Inert until the binding is subscribed to
|
|
72
|
+
* (see {@link FieldBinding}).
|
|
73
|
+
*/
|
|
74
|
+
bindField(options: AmbientFieldOptions): AmbientFieldBinding;
|
|
75
|
+
/** Binds one array field of this form. Inert until subscribed to. */
|
|
76
|
+
bindArray(options: AmbientArrayOptions): AmbientArrayBinding;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The value at one path of `TRecord`. The path-union case — what a generic
|
|
80
|
+
* signature comparison instantiates `TPath` with — short-circuits instead of
|
|
81
|
+
* walking `Get` over the whole `Paths` union.
|
|
82
|
+
*/
|
|
83
|
+
type HandleValue<TRecord, TPath extends string> = string extends TPath ? unknown : SourceValue<TRecord, TPath>;
|
|
84
|
+
/**
|
|
85
|
+
* The record-typed way into a form: every `source` is a path of `TRecord` and
|
|
86
|
+
* every value read or written is the value AT that path, not `unknown`.
|
|
87
|
+
*
|
|
88
|
+
* The handle is the form side of a form — values and state. Submission belongs
|
|
89
|
+
* to the controller that owns the save pipeline (`createFormController`,
|
|
90
|
+
* `createEditFormController`), which exposes a handle next to its own `submit`.
|
|
91
|
+
*
|
|
92
|
+
* Nothing here is keyed by the record's path union, so a handle always widens
|
|
93
|
+
* to {@link UnboundFormHandle} — the way a form context erases it.
|
|
94
|
+
*/
|
|
95
|
+
export interface FormHandle<TRecord> {
|
|
96
|
+
/** The same form with its record type erased — what a form context carries. */
|
|
97
|
+
readonly ambient: AmbientForm;
|
|
98
|
+
/** The current values and form flags. */
|
|
99
|
+
getSnapshot(): FormSnapshot<TRecord>;
|
|
100
|
+
/** The value at `TPath`, including the `null`/`undefined` of an optional parent. */
|
|
101
|
+
getFieldValue<TPath extends string>(path: TPath & SourcePath<TRecord>): HandleValue<TRecord, TPath>;
|
|
102
|
+
/** Writes `value` at `TPath` (touched + dirty, validated per the form's logic). */
|
|
103
|
+
setFieldValue<TPath extends string>(path: TPath & SourcePath<TRecord>, value: HandleValue<TRecord, TPath>): void;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A {@link FormHandle} tagged with the resource its form edits. `TFormData` is
|
|
107
|
+
* the form's data type (a `Partial` of the record: a create form holds none of
|
|
108
|
+
* it, an edit form some).
|
|
109
|
+
*/
|
|
110
|
+
export interface ResourceFormHandle<TName extends string, TFormData = unknown> extends FormHandle<TFormData> {
|
|
111
|
+
readonly resource: TName;
|
|
112
|
+
}
|
|
113
|
+
/** A handle the registry types no record for — the ambient form as a context member. */
|
|
114
|
+
export interface UnboundFormHandle extends FormHandle<unknown> {
|
|
115
|
+
readonly resource: undefined;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The handle a form context can hold: one member per resource of `TRecords`,
|
|
119
|
+
* each narrowing on its own `resource`, plus {@link UnboundFormHandle} for a
|
|
120
|
+
* form the registry does not type. `UnboundFormHandle` alone when the map names
|
|
121
|
+
* no fixed resource (`ResourceMap`, the unregistered case).
|
|
122
|
+
*/
|
|
123
|
+
export type FormContextHandleOf<TRecords extends RecordMap> = string extends keyof TRecords ? UnboundFormHandle : {
|
|
124
|
+
[TName in keyof TRecords & string]: ResourceFormHandle<TName, Partial<TRecords[TName]>>;
|
|
125
|
+
}[keyof TRecords & string] | UnboundFormHandle;
|
|
126
|
+
/** {@link FormContextHandleOf} over the app's registered resources. */
|
|
127
|
+
export type FormContextHandle = FormContextHandleOf<RecordsOf<RegisteredResources>>;
|
|
128
|
+
/**
|
|
129
|
+
* Wraps a form-core form in a {@link FormHandle}: the typed members read and
|
|
130
|
+
* write through the form, and `ambient` is the same form with its record type
|
|
131
|
+
* erased.
|
|
132
|
+
*/
|
|
133
|
+
export declare function createFormHandle<TRecord>(form: CheloneFormApi<TRecord>): FormHandle<TRecord>;
|
|
134
|
+
/**
|
|
135
|
+
* Tags `handle` with `resource`, keeping its record type: the handle a record
|
|
136
|
+
* view hands to its form components.
|
|
137
|
+
*/
|
|
138
|
+
export declare function toResourceFormHandle<TName extends string, TFormData>(handle: FormHandle<TFormData>, resource: TName): ResourceFormHandle<TName, TFormData>;
|
|
139
|
+
/**
|
|
140
|
+
* Widens a handle to the form-context union, tagging it with `resource`, or
|
|
141
|
+
* leaving it unbound when the form edits no resource.
|
|
142
|
+
*
|
|
143
|
+
* A type-level move only: at runtime the object is `handle` with `resource`
|
|
144
|
+
* set. The registry — not the caller's generic — decides which record type the
|
|
145
|
+
* name carries, so no plain assignment can express it (see
|
|
146
|
+
* `form-handle.test-d.ts`).
|
|
147
|
+
*/
|
|
148
|
+
export declare function toFormContextHandle<TName extends string, TFormData>(handle: FormHandle<TFormData>, resource: TName | undefined): FormContextHandle;
|
|
149
|
+
/**
|
|
150
|
+
* The members every form handle carries, whatever record it types. The
|
|
151
|
+
* inference anchor for {@link isResourceHandle}: a context union is a union of
|
|
152
|
+
* handles, and a union is not inferable through a mapped type.
|
|
153
|
+
*/
|
|
154
|
+
export interface AnyFormHandle {
|
|
155
|
+
readonly resource?: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The names `THandle` can carry. A handle union that names no resource (nothing
|
|
159
|
+
* registered) takes any string, so a generic caller still compiles.
|
|
160
|
+
*/
|
|
161
|
+
export type HandleName<THandle extends AnyFormHandle> = [
|
|
162
|
+
NonNullable<THandle["resource"]>
|
|
163
|
+
] extends [never] ? string : NonNullable<THandle["resource"]>;
|
|
164
|
+
/**
|
|
165
|
+
* Narrows a handle to the member that names `name`.
|
|
166
|
+
*
|
|
167
|
+
* The narrowing a generic body cannot do itself: `handle.resource === name`
|
|
168
|
+
* with a generic `name` leaves the whole union (probed in
|
|
169
|
+
* `form-handle.test-d.ts`) because the compiler cannot rule out the other
|
|
170
|
+
* members. At runtime this checks `resource` and nothing else.
|
|
171
|
+
*/
|
|
172
|
+
export declare function isResourceHandle<THandle extends AnyFormHandle, TName extends HandleName<THandle>>(handle: THandle, name: TName): handle is Extract<THandle, {
|
|
173
|
+
resource: TName;
|
|
174
|
+
}> & ResourceFormHandle<TName>;
|
|
175
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createArrayBinding } from "./array-binding.js";
|
|
2
|
+
import { createFieldBinding } from "./input-binding.js";
|
|
3
|
+
// src/form/form-handle.ts
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a form-core form in a {@link FormHandle}: the typed members read and
|
|
6
|
+
* write through the form, and `ambient` is the same form with its record type
|
|
7
|
+
* erased.
|
|
8
|
+
*/
|
|
9
|
+
function createFormHandle(form) {
|
|
10
|
+
const loose = form;
|
|
11
|
+
const bindField = (options) => createFieldBinding(loose, options);
|
|
12
|
+
const bindArray = (options) => createArrayBinding(loose, options);
|
|
13
|
+
return {
|
|
14
|
+
ambient: {
|
|
15
|
+
store: loose.store,
|
|
16
|
+
getFieldValue: (path) => loose.getFieldValue(path),
|
|
17
|
+
setFieldValue: (path, value) => loose.setFieldValue(path, value),
|
|
18
|
+
getFieldMeta: (path) => loose.getFieldMeta(path),
|
|
19
|
+
validateField: async (path, cause) => {
|
|
20
|
+
await loose.validateField(path, cause);
|
|
21
|
+
},
|
|
22
|
+
handleSubmit: () => loose.handleSubmit(),
|
|
23
|
+
reset: (values) => loose.reset(values),
|
|
24
|
+
bindField,
|
|
25
|
+
bindArray
|
|
26
|
+
},
|
|
27
|
+
getSnapshot: () => form.state,
|
|
28
|
+
getFieldValue: (path) => loose.getFieldValue(path),
|
|
29
|
+
setFieldValue: (path, value) => loose.setFieldValue(path, value)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Tags `handle` with `resource`, keeping its record type: the handle a record
|
|
34
|
+
* view hands to its form components.
|
|
35
|
+
*/
|
|
36
|
+
function toResourceFormHandle(handle, resource) {
|
|
37
|
+
return {
|
|
38
|
+
...handle,
|
|
39
|
+
resource
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Widens a handle to the form-context union, tagging it with `resource`, or
|
|
44
|
+
* leaving it unbound when the form edits no resource.
|
|
45
|
+
*
|
|
46
|
+
* A type-level move only: at runtime the object is `handle` with `resource`
|
|
47
|
+
* set. The registry — not the caller's generic — decides which record type the
|
|
48
|
+
* name carries, so no plain assignment can express it (see
|
|
49
|
+
* `form-handle.test-d.ts`).
|
|
50
|
+
*/
|
|
51
|
+
function toFormContextHandle(handle, resource) {
|
|
52
|
+
return {
|
|
53
|
+
...handle,
|
|
54
|
+
resource
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Narrows a handle to the member that names `name`.
|
|
59
|
+
*
|
|
60
|
+
* The narrowing a generic body cannot do itself: `handle.resource === name`
|
|
61
|
+
* with a generic `name` leaves the whole union (probed in
|
|
62
|
+
* `form-handle.test-d.ts`) because the compiler cannot rule out the other
|
|
63
|
+
* members. At runtime this checks `resource` and nothing else.
|
|
64
|
+
*/
|
|
65
|
+
function isResourceHandle(handle, name) {
|
|
66
|
+
return handle.resource === name;
|
|
67
|
+
}
|
|
68
|
+
export { createFormHandle, isResourceHandle, toFormContextHandle, toResourceFormHandle };
|
|
@@ -1,32 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FieldApi, type FieldAsyncValidateOrFn, type FieldValidateOrFn, type StandardSchemaV1 } from "@tanstack/form-core";
|
|
2
2
|
import type { LooseRecord } from "../resource/record.js";
|
|
3
|
-
import type { SourcePath } from "../resource/source-path.js";
|
|
3
|
+
import type { SourcePath, SourceValue } from "../resource/source-path.js";
|
|
4
|
+
import { type FieldData, type FieldName } from "./field-name.js";
|
|
4
5
|
import type { AsyncValidator, CheloneFormApi, SyncValidator } from "./form-controller.js";
|
|
5
6
|
import { type Validator } from "./validators.js";
|
|
6
7
|
/**
|
|
7
8
|
* ra's default format: render `null`/`undefined` as `''` so text inputs stay
|
|
8
9
|
* controlled; every other value (including `0`/`false`) passes through.
|
|
9
10
|
*/
|
|
10
|
-
export declare function defaultFormat(value:
|
|
11
|
+
export declare function defaultFormat<TValue>(value: TValue): FormattedValue<TValue>;
|
|
12
|
+
/**
|
|
13
|
+
* What a field's input holds under {@link defaultFormat}: the stored value with
|
|
14
|
+
* its holes spelled as `''`.
|
|
15
|
+
*/
|
|
16
|
+
export type FormattedValue<TValue> = Exclude<TValue, null | undefined> | "";
|
|
11
17
|
/**
|
|
12
18
|
* ra's default parse: store a cleared text input (`''`) as `null` — the
|
|
13
19
|
* shape most backends expect; every other value (including `0`/`false`)
|
|
14
20
|
* passes through.
|
|
15
21
|
*/
|
|
16
|
-
export declare function defaultParse(input:
|
|
17
|
-
/** The widest data type a runtime-composed source path can address. */
|
|
18
|
-
type FieldData<TRecord> = DeepValue<TRecord, DeepKeys<TRecord>>;
|
|
22
|
+
export declare function defaultParse<TInput>(input: TInput): Exclude<TInput, ""> | null;
|
|
19
23
|
/** A sync field-validator slot at its widest legal instantiation. */
|
|
20
|
-
type FieldSync<TRecord> = FieldValidateOrFn<TRecord,
|
|
24
|
+
type FieldSync<TRecord> = FieldValidateOrFn<TRecord, FieldName<TRecord>, FieldData<TRecord>> | undefined;
|
|
21
25
|
/** An async field-validator slot at its widest legal instantiation. */
|
|
22
|
-
type FieldAsync<TRecord> = FieldAsyncValidateOrFn<TRecord,
|
|
26
|
+
type FieldAsync<TRecord> = FieldAsyncValidateOrFn<TRecord, FieldName<TRecord>, FieldData<TRecord>> | undefined;
|
|
23
27
|
/**
|
|
24
28
|
* The wrapped FieldApi, generic in the DATA type only (the T1 strategy):
|
|
25
29
|
* source paths are composed at runtime (`items[0].sku`), so the name/value
|
|
26
30
|
* generics are pinned to their widest legal instantiations and the form
|
|
27
31
|
* slots mirror {@link CheloneFormApi} exactly.
|
|
28
32
|
*/
|
|
29
|
-
export type CheloneFieldApi<TRecord> = FieldApi<TRecord,
|
|
33
|
+
export type CheloneFieldApi<TRecord> = FieldApi<TRecord, FieldName<TRecord>, FieldData<TRecord>, FieldSync<TRecord>, // onMount
|
|
30
34
|
FieldSync<TRecord>, // onChange
|
|
31
35
|
FieldAsync<TRecord>, // onChangeAsync
|
|
32
36
|
FieldSync<TRecord>, // onBlur
|
|
@@ -49,28 +53,31 @@ never>;
|
|
|
49
53
|
/** The field state snapshot type — what {@link FieldBinding.getSnapshot} returns. */
|
|
50
54
|
export type CheloneFieldState<TRecord> = CheloneFieldApi<TRecord>["state"];
|
|
51
55
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* layer) is merged in.
|
|
56
|
+
* form-core's field store: `{ value, meta }` for an adapter selector. An adapter
|
|
57
|
+
* cannot know the record of the field it renders, so it reads the default.
|
|
55
58
|
*/
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
export type CheloneFieldStore<TRecord = unknown> = CheloneFieldApi<TRecord>["store"];
|
|
60
|
+
/**
|
|
61
|
+
* Everything a field binding takes except the identity-defining `source`,
|
|
62
|
+
* spelled for one value type: `TValue` is what the field holds, `TInput` what
|
|
63
|
+
* the input holds.
|
|
64
|
+
*/
|
|
65
|
+
export interface FieldBindingBaseOptions<TValue, TInput> {
|
|
63
66
|
/**
|
|
64
67
|
* Input-level default, ra precedence: seeds ONLY into a hole (untouched
|
|
65
68
|
* field whose form value is `undefined`) — an existing record value,
|
|
66
69
|
* including `null`, always wins. Re-applied whenever the hole reopens
|
|
67
70
|
* (reset/rehydration), never dirties the form.
|
|
68
71
|
*/
|
|
69
|
-
defaultValue?:
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
+
defaultValue?: TValue;
|
|
73
|
+
/**
|
|
74
|
+
* Read transform (raw form value -> input value). Default {@link defaultFormat}.
|
|
75
|
+
* Takes the holes a record's type does not show: an untouched field is
|
|
76
|
+
* `undefined`, and {@link defaultParse} stores a cleared input as `null`.
|
|
77
|
+
*/
|
|
78
|
+
format?: (value: TValue | null | undefined) => TInput;
|
|
72
79
|
/** Write transform (input value -> stored value). Default {@link defaultParse}. */
|
|
73
|
-
parse?: (input:
|
|
80
|
+
parse?: (input: TInput) => TValue;
|
|
74
81
|
/** ra-contract validators — composed, first error wins, sync stays sync. */
|
|
75
82
|
validate?: Validator | readonly Validator[];
|
|
76
83
|
/**
|
|
@@ -82,8 +89,15 @@ export interface FieldBindingOptions<TRecord = LooseRecord> {
|
|
|
82
89
|
/** Extra context merged into the validators' meta argument. */
|
|
83
90
|
validatorMeta?: Record<string, unknown>;
|
|
84
91
|
}
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
export interface FieldBindingOptions<TRecord = LooseRecord, TPath extends SourcePath<TRecord> = SourcePath<TRecord>, TInput = FormattedValue<SourceValue<TRecord, TPath>>> extends FieldBindingBaseOptions<SourceValue<TRecord, TPath>, TInput> {
|
|
93
|
+
/** The FINAL field path (already source-scoped — see `source.ts`). */
|
|
94
|
+
source: TPath;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* `update()` payload: everything but the identity-defining source. An adapter
|
|
98
|
+
* renders under any record, so it reads the defaults.
|
|
99
|
+
*/
|
|
100
|
+
export type FieldBindingUpdateOptions<TValue = unknown, TInput = unknown> = FieldBindingBaseOptions<TValue, TInput>;
|
|
87
101
|
/**
|
|
88
102
|
* Vanilla field binding over a single `@tanstack/form-core` FieldApi —
|
|
89
103
|
* the useInput substrate (T4 wraps it in React contexts).
|
|
@@ -102,12 +116,17 @@ export type FieldBindingUpdateOptions = Omit<FieldBindingOptions<LooseRecord>, "
|
|
|
102
116
|
* submit). The sync slot stays synchronous; the FIRST thenable result
|
|
103
117
|
* latches the binding onto the async slot (injected lazily so pure-sync
|
|
104
118
|
* validators never flicker `isValidating`).
|
|
119
|
+
*
|
|
120
|
+
* `TRecord` is the form's data type, `TPath` the field's own source path and
|
|
121
|
+
* `TInput` what the input holds (`TPath`'s value under `format`), so
|
|
122
|
+
* `getValue()`/`setValue()` are typed for one field, not for the widest value
|
|
123
|
+
* in the record.
|
|
105
124
|
*/
|
|
106
|
-
export interface FieldBinding<TRecord> {
|
|
125
|
+
export interface FieldBinding<TRecord, TPath extends SourcePath<TRecord> = SourcePath<TRecord>, TInput = unknown> {
|
|
107
126
|
/** The wrapped FieldApi — the full form-core surface for advanced use. */
|
|
108
127
|
field: CheloneFieldApi<TRecord>;
|
|
109
128
|
/** The final field path this binding is attached to. */
|
|
110
|
-
readonly source:
|
|
129
|
+
readonly source: TPath;
|
|
111
130
|
/**
|
|
112
131
|
* Refcounted subscription (uSES-compatible): registers `listener` with the
|
|
113
132
|
* identity-memoized `field.store` (per-field render isolation) and mounts
|
|
@@ -117,10 +136,16 @@ export interface FieldBinding<TRecord> {
|
|
|
117
136
|
subscribe(listener: () => void): () => void;
|
|
118
137
|
/** The current field state — `field.store.state` (reference-stable until a write). */
|
|
119
138
|
getSnapshot(): CheloneFieldState<TRecord>;
|
|
139
|
+
/**
|
|
140
|
+
* form-core's derived store of this field: `{ value, meta }` for an adapter
|
|
141
|
+
* selector. Same store object as `field.store` — subscribing here and to the
|
|
142
|
+
* form costs one subscription each, and neither mounts the field.
|
|
143
|
+
*/
|
|
144
|
+
readonly store: CheloneFieldStore<TRecord>;
|
|
120
145
|
/** The input-facing value: `format(raw)` — never `null`/`undefined` under the default format. */
|
|
121
|
-
getValue():
|
|
146
|
+
getValue(): TInput;
|
|
122
147
|
/** Writes `parse(input)` through `handleChange` (marks touched+dirty, validates per revalidation mode). */
|
|
123
|
-
setValue(input:
|
|
148
|
+
setValue(input: TInput): void;
|
|
124
149
|
/** Marks the field blurred and runs blur-cause validation. */
|
|
125
150
|
blur(): void;
|
|
126
151
|
/** Requiredness detected from the RAW validate prop (`required()`'s tag). */
|
|
@@ -131,7 +156,7 @@ export interface FieldBinding<TRecord> {
|
|
|
131
156
|
* current option set each render. Re-applies the guarded default when the
|
|
132
157
|
* value hole reopened. Inert while detached (options apply on attach).
|
|
133
158
|
*/
|
|
134
|
-
update(options: FieldBindingUpdateOptions): void;
|
|
159
|
+
update(options: FieldBindingUpdateOptions<SourceValue<TRecord, TPath>, TInput>): void;
|
|
135
160
|
/**
|
|
136
161
|
* Imperative teardown for vanilla (non-React) consumers: unmounts if
|
|
137
162
|
* attached, severs every listener, and blocks re-attach. Idempotent. React
|
|
@@ -139,5 +164,5 @@ export interface FieldBinding<TRecord> {
|
|
|
139
164
|
*/
|
|
140
165
|
destroy(): void;
|
|
141
166
|
}
|
|
142
|
-
export declare function createFieldBinding<TRecord>(form: CheloneFormApi<TRecord>, options: FieldBindingOptions<TRecord>): FieldBinding<TRecord>;
|
|
167
|
+
export declare function createFieldBinding<TRecord, TPath extends SourcePath<TRecord> = SourcePath<TRecord>, TInput = FormattedValue<SourceValue<TRecord, TPath>>>(form: CheloneFormApi<TRecord>, options: FieldBindingOptions<TRecord, TPath, TInput>): FieldBinding<TRecord, TPath, TInput>;
|
|
143
168
|
export {};
|