boneless-table 0.1.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/index.mjs ADDED
@@ -0,0 +1,959 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+ var __objRest = (source, exclude) => {
22
+ var target = {};
23
+ for (var prop in source)
24
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
+ target[prop] = source[prop];
26
+ if (source != null && __getOwnPropSymbols)
27
+ for (var prop of __getOwnPropSymbols(source)) {
28
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
+ target[prop] = source[prop];
30
+ }
31
+ return target;
32
+ };
33
+
34
+ // src/boneless-table.tsx
35
+ import {
36
+ flexRender,
37
+ getExpandedRowModel,
38
+ getCoreRowModel,
39
+ getFilteredRowModel,
40
+ getSortedRowModel,
41
+ useReactTable
42
+ } from "@tanstack/react-table";
43
+ import { useVirtualizer } from "@tanstack/react-virtual";
44
+ import {
45
+ useEffect as useEffect3,
46
+ useId,
47
+ useMemo,
48
+ useRef as useRef2,
49
+ useState as useState2
50
+ } from "react";
51
+
52
+ // src/column-filter.tsx
53
+ import { useEffect, useState } from "react";
54
+ import { jsx, jsxs } from "react/jsx-runtime";
55
+ function ColumnFilter({
56
+ column,
57
+ settings,
58
+ className,
59
+ icon,
60
+ debounceMs = 0
61
+ }) {
62
+ var _a, _b;
63
+ const value = String((_a = column.getFilterValue()) != null ? _a : "");
64
+ const [inputValue, setInputValue] = useState(value);
65
+ useEffect(() => {
66
+ setInputValue(value);
67
+ }, [value]);
68
+ useEffect(() => {
69
+ if (settings.type !== "text" || inputValue === value) return;
70
+ if (debounceMs <= 0) {
71
+ column.setFilterValue(inputValue || void 0);
72
+ return;
73
+ }
74
+ const timer = window.setTimeout(
75
+ () => column.setFilterValue(inputValue || void 0),
76
+ debounceMs
77
+ );
78
+ return () => window.clearTimeout(timer);
79
+ }, [column, debounceMs, inputValue, settings.type, value]);
80
+ if (settings.type === "select") {
81
+ return /* @__PURE__ */ jsxs(
82
+ "select",
83
+ {
84
+ className,
85
+ "data-slot": "filter-select",
86
+ "aria-label": `Filter ${column.id}`,
87
+ value,
88
+ onChange: (event) => column.setFilterValue(event.target.value || void 0),
89
+ children: [
90
+ /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
91
+ (_b = settings.options) == null ? void 0 : _b.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
92
+ ]
93
+ }
94
+ );
95
+ }
96
+ return /* @__PURE__ */ jsxs("label", { className, "data-slot": "filter-text", children: [
97
+ icon,
98
+ /* @__PURE__ */ jsx(
99
+ "input",
100
+ {
101
+ "data-slot": "filter-input",
102
+ "aria-label": `Filter ${column.id}`,
103
+ value: inputValue,
104
+ placeholder: "Filter",
105
+ onChange: (event) => setInputValue(event.target.value)
106
+ }
107
+ )
108
+ ] });
109
+ }
110
+
111
+ // src/column-menu.tsx
112
+ import { useEffect as useEffect2, useRef } from "react";
113
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
114
+ function ColumnMenu({
115
+ table,
116
+ onClose,
117
+ onMoveColumn,
118
+ className,
119
+ icons,
120
+ id = "boneless-table-column-menu",
121
+ triggerRef
122
+ }) {
123
+ var _a;
124
+ const menuRef = useRef(null);
125
+ const closeButtonRef = useRef(null);
126
+ useEffect2(() => {
127
+ var _a2;
128
+ (_a2 = closeButtonRef.current) == null ? void 0 : _a2.focus();
129
+ function dismissOnOutsideClick(event) {
130
+ var _a3, _b;
131
+ const target = event.target;
132
+ if (!((_a3 = menuRef.current) == null ? void 0 : _a3.contains(target)) && !((_b = triggerRef == null ? void 0 : triggerRef.current) == null ? void 0 : _b.contains(target))) onClose();
133
+ }
134
+ document.addEventListener("mousedown", dismissOnOutsideClick);
135
+ return () => document.removeEventListener("mousedown", dismissOnOutsideClick);
136
+ }, [onClose]);
137
+ return /* @__PURE__ */ jsxs2(
138
+ "div",
139
+ {
140
+ className,
141
+ "data-slot": "column-menu",
142
+ ref: menuRef,
143
+ id,
144
+ role: "dialog",
145
+ "aria-modal": "false",
146
+ "aria-labelledby": `${id}-title`,
147
+ onKeyDown: (event) => {
148
+ if (event.key === "Escape") {
149
+ event.preventDefault();
150
+ onClose();
151
+ }
152
+ },
153
+ children: [
154
+ /* @__PURE__ */ jsxs2("div", { "data-slot": "column-menu-header", children: [
155
+ /* @__PURE__ */ jsx2("span", { id: `${id}-title`, children: "Configure columns" }),
156
+ /* @__PURE__ */ jsx2(
157
+ "button",
158
+ {
159
+ "data-slot": "column-menu-close",
160
+ onClick: onClose,
161
+ "aria-label": "Close column settings",
162
+ ref: closeButtonRef,
163
+ children: (_a = icons == null ? void 0 : icons.close) != null ? _a : "Close"
164
+ }
165
+ )
166
+ ] }),
167
+ table.getAllLeafColumns().map((column) => {
168
+ var _a2, _b, _c, _d;
169
+ return /* @__PURE__ */ jsxs2("div", { "data-slot": "column-menu-item", children: [
170
+ /* @__PURE__ */ jsxs2("label", { "data-slot": "column-menu-label", children: [
171
+ /* @__PURE__ */ jsx2(
172
+ "input",
173
+ {
174
+ type: "checkbox",
175
+ checked: column.getIsVisible(),
176
+ onChange: column.getToggleVisibilityHandler()
177
+ }
178
+ ),
179
+ (_b = (_a2 = column.columnDef.meta) == null ? void 0 : _a2.columnLabel) != null ? _b : typeof column.columnDef.header === "string" || typeof column.columnDef.header === "number" ? String(column.columnDef.header) : column.id
180
+ ] }),
181
+ /* @__PURE__ */ jsxs2("div", { "data-slot": "column-menu-actions", children: [
182
+ /* @__PURE__ */ jsx2(
183
+ "button",
184
+ {
185
+ "data-slot": "column-menu-move-earlier",
186
+ onClick: () => onMoveColumn(column.id, -1),
187
+ title: "Move column earlier",
188
+ children: (_c = icons == null ? void 0 : icons.moveEarlier) != null ? _c : "Earlier"
189
+ }
190
+ ),
191
+ /* @__PURE__ */ jsx2(
192
+ "button",
193
+ {
194
+ "data-slot": "column-menu-move-later",
195
+ onClick: () => onMoveColumn(column.id, 1),
196
+ title: "Move column later",
197
+ children: (_d = icons == null ? void 0 : icons.moveLater) != null ? _d : "Later"
198
+ }
199
+ )
200
+ ] })
201
+ ] }, column.id);
202
+ })
203
+ ]
204
+ }
205
+ );
206
+ }
207
+
208
+ // src/class-names.ts
209
+ function cn(...classNames) {
210
+ return classNames.filter(Boolean).join(" ");
211
+ }
212
+
213
+ // src/settings.ts
214
+ var defaultBonelessTableSettings = {
215
+ columnDefaults: {
216
+ sizing: { minPx: 120, flex: 1 },
217
+ align: "left",
218
+ borders: "none",
219
+ sorting: { enabled: true, reveal: "hover" }
220
+ },
221
+ interactions: { filterReveal: "hover", horizontalOverflow: "auto" }
222
+ };
223
+ function mergeBonelessTableSettings(override, base = defaultBonelessTableSettings) {
224
+ var _a, _b, _c, _d, _e, _f;
225
+ if (!override) return base;
226
+ return {
227
+ columnDefaults: {
228
+ sizing: __spreadValues(__spreadValues({}, base.columnDefaults.sizing), (_a = override.columnDefaults) == null ? void 0 : _a.sizing),
229
+ align: (_c = (_b = override.columnDefaults) == null ? void 0 : _b.align) != null ? _c : base.columnDefaults.align,
230
+ borders: (_e = (_d = override.columnDefaults) == null ? void 0 : _d.borders) != null ? _e : base.columnDefaults.borders,
231
+ sorting: __spreadValues(__spreadValues({}, base.columnDefaults.sorting), (_f = override.columnDefaults) == null ? void 0 : _f.sorting)
232
+ },
233
+ interactions: __spreadValues(__spreadValues({}, base.interactions), override.interactions)
234
+ };
235
+ }
236
+ function resolveColumnSettings(settings, column) {
237
+ var _a, _b, _c;
238
+ const configured = (_a = column.columnDef.meta) == null ? void 0 : _a.bonelessTable;
239
+ const configuredSizing = configured == null ? void 0 : configured.sizing;
240
+ return {
241
+ sizing: configuredSizing && "widthPx" in configuredSizing ? { widthPx: configuredSizing.widthPx } : __spreadValues(__spreadValues({}, settings.columnDefaults.sizing), configuredSizing),
242
+ align: (_b = configured == null ? void 0 : configured.align) != null ? _b : settings.columnDefaults.align,
243
+ borders: (_c = configured == null ? void 0 : configured.borders) != null ? _c : settings.columnDefaults.borders,
244
+ sorting: __spreadValues(__spreadValues({}, settings.columnDefaults.sorting), configured == null ? void 0 : configured.sorting),
245
+ filtering: configured == null ? void 0 : configured.filtering,
246
+ valueDisplay: configured == null ? void 0 : configured.valueDisplay
247
+ };
248
+ }
249
+ function compactValue(value, settings) {
250
+ var _a;
251
+ const text = value == null ? "" : String(value);
252
+ const display = settings == null ? void 0 : settings.valueDisplay;
253
+ if (!display || text.length <= display.truncateAt) return text;
254
+ return `${text.slice(0, display.truncateAt).trimEnd()}${(_a = display.suffix) != null ? _a : "..."}`;
255
+ }
256
+
257
+ // src/columns.ts
258
+ function defineColumns(columns) {
259
+ return columns;
260
+ }
261
+ function resolveColumns(columns) {
262
+ return columns.map((column) => {
263
+ if ("key" in column && column.key) {
264
+ const _a = column, { key } = _a, definition = __objRest(_a, ["key"]);
265
+ return __spreadProps(__spreadValues({}, definition), { accessorKey: key });
266
+ }
267
+ return column;
268
+ });
269
+ }
270
+ function withDefaultCells(columns) {
271
+ return columns.map((column) => {
272
+ if (column.cell) return column;
273
+ return __spreadProps(__spreadValues({}, column), {
274
+ cell: ({ getValue, column: tableColumn }) => {
275
+ var _a;
276
+ const text = compactValue(getValue(), (_a = tableColumn.columnDef.meta) == null ? void 0 : _a.bonelessTable);
277
+ return text;
278
+ }
279
+ });
280
+ });
281
+ }
282
+ function getColumnIds(columns) {
283
+ return columns.map((column, index) => {
284
+ if ("id" in column && column.id) return column.id;
285
+ if ("key" in column && column.key != null) return String(column.key);
286
+ if ("accessorKey" in column && column.accessorKey != null) {
287
+ return String(column.accessorKey);
288
+ }
289
+ return String(index);
290
+ });
291
+ }
292
+
293
+ // src/table-skeleton.tsx
294
+ import { jsx as jsx3 } from "react/jsx-runtime";
295
+ function TableSkeleton({
296
+ columnCount,
297
+ rows,
298
+ label = "Loading rows",
299
+ classNames = {}
300
+ }) {
301
+ return /* @__PURE__ */ jsx3("div", { "data-slot": "skeleton", className: classNames.skeleton, "aria-busy": "true", children: Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ jsx3(
302
+ "div",
303
+ {
304
+ className: classNames.row,
305
+ "data-slot": "skeleton-row",
306
+ role: "status",
307
+ "aria-label": label,
308
+ style: { display: "grid", gridTemplateColumns: "var(--boneless-table-grid)" },
309
+ children: Array.from({ length: columnCount }, (_2, columnIndex) => /* @__PURE__ */ jsx3("div", { className: cn(classNames.cell), "data-slot": "skeleton-cell", children: /* @__PURE__ */ jsx3("span", { "data-slot": "skeleton-line" }) }, columnIndex))
310
+ },
311
+ rowIndex
312
+ )) });
313
+ }
314
+
315
+ // src/row-link.ts
316
+ function readPath(row, path) {
317
+ return path.split(".").reduce((value, key) => {
318
+ if (value === null || typeof value !== "object") return void 0;
319
+ return value[key];
320
+ }, row);
321
+ }
322
+ function resolveRowHref(link, row) {
323
+ if (typeof link.href === "function") return link.href(row);
324
+ let hasMissingValue = false;
325
+ const href = link.href.replace(/\{(?:row\.)?([\w.]+)\}/g, (token, path) => {
326
+ const value = readPath(row, path);
327
+ if (value === void 0 || value === null) {
328
+ hasMissingValue = true;
329
+ return token;
330
+ }
331
+ return encodeURIComponent(String(value));
332
+ });
333
+ return hasMissingValue ? void 0 : href;
334
+ }
335
+ function resolveRowLinkLabel(link, row) {
336
+ return typeof link.label === "function" ? link.label(row) : link.label;
337
+ }
338
+
339
+ // src/boneless-table.tsx
340
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
341
+ function moveItem(order, id, direction) {
342
+ const index = order.indexOf(id);
343
+ const target = index + direction;
344
+ if (index < 0 || target < 0 || target >= order.length) return order;
345
+ const next = [...order];
346
+ [next[index], next[target]] = [next[target], next[index]];
347
+ return next;
348
+ }
349
+ function renderSlot(slot, table, fallback) {
350
+ if (slot === false) return null;
351
+ if (typeof slot === "function") return slot(table);
352
+ if (slot && slot !== true) return slot;
353
+ return fallback;
354
+ }
355
+ function BonelessTable(_a) {
356
+ var _b = _a, {
357
+ data,
358
+ columns,
359
+ settings: settingsOverride,
360
+ className,
361
+ classNames = {},
362
+ icons,
363
+ tree: treeOption,
364
+ isLoading = false,
365
+ isFetchingMore = false,
366
+ skeletonRows = 8,
367
+ skeletonLabel = "Loading rows",
368
+ onNearEnd,
369
+ canLoadMore = true,
370
+ nearEndOffset = 220,
371
+ filterDebounceMs = 150,
372
+ scroller = "auto",
373
+ toolbar = true,
374
+ toolbarLayout,
375
+ footer = true,
376
+ totalCount,
377
+ resultLabel = "rows",
378
+ resultHint,
379
+ onReset,
380
+ onRowClick,
381
+ scrollToTopOn,
382
+ rowLink,
383
+ emptyState,
384
+ virtualization = {}
385
+ } = _b, tableOptions = __objRest(_b, [
386
+ "data",
387
+ "columns",
388
+ "settings",
389
+ "className",
390
+ "classNames",
391
+ "icons",
392
+ "tree",
393
+ "isLoading",
394
+ "isFetchingMore",
395
+ "skeletonRows",
396
+ "skeletonLabel",
397
+ "onNearEnd",
398
+ "canLoadMore",
399
+ "nearEndOffset",
400
+ "filterDebounceMs",
401
+ "scroller",
402
+ "toolbar",
403
+ "toolbarLayout",
404
+ "footer",
405
+ "totalCount",
406
+ "resultLabel",
407
+ "resultHint",
408
+ "onReset",
409
+ "onRowClick",
410
+ "scrollToTopOn",
411
+ "rowLink",
412
+ "emptyState",
413
+ "virtualization"
414
+ ]);
415
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _l, _m, _n, _o, _p, _q, _r, _s;
416
+ const settings = useMemo(() => mergeBonelessTableSettings(settingsOverride), [settingsOverride]);
417
+ const resolvedColumns = useMemo(() => withDefaultCells(resolveColumns(columns)), [columns]);
418
+ 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);
423
+ const columnMenuId = useId();
424
+ const [showColumns, setShowColumns] = useState2(false);
425
+ const [headerScrollbarGutter, setHeaderScrollbarGutter] = useState2(0);
426
+ const [sorting, setSorting] = useState2((_b2 = (_a2 = tableOptions.initialState) == null ? void 0 : _a2.sorting) != null ? _b2 : []);
427
+ const [columnFilters, setColumnFilters] = useState2(
428
+ (_d = (_c = tableOptions.initialState) == null ? void 0 : _c.columnFilters) != null ? _d : []
429
+ );
430
+ const [columnVisibility, setColumnVisibility] = useState2(
431
+ (_f = (_e = tableOptions.initialState) == null ? void 0 : _e.columnVisibility) != null ? _f : {}
432
+ );
433
+ const [columnOrder, setColumnOrder] = useState2(
434
+ (_h = (_g = tableOptions.initialState) == null ? void 0 : _g.columnOrder) != null ? _h : defaultOrder
435
+ );
436
+ const [expanded, setExpanded] = useState2((_j = (_i = tableOptions.initialState) == null ? void 0 : _i.expanded) != null ? _j : {});
437
+ const _k = tableOptions, {
438
+ state: controlledState,
439
+ onSortingChange,
440
+ onColumnFiltersChange,
441
+ onColumnVisibilityChange,
442
+ onColumnOrderChange,
443
+ onExpandedChange,
444
+ getCoreRowModel: getCoreRowModelOption,
445
+ getSortedRowModel: getSortedRowModelOption,
446
+ getFilteredRowModel: getFilteredRowModelOption,
447
+ getSubRows,
448
+ manualSorting,
449
+ manualFiltering,
450
+ initialState
451
+ } = _k, restTableOptions = __objRest(_k, [
452
+ "state",
453
+ "onSortingChange",
454
+ "onColumnFiltersChange",
455
+ "onColumnVisibilityChange",
456
+ "onColumnOrderChange",
457
+ "onExpandedChange",
458
+ "getCoreRowModel",
459
+ "getSortedRowModel",
460
+ "getFilteredRowModel",
461
+ "getSubRows",
462
+ "manualSorting",
463
+ "manualFiltering",
464
+ "initialState"
465
+ ]);
466
+ useEffect3(() => {
467
+ var _a3;
468
+ if (scrollToTopOn !== void 0) (_a3 = scrollerRef.current) == null ? void 0 : _a3.scrollTo({ top: 0 });
469
+ }, [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
+ const table = useReactTable(__spreadProps(__spreadValues({}, restTableOptions), {
483
+ data,
484
+ columns: resolvedColumns,
485
+ initialState,
486
+ state: __spreadValues({
487
+ sorting,
488
+ columnFilters,
489
+ columnVisibility,
490
+ columnOrder,
491
+ expanded
492
+ }, controlledState),
493
+ onSortingChange: onSortingChange != null ? onSortingChange : setSorting,
494
+ onColumnFiltersChange: onColumnFiltersChange != null ? onColumnFiltersChange : setColumnFilters,
495
+ onColumnVisibilityChange: onColumnVisibilityChange != null ? onColumnVisibilityChange : setColumnVisibility,
496
+ onColumnOrderChange: onColumnOrderChange != null ? onColumnOrderChange : setColumnOrder,
497
+ onExpandedChange: onExpandedChange != null ? onExpandedChange : setExpanded,
498
+ getCoreRowModel: getCoreRowModelOption != null ? getCoreRowModelOption : getCoreRowModel(),
499
+ getSortedRowModel: getSortedRowModelOption != null ? getSortedRowModelOption : manualSorting ? void 0 : getSortedRowModel(),
500
+ getFilteredRowModel: getFilteredRowModelOption != null ? getFilteredRowModelOption : manualFiltering ? void 0 : getFilteredRowModel(),
501
+ getExpandedRowModel: treeOption ? getExpandedRowModel() : void 0,
502
+ getSubRows,
503
+ manualSorting,
504
+ manualFiltering
505
+ }));
506
+ const visibleColumns = table.getVisibleLeafColumns();
507
+ const allColumns = table.getAllFlatColumns();
508
+ const columnSettingsById = useMemo(
509
+ () => new Map(allColumns.map((column) => [column.id, resolveColumnSettings(settings, column)])),
510
+ [allColumns, settings]
511
+ );
512
+ const getColumnSettings = (column) => {
513
+ var _a3;
514
+ return (_a3 = columnSettingsById.get(column.id)) != null ? _a3 : resolveColumnSettings(settings, column);
515
+ };
516
+ const grid = visibleColumns.map((column) => {
517
+ 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)`;
520
+ }).join(" ");
521
+ const minWidth = visibleColumns.reduce((width, column) => {
522
+ const { sizing } = getColumnSettings(column);
523
+ return width + ("widthPx" in sizing ? sizing.widthPx : sizing.minPx);
524
+ }, 0);
525
+ const gridStyle = {
526
+ "--boneless-table-grid": grid,
527
+ minWidth: `${minWidth}px`
528
+ };
529
+ const tree = treeOption === true ? {} : treeOption === false ? void 0 : treeOption;
530
+ const treeColumnId = (_m = tree == null ? void 0 : tree.columnId) != null ? _m : (_l = visibleColumns[0]) == null ? void 0 : _l.id;
531
+ const treeIndentPx = (_n = tree == null ? void 0 : tree.indentPx) != null ? _n : 16;
532
+ const preExpandedRowModel = tree ? table.getPreExpandedRowModel() : void 0;
533
+ const expandableRows = useMemo(
534
+ () => {
535
+ var _a3;
536
+ return (_a3 = preExpandedRowModel == null ? void 0 : preExpandedRowModel.flatRows.filter((row) => row.getCanExpand())) != null ? _a3 : [];
537
+ },
538
+ [preExpandedRowModel]
539
+ );
540
+ const allTreeRowsExpanded = useMemo(
541
+ () => expandableRows.length > 0 && expandableRows.every((row) => row.getIsExpanded()),
542
+ [expandableRows, expanded]
543
+ );
544
+ useEffect3(() => {
545
+ if (!isFetchingMore || !canLoadMore) nearEndTriggeredRef.current = false;
546
+ }, [canLoadMore, isFetchingMore]);
547
+ function handleScroll() {
548
+ const element = scrollerRef.current;
549
+ if (!element) return;
550
+ if (headerScrollerRef.current) headerScrollerRef.current.scrollLeft = element.scrollLeft;
551
+ const isNearEnd = element.scrollHeight - element.scrollTop - element.clientHeight < nearEndOffset;
552
+ if (!isNearEnd) {
553
+ nearEndTriggeredRef.current = false;
554
+ return;
555
+ }
556
+ if (!onNearEnd || !canLoadMore || isFetchingMore || nearEndTriggeredRef.current) return;
557
+ nearEndTriggeredRef.current = true;
558
+ onNearEnd();
559
+ }
560
+ function resetTable() {
561
+ if (onReset) return onReset();
562
+ table.resetSorting();
563
+ table.resetColumnFilters();
564
+ table.resetColumnVisibility();
565
+ table.setColumnOrder(defaultOrder);
566
+ table.resetExpanded();
567
+ }
568
+ function moveColumn(id, direction) {
569
+ const current = table.getState().columnOrder;
570
+ table.setColumnOrder(moveItem(current.length ? current : defaultOrder, id, direction));
571
+ }
572
+ const shown = table.getRowModel().rows.length;
573
+ const total = totalCount != null ? totalCount : shown;
574
+ const rows = table.getRowModel().rows;
575
+ const isVirtualized = scroller === "fill" && virtualization !== false;
576
+ const rowVirtualizer = useVirtualizer({
577
+ count: rows.length,
578
+ getScrollElement: () => scrollerRef.current,
579
+ estimateSize: () => {
580
+ var _a3;
581
+ return virtualization === false ? 58 : (_a3 = virtualization.estimateSize) != null ? _a3 : 58;
582
+ },
583
+ overscan: isVirtualized ? (_o = virtualization.overscan) != null ? _o : 5 : 0,
584
+ enabled: isVirtualized,
585
+ initialRect: { width: 0, height: 600 }
586
+ });
587
+ const estimatedRowSize = virtualization === false ? 58 : (_p = virtualization.estimateSize) != null ? _p : 58;
588
+ 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
+ index,
590
+ start: index * estimatedRowSize,
591
+ key: row.id
592
+ }));
593
+ function closeColumnMenu() {
594
+ var _a3;
595
+ setShowColumns(false);
596
+ (_a3 = columnsButtonRef.current) == null ? void 0 : _a3.focus();
597
+ }
598
+ const toolbarSummary = /* @__PURE__ */ jsxs3("div", { className: classNames.toolbarSummary, "data-slot": "toolbar-summary", children: [
599
+ /* @__PURE__ */ jsx4("strong", { children: total.toLocaleString() }),
600
+ " ",
601
+ resultLabel,
602
+ resultHint ? /* @__PURE__ */ jsx4("span", { className: classNames.toolbarHint, "data-slot": "toolbar-hint", children: resultHint }) : null
603
+ ] });
604
+ const toolbarActions = /* @__PURE__ */ jsxs3("div", { className: classNames.toolbarActions, "data-slot": "toolbar-actions", children: [
605
+ tree && expandableRows.length > 0 ? /* @__PURE__ */ jsxs3(
606
+ "button",
607
+ {
608
+ "data-slot": "tree-toggle-all",
609
+ onClick: () => table.toggleAllRowsExpanded(!allTreeRowsExpanded),
610
+ children: [
611
+ allTreeRowsExpanded ? icons == null ? void 0 : icons.collapse : icons == null ? void 0 : icons.expand,
612
+ allTreeRowsExpanded ? (_r = tree.collapseAllLabel) != null ? _r : "Collapse all" : (_s = tree.expandAllLabel) != null ? _s : "Expand all"
613
+ ]
614
+ }
615
+ ) : null,
616
+ /* @__PURE__ */ jsxs3("button", { "data-slot": "reset", onClick: resetTable, title: "Reset table settings", children: [
617
+ icons == null ? void 0 : icons.reset,
618
+ "Reset"
619
+ ] }),
620
+ /* @__PURE__ */ jsxs3("div", { "data-slot": "column-settings", children: [
621
+ /* @__PURE__ */ jsxs3(
622
+ "button",
623
+ {
624
+ "data-slot": "columns-button",
625
+ onClick: () => setShowColumns((value) => !value),
626
+ "aria-expanded": showColumns,
627
+ "aria-haspopup": "dialog",
628
+ "aria-controls": columnMenuId,
629
+ ref: columnsButtonRef,
630
+ children: [
631
+ icons == null ? void 0 : icons.columns,
632
+ "Columns"
633
+ ]
634
+ }
635
+ ),
636
+ showColumns ? /* @__PURE__ */ jsx4(
637
+ ColumnMenu,
638
+ {
639
+ table,
640
+ onClose: closeColumnMenu,
641
+ onMoveColumn: moveColumn,
642
+ className: classNames.columnMenu,
643
+ icons,
644
+ id: columnMenuId,
645
+ triggerRef: columnsButtonRef
646
+ }
647
+ ) : null
648
+ ] })
649
+ ] });
650
+ const defaultToolbar = toolbarLayout ? toolbarLayout({ table, summary: toolbarSummary, actions: toolbarActions }) : /* @__PURE__ */ jsxs3("div", { className: classNames.toolbar, "data-slot": "toolbar", children: [
651
+ toolbarSummary,
652
+ toolbarActions
653
+ ] });
654
+ const defaultFooter = /* @__PURE__ */ jsxs3("footer", { className: classNames.footer, "data-slot": "footer", children: [
655
+ "Showing ",
656
+ shown.toLocaleString(),
657
+ " of ",
658
+ total.toLocaleString(),
659
+ " ",
660
+ resultLabel
661
+ ] });
662
+ const rootStyle = scroller === "fill" ? { display: "flex", flexDirection: "column", minHeight: 0 } : void 0;
663
+ const scrollerStyle = __spreadValues({
664
+ overflowX: settings.interactions.horizontalOverflow
665
+ }, scroller === "fill" ? { flex: "1 1 auto", minHeight: 0, overflowY: "auto" } : {});
666
+ const tableStyle = __spreadValues({
667
+ "--boneless-table-grid": grid
668
+ }, 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(
692
+ "div",
693
+ {
694
+ className: classNames.headerGroup,
695
+ "data-slot": "header-group",
696
+ role: "rowgroup",
697
+ style: gridStyle,
698
+ children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx4(
699
+ "div",
700
+ {
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(
806
+ "div",
807
+ {
808
+ className: classNames.row,
809
+ "data-slot": "row",
810
+ role: "row",
811
+ 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());
827
+ return /* @__PURE__ */ jsx4(
828
+ "div",
829
+ {
830
+ className: classNames.cell,
831
+ "data-slot": "cell",
832
+ "data-align": columnSettings.align,
833
+ "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
860
+ },
861
+ cell.id
862
+ );
863
+ })
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,
892
+ {
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
899
+ }
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
+ ] });
924
+ }
925
+
926
+ // src/index.ts
927
+ import {
928
+ createColumnHelper,
929
+ flexRender as flexRender2,
930
+ getCoreRowModel as getCoreRowModel2,
931
+ getExpandedRowModel as getExpandedRowModel2,
932
+ getFilteredRowModel as getFilteredRowModel2,
933
+ getSortedRowModel as getSortedRowModel2,
934
+ useReactTable as useReactTable2
935
+ } from "@tanstack/react-table";
936
+ export {
937
+ BonelessTable,
938
+ ColumnFilter,
939
+ ColumnMenu,
940
+ TableSkeleton,
941
+ compactValue,
942
+ createColumnHelper,
943
+ defaultBonelessTableSettings,
944
+ defineColumns,
945
+ flexRender2 as flexRender,
946
+ getColumnIds,
947
+ getCoreRowModel2 as getCoreRowModel,
948
+ getExpandedRowModel2 as getExpandedRowModel,
949
+ getFilteredRowModel2 as getFilteredRowModel,
950
+ getSortedRowModel2 as getSortedRowModel,
951
+ mergeBonelessTableSettings,
952
+ resolveColumnSettings,
953
+ resolveColumns,
954
+ resolveRowHref,
955
+ resolveRowLinkLabel,
956
+ useReactTable2 as useReactTable,
957
+ withDefaultCells
958
+ };
959
+ //# sourceMappingURL=index.mjs.map