@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.
Files changed (242) hide show
  1. package/dist/assistant/assistant-session.d.ts +6 -0
  2. package/dist/assistant/assistant-session.js +204 -181
  3. package/dist/assistant/echo-transport.js +26 -29
  4. package/dist/assistant/http-transport.d.ts +10 -0
  5. package/dist/assistant/http-transport.js +98 -0
  6. package/dist/assistant/types.d.ts +1 -7
  7. package/dist/auth/access-control.d.ts +13 -5
  8. package/dist/auth/access-control.js +25 -21
  9. package/dist/auth/auth-options.d.ts +21 -17
  10. package/dist/auth/auth-options.js +80 -48
  11. package/dist/auth/permission-matrix.d.ts +12 -0
  12. package/dist/auth/permission-matrix.js +67 -0
  13. package/dist/auth/types.d.ts +14 -4
  14. package/dist/block-editor/block-document.d.ts +60 -0
  15. package/dist/block-editor/block-document.js +143 -0
  16. package/dist/block-editor/data-blocks.d.ts +63 -0
  17. package/dist/block-editor/data-blocks.js +117 -0
  18. package/dist/chart/chart-data.js +46 -44
  19. package/dist/command-menu/hotkey.js +58 -47
  20. package/dist/command-menu/recents.js +8 -11
  21. package/dist/command-menu/registry.d.ts +9 -5
  22. package/dist/command-menu/registry.js +36 -46
  23. package/dist/controller/calendar-dates.js +82 -113
  24. package/dist/controller/calendar-events.js +93 -88
  25. package/dist/controller/client-list-controller.d.ts +2 -2
  26. package/dist/controller/client-list-controller.js +251 -254
  27. package/dist/controller/data-table-columns.d.ts +2 -0
  28. package/dist/controller/data-table-columns.js +50 -56
  29. package/dist/controller/deep-equal.d.ts +1 -0
  30. package/dist/controller/deep-equal.js +14 -0
  31. package/dist/controller/filter-toggle.js +36 -44
  32. package/dist/controller/in-place-editor.d.ts +42 -0
  33. package/dist/controller/in-place-editor.js +42 -0
  34. package/dist/controller/infinite-list-controller.js +219 -189
  35. package/dist/controller/inspector.d.ts +49 -0
  36. package/dist/controller/inspector.js +64 -0
  37. package/dist/controller/job-monitor.d.ts +3 -4
  38. package/dist/controller/job-monitor.js +37 -47
  39. package/dist/controller/kanban-board.d.ts +2 -3
  40. package/dist/controller/kanban-board.js +43 -24
  41. package/dist/controller/list-controller.d.ts +5 -5
  42. package/dist/controller/list-controller.js +444 -400
  43. package/dist/controller/list-params.js +96 -87
  44. package/dist/controller/pivot-grid.js +89 -95
  45. package/dist/controller/prev-next.d.ts +2 -2
  46. package/dist/controller/prev-next.js +74 -54
  47. package/dist/controller/record-controllers.d.ts +14 -6
  48. package/dist/controller/record-controllers.js +288 -257
  49. package/dist/controller/record-diff.d.ts +21 -0
  50. package/dist/controller/record-diff.js +50 -0
  51. package/dist/controller/record-timeline.js +42 -30
  52. package/dist/controller/record-tree.js +57 -47
  53. package/dist/controller/record-workflow.d.ts +38 -16
  54. package/dist/controller/record-workflow.js +85 -64
  55. package/dist/controller/reference-many-controller.js +198 -192
  56. package/dist/controller/reference-one-controller.d.ts +1 -1
  57. package/dist/controller/reference-one-controller.js +63 -56
  58. package/dist/controller/saved-queries.js +104 -91
  59. package/dist/controller/translate-fallback.js +10 -6
  60. package/dist/csv/export.d.ts +2 -1
  61. package/dist/csv/export.js +43 -40
  62. package/dist/csv/import-controller.js +185 -169
  63. package/dist/csv/match-headers.js +29 -22
  64. package/dist/csv/parse-with.js +60 -54
  65. package/dist/csv/run-import.js +69 -72
  66. package/dist/csv/validate-rows.d.ts +1 -1
  67. package/dist/csv/validate-rows.js +65 -52
  68. package/dist/data/cache-ops.d.ts +5 -4
  69. package/dist/data/cache-ops.js +208 -188
  70. package/dist/data/combine-data-providers.js +25 -23
  71. package/dist/data/data-provider-log.d.ts +45 -0
  72. package/dist/data/data-provider-log.js +93 -0
  73. package/dist/data/errors.js +14 -10
  74. package/dist/data/fake-provider.d.ts +6 -3
  75. package/dist/data/fake-provider.js +129 -114
  76. package/dist/data/fetch-utils.js +61 -44
  77. package/dist/data/filter-branch.d.ts +5 -0
  78. package/dist/data/filter-branch.js +10 -0
  79. package/dist/data/get-many-aggregate.js +84 -76
  80. package/dist/data/http-error.js +17 -12
  81. package/dist/data/keys.js +50 -36
  82. package/dist/data/mutation-options.d.ts +14 -9
  83. package/dist/data/mutation-options.js +93 -109
  84. package/dist/data/query-options.js +73 -68
  85. package/dist/data/remove-empty.d.ts +3 -0
  86. package/dist/data/remove-empty.js +11 -0
  87. package/dist/data/undoable.d.ts +2 -2
  88. package/dist/data/undoable.js +190 -174
  89. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  90. package/dist/data/with-lifecycle-callbacks.js +66 -52
  91. package/dist/environment/application-update-store.js +58 -55
  92. package/dist/environment/offline-store.js +21 -21
  93. package/dist/form/array-binding.d.ts +30 -11
  94. package/dist/form/array-binding.js +184 -166
  95. package/dist/form/choice-ids.d.ts +7 -0
  96. package/dist/form/choice-ids.js +11 -0
  97. package/dist/form/choices-controller.d.ts +3 -2
  98. package/dist/form/choices-controller.js +302 -275
  99. package/dist/form/date-format.d.ts +38 -0
  100. package/dist/form/date-format.js +89 -0
  101. package/dist/form/dirty-values.d.ts +2 -2
  102. package/dist/form/dirty-values.js +22 -11
  103. package/dist/form/duration.d.ts +22 -0
  104. package/dist/form/duration.js +49 -0
  105. package/dist/form/edit-create-form.d.ts +12 -5
  106. package/dist/form/edit-create-form.js +243 -188
  107. package/dist/form/event-value.d.ts +6 -0
  108. package/dist/form/event-value.js +17 -0
  109. package/dist/form/field-error.d.ts +14 -0
  110. package/dist/form/field-error.js +28 -0
  111. package/dist/form/field-name.d.ts +20 -0
  112. package/dist/form/field-name.js +6 -0
  113. package/dist/form/filter-form-values.js +63 -49
  114. package/dist/form/form-controller.d.ts +9 -1
  115. package/dist/form/form-controller.js +165 -160
  116. package/dist/form/form-groups.d.ts +4 -3
  117. package/dist/form/form-groups.js +79 -79
  118. package/dist/form/form-handle.d.ts +175 -0
  119. package/dist/form/form-handle.js +68 -0
  120. package/dist/form/input-binding.d.ts +56 -31
  121. package/dist/form/input-binding.js +169 -118
  122. package/dist/form/input-values.d.ts +130 -0
  123. package/dist/form/input-values.js +177 -0
  124. package/dist/form/json-text.js +51 -37
  125. package/dist/form/save-pipeline.d.ts +6 -8
  126. package/dist/form/save-pipeline.js +91 -82
  127. package/dist/form/set-form-values.d.ts +6 -0
  128. package/dist/form/set-form-values.js +10 -0
  129. package/dist/form/source.js +97 -65
  130. package/dist/form/text-of.d.ts +2 -0
  131. package/dist/form/text-of.js +7 -0
  132. package/dist/form/validators.d.ts +58 -19
  133. package/dist/form/validators.js +97 -71
  134. package/dist/geo/bbox.d.ts +5 -4
  135. package/dist/geo/bbox.js +69 -48
  136. package/dist/geo/bounds.d.ts +14 -3
  137. package/dist/geo/bounds.js +98 -68
  138. package/dist/geo/constraints.js +88 -93
  139. package/dist/geo/coordinates.js +31 -23
  140. package/dist/geo/editor.d.ts +13 -13
  141. package/dist/geo/editor.js +176 -153
  142. package/dist/geo/geocoding.d.ts +4 -4
  143. package/dist/geo/geocoding.js +38 -41
  144. package/dist/geo/geojson.d.ts +12 -74
  145. package/dist/geo/geojson.js +52 -40
  146. package/dist/geo/geometry-math.d.ts +4 -4
  147. package/dist/geo/geometry-math.js +100 -93
  148. package/dist/geo/index.d.ts +7 -6
  149. package/dist/geo/map-layers.d.ts +42 -7
  150. package/dist/geo/map-layers.js +88 -62
  151. package/dist/geo/map-style.js +31 -32
  152. package/dist/geo/messages.js +48 -45
  153. package/dist/geo/nominatim.js +88 -80
  154. package/dist/geo/osm-presets.d.ts +4 -3
  155. package/dist/geo/osm-presets.js +108 -86
  156. package/dist/geo/osm-tags.js +14 -12
  157. package/dist/geo/overpass.d.ts +14 -2
  158. package/dist/geo/overpass.js +83 -90
  159. package/dist/geo/shape-value.d.ts +9 -5
  160. package/dist/geo/shape-value.js +169 -146
  161. package/dist/geo/snap-to-roads.d.ts +3 -3
  162. package/dist/geo/snap-to-roads.js +55 -50
  163. package/dist/geo/throttle.js +36 -39
  164. package/dist/guards.d.ts +37 -0
  165. package/dist/guards.js +62 -0
  166. package/dist/i18n/default-messages.js +34 -29
  167. package/dist/i18n/english-messages.js +187 -189
  168. package/dist/i18n/i18n.d.ts +2 -2
  169. package/dist/i18n/i18n.js +47 -60
  170. package/dist/i18n/inflection.d.ts +34 -0
  171. package/dist/i18n/inflection.js +82 -0
  172. package/dist/i18n/messages.d.ts +10 -2
  173. package/dist/i18n/messages.js +17 -4
  174. package/dist/i18n/missing-keys.js +66 -62
  175. package/dist/i18n/translatable.d.ts +16 -1
  176. package/dist/i18n/translatable.js +40 -47
  177. package/dist/index.d.ts +71 -24
  178. package/dist/index.js +40 -288
  179. package/dist/json/deep-equal.d.ts +8 -0
  180. package/dist/json/deep-equal.js +24 -0
  181. package/dist/json/strip-json-comments.js +38 -42
  182. package/dist/noop.d.ts +2 -0
  183. package/dist/noop.js +4 -0
  184. package/dist/notification/error-message.d.ts +2 -0
  185. package/dist/notification/error-message.js +10 -0
  186. package/dist/notification/notification-store.d.ts +3 -2
  187. package/dist/notification/notification-store.js +41 -45
  188. package/dist/onboarding-tour/placement.js +63 -78
  189. package/dist/onboarding-tour/tour-controller.js +46 -54
  190. package/dist/realtime/add-events-for-mutations.js +75 -81
  191. package/dist/realtime/announce-lock.js +16 -13
  192. package/dist/realtime/broadcast-channel-transport.js +65 -59
  193. package/dist/realtime/copy-members.js +22 -14
  194. package/dist/realtime/in-memory-lock-provider.js +40 -48
  195. package/dist/realtime/live.js +95 -101
  196. package/dist/realtime/lock-options.js +16 -16
  197. package/dist/realtime/lock-session.js +91 -78
  198. package/dist/realtime/memory-transport.js +25 -25
  199. package/dist/realtime/presence.js +120 -122
  200. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  201. package/dist/realtime/realtime-data-provider.js +45 -42
  202. package/dist/realtime/reconnect-backoff.js +17 -0
  203. package/dist/realtime/sse-transport.d.ts +31 -0
  204. package/dist/realtime/sse-transport.js +235 -0
  205. package/dist/realtime/status-store.js +23 -28
  206. package/dist/realtime/topic-hub.js +62 -71
  207. package/dist/realtime/topics.js +10 -12
  208. package/dist/realtime/types.js +10 -12
  209. package/dist/realtime/unread-count.js +21 -21
  210. package/dist/realtime/websocket-transport.d.ts +32 -0
  211. package/dist/realtime/websocket-transport.js +306 -0
  212. package/dist/resource/describe-resource.d.ts +2 -0
  213. package/dist/resource/describe-resource.js +232 -219
  214. package/dist/resource/get-at-path.js +29 -14
  215. package/dist/resource/identifier.d.ts +18 -0
  216. package/dist/resource/identifier.js +27 -0
  217. package/dist/resource/record.d.ts +4 -0
  218. package/dist/resource/record.js +10 -0
  219. package/dist/resource/register.d.ts +7 -0
  220. package/dist/resource/registry-store.d.ts +45 -15
  221. package/dist/resource/registry-store.js +67 -60
  222. package/dist/resource/registry.d.ts +10 -0
  223. package/dist/resource/registry.js +8 -4
  224. package/dist/resource/source-path.d.ts +16 -1
  225. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  226. package/dist/rich-text/rich-text-extras.js +179 -0
  227. package/dist/rich-text/rich-text-image.d.ts +124 -0
  228. package/dist/rich-text/rich-text-image.js +163 -0
  229. package/dist/rich-text/rich-text.d.ts +6 -0
  230. package/dist/rich-text/rich-text.js +125 -88
  231. package/dist/router/location.d.ts +22 -4
  232. package/dist/router/location.js +41 -18
  233. package/dist/router/match.js +47 -39
  234. package/dist/router/paths.js +40 -34
  235. package/dist/router/record-from-location.d.ts +1 -1
  236. package/dist/router/record-from-location.js +25 -17
  237. package/dist/store/adapters.js +51 -55
  238. package/dist/store/micro-store.js +57 -59
  239. package/dist/store/shallow-equal.d.ts +12 -0
  240. package/dist/store/shallow-equal.js +20 -0
  241. package/dist/store/store.js +54 -57
  242. package/package.json +13 -11
@@ -0,0 +1 @@
1
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
@@ -0,0 +1,14 @@
1
+ import { isPlainObject } from "../guards.js";
2
+ // src/controller/deep-equal.ts
3
+ function deepEqual(a, b) {
4
+ if (Object.is(a, b)) return true;
5
+ if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
6
+ if (a instanceof Date) return b instanceof Date && Object.is(+a, +b);
7
+ if (isPlainObject(a)) {
8
+ if (!isPlainObject(b)) return false;
9
+ const keys = Object.keys(a);
10
+ return keys.length === Object.keys(b).length && keys.every((key) => key in b && deepEqual(a[key], b[key]));
11
+ }
12
+ return false;
13
+ }
14
+ export { deepEqual };
@@ -1,52 +1,44 @@
1
- const isPlainObject = (value) => {
2
- if (typeof value !== "object" || value === null) return false;
3
- const proto = Object.getPrototypeOf(value);
4
- return proto === Object.prototype || proto === null;
5
- };
6
- function deepEqual(a, b) {
7
- if (Object.is(a, b)) return true;
8
- if (Array.isArray(a)) {
9
- return Array.isArray(b) && a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
10
- }
11
- if (a instanceof Date) return b instanceof Date && +a === +b;
12
- if (isPlainObject(a)) {
13
- if (!isPlainObject(b)) return false;
14
- const keys = Object.keys(a);
15
- return keys.length === Object.keys(b).length && keys.every((key) => key in b && deepEqual(a[key], b[key]));
16
- }
17
- return false;
18
- }
1
+ import { isPlainObject } from "../guards.js";
2
+ import { deepEqual } from "./deep-equal.js";
3
+ // src/controller/filter-toggle.ts
4
+ /**
5
+ * The select and toggle rules behind a one-click list filter (ra-core's
6
+ * `FilterListItem` and `ToggleFilterButton`), pure so both adapters share them.
7
+ *
8
+ * Chelone divergence: arrays compare for deep equality, not lodash's
9
+ * contains-all rule.
10
+ */
19
11
  function matches(expected, actual) {
20
- if (isPlainObject(expected)) {
21
- return isPlainObject(actual) && Object.keys(expected).every(
22
- (key) => expected[key] === void 0 || matches(expected[key], actual[key])
23
- );
24
- }
25
- return deepEqual(expected, actual);
12
+ if (isPlainObject(expected)) return isPlainObject(actual) && Object.keys(expected).every((key) => expected[key] === void 0 || matches(expected[key], actual[key]));
13
+ return deepEqual(expected, actual);
26
14
  }
15
+ /** True when every defined key of `value` is held by `filters`. */
27
16
  function isFilterSelected(value, filters) {
28
- return matches(value, filters);
17
+ return matches(value, filters);
29
18
  }
30
19
  function omitValue(filters, value) {
31
- const next = { ...filters };
32
- for (const key of Object.keys(value)) {
33
- if (value[key] === void 0) continue;
34
- const held = next[key];
35
- if (isPlainObject(value[key]) && isPlainObject(held)) {
36
- const rest = omitValue(held, value[key]);
37
- if (Object.keys(rest).length > 0) next[key] = rest;
38
- else delete next[key];
39
- } else {
40
- delete next[key];
41
- }
42
- }
43
- return next;
20
+ const next = { ...filters };
21
+ for (const key of Object.keys(value)) {
22
+ if (value[key] === void 0) continue;
23
+ const held = next[key];
24
+ if (isPlainObject(value[key]) && isPlainObject(held)) {
25
+ const rest = omitValue(held, value[key]);
26
+ if (Object.keys(rest).length > 0) next[key] = rest;
27
+ else delete next[key];
28
+ } else delete next[key];
29
+ }
30
+ return next;
44
31
  }
32
+ /**
33
+ * Selecting a value merges it into the filters; toggling a selected one drops
34
+ * the keys it holds, down to nested sub-keys, and keeps the rest. Neither
35
+ * argument is mutated.
36
+ */
45
37
  function toggleFilterValue(value, filters) {
46
- if (!isFilterSelected(value, filters)) return { ...filters, ...value };
47
- return omitValue(filters, value);
38
+ if (!isFilterSelected(value, filters)) return {
39
+ ...filters,
40
+ ...value
41
+ };
42
+ return omitValue(filters, value);
48
43
  }
49
- export {
50
- isFilterSelected,
51
- toggleFilterValue
52
- };
44
+ export { isFilterSelected, toggleFilterValue };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Pure rules for an in-place editor. No react, no vue: both InPlaceEditor
3
+ * adapters step through the same states and send the same fields on a save.
4
+ */
5
+ import type { ResourceRecord } from "../resource/record.js";
6
+ export type InPlaceEditorState = {
7
+ status: "reading";
8
+ } | {
9
+ status: "editing";
10
+ } | {
11
+ status: "saving";
12
+ preview: Record<string, unknown>;
13
+ };
14
+ export type InPlaceEditorEvent = {
15
+ type: "edit";
16
+ } | {
17
+ type: "cancel";
18
+ } | {
19
+ type: "save";
20
+ preview: Record<string, unknown>;
21
+ } | {
22
+ type: "settled";
23
+ };
24
+ /**
25
+ * Steps the editor: reading to editing on `edit`, editing to reading on
26
+ * `cancel`, editing to saving on `save`, saving to reading on `settled`. Any
27
+ * other pair returns the same state object, so a click while saving does nothing.
28
+ */
29
+ export declare function nextInPlaceEditorState(state: InPlaceEditorState, event: InPlaceEditorEvent): InPlaceEditorState;
30
+ export interface InPlaceSave<R extends ResourceRecord> {
31
+ id: R["id"];
32
+ data: Record<string, unknown>;
33
+ previousData: R;
34
+ preview: Record<string, unknown>;
35
+ }
36
+ /**
37
+ * Plans a save from the record and the dirty form values. `data` keeps the
38
+ * fields whose value is not deeply equal to the record's, so a value typed and
39
+ * then typed back is dropped. Returns `null` when nothing changed, so a save
40
+ * with no change makes no call.
41
+ */
42
+ export declare function planInPlaceSave<R extends ResourceRecord>(record: R, dirty: Record<string, unknown>): InPlaceSave<R> | null;
@@ -0,0 +1,42 @@
1
+ import { isLooseRecord } from "../resource/record.js";
2
+ import { deepEqual } from "./deep-equal.js";
3
+ // src/controller/in-place-editor.ts
4
+ /**
5
+ * Steps the editor: reading to editing on `edit`, editing to reading on
6
+ * `cancel`, editing to saving on `save`, saving to reading on `settled`. Any
7
+ * other pair returns the same state object, so a click while saving does nothing.
8
+ */
9
+ function nextInPlaceEditorState(state, event) {
10
+ switch (state.status) {
11
+ case "reading": return event.type === "edit" ? { status: "editing" } : state;
12
+ case "editing":
13
+ if (event.type === "cancel") return { status: "reading" };
14
+ if (event.type === "save") return {
15
+ status: "saving",
16
+ preview: event.preview
17
+ };
18
+ return state;
19
+ case "saving": return event.type === "settled" ? { status: "reading" } : state;
20
+ }
21
+ }
22
+ /**
23
+ * Plans a save from the record and the dirty form values. `data` keeps the
24
+ * fields whose value is not deeply equal to the record's, so a value typed and
25
+ * then typed back is dropped. Returns `null` when nothing changed, so a save
26
+ * with no change makes no call.
27
+ */
28
+ function planInPlaceSave(record, dirty) {
29
+ if (!isLooseRecord(record)) return null;
30
+ const data = Object.fromEntries(Object.entries(dirty).filter(([key, value]) => !deepEqual(record[key], value)));
31
+ if (Object.keys(data).length === 0) return null;
32
+ return {
33
+ id: record.id,
34
+ data,
35
+ previousData: record,
36
+ preview: {
37
+ ...record,
38
+ ...data
39
+ }
40
+ };
41
+ }
42
+ export { nextInPlaceEditorState, planInPlaceSave };
@@ -1,193 +1,223 @@
1
- import { QueryClient, InfiniteQueryObserver } from "@tanstack/query-core";
2
1
  import { getInfiniteListOptions, getListOptions } from "../data/query-options.js";
3
2
  import { createMicroStore } from "../store/micro-store.js";
4
- import { createListController, createErrorAnnouncer, runSelectAll } from "./list-controller.js";
3
+ import { createErrorAnnouncer, createListController, runSelectAll } from "./list-controller.js";
4
+ import { InfiniteQueryObserver, QueryClient } from "@tanstack/query-core";
5
+ // src/controller/infinite-list-controller.ts
6
+ /**
7
+ * Vanilla infinite list controller: list params and selection from an inner
8
+ * {@link createListController} (disabled, so it never fetches), data from its
9
+ * own InfiniteQueryObserver that gathers every loaded page into one list.
10
+ *
11
+ * Lifecycle (Task 4R): construction is INERT. The first subscriber acquires
12
+ * the inner controller and the observer, the last unsubscribe releases both,
13
+ * and `destroy()` is the hard teardown. Re-attach rebuilds the query options
14
+ * from the inner controller's CURRENT params.
15
+ *
16
+ * Loop-prevention rule, as in `createListController`: `observer.setOptions`
17
+ * runs ONLY when the inner params change, never from the observer result.
18
+ * The query key holds the start page, so any param change, `setPage`
19
+ * included, drops the loaded pages and loads from the new start page. The
20
+ * out-of-range page reset of the list controller does not apply: the inner
21
+ * controller reads a private empty cache, so no result can trigger it.
22
+ */
5
23
  function createInfiniteListController(ctx, resource, options = {}) {
6
- const innerCache = new QueryClient({
7
- defaultOptions: { queries: { gcTime: 0 } }
8
- });
9
- const inner = createListController(
10
- { ...ctx, queryClient: innerCache, notifications: void 0 },
11
- resource,
12
- { ...options, enabled: false }
13
- );
14
- const paramsOf = () => {
15
- const { page, perPage, sort, filter, displayedFilters, selectedIds } = inner.getSnapshot();
16
- return { page, perPage, sort, filter, displayedFilters, selectedIds };
17
- };
18
- const store = createMicroStore(
19
- {
20
- ...paramsOf(),
21
- data: void 0,
22
- total: void 0,
23
- pageInfo: void 0,
24
- isPending: true,
25
- isFetching: false,
26
- isFetchingNextPage: false,
27
- isFetchingPreviousPage: false,
28
- error: null,
29
- hasNextPage: void 0,
30
- hasPreviousPage: void 0
31
- },
32
- { onSubscribed: acquire }
33
- );
34
- let destroyed = false;
35
- store.onDestroy(() => {
36
- destroyed = true;
37
- inner.destroy();
38
- innerCache.clear();
39
- });
40
- let observer = null;
41
- let applied = null;
42
- let busy = null;
43
- const requestParams = (s) => ({
44
- // A single object when there is exactly one key (adapter compat).
45
- sort: s.sort.length === 1 ? s.sort[0] : s.sort,
46
- // The permanent filter merges OVER the user filter.
47
- filter: { ...s.filter, ...options.filter ?? {} }
48
- });
49
- const buildOptions = () => {
50
- const s = inner.getSnapshot();
51
- applied = s;
52
- return {
53
- ...getInfiniteListOptions(ctx, resource, {
54
- pagination: { page: s.page, perPage: s.perPage },
55
- ...requestParams(s)
56
- }),
57
- retry: false,
58
- enabled: options.enabled ?? true
59
- };
60
- };
61
- const mirror = () => {
62
- store.setState(paramsOf());
63
- };
64
- const onInner = () => {
65
- mirror();
66
- if (!observer || !applied) return;
67
- const next = inner.getSnapshot();
68
- if (next.page !== applied.page || next.perPage !== applied.perPage || next.sort !== applied.sort || next.filter !== applied.filter) {
69
- busy = null;
70
- observer.setOptions(buildOptions());
71
- }
72
- };
73
- const errors = createErrorAnnouncer(ctx.notifications);
74
- let lastPages;
75
- let lastRows;
76
- const onResult = (result) => {
77
- const pages = result.data?.pages;
78
- if (pages !== lastPages) {
79
- lastPages = pages;
80
- lastRows = pages?.flatMap((page) => page.data);
81
- }
82
- const last = pages?.[pages.length - 1];
83
- store.setState({
84
- data: lastRows,
85
- total: last?.total,
86
- pageInfo: last?.pageInfo,
87
- isPending: result.isPending,
88
- isFetching: result.isFetching,
89
- isFetchingNextPage: result.isFetchingNextPage,
90
- isFetchingPreviousPage: result.isFetchingPreviousPage,
91
- error: result.error ?? null,
92
- hasNextPage: pages ? result.hasNextPage : void 0,
93
- hasPreviousPage: pages ? result.hasPreviousPage : void 0
94
- });
95
- if (result.error) errors.announce(result.error);
96
- else errors.rearm();
97
- };
98
- function acquire() {
99
- const unsubscribeInner = inner.subscribe(onInner);
100
- mirror();
101
- const attached = new InfiniteQueryObserver(
102
- ctx.queryClient,
103
- buildOptions()
104
- );
105
- observer = attached;
106
- const unsubscribeObserver = attached.subscribe(onResult);
107
- onResult(attached.getCurrentResult());
108
- return () => {
109
- unsubscribeObserver();
110
- unsubscribeInner();
111
- observer = null;
112
- applied = null;
113
- lastPages = void 0;
114
- lastRows = void 0;
115
- busy = null;
116
- };
117
- }
118
- const through = (action) => (...args) => {
119
- const result = action(...args);
120
- if (!destroyed) mirror();
121
- return result;
122
- };
123
- const track = (kind, promise) => {
124
- const entry = { kind, promise };
125
- busy = entry;
126
- void promise.then(() => {
127
- if (busy === entry) busy = null;
128
- });
129
- return promise;
130
- };
131
- const swallow = () => void 0;
132
- const fetchMore = (direction) => {
133
- if (destroyed || !observer) return Promise.resolve();
134
- if (busy) {
135
- if (busy.kind === direction) return busy.promise;
136
- return busy.promise.then(() => fetchMore(direction));
137
- }
138
- const current = observer.getCurrentResult();
139
- if (direction === "next" ? !current.hasNextPage : !current.hasPreviousPage) {
140
- return Promise.resolve();
141
- }
142
- const started = direction === "next" ? observer.fetchNextPage() : observer.fetchPreviousPage();
143
- return track(direction, started.then(swallow, swallow));
144
- };
145
- const refetch = () => {
146
- if (destroyed || !observer) return;
147
- if (busy) {
148
- if (busy.kind !== "refetch") void busy.promise.then(refetch);
149
- return;
150
- }
151
- void track("refetch", observer.refetch().then(swallow, swallow));
152
- };
153
- const selectAll = (opts) => runSelectAll(
154
- ctx.notifications,
155
- async (limit) => {
156
- const s = inner.getSnapshot();
157
- const result = await ctx.queryClient.fetchQuery({
158
- ...getListOptions(ctx, resource, {
159
- pagination: { page: 1, perPage: limit },
160
- ...requestParams(s)
161
- }),
162
- retry: false
163
- });
164
- return result.data;
165
- },
166
- (ids) => {
167
- inner.select(ids);
168
- mirror();
169
- },
170
- () => destroyed,
171
- opts?.limit
172
- );
173
- return {
174
- ...store,
175
- setPage: through(inner.setPage),
176
- setPerPage: through(inner.setPerPage),
177
- setSort: through(inner.setSort),
178
- setFilters: through(inner.setFilters),
179
- setParams: through(inner.setParams),
180
- showFilter: through(inner.showFilter),
181
- hideFilter: through(inner.hideFilter),
182
- select: through(inner.select),
183
- toggleItem: through(inner.toggleItem),
184
- unselectAll: through(inner.unselectAll),
185
- selectAll,
186
- refetch,
187
- fetchNextPage: () => fetchMore("next"),
188
- fetchPreviousPage: () => fetchMore("previous")
189
- };
24
+ const innerCache = new QueryClient({ defaultOptions: { queries: { gcTime: 0 } } });
25
+ const inner = createListController({
26
+ ...ctx,
27
+ queryClient: innerCache,
28
+ notifications: void 0
29
+ }, resource, {
30
+ ...options,
31
+ enabled: false
32
+ });
33
+ const paramsOf = () => {
34
+ const { page, perPage, sort, filter, displayedFilters, selectedIds } = inner.getSnapshot();
35
+ return {
36
+ page,
37
+ perPage,
38
+ sort,
39
+ filter,
40
+ displayedFilters,
41
+ selectedIds
42
+ };
43
+ };
44
+ const store = createMicroStore({
45
+ ...paramsOf(),
46
+ data: void 0,
47
+ total: void 0,
48
+ pageInfo: void 0,
49
+ isPending: true,
50
+ isFetching: false,
51
+ isFetchingNextPage: false,
52
+ isFetchingPreviousPage: false,
53
+ error: null,
54
+ hasNextPage: void 0,
55
+ hasPreviousPage: void 0
56
+ }, { onSubscribed: acquire });
57
+ let destroyed = false;
58
+ store.onDestroy(() => {
59
+ destroyed = true;
60
+ inner.destroy();
61
+ innerCache.clear();
62
+ });
63
+ /** Non-null exactly while attached. */
64
+ let observer = null;
65
+ /** The params the observer's options were last built from. */
66
+ let applied = null;
67
+ /**
68
+ * The page load or refetch this controller has running. A second load
69
+ * started on the same query would cancel the first (query-core's default),
70
+ * so another load waits for it instead.
71
+ */
72
+ let busy = null;
73
+ /** Sort and filter of a request: the infinite fetch and `selectAll` share them. */
74
+ const requestParams = (s) => ({
75
+ sort: s.sort.length === 1 ? s.sort[0] : s.sort,
76
+ filter: {
77
+ ...s.filter,
78
+ ...options.filter ?? {}
79
+ }
80
+ });
81
+ const buildOptions = () => {
82
+ const s = inner.getSnapshot();
83
+ applied = s;
84
+ return {
85
+ ...getInfiniteListOptions(ctx, resource, {
86
+ pagination: {
87
+ page: s.page,
88
+ perPage: s.perPage
89
+ },
90
+ ...requestParams(s)
91
+ }),
92
+ retry: false,
93
+ enabled: options.enabled ?? true
94
+ };
95
+ };
96
+ /** Params and selection live in the inner controller; this copies them over. */
97
+ const mirror = () => {
98
+ store.setState(paramsOf());
99
+ };
100
+ /** The ONLY path that reconfigures the observer: the inner params changed. */
101
+ const onInner = () => {
102
+ mirror();
103
+ if (!observer || !applied) return;
104
+ const next = inner.getSnapshot();
105
+ if (next.page !== applied.page || next.perPage !== applied.perPage || next.sort !== applied.sort || next.filter !== applied.filter) {
106
+ busy = null;
107
+ observer.setOptions(buildOptions());
108
+ }
109
+ };
110
+ const errors = createErrorAnnouncer(ctx.notifications);
111
+ let lastPages;
112
+ let lastRows;
113
+ const onResult = (result) => {
114
+ const pages = result.data?.pages;
115
+ if (pages !== lastPages) {
116
+ lastPages = pages;
117
+ lastRows = pages?.flatMap((page) => page.data);
118
+ }
119
+ const last = pages?.[pages.length - 1];
120
+ store.setState({
121
+ data: lastRows,
122
+ total: last?.total,
123
+ pageInfo: last?.pageInfo,
124
+ isPending: result.isPending,
125
+ isFetching: result.isFetching,
126
+ isFetchingNextPage: result.isFetchingNextPage,
127
+ isFetchingPreviousPage: result.isFetchingPreviousPage,
128
+ error: result.error ?? null,
129
+ hasNextPage: pages ? result.hasNextPage : void 0,
130
+ hasPreviousPage: pages ? result.hasPreviousPage : void 0
131
+ });
132
+ if (result.error) errors.announce(result.error);
133
+ else errors.rearm();
134
+ };
135
+ function acquire() {
136
+ const unsubscribeInner = inner.subscribe(onInner);
137
+ mirror();
138
+ const attached = new InfiniteQueryObserver(ctx.queryClient, buildOptions());
139
+ observer = attached;
140
+ const unsubscribeObserver = attached.subscribe(onResult);
141
+ onResult(attached.getCurrentResult());
142
+ return () => {
143
+ unsubscribeObserver();
144
+ unsubscribeInner();
145
+ observer = null;
146
+ applied = null;
147
+ lastPages = void 0;
148
+ lastRows = void 0;
149
+ busy = null;
150
+ };
151
+ }
152
+ const through = (action) => (...args) => {
153
+ const result = action(...args);
154
+ if (!destroyed) mirror();
155
+ return result;
156
+ };
157
+ /** Records `promise` as the running load until it settles. */
158
+ const track = (kind, promise) => {
159
+ const entry = {
160
+ kind,
161
+ promise
162
+ };
163
+ busy = entry;
164
+ promise.then(() => {
165
+ if (busy === entry) busy = null;
166
+ });
167
+ return promise;
168
+ };
169
+ const swallow = () => void 0;
170
+ const fetchMore = (direction) => {
171
+ if (destroyed || !observer) return Promise.resolve();
172
+ if (busy) {
173
+ if (busy.kind === direction) return busy.promise;
174
+ return busy.promise.then(() => fetchMore(direction));
175
+ }
176
+ const current = observer.getCurrentResult();
177
+ if (direction === "next" ? !current.hasNextPage : !current.hasPreviousPage) return Promise.resolve();
178
+ const started = direction === "next" ? observer.fetchNextPage() : observer.fetchPreviousPage();
179
+ return track(direction, started.then(swallow, swallow));
180
+ };
181
+ const refetch = () => {
182
+ if (destroyed || !observer) return;
183
+ if (busy) {
184
+ if (busy.kind !== "refetch") busy.promise.then(refetch);
185
+ return;
186
+ }
187
+ track("refetch", observer.refetch().then(swallow, swallow));
188
+ };
189
+ const selectAll = (opts) => runSelectAll(ctx.notifications, async (limit) => {
190
+ const s = inner.getSnapshot();
191
+ return (await ctx.queryClient.fetchQuery({
192
+ ...getListOptions(ctx, resource, {
193
+ pagination: {
194
+ page: 1,
195
+ perPage: limit
196
+ },
197
+ ...requestParams(s)
198
+ }),
199
+ retry: false
200
+ })).data;
201
+ }, (ids) => {
202
+ inner.select(ids);
203
+ mirror();
204
+ }, () => destroyed, opts?.limit);
205
+ return {
206
+ ...store,
207
+ setPage: through(inner.setPage),
208
+ setPerPage: through(inner.setPerPage),
209
+ setSort: through(inner.setSort),
210
+ setFilters: through(inner.setFilters),
211
+ setParams: through(inner.setParams),
212
+ showFilter: through(inner.showFilter),
213
+ hideFilter: through(inner.hideFilter),
214
+ select: through(inner.select),
215
+ toggleItem: through(inner.toggleItem),
216
+ unselectAll: through(inner.unselectAll),
217
+ selectAll,
218
+ refetch,
219
+ fetchNextPage: () => fetchMore("next"),
220
+ fetchPreviousPage: () => fetchMore("previous")
221
+ };
190
222
  }
191
- export {
192
- createInfiniteListController
193
- };
223
+ export { createInfiniteListController };
@@ -0,0 +1,49 @@
1
+ import { type MicroStore } from "../store/micro-store.js";
2
+ import { type DataTableColumnInfo } from "./data-table-columns.js";
3
+ /**
4
+ * What the panel edits. `editor` is generic so each adapter stores its own
5
+ * editor type (a React node, a Vue component).
6
+ */
7
+ export interface InspectorSelection<TEditor> {
8
+ key: string;
9
+ title?: string;
10
+ editor: TEditor;
11
+ }
12
+ export interface InspectorState<TEditor> {
13
+ enabled: boolean;
14
+ selected: InspectorSelection<TEditor> | null;
15
+ }
16
+ export interface Inspector<TEditor> {
17
+ store: MicroStore<InspectorState<TEditor>>;
18
+ enable(): void;
19
+ /** Also clears the selection. */
20
+ disable(): void;
21
+ toggle(): void;
22
+ /** Replaces the selection while enabled and does nothing while disabled. */
23
+ select(selection: InspectorSelection<TEditor>): void;
24
+ /** Clears the selection only when its key is `key`, so a stale unmount cannot clear a newer one. */
25
+ release(key: string): void;
26
+ }
27
+ export declare function createInspector<TEditor = unknown>(): Inspector<TEditor>;
28
+ export declare const inspectorPositionKey = "chelone.inspector.position";
29
+ export interface InspectorPosition {
30
+ x: number;
31
+ y: number;
32
+ }
33
+ export interface InspectorViewport {
34
+ viewportWidth: number;
35
+ viewportHeight: number;
36
+ panelWidth: number;
37
+ margin?: number;
38
+ handleHeight?: number;
39
+ }
40
+ /**
41
+ * Keeps the panel's drag handle on screen. A missing or non-finite position
42
+ * gives the top right corner.
43
+ */
44
+ export declare function clampInspectorPosition(position: InspectorPosition | null | undefined, { viewportWidth, viewportHeight, panelWidth, margin, handleHeight, }: InspectorViewport): InspectorPosition;
45
+ export declare function defaultConfigurableInputsStoreKey(resource: string): string;
46
+ export declare function configurableInputColumns(inputs: readonly {
47
+ source?: unknown;
48
+ label?: unknown;
49
+ }[]): DataTableColumnInfo[];