@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,60 +1,63 @@
|
|
|
1
1
|
import { createMicroStore } from "../store/micro-store.js";
|
|
2
|
+
// src/environment/application-update-store.ts
|
|
3
|
+
/** djb2, forced to an unsigned 32-bit integer. */
|
|
2
4
|
function hashText(text) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
return hash;
|
|
5
|
+
let hash = 5381;
|
|
6
|
+
for (let i = 0; i < text.length; i++) hash = (hash << 5) + hash + text.charCodeAt(i) >>> 0;
|
|
7
|
+
return hash;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Polls the deployed page and reports when its body differs from the first
|
|
11
|
+
* one seen, which means a new build is live. Inert until subscribed. The
|
|
12
|
+
* baseline survives detach and re-attach; polling stops for good once a change
|
|
13
|
+
* is seen.
|
|
14
|
+
*/
|
|
9
15
|
function createApplicationUpdateStore(options = {}) {
|
|
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
|
-
return store;
|
|
16
|
+
const { interval = 36e5, disabled = false } = options;
|
|
17
|
+
let baseline;
|
|
18
|
+
/** Bumped on every detach so a check still in flight can tell it was dropped. */
|
|
19
|
+
let attachment = 0;
|
|
20
|
+
let stopPolling;
|
|
21
|
+
const store = createMicroStore({ hasNewVersion: false }, { onSubscribed() {
|
|
22
|
+
if (disabled || store.getSnapshot().hasNewVersion) return;
|
|
23
|
+
const current = ++attachment;
|
|
24
|
+
const timer = setInterval(() => void check(current), interval);
|
|
25
|
+
stopPolling = () => clearInterval(timer);
|
|
26
|
+
check(current);
|
|
27
|
+
return () => {
|
|
28
|
+
attachment++;
|
|
29
|
+
stopPolling?.();
|
|
30
|
+
stopPolling = void 0;
|
|
31
|
+
};
|
|
32
|
+
} });
|
|
33
|
+
async function check(current) {
|
|
34
|
+
const url = options.url ?? (typeof location === "undefined" ? void 0 : `${location.origin}/index.html`);
|
|
35
|
+
if (url === void 0) return;
|
|
36
|
+
const { fetchOptions } = options;
|
|
37
|
+
let text;
|
|
38
|
+
try {
|
|
39
|
+
const init = typeof fetchOptions === "function" ? fetchOptions() : fetchOptions;
|
|
40
|
+
const response = await fetch(url, {
|
|
41
|
+
...init,
|
|
42
|
+
cache: "no-store"
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) return;
|
|
45
|
+
text = await response.text();
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (current !== attachment || store.getSnapshot().hasNewVersion) return;
|
|
50
|
+
const hash = hashText(text);
|
|
51
|
+
if (baseline === void 0) {
|
|
52
|
+
baseline = hash;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (hash === baseline) return;
|
|
56
|
+
stopPolling?.();
|
|
57
|
+
stopPolling = void 0;
|
|
58
|
+
store.setState({ hasNewVersion: true });
|
|
59
|
+
options.onNewVersionAvailable?.();
|
|
60
|
+
}
|
|
61
|
+
return store;
|
|
57
62
|
}
|
|
58
|
-
export {
|
|
59
|
-
createApplicationUpdateStore
|
|
60
|
-
};
|
|
63
|
+
export { createApplicationUpdateStore };
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { createMicroStore } from "../store/micro-store.js";
|
|
2
|
-
|
|
2
|
+
// src/environment/offline-store.ts
|
|
3
|
+
var readIsOffline = () => typeof navigator !== "undefined" && navigator.onLine === false;
|
|
4
|
+
/**
|
|
5
|
+
* Whether the browser reports itself offline. Reads `navigator.onLine` rather
|
|
6
|
+
* than assuming online until an event fires, so a page opened offline is
|
|
7
|
+
* offline from the first snapshot. Inert until subscribed: nothing touches
|
|
8
|
+
* `window` before then, so it is safe to build during SSR.
|
|
9
|
+
*/
|
|
3
10
|
function createOfflineStore() {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
);
|
|
20
|
-
return store;
|
|
11
|
+
const store = createMicroStore({ isOffline: readIsOffline() }, { onSubscribed() {
|
|
12
|
+
store.setState({ isOffline: readIsOffline() });
|
|
13
|
+
const goOffline = () => store.setState({ isOffline: true });
|
|
14
|
+
const goOnline = () => store.setState({ isOffline: false });
|
|
15
|
+
window.addEventListener("offline", goOffline);
|
|
16
|
+
window.addEventListener("online", goOnline);
|
|
17
|
+
return () => {
|
|
18
|
+
window.removeEventListener("offline", goOffline);
|
|
19
|
+
window.removeEventListener("online", goOnline);
|
|
20
|
+
};
|
|
21
|
+
} });
|
|
22
|
+
return store;
|
|
21
23
|
}
|
|
22
|
-
export {
|
|
23
|
-
createOfflineStore
|
|
24
|
-
};
|
|
24
|
+
export { createOfflineStore };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LooseRecord } from "../resource/record.js";
|
|
2
|
+
import type { SourcePath } from "../resource/source-path.js";
|
|
3
|
+
import type { FieldName, FieldValue } from "./field-name.js";
|
|
2
4
|
import type { CheloneFormApi } from "./form-controller.js";
|
|
3
5
|
import { type FieldBinding, type FieldBindingOptions } from "./input-binding.js";
|
|
4
6
|
/**
|
|
@@ -28,13 +30,29 @@ export interface ArrayRow {
|
|
|
28
30
|
/** Moves THIS row to `to` — {@link ArrayBinding.reOrder} from the row's index. */
|
|
29
31
|
reOrder(to: number): void;
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
/** The array form-core stores at the binding's own path. */
|
|
34
|
+
type PathValue<TRecord, TPath extends string> = FieldValue<TRecord, TPath & FieldName<TRecord>>;
|
|
35
|
+
/**
|
|
36
|
+
* The item type of the array at `TPath`: the row shape of THIS binding's
|
|
37
|
+
* source, not the union of every array in the record. `unknown` when the path
|
|
38
|
+
* is one form-core cannot type — a composed `string` source (the ambient form),
|
|
39
|
+
* a scalar path, or a row-relative path that is no path of the record.
|
|
40
|
+
*/
|
|
41
|
+
export type ArrayItemOf<TRecord, TPath extends string = SourcePath<TRecord>> = string extends TPath ? unknown : [PathValue<TRecord, TPath>] extends [never] ? unknown : PathValue<TRecord, TPath> extends readonly unknown[] ? PathValue<TRecord, TPath>[number] : unknown;
|
|
42
|
+
/**
|
|
43
|
+
* What a new row may be seeded with: a partial of an object row, or any object
|
|
44
|
+
* when the row type is unknown (a composed `string` source).
|
|
45
|
+
*/
|
|
46
|
+
export type ItemDefaultsOf<TItem> = unknown extends TItem ? object : Partial<Extract<TItem, object>>;
|
|
47
|
+
export interface ArrayBindingOptions<TRecord = LooseRecord, TPath extends SourcePath<TRecord> = SourcePath<TRecord>> extends Omit<FieldBindingOptions<TRecord>, "format" | "parse" | "defaultValue"> {
|
|
48
|
+
/** The array field path this binding drives. */
|
|
49
|
+
source: TPath;
|
|
32
50
|
/**
|
|
33
51
|
* Seed rows for a value hole (ra precedence: an existing record value,
|
|
34
52
|
* including `[]`, always wins). Defaults to `[]` so positional ops always
|
|
35
53
|
* have an array to act on once attached. Never dirties the form.
|
|
36
54
|
*/
|
|
37
|
-
defaultValue?: readonly
|
|
55
|
+
defaultValue?: readonly ArrayItemOf<TRecord, TPath>[];
|
|
38
56
|
/**
|
|
39
57
|
* Scalar-array mode (#20): rows are primitives and `add()` pushes `''`.
|
|
40
58
|
* When omitted the mode is DETECTED: `itemDefaults` present -> object;
|
|
@@ -49,10 +67,10 @@ export interface ArrayBindingOptions<TRecord = LooseRecord> extends Omit<FieldBi
|
|
|
49
67
|
* (ra parity: object new item = first-row-keys-nulled + provided
|
|
50
68
|
* defaults). Providing this implies object mode.
|
|
51
69
|
*/
|
|
52
|
-
itemDefaults?:
|
|
70
|
+
itemDefaults?: ItemDefaultsOf<ArrayItemOf<TRecord, TPath>>;
|
|
53
71
|
}
|
|
54
72
|
/** `update()` payload: everything but the identity-defining source. */
|
|
55
|
-
export type ArrayBindingUpdateOptions = Omit<ArrayBindingOptions<
|
|
73
|
+
export type ArrayBindingUpdateOptions<TRecord = LooseRecord, TPath extends SourcePath<TRecord> = SourcePath<TRecord>> = Omit<ArrayBindingOptions<TRecord, TPath>, "source">;
|
|
56
74
|
/**
|
|
57
75
|
* Vanilla array binding over a form-core array field — the useArrayInput
|
|
58
76
|
* substrate (T8 wraps it in React contexts).
|
|
@@ -76,13 +94,13 @@ export type ArrayBindingUpdateOptions = Omit<ArrayBindingOptions<LooseRecord>, "
|
|
|
76
94
|
* reconcile positionally (append/truncate) — use the binding's ops when row
|
|
77
95
|
* identity matters.
|
|
78
96
|
*/
|
|
79
|
-
export interface ArrayBinding<TRecord> {
|
|
97
|
+
export interface ArrayBinding<TRecord, TPath extends SourcePath<TRecord> = SourcePath<TRecord>> {
|
|
80
98
|
/**
|
|
81
99
|
* The array field's own binding (validators via options, meta/errors via
|
|
82
100
|
* `field.getSnapshot()`). Do NOT create a second FieldBinding on the same
|
|
83
101
|
* source — form-core registers ONE instance per field name.
|
|
84
102
|
*/
|
|
85
|
-
field: FieldBinding<TRecord>;
|
|
103
|
+
field: FieldBinding<TRecord, TPath>;
|
|
86
104
|
/** The array field path this binding is attached to. */
|
|
87
105
|
readonly source: string;
|
|
88
106
|
/**
|
|
@@ -103,11 +121,11 @@ export interface ArrayBinding<TRecord> {
|
|
|
103
121
|
* Appends a row. Without `item`: `''` in scalar mode; in object mode the
|
|
104
122
|
* first row's keys nulled merged with `itemDefaults`.
|
|
105
123
|
*/
|
|
106
|
-
add(item?:
|
|
124
|
+
add(item?: ArrayItemOf<TRecord, TPath>): void;
|
|
107
125
|
/** Inserts a row at `index` (clamped into range); same default item as `add`. */
|
|
108
|
-
insert(index: number, item?:
|
|
126
|
+
insert(index: number, item?: ArrayItemOf<TRecord, TPath>): Promise<void>;
|
|
109
127
|
/** Replaces the row at `index` with `item` — the slot gets a NEW id. */
|
|
110
|
-
replace(index: number, item:
|
|
128
|
+
replace(index: number, item: ArrayItemOf<TRecord, TPath>): Promise<void>;
|
|
111
129
|
/** Removes the row at `index` (form-core shifts meta, deletes the trailing slot, revalidates). */
|
|
112
130
|
remove(index: number): Promise<void>;
|
|
113
131
|
/** Swaps two rows — ids travel with the values. */
|
|
@@ -121,7 +139,7 @@ export interface ArrayBinding<TRecord> {
|
|
|
121
139
|
* (validators flow into the underlying field binding; `scalar` /
|
|
122
140
|
* `itemDefaults` / `defaultValue` update the array layer).
|
|
123
141
|
*/
|
|
124
|
-
update(options: ArrayBindingUpdateOptions): void;
|
|
142
|
+
update(options: ArrayBindingUpdateOptions<TRecord, TPath>): void;
|
|
125
143
|
/**
|
|
126
144
|
* Imperative teardown for vanilla (non-React) consumers — delegates to the
|
|
127
145
|
* underlying field binding: unmounts if attached, severs listeners, blocks
|
|
@@ -129,4 +147,5 @@ export interface ArrayBinding<TRecord> {
|
|
|
129
147
|
*/
|
|
130
148
|
destroy(): void;
|
|
131
149
|
}
|
|
132
|
-
export declare function createArrayBinding<TRecord>(form: CheloneFormApi<TRecord>, options: ArrayBindingOptions<TRecord>): ArrayBinding<TRecord>;
|
|
150
|
+
export declare function createArrayBinding<TRecord, TPath extends SourcePath<TRecord> = SourcePath<TRecord>>(form: CheloneFormApi<TRecord>, options: ArrayBindingOptions<TRecord, TPath>): ArrayBinding<TRecord, TPath>;
|
|
151
|
+
export {};
|
|
@@ -1,170 +1,188 @@
|
|
|
1
|
+
import { isRecord } from "../guards.js";
|
|
1
2
|
import { createFieldBinding } from "./input-binding.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
// src/form/array-binding.ts
|
|
4
|
+
/** Record-hydration write: value only, meta untouched, no validation. */
|
|
5
|
+
var HYDRATION_WRITE = {
|
|
6
|
+
dontUpdateMeta: true,
|
|
7
|
+
dontValidate: true,
|
|
8
|
+
dontRunListeners: true
|
|
6
9
|
};
|
|
7
|
-
|
|
8
|
-
const isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
var EMPTY = [];
|
|
9
11
|
function createArrayBinding(form, options) {
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
12
|
+
const { source } = options;
|
|
13
|
+
let current = options;
|
|
14
|
+
const name = source;
|
|
15
|
+
/** A fresh copy of the seed rows — never the option's own array. */
|
|
16
|
+
const seedRows = () => current.defaultValue ? [...current.defaultValue] : [];
|
|
17
|
+
/** The field-binding view of the current options (fresh seed array per call). */
|
|
18
|
+
const toFieldOptions = () => ({
|
|
19
|
+
defaultValue: seedRows(),
|
|
20
|
+
validate: current.validate,
|
|
21
|
+
schema: current.schema,
|
|
22
|
+
validatorMeta: current.validatorMeta
|
|
23
|
+
});
|
|
24
|
+
const fieldBinding = createFieldBinding(form, {
|
|
25
|
+
source,
|
|
26
|
+
...toFieldOptions()
|
|
27
|
+
});
|
|
28
|
+
const currentArray = () => {
|
|
29
|
+
const value = form.getFieldValue(name);
|
|
30
|
+
return Array.isArray(value) ? value : EMPTY;
|
|
31
|
+
};
|
|
32
|
+
/** Materializes the guarded default so positional ops act on a real array. */
|
|
33
|
+
const ensureSeeded = () => {
|
|
34
|
+
if (form.getFieldValue(name) !== void 0) return;
|
|
35
|
+
const seed = seedRows();
|
|
36
|
+
form.setFieldValue(name, seed, HYDRATION_WRITE);
|
|
37
|
+
};
|
|
38
|
+
let nextRowId = 0;
|
|
39
|
+
const ids = [];
|
|
40
|
+
const allocId = () => `row-${nextRowId++}`;
|
|
41
|
+
/**
|
|
42
|
+
* Reconciles ids with the actual row count before every op/read — external
|
|
43
|
+
* writes (reset, hydration, direct setFieldValue) reconcile positionally:
|
|
44
|
+
* extra rows get fresh ids appended, missing rows truncate from the end.
|
|
45
|
+
*/
|
|
46
|
+
const syncIds = (length) => {
|
|
47
|
+
while (ids.length < length) ids.push(allocId());
|
|
48
|
+
if (ids.length > length) ids.length = length;
|
|
49
|
+
};
|
|
50
|
+
/** Changes exactly when the row STRUCTURE changes (ground truth #6). */
|
|
51
|
+
const structuralStamp = () => `${form.getFieldMeta(name)?._arrayVersion ?? 0}:${currentArray().length}`;
|
|
52
|
+
/** The row shape probe: first current row, else first default row. */
|
|
53
|
+
const firstRowProbe = () => {
|
|
54
|
+
const rows = currentArray();
|
|
55
|
+
return rows.length > 0 ? rows[0] : current.defaultValue?.[0];
|
|
56
|
+
};
|
|
57
|
+
const isScalarMode = () => {
|
|
58
|
+
if (current.scalar !== void 0) return current.scalar;
|
|
59
|
+
if (current.itemDefaults !== void 0) return false;
|
|
60
|
+
const probe = firstRowProbe();
|
|
61
|
+
return probe === void 0 ? true : !isRecord(probe);
|
|
62
|
+
};
|
|
63
|
+
/** Object mode: the first row's keys, every one nulled. */
|
|
64
|
+
const emptyRowTemplate = () => {
|
|
65
|
+
const template = {};
|
|
66
|
+
const probe = firstRowProbe();
|
|
67
|
+
if (isRecord(probe)) for (const key of Object.keys(probe)) template[key] = null;
|
|
68
|
+
return template;
|
|
69
|
+
};
|
|
70
|
+
const newItem = () => {
|
|
71
|
+
return isScalarMode() ? "" : {
|
|
72
|
+
...emptyRowTemplate(),
|
|
73
|
+
...current.itemDefaults
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Hands a row to form-core, whose array ops are typed by the union of every
|
|
78
|
+
* array path in the record. {@link ArrayItemOf} is the row type of THIS
|
|
79
|
+
* binding's path, and `unknown` for a composed `string` source, where
|
|
80
|
+
* form-core types the item as `never`.
|
|
81
|
+
*/
|
|
82
|
+
const formItem = (value) => value;
|
|
83
|
+
const add = (item) => {
|
|
84
|
+
ensureSeeded();
|
|
85
|
+
syncIds(currentArray().length);
|
|
86
|
+
ids.push(allocId());
|
|
87
|
+
form.pushFieldValue(name, formItem(item !== void 0 ? item : newItem()));
|
|
88
|
+
};
|
|
89
|
+
const insert = async (index, item) => {
|
|
90
|
+
ensureSeeded();
|
|
91
|
+
const length = currentArray().length;
|
|
92
|
+
syncIds(length);
|
|
93
|
+
const at = Math.max(0, Math.min(index, length));
|
|
94
|
+
ids.splice(at, 0, allocId());
|
|
95
|
+
await form.insertFieldValue(name, at, formItem(item !== void 0 ? item : newItem()));
|
|
96
|
+
};
|
|
97
|
+
const replace = async (index, item) => {
|
|
98
|
+
ensureSeeded();
|
|
99
|
+
const length = currentArray().length;
|
|
100
|
+
syncIds(length);
|
|
101
|
+
if (index < 0 || index >= length) return;
|
|
102
|
+
ids[index] = allocId();
|
|
103
|
+
await form.replaceFieldValue(name, index, formItem(item));
|
|
104
|
+
};
|
|
105
|
+
const remove = async (index) => {
|
|
106
|
+
ensureSeeded();
|
|
107
|
+
const length = currentArray().length;
|
|
108
|
+
syncIds(length);
|
|
109
|
+
if (index < 0 || index >= length) return;
|
|
110
|
+
ids.splice(index, 1);
|
|
111
|
+
await form.removeFieldValue(name, index);
|
|
112
|
+
};
|
|
113
|
+
const swap = (indexA, indexB) => {
|
|
114
|
+
ensureSeeded();
|
|
115
|
+
const length = currentArray().length;
|
|
116
|
+
syncIds(length);
|
|
117
|
+
if (indexA < 0 || indexA >= length || indexB < 0 || indexB >= length) return;
|
|
118
|
+
const idA = ids[indexA];
|
|
119
|
+
const idB = ids[indexB];
|
|
120
|
+
if (idA === void 0 || idB === void 0) return;
|
|
121
|
+
ids[indexA] = idB;
|
|
122
|
+
ids[indexB] = idA;
|
|
123
|
+
form.swapFieldValues(name, indexA, indexB);
|
|
124
|
+
};
|
|
125
|
+
const reOrder = (from, to) => {
|
|
126
|
+
ensureSeeded();
|
|
127
|
+
const length = currentArray().length;
|
|
128
|
+
syncIds(length);
|
|
129
|
+
if (from < 0 || from >= length) return;
|
|
130
|
+
const [moved] = ids.splice(from, 1);
|
|
131
|
+
if (moved === void 0) return;
|
|
132
|
+
ids.splice(to, 0, moved);
|
|
133
|
+
form.moveFieldValues(name, from, to);
|
|
134
|
+
};
|
|
135
|
+
const clear = () => {
|
|
136
|
+
ensureSeeded();
|
|
137
|
+
ids.length = 0;
|
|
138
|
+
form.clearFieldValues(name);
|
|
139
|
+
};
|
|
140
|
+
const makeRow = (id, index) => ({
|
|
141
|
+
id,
|
|
142
|
+
index,
|
|
143
|
+
name: `${source}[${index}]`,
|
|
144
|
+
remove: () => remove(index),
|
|
145
|
+
reOrder: (to) => reOrder(index, to)
|
|
146
|
+
});
|
|
147
|
+
let cachedRows;
|
|
148
|
+
let cachedStamp;
|
|
149
|
+
const getRows = () => {
|
|
150
|
+
const stamp = structuralStamp();
|
|
151
|
+
if (cachedRows !== void 0 && cachedStamp === stamp) return cachedRows;
|
|
152
|
+
syncIds(currentArray().length);
|
|
153
|
+
cachedRows = ids.map((id, index) => makeRow(id, index));
|
|
154
|
+
cachedStamp = stamp;
|
|
155
|
+
return cachedRows;
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
field: fieldBinding,
|
|
159
|
+
source,
|
|
160
|
+
subscribe(listener) {
|
|
161
|
+
let previous = structuralStamp();
|
|
162
|
+
return fieldBinding.subscribe(() => {
|
|
163
|
+
const next = structuralStamp();
|
|
164
|
+
if (next === previous) return;
|
|
165
|
+
previous = next;
|
|
166
|
+
listener();
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
getRows,
|
|
170
|
+
getTotal: () => currentArray().length,
|
|
171
|
+
add,
|
|
172
|
+
insert,
|
|
173
|
+
replace,
|
|
174
|
+
remove,
|
|
175
|
+
swap,
|
|
176
|
+
reOrder,
|
|
177
|
+
clear,
|
|
178
|
+
update(next) {
|
|
179
|
+
current = {
|
|
180
|
+
source,
|
|
181
|
+
...next
|
|
182
|
+
};
|
|
183
|
+
fieldBinding.update(toFieldOptions());
|
|
184
|
+
},
|
|
185
|
+
destroy: () => fieldBinding.destroy()
|
|
186
|
+
};
|
|
167
187
|
}
|
|
168
|
-
export {
|
|
169
|
-
createArrayBinding
|
|
170
|
-
};
|
|
188
|
+
export { createArrayBinding };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Identifier } from "../resource/identifier.js";
|
|
2
|
+
/**
|
|
3
|
+
* The ids a choice input holds: the field value, or the values of its choices.
|
|
4
|
+
* A member that is not an {@link Identifier} is skipped, and anything that is
|
|
5
|
+
* not an array is no ids.
|
|
6
|
+
*/
|
|
7
|
+
export declare function toIdentifierList(value: unknown): Identifier[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { identifiersOf } from "../resource/identifier.js";
|
|
2
|
+
// src/form/choice-ids.ts
|
|
3
|
+
/**
|
|
4
|
+
* The ids a choice input holds: the field value, or the values of its choices.
|
|
5
|
+
* A member that is not an {@link Identifier} is skipped, and anything that is
|
|
6
|
+
* not an array is no ids.
|
|
7
|
+
*/
|
|
8
|
+
function toIdentifierList(value) {
|
|
9
|
+
return identifiersOf(value);
|
|
10
|
+
}
|
|
11
|
+
export { toIdentifierList };
|