boneless-table 0.1.0 → 0.1.2

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/index.mjs CHANGED
@@ -45,12 +45,12 @@ import {
45
45
  useEffect as useEffect3,
46
46
  useId,
47
47
  useMemo,
48
- useRef as useRef2,
48
+ useRef as useRef3,
49
49
  useState as useState2
50
50
  } from "react";
51
51
 
52
52
  // src/column-filter.tsx
53
- import { useEffect, useState } from "react";
53
+ import { useEffect, useRef, useState } from "react";
54
54
  import { jsx, jsxs } from "react/jsx-runtime";
55
55
  function ColumnFilter({
56
56
  column,
@@ -62,10 +62,13 @@ function ColumnFilter({
62
62
  var _a, _b;
63
63
  const value = String((_a = column.getFilterValue()) != null ? _a : "");
64
64
  const [inputValue, setInputValue] = useState(value);
65
+ const lastColumnValueRef = useRef(value);
65
66
  useEffect(() => {
66
- setInputValue(value);
67
- }, [value]);
68
- useEffect(() => {
67
+ if (value !== lastColumnValueRef.current) {
68
+ lastColumnValueRef.current = value;
69
+ setInputValue(value);
70
+ return;
71
+ }
69
72
  if (settings.type !== "text" || inputValue === value) return;
70
73
  if (debounceMs <= 0) {
71
74
  column.setFilterValue(inputValue || void 0);
@@ -109,7 +112,7 @@ function ColumnFilter({
109
112
  }
110
113
 
111
114
  // src/column-menu.tsx
112
- import { useEffect as useEffect2, useRef } from "react";
115
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
113
116
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
114
117
  function ColumnMenu({
115
118
  table,
@@ -121,8 +124,8 @@ function ColumnMenu({
121
124
  triggerRef
122
125
  }) {
123
126
  var _a;
124
- const menuRef = useRef(null);
125
- const closeButtonRef = useRef(null);
127
+ const menuRef = useRef2(null);
128
+ const closeButtonRef = useRef2(null);
126
129
  useEffect2(() => {
127
130
  var _a2;
128
131
  (_a2 = closeButtonRef.current) == null ? void 0 : _a2.focus();
@@ -218,7 +221,7 @@ var defaultBonelessTableSettings = {
218
221
  borders: "none",
219
222
  sorting: { enabled: true, reveal: "hover" }
220
223
  },
221
- interactions: { filterReveal: "hover", horizontalOverflow: "auto" }
224
+ interactions: { horizontalOverflow: "auto" }
222
225
  };
223
226
  function mergeBonelessTableSettings(override, base = defaultBonelessTableSettings) {
224
227
  var _a, _b, _c, _d, _e, _f;
@@ -238,7 +241,7 @@ function resolveColumnSettings(settings, column) {
238
241
  const configured = (_a = column.columnDef.meta) == null ? void 0 : _a.bonelessTable;
239
242
  const configuredSizing = configured == null ? void 0 : configured.sizing;
240
243
  return {
241
- sizing: configuredSizing && "widthPx" in configuredSizing ? { widthPx: configuredSizing.widthPx } : __spreadValues(__spreadValues({}, settings.columnDefaults.sizing), configuredSizing),
244
+ sizing: (configuredSizing == null ? void 0 : configuredSizing.widthPx) != null && configuredSizing.minPx == null && configuredSizing.maxPx == null && configuredSizing.flex == null && configuredSizing.resizable == null ? { widthPx: configuredSizing.widthPx } : __spreadValues(__spreadValues({}, settings.columnDefaults.sizing), configuredSizing),
242
245
  align: (_b = configured == null ? void 0 : configured.align) != null ? _b : settings.columnDefaults.align,
243
246
  borders: (_c = configured == null ? void 0 : configured.borders) != null ? _c : settings.columnDefaults.borders,
244
247
  sorting: __spreadValues(__spreadValues({}, settings.columnDefaults.sorting), configured == null ? void 0 : configured.sorting),
@@ -337,7 +340,7 @@ function resolveRowLinkLabel(link, row) {
337
340
  }
338
341
 
339
342
  // src/boneless-table.tsx
340
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
343
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
341
344
  function moveItem(order, id, direction) {
342
345
  const index = order.indexOf(id);
343
346
  const target = index + direction;
@@ -372,6 +375,8 @@ function BonelessTable(_a) {
372
375
  scroller = "auto",
373
376
  toolbar = true,
374
377
  toolbarLayout,
378
+ filters = true,
379
+ filterPlacement = "above",
375
380
  footer = true,
376
381
  totalCount,
377
382
  resultLabel = "rows",
@@ -401,6 +406,8 @@ function BonelessTable(_a) {
401
406
  "scroller",
402
407
  "toolbar",
403
408
  "toolbarLayout",
409
+ "filters",
410
+ "filterPlacement",
404
411
  "footer",
405
412
  "totalCount",
406
413
  "resultLabel",
@@ -416,13 +423,15 @@ function BonelessTable(_a) {
416
423
  const settings = useMemo(() => mergeBonelessTableSettings(settingsOverride), [settingsOverride]);
417
424
  const resolvedColumns = useMemo(() => withDefaultCells(resolveColumns(columns)), [columns]);
418
425
  const defaultOrder = useMemo(() => getColumnIds(resolvedColumns), [resolvedColumns]);
419
- const scrollerRef = useRef2(null);
420
- const headerScrollerRef = useRef2(null);
421
- const columnsButtonRef = useRef2(null);
422
- const nearEndTriggeredRef = useRef2(false);
426
+ const scrollerRef = useRef3(null);
427
+ const headerScrollerRef = useRef3(null);
428
+ const rootRef = useRef3(null);
429
+ const lastScrollLeftRef = useRef3(0);
430
+ const columnsButtonRef = useRef3(null);
431
+ const nearEndTriggeredRef = useRef3(false);
423
432
  const columnMenuId = useId();
424
433
  const [showColumns, setShowColumns] = useState2(false);
425
- const [headerScrollbarGutter, setHeaderScrollbarGutter] = useState2(0);
434
+ const [headerScrollbarInset, setHeaderScrollbarInset] = useState2(0);
426
435
  const [sorting, setSorting] = useState2((_b2 = (_a2 = tableOptions.initialState) == null ? void 0 : _a2.sorting) != null ? _b2 : []);
427
436
  const [columnFilters, setColumnFilters] = useState2(
428
437
  (_d = (_c = tableOptions.initialState) == null ? void 0 : _c.columnFilters) != null ? _d : []
@@ -434,6 +443,7 @@ function BonelessTable(_a) {
434
443
  (_h = (_g = tableOptions.initialState) == null ? void 0 : _g.columnOrder) != null ? _h : defaultOrder
435
444
  );
436
445
  const [expanded, setExpanded] = useState2((_j = (_i = tableOptions.initialState) == null ? void 0 : _i.expanded) != null ? _j : {});
446
+ const [columnSizing, setColumnSizing] = useState2({});
437
447
  const _k = tableOptions, {
438
448
  state: controlledState,
439
449
  onSortingChange,
@@ -465,19 +475,27 @@ function BonelessTable(_a) {
465
475
  ]);
466
476
  useEffect3(() => {
467
477
  var _a3;
468
- if (scrollToTopOn !== void 0) (_a3 = scrollerRef.current) == null ? void 0 : _a3.scrollTo({ top: 0 });
478
+ if (scrollToTopOn !== void 0) {
479
+ nearEndTriggeredRef.current = false;
480
+ (_a3 = scrollerRef.current) == null ? void 0 : _a3.scrollTo({ top: 0 });
481
+ }
469
482
  }, [scrollToTopOn]);
470
483
  useEffect3(() => {
471
- const scroller2 = scrollerRef.current;
472
- if (!scroller2) return;
473
- const syncHeaderScrollbarGutter = () => {
474
- setHeaderScrollbarGutter(Math.max(0, scroller2.offsetWidth - scroller2.clientWidth));
484
+ const scrollerElement = scrollerRef.current;
485
+ if (!scrollerElement) return;
486
+ const syncHeaderScrollbarInset = () => {
487
+ setHeaderScrollbarInset(
488
+ Math.max(0, scrollerElement.offsetWidth - scrollerElement.clientWidth)
489
+ );
490
+ };
491
+ syncHeaderScrollbarInset();
492
+ const resizeObserver = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(syncHeaderScrollbarInset);
493
+ resizeObserver == null ? void 0 : resizeObserver.observe(scrollerElement);
494
+ window.addEventListener("resize", syncHeaderScrollbarInset);
495
+ return () => {
496
+ resizeObserver == null ? void 0 : resizeObserver.disconnect();
497
+ window.removeEventListener("resize", syncHeaderScrollbarInset);
475
498
  };
476
- syncHeaderScrollbarGutter();
477
- if (typeof ResizeObserver === "undefined") return;
478
- const observer = new ResizeObserver(syncHeaderScrollbarGutter);
479
- observer.observe(scroller2);
480
- return () => observer.disconnect();
481
499
  }, []);
482
500
  const table = useReactTable(__spreadProps(__spreadValues({}, restTableOptions), {
483
501
  data,
@@ -513,18 +531,33 @@ function BonelessTable(_a) {
513
531
  var _a3;
514
532
  return (_a3 = columnSettingsById.get(column.id)) != null ? _a3 : resolveColumnSettings(settings, column);
515
533
  };
534
+ const columnResizeVariables = useMemo(
535
+ () => new Map(
536
+ allColumns.map((column, index) => [column.id, `--boneless-table-column-${index}-width`])
537
+ ),
538
+ [allColumns]
539
+ );
540
+ const getColumnResizeVariable = (columnId) => {
541
+ var _a3;
542
+ return (_a3 = columnResizeVariables.get(columnId)) != null ? _a3 : `--boneless-table-column-${columnId}-width`;
543
+ };
516
544
  const grid = visibleColumns.map((column) => {
545
+ var _a3, _b3, _c2;
517
546
  const columnSettings = getColumnSettings(column);
518
- if ("widthPx" in columnSettings.sizing) return `${columnSettings.sizing.widthPx}px`;
519
- return `minmax(${columnSettings.sizing.minPx}px, ${columnSettings.sizing.flex}fr)`;
547
+ const { sizing } = columnSettings;
548
+ const width = (_a3 = columnSizing[column.id]) != null ? _a3 : sizing.widthPx;
549
+ const track = width != null ? `${width}px` : `minmax(${(_b3 = sizing.minPx) != null ? _b3 : settings.columnDefaults.sizing.minPx}px, ${(_c2 = sizing.flex) != null ? _c2 : settings.columnDefaults.sizing.flex}fr)`;
550
+ return `var(${getColumnResizeVariable(column.id)}, ${track})`;
520
551
  }).join(" ");
521
552
  const minWidth = visibleColumns.reduce((width, column) => {
553
+ var _a3, _b3;
522
554
  const { sizing } = getColumnSettings(column);
523
- return width + ("widthPx" in sizing ? sizing.widthPx : sizing.minPx);
555
+ const configuredWidth = (_a3 = columnSizing[column.id]) != null ? _a3 : sizing.widthPx;
556
+ return width + ((_b3 = configuredWidth != null ? configuredWidth : sizing.minPx) != null ? _b3 : settings.columnDefaults.sizing.minPx);
524
557
  }, 0);
525
558
  const gridStyle = {
526
559
  "--boneless-table-grid": grid,
527
- minWidth: `${minWidth}px`
560
+ minWidth: `var(--boneless-table-min-width, ${minWidth}px)`
528
561
  };
529
562
  const tree = treeOption === true ? {} : treeOption === false ? void 0 : treeOption;
530
563
  const treeColumnId = (_m = tree == null ? void 0 : tree.columnId) != null ? _m : (_l = visibleColumns[0]) == null ? void 0 : _l.id;
@@ -542,28 +575,94 @@ function BonelessTable(_a) {
542
575
  [expandableRows, expanded]
543
576
  );
544
577
  useEffect3(() => {
545
- if (!isFetchingMore || !canLoadMore) nearEndTriggeredRef.current = false;
546
- }, [canLoadMore, isFetchingMore]);
578
+ if (!canLoadMore) nearEndTriggeredRef.current = false;
579
+ }, [canLoadMore]);
547
580
  function handleScroll() {
548
581
  const element = scrollerRef.current;
549
582
  if (!element) return;
550
- if (headerScrollerRef.current) headerScrollerRef.current.scrollLeft = element.scrollLeft;
583
+ if (element.scrollLeft !== lastScrollLeftRef.current) {
584
+ lastScrollLeftRef.current = element.scrollLeft;
585
+ if (headerScrollerRef.current) headerScrollerRef.current.scrollLeft = element.scrollLeft;
586
+ }
587
+ if (!onNearEnd) return;
551
588
  const isNearEnd = element.scrollHeight - element.scrollTop - element.clientHeight < nearEndOffset;
552
589
  if (!isNearEnd) {
553
590
  nearEndTriggeredRef.current = false;
554
591
  return;
555
592
  }
556
- if (!onNearEnd || !canLoadMore || isFetchingMore || nearEndTriggeredRef.current) return;
593
+ if (!canLoadMore || isFetchingMore || nearEndTriggeredRef.current) return;
557
594
  nearEndTriggeredRef.current = true;
558
595
  onNearEnd();
559
596
  }
560
597
  function resetTable() {
598
+ var _a3;
561
599
  if (onReset) return onReset();
562
600
  table.resetSorting();
563
601
  table.resetColumnFilters();
564
602
  table.resetColumnVisibility();
565
603
  table.setColumnOrder(defaultOrder);
566
604
  table.resetExpanded();
605
+ columnResizeVariables.forEach((property) => {
606
+ var _a4;
607
+ return (_a4 = rootRef.current) == null ? void 0 : _a4.style.removeProperty(property);
608
+ });
609
+ (_a3 = rootRef.current) == null ? void 0 : _a3.style.removeProperty("--boneless-table-min-width");
610
+ setColumnSizing({});
611
+ }
612
+ function resizeColumn(columnId, startWidth, startX, minPx, maxPx) {
613
+ const property = getColumnResizeVariable(columnId);
614
+ const startMinWidth = minWidth;
615
+ let nextWidth = startWidth;
616
+ let hasMoved = false;
617
+ const onMove = (event) => {
618
+ var _a3, _b3;
619
+ nextWidth = constrainColumnWidth(startWidth + event.clientX - startX, minPx, maxPx);
620
+ hasMoved || (hasMoved = nextWidth !== startWidth);
621
+ (_a3 = rootRef.current) == null ? void 0 : _a3.style.setProperty(property, `${nextWidth}px`);
622
+ (_b3 = rootRef.current) == null ? void 0 : _b3.style.setProperty(
623
+ "--boneless-table-min-width",
624
+ `${startMinWidth - startWidth + nextWidth}px`
625
+ );
626
+ };
627
+ const onEnd = () => {
628
+ window.removeEventListener("pointermove", onMove);
629
+ window.removeEventListener("pointerup", onEnd);
630
+ window.removeEventListener("pointercancel", onEnd);
631
+ if (hasMoved) {
632
+ setColumnSizing((current) => __spreadProps(__spreadValues({}, current), { [columnId]: nextWidth }));
633
+ }
634
+ };
635
+ window.addEventListener("pointermove", onMove);
636
+ window.addEventListener("pointerup", onEnd);
637
+ window.addEventListener("pointercancel", onEnd);
638
+ }
639
+ function constrainColumnWidth(width, minPx, maxPx) {
640
+ return Math.min(maxPx != null ? maxPx : Number.POSITIVE_INFINITY, Math.max(minPx, Math.round(width)));
641
+ }
642
+ function getCurrentColumnWidth(columnId, fallback, element) {
643
+ var _a3, _b3, _c2, _d2;
644
+ const column = allColumns.find((item) => item.id === columnId);
645
+ const configuredWidth = column ? getColumnSettings(column).sizing.widthPx : void 0;
646
+ return (_d2 = (_c2 = (_a3 = columnSizing[columnId]) != null ? _a3 : configuredWidth) != null ? _c2 : (_b3 = element == null ? void 0 : element.parentElement) == null ? void 0 : _b3.getBoundingClientRect().width) != null ? _d2 : fallback;
647
+ }
648
+ function handleResizeStart(event, columnId, minPx, maxPx) {
649
+ var _a3, _b3;
650
+ event.preventDefault();
651
+ event.stopPropagation();
652
+ const currentWidth = getCurrentColumnWidth(columnId, minPx, event.currentTarget);
653
+ (_b3 = (_a3 = event.currentTarget).setPointerCapture) == null ? void 0 : _b3.call(_a3, event.pointerId);
654
+ resizeColumn(columnId, currentWidth, event.clientX, minPx, maxPx);
655
+ }
656
+ function handleResizeKeyDown(event, columnId, minPx, maxPx) {
657
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
658
+ event.preventDefault();
659
+ event.stopPropagation();
660
+ const step = event.shiftKey ? 32 : 8;
661
+ const currentWidth = getCurrentColumnWidth(columnId, minPx, event.currentTarget);
662
+ const direction = event.key === "ArrowLeft" ? -1 : 1;
663
+ setColumnSizing((current) => __spreadProps(__spreadValues({}, current), {
664
+ [columnId]: constrainColumnWidth(currentWidth + direction * step, minPx, maxPx)
665
+ }));
567
666
  }
568
667
  function moveColumn(id, direction) {
569
668
  const current = table.getState().columnOrder;
@@ -585,6 +684,11 @@ function BonelessTable(_a) {
585
684
  initialRect: { width: 0, height: 600 }
586
685
  });
587
686
  const estimatedRowSize = virtualization === false ? 58 : (_p = virtualization.estimateSize) != null ? _p : 58;
687
+ const measuredVirtualHeight = rowVirtualizer.getTotalSize();
688
+ const virtualHeight = isVirtualized ? Math.max(
689
+ Number.isFinite(measuredVirtualHeight) ? measuredVirtualHeight : 0,
690
+ rows.length * estimatedRowSize
691
+ ) : void 0;
588
692
  const virtualRows = !isVirtualized ? rows.map((row, index) => ({ index, start: index * 58, key: row.id })) : rowVirtualizer.getVirtualItems().length > 0 ? rowVirtualizer.getVirtualItems() : rows.slice(0, Math.ceil(600 / estimatedRowSize) + ((_q = virtualization.overscan) != null ? _q : 5)).map((row, index) => ({
589
693
  index,
590
694
  start: index * estimatedRowSize,
@@ -596,10 +700,13 @@ function BonelessTable(_a) {
596
700
  (_a3 = columnsButtonRef.current) == null ? void 0 : _a3.focus();
597
701
  }
598
702
  const toolbarSummary = /* @__PURE__ */ jsxs3("div", { className: classNames.toolbarSummary, "data-slot": "toolbar-summary", children: [
599
- /* @__PURE__ */ jsx4("strong", { children: total.toLocaleString() }),
703
+ /* @__PURE__ */ jsx4("strong", { className: classNames.toolbarCount, "data-slot": "toolbar-count", children: total.toLocaleString() }),
600
704
  " ",
601
- resultLabel,
602
- resultHint ? /* @__PURE__ */ jsx4("span", { className: classNames.toolbarHint, "data-slot": "toolbar-hint", children: resultHint }) : null
705
+ /* @__PURE__ */ jsx4("span", { className: classNames.toolbarLabel, "data-slot": "toolbar-label", children: resultLabel }),
706
+ resultHint ? /* @__PURE__ */ jsxs3(Fragment, { children: [
707
+ " ",
708
+ /* @__PURE__ */ jsx4("span", { className: classNames.toolbarHint, "data-slot": "toolbar-hint", children: resultHint })
709
+ ] }) : null
603
710
  ] });
604
711
  const toolbarActions = /* @__PURE__ */ jsxs3("div", { className: classNames.toolbarActions, "data-slot": "toolbar-actions", children: [
605
712
  tree && expandableRows.length > 0 ? /* @__PURE__ */ jsxs3(
@@ -651,6 +758,43 @@ function BonelessTable(_a) {
651
758
  toolbarSummary,
652
759
  toolbarActions
653
760
  ] });
761
+ const filterHeaders = table.getLeafHeaders().filter((header) => !header.isPlaceholder && getColumnSettings(header.column).filtering);
762
+ const defaultFilters = filterHeaders.length > 0 ? /* @__PURE__ */ jsx4(
763
+ "div",
764
+ {
765
+ "aria-label": "Table filters",
766
+ className: classNames.filters,
767
+ "data-slot": "filters",
768
+ role: "group",
769
+ children: filterHeaders.map((header) => {
770
+ var _a3, _b3;
771
+ const filterSettings = getColumnSettings(header.column).filtering;
772
+ return /* @__PURE__ */ jsxs3(
773
+ "div",
774
+ {
775
+ className: classNames.filterItem,
776
+ "data-column-id": header.column.id,
777
+ "data-slot": "filter-item",
778
+ children: [
779
+ /* @__PURE__ */ jsx4("span", { className: classNames.filterLabel, "data-slot": "filter-label", children: (_b3 = (_a3 = header.column.columnDef.meta) == null ? void 0 : _a3.columnLabel) != null ? _b3 : typeof header.column.columnDef.header === "string" || typeof header.column.columnDef.header === "number" ? String(header.column.columnDef.header) : header.column.id }),
780
+ /* @__PURE__ */ jsx4(
781
+ ColumnFilter,
782
+ {
783
+ className: classNames.filter,
784
+ column: header.column,
785
+ settings: filterSettings,
786
+ icon: filterSettings.type === "text" ? icons == null ? void 0 : icons.search : void 0,
787
+ debounceMs: filterDebounceMs
788
+ }
789
+ )
790
+ ]
791
+ },
792
+ header.id
793
+ );
794
+ })
795
+ }
796
+ ) : null;
797
+ const renderedFilters = renderSlot(filters, table, defaultFilters);
654
798
  const defaultFooter = /* @__PURE__ */ jsxs3("footer", { className: classNames.footer, "data-slot": "footer", children: [
655
799
  "Showing ",
656
800
  shown.toLocaleString(),
@@ -661,266 +805,322 @@ function BonelessTable(_a) {
661
805
  ] });
662
806
  const rootStyle = scroller === "fill" ? { display: "flex", flexDirection: "column", minHeight: 0 } : void 0;
663
807
  const scrollerStyle = __spreadValues({
664
- overflowX: settings.interactions.horizontalOverflow
808
+ overflowX: settings.interactions.horizontalOverflow,
809
+ scrollbarGutter: "stable"
665
810
  }, scroller === "fill" ? { flex: "1 1 auto", minHeight: 0, overflowY: "auto" } : {});
811
+ const headerScrollerStyle = {
812
+ flex: "0 0 auto",
813
+ minWidth: 0,
814
+ overflow: "hidden",
815
+ // The body loses this space to a classic vertical scrollbar on Windows. Reserving the
816
+ // same width here keeps both horizontal scroll ranges identical at their far edge.
817
+ marginInlineEnd: `${headerScrollbarInset}px`
818
+ };
666
819
  const tableStyle = __spreadValues({
667
820
  "--boneless-table-grid": grid
668
821
  }, scroller === "fill" ? { display: "flex", flexDirection: "column", flex: "1 1 auto", minHeight: 0 } : {});
669
- return /* @__PURE__ */ jsxs3("div", { className: cn(className, classNames.root), "data-slot": "root", style: rootStyle, children: [
670
- renderSlot(toolbar, table, defaultToolbar),
671
- /* @__PURE__ */ jsxs3(
672
- "div",
673
- {
674
- className: classNames.table,
675
- "data-slot": "table",
676
- style: tableStyle,
677
- "aria-busy": isLoading || isFetchingMore,
678
- role: "table",
679
- children: [
680
- /* @__PURE__ */ jsx4(
681
- "div",
682
- {
683
- "data-slot": "header-scroller",
684
- ref: headerScrollerRef,
685
- style: {
686
- flex: "0 0 auto",
687
- minWidth: 0,
688
- overflow: "hidden",
689
- paddingInlineEnd: headerScrollbarGutter || void 0
690
- },
691
- children: /* @__PURE__ */ jsx4(
822
+ return /* @__PURE__ */ jsxs3(
823
+ "div",
824
+ {
825
+ className: cn(className, classNames.root),
826
+ "data-slot": "root",
827
+ ref: rootRef,
828
+ style: rootStyle,
829
+ children: [
830
+ renderSlot(toolbar, table, defaultToolbar),
831
+ filterPlacement === "above" ? renderedFilters : null,
832
+ /* @__PURE__ */ jsxs3(
833
+ "div",
834
+ {
835
+ className: classNames.table,
836
+ "data-slot": "table",
837
+ style: tableStyle,
838
+ "aria-busy": isLoading || isFetchingMore,
839
+ role: "table",
840
+ children: [
841
+ /* @__PURE__ */ jsx4(
692
842
  "div",
693
843
  {
694
- className: classNames.headerGroup,
695
- "data-slot": "header-group",
696
- role: "rowgroup",
697
- style: gridStyle,
698
- children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx4(
844
+ className: classNames.headerScroller,
845
+ "data-slot": "header-scroller",
846
+ ref: headerScrollerRef,
847
+ style: headerScrollerStyle,
848
+ children: /* @__PURE__ */ jsx4(
699
849
  "div",
700
850
  {
701
- className: classNames.headerRow,
702
- "data-slot": "header-row",
703
- role: "row",
704
- style: { display: "grid", gridTemplateColumns: "var(--boneless-table-grid)" },
705
- children: headerGroup.headers.map((header) => {
706
- var _a3;
707
- const columnSettings = getColumnSettings(header.column);
708
- const direction = header.column.getIsSorted();
709
- const isSortable = !header.isPlaceholder && header.column.getCanSort() && columnSettings.sorting.enabled;
710
- return /* @__PURE__ */ jsxs3(
711
- "div",
712
- {
713
- className: classNames.header,
714
- "data-slot": "header",
715
- "data-align": columnSettings.align,
716
- "data-borders": columnSettings.borders,
717
- role: "columnheader",
718
- "aria-colspan": header.colSpan > 1 ? header.colSpan : void 0,
719
- "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : isSortable ? "none" : void 0,
720
- style: { gridColumn: `span ${header.colSpan}` },
721
- children: [
722
- header.isPlaceholder ? null : isSortable ? /* @__PURE__ */ jsxs3(
723
- "button",
724
- {
725
- className: classNames.headerButton,
726
- "data-align": columnSettings.align,
727
- "data-slot": "header-button",
728
- onClick: header.column.getToggleSortingHandler(),
729
- children: [
730
- flexRender(header.column.columnDef.header, header.getContext()),
731
- (icons == null ? void 0 : icons.sort) ? /* @__PURE__ */ jsx4(
732
- "span",
733
- {
734
- className: classNames.sortIndicator,
735
- "data-align": columnSettings.align,
736
- "data-slot": "sort-indicator",
737
- "data-reveal": columnSettings.sorting.reveal,
738
- children: icons.sort(direction)
739
- }
740
- ) : null
741
- ]
742
- }
743
- ) : flexRender(header.column.columnDef.header, header.getContext()),
744
- !header.isPlaceholder && columnSettings.filtering ? /* @__PURE__ */ jsx4(
745
- "div",
746
- {
747
- className: classNames.filter,
748
- "data-slot": "filter",
749
- "data-reveal": (_a3 = columnSettings.filtering.reveal) != null ? _a3 : settings.interactions.filterReveal,
750
- children: /* @__PURE__ */ jsx4(
751
- ColumnFilter,
752
- {
753
- column: header.column,
754
- settings: columnSettings.filtering,
755
- icon: icons == null ? void 0 : icons.search,
756
- debounceMs: filterDebounceMs
757
- }
758
- )
759
- }
760
- ) : null
761
- ]
762
- },
763
- header.id
764
- );
765
- })
766
- },
767
- headerGroup.id
768
- ))
769
- }
770
- )
771
- }
772
- ),
773
- /* @__PURE__ */ jsxs3(
774
- "div",
775
- {
776
- className: classNames.scroller,
777
- "data-slot": "scroller",
778
- ref: scrollerRef,
779
- onScroll: handleScroll,
780
- style: scrollerStyle,
781
- children: [
782
- isLoading ? /* @__PURE__ */ jsx4(
783
- TableSkeleton,
784
- {
785
- columnCount: visibleColumns.length,
786
- rows: skeletonRows,
787
- label: skeletonLabel,
788
- classNames
789
- }
790
- ) : rows.length === 0 ? /* @__PURE__ */ jsx4("div", { className: classNames.emptyState, "data-slot": "empty-state", role: "status", children: emptyState ? typeof emptyState === "function" ? emptyState(table) : emptyState : table.getState().columnFilters.length > 0 ? "No rows match the current filters." : "No rows to display." }) : /* @__PURE__ */ jsx4(
791
- "div",
792
- {
793
- className: classNames.rowGroup,
794
- "data-slot": "row-group",
795
- style: __spreadProps(__spreadValues({}, gridStyle), {
796
- height: `${isVirtualized ? rowVirtualizer.getTotalSize() : "auto"}`,
797
- position: "relative"
798
- }),
799
- role: "rowgroup",
800
- children: virtualRows.map((virtualRow) => {
801
- var _a3;
802
- const row = rows[virtualRow.index];
803
- const href = rowLink ? resolveRowHref(rowLink, row.original) : void 0;
804
- const RowLink = rowLink == null ? void 0 : rowLink.component;
805
- const rowContent = /* @__PURE__ */ jsx4(
851
+ className: classNames.headerGroup,
852
+ "data-slot": "header-group",
853
+ role: "rowgroup",
854
+ style: gridStyle,
855
+ children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx4(
806
856
  "div",
807
857
  {
808
- className: classNames.row,
809
- "data-slot": "row",
858
+ className: classNames.headerRow,
859
+ "data-slot": "header-row",
810
860
  role: "row",
811
861
  style: { display: "grid", gridTemplateColumns: "var(--boneless-table-grid)" },
812
- tabIndex: onRowClick && !href ? 0 : void 0,
813
- onClick: (event) => onRowClick == null ? void 0 : onRowClick(row.original, event),
814
- onKeyDown: (event) => {
815
- if (!onRowClick || href || event.key !== "Enter" && event.key !== " ")
816
- return;
817
- event.preventDefault();
818
- onRowClick(row.original, event);
819
- },
820
- children: row.getVisibleCells().map((cell) => {
821
- var _a4, _b3, _c2;
822
- const columnSettings = getColumnSettings(cell.column);
823
- const title = columnSettings.valueDisplay != null ? String((_a4 = cell.getValue()) != null ? _a4 : "") : void 0;
824
- const isTreeCell = Boolean(tree && cell.column.id === treeColumnId);
825
- const canExpand = row.getCanExpand();
826
- const cellContent = flexRender(cell.column.columnDef.cell, cell.getContext());
862
+ children: headerGroup.headers.map((header) => {
863
+ const columnSettings = getColumnSettings(header.column);
864
+ const direction = header.column.getIsSorted();
865
+ const isSortable = !header.isPlaceholder && header.column.getCanSort() && columnSettings.sorting.enabled;
827
866
  return /* @__PURE__ */ jsx4(
828
867
  "div",
829
868
  {
830
- className: classNames.cell,
831
- "data-slot": "cell",
869
+ className: classNames.header,
870
+ "data-slot": "header",
832
871
  "data-align": columnSettings.align,
833
872
  "data-borders": columnSettings.borders,
834
- role: "cell",
835
- title,
836
- children: isTreeCell ? /* @__PURE__ */ jsxs3(
837
- "div",
838
- {
839
- "data-slot": "tree-content",
840
- style: { paddingInlineStart: `${row.depth * treeIndentPx}px` },
841
- children: [
842
- canExpand ? /* @__PURE__ */ jsx4(
843
- "button",
844
- {
845
- "data-slot": "tree-row-toggle",
846
- type: "button",
847
- "aria-expanded": row.getIsExpanded(),
848
- "aria-label": `${row.getIsExpanded() ? "Collapse" : "Expand"} row`,
849
- onClick: (event) => {
850
- event.stopPropagation();
851
- row.toggleExpanded();
852
- },
853
- children: row.getIsExpanded() ? (_b3 = icons == null ? void 0 : icons.collapse) != null ? _b3 : "\u2212" : (_c2 = icons == null ? void 0 : icons.expand) != null ? _c2 : "+"
854
- }
855
- ) : /* @__PURE__ */ jsx4("span", { "data-slot": "tree-row-toggle-spacer", "aria-hidden": "true" }),
856
- /* @__PURE__ */ jsx4("span", { "data-slot": "tree-value", children: cellContent })
857
- ]
858
- }
859
- ) : cellContent
873
+ role: "columnheader",
874
+ "aria-colspan": header.colSpan > 1 ? header.colSpan : void 0,
875
+ "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : isSortable ? "none" : void 0,
876
+ style: { gridColumn: `span ${header.colSpan}` },
877
+ children: header.isPlaceholder ? null : isSortable ? /* @__PURE__ */ jsxs3(Fragment, { children: [
878
+ /* @__PURE__ */ jsxs3(
879
+ "button",
880
+ {
881
+ className: classNames.headerButton,
882
+ "data-align": columnSettings.align,
883
+ "data-slot": "header-button",
884
+ onClick: header.column.getToggleSortingHandler(),
885
+ children: [
886
+ /* @__PURE__ */ jsx4("span", { className: classNames.headerLabel, "data-slot": "header-label", children: flexRender(header.column.columnDef.header, header.getContext()) }),
887
+ (icons == null ? void 0 : icons.sort) ? /* @__PURE__ */ jsx4(
888
+ "span",
889
+ {
890
+ className: classNames.sortIndicator,
891
+ "data-align": columnSettings.align,
892
+ "data-slot": "sort-indicator",
893
+ "data-reveal": columnSettings.sorting.reveal,
894
+ children: icons.sort(direction)
895
+ }
896
+ ) : null
897
+ ]
898
+ }
899
+ ),
900
+ header.colSpan === 1 && columnSettings.sizing.resizable ? /* @__PURE__ */ jsx4(
901
+ "button",
902
+ {
903
+ "aria-label": `Resize ${header.column.id} column`,
904
+ className: classNames.resizeHandle,
905
+ "data-slot": "resize-handle",
906
+ onKeyDown: (event) => {
907
+ var _a3;
908
+ return handleResizeKeyDown(
909
+ event,
910
+ header.column.id,
911
+ (_a3 = columnSettings.sizing.minPx) != null ? _a3 : settings.columnDefaults.sizing.minPx,
912
+ columnSettings.sizing.maxPx
913
+ );
914
+ },
915
+ onPointerDown: (event) => {
916
+ var _a3;
917
+ return handleResizeStart(
918
+ event,
919
+ header.column.id,
920
+ (_a3 = columnSettings.sizing.minPx) != null ? _a3 : settings.columnDefaults.sizing.minPx,
921
+ columnSettings.sizing.maxPx
922
+ );
923
+ },
924
+ type: "button"
925
+ }
926
+ ) : null
927
+ ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
928
+ flexRender(header.column.columnDef.header, header.getContext()),
929
+ header.colSpan === 1 && columnSettings.sizing.resizable ? /* @__PURE__ */ jsx4(
930
+ "button",
931
+ {
932
+ "aria-label": `Resize ${header.column.id} column`,
933
+ className: classNames.resizeHandle,
934
+ "data-slot": "resize-handle",
935
+ onKeyDown: (event) => {
936
+ var _a3;
937
+ return handleResizeKeyDown(
938
+ event,
939
+ header.column.id,
940
+ (_a3 = columnSettings.sizing.minPx) != null ? _a3 : settings.columnDefaults.sizing.minPx,
941
+ columnSettings.sizing.maxPx
942
+ );
943
+ },
944
+ onPointerDown: (event) => {
945
+ var _a3;
946
+ return handleResizeStart(
947
+ event,
948
+ header.column.id,
949
+ (_a3 = columnSettings.sizing.minPx) != null ? _a3 : settings.columnDefaults.sizing.minPx,
950
+ columnSettings.sizing.maxPx
951
+ );
952
+ },
953
+ type: "button"
954
+ }
955
+ ) : null
956
+ ] })
860
957
  },
861
- cell.id
958
+ header.id
862
959
  );
863
960
  })
864
- }
865
- );
866
- const rowStyle = !isVirtualized ? void 0 : {
867
- position: "absolute",
868
- top: 0,
869
- left: 0,
870
- width: "100%",
871
- transform: `translateY(${virtualRow.start}px)`
872
- };
873
- if (!href || !rowLink || !RowLink || tree && row.getCanExpand())
874
- return /* @__PURE__ */ jsx4(
875
- "div",
876
- {
877
- "data-index": isVirtualized ? virtualRow.index : void 0,
878
- ref: isVirtualized ? rowVirtualizer.measureElement : void 0,
879
- style: rowStyle,
880
- children: rowContent
881
- },
882
- row.id
883
- );
884
- return /* @__PURE__ */ jsx4(
885
- "div",
886
- {
887
- "data-index": isVirtualized ? virtualRow.index : void 0,
888
- ref: isVirtualized ? rowVirtualizer.measureElement : void 0,
889
- style: rowStyle,
890
- children: /* @__PURE__ */ jsx4(
891
- RowLink,
961
+ },
962
+ headerGroup.id
963
+ ))
964
+ }
965
+ )
966
+ }
967
+ ),
968
+ /* @__PURE__ */ jsxs3(
969
+ "div",
970
+ {
971
+ "aria-label": "Table rows",
972
+ className: classNames.scroller,
973
+ "data-slot": "scroller",
974
+ ref: scrollerRef,
975
+ onScroll: handleScroll,
976
+ style: scrollerStyle,
977
+ tabIndex: 0,
978
+ children: [
979
+ isLoading ? /* @__PURE__ */ jsx4(
980
+ TableSkeleton,
981
+ {
982
+ columnCount: visibleColumns.length,
983
+ rows: skeletonRows,
984
+ label: skeletonLabel,
985
+ classNames
986
+ }
987
+ ) : rows.length === 0 ? /* @__PURE__ */ jsx4("div", { className: classNames.emptyState, "data-slot": "empty-state", role: "status", children: emptyState ? typeof emptyState === "function" ? emptyState(table) : emptyState : table.getState().columnFilters.length > 0 ? "No rows match the current filters." : "No rows to display." }) : /* @__PURE__ */ jsx4(
988
+ "div",
989
+ {
990
+ className: classNames.rowGroup,
991
+ "data-slot": "row-group",
992
+ style: __spreadProps(__spreadValues({}, gridStyle), {
993
+ height: isVirtualized ? `${virtualHeight}px` : "auto",
994
+ position: "relative"
995
+ }),
996
+ role: "rowgroup",
997
+ children: virtualRows.map((virtualRow) => {
998
+ var _a3;
999
+ const row = rows[virtualRow.index];
1000
+ const href = rowLink ? resolveRowHref(rowLink, row.original) : void 0;
1001
+ const RowLink = rowLink == null ? void 0 : rowLink.component;
1002
+ const rowContent = /* @__PURE__ */ jsx4(
1003
+ "div",
892
1004
  {
893
- className: (_a3 = classNames.rowLink) != null ? _a3 : "",
894
- href,
895
- "aria-label": resolveRowLinkLabel(rowLink, row.original),
896
- target: rowLink.target,
897
- rel: rowLink.rel,
898
- children: rowContent
1005
+ className: classNames.row,
1006
+ "data-slot": "row",
1007
+ role: "row",
1008
+ style: { display: "grid", gridTemplateColumns: "var(--boneless-table-grid)" },
1009
+ tabIndex: onRowClick && !href ? 0 : void 0,
1010
+ onClick: (event) => onRowClick == null ? void 0 : onRowClick(row.original, event),
1011
+ onKeyDown: (event) => {
1012
+ if (!onRowClick || href || event.key !== "Enter" && event.key !== " ")
1013
+ return;
1014
+ event.preventDefault();
1015
+ onRowClick(row.original, event);
1016
+ },
1017
+ children: row.getVisibleCells().map((cell) => {
1018
+ var _a4, _b3, _c2;
1019
+ const columnSettings = getColumnSettings(cell.column);
1020
+ const title = columnSettings.valueDisplay != null ? String((_a4 = cell.getValue()) != null ? _a4 : "") : void 0;
1021
+ const isTreeCell = Boolean(tree && cell.column.id === treeColumnId);
1022
+ const canExpand = row.getCanExpand();
1023
+ const cellContent = flexRender(cell.column.columnDef.cell, cell.getContext());
1024
+ return /* @__PURE__ */ jsx4(
1025
+ "div",
1026
+ {
1027
+ className: classNames.cell,
1028
+ "data-slot": "cell",
1029
+ "data-align": columnSettings.align,
1030
+ "data-borders": columnSettings.borders,
1031
+ role: "cell",
1032
+ title,
1033
+ children: isTreeCell ? /* @__PURE__ */ jsxs3(
1034
+ "div",
1035
+ {
1036
+ "data-slot": "tree-content",
1037
+ style: { paddingInlineStart: `${row.depth * treeIndentPx}px` },
1038
+ children: [
1039
+ canExpand ? /* @__PURE__ */ jsx4(
1040
+ "button",
1041
+ {
1042
+ "data-slot": "tree-row-toggle",
1043
+ type: "button",
1044
+ "aria-expanded": row.getIsExpanded(),
1045
+ "aria-label": `${row.getIsExpanded() ? "Collapse" : "Expand"} row`,
1046
+ onClick: (event) => {
1047
+ event.stopPropagation();
1048
+ row.toggleExpanded();
1049
+ },
1050
+ children: row.getIsExpanded() ? (_b3 = icons == null ? void 0 : icons.collapse) != null ? _b3 : "\u2212" : (_c2 = icons == null ? void 0 : icons.expand) != null ? _c2 : "+"
1051
+ }
1052
+ ) : /* @__PURE__ */ jsx4("span", { "data-slot": "tree-row-toggle-spacer", "aria-hidden": "true" }),
1053
+ /* @__PURE__ */ jsx4("span", { "data-slot": "tree-value", children: cellContent })
1054
+ ]
1055
+ }
1056
+ ) : cellContent
1057
+ },
1058
+ cell.id
1059
+ );
1060
+ })
899
1061
  }
900
- )
901
- },
902
- row.id
903
- );
904
- })
905
- }
906
- ),
907
- isFetchingMore ? /* @__PURE__ */ jsx4(
908
- TableSkeleton,
909
- {
910
- columnCount: visibleColumns.length,
911
- rows: 3,
912
- label: skeletonLabel,
913
- classNames
914
- }
915
- ) : null
916
- ]
917
- }
918
- )
919
- ]
920
- }
921
- ),
922
- renderSlot(footer, table, defaultFooter)
923
- ] });
1062
+ );
1063
+ const rowStyle = !isVirtualized ? void 0 : {
1064
+ position: "absolute",
1065
+ top: 0,
1066
+ left: 0,
1067
+ width: "100%",
1068
+ transform: `translateY(${virtualRow.start}px)`
1069
+ };
1070
+ if (!href || !rowLink || !RowLink || tree && row.getCanExpand())
1071
+ return /* @__PURE__ */ jsx4(
1072
+ "div",
1073
+ {
1074
+ "data-index": isVirtualized ? virtualRow.index : void 0,
1075
+ ref: isVirtualized ? rowVirtualizer.measureElement : void 0,
1076
+ style: rowStyle,
1077
+ children: rowContent
1078
+ },
1079
+ row.id
1080
+ );
1081
+ return /* @__PURE__ */ jsx4(
1082
+ "div",
1083
+ {
1084
+ "data-index": isVirtualized ? virtualRow.index : void 0,
1085
+ ref: isVirtualized ? rowVirtualizer.measureElement : void 0,
1086
+ style: rowStyle,
1087
+ children: /* @__PURE__ */ jsx4(
1088
+ RowLink,
1089
+ {
1090
+ className: (_a3 = classNames.rowLink) != null ? _a3 : "",
1091
+ href,
1092
+ "aria-label": resolveRowLinkLabel(rowLink, row.original),
1093
+ target: rowLink.target,
1094
+ rel: rowLink.rel,
1095
+ children: rowContent
1096
+ }
1097
+ )
1098
+ },
1099
+ row.id
1100
+ );
1101
+ })
1102
+ }
1103
+ ),
1104
+ isFetchingMore ? /* @__PURE__ */ jsx4(
1105
+ TableSkeleton,
1106
+ {
1107
+ columnCount: visibleColumns.length,
1108
+ rows: 3,
1109
+ label: skeletonLabel,
1110
+ classNames
1111
+ }
1112
+ ) : null
1113
+ ]
1114
+ }
1115
+ )
1116
+ ]
1117
+ }
1118
+ ),
1119
+ filterPlacement === "below" ? renderedFilters : null,
1120
+ renderSlot(footer, table, defaultFooter)
1121
+ ]
1122
+ }
1123
+ );
924
1124
  }
925
1125
 
926
1126
  // src/index.ts