react-glide-table 1.4.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/compound.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/components/ui/table/components/DataTable/DataTable.tsx
2
2
  import { flexRender as flexRender2 } from "@tanstack/react-table";
3
- import { useMemo as useMemo3 } from "react";
3
+ import { useMemo as useMemo4 } from "react";
4
4
 
5
5
  // src/components/ui/table/components/DataTable/DataTableRow.tsx
6
6
  import { flexRender } from "@tanstack/react-table";
@@ -16,6 +16,7 @@ var ROW_HOVER_CLASS = "row-hoverable";
16
16
  var ROW_HOVERED_BG_CLASS = "row-hovered";
17
17
  var CELL_SELECTION_FILL_CLASS = "cell-selection-fill";
18
18
  var DATA_TABLE_ROW_HEIGHT = 44;
19
+ var DATA_TABLE_HEADER_ROW_HEIGHT = 40;
19
20
  var DATA_TABLE_VIRTUAL_OVERSCAN = 8;
20
21
  var DATA_TABLE_COLUMN_SIZE = 150;
21
22
  var DATA_TABLE_COLUMN_MIN_SIZE = 40;
@@ -103,6 +104,36 @@ function getCellSelectionBounds(start, end) {
103
104
  endCol: Math.max(start.col, end.col)
104
105
  };
105
106
  }
107
+ function getCellNavigationDelta(key) {
108
+ switch (key) {
109
+ case "ArrowUp":
110
+ case "w":
111
+ case "W":
112
+ return { row: -1, col: 0 };
113
+ case "ArrowDown":
114
+ case "s":
115
+ case "S":
116
+ return { row: 1, col: 0 };
117
+ case "ArrowLeft":
118
+ case "a":
119
+ case "A":
120
+ return { row: 0, col: -1 };
121
+ case "ArrowRight":
122
+ case "d":
123
+ case "D":
124
+ return { row: 0, col: 1 };
125
+ default:
126
+ return null;
127
+ }
128
+ }
129
+ function clampCellPosition(position, rowCount, columnCount) {
130
+ const maxRow = Math.max(rowCount - 1, 0);
131
+ const maxCol = Math.max(columnCount - 1, 0);
132
+ return {
133
+ row: Math.min(Math.max(position.row, 0), maxRow),
134
+ col: Math.min(Math.max(position.col, 0), maxCol)
135
+ };
136
+ }
106
137
  function measureMergedSpanRowHeights(rowIndex, rowSpan, cellElement) {
107
138
  if (rowSpan <= 1) return void 0;
108
139
  const tbody = cellElement?.closest("tbody") ?? (typeof document !== "undefined" ? document.querySelector("tbody.data-table-body") : null);
@@ -439,7 +470,7 @@ function getColumnFreezeStyle(offset, options) {
439
470
  position: "sticky",
440
471
  ...offset.side === "left" ? { left: offset.offset } : { right: offset.offset },
441
472
  zIndex: zBase + offset.stack,
442
- ...options?.isHeader ? { top: 0 } : {}
473
+ ...options?.isHeader ? { top: options.headerTop ?? 0 } : {}
443
474
  };
444
475
  }
445
476
 
@@ -456,6 +487,162 @@ function getColumnSizeStyle(size, options) {
456
487
  };
457
488
  }
458
489
 
490
+ // src/components/ui/table/features/inline-search/inlineSearch.ts
491
+ var INLINE_SEARCH_MAX_RESULTS = 1e3;
492
+ var INLINE_SEARCH_TARGET_TICK_MS = 10;
493
+ var INLINE_SEARCH_INITIAL_STRIDE = 10;
494
+ function escapeSearchRegex(value) {
495
+ return value.replace(/([$()*+.?[\\\]^{|}-])/g, "\\$1");
496
+ }
497
+ function createSearchRegex(query) {
498
+ const trimmed = query.trim();
499
+ if (!trimmed) return null;
500
+ return new RegExp(escapeSearchRegex(trimmed), "i");
501
+ }
502
+ function cellValueToSearchText(value) {
503
+ if (value == null) return void 0;
504
+ if (typeof value === "string") return value;
505
+ if (typeof value === "number" || typeof value === "boolean") {
506
+ return String(value);
507
+ }
508
+ if (Array.isArray(value)) {
509
+ return value.map((item) => cellValueToSearchText(item) ?? "").join(" ");
510
+ }
511
+ if (typeof value === "object") {
512
+ try {
513
+ return JSON.stringify(value);
514
+ } catch {
515
+ return String(value);
516
+ }
517
+ }
518
+ return String(value);
519
+ }
520
+ function formatSearchResultLabel(status) {
521
+ const countLabel = status.results >= INLINE_SEARCH_MAX_RESULTS ? `over ${INLINE_SEARCH_MAX_RESULTS}` : `${status.results} result${status.results !== 1 ? "s" : ""}`;
522
+ if (status.selectedIndex >= 0 && status.results > 0) {
523
+ return `${status.selectedIndex + 1} of ${countLabel}`;
524
+ }
525
+ return countLabel;
526
+ }
527
+ function nextSearchIndex(selectedIndex, results) {
528
+ if (results <= 0) return -1;
529
+ if (selectedIndex < 0) return 0;
530
+ return (selectedIndex + 1) % results;
531
+ }
532
+ function previousSearchIndex(selectedIndex, results) {
533
+ if (results <= 0) return -1;
534
+ if (selectedIndex < 0) return results - 1;
535
+ let next = (selectedIndex - 1) % results;
536
+ if (next < 0) next += results;
537
+ return next;
538
+ }
539
+ function buildSearchMatchKey(colIndex, rowIndex) {
540
+ return `${colIndex}:${rowIndex}`;
541
+ }
542
+ function buildSearchMatchKeys(results) {
543
+ const keys = /* @__PURE__ */ new Set();
544
+ for (const [colIndex, rowIndex] of results) {
545
+ keys.add(buildSearchMatchKey(colIndex, rowIndex));
546
+ }
547
+ return keys;
548
+ }
549
+ function collectSearchMatchesInRange(options) {
550
+ const {
551
+ query,
552
+ startRow,
553
+ rowCount,
554
+ columnCount,
555
+ getCellValue,
556
+ maxResults = INLINE_SEARCH_MAX_RESULTS
557
+ } = options;
558
+ const regex = createSearchRegex(query);
559
+ if (!regex || rowCount <= 0 || columnCount <= 0) return [];
560
+ const matches = [];
561
+ for (let rowOffset = 0; rowOffset < rowCount; rowOffset += 1) {
562
+ const rowIndex = startRow + rowOffset;
563
+ for (let colIndex = 0; colIndex < columnCount; colIndex += 1) {
564
+ const text = cellValueToSearchText(getCellValue(rowIndex, colIndex));
565
+ if (text !== void 0 && regex.test(text)) {
566
+ matches.push([colIndex, rowIndex]);
567
+ if (matches.length >= maxResults) {
568
+ return matches;
569
+ }
570
+ }
571
+ }
572
+ }
573
+ return matches;
574
+ }
575
+ function nextSearchStride(currentStride, elapsedMs, targetMs = INLINE_SEARCH_TARGET_TICK_MS) {
576
+ const rounded = Math.max(elapsedMs, 1);
577
+ const scalar = targetMs / rounded;
578
+ return Math.max(1, Math.ceil(currentStride * scalar));
579
+ }
580
+ function buildFlatSearchCorpus(rows, getRowId) {
581
+ return rows.map((data, index) => ({
582
+ id: getRowId(data, index),
583
+ data,
584
+ ancestorToggleKeys: []
585
+ }));
586
+ }
587
+ function buildTreeSearchCorpus(visibleRows, options) {
588
+ const { toggleField, getRowId } = options;
589
+ const corpus = [];
590
+ const seen = /* @__PURE__ */ new Set();
591
+ const walk = (node, ancestorToggleKeys) => {
592
+ const id = getRowId(node, corpus.length);
593
+ if (seen.has(id)) return;
594
+ seen.add(id);
595
+ corpus.push({
596
+ id,
597
+ data: node,
598
+ ancestorToggleKeys
599
+ });
600
+ const children = node.children;
601
+ if (!Array.isArray(children) || children.length === 0) return;
602
+ const toggleValue = node[toggleField];
603
+ const childAncestors = typeof toggleValue === "string" && toggleValue.length > 0 ? [...ancestorToggleKeys, toggleValue] : ancestorToggleKeys;
604
+ for (const child of children) {
605
+ if (child && typeof child === "object") {
606
+ walk(child, childAncestors);
607
+ }
608
+ }
609
+ };
610
+ for (const row of visibleRows) {
611
+ const level = row.level;
612
+ if (level === 0 || level === void 0) {
613
+ walk(row, []);
614
+ }
615
+ }
616
+ for (const row of visibleRows) {
617
+ const id = getRowId(row, corpus.length);
618
+ if (seen.has(id)) continue;
619
+ walk(row, []);
620
+ }
621
+ return corpus;
622
+ }
623
+ function mapSearchResultsToVisibleKeys(results, corpus, visibleRowIndexById) {
624
+ const keys = /* @__PURE__ */ new Set();
625
+ for (const [colIndex, corpusRowIndex] of results) {
626
+ const corpusRow = corpus[corpusRowIndex];
627
+ if (!corpusRow) continue;
628
+ const visibleRowIndex = visibleRowIndexById.get(corpusRow.id);
629
+ if (visibleRowIndex === void 0) continue;
630
+ keys.add(buildSearchMatchKey(colIndex, visibleRowIndex));
631
+ }
632
+ return keys;
633
+ }
634
+ function mapSearchResultToVisibleItem(item, corpus, visibleRowIndexById) {
635
+ const [colIndex, corpusRowIndex] = item;
636
+ const corpusRow = corpus[corpusRowIndex];
637
+ if (!corpusRow) return null;
638
+ const visibleRowIndex = visibleRowIndexById.get(corpusRow.id);
639
+ if (visibleRowIndex === void 0) return null;
640
+ return [colIndex, visibleRowIndex];
641
+ }
642
+ function collectAncestorKeysToExpand(corpusRow, expandedRows) {
643
+ return corpusRow.ancestorToggleKeys.filter((key) => !expandedRows.has(key));
644
+ }
645
+
459
646
  // src/components/ui/table/features/row-expand/row-expand.ts
460
647
  import { useEffect, useMemo, useRef } from "react";
461
648
 
@@ -901,10 +1088,16 @@ function DataTableRow({
901
1088
  cellEdit,
902
1089
  expand,
903
1090
  columnResize,
904
- columnFreeze
1091
+ columnFreeze,
1092
+ inlineSearch
905
1093
  } = useDataTableRowContext();
906
1094
  const { enableColumnResize } = columnResize;
907
1095
  const { enableColumnFreeze, offsets: freezeOffsets } = columnFreeze;
1096
+ const {
1097
+ enabled: enableInlineSearch,
1098
+ matchKeys: searchMatchKeys,
1099
+ activeMatch
1100
+ } = inlineSearch;
908
1101
  const {
909
1102
  enableRowSpan,
910
1103
  primaryRowSpanColumnId,
@@ -1096,9 +1289,14 @@ function DataTableRow({
1096
1289
  ...freezeStyle,
1097
1290
  ...selectionEdgeStyle
1098
1291
  };
1292
+ const searchMatchKey = buildSearchMatchKey(cellIndex, rowIndex);
1293
+ const isSearchMatch = enableInlineSearch && searchMatchKeys.has(searchMatchKey);
1294
+ const isSearchActive = isSearchMatch && activeMatch !== null && activeMatch[0] === cellIndex && activeMatch[1] === rowIndex;
1099
1295
  return /* @__PURE__ */ jsxs2(
1100
1296
  "td",
1101
1297
  {
1298
+ "data-row-index": rowIndex,
1299
+ "data-col-index": cellIndex,
1102
1300
  rowSpan: rowSpanInfo && rowSpanInfo.rowSpan > 1 ? rowSpanInfo.rowSpan : void 0,
1103
1301
  "data-merged": isMerged && cellIndex > 0 ? "" : void 0,
1104
1302
  "data-merged-edge-right": showMergedRightEdge ? "" : void 0,
@@ -1108,6 +1306,8 @@ function DataTableRow({
1108
1306
  "data-group-hovered": enableRowSpan && showCellHover && !showCellSelected ? "" : void 0,
1109
1307
  "data-selection-fill": isCellDragSelected ? "" : void 0,
1110
1308
  "data-selection-edges": hasSelectionEdges ? "" : void 0,
1309
+ "data-search-match": isSearchMatch ? "" : void 0,
1310
+ "data-search-active": isSearchActive ? "" : void 0,
1111
1311
  "data-editable": editable ? "" : void 0,
1112
1312
  "data-editing": isEditing ? "" : void 0,
1113
1313
  "data-frozen": freezeOffset?.side,
@@ -1122,7 +1322,8 @@ function DataTableRow({
1122
1322
  event.preventDefault();
1123
1323
  onCellMouseDown(
1124
1324
  resolveCellRowIndex(event.clientY, event.currentTarget),
1125
- cellIndex
1325
+ cellIndex,
1326
+ { shiftKey: event.shiftKey }
1126
1327
  );
1127
1328
  },
1128
1329
  onMouseEnter: (event) => {
@@ -1159,6 +1360,8 @@ function DataTableRow({
1159
1360
  enableRowSpan && showCellHover && !showCellSelected && "is-group-hovered",
1160
1361
  isCellDragSelected && CELL_SELECTION_FILL_CLASS,
1161
1362
  hasSelectionEdges && CELL_SELECTION_EDGES_CLASS,
1363
+ isSearchMatch && "is-search-match",
1364
+ isSearchActive && "is-search-active",
1162
1365
  editable && "is-editable",
1163
1366
  classNames?.cell
1164
1367
  ),
@@ -1293,8 +1496,169 @@ function DataTableRow({
1293
1496
  );
1294
1497
  }
1295
1498
 
1499
+ // src/components/ui/table/components/DataTable/DataTableSearch.tsx
1500
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1501
+ function SearchCloseIcon({ className }) {
1502
+ return /* @__PURE__ */ jsxs3(
1503
+ "svg",
1504
+ {
1505
+ className,
1506
+ "aria-hidden": true,
1507
+ width: "16",
1508
+ height: "16",
1509
+ viewBox: "0 0 24 24",
1510
+ fill: "none",
1511
+ stroke: "currentColor",
1512
+ strokeWidth: "2",
1513
+ strokeLinecap: "round",
1514
+ strokeLinejoin: "round",
1515
+ children: [
1516
+ /* @__PURE__ */ jsx4("path", { d: "M18 6 6 18" }),
1517
+ /* @__PURE__ */ jsx4("path", { d: "m6 6 12 12" })
1518
+ ]
1519
+ }
1520
+ );
1521
+ }
1522
+ function DataTableSearch({
1523
+ showSearch,
1524
+ searchValue,
1525
+ searchStatus,
1526
+ searchInputId,
1527
+ searchInputRef,
1528
+ canClose,
1529
+ placeholder,
1530
+ resultHint,
1531
+ previousLabel,
1532
+ nextLabel,
1533
+ closeLabel,
1534
+ rowsTotal,
1535
+ classNames,
1536
+ onSearchValueChange,
1537
+ onClose,
1538
+ onNext,
1539
+ onPrevious
1540
+ }) {
1541
+ if (!showSearch) return null;
1542
+ const resultString = searchStatus ? formatSearchResultLabel(searchStatus) : resultHint;
1543
+ const progress = rowsTotal > 0 ? Math.floor((searchStatus?.rowsSearched ?? 0) / rowsTotal * 100) : 0;
1544
+ const handleKeyDown = (event) => {
1545
+ if ((event.ctrlKey || event.metaKey) && event.code === "KeyF" || event.key === "Escape") {
1546
+ event.preventDefault();
1547
+ event.stopPropagation();
1548
+ if (canClose) {
1549
+ onClose();
1550
+ }
1551
+ return;
1552
+ }
1553
+ if (event.key === "ArrowDown" || event.key === "Enter" && !event.shiftKey) {
1554
+ event.preventDefault();
1555
+ onNext();
1556
+ return;
1557
+ }
1558
+ if (event.key === "ArrowUp" || event.key === "Enter" && event.shiftKey) {
1559
+ event.preventDefault();
1560
+ onPrevious();
1561
+ }
1562
+ };
1563
+ return /* @__PURE__ */ jsxs3(
1564
+ "div",
1565
+ {
1566
+ className: cn("data-table-search", classNames?.search),
1567
+ role: "search",
1568
+ onMouseDown: (event) => event.stopPropagation(),
1569
+ children: [
1570
+ /* @__PURE__ */ jsxs3("div", { className: "data-table-search-row", children: [
1571
+ /* @__PURE__ */ jsx4(
1572
+ "input",
1573
+ {
1574
+ ref: searchInputRef,
1575
+ id: searchInputId,
1576
+ type: "search",
1577
+ value: searchValue,
1578
+ placeholder,
1579
+ autoComplete: "off",
1580
+ spellCheck: false,
1581
+ "aria-label": placeholder,
1582
+ className: cn("data-table-search-input", classNames?.searchInput),
1583
+ onChange: (event) => onSearchValueChange(event.target.value),
1584
+ onKeyDown: handleKeyDown
1585
+ }
1586
+ ),
1587
+ /* @__PURE__ */ jsx4(
1588
+ "button",
1589
+ {
1590
+ type: "button",
1591
+ "aria-label": previousLabel,
1592
+ className: cn("data-table-search-button", classNames?.searchButton),
1593
+ onClick: (event) => {
1594
+ event.stopPropagation();
1595
+ onPrevious();
1596
+ },
1597
+ children: /* @__PURE__ */ jsx4(ChevronUp, { className: "data-table-search-icon" })
1598
+ }
1599
+ ),
1600
+ /* @__PURE__ */ jsx4(
1601
+ "button",
1602
+ {
1603
+ type: "button",
1604
+ "aria-label": nextLabel,
1605
+ className: cn("data-table-search-button", classNames?.searchButton),
1606
+ onClick: (event) => {
1607
+ event.stopPropagation();
1608
+ onNext();
1609
+ },
1610
+ children: /* @__PURE__ */ jsx4(ChevronDown, { className: "data-table-search-icon" })
1611
+ }
1612
+ ),
1613
+ canClose ? /* @__PURE__ */ jsx4(
1614
+ "button",
1615
+ {
1616
+ type: "button",
1617
+ "aria-label": closeLabel,
1618
+ className: cn("data-table-search-button", classNames?.searchButton),
1619
+ onClick: (event) => {
1620
+ event.stopPropagation();
1621
+ onClose();
1622
+ },
1623
+ children: /* @__PURE__ */ jsx4(SearchCloseIcon, { className: "data-table-search-icon" })
1624
+ }
1625
+ ) : null
1626
+ ] }),
1627
+ /* @__PURE__ */ jsx4(
1628
+ "div",
1629
+ {
1630
+ className: cn("data-table-search-status", classNames?.searchStatus),
1631
+ "aria-live": "polite",
1632
+ children: resultString
1633
+ }
1634
+ ),
1635
+ searchStatus !== void 0 ? /* @__PURE__ */ jsx4(
1636
+ "div",
1637
+ {
1638
+ className: cn(
1639
+ "data-table-search-progress",
1640
+ classNames?.searchProgress
1641
+ ),
1642
+ role: "progressbar",
1643
+ "aria-valuemin": 0,
1644
+ "aria-valuemax": 100,
1645
+ "aria-valuenow": progress,
1646
+ children: /* @__PURE__ */ jsx4(
1647
+ "div",
1648
+ {
1649
+ className: "data-table-search-progress-bar",
1650
+ style: { width: `${progress}%` }
1651
+ }
1652
+ )
1653
+ }
1654
+ ) : null
1655
+ ]
1656
+ }
1657
+ );
1658
+ }
1659
+
1296
1660
  // src/components/ui/table/components/DataTable/DataTableToolbar.tsx
1297
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1661
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1298
1662
  function DataTableToolbar({
1299
1663
  filteredCount,
1300
1664
  totalCount,
@@ -1312,24 +1676,56 @@ function DataTableToolbar({
1312
1676
  const selectionContent = selectionLabel?.(selectedCount) ?? null;
1313
1677
  const hasSelectionContent = selectionContent !== null && selectionContent !== false && selectionContent !== void 0 && typeof selectionContent !== "boolean";
1314
1678
  if (!hasLeftContent && !hasToolbar && !hasSelectionContent) return null;
1315
- return /* @__PURE__ */ jsxs3("div", { className: cn("DataTableToolbarJSX", classNames?.toolbar, className), children: [
1316
- /* @__PURE__ */ jsxs3("div", { className: cn("toolbar-left", classNames?.toolbarLeft), children: [
1317
- hasCount && /* @__PURE__ */ jsx4("span", { className: cn("toolbar-count", classNames?.toolbarCount), children: displayFiltered !== void 0 && totalCount !== void 0 ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1318
- /* @__PURE__ */ jsx4("span", { className: "toolbar-count-primary", children: displayFiltered }),
1319
- /* @__PURE__ */ jsxs3("span", { className: "toolbar-count-placeholder", children: [
1679
+ return /* @__PURE__ */ jsxs4("div", { className: cn("DataTableToolbarJSX", classNames?.toolbar, className), children: [
1680
+ /* @__PURE__ */ jsxs4("div", { className: cn("toolbar-left", classNames?.toolbarLeft), children: [
1681
+ hasCount && /* @__PURE__ */ jsx5("span", { className: cn("toolbar-count", classNames?.toolbarCount), children: displayFiltered !== void 0 && totalCount !== void 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1682
+ /* @__PURE__ */ jsx5("span", { className: "toolbar-count-primary", children: displayFiltered }),
1683
+ /* @__PURE__ */ jsxs4("span", { className: "toolbar-count-placeholder", children: [
1320
1684
  " / ",
1321
1685
  totalCount
1322
1686
  ] })
1323
- ] }) : /* @__PURE__ */ jsx4("span", { className: "toolbar-count-primary", children: displayFiltered ?? totalCount }) }),
1687
+ ] }) : /* @__PURE__ */ jsx5("span", { className: "toolbar-count-primary", children: displayFiltered ?? totalCount }) }),
1324
1688
  summary
1325
1689
  ] }),
1326
- /* @__PURE__ */ jsxs3("div", { className: cn("toolbar-right", classNames?.toolbarRight), children: [
1327
- hasSelectionContent && (typeof selectionContent === "string" || typeof selectionContent === "number" ? /* @__PURE__ */ jsx4("span", { className: cn("toolbar-selection", classNames?.toolbarSelection), children: selectionContent }) : selectionContent),
1328
- hasToolbar && /* @__PURE__ */ jsx4("div", { className: cn("toolbar-actions", classNames?.toolbarActions), children: toolbar })
1690
+ /* @__PURE__ */ jsxs4("div", { className: cn("toolbar-right", classNames?.toolbarRight), children: [
1691
+ hasSelectionContent && (typeof selectionContent === "string" || typeof selectionContent === "number" ? /* @__PURE__ */ jsx5("span", { className: cn("toolbar-selection", classNames?.toolbarSelection), children: selectionContent }) : selectionContent),
1692
+ hasToolbar && /* @__PURE__ */ jsx5("div", { className: cn("toolbar-actions", classNames?.toolbarActions), children: toolbar })
1329
1693
  ] })
1330
1694
  ] });
1331
1695
  }
1332
1696
 
1697
+ // src/components/ui/table/features/column-groups/mergeHeaderGroups.ts
1698
+ function getMergedHeaderGroups(headerGroups) {
1699
+ if (headerGroups.length <= 1) {
1700
+ return headerGroups.map((group) => ({
1701
+ ...group,
1702
+ headers: group.headers.map((header) => ({
1703
+ ...header,
1704
+ mergedRowSpan: 1
1705
+ }))
1706
+ }));
1707
+ }
1708
+ const seenColumnIds = /* @__PURE__ */ new Set();
1709
+ const fullDepth = headerGroups.length;
1710
+ return headerGroups.map((group, depth) => ({
1711
+ ...group,
1712
+ headers: group.headers.filter((header) => !seenColumnIds.has(header.column.id)).map((header) => {
1713
+ seenColumnIds.add(header.column.id);
1714
+ if (header.isPlaceholder) {
1715
+ return {
1716
+ ...header,
1717
+ isPlaceholder: false,
1718
+ mergedRowSpan: fullDepth - depth
1719
+ };
1720
+ }
1721
+ return {
1722
+ ...header,
1723
+ mergedRowSpan: 1
1724
+ };
1725
+ })
1726
+ }));
1727
+ }
1728
+
1333
1729
  // src/core/useGlideTable.ts
1334
1730
  import {
1335
1731
  getCoreRowModel,
@@ -1339,11 +1735,11 @@ import {
1339
1735
  useVirtualizer
1340
1736
  } from "@tanstack/react-virtual";
1341
1737
  import {
1342
- useCallback as useCallback3,
1343
- useEffect as useEffect5,
1344
- useMemo as useMemo2,
1345
- useRef as useRef5,
1346
- useState as useState3
1738
+ useCallback as useCallback4,
1739
+ useEffect as useEffect6,
1740
+ useMemo as useMemo3,
1741
+ useRef as useRef6,
1742
+ useState as useState4
1347
1743
  } from "react";
1348
1744
 
1349
1745
  // src/components/ui/table/features/cell-edit/useCellEdit.ts
@@ -1672,26 +2068,44 @@ function useCellSelection({
1672
2068
  data,
1673
2069
  rows,
1674
2070
  enabled = true,
2071
+ columnCount = 0,
1675
2072
  enableSubtreeCopy = false,
1676
2073
  enableInsertPaste = true,
1677
2074
  onDataChange,
1678
2075
  onBatchChange,
1679
- onRowsPaste
2076
+ onRowsPaste,
2077
+ onCellNavigate
1680
2078
  }) {
1681
2079
  const [dragState, setDragState] = useState2(INITIAL_DRAG_STATE);
1682
2080
  const pendingPasteModeRef = useRef4(null);
2081
+ const dragStateRef = useRef4(dragState);
2082
+ const onCellNavigateRef = useRef4(onCellNavigate);
2083
+ dragStateRef.current = dragState;
2084
+ onCellNavigateRef.current = onCellNavigate;
1683
2085
  const cellSelectionBounds = getCellSelectionBounds(dragState.start, dragState.end);
1684
2086
  const activeSelectionBounds = enabled ? getActiveSelectionBounds(dragState, cellSelectionBounds) : null;
1685
2087
  const handleCellMouseDown = useCallback2(
1686
- (rowIndex, colIndex) => {
2088
+ (rowIndex, colIndex, options) => {
1687
2089
  if (!enabled) return;
1688
- setDragState({
1689
- isSelecting: true,
1690
- isFillDragging: false,
1691
- start: { row: rowIndex, col: colIndex },
1692
- end: { row: rowIndex, col: colIndex },
1693
- fillAnchor: null,
1694
- fillEnd: null
2090
+ setDragState((prev) => {
2091
+ if (options?.shiftKey && prev.start) {
2092
+ return {
2093
+ ...prev,
2094
+ isSelecting: true,
2095
+ isFillDragging: false,
2096
+ end: { row: rowIndex, col: colIndex },
2097
+ fillAnchor: null,
2098
+ fillEnd: null
2099
+ };
2100
+ }
2101
+ return {
2102
+ isSelecting: true,
2103
+ isFillDragging: false,
2104
+ start: { row: rowIndex, col: colIndex },
2105
+ end: { row: rowIndex, col: colIndex },
2106
+ fillAnchor: null,
2107
+ fillEnd: null
2108
+ };
1695
2109
  });
1696
2110
  },
1697
2111
  [enabled]
@@ -1733,6 +2147,53 @@ function useCellSelection({
1733
2147
  setDragState(INITIAL_DRAG_STATE);
1734
2148
  }
1735
2149
  }, [enabled]);
2150
+ useEffect4(() => {
2151
+ if (!enabled) return;
2152
+ const handleKeyDown = (e) => {
2153
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
2154
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
2155
+ return;
2156
+ }
2157
+ const delta = getCellNavigationDelta(e.key);
2158
+ if (!delta) return;
2159
+ const prev = dragStateRef.current;
2160
+ if (!prev.start || !prev.end) return;
2161
+ if (prev.isSelecting || prev.isFillDragging) return;
2162
+ const rowCount = rows.length;
2163
+ const resolvedColumnCount = columnCount > 0 ? columnCount : rows[0]?.getVisibleCells().length ?? 0;
2164
+ if (rowCount <= 0 || resolvedColumnCount <= 0) return;
2165
+ const nextEnd = clampCellPosition(
2166
+ {
2167
+ row: prev.end.row + delta.row,
2168
+ col: prev.end.col + delta.col
2169
+ },
2170
+ rowCount,
2171
+ resolvedColumnCount
2172
+ );
2173
+ if (nextEnd.row === prev.end.row && nextEnd.col === prev.end.col) return;
2174
+ e.preventDefault();
2175
+ const nextState = e.shiftKey ? {
2176
+ ...prev,
2177
+ isSelecting: false,
2178
+ isFillDragging: false,
2179
+ end: nextEnd,
2180
+ fillAnchor: null,
2181
+ fillEnd: null
2182
+ } : {
2183
+ isSelecting: false,
2184
+ isFillDragging: false,
2185
+ start: nextEnd,
2186
+ end: nextEnd,
2187
+ fillAnchor: null,
2188
+ fillEnd: null
2189
+ };
2190
+ dragStateRef.current = nextState;
2191
+ setDragState(nextState);
2192
+ onCellNavigateRef.current?.(nextEnd);
2193
+ };
2194
+ window.addEventListener("keydown", handleKeyDown);
2195
+ return () => window.removeEventListener("keydown", handleKeyDown);
2196
+ }, [columnCount, enabled, rows]);
1736
2197
  const copySelection = useCallback2(
1737
2198
  async (options) => {
1738
2199
  if (!enabled || !activeSelectionBounds) return false;
@@ -1890,6 +2351,311 @@ function useCellSelection({
1890
2351
  };
1891
2352
  }
1892
2353
 
2354
+ // src/components/ui/table/features/inline-search/useInlineSearch.ts
2355
+ import {
2356
+ useCallback as useCallback3,
2357
+ useEffect as useEffect5,
2358
+ useId,
2359
+ useMemo as useMemo2,
2360
+ useRef as useRef5,
2361
+ useState as useState3
2362
+ } from "react";
2363
+ var EMPTY_MATCH_KEYS = /* @__PURE__ */ new Set();
2364
+ function useInlineSearch({
2365
+ enabled = false,
2366
+ rowCount,
2367
+ columnCount,
2368
+ getCellValue,
2369
+ initialStartRow = 0,
2370
+ showSearch: controlledShowSearch,
2371
+ searchValue: controlledSearchValue,
2372
+ searchResults: controlledSearchResults,
2373
+ onSearchValueChange,
2374
+ onSearchClose,
2375
+ onSearchResultsChanged,
2376
+ onNavigateToResult,
2377
+ rootRef
2378
+ }) {
2379
+ const searchInputId = useId();
2380
+ const searchInputRef = useRef5(null);
2381
+ const [internalShowSearch, setInternalShowSearch] = useState3(false);
2382
+ const [internalSearchValue, setInternalSearchValue] = useState3("");
2383
+ const [internalResults, setInternalResults] = useState3(
2384
+ []
2385
+ );
2386
+ const [searchStatus, setSearchStatus] = useState3();
2387
+ const searchStatusRef = useRef5(searchStatus);
2388
+ searchStatusRef.current = searchStatus;
2389
+ const abortControllerRef = useRef5(null);
2390
+ const searchHandleRef = useRef5(void 0);
2391
+ const initialStartRowRef = useRef5(initialStartRow);
2392
+ initialStartRowRef.current = initialStartRow;
2393
+ const getCellValueRef = useRef5(getCellValue);
2394
+ getCellValueRef.current = getCellValue;
2395
+ const showSearch = controlledShowSearch ?? internalShowSearch;
2396
+ const searchValue = controlledSearchValue ?? internalSearchValue;
2397
+ const searchResults = controlledSearchResults ?? internalResults;
2398
+ const setSearchValue = useCallback3(
2399
+ (value) => {
2400
+ setInternalSearchValue(value);
2401
+ onSearchValueChange?.(value);
2402
+ },
2403
+ [onSearchValueChange]
2404
+ );
2405
+ const cancelSearch = useCallback3(() => {
2406
+ if (searchHandleRef.current !== void 0) {
2407
+ window.cancelAnimationFrame(searchHandleRef.current);
2408
+ searchHandleRef.current = void 0;
2409
+ }
2410
+ abortControllerRef.current?.abort();
2411
+ }, []);
2412
+ const emitResultsChanged = useCallback3(
2413
+ (results, navIndex) => {
2414
+ onSearchResultsChanged?.(results, navIndex);
2415
+ },
2416
+ [onSearchResultsChanged]
2417
+ );
2418
+ const navigateToIndex = useCallback3(
2419
+ (results, navIndex) => {
2420
+ if (onSearchResultsChanged) return;
2421
+ if (navIndex < 0 || navIndex >= results.length) return;
2422
+ const item = results[navIndex];
2423
+ if (!item) return;
2424
+ onNavigateToResult?.(item);
2425
+ },
2426
+ [onNavigateToResult, onSearchResultsChanged]
2427
+ );
2428
+ const beginSearch = useCallback3(
2429
+ (query) => {
2430
+ if (controlledSearchResults !== void 0) return;
2431
+ const totalRows = rowCount;
2432
+ if (totalRows === 0 || columnCount === 0) {
2433
+ setSearchStatus(void 0);
2434
+ setInternalResults([]);
2435
+ emitResultsChanged([], -1);
2436
+ return;
2437
+ }
2438
+ let startY = Math.min(
2439
+ Math.max(0, initialStartRowRef.current),
2440
+ totalRows - 1
2441
+ );
2442
+ let searchStride = Math.min(INLINE_SEARCH_INITIAL_STRIDE, totalRows);
2443
+ let rowsSearched = 0;
2444
+ const runningResult = [];
2445
+ setSearchStatus(void 0);
2446
+ setInternalResults([]);
2447
+ const tick = () => {
2448
+ if (abortControllerRef.current?.signal.aborted) return;
2449
+ const tStart = performance.now();
2450
+ const rowsLeft = totalRows - rowsSearched;
2451
+ const height = Math.min(searchStride, rowsLeft, totalRows - startY);
2452
+ if (height <= 0) {
2453
+ return;
2454
+ }
2455
+ const chunk = collectSearchMatchesInRange({
2456
+ query,
2457
+ startRow: startY,
2458
+ rowCount: height,
2459
+ columnCount,
2460
+ getCellValue: (rowIndex, colIndex) => getCellValueRef.current(rowIndex, colIndex),
2461
+ maxResults: INLINE_SEARCH_MAX_RESULTS - runningResult.length
2462
+ });
2463
+ if (chunk.length > 0) {
2464
+ runningResult.push(...chunk);
2465
+ setInternalResults([...runningResult]);
2466
+ }
2467
+ rowsSearched += height;
2468
+ const selectedIndex = searchStatusRef.current?.selectedIndex ?? -1;
2469
+ setSearchStatus({
2470
+ results: runningResult.length,
2471
+ rowsSearched,
2472
+ selectedIndex
2473
+ });
2474
+ emitResultsChanged(runningResult, selectedIndex);
2475
+ if (startY + height >= totalRows) {
2476
+ startY = 0;
2477
+ } else {
2478
+ startY += height;
2479
+ }
2480
+ searchStride = nextSearchStride(
2481
+ searchStride,
2482
+ performance.now() - tStart
2483
+ );
2484
+ if (rowsSearched < totalRows && runningResult.length < INLINE_SEARCH_MAX_RESULTS) {
2485
+ searchHandleRef.current = window.requestAnimationFrame(tick);
2486
+ }
2487
+ };
2488
+ cancelSearch();
2489
+ abortControllerRef.current = new AbortController();
2490
+ searchHandleRef.current = window.requestAnimationFrame(tick);
2491
+ },
2492
+ [
2493
+ cancelSearch,
2494
+ columnCount,
2495
+ controlledSearchResults,
2496
+ emitResultsChanged,
2497
+ rowCount
2498
+ ]
2499
+ );
2500
+ const openSearch = useCallback3(() => {
2501
+ if (controlledShowSearch === void 0) {
2502
+ setInternalShowSearch(true);
2503
+ }
2504
+ }, [controlledShowSearch]);
2505
+ const closeSearch = useCallback3(() => {
2506
+ if (controlledShowSearch === void 0) {
2507
+ setInternalShowSearch(false);
2508
+ }
2509
+ onSearchClose?.();
2510
+ setSearchStatus(void 0);
2511
+ setInternalResults([]);
2512
+ emitResultsChanged([], -1);
2513
+ cancelSearch();
2514
+ }, [
2515
+ cancelSearch,
2516
+ controlledShowSearch,
2517
+ emitResultsChanged,
2518
+ onSearchClose
2519
+ ]);
2520
+ const goToNext = useCallback3(() => {
2521
+ if (!searchStatus || searchStatus.results === 0) return;
2522
+ const newIndex = nextSearchIndex(
2523
+ searchStatus.selectedIndex,
2524
+ searchStatus.results
2525
+ );
2526
+ setSearchStatus({ ...searchStatus, selectedIndex: newIndex });
2527
+ emitResultsChanged(searchResults, newIndex);
2528
+ navigateToIndex(searchResults, newIndex);
2529
+ }, [emitResultsChanged, navigateToIndex, searchResults, searchStatus]);
2530
+ const goToPrevious = useCallback3(() => {
2531
+ if (!searchStatus || searchStatus.results === 0) return;
2532
+ const newIndex = previousSearchIndex(
2533
+ searchStatus.selectedIndex,
2534
+ searchStatus.results
2535
+ );
2536
+ setSearchStatus({ ...searchStatus, selectedIndex: newIndex });
2537
+ emitResultsChanged(searchResults, newIndex);
2538
+ navigateToIndex(searchResults, newIndex);
2539
+ }, [emitResultsChanged, navigateToIndex, searchResults, searchStatus]);
2540
+ useEffect5(() => {
2541
+ if (controlledSearchResults === void 0) return;
2542
+ if (controlledSearchResults.length > 0) {
2543
+ setSearchStatus((current) => ({
2544
+ rowsSearched: rowCount,
2545
+ results: controlledSearchResults.length,
2546
+ selectedIndex: current?.selectedIndex ?? -1
2547
+ }));
2548
+ } else {
2549
+ setSearchStatus(void 0);
2550
+ }
2551
+ }, [controlledSearchResults, rowCount]);
2552
+ useEffect5(() => {
2553
+ if (!enabled) return;
2554
+ setSearchStatus(void 0);
2555
+ setInternalResults([]);
2556
+ emitResultsChanged([], -1);
2557
+ if (showSearch) {
2558
+ queueMicrotask(() => {
2559
+ searchInputRef.current?.focus({ preventScroll: true });
2560
+ });
2561
+ } else {
2562
+ cancelSearch();
2563
+ }
2564
+ }, [enabled, showSearch]);
2565
+ useEffect5(() => {
2566
+ if (!enabled || !showSearch) return;
2567
+ if (controlledSearchResults !== void 0) return;
2568
+ if (searchValue.trim() === "") {
2569
+ setSearchStatus(void 0);
2570
+ setInternalResults([]);
2571
+ cancelSearch();
2572
+ emitResultsChanged([], -1);
2573
+ return;
2574
+ }
2575
+ beginSearch(searchValue);
2576
+ }, [
2577
+ beginSearch,
2578
+ cancelSearch,
2579
+ controlledSearchResults,
2580
+ emitResultsChanged,
2581
+ enabled,
2582
+ searchValue,
2583
+ showSearch
2584
+ ]);
2585
+ useEffect5(() => {
2586
+ if (!enabled) return;
2587
+ const handleKeyDown = (event) => {
2588
+ if (!(event.ctrlKey || event.metaKey)) return;
2589
+ if (event.key.toLowerCase() !== "f") return;
2590
+ const root = rootRef?.current;
2591
+ if (root) {
2592
+ const active = document.activeElement;
2593
+ const focusInside = active === root || active instanceof Node && root.contains(active);
2594
+ if (!focusInside && active !== document.body) {
2595
+ return;
2596
+ }
2597
+ }
2598
+ event.preventDefault();
2599
+ event.stopPropagation();
2600
+ if (showSearch) {
2601
+ searchInputRef.current?.focus({ preventScroll: true });
2602
+ searchInputRef.current?.select();
2603
+ return;
2604
+ }
2605
+ if (controlledShowSearch === void 0) {
2606
+ setInternalShowSearch(true);
2607
+ }
2608
+ };
2609
+ window.addEventListener("keydown", handleKeyDown, true);
2610
+ return () => window.removeEventListener("keydown", handleKeyDown, true);
2611
+ }, [controlledShowSearch, enabled, rootRef, showSearch]);
2612
+ useEffect5(() => () => cancelSearch(), [cancelSearch]);
2613
+ const searchMatchKeys = useMemo2(
2614
+ () => buildSearchMatchKeys(searchResults),
2615
+ [searchResults]
2616
+ );
2617
+ const activeMatch = useMemo2(() => {
2618
+ if (!searchStatus || searchStatus.selectedIndex < 0) return null;
2619
+ return searchResults[searchStatus.selectedIndex] ?? null;
2620
+ }, [searchResults, searchStatus]);
2621
+ if (!enabled) {
2622
+ return {
2623
+ enabled: false,
2624
+ showSearch: false,
2625
+ searchValue: "",
2626
+ searchResults: [],
2627
+ searchStatus: void 0,
2628
+ searchMatchKeys: EMPTY_MATCH_KEYS,
2629
+ activeMatch: null,
2630
+ searchInputRef,
2631
+ searchInputId,
2632
+ canClose: false,
2633
+ openSearch,
2634
+ closeSearch,
2635
+ setSearchValue,
2636
+ goToNext,
2637
+ goToPrevious
2638
+ };
2639
+ }
2640
+ return {
2641
+ enabled: true,
2642
+ showSearch,
2643
+ searchValue,
2644
+ searchResults,
2645
+ searchStatus,
2646
+ searchMatchKeys,
2647
+ activeMatch,
2648
+ searchInputRef,
2649
+ searchInputId,
2650
+ canClose: controlledShowSearch === void 0 || onSearchClose !== void 0,
2651
+ openSearch,
2652
+ closeSearch,
2653
+ setSearchValue,
2654
+ goToNext,
2655
+ goToPrevious
2656
+ };
2657
+ }
2658
+
1893
2659
  // src/components/ui/table/features/row-selection/rowSelection.ts
1894
2660
  function resolveRowSelection(mode, controlledSelection, internalSelection) {
1895
2661
  if (mode === "none") return {};
@@ -1912,7 +2678,12 @@ var DEFAULT_DATA_TABLE_LABELS = {
1912
2678
  selection: (selectedCount) => selectedCount > 0 ? `\u2713 ${selectedCount} selected` : null,
1913
2679
  expandRow: "Expand row",
1914
2680
  collapseRow: "Collapse row",
1915
- resizeColumn: "Resize column"
2681
+ resizeColumn: "Resize column",
2682
+ searchPlaceholder: "Search\u2026",
2683
+ searchResultHint: "Type to search",
2684
+ searchPrevious: "Previous result",
2685
+ searchNext: "Next result",
2686
+ searchClose: "Close search"
1916
2687
  };
1917
2688
  function resolveDataTableLabels(partial) {
1918
2689
  return {
@@ -1923,6 +2694,7 @@ function resolveDataTableLabels(partial) {
1923
2694
 
1924
2695
  // src/core/useGlideTable.ts
1925
2696
  var EMPTY_COLUMN_FREEZE_OFFSETS = /* @__PURE__ */ new Map();
2697
+ var EMPTY_SEARCH_MATCH_KEYS = /* @__PURE__ */ new Set();
1926
2698
  function useGlideTable(options) {
1927
2699
  const {
1928
2700
  data,
@@ -1963,9 +2735,16 @@ function useGlideTable(options) {
1963
2735
  columnSizing: controlledColumnSizing,
1964
2736
  onColumnSizingChange,
1965
2737
  columnResizeMode = "onChange",
1966
- enableColumnFreeze = false
2738
+ enableColumnFreeze = false,
2739
+ enableInlineSearch = false,
2740
+ showSearch,
2741
+ searchValue,
2742
+ onSearchValueChange,
2743
+ onSearchClose,
2744
+ searchResults,
2745
+ onSearchResultsChanged
1967
2746
  } = options;
1968
- const labels = useMemo2(() => {
2747
+ const labels = useMemo3(() => {
1969
2748
  const resolved = resolveDataTableLabels(labelsProp);
1970
2749
  return {
1971
2750
  ...resolved,
@@ -1976,15 +2755,16 @@ function useGlideTable(options) {
1976
2755
  }, [labelsProp, emptyText, loadingText, selectionLabel]);
1977
2756
  const enableExpand = Boolean(toggleField);
1978
2757
  const resolvedEnableSubtreeCopy = enableSubtreeCopy ?? enableExpand;
1979
- const [internalRowSelection, setInternalRowSelection] = useState3({});
1980
- const [internalColumnSizing, setInternalColumnSizing] = useState3({});
1981
- const [internalExpandedRows, setInternalExpandedRows] = useState3(
2758
+ const [internalRowSelection, setInternalRowSelection] = useState4({});
2759
+ const [internalColumnSizing, setInternalColumnSizing] = useState4({});
2760
+ const [internalExpandedRows, setInternalExpandedRows] = useState4(
1982
2761
  () => /* @__PURE__ */ new Set()
1983
2762
  );
1984
- const [hoveredRowIndex, setHoveredRowIndex] = useState3(null);
1985
- const scrollRef = useRef5(null);
2763
+ const [hoveredRowIndex, setHoveredRowIndex] = useState4(null);
2764
+ const scrollRef = useRef6(null);
2765
+ const rootRef = useRef6(null);
1986
2766
  const shouldVirtualize = enableVirtualization && !enableRowSpan;
1987
- useEffect5(() => {
2767
+ useEffect6(() => {
1988
2768
  if (enableVirtualization && enableRowSpan) {
1989
2769
  console.warn(
1990
2770
  "[useGlideTable] enableRowSpan is on; virtualization is disabled to preserve cell merges."
@@ -1998,7 +2778,7 @@ function useGlideTable(options) {
1998
2778
  );
1999
2779
  const columnSizing = controlledColumnSizing ?? internalColumnSizing;
2000
2780
  const expandedRows = controlledExpandedRows ?? internalExpandedRows;
2001
- const handleExpandedRowsChange = useCallback3(
2781
+ const handleExpandedRowsChange = useCallback4(
2002
2782
  (next) => {
2003
2783
  if (onExpandedRowsChange) {
2004
2784
  onExpandedRowsChange(next);
@@ -2059,13 +2839,13 @@ function useGlideTable(options) {
2059
2839
  getCoreRowModel: getCoreRowModel(),
2060
2840
  getRowId: getRowId ? (originalRow, index) => getRowId(originalRow, index) : (_originalRow, index) => String(index)
2061
2841
  });
2062
- const rowSpanColumnKeys = useMemo2(() => {
2842
+ const rowSpanColumnKeys = useMemo3(() => {
2063
2843
  if (!enableRowSpan) return [];
2064
2844
  return collectRowSpanColumns(columns);
2065
2845
  }, [enableRowSpan, columns]);
2066
2846
  const primaryRowSpanKey = rowSpanColumnKeys[0]?.rowSpanKey;
2067
2847
  const primaryRowSpanColumnId = rowSpanColumnKeys[0]?.columnId;
2068
- const columnRowSpanMap = useMemo2(
2848
+ const columnRowSpanMap = useMemo3(
2069
2849
  () => buildColumnRowSpanMap(tableData, rowSpanColumnKeys),
2070
2850
  [tableData, rowSpanColumnKeys]
2071
2851
  );
@@ -2074,7 +2854,7 @@ function useGlideTable(options) {
2074
2854
  const rows = table.getRowModel().rows;
2075
2855
  const columnCount = table.getAllLeafColumns().length || 1;
2076
2856
  const visibleLeafColumns = table.getVisibleLeafColumns();
2077
- const columnFreezeOffsets = useMemo2(() => {
2857
+ const columnFreezeOffsets = useMemo3(() => {
2078
2858
  if (!enableColumnFreeze) return EMPTY_COLUMN_FREEZE_OFFSETS;
2079
2859
  return buildColumnFreezeOffsets(
2080
2860
  visibleLeafColumns.map((column) => ({
@@ -2094,13 +2874,46 @@ function useGlideTable(options) {
2094
2874
  const totalSize = rowVirtualizer.getTotalSize();
2095
2875
  const paddingTop = virtualRows.length > 0 ? virtualRows[0]?.start ?? 0 : 0;
2096
2876
  const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows[virtualRows.length - 1]?.end ?? 0) : 0;
2097
- const selectedRowIndices = useMemo2(() => {
2877
+ const selectedRowIndices = useMemo3(() => {
2098
2878
  const indices = /* @__PURE__ */ new Set();
2099
2879
  for (const selectedRow of selectedRows) {
2100
2880
  indices.add(selectedRow.index);
2101
2881
  }
2102
2882
  return indices;
2103
2883
  }, [selectedRows]);
2884
+ const scrollCellIntoView = useCallback4(
2885
+ (rowIndex, colIndex, options2) => {
2886
+ const align = options2?.align ?? "nearest";
2887
+ const blockAlign = align === "center" ? "center" : "nearest";
2888
+ if (shouldVirtualize) {
2889
+ rowVirtualizer.scrollToIndex(rowIndex, {
2890
+ align: align === "nearest" ? "auto" : align
2891
+ });
2892
+ }
2893
+ const scrollElement = scrollRef.current;
2894
+ if (!scrollElement) return;
2895
+ const scrollToMatchedCell = () => {
2896
+ const cell = scrollElement.querySelector(
2897
+ `[data-row-index="${rowIndex}"][data-col-index="${colIndex}"]`
2898
+ );
2899
+ if (cell instanceof HTMLElement) {
2900
+ cell.scrollIntoView({ block: blockAlign, inline: "nearest" });
2901
+ }
2902
+ };
2903
+ if (shouldVirtualize) {
2904
+ requestAnimationFrame(scrollToMatchedCell);
2905
+ return;
2906
+ }
2907
+ scrollToMatchedCell();
2908
+ },
2909
+ [rowVirtualizer, shouldVirtualize]
2910
+ );
2911
+ const handleCellNavigate = useCallback4(
2912
+ (position) => {
2913
+ scrollCellIntoView(position.row, position.col, { align: "nearest" });
2914
+ },
2915
+ [scrollCellIntoView]
2916
+ );
2104
2917
  const {
2105
2918
  dragState,
2106
2919
  activeSelectionBounds,
@@ -2112,11 +2925,13 @@ function useGlideTable(options) {
2112
2925
  data: tableData,
2113
2926
  rows,
2114
2927
  enabled: enableCellSelection,
2928
+ columnCount: visibleLeafColumns.length,
2115
2929
  enableSubtreeCopy: resolvedEnableSubtreeCopy,
2116
2930
  enableInsertPaste: enableInsertPaste ?? true,
2117
2931
  onDataChange,
2118
2932
  onBatchChange,
2119
- onRowsPaste
2933
+ onRowsPaste,
2934
+ onCellNavigate: handleCellNavigate
2120
2935
  });
2121
2936
  const {
2122
2937
  editingCell,
@@ -2126,23 +2941,193 @@ function useGlideTable(options) {
2126
2941
  commitEdit,
2127
2942
  cancelEdit
2128
2943
  } = useCellEdit({ data: tableData, rows, onDataChange, onCellChange });
2129
- const handleCellMouseDownWithCommit = useCallback3(
2130
- (rowIndex, colIndex) => {
2944
+ const handleCellMouseDownWithCommit = useCallback4(
2945
+ (rowIndex, colIndex, options2) => {
2131
2946
  const isSameEditingCell = editingCell?.rowIndex === rowIndex && editingCell?.colIndex === colIndex;
2132
2947
  if (editingCell && !isSameEditingCell && !commitEdit()) {
2133
2948
  return;
2134
2949
  }
2135
- handleCellMouseDown(rowIndex, colIndex);
2950
+ handleCellMouseDown(rowIndex, colIndex, options2);
2136
2951
  },
2137
2952
  [commitEdit, editingCell, handleCellMouseDown]
2138
2953
  );
2139
- const clearHover = useCallback3(() => {
2954
+ const navigateToSearchResult = useCallback4(
2955
+ (item) => {
2956
+ const [colIndex, rowIndex] = item;
2957
+ handleCellMouseDownWithCommit(rowIndex, colIndex);
2958
+ scrollCellIntoView(rowIndex, colIndex, { align: "center" });
2959
+ },
2960
+ [handleCellMouseDownWithCommit, scrollCellIntoView]
2961
+ );
2962
+ const resolveSearchRowId = useCallback4(
2963
+ (row, index) => {
2964
+ if (getRowId) return getRowId(row, index);
2965
+ if (enableExpand) {
2966
+ const record = row;
2967
+ const idValue = record.id;
2968
+ if (idValue != null && String(idValue).length > 0) {
2969
+ return String(idValue);
2970
+ }
2971
+ const uniqueId = record.uniqueId;
2972
+ if (uniqueId != null && String(uniqueId).length > 0) {
2973
+ return String(uniqueId);
2974
+ }
2975
+ if (toggleField) {
2976
+ const toggleValue = record[toggleField];
2977
+ if (toggleValue != null && String(toggleValue).length > 0) {
2978
+ return String(toggleValue);
2979
+ }
2980
+ }
2981
+ }
2982
+ return String(index);
2983
+ },
2984
+ [enableExpand, getRowId, toggleField]
2985
+ );
2986
+ const searchCorpus = useMemo3(() => {
2987
+ if (!enableInlineSearch) return [];
2988
+ if (enableExpand && toggleField) {
2989
+ return buildTreeSearchCorpus(tableData, {
2990
+ toggleField,
2991
+ getRowId: resolveSearchRowId
2992
+ });
2993
+ }
2994
+ return buildFlatSearchCorpus(tableData, resolveSearchRowId);
2995
+ }, [
2996
+ enableExpand,
2997
+ enableInlineSearch,
2998
+ resolveSearchRowId,
2999
+ tableData,
3000
+ toggleField
3001
+ ]);
3002
+ const searchCorpusRef = useRef6(searchCorpus);
3003
+ searchCorpusRef.current = searchCorpus;
3004
+ const visibleRowIndexById = useMemo3(() => {
3005
+ const map = /* @__PURE__ */ new Map();
3006
+ for (const row of rows) {
3007
+ map.set(resolveSearchRowId(row.original, row.index), row.index);
3008
+ }
3009
+ return map;
3010
+ }, [resolveSearchRowId, rows]);
3011
+ const getSearchCellValue = useCallback4(
3012
+ (rowIndex, colIndex) => {
3013
+ const corpusRow = searchCorpusRef.current[rowIndex];
3014
+ const column = visibleLeafColumns[colIndex];
3015
+ if (!corpusRow || !column) return void 0;
3016
+ const visibleIndex = visibleRowIndexById.get(corpusRow.id);
3017
+ if (visibleIndex !== void 0) {
3018
+ const visibleRow = rows[visibleIndex];
3019
+ if (visibleRow) {
3020
+ return visibleRow.getValue(column.id);
3021
+ }
3022
+ }
3023
+ const columnDef = column.columnDef;
3024
+ if ("accessorFn" in columnDef && typeof columnDef.accessorFn === "function") {
3025
+ return columnDef.accessorFn(corpusRow.data, rowIndex);
3026
+ }
3027
+ if ("accessorKey" in columnDef && columnDef.accessorKey != null && columnDef.accessorKey !== "") {
3028
+ return corpusRow.data[String(columnDef.accessorKey)];
3029
+ }
3030
+ return corpusRow.data[column.id];
3031
+ },
3032
+ [rows, visibleLeafColumns, visibleRowIndexById]
3033
+ );
3034
+ const pendingSearchNavRef = useRef6(null);
3035
+ const focusSearchResult = useCallback4(
3036
+ (colIndex, visibleRowIndex) => {
3037
+ navigateToSearchResult([colIndex, visibleRowIndex]);
3038
+ },
3039
+ [navigateToSearchResult]
3040
+ );
3041
+ const navigateToCorpusSearchResult = useCallback4(
3042
+ (item) => {
3043
+ const [colIndex, corpusRowIndex] = item;
3044
+ const corpusRow = searchCorpusRef.current[corpusRowIndex];
3045
+ if (!corpusRow) return;
3046
+ const missingKeys = collectAncestorKeysToExpand(corpusRow, expandedRows);
3047
+ if (missingKeys.length > 0) {
3048
+ pendingSearchNavRef.current = {
3049
+ colIndex,
3050
+ rowId: corpusRow.id
3051
+ };
3052
+ const next = new Set(expandedRows);
3053
+ for (const key of corpusRow.ancestorToggleKeys) {
3054
+ next.add(key);
3055
+ }
3056
+ handleExpandedRowsChange(next);
3057
+ return;
3058
+ }
3059
+ const visibleItem = mapSearchResultToVisibleItem(
3060
+ item,
3061
+ searchCorpusRef.current,
3062
+ visibleRowIndexById
3063
+ );
3064
+ if (!visibleItem) return;
3065
+ focusSearchResult(visibleItem[0], visibleItem[1]);
3066
+ },
3067
+ [
3068
+ expandedRows,
3069
+ focusSearchResult,
3070
+ handleExpandedRowsChange,
3071
+ visibleRowIndexById
3072
+ ]
3073
+ );
3074
+ useEffect6(() => {
3075
+ const pending = pendingSearchNavRef.current;
3076
+ if (!pending) return;
3077
+ const visibleRowIndex = visibleRowIndexById.get(pending.rowId);
3078
+ if (visibleRowIndex === void 0) return;
3079
+ pendingSearchNavRef.current = null;
3080
+ focusSearchResult(pending.colIndex, visibleRowIndex);
3081
+ }, [focusSearchResult, rows, visibleRowIndexById]);
3082
+ const initialSearchStartRow = virtualRows[0]?.index ?? 0;
3083
+ const inlineSearch = useInlineSearch({
3084
+ enabled: enableInlineSearch,
3085
+ rowCount: searchCorpus.length,
3086
+ columnCount: visibleLeafColumns.length,
3087
+ getCellValue: getSearchCellValue,
3088
+ initialStartRow: initialSearchStartRow,
3089
+ showSearch,
3090
+ searchValue,
3091
+ searchResults,
3092
+ onSearchValueChange,
3093
+ onSearchClose,
3094
+ onSearchResultsChanged,
3095
+ onNavigateToResult: navigateToCorpusSearchResult,
3096
+ rootRef
3097
+ });
3098
+ const visibleSearchMatchKeys = useMemo3(() => {
3099
+ if (!enableInlineSearch) return EMPTY_SEARCH_MATCH_KEYS;
3100
+ return mapSearchResultsToVisibleKeys(
3101
+ inlineSearch.searchResults,
3102
+ searchCorpus,
3103
+ visibleRowIndexById
3104
+ );
3105
+ }, [
3106
+ enableInlineSearch,
3107
+ inlineSearch.searchResults,
3108
+ searchCorpus,
3109
+ visibleRowIndexById
3110
+ ]);
3111
+ const visibleActiveMatch = useMemo3(() => {
3112
+ if (!enableInlineSearch || !inlineSearch.activeMatch) return null;
3113
+ return mapSearchResultToVisibleItem(
3114
+ inlineSearch.activeMatch,
3115
+ searchCorpus,
3116
+ visibleRowIndexById
3117
+ );
3118
+ }, [
3119
+ enableInlineSearch,
3120
+ inlineSearch.activeMatch,
3121
+ searchCorpus,
3122
+ visibleRowIndexById
3123
+ ]);
3124
+ const clearHover = useCallback4(() => {
2140
3125
  setHoveredRowIndex(null);
2141
3126
  }, []);
2142
- const handleRowHover = useCallback3((rowIndex, _rowData) => {
3127
+ const handleRowHover = useCallback4((rowIndex, _rowData) => {
2143
3128
  setHoveredRowIndex(rowIndex);
2144
3129
  }, []);
2145
- const handleToggleSelect = useCallback3(
3130
+ const handleToggleSelect = useCallback4(
2146
3131
  (row) => {
2147
3132
  if (!row.getCanSelect()) return;
2148
3133
  if (preserveRowSelection && row.getIsSelected()) {
@@ -2152,14 +3137,14 @@ function useGlideTable(options) {
2152
3137
  },
2153
3138
  [preserveRowSelection]
2154
3139
  );
2155
- const handleToggleExpand = useCallback3(
3140
+ const handleToggleExpand = useCallback4(
2156
3141
  (rowKey) => {
2157
3142
  if (preventExpand) return;
2158
3143
  handleExpandedRowsChange(toggleExpandedRowId(rowKey, expandedRows));
2159
3144
  },
2160
3145
  [preventExpand, handleExpandedRowsChange, expandedRows]
2161
3146
  );
2162
- const rowContextValue = useMemo2(() => {
3147
+ const rowContextValue = useMemo3(() => {
2163
3148
  return {
2164
3149
  rowSpan: {
2165
3150
  enableRowSpan,
@@ -2207,6 +3192,11 @@ function useGlideTable(options) {
2207
3192
  columnFreeze: {
2208
3193
  enableColumnFreeze,
2209
3194
  offsets: columnFreezeOffsets
3195
+ },
3196
+ inlineSearch: {
3197
+ enabled: enableInlineSearch,
3198
+ matchKeys: visibleSearchMatchKeys,
3199
+ activeMatch: visibleActiveMatch
2210
3200
  }
2211
3201
  };
2212
3202
  }, [
@@ -2242,14 +3232,17 @@ function useGlideTable(options) {
2242
3232
  labels.collapseRow,
2243
3233
  enableColumnResize,
2244
3234
  enableColumnFreeze,
2245
- columnFreezeOffsets
3235
+ columnFreezeOffsets,
3236
+ enableInlineSearch,
3237
+ visibleSearchMatchKeys,
3238
+ visibleActiveMatch
2246
3239
  ]);
2247
- const copySelectionRef = useRef5(copySelection);
2248
- useEffect5(() => {
3240
+ const copySelectionRef = useRef6(copySelection);
3241
+ useEffect6(() => {
2249
3242
  copySelectionRef.current = copySelection;
2250
3243
  }, [copySelection]);
2251
- const stableCopySelection = useCallback3((options2) => copySelectionRef.current(options2), []);
2252
- useEffect5(() => {
3244
+ const stableCopySelection = useCallback4((options2) => copySelectionRef.current(options2), []);
3245
+ useEffect6(() => {
2253
3246
  onCopyActionsReady?.({ copySelection: stableCopySelection });
2254
3247
  }, [onCopyActionsReady, stableCopySelection]);
2255
3248
  return {
@@ -2265,8 +3258,10 @@ function useGlideTable(options) {
2265
3258
  enableCellSelection,
2266
3259
  enableColumnResize,
2267
3260
  enableColumnFreeze,
3261
+ enableInlineSearch,
2268
3262
  shouldVirtualize,
2269
3263
  scrollRef,
3264
+ rootRef,
2270
3265
  rowVirtualizer,
2271
3266
  virtualRows,
2272
3267
  paddingTop,
@@ -2274,25 +3269,39 @@ function useGlideTable(options) {
2274
3269
  rowContextValue,
2275
3270
  handleToggleSelect,
2276
3271
  clearHover,
2277
- copySelection: stableCopySelection
3272
+ copySelection: stableCopySelection,
3273
+ inlineSearch: {
3274
+ showSearch: inlineSearch.showSearch,
3275
+ searchValue: inlineSearch.searchValue,
3276
+ searchStatus: inlineSearch.searchStatus,
3277
+ searchInputRef: inlineSearch.searchInputRef,
3278
+ searchInputId: inlineSearch.searchInputId,
3279
+ canClose: inlineSearch.canClose,
3280
+ searchRowCount: searchCorpus.length,
3281
+ setSearchValue: inlineSearch.setSearchValue,
3282
+ closeSearch: inlineSearch.closeSearch,
3283
+ goToNext: inlineSearch.goToNext,
3284
+ goToPrevious: inlineSearch.goToPrevious,
3285
+ openSearch: inlineSearch.openSearch
3286
+ }
2278
3287
  };
2279
3288
  }
2280
3289
 
2281
3290
  // src/components/ui/table/components/DataTable/DataTable.tsx
2282
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3291
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2283
3292
  function DefaultScroll({
2284
3293
  scrollRef,
2285
3294
  children,
2286
3295
  className
2287
3296
  }) {
2288
- return /* @__PURE__ */ jsx5("div", { ref: scrollRef, className: cn("data-table-scroll", className), children });
3297
+ return /* @__PURE__ */ jsx6("div", { ref: scrollRef, className: cn("data-table-scroll", className), children });
2289
3298
  }
2290
3299
  function DefaultPending({
2291
3300
  loadingText,
2292
3301
  className,
2293
3302
  classNames
2294
3303
  }) {
2295
- return /* @__PURE__ */ jsx5(
3304
+ return /* @__PURE__ */ jsx6(
2296
3305
  "div",
2297
3306
  {
2298
3307
  className: cn(
@@ -2302,7 +3311,7 @@ function DefaultPending({
2302
3311
  classNames?.pending,
2303
3312
  className
2304
3313
  ),
2305
- children: /* @__PURE__ */ jsx5("span", { className: cn("data-table-loading-text", classNames?.loadingText), children: loadingText })
3314
+ children: /* @__PURE__ */ jsx6("span", { className: cn("data-table-loading-text", classNames?.loadingText), children: loadingText })
2306
3315
  }
2307
3316
  );
2308
3317
  }
@@ -2311,7 +3320,7 @@ function DefaultEmpty({
2311
3320
  columnCount,
2312
3321
  classNames
2313
3322
  }) {
2314
- return /* @__PURE__ */ jsx5("tr", { children: /* @__PURE__ */ jsx5(
3323
+ return /* @__PURE__ */ jsx6("tr", { children: /* @__PURE__ */ jsx6(
2315
3324
  "td",
2316
3325
  {
2317
3326
  colSpan: columnCount,
@@ -2344,15 +3353,18 @@ function DataTable({
2344
3353
  enableCellSelection,
2345
3354
  enableColumnResize,
2346
3355
  enableColumnFreeze,
3356
+ enableInlineSearch,
2347
3357
  shouldVirtualize,
2348
3358
  scrollRef,
3359
+ rootRef,
2349
3360
  rowVirtualizer,
2350
3361
  virtualRows,
2351
3362
  paddingTop,
2352
3363
  paddingBottom,
2353
3364
  rowContextValue,
2354
3365
  handleToggleSelect,
2355
- clearHover
3366
+ clearHover,
3367
+ inlineSearch
2356
3368
  } = useGlideTable(glideOptions);
2357
3369
  const ToolbarSlot = slots?.Toolbar ?? DataTableToolbar;
2358
3370
  const ScrollSlot = slots?.Scroll ?? DefaultScroll;
@@ -2360,12 +3372,13 @@ function DataTable({
2360
3372
  const PendingSlot = slots?.Pending ?? DefaultPending;
2361
3373
  const EmptySlot = slots?.Empty ?? DefaultEmpty;
2362
3374
  const freezeOffsets = rowContextValue.columnFreeze.offsets;
2363
- const contextValue = useMemo3(
3375
+ const headerGroups = getMergedHeaderGroups(table.getHeaderGroups());
3376
+ const contextValue = useMemo4(
2364
3377
  () => ({ ...rowContextValue, classNames }),
2365
3378
  [rowContextValue, classNames]
2366
3379
  );
2367
3380
  if (isPending) {
2368
- return /* @__PURE__ */ jsx5(
3381
+ return /* @__PURE__ */ jsx6(
2369
3382
  PendingSlot,
2370
3383
  {
2371
3384
  loadingText,
@@ -2374,19 +3387,21 @@ function DataTable({
2374
3387
  }
2375
3388
  );
2376
3389
  }
2377
- return /* @__PURE__ */ jsxs4(
3390
+ return /* @__PURE__ */ jsxs5(
2378
3391
  "div",
2379
3392
  {
3393
+ ref: rootRef,
2380
3394
  className: cn(
2381
3395
  "DataTableJSX",
2382
3396
  !enableCellSelection && "DataTableJSX--no-cell-selection",
2383
3397
  enableColumnResize && "DataTableJSX--column-resize",
2384
3398
  enableColumnFreeze && "DataTableJSX--column-freeze",
3399
+ enableInlineSearch && "DataTableJSX--inline-search",
2385
3400
  classNames?.root,
2386
3401
  className
2387
3402
  ),
2388
3403
  children: [
2389
- /* @__PURE__ */ jsx5(
3404
+ /* @__PURE__ */ jsx6(
2390
3405
  ToolbarSlot,
2391
3406
  {
2392
3407
  filteredCount: filteredCount ?? tableData.length,
@@ -2398,14 +3413,36 @@ function DataTable({
2398
3413
  classNames
2399
3414
  }
2400
3415
  ),
2401
- /* @__PURE__ */ jsx5(ScrollSlot, { scrollRef, className: classNames?.scroll, children: /* @__PURE__ */ jsxs4(
3416
+ enableInlineSearch ? /* @__PURE__ */ jsx6(
3417
+ DataTableSearch,
3418
+ {
3419
+ showSearch: inlineSearch.showSearch,
3420
+ searchValue: inlineSearch.searchValue,
3421
+ searchStatus: inlineSearch.searchStatus,
3422
+ searchInputId: inlineSearch.searchInputId,
3423
+ searchInputRef: inlineSearch.searchInputRef,
3424
+ canClose: inlineSearch.canClose,
3425
+ placeholder: labels.searchPlaceholder,
3426
+ resultHint: labels.searchResultHint,
3427
+ previousLabel: labels.searchPrevious,
3428
+ nextLabel: labels.searchNext,
3429
+ closeLabel: labels.searchClose,
3430
+ rowsTotal: inlineSearch.searchRowCount,
3431
+ classNames,
3432
+ onSearchValueChange: inlineSearch.setSearchValue,
3433
+ onClose: inlineSearch.closeSearch,
3434
+ onNext: inlineSearch.goToNext,
3435
+ onPrevious: inlineSearch.goToPrevious
3436
+ }
3437
+ ) : null,
3438
+ /* @__PURE__ */ jsx6(ScrollSlot, { scrollRef, className: classNames?.scroll, children: /* @__PURE__ */ jsxs5(
2402
3439
  "table",
2403
3440
  {
2404
3441
  className: cn("data-table", classNames?.table),
2405
3442
  style: enableColumnResize ? { width: table.getTotalSize() } : void 0,
2406
3443
  onDragStart: enableCellSelection ? (event) => event.preventDefault() : void 0,
2407
3444
  children: [
2408
- /* @__PURE__ */ jsx5("thead", { className: cn("data-table-head", classNames?.head), children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx5(
3445
+ /* @__PURE__ */ jsx6("thead", { className: cn("data-table-head", classNames?.head), children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx6(
2409
3446
  "tr",
2410
3447
  {
2411
3448
  className: cn("data-table-head-row", classNames?.headRow),
@@ -2419,15 +3456,18 @@ function DataTable({
2419
3456
  });
2420
3457
  const freezeOffset = enableColumnFreeze ? freezeOffsets.get(header.column.id) : void 0;
2421
3458
  const freezeStyle = getColumnFreezeStyle(freezeOffset, {
2422
- isHeader: true
3459
+ isHeader: true,
3460
+ headerTop: header.depth * DATA_TABLE_HEADER_ROW_HEIGHT
2423
3461
  });
2424
3462
  const headerStyle = {
2425
3463
  ...sizeStyle,
2426
3464
  ...freezeStyle
2427
3465
  };
2428
- return /* @__PURE__ */ jsxs4(
3466
+ return /* @__PURE__ */ jsxs5(
2429
3467
  "th",
2430
3468
  {
3469
+ colSpan: header.colSpan,
3470
+ rowSpan: header.mergedRowSpan,
2431
3471
  "data-resizing": header.column.getIsResizing() ? "" : void 0,
2432
3472
  "data-frozen": freezeOffset?.side,
2433
3473
  "data-freeze-edge": getColumnFreezeEdgeAttr(freezeOffset),
@@ -2441,7 +3481,7 @@ function DataTable({
2441
3481
  ),
2442
3482
  children: [
2443
3483
  header.isPlaceholder ? null : flexRender2(header.column.columnDef.header, header.getContext()),
2444
- canResize ? /* @__PURE__ */ jsx5(
3484
+ canResize ? /* @__PURE__ */ jsx6(
2445
3485
  "div",
2446
3486
  {
2447
3487
  role: "separator",
@@ -2467,20 +3507,20 @@ function DataTable({
2467
3507
  },
2468
3508
  headerGroup.id
2469
3509
  )) }),
2470
- /* @__PURE__ */ jsx5(DataTableContextProvider, { value: contextValue, children: /* @__PURE__ */ jsx5(
3510
+ /* @__PURE__ */ jsx6(DataTableContextProvider, { value: contextValue, children: /* @__PURE__ */ jsx6(
2471
3511
  "tbody",
2472
3512
  {
2473
3513
  onMouseLeave: clearHover,
2474
3514
  className: cn("data-table-body", classNames?.body),
2475
- children: rows.length === 0 ? /* @__PURE__ */ jsx5(
3515
+ children: rows.length === 0 ? /* @__PURE__ */ jsx6(
2476
3516
  EmptySlot,
2477
3517
  {
2478
3518
  emptyText,
2479
3519
  columnCount,
2480
3520
  classNames
2481
3521
  }
2482
- ) : shouldVirtualize ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
2483
- paddingTop > 0 && /* @__PURE__ */ jsx5(
3522
+ ) : shouldVirtualize ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3523
+ paddingTop > 0 && /* @__PURE__ */ jsx6(
2484
3524
  "tr",
2485
3525
  {
2486
3526
  "aria-hidden": true,
@@ -2488,7 +3528,7 @@ function DataTable({
2488
3528
  "data-table-virtual-spacer",
2489
3529
  classNames?.virtualSpacer
2490
3530
  ),
2491
- children: /* @__PURE__ */ jsx5(
3531
+ children: /* @__PURE__ */ jsx6(
2492
3532
  "td",
2493
3533
  {
2494
3534
  colSpan: columnCount,
@@ -2504,7 +3544,7 @@ function DataTable({
2504
3544
  virtualRows.map((virtualRow) => {
2505
3545
  const row = rows[virtualRow.index];
2506
3546
  if (!row) return null;
2507
- return /* @__PURE__ */ jsx5(
3547
+ return /* @__PURE__ */ jsx6(
2508
3548
  RowSlot,
2509
3549
  {
2510
3550
  row,
@@ -2515,7 +3555,7 @@ function DataTable({
2515
3555
  row.id
2516
3556
  );
2517
3557
  }),
2518
- paddingBottom > 0 && /* @__PURE__ */ jsx5(
3558
+ paddingBottom > 0 && /* @__PURE__ */ jsx6(
2519
3559
  "tr",
2520
3560
  {
2521
3561
  "aria-hidden": true,
@@ -2523,7 +3563,7 @@ function DataTable({
2523
3563
  "data-table-virtual-spacer",
2524
3564
  classNames?.virtualSpacer
2525
3565
  ),
2526
- children: /* @__PURE__ */ jsx5(
3566
+ children: /* @__PURE__ */ jsx6(
2527
3567
  "td",
2528
3568
  {
2529
3569
  colSpan: columnCount,
@@ -2536,7 +3576,7 @@ function DataTable({
2536
3576
  )
2537
3577
  }
2538
3578
  )
2539
- ] }) : rows.map((row) => /* @__PURE__ */ jsx5(
3579
+ ] }) : rows.map((row) => /* @__PURE__ */ jsx6(
2540
3580
  RowSlot,
2541
3581
  {
2542
3582
  row,
@@ -2555,10 +3595,10 @@ function DataTable({
2555
3595
  }
2556
3596
 
2557
3597
  // src/components/ui/table/components/Table/Table.tsx
2558
- import { useCallback as useCallback4, useMemo as useMemo4, useState as useState4 } from "react";
3598
+ import { useCallback as useCallback5, useMemo as useMemo5, useState as useState5 } from "react";
2559
3599
 
2560
3600
  // src/components/ui/table/components/Table/buildColumnDef.tsx
2561
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3601
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2562
3602
  function SortableHeader({
2563
3603
  label,
2564
3604
  field,
@@ -2567,15 +3607,15 @@ function SortableHeader({
2567
3607
  }) {
2568
3608
  const isActive = sort?.field === field;
2569
3609
  const Icon = isActive ? sort.direction === "asc" ? ArrowUp : ArrowDown : ArrowUpDown;
2570
- return /* @__PURE__ */ jsxs5(
3610
+ return /* @__PURE__ */ jsxs6(
2571
3611
  "button",
2572
3612
  {
2573
3613
  type: "button",
2574
3614
  className: cn("SortableHeaderJSX", isActive ? "is-active" : "is-inactive"),
2575
3615
  onClick: () => onSort(field),
2576
3616
  children: [
2577
- /* @__PURE__ */ jsx6("span", { children: label }),
2578
- /* @__PURE__ */ jsx6(Icon, { className: "sortable-header-icon" })
3617
+ /* @__PURE__ */ jsx7("span", { children: label }),
3618
+ /* @__PURE__ */ jsx7(Icon, { className: "sortable-header-icon" })
2579
3619
  ]
2580
3620
  }
2581
3621
  );
@@ -2608,7 +3648,7 @@ function buildColumnDef(props, sort, onSort) {
2608
3648
  ...minWidth != null ? { minSize: minWidth } : {},
2609
3649
  ...maxWidth != null ? { maxSize: maxWidth } : {},
2610
3650
  ...resizable === false ? { enableResizing: false } : {},
2611
- header: sortable ? () => /* @__PURE__ */ jsx6(SortableHeader, { label: children, field, sort, onSort }) : (
3651
+ header: sortable ? () => /* @__PURE__ */ jsx7(SortableHeader, { label: children, field, sort, onSort }) : (
2612
3652
  // eslint-disable-next-line @typescript-eslint/promise-function-async
2613
3653
  () => children
2614
3654
  ),
@@ -2633,6 +3673,46 @@ function buildColumnDef(props, sort, onSort) {
2633
3673
  }
2634
3674
  };
2635
3675
  }
3676
+ function resolveGroupId(props, index) {
3677
+ if (props.id) return props.id;
3678
+ if (typeof props.header === "string" || typeof props.header === "number") {
3679
+ return `group:${props.header}:${index}`;
3680
+ }
3681
+ return `group:${index}`;
3682
+ }
3683
+ function buildColumnDefsFromTree(nodes, sort, onSort) {
3684
+ return nodes.map((node, index) => {
3685
+ if (node.type === "leaf") {
3686
+ return buildColumnDef(node.props, sort, onSort);
3687
+ }
3688
+ const childDefs = buildColumnDefsFromTree(node.columns, sort, onSort);
3689
+ const { header, align, headerClassName } = node.props;
3690
+ return {
3691
+ id: resolveGroupId(node.props, index),
3692
+ header: (
3693
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
3694
+ () => header
3695
+ ),
3696
+ columns: childDefs,
3697
+ enableResizing: false,
3698
+ meta: {
3699
+ align,
3700
+ headerClassName
3701
+ }
3702
+ };
3703
+ });
3704
+ }
3705
+ function countLeafColumns(nodes) {
3706
+ let count = 0;
3707
+ for (const node of nodes) {
3708
+ if (node.type === "leaf") {
3709
+ count += 1;
3710
+ } else {
3711
+ count += countLeafColumns(node.columns);
3712
+ }
3713
+ }
3714
+ return count;
3715
+ }
2636
3716
 
2637
3717
  // src/components/ui/table/components/Table/parseTableChildren.ts
2638
3718
  import { Children, isValidElement as isValidElement2 } from "react";
@@ -2642,6 +3722,7 @@ import { isValidElement } from "react";
2642
3722
  var TABLE_HEADER_DISPLAY_NAME = "Table.Header";
2643
3723
  var TABLE_BODY_DISPLAY_NAME = "Table.Body";
2644
3724
  var TABLE_COLUMN_DISPLAY_NAME = "Table.Column";
3725
+ var TABLE_COLUMN_GROUP_DISPLAY_NAME = "Table.ColumnGroup";
2645
3726
  var TABLE_PAGINATION_DISPLAY_NAME = "Table.Pagination";
2646
3727
  function getComponentDisplayName(type) {
2647
3728
  if (typeof type === "function" || typeof type === "object" && type !== null) {
@@ -2658,6 +3739,9 @@ function isTableBodyElement(child) {
2658
3739
  function isTableColumnElement(child) {
2659
3740
  return isValidElement(child) && getComponentDisplayName(child.type) === TABLE_COLUMN_DISPLAY_NAME;
2660
3741
  }
3742
+ function isTableColumnGroupElement(child) {
3743
+ return isValidElement(child) && getComponentDisplayName(child.type) === TABLE_COLUMN_GROUP_DISPLAY_NAME;
3744
+ }
2661
3745
  function isTablePaginationElement(child) {
2662
3746
  return isValidElement(child) && getComponentDisplayName(child.type) === TABLE_PAGINATION_DISPLAY_NAME;
2663
3747
  }
@@ -2684,26 +3768,38 @@ function parseTableChildren(children) {
2684
3768
  }
2685
3769
  return slots;
2686
3770
  }
2687
- function flattenColumnElements(children) {
3771
+ function walkColumnTreeNodes(children) {
2688
3772
  const result = [];
2689
3773
  for (const child of Children.toArray(children)) {
2690
3774
  if (isTableColumnElement(child)) {
2691
- result.push(child);
3775
+ result.push({
3776
+ type: "leaf",
3777
+ props: child.props
3778
+ });
3779
+ continue;
3780
+ }
3781
+ if (isTableColumnGroupElement(child)) {
3782
+ const groupProps = child.props;
3783
+ result.push({
3784
+ type: "group",
3785
+ props: groupProps,
3786
+ columns: walkColumnTreeNodes(groupProps.children)
3787
+ });
2692
3788
  continue;
2693
3789
  }
2694
3790
  if (isValidElement2(child)) {
2695
3791
  const nested = child.props.children;
2696
3792
  if (nested != null) {
2697
- result.push(...flattenColumnElements(nested));
3793
+ result.push(...walkColumnTreeNodes(nested));
2698
3794
  }
2699
3795
  }
2700
3796
  }
2701
3797
  return result;
2702
3798
  }
2703
- function extractColumnElements(header) {
3799
+ function extractColumnTree(header) {
2704
3800
  if (!header) return [];
2705
3801
  const { children } = header.props;
2706
- return flattenColumnElements(children);
3802
+ return walkColumnTreeNodes(children);
2707
3803
  }
2708
3804
 
2709
3805
  // src/components/ui/table/components/Table/TableBody.tsx
@@ -2719,6 +3815,13 @@ function TableColumn(props) {
2719
3815
  }
2720
3816
  TableColumn.displayName = TABLE_COLUMN_DISPLAY_NAME;
2721
3817
 
3818
+ // src/components/ui/table/components/Table/TableColumnGroup.tsx
3819
+ function TableColumnGroup(props) {
3820
+ void props;
3821
+ return null;
3822
+ }
3823
+ TableColumnGroup.displayName = TABLE_COLUMN_GROUP_DISPLAY_NAME;
3824
+
2722
3825
  // src/components/ui/table/components/Table/tableDataPipeline.ts
2723
3826
  function sortTableData(data, sort) {
2724
3827
  if (!sort) return data;
@@ -2756,7 +3859,7 @@ function TableHeader(props) {
2756
3859
  TableHeader.displayName = TABLE_HEADER_DISPLAY_NAME;
2757
3860
 
2758
3861
  // src/components/ui/table/components/Table/TablePagination.tsx
2759
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3862
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2760
3863
  function TablePagination({
2761
3864
  page,
2762
3865
  pageSize = 10,
@@ -2768,8 +3871,8 @@ function TablePagination({
2768
3871
  const safePage = Math.min(Math.max(1, page), totalPages);
2769
3872
  const canGoPrev = safePage > 1;
2770
3873
  const canGoNext = safePage < totalPages;
2771
- return /* @__PURE__ */ jsxs6("div", { className: cn("TablePaginationJSX", className), children: [
2772
- /* @__PURE__ */ jsx7(
3874
+ return /* @__PURE__ */ jsxs7("div", { className: cn("TablePaginationJSX", className), children: [
3875
+ /* @__PURE__ */ jsx8(
2773
3876
  "button",
2774
3877
  {
2775
3878
  type: "button",
@@ -2777,15 +3880,15 @@ function TablePagination({
2777
3880
  disabled: !canGoPrev,
2778
3881
  onClick: () => onChange(safePage - 1),
2779
3882
  "aria-label": "Previous page",
2780
- children: /* @__PURE__ */ jsx7(ChevronLeft, { className: "pagination-button-icon" })
3883
+ children: /* @__PURE__ */ jsx8(ChevronLeft, { className: "pagination-button-icon" })
2781
3884
  }
2782
3885
  ),
2783
- /* @__PURE__ */ jsxs6("span", { className: "pagination-label", children: [
3886
+ /* @__PURE__ */ jsxs7("span", { className: "pagination-label", children: [
2784
3887
  safePage,
2785
3888
  " / ",
2786
3889
  totalPages
2787
3890
  ] }),
2788
- /* @__PURE__ */ jsx7(
3891
+ /* @__PURE__ */ jsx8(
2789
3892
  "button",
2790
3893
  {
2791
3894
  type: "button",
@@ -2793,7 +3896,7 @@ function TablePagination({
2793
3896
  disabled: !canGoNext,
2794
3897
  onClick: () => onChange(safePage + 1),
2795
3898
  "aria-label": "Next page",
2796
- children: /* @__PURE__ */ jsx7(ChevronRight, { className: "pagination-button-icon" })
3899
+ children: /* @__PURE__ */ jsx8(ChevronRight, { className: "pagination-button-icon" })
2797
3900
  }
2798
3901
  )
2799
3902
  ] });
@@ -2801,7 +3904,7 @@ function TablePagination({
2801
3904
  TablePagination.displayName = TABLE_PAGINATION_DISPLAY_NAME;
2802
3905
 
2803
3906
  // src/components/ui/table/components/Table/Table.tsx
2804
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3907
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2805
3908
  function TableRoot({
2806
3909
  data,
2807
3910
  children,
@@ -2810,12 +3913,12 @@ function TableRoot({
2810
3913
  filteredCount,
2811
3914
  ...dataTableProps
2812
3915
  }) {
2813
- const { header, pagination: paginationElement } = useMemo4(
3916
+ const { header, pagination: paginationElement } = useMemo5(
2814
3917
  () => parseTableChildren(children),
2815
3918
  [children]
2816
3919
  );
2817
- const [sort, setSort] = useState4(null);
2818
- const handleSort = useCallback4((field) => {
3920
+ const [sort, setSort] = useState5(null);
3921
+ const handleSort = useCallback5((field) => {
2819
3922
  setSort((previous) => {
2820
3923
  if (previous?.field !== field) {
2821
3924
  return { field, direction: "asc" };
@@ -2826,25 +3929,25 @@ function TableRoot({
2826
3929
  return null;
2827
3930
  });
2828
3931
  }, []);
2829
- const columns = useMemo4(() => {
2830
- return extractColumnElements(header).map(
2831
- (columnElement) => buildColumnDef(columnElement.props, sort, handleSort)
2832
- );
2833
- }, [header, sort, handleSort]);
3932
+ const columnTree = useMemo5(() => extractColumnTree(header), [header]);
3933
+ const columns = useMemo5(
3934
+ () => buildColumnDefsFromTree(columnTree, sort, handleSort),
3935
+ [columnTree, sort, handleSort]
3936
+ );
2834
3937
  const paginationProps = paginationElement?.props;
2835
3938
  const pageSize = paginationProps?.pageSize ?? 10;
2836
3939
  const page = paginationProps?.page ?? 1;
2837
3940
  const resolvedTotalCount = paginationProps?.totalCount ?? totalCount ?? data.length;
2838
- const tableData = useMemo4(() => {
3941
+ const tableData = useMemo5(() => {
2839
3942
  const sortedData = sortTableData(data, sort);
2840
3943
  if (!paginationProps) return sortedData;
2841
3944
  return paginateTableData(sortedData, page, pageSize);
2842
3945
  }, [data, sort, paginationProps, page, pageSize]);
2843
- if (columns.length === 0) {
3946
+ if (countLeafColumns(columnTree) === 0) {
2844
3947
  console.warn("[Table] Declare at least one Table.Column inside Table.Header.");
2845
3948
  }
2846
- return /* @__PURE__ */ jsxs7("div", { className: "TableJSX", children: [
2847
- /* @__PURE__ */ jsx8(
3949
+ return /* @__PURE__ */ jsxs8("div", { className: "TableJSX", children: [
3950
+ /* @__PURE__ */ jsx9(
2848
3951
  DataTable,
2849
3952
  {
2850
3953
  ...dataTableProps,
@@ -2855,7 +3958,7 @@ function TableRoot({
2855
3958
  className: cn(className, paginationProps && "DataTableJSX--with-pagination")
2856
3959
  }
2857
3960
  ),
2858
- paginationProps && /* @__PURE__ */ jsx8(
3961
+ paginationProps && /* @__PURE__ */ jsx9(
2859
3962
  TablePagination,
2860
3963
  {
2861
3964
  page,
@@ -2873,13 +3976,19 @@ function createTable() {
2873
3976
  return null;
2874
3977
  }
2875
3978
  Column.displayName = TABLE_COLUMN_DISPLAY_NAME;
3979
+ function ColumnGroup(props) {
3980
+ void props;
3981
+ return null;
3982
+ }
3983
+ ColumnGroup.displayName = TABLE_COLUMN_GROUP_DISPLAY_NAME;
2876
3984
  return Object.assign(
2877
3985
  function BoundTable(props) {
2878
- return /* @__PURE__ */ jsx8(TableRoot, { ...props });
3986
+ return /* @__PURE__ */ jsx9(TableRoot, { ...props });
2879
3987
  },
2880
3988
  {
2881
3989
  Header: TableHeader,
2882
3990
  Column,
3991
+ ColumnGroup,
2883
3992
  Body: TableBody,
2884
3993
  Pagination: TablePagination
2885
3994
  }
@@ -2888,6 +3997,7 @@ function createTable() {
2888
3997
  var Table = Object.assign(TableRoot, {
2889
3998
  Header: TableHeader,
2890
3999
  Column: TableColumn,
4000
+ ColumnGroup: TableColumnGroup,
2891
4001
  Body: TableBody,
2892
4002
  Pagination: TablePagination
2893
4003
  });