@chelone/core 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/assistant-session.d.ts +28 -0
- package/dist/assistant/assistant-session.js +208 -0
- package/dist/assistant/echo-transport.d.ts +7 -0
- package/dist/assistant/echo-transport.js +28 -0
- package/dist/assistant/http-transport.d.ts +10 -0
- package/dist/assistant/http-transport.js +98 -0
- package/dist/assistant/types.d.ts +54 -0
- package/dist/auth/access-control.d.ts +13 -5
- package/dist/auth/access-control.js +25 -21
- package/dist/auth/auth-options.d.ts +21 -17
- package/dist/auth/auth-options.js +80 -48
- package/dist/auth/permission-matrix.d.ts +12 -0
- package/dist/auth/permission-matrix.js +67 -0
- package/dist/auth/types.d.ts +14 -4
- package/dist/block-editor/block-document.d.ts +60 -0
- package/dist/block-editor/block-document.js +143 -0
- package/dist/block-editor/data-blocks.d.ts +63 -0
- package/dist/block-editor/data-blocks.js +117 -0
- package/dist/chart/chart-data.d.ts +25 -0
- package/dist/chart/chart-data.js +61 -0
- package/dist/command-menu/hotkey.js +58 -47
- package/dist/command-menu/recents.js +8 -11
- package/dist/command-menu/registry.d.ts +9 -5
- package/dist/command-menu/registry.js +36 -46
- package/dist/controller/calendar-dates.js +82 -113
- package/dist/controller/calendar-events.js +93 -88
- package/dist/controller/client-list-controller.d.ts +2 -2
- package/dist/controller/client-list-controller.js +251 -254
- package/dist/controller/data-table-columns.d.ts +2 -0
- package/dist/controller/data-table-columns.js +50 -56
- package/dist/controller/deep-equal.d.ts +1 -0
- package/dist/controller/deep-equal.js +14 -0
- package/dist/controller/filter-toggle.js +36 -44
- package/dist/controller/in-place-editor.d.ts +42 -0
- package/dist/controller/in-place-editor.js +42 -0
- package/dist/controller/infinite-list-controller.js +220 -190
- package/dist/controller/inspector.d.ts +49 -0
- package/dist/controller/inspector.js +64 -0
- package/dist/controller/job-monitor.d.ts +41 -0
- package/dist/controller/job-monitor.js +49 -0
- package/dist/controller/kanban-board.d.ts +2 -3
- package/dist/controller/kanban-board.js +43 -24
- package/dist/controller/list-controller.d.ts +5 -5
- package/dist/controller/list-controller.js +444 -400
- package/dist/controller/list-params.js +96 -87
- package/dist/controller/pivot-grid.d.ts +36 -0
- package/dist/controller/pivot-grid.js +99 -0
- package/dist/controller/prev-next.d.ts +2 -2
- package/dist/controller/prev-next.js +74 -54
- package/dist/controller/record-controllers.d.ts +14 -6
- package/dist/controller/record-controllers.js +288 -257
- package/dist/controller/record-diff.d.ts +21 -0
- package/dist/controller/record-diff.js +50 -0
- package/dist/controller/record-timeline.d.ts +35 -0
- package/dist/controller/record-timeline.js +48 -0
- package/dist/controller/record-tree.js +57 -47
- package/dist/controller/record-workflow.d.ts +38 -16
- package/dist/controller/record-workflow.js +85 -64
- package/dist/controller/reference-many-controller.js +198 -192
- package/dist/controller/reference-one-controller.d.ts +1 -1
- package/dist/controller/reference-one-controller.js +63 -56
- package/dist/controller/saved-queries.js +104 -91
- package/dist/controller/translate-fallback.js +10 -6
- package/dist/csv/export.d.ts +2 -1
- package/dist/csv/export.js +43 -40
- package/dist/csv/import-controller.js +185 -169
- package/dist/csv/match-headers.js +29 -22
- package/dist/csv/parse-with.js +60 -54
- package/dist/csv/run-import.js +69 -72
- package/dist/csv/validate-rows.d.ts +1 -1
- package/dist/csv/validate-rows.js +65 -52
- package/dist/data/cache-ops.d.ts +5 -4
- package/dist/data/cache-ops.js +208 -190
- package/dist/data/combine-data-providers.js +25 -23
- package/dist/data/data-provider-log.d.ts +45 -0
- package/dist/data/data-provider-log.js +93 -0
- package/dist/data/errors.js +14 -10
- package/dist/data/fake-provider.d.ts +6 -3
- package/dist/data/fake-provider.js +129 -114
- package/dist/data/fetch-utils.js +61 -44
- package/dist/data/filter-branch.d.ts +5 -0
- package/dist/data/filter-branch.js +10 -0
- package/dist/data/get-many-aggregate.js +84 -76
- package/dist/data/http-error.js +17 -12
- package/dist/data/keys.js +50 -36
- package/dist/data/mutation-options.d.ts +14 -9
- package/dist/data/mutation-options.js +93 -109
- package/dist/data/query-options.js +73 -68
- package/dist/data/remove-empty.d.ts +3 -0
- package/dist/data/remove-empty.js +11 -0
- package/dist/data/undoable.d.ts +2 -2
- package/dist/data/undoable.js +190 -174
- package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
- package/dist/data/with-lifecycle-callbacks.js +66 -52
- package/dist/environment/application-update-store.js +58 -55
- package/dist/environment/offline-store.js +21 -21
- package/dist/form/array-binding.d.ts +30 -11
- package/dist/form/array-binding.js +184 -166
- package/dist/form/choice-ids.d.ts +7 -0
- package/dist/form/choice-ids.js +11 -0
- package/dist/form/choices-controller.d.ts +3 -2
- package/dist/form/choices-controller.js +302 -275
- package/dist/form/date-format.d.ts +38 -0
- package/dist/form/date-format.js +89 -0
- package/dist/form/dirty-values.d.ts +2 -2
- package/dist/form/dirty-values.js +22 -11
- package/dist/form/duration.d.ts +22 -0
- package/dist/form/duration.js +49 -0
- package/dist/form/edit-create-form.d.ts +12 -5
- package/dist/form/edit-create-form.js +243 -188
- package/dist/form/event-value.d.ts +6 -0
- package/dist/form/event-value.js +17 -0
- package/dist/form/field-error.d.ts +14 -0
- package/dist/form/field-error.js +28 -0
- package/dist/form/field-name.d.ts +20 -0
- package/dist/form/field-name.js +6 -0
- package/dist/form/filter-form-values.js +63 -49
- package/dist/form/form-controller.d.ts +9 -1
- package/dist/form/form-controller.js +165 -160
- package/dist/form/form-groups.d.ts +4 -3
- package/dist/form/form-groups.js +79 -79
- package/dist/form/form-handle.d.ts +175 -0
- package/dist/form/form-handle.js +68 -0
- package/dist/form/input-binding.d.ts +56 -31
- package/dist/form/input-binding.js +169 -118
- package/dist/form/input-values.d.ts +130 -0
- package/dist/form/input-values.js +177 -0
- package/dist/form/json-text.js +51 -37
- package/dist/form/save-pipeline.d.ts +6 -8
- package/dist/form/save-pipeline.js +91 -82
- package/dist/form/set-form-values.d.ts +6 -0
- package/dist/form/set-form-values.js +10 -0
- package/dist/form/source.js +97 -65
- package/dist/form/text-of.d.ts +2 -0
- package/dist/form/text-of.js +7 -0
- package/dist/form/validators.d.ts +58 -19
- package/dist/form/validators.js +97 -71
- package/dist/geo/bbox.d.ts +5 -4
- package/dist/geo/bbox.js +69 -48
- package/dist/geo/bounds.d.ts +14 -3
- package/dist/geo/bounds.js +98 -68
- package/dist/geo/constraints.js +88 -93
- package/dist/geo/coordinates.js +31 -23
- package/dist/geo/editor.d.ts +13 -13
- package/dist/geo/editor.js +176 -153
- package/dist/geo/geocoding.d.ts +4 -4
- package/dist/geo/geocoding.js +38 -41
- package/dist/geo/geojson.d.ts +12 -74
- package/dist/geo/geojson.js +52 -40
- package/dist/geo/geometry-math.d.ts +4 -4
- package/dist/geo/geometry-math.js +100 -93
- package/dist/geo/index.d.ts +7 -6
- package/dist/geo/map-layers.d.ts +42 -7
- package/dist/geo/map-layers.js +88 -62
- package/dist/geo/map-style.js +31 -32
- package/dist/geo/messages.js +48 -45
- package/dist/geo/nominatim.js +88 -80
- package/dist/geo/osm-presets.d.ts +4 -3
- package/dist/geo/osm-presets.js +108 -86
- package/dist/geo/osm-tags.js +14 -12
- package/dist/geo/overpass.d.ts +14 -2
- package/dist/geo/overpass.js +83 -90
- package/dist/geo/shape-value.d.ts +9 -5
- package/dist/geo/shape-value.js +169 -146
- package/dist/geo/snap-to-roads.d.ts +3 -3
- package/dist/geo/snap-to-roads.js +55 -50
- package/dist/geo/throttle.js +36 -39
- package/dist/guards.d.ts +37 -0
- package/dist/guards.js +62 -0
- package/dist/i18n/default-messages.js +34 -29
- package/dist/i18n/english-messages.js +187 -189
- package/dist/i18n/i18n.d.ts +8 -2
- package/dist/i18n/i18n.js +47 -48
- package/dist/i18n/inflection.d.ts +34 -0
- package/dist/i18n/inflection.js +82 -0
- package/dist/i18n/messages.d.ts +10 -2
- package/dist/i18n/messages.js +17 -4
- package/dist/i18n/missing-keys.d.ts +19 -0
- package/dist/i18n/missing-keys.js +68 -0
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +96 -22
- package/dist/index.js +52 -257
- package/dist/json/deep-equal.d.ts +8 -0
- package/dist/json/deep-equal.js +24 -0
- package/dist/json/strip-json-comments.js +38 -42
- package/dist/noop.d.ts +2 -0
- package/dist/noop.js +4 -0
- package/dist/notification/error-message.d.ts +2 -0
- package/dist/notification/error-message.js +10 -0
- package/dist/notification/notification-store.d.ts +3 -2
- package/dist/notification/notification-store.js +41 -45
- package/dist/onboarding-tour/placement.d.ts +34 -0
- package/dist/onboarding-tour/placement.js +67 -0
- package/dist/onboarding-tour/tour-controller.d.ts +25 -0
- package/dist/onboarding-tour/tour-controller.js +50 -0
- package/dist/realtime/add-events-for-mutations.js +75 -81
- package/dist/realtime/announce-lock.d.ts +7 -0
- package/dist/realtime/announce-lock.js +18 -0
- package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
- package/dist/realtime/broadcast-channel-transport.js +69 -0
- package/dist/realtime/copy-members.js +22 -14
- package/dist/realtime/in-memory-lock-provider.js +41 -60
- package/dist/realtime/live.js +95 -101
- package/dist/realtime/lock-options.js +16 -16
- package/dist/realtime/lock-session.js +91 -78
- package/dist/realtime/memory-transport.js +26 -67
- package/dist/realtime/presence.js +120 -122
- package/dist/realtime/realtime-data-provider.d.ts +10 -0
- package/dist/realtime/realtime-data-provider.js +45 -42
- package/dist/realtime/reconnect-backoff.d.ts +15 -0
- package/dist/realtime/reconnect-backoff.js +17 -0
- package/dist/realtime/sse-transport.d.ts +31 -0
- package/dist/realtime/sse-transport.js +235 -0
- package/dist/realtime/status-store.js +23 -28
- package/dist/realtime/topic-hub.d.ts +24 -0
- package/dist/realtime/topic-hub.js +66 -0
- package/dist/realtime/topics.js +10 -12
- package/dist/realtime/types.js +10 -12
- package/dist/realtime/unread-count.js +21 -21
- package/dist/realtime/websocket-transport.d.ts +32 -0
- package/dist/realtime/websocket-transport.js +306 -0
- package/dist/resource/describe-resource.d.ts +2 -0
- package/dist/resource/describe-resource.js +232 -219
- package/dist/resource/get-at-path.js +29 -14
- package/dist/resource/identifier.d.ts +18 -0
- package/dist/resource/identifier.js +27 -0
- package/dist/resource/record.d.ts +4 -0
- package/dist/resource/record.js +10 -0
- package/dist/resource/register.d.ts +7 -0
- package/dist/resource/registry-store.d.ts +45 -15
- package/dist/resource/registry-store.js +67 -60
- package/dist/resource/registry.d.ts +10 -0
- package/dist/resource/registry.js +8 -4
- package/dist/resource/source-path.d.ts +16 -1
- package/dist/rich-text/rich-text-extras.d.ts +132 -0
- package/dist/rich-text/rich-text-extras.js +179 -0
- package/dist/rich-text/rich-text-image.d.ts +124 -0
- package/dist/rich-text/rich-text-image.js +163 -0
- package/dist/rich-text/rich-text.d.ts +62 -0
- package/dist/rich-text/rich-text.js +142 -0
- package/dist/router/location.d.ts +22 -4
- package/dist/router/location.js +41 -18
- package/dist/router/match.js +47 -39
- package/dist/router/paths.js +40 -34
- package/dist/router/record-from-location.d.ts +1 -1
- package/dist/router/record-from-location.js +25 -17
- package/dist/store/adapters.js +51 -55
- package/dist/store/micro-store.js +57 -59
- package/dist/store/shallow-equal.d.ts +12 -0
- package/dist/store/shallow-equal.js +20 -0
- package/dist/store/store.js +54 -57
- package/package.json +13 -11
|
@@ -2,6 +2,7 @@ import { type DataContext } from "../data/query-options.js";
|
|
|
2
2
|
import type { FilterPayload, PageInfo, SortPayload } from "../data/types.js";
|
|
3
3
|
import type { NotificationStore } from "../notification/notification-store.js";
|
|
4
4
|
import type { RecordOf, ResourceMap } from "../resource/registry.js";
|
|
5
|
+
import type { SourceKey } from "../resource/source-path.js";
|
|
5
6
|
import { type MicroStore } from "../store/micro-store.js";
|
|
6
7
|
/**
|
|
7
8
|
* The choices-context state (§0b parity). A list-like base (page/perPage/sort/
|
|
@@ -31,7 +32,7 @@ export interface ChoicesControllerState<TRecord> {
|
|
|
31
32
|
hasPreviousPage: boolean | undefined;
|
|
32
33
|
isPending: boolean;
|
|
33
34
|
isFetching: boolean;
|
|
34
|
-
error:
|
|
35
|
+
error: Error | null;
|
|
35
36
|
/** The referenced resource name. */
|
|
36
37
|
resource: string;
|
|
37
38
|
/** The FK field on the referencing side (metadata for consumers). */
|
|
@@ -62,7 +63,7 @@ export interface ChoicesControllerOptions<TResources extends ResourceMap = Resou
|
|
|
62
63
|
* `'id'` (default) resolves selected records via `getMany`; any other key
|
|
63
64
|
* resolves via `getList({ filter: { [lookupKey]: value } })` (#16).
|
|
64
65
|
*/
|
|
65
|
-
lookupKey?:
|
|
66
|
+
lookupKey?: SourceKey<RecordOf<TResources, TName>>;
|
|
66
67
|
/** Permanent filter — merged OVER the dynamic filter for every choices fetch. */
|
|
67
68
|
filter?: FilterPayload;
|
|
68
69
|
page?: number;
|
|
@@ -1,281 +1,308 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { notifyFetchError } from "../controller/list-controller.js";
|
|
2
2
|
import { getListOptions, getManyOptions } from "../data/query-options.js";
|
|
3
|
+
import { removeEmpty } from "../data/remove-empty.js";
|
|
3
4
|
import { createMicroStore } from "../store/micro-store.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
function errorMessageKey(error) {
|
|
18
|
-
const message = error instanceof Error ? error.message : "";
|
|
19
|
-
return message || "chelone.notification.http_error";
|
|
20
|
-
}
|
|
5
|
+
import { QueryObserver, keepPreviousData } from "@tanstack/query-core";
|
|
6
|
+
// src/form/choices-controller.ts
|
|
7
|
+
var DEFAULT_PAGE = 1;
|
|
8
|
+
var DEFAULT_PER_PAGE = 25;
|
|
9
|
+
var DEFAULT_DEBOUNCE_MS = 500;
|
|
10
|
+
var DEFAULT_SORT = {
|
|
11
|
+
field: "id",
|
|
12
|
+
order: "DESC"
|
|
13
|
+
};
|
|
14
|
+
/** perPage for the non-id selected getList — generous, real values sets are small. */
|
|
15
|
+
var SELECTED_LIST_PER_PAGE = 1e3;
|
|
16
|
+
/** Normalizes a value into a non-empty identifier list, or `[]` when there is nothing to resolve. */
|
|
21
17
|
function toValueList(value) {
|
|
22
|
-
|
|
23
|
-
return raw.filter(
|
|
24
|
-
(v) => v != null && v !== "" && (typeof v === "string" || typeof v === "number")
|
|
25
|
-
);
|
|
18
|
+
return (Array.isArray(value) ? value : [value]).filter((v) => v != null && v !== "" && (typeof v === "string" || typeof v === "number"));
|
|
26
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Vanilla choices controller: TWO QueryObservers (choices page + selected
|
|
22
|
+
* resolution) mapped into one micro-store, closing charter §11 #16 — selected
|
|
23
|
+
* records resolve by an arbitrary `lookupKey`, not just `id`.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle (P4 4R): construction is INERT — pure state + closures, zero
|
|
26
|
+
* subscriptions, zero fetching. Acquisition (both observers + KV nothing) runs
|
|
27
|
+
* on the micro-store's FIRST subscriber; the LAST unsubscribe releases both
|
|
28
|
+
* observers and clears the debounce timer. Attach/detach cycles are
|
|
29
|
+
* re-entrant; re-attach rebuilds both observers from CURRENT state.
|
|
30
|
+
* `destroy()` remains the hard imperative teardown for non-React consumers.
|
|
31
|
+
*
|
|
32
|
+
* Loop-prevention: `observer.setOptions` is called ONLY from param-mutating
|
|
33
|
+
* actions (`setPage`/`setSort`/`setFilters`/`setValue`), never from the
|
|
34
|
+
* observer result subscription — the subscription only maps results into state.
|
|
35
|
+
*/
|
|
27
36
|
function createChoicesController(ctx, options) {
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
37
|
+
const reference = options.reference;
|
|
38
|
+
const lookupKey = options.lookupKey ?? "id";
|
|
39
|
+
const isIdLookup = lookupKey === "id";
|
|
40
|
+
const permanentFilter = options.filter ?? {};
|
|
41
|
+
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
42
|
+
const store = createMicroStore({
|
|
43
|
+
page: options.page ?? DEFAULT_PAGE,
|
|
44
|
+
perPage: options.perPage ?? DEFAULT_PER_PAGE,
|
|
45
|
+
sort: { ...options.sort ?? DEFAULT_SORT },
|
|
46
|
+
filter: {},
|
|
47
|
+
allChoices: [],
|
|
48
|
+
availableChoices: [],
|
|
49
|
+
selectedChoices: [],
|
|
50
|
+
total: void 0,
|
|
51
|
+
pageInfo: void 0,
|
|
52
|
+
hasNextPage: void 0,
|
|
53
|
+
hasPreviousPage: void 0,
|
|
54
|
+
isPending: true,
|
|
55
|
+
isFetching: false,
|
|
56
|
+
error: null,
|
|
57
|
+
resource: reference,
|
|
58
|
+
source: options.source,
|
|
59
|
+
isFromReference: true
|
|
60
|
+
}, { onSubscribed: acquire });
|
|
61
|
+
let destroyed = false;
|
|
62
|
+
store.onDestroy(() => {
|
|
63
|
+
destroyed = true;
|
|
64
|
+
});
|
|
65
|
+
/** Non-null exactly while attached. */
|
|
66
|
+
let choicesObserver = null;
|
|
67
|
+
/** Selected observer: getMany (id) OR getList (non-id). Rebuilt on setValue. */
|
|
68
|
+
let selectedObserver = null;
|
|
69
|
+
/** The value(s) currently driving selected resolution. */
|
|
70
|
+
let selectedValues = [];
|
|
71
|
+
/** Latest raw fetch results, cached so `recompute()` can re-merge on any change. */
|
|
72
|
+
let pageData;
|
|
73
|
+
let pageTotal;
|
|
74
|
+
let pageInfo;
|
|
75
|
+
let selectedData = [];
|
|
76
|
+
const isGetChoicesEnabled = () => options.enableGetChoices ? options.enableGetChoices(store.getSnapshot().filter) : true;
|
|
77
|
+
const buildChoicesOptions = () => {
|
|
78
|
+
const s = store.getSnapshot();
|
|
79
|
+
return {
|
|
80
|
+
...getListOptions(ctx, reference, {
|
|
81
|
+
pagination: {
|
|
82
|
+
page: s.page,
|
|
83
|
+
perPage: s.perPage
|
|
84
|
+
},
|
|
85
|
+
sort: s.sort,
|
|
86
|
+
filter: {
|
|
87
|
+
...s.filter,
|
|
88
|
+
...permanentFilter
|
|
89
|
+
},
|
|
90
|
+
meta: options.meta
|
|
91
|
+
}),
|
|
92
|
+
placeholderData: keepPreviousData,
|
|
93
|
+
retry: false,
|
|
94
|
+
enabled: isGetChoicesEnabled()
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Options for the selected observer. `id` lookup batches through getMany;
|
|
99
|
+
* any other key resolves through a getList filter (single OR array value).
|
|
100
|
+
* Disabled when there is nothing to resolve so it never pins `isPending`.
|
|
101
|
+
*/
|
|
102
|
+
const buildSelectedOptions = () => {
|
|
103
|
+
const enabled = selectedValues.length > 0;
|
|
104
|
+
if (isIdLookup) return {
|
|
105
|
+
...getManyOptions(ctx, reference, {
|
|
106
|
+
ids: selectedValues,
|
|
107
|
+
meta: options.meta
|
|
108
|
+
}),
|
|
109
|
+
retry: false,
|
|
110
|
+
enabled
|
|
111
|
+
};
|
|
112
|
+
const filterValue = selectedValues.length === 1 ? selectedValues[0] : selectedValues;
|
|
113
|
+
return {
|
|
114
|
+
...getListOptions(ctx, reference, {
|
|
115
|
+
pagination: {
|
|
116
|
+
page: 1,
|
|
117
|
+
perPage: SELECTED_LIST_PER_PAGE
|
|
118
|
+
},
|
|
119
|
+
filter: { [lookupKey]: filterValue },
|
|
120
|
+
meta: options.meta
|
|
121
|
+
}),
|
|
122
|
+
retry: false,
|
|
123
|
+
enabled
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
/** Records from the selected observer result, whichever shape it is. */
|
|
127
|
+
const readSelectedData = (result) => {
|
|
128
|
+
const data = result.data;
|
|
129
|
+
if (data == null) return [];
|
|
130
|
+
return Array.isArray(data) ? data : data.data;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Merge the page + selected records into the three views (§0b parity), then
|
|
134
|
+
* write the derived state. Called from BOTH result subscriptions.
|
|
135
|
+
*/
|
|
136
|
+
const recompute = (patch) => {
|
|
137
|
+
const s = store.getSnapshot();
|
|
138
|
+
const page = pageData ?? [];
|
|
139
|
+
const selected = selectedData;
|
|
140
|
+
const keyOf = (record) => record[lookupKey];
|
|
141
|
+
const pageKeys = new Set(page.map(keyOf));
|
|
142
|
+
const missing = selected.filter((record) => !pageKeys.has(keyOf(record)));
|
|
143
|
+
const allChoices = missing.length > 0 ? [...missing, ...page] : page;
|
|
144
|
+
let total = pageTotal;
|
|
145
|
+
if (total !== void 0 && missing.length === 1 && selected.length === 1) total = total + 1;
|
|
146
|
+
let hasNextPage;
|
|
147
|
+
let hasPreviousPage;
|
|
148
|
+
if (pageInfo) {
|
|
149
|
+
hasNextPage = pageInfo.hasNextPage;
|
|
150
|
+
hasPreviousPage = pageInfo.hasPreviousPage;
|
|
151
|
+
} else if (pageTotal !== void 0) {
|
|
152
|
+
hasNextPage = s.page * s.perPage < pageTotal;
|
|
153
|
+
hasPreviousPage = s.page > 1;
|
|
154
|
+
}
|
|
155
|
+
store.setState({
|
|
156
|
+
allChoices,
|
|
157
|
+
availableChoices: page,
|
|
158
|
+
selectedChoices: selected,
|
|
159
|
+
total,
|
|
160
|
+
pageInfo,
|
|
161
|
+
hasNextPage,
|
|
162
|
+
hasPreviousPage,
|
|
163
|
+
isPending: patch.isPending,
|
|
164
|
+
isFetching: patch.isFetching,
|
|
165
|
+
error: patch.error
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
let lastNotifiedError = null;
|
|
169
|
+
const notifyError = (error) => {
|
|
170
|
+
if (error === lastNotifiedError) return;
|
|
171
|
+
lastNotifiedError = error;
|
|
172
|
+
if (error === null) return;
|
|
173
|
+
notifyFetchError(ctx.notifications, error);
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* The combined status union. `isPending` reflects the choices query and the
|
|
177
|
+
* selected query ONLY when a value is being resolved — a valueless selected
|
|
178
|
+
* query is disabled and must never pin `isPending` (§0b).
|
|
179
|
+
*/
|
|
180
|
+
const onChoicesResult = (result) => {
|
|
181
|
+
const list = result.data;
|
|
182
|
+
pageData = list?.data;
|
|
183
|
+
pageTotal = list?.total;
|
|
184
|
+
pageInfo = list?.pageInfo;
|
|
185
|
+
const selectedPending = selectedValues.length > 0 && selectedObserver ? selectedObserver.getCurrentResult().isPending : false;
|
|
186
|
+
const selectedFetching = selectedValues.length > 0 && selectedObserver ? selectedObserver.getCurrentResult().isFetching : false;
|
|
187
|
+
const error = result.error ?? selectedObserver?.getCurrentResult().error ?? null;
|
|
188
|
+
recompute({
|
|
189
|
+
isPending: result.isPending || selectedPending,
|
|
190
|
+
isFetching: result.isFetching || selectedFetching,
|
|
191
|
+
error
|
|
192
|
+
});
|
|
193
|
+
notifyError(result.error ?? null);
|
|
194
|
+
};
|
|
195
|
+
const onSelectedResult = (result) => {
|
|
196
|
+
selectedData = readSelectedData(result);
|
|
197
|
+
const choicesResult = choicesObserver?.getCurrentResult();
|
|
198
|
+
const choicesPending = choicesResult?.isPending ?? false;
|
|
199
|
+
const choicesFetching = choicesResult?.isFetching ?? false;
|
|
200
|
+
const selectedPending = selectedValues.length > 0 && result.isPending;
|
|
201
|
+
const selectedFetching = selectedValues.length > 0 && result.isFetching;
|
|
202
|
+
const error = choicesResult?.error ?? result.error ?? null;
|
|
203
|
+
recompute({
|
|
204
|
+
isPending: choicesPending || selectedPending,
|
|
205
|
+
isFetching: choicesFetching || selectedFetching,
|
|
206
|
+
error
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
const setPage = (page) => {
|
|
210
|
+
if (destroyed) return;
|
|
211
|
+
store.setState({ page });
|
|
212
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
213
|
+
};
|
|
214
|
+
const setPerPage = (perPage) => {
|
|
215
|
+
if (destroyed) return;
|
|
216
|
+
store.setState({
|
|
217
|
+
perPage,
|
|
218
|
+
page: 1
|
|
219
|
+
});
|
|
220
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
221
|
+
};
|
|
222
|
+
const setSort = (sort) => {
|
|
223
|
+
if (destroyed) return;
|
|
224
|
+
store.setState({
|
|
225
|
+
sort: { ...sort },
|
|
226
|
+
page: 1
|
|
227
|
+
});
|
|
228
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
229
|
+
};
|
|
230
|
+
const applyFilters = (filter) => {
|
|
231
|
+
store.setState({
|
|
232
|
+
filter: removeEmpty(filter),
|
|
233
|
+
page: 1
|
|
234
|
+
});
|
|
235
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
236
|
+
};
|
|
237
|
+
let debounceTimer;
|
|
238
|
+
const clearDebounce = () => {
|
|
239
|
+
if (debounceTimer !== void 0) {
|
|
240
|
+
clearTimeout(debounceTimer);
|
|
241
|
+
debounceTimer = void 0;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
store.onDestroy(clearDebounce);
|
|
245
|
+
const setFilters = (filter, debounce = false) => {
|
|
246
|
+
if (destroyed) return;
|
|
247
|
+
clearDebounce();
|
|
248
|
+
if (!debounce) {
|
|
249
|
+
applyFilters(filter);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
debounceTimer = setTimeout(() => {
|
|
253
|
+
debounceTimer = void 0;
|
|
254
|
+
if (destroyed) return;
|
|
255
|
+
applyFilters(filter);
|
|
256
|
+
}, debounceMs);
|
|
257
|
+
};
|
|
258
|
+
const setValue = (value) => {
|
|
259
|
+
if (destroyed) return;
|
|
260
|
+
const next = toValueList(value);
|
|
261
|
+
if (next.length === selectedValues.length && next.every((v, i) => v === selectedValues[i])) return;
|
|
262
|
+
selectedValues = next;
|
|
263
|
+
if (next.length === 0) selectedData = [];
|
|
264
|
+
selectedObserver?.setOptions(buildSelectedOptions());
|
|
265
|
+
if (!selectedObserver && next.length === 0) recompute({
|
|
266
|
+
isPending: store.getSnapshot().isPending,
|
|
267
|
+
isFetching: store.getSnapshot().isFetching,
|
|
268
|
+
error: store.getSnapshot().error
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
const refetch = () => {
|
|
272
|
+
if (destroyed) return;
|
|
273
|
+
choicesObserver?.refetch();
|
|
274
|
+
if (selectedValues.length > 0) selectedObserver?.refetch();
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Attach: runs on the micro-store's FIRST subscriber. Creates and subscribes
|
|
278
|
+
* BOTH observers with options built from CURRENT state, and returns the
|
|
279
|
+
* teardown the micro-store runs on last-unsubscribe AND destroy-while-attached.
|
|
280
|
+
*/
|
|
281
|
+
function acquire() {
|
|
282
|
+
const choices = new QueryObserver(ctx.queryClient, buildChoicesOptions());
|
|
283
|
+
choicesObserver = choices;
|
|
284
|
+
const selected = new QueryObserver(ctx.queryClient, buildSelectedOptions());
|
|
285
|
+
selectedObserver = selected;
|
|
286
|
+
const unsubChoices = choices.subscribe(onChoicesResult);
|
|
287
|
+
const unsubSelected = selected.subscribe(onSelectedResult);
|
|
288
|
+
onSelectedResult(selected.getCurrentResult());
|
|
289
|
+
onChoicesResult(choices.getCurrentResult());
|
|
290
|
+
return () => {
|
|
291
|
+
unsubChoices();
|
|
292
|
+
unsubSelected();
|
|
293
|
+
choicesObserver = null;
|
|
294
|
+
selectedObserver = null;
|
|
295
|
+
clearDebounce();
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
...store,
|
|
300
|
+
setPage,
|
|
301
|
+
setPerPage,
|
|
302
|
+
setSort,
|
|
303
|
+
setFilters,
|
|
304
|
+
setValue,
|
|
305
|
+
refetch
|
|
306
|
+
};
|
|
278
307
|
}
|
|
279
|
-
export {
|
|
280
|
-
createChoicesController
|
|
281
|
-
};
|
|
308
|
+
export { createChoicesController };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure date, date-time and time format and parse helpers shared by the React and
|
|
3
|
+
* Vue `<DateInput>`, `<DateTimeInput>` and `<TimeInput>`, so both adapters
|
|
4
|
+
* format and validate a value the same way.
|
|
5
|
+
*/
|
|
6
|
+
import type { DateInputValue } from "./input-values.js";
|
|
7
|
+
/**
|
|
8
|
+
* Format a form value (string / ISO / `Date` / timestamp / "") to a bare
|
|
9
|
+
* "YYYY-MM-DD" string for `<input type="date">`. Returns `null` for nullish/empty.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatDate(value: DateInputValue): string | null;
|
|
12
|
+
/** Whether a raw `<input type="date">` string is empty or a valid date. */
|
|
13
|
+
export declare function isValidDate(raw: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Format a form value (ISO string / `Date` / timestamp) to the local
|
|
16
|
+
* `datetime-local` display string. Empty → "" (keeps the input controlled).
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatDateTime(value: DateInputValue): string;
|
|
19
|
+
/**
|
|
20
|
+
* Parse a local `datetime-local` string back to an ISO 8601 string (browser
|
|
21
|
+
* timezone). Returns `null` for empty/unparseable so a required validator fires.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseDateTimeToISO(value: string): string | null;
|
|
24
|
+
/** Whether a raw `datetime-local` string is empty or parses to a valid date. */
|
|
25
|
+
export declare function isValidDateTime(raw: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Format a form value (`Date` / "HH:mm" string) to the "HH:mm" display string.
|
|
28
|
+
* Empty → "" (keeps the input controlled).
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatTime(value: DateInputValue): string;
|
|
31
|
+
/**
|
|
32
|
+
* Parse a "HH:mm"/"HH:mm:ss" string to a `Date` (today at that local time), for
|
|
33
|
+
* dataProvider compatibility. Returns `null` for empty/unparseable so a required
|
|
34
|
+
* validator still fires.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseTimeToDate(value: string): Date | null;
|
|
37
|
+
/** Whether a raw `<input type="time">` string is empty or a valid time. */
|
|
38
|
+
export declare function isValidTime(raw: string): boolean;
|