@assure-one/design-system 1.47.0 → 1.48.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 (32) hide show
  1. package/codemods/lib/entry-map.mjs +4 -0
  2. package/dist/{brand-scope-DwLzUrSY.d.ts → brand-scope-ObBYNT0J.d.ts} +3 -63
  3. package/dist/{chunk-AM4TSTSH.js → chunk-FO2WSYUA.js} +800 -340
  4. package/dist/chunk-FO2WSYUA.js.map +1 -0
  5. package/dist/{chunk-IIBDSTOG.js → chunk-HV24JSRN.js} +215 -10
  6. package/dist/chunk-HV24JSRN.js.map +1 -0
  7. package/dist/{chunk-FIRPQWYR.js → chunk-YRFLK7ER.js} +1258 -270
  8. package/dist/chunk-YRFLK7ER.js.map +1 -0
  9. package/dist/css/components.css +1 -1
  10. package/dist/{data-table-view-D9a1_IRF.d.ts → data-table-view-CNTfzEyS.d.ts} +769 -16
  11. package/dist/{design-system-provider-C6Tu1vyg.d.ts → design-system-provider-YblRtwYw.d.ts} +1 -1
  12. package/dist/index.d.ts +914 -392
  13. package/dist/index.js +3227 -340
  14. package/dist/index.js.map +1 -1
  15. package/dist/legacy/index.d.ts +4 -3
  16. package/dist/legacy/index.js +2 -2
  17. package/dist/next/index.d.ts +2 -2
  18. package/dist/option-izy_4w4Z.d.ts +64 -0
  19. package/dist/styles.css +1 -1
  20. package/dist/suite/index.d.ts +38 -4
  21. package/dist/suite/index.js +2 -2
  22. package/dist/{types-BTHIs2nk.d.ts → types-CKpkEBvW.d.ts} +282 -1
  23. package/docs/components.md +19 -7
  24. package/docs/components.registry.json +152 -8
  25. package/package.json +2 -1
  26. package/public/brand/assure-audit.svg +1 -1
  27. package/public/brand/assure-books.svg +1 -1
  28. package/public/brand/assure-pro.svg +1 -1
  29. package/public/brand/assure-tax.svg +1 -1
  30. package/dist/chunk-AM4TSTSH.js.map +0 -1
  31. package/dist/chunk-FIRPQWYR.js.map +0 -1
  32. package/dist/chunk-IIBDSTOG.js.map +0 -1
@@ -1,14 +1,16 @@
1
- import { slot, badgeAnatomy, stateAttributes, useDsMessages, deprecate, paginationAnatomy, Select, microInteractions, useFieldsetContext, useFieldControl, useControllableState, useFormReset2, useMergedRefs, switchAnatomy, CONTROL_LABEL_TEXT, tableAnatomy, Pressable, Tooltip, TooltipTrigger, TooltipContent, FormBridge, CONTROL_SIZE_CLASSES, FileUpload, FileChip, Button, Avatar, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuLabel, dataTableAnatomy, Checkbox, DocumentDetailPanel, DocumentDetailHeader, DocumentDetailTitle, DocumentDetailRequester, DocumentDetailMetaRow, DocumentDetailActions, DropdownMenuItem, DocumentList, DocumentRow, DocumentListSection, Textarea, DropdownMenuCheckboxItem, Popover, PopoverTrigger, PopoverContent, PopoverClose, DropdownMenuShortcut, useId } from './chunk-IIBDSTOG.js';
2
- import { cn, ShieldIcon, UploadIcon, SendIcon, ChevronLeftIcon, MoreHorizontalIcon, ChevronRightIcon, ChevronUpIcon, ChevronDownIcon, ChevronsUpDownIcon, LockIcon, TrashIcon, HelpCircleIcon, ExtractIcon, ArrowRightIcon, CheckIcon, EyeIcon, FileTextIcon, CloseIcon, SparklesIcon, PlusIcon, SearchIcon, AlertTriangleIcon, FolderClosedIcon, CalendarIcon, PencilIcon, LockOpenIcon, FlagIcon, CheckCircleSolidIcon, DownloadIcon, XCircleSolidIcon, MessageCircleIcon, BellIcon, PaperclipIcon, UsersIcon, ClockIcon, PlayIcon, PauseIcon, StopIcon, SettingsIcon, Trash2Icon, FileIcon, SparkleIcon, MailIcon } from './chunk-ARCWF2S3.js';
1
+ import { slot, badgeAnatomy, stateAttributes, useDsMessages, deprecate, paginationAnatomy, Select, microInteractions, useFieldsetContext, useFieldControl, useControllableState, useFormReset2, useMergedRefs, switchAnatomy, CONTROL_LABEL_TEXT, tableAnatomy, Pressable, Tooltip, TooltipTrigger, TooltipContent, FormBridge, CONTROL_SIZE_CLASSES, FileUpload, FileChip, Button, Avatar, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuLabel, dataTableAnatomy, Checkbox, IconButton, DropdownMenuItem, DocumentDetailPanel, DocumentDetailHeader, DocumentDetailTitle, DocumentDetailRequester, DocumentDetailMetaRow, DocumentDetailActions, DocumentList, DocumentRow, DocumentListSection, Textarea, DropdownMenuCheckboxItem, Popover, PopoverTrigger, PopoverContent, PopoverClose, DropdownMenuShortcut, useId } from './chunk-HV24JSRN.js';
2
+ import { cn, ShieldIcon, UploadIcon, SendIcon, ChevronLeftIcon, MoreHorizontalIcon, ChevronRightIcon, ChevronUpIcon, ChevronDownIcon, ChevronsUpDownIcon, LockIcon, TrashIcon, HelpCircleIcon, ExtractIcon, ArrowRightIcon, CheckIcon, EyeIcon, FileTextIcon, CloseIcon, SparklesIcon, PlusIcon, SearchIcon, ArrowUpIcon, ArrowDownIcon, XIcon, ArrowLeftIcon, PinOffIcon, PinIcon, EyeOffIcon, RotateCcwIcon, AlertTriangleIcon, FolderClosedIcon, CalendarIcon, PencilIcon, LockOpenIcon, FlagIcon, CheckCircleSolidIcon, DownloadIcon, XCircleSolidIcon, MessageCircleIcon, BellIcon, PaperclipIcon, UsersIcon, ClockIcon, PlayIcon, PauseIcon, StopIcon, SettingsIcon, Trash2Icon, FileIcon, SparkleIcon, MailIcon } from './chunk-ARCWF2S3.js';
3
3
  import { clsx } from 'clsx';
4
4
  import * as React3 from 'react';
5
- import { forwardRef, useMemo, createContext, useRef, useContext, useState, useCallback, useEffect } from 'react';
5
+ import { forwardRef, useMemo, createContext, useRef, useContext, useState, useCallback, useEffect, useLayoutEffect } from 'react';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import { cva } from 'class-variance-authority';
8
8
  import * as ProgressPrimitive from '@radix-ui/react-progress';
9
9
  import * as SwitchPrimitive from '@radix-ui/react-switch';
10
+ import * as VisuallyHiddenPrimitive from '@radix-ui/react-visually-hidden';
10
11
  import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
11
12
  import * as TabsPrimitive from '@radix-ui/react-tabs';
13
+ import { useVirtualizer } from '@tanstack/react-virtual';
12
14
 
13
15
  var LEGACY_PAIRS = {
14
16
  default: { variant: "solid", intent: "brand" },
@@ -1039,18 +1041,12 @@ var Switch = React3.forwardRef(
1039
1041
  }
1040
1042
  );
1041
1043
  Switch.displayName = SwitchPrimitive.Root.displayName;
1042
- function useOverflowTooltip() {
1043
- const ref = React3.useRef(null);
1044
- const [overflowing, setOverflowing] = React3.useState(false);
1045
- const measure = React3.useCallback(() => {
1046
- const el = ref.current;
1047
- if (!el) return;
1048
- setOverflowing(el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight);
1049
- }, []);
1050
- return { ref, overflowing, onPointerEnter: measure, onFocus: measure };
1051
- }
1052
- var DEFAULT_TABLE_CONTEXT = { headerStyle: "plain", stickyHeader: false };
1053
- var TableContext = createContext(DEFAULT_TABLE_CONTEXT);
1044
+ var VisuallyHidden = forwardRef(
1045
+ function VisuallyHidden2({ children, ...props }, ref) {
1046
+ return /* @__PURE__ */ jsx(VisuallyHiddenPrimitive.Root, { ref, ...props, children });
1047
+ }
1048
+ );
1049
+ VisuallyHidden.displayName = "VisuallyHidden";
1054
1050
  function useOverflowing(target) {
1055
1051
  const [overflowing, setOverflowing] = useState(false);
1056
1052
  useEffect(() => {
@@ -1062,12 +1058,23 @@ function useOverflowing(target) {
1062
1058
  measure();
1063
1059
  const observer = new ResizeObserver(measure);
1064
1060
  observer.observe(element);
1065
- const table = element.firstElementChild;
1066
- if (table) observer.observe(table);
1061
+ for (const table of element.querySelectorAll("table")) observer.observe(table);
1067
1062
  return () => observer.disconnect();
1068
1063
  }, [target]);
1069
1064
  return overflowing;
1070
1065
  }
1066
+ function useOverflowTooltip() {
1067
+ const ref = React3.useRef(null);
1068
+ const [overflowing, setOverflowing] = React3.useState(false);
1069
+ const measure = React3.useCallback(() => {
1070
+ const el = ref.current;
1071
+ if (!el) return;
1072
+ setOverflowing(el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight);
1073
+ }, []);
1074
+ return { ref, overflowing, onPointerEnter: measure, onFocus: measure };
1075
+ }
1076
+ var DEFAULT_TABLE_CONTEXT = { headerStyle: "plain", stickyHeader: false };
1077
+ var TableContext = createContext(DEFAULT_TABLE_CONTEXT);
1071
1078
  var Table = forwardRef(function Table2({
1072
1079
  className,
1073
1080
  wrapperClassName,
@@ -1076,6 +1083,7 @@ var Table = forwardRef(function Table2({
1076
1083
  density,
1077
1084
  stickyHeader = false,
1078
1085
  messages: messagesOverride,
1086
+ stickyFirstColumn = false,
1079
1087
  ...props
1080
1088
  }, ref) {
1081
1089
  const messages = useDsMessages("table", messagesOverride);
@@ -1088,9 +1096,11 @@ var Table = forwardRef(function Table2({
1088
1096
  ref: wrapperRef,
1089
1097
  ...slot(tableAnatomy, "wrapper"),
1090
1098
  ...density === void 0 ? {} : { "data-ds-density": density },
1099
+ ...stickyFirstColumn ? { "data-sticky-first": "" } : {},
1091
1100
  ...overflowing ? { role: "region", "aria-label": messages.region, tabIndex: 0 } : {},
1092
1101
  className: cn(
1093
- "ds:rounded-card ds:border-rule-strong ds:bg-surface ds:w-full ds:overflow-x-auto ds:border ds:shadow-quiet",
1102
+ "ds:rounded-card ds:border-rule-strong ds:bg-surface ds:w-full ds:min-w-0 ds:overflow-x-auto ds:border ds:shadow-quiet",
1103
+ stickyFirstColumn && tableStickyFirstColumnClass,
1094
1104
  overflowing && "ds:focus-visible:outline-none ds:focus-visible:[box-shadow:var(--shadow-focus-ring)]",
1095
1105
  wrapperClassName,
1096
1106
  classNames?.wrapper
@@ -1203,6 +1213,15 @@ var STICKY_CLASSES = {
1203
1213
  end: "ds:sticky ds:end-0 ds:[box-shadow:inset_1px_0_0_var(--color-rule-soft)]"
1204
1214
  };
1205
1215
  var STICKY_CELL_SURFACE = "ds:bg-surface ds:[tr:hover>&]:bg-surface-hover ds:[tr[data-state=selected]>&]:bg-brand-bg ds:[tr[data-state=selected]:hover>&]:bg-brand-bg-hover";
1216
+ var tableStickyFirstColumnClass = cn(
1217
+ // Group rows (`TableGroupRow`) are left out: their label cell spans several
1218
+ // columns and keeps its own surface and sticky-top rule, so it must not
1219
+ // pick up the identity column's edge rule or body surface.
1220
+ "ds:[&_tr:not([data-slot=table-group-row])>:first-child]:sticky ds:[&_tr:not([data-slot=table-group-row])>:first-child]:start-0 ds:[&_tr:not([data-slot=table-group-row])>:first-child]:[box-shadow:inset_-1px_0_0_var(--color-rule-soft)]",
1221
+ "ds:[&_tbody_tr:not([data-slot=table-group-row])>td:first-child]:bg-surface ds:[&_tbody_tr:not([data-slot=table-group-row]):hover>td:first-child]:bg-surface-hover",
1222
+ "ds:[&_tbody_tr[data-state=selected]>td:first-child]:bg-brand-bg ds:[&_tbody_tr[data-state=selected]:hover>td:first-child]:bg-brand-bg-hover",
1223
+ "ds:[&_thead_tr>th:first-child]:z-[calc(var(--z-base)+2)] ds:[&_tbody_tr:not([data-slot=table-group-row])>td:first-child]:z-[var(--z-base)]"
1224
+ );
1206
1225
  function cellCommon({ align, numeric, truncate, sticky }, stickySurface) {
1207
1226
  const resolvedAlign = align ?? (numeric ? "end" : void 0);
1208
1227
  return cn(
@@ -1217,6 +1236,10 @@ function widthStyle(width, style) {
1217
1236
  if (width === void 0) return style;
1218
1237
  return { width, ...style };
1219
1238
  }
1239
+ var RESIZE_MIN = 48;
1240
+ var RESIZE_MAX = 800;
1241
+ var RESIZE_STEP = 10;
1242
+ var RESIZE_STEP_LARGE = 50;
1220
1243
  var HEAD_TYPOGRAPHY = {
1221
1244
  plain: "ds:font-sans ds:text-sm ds:font-medium ds:tracking-normal ds:normal-case ds:text-fg-3",
1222
1245
  overline: "ds:font-mono ds:text-overline ds:font-medium ds:tracking-[0.14em] ds:uppercase ds:text-fg-4"
@@ -1230,27 +1253,42 @@ var TableHead = forwardRef(function TableHead2({
1230
1253
  sticky,
1231
1254
  sort,
1232
1255
  onSortChange,
1256
+ sortIndex,
1257
+ sortFirstDirection = "asc",
1258
+ menu,
1259
+ resizable = false,
1260
+ onWidthChange,
1261
+ onAutoSize,
1262
+ minWidth = RESIZE_MIN,
1263
+ maxWidth = RESIZE_MAX,
1233
1264
  style,
1234
1265
  children,
1235
1266
  ...props
1236
1267
  }, ref) {
1237
1268
  const { headerStyle, stickyHeader } = useContext(TableContext);
1269
+ const messages = useDsMessages("table");
1270
+ const cellRef = useRef(null);
1271
+ const mergedRef = useMergedRefs(ref, cellRef);
1238
1272
  const sortable = sort !== void 0;
1239
- const ariaSort = sortable ? sort === "asc" ? "ascending" : sort === "desc" ? "descending" : "none" : void 0;
1240
- const handleSort = () => {
1241
- const next = sort === null ? "asc" : sort === "asc" ? "desc" : null;
1242
- onSortChange?.(next);
1273
+ const ariaSort = sortable ? sortIndex !== void 0 && sortIndex > 1 ? "none" : sort === "asc" ? "ascending" : sort === "desc" ? "descending" : "none" : void 0;
1274
+ const handleSort = (event) => {
1275
+ const second = sortFirstDirection === "asc" ? "desc" : "asc";
1276
+ const next = sort === null || sort === void 0 ? sortFirstDirection : sort === sortFirstDirection ? second : null;
1277
+ onSortChange?.(next, { additive: event.shiftKey });
1243
1278
  };
1244
- return /* @__PURE__ */ jsx(
1279
+ const showRank = sortable && sort != null && sortIndex !== void 0 && sortIndex > 0;
1280
+ const rankText = showRank && sortIndex !== void 0 ? sort === "asc" ? messages.sortedAscending(sortIndex) : messages.sortedDescending(sortIndex) : void 0;
1281
+ const label = typeof children === "string" ? children : void 0;
1282
+ return /* @__PURE__ */ jsxs(
1245
1283
  "th",
1246
1284
  {
1247
- ref,
1285
+ ref: mergedRef,
1248
1286
  ...slot(tableAnatomy, "head"),
1249
1287
  ...sortable ? { "aria-sort": ariaSort, ...stateAttributes({ state: sort ?? void 0 }) } : {},
1250
1288
  className: cn(
1251
1289
  // Sentence-case column labels in the body font (Plus Jakarta Sans),
1252
1290
  // quiet gray — not mono uppercase eyebrows.
1253
- "ds:h-(--table-row-height) ds:px-(--table-cell-padding-x) ds:py-(--table-head-padding-y) ds:text-left ds:align-middle ds:whitespace-nowrap",
1291
+ "ds:group/table-head ds:h-(--table-row-height) ds:px-(--table-cell-padding-x) ds:py-(--table-head-padding-y) ds:text-left ds:align-middle ds:whitespace-nowrap",
1254
1292
  "ds:font-sans ds:text-(length:--control-font-size-md) ds:font-medium ds:tracking-[0.01em] ds:normal-case ds:tabular-nums",
1255
1293
  "ds:text-fg-3 ds:bg-surface-2",
1256
1294
  "ds:border-rule ds:border-b",
@@ -1263,42 +1301,141 @@ var TableHead = forwardRef(function TableHead2({
1263
1301
  ),
1264
1302
  // A head cell that is both a sticky column and in a sticky header is the corner: above both.
1265
1303
  cellCommon({ align, numeric, truncate, sticky }, STICKY_CORNER_Z),
1304
+ // The handle is absolutely positioned on the trailing edge.
1305
+ resizable && !sticky && !stickyHeader && "ds:relative",
1266
1306
  className
1267
1307
  ),
1268
1308
  style: widthStyle(width, style),
1269
1309
  ...props,
1270
- children: sortable ? /* @__PURE__ */ jsxs(
1271
- Pressable,
1272
- {
1273
- onClick: handleSort,
1274
- ...slot(tableAnatomy, "sort"),
1275
- className: cn(
1276
- "ds:-mx-1 ds:-my-1 ds:inline-flex ds:items-center ds:gap-1 ds:rounded-icon ds:px-1 ds:py-1",
1277
- HEAD_TYPOGRAPHY[headerStyle],
1278
- "ds:transition-colors ds:duration-[var(--duration-fast)] ds:ease-[var(--ease-out-quart)] ds:motion-reduce:transition-none",
1279
- "ds:hover:text-fg"
1280
- ),
1281
- children: [
1282
- children,
1283
- /* @__PURE__ */ jsx(
1284
- "span",
1285
- {
1286
- "aria-hidden": "true",
1287
- ...slot(tableAnatomy, "sort-indicator"),
1288
- className: cn(
1289
- "ds:text-fg-5 ds:inline-flex ds:shrink-0 ds:items-center ds:justify-center",
1290
- sort && "ds:text-fg-3"
1291
- ),
1292
- children: sort === "asc" ? /* @__PURE__ */ jsx(ChevronUpIcon, { className: "ds:size-3", strokeWidth: 2.2 }) : sort === "desc" ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "ds:size-3", strokeWidth: 2.2 }) : /* @__PURE__ */ jsx(ChevronsUpDownIcon, { className: "ds:size-3", strokeWidth: 2 })
1293
- }
1294
- )
1295
- ]
1296
- }
1297
- ) : children
1310
+ children: [
1311
+ sortable ? /* @__PURE__ */ jsxs(
1312
+ Pressable,
1313
+ {
1314
+ onClick: handleSort,
1315
+ ...slot(tableAnatomy, "sort"),
1316
+ className: cn(
1317
+ "ds:-mx-1 ds:-my-1 ds:inline-flex ds:items-center ds:gap-1 ds:rounded-icon ds:px-1 ds:py-1",
1318
+ HEAD_TYPOGRAPHY[headerStyle],
1319
+ "ds:transition-colors ds:duration-[var(--duration-fast)] ds:ease-[var(--ease-out-quart)] ds:motion-reduce:transition-none",
1320
+ "ds:hover:text-fg"
1321
+ ),
1322
+ children: [
1323
+ children,
1324
+ /* @__PURE__ */ jsx(
1325
+ "span",
1326
+ {
1327
+ "aria-hidden": "true",
1328
+ ...slot(tableAnatomy, "sort-indicator"),
1329
+ className: cn(
1330
+ "ds:text-fg-5 ds:inline-flex ds:shrink-0 ds:items-center ds:justify-center",
1331
+ sort && "ds:text-fg-3"
1332
+ ),
1333
+ children: sort === "asc" ? /* @__PURE__ */ jsx(ChevronUpIcon, { className: "ds:size-3", strokeWidth: 2.2 }) : sort === "desc" ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "ds:size-3", strokeWidth: 2.2 }) : /* @__PURE__ */ jsx(ChevronsUpDownIcon, { className: "ds:size-3", strokeWidth: 2 })
1334
+ }
1335
+ ),
1336
+ showRank && /* @__PURE__ */ jsx(
1337
+ "span",
1338
+ {
1339
+ "aria-hidden": "true",
1340
+ ...slot(tableAnatomy, "sort-rank"),
1341
+ className: "ds:-ms-0.5 ds:self-start ds:text-(length:--text-caption) ds:leading-none ds:font-semibold ds:text-fg-3 ds:tabular-nums",
1342
+ children: sortIndex
1343
+ }
1344
+ ),
1345
+ rankText && /* @__PURE__ */ jsxs(VisuallyHidden, { children: [
1346
+ ", ",
1347
+ rankText
1348
+ ] })
1349
+ ]
1350
+ }
1351
+ ) : children,
1352
+ menu,
1353
+ resizable && /* @__PURE__ */ jsx(
1354
+ ResizeHandle,
1355
+ {
1356
+ label: label ? messages.resizeColumnNamed(label) : messages.resizeColumn,
1357
+ cell: cellRef,
1358
+ width: typeof width === "number" ? width : void 0,
1359
+ min: minWidth,
1360
+ max: maxWidth,
1361
+ onWidthChange,
1362
+ onAutoSize
1363
+ }
1364
+ )
1365
+ ]
1298
1366
  }
1299
1367
  );
1300
1368
  });
1301
1369
  TableHead.displayName = "TableHead";
1370
+ function ResizeHandle({
1371
+ label,
1372
+ cell,
1373
+ width,
1374
+ min,
1375
+ max,
1376
+ onWidthChange,
1377
+ onAutoSize
1378
+ }) {
1379
+ const drag = useRef(null);
1380
+ const current = () => width ?? Math.round(cell.current?.getBoundingClientRect().width ?? min);
1381
+ const clamp = (n) => Math.min(max, Math.max(min, Math.round(n)));
1382
+ return (
1383
+ // A focusable separator with a value is the WAI-ARIA window-splitter pattern; jsx-a11y does not model it.
1384
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
1385
+ /* @__PURE__ */ jsx(
1386
+ "div",
1387
+ {
1388
+ role: "separator",
1389
+ "aria-orientation": "vertical",
1390
+ "aria-label": label,
1391
+ "aria-valuenow": width ?? void 0,
1392
+ "aria-valuemin": min,
1393
+ "aria-valuemax": max,
1394
+ tabIndex: 0,
1395
+ ...slot(tableAnatomy, "resize-handle"),
1396
+ onClick: (event) => event.stopPropagation(),
1397
+ onDoubleClick: (event) => {
1398
+ event.stopPropagation();
1399
+ onAutoSize?.();
1400
+ },
1401
+ onPointerDown: (event) => {
1402
+ event.preventDefault();
1403
+ event.stopPropagation();
1404
+ const rtl = cell.current ? getComputedStyle(cell.current).direction === "rtl" : false;
1405
+ drag.current = { startX: event.clientX, startWidth: current(), dir: rtl ? -1 : 1 };
1406
+ event.currentTarget.setPointerCapture?.(event.pointerId);
1407
+ },
1408
+ onPointerMove: (event) => {
1409
+ const state = drag.current;
1410
+ if (!state) return;
1411
+ onWidthChange?.(clamp(state.startWidth + (event.clientX - state.startX) * state.dir));
1412
+ },
1413
+ onPointerUp: (event) => {
1414
+ drag.current = null;
1415
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
1416
+ },
1417
+ onKeyDown: (event) => {
1418
+ const step = event.shiftKey ? RESIZE_STEP_LARGE : RESIZE_STEP;
1419
+ if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
1420
+ event.preventDefault();
1421
+ event.stopPropagation();
1422
+ onWidthChange?.(clamp(current() + (event.key === "ArrowRight" ? step : -step)));
1423
+ } else if (event.key === "Enter") {
1424
+ event.preventDefault();
1425
+ event.stopPropagation();
1426
+ onAutoSize?.();
1427
+ }
1428
+ },
1429
+ className: cn(
1430
+ "ds:absolute ds:inset-y-0 ds:end-0 ds:w-2 ds:cursor-col-resize ds:touch-none ds:select-none",
1431
+ // Reveal the guide on header hover or keyboard focus; the hit target stays available.
1432
+ "ds:after:absolute ds:after:inset-y-2 ds:after:end-0.5 ds:after:w-px ds:after:bg-rule ds:after:opacity-0 ds:group-hover/table-head:after:opacity-100 ds:focus-visible:after:opacity-100 ds:after:transition-colors ds:motion-reduce:after:transition-none",
1433
+ "ds:hover:after:w-0.5 ds:hover:after:bg-brand ds:focus-visible:outline-none ds:focus-visible:after:w-0.5 ds:focus-visible:after:bg-brand"
1434
+ )
1435
+ }
1436
+ )
1437
+ );
1438
+ }
1302
1439
  var TableCell = forwardRef(function TableCell2({
1303
1440
  className,
1304
1441
  align,
@@ -1365,6 +1502,24 @@ var TableCell = forwardRef(function TableCell2({
1365
1502
  );
1366
1503
  });
1367
1504
  TableCell.displayName = "TableCell";
1505
+ var TableCellSecondary = forwardRef(
1506
+ function TableCellSecondary2({ className, ...props }, ref) {
1507
+ return /* @__PURE__ */ jsx(
1508
+ "span",
1509
+ {
1510
+ ref,
1511
+ ...slot(tableAnatomy, "cell-secondary"),
1512
+ className: cn(
1513
+ "ds:mt-0.5 ds:block ds:truncate ds:text-xs ds:font-normal ds:text-fg-3",
1514
+ "ds:[[data-ds-density=compact]_&]:hidden",
1515
+ className
1516
+ ),
1517
+ ...props
1518
+ }
1519
+ );
1520
+ }
1521
+ );
1522
+ TableCellSecondary.displayName = "TableCellSecondary";
1368
1523
  var STICKY_GROUP_ROW_Z = "ds:z-[var(--z-base)]";
1369
1524
  var TableGroupRow = forwardRef(
1370
1525
  function TableGroupRow2({
@@ -1375,65 +1530,102 @@ var TableGroupRow = forwardRef(
1375
1530
  open = true,
1376
1531
  onOpenChange,
1377
1532
  messages: messagesProp,
1533
+ count,
1534
+ totalCount,
1535
+ locale,
1536
+ subtotals,
1537
+ sticky: stickyProp,
1378
1538
  className,
1379
1539
  ...props
1380
1540
  }, ref) {
1381
1541
  const messages = useDsMessages("table", messagesProp);
1382
1542
  const { stickyHeader } = useContext(TableContext);
1383
- return /* @__PURE__ */ jsx("tr", { ref, ...slot(tableAnatomy, "group-row"), ...props, children: /* @__PURE__ */ jsx(
1384
- "td",
1385
- {
1386
- colSpan,
1387
- className: cn(
1388
- "ds:bg-surface-2 ds:border-rule ds:h-(--table-row-height) ds:border-b ds:px-(--table-cell-padding-x) ds:align-middle",
1389
- // Stuck under the sticky header (W6-10's own token, so the two
1390
- // heights can never drift apart) rather than on the `<tr>`: a
1391
- // sticky `<tr>` in a collapsed-border table is the flaky part
1392
- // across engines, a sticky cell is the reliable one — the same
1393
- // choice `TableHead`'s own sticky rule makes above.
1394
- stickyHeader && cn("ds:sticky ds:top-(--table-row-height) ds:[box-shadow:inset_0_-1px_0_var(--color-rule)]", STICKY_GROUP_ROW_Z),
1395
- className
1396
- ),
1397
- children: /* @__PURE__ */ jsxs("div", { className: "ds:flex ds:items-center ds:gap-1.5", children: [
1398
- collapsible && /* @__PURE__ */ jsx(
1399
- Pressable,
1400
- {
1401
- ...slot(tableAnatomy, "group-row-toggle"),
1402
- "aria-expanded": open,
1403
- onClick: () => onOpenChange?.(!open),
1404
- "aria-label": open ? messages.collapseSection : messages.expandSection,
1405
- className: "ds:-ml-1 ds:inline-flex ds:size-5 ds:shrink-0 ds:items-center ds:justify-center ds:rounded-icon ds:text-fg-4 ds:hover:text-fg-2",
1406
- children: /* @__PURE__ */ jsx(
1407
- ChevronDownIcon,
1408
- {
1409
- className: cn(
1410
- "ds:size-3.5 ds:transition-transform ds:duration-[var(--duration-fast)] ds:ease-[var(--ease-out-quart)] ds:motion-reduce:transition-none",
1411
- !open && "ds:-rotate-90"
1412
- ),
1413
- "aria-hidden": "true"
1414
- }
1415
- )
1416
- }
1543
+ const sticky = stickyProp ?? stickyHeader;
1544
+ const firstSubtotal = subtotals ? subtotals.findIndex((cell, i) => i > 0 && cell != null) : -1;
1545
+ const labelSpan = firstSubtotal > 0 ? firstSubtotal : colSpan;
1546
+ const format = (n) => n.toLocaleString(locale);
1547
+ const countText = count === void 0 ? null : totalCount !== void 0 && totalCount > count ? messages.groupLoadedOf(format(count), format(totalCount)) : format(count);
1548
+ const cellClass = cn(
1549
+ "ds:bg-surface-2 ds:border-rule ds:h-(--table-row-height) ds:border-b ds:px-(--table-cell-padding-x) ds:align-middle",
1550
+ sticky && cn("ds:sticky ds:top-(--table-row-height) ds:[box-shadow:inset_0_-1px_0_var(--color-rule)]", STICKY_GROUP_ROW_Z)
1551
+ );
1552
+ return /* @__PURE__ */ jsxs("tr", { ref, ...slot(tableAnatomy, "group-row"), ...props, children: [
1553
+ /* @__PURE__ */ jsx(
1554
+ "td",
1555
+ {
1556
+ colSpan: labelSpan,
1557
+ className: cn(
1558
+ "ds:bg-surface-2 ds:border-rule ds:h-(--table-row-height) ds:border-b ds:px-(--table-cell-padding-x) ds:align-middle",
1559
+ // Stuck under the sticky header (W6-10's own token, so the two
1560
+ // heights can never drift apart) rather than on the `<tr>`: a
1561
+ // sticky `<tr>` in a collapsed-border table is the flaky part
1562
+ // across engines, a sticky cell is the reliable one — the same
1563
+ // choice `TableHead`'s own sticky rule makes above.
1564
+ sticky && cn("ds:sticky ds:top-(--table-row-height) ds:[box-shadow:inset_0_-1px_0_var(--color-rule)]", STICKY_GROUP_ROW_Z),
1565
+ className
1417
1566
  ),
1418
- /* @__PURE__ */ jsx(
1419
- "span",
1420
- {
1421
- ...slot(tableAnatomy, "group-row-label"),
1422
- className: "ds:font-mono ds:text-overline ds:text-fg-4 ds:font-medium ds:tracking-[0.14em] ds:uppercase",
1423
- children: label
1424
- }
1567
+ children: /* @__PURE__ */ jsxs("div", { className: "ds:flex ds:items-center ds:gap-1.5", children: [
1568
+ collapsible && /* @__PURE__ */ jsx(
1569
+ Pressable,
1570
+ {
1571
+ ...slot(tableAnatomy, "group-row-toggle"),
1572
+ "aria-expanded": open,
1573
+ onClick: () => onOpenChange?.(!open),
1574
+ "aria-label": open ? messages.collapseSection : messages.expandSection,
1575
+ className: "ds:-ml-1 ds:inline-flex ds:size-5 ds:shrink-0 ds:items-center ds:justify-center ds:rounded-icon ds:text-fg-4 ds:hover:text-fg-2",
1576
+ children: /* @__PURE__ */ jsx(
1577
+ ChevronDownIcon,
1578
+ {
1579
+ className: cn(
1580
+ "ds:size-3.5 ds:transition-transform ds:duration-[var(--duration-fast)] ds:ease-[var(--ease-out-quart)] ds:motion-reduce:transition-none",
1581
+ !open && "ds:-rotate-90"
1582
+ ),
1583
+ "aria-hidden": "true"
1584
+ }
1585
+ )
1586
+ }
1587
+ ),
1588
+ /* @__PURE__ */ jsx(
1589
+ "span",
1590
+ {
1591
+ ...slot(tableAnatomy, "group-row-label"),
1592
+ className: "ds:font-mono ds:text-overline ds:text-fg-4 ds:font-medium ds:tracking-[0.14em] ds:uppercase",
1593
+ children: label
1594
+ }
1595
+ ),
1596
+ countText !== null && /* @__PURE__ */ jsx(
1597
+ "span",
1598
+ {
1599
+ ...slot(tableAnatomy, "group-row-count"),
1600
+ className: "ds:text-fg-3 ds:shrink-0 ds:text-xs ds:tabular-nums",
1601
+ children: countText
1602
+ }
1603
+ ),
1604
+ trailing && /* @__PURE__ */ jsx(
1605
+ "span",
1606
+ {
1607
+ ...slot(tableAnatomy, "group-row-trailing"),
1608
+ className: "ds:text-fg-3 ds:ml-auto ds:shrink-0 ds:text-xs",
1609
+ children: trailing
1610
+ }
1611
+ )
1612
+ ] })
1613
+ }
1614
+ ),
1615
+ subtotals && firstSubtotal > 0 && subtotals.slice(labelSpan, colSpan).map((cell, i) => /* @__PURE__ */ jsx(
1616
+ "td",
1617
+ {
1618
+ ...slot(tableAnatomy, "group-row-subtotal"),
1619
+ className: cn(
1620
+ cellClass,
1621
+ "ds:text-sm ds:font-medium ds:text-fg-2",
1622
+ cell?.numeric && "ds:text-right ds:tabular-nums"
1425
1623
  ),
1426
- trailing && /* @__PURE__ */ jsx(
1427
- "span",
1428
- {
1429
- ...slot(tableAnatomy, "group-row-trailing"),
1430
- className: "ds:text-fg-3 ds:ml-auto ds:shrink-0 ds:text-xs",
1431
- children: trailing
1432
- }
1433
- )
1434
- ] })
1435
- }
1436
- ) });
1624
+ children: cell?.value
1625
+ },
1626
+ labelSpan + i
1627
+ ))
1628
+ ] });
1437
1629
  }
1438
1630
  );
1439
1631
  TableGroupRow.displayName = "TableGroupRow";
@@ -1900,8 +2092,26 @@ function filterValues(value) {
1900
2092
  if (value == null || value === "") return EMPTY_LIST;
1901
2093
  return typeof value === "string" ? [value] : value;
1902
2094
  }
1903
- function nextSortDirection(current) {
1904
- return current === null ? "asc" : current === "asc" ? "desc" : null;
2095
+ function nextSortDirection(current, first = "asc") {
2096
+ const second = first === "asc" ? "desc" : "asc";
2097
+ return current === null ? first : current === first ? second : null;
2098
+ }
2099
+ var EMPTY_PINNING = Object.freeze({ start: EMPTY_LIST });
2100
+ function sameSort(a, b) {
2101
+ return a === b || !!a && !!b && a.columnId === b.columnId && a.direction === b.direction;
2102
+ }
2103
+ function resolveOrder(ids, order) {
2104
+ const known = new Set(ids);
2105
+ const seen = /* @__PURE__ */ new Set();
2106
+ const result = [];
2107
+ for (const id of order) {
2108
+ if (known.has(id) && !seen.has(id)) {
2109
+ seen.add(id);
2110
+ result.push(id);
2111
+ }
2112
+ }
2113
+ for (const id of ids) if (!seen.has(id)) result.push(id);
2114
+ return result;
1905
2115
  }
1906
2116
  function useDataTable(options) {
1907
2117
  const {
@@ -1915,13 +2125,21 @@ function useDataTable(options) {
1915
2125
  resetPageOnChange = true
1916
2126
  } = options;
1917
2127
  const id = useId(options.id, "ds-table");
1918
- const [sorting, setSortingState] = useSlice(
1919
- options.sorting,
1920
- options.state?.sorting,
1921
- options.defaultSorting,
1922
- options.defaultState?.sorting,
1923
- null
1924
- );
2128
+ const [internalMulti, setInternalMulti] = useState(() => {
2129
+ const multi = options.defaultMultiSorting ?? options.defaultState?.multiSorting;
2130
+ if (multi !== void 0) return multi;
2131
+ const single = options.defaultSorting !== void 0 ? options.defaultSorting : options.defaultState?.sorting;
2132
+ return single ? [single] : EMPTY_LIST;
2133
+ });
2134
+ const controlledMulti = options.multiSorting ?? options.state?.multiSorting;
2135
+ const controlledSorting = options.sorting !== void 0 ? options.sorting : options.state?.sorting;
2136
+ const multiSorting = useMemo(() => {
2137
+ if (controlledMulti !== void 0) return controlledMulti;
2138
+ if (controlledSorting === void 0) return internalMulti;
2139
+ if (controlledSorting === null) return EMPTY_LIST;
2140
+ return sameSort(internalMulti[0], controlledSorting) ? [controlledSorting, ...internalMulti.slice(1)] : [controlledSorting];
2141
+ }, [controlledMulti, controlledSorting, internalMulti]);
2142
+ const sorting = controlledMulti === void 0 && controlledSorting !== void 0 ? controlledSorting : multiSorting[0] ?? null;
1925
2143
  const [pagination, setPaginationState] = useSlice(
1926
2144
  options.pagination,
1927
2145
  options.state?.pagination,
@@ -1960,9 +2178,52 @@ function useDataTable(options) {
1960
2178
  options.defaultState?.filters,
1961
2179
  EMPTY_RECORD
1962
2180
  );
2181
+ const [columnOrderState, setColumnOrderState] = useSlice(
2182
+ options.columnOrder,
2183
+ options.state?.columnOrder,
2184
+ options.defaultColumnOrder,
2185
+ options.defaultState?.columnOrder,
2186
+ EMPTY_LIST
2187
+ );
2188
+ const [columnWidths, setColumnWidthsState] = useSlice(
2189
+ options.columnWidths,
2190
+ options.state?.columnWidths,
2191
+ options.defaultColumnWidths,
2192
+ options.defaultState?.columnWidths,
2193
+ EMPTY_RECORD
2194
+ );
2195
+ const [columnPinning, setColumnPinningState] = useSlice(
2196
+ options.columnPinning,
2197
+ options.state?.columnPinning,
2198
+ options.defaultColumnPinning,
2199
+ options.defaultState?.columnPinning,
2200
+ EMPTY_PINNING
2201
+ );
1963
2202
  const state = useMemo(
1964
- () => ({ sorting, pagination, search, selection, columnVisibility, filters }),
1965
- [sorting, pagination, search, selection, columnVisibility, filters]
2203
+ () => ({
2204
+ sorting,
2205
+ pagination,
2206
+ search,
2207
+ selection,
2208
+ columnVisibility,
2209
+ filters,
2210
+ multiSorting,
2211
+ columnOrder: columnOrderState,
2212
+ columnWidths,
2213
+ columnPinning
2214
+ }),
2215
+ [
2216
+ sorting,
2217
+ pagination,
2218
+ search,
2219
+ selection,
2220
+ columnVisibility,
2221
+ filters,
2222
+ multiSorting,
2223
+ columnOrderState,
2224
+ columnWidths,
2225
+ columnPinning
2226
+ ]
1966
2227
  );
1967
2228
  const latest = useRef({ state, options });
1968
2229
  latest.current = { state, options };
@@ -1970,10 +2231,23 @@ function useDataTable(options) {
1970
2231
  (patch) => {
1971
2232
  const { state: current, options: opts } = latest.current;
1972
2233
  const next = { ...current, ...patch };
2234
+ if ("multiSorting" in patch) {
2235
+ next.multiSorting = patch.multiSorting ?? EMPTY_LIST;
2236
+ const head = next.multiSorting[0] ?? null;
2237
+ next.sorting = sameSort(head, current.sorting) ? current.sorting : head;
2238
+ } else if ("sorting" in patch && !Object.is(patch.sorting, current.sorting)) {
2239
+ next.multiSorting = next.sorting ? [next.sorting] : EMPTY_LIST;
2240
+ } else if ("sorting" in patch) {
2241
+ next.sorting = current.sorting;
2242
+ }
1973
2243
  let changed = false;
1974
- if ("sorting" in patch && !Object.is(patch.sorting, current.sorting)) {
2244
+ if (!Object.is(next.multiSorting, current.multiSorting)) {
2245
+ changed = true;
2246
+ setInternalMulti(next.multiSorting ?? EMPTY_LIST);
2247
+ opts.onMultiSortingChange?.(next.multiSorting ?? EMPTY_LIST);
2248
+ }
2249
+ if (!Object.is(next.sorting, current.sorting)) {
1975
2250
  changed = true;
1976
- setSortingState(next.sorting);
1977
2251
  opts.onSortingChange?.(next.sorting);
1978
2252
  }
1979
2253
  if ("pagination" in patch && !Object.is(patch.pagination, current.pagination)) {
@@ -2001,28 +2275,56 @@ function useDataTable(options) {
2001
2275
  setFiltersState(next.filters);
2002
2276
  opts.onFiltersChange?.(next.filters);
2003
2277
  }
2278
+ if ("columnOrder" in patch && !Object.is(patch.columnOrder, current.columnOrder)) {
2279
+ changed = true;
2280
+ setColumnOrderState(next.columnOrder ?? EMPTY_LIST);
2281
+ opts.onColumnOrderChange?.(next.columnOrder ?? EMPTY_LIST);
2282
+ }
2283
+ if ("columnWidths" in patch && !Object.is(patch.columnWidths, current.columnWidths)) {
2284
+ changed = true;
2285
+ setColumnWidthsState(next.columnWidths ?? EMPTY_RECORD);
2286
+ opts.onColumnWidthsChange?.(next.columnWidths ?? EMPTY_RECORD);
2287
+ }
2288
+ if ("columnPinning" in patch && !Object.is(patch.columnPinning, current.columnPinning)) {
2289
+ changed = true;
2290
+ setColumnPinningState(next.columnPinning ?? EMPTY_PINNING);
2291
+ opts.onColumnPinningChange?.(next.columnPinning ?? EMPTY_PINNING);
2292
+ }
2004
2293
  if (changed) {
2005
2294
  latest.current = { state: next, options: opts };
2006
2295
  opts.onStateChange?.(next);
2007
2296
  }
2008
2297
  },
2009
2298
  [
2010
- setSortingState,
2011
2299
  setPaginationState,
2012
2300
  setSearchState,
2013
2301
  setSelectionState,
2014
2302
  setColumnVisibilityState,
2015
- setFiltersState
2303
+ setFiltersState,
2304
+ setColumnOrderState,
2305
+ setColumnWidthsState,
2306
+ setColumnPinningState
2016
2307
  ]
2017
2308
  );
2018
2309
  const firstPage = useCallback(
2019
2310
  (current) => resetPageOnChange && current.page !== 1 ? { pagination: { ...current, page: 1 } } : {},
2020
2311
  [resetPageOnChange]
2021
2312
  );
2022
- const columns = useMemo(
2023
- () => allColumns.filter((column) => columnVisibility[column.id] !== false),
2024
- [allColumns, columnVisibility]
2313
+ const allIds = useMemo(() => allColumns.map((c) => c.id), [allColumns]);
2314
+ const resolvedOrder = useMemo(
2315
+ () => resolveOrder(allIds, columnOrderState),
2316
+ [allIds, columnOrderState]
2025
2317
  );
2318
+ const columns = useMemo(() => {
2319
+ const ordered = columnOrderState.length === 0 && columnPinning.start.length === 0 ? allColumns : (() => {
2320
+ const byId = new Map(allColumns.map((c) => [c.id, c]));
2321
+ const pinned = columnPinning.start.filter((id2) => byId.has(id2));
2322
+ const pinnedSet = new Set(pinned);
2323
+ const ids = [...pinned, ...resolvedOrder.filter((id2) => !pinnedSet.has(id2))];
2324
+ return ids.map((id2) => byId.get(id2));
2325
+ })();
2326
+ return ordered.filter((column) => columnVisibility[column.id] !== false);
2327
+ }, [allColumns, columnOrderState, columnPinning, resolvedOrder, columnVisibility]);
2026
2328
  const getCellValue = useCallback(
2027
2329
  (row, column) => readAccessor(row, column),
2028
2330
  []
@@ -2046,17 +2348,23 @@ function useDataTable(options) {
2046
2348
  }
2047
2349
  return true;
2048
2350
  });
2049
- if (sorting) {
2050
- const column = allColumns.find((c) => c.id === sorting.columnId);
2051
- if (column) {
2052
- const sortValue = column.sortValue ?? ((row) => readAccessor(row, column));
2053
- rows2 = [...rows2].sort(
2054
- (a, b) => compareSortValues(sortValue(a), sortValue(b), sorting.direction)
2055
- );
2056
- }
2351
+ const keys = (multiSorting.length > 0 ? multiSorting : sorting ? [sorting] : []).map((key) => {
2352
+ const column = allColumns.find((c) => c.id === key.columnId);
2353
+ if (!column) return null;
2354
+ const sortValue = column.sortValue ?? ((row) => readAccessor(row, column));
2355
+ return { sortValue, direction: key.direction };
2356
+ }).filter((key) => key !== null);
2357
+ if (keys.length > 0) {
2358
+ rows2 = [...rows2].sort((a, b) => {
2359
+ for (const key of keys) {
2360
+ const order = compareSortValues(key.sortValue(a), key.sortValue(b), key.direction);
2361
+ if (order !== 0) return order;
2362
+ }
2363
+ return 0;
2364
+ });
2057
2365
  }
2058
2366
  return rows2;
2059
- }, [manual, data, allColumns, filterDefinitions, filters, search, sorting]);
2367
+ }, [manual, data, allColumns, filterDefinitions, filters, search, sorting, multiSorting]);
2060
2368
  const rowCount = manual ? options.rowCount ?? data.length : matching.length;
2061
2369
  const pageSize = Math.max(1, pagination.pageSize);
2062
2370
  const pageCount = Math.max(1, Math.ceil(rowCount / pageSize));
@@ -2097,24 +2405,128 @@ function useDataTable(options) {
2097
2405
  );
2098
2406
  const getColumnId = useCallback((columnId) => `${id}-col-${columnId}`, [id]);
2099
2407
  const getRowDomId = useCallback((rowId) => `${id}-row-${rowId}`, [id]);
2408
+ const multiSortEnabled = options.multiSort === true;
2100
2409
  const sortingApi = useMemo(() => {
2101
- const getDirection = (columnId) => sorting?.columnId === columnId ? sorting.direction : null;
2410
+ const keys = multiSorting.length > 0 ? multiSorting : sorting ? [sorting] : EMPTY_LIST;
2411
+ const getDirection = (columnId) => keys.find((key) => key.columnId === columnId)?.direction ?? null;
2412
+ const firstDirection = (columnId) => allColumns.find((c) => c.id === columnId)?.sortFirstDirection ?? "asc";
2102
2413
  return {
2103
2414
  value: sorting,
2104
2415
  set: (next) => commit({ sorting: next }),
2416
+ multi: keys,
2417
+ setMulti: (next) => commit({ multiSorting: next }),
2105
2418
  getDirection,
2106
- toggle: (columnId) => {
2107
- const direction = nextSortDirection(getDirection(columnId));
2108
- commit({ sorting: direction ? { columnId, direction } : null });
2419
+ getIndex: (columnId) => keys.findIndex((key) => key.columnId === columnId) + 1,
2420
+ toggle: (columnId, toggleOptions) => {
2421
+ const direction = nextSortDirection(getDirection(columnId), firstDirection(columnId));
2422
+ if (!toggleOptions?.additive || !multiSortEnabled) {
2423
+ commit({ sorting: direction ? { columnId, direction } : null });
2424
+ return;
2425
+ }
2426
+ const index = keys.findIndex((key) => key.columnId === columnId);
2427
+ let next;
2428
+ if (index === -1) next = direction ? [...keys, { columnId, direction }] : [...keys];
2429
+ else if (direction === null) next = keys.filter((_, i) => i !== index);
2430
+ else next = keys.map((key, i) => i === index ? { columnId, direction } : key);
2431
+ commit({ multiSorting: next });
2109
2432
  },
2110
2433
  getAriaSort: (columnId) => {
2111
2434
  const column = allColumns.find((c) => c.id === columnId);
2112
2435
  if (!column?.sortable) return void 0;
2113
- const direction = getDirection(columnId);
2114
- return direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none";
2436
+ const primary = keys[0];
2437
+ if (primary?.columnId !== columnId) return "none";
2438
+ return primary.direction === "asc" ? "ascending" : "descending";
2439
+ }
2440
+ };
2441
+ }, [sorting, multiSorting, allColumns, commit, multiSortEnabled]);
2442
+ const columnOrderApi = useMemo(() => {
2443
+ const pinnedSet = new Set(columnPinning.start);
2444
+ const targetOf = (columnId, delta) => {
2445
+ const visible = columns.map((c) => c.id);
2446
+ const from = visible.indexOf(columnId);
2447
+ const to = from + delta;
2448
+ if (from === -1 || delta === 0 || to < 0 || to >= visible.length) return null;
2449
+ const target = visible[to];
2450
+ return pinnedSet.has(target) === pinnedSet.has(columnId) ? target : null;
2451
+ };
2452
+ return {
2453
+ value: resolvedOrder,
2454
+ set: (next) => commit({ columnOrder: next }),
2455
+ canMove: (columnId, delta) => targetOf(columnId, delta) !== null,
2456
+ move: (columnId, delta) => {
2457
+ const target = targetOf(columnId, delta);
2458
+ if (target === null) return;
2459
+ if (pinnedSet.has(columnId)) {
2460
+ const start = [...columnPinning.start];
2461
+ const a = start.indexOf(columnId);
2462
+ const b = start.indexOf(target);
2463
+ start.splice(a, 1);
2464
+ start.splice(b, 0, columnId);
2465
+ commit({ columnPinning: { start } });
2466
+ return;
2467
+ }
2468
+ const order = resolvedOrder.filter((id2) => id2 !== columnId);
2469
+ const at = order.indexOf(target) + (delta > 0 ? 1 : 0);
2470
+ order.splice(at, 0, columnId);
2471
+ commit({ columnOrder: order });
2472
+ },
2473
+ reset: () => {
2474
+ if (latest.current.state.columnOrder?.length) commit({ columnOrder: EMPTY_LIST });
2115
2475
  }
2116
2476
  };
2117
- }, [sorting, allColumns, commit]);
2477
+ }, [columns, columnPinning, resolvedOrder, commit]);
2478
+ const columnWidthsApi = useMemo(
2479
+ () => ({
2480
+ value: columnWidths,
2481
+ get: (columnId) => columnWidths[columnId],
2482
+ set: (columnId, width) => {
2483
+ const current = latest.current.state.columnWidths ?? EMPTY_RECORD;
2484
+ const rounded = Math.round(width);
2485
+ if (current[columnId] === rounded) return;
2486
+ commit({ columnWidths: { ...current, [columnId]: rounded } });
2487
+ },
2488
+ reset: (columnId) => {
2489
+ const current = latest.current.state.columnWidths ?? EMPTY_RECORD;
2490
+ if (columnId === void 0) {
2491
+ if (Object.keys(current).length > 0) commit({ columnWidths: EMPTY_RECORD });
2492
+ return;
2493
+ }
2494
+ if (!(columnId in current)) return;
2495
+ const next = { ...current };
2496
+ delete next[columnId];
2497
+ commit({ columnWidths: next });
2498
+ }
2499
+ }),
2500
+ [columnWidths, commit]
2501
+ );
2502
+ const columnPinningApi = useMemo(() => {
2503
+ const isPinned = (columnId) => columnPinning.start.includes(columnId);
2504
+ return {
2505
+ value: columnPinning,
2506
+ isPinned,
2507
+ pin: (columnId) => {
2508
+ if (!isPinned(columnId)) commit({ columnPinning: { start: [...columnPinning.start, columnId] } });
2509
+ },
2510
+ unpin: (columnId) => {
2511
+ if (isPinned(columnId)) {
2512
+ commit({ columnPinning: { start: columnPinning.start.filter((id2) => id2 !== columnId) } });
2513
+ }
2514
+ },
2515
+ set: (next) => commit({ columnPinning: next }),
2516
+ reset: () => {
2517
+ if (columnPinning.start.length > 0) commit({ columnPinning: EMPTY_PINNING });
2518
+ }
2519
+ };
2520
+ }, [columnPinning, commit]);
2521
+ const resetColumns = useCallback(() => {
2522
+ const current = latest.current.state;
2523
+ const patch = {};
2524
+ if (current.columnOrder?.length) patch.columnOrder = EMPTY_LIST;
2525
+ if (current.columnWidths && Object.keys(current.columnWidths).length) patch.columnWidths = EMPTY_RECORD;
2526
+ if (current.columnPinning?.start.length) patch.columnPinning = EMPTY_PINNING;
2527
+ if (Object.keys(current.columnVisibility).length) patch.columnVisibility = EMPTY_RECORD;
2528
+ if (Object.keys(patch).length > 0) commit(patch);
2529
+ }, [commit]);
2118
2530
  const paginationApi = useMemo(() => {
2119
2531
  const setPage = (next) => commit({ pagination: { page: Math.min(Math.max(1, next), pageCount), pageSize } });
2120
2532
  return {
@@ -2263,7 +2675,11 @@ function useDataTable(options) {
2263
2675
  search: searchApi,
2264
2676
  selection: selectionApi,
2265
2677
  columnVisibility: columnVisibilityApi,
2266
- filters: filtersApi
2678
+ filters: filtersApi,
2679
+ columnOrder: columnOrderApi,
2680
+ columnWidths: columnWidthsApi,
2681
+ columnPinning: columnPinningApi,
2682
+ resetColumns
2267
2683
  };
2268
2684
  }
2269
2685
  function useSlice(prop, stateProp, defaultProp, defaultStateProp, fallback) {
@@ -2281,6 +2697,104 @@ function useSlice(prop, stateProp, defaultProp, defaultStateProp, fallback) {
2281
2697
  );
2282
2698
  return [value, setValue];
2283
2699
  }
2700
+ var DATA_TABLE_NO_GROUPING = "none";
2701
+ var EMPTY = [];
2702
+ function readStored(storage, key) {
2703
+ try {
2704
+ const raw = (storage ?? window.localStorage).getItem(key);
2705
+ if (!raw) return null;
2706
+ const parsed = JSON.parse(raw);
2707
+ return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string") : null;
2708
+ } catch {
2709
+ return null;
2710
+ }
2711
+ }
2712
+ function useDataTableGrouping({
2713
+ rows,
2714
+ groupBy,
2715
+ getGroupKey,
2716
+ getRowKey,
2717
+ compareGroups,
2718
+ collapsed: collapsedProp,
2719
+ defaultCollapsed = EMPTY,
2720
+ onCollapsedChange,
2721
+ persistKey,
2722
+ storage
2723
+ }) {
2724
+ const enabled = groupBy != null && groupBy !== DATA_TABLE_NO_GROUPING;
2725
+ const [collapsed, setCollapsed] = useControllableState({
2726
+ prop: collapsedProp,
2727
+ defaultProp: defaultCollapsed,
2728
+ onChange: onCollapsedChange
2729
+ });
2730
+ const controlled = collapsedProp !== void 0;
2731
+ const storageKey = persistKey && enabled ? `ds-groups:${persistKey}:${groupBy}` : null;
2732
+ const restoredFor = useRef(null);
2733
+ useEffect(() => {
2734
+ if (controlled || !storageKey || restoredFor.current === storageKey) return;
2735
+ restoredFor.current = storageKey;
2736
+ if (typeof window === "undefined") return;
2737
+ setCollapsed(readStored(storage, storageKey) ?? defaultCollapsed);
2738
+ }, [controlled, storageKey, storage, setCollapsed]);
2739
+ useEffect(() => {
2740
+ if (controlled || !storageKey || restoredFor.current !== storageKey) return;
2741
+ try {
2742
+ (storage ?? window.localStorage).setItem(storageKey, JSON.stringify(collapsed));
2743
+ } catch {
2744
+ }
2745
+ }, [controlled, storageKey, storage, collapsed]);
2746
+ const collapsedSet = useMemo(() => new Set(collapsed), [collapsed]);
2747
+ const groups = useMemo(() => {
2748
+ if (!enabled) return [];
2749
+ const byKey = /* @__PURE__ */ new Map();
2750
+ for (const row of rows) {
2751
+ const key = getGroupKey(row, groupBy);
2752
+ const bucket = byKey.get(key);
2753
+ if (bucket) bucket.push(row);
2754
+ else byKey.set(key, [row]);
2755
+ }
2756
+ const keys = Array.from(byKey.keys());
2757
+ if (compareGroups) keys.sort(compareGroups);
2758
+ return keys.map((key) => {
2759
+ const groupRows = byKey.get(key) ?? [];
2760
+ return { key, rows: groupRows, count: groupRows.length, open: !collapsedSet.has(key) };
2761
+ });
2762
+ }, [enabled, rows, groupBy, getGroupKey, compareGroups, collapsedSet]);
2763
+ const items = useMemo(() => {
2764
+ if (!enabled) {
2765
+ return rows.map((row, index2) => ({ type: "row", key: getRowKey(row, index2), row, groupKey: null }));
2766
+ }
2767
+ const out = [];
2768
+ let index = 0;
2769
+ for (const group of groups) {
2770
+ out.push({ type: "group", key: `group:${group.key}`, group });
2771
+ for (const row of group.rows) {
2772
+ if (group.open) out.push({ type: "row", key: getRowKey(row, index), row, groupKey: group.key });
2773
+ index += 1;
2774
+ }
2775
+ }
2776
+ return out;
2777
+ }, [enabled, rows, groups, getRowKey]);
2778
+ const setOpen = useCallback(
2779
+ (groupKey, open) => setCollapsed((prev) => {
2780
+ const has = prev.includes(groupKey);
2781
+ if (open === !has) return prev;
2782
+ return open ? prev.filter((k) => k !== groupKey) : [...prev, groupKey];
2783
+ }),
2784
+ [setCollapsed]
2785
+ );
2786
+ return {
2787
+ enabled,
2788
+ groups,
2789
+ items,
2790
+ collapsed,
2791
+ isOpen: (groupKey) => !collapsedSet.has(groupKey),
2792
+ setOpen,
2793
+ toggle: (groupKey) => setOpen(groupKey, collapsedSet.has(groupKey)),
2794
+ collapseAll: () => setCollapsed(groups.map((g) => g.key)),
2795
+ expandAll: () => setCollapsed(EMPTY)
2796
+ };
2797
+ }
2284
2798
  var DocumentRequestUpload = forwardRef(
2285
2799
  function DocumentRequestUpload2({
2286
2800
  label,
@@ -3662,11 +4176,16 @@ var NewMenu = forwardRef(function NewMenu2({ actions, groups, triggerLabel = "Ne
3662
4176
  ] });
3663
4177
  });
3664
4178
  NewMenu.displayName = "NewMenu";
3665
- var DataTable = forwardRef(function DataTable2({ withToolbar, className, children, ...props }, ref) {
4179
+ var DataTable = forwardRef(function DataTable2({ withToolbar, stickyHeader = false, regionLabel, className, children, ...props }, ref) {
4180
+ const wrapperRef = useRef(null);
4181
+ const mergedRef = useMergedRefs(ref, wrapperRef);
4182
+ const overflowing = useOverflowing(wrapperRef);
4183
+ const messages = useDsMessages("table");
3666
4184
  return /* @__PURE__ */ jsx(
3667
4185
  "div",
3668
4186
  {
3669
- ref,
4187
+ ref: mergedRef,
4188
+ ...overflowing ? { role: "region", "aria-label": regionLabel ?? messages.region, tabIndex: 0 } : {},
3670
4189
  "data-with-toolbar": withToolbar ? "true" : void 0,
3671
4190
  ...slot(dataTableAnatomy, "root"),
3672
4191
  className: cn(
@@ -3675,12 +4194,14 @@ var DataTable = forwardRef(function DataTable2({ withToolbar, className, childre
3675
4194
  // card. The overflow clips the header/cell backgrounds to the
3676
4195
  // rounded corners; a table wider than the card scrolls sideways
3677
4196
  // inside it instead of being cut off (the toolbar stays put).
3678
- "ds:w-full ds:overflow-x-auto ds:overflow-y-hidden ds:rounded-card ds:border ds:border-rule-strong ds:bg-surface ds:shadow-quiet",
4197
+ "ds:w-full ds:min-w-0 ds:overflow-x-auto ds:overflow-y-hidden ds:rounded-card ds:border ds:border-rule-strong ds:bg-surface ds:shadow-quiet",
3679
4198
  "ds:[&_table]:bg-surface ds:[&_table]:w-full ds:[&_table]:border-collapse",
4199
+ stickyHeader && "ds:overflow-y-auto",
4200
+ overflowing && "ds:focus-visible:outline-none ds:focus-visible:[box-shadow:var(--shadow-focus-ring)]",
3680
4201
  className
3681
4202
  ),
3682
4203
  ...props,
3683
- children
4204
+ children: /* @__PURE__ */ jsx(TableContext.Provider, { value: { headerStyle: "plain", stickyHeader }, children })
3684
4205
  }
3685
4206
  );
3686
4207
  });
@@ -3784,19 +4305,34 @@ var DataTableRow = forwardRef(
3784
4305
  );
3785
4306
  DataTableRow.displayName = "DataTableRow";
3786
4307
  var DataTableHeader = forwardRef(
3787
- function DataTableHeader2({ sortable = false, defaultSort, sort: controlledSort, onSortChange, children, ...props }, ref) {
4308
+ function DataTableHeader2({
4309
+ sortable = false,
4310
+ defaultSort,
4311
+ sort: controlledSort,
4312
+ onSortChange,
4313
+ sortIndex,
4314
+ sortFirstDirection = "asc",
4315
+ children,
4316
+ ...props
4317
+ }, ref) {
4318
+ const tableMessages = useDsMessages("table");
3788
4319
  const [uncontrolledSort, setUncontrolledSort] = useState(
3789
4320
  defaultSort ?? null
3790
4321
  );
3791
4322
  const isControlled = controlledSort !== void 0;
3792
4323
  const currentSort = isControlled ? controlledSort : uncontrolledSort;
3793
- const handleClick = useCallback(() => {
3794
- if (!sortable) return;
3795
- const next = currentSort === null ? "asc" : currentSort === "asc" ? "desc" : null;
3796
- if (!isControlled) setUncontrolledSort(next);
3797
- onSortChange?.(next);
3798
- }, [sortable, currentSort, isControlled, onSortChange]);
3799
- const ariaSort = sortable ? currentSort === "asc" ? "ascending" : currentSort === "desc" ? "descending" : "none" : void 0;
4324
+ const handleClick = useCallback(
4325
+ (event) => {
4326
+ if (!sortable) return;
4327
+ const second = sortFirstDirection === "asc" ? "desc" : "asc";
4328
+ const next = currentSort === null ? sortFirstDirection : currentSort === sortFirstDirection ? second : null;
4329
+ if (!isControlled) setUncontrolledSort(next);
4330
+ onSortChange?.(next, { additive: event.shiftKey });
4331
+ },
4332
+ [sortable, currentSort, isControlled, onSortChange, sortFirstDirection]
4333
+ );
4334
+ const showRank = sortable && currentSort !== null && sortIndex !== void 0 && sortIndex > 0;
4335
+ const ariaSort = sortable ? sortIndex !== void 0 && sortIndex > 1 ? "none" : currentSort === "asc" ? "ascending" : currentSort === "desc" ? "descending" : "none" : void 0;
3800
4336
  return /* @__PURE__ */ jsx(
3801
4337
  TableHead,
3802
4338
  {
@@ -3832,7 +4368,23 @@ var DataTableHeader = forwardRef(
3832
4368
  ),
3833
4369
  children: currentSort === "asc" ? /* @__PURE__ */ jsx(ChevronUpIcon, { className: "ds:size-3", strokeWidth: 2.2 }) : currentSort === "desc" ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "ds:size-3", strokeWidth: 2.2 }) : /* @__PURE__ */ jsx(ChevronsUpDownIcon, { className: "ds:size-3", strokeWidth: 2 })
3834
4370
  }
3835
- )
4371
+ ),
4372
+ showRank && /* @__PURE__ */ jsxs(Fragment, { children: [
4373
+ /* @__PURE__ */ jsx(
4374
+ "span",
4375
+ {
4376
+ "aria-hidden": "true",
4377
+ ...slot(tableAnatomy, "sort-rank"),
4378
+ className: "ds:-ms-0.5 ds:self-start ds:text-(length:--text-caption) ds:leading-none ds:font-semibold ds:text-fg-3 ds:tabular-nums",
4379
+ children: sortIndex
4380
+ }
4381
+ ),
4382
+ /* @__PURE__ */ jsxs(VisuallyHidden, { children: [
4383
+ ",",
4384
+ " ",
4385
+ currentSort === "asc" ? tableMessages.sortedAscending(sortIndex) : tableMessages.sortedDescending(sortIndex)
4386
+ ] })
4387
+ ] })
3836
4388
  ]
3837
4389
  }
3838
4390
  ) : children
@@ -3956,6 +4508,266 @@ var DataTableCheckbox = forwardRef(
3956
4508
  }
3957
4509
  );
3958
4510
  DataTableCheckbox.displayName = "DataTableCheckbox";
4511
+ var DataTableCellSecondary = forwardRef(
4512
+ function DataTableCellSecondary2(props, ref) {
4513
+ return /* @__PURE__ */ jsx(TableCellSecondary, { ref, ...slot(dataTableAnatomy, "cell-secondary"), ...props });
4514
+ }
4515
+ );
4516
+ DataTableCellSecondary.displayName = "DataTableCellSecondary";
4517
+ var DEFAULT_ROW_ESTIMATE = 44;
4518
+ var DEFAULT_OVERSCAN = 15;
4519
+ function useDataTableVirtualRows({
4520
+ count,
4521
+ getScrollElement,
4522
+ estimateSize,
4523
+ overscan = DEFAULT_OVERSCAN,
4524
+ getItemKey
4525
+ }) {
4526
+ const virtualizer = useVirtualizer({
4527
+ count,
4528
+ getScrollElement,
4529
+ estimateSize: estimateSize ?? (() => DEFAULT_ROW_ESTIMATE),
4530
+ overscan,
4531
+ ...getItemKey ? { getItemKey } : {}
4532
+ });
4533
+ const virtual = virtualizer.getVirtualItems();
4534
+ const totalSize = virtualizer.getTotalSize();
4535
+ const first = virtual[0];
4536
+ const last = virtual[virtual.length - 1];
4537
+ return {
4538
+ items: virtual.map((v) => ({ index: v.index, key: v.key, start: v.start })),
4539
+ paddingTop: first ? first.start : 0,
4540
+ paddingBottom: last ? totalSize - last.end : 0,
4541
+ totalSize,
4542
+ measureElement: virtualizer.measureElement,
4543
+ scrollToIndex: (index) => virtualizer.scrollToIndex(index)
4544
+ };
4545
+ }
4546
+ var DataTableSpacerRow = forwardRef(
4547
+ function DataTableSpacerRow2({ height, colSpan, className, ...props }, ref) {
4548
+ if (height <= 0) return null;
4549
+ return /* @__PURE__ */ jsx(
4550
+ TableRow,
4551
+ {
4552
+ ref,
4553
+ "aria-hidden": "true",
4554
+ ...slot(tableAnatomy, "spacer-row"),
4555
+ className: cn("ds:border-0 ds:hover:bg-transparent", className),
4556
+ ...props,
4557
+ children: /* @__PURE__ */ jsx(TableCell, { colSpan, style: { height, padding: 0, border: 0 } })
4558
+ }
4559
+ );
4560
+ }
4561
+ );
4562
+ DataTableSpacerRow.displayName = "DataTableSpacerRow";
4563
+ var DataTableLoadMore = forwardRef(
4564
+ function DataTableLoadMore2({
4565
+ hasMore,
4566
+ loading = false,
4567
+ error,
4568
+ onLoadMore,
4569
+ loadedCount,
4570
+ totalCount,
4571
+ root,
4572
+ rootMargin = "400px",
4573
+ locale,
4574
+ className,
4575
+ ...props
4576
+ }, ref) {
4577
+ const messages = useDsMessages("dataTable");
4578
+ const errorMessages = useDsMessages("errorState");
4579
+ const sentinel = useRef(null);
4580
+ const latest = useRef({ hasMore, loading, error, loadedCount, onLoadMore });
4581
+ latest.current = { hasMore, loading, error, loadedCount, onLoadMore };
4582
+ const firing = useRef(false);
4583
+ const lastAutomaticCount = useRef(void 0);
4584
+ useEffect(() => {
4585
+ if (!loading) firing.current = false;
4586
+ }, [loading, loadedCount, hasMore, error]);
4587
+ useEffect(() => {
4588
+ const element = sentinel.current;
4589
+ if (!element || typeof IntersectionObserver === "undefined") return;
4590
+ const observer = new IntersectionObserver(
4591
+ (entries) => {
4592
+ const { hasMore: more, loading: busy, error: failed, loadedCount: count, onLoadMore: load } = latest.current;
4593
+ if (!entries.some((e) => e.isIntersecting) || !more || busy || failed || firing.current) return;
4594
+ if (count !== void 0 && count === lastAutomaticCount.current) return;
4595
+ lastAutomaticCount.current = count;
4596
+ firing.current = true;
4597
+ load();
4598
+ },
4599
+ { root: root?.current ?? null, rootMargin }
4600
+ );
4601
+ observer.observe(element);
4602
+ return () => observer.disconnect();
4603
+ }, [root, rootMargin, loading, hasMore, loadedCount, error]);
4604
+ const format = (n) => n.toLocaleString(locale);
4605
+ const status = loading ? messages.loadingMore : !hasMore && loadedCount !== void 0 ? messages.allLoaded(format(loadedCount)) : loadedCount !== void 0 && totalCount !== void 0 ? messages.loadedOf(format(loadedCount), format(totalCount)) : "";
4606
+ return /* @__PURE__ */ jsxs(
4607
+ "div",
4608
+ {
4609
+ ref,
4610
+ ...slot(dataTableAnatomy, "load-more"),
4611
+ className: cn("ds:flex ds:items-center ds:justify-center ds:gap-3 ds:px-4 ds:py-2", className),
4612
+ ...props,
4613
+ children: [
4614
+ /* @__PURE__ */ jsx("div", { ref: sentinel, "aria-hidden": "true", className: "ds:h-px ds:w-px" }),
4615
+ /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "ds:text-xs ds:text-fg-3 ds:tabular-nums", children: status }),
4616
+ error && /* @__PURE__ */ jsx("span", { role: "alert", className: "ds:text-sm ds:text-danger-fg", children: error }),
4617
+ hasMore && /* @__PURE__ */ jsx(
4618
+ Button,
4619
+ {
4620
+ variant: "outline",
4621
+ size: "sm",
4622
+ loading,
4623
+ disabled: loading,
4624
+ onClick: () => {
4625
+ if (!latest.current.hasMore || latest.current.loading || firing.current) return;
4626
+ firing.current = true;
4627
+ onLoadMore();
4628
+ },
4629
+ children: error ? errorMessages.retry : messages.loadMore
4630
+ }
4631
+ )
4632
+ ]
4633
+ }
4634
+ );
4635
+ }
4636
+ );
4637
+ DataTableLoadMore.displayName = "DataTableLoadMore";
4638
+ var SAVE_THROTTLE_MS = 150;
4639
+ function useScrollRestoration(key, ref, options) {
4640
+ const ready = options?.ready ?? true;
4641
+ const storageOption = options?.storage;
4642
+ const restored = useRef(null);
4643
+ useEffect(() => {
4644
+ if (restored.current === key || !ready) return;
4645
+ const element = ref.current;
4646
+ if (!element || typeof window === "undefined") return;
4647
+ restored.current = key;
4648
+ try {
4649
+ const storage = storageOption ?? window.sessionStorage;
4650
+ const saved = Number(storage.getItem(`ds-scroll:${key}`));
4651
+ element.scrollTop = Number.isFinite(saved) && saved > 0 ? saved : 0;
4652
+ } catch {
4653
+ }
4654
+ }, [key, ref, ready, storageOption]);
4655
+ useEffect(() => {
4656
+ const element = ref.current;
4657
+ if (!ready || !element || typeof window === "undefined") return;
4658
+ let timer = null;
4659
+ const save = () => {
4660
+ try {
4661
+ (storageOption ?? window.sessionStorage).setItem(`ds-scroll:${key}`, String(element.scrollTop));
4662
+ } catch {
4663
+ }
4664
+ };
4665
+ const onScroll = () => {
4666
+ if (timer) return;
4667
+ timer = setTimeout(() => {
4668
+ timer = null;
4669
+ save();
4670
+ }, SAVE_THROTTLE_MS);
4671
+ };
4672
+ element.addEventListener("scroll", onScroll, { passive: true });
4673
+ window.addEventListener("pagehide", save);
4674
+ return () => {
4675
+ if (timer) clearTimeout(timer);
4676
+ save();
4677
+ element.removeEventListener("scroll", onScroll);
4678
+ window.removeEventListener("pagehide", save);
4679
+ };
4680
+ }, [key, ref, ready, storageOption]);
4681
+ }
4682
+ var DataTableHeaderMenu = forwardRef(
4683
+ function DataTableHeaderMenu2({
4684
+ label,
4685
+ onSortAscending,
4686
+ onSortDescending,
4687
+ onClearSort,
4688
+ sorted = false,
4689
+ onMoveLeft,
4690
+ onMoveRight,
4691
+ canMoveLeft = true,
4692
+ canMoveRight = true,
4693
+ onHide,
4694
+ pinned = false,
4695
+ onPinChange,
4696
+ onAutoSize,
4697
+ onResetColumns,
4698
+ className
4699
+ }, ref) {
4700
+ const messages = useDsMessages("dataTable");
4701
+ const sortGroup = Boolean(onSortAscending || onSortDescending || sorted && onClearSort);
4702
+ const moveGroup = Boolean(onMoveLeft || onMoveRight);
4703
+ const columnGroup = Boolean(onHide || onPinChange || onAutoSize);
4704
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
4705
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
4706
+ IconButton,
4707
+ {
4708
+ ref,
4709
+ variant: "ghost",
4710
+ intent: "neutral",
4711
+ size: "xs",
4712
+ tooltip: false,
4713
+ label: messages.columnOptions(label),
4714
+ icon: /* @__PURE__ */ jsx(MoreHorizontalIcon, {}),
4715
+ ...slot(dataTableAnatomy, "head-menu"),
4716
+ className: cn(
4717
+ "ds:ms-1 ds:size-6 ds:align-middle ds:opacity-0 ds:transition-opacity ds:motion-reduce:transition-none",
4718
+ "ds:[th:hover_&]:opacity-100 ds:focus-visible:opacity-100 ds:data-[state=open]:opacity-100",
4719
+ className
4720
+ ),
4721
+ onClick: (event) => event.stopPropagation()
4722
+ }
4723
+ ) }),
4724
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", children: [
4725
+ onSortAscending && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onSortAscending, children: [
4726
+ /* @__PURE__ */ jsx(ArrowUpIcon, { "aria-hidden": "true" }),
4727
+ messages.sortAscending
4728
+ ] }),
4729
+ onSortDescending && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onSortDescending, children: [
4730
+ /* @__PURE__ */ jsx(ArrowDownIcon, { "aria-hidden": "true" }),
4731
+ messages.sortDescending
4732
+ ] }),
4733
+ sorted && onClearSort && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onClearSort, children: [
4734
+ /* @__PURE__ */ jsx(XIcon, { "aria-hidden": "true" }),
4735
+ messages.clearSort
4736
+ ] }),
4737
+ sortGroup && (moveGroup || columnGroup || onResetColumns) && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
4738
+ onMoveLeft && /* @__PURE__ */ jsxs(DropdownMenuItem, { disabled: !canMoveLeft, onSelect: onMoveLeft, children: [
4739
+ /* @__PURE__ */ jsx(ArrowLeftIcon, { "aria-hidden": "true" }),
4740
+ messages.moveLeft
4741
+ ] }),
4742
+ onMoveRight && /* @__PURE__ */ jsxs(DropdownMenuItem, { disabled: !canMoveRight, onSelect: onMoveRight, children: [
4743
+ /* @__PURE__ */ jsx(ArrowRightIcon, { "aria-hidden": "true" }),
4744
+ messages.moveRight
4745
+ ] }),
4746
+ moveGroup && (columnGroup || onResetColumns) && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
4747
+ onPinChange && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => onPinChange(!pinned), children: [
4748
+ pinned ? /* @__PURE__ */ jsx(PinOffIcon, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(PinIcon, { "aria-hidden": "true" }),
4749
+ pinned ? messages.unpinColumn : messages.pinColumn
4750
+ ] }),
4751
+ onAutoSize && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onAutoSize, children: [
4752
+ /* @__PURE__ */ jsx(ArrowRightIcon, { "aria-hidden": "true" }),
4753
+ messages.autosizeColumn
4754
+ ] }),
4755
+ onHide && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onHide, children: [
4756
+ /* @__PURE__ */ jsx(EyeOffIcon, { "aria-hidden": "true" }),
4757
+ messages.hideColumn
4758
+ ] }),
4759
+ onResetColumns && /* @__PURE__ */ jsxs(Fragment, { children: [
4760
+ (columnGroup || moveGroup || sortGroup) && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
4761
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onResetColumns, children: [
4762
+ /* @__PURE__ */ jsx(RotateCcwIcon, { "aria-hidden": "true" }),
4763
+ messages.resetColumns
4764
+ ] })
4765
+ ] })
4766
+ ] })
4767
+ ] });
4768
+ }
4769
+ );
4770
+ DataTableHeaderMenu.displayName = "DataTableHeaderMenu";
3959
4771
  var ALL = "__all";
3960
4772
  var SEARCH_ONLY = "__search:";
3961
4773
  function colStyle(width) {
@@ -3986,8 +4798,39 @@ function DataTableView({
3986
4798
  hideOnSinglePage = true,
3987
4799
  itemLabel = "items",
3988
4800
  selection,
3989
- className
4801
+ className,
4802
+ loadMore,
4803
+ multiSort,
4804
+ multiSorting,
4805
+ onMultiSortingChange,
4806
+ columnMenu = false,
4807
+ resizableColumns = false,
4808
+ columnOrder,
4809
+ defaultColumnOrder,
4810
+ onColumnOrderChange,
4811
+ columnVisibility,
4812
+ defaultColumnVisibility,
4813
+ onColumnVisibilityChange,
4814
+ columnWidths,
4815
+ defaultColumnWidths,
4816
+ onColumnWidthsChange,
4817
+ columnPinning,
4818
+ defaultColumnPinning,
4819
+ onColumnPinningChange,
4820
+ stickyFirstColumn = false,
4821
+ stickyHeader = false,
4822
+ regionLabel,
4823
+ density,
4824
+ groupBy,
4825
+ getGroupKey,
4826
+ renderGroupLabel,
4827
+ compareGroups,
4828
+ collapsedGroups,
4829
+ defaultCollapsedGroups,
4830
+ onCollapsedGroupsChange,
4831
+ groupPersistKey
3990
4832
  }) {
4833
+ const scrollRef = useRef(null);
3991
4834
  const [uncontrolledPage, setUncontrolledPage] = useState(1);
3992
4835
  const page = pageProp ?? uncontrolledPage;
3993
4836
  const controllerColumns = useMemo(() => {
@@ -3997,6 +4840,8 @@ function DataTableView({
3997
4840
  header: c.header,
3998
4841
  sortable: c.sortable,
3999
4842
  sortValue: c.sortValue,
4843
+ sortFirstDirection: c.sortFirstDirection,
4844
+ ...c.hideable === void 0 ? {} : { hideable: c.hideable },
4000
4845
  searchable: searched.has(c.key),
4001
4846
  searchText: (row) => fieldText(row, c.key)
4002
4847
  }));
@@ -4035,7 +4880,7 @@ function DataTableView({
4035
4880
  sorting: toControllerSort(sortingProp),
4036
4881
  defaultSorting: toControllerSort(initialSort),
4037
4882
  onSortingChange: onSortingChange ? (next) => onSortingChange(fromControllerSort(next)) : void 0,
4038
- pagination: { page, pageSize },
4883
+ pagination: loadMore ? { page: 1, pageSize: Math.max(1, data.length) } : { page, pageSize },
4039
4884
  onPaginationChange: (next) => {
4040
4885
  setUncontrolledPage(next.page);
4041
4886
  onPageChange?.(next.page);
@@ -4044,8 +4889,67 @@ function DataTableView({
4044
4889
  search: searchKeys.length > 0 ? searchProp : "",
4045
4890
  onSearchChange,
4046
4891
  selection: controlledSelection,
4047
- onSelectionChange: selection ? (next) => selection.onSelectionChange(new Set(next)) : void 0
4892
+ onSelectionChange: selection ? (next) => selection.onSelectionChange(new Set(next)) : void 0,
4893
+ multiSort,
4894
+ multiSorting,
4895
+ onMultiSortingChange,
4896
+ columnOrder,
4897
+ defaultColumnOrder,
4898
+ onColumnOrderChange,
4899
+ columnVisibility,
4900
+ defaultColumnVisibility,
4901
+ onColumnVisibilityChange,
4902
+ columnWidths,
4903
+ defaultColumnWidths,
4904
+ onColumnWidthsChange,
4905
+ columnPinning,
4906
+ defaultColumnPinning,
4907
+ onColumnPinningChange
4908
+ });
4909
+ const byKey = useMemo(() => new Map(columns.map((c) => [c.key, c])), [columns]);
4910
+ const visibleColumns = table.columns.map((c) => byKey.get(c.id)).filter((c) => c !== void 0);
4911
+ const columnCount = visibleColumns.length + (selection != null ? 1 : 0);
4912
+ const pxWidth = (c) => {
4913
+ const own = c.width?.match(/^([\d.]+)px$/) ?? c.width?.match(/minmax\(\s*([\d.]+)px/);
4914
+ return table.columnWidths.get(c.key) ?? (own ? Number(own[1]) : 160);
4915
+ };
4916
+ const tableElement = useRef(null);
4917
+ const [pinnedOffsets, setPinnedOffsets] = useState({});
4918
+ useLayoutEffect(() => {
4919
+ const element = tableElement.current;
4920
+ if (!element || table.columnPinning.value.start.length === 0) return;
4921
+ const measure = () => {
4922
+ const next = {};
4923
+ const cells = element.tHead?.rows[0]?.cells;
4924
+ let offset = selection && stickyFirstColumn ? cells?.[0]?.getBoundingClientRect().width ?? 0 : 0;
4925
+ visibleColumns.forEach((column, index) => {
4926
+ if (!table.columnPinning.isPinned(column.key)) return;
4927
+ next[column.key] = offset;
4928
+ offset += cells?.[index + (selection ? 1 : 0)]?.getBoundingClientRect().width ?? 0;
4929
+ });
4930
+ setPinnedOffsets(
4931
+ (previous) => Object.keys(previous).length === Object.keys(next).length && Object.keys(next).every((key) => previous[key] === next[key]) ? previous : next
4932
+ );
4933
+ };
4934
+ measure();
4935
+ if (typeof ResizeObserver === "undefined") return;
4936
+ const observer = new ResizeObserver(measure);
4937
+ for (const cell of Array.from(element.tHead?.rows[0]?.cells ?? [])) observer.observe(cell);
4938
+ return () => observer.disconnect();
4048
4939
  });
4940
+ const pinProps = (key) => table.columnPinning.isPinned(key) ? { sticky: "start", style: { insetInlineStart: pinnedOffsets[key] ?? 0 } } : {};
4941
+ const grouping = useDataTableGrouping({
4942
+ rows: table.rows,
4943
+ groupBy,
4944
+ getGroupKey: (entry, by) => getGroupKey ? getGroupKey(entry.row, by) : fieldText(entry.row, by),
4945
+ getRowKey: (entry) => entry.id,
4946
+ compareGroups,
4947
+ collapsed: collapsedGroups,
4948
+ defaultCollapsed: defaultCollapsedGroups,
4949
+ onCollapsedChange: onCollapsedGroupsChange,
4950
+ persistKey: groupPersistKey
4951
+ });
4952
+ const hasSubtotals = visibleColumns.some((c) => c.subtotal);
4049
4953
  const total = table.pagination.rowCount;
4050
4954
  const hasToolbar = searchKeys.length > 0 || filters.length > 0;
4051
4955
  const selectCol = selection != null;
@@ -4067,125 +4971,209 @@ function DataTableView({
4067
4971
  }
4068
4972
  };
4069
4973
  const selectAllLabel = filteredScope ? `Select all ${scopeIds.length} ${itemLabel}` : `Select all ${itemLabel} on this page`;
4070
- return /* @__PURE__ */ jsxs(DataTable, { withToolbar: hasToolbar, className, children: [
4071
- hasToolbar && /* @__PURE__ */ jsxs(DataTableToolbar, { children: [
4072
- searchKeys.length > 0 && /* @__PURE__ */ jsx(
4073
- DataTableSearch,
4074
- {
4075
- placeholder: searchPlaceholder,
4076
- value: table.search.value,
4077
- onChange: (e) => table.search.set(e.target.value)
4078
- }
4079
- ),
4080
- filters.map((filter) => {
4081
- const options = [
4082
- { value: ALL, label: filter.allLabel ?? `All ${filter.label ?? ""}`.trim() },
4083
- ...filter.options.map((o) => typeof o === "string" ? { value: o, label: o } : o)
4084
- ];
4085
- return /* @__PURE__ */ jsx(
4086
- Select,
4087
- {
4088
- options,
4089
- value: table.filters.get(filter.key) ?? ALL,
4090
- onValueChange: (value) => table.filters.set(filter.key, value === ALL ? null : value),
4091
- "aria-label": filter.label ?? filter.key,
4092
- className: "ds:w-auto ds:min-w-[150px]"
4093
- },
4094
- filter.key
4095
- );
4096
- }),
4097
- /* @__PURE__ */ jsx(DataTableSpacer, {}),
4098
- /* @__PURE__ */ jsx(DataTableResultsCount, { current: total, total: data.length })
4099
- ] }),
4100
- /* @__PURE__ */ jsxs("table", { className: "ds:table-auto", children: [
4101
- /* @__PURE__ */ jsxs("colgroup", { children: [
4102
- selectCol && /* @__PURE__ */ jsx("col", { style: { width: "32px", minWidth: "32px" } }),
4103
- columns.map((c) => /* @__PURE__ */ jsx("col", { style: colStyle(c.width) }, c.key))
4104
- ] }),
4105
- /* @__PURE__ */ jsx(DataTableHead, { children: /* @__PURE__ */ jsxs(DataTableRow, { children: [
4106
- selection && /* @__PURE__ */ jsx(
4107
- DataTableCheckbox,
4108
- {
4109
- asHeader: true,
4110
- checked: allInScopeSelected ? true : someInScopeSelected ? "indeterminate" : false,
4111
- onCheckedChange: toggleAll,
4112
- disabled: scopeIds.length === 0,
4113
- ariaLabel: selectAllLabel
4114
- }
4115
- ),
4116
- columns.map(
4117
- (c) => c.sortable ? /* @__PURE__ */ jsx(
4118
- DataTableHeader,
4119
- {
4120
- sortable: true,
4121
- sort: table.sorting.getDirection(c.key),
4122
- onSortChange: () => table.sorting.toggle(c.key),
4123
- className: c.align === "right" ? "ds:text-right ds:[&>button]:ml-auto" : void 0,
4124
- children: c.header
4125
- },
4126
- c.key
4127
- ) : /* @__PURE__ */ jsx(
4128
- DataTableHeader,
4974
+ return /* @__PURE__ */ jsxs(
4975
+ DataTable,
4976
+ {
4977
+ ref: scrollRef,
4978
+ withToolbar: hasToolbar,
4979
+ stickyHeader,
4980
+ regionLabel,
4981
+ className: cn(stickyFirstColumn && tableStickyFirstColumnClass, className),
4982
+ ...density === void 0 ? {} : { "data-ds-density": density },
4983
+ ...stickyFirstColumn ? { "data-sticky-first": "" } : {},
4984
+ children: [
4985
+ hasToolbar && /* @__PURE__ */ jsxs(DataTableToolbar, { children: [
4986
+ searchKeys.length > 0 && /* @__PURE__ */ jsx(
4987
+ DataTableSearch,
4129
4988
  {
4130
- className: c.align === "right" ? "ds:text-right" : void 0,
4131
- children: c.header
4132
- },
4133
- c.key
4134
- )
4135
- )
4136
- ] }) }),
4137
- /* @__PURE__ */ jsx(DataTableBody, { children: table.rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs(
4138
- DataTableCell,
4139
- {
4140
- colSpan: columns.length + (selectCol ? 1 : 0),
4141
- className: "ds:text-fg-3 ds:py-12 ds:text-center",
4142
- children: [
4143
- "No ",
4144
- itemLabel,
4145
- " match your filters."
4146
- ]
4147
- }
4148
- ) }) : table.rows.map(({ id, row, selected, selectable }) => /* @__PURE__ */ jsxs(
4149
- DataTableRow,
4150
- {
4151
- selected: selection ? selected : void 0,
4152
- onClick: onRowClick ? () => onRowClick(row) : void 0,
4153
- className: onRowClick ? "ds:cursor-pointer" : void 0,
4154
- children: [
4989
+ placeholder: searchPlaceholder,
4990
+ value: table.search.value,
4991
+ onChange: (e) => table.search.set(e.target.value)
4992
+ }
4993
+ ),
4994
+ filters.map((filter) => {
4995
+ const options = [
4996
+ { value: ALL, label: filter.allLabel ?? `All ${filter.label ?? ""}`.trim() },
4997
+ ...filter.options.map((o) => typeof o === "string" ? { value: o, label: o } : o)
4998
+ ];
4999
+ return /* @__PURE__ */ jsx(
5000
+ Select,
5001
+ {
5002
+ options,
5003
+ value: table.filters.get(filter.key) ?? ALL,
5004
+ onValueChange: (value) => table.filters.set(filter.key, value === ALL ? null : value),
5005
+ "aria-label": filter.label ?? filter.key,
5006
+ className: "ds:w-auto ds:min-w-[150px]"
5007
+ },
5008
+ filter.key
5009
+ );
5010
+ }),
5011
+ /* @__PURE__ */ jsx(DataTableSpacer, {}),
5012
+ /* @__PURE__ */ jsx(DataTableResultsCount, { current: total, total: data.length })
5013
+ ] }),
5014
+ /* @__PURE__ */ jsxs("table", { ref: tableElement, className: "ds:table-auto", children: [
5015
+ /* @__PURE__ */ jsxs("colgroup", { children: [
5016
+ selectCol && /* @__PURE__ */ jsx("col", { style: { width: "32px", minWidth: "32px" } }),
5017
+ visibleColumns.map((c) => {
5018
+ const resized = table.columnWidths.get(c.key);
5019
+ return /* @__PURE__ */ jsx(
5020
+ "col",
5021
+ {
5022
+ style: resized === void 0 ? colStyle(c.width) : { width: resized, minWidth: resized }
5023
+ },
5024
+ c.key
5025
+ );
5026
+ })
5027
+ ] }),
5028
+ /* @__PURE__ */ jsx(DataTableHead, { children: /* @__PURE__ */ jsxs(DataTableRow, { children: [
4155
5029
  selection && /* @__PURE__ */ jsx(
4156
5030
  DataTableCheckbox,
4157
5031
  {
4158
- checked: selected,
4159
- onCheckedChange: () => table.selection.toggle(id),
4160
- disabled: !selectable,
4161
- ariaLabel: selection.rowLabel?.(row) ?? `Select ${id}`,
4162
- onClick: (event) => event.stopPropagation()
5032
+ asHeader: true,
5033
+ checked: allInScopeSelected ? true : someInScopeSelected ? "indeterminate" : false,
5034
+ onCheckedChange: toggleAll,
5035
+ disabled: scopeIds.length === 0,
5036
+ ariaLabel: selectAllLabel
4163
5037
  }
4164
5038
  ),
4165
- columns.map((c) => /* @__PURE__ */ jsx(
4166
- DataTableCell,
5039
+ visibleColumns.map((c) => {
5040
+ const right = c.align === "right" || c.numeric;
5041
+ const rank = table.sorting.getIndex(c.key);
5042
+ const extras = {
5043
+ ...pinProps(c.key),
5044
+ ...resizableColumns ? {
5045
+ resizable: true,
5046
+ width: pxWidth(c),
5047
+ onWidthChange: (w) => table.columnWidths.set(c.key, w),
5048
+ onAutoSize: () => table.columnWidths.reset(c.key)
5049
+ } : {},
5050
+ ...columnMenu ? {
5051
+ menu: /* @__PURE__ */ jsx(
5052
+ DataTableHeaderMenu,
5053
+ {
5054
+ label: c.header,
5055
+ sorted: c.sortable ? table.sorting.getDirection(c.key) !== null : void 0,
5056
+ onSortAscending: c.sortable ? () => table.sorting.set({ columnId: c.key, direction: "asc" }) : void 0,
5057
+ onSortDescending: c.sortable ? () => table.sorting.set({ columnId: c.key, direction: "desc" }) : void 0,
5058
+ onClearSort: c.sortable ? () => table.sorting.set(null) : void 0,
5059
+ onMoveLeft: () => table.columnOrder.move(c.key, -1),
5060
+ onMoveRight: () => table.columnOrder.move(c.key, 1),
5061
+ canMoveLeft: table.columnOrder.canMove(c.key, -1),
5062
+ canMoveRight: table.columnOrder.canMove(c.key, 1),
5063
+ onHide: c.hideable === false || visibleColumns.length < 2 ? void 0 : () => table.columnVisibility.setVisible(c.key, false),
5064
+ pinned: table.columnPinning.isPinned(c.key),
5065
+ onPinChange: (pin) => pin ? table.columnPinning.pin(c.key) : table.columnPinning.unpin(c.key),
5066
+ onAutoSize: resizableColumns ? () => table.columnWidths.reset(c.key) : void 0,
5067
+ onResetColumns: table.resetColumns
5068
+ }
5069
+ )
5070
+ } : {}
5071
+ };
5072
+ return c.sortable ? /* @__PURE__ */ jsx(
5073
+ DataTableHeader,
5074
+ {
5075
+ sortable: true,
5076
+ sort: table.sorting.getDirection(c.key),
5077
+ sortFirstDirection: c.sortFirstDirection,
5078
+ sortIndex: multiSort && table.sorting.multi.length > 1 && rank > 0 ? rank : void 0,
5079
+ onSortChange: (_, meta) => table.sorting.toggle(c.key, meta),
5080
+ className: right ? "ds:text-right ds:[&>button]:ml-auto" : void 0,
5081
+ ...extras,
5082
+ children: c.header
5083
+ },
5084
+ c.key
5085
+ ) : /* @__PURE__ */ jsx(DataTableHeader, { className: right ? "ds:text-right" : void 0, ...extras, children: c.header }, c.key);
5086
+ })
5087
+ ] }) }),
5088
+ /* @__PURE__ */ jsx(DataTableBody, { children: table.rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs(
5089
+ DataTableCell,
5090
+ {
5091
+ colSpan: columnCount,
5092
+ className: "ds:text-fg-3 ds:py-12 ds:text-center",
5093
+ children: [
5094
+ "No ",
5095
+ itemLabel,
5096
+ " match your filters."
5097
+ ]
5098
+ }
5099
+ ) }) : grouping.items.map((item) => {
5100
+ if (item.type === "group") {
5101
+ const { group } = item;
5102
+ const groupRows = group.rows.map((entry) => entry.row);
5103
+ const subtotals = hasSubtotals ? [
5104
+ ...selectCol ? [null] : [],
5105
+ ...visibleColumns.map(
5106
+ (c, i) => (
5107
+ // The first cell holds the label, so a subtotal there is skipped.
5108
+ c.subtotal && (i > 0 || selectCol) ? { value: c.subtotal(groupRows), numeric: c.numeric || c.align === "right" } : null
5109
+ )
5110
+ )
5111
+ ] : void 0;
5112
+ return /* @__PURE__ */ jsx(
5113
+ TableGroupRow,
5114
+ {
5115
+ colSpan: columnCount,
5116
+ label: renderGroupLabel ? renderGroupLabel(group.key, groupRows) : group.key,
5117
+ count: group.count,
5118
+ collapsible: true,
5119
+ open: group.open,
5120
+ onOpenChange: (open) => grouping.setOpen(group.key, open),
5121
+ subtotals
5122
+ },
5123
+ item.key
5124
+ );
5125
+ }
5126
+ const { id, row, selected, selectable } = item.row;
5127
+ return /* @__PURE__ */ jsxs(
5128
+ DataTableRow,
4167
5129
  {
4168
- className: c.align === "right" ? "ds:text-right" : void 0,
4169
- children: c.render ? c.render(row) : row[c.key] ?? /* @__PURE__ */ jsx(EmptyValue, {})
5130
+ selected: selection ? selected : void 0,
5131
+ onClick: onRowClick ? () => onRowClick(row) : void 0,
5132
+ className: onRowClick ? "ds:cursor-pointer" : void 0,
5133
+ children: [
5134
+ selection && /* @__PURE__ */ jsx(
5135
+ DataTableCheckbox,
5136
+ {
5137
+ checked: selected,
5138
+ onCheckedChange: () => table.selection.toggle(id),
5139
+ disabled: !selectable,
5140
+ ariaLabel: selection.rowLabel?.(row) ?? `Select ${id}`,
5141
+ onClick: (event) => event.stopPropagation()
5142
+ }
5143
+ ),
5144
+ visibleColumns.map((c) => /* @__PURE__ */ jsxs(
5145
+ DataTableCell,
5146
+ {
5147
+ numeric: c.numeric,
5148
+ ...pinProps(c.key),
5149
+ className: c.align === "right" ? "ds:text-right" : void 0,
5150
+ children: [
5151
+ c.render ? c.render(row) : row[c.key] ?? /* @__PURE__ */ jsx(EmptyValue, {}),
5152
+ c.secondary && /* @__PURE__ */ jsx(DataTableCellSecondary, { children: c.secondary(row) })
5153
+ ]
5154
+ },
5155
+ c.key
5156
+ ))
5157
+ ]
4170
5158
  },
4171
- c.key
4172
- ))
4173
- ]
4174
- },
4175
- id
4176
- )) })
4177
- ] }),
4178
- /* @__PURE__ */ jsx(
4179
- DataTablePagination,
4180
- {
4181
- page: table.pagination.page,
4182
- totalItems: total,
4183
- pageSize,
4184
- onPageChange: table.pagination.setPage,
4185
- hideOnSinglePage
4186
- }
4187
- )
4188
- ] });
5159
+ id
5160
+ );
5161
+ }) })
5162
+ ] }),
5163
+ loadMore ? /* @__PURE__ */ jsx(DataTableLoadMore, { loadedCount: data.length, ...loadMore, root: scrollRef }) : /* @__PURE__ */ jsx(
5164
+ DataTablePagination,
5165
+ {
5166
+ className: "ds:mt-0 ds:border-t ds:border-rule ds:px-(--table-cell-padding-x) ds:py-3",
5167
+ page: table.pagination.page,
5168
+ totalItems: total,
5169
+ pageSize,
5170
+ onPageChange: table.pagination.setPage,
5171
+ hideOnSinglePage
5172
+ }
5173
+ )
5174
+ ]
5175
+ }
5176
+ );
4189
5177
  }
4190
5178
  function EmptyValue() {
4191
5179
  return /* @__PURE__ */ jsx("span", { className: "ds:text-fg-5", children: "\u2014" });
@@ -5749,6 +6737,6 @@ function SignatureEditor({
5749
6737
  }
5750
6738
  SignatureEditor.displayName = "SignatureEditor";
5751
6739
 
5752
- export { AIReceiptPanel, AiDraftCard, Assignee, AttentionItem, Badge, Blockquote, BottomNav, CategoryDivider, CategoryTag, ChannelTabs, ClientRailGroupHeader, ClientRailItem, Dash, DashGrid, DataTable, DataTableBody, DataTableCell, DataTableCellDue, DataTableCellId, DataTableCellMono, DataTableCellName, DataTableCheckbox, DataTableHead, DataTableHeader, DataTablePagination, DataTableResultsCount, DataTableRow, DataTableSearch, DataTableSpacer, DataTableToolbar, DataTableView, DocumentChecklist, DocumentFileCard, DocumentFileLine, DocumentFileRow, DocumentRequestCard, DocumentRequestDetail, DocumentRequestField, DocumentRequestUpload, DocumentSourceFilter, DocumentSourceTag, DocumentsWorkspaceLayout, EmailMessageCard, EmptyValue, EngagementCard, EngagementTimeline, EngagementTimelineStep, IntentBadge, MissingDocumentsPanel, MoneyCell, NewMenu, PROGRESS_LEGACY_TO_INTENT, Pagination, ProgressBar, ProgressRing, SERVICE_TONES, STATUS_DOT_TONE_TO_INTENT, Section, SectionHead, SectionHeader, SegmentedProgress, SignatureEditor, Skeleton, SkeletonCircle, SkeletonText, StagePill, Stat, StatusDot, StatusPill, SuggestionPills, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableGroupRow, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TagsCell, TeamMemberSelect, TimeLogger, TimeLoggerActions, TimeLoggerBillable, TimeLoggerContextRow, TimeLoggerEntry, TimeLoggerEntryList, TimeLoggerField, TimeLoggerFooter, TimeLoggerHeader, TimeLoggerNotes, TimeLoggerTimer, ToggleGroup, ToggleGroupItem, badgeVariants, compareSortValues, formatClock, formatCurrency, formatDuration, nextSortDirection, parseDuration, progressBarVariants, progressRingVariants, resolveBadgeAppearance, serviceToneLabel, serviceToneStyle, statusDotVariants, useDataTable, useOverflowTooltip, useStopwatch };
5753
- //# sourceMappingURL=chunk-FIRPQWYR.js.map
5754
- //# sourceMappingURL=chunk-FIRPQWYR.js.map
6740
+ export { AIReceiptPanel, AiDraftCard, Assignee, AttentionItem, Badge, Blockquote, BottomNav, CategoryDivider, CategoryTag, ChannelTabs, ClientRailGroupHeader, ClientRailItem, DATA_TABLE_NO_GROUPING, Dash, DashGrid, DataTable, DataTableBody, DataTableCell, DataTableCellDue, DataTableCellId, DataTableCellMono, DataTableCellName, DataTableCellSecondary, DataTableCheckbox, DataTableHead, DataTableHeader, DataTableHeaderMenu, DataTableLoadMore, DataTablePagination, DataTableResultsCount, DataTableRow, DataTableSearch, DataTableSpacer, DataTableSpacerRow, DataTableToolbar, DataTableView, DocumentChecklist, DocumentFileCard, DocumentFileLine, DocumentFileRow, DocumentRequestCard, DocumentRequestDetail, DocumentRequestField, DocumentRequestUpload, DocumentSourceFilter, DocumentSourceTag, DocumentsWorkspaceLayout, EmailMessageCard, EmptyValue, EngagementCard, EngagementTimeline, EngagementTimelineStep, IntentBadge, MissingDocumentsPanel, MoneyCell, NewMenu, PROGRESS_LEGACY_TO_INTENT, Pagination, ProgressBar, ProgressRing, SERVICE_TONES, STATUS_DOT_TONE_TO_INTENT, Section, SectionHead, SectionHeader, SegmentedProgress, SignatureEditor, Skeleton, SkeletonCircle, SkeletonText, StagePill, Stat, StatusDot, StatusPill, SuggestionPills, Switch, Table, TableBody, TableCaption, TableCell, TableCellSecondary, TableFooter, TableGroupRow, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TagsCell, TeamMemberSelect, TimeLogger, TimeLoggerActions, TimeLoggerBillable, TimeLoggerContextRow, TimeLoggerEntry, TimeLoggerEntryList, TimeLoggerField, TimeLoggerFooter, TimeLoggerHeader, TimeLoggerNotes, TimeLoggerTimer, ToggleGroup, ToggleGroupItem, VisuallyHidden, badgeVariants, compareSortValues, formatClock, formatCurrency, formatDuration, nextSortDirection, parseDuration, progressBarVariants, progressRingVariants, resolveBadgeAppearance, serviceToneLabel, serviceToneStyle, statusDotVariants, useDataTable, useDataTableGrouping, useDataTableVirtualRows, useOverflowTooltip, useScrollRestoration, useStopwatch };
6741
+ //# sourceMappingURL=chunk-YRFLK7ER.js.map
6742
+ //# sourceMappingURL=chunk-YRFLK7ER.js.map