@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
@@ -1,266 +1,263 @@
1
+ import { isFilterBranch } from "../data/filter-branch.js";
2
+ import { removeEmpty } from "../data/remove-empty.js";
1
3
  import { getAtPath } from "../resource/get-at-path.js";
2
4
  import { createMicroStore } from "../store/micro-store.js";
3
- import { removeFilterPath, listParamsFromPatch } from "./list-controller.js";
4
- const DEFAULT_PER_PAGE = 1e3;
5
- const DEFAULT_SORT = [];
5
+ import { listParamsFromPatch, removeFilterPath } from "./list-controller.js";
6
+ // src/controller/client-list-controller.ts
7
+ var DEFAULT_PER_PAGE = 1e3;
8
+ var DEFAULT_SORT = [];
6
9
  function normalizeSort(sort) {
7
- if (!sort) return DEFAULT_SORT.map((entry) => ({ ...entry }));
8
- const entries = Array.isArray(sort) ? sort : [sort];
9
- return entries.map((entry) => ({ ...entry }));
10
- }
11
- function removeEmpty(filter) {
12
- const result = {};
13
- for (const [key, value] of Object.entries(filter)) {
14
- if (value === "" || value === null || value === void 0) continue;
15
- result[key] = value;
16
- }
17
- return result;
10
+ if (!sort) return DEFAULT_SORT.map((entry) => ({ ...entry }));
11
+ return (Array.isArray(sort) ? sort : [sort]).map((entry) => ({ ...entry }));
18
12
  }
13
+ /**
14
+ * Flatten a nested filter object into dotted keys (`{nested:{level:5}}` →
15
+ * `{'nested.level':5}`), matching ra-core's `flattenObject`. Arrays and
16
+ * non-plain values are treated as LEAVES (never recursed).
17
+ */
19
18
  function flattenFilter(filter, prefix = "") {
20
- const result = {};
21
- for (const [key, value] of Object.entries(filter)) {
22
- const path = prefix ? `${prefix}.${key}` : key;
23
- if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
24
- Object.assign(result, flattenFilter(value, path));
25
- } else {
26
- result[path] = value;
27
- }
28
- }
29
- return result;
19
+ const result = {};
20
+ for (const [key, value] of Object.entries(filter)) {
21
+ const path = prefix ? `${prefix}.${key}` : key;
22
+ if (isFilterBranch(value)) Object.assign(result, flattenFilter(value, path));
23
+ else result[path] = value;
24
+ }
25
+ return result;
30
26
  }
27
+ /** Does one record satisfy every flattened filter entry (ra-core semantics)? */
31
28
  function matchesFilter(record, flatFilter) {
32
- return Object.entries(flatFilter).every(([filterName, filterValue]) => {
33
- const recordValue = getAtPath(record, filterName);
34
- if (Array.isArray(recordValue)) {
35
- return Array.isArray(filterValue) ? recordValue.some((item) => filterValue.includes(item)) : recordValue.includes(filterValue);
36
- }
37
- if (Array.isArray(filterValue)) {
38
- return filterValue.includes(recordValue);
39
- }
40
- if (filterName === "q") {
41
- const needle = String(filterValue).toLowerCase();
42
- return Object.values(record).some(
43
- (value) => typeof value === "string" && value.toLowerCase().includes(needle)
44
- );
45
- }
46
- return filterValue == recordValue;
47
- });
29
+ return Object.entries(flatFilter).every(([filterName, filterValue]) => {
30
+ const recordValue = getAtPath(record, filterName);
31
+ if (Array.isArray(recordValue)) return Array.isArray(filterValue) ? recordValue.some((item) => filterValue.includes(item)) : recordValue.includes(filterValue);
32
+ if (Array.isArray(filterValue)) return filterValue.includes(recordValue);
33
+ if (filterName === "q") {
34
+ const needle = String(filterValue).toLowerCase();
35
+ return Object.values(record).some((value) => typeof value === "string" && value.toLowerCase().includes(needle));
36
+ }
37
+ return filterValue == recordValue;
38
+ });
48
39
  }
49
40
  function applyFilterAndSort(records, filter, sort, filterCallback) {
50
- let result = records;
51
- const flatFilter = flattenFilter(filter);
52
- if (Object.keys(flatFilter).length > 0) {
53
- result = result.filter((record) => matchesFilter(record, flatFilter));
54
- }
55
- result = result.filter(filterCallback);
56
- const keys = sort.filter((entry) => entry.field);
57
- if (keys.length > 0) {
58
- result = [...result].sort((a, b) => {
59
- for (const { field, order } of keys) {
60
- const asc = order === "ASC";
61
- const av = getAtPath(a, field);
62
- const bv = getAtPath(b, field);
63
- if (av === bv || av == null && bv == null) continue;
64
- if (av == null) return asc ? -1 : 1;
65
- if (bv == null) return asc ? 1 : -1;
66
- if (av > bv) return asc ? 1 : -1;
67
- if (av < bv) return asc ? -1 : 1;
68
- }
69
- return 0;
70
- });
71
- }
72
- return result;
41
+ let result = records;
42
+ const flatFilter = flattenFilter(filter);
43
+ if (Object.keys(flatFilter).length > 0) result = result.filter((record) => matchesFilter(record, flatFilter));
44
+ result = result.filter(filterCallback);
45
+ const keys = sort.filter((entry) => entry.field);
46
+ if (keys.length > 0) result = [...result].sort((a, b) => {
47
+ for (const { field, order } of keys) {
48
+ const asc = order === "ASC";
49
+ const av = getAtPath(a, field);
50
+ const bv = getAtPath(b, field);
51
+ if (av === bv || av == null && bv == null) continue;
52
+ if (av == null) return asc ? -1 : 1;
53
+ if (bv == null) return asc ? 1 : -1;
54
+ if (av > bv) return asc ? 1 : -1;
55
+ if (av < bv) return asc ? -1 : 1;
56
+ }
57
+ return 0;
58
+ });
59
+ return result;
73
60
  }
61
+ /**
62
+ * Vanilla client-side list controller. Pure state + closures over a
63
+ * {@link createMicroStore}; NO QueryObserver, NO subscriptions to acquire —
64
+ * every derived value is recomputed synchronously from the current params +
65
+ * source data. Selection is local (never KV-synced — an in-memory list has no
66
+ * cross-instance identity).
67
+ *
68
+ * Shape parity: it satisfies {@link ListController} so the React `useList` hook
69
+ * returns the SAME `UseListControllerResult` a `<ListContextProvider>` /
70
+ * `useListContext` consumer already reads.
71
+ */
74
72
  function createClientListController(options) {
75
- const filterCallback = options.filterCallback ?? ((record) => Boolean(record));
76
- let source = options.data;
77
- const store = createMicroStore({
78
- page: options.page ?? 1,
79
- perPage: options.perPage ?? DEFAULT_PER_PAGE,
80
- sort: normalizeSort(options.sort),
81
- filter: removeEmpty(options.filter ?? {}),
82
- displayedFilters: {},
83
- selectedIds: [],
84
- data: void 0,
85
- total: void 0,
86
- pageInfo: void 0,
87
- isPending: options.isPending ?? false,
88
- isFetching: options.isFetching ?? false,
89
- error: options.error ?? null,
90
- hasNextPage: void 0,
91
- hasPreviousPage: void 0
92
- });
93
- let destroyed = false;
94
- store.onDestroy(() => {
95
- destroyed = true;
96
- });
97
- const recompute = () => {
98
- const s = store.getSnapshot();
99
- if (s.isPending || source === void 0) {
100
- store.setState({
101
- data: void 0,
102
- total: void 0,
103
- hasNextPage: false,
104
- hasPreviousPage: false
105
- });
106
- return;
107
- }
108
- const filteredAndSorted = applyFilterAndSort(
109
- source,
110
- s.filter,
111
- s.sort,
112
- filterCallback
113
- );
114
- const total = filteredAndSorted.length;
115
- const lastPage = Math.max(Math.ceil(total / s.perPage), 1);
116
- const page = Math.min(Math.max(s.page, 1), lastPage);
117
- const start = (page - 1) * s.perPage;
118
- const paginated = filteredAndSorted.slice(start, start + s.perPage);
119
- store.setState({
120
- page,
121
- data: paginated,
122
- total,
123
- hasNextPage: page * s.perPage < total,
124
- hasPreviousPage: page > 1
125
- });
126
- };
127
- recompute();
128
- const setPage = (page) => {
129
- if (destroyed) return;
130
- store.setState({ page });
131
- recompute();
132
- };
133
- const setPerPage = (perPage) => {
134
- if (destroyed) return;
135
- store.setState({ perPage, page: 1 });
136
- recompute();
137
- };
138
- const setSort = (input) => {
139
- if (destroyed) return;
140
- const current = store.getSnapshot().sort;
141
- const single = current.length === 1 ? current[0] : void 0;
142
- let next;
143
- if (Array.isArray(input)) {
144
- next = input.map((entry) => ({ ...entry }));
145
- } else if (single && single.field === input.field) {
146
- next = [
147
- { field: input.field, order: single.order === "ASC" ? "DESC" : "ASC" }
148
- ];
149
- } else {
150
- next = [{ field: input.field, order: input.order ?? "ASC" }];
151
- }
152
- store.setState({ sort: next, page: 1 });
153
- recompute();
154
- };
155
- const setFilters = (filter, displayedFilters) => {
156
- if (destroyed) return;
157
- store.setState({
158
- filter: removeEmpty(filter),
159
- displayedFilters: displayedFilters === void 0 ? store.getSnapshot().displayedFilters : { ...displayedFilters },
160
- page: 1
161
- });
162
- recompute();
163
- };
164
- const setParams = (patch) => {
165
- if (destroyed) return;
166
- const next = listParamsFromPatch(patch);
167
- if (Object.keys(next).length === 0) return;
168
- store.setState(next);
169
- recompute();
170
- };
171
- const showFilter = (name, defaultValue) => {
172
- if (destroyed) return;
173
- const s = store.getSnapshot();
174
- store.setState({
175
- filter: defaultValue === void 0 ? s.filter : removeEmpty({ ...s.filter, [name]: defaultValue }),
176
- displayedFilters: { ...s.displayedFilters, [name]: true }
177
- });
178
- recompute();
179
- };
180
- const hideFilter = (name) => {
181
- if (destroyed) return;
182
- const s = store.getSnapshot();
183
- const filter = removeFilterPath(s.filter, name);
184
- const { [name]: _shown, ...displayedFilters } = s.displayedFilters;
185
- store.setState({ filter, displayedFilters, page: 1 });
186
- recompute();
187
- };
188
- const select = (ids) => {
189
- if (destroyed) return;
190
- store.setState({ selectedIds: [...ids] });
191
- };
192
- const toggleItem = (id) => {
193
- if (destroyed) return;
194
- const current = store.getSnapshot().selectedIds;
195
- store.setState({
196
- selectedIds: current.includes(id) ? current.filter((existing) => existing !== id) : [...current, id]
197
- });
198
- };
199
- const unselectAll = () => {
200
- if (destroyed) return;
201
- store.setState({ selectedIds: [] });
202
- };
203
- const selectAll = async (opts) => {
204
- if (destroyed) return;
205
- if (source === void 0) return;
206
- const s = store.getSnapshot();
207
- const filteredAndSorted = applyFilterAndSort(
208
- source,
209
- s.filter,
210
- s.sort,
211
- filterCallback
212
- );
213
- const limited = opts?.limit != null ? filteredAndSorted.slice(0, opts.limit) : filteredAndSorted;
214
- store.setState({ selectedIds: limited.map((record) => record.id) });
215
- };
216
- const refetch = () => {
217
- };
218
- const setData = (data) => {
219
- if (destroyed) return;
220
- source = data;
221
- recompute();
222
- };
223
- const setSourceFilter = (filter) => {
224
- if (destroyed) return;
225
- store.setState({ filter: removeEmpty(filter), page: 1 });
226
- recompute();
227
- };
228
- const setLoadingState = (patch) => {
229
- if (destroyed) return;
230
- const s = store.getSnapshot();
231
- const next = {};
232
- if (patch.isPending !== void 0 && patch.isPending !== s.isPending) {
233
- next.isPending = patch.isPending;
234
- }
235
- if (patch.isFetching !== void 0 && patch.isFetching !== s.isFetching) {
236
- next.isFetching = patch.isFetching;
237
- }
238
- if (patch.error !== void 0 && patch.error !== s.error) {
239
- next.error = patch.error;
240
- }
241
- if (Object.keys(next).length === 0) return;
242
- store.setState(next);
243
- if (next.isPending !== void 0) recompute();
244
- };
245
- return {
246
- ...store,
247
- setPage,
248
- setPerPage,
249
- setSort,
250
- setFilters,
251
- setParams,
252
- showFilter,
253
- hideFilter,
254
- select,
255
- toggleItem,
256
- unselectAll,
257
- selectAll,
258
- refetch,
259
- setData,
260
- setSourceFilter,
261
- setLoadingState
262
- };
73
+ const filterCallback = options.filterCallback ?? ((record) => Boolean(record));
74
+ let source = options.data;
75
+ const store = createMicroStore({
76
+ page: options.page ?? 1,
77
+ perPage: options.perPage ?? DEFAULT_PER_PAGE,
78
+ sort: normalizeSort(options.sort),
79
+ filter: removeEmpty(options.filter ?? {}),
80
+ displayedFilters: {},
81
+ selectedIds: [],
82
+ data: void 0,
83
+ total: void 0,
84
+ pageInfo: void 0,
85
+ isPending: options.isPending ?? false,
86
+ isFetching: options.isFetching ?? false,
87
+ error: options.error ?? null,
88
+ hasNextPage: void 0,
89
+ hasPreviousPage: void 0
90
+ });
91
+ let destroyed = false;
92
+ store.onDestroy(() => {
93
+ destroyed = true;
94
+ });
95
+ /** Recompute the derived slice (data/total/paging flags) from source + params. */
96
+ const recompute = () => {
97
+ const s = store.getSnapshot();
98
+ if (s.isPending || source === void 0) {
99
+ store.setState({
100
+ data: void 0,
101
+ total: void 0,
102
+ hasNextPage: false,
103
+ hasPreviousPage: false
104
+ });
105
+ return;
106
+ }
107
+ const filteredAndSorted = applyFilterAndSort(source, s.filter, s.sort, filterCallback);
108
+ const total = filteredAndSorted.length;
109
+ const lastPage = Math.max(Math.ceil(total / s.perPage), 1);
110
+ const page = Math.min(Math.max(s.page, 1), lastPage);
111
+ const start = (page - 1) * s.perPage;
112
+ const paginated = filteredAndSorted.slice(start, start + s.perPage);
113
+ store.setState({
114
+ page,
115
+ data: paginated,
116
+ total,
117
+ hasNextPage: page * s.perPage < total,
118
+ hasPreviousPage: page > 1
119
+ });
120
+ };
121
+ recompute();
122
+ const setPage = (page) => {
123
+ if (destroyed) return;
124
+ store.setState({ page });
125
+ recompute();
126
+ };
127
+ const setPerPage = (perPage) => {
128
+ if (destroyed) return;
129
+ store.setState({
130
+ perPage,
131
+ page: 1
132
+ });
133
+ recompute();
134
+ };
135
+ const setSort = (input) => {
136
+ if (destroyed) return;
137
+ const current = store.getSnapshot().sort;
138
+ const single = current.length === 1 ? current[0] : void 0;
139
+ let next;
140
+ if (Array.isArray(input)) next = input.map((entry) => ({ ...entry }));
141
+ else if (single && single.field === input.field) next = [{
142
+ field: input.field,
143
+ order: single.order === "ASC" ? "DESC" : "ASC"
144
+ }];
145
+ else next = [{
146
+ field: input.field,
147
+ order: input.order ?? "ASC"
148
+ }];
149
+ store.setState({
150
+ sort: next,
151
+ page: 1
152
+ });
153
+ recompute();
154
+ };
155
+ const setFilters = (filter, displayedFilters) => {
156
+ if (destroyed) return;
157
+ store.setState({
158
+ filter: removeEmpty(filter),
159
+ displayedFilters: displayedFilters === void 0 ? store.getSnapshot().displayedFilters : { ...displayedFilters },
160
+ page: 1
161
+ });
162
+ recompute();
163
+ };
164
+ const setParams = (patch) => {
165
+ if (destroyed) return;
166
+ const next = listParamsFromPatch(patch);
167
+ if (Object.keys(next).length === 0) return;
168
+ store.setState(next);
169
+ recompute();
170
+ };
171
+ const showFilter = (name, defaultValue) => {
172
+ if (destroyed) return;
173
+ const s = store.getSnapshot();
174
+ store.setState({
175
+ filter: defaultValue === void 0 ? s.filter : removeEmpty({
176
+ ...s.filter,
177
+ [name]: defaultValue
178
+ }),
179
+ displayedFilters: {
180
+ ...s.displayedFilters,
181
+ [name]: true
182
+ }
183
+ });
184
+ recompute();
185
+ };
186
+ const hideFilter = (name) => {
187
+ if (destroyed) return;
188
+ const s = store.getSnapshot();
189
+ const filter = removeFilterPath(s.filter, name);
190
+ const { [name]: _shown, ...displayedFilters } = s.displayedFilters;
191
+ store.setState({
192
+ filter,
193
+ displayedFilters,
194
+ page: 1
195
+ });
196
+ recompute();
197
+ };
198
+ const select = (ids) => {
199
+ if (destroyed) return;
200
+ store.setState({ selectedIds: [...ids] });
201
+ };
202
+ const toggleItem = (id) => {
203
+ if (destroyed) return;
204
+ const current = store.getSnapshot().selectedIds;
205
+ store.setState({ selectedIds: current.includes(id) ? current.filter((existing) => existing !== id) : [...current, id] });
206
+ };
207
+ const unselectAll = () => {
208
+ if (destroyed) return;
209
+ store.setState({ selectedIds: [] });
210
+ };
211
+ const selectAll = async (opts) => {
212
+ if (destroyed) return;
213
+ if (source === void 0) return;
214
+ const s = store.getSnapshot();
215
+ const filteredAndSorted = applyFilterAndSort(source, s.filter, s.sort, filterCallback);
216
+ const limited = opts?.limit != null ? filteredAndSorted.slice(0, opts.limit) : filteredAndSorted;
217
+ store.setState({ selectedIds: limited.map((record) => record.id) });
218
+ };
219
+ const refetch = () => {};
220
+ const setData = (data) => {
221
+ if (destroyed) return;
222
+ source = data;
223
+ recompute();
224
+ };
225
+ const setSourceFilter = (filter) => {
226
+ if (destroyed) return;
227
+ store.setState({
228
+ filter: removeEmpty(filter),
229
+ page: 1
230
+ });
231
+ recompute();
232
+ };
233
+ const setLoadingState = (patch) => {
234
+ if (destroyed) return;
235
+ const s = store.getSnapshot();
236
+ const next = {};
237
+ if (patch.isPending !== void 0 && patch.isPending !== s.isPending) next.isPending = patch.isPending;
238
+ if (patch.isFetching !== void 0 && patch.isFetching !== s.isFetching) next.isFetching = patch.isFetching;
239
+ if (patch.error !== void 0 && patch.error !== s.error) next.error = patch.error;
240
+ if (Object.keys(next).length === 0) return;
241
+ store.setState(next);
242
+ if (next.isPending !== void 0) recompute();
243
+ };
244
+ return {
245
+ ...store,
246
+ setPage,
247
+ setPerPage,
248
+ setSort,
249
+ setFilters,
250
+ setParams,
251
+ showFilter,
252
+ hideFilter,
253
+ select,
254
+ toggleItem,
255
+ unselectAll,
256
+ selectAll,
257
+ refetch,
258
+ setData,
259
+ setSourceFilter,
260
+ setLoadingState
261
+ };
263
262
  }
264
- export {
265
- createClientListController
266
- };
263
+ export { createClientListController };
@@ -20,4 +20,6 @@ export declare function arrangeColumns(ids: readonly (string | undefined)[], { h
20
20
  }): number[];
21
21
  export declare function toggleHiddenColumn(hidden: readonly string[], id: string): string[];
22
22
  export declare function moveColumn(order: readonly string[], ids: readonly (string | undefined)[], id: string, direction: -1 | 1): string[];
23
+ /** Moves `id` to `index` in the resolved order, clamping `index` to the ends. */
24
+ export declare function moveColumnTo(order: readonly string[], ids: readonly (string | undefined)[], id: string, index: number): string[];
23
25
  export declare function columnLabelMatches(label: string, query: string): boolean;
@@ -1,73 +1,67 @@
1
+ // src/controller/data-table-columns.ts
1
2
  function defaultDataTableStoreKey(resource) {
2
- return `${resource}.datatable`;
3
+ return `${resource}.datatable`;
3
4
  }
4
5
  function dataTableColumnKeys(storeKey) {
5
- return {
6
- hidden: storeKey,
7
- order: `${storeKey}_columnOrder`,
8
- columns: `${storeKey}_columns`
9
- };
6
+ return {
7
+ hidden: storeKey,
8
+ order: `${storeKey}_columnOrder`,
9
+ columns: `${storeKey}_columns`
10
+ };
10
11
  }
11
- function getDataTableColumnId({
12
- source,
13
- label
14
- }) {
15
- if (typeof source === "string" && source !== "") return source;
16
- if (typeof label === "string" && label !== "") return label;
17
- return void 0;
12
+ function getDataTableColumnId({ source, label }) {
13
+ if (typeof source === "string" && source !== "") return source;
14
+ if (typeof label === "string" && label !== "") return label;
18
15
  }
16
+ /** The distinct ids in display order: the stored order first, then the rest as declared. */
19
17
  function resolveOrder(ids, order) {
20
- const present = /* @__PURE__ */ new Set();
21
- for (const id of ids) if (id !== void 0) present.add(id);
22
- const resolved = /* @__PURE__ */ new Set();
23
- for (const id of order) if (present.has(id)) resolved.add(id);
24
- for (const id of present) resolved.add(id);
25
- return [...resolved];
18
+ const present = /* @__PURE__ */ new Set();
19
+ for (const id of ids) if (id !== void 0) present.add(id);
20
+ const resolved = /* @__PURE__ */ new Set();
21
+ for (const id of order) if (present.has(id)) resolved.add(id);
22
+ for (const id of present) resolved.add(id);
23
+ return [...resolved];
26
24
  }
27
25
  function arrangeColumns(ids, { hidden, order }) {
28
- const indexesById = /* @__PURE__ */ new Map();
29
- ids.forEach((id, index) => {
30
- if (id === void 0) return;
31
- const indexes = indexesById.get(id);
32
- if (indexes) indexes.push(index);
33
- else indexesById.set(id, [index]);
34
- });
35
- const sequence = resolveOrder(ids, order).flatMap(
36
- (id) => indexesById.get(id) ?? []
37
- );
38
- let next = 0;
39
- const arranged = ids.map(
40
- (id, index) => id === void 0 ? index : sequence[next++] ?? index
41
- );
42
- return arranged.filter((index) => {
43
- const id = ids[index];
44
- return id === void 0 || !hidden.includes(id);
45
- });
26
+ const indexesById = /* @__PURE__ */ new Map();
27
+ ids.forEach((id, index) => {
28
+ if (id === void 0) return;
29
+ const indexes = indexesById.get(id);
30
+ if (indexes) indexes.push(index);
31
+ else indexesById.set(id, [index]);
32
+ });
33
+ const sequence = resolveOrder(ids, order).flatMap((id) => indexesById.get(id) ?? []);
34
+ let next = 0;
35
+ return ids.map((id, index) => id === void 0 ? index : sequence[next++] ?? index).filter((index) => {
36
+ const id = ids[index];
37
+ return id === void 0 || !hidden.includes(id);
38
+ });
46
39
  }
47
40
  function toggleHiddenColumn(hidden, id) {
48
- return hidden.includes(id) ? hidden.filter((entry) => entry !== id) : [...hidden, id];
41
+ return hidden.includes(id) ? hidden.filter((entry) => entry !== id) : [...hidden, id];
49
42
  }
50
43
  function moveColumn(order, ids, id, direction) {
51
- const resolved = resolveOrder(ids, order);
52
- const from = resolved.indexOf(id);
53
- const to = from + direction;
54
- if (from < 0 || to < 0 || to >= resolved.length) return resolved;
55
- resolved.splice(to, 0, ...resolved.splice(from, 1));
56
- return resolved;
44
+ const resolved = resolveOrder(ids, order);
45
+ const from = resolved.indexOf(id);
46
+ const to = from + direction;
47
+ if (from < 0 || to < 0 || to >= resolved.length) return resolved;
48
+ resolved.splice(to, 0, ...resolved.splice(from, 1));
49
+ return resolved;
50
+ }
51
+ /** Moves `id` to `index` in the resolved order, clamping `index` to the ends. */
52
+ function moveColumnTo(order, ids, id, index) {
53
+ const resolved = resolveOrder(ids, order);
54
+ const from = resolved.indexOf(id);
55
+ if (from < 0) return resolved;
56
+ const to = Math.min(Math.max(index, 0), resolved.length - 1);
57
+ resolved.splice(to, 0, ...resolved.splice(from, 1));
58
+ return resolved;
57
59
  }
58
60
  function fold(text) {
59
- return text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
61
+ return text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
60
62
  }
61
63
  function columnLabelMatches(label, query) {
62
- if (query === "") return true;
63
- return fold(label).includes(fold(query));
64
+ if (query === "") return true;
65
+ return fold(label).includes(fold(query));
64
66
  }
65
- export {
66
- arrangeColumns,
67
- columnLabelMatches,
68
- dataTableColumnKeys,
69
- defaultDataTableStoreKey,
70
- getDataTableColumnId,
71
- moveColumn,
72
- toggleHiddenColumn
73
- };
67
+ export { arrangeColumns, columnLabelMatches, dataTableColumnKeys, defaultDataTableStoreKey, getDataTableColumnId, moveColumn, moveColumnTo, toggleHiddenColumn };