@undevy-org/tablewright 0.8.1 → 0.9.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.
@@ -1,3 +1,46 @@
1
+ function columnWidthPx(columnId, meta, columnWidths) {
2
+ return columnWidths?.[columnId] ?? meta[columnId]?.minW ?? 0;
3
+ }
4
+ function applyColumnPinningToMeta(baseMeta, pinning, options) {
5
+ const order = options?.columnOrder ?? Object.keys(baseMeta);
6
+ const pinnedLeft = new Set(pinning.left ?? []);
7
+ const pinnedRight = new Set(pinning.right ?? []);
8
+ const result = {};
9
+ for (const [id, meta] of Object.entries(baseMeta)) {
10
+ if (pinnedLeft.has(id)) {
11
+ result[id] = { ...meta, sticky: "left" };
12
+ } else if (pinnedRight.has(id)) {
13
+ result[id] = { ...meta, sticky: "right" };
14
+ } else {
15
+ result[id] = { ...meta };
16
+ }
17
+ }
18
+ let leftOffset = 0;
19
+ for (const id of order) {
20
+ const meta = result[id];
21
+ if (meta?.sticky !== "left") continue;
22
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
23
+ if (pinnedLeft.has(id)) {
24
+ result[id] = { ...meta, stickyOffset: leftOffset };
25
+ leftOffset += width;
26
+ } else {
27
+ leftOffset = (meta.stickyOffset ?? 0) + width;
28
+ }
29
+ }
30
+ let rightOffset = 0;
31
+ for (const id of [...order].reverse()) {
32
+ const meta = result[id];
33
+ if (meta?.sticky !== "right") continue;
34
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
35
+ if (pinnedRight.has(id)) {
36
+ result[id] = { ...meta, stickyOffset: rightOffset };
37
+ rightOffset += width;
38
+ } else {
39
+ rightOffset = (meta.stickyOffset ?? 0) + width;
40
+ }
41
+ }
42
+ return result;
43
+ }
1
44
  function stickyStyle(meta, isHeader) {
2
45
  if (isHeader) {
3
46
  return {
@@ -20,6 +63,8 @@ function stickyBorderClass(meta) {
20
63
  return edge === "right" ? "[box-shadow:1px_0_0_0_var(--border-subtle)]" : "[box-shadow:-1px_0_0_0_var(--border-subtle)]";
21
64
  }
22
65
  export {
66
+ applyColumnPinningToMeta,
67
+ columnWidthPx,
23
68
  stickyBorderClass,
24
69
  stickyStyle
25
70
  };
@@ -28,6 +28,7 @@ function createTableOrchestrationStore({
28
28
  requestOpenFilterId: null,
29
29
  currentSort: null,
30
30
  columnVisibility: initialColumnVisibility ?? {},
31
+ columnPinning: { left: [], right: [] },
31
32
  page: 1,
32
33
  rowsPerPage: initialRowsPerPage,
33
34
  density: "normal",
@@ -108,6 +109,19 @@ function createTableOrchestrationStore({
108
109
  set({ columnVisibility: {} });
109
110
  goCustom();
110
111
  },
112
+ pinColumn: (columnId, side) => set((s) => {
113
+ const left = (s.columnPinning.left ?? []).filter((id) => id !== columnId);
114
+ const right = (s.columnPinning.right ?? []).filter((id) => id !== columnId);
115
+ if (side === "left") left.push(columnId);
116
+ else right.push(columnId);
117
+ return { columnPinning: { left, right } };
118
+ }),
119
+ unpinColumn: (columnId) => set((s) => ({
120
+ columnPinning: {
121
+ left: (s.columnPinning.left ?? []).filter((id) => id !== columnId),
122
+ right: (s.columnPinning.right ?? []).filter((id) => id !== columnId)
123
+ }
124
+ })),
111
125
  setPage: (p) => set({ page: p }),
112
126
  setRowsPerPage: (n) => set({ rowsPerPage: n, page: 1 }),
113
127
  setDensity: (v) => set({ density: v }),
@@ -1,6 +1,7 @@
1
1
  import { useState, useRef, useMemo, useCallback, useLayoutEffect, useEffect } from "react";
2
2
  import { useStore } from "zustand";
3
3
  import { useColumnResize } from "../components/data-table/hooks/use-column-resize.js";
4
+ import { applyColumnPinningToMeta } from "../components/data-table/sticky-utils.js";
4
5
  import { getVisiblePageNumbers } from "../components/data-table/hooks/use-table-pagination.js";
5
6
  import { createTableOrchestrationStore } from "./table-orchestration-store.js";
6
7
  import { useColumnFilterStats } from "./table-orchestration/use-column-filter-stats.js";
@@ -8,6 +9,26 @@ import { useColumnGaps } from "./table-orchestration/use-column-gaps.js";
8
9
  import { useFilteredRows } from "./table-orchestration/use-filtered-rows.js";
9
10
  import { useOrchestrationActions } from "./table-orchestration/use-orchestration-actions.js";
10
11
  import { useResponsiveColumns } from "./table-orchestration/use-responsive-columns.js";
12
+ import { gapColumnId } from "../lib/column-gaps.js";
13
+ function computeVisibleColumnOrder(input) {
14
+ const { columnMeta, toggleableColumnIds, columnVisibility, columnGaps } = input;
15
+ const order = [];
16
+ if (columnMeta.rowControl) order.push("rowControl");
17
+ for (const id of toggleableColumnIds) {
18
+ if (columnVisibility[id] !== false) order.push(id);
19
+ }
20
+ for (const gap of columnGaps) {
21
+ const gapId = gapColumnId(gap.afterColumnId);
22
+ if (gap.afterColumnId === null) {
23
+ order.unshift(gapId);
24
+ } else {
25
+ const afterIdx = order.indexOf(gap.afterColumnId);
26
+ if (afterIdx >= 0) order.splice(afterIdx + 1, 0, gapId);
27
+ }
28
+ }
29
+ if (columnMeta.actions) order.push("actions");
30
+ return order;
31
+ }
11
32
  const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
12
33
  function useTableOrchestration(config) {
13
34
  const {
@@ -40,6 +61,9 @@ function useTableOrchestration(config) {
40
61
  const requestOpenFilterId = useStore(store, (s) => s.requestOpenFilterId);
41
62
  const currentSort = useStore(store, (s) => s.currentSort);
42
63
  const columnVisibility = useStore(store, (s) => s.columnVisibility);
64
+ const columnPinning = useStore(store, (s) => s.columnPinning);
65
+ const pinColumn = useStore(store, (s) => s.pinColumn);
66
+ const unpinColumn = useStore(store, (s) => s.unpinColumn);
43
67
  const storedPage = useStore(store, (s) => s.page);
44
68
  const rowsPerPage = useStore(store, (s) => s.rowsPerPage);
45
69
  const density = useStore(store, (s) => s.density);
@@ -120,13 +144,29 @@ function useTableOrchestration(config) {
120
144
  );
121
145
  const allOnPageSelected = pageIds.size > 0 && selectedOnPage === pageIds.size;
122
146
  const headerCheckState = allOnPageSelected ? true : selectedOnPage > 0 ? "indeterminate" : false;
123
- const { columnGaps, extendedColumnMeta, extendedWidths } = useColumnGaps({
147
+ const { columnGaps, extendedColumnMeta: gapExtendedColumnMeta, extendedWidths } = useColumnGaps({
124
148
  columnGroups,
125
149
  toggleableColumnIds,
126
150
  columnVisibility,
127
151
  columnMeta,
128
152
  widths
129
153
  });
154
+ const visibleColumnOrder = useMemo(
155
+ () => computeVisibleColumnOrder({
156
+ columnMeta,
157
+ toggleableColumnIds,
158
+ columnVisibility,
159
+ columnGaps
160
+ }),
161
+ [columnGaps, columnMeta, columnVisibility, toggleableColumnIds]
162
+ );
163
+ const extendedColumnMeta = useMemo(
164
+ () => applyColumnPinningToMeta(gapExtendedColumnMeta, columnPinning, {
165
+ columnOrder: visibleColumnOrder,
166
+ columnWidths: extendedWidths
167
+ }),
168
+ [columnPinning, extendedWidths, gapExtendedColumnMeta, visibleColumnOrder]
169
+ );
130
170
  const hiddenColumnsCount = useMemo(() => {
131
171
  return toggleableColumnIds.filter((id) => columnVisibility[id] === false).length;
132
172
  }, [toggleableColumnIds, columnVisibility]);
@@ -185,6 +225,9 @@ function useTableOrchestration(config) {
185
225
  currentSort,
186
226
  columnVisibility,
187
227
  setColumnVisibility,
228
+ columnPinning,
229
+ pinColumn,
230
+ unpinColumn,
188
231
  page,
189
232
  rowsPerPage,
190
233
  density,
@@ -249,5 +292,6 @@ function useTableOrchestration(config) {
249
292
  };
250
293
  }
251
294
  export {
295
+ computeVisibleColumnOrder,
252
296
  useTableOrchestration
253
297
  };
package/dist/es/index.js CHANGED
@@ -67,6 +67,7 @@ import { useTableOrchestration } from "./hooks/useTableOrchestration.js";
67
67
  import { createTableOrchestrationStore, defaultValueForFilter } from "./hooks/table-orchestration-store.js";
68
68
  import { useRowDragOverride } from "./hooks/useRowDragOverride.js";
69
69
  import { downloadCsvFile, exportTableToCsv } from "./lib/export-table-csv.js";
70
+ import { applyColumnPinningToMeta } from "./components/data-table/sticky-utils.js";
70
71
  import { computeColumnGaps, gapColumnId } from "./lib/column-gaps.js";
71
72
  import { computeColumnStats } from "./lib/filter-stats.js";
72
73
  import { formatEnumLabel } from "./lib/format-enum.js";
@@ -176,6 +177,7 @@ export {
176
177
  TopBar,
177
178
  TwoPanelMenuBase,
178
179
  ViewChip,
180
+ applyColumnPinningToMeta,
179
181
  applyManagedChanges,
180
182
  badgeVariants,
181
183
  buttonVariants,
package/dist/styles.css CHANGED
@@ -402,6 +402,9 @@
402
402
  .collapse {
403
403
  visibility: collapse
404
404
  }
405
+ .static {
406
+ position: static
407
+ }
405
408
  .fixed {
406
409
  position: fixed
407
410
  }
@@ -931,6 +931,49 @@ function ScrollArea({ children, className }) {
931
931
  );
932
932
  }
933
933
  TableLayout.ScrollArea = ScrollArea;
934
+ function columnWidthPx(columnId, meta, columnWidths) {
935
+ return columnWidths?.[columnId] ?? meta[columnId]?.minW ?? 0;
936
+ }
937
+ function applyColumnPinningToMeta(baseMeta, pinning, options) {
938
+ const order = options?.columnOrder ?? Object.keys(baseMeta);
939
+ const pinnedLeft = new Set(pinning.left ?? []);
940
+ const pinnedRight = new Set(pinning.right ?? []);
941
+ const result = {};
942
+ for (const [id, meta] of Object.entries(baseMeta)) {
943
+ if (pinnedLeft.has(id)) {
944
+ result[id] = { ...meta, sticky: "left" };
945
+ } else if (pinnedRight.has(id)) {
946
+ result[id] = { ...meta, sticky: "right" };
947
+ } else {
948
+ result[id] = { ...meta };
949
+ }
950
+ }
951
+ let leftOffset = 0;
952
+ for (const id of order) {
953
+ const meta = result[id];
954
+ if (meta?.sticky !== "left") continue;
955
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
956
+ if (pinnedLeft.has(id)) {
957
+ result[id] = { ...meta, stickyOffset: leftOffset };
958
+ leftOffset += width;
959
+ } else {
960
+ leftOffset = (meta.stickyOffset ?? 0) + width;
961
+ }
962
+ }
963
+ let rightOffset = 0;
964
+ for (const id of [...order].reverse()) {
965
+ const meta = result[id];
966
+ if (meta?.sticky !== "right") continue;
967
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
968
+ if (pinnedRight.has(id)) {
969
+ result[id] = { ...meta, stickyOffset: rightOffset };
970
+ rightOffset += width;
971
+ } else {
972
+ rightOffset = (meta.stickyOffset ?? 0) + width;
973
+ }
974
+ }
975
+ return result;
976
+ }
934
977
  function stickyStyle(meta, isHeader) {
935
978
  if (isHeader) {
936
979
  return {
@@ -4868,6 +4911,7 @@ function createTableOrchestrationStore({
4868
4911
  requestOpenFilterId: null,
4869
4912
  currentSort: null,
4870
4913
  columnVisibility: initialColumnVisibility ?? {},
4914
+ columnPinning: { left: [], right: [] },
4871
4915
  page: 1,
4872
4916
  rowsPerPage: initialRowsPerPage,
4873
4917
  density: "normal",
@@ -4948,6 +4992,19 @@ function createTableOrchestrationStore({
4948
4992
  set({ columnVisibility: {} });
4949
4993
  goCustom();
4950
4994
  },
4995
+ pinColumn: (columnId, side) => set((s) => {
4996
+ const left = (s.columnPinning.left ?? []).filter((id) => id !== columnId);
4997
+ const right = (s.columnPinning.right ?? []).filter((id) => id !== columnId);
4998
+ if (side === "left") left.push(columnId);
4999
+ else right.push(columnId);
5000
+ return { columnPinning: { left, right } };
5001
+ }),
5002
+ unpinColumn: (columnId) => set((s) => ({
5003
+ columnPinning: {
5004
+ left: (s.columnPinning.left ?? []).filter((id) => id !== columnId),
5005
+ right: (s.columnPinning.right ?? []).filter((id) => id !== columnId)
5006
+ }
5007
+ })),
4951
5008
  setPage: (p) => set({ page: p }),
4952
5009
  setRowsPerPage: (n) => set({ rowsPerPage: n, page: 1 }),
4953
5010
  setDensity: (v) => set({ density: v }),
@@ -5372,6 +5429,25 @@ function useResponsiveColumns(columnPriorities, wideBreakpoint, compactBreakpoin
5372
5429
  }, [isWide, columnPriorities]);
5373
5430
  return { responsiveHiddenColumnIds, isCompact };
5374
5431
  }
5432
+ function computeVisibleColumnOrder(input) {
5433
+ const { columnMeta, toggleableColumnIds, columnVisibility, columnGaps } = input;
5434
+ const order = [];
5435
+ if (columnMeta.rowControl) order.push("rowControl");
5436
+ for (const id of toggleableColumnIds) {
5437
+ if (columnVisibility[id] !== false) order.push(id);
5438
+ }
5439
+ for (const gap of columnGaps) {
5440
+ const gapId = gapColumnId(gap.afterColumnId);
5441
+ if (gap.afterColumnId === null) {
5442
+ order.unshift(gapId);
5443
+ } else {
5444
+ const afterIdx = order.indexOf(gap.afterColumnId);
5445
+ if (afterIdx >= 0) order.splice(afterIdx + 1, 0, gapId);
5446
+ }
5447
+ }
5448
+ if (columnMeta.actions) order.push("actions");
5449
+ return order;
5450
+ }
5375
5451
  const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
5376
5452
  function useTableOrchestration(config) {
5377
5453
  const {
@@ -5404,6 +5480,9 @@ function useTableOrchestration(config) {
5404
5480
  const requestOpenFilterId = zustand.useStore(store, (s) => s.requestOpenFilterId);
5405
5481
  const currentSort = zustand.useStore(store, (s) => s.currentSort);
5406
5482
  const columnVisibility = zustand.useStore(store, (s) => s.columnVisibility);
5483
+ const columnPinning = zustand.useStore(store, (s) => s.columnPinning);
5484
+ const pinColumn = zustand.useStore(store, (s) => s.pinColumn);
5485
+ const unpinColumn = zustand.useStore(store, (s) => s.unpinColumn);
5407
5486
  const storedPage = zustand.useStore(store, (s) => s.page);
5408
5487
  const rowsPerPage = zustand.useStore(store, (s) => s.rowsPerPage);
5409
5488
  const density = zustand.useStore(store, (s) => s.density);
@@ -5484,13 +5563,29 @@ function useTableOrchestration(config) {
5484
5563
  );
5485
5564
  const allOnPageSelected = pageIds.size > 0 && selectedOnPage === pageIds.size;
5486
5565
  const headerCheckState = allOnPageSelected ? true : selectedOnPage > 0 ? "indeterminate" : false;
5487
- const { columnGaps, extendedColumnMeta, extendedWidths } = useColumnGaps({
5566
+ const { columnGaps, extendedColumnMeta: gapExtendedColumnMeta, extendedWidths } = useColumnGaps({
5488
5567
  columnGroups,
5489
5568
  toggleableColumnIds,
5490
5569
  columnVisibility,
5491
5570
  columnMeta,
5492
5571
  widths
5493
5572
  });
5573
+ const visibleColumnOrder = React.useMemo(
5574
+ () => computeVisibleColumnOrder({
5575
+ columnMeta,
5576
+ toggleableColumnIds,
5577
+ columnVisibility,
5578
+ columnGaps
5579
+ }),
5580
+ [columnGaps, columnMeta, columnVisibility, toggleableColumnIds]
5581
+ );
5582
+ const extendedColumnMeta = React.useMemo(
5583
+ () => applyColumnPinningToMeta(gapExtendedColumnMeta, columnPinning, {
5584
+ columnOrder: visibleColumnOrder,
5585
+ columnWidths: extendedWidths
5586
+ }),
5587
+ [columnPinning, extendedWidths, gapExtendedColumnMeta, visibleColumnOrder]
5588
+ );
5494
5589
  const hiddenColumnsCount = React.useMemo(() => {
5495
5590
  return toggleableColumnIds.filter((id) => columnVisibility[id] === false).length;
5496
5591
  }, [toggleableColumnIds, columnVisibility]);
@@ -5549,6 +5644,9 @@ function useTableOrchestration(config) {
5549
5644
  currentSort,
5550
5645
  columnVisibility,
5551
5646
  setColumnVisibility,
5647
+ columnPinning,
5648
+ pinColumn,
5649
+ unpinColumn,
5552
5650
  page,
5553
5651
  rowsPerPage,
5554
5652
  density,
@@ -6089,6 +6187,7 @@ exports.TimestampCell = TimestampCell;
6089
6187
  exports.TopBar = TopBar;
6090
6188
  exports.TwoPanelMenuBase = TwoPanelMenuBase;
6091
6189
  exports.ViewChip = ViewChip;
6190
+ exports.applyColumnPinningToMeta = applyColumnPinningToMeta;
6092
6191
  exports.applyManagedChanges = applyManagedChanges;
6093
6192
  exports.badgeVariants = badgeVariants;
6094
6193
  exports.buttonVariants = buttonVariants;
@@ -250,6 +250,9 @@
250
250
  .collapse {
251
251
  visibility: collapse
252
252
  }
253
+ .static {
254
+ position: static
255
+ }
253
256
  .fixed {
254
257
  position: fixed
255
258
  }
@@ -1,4 +1,15 @@
1
+ import type { ColumnPinningState } from "@tanstack/react-table";
1
2
  import type { CSSProperties } from "react";
2
3
  import type { ColumnMetaDef } from "./types.js";
4
+ export interface ApplyColumnPinningToMetaOptions {
5
+ /** Visible columns in left-to-right display order (include gap columns when present). */
6
+ columnOrder: readonly string[];
7
+ /** Resolved widths in px; falls back to `meta.minW`. */
8
+ columnWidths?: Record<string, number>;
9
+ }
10
+ /** @internal Exported for unit tests (not re-exported from package entry). */
11
+ export declare function columnWidthPx(columnId: string, meta: Record<string, ColumnMetaDef>, columnWidths?: Record<string, number>): number;
12
+ /** Merges TanStack `ColumnPinningState` into column meta sticky fields for rendering. */
13
+ export declare function applyColumnPinningToMeta(baseMeta: Record<string, ColumnMetaDef>, pinning: ColumnPinningState, options?: ApplyColumnPinningToMetaOptions): Record<string, ColumnMetaDef>;
3
14
  export declare function stickyStyle(meta: ColumnMetaDef | undefined, isHeader: boolean): CSSProperties | undefined;
4
15
  export declare function stickyBorderClass(meta: ColumnMetaDef | undefined): string;
@@ -1,4 +1,4 @@
1
- import type { VisibilityState } from "@tanstack/react-table";
1
+ import type { ColumnPinningState, VisibilityState } from "@tanstack/react-table";
2
2
  import type { ActiveFilter, ActiveFilterValue, ColumnFilterConfig } from "../components/data-table/filter-types.js";
3
3
  export interface TableOrchestrationState<TViewKey extends string = string> {
4
4
  searchInput: string;
@@ -11,6 +11,7 @@ export interface TableOrchestrationState<TViewKey extends string = string> {
11
11
  direction: "asc" | "desc";
12
12
  } | null;
13
13
  columnVisibility: VisibilityState;
14
+ columnPinning: ColumnPinningState;
14
15
  page: number;
15
16
  rowsPerPage: number;
16
17
  density: "normal" | "dense";
@@ -39,6 +40,8 @@ export interface TableOrchestrationActions<TViewKey extends string = string> {
39
40
  hideColumn: (columnId: string) => void;
40
41
  showColumns: (columnIds: string[]) => void;
41
42
  resetColumns: () => void;
43
+ pinColumn: (columnId: string, side: "left" | "right") => void;
44
+ unpinColumn: (columnId: string) => void;
42
45
  setPage: (p: number) => void;
43
46
  setRowsPerPage: (n: number) => void;
44
47
  setDensity: (v: "normal" | "dense") => void;
@@ -1,7 +1,14 @@
1
- import type { VisibilityState } from "@tanstack/react-table";
1
+ import type { ColumnPinningState, VisibilityState } from "@tanstack/react-table";
2
2
  import type { ColumnMetaDef, SortDirection } from "../components/data-table/types.js";
3
3
  import type { ActiveFilter, ActiveFilterValue, ColumnFilterConfig, ColumnGap, ColumnGroup, FilterStatsSlice, ViewPreset } from "../components/data-table/filter-types.js";
4
4
  import { type TableOrchestrationStoreApi } from "./table-orchestration-store.js";
5
+ /** @internal Exported for unit tests (not re-exported from package entry). */
6
+ export declare function computeVisibleColumnOrder(input: {
7
+ columnMeta: Record<string, ColumnMetaDef>;
8
+ toggleableColumnIds: readonly string[];
9
+ columnVisibility: VisibilityState;
10
+ columnGaps: ColumnGap[];
11
+ }): string[];
5
12
  export interface TableOrchestrationConfig<TRow extends object, TViewKey extends string> {
6
13
  rows: TRow[];
7
14
  getRowId: (row: TRow) => string;
@@ -49,6 +56,9 @@ export interface TableOrchestrationReturn<TRow extends object, TViewKey extends
49
56
  } | null;
50
57
  columnVisibility: VisibilityState;
51
58
  setColumnVisibility: React.Dispatch<React.SetStateAction<VisibilityState>>;
59
+ columnPinning: ColumnPinningState;
60
+ pinColumn: (columnId: string, side: "left" | "right") => void;
61
+ unpinColumn: (columnId: string) => void;
52
62
  page: number;
53
63
  rowsPerPage: number;
54
64
  density: "normal" | "dense";
@@ -104,6 +104,8 @@ export { useRowDragOverride } from "./hooks/useRowDragOverride.js";
104
104
  export type { UseRowDragOverrideResult } from "./hooks/useRowDragOverride.js";
105
105
  export { downloadCsvFile, exportTableToCsv, } from "./lib/export-table-csv.js";
106
106
  export type { DownloadCsvFileOptions, ExportTableToCsvOptions, } from "./lib/export-table-csv.js";
107
+ export { applyColumnPinningToMeta } from "./components/data-table/sticky-utils.js";
108
+ export type { ApplyColumnPinningToMetaOptions } from "./components/data-table/sticky-utils.js";
107
109
  export { computeColumnGaps, gapColumnId } from "./lib/column-gaps.js";
108
110
  export { computeColumnStats } from "./lib/filter-stats.js";
109
111
  export { formatEnumLabel } from "./lib/format-enum.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undevy-org/tablewright",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "description": "React data-table and application-shell components: filters, drawers, row controls, sortable and resizable columns, built on Radix UI and TanStack Table.",
5
5
  "license": "MIT",
6
6
  "repository": {