@chelone/core 0.6.0 → 0.9.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 +23 -17
- package/dist/auth/auth-options.js +82 -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 +64 -0
- package/dist/block-editor/block-document.js +147 -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.d.ts +4 -0
- package/dist/command-menu/recents.js +12 -11
- package/dist/command-menu/registry.d.ts +9 -5
- package/dist/command-menu/registry.js +36 -46
- package/dist/controller/calendar-dates.d.ts +12 -0
- package/dist/controller/calendar-dates.js +94 -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 +43 -0
- package/dist/controller/in-place-editor.js +43 -0
- package/dist/controller/infinite-list-controller.js +219 -189
- package/dist/controller/inspector.d.ts +55 -0
- package/dist/controller/inspector.js +70 -0
- package/dist/controller/job-monitor.d.ts +10 -4
- package/dist/controller/job-monitor.js +46 -47
- package/dist/controller/kanban-board.js +45 -24
- package/dist/controller/list-controller.d.ts +8 -5
- package/dist/controller/list-controller.js +447 -400
- package/dist/controller/list-params.js +96 -87
- package/dist/controller/pivot-grid.d.ts +4 -4
- 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.d.ts +5 -5
- 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.d.ts +2 -0
- package/dist/controller/reference-many-controller.js +199 -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 +4 -3
- package/dist/csv/export.js +43 -40
- package/dist/csv/import-controller.d.ts +4 -0
- package/dist/csv/import-controller.js +189 -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 +135 -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 +22 -2
- package/dist/form/choices-controller.js +315 -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 +171 -118
- package/dist/form/input-values.d.ts +140 -0
- package/dist/form/input-values.js +206 -0
- package/dist/form/json-text.d.ts +11 -0
- package/dist/form/json-text.js +62 -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.d.ts +10 -0
- package/dist/geo/coordinates.js +44 -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 +9 -8
- package/dist/geo/map-layers.d.ts +42 -7
- package/dist/geo/map-layers.js +88 -62
- package/dist/geo/map-style.d.ts +12 -0
- package/dist/geo/map-style.js +43 -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 +44 -0
- package/dist/guards.js +77 -0
- package/dist/i18n/default-messages.js +34 -29
- package/dist/i18n/english-messages.d.ts +172 -0
- package/dist/i18n/english-messages.js +359 -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.d.ts +4 -0
- package/dist/i18n/missing-keys.js +70 -62
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +87 -39
- package/dist/index.js +54 -301
- 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 +2 -0
- package/dist/onboarding-tour/placement.js +65 -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 +247 -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 +23 -2
- package/dist/rich-text/rich-text-extras.d.ts +134 -0
- package/dist/rich-text/rich-text-extras.js +181 -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 +24 -4
- package/dist/router/location.js +43 -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/dist/ui-defaults.d.ts +15 -0
- package/dist/ui-defaults.js +17 -0
- package/package.json +13 -11
|
@@ -2,7 +2,14 @@ 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";
|
|
7
|
+
/**
|
|
8
|
+
* An erased choice record: choices carry arbitrary shapes (`{id, name, ...}`),
|
|
9
|
+
* and an adapter's choice inputs are generic over one. Both adapters re-export
|
|
10
|
+
* this as their own `ChoiceRecord`.
|
|
11
|
+
*/
|
|
12
|
+
export type ChoiceRecord = Record<string, unknown>;
|
|
6
13
|
/**
|
|
7
14
|
* The choices-context state (§0b parity). A list-like base (page/perPage/sort/
|
|
8
15
|
* filter) plus a status union over three record views:
|
|
@@ -31,7 +38,7 @@ export interface ChoicesControllerState<TRecord> {
|
|
|
31
38
|
hasPreviousPage: boolean | undefined;
|
|
32
39
|
isPending: boolean;
|
|
33
40
|
isFetching: boolean;
|
|
34
|
-
error:
|
|
41
|
+
error: Error | null;
|
|
35
42
|
/** The referenced resource name. */
|
|
36
43
|
resource: string;
|
|
37
44
|
/** The FK field on the referencing side (metadata for consumers). */
|
|
@@ -62,7 +69,7 @@ export interface ChoicesControllerOptions<TResources extends ResourceMap = Resou
|
|
|
62
69
|
* `'id'` (default) resolves selected records via `getMany`; any other key
|
|
63
70
|
* resolves via `getList({ filter: { [lookupKey]: value } })` (#16).
|
|
64
71
|
*/
|
|
65
|
-
lookupKey?:
|
|
72
|
+
lookupKey?: SourceKey<RecordOf<TResources, TName>>;
|
|
66
73
|
/** Permanent filter — merged OVER the dynamic filter for every choices fetch. */
|
|
67
74
|
filter?: FilterPayload;
|
|
68
75
|
page?: number;
|
|
@@ -93,3 +100,16 @@ export interface ChoicesControllerOptions<TResources extends ResourceMap = Resou
|
|
|
93
100
|
* observer result subscription — the subscription only maps results into state.
|
|
94
101
|
*/
|
|
95
102
|
export declare function createChoicesController<TResources extends ResourceMap, TName extends keyof TResources & string>(ctx: ChoicesControllerContext<TResources>, options: ChoicesControllerOptions<TResources, TName>): ChoicesController<RecordOf<TResources, TName>>;
|
|
103
|
+
/** The three choice views of a state, erased to `ChoiceRecord[]`. */
|
|
104
|
+
export interface ChoiceLists {
|
|
105
|
+
allChoices: ChoiceRecord[];
|
|
106
|
+
availableChoices: ChoiceRecord[];
|
|
107
|
+
selectedChoices: ChoiceRecord[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The state's three choice views as the erased lists an adapter's
|
|
111
|
+
* `ChoicesContextValue` carries — the one place a record becomes a
|
|
112
|
+
* `ChoiceRecord`, so neither adapter writes the cast. The arrays are the
|
|
113
|
+
* state's own, not copies.
|
|
114
|
+
*/
|
|
115
|
+
export declare function choiceListsOf<TRecord>(state: ChoicesControllerState<TRecord>): ChoiceLists;
|
|
@@ -1,281 +1,321 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { notifyFetchError } from "../controller/list-controller.js";
|
|
2
|
+
import "../controller/reference-many-controller.js";
|
|
2
3
|
import { getListOptions, getManyOptions } from "../data/query-options.js";
|
|
4
|
+
import { removeEmpty } from "../data/remove-empty.js";
|
|
3
5
|
import { createMicroStore } from "../store/micro-store.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
return result;
|
|
16
|
-
}
|
|
17
|
-
function errorMessageKey(error) {
|
|
18
|
-
const message = error instanceof Error ? error.message : "";
|
|
19
|
-
return message || "chelone.notification.http_error";
|
|
20
|
-
}
|
|
6
|
+
import { QueryObserver, keepPreviousData } from "@tanstack/query-core";
|
|
7
|
+
// src/form/choices-controller.ts
|
|
8
|
+
var DEFAULT_PAGE = 1;
|
|
9
|
+
var DEFAULT_SORT = {
|
|
10
|
+
field: "id",
|
|
11
|
+
order: "DESC"
|
|
12
|
+
};
|
|
13
|
+
/** perPage for the non-id selected getList — generous, real values sets are small. */
|
|
14
|
+
var SELECTED_LIST_PER_PAGE = 1e3;
|
|
15
|
+
/** Normalizes a value into a non-empty identifier list, or `[]` when there is nothing to resolve. */
|
|
21
16
|
function toValueList(value) {
|
|
22
|
-
|
|
23
|
-
return raw.filter(
|
|
24
|
-
(v) => v != null && v !== "" && (typeof v === "string" || typeof v === "number")
|
|
25
|
-
);
|
|
17
|
+
return (Array.isArray(value) ? value : [value]).filter((v) => v != null && v !== "" && (typeof v === "string" || typeof v === "number"));
|
|
26
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Vanilla choices controller: TWO QueryObservers (choices page + selected
|
|
21
|
+
* resolution) mapped into one micro-store, closing charter §11 #16 — selected
|
|
22
|
+
* records resolve by an arbitrary `lookupKey`, not just `id`.
|
|
23
|
+
*
|
|
24
|
+
* Lifecycle (P4 4R): construction is INERT — pure state + closures, zero
|
|
25
|
+
* subscriptions, zero fetching. Acquisition (both observers + KV nothing) runs
|
|
26
|
+
* on the micro-store's FIRST subscriber; the LAST unsubscribe releases both
|
|
27
|
+
* observers and clears the debounce timer. Attach/detach cycles are
|
|
28
|
+
* re-entrant; re-attach rebuilds both observers from CURRENT state.
|
|
29
|
+
* `destroy()` remains the hard imperative teardown for non-React consumers.
|
|
30
|
+
*
|
|
31
|
+
* Loop-prevention: `observer.setOptions` is called ONLY from param-mutating
|
|
32
|
+
* actions (`setPage`/`setSort`/`setFilters`/`setValue`), never from the
|
|
33
|
+
* observer result subscription — the subscription only maps results into state.
|
|
34
|
+
*/
|
|
27
35
|
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
|
-
|
|
36
|
+
const reference = options.reference;
|
|
37
|
+
const lookupKey = options.lookupKey ?? "id";
|
|
38
|
+
const isIdLookup = lookupKey === "id";
|
|
39
|
+
const permanentFilter = options.filter ?? {};
|
|
40
|
+
const debounceMs = options.debounceMs ?? 500;
|
|
41
|
+
const store = createMicroStore({
|
|
42
|
+
page: options.page ?? DEFAULT_PAGE,
|
|
43
|
+
perPage: options.perPage ?? 25,
|
|
44
|
+
sort: { ...options.sort ?? DEFAULT_SORT },
|
|
45
|
+
filter: {},
|
|
46
|
+
allChoices: [],
|
|
47
|
+
availableChoices: [],
|
|
48
|
+
selectedChoices: [],
|
|
49
|
+
total: void 0,
|
|
50
|
+
pageInfo: void 0,
|
|
51
|
+
hasNextPage: void 0,
|
|
52
|
+
hasPreviousPage: void 0,
|
|
53
|
+
isPending: true,
|
|
54
|
+
isFetching: false,
|
|
55
|
+
error: null,
|
|
56
|
+
resource: reference,
|
|
57
|
+
source: options.source,
|
|
58
|
+
isFromReference: true
|
|
59
|
+
}, { onSubscribed: acquire });
|
|
60
|
+
let destroyed = false;
|
|
61
|
+
store.onDestroy(() => {
|
|
62
|
+
destroyed = true;
|
|
63
|
+
});
|
|
64
|
+
/** Non-null exactly while attached. */
|
|
65
|
+
let choicesObserver = null;
|
|
66
|
+
/** Selected observer: getMany (id) OR getList (non-id). Rebuilt on setValue. */
|
|
67
|
+
let selectedObserver = null;
|
|
68
|
+
/** The value(s) currently driving selected resolution. */
|
|
69
|
+
let selectedValues = [];
|
|
70
|
+
/** Latest raw fetch results, cached so `recompute()` can re-merge on any change. */
|
|
71
|
+
let pageData;
|
|
72
|
+
let pageTotal;
|
|
73
|
+
let pageInfo;
|
|
74
|
+
let selectedData = [];
|
|
75
|
+
const isGetChoicesEnabled = () => options.enableGetChoices ? options.enableGetChoices(store.getSnapshot().filter) : true;
|
|
76
|
+
const buildChoicesOptions = () => {
|
|
77
|
+
const s = store.getSnapshot();
|
|
78
|
+
return {
|
|
79
|
+
...getListOptions(ctx, reference, {
|
|
80
|
+
pagination: {
|
|
81
|
+
page: s.page,
|
|
82
|
+
perPage: s.perPage
|
|
83
|
+
},
|
|
84
|
+
sort: s.sort,
|
|
85
|
+
filter: {
|
|
86
|
+
...s.filter,
|
|
87
|
+
...permanentFilter
|
|
88
|
+
},
|
|
89
|
+
meta: options.meta
|
|
90
|
+
}),
|
|
91
|
+
placeholderData: keepPreviousData,
|
|
92
|
+
retry: false,
|
|
93
|
+
enabled: isGetChoicesEnabled()
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Options for the selected observer. `id` lookup batches through getMany;
|
|
98
|
+
* any other key resolves through a getList filter (single OR array value).
|
|
99
|
+
* Disabled when there is nothing to resolve so it never pins `isPending`.
|
|
100
|
+
*/
|
|
101
|
+
const buildSelectedOptions = () => {
|
|
102
|
+
const enabled = selectedValues.length > 0;
|
|
103
|
+
if (isIdLookup) return {
|
|
104
|
+
...getManyOptions(ctx, reference, {
|
|
105
|
+
ids: selectedValues,
|
|
106
|
+
meta: options.meta
|
|
107
|
+
}),
|
|
108
|
+
retry: false,
|
|
109
|
+
enabled
|
|
110
|
+
};
|
|
111
|
+
const filterValue = selectedValues.length === 1 ? selectedValues[0] : selectedValues;
|
|
112
|
+
return {
|
|
113
|
+
...getListOptions(ctx, reference, {
|
|
114
|
+
pagination: {
|
|
115
|
+
page: 1,
|
|
116
|
+
perPage: SELECTED_LIST_PER_PAGE
|
|
117
|
+
},
|
|
118
|
+
filter: { [lookupKey]: filterValue },
|
|
119
|
+
meta: options.meta
|
|
120
|
+
}),
|
|
121
|
+
retry: false,
|
|
122
|
+
enabled
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/** Records from the selected observer result, whichever shape it is. */
|
|
126
|
+
const readSelectedData = (result) => {
|
|
127
|
+
const data = result.data;
|
|
128
|
+
if (data == null) return [];
|
|
129
|
+
return Array.isArray(data) ? data : data.data;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Merge the page + selected records into the three views (§0b parity), then
|
|
133
|
+
* write the derived state. Called from BOTH result subscriptions.
|
|
134
|
+
*/
|
|
135
|
+
const recompute = (patch) => {
|
|
136
|
+
const s = store.getSnapshot();
|
|
137
|
+
const page = pageData ?? [];
|
|
138
|
+
const selected = selectedData;
|
|
139
|
+
const keyOf = (record) => record[lookupKey];
|
|
140
|
+
const pageKeys = new Set(page.map(keyOf));
|
|
141
|
+
const missing = selected.filter((record) => !pageKeys.has(keyOf(record)));
|
|
142
|
+
const allChoices = missing.length > 0 ? [...missing, ...page] : page;
|
|
143
|
+
let total = pageTotal;
|
|
144
|
+
if (total !== void 0 && missing.length === 1 && selected.length === 1) total = total + 1;
|
|
145
|
+
let hasNextPage;
|
|
146
|
+
let hasPreviousPage;
|
|
147
|
+
if (pageInfo) {
|
|
148
|
+
hasNextPage = pageInfo.hasNextPage;
|
|
149
|
+
hasPreviousPage = pageInfo.hasPreviousPage;
|
|
150
|
+
} else if (pageTotal !== void 0) {
|
|
151
|
+
hasNextPage = s.page * s.perPage < pageTotal;
|
|
152
|
+
hasPreviousPage = s.page > 1;
|
|
153
|
+
}
|
|
154
|
+
store.setState({
|
|
155
|
+
allChoices,
|
|
156
|
+
availableChoices: page,
|
|
157
|
+
selectedChoices: selected,
|
|
158
|
+
total,
|
|
159
|
+
pageInfo,
|
|
160
|
+
hasNextPage,
|
|
161
|
+
hasPreviousPage,
|
|
162
|
+
isPending: patch.isPending,
|
|
163
|
+
isFetching: patch.isFetching,
|
|
164
|
+
error: patch.error
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
let lastNotifiedError = null;
|
|
168
|
+
const notifyError = (error) => {
|
|
169
|
+
if (error === lastNotifiedError) return;
|
|
170
|
+
lastNotifiedError = error;
|
|
171
|
+
if (error === null) return;
|
|
172
|
+
notifyFetchError(ctx.notifications, error);
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* The combined status union. `isPending` reflects the choices query and the
|
|
176
|
+
* selected query ONLY when a value is being resolved — a valueless selected
|
|
177
|
+
* query is disabled and must never pin `isPending` (§0b).
|
|
178
|
+
*/
|
|
179
|
+
const onChoicesResult = (result) => {
|
|
180
|
+
const list = result.data;
|
|
181
|
+
pageData = list?.data;
|
|
182
|
+
pageTotal = list?.total;
|
|
183
|
+
pageInfo = list?.pageInfo;
|
|
184
|
+
const selectedPending = selectedValues.length > 0 && selectedObserver ? selectedObserver.getCurrentResult().isPending : false;
|
|
185
|
+
const selectedFetching = selectedValues.length > 0 && selectedObserver ? selectedObserver.getCurrentResult().isFetching : false;
|
|
186
|
+
const error = result.error ?? selectedObserver?.getCurrentResult().error ?? null;
|
|
187
|
+
recompute({
|
|
188
|
+
isPending: result.isPending || selectedPending,
|
|
189
|
+
isFetching: result.isFetching || selectedFetching,
|
|
190
|
+
error
|
|
191
|
+
});
|
|
192
|
+
notifyError(result.error ?? null);
|
|
193
|
+
};
|
|
194
|
+
const onSelectedResult = (result) => {
|
|
195
|
+
selectedData = readSelectedData(result);
|
|
196
|
+
const choicesResult = choicesObserver?.getCurrentResult();
|
|
197
|
+
const choicesPending = choicesResult?.isPending ?? false;
|
|
198
|
+
const choicesFetching = choicesResult?.isFetching ?? false;
|
|
199
|
+
const selectedPending = selectedValues.length > 0 && result.isPending;
|
|
200
|
+
const selectedFetching = selectedValues.length > 0 && result.isFetching;
|
|
201
|
+
const error = choicesResult?.error ?? result.error ?? null;
|
|
202
|
+
recompute({
|
|
203
|
+
isPending: choicesPending || selectedPending,
|
|
204
|
+
isFetching: choicesFetching || selectedFetching,
|
|
205
|
+
error
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
const setPage = (page) => {
|
|
209
|
+
if (destroyed) return;
|
|
210
|
+
store.setState({ page });
|
|
211
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
212
|
+
};
|
|
213
|
+
const setPerPage = (perPage) => {
|
|
214
|
+
if (destroyed) return;
|
|
215
|
+
store.setState({
|
|
216
|
+
perPage,
|
|
217
|
+
page: 1
|
|
218
|
+
});
|
|
219
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
220
|
+
};
|
|
221
|
+
const setSort = (sort) => {
|
|
222
|
+
if (destroyed) return;
|
|
223
|
+
store.setState({
|
|
224
|
+
sort: { ...sort },
|
|
225
|
+
page: 1
|
|
226
|
+
});
|
|
227
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
228
|
+
};
|
|
229
|
+
const applyFilters = (filter) => {
|
|
230
|
+
store.setState({
|
|
231
|
+
filter: removeEmpty(filter),
|
|
232
|
+
page: 1
|
|
233
|
+
});
|
|
234
|
+
choicesObserver?.setOptions(buildChoicesOptions());
|
|
235
|
+
};
|
|
236
|
+
let debounceTimer;
|
|
237
|
+
const clearDebounce = () => {
|
|
238
|
+
if (debounceTimer !== void 0) {
|
|
239
|
+
clearTimeout(debounceTimer);
|
|
240
|
+
debounceTimer = void 0;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
store.onDestroy(clearDebounce);
|
|
244
|
+
const setFilters = (filter, debounce = false) => {
|
|
245
|
+
if (destroyed) return;
|
|
246
|
+
clearDebounce();
|
|
247
|
+
if (!debounce) {
|
|
248
|
+
applyFilters(filter);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
debounceTimer = setTimeout(() => {
|
|
252
|
+
debounceTimer = void 0;
|
|
253
|
+
if (destroyed) return;
|
|
254
|
+
applyFilters(filter);
|
|
255
|
+
}, debounceMs);
|
|
256
|
+
};
|
|
257
|
+
const setValue = (value) => {
|
|
258
|
+
if (destroyed) return;
|
|
259
|
+
const next = toValueList(value);
|
|
260
|
+
if (next.length === selectedValues.length && next.every((v, i) => v === selectedValues[i])) return;
|
|
261
|
+
selectedValues = next;
|
|
262
|
+
if (next.length === 0) selectedData = [];
|
|
263
|
+
selectedObserver?.setOptions(buildSelectedOptions());
|
|
264
|
+
if (!selectedObserver && next.length === 0) recompute({
|
|
265
|
+
isPending: store.getSnapshot().isPending,
|
|
266
|
+
isFetching: store.getSnapshot().isFetching,
|
|
267
|
+
error: store.getSnapshot().error
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
const refetch = () => {
|
|
271
|
+
if (destroyed) return;
|
|
272
|
+
choicesObserver?.refetch();
|
|
273
|
+
if (selectedValues.length > 0) selectedObserver?.refetch();
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Attach: runs on the micro-store's FIRST subscriber. Creates and subscribes
|
|
277
|
+
* BOTH observers with options built from CURRENT state, and returns the
|
|
278
|
+
* teardown the micro-store runs on last-unsubscribe AND destroy-while-attached.
|
|
279
|
+
*/
|
|
280
|
+
function acquire() {
|
|
281
|
+
const choices = new QueryObserver(ctx.queryClient, buildChoicesOptions());
|
|
282
|
+
choicesObserver = choices;
|
|
283
|
+
const selected = new QueryObserver(ctx.queryClient, buildSelectedOptions());
|
|
284
|
+
selectedObserver = selected;
|
|
285
|
+
const unsubChoices = choices.subscribe(onChoicesResult);
|
|
286
|
+
const unsubSelected = selected.subscribe(onSelectedResult);
|
|
287
|
+
onSelectedResult(selected.getCurrentResult());
|
|
288
|
+
onChoicesResult(choices.getCurrentResult());
|
|
289
|
+
return () => {
|
|
290
|
+
unsubChoices();
|
|
291
|
+
unsubSelected();
|
|
292
|
+
choicesObserver = null;
|
|
293
|
+
selectedObserver = null;
|
|
294
|
+
clearDebounce();
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
...store,
|
|
299
|
+
setPage,
|
|
300
|
+
setPerPage,
|
|
301
|
+
setSort,
|
|
302
|
+
setFilters,
|
|
303
|
+
setValue,
|
|
304
|
+
refetch
|
|
305
|
+
};
|
|
278
306
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
307
|
+
/**
|
|
308
|
+
* The state's three choice views as the erased lists an adapter's
|
|
309
|
+
* `ChoicesContextValue` carries — the one place a record becomes a
|
|
310
|
+
* `ChoiceRecord`, so neither adapter writes the cast. The arrays are the
|
|
311
|
+
* state's own, not copies.
|
|
312
|
+
*/
|
|
313
|
+
function choiceListsOf(state) {
|
|
314
|
+
const { allChoices, availableChoices, selectedChoices } = state;
|
|
315
|
+
return {
|
|
316
|
+
allChoices,
|
|
317
|
+
availableChoices,
|
|
318
|
+
selectedChoices
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
export { choiceListsOf, createChoicesController };
|