@scattered-light/loomiere-base-ui 0.6.1 → 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 (100) hide show
  1. package/README.md +135 -5
  2. package/dist/chunk-JUUPOOQQ.js +3723 -0
  3. package/dist/chunk-JUUPOOQQ.js.map +1 -0
  4. package/dist/chunk-M7KJASGK.js +802 -0
  5. package/dist/chunk-M7KJASGK.js.map +1 -0
  6. package/dist/chunk-Y2CPBMFJ.js +147 -0
  7. package/dist/chunk-Y2CPBMFJ.js.map +1 -0
  8. package/dist/custom/collection/index.d.ts +534 -0
  9. package/dist/custom/collection/index.js +1471 -0
  10. package/dist/custom/collection/index.js.map +1 -0
  11. package/dist/custom/index.d.ts +5 -0
  12. package/dist/custom/index.js +4 -0
  13. package/dist/custom/index.js.map +1 -0
  14. package/dist/design-system/components/Badge/README.md +35 -0
  15. package/dist/design-system/components/Badge/preview.html +19 -0
  16. package/dist/design-system/components/Collection/README.md +14 -0
  17. package/dist/design-system/components/CollectionPagination/README.md +15 -0
  18. package/dist/design-system/components/CollectionToolbar/README.md +24 -0
  19. package/dist/design-system/components/DataList/README.md +39 -0
  20. package/dist/design-system/components/DataList/preview.html +475 -0
  21. package/dist/design-system/components/DataTable/README.md +41 -0
  22. package/dist/design-system/components/DataTable/preview.html +475 -0
  23. package/dist/design-system/components/DatePicker/README.md +55 -0
  24. package/dist/design-system/components/DatePicker/preview.html +475 -0
  25. package/dist/design-system/components/EditableList/README.md +33 -0
  26. package/dist/design-system/components/EditableList/preview.html +475 -0
  27. package/dist/design-system/components/EmptyState/README.md +60 -0
  28. package/dist/design-system/components/EmptyState/preview.html +475 -0
  29. package/dist/design-system/components/ListDetail/README.md +43 -0
  30. package/dist/design-system/components/ListDetail/preview.html +475 -0
  31. package/dist/design-system/components/MoneyField/README.md +52 -0
  32. package/dist/design-system/components/MoneyField/preview.html +475 -0
  33. package/dist/design-system/components/Splitter/README.md +42 -0
  34. package/dist/design-system/components/Splitter/preview.html +475 -0
  35. package/dist/design-system/components/Table/README.md +2 -1
  36. package/dist/design-system/components/Table/preview.html +1 -1
  37. package/dist/design-system/components/bundle.js +7 -5
  38. package/dist/design-system/components/index.d.ts +3 -21
  39. package/dist/design-system/components/styles.css +2266 -186
  40. package/dist/index-ChJXlxzq.d.ts +809 -0
  41. package/dist/index.d.ts +3 -21
  42. package/dist/index.js +176 -891
  43. package/dist/index.js.map +1 -1
  44. package/dist/styles/components/Accordion.css +1 -1
  45. package/dist/styles/components/AlertDialog.css +1 -1
  46. package/dist/styles/components/Autocomplete.css +1 -1
  47. package/dist/styles/components/Avatar.css +1 -1
  48. package/dist/styles/components/Button.css +1 -1
  49. package/dist/styles/components/Checkbox.css +1 -1
  50. package/dist/styles/components/CheckboxGroup.css +1 -1
  51. package/dist/styles/components/Collapsible.css +1 -1
  52. package/dist/styles/components/Combobox.css +1 -1
  53. package/dist/styles/components/ContextMenu.css +1 -1
  54. package/dist/styles/components/Dialog.css +1 -1
  55. package/dist/styles/components/Drawer.css +1 -1
  56. package/dist/styles/components/Field.css +1 -1
  57. package/dist/styles/components/Fieldset.css +1 -1
  58. package/dist/styles/components/Form.css +1 -1
  59. package/dist/styles/components/Input.css +1 -1
  60. package/dist/styles/components/Menu.css +1 -1
  61. package/dist/styles/components/Menubar.css +1 -1
  62. package/dist/styles/components/Meter.css +1 -1
  63. package/dist/styles/components/NavigationMenu.css +1 -1
  64. package/dist/styles/components/NumberField.css +1 -1
  65. package/dist/styles/components/OTPField.css +1 -1
  66. package/dist/styles/components/Popover.css +1 -1
  67. package/dist/styles/components/PreviewCard.css +1 -1
  68. package/dist/styles/components/Progress.css +1 -1
  69. package/dist/styles/components/Radio.css +1 -1
  70. package/dist/styles/components/ScrollArea.css +1 -1
  71. package/dist/styles/components/Select.css +1 -1
  72. package/dist/styles/components/Separator.css +1 -1
  73. package/dist/styles/components/Slider.css +1 -1
  74. package/dist/styles/components/Switch.css +1 -1
  75. package/dist/styles/components/Tabs.css +1 -1
  76. package/dist/styles/components/Toast.css +1 -1
  77. package/dist/styles/components/Toggle.css +1 -1
  78. package/dist/styles/components/ToggleGroup.css +1 -1
  79. package/dist/styles/components/Toolbar.css +1 -1
  80. package/dist/styles/components/Tooltip.css +1 -1
  81. package/dist/styles/custom/Badge.css +104 -0
  82. package/dist/styles/custom/Collection.css +15 -0
  83. package/dist/styles/custom/CollectionPagination.css +24 -0
  84. package/dist/styles/custom/CollectionToolbar.css +57 -0
  85. package/dist/styles/custom/DataList.css +123 -0
  86. package/dist/styles/custom/DataTable.css +120 -0
  87. package/dist/styles/custom/DatePicker.css +572 -0
  88. package/dist/styles/custom/EditableList.css +98 -0
  89. package/dist/styles/custom/EmptyState.css +255 -0
  90. package/dist/styles/custom/ListDetail.css +126 -0
  91. package/dist/styles/custom/MoneyField.css +344 -0
  92. package/dist/styles/custom/Splitter.css +207 -0
  93. package/dist/styles/{components → custom}/Table.css +31 -4
  94. package/dist/styles/index.css +12 -2
  95. package/dist/styles/reset/accessibility.css +1 -1
  96. package/dist/styles/reset/reset-accessibility.css +1 -1
  97. package/dist/styles/reset/reset-tailwind.css +1 -1
  98. package/dist/styles/reset/reset.css +1 -1
  99. package/dist/styles/tailwind.css +12 -2
  100. package/package.json +35 -7
@@ -0,0 +1,1471 @@
1
+ import { Input, Tabs, Checkbox, Menu } from '../../chunk-Y2CPBMFJ.js';
2
+ import { useUnderWidth, Button, cn, Select, Combobox, isEmptyValue, formatPercent, formatNumber, sumMoney, fractionDigitsOf, formatMoney, minorToDecimal, EmptyState, selectableIds, pageFlat, hiddenSelectedCount, nextSort, searchMatches, treeVisibleIds, isoDate, toMinor, comparePresent, collatorFor, formatDate, moneySearchForms, searchHaystack } from '../../chunk-M7KJASGK.js';
3
+ export { balanceOf, compareDecimal, countFacets, formatDate, formatMoney, hiddenSelectedCount, nextSort, pageBounds, pageFlat, searchMatches, selectableIds, sortRows, sumMoney, toMinor, treeVisibleIds } from '../../chunk-M7KJASGK.js';
4
+ import * as React4 from 'react';
5
+ import { useMemo, useRef, useState, useDeferredValue, useEffect } from 'react';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import { tableFeatures, columnVisibilityFeature, columnOrderingFeature, rowExpandingFeature, columnGroupingFeature, rowSelectionFeature, rowPaginationFeature, columnFacetingFeature, globalFilteringFeature, columnFilteringFeature, rowSortingFeature, createFacetedUniqueValues, createFacetedRowModel, createPaginatedRowModel, createExpandedRowModel, createGroupedRowModel, createFilteredRowModel, createSortedRowModel, useTable } from '@tanstack/react-table';
8
+
9
+ // src/custom/collection/columns.ts
10
+ function headerLabel(column) {
11
+ return typeof column.header === "string" ? column.header : column.header.label;
12
+ }
13
+ function defineColumns(columns) {
14
+ if (process.env.NODE_ENV !== "production") {
15
+ const headers = columns.filter((column) => column.rowHeader);
16
+ if (headers.length !== 1) {
17
+ console.warn(`A collection should have exactly one rowHeader column so each row has a name. This one has ${headers.length}.`);
18
+ }
19
+ const tabs = columns.filter((column) => column.filter?.kind === "tabs");
20
+ if (tabs.length > 1) console.warn("Only one column can use a tabs filter. The toolbar shows the first.");
21
+ }
22
+ return columns;
23
+ }
24
+ function defaultAlign(column) {
25
+ if (column.align) return column.align;
26
+ const kind = column.kind ?? "text";
27
+ if (kind === "number" || kind === "money" || kind === "percent") return "end";
28
+ return "start";
29
+ }
30
+ function listSlot(column, columns) {
31
+ if (column.list) return column.list;
32
+ if (column.rowHeader) return "primary";
33
+ if (column.kind === "money") return "amount";
34
+ if (column.filter?.kind === "tabs") return "status";
35
+ if (column.cell && column.kind == null && column.id.toLowerCase().includes("status")) return "status";
36
+ const primary = columns.find((item) => item.rowHeader) ?? columns[0];
37
+ const secondary = columns.find((item) => item !== primary && item.searchable && (item.kind ?? "text") === "text" && !item.rowHeader);
38
+ if (column === secondary) return "secondary";
39
+ const meta = columns.filter((item) => item !== primary && item !== secondary && item.kind !== "money" && item.filter?.kind !== "tabs");
40
+ if (meta.slice(0, 3).includes(column)) return "meta";
41
+ return "hidden";
42
+ }
43
+ function currencyOf(column, row) {
44
+ if (typeof column.currency === "function") return column.currency(row);
45
+ return column.currency ?? "USD";
46
+ }
47
+
48
+ // src/custom/collection/format.ts
49
+ function rowSearchText(row, columns, locale) {
50
+ const parts = [];
51
+ for (const column of columns) {
52
+ if (column.searchable == null || column.searchable === false) continue;
53
+ const value = column.value(row);
54
+ if (typeof column.searchable === "function") {
55
+ const extra = column.searchable(row);
56
+ parts.push(...Array.isArray(extra) ? extra : [extra]);
57
+ }
58
+ if (isEmptyValue(value)) continue;
59
+ if (column.kind === "money") {
60
+ parts.push(...moneySearchForms(value, currencyOf(column, row), locale, column.fractionDigits));
61
+ } else if (column.kind === "number" || column.kind === "percent") {
62
+ parts.push(String(value), formatNumber(value, locale, column.fractionDigits));
63
+ } else if (column.kind === "date") {
64
+ parts.push(String(value), formatDate(value, locale, column.dateFormat), isoDate(value));
65
+ } else {
66
+ parts.push(String(value));
67
+ }
68
+ }
69
+ return searchHaystack(parts);
70
+ }
71
+ function formattedValue(column, row, locale, hideCurrency = false) {
72
+ const value = column.value(row);
73
+ if (isEmptyValue(value)) return "";
74
+ if (column.kind === "money") {
75
+ const currency = currencyOf(column, row);
76
+ if (hideCurrency) {
77
+ const digits = column.fractionDigits ?? fractionDigitsOf(currency);
78
+ return formatNumber(value, locale, digits);
79
+ }
80
+ return formatMoney(value, {
81
+ locale,
82
+ currency,
83
+ fractionDigits: column.fractionDigits,
84
+ negative: column.negative
85
+ });
86
+ }
87
+ if (column.kind === "number") return formatNumber(value, locale, column.fractionDigits);
88
+ if (column.kind === "percent") return formatPercent(value, locale);
89
+ if (column.kind === "date") return formatDate(value, locale, column.dateFormat);
90
+ if (column.kind === "boolean") return value ? "Yes" : "No";
91
+ return String(value);
92
+ }
93
+ function columnSharesCurrency(column, rows) {
94
+ if (column.kind !== "money") return null;
95
+ const currencies = new Set(rows.map((row) => currencyOf(column, row)));
96
+ return currencies.size === 1 ? [...currencies][0] : null;
97
+ }
98
+ function displayHeader(column, rows) {
99
+ const label = headerLabel(column);
100
+ if (!column.showCurrencyInHeader) return label;
101
+ const currency = columnSharesCurrency(column, rows);
102
+ return currency ? `${label} (${currency})` : label;
103
+ }
104
+ function hideCurrencyInCells(column, rows) {
105
+ return Boolean(column.showCurrencyInHeader && columnSharesCurrency(column, rows));
106
+ }
107
+ function totalText(column, rows, locale) {
108
+ if (!column.total) return "";
109
+ if (column.total === "count") return String(rows.length);
110
+ if (typeof column.total === "function") return "";
111
+ if (column.kind === "number" || column.kind === "percent") {
112
+ let sum2 = 0;
113
+ for (const row of rows) {
114
+ const value = column.value(row);
115
+ if (isEmptyValue(value)) continue;
116
+ const numeric = typeof value === "number" ? value : Number(String(value).replace(/,/g, ""));
117
+ if (Number.isFinite(numeric)) sum2 += numeric;
118
+ }
119
+ return column.kind === "percent" ? formatPercent(sum2, locale) : formatNumber(sum2, locale, column.fractionDigits);
120
+ }
121
+ const values = rows.map((row) => ({ amount: column.value(row), currency: currencyOf(column, row) }));
122
+ const sum = sumMoney(values, column.fractionDigits);
123
+ if (sum.mixed) {
124
+ if (process.env.NODE_ENV !== "production") console.warn(`Column "${column.id}" has more than one currency, so it has no single total.`);
125
+ return "Mixed currencies";
126
+ }
127
+ const currency = sum.currency ?? (typeof column.currency === "string" ? column.currency : "USD");
128
+ const digits = column.fractionDigits ?? fractionDigitsOf(currency);
129
+ return formatMoney(minorToDecimal(sum.minor ?? 0n, digits), {
130
+ locale,
131
+ currency,
132
+ fractionDigits: digits,
133
+ negative: column.negative
134
+ });
135
+ }
136
+ function rowHeaderText(collection, row) {
137
+ const column = collection.columns.find((item) => item.id === collection.rowHeaderId);
138
+ if (!column) return collection.getRowId(row);
139
+ const value = column.value(row);
140
+ return value == null || value === "" ? "None" : String(value);
141
+ }
142
+ function levelName(collection, tableRow, text) {
143
+ if (!tableRow.depth) return text;
144
+ const parent = tableRow.getParentRow?.();
145
+ const parentText = parent ? rowHeaderText(collection, parent.original) : "";
146
+ return parentText ? `${text}, level ${tableRow.depth + 1}, in ${parentText}` : `${text}, level ${tableRow.depth + 1}`;
147
+ }
148
+ function CellValue({ column, row, locale, rows, view, selected, active }) {
149
+ const ctx = { view, selected, active };
150
+ if (column.cell) return /* @__PURE__ */ jsx(Fragment, { children: column.cell(row, ctx) });
151
+ const value = column.value(row);
152
+ if (value == null || value === "") {
153
+ return /* @__PURE__ */ jsxs("span", { "data-subtle": "", children: [
154
+ "\u2014",
155
+ /* @__PURE__ */ jsx("span", { className: "visually-hidden", children: " None" })
156
+ ] });
157
+ }
158
+ if (column.kind === "boolean") {
159
+ return /* @__PURE__ */ jsxs("span", { children: [
160
+ value ? /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "check" }) : null,
161
+ /* @__PURE__ */ jsx("span", { className: "visually-hidden", children: value ? "Yes" : "No" })
162
+ ] });
163
+ }
164
+ if (column.kind === "date") {
165
+ const text = formattedValue(column, row, locale);
166
+ return /* @__PURE__ */ jsx("time", { dateTime: isoDate(value), children: text });
167
+ }
168
+ const negative = column.kind === "money" && (toMinor(value, column.fractionDigits ?? 2) ?? 0n) < 0n;
169
+ return /* @__PURE__ */ jsx("span", { "data-negative": negative ? "" : void 0, children: formattedValue(column, row, locale, hideCurrencyInCells(column, rows)) });
170
+ }
171
+ function SelectBox({ checked, indeterminate, label, onChange }) {
172
+ return /* @__PURE__ */ jsx(
173
+ Checkbox.Root,
174
+ {
175
+ checked,
176
+ indeterminate,
177
+ onCheckedChange: (value) => onChange(value === true),
178
+ "aria-label": label,
179
+ children: /* @__PURE__ */ jsx(Checkbox.Indicator, { children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined CheckboxIcon", "aria-hidden": "true", children: indeterminate ? "remove" : "check" }) })
180
+ }
181
+ );
182
+ }
183
+ function RowMenu({ label, actions }) {
184
+ return /* @__PURE__ */ jsxs(Menu.Root, { children: [
185
+ /* @__PURE__ */ jsx(Menu.Trigger, { className: "Button", "data-emphasis": "quiet", "data-icon-only": "", "aria-label": label, children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "more_horiz" }) }),
186
+ /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { children: /* @__PURE__ */ jsx(Menu.Popup, { children: actions.map((action) => /* @__PURE__ */ jsx(Menu.Item, { disabled: action.disabled, "data-color": action.tone === "danger" ? "error" : void 0, onClick: action.onSelect, children: action.label }, action.label)) }) }) })
187
+ ] });
188
+ }
189
+ function useRovingRows(count, enabled) {
190
+ const [cursor, setCursor] = React4.useState(0);
191
+ const refs = React4.useRef([]);
192
+ React4.useEffect(() => {
193
+ if (cursor > count - 1) setCursor(Math.max(0, count - 1));
194
+ }, [count, cursor]);
195
+ const focusAt = (index) => {
196
+ const next = Math.min(Math.max(0, index), Math.max(0, count - 1));
197
+ setCursor(next);
198
+ refs.current[next]?.focus();
199
+ };
200
+ const onKeyDown = (event, index, toggle, activate, tree) => {
201
+ const target = event.target;
202
+ if (target.closest('input, textarea, select, [role="menu"], [role="listbox"]')) return;
203
+ const rtl = Boolean(target.closest('[dir="rtl"]'));
204
+ const openKey = rtl ? "ArrowLeft" : "ArrowRight";
205
+ const closeKey = rtl ? "ArrowRight" : "ArrowLeft";
206
+ if (tree && event.key === openKey && !tree.expanded) {
207
+ event.preventDefault();
208
+ tree.toggle();
209
+ return;
210
+ }
211
+ if (tree && event.key === closeKey && tree.expanded) {
212
+ event.preventDefault();
213
+ tree.toggle();
214
+ return;
215
+ }
216
+ if (!enabled) return;
217
+ if (event.key === "ArrowDown") {
218
+ event.preventDefault();
219
+ focusAt(index + 1);
220
+ } else if (event.key === "ArrowUp") {
221
+ event.preventDefault();
222
+ focusAt(index - 1);
223
+ } else if (event.key === "Home") {
224
+ event.preventDefault();
225
+ focusAt(0);
226
+ } else if (event.key === "End") {
227
+ event.preventDefault();
228
+ focusAt(count - 1);
229
+ } else if (event.key === " " && toggle) {
230
+ event.preventDefault();
231
+ toggle();
232
+ } else if (event.key === "Enter" && !(event.target instanceof HTMLAnchorElement) && activate) {
233
+ event.preventDefault();
234
+ activate();
235
+ }
236
+ };
237
+ return { cursor, refs, focusAt, onKeyDown };
238
+ }
239
+ function interactiveTarget(target) {
240
+ return target instanceof Element && Boolean(target.closest('a, button, input, textarea, select, label, [role="menu"], [role="menuitem"], [role="listbox"], [role="checkbox"], [role="option"], [data-no-activate]'));
241
+ }
242
+ function selectingText() {
243
+ const selection = typeof window === "undefined" ? null : window.getSelection();
244
+ return Boolean(selection && !selection.isCollapsed);
245
+ }
246
+ function EmptyRow({ colSpan, filtered, onClear, empty, title = "No results", description = "Nothing matches the current search or filters.", clearLabel = "Clear filters", emptyTitle = "Nothing here yet" }) {
247
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { "data-empty": "", colSpan, children: filtered ? /* @__PURE__ */ jsx(
248
+ EmptyState,
249
+ {
250
+ size: "compact",
251
+ kind: "no-results",
252
+ title,
253
+ description,
254
+ actions: /* @__PURE__ */ jsx(Button, { emphasis: "standard", onClick: onClear, children: clearLabel })
255
+ }
256
+ ) : empty ?? /* @__PURE__ */ jsx(EmptyState, { size: "compact", kind: "start", title: emptyTitle }) }) });
257
+ }
258
+ function LoadingNote({ text }) {
259
+ const [live, setLive] = React4.useState("");
260
+ React4.useEffect(() => {
261
+ const frame = requestAnimationFrame(() => setLive(text));
262
+ return () => cancelAnimationFrame(frame);
263
+ }, [text]);
264
+ return /* @__PURE__ */ jsx("span", { className: "CollectionStatus visually-hidden", role: "status", children: live });
265
+ }
266
+ function alignOf(column) {
267
+ const align = defaultAlign(column);
268
+ return align === "start" ? void 0 : align;
269
+ }
270
+ function DataList({
271
+ collection,
272
+ label,
273
+ onRowActivate,
274
+ getRowHref,
275
+ activeRowId,
276
+ rowActions,
277
+ empty,
278
+ loading,
279
+ rowNavigation,
280
+ size = "sm",
281
+ className,
282
+ variant = "rows",
283
+ totals = "summary",
284
+ renderItemExtra
285
+ }) {
286
+ const leaves = collection.pageEntries.filter((entry) => entry.kind === "leaf");
287
+ const navigate = rowNavigation ?? Boolean(onRowActivate || getRowHref);
288
+ const roving = useRovingRows(leaves.length, navigate);
289
+ React4.useEffect(() => {
290
+ collection.registerRowFocus(() => roving.focusAt(0));
291
+ return () => collection.registerRowFocus(null);
292
+ }, [collection, roving]);
293
+ React4.useLayoutEffect(() => {
294
+ if (!collection.focusAfterPage.current) return;
295
+ collection.focusAfterPage.current = false;
296
+ roving.focusAt(0);
297
+ }, [collection, collection.page, roving]);
298
+ const slots = slotMap(collection);
299
+ const filteredEmpty = collection.totalCount > 0 && collection.filteredCount === 0;
300
+ return /* @__PURE__ */ jsxs(
301
+ "div",
302
+ {
303
+ className: cn("DataList", className),
304
+ "data-view": "list",
305
+ "data-size": size,
306
+ "data-variant": variant,
307
+ "data-filtered": collection.isFiltered ? "" : void 0,
308
+ "data-selecting": collection.selection.length ? "" : void 0,
309
+ "data-loading": loading ? "" : void 0,
310
+ "data-grouped": collection.grouping ? "" : void 0,
311
+ "aria-busy": loading || void 0,
312
+ children: [
313
+ /* @__PURE__ */ jsxs("ul", { role: "list", "aria-label": label, children: [
314
+ loading && collection.totalCount === 0 ? /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(LoadingNote, { text: collection.messages.loading }) }) : null,
315
+ !loading || collection.totalCount > 0 ? leaves.length === 0 ? /* @__PURE__ */ jsx("li", { children: filteredEmpty || collection.isFiltered ? /* @__PURE__ */ jsx(EmptyState, { size: "compact", kind: "no-results", title: collection.messages.noResults, description: collection.messages.noResultsDescription, actions: /* @__PURE__ */ jsx(Button, { emphasis: "standard", onClick: collection.clearFilters, children: collection.messages.clearFilters }) }) : empty ?? /* @__PURE__ */ jsx(EmptyState, { size: "compact", kind: "start", title: collection.messages.nothingHere }) }) : listItems({ collection, slots, roving, navigate, onRowActivate, getRowHref, activeRowId, rowActions, renderItemExtra }) : null
316
+ ] }),
317
+ totals === "summary" && Object.keys(collection.totals).length ? /* @__PURE__ */ jsxs("p", { className: "DataListSummary", children: [
318
+ collection.filteredCount,
319
+ " rows",
320
+ Object.entries(collection.totals).map(([id, text]) => /* @__PURE__ */ jsxs("span", { children: [
321
+ " \xB7 Total ",
322
+ text
323
+ ] }, id))
324
+ ] }) : null
325
+ ]
326
+ }
327
+ );
328
+ }
329
+ function listItems(args) {
330
+ const nodes = [];
331
+ let leafIndex = 0;
332
+ for (const entry of args.collection.pageEntries) {
333
+ if (entry.kind === "group") {
334
+ const row = entry.row;
335
+ const children = [];
336
+ const rest = args.collection.pageEntries.slice(args.collection.pageEntries.indexOf(entry) + 1);
337
+ for (const child of rest) {
338
+ if (child.kind === "group") break;
339
+ children.push(/* @__PURE__ */ jsx(ListRow, { entry: child, index: leafIndex, ...args }, child.row.id));
340
+ leafIndex += 1;
341
+ }
342
+ const selectable = (row.subRows ?? []).filter((child) => args.collection.canSelect?.(child.original));
343
+ const subtotal = args.slots.amount ? totalText(args.slots.amount, (row.subRows ?? []).map((child) => child.original), args.collection.locale) : "";
344
+ nodes.push(
345
+ /* @__PURE__ */ jsxs("li", { className: "DataListGroup", children: [
346
+ /* @__PURE__ */ jsxs("div", { className: "DataListGroupHeader", children: [
347
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "CollectionGroupButton", "aria-expanded": row.getIsExpanded(), onClick: () => row.toggleExpanded(), children: [
348
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: row.getIsExpanded() ? "expand_more" : "chevron_right" }),
349
+ String(row.groupingValue ?? ""),
350
+ entry.continued ? " (continued)" : "",
351
+ " \xB7 ",
352
+ selectable.length || (row.subRows?.length ?? 0)
353
+ ] }),
354
+ selectable.length && args.collection.canSelect ? /* @__PURE__ */ jsx(SelectBox, { checked: row.getIsAllSubRowsSelected?.() ?? false, indeterminate: Boolean(row.getIsSomeSelected?.() && !row.getIsAllSubRowsSelected?.()), label: `Select ${selectable.length} rows in ${row.groupingValue ?? "group"}`, onChange: () => row.toggleSelected?.() }) : null,
355
+ subtotal ? /* @__PURE__ */ jsx("span", { children: subtotal }) : null
356
+ ] }),
357
+ row.getIsExpanded() ? /* @__PURE__ */ jsx("ul", { role: "list", children }) : null
358
+ ] }, `g-${row.id}`)
359
+ );
360
+ continue;
361
+ }
362
+ if (entry.row.getParentRow?.()?.getIsGrouped?.()) continue;
363
+ nodes.push(
364
+ /* @__PURE__ */ jsx(ListRow, { entry, index: leafIndex, ...args }, entry.row.id)
365
+ );
366
+ leafIndex += 1;
367
+ }
368
+ return nodes;
369
+ }
370
+ function ListRow({ entry, index, collection, slots, roving, navigate, onRowActivate, getRowHref, activeRowId, rowActions, renderItemExtra }) {
371
+ const original = entry.row.original;
372
+ const selected = collection.selectionSet.has(entry.row.id);
373
+ const active = activeRowId === entry.row.id;
374
+ const name = rowHeaderText(collection, original);
375
+ const named = levelName(collection, entry.row, name);
376
+ const canSelect = collection.canSelect?.(original) ?? false;
377
+ const href = getRowHref?.(original);
378
+ const activate = () => onRowActivate?.(original);
379
+ const tabIndex = navigate ? roving.cursor === index ? 0 : -1 : void 0;
380
+ const onKeyDown = (event) => roving.onKeyDown(event, index, canSelect ? () => entry.row.toggleSelected?.(!selected) : void 0, href ? void 0 : activate);
381
+ const primary = href ? /* @__PURE__ */ jsx("a", { ref: (node) => {
382
+ roving.refs.current[index] = node;
383
+ }, href, tabIndex, "aria-label": named === name ? void 0 : named, onKeyDown, children: name }) : /* @__PURE__ */ jsx("button", { type: "button", className: "CollectionRowButton", ref: (node) => {
384
+ roving.refs.current[index] = node;
385
+ }, tabIndex, "aria-label": named === name ? void 0 : named, onKeyDown, onClick: () => {
386
+ roving.focusAt(index);
387
+ activate();
388
+ }, children: name });
389
+ return (
390
+ // The row is not a control. The primary button is. The checkbox carries selection.
391
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events
392
+ /* @__PURE__ */ jsxs(
393
+ "li",
394
+ {
395
+ "data-selected": selected ? "" : void 0,
396
+ "aria-current": active ? "true" : void 0,
397
+ onClick: (event) => {
398
+ if (interactiveTarget(event.target) || selectingText()) return;
399
+ if (event.target instanceof Element && event.target.closest(".DataListExtra, [data-actions]")) return;
400
+ if (href && !onRowActivate) {
401
+ event.currentTarget.querySelector("a")?.click();
402
+ return;
403
+ }
404
+ activate();
405
+ },
406
+ children: [
407
+ collection.canSelect ? canSelect ? /* @__PURE__ */ jsx(SelectBox, { checked: selected, label: `Select ${named}`, onChange: (checked) => entry.row.toggleSelected?.(checked) }) : /* @__PURE__ */ jsx("span", {}) : null,
408
+ /* @__PURE__ */ jsxs("div", { className: "DataListBody", children: [
409
+ /* @__PURE__ */ jsx("div", { className: "DataListPrimary", children: slots.primary ? primary : name }),
410
+ slots.secondary ? /* @__PURE__ */ jsx("div", { className: "DataListSecondary", "data-subtle": "", children: /* @__PURE__ */ jsx(CellValue, { column: slots.secondary, row: original, locale: collection.locale, rows: collection.filteredRows, view: "list", selected, active }) }) : null,
411
+ slots.meta.length ? /* @__PURE__ */ jsx("div", { className: "DataListMeta", "data-subtle": "", children: slots.meta.map((column) => /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(CellValue, { column, row: original, locale: collection.locale, rows: collection.filteredRows, view: "list", selected, active }) }, column.id)) }) : null,
412
+ renderItemExtra ? /* @__PURE__ */ jsx("div", { className: "DataListExtra", children: renderItemExtra(original) }) : null
413
+ ] }),
414
+ slots.amount ? /* @__PURE__ */ jsx("div", { className: "DataListAmount", "data-align": "end", children: /* @__PURE__ */ jsx(CellValue, { column: slots.amount, row: original, locale: collection.locale, rows: collection.filteredRows, view: "list", selected, active }) }) : null,
415
+ slots.status ? /* @__PURE__ */ jsx("div", { className: "DataListStatus", children: /* @__PURE__ */ jsx(CellValue, { column: slots.status, row: original, locale: collection.locale, rows: collection.filteredRows, view: "list", selected, active }) }) : null,
416
+ rowActions ? /* @__PURE__ */ jsx("span", { "data-actions": "", children: /* @__PURE__ */ jsx(RowMenu, { label: collection.messages.actionsFor(name), actions: rowActions(original) }) }) : null
417
+ ]
418
+ }
419
+ )
420
+ );
421
+ }
422
+ function slotMap(collection) {
423
+ const placed = collection.columns.map((column) => ({ column, slot: listSlot(column, collection.columns) }));
424
+ return {
425
+ primary: placed.find((item) => item.slot === "primary")?.column,
426
+ secondary: placed.find((item) => item.slot === "secondary")?.column,
427
+ amount: placed.find((item) => item.slot === "amount")?.column,
428
+ status: placed.find((item) => item.slot === "status")?.column,
429
+ meta: placed.filter((item) => item.slot === "meta").map((item) => item.column).slice(0, 3)
430
+ };
431
+ }
432
+ function DataTable({
433
+ collection,
434
+ label,
435
+ caption,
436
+ onRowActivate,
437
+ getRowHref,
438
+ activeRowId,
439
+ rowActions,
440
+ empty,
441
+ loading,
442
+ stickyHeader = true,
443
+ stickyRowHeader = false,
444
+ narrow = "scroll",
445
+ size = "sm",
446
+ rowNavigation,
447
+ className
448
+ }) {
449
+ const narrowWidth = useUnderWidth("--sl-collection-narrow-width");
450
+ const asList = narrow === "list" && narrowWidth.under;
451
+ const columns = visibleColumns(collection);
452
+ const leaves = collection.pageEntries.filter((entry) => entry.kind === "leaf");
453
+ const navigate = rowNavigation ?? Boolean(onRowActivate || getRowHref);
454
+ const colSpan = columns.length + (collection.canSelect ? 1 : 0) + (rowActions ? 1 : 0);
455
+ const selectableIds2 = collection.filteredRows.filter((row) => collection.canSelect?.(row)).map((row) => collection.getRowId(row));
456
+ const selectedFiltered = selectableIds2.filter((id) => collection.selection.includes(id));
457
+ const allSelected = selectableIds2.length > 0 && selectedFiltered.length === selectableIds2.length;
458
+ const someSelected = selectedFiltered.length > 0 && !allSelected;
459
+ const view = asList ? /* @__PURE__ */ jsx(
460
+ DataList,
461
+ {
462
+ collection,
463
+ label,
464
+ onRowActivate,
465
+ getRowHref,
466
+ activeRowId,
467
+ rowActions,
468
+ empty,
469
+ loading,
470
+ size,
471
+ rowNavigation
472
+ }
473
+ ) : /* @__PURE__ */ jsx(
474
+ TableView,
475
+ {
476
+ collection,
477
+ columns,
478
+ label,
479
+ caption,
480
+ colSpan,
481
+ leaves,
482
+ navigate,
483
+ allSelected,
484
+ someSelected,
485
+ loading,
486
+ empty,
487
+ stickyHeader,
488
+ stickyRowHeader,
489
+ size,
490
+ className,
491
+ onRowActivate,
492
+ getRowHref,
493
+ activeRowId,
494
+ rowActions
495
+ }
496
+ );
497
+ return /* @__PURE__ */ jsx("div", { ref: narrowWidth.ref, className: cn("CollectionView", asList ? className : void 0), "data-narrow": narrowWidth.under ? "" : void 0, children: view });
498
+ }
499
+ function TableView({ collection, columns, label, caption, colSpan, leaves, navigate, allSelected, someSelected, loading, empty, stickyHeader, stickyRowHeader, size, className, onRowActivate, getRowHref, activeRowId, rowActions }) {
500
+ const roving = useRovingRows(leaves.length, navigate);
501
+ const scrollerRef = React4.useRef(null);
502
+ const [overflows, setOverflows] = React4.useState(false);
503
+ React4.useEffect(() => {
504
+ collection.registerRowFocus(() => roving.focusAt(0));
505
+ return () => collection.registerRowFocus(null);
506
+ }, [collection, roving]);
507
+ React4.useLayoutEffect(() => {
508
+ if (!collection.focusAfterPage.current) return;
509
+ collection.focusAfterPage.current = false;
510
+ roving.focusAt(0);
511
+ }, [collection, collection.page, roving]);
512
+ React4.useEffect(() => {
513
+ const node = scrollerRef.current;
514
+ if (!node) return;
515
+ const watch = () => {
516
+ const wide = node.scrollWidth > node.clientWidth + 1;
517
+ const tall = stickyHeader && node.scrollHeight > node.clientHeight + 1;
518
+ setOverflows(wide || tall);
519
+ };
520
+ const observer = new ResizeObserver(watch);
521
+ observer.observe(node);
522
+ watch();
523
+ return () => observer.disconnect();
524
+ }, [stickyHeader, leaves.length, columns.length]);
525
+ const filteredEmpty = collection.totalCount > 0 && collection.filteredCount === 0;
526
+ const text = collection.messages;
527
+ return /* @__PURE__ */ jsxs(
528
+ "div",
529
+ {
530
+ ref: scrollerRef,
531
+ className: cn("CollectionScroll", className),
532
+ tabIndex: overflows ? 0 : void 0,
533
+ role: overflows ? "region" : void 0,
534
+ "aria-label": overflows ? `${label}, scrollable` : void 0,
535
+ children: [
536
+ /* @__PURE__ */ jsxs(
537
+ "table",
538
+ {
539
+ className: "Table DataTable",
540
+ "aria-label": caption ? void 0 : label,
541
+ "aria-busy": loading || void 0,
542
+ "data-size": size,
543
+ "data-view": "table",
544
+ "data-sorted": collection.sort ? "" : void 0,
545
+ "data-filtered": collection.isFiltered ? "" : void 0,
546
+ "data-selecting": collection.selection.length ? "" : void 0,
547
+ "data-loading": loading ? "" : void 0,
548
+ "data-grouped": collection.grouping ? "" : void 0,
549
+ "data-sticky-header": stickyHeader ? "" : void 0,
550
+ "data-sticky-row-header": stickyRowHeader ? "" : void 0,
551
+ children: [
552
+ caption ? /* @__PURE__ */ jsx("caption", { children: caption }) : null,
553
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
554
+ collection.canSelect ? /* @__PURE__ */ jsx("th", { scope: "col", "data-width": "min", className: "CollectionSelectColumn", children: /* @__PURE__ */ jsx(
555
+ SelectBox,
556
+ {
557
+ checked: allSelected,
558
+ indeterminate: someSelected,
559
+ label: text.selectAll(collection.filteredRows.filter((row) => collection.canSelect?.(row)).length),
560
+ onChange: (checked) => checked ? collection.selectAllFiltered() : collection.clearFilteredSelection()
561
+ }
562
+ ) }) : null,
563
+ columns.map((column) => {
564
+ const sorted = collection.sort?.id === column.id ? collection.sort.desc ? "descending" : "ascending" : "none";
565
+ return /* @__PURE__ */ jsx("th", { scope: "col", "aria-sort": column.sortable ? sorted : void 0, "data-align": alignOf(column), "data-width": column.width === "min" ? "min" : void 0, style: widthStyle(column.width), children: column.sortable ? /* @__PURE__ */ jsxs("button", { type: "button", className: "TableSortButton", onClick: () => collection.cycleSort(column.id), children: [
566
+ displayHeader(column, collection.filteredRows),
567
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined TableSortIcon", "data-state": sorted, "aria-hidden": "true", children: sorted === "ascending" ? "arrow_upward" : sorted === "descending" ? "arrow_downward" : "swap_vert" })
568
+ ] }) : displayHeader(column, collection.filteredRows) }, column.id);
569
+ }),
570
+ rowActions ? /* @__PURE__ */ jsx("th", { scope: "col", "data-width": "min", children: /* @__PURE__ */ jsx("span", { className: "visually-hidden", children: "Actions" }) }) : null
571
+ ] }) }),
572
+ collection.pageEntries.some((entry) => entry.kind === "group") ? groupedBodies({ collection, columns, leaves, roving, navigate, onRowActivate, getRowHref, activeRowId, rowActions, colSpan, filteredEmpty, empty, loading }) : /* @__PURE__ */ jsx("tbody", { children: bodyRows({ collection, columns, leaves, roving, navigate, onRowActivate, getRowHref, activeRowId, rowActions, colSpan, filteredEmpty, empty, loading }) }),
573
+ Object.keys(collection.totals).length ? /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsxs("tr", { children: [
574
+ collection.canSelect ? /* @__PURE__ */ jsx("td", {}) : null,
575
+ columns.map((column, index) => /* @__PURE__ */ jsxs("td", { "data-align": alignOf(column), children: [
576
+ index === 0 ? text.total : typeof column.total === "function" ? column.total(collection.filteredRows) : collection.totals[column.id] ?? "",
577
+ index === 0 && collection.isFiltered ? /* @__PURE__ */ jsxs("span", { className: "visually-hidden", children: [
578
+ " of ",
579
+ collection.filteredCount,
580
+ " filtered rows"
581
+ ] }) : null
582
+ ] }, column.id)),
583
+ rowActions ? /* @__PURE__ */ jsx("td", {}) : null
584
+ ] }) }) : null
585
+ ]
586
+ }
587
+ ),
588
+ loading && collection.totalCount === 0 ? /* @__PURE__ */ jsx(LoadingNote, { text: text.loading }) : null
589
+ ]
590
+ }
591
+ );
592
+ }
593
+ function bodyRows(args) {
594
+ if (args.loading && args.collection.totalCount === 0) return null;
595
+ if (args.leaves.length === 0) {
596
+ return /* @__PURE__ */ jsx(EmptyRow, { colSpan: args.colSpan, filtered: args.filteredEmpty || args.collection.isFiltered, onClear: args.collection.clearFilters, empty: args.empty });
597
+ }
598
+ return args.leaves.map((entry, index) => /* @__PURE__ */ jsx(DataRow, { entry, index, ...args }, entry.row.id));
599
+ }
600
+ function groupedBodies(args) {
601
+ const groups = [];
602
+ let buffer = [];
603
+ let header = null;
604
+ let bodyKey = "rows";
605
+ const flush = () => {
606
+ if (!header && buffer.length === 0) return;
607
+ groups.push(
608
+ /* @__PURE__ */ jsxs("tbody", { children: [
609
+ header,
610
+ buffer.map((item) => /* @__PURE__ */ jsx(DataRow, { entry: item, index: item.index, ...args }, item.row.id))
611
+ ] }, bodyKey)
612
+ );
613
+ header = null;
614
+ buffer = [];
615
+ };
616
+ let leafIndex = 0;
617
+ for (const entry of args.collection.pageEntries) {
618
+ if (entry.kind === "group") {
619
+ flush();
620
+ const row = entry.row;
621
+ bodyKey = row.id;
622
+ const selectable = (row.subRows ?? []).filter((child) => args.collection.canSelect?.(child.original));
623
+ const count = selectable.length;
624
+ const name = `${String(row.groupingValue ?? "group")}${entry.continued ? " (continued)" : ""}`;
625
+ const leaves = (row.subRows ?? []).map((child) => child.original);
626
+ header = /* @__PURE__ */ jsxs("tr", { "data-group": "", children: [
627
+ args.collection.canSelect ? /* @__PURE__ */ jsx("td", { "data-width": "min", children: count > 0 ? /* @__PURE__ */ jsx(
628
+ SelectBox,
629
+ {
630
+ checked: row.getIsAllSubRowsSelected?.() ?? false,
631
+ indeterminate: Boolean(row.getIsSomeSelected?.() && !row.getIsAllSubRowsSelected?.()),
632
+ label: `Select ${count} rows in ${row.groupingValue ?? "group"}`,
633
+ onChange: () => row.toggleSelected?.()
634
+ }
635
+ ) : null }) : null,
636
+ args.columns.map((column, index) => {
637
+ const subtotal = column.total ? typeof column.total === "function" ? column.total(leaves) : totalText(column, leaves, args.collection.locale) : "";
638
+ const label = `${name} \xB7 ${count}`;
639
+ if (index === 0) {
640
+ return /* @__PURE__ */ jsxs("th", { scope: "rowgroup", "data-align": alignOf(column), children: [
641
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "CollectionGroupButton", "aria-expanded": row.getIsExpanded(), onClick: () => row.toggleExpanded(), children: [
642
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: row.getIsExpanded() ? "expand_more" : "chevron_right" }),
643
+ label
644
+ ] }),
645
+ subtotal ? /* @__PURE__ */ jsx("span", { children: subtotal }) : null
646
+ ] }, column.id);
647
+ }
648
+ return /* @__PURE__ */ jsx("td", { "data-align": alignOf(column), children: subtotal }, column.id);
649
+ }),
650
+ args.rowActions ? /* @__PURE__ */ jsx("td", {}) : null
651
+ ] });
652
+ continue;
653
+ }
654
+ buffer.push({ row: entry.row, index: leafIndex });
655
+ leafIndex += 1;
656
+ }
657
+ flush();
658
+ if (!groups.length) return /* @__PURE__ */ jsx("tbody", { children: bodyRows(args) });
659
+ return groups;
660
+ }
661
+ function DataRow({ entry, index, collection, columns, roving, navigate, onRowActivate, getRowHref, activeRowId, rowActions }) {
662
+ const original = entry.row.original;
663
+ const id = entry.row.id;
664
+ const selected = collection.selectionSet.has(id);
665
+ const active = activeRowId === id;
666
+ const name = rowHeaderText(collection, original);
667
+ const named = levelName(collection, entry.row, name);
668
+ const canSelect = collection.canSelect?.(original) ?? false;
669
+ const href = getRowHref?.(original);
670
+ const activate = () => onRowActivate?.(original);
671
+ const toggle = canSelect ? () => entry.row.toggleSelected?.(!selected) : void 0;
672
+ const tabIndex = navigate ? roving.cursor === index ? 0 : -1 : void 0;
673
+ const parent = Boolean(entry.row.subRows?.length);
674
+ const onKeyDown = (event) => roving.onKeyDown(event, index, toggle, href ? void 0 : activate, parent ? { expanded: Boolean(entry.row.getIsExpanded?.()), toggle: () => entry.row.toggleExpanded?.() } : void 0);
675
+ const disclosure = parent ? /* @__PURE__ */ jsx("button", { type: "button", className: "CollectionGroupButton", "aria-expanded": entry.row.getIsExpanded(), "aria-label": `${entry.row.getIsExpanded() ? "Collapse" : "Expand"} ${name}`, onClick: (event) => {
676
+ event.stopPropagation();
677
+ entry.row.toggleExpanded?.();
678
+ }, children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: entry.row.getIsExpanded() ? "expand_more" : "chevron_right" }) }) : null;
679
+ const header = href ? /* @__PURE__ */ jsx("a", { ref: (node) => {
680
+ roving.refs.current[index] = node;
681
+ }, href, tabIndex, onKeyDown, onClick: () => roving.focusAt(index), children: name }) : /* @__PURE__ */ jsx(
682
+ "button",
683
+ {
684
+ type: "button",
685
+ className: "CollectionRowButton",
686
+ ref: (node) => {
687
+ roving.refs.current[index] = node;
688
+ },
689
+ tabIndex,
690
+ "aria-label": named === name ? void 0 : named,
691
+ onKeyDown,
692
+ onClick: () => {
693
+ roving.focusAt(index);
694
+ activate();
695
+ },
696
+ children: name
697
+ }
698
+ );
699
+ return /* @__PURE__ */ jsxs(
700
+ "tr",
701
+ {
702
+ "aria-selected": selected || void 0,
703
+ "aria-current": active ? "true" : void 0,
704
+ "data-depth": entry.row.depth || void 0,
705
+ onClick: (event) => {
706
+ const target = event.target;
707
+ if (interactiveTarget(target) || selectingText()) return;
708
+ if (target instanceof Element && target.closest(".CollectionSelectColumn, [data-actions]")) return;
709
+ if (href && !onRowActivate) {
710
+ event.currentTarget.querySelector("a")?.click();
711
+ return;
712
+ }
713
+ activate();
714
+ },
715
+ children: [
716
+ collection.canSelect ? /* @__PURE__ */ jsx("td", { "data-width": "min", children: canSelect ? /* @__PURE__ */ jsx(SelectBox, { checked: selected, label: `Select ${named}`, onChange: (checked) => entry.row.toggleSelected?.(checked) }) : null }) : null,
717
+ columns.map((column) => {
718
+ const Tag = column.rowHeader ? "th" : "td";
719
+ return /* @__PURE__ */ jsx(Tag, { scope: column.rowHeader ? "row" : void 0, "data-align": alignOf(column), style: column.rowHeader ? { paddingInlineStart: `calc(var(--sl-table-cell-padding-inline) + var(--sl-collection-tree-indent) * ${entry.row.depth})` } : void 0, children: column.rowHeader ? /* @__PURE__ */ jsxs(Fragment, { children: [
720
+ disclosure,
721
+ header
722
+ ] }) : /* @__PURE__ */ jsx(CellValue, { column, row: original, locale: collection.locale, rows: collection.filteredRows, view: "table", selected, active }) }, column.id);
723
+ }),
724
+ rowActions ? /* @__PURE__ */ jsx("td", { "data-width": "min", "data-actions": "", children: /* @__PURE__ */ jsx(RowMenu, { label: collection.messages.actionsFor(name), actions: rowActions(original) }) }) : null
725
+ ]
726
+ }
727
+ );
728
+ }
729
+ function visibleColumns(collection) {
730
+ const order = collection.columnOrder.length ? collection.columnOrder : collection.columns.map((column) => column.id);
731
+ return order.map((id) => collection.columns.find((column) => column.id === id)).filter((column) => Boolean(column && collection.columnVisibility[column.id] !== false));
732
+ }
733
+ function widthStyle(width) {
734
+ if (!width || width === "auto" || width === "min") return void 0;
735
+ return { width: `var(--sl-${width.replace(/\./g, "-")})` };
736
+ }
737
+ function CollectionPagination({
738
+ page,
739
+ pageCount,
740
+ pageSize,
741
+ total,
742
+ filtered,
743
+ onPageChange,
744
+ onPageSizeChange,
745
+ pageSizeOptions,
746
+ label,
747
+ previousLabel = "Previous",
748
+ nextLabel = "Next",
749
+ rowsPerPageLabel = "Rows per page",
750
+ className
751
+ }) {
752
+ const size = pageSize && pageSize > 0 ? pageSize : total;
753
+ const start = total === 0 ? 0 : (page - 1) * size + 1;
754
+ const end = Math.min(total, page * size);
755
+ return /* @__PURE__ */ jsxs("nav", { className: cn("CollectionPagination", className), "aria-label": `Pagination for ${label}`, children: [
756
+ /* @__PURE__ */ jsxs("p", { children: [
757
+ total === 0 ? "0" : `${start}\u2013${end}`,
758
+ " of ",
759
+ total,
760
+ filtered ? " filtered" : ""
761
+ ] }),
762
+ /* @__PURE__ */ jsxs(Button, { emphasis: "quiet", disabled: page <= 1, onClick: () => onPageChange(page - 1), children: [
763
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "chevron_left" }),
764
+ previousLabel
765
+ ] }),
766
+ /* @__PURE__ */ jsxs(Button, { emphasis: "quiet", disabled: page >= pageCount, onClick: () => onPageChange(page + 1), children: [
767
+ nextLabel,
768
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "chevron_right" })
769
+ ] }),
770
+ pageSizeOptions?.length && onPageSizeChange ? /* @__PURE__ */ jsxs(
771
+ Select.Root,
772
+ {
773
+ items: pageSizeOptions.map((option) => ({ value: String(option), label: `${option} per page` })),
774
+ value: String(pageSize ?? ""),
775
+ onValueChange: (value) => onPageSizeChange(Number(value)),
776
+ children: [
777
+ /* @__PURE__ */ jsxs(Select.Trigger, { "aria-label": rowsPerPageLabel, children: [
778
+ /* @__PURE__ */ jsx(Select.Value, {}),
779
+ /* @__PURE__ */ jsx(Select.Icon, { children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "expand_more" }) })
780
+ ] }),
781
+ /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, { children: /* @__PURE__ */ jsx(Select.Popup, { children: /* @__PURE__ */ jsx(Select.List, { children: pageSizeOptions.map((option) => /* @__PURE__ */ jsxs(Select.Item, { value: String(option), children: [
782
+ option,
783
+ " per page"
784
+ ] }, option)) }) }) }) })
785
+ ]
786
+ }
787
+ ) : null
788
+ ] });
789
+ }
790
+ var COMBOBOX_AT = 12;
791
+ function CollectionToolbarImpl({ collection, searchLabel, searchLabelVisible, showSort, groupOptions, actions, bulkActions, className }) {
792
+ const tabs = collection.columns.find((column) => column.filter?.kind === "tabs");
793
+ const selects = collection.columns.filter((column) => column.filter && column.filter.kind !== "tabs");
794
+ const searchable = collection.columns.some((column) => column.searchable);
795
+ if (process.env.NODE_ENV !== "production" && searchable && !searchLabel) {
796
+ console.warn("CollectionToolbar needs searchLabel when a column is searchable.");
797
+ }
798
+ const [announced, setAnnounced] = React4.useState("");
799
+ const seenSelection = React4.useRef(false);
800
+ React4.useEffect(() => {
801
+ const first = !seenSelection.current;
802
+ seenSelection.current = true;
803
+ if (first && collection.selection.length === 0) return;
804
+ setAnnounced("");
805
+ const timer = window.setTimeout(() => {
806
+ if (!collection.selection.length) {
807
+ setAnnounced(collection.messages.selectionCleared);
808
+ return;
809
+ }
810
+ const hidden = collection.hiddenSelectedCount ? `, ${collection.messages.hiddenByFilters(collection.hiddenSelectedCount)}` : "";
811
+ setAnnounced(`${collection.messages.selected(collection.selection.length)}${hidden}`);
812
+ }, 200);
813
+ return () => window.clearTimeout(timer);
814
+ }, [collection.selection, collection.hiddenSelectedCount, collection.messages]);
815
+ const selecting = collection.selection.length > 0;
816
+ const narrow = useUnderWidth("--sl-collection-narrow-width");
817
+ return /* @__PURE__ */ jsxs("div", { ref: narrow.ref, className: cn("CollectionToolbar", className), "data-selecting": selecting ? "" : void 0, "data-narrow": narrow.under ? "" : void 0, children: [
818
+ tabs ? /* @__PURE__ */ jsx(ToolbarTabs, { collection, column: tabs }) : null,
819
+ /* @__PURE__ */ jsxs("div", { className: "CollectionToolbarMain", children: [
820
+ searchable ? /* @__PURE__ */ jsx(
821
+ SearchField,
822
+ {
823
+ label: searchLabel ?? "Search",
824
+ visible: searchLabelVisible,
825
+ value: collection.search,
826
+ onChange: collection.setSearch
827
+ }
828
+ ) : null,
829
+ selecting ? /* @__PURE__ */ jsxs("div", { className: "CollectionSelectionBar", role: "region", "aria-label": "Selection", children: [
830
+ /* @__PURE__ */ jsxs("p", { className: "CollectionCount", children: [
831
+ collection.messages.selected(collection.selection.length),
832
+ collection.hiddenSelectedCount > 0 ? /* @__PURE__ */ jsxs("span", { children: [
833
+ " \xB7 ",
834
+ collection.messages.hiddenByFilters(collection.hiddenSelectedCount)
835
+ ] }) : null
836
+ ] }),
837
+ /* @__PURE__ */ jsx("div", { className: "CollectionToolbarActions", children: bulkActions }),
838
+ /* @__PURE__ */ jsx(Button, { emphasis: "quiet", onClick: collection.clearSelection, children: "Clear selection" })
839
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "CollectionToolbarTrailing", children: [
840
+ selects.map((column) => /* @__PURE__ */ jsx(FilterControl, { collection, column }, column.id)),
841
+ groupOptions?.length ? /* @__PURE__ */ jsx(
842
+ FilterSelect,
843
+ {
844
+ label: "Group by",
845
+ value: collection.grouping ?? "",
846
+ options: [{ value: "", label: "No grouping" }, ...groupOptions],
847
+ onChange: (value) => collection.setGrouping(typeof value === "string" && value ? value : null)
848
+ }
849
+ ) : null,
850
+ showSort ? /* @__PURE__ */ jsx(SortControl, { collection }) : null,
851
+ collection.isFiltered ? /* @__PURE__ */ jsx(Button, { emphasis: "quiet", onClick: collection.clearFilters, children: "Clear filters" }) : null,
852
+ actions ? /* @__PURE__ */ jsx("div", { className: "CollectionToolbarActions", children: actions }) : null
853
+ ] })
854
+ ] }),
855
+ /* @__PURE__ */ jsx("div", { className: "visually-hidden", "aria-live": "polite", children: announced })
856
+ ] });
857
+ }
858
+ function ToolbarTabs({ collection, column }) {
859
+ const counts = collection.facetCounts(column.id);
860
+ const options = column.filter?.options ?? [...counts.keys()].filter((value2) => value2 !== "").sort().map((value2) => ({ value: value2, label: value2 }));
861
+ const allCount = [...counts.values()].reduce((sum, count) => sum + count, 0);
862
+ const current = collection.filters[column.id];
863
+ const value = current == null || current === "" ? "" : String(current);
864
+ return /* @__PURE__ */ jsx(Tabs.Root, { value, onValueChange: (next) => collection.setFilter(column.id, next === "" ? void 0 : next), children: /* @__PURE__ */ jsx(Tabs.List, { children: options.map((option) => {
865
+ const count = option.value === "" || option.all ? allCount : counts.get(option.value) ?? 0;
866
+ const tabValue = option.all ? "" : option.value;
867
+ return /* @__PURE__ */ jsxs(Tabs.Tab, { value: tabValue, children: [
868
+ option.label,
869
+ " ",
870
+ count
871
+ ] }, tabValue || "all");
872
+ }) }) });
873
+ }
874
+ function SearchField({ label, visible, value, onChange }) {
875
+ const id = React4.useId();
876
+ return /* @__PURE__ */ jsxs("div", { className: "CollectionSearch", children: [
877
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: visible ? void 0 : "visually-hidden", children: label }),
878
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "search" }),
879
+ /* @__PURE__ */ jsx(
880
+ Input,
881
+ {
882
+ id,
883
+ type: "search",
884
+ value,
885
+ onChange: (event) => onChange(event.target.value),
886
+ onKeyDown: (event) => {
887
+ if (event.key === "Escape" && value) {
888
+ event.preventDefault();
889
+ onChange("");
890
+ }
891
+ }
892
+ }
893
+ )
894
+ ] });
895
+ }
896
+ function FilterControl({ collection, column }) {
897
+ const counts = collection.facetCounts(column.id);
898
+ const options = column.filter?.options ?? [...counts.keys()].filter((value) => value !== "").sort().map((value) => ({ value, label: value }));
899
+ const label = column.filter?.label ?? headerLabel(column);
900
+ if (column.filter?.kind === "boolean") {
901
+ const value = collection.filters[column.id];
902
+ return /* @__PURE__ */ jsx(
903
+ FilterSelect,
904
+ {
905
+ label,
906
+ value: value == null || value === "" ? "" : String(value),
907
+ options: [{ value: "", label: "Any" }, { value: "true", label: "Yes" }, { value: "false", label: "No" }],
908
+ onChange: (next) => collection.setFilter(column.id, next === "" || Array.isArray(next) ? void 0 : next === "true")
909
+ }
910
+ );
911
+ }
912
+ if (column.filter?.kind === "multi-select") {
913
+ const selected = Array.isArray(collection.filters[column.id]) ? collection.filters[column.id] : [];
914
+ return /* @__PURE__ */ jsx(
915
+ FilterSelect,
916
+ {
917
+ label,
918
+ multiple: true,
919
+ value: selected,
920
+ options,
921
+ onChange: (next) => {
922
+ const values = Array.isArray(next) ? next : [];
923
+ collection.setFilter(column.id, values.length ? values : void 0);
924
+ }
925
+ }
926
+ );
927
+ }
928
+ if (options.length > COMBOBOX_AT) {
929
+ return /* @__PURE__ */ jsx(
930
+ ComboboxField,
931
+ {
932
+ label,
933
+ value: String(collection.filters[column.id] ?? ""),
934
+ options,
935
+ onChange: (next) => collection.setFilter(column.id, next || void 0)
936
+ }
937
+ );
938
+ }
939
+ return /* @__PURE__ */ jsx(
940
+ FilterSelect,
941
+ {
942
+ label,
943
+ value: String(collection.filters[column.id] ?? ""),
944
+ options: [{ value: "", label: "Any" }, ...options],
945
+ onChange: (next) => collection.setFilter(column.id, next || void 0)
946
+ }
947
+ );
948
+ }
949
+ function FilterSelect({ label, value, options, onChange, multiple }) {
950
+ const labelId = React4.useId();
951
+ const valueId = React4.useId();
952
+ const items = options.map((option) => ({ value: option.value, label: option.label }));
953
+ const chosen = Array.isArray(value) ? value.length ? `${value.length} selected` : "Any" : options.find((option) => option.value === value)?.label ?? "Any";
954
+ return /* @__PURE__ */ jsxs(Select.Root, { items, multiple, value: multiple ? value : value, onValueChange: (next) => onChange(multiple ? Array.isArray(next) ? next.map(String) : [] : next == null ? "" : String(next)), children: [
955
+ /* @__PURE__ */ jsxs(Select.Trigger, { "aria-labelledby": `${labelId} ${valueId}`, children: [
956
+ /* @__PURE__ */ jsx("span", { id: labelId, className: "visually-hidden", children: label }),
957
+ /* @__PURE__ */ jsxs("span", { id: valueId, children: [
958
+ label,
959
+ ": ",
960
+ chosen
961
+ ] }),
962
+ /* @__PURE__ */ jsx(Select.Icon, { children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "expand_more" }) })
963
+ ] }),
964
+ /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, { children: /* @__PURE__ */ jsx(Select.Popup, { children: /* @__PURE__ */ jsx(Select.List, { children: items.map((item) => /* @__PURE__ */ jsx(Select.Item, { value: item.value, children: item.label }, item.value || "any")) }) }) }) })
965
+ ] });
966
+ }
967
+ function ComboboxField({ label, value, options, onChange }) {
968
+ const id = React4.useId();
969
+ return /* @__PURE__ */ jsxs(Combobox.Root, { items: options.map((option) => option.label), value: options.find((option) => option.value === value)?.label ?? null, onValueChange: (next) => {
970
+ const match = options.find((option) => option.label === next);
971
+ onChange(match?.value ?? "");
972
+ }, children: [
973
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "visually-hidden", children: label }),
974
+ /* @__PURE__ */ jsx("div", { className: "ComboboxInputWrapper", children: /* @__PURE__ */ jsx(Combobox.Input, { id, placeholder: label, "aria-label": label }) }),
975
+ /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, { children: /* @__PURE__ */ jsxs(Combobox.Popup, { children: [
976
+ /* @__PURE__ */ jsx(Combobox.Empty, { children: "No matches" }),
977
+ /* @__PURE__ */ jsx(Combobox.List, { children: (item) => /* @__PURE__ */ jsx(Combobox.Item, { value: item, children: item }, item) })
978
+ ] }) }) })
979
+ ] });
980
+ }
981
+ function SortControl({ collection }) {
982
+ const columns = collection.columns.filter((column) => column.sortable);
983
+ return /* @__PURE__ */ jsxs("div", { className: "CollectionSort", children: [
984
+ /* @__PURE__ */ jsx(
985
+ FilterSelect,
986
+ {
987
+ label: "Sort",
988
+ value: collection.sort?.id ?? "",
989
+ options: columns.map((column) => ({ value: column.id, label: headerLabel(column) })),
990
+ onChange: (id) => {
991
+ const next = typeof id === "string" ? id : "";
992
+ collection.setSort(next ? { id: next, desc: collection.sort?.id === next ? collection.sort.desc : false } : null);
993
+ }
994
+ }
995
+ ),
996
+ /* @__PURE__ */ jsx(
997
+ Button,
998
+ {
999
+ emphasis: "quiet",
1000
+ "aria-pressed": Boolean(collection.sort?.desc),
1001
+ "aria-label": collection.sort?.desc ? "Sort direction, descending" : "Sort direction, ascending",
1002
+ disabled: !collection.sort,
1003
+ onClick: () => collection.sort && collection.setSort({ ...collection.sort, desc: !collection.sort.desc }),
1004
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: collection.sort?.desc ? "arrow_downward" : "arrow_upward" })
1005
+ }
1006
+ )
1007
+ ] });
1008
+ }
1009
+ function ToolbarClear({ collection }) {
1010
+ if (!collection.isFiltered) return null;
1011
+ return /* @__PURE__ */ jsx(Button, { emphasis: "quiet", onClick: collection.clearFilters, children: collection.messages.clearFilters });
1012
+ }
1013
+ function ToolbarActions({ children }) {
1014
+ return /* @__PURE__ */ jsx("div", { className: "CollectionToolbarActions", children });
1015
+ }
1016
+ function ToolbarSelection({ collection, children }) {
1017
+ if (!collection.selection.length) return null;
1018
+ return /* @__PURE__ */ jsxs("div", { className: "CollectionSelectionBar", role: "region", "aria-label": "Selection", children: [
1019
+ /* @__PURE__ */ jsx("p", { className: "CollectionCount", children: collection.messages.selected(collection.selection.length) }),
1020
+ children,
1021
+ /* @__PURE__ */ jsx(Button, { emphasis: "quiet", onClick: collection.clearSelection, children: "Clear selection" })
1022
+ ] });
1023
+ }
1024
+ function ToolbarRoot({ className, children }) {
1025
+ return /* @__PURE__ */ jsx("div", { className: cn("CollectionToolbar", className), children });
1026
+ }
1027
+ var CollectionToolbar = Object.assign(CollectionToolbarImpl, {
1028
+ Root: ToolbarRoot,
1029
+ Tabs: ToolbarTabs,
1030
+ Search: SearchField,
1031
+ Filter: FilterControl,
1032
+ Sort: SortControl,
1033
+ Clear: ToolbarClear,
1034
+ Actions: ToolbarActions,
1035
+ SelectionBar: ToolbarSelection
1036
+ });
1037
+ var collectionFeatures = tableFeatures({
1038
+ rowSortingFeature,
1039
+ columnFilteringFeature,
1040
+ globalFilteringFeature,
1041
+ columnFacetingFeature,
1042
+ rowPaginationFeature,
1043
+ rowSelectionFeature,
1044
+ columnGroupingFeature,
1045
+ rowExpandingFeature,
1046
+ columnOrderingFeature,
1047
+ columnVisibilityFeature,
1048
+ sortedRowModel: createSortedRowModel(),
1049
+ filteredRowModel: createFilteredRowModel(),
1050
+ groupedRowModel: createGroupedRowModel(),
1051
+ expandedRowModel: createExpandedRowModel(),
1052
+ paginatedRowModel: createPaginatedRowModel(),
1053
+ facetedRowModel: createFacetedRowModel(),
1054
+ facetedUniqueValues: createFacetedUniqueValues()
1055
+ });
1056
+ function toColumnDefs(columns) {
1057
+ return columns.map((column) => ({
1058
+ id: column.id,
1059
+ accessorFn: (row) => {
1060
+ const value = column.value(row);
1061
+ return isEmptyValue(value) ? void 0 : value;
1062
+ },
1063
+ header: headerLabel(column),
1064
+ enableSorting: Boolean(column.sortable),
1065
+ sortUndefined: "last",
1066
+ // A tie returns 0. TanStack multiplies the result by -1 when descending, so an index tie would reverse, and it already falls back to the row index.
1067
+ sortFn: ((rowA, rowB, columnId) => {
1068
+ const locale = rowA.table.options.meta?.locale ?? "en";
1069
+ return comparePresent(rowA.getValue(columnId), rowB.getValue(columnId), column.kind ?? "text", collatorFor(locale));
1070
+ }),
1071
+ enableGlobalFilter: column.searchable != null && column.searchable !== false,
1072
+ enableColumnFilter: Boolean(column.filter),
1073
+ enableGrouping: true,
1074
+ enableHiding: true,
1075
+ filterFn: column.filter ? filterFor() : void 0,
1076
+ meta: column,
1077
+ ...column.tanstack
1078
+ }));
1079
+ }
1080
+ function filterFor(column) {
1081
+ const fn = (row, columnId, filterValue) => {
1082
+ if (filterValue == null || filterValue === "" || Array.isArray(filterValue) && filterValue.length === 0) return true;
1083
+ const value = String(row.getValue(columnId) ?? "");
1084
+ if (Array.isArray(filterValue)) return filterValue.map(String).includes(value);
1085
+ if (typeof filterValue === "boolean") return String(Boolean(row.getValue(columnId))) === String(filterValue) || value === String(filterValue);
1086
+ return value === String(filterValue);
1087
+ };
1088
+ fn.autoRemove = (filterValue) => filterValue == null || filterValue === "" || Array.isArray(filterValue) && filterValue.length === 0;
1089
+ return fn;
1090
+ }
1091
+ function collectionTableOptions(input) {
1092
+ const columns = input.columns;
1093
+ const locale = input.locale;
1094
+ const searchKey = `${locale}\0${columns.map((column) => column.id).join("\0")}`;
1095
+ const searchCache = /* @__PURE__ */ new WeakMap();
1096
+ const textOf = (row) => {
1097
+ const cached = searchCache.get(row);
1098
+ if (cached?.key === searchKey) return cached.text;
1099
+ const text = rowSearchText(row, columns, locale);
1100
+ searchCache.set(row, { key: searchKey, text });
1101
+ return text;
1102
+ };
1103
+ let treeCacheQuery = "\0";
1104
+ let treeCache = /* @__PURE__ */ new Set();
1105
+ const globalFilterFn = (row, _columnId, query) => {
1106
+ const text = String(query ?? "");
1107
+ if (!input.getSubRows) return searchMatches(text, textOf(row.original));
1108
+ if (treeCacheQuery !== text) {
1109
+ treeCacheQuery = text;
1110
+ treeCache = treeVisibleIds(
1111
+ input.rows,
1112
+ input.getRowId,
1113
+ (item) => input.getSubRows?.(item),
1114
+ (item) => searchMatches(text, textOf(item))
1115
+ );
1116
+ }
1117
+ return treeCache.has(input.getRowId(row.original));
1118
+ };
1119
+ return {
1120
+ features: collectionFeatures,
1121
+ data: input.rows,
1122
+ columns: toColumnDefs(columns),
1123
+ getRowId: (row) => input.getRowId(row),
1124
+ getSubRows: input.getSubRows,
1125
+ // A column filter on a tree keeps the parents of a matching child, the same way search does.
1126
+ filterFromLeafRows: Boolean(input.getSubRows),
1127
+ getColumnCanGlobalFilter: (column) => {
1128
+ const meta = column.columnDef.meta;
1129
+ return meta?.searchable != null && meta.searchable !== false;
1130
+ },
1131
+ enableRowSelection: input.canSelect ? (row) => input.canSelect(row.original) : false,
1132
+ enableMultiRowSelection: true,
1133
+ enableSubRowSelection: true,
1134
+ globalFilterFn,
1135
+ meta: { locale, columns },
1136
+ state: input.state,
1137
+ initialState: input.initialState,
1138
+ onGlobalFilterChange: input.onStateChange?.globalFilter,
1139
+ onSortingChange: input.onStateChange?.sorting,
1140
+ onColumnFiltersChange: input.onStateChange?.columnFilters,
1141
+ onPaginationChange: input.onStateChange?.pagination,
1142
+ onRowSelectionChange: input.onStateChange?.rowSelection,
1143
+ onGroupingChange: input.onStateChange?.grouping,
1144
+ onExpandedChange: input.onStateChange?.expanded,
1145
+ onColumnOrderChange: input.onStateChange?.columnOrder,
1146
+ onColumnVisibilityChange: input.onStateChange?.columnVisibility,
1147
+ autoResetPageIndex: false,
1148
+ // Resetting expanded would close every group on the first data change. Groups start open.
1149
+ autoResetExpanded: false
1150
+ };
1151
+ }
1152
+
1153
+ // src/custom/collection/use-collection.ts
1154
+ var defaultMessages = {
1155
+ selectAll: (count) => `Select all ${count} filtered rows`,
1156
+ actionsFor: (name) => `Actions for ${name}`,
1157
+ total: "Total",
1158
+ selected: (count) => `${count} selected`,
1159
+ hiddenByFilters: (count) => `${count} hidden by filters`,
1160
+ noResults: "No results",
1161
+ noResultsDescription: "Nothing matches the current search or filters.",
1162
+ clearFilters: "Clear filters",
1163
+ nothingHere: "Nothing here yet",
1164
+ previous: "Previous",
1165
+ next: "Next",
1166
+ rowsPerPage: "Rows per page",
1167
+ selectionCleared: "Selection cleared",
1168
+ loading: "Loading"
1169
+ };
1170
+ function readStored(storage, key, columnIds, pageSizeOptions) {
1171
+ if (!storage || !key) return {};
1172
+ try {
1173
+ const raw = storage.getItem(`loomiere:collection:${key}`);
1174
+ const stored = raw ? JSON.parse(raw) : {};
1175
+ if (stored.sort && !columnIds.has(stored.sort.id)) stored.sort = null;
1176
+ if (stored.filters) {
1177
+ stored.filters = Object.fromEntries(Object.entries(stored.filters).filter(([id]) => columnIds.has(id)));
1178
+ }
1179
+ if (stored.grouping && !columnIds.has(stored.grouping)) stored.grouping = null;
1180
+ if (stored.pageSize != null && pageSizeOptions?.length && !pageSizeOptions.includes(stored.pageSize)) stored.pageSize = void 0;
1181
+ return stored;
1182
+ } catch {
1183
+ return {};
1184
+ }
1185
+ }
1186
+ function withTabDefaults(columns, filters) {
1187
+ const next = { ...filters };
1188
+ for (const column of columns) {
1189
+ if (column.filter?.kind !== "tabs") continue;
1190
+ const current = next[column.id];
1191
+ if (current != null && current !== "") continue;
1192
+ const options = column.filter.options ?? [];
1193
+ if (options.some((option) => option.value === "" || option.all)) continue;
1194
+ if (options[0]) next[column.id] = options[0].value;
1195
+ }
1196
+ return next;
1197
+ }
1198
+ function filtersToList(filters) {
1199
+ return Object.entries(filters).filter(([, value]) => value != null && value !== "" && !(Array.isArray(value) && value.length === 0)).map(([id, value]) => ({ id, value }));
1200
+ }
1201
+ function selectionRecord(ids) {
1202
+ const record = {};
1203
+ for (const id of ids) record[id] = true;
1204
+ return record;
1205
+ }
1206
+ function useCollection(options) {
1207
+ const storage = options.storage ?? (typeof sessionStorage === "undefined" ? void 0 : sessionStorage);
1208
+ const columnIds = useMemo(() => new Set(options.columns.map((column) => column.id)), [options.columns]);
1209
+ const stored = useRef(readStored(storage, options.stateKey, columnIds, options.pageSizeOptions)).current;
1210
+ const locale = options.locale || (typeof document === "undefined" ? "en" : document.documentElement.lang) || "en";
1211
+ const messages = { ...defaultMessages, ...options.messages };
1212
+ const initialSort = options.initialSort ?? null;
1213
+ const initialFilters = withTabDefaults(options.columns, options.initialFilters ?? {});
1214
+ const initialGrouping = options.groupBy ?? null;
1215
+ const initialVisibility = Object.fromEntries(options.columns.map((column) => [column.id, !column.hidden]));
1216
+ const initialOrder = options.columns.map((column) => column.id);
1217
+ const [searchState, setSearchState] = useState(stored.search ?? "");
1218
+ const [sortState, setSortState] = useState(stored.sort ?? initialSort);
1219
+ const [filterState, setFilterState] = useState(stored.filters ?? initialFilters);
1220
+ const [selectionState, setSelectionState] = useState([]);
1221
+ const [pageState, setPageState] = useState(1);
1222
+ const [pageSizeState, setPageSizeState] = useState(stored.pageSize ?? options.pageSize);
1223
+ const [groupingState, setGroupingState] = useState(stored.grouping ?? initialGrouping);
1224
+ const [orderState, setOrderState] = useState(initialOrder);
1225
+ const [visibilityState, setVisibilityState] = useState(initialVisibility);
1226
+ const [expanded, setExpanded] = useState(options.defaultExpanded === false ? {} : true);
1227
+ const expandedBeforeSearch = useRef(null);
1228
+ const focusAfterPage = useRef(false);
1229
+ const search = options.search ?? searchState;
1230
+ const sort = options.sort === void 0 ? sortState : options.sort;
1231
+ const filters = options.filters ?? filterState;
1232
+ const selection = options.selection ?? selectionState;
1233
+ const page = options.page ?? pageState;
1234
+ const grouping = options.grouping === void 0 ? groupingState : options.grouping;
1235
+ const columnOrder = options.columnOrder ?? orderState;
1236
+ const columnVisibility = options.columnVisibility ?? visibilityState;
1237
+ const pageSize = pageSizeState;
1238
+ const deferredSearch = useDeferredValue(search);
1239
+ const focusRef = useRef(null);
1240
+ const rowsRef = useRef(options.rows);
1241
+ const idOf = options.getRowId;
1242
+ const updatePage = (value) => {
1243
+ options.onPageChange?.(value);
1244
+ if (options.page === void 0) setPageState(value);
1245
+ };
1246
+ const setSearch = (value) => {
1247
+ options.onSearchChange?.(value);
1248
+ if (options.search === void 0) setSearchState(value);
1249
+ updatePage(1);
1250
+ };
1251
+ const setSort = (value) => {
1252
+ options.onSortChange?.(value);
1253
+ if (options.sort === void 0) setSortState(value);
1254
+ };
1255
+ const setFilters = (value) => {
1256
+ options.onFiltersChange?.(value);
1257
+ if (options.filters === void 0) setFilterState(value);
1258
+ updatePage(1);
1259
+ };
1260
+ const setSelection = (ids) => {
1261
+ options.onSelectionChange?.(ids);
1262
+ if (options.selection === void 0) setSelectionState(ids);
1263
+ };
1264
+ const setGrouping = (value) => {
1265
+ options.onGroupingChange?.(value);
1266
+ if (options.grouping === void 0) setGroupingState(value);
1267
+ };
1268
+ const tableOptions = collectionTableOptions({
1269
+ rows: options.rows,
1270
+ columns: options.columns,
1271
+ getRowId: idOf,
1272
+ getSubRows: options.getSubRows,
1273
+ locale,
1274
+ canSelect: options.canSelect,
1275
+ state: {
1276
+ globalFilter: deferredSearch,
1277
+ sorting: sort ? [{ id: sort.id, desc: sort.desc }] : [],
1278
+ columnFilters: filtersToList(filters),
1279
+ pagination: { pageIndex: 0, pageSize: Number.POSITIVE_INFINITY },
1280
+ rowSelection: selectionRecord(selection),
1281
+ grouping: grouping ? [grouping] : [],
1282
+ expanded,
1283
+ columnOrder,
1284
+ columnVisibility
1285
+ },
1286
+ initialState: {
1287
+ sorting: initialSort ? [{ id: initialSort.id, desc: initialSort.desc }] : [],
1288
+ columnFilters: filtersToList(initialFilters),
1289
+ grouping: initialGrouping ? [initialGrouping] : [],
1290
+ columnVisibility: initialVisibility,
1291
+ columnOrder: initialOrder
1292
+ },
1293
+ onStateChange: {
1294
+ rowSelection: (updater) => {
1295
+ const current = selectionRecord(selection);
1296
+ const next = typeof updater === "function" ? updater(current) : updater;
1297
+ setSelection(Object.keys(next).filter((id) => next[id]));
1298
+ },
1299
+ expanded: (updater) => {
1300
+ setExpanded((current) => typeof updater === "function" ? updater(current) : updater);
1301
+ }
1302
+ }
1303
+ });
1304
+ const table = useTable(tableOptions);
1305
+ useEffect(() => {
1306
+ if (!options.getSubRows) return;
1307
+ if (deferredSearch) {
1308
+ if (expandedBeforeSearch.current == null) expandedBeforeSearch.current = expanded;
1309
+ setExpanded(true);
1310
+ return;
1311
+ }
1312
+ if (expandedBeforeSearch.current != null) {
1313
+ setExpanded(expandedBeforeSearch.current);
1314
+ expandedBeforeSearch.current = null;
1315
+ }
1316
+ }, [deferredSearch, options.getSubRows]);
1317
+ useEffect(() => {
1318
+ if (options.clearSelectionOnRowsChange && rowsRef.current !== options.rows) {
1319
+ rowsRef.current = options.rows;
1320
+ setSelection([]);
1321
+ return;
1322
+ }
1323
+ rowsRef.current = options.rows;
1324
+ const kept = selectableIds(selection, options.rows, idOf, options.canSelect, options.getSubRows);
1325
+ if (kept.length !== selection.length) setSelection(kept);
1326
+ }, [options.rows, options.clearSelectionOnRowsChange, options.canSelect, options.getSubRows]);
1327
+ useEffect(() => {
1328
+ if (!options.stateKey || !storage) return;
1329
+ try {
1330
+ storage.setItem(
1331
+ `loomiere:collection:${options.stateKey}`,
1332
+ JSON.stringify({ search, sort, filters, pageSize, grouping })
1333
+ );
1334
+ } catch {
1335
+ }
1336
+ }, [options.stateKey, storage, search, sort, filters, pageSize, grouping]);
1337
+ const filteredModel = table.getFilteredRowModel();
1338
+ const filteredLeaves = filteredModel.flatRows.filter((row) => !row.getIsGrouped?.() && !(options.getSubRows && row.subRows?.length));
1339
+ const filteredRows = filteredLeaves.map((row) => row.original);
1340
+ const filteredIds = new Set(filteredLeaves.map((row) => row.id));
1341
+ const topRows = table.getRowModel().rows;
1342
+ const flat = flattenVisible(topRows);
1343
+ const paged = pageFlat(flat, page - 1, pageSize && pageSize > 0 ? pageSize : flat.filter((entry) => entry.kind === "leaf").length || 1);
1344
+ const totals = useMemo(() => {
1345
+ const rows = filteredModel.flatRows.filter((row) => !row.getIsGrouped?.() && !(options.getSubRows && row.subRows?.length)).map((row) => row.original);
1346
+ const result = {};
1347
+ for (const column of options.columns) {
1348
+ if (!column.total) continue;
1349
+ result[column.id] = totalText(column, rows, locale);
1350
+ }
1351
+ return result;
1352
+ }, [options.columns, options.getSubRows, filteredModel, locale]);
1353
+ const byId = useMemo(() => {
1354
+ const map = /* @__PURE__ */ new Map();
1355
+ const walk = (rows) => {
1356
+ for (const row of rows) {
1357
+ map.set(idOf(row), row);
1358
+ const children = options.getSubRows?.(row);
1359
+ if (children) walk(children);
1360
+ }
1361
+ };
1362
+ walk(options.rows);
1363
+ return map;
1364
+ }, [options.rows, options.getSubRows, idOf]);
1365
+ const isFiltered = Boolean(search) || !sameFilters(filters, initialFilters);
1366
+ return {
1367
+ table,
1368
+ columns: options.columns,
1369
+ getRowId: idOf,
1370
+ locale,
1371
+ search,
1372
+ setSearch,
1373
+ sort,
1374
+ setSort,
1375
+ cycleSort: (columnId) => setSort(nextSort(sort, columnId, initialSort)),
1376
+ filters,
1377
+ setFilter: (columnId, value) => setFilters({ ...filters, [columnId]: value }),
1378
+ clearFilters: () => {
1379
+ setSearch("");
1380
+ setFilters({ ...initialFilters });
1381
+ },
1382
+ facetCounts: (columnId) => {
1383
+ const column = table.getColumn(columnId);
1384
+ const counts = column?.getFacetedUniqueValues?.() ?? /* @__PURE__ */ new Map();
1385
+ const mapped = /* @__PURE__ */ new Map();
1386
+ for (const [key, count] of counts) mapped.set(key == null ? "" : String(key), count);
1387
+ return mapped;
1388
+ },
1389
+ selection,
1390
+ selectedRows: selection.map((id) => byId.get(id)).filter((row) => row != null),
1391
+ selectAllFiltered: () => table.toggleAllRowsSelected(true),
1392
+ clearSelection: () => setSelection([]),
1393
+ clearFilteredSelection: () => setSelection(selection.filter((id) => !filteredIds.has(id))),
1394
+ selectionSet: new Set(selection),
1395
+ messages,
1396
+ hiddenSelectedCount: hiddenSelectedCount(selection, filteredIds),
1397
+ filteredRows,
1398
+ filteredCount: filteredRows.length,
1399
+ totalCount: options.rows.length,
1400
+ totals,
1401
+ page: paged.bounds.pageIndex + 1,
1402
+ pageCount: pageSize ? paged.bounds.pageCount : 1,
1403
+ setPage: (value) => {
1404
+ focusAfterPage.current = true;
1405
+ updatePage(value);
1406
+ },
1407
+ pageSize,
1408
+ setPageSize: (size) => {
1409
+ setPageSizeState(size);
1410
+ updatePage(1);
1411
+ },
1412
+ isFiltered,
1413
+ reset: () => {
1414
+ setSearch("");
1415
+ setSort(initialSort);
1416
+ setFilters({ ...initialFilters });
1417
+ setGrouping(initialGrouping);
1418
+ setPageSizeState(options.pageSize);
1419
+ setExpanded(options.defaultExpanded === false ? {} : true);
1420
+ expandedBeforeSearch.current = null;
1421
+ updatePage(1);
1422
+ setOrderState(initialOrder);
1423
+ setVisibilityState(initialVisibility);
1424
+ options.onColumnOrderChange?.(initialOrder);
1425
+ options.onColumnVisibilityChange?.(initialVisibility);
1426
+ },
1427
+ grouping,
1428
+ setGrouping,
1429
+ columnOrder,
1430
+ setColumnOrder: (order) => {
1431
+ options.onColumnOrderChange?.(order);
1432
+ if (options.columnOrder === void 0) setOrderState(order);
1433
+ },
1434
+ columnVisibility,
1435
+ setColumnVisibility: (visibility) => {
1436
+ options.onColumnVisibilityChange?.(visibility);
1437
+ if (options.columnVisibility === void 0) setVisibilityState(visibility);
1438
+ },
1439
+ canSelect: options.canSelect,
1440
+ pageEntries: paged.entries,
1441
+ rowHeaderId: options.columns.find((column) => column.rowHeader)?.id,
1442
+ registerRowFocus: (focus) => {
1443
+ focusRef.current = focus;
1444
+ },
1445
+ focusFirstRow: () => focusRef.current?.(),
1446
+ focusAfterPage
1447
+ };
1448
+ }
1449
+ function sameFilters(a, b) {
1450
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
1451
+ for (const key of keys) if (String(a[key] ?? "") !== String(b[key] ?? "")) return false;
1452
+ return true;
1453
+ }
1454
+ function flattenVisible(rows, groupId, nested = false) {
1455
+ const out = [];
1456
+ for (const row of rows) {
1457
+ if (!nested && !row.getIsGrouped?.() && row.getParentRow?.()) continue;
1458
+ if (row.getIsGrouped?.()) {
1459
+ out.push({ kind: "group", row, groupId: row.id });
1460
+ if (row.getIsExpanded()) out.push(...flattenVisible(row.subRows, row.id, true));
1461
+ } else {
1462
+ out.push({ kind: "leaf", row, groupId });
1463
+ if (row.subRows?.length && row.getIsExpanded?.()) out.push(...flattenVisible(row.subRows, groupId, true));
1464
+ }
1465
+ }
1466
+ return out;
1467
+ }
1468
+
1469
+ export { CollectionPagination, CollectionToolbar, DataList, DataTable, collectionTableOptions, defineColumns, headerLabel, totalText, useCollection };
1470
+ //# sourceMappingURL=index.js.map
1471
+ //# sourceMappingURL=index.js.map