@scattered-light/loomiere-base-ui 0.11.0 → 0.13.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 (72) hide show
  1. package/README.md +10 -0
  2. package/dist/custom/collection/index.d.ts +40 -3
  3. package/dist/custom/collection/index.js +68 -26
  4. package/dist/custom/collection/index.js.map +1 -1
  5. package/dist/design-system/components/CollectionToolbar/README.md +3 -1
  6. package/dist/design-system/components/DataTable/README.md +1 -1
  7. package/dist/design-system/components/DataTable/preview.html +171 -171
  8. package/dist/design-system/components/bundle.js +6 -6
  9. package/dist/design-system/components/styles.css +153 -61
  10. package/dist/styles/components/Accordion.css +1 -1
  11. package/dist/styles/components/AlertDialog.css +1 -1
  12. package/dist/styles/components/Autocomplete.css +1 -1
  13. package/dist/styles/components/Avatar.css +1 -1
  14. package/dist/styles/components/Button.css +1 -1
  15. package/dist/styles/components/Checkbox.css +1 -1
  16. package/dist/styles/components/CheckboxGroup.css +1 -1
  17. package/dist/styles/components/Collapsible.css +1 -1
  18. package/dist/styles/components/Combobox.css +1 -1
  19. package/dist/styles/components/ContextMenu.css +1 -1
  20. package/dist/styles/components/Dialog.css +1 -1
  21. package/dist/styles/components/Drawer.css +1 -1
  22. package/dist/styles/components/Field.css +1 -1
  23. package/dist/styles/components/Fieldset.css +1 -1
  24. package/dist/styles/components/Form.css +1 -1
  25. package/dist/styles/components/Input.css +1 -1
  26. package/dist/styles/components/Menu.css +1 -1
  27. package/dist/styles/components/Menubar.css +1 -1
  28. package/dist/styles/components/Meter.css +1 -1
  29. package/dist/styles/components/NavigationMenu.css +1 -1
  30. package/dist/styles/components/NumberField.css +1 -1
  31. package/dist/styles/components/OTPField.css +1 -1
  32. package/dist/styles/components/Popover.css +1 -1
  33. package/dist/styles/components/PreviewCard.css +1 -1
  34. package/dist/styles/components/Progress.css +1 -1
  35. package/dist/styles/components/Radio.css +1 -1
  36. package/dist/styles/components/ScrollArea.css +1 -1
  37. package/dist/styles/components/Select.css +1 -1
  38. package/dist/styles/components/Separator.css +1 -1
  39. package/dist/styles/components/Slider.css +1 -1
  40. package/dist/styles/components/Switch.css +1 -1
  41. package/dist/styles/components/Tabs.css +1 -1
  42. package/dist/styles/components/Toast.css +1 -1
  43. package/dist/styles/components/Toggle.css +1 -1
  44. package/dist/styles/components/ToggleGroup.css +1 -1
  45. package/dist/styles/components/Toolbar.css +1 -1
  46. package/dist/styles/components/Tooltip.css +1 -1
  47. package/dist/styles/custom/AppBar.css +1 -1
  48. package/dist/styles/custom/Badge.css +1 -1
  49. package/dist/styles/custom/Collection.css +1 -1
  50. package/dist/styles/custom/CollectionPagination.css +1 -1
  51. package/dist/styles/custom/CollectionToolbar.css +26 -1
  52. package/dist/styles/custom/DataList.css +1 -1
  53. package/dist/styles/custom/DataTable.css +68 -1
  54. package/dist/styles/custom/DatePicker.css +1 -1
  55. package/dist/styles/custom/DescriptionList.css +1 -1
  56. package/dist/styles/custom/EditableList.css +1 -1
  57. package/dist/styles/custom/EmptyState.css +1 -1
  58. package/dist/styles/custom/ListDetail.css +1 -1
  59. package/dist/styles/custom/MoneyField.css +1 -1
  60. package/dist/styles/custom/PickerButton.css +1 -1
  61. package/dist/styles/custom/Sheet.css +1 -1
  62. package/dist/styles/custom/SideNav.css +1 -1
  63. package/dist/styles/custom/Splitter.css +1 -1
  64. package/dist/styles/custom/StatFigure.css +1 -1
  65. package/dist/styles/custom/Table.css +2 -2
  66. package/dist/styles/index.css +1 -1
  67. package/dist/styles/reset/accessibility.css +1 -1
  68. package/dist/styles/reset/reset-accessibility.css +1 -1
  69. package/dist/styles/reset/reset-tailwind.css +1 -1
  70. package/dist/styles/reset/reset.css +1 -1
  71. package/dist/styles/tailwind.css +1 -1
  72. package/package.json +2 -2
package/README.md CHANGED
@@ -166,6 +166,16 @@ Pieces for pages that read like an accountant's working paper. They use the them
166
166
 
167
167
  Totals in a plain table are rows: `<tr data-total="subtotal">` rules a line above, `<tr data-total="grand">` adds a double rule under. Put `data-rule="none"` on label and blank cells so the rules only run under the figures. In a DataTable, `totalStyle="subtotal"` or `"grand"` does the same to the footer (the default, `"band"`, keeps the header tray).
168
168
 
169
+ A tree in a DataTable (`getSubRows`) has options for a register of parents and children, such as clients and their Entities:
170
+
171
+ - `useCollection({ pageBy: 'top-level', pageSize: 5 })` pages by parents, so a page holds whole parents with their children. The pager counts parents: pass `collection.pageTotal` as `total`. `collection.pageRows` is the leaf rows of the page, children of collapsed parents included.
172
+ - `totalRows={[{ scope: 'page', label }, { scope: 'all', label }]}` writes several footer rows. A `page` row totals the current page and is ruled as a subtotal; an `all` row totals every filtered row and is ruled as the grand total. `label` is text or a function of the collection, such as `(c) => \`Page ${c.page} of ${c.pageCount} subtotal\``.
173
+ - A `rowHeader` column's `cell` now draws the row's name: put a second line or a count beside it. The row's link or button keeps the plain name as its accessible name.
174
+ - `treeLines` also gives parent, child and total rows their own heights, from `collection.tree-row-height-parent`, `tree-row-height-child`, `total-row-height-subtotal` and `total-row-height-grand` (all the dense row until a brand sets them), and starts the elbow `collection.tree-line-offset` in. With `disclosure="small"` the chevron takes `collection.disclosure-color`. A row with `onRowActivate` or `getRowHref` shows a pointer. `table.row-hover-color` is the text color on the hovered row.
175
+ - `CollectionToolbar` takes `searchWidth="fixed"` (the search keeps `collection.search-fixed-width`), `meta` and `metaEnd` (quiet text beside the search and at the far end of the row) and `framed` (padding and a rule under it, inside a Sheet).
176
+ - `treeLines` draws an elbow from each child's name back up to its parent. `disclosure="small"` shrinks the expand button so a parent is as tall as its children. `getRowKind={(row) => 'link'}` marks a row (`data-kind="link"`) whose name reads as a link, for a row such as "3 more Entities".
177
+
178
+
169
179
  ### App bar: AppBar, PickerButton and a segmented ToggleGroup
170
180
 
171
181
  `AppBar` is the bar across the top of an app window: one row on a line in the rule color, so an ink-rule brand gets the working-paper rule under it. `AppBarSpacer` pushes what follows it to the end, and a vertical `Separator` is a short divider between icon actions. Icon actions are quiet `Button`s holding only an icon, with an `aria-label`.
@@ -280,6 +280,7 @@ interface CollectionMessages {
280
280
  selectAll: (count: number) => string;
281
281
  actionsFor: (name: string) => string;
282
282
  total: string;
283
+ pageSubtotal: string;
283
284
  selected: (count: number) => string;
284
285
  hiddenByFilters: (count: number) => string;
285
286
  noResults: string;
@@ -305,6 +306,13 @@ interface UseCollectionOptions<T> {
305
306
  pageSize?: number;
306
307
  groupBy?: string;
307
308
  getSubRows?: (row: T) => readonly T[] | undefined;
309
+ /**
310
+ * What a page counts. 'row' (the default) pages by rows, so a page can end partway through a
311
+ * parent's children. 'top-level' pages a tree by its top-level rows: each page holds whole
312
+ * parents with all their visible children, and the page size, page count and
313
+ * `pageTotal` all count parents.
314
+ */
315
+ pageBy?: 'row' | 'top-level';
308
316
  /** Groups and tree parents start open. Pass false to start them closed. */
309
317
  defaultExpanded?: boolean;
310
318
  /** Page sizes the control offers. A stored size outside this list is dropped. */
@@ -359,6 +367,10 @@ interface CollectionApi<T> {
359
367
  hiddenSelectedCount: number;
360
368
  filteredRows: T[];
361
369
  filteredCount: number;
370
+ /** What the pager counts: filtered rows, or the filtered top-level rows when `pageBy` is 'top-level'. */
371
+ pageTotal: number;
372
+ /** The leaf rows of this page. With `pageBy: 'top-level'` that includes children of collapsed parents. */
373
+ pageRows: T[];
362
374
  totalCount: number;
363
375
  totals: Record<string, string>;
364
376
  page: number;
@@ -437,12 +449,29 @@ interface DataTableProps<T> extends CollectionViewProps<T> {
437
449
  * a single line above it; 'grand' adds a double rule under it, as on a working paper.
438
450
  */
439
451
  totalStyle?: 'band' | 'subtotal' | 'grand';
452
+ /**
453
+ * Several footer rows, top to bottom, each totalling a scope: 'page' is the rows on this page,
454
+ * 'all' every filtered row. A 'page' row is ruled as a subtotal and an 'all' row as the grand
455
+ * total, so totalStyle is ignored. The label defaults to the messages for page subtotal and total.
456
+ */
457
+ totalRows?: DataTableTotalRow<T>[];
458
+ /** Draws an elbow connector from each child row's name back up to its parent. Use it with getSubRows. */
459
+ treeLines?: boolean;
460
+ /** 'small' shrinks the expand/collapse button so a parent row stays the height of its siblings. */
461
+ disclosure?: 'default' | 'small';
462
+ /** Names a row's kind, set as `data-kind` on the row. 'link' draws the row's name as a link, for a row that opens more rows or a page. */
463
+ getRowKind?: (row: T) => 'link' | (string & {}) | undefined;
464
+ }
465
+ interface DataTableTotalRow<T = unknown> {
466
+ scope: 'page' | 'all';
467
+ /** Text, or a function of the collection for a label such as "Page 2 of 5 subtotal". */
468
+ label?: React.ReactNode | ((collection: CollectionApi<T>) => React.ReactNode);
440
469
  }
441
470
  /**
442
471
  * A data table, the APG pattern: one tab stop for the rows, headers announce sort, and it is
443
472
  * not a grid. A grid would say this is a spreadsheet, and it is not. AG Grid is that tool.
444
473
  */
445
- declare function DataTable<T>({ collection, label, caption, onRowActivate, getRowHref, activeRowId, rowActions, empty, loading, stickyHeader, stickyRowHeader, narrow, size, totalStyle, rowNavigation, className, }: DataTableProps<T>): react_jsx_runtime.JSX.Element;
474
+ declare function DataTable<T>({ collection, label, caption, onRowActivate, getRowHref, activeRowId, rowActions, empty, loading, stickyHeader, stickyRowHeader, narrow, size, totalStyle, totalRows, treeLines, disclosure, getRowKind, rowNavigation, className, }: DataTableProps<T>): react_jsx_runtime.JSX.Element;
446
475
 
447
476
  interface CollectionPaginationProps {
448
477
  /** 1-based. */
@@ -481,13 +510,21 @@ interface CollectionToolbarProps<T> {
481
510
  }>;
482
511
  actions?: React.ReactNode;
483
512
  bulkActions?: React.ReactNode;
513
+ /** 'fill' (the default) lets the search grow. 'fixed' gives it collection.search-fixed-width, so the meta beside it has room. */
514
+ searchWidth?: 'fill' | 'fixed';
515
+ /** Quiet text beside the search, such as "12 clients". */
516
+ meta?: React.ReactNode;
517
+ /** Quiet text at the far end of the row, such as "Sorted by most open". */
518
+ metaEnd?: React.ReactNode;
519
+ /** Pads the toolbar and rules it from the table below, for a toolbar inside a Sheet above an edge-to-edge table. */
520
+ framed?: boolean;
484
521
  className?: string;
485
522
  }
486
523
  /**
487
524
  * Tabs and search stay put while rows are selected. The trailing controls swap for the
488
525
  * selection bar, so the bar takes their place and the row does not grow.
489
526
  */
490
- declare function CollectionToolbarImpl<T>({ collection, searchLabel, searchLabelVisible, showSort, groupOptions, actions, bulkActions, className }: CollectionToolbarProps<T>): react_jsx_runtime.JSX.Element;
527
+ declare function CollectionToolbarImpl<T>({ collection, searchLabel, searchLabelVisible, showSort, groupOptions, actions, bulkActions, searchWidth, meta, metaEnd, framed, className }: CollectionToolbarProps<T>): react_jsx_runtime.JSX.Element;
491
528
  declare function ToolbarTabs<T>({ collection, column }: {
492
529
  collection: CollectionApi<T>;
493
530
  column: CollectionColumn<T>;
@@ -542,4 +579,4 @@ declare const CollectionToolbar: typeof CollectionToolbarImpl & {
542
579
  */
543
580
  declare function totalText<T>(column: CollectionColumn<T>, rows: readonly T[], locale: string): string;
544
581
 
545
- export { type CellContext, type CollectionAlign, type CollectionApi, type CollectionColumn, type CollectionFilter, type CollectionKind, CollectionPagination, type CollectionPaginationProps, type CollectionSort, CollectionToolbar, type CollectionToolbarProps, DataList, type DataListProps, DataTable, type DataTableProps, type FilterKind, type FilterOption, type ListSlot, type UseCollectionOptions, balanceOf, collectionTableOptions, compareDecimal, countFacets, defineColumns, formatDate, formatMoney, headerLabel, hiddenSelectedCount, nextSort, pageBounds, pageFlat, searchMatches, selectableIds, sortRows, sumMoney, toMinor, totalText, treeVisibleIds, useCollection };
582
+ export { type CellContext, type CollectionAlign, type CollectionApi, type CollectionColumn, type CollectionFilter, type CollectionKind, CollectionPagination, type CollectionPaginationProps, type CollectionSort, CollectionToolbar, type CollectionToolbarProps, DataList, type DataListProps, DataTable, type DataTableProps, type DataTableTotalRow, type FilterKind, type FilterOption, type ListSlot, type UseCollectionOptions, balanceOf, collectionTableOptions, compareDecimal, countFacets, defineColumns, formatDate, formatMoney, headerLabel, hiddenSelectedCount, nextSort, pageBounds, pageFlat, searchMatches, selectableIds, sortRows, sumMoney, toMinor, totalText, treeVisibleIds, useCollection };
@@ -1,5 +1,5 @@
1
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';
2
+ import { useUnderWidth, Button, cn, Select, Combobox, isEmptyValue, formatPercent, formatNumber, sumMoney, fractionDigitsOf, formatMoney, minorToDecimal, EmptyState, selectableIds, pageFlat, hiddenSelectedCount, nextSort, pageBounds, searchMatches, treeVisibleIds, isoDate, toMinor, comparePresent, collatorFor, formatDate, moneySearchForms, searchHaystack } from '../../chunk-M7KJASGK.js';
3
3
  export { balanceOf, compareDecimal, countFacets, formatDate, formatMoney, hiddenSelectedCount, nextSort, pageBounds, pageFlat, searchMatches, selectableIds, sortRows, sumMoney, toMinor, treeVisibleIds } from '../../chunk-M7KJASGK.js';
4
4
  import * as React4 from 'react';
5
5
  import { useMemo, useRef, useState, useDeferredValue, useEffect } from 'react';
@@ -444,6 +444,10 @@ function DataTable({
444
444
  narrow = "scroll",
445
445
  size = "sm",
446
446
  totalStyle = "band",
447
+ totalRows,
448
+ treeLines,
449
+ disclosure = "default",
450
+ getRowKind,
447
451
  rowNavigation,
448
452
  className
449
453
  }) {
@@ -489,6 +493,10 @@ function DataTable({
489
493
  stickyRowHeader,
490
494
  size,
491
495
  totalStyle,
496
+ totalRows,
497
+ treeLines,
498
+ disclosure,
499
+ getRowKind,
492
500
  className,
493
501
  onRowActivate,
494
502
  getRowHref,
@@ -498,7 +506,7 @@ function DataTable({
498
506
  );
499
507
  return /* @__PURE__ */ jsx("div", { ref: narrowWidth.ref, className: cn("CollectionView", asList ? className : void 0), "data-narrow": narrowWidth.under ? "" : void 0, children: view });
500
508
  }
501
- function TableView({ collection, columns, label, caption, colSpan, leaves, navigate, allSelected, someSelected, loading, empty, stickyHeader, stickyRowHeader, size, totalStyle, className, onRowActivate, getRowHref, activeRowId, rowActions }) {
509
+ function TableView({ collection, columns, label, caption, colSpan, leaves, navigate, allSelected, someSelected, loading, empty, stickyHeader, stickyRowHeader, size, totalStyle, totalRows, treeLines, disclosure, getRowKind, className, onRowActivate, getRowHref, activeRowId, rowActions }) {
502
510
  const roving = useRovingRows(leaves.length, navigate);
503
511
  const scrollerRef = React4.useRef(null);
504
512
  const [overflows, setOverflows] = React4.useState(false);
@@ -551,6 +559,8 @@ function TableView({ collection, columns, label, caption, colSpan, leaves, navig
551
559
  "data-grouped": collection.grouping ? "" : void 0,
552
560
  "data-sticky-header": stickyHeader ? "" : void 0,
553
561
  "data-sticky-row-header": stickyRowHeader ? "" : void 0,
562
+ "data-tree-lines": treeLines ? "" : void 0,
563
+ "data-disclosure": disclosure === "small" ? "small" : void 0,
554
564
  children: [
555
565
  caption ? /* @__PURE__ */ jsx("caption", { children: caption }) : null,
556
566
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
@@ -572,22 +582,28 @@ function TableView({ collection, columns, label, caption, colSpan, leaves, navig
572
582
  }),
573
583
  rowActions ? /* @__PURE__ */ jsx("th", { scope: "col", "data-width": "min", children: /* @__PURE__ */ jsx("span", { className: "visually-hidden", children: "Actions" }) }) : null
574
584
  ] }) }),
575
- 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 }) }),
576
- Object.keys(collection.totals).length ? /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsxs("tr", { "data-total": ruled ? totalStyle : void 0, children: [
577
- collection.canSelect ? /* @__PURE__ */ jsx("td", { "data-rule": ruled ? "none" : void 0 }) : null,
578
- columns.map((column, index) => (
579
- // Ruled totals draw their lines under the figures only, not the label or blank cells.
580
- /* @__PURE__ */ jsxs("td", { "data-align": alignOf(column), "data-rule": ruled && (index === 0 || !column.total) ? "none" : void 0, children: [
581
- index === 0 ? text.total : typeof column.total === "function" ? column.total(collection.filteredRows) : collection.totals[column.id] ?? "",
582
- index === 0 && collection.isFiltered ? /* @__PURE__ */ jsxs("span", { className: "visually-hidden", children: [
583
- " of ",
584
- collection.filteredCount,
585
- " filtered rows"
586
- ] }) : null
587
- ] }, column.id)
588
- )),
589
- rowActions ? /* @__PURE__ */ jsx("td", { "data-rule": ruled ? "none" : void 0 }) : null
590
- ] }) }) : null
585
+ collection.pageEntries.some((entry) => entry.kind === "group") ? groupedBodies({ collection, columns, leaves, roving, navigate, onRowActivate, getRowHref, getRowKind, activeRowId, rowActions, colSpan, filteredEmpty, empty, loading }) : /* @__PURE__ */ jsx("tbody", { children: bodyRows({ collection, columns, leaves, roving, navigate, onRowActivate, getRowHref, getRowKind, activeRowId, rowActions, colSpan, filteredEmpty, empty, loading }) }),
586
+ Object.keys(collection.totals).length ? /* @__PURE__ */ jsx("tfoot", { children: (totalRows ?? [{ scope: "all" }]).map((total, rowIndex) => {
587
+ const custom = Boolean(totalRows);
588
+ const ruledRow = custom ? total.scope === "page" ? "subtotal" : "grand" : ruled ? totalStyle : void 0;
589
+ const rowsInScope = total.scope === "page" ? collection.pageRows : collection.filteredRows;
590
+ const label2 = total.label == null ? total.scope === "page" ? text.pageSubtotal : text.total : typeof total.label === "function" ? total.label(collection) : total.label;
591
+ return /* @__PURE__ */ jsxs("tr", { "data-total": ruledRow, children: [
592
+ collection.canSelect ? /* @__PURE__ */ jsx("td", { "data-rule": ruledRow ? "none" : void 0 }) : null,
593
+ columns.map((column, index) => (
594
+ // Ruled totals draw their lines under the figures only, not the label or blank cells.
595
+ /* @__PURE__ */ jsxs("td", { "data-align": alignOf(column), "data-rule": ruledRow && (index === 0 || !column.total) ? "none" : void 0, children: [
596
+ index === 0 ? label2 : column.total ? typeof column.total === "function" ? column.total(rowsInScope) : totalText(column, rowsInScope, collection.locale) : "",
597
+ index === 0 && total.scope === "all" && collection.isFiltered ? /* @__PURE__ */ jsxs("span", { className: "visually-hidden", children: [
598
+ " of ",
599
+ collection.filteredCount,
600
+ " filtered rows"
601
+ ] }) : null
602
+ ] }, column.id)
603
+ )),
604
+ rowActions ? /* @__PURE__ */ jsx("td", { "data-rule": ruledRow ? "none" : void 0 }) : null
605
+ ] }, rowIndex);
606
+ }) }) : null
591
607
  ]
592
608
  }
593
609
  ),
@@ -664,7 +680,7 @@ function groupedBodies(args) {
664
680
  if (!groups.length) return /* @__PURE__ */ jsx("tbody", { children: bodyRows(args) });
665
681
  return groups;
666
682
  }
667
- function DataRow({ entry, index, collection, columns, roving, navigate, onRowActivate, getRowHref, activeRowId, rowActions }) {
683
+ function DataRow({ entry, index, collection, columns, roving, navigate, onRowActivate, getRowHref, getRowKind, activeRowId, rowActions }) {
668
684
  const original = entry.row.original;
669
685
  const id = entry.row.id;
670
686
  const selected = collection.selectionSet.has(id);
@@ -673,6 +689,10 @@ function DataRow({ entry, index, collection, columns, roving, navigate, onRowAct
673
689
  const named = levelName(collection, entry.row, name);
674
690
  const canSelect = collection.canSelect?.(original) ?? false;
675
691
  const href = getRowHref?.(original);
692
+ const rowKind = getRowKind?.(original);
693
+ const headerColumn = columns.find((column) => column.rowHeader);
694
+ const headerContent = headerColumn?.cell ? headerColumn.cell(original, { view: "table", selected, active }) : name;
695
+ const customHeader = Boolean(headerColumn?.cell);
676
696
  const activate = () => onRowActivate?.(original);
677
697
  const toggle = canSelect ? () => entry.row.toggleSelected?.(!selected) : void 0;
678
698
  const tabIndex = navigate ? roving.cursor === index ? 0 : -1 : void 0;
@@ -684,7 +704,7 @@ function DataRow({ entry, index, collection, columns, roving, navigate, onRowAct
684
704
  }, children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: entry.row.getIsExpanded() ? "expand_more" : "chevron_right" }) }) : null;
685
705
  const header = href ? /* @__PURE__ */ jsx("a", { ref: (node) => {
686
706
  roving.refs.current[index] = node;
687
- }, href, tabIndex, onKeyDown, onClick: () => roving.focusAt(index), children: name }) : /* @__PURE__ */ jsx(
707
+ }, href, tabIndex, "aria-label": customHeader ? named : void 0, onKeyDown, onClick: () => roving.focusAt(index), children: headerContent }) : /* @__PURE__ */ jsx(
688
708
  "button",
689
709
  {
690
710
  type: "button",
@@ -693,13 +713,13 @@ function DataRow({ entry, index, collection, columns, roving, navigate, onRowAct
693
713
  roving.refs.current[index] = node;
694
714
  },
695
715
  tabIndex,
696
- "aria-label": named === name ? void 0 : named,
716
+ "aria-label": named === name && !customHeader ? void 0 : named,
697
717
  onKeyDown,
698
718
  onClick: () => {
699
719
  roving.focusAt(index);
700
720
  activate();
701
721
  },
702
- children: name
722
+ children: headerContent
703
723
  }
704
724
  );
705
725
  return /* @__PURE__ */ jsxs(
@@ -708,6 +728,8 @@ function DataRow({ entry, index, collection, columns, roving, navigate, onRowAct
708
728
  "aria-selected": selected || void 0,
709
729
  "aria-current": active ? "true" : void 0,
710
730
  "data-depth": entry.row.depth || void 0,
731
+ "data-kind": rowKind,
732
+ "data-activates": href || onRowActivate ? "" : void 0,
711
733
  onClick: (event) => {
712
734
  const target = event.target;
713
735
  if (interactiveTarget(target) || selectingText()) return;
@@ -722,7 +744,7 @@ function DataRow({ entry, index, collection, columns, roving, navigate, onRowAct
722
744
  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,
723
745
  columns.map((column) => {
724
746
  const Tag = column.rowHeader ? "th" : "td";
725
- 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: [
747
+ 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})`, "--row-depth": entry.row.depth } : void 0, children: column.rowHeader ? /* @__PURE__ */ jsxs(Fragment, { children: [
726
748
  disclosure,
727
749
  header
728
750
  ] }) : /* @__PURE__ */ jsx(CellValue, { column, row: original, locale: collection.locale, rows: collection.filteredRows, view: "table", selected, active }) }, column.id);
@@ -794,7 +816,7 @@ function CollectionPagination({
794
816
  ] });
795
817
  }
796
818
  var COMBOBOX_AT = 12;
797
- function CollectionToolbarImpl({ collection, searchLabel, searchLabelVisible, showSort, groupOptions, actions, bulkActions, className }) {
819
+ function CollectionToolbarImpl({ collection, searchLabel, searchLabelVisible, showSort, groupOptions, actions, bulkActions, searchWidth = "fill", meta, metaEnd, framed, className }) {
798
820
  const tabs = collection.columns.find((column) => column.filter?.kind === "tabs");
799
821
  const selects = collection.columns.filter((column) => column.filter && column.filter.kind !== "tabs");
800
822
  const searchable = collection.columns.some((column) => column.searchable);
@@ -820,7 +842,7 @@ function CollectionToolbarImpl({ collection, searchLabel, searchLabelVisible, sh
820
842
  }, [collection.selection, collection.hiddenSelectedCount, collection.messages]);
821
843
  const selecting = collection.selection.length > 0;
822
844
  const narrow = useUnderWidth("--sl-collection-narrow-width");
823
- return /* @__PURE__ */ jsxs("div", { ref: narrow.ref, className: cn("CollectionToolbar", className), "data-selecting": selecting ? "" : void 0, "data-narrow": narrow.under ? "" : void 0, children: [
845
+ return /* @__PURE__ */ jsxs("div", { ref: narrow.ref, className: cn("CollectionToolbar", className), "data-selecting": selecting ? "" : void 0, "data-narrow": narrow.under ? "" : void 0, "data-search": searchWidth === "fixed" ? "fixed" : void 0, "data-framed": framed ? "" : void 0, children: [
824
846
  tabs ? /* @__PURE__ */ jsx(ToolbarTabs, { collection, column: tabs }) : null,
825
847
  /* @__PURE__ */ jsxs("div", { className: "CollectionToolbarMain", children: [
826
848
  searchable ? /* @__PURE__ */ jsx(
@@ -833,6 +855,8 @@ function CollectionToolbarImpl({ collection, searchLabel, searchLabelVisible, sh
833
855
  clearLabel: collection.messages.clearSearch
834
856
  }
835
857
  ) : null,
858
+ meta ? /* @__PURE__ */ jsx("p", { className: "CollectionToolbarMeta", "aria-live": "polite", children: meta }) : null,
859
+ metaEnd ? /* @__PURE__ */ jsx("p", { className: "CollectionToolbarMeta CollectionToolbarMetaEnd", children: metaEnd }) : null,
836
860
  selecting ? /* @__PURE__ */ jsxs("div", { className: "CollectionSelectionBar", role: "region", "aria-label": "Selection", children: [
837
861
  /* @__PURE__ */ jsxs("p", { className: "CollectionCount", children: [
838
862
  collection.messages.selected(collection.selection.length),
@@ -1178,6 +1202,7 @@ var defaultMessages = {
1178
1202
  selectAll: (count) => `Select all ${count} filtered rows`,
1179
1203
  actionsFor: (name) => `Actions for ${name}`,
1180
1204
  total: "Total",
1205
+ pageSubtotal: "Page subtotal",
1181
1206
  selected: (count) => `${count} selected`,
1182
1207
  hiddenByFilters: (count) => `${count} hidden by filters`,
1183
1208
  noResults: "No results",
@@ -1364,7 +1389,11 @@ function useCollection(options) {
1364
1389
  const filteredIds = new Set(filteredLeaves.map((row) => row.id));
1365
1390
  const topRows = table.getRowModel().rows;
1366
1391
  const flat = flattenVisible(topRows);
1367
- const paged = pageFlat(flat, page - 1, pageSize && pageSize > 0 ? pageSize : flat.filter((entry) => entry.kind === "leaf").length || 1);
1392
+ const byTopLevel = options.pageBy === "top-level" && Boolean(options.getSubRows);
1393
+ const parents = byTopLevel ? topRows.filter((row) => row.depth === 0) : [];
1394
+ const paged = byTopLevel ? pageTopLevel(parents, page - 1, pageSize && pageSize > 0 ? pageSize : parents.length || 1) : pageFlat(flat, page - 1, pageSize && pageSize > 0 ? pageSize : flat.filter((entry) => entry.kind === "leaf").length || 1);
1395
+ const pageTotal = byTopLevel ? parents.length : filteredRows.length;
1396
+ const pageRows = byTopLevel ? parents.slice(paged.bounds.start, paged.bounds.end).flatMap((row) => leafRows(row)).map((row) => row.original) : paged.entries.filter((entry) => entry.kind === "leaf").map((entry) => entry.row.original);
1368
1397
  const totals = useMemo(() => {
1369
1398
  const rows = filteredModel.flatRows.filter((row) => !row.getIsGrouped?.() && !(options.getSubRows && row.subRows?.length)).map((row) => row.original);
1370
1399
  const result = {};
@@ -1420,6 +1449,8 @@ function useCollection(options) {
1420
1449
  hiddenSelectedCount: hiddenSelectedCount(selection, filteredIds),
1421
1450
  filteredRows,
1422
1451
  filteredCount: filteredRows.length,
1452
+ pageTotal,
1453
+ pageRows,
1423
1454
  totalCount: options.rows.length,
1424
1455
  totals,
1425
1456
  page: paged.bounds.pageIndex + 1,
@@ -1489,6 +1520,17 @@ function flattenVisible(rows, groupId, nested = false) {
1489
1520
  }
1490
1521
  return out;
1491
1522
  }
1523
+ function pageTopLevel(rows, pageIndex, pageSize) {
1524
+ const bounds = pageBounds(pageIndex, pageSize, rows.length);
1525
+ const entries = [];
1526
+ for (const row of rows.slice(bounds.start, bounds.end)) entries.push(...flattenVisible([row]));
1527
+ return { entries, leafCount: entries.filter((entry) => entry.kind === "leaf").length, bounds };
1528
+ }
1529
+ function leafRows(row) {
1530
+ const children = row.subRows;
1531
+ if (!children?.length) return [row];
1532
+ return children.flatMap(leafRows);
1533
+ }
1492
1534
 
1493
1535
  export { CollectionPagination, CollectionToolbar, DataList, DataTable, collectionTableOptions, defineColumns, headerLabel, totalText, useCollection };
1494
1536
  //# sourceMappingURL=index.js.map