boneless-table 0.1.0 → 0.1.1

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;
@@ -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,13 @@ 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 lastScrollLeftRef = useRef3(0);
429
+ const columnsButtonRef = useRef3(null);
430
+ const nearEndTriggeredRef = useRef3(false);
423
431
  const columnMenuId = useId();
424
432
  const [showColumns, setShowColumns] = useState2(false);
425
- const [headerScrollbarGutter, setHeaderScrollbarGutter] = useState2(0);
426
433
  const [sorting, setSorting] = useState2((_b2 = (_a2 = tableOptions.initialState) == null ? void 0 : _a2.sorting) != null ? _b2 : []);
427
434
  const [columnFilters, setColumnFilters] = useState2(
428
435
  (_d = (_c = tableOptions.initialState) == null ? void 0 : _c.columnFilters) != null ? _d : []
@@ -467,18 +474,6 @@ function BonelessTable(_a) {
467
474
  var _a3;
468
475
  if (scrollToTopOn !== void 0) (_a3 = scrollerRef.current) == null ? void 0 : _a3.scrollTo({ top: 0 });
469
476
  }, [scrollToTopOn]);
470
- useEffect3(() => {
471
- const scroller2 = scrollerRef.current;
472
- if (!scroller2) return;
473
- const syncHeaderScrollbarGutter = () => {
474
- setHeaderScrollbarGutter(Math.max(0, scroller2.offsetWidth - scroller2.clientWidth));
475
- };
476
- syncHeaderScrollbarGutter();
477
- if (typeof ResizeObserver === "undefined") return;
478
- const observer = new ResizeObserver(syncHeaderScrollbarGutter);
479
- observer.observe(scroller2);
480
- return () => observer.disconnect();
481
- }, []);
482
477
  const table = useReactTable(__spreadProps(__spreadValues({}, restTableOptions), {
483
478
  data,
484
479
  columns: resolvedColumns,
@@ -547,13 +542,17 @@ function BonelessTable(_a) {
547
542
  function handleScroll() {
548
543
  const element = scrollerRef.current;
549
544
  if (!element) return;
550
- if (headerScrollerRef.current) headerScrollerRef.current.scrollLeft = element.scrollLeft;
545
+ if (element.scrollLeft !== lastScrollLeftRef.current) {
546
+ lastScrollLeftRef.current = element.scrollLeft;
547
+ if (headerScrollerRef.current) headerScrollerRef.current.scrollLeft = element.scrollLeft;
548
+ }
549
+ if (!onNearEnd) return;
551
550
  const isNearEnd = element.scrollHeight - element.scrollTop - element.clientHeight < nearEndOffset;
552
551
  if (!isNearEnd) {
553
552
  nearEndTriggeredRef.current = false;
554
553
  return;
555
554
  }
556
- if (!onNearEnd || !canLoadMore || isFetchingMore || nearEndTriggeredRef.current) return;
555
+ if (!canLoadMore || isFetchingMore || nearEndTriggeredRef.current) return;
557
556
  nearEndTriggeredRef.current = true;
558
557
  onNearEnd();
559
558
  }
@@ -596,10 +595,13 @@ function BonelessTable(_a) {
596
595
  (_a3 = columnsButtonRef.current) == null ? void 0 : _a3.focus();
597
596
  }
598
597
  const toolbarSummary = /* @__PURE__ */ jsxs3("div", { className: classNames.toolbarSummary, "data-slot": "toolbar-summary", children: [
599
- /* @__PURE__ */ jsx4("strong", { children: total.toLocaleString() }),
598
+ /* @__PURE__ */ jsx4("strong", { className: classNames.toolbarCount, "data-slot": "toolbar-count", children: total.toLocaleString() }),
600
599
  " ",
601
- resultLabel,
602
- resultHint ? /* @__PURE__ */ jsx4("span", { className: classNames.toolbarHint, "data-slot": "toolbar-hint", children: resultHint }) : null
600
+ /* @__PURE__ */ jsx4("span", { className: classNames.toolbarLabel, "data-slot": "toolbar-label", children: resultLabel }),
601
+ resultHint ? /* @__PURE__ */ jsxs3(Fragment, { children: [
602
+ " ",
603
+ /* @__PURE__ */ jsx4("span", { className: classNames.toolbarHint, "data-slot": "toolbar-hint", children: resultHint })
604
+ ] }) : null
603
605
  ] });
604
606
  const toolbarActions = /* @__PURE__ */ jsxs3("div", { className: classNames.toolbarActions, "data-slot": "toolbar-actions", children: [
605
607
  tree && expandableRows.length > 0 ? /* @__PURE__ */ jsxs3(
@@ -651,6 +653,43 @@ function BonelessTable(_a) {
651
653
  toolbarSummary,
652
654
  toolbarActions
653
655
  ] });
656
+ const filterHeaders = table.getLeafHeaders().filter((header) => !header.isPlaceholder && getColumnSettings(header.column).filtering);
657
+ const defaultFilters = filterHeaders.length > 0 ? /* @__PURE__ */ jsx4(
658
+ "div",
659
+ {
660
+ "aria-label": "Table filters",
661
+ className: classNames.filters,
662
+ "data-slot": "filters",
663
+ role: "group",
664
+ children: filterHeaders.map((header) => {
665
+ var _a3, _b3;
666
+ const filterSettings = getColumnSettings(header.column).filtering;
667
+ return /* @__PURE__ */ jsxs3(
668
+ "div",
669
+ {
670
+ className: classNames.filterItem,
671
+ "data-column-id": header.column.id,
672
+ "data-slot": "filter-item",
673
+ children: [
674
+ /* @__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 }),
675
+ /* @__PURE__ */ jsx4(
676
+ ColumnFilter,
677
+ {
678
+ className: classNames.filter,
679
+ column: header.column,
680
+ settings: filterSettings,
681
+ icon: filterSettings.type === "text" ? icons == null ? void 0 : icons.search : void 0,
682
+ debounceMs: filterDebounceMs
683
+ }
684
+ )
685
+ ]
686
+ },
687
+ header.id
688
+ );
689
+ })
690
+ }
691
+ ) : null;
692
+ const renderedFilters = renderSlot(filters, table, defaultFilters);
654
693
  const defaultFooter = /* @__PURE__ */ jsxs3("footer", { className: classNames.footer, "data-slot": "footer", children: [
655
694
  "Showing ",
656
695
  shown.toLocaleString(),
@@ -661,13 +700,21 @@ function BonelessTable(_a) {
661
700
  ] });
662
701
  const rootStyle = scroller === "fill" ? { display: "flex", flexDirection: "column", minHeight: 0 } : void 0;
663
702
  const scrollerStyle = __spreadValues({
664
- overflowX: settings.interactions.horizontalOverflow
703
+ overflowX: settings.interactions.horizontalOverflow,
704
+ scrollbarGutter: "stable"
665
705
  }, scroller === "fill" ? { flex: "1 1 auto", minHeight: 0, overflowY: "auto" } : {});
706
+ const headerScrollerStyle = {
707
+ flex: "0 0 auto",
708
+ minWidth: 0,
709
+ overflow: "hidden",
710
+ scrollbarGutter: "stable"
711
+ };
666
712
  const tableStyle = __spreadValues({
667
713
  "--boneless-table-grid": grid
668
714
  }, scroller === "fill" ? { display: "flex", flexDirection: "column", flex: "1 1 auto", minHeight: 0 } : {});
669
715
  return /* @__PURE__ */ jsxs3("div", { className: cn(className, classNames.root), "data-slot": "root", style: rootStyle, children: [
670
716
  renderSlot(toolbar, table, defaultToolbar),
717
+ filterPlacement === "above" ? renderedFilters : null,
671
718
  /* @__PURE__ */ jsxs3(
672
719
  "div",
673
720
  {
@@ -680,14 +727,10 @@ function BonelessTable(_a) {
680
727
  /* @__PURE__ */ jsx4(
681
728
  "div",
682
729
  {
730
+ className: classNames.headerScroller,
683
731
  "data-slot": "header-scroller",
684
732
  ref: headerScrollerRef,
685
- style: {
686
- flex: "0 0 auto",
687
- minWidth: 0,
688
- overflow: "hidden",
689
- paddingInlineEnd: headerScrollbarGutter || void 0
690
- },
733
+ style: headerScrollerStyle,
691
734
  children: /* @__PURE__ */ jsx4(
692
735
  "div",
693
736
  {
@@ -703,11 +746,10 @@ function BonelessTable(_a) {
703
746
  role: "row",
704
747
  style: { display: "grid", gridTemplateColumns: "var(--boneless-table-grid)" },
705
748
  children: headerGroup.headers.map((header) => {
706
- var _a3;
707
749
  const columnSettings = getColumnSettings(header.column);
708
750
  const direction = header.column.getIsSorted();
709
751
  const isSortable = !header.isPlaceholder && header.column.getCanSort() && columnSettings.sorting.enabled;
710
- return /* @__PURE__ */ jsxs3(
752
+ return /* @__PURE__ */ jsx4(
711
753
  "div",
712
754
  {
713
755
  className: classNames.header,
@@ -718,47 +760,28 @@ function BonelessTable(_a) {
718
760
  "aria-colspan": header.colSpan > 1 ? header.colSpan : void 0,
719
761
  "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : isSortable ? "none" : void 0,
720
762
  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,
763
+ children: header.isPlaceholder ? null : isSortable ? /* @__PURE__ */ jsxs3(
764
+ "button",
765
+ {
766
+ className: classNames.headerButton,
767
+ "data-align": columnSettings.align,
768
+ "data-slot": "header-button",
769
+ onClick: header.column.getToggleSortingHandler(),
770
+ children: [
771
+ /* @__PURE__ */ jsx4("span", { className: classNames.headerLabel, "data-slot": "header-label", children: flexRender(header.column.columnDef.header, header.getContext()) }),
772
+ (icons == null ? void 0 : icons.sort) ? /* @__PURE__ */ jsx4(
773
+ "span",
752
774
  {
753
- column: header.column,
754
- settings: columnSettings.filtering,
755
- icon: icons == null ? void 0 : icons.search,
756
- debounceMs: filterDebounceMs
775
+ className: classNames.sortIndicator,
776
+ "data-align": columnSettings.align,
777
+ "data-slot": "sort-indicator",
778
+ "data-reveal": columnSettings.sorting.reveal,
779
+ children: icons.sort(direction)
757
780
  }
758
- )
759
- }
760
- ) : null
761
- ]
781
+ ) : null
782
+ ]
783
+ }
784
+ ) : flexRender(header.column.columnDef.header, header.getContext())
762
785
  },
763
786
  header.id
764
787
  );
@@ -773,11 +796,13 @@ function BonelessTable(_a) {
773
796
  /* @__PURE__ */ jsxs3(
774
797
  "div",
775
798
  {
799
+ "aria-label": "Table rows",
776
800
  className: classNames.scroller,
777
801
  "data-slot": "scroller",
778
802
  ref: scrollerRef,
779
803
  onScroll: handleScroll,
780
804
  style: scrollerStyle,
805
+ tabIndex: 0,
781
806
  children: [
782
807
  isLoading ? /* @__PURE__ */ jsx4(
783
808
  TableSkeleton,
@@ -919,6 +944,7 @@ function BonelessTable(_a) {
919
944
  ]
920
945
  }
921
946
  ),
947
+ filterPlacement === "below" ? renderedFilters : null,
922
948
  renderSlot(footer, table, defaultFooter)
923
949
  ] });
924
950
  }