@nexgrid/react 0.1.0 → 0.2.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.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  DEFAULT_PAGE_SIZE: () => import_core4.DEFAULT_PAGE_SIZE,
36
36
  NexGrid: () => NexGrid,
37
37
  PAGE_SIZES: () => import_core4.PAGE_SIZES,
38
+ TableX: () => TableX,
38
39
  buildODataUrl: () => import_core4.buildODataUrl,
39
40
  buildQueryUrl: () => import_core4.buildQueryUrl,
40
41
  defaultQuery: () => import_core4.defaultQuery,
@@ -48,16 +49,18 @@ __export(index_exports, {
48
49
  toODataParams: () => import_core4.toODataParams,
49
50
  totalPagesFor: () => import_core4.totalPagesFor,
50
51
  useClientNexGrid: () => useClientNexGrid,
52
+ useClientTableX: () => useClientTableX,
51
53
  withFilter: () => import_core4.withFilter,
52
54
  withPage: () => import_core4.withPage,
53
55
  withPageSize: () => import_core4.withPageSize,
54
56
  withSearch: () => import_core4.withSearch,
55
57
  withSort: () => import_core4.withSort,
58
+ withToggledMultiSort: () => import_core4.withToggledMultiSort,
56
59
  withToggledSort: () => import_core4.withToggledSort
57
60
  });
58
61
  module.exports = __toCommonJS(index_exports);
59
62
 
60
- // src/nex-grid.tsx
63
+ // src/table-x.tsx
61
64
  var React4 = __toESM(require("react"), 1);
62
65
  var import_core2 = require("@nexgrid/core");
63
66
 
@@ -99,29 +102,22 @@ function XIcon(props) {
99
102
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })
100
103
  ] });
101
104
  }
102
- function SlidersIcon(props) {
105
+ function ColumnsIcon(props) {
103
106
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { ...props, children: [
104
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", x2: "14", y1: "4", y2: "4" }),
105
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "10", x2: "3", y1: "4", y2: "4" }),
106
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", x2: "12", y1: "12", y2: "12" }),
107
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", x2: "3", y1: "12", y2: "12" }),
108
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", x2: "16", y1: "20", y2: "20" }),
109
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "3", y1: "20", y2: "20" }),
110
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "14", x2: "14", y1: "2", y2: "6" }),
111
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", x2: "8", y1: "10", y2: "14" }),
112
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "16", x2: "16", y1: "18", y2: "22" })
107
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 3v18" }),
109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 3v18" })
113
110
  ] });
114
111
  }
115
- function FilterIcon(props) {
116
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }) });
117
- }
118
- function DownloadIcon(props) {
112
+ function DensityIcon(props) {
119
113
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { ...props, children: [
120
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
121
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
122
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "4.5", width: "18", height: "6", rx: "1.5" }),
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "13.5", width: "18", height: "6", rx: "1.5" })
123
116
  ] });
124
117
  }
118
+ function ChevronDownIcon(props) {
119
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" }) });
120
+ }
125
121
  function FileSpreadsheetIcon(props) {
126
122
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { ...props, children: [
127
123
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
@@ -170,6 +166,26 @@ function ArrowUpDownIcon(props) {
170
166
  function CheckIcon(props) {
171
167
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
172
168
  }
169
+ function RotateCcwIcon(props) {
170
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { ...props, children: [
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 3v5h5" })
173
+ ] });
174
+ }
175
+ function DownloadTrayIcon(props) {
176
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { ...props, children: [
177
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
180
+ ] });
181
+ }
182
+ function DotsVerticalIcon(props) {
183
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Icon, { ...props, children: [
184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "5", r: "1.5" }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "19", r: "1.5" })
187
+ ] });
188
+ }
173
189
 
174
190
  // src/render.tsx
175
191
  var React = __toESM(require("react"), 1);
@@ -201,9 +217,9 @@ function bodyCellStyle(col) {
201
217
  }
202
218
  function headerInnerClass(col) {
203
219
  const align = col.meta?.align;
204
- if (align === "center") return "nxg-th-inner nxg-th-inner--center";
205
- if (align === "right") return "nxg-th-inner nxg-th-inner--right";
206
- return "nxg-th-inner";
220
+ if (align === "center") return "tbx-th-inner tbx-th-inner--center";
221
+ if (align === "right") return "tbx-th-inner tbx-th-inner--right";
222
+ return "tbx-th-inner";
207
223
  }
208
224
  function renderTemplate(template, values) {
209
225
  const parts = [];
@@ -275,7 +291,7 @@ function useDropdown() {
275
291
  return { isOpen, toggle, close, containerRef, triggerRef };
276
292
  }
277
293
 
278
- // src/nex-grid.tsx
294
+ // src/table-x.tsx
279
295
  var import_jsx_runtime3 = require("react/jsx-runtime");
280
296
  function defaultRowId(row) {
281
297
  const record = row;
@@ -287,37 +303,72 @@ var DENSITY_LABEL_KEY = {
287
303
  default: "densityDefault",
288
304
  comfortable: "densityComfortable"
289
305
  };
290
- function NexGrid({
291
- columns,
292
- data,
293
- total,
294
- query,
295
- onQueryChange,
296
- caption,
297
- density: initialDensity = "default",
298
- isLoading = false,
299
- error = false,
300
- onRetry,
301
- enableSelection = false,
302
- onSelectionChange,
303
- enableSearch = true,
304
- searchPlaceholder,
305
- toolbarActions,
306
- onRowClick,
307
- getRowId = defaultRowId,
308
- className,
309
- showSerialNumber = true,
310
- enableExport = true,
311
- exportFileName,
312
- onExportAll,
313
- fetchEndpoint,
314
- badgeRules,
315
- locale: localeOverrides,
316
- onNotify,
317
- theme = "light"
318
- }) {
306
+ function TableX(props) {
307
+ const {
308
+ columns,
309
+ data,
310
+ total,
311
+ query,
312
+ onQueryChange,
313
+ caption,
314
+ density: initialDensity = "default",
315
+ isLoading = false,
316
+ error = false,
317
+ onRetry,
318
+ enableSelection = false,
319
+ selectionMode = "multi",
320
+ enableColumnFilters = true,
321
+ enableColumnResize = true,
322
+ onSelectionChange,
323
+ enableSearch = true,
324
+ searchPlaceholder,
325
+ enableColumns = true,
326
+ showColumnsButton,
327
+ enableDensity = true,
328
+ showDensityButton,
329
+ enableExport = true,
330
+ showExportButton,
331
+ enableSorting = true,
332
+ enablePagination = true,
333
+ showPagination,
334
+ enableRowsPerPage = true,
335
+ showRowsPerPage,
336
+ enableJumpToPage = true,
337
+ showJumpToPage,
338
+ showToolbar = true,
339
+ showFooter = true,
340
+ renderExpandedRow,
341
+ enableBulkActions = true,
342
+ bulkActions,
343
+ enableSummaryRow,
344
+ enableColumnReorder = false,
345
+ onColumnOrderChange,
346
+ onCellEdit,
347
+ toolbarActions,
348
+ onRowClick,
349
+ getRowId = defaultRowId,
350
+ className,
351
+ showSerialNumber = true,
352
+ exportFileName,
353
+ onExportAll,
354
+ fetchEndpoint,
355
+ badgeRules,
356
+ locale: localeOverrides,
357
+ onNotify,
358
+ theme = "light"
359
+ } = props;
319
360
  const locale = (0, import_core2.resolveLocale)(localeOverrides);
320
361
  const boolLabels = { yes: locale.booleanYes, no: locale.booleanNo };
362
+ const isColumnsVisible = enableColumns && showColumnsButton !== false;
363
+ const isDensityVisible = enableDensity && showDensityButton !== false;
364
+ const isExportVisible = enableExport && showExportButton !== false;
365
+ const isPaginationVisible = enablePagination && showPagination !== false;
366
+ const isRowsPerPageVisible = enableRowsPerPage && showRowsPerPage !== false;
367
+ const isJumpToPageVisible = enableJumpToPage && showJumpToPage !== false;
368
+ const [colList, setColList] = React4.useState(columns);
369
+ React4.useEffect(() => {
370
+ setColList(columns);
371
+ }, [columns]);
321
372
  const [density, setDensity] = React4.useState(initialDensity);
322
373
  const [hiddenCols, setHiddenCols] = React4.useState(
323
374
  () => (0, import_core2.initialHiddenColumns)(columns)
@@ -325,7 +376,14 @@ function NexGrid({
325
376
  const [selectedIds, setSelectedIds] = React4.useState(
326
377
  () => /* @__PURE__ */ new Set()
327
378
  );
379
+ const [expandedRows, setExpandedRows] = React4.useState(
380
+ () => /* @__PURE__ */ new Set()
381
+ );
382
+ const [editingCell, setEditingCell] = React4.useState(null);
383
+ const [draggedColId, setDraggedColId] = React4.useState(null);
328
384
  const [isExporting, setIsExporting] = React4.useState(false);
385
+ const [openFilterCol, setOpenFilterCol] = React4.useState(null);
386
+ const [colWidths, setColWidths] = React4.useState({});
329
387
  const columnsMenu = useDropdown();
330
388
  const densityMenu = useDropdown();
331
389
  const exportMenu = useDropdown();
@@ -334,6 +392,7 @@ function NexGrid({
334
392
  const densityButtonId = `${instanceId}-density`;
335
393
  const exportButtonId = `${instanceId}-export`;
336
394
  const jumpInputId = `${instanceId}-jump`;
395
+ const rootRef = React4.useRef(null);
337
396
  const notify = (type, message) => {
338
397
  onNotify?.({ type, message });
339
398
  };
@@ -343,126 +402,185 @@ function NexGrid({
343
402
  const range = (0, import_core2.getRecordRange)(currentPage, pageSize, total);
344
403
  const sort = (0, import_core2.primarySort)(query);
345
404
  const visible = React4.useMemo(
346
- () => (0, import_core2.visibleColumns)(columns, hiddenCols),
347
- [columns, hiddenCols]
405
+ () => (0, import_core2.visibleColumns)(colList, hiddenCols),
406
+ [colList, hiddenCols]
348
407
  );
349
- const hideable = React4.useMemo(() => columns.filter(import_core2.isHideable), [columns]);
408
+ const hideable = React4.useMemo(() => colList.filter(import_core2.isHideable), [colList]);
350
409
  const pageItems = React4.useMemo(
351
410
  () => (0, import_core2.getPageNumbers)(currentPage, totalPages),
352
411
  [currentPage, totalPages]
353
412
  );
354
413
  const pageRowIds = React4.useMemo(() => data.map((row) => getRowId(row)), [data, getRowId]);
355
- const columnCount = visible.length + (showSerialNumber ? 1 : 0) + (enableSelection ? 1 : 0);
414
+ const columnCount = visible.length + (renderExpandedRow ? 1 : 0) + (showSerialNumber ? 1 : 0) + (enableSelection ? 1 : 0);
415
+ const getPinnedOffsets = (visibleCols) => {
416
+ const leftOffsets2 = /* @__PURE__ */ new Map();
417
+ const rightOffsets2 = /* @__PURE__ */ new Map();
418
+ let currentLeft = 0;
419
+ let lastLeftPinnedId2 = null;
420
+ if (renderExpandedRow) {
421
+ leftOffsets2.set("__expand", currentLeft);
422
+ currentLeft += 40;
423
+ lastLeftPinnedId2 = "__expand";
424
+ }
425
+ if (enableSelection) {
426
+ leftOffsets2.set("__select", currentLeft);
427
+ currentLeft += 44;
428
+ lastLeftPinnedId2 = "__select";
429
+ }
430
+ if (showSerialNumber) {
431
+ leftOffsets2.set("__serial", currentLeft);
432
+ currentLeft += 56;
433
+ lastLeftPinnedId2 = "__serial";
434
+ }
435
+ for (const col of visibleCols) {
436
+ const id = (0, import_core2.getColumnId)(col);
437
+ const pin = col.meta?.pinned;
438
+ const width = colWidths[id] ?? col.meta?.width ?? 140;
439
+ if (pin === "left") {
440
+ leftOffsets2.set(id, currentLeft);
441
+ currentLeft += width;
442
+ lastLeftPinnedId2 = id;
443
+ }
444
+ }
445
+ let currentRight = 0;
446
+ let firstRightPinnedId2 = null;
447
+ for (let i = visibleCols.length - 1; i >= 0; i--) {
448
+ const col = visibleCols[i];
449
+ if (!col) continue;
450
+ const id = (0, import_core2.getColumnId)(col);
451
+ const pin = col.meta?.pinned;
452
+ const width = colWidths[id] ?? col.meta?.width ?? 140;
453
+ if (pin === "right") {
454
+ rightOffsets2.set(id, currentRight);
455
+ currentRight += width;
456
+ firstRightPinnedId2 = id;
457
+ }
458
+ }
459
+ return { leftOffsets: leftOffsets2, rightOffsets: rightOffsets2, lastLeftPinnedId: lastLeftPinnedId2, firstRightPinnedId: firstRightPinnedId2 };
460
+ };
461
+ const { leftOffsets, rightOffsets, lastLeftPinnedId, firstRightPinnedId } = getPinnedOffsets(visible);
356
462
  const [searchText, setSearchText] = useDebouncedSearch(query.q ?? "", (text) => {
357
463
  onQueryChange((0, import_core2.withSearch)(query, text));
358
464
  });
359
465
  const [jumpText, setJumpText] = React4.useState(() => String(currentPage));
360
466
  React4.useEffect(() => setJumpText(String(currentPage)), [currentPage]);
361
- const submitJump = (event) => {
362
- event?.preventDefault();
363
- const parsed = Number.parseInt(jumpText, 10);
364
- if (Number.isFinite(parsed) && parsed >= 1 && parsed <= totalPages) {
365
- setJumpText(String(parsed));
366
- if (parsed !== currentPage) onQueryChange((0, import_core2.withPage)(query, parsed, totalPages));
467
+ const submitJump = (raw) => {
468
+ const page = Number.parseInt(raw, 10);
469
+ if (Number.isNaN(page) || page < 1 || page > totalPages) {
470
+ setJumpText(String(currentPage));
367
471
  return;
368
472
  }
369
- setJumpText(String(currentPage));
473
+ if (page !== currentPage) {
474
+ onQueryChange((0, import_core2.withPage)(query, page, totalPages));
475
+ }
370
476
  };
371
- const toggleSort = (columnId) => {
372
- onQueryChange((0, import_core2.withToggledSort)(query, columnId));
477
+ const toggleSort = (field, multiSort = false) => {
478
+ const next = multiSort ? (0, import_core2.withToggledMultiSort)(query, field) : (0, import_core2.withToggledSort)(query, field);
479
+ onQueryChange(next);
373
480
  };
374
- const selectedOnPage = pageRowIds.filter((id) => selectedIds.has(id)).length;
375
- const allPageSelected = pageRowIds.length > 0 && selectedOnPage === pageRowIds.length;
376
- const somePageSelected = selectedOnPage > 0 && !allPageSelected;
377
- const commitSelection = (next) => {
481
+ const isSingleSelect = selectionMode === "single";
482
+ const allPageSelected = pageRowIds.length > 0 && pageRowIds.every((id) => selectedIds.has(id));
483
+ const somePageSelected = !allPageSelected && pageRowIds.some((id) => selectedIds.has(id));
484
+ const toggleSelectAll = () => {
485
+ if (isSingleSelect) return;
486
+ const next = new Set(selectedIds);
487
+ if (allPageSelected) {
488
+ for (const id of pageRowIds) next.delete(id);
489
+ } else {
490
+ for (const id of pageRowIds) next.add(id);
491
+ }
378
492
  setSelectedIds(next);
379
493
  onSelectionChange?.(Array.from(next), false);
380
494
  };
381
- const toggleSelectAll = () => {
382
- const next = new Set(selectedIds);
383
- for (const id of pageRowIds) {
384
- if (allPageSelected) next.delete(id);
495
+ const toggleSelectRow = (id) => {
496
+ const next = isSingleSelect ? selectedIds.has(id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id]) : new Set(selectedIds);
497
+ if (!isSingleSelect) {
498
+ if (next.has(id)) next.delete(id);
385
499
  else next.add(id);
386
500
  }
387
- commitSelection(next);
501
+ setSelectedIds(next);
502
+ onSelectionChange?.(Array.from(next), false);
388
503
  };
389
- const toggleSelectRow = (id) => {
390
- const next = new Set(selectedIds);
391
- if (next.has(id)) next.delete(id);
392
- else next.add(id);
393
- commitSelection(next);
504
+ const deselectAll = () => {
505
+ setSelectedIds(/* @__PURE__ */ new Set());
506
+ onSelectionChange?.([], false);
507
+ };
508
+ const toggleExpandRow = (id) => {
509
+ setExpandedRows((prev) => {
510
+ const next = new Set(prev);
511
+ if (next.has(id)) next.delete(id);
512
+ else next.add(id);
513
+ return next;
514
+ });
394
515
  };
395
516
  const toggleColumn = (columnId) => {
396
517
  setHiddenCols((prev) => ({ ...prev, [columnId]: prev[columnId] !== true }));
397
518
  };
398
- const collectExportRows = async () => {
399
- if (data.length >= total || !fetchEndpoint) return data;
400
- notify("info", (0, import_core2.formatMessage)(locale.exportFetchingAll, { total: total.toLocaleString() }));
401
- try {
402
- const result = await (0, import_core2.fetchAllPages)(async (page, size) => {
403
- const url = (0, import_core2.buildQueryUrl)(fetchEndpoint, {
404
- ...query,
405
- page,
406
- pageSize: (0, import_core2.isPageSize)(size) ? size : query.pageSize
407
- });
408
- const response = await fetch(url, { cache: "no-store" });
409
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
410
- return await response.json();
411
- });
412
- return result.items;
413
- } catch {
414
- notify("error", locale.exportFetchFailed);
415
- return data;
416
- }
417
- };
418
519
  const handleExport = async (format) => {
419
520
  exportMenu.close();
420
521
  if (onExportAll) {
421
- void onExportAll();
522
+ try {
523
+ await onExportAll();
524
+ } catch (err) {
525
+ notify("error", err instanceof Error ? err.message : "Export failed");
526
+ }
422
527
  return;
423
528
  }
424
529
  setIsExporting(true);
530
+ notify("info", "Exporting...");
425
531
  try {
426
- const rows = await collectExportRows();
427
- if (rows.length === 0) {
428
- notify("error", locale.exportNoData);
429
- return;
532
+ let exportRows = data;
533
+ if (fetchEndpoint) {
534
+ const full = await (0, import_core2.fetchAllPages)(async (page, size) => {
535
+ const url = (0, import_core2.buildQueryUrl)(fetchEndpoint, {
536
+ ...query,
537
+ page,
538
+ pageSize: (0, import_core2.isPageSize)(size) ? size : query.pageSize
539
+ });
540
+ const response = await fetch(url, { cache: "no-store" });
541
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
542
+ return await response.json();
543
+ });
544
+ exportRows = full.items;
430
545
  }
431
546
  const exportColumns = (0, import_core2.toExportColumns)(visible, boolLabels);
432
547
  const prefix = exportFileName ?? (0, import_core2.filePrefixFromCaption)(caption);
433
- if (format === "excel") {
434
- const count = (0, import_core2.downloadExcel)({
548
+ if (format === "clipboard") {
549
+ const ok = await (0, import_core2.copyToClipboard)(exportRows, exportColumns);
550
+ if (ok) notify("success", "Copied to clipboard");
551
+ else notify("error", "Failed to copy to clipboard");
552
+ } else if (format === "excel") {
553
+ (0, import_core2.downloadExcel)({
435
554
  filename: prefix,
436
555
  caption,
437
- rows,
556
+ rows: exportRows,
438
557
  columns: exportColumns,
439
558
  badgeRules,
440
559
  serialHeader: locale.serialHeader
441
560
  });
442
- notify(
443
- "success",
444
- (0, import_core2.formatMessage)(locale.exportExcelSuccess, { count: count.toLocaleString() })
445
- );
561
+ notify("success", "Excel downloaded");
446
562
  } else {
447
- const count = (0, import_core2.downloadCsv)((0, import_core2.timestampedFilename)(prefix), rows, exportColumns);
448
- notify(
449
- "success",
450
- (0, import_core2.formatMessage)(locale.exportCsvSuccess, { count: count.toLocaleString() })
451
- );
563
+ (0, import_core2.downloadCsv)((0, import_core2.timestampedFilename)(prefix), exportRows, exportColumns);
564
+ notify("success", "CSV downloaded");
452
565
  }
566
+ } catch {
567
+ notify("error", "Export failed");
453
568
  } finally {
454
569
  setIsExporting(false);
455
570
  }
456
571
  };
457
- const rootClassName = [
458
- "nxg-root",
459
- theme === "dark" ? "nxg-dark" : theme === "auto" ? "nxg-auto" : void 0,
572
+ const rootClasses = [
573
+ "tbx-root",
574
+ theme === "dark" ? "tbx-dark" : theme === "auto" ? "tbx-auto" : void 0,
460
575
  className
461
- ].filter((part) => Boolean(part)).join(" ");
576
+ ].filter(Boolean);
462
577
  if (error) {
463
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: rootClassName, "data-density": density, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-state-card", children: [
464
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "nxg-state-text", children: locale.errorText }),
465
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "nxg-btn", onClick: onRetry, children: locale.retryButton }) : null
578
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: rootClasses.join(" "), "data-density": density, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-state-card", children: [
579
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "tbx-state-text", children: locale.errorText }),
580
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", className: "tbx-btn", onClick: onRetry, children: [
581
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RotateCcwIcon, { className: "tbx-icon" }),
582
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: locale.retryButton })
583
+ ] }) : null
466
584
  ] }) });
467
585
  }
468
586
  const rowIsClickable = typeof onRowClick === "function";
@@ -474,56 +592,57 @@ function NexGrid({
474
592
  event.preventDefault();
475
593
  onRowClick?.(row);
476
594
  };
477
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: rootClassName, "data-density": density, children: [
478
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-toolbar", children: [
479
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-toolbar-group", children: [
480
- enableSearch ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-search", children: [
481
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SearchIcon, { className: "nxg-search-icon" }),
482
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
483
- "input",
484
- {
485
- type: "search",
486
- className: "nxg-search-input",
487
- value: searchText,
488
- onChange: (event) => setSearchText(event.target.value),
489
- placeholder: searchPlaceholder ?? locale.searchPlaceholder,
490
- "aria-label": `Search ${caption}`
491
- }
492
- ),
493
- searchText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
494
- "button",
495
- {
496
- type: "button",
497
- className: "nxg-search-clear",
498
- onClick: () => setSearchText(""),
499
- "aria-label": locale.clearSearch,
500
- children: [
501
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(XIcon, { className: "nxg-icon" }),
502
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "nxg-sr-only", children: locale.clearSearch })
503
- ]
504
- }
505
- ) : null
506
- ] }) : null,
507
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-menu-wrap", ref: columnsMenu.containerRef, children: [
595
+ const hasSummary = enableSummaryRow === true || visible.some((col) => col.meta?.aggregation !== void 0);
596
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: rootClasses.join(" "), "data-density": density, ref: rootRef, children: [
597
+ showToolbar ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-toolbar", children: [
598
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-toolbar-group", children: enableSearch ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-search", children: [
599
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SearchIcon, { className: "tbx-search-icon" }),
600
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
601
+ "input",
602
+ {
603
+ type: "search",
604
+ className: "tbx-search-input",
605
+ value: searchText,
606
+ onChange: (event) => setSearchText(event.target.value),
607
+ placeholder: searchPlaceholder ?? locale.searchPlaceholder,
608
+ "aria-label": `Search ${caption}`
609
+ }
610
+ ),
611
+ searchText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
612
+ "button",
613
+ {
614
+ type: "button",
615
+ className: "tbx-search-clear",
616
+ onClick: () => setSearchText(""),
617
+ "aria-label": locale.clearSearch,
618
+ children: [
619
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(XIcon, { className: "tbx-icon" }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-sr-only", children: locale.clearSearch })
621
+ ]
622
+ }
623
+ ) : null
624
+ ] }) : null }),
625
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-toolbar-group tbx-toolbar-group--end", children: [
626
+ isColumnsVisible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-menu-wrap", ref: columnsMenu.containerRef, children: [
508
627
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
509
628
  "button",
510
629
  {
511
630
  type: "button",
512
631
  id: columnsButtonId,
513
- className: "nxg-btn",
632
+ className: "tbx-btn",
514
633
  ref: columnsMenu.triggerRef,
515
634
  "aria-haspopup": "menu",
516
635
  "aria-expanded": columnsMenu.isOpen,
517
636
  onClick: columnsMenu.toggle,
518
637
  children: [
519
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SlidersIcon, { className: "nxg-icon" }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ColumnsIcon, { className: "tbx-icon" }),
520
639
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: locale.columnsButton })
521
640
  ]
522
641
  }
523
642
  ),
524
- columnsMenu.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-menu", role: "menu", "aria-labelledby": columnsButtonId, children: [
525
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-menu-label", children: locale.toggleColumnsLabel }),
526
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-menu-separator" }),
643
+ columnsMenu.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-menu", role: "menu", "aria-labelledby": columnsButtonId, children: [
644
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-menu-label", children: locale.toggleColumnsLabel }),
645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-menu-separator" }),
527
646
  hideable.map((col) => {
528
647
  const id = (0, import_core2.getColumnId)(col);
529
648
  const checked = hiddenCols[id] !== true;
@@ -531,12 +650,12 @@ function NexGrid({
531
650
  "button",
532
651
  {
533
652
  type: "button",
534
- className: "nxg-menu-item",
653
+ className: "tbx-menu-item",
535
654
  role: "menuitemcheckbox",
536
655
  "aria-checked": checked,
537
656
  onClick: () => toggleColumn(id),
538
657
  children: [
539
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "nxg-check" }),
658
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "tbx-check" }),
540
659
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: (0, import_core2.getColumnTitle)(col) || id })
541
660
  ]
542
661
  },
@@ -544,29 +663,29 @@ function NexGrid({
544
663
  );
545
664
  })
546
665
  ] }) : null
547
- ] }),
548
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-menu-wrap", ref: densityMenu.containerRef, children: [
666
+ ] }) : null,
667
+ isDensityVisible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-menu-wrap", ref: densityMenu.containerRef, children: [
549
668
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
550
669
  "button",
551
670
  {
552
671
  type: "button",
553
672
  id: densityButtonId,
554
- className: "nxg-btn nxg-capitalize",
673
+ className: "tbx-btn tbx-capitalize",
555
674
  ref: densityMenu.triggerRef,
556
675
  "aria-haspopup": "menu",
557
676
  "aria-expanded": densityMenu.isOpen,
558
677
  onClick: densityMenu.toggle,
559
678
  children: [
560
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FilterIcon, { className: "nxg-icon" }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DensityIcon, { className: "tbx-icon" }),
561
680
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: (0, import_core2.formatMessage)(locale.densityButton, { density }) })
562
681
  ]
563
682
  }
564
683
  ),
565
- densityMenu.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-menu", role: "menu", "aria-labelledby": densityButtonId, children: import_core2.DENSITIES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
684
+ densityMenu.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-menu", role: "menu", "aria-labelledby": densityButtonId, children: import_core2.DENSITIES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
566
685
  "button",
567
686
  {
568
687
  type: "button",
569
- className: "nxg-menu-item",
688
+ className: "tbx-menu-item",
570
689
  role: "menuitemcheckbox",
571
690
  "aria-checked": density === option,
572
691
  onClick: () => {
@@ -574,37 +693,36 @@ function NexGrid({
574
693
  densityMenu.close();
575
694
  },
576
695
  children: [
577
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "nxg-check" }),
696
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "tbx-check" }),
578
697
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: locale[DENSITY_LABEL_KEY[option]] })
579
698
  ]
580
699
  },
581
700
  option
582
701
  )) }) : null
583
- ] })
584
- ] }),
585
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-toolbar-group nxg-toolbar-group--end", children: [
586
- enableExport ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-menu-wrap", ref: exportMenu.containerRef, children: [
702
+ ] }) : null,
703
+ isExportVisible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-menu-wrap", ref: exportMenu.containerRef, children: [
587
704
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
588
705
  "button",
589
706
  {
590
707
  type: "button",
591
708
  id: exportButtonId,
592
- className: "nxg-btn nxg-btn--export",
709
+ className: "tbx-btn tbx-btn--export",
593
710
  ref: exportMenu.triggerRef,
594
711
  disabled: isExporting,
595
712
  "aria-haspopup": "menu",
596
713
  "aria-expanded": exportMenu.isOpen,
597
714
  onClick: exportMenu.toggle,
598
715
  children: [
599
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DownloadIcon, { className: "nxg-icon" }),
600
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: isExporting ? locale.exportingButton : locale.exportButton })
716
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DownloadTrayIcon, { className: "tbx-icon" }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: isExporting ? locale.exportingButton : locale.exportButton }),
718
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, { className: "tbx-icon tbx-chevron" })
601
719
  ]
602
720
  }
603
721
  ),
604
722
  exportMenu.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
605
723
  "div",
606
724
  {
607
- className: "nxg-menu nxg-menu--end",
725
+ className: "tbx-menu tbx-menu--end",
608
726
  role: "menu",
609
727
  "aria-labelledby": exportButtonId,
610
728
  children: [
@@ -612,12 +730,12 @@ function NexGrid({
612
730
  "button",
613
731
  {
614
732
  type: "button",
615
- className: "nxg-menu-item",
733
+ className: "tbx-menu-item",
616
734
  role: "menuitem",
617
735
  onClick: () => void handleExport("excel"),
618
736
  children: [
619
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FileSpreadsheetIcon, { className: "nxg-icon--excel" }),
620
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-menu-item-title", children: [
737
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FileSpreadsheetIcon, { className: "tbx-icon--excel" }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-menu-item-title", children: [
621
739
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: locale.exportExcelTitle }),
622
740
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: locale.exportExcelSubtitle })
623
741
  ] })
@@ -628,17 +746,33 @@ function NexGrid({
628
746
  "button",
629
747
  {
630
748
  type: "button",
631
- className: "nxg-menu-item",
749
+ className: "tbx-menu-item",
632
750
  role: "menuitem",
633
751
  onClick: () => void handleExport("csv"),
634
752
  children: [
635
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FileTextIcon, { className: "nxg-icon--csv" }),
636
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-menu-item-title", children: [
753
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FileTextIcon, { className: "tbx-icon--csv" }),
754
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-menu-item-title", children: [
637
755
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: locale.exportCsvTitle }),
638
756
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: locale.exportCsvSubtitle })
639
757
  ] })
640
758
  ]
641
759
  }
760
+ ),
761
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
762
+ "button",
763
+ {
764
+ type: "button",
765
+ className: "tbx-menu-item",
766
+ role: "menuitem",
767
+ onClick: () => void handleExport("clipboard"),
768
+ children: [
769
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FileTextIcon, { className: "tbx-icon--csv" }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-menu-item-title", children: [
771
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: locale.exportClipboardTitle }),
772
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: locale.exportClipboardSubtitle })
773
+ ] })
774
+ ]
775
+ }
642
776
  )
643
777
  ]
644
778
  }
@@ -646,137 +780,539 @@ function NexGrid({
646
780
  ] }) : null,
647
781
  toolbarActions
648
782
  ] })
649
- ] }),
650
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "nxg-table", "aria-label": caption, children: [
783
+ ] }) : null,
784
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "tbx-table", "aria-label": caption, children: [
651
785
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
652
- showSerialNumber ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "nxg-th nxg-th--serial", scope: "col", children: locale.serialHeader }) : null,
653
- enableSelection ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "nxg-th nxg-th--select", scope: "col", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
654
- "input",
786
+ renderExpandedRow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
787
+ "th",
788
+ {
789
+ className: [
790
+ "tbx-th tbx-th--expand",
791
+ leftOffsets.has("__expand") ? "tbx-th--pinned-left" : void 0,
792
+ lastLeftPinnedId === "__expand" ? "tbx-pinned-border-left" : void 0
793
+ ].filter(Boolean).join(" "),
794
+ style: {
795
+ width: "40px",
796
+ left: leftOffsets.get("__expand")
797
+ },
798
+ scope: "col"
799
+ }
800
+ ) : null,
801
+ enableSelection ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
802
+ "th",
655
803
  {
656
- type: "checkbox",
657
- className: "nxg-checkbox",
658
- checked: allPageSelected,
659
- ref: (el) => {
660
- if (el) el.indeterminate = somePageSelected;
804
+ className: [
805
+ "tbx-th tbx-th--select",
806
+ leftOffsets.has("__select") ? "tbx-th--pinned-left" : void 0,
807
+ lastLeftPinnedId === "__select" ? "tbx-pinned-border-left" : void 0
808
+ ].filter(Boolean).join(" "),
809
+ style: {
810
+ left: leftOffsets.get("__select")
661
811
  },
662
- onChange: toggleSelectAll,
663
- "aria-label": locale.selectAllLabel
812
+ scope: "col",
813
+ children: !isSingleSelect ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
814
+ "input",
815
+ {
816
+ type: "checkbox",
817
+ className: "tbx-checkbox",
818
+ checked: allPageSelected,
819
+ ref: (el) => {
820
+ if (el) el.indeterminate = somePageSelected;
821
+ },
822
+ onChange: toggleSelectAll,
823
+ "aria-label": locale.selectAllLabel
824
+ }
825
+ ) : null
664
826
  }
665
- ) }) : null,
827
+ ) : null,
828
+ showSerialNumber ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
829
+ "th",
830
+ {
831
+ className: [
832
+ "tbx-th tbx-th--serial",
833
+ leftOffsets.has("__serial") ? "tbx-th--pinned-left" : void 0,
834
+ lastLeftPinnedId === "__serial" ? "tbx-pinned-border-left" : void 0
835
+ ].filter(Boolean).join(" "),
836
+ style: {
837
+ left: leftOffsets.get("__serial")
838
+ },
839
+ scope: "col",
840
+ children: locale.serialHeader
841
+ }
842
+ ) : null,
666
843
  visible.map((col, index) => {
667
844
  const id = (0, import_core2.getColumnId)(col);
668
- const sortable = (0, import_core2.isSortable)(col);
669
- const sorted = sortable && id !== "" && sort?.field === id;
845
+ const sortable = (0, import_core2.isSortable)(col) && enableSorting !== false;
846
+ const sortIndex = query.sort.findIndex((s) => s.field === id);
847
+ const sortItem = sortIndex >= 0 ? query.sort[sortIndex] : void 0;
848
+ const sorted = sortable && sortItem !== void 0;
670
849
  const title = (0, import_core2.getColumnTitle)(col) || id;
671
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
850
+ const meta = col.meta;
851
+ const activeFilter = query.filter?.[id];
852
+ const isFilterActive = activeFilter !== void 0 && activeFilter !== "";
853
+ const customWidth = colWidths[id];
854
+ const baseStyle = headerCellStyle(col);
855
+ const thStyle = {
856
+ ...baseStyle,
857
+ ...customWidth !== void 0 ? { width: `${customWidth}px` } : {},
858
+ ...leftOffsets.has(id) ? { left: leftOffsets.get(id) } : {},
859
+ ...rightOffsets.has(id) ? { right: rightOffsets.get(id) } : {}
860
+ };
861
+ const startResize = (e) => {
862
+ e.preventDefault();
863
+ e.stopPropagation();
864
+ const startX = e.clientX;
865
+ const targetTh = e.currentTarget.parentElement || null;
866
+ const startWidth = targetTh ? targetTh.getBoundingClientRect().width : customWidth ?? meta?.width ?? 120;
867
+ const onMove = (moveEvent) => {
868
+ const nextW = Math.max(meta?.minWidth ?? 60, Math.round(startWidth + (moveEvent.clientX - startX)));
869
+ setColWidths((prev) => ({ ...prev, [id]: nextW }));
870
+ };
871
+ const onUp = () => {
872
+ document.removeEventListener("pointermove", onMove);
873
+ document.removeEventListener("pointerup", onUp);
874
+ document.body?.classList.remove("tbx-resizing");
875
+ };
876
+ document.body?.classList.add("tbx-resizing");
877
+ document.addEventListener("pointermove", onMove);
878
+ document.addEventListener("pointerup", onUp);
879
+ };
880
+ const isPinnedLeft = leftOffsets.has(id);
881
+ const isPinnedRight = rightOffsets.has(id);
882
+ const isLastLeft = lastLeftPinnedId === id;
883
+ const isFirstRight = firstRightPinnedId === id;
884
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
672
885
  "th",
673
886
  {
674
887
  scope: "col",
675
- className: sortable ? "nxg-th nxg-th--sortable" : "nxg-th",
676
- style: headerCellStyle(col),
677
- "aria-sort": sorted ? sort?.dir === "asc" ? "ascending" : "descending" : sortable ? "none" : void 0,
888
+ className: [
889
+ "tbx-th",
890
+ sortable ? "tbx-th--sortable" : void 0,
891
+ enableColumnReorder ? "tbx-th--draggable" : void 0,
892
+ isPinnedLeft ? "tbx-th--pinned-left" : void 0,
893
+ isPinnedRight ? "tbx-th--pinned-right" : void 0,
894
+ isLastLeft ? "tbx-pinned-border-left" : void 0,
895
+ isFirstRight ? "tbx-pinned-border-right" : void 0
896
+ ].filter(Boolean).join(" "),
897
+ style: thStyle,
898
+ "aria-sort": sorted ? sortItem?.dir === "asc" ? "ascending" : "descending" : sortable ? "none" : void 0,
678
899
  "aria-label": title || void 0,
679
900
  tabIndex: sortable ? 0 : void 0,
680
- onClick: sortable ? () => toggleSort(id) : void 0,
901
+ draggable: enableColumnReorder,
902
+ onDragStart: enableColumnReorder ? (e) => {
903
+ setDraggedColId(id);
904
+ e.dataTransfer.setData("text/plain", id);
905
+ } : void 0,
906
+ onDragOver: enableColumnReorder ? (e) => {
907
+ e.preventDefault();
908
+ } : void 0,
909
+ onDrop: enableColumnReorder ? (e) => {
910
+ e.preventDefault();
911
+ if (!draggedColId || draggedColId === id) return;
912
+ const fromIdx = colList.findIndex((c) => (0, import_core2.getColumnId)(c) === draggedColId);
913
+ const toIdx = colList.findIndex((c) => (0, import_core2.getColumnId)(c) === id);
914
+ if (fromIdx >= 0 && toIdx >= 0) {
915
+ const nextCols = [...colList];
916
+ const moved = nextCols[fromIdx];
917
+ if (!moved) return;
918
+ nextCols.splice(fromIdx, 1);
919
+ nextCols.splice(toIdx, 0, moved);
920
+ setColList(nextCols);
921
+ onColumnOrderChange?.(nextCols.map(import_core2.getColumnId));
922
+ }
923
+ setDraggedColId(null);
924
+ } : void 0,
925
+ onClick: sortable ? (event) => {
926
+ const t = event.target;
927
+ if (t?.closest(".tbx-col-filter-wrap") || t?.closest(".tbx-resize-handle")) return;
928
+ toggleSort(id, event.shiftKey);
929
+ } : void 0,
681
930
  onKeyDown: sortable ? (event) => {
682
931
  if (event.key !== "Enter" && event.key !== " ") return;
932
+ const t = event.target;
933
+ if (t?.closest(".tbx-col-filter-wrap")) return;
683
934
  event.preventDefault();
684
- toggleSort(id);
935
+ toggleSort(id, event.shiftKey);
685
936
  } : void 0,
686
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: headerInnerClass(col), children: [
687
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: renderColumnHeader(col) }),
688
- sortable ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: sorted ? sort?.dir === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArrowUpIcon, { className: "nxg-sort-icon" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArrowDownIcon, { className: "nxg-sort-icon" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArrowUpDownIcon, { className: "nxg-sort-icon nxg-sort-icon--idle" }) }) : null
689
- ] })
937
+ children: [
938
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: headerInnerClass(col), children: [
939
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: renderColumnHeader(col) }),
940
+ sortable ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "tbx-sort-icon-wrap", children: [
941
+ sorted ? sortItem?.dir === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArrowUpIcon, { className: "tbx-sort-icon" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArrowDownIcon, { className: "tbx-sort-icon" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ArrowUpDownIcon, { className: "tbx-sort-icon tbx-sort-icon--idle" }),
942
+ query.sort.length > 1 && sortIndex >= 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-sort-order", children: sortIndex + 1 }) : null
943
+ ] }) : null,
944
+ (0, import_core2.isFilterable)(col, enableColumnFilters) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-col-filter-wrap", children: [
945
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
946
+ "button",
947
+ {
948
+ type: "button",
949
+ className: isFilterActive ? "tbx-col-filter-btn tbx-col-filter-btn--active" : "tbx-col-filter-btn",
950
+ "aria-label": `Filter ${title}`,
951
+ onClick: (e) => {
952
+ e.stopPropagation();
953
+ setOpenFilterCol(openFilterCol === id ? null : id);
954
+ },
955
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DotsVerticalIcon, { className: "tbx-icon" })
956
+ }
957
+ ),
958
+ openFilterCol === id ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
959
+ "div",
960
+ {
961
+ className: "tbx-filter-popover",
962
+ onClick: (e) => e.stopPropagation(),
963
+ children: [
964
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
965
+ "input",
966
+ {
967
+ autoFocus: true,
968
+ type: "text",
969
+ className: "tbx-filter-popover-input",
970
+ defaultValue: activeFilter ?? "",
971
+ placeholder: meta?.filterPlaceholder || (0, import_core2.formatMessage)(locale.filterColumnPlaceholder, { column: title }),
972
+ "aria-label": `Filter ${title}`,
973
+ onChange: (e) => {
974
+ const val = e.currentTarget.value.toLowerCase().trim();
975
+ const pop = e.currentTarget.closest(".tbx-filter-popover");
976
+ const opts = pop?.querySelectorAll(".tbx-filter-option:not(:first-child)");
977
+ opts?.forEach((opt) => {
978
+ const text = opt.textContent?.toLowerCase() ?? "";
979
+ opt.style.display = !val || text.includes(val) ? "" : "none";
980
+ });
981
+ },
982
+ onKeyDown: (e) => {
983
+ if (e.key === "Enter") {
984
+ e.preventDefault();
985
+ const val = e.currentTarget.value.trim();
986
+ setOpenFilterCol(null);
987
+ onQueryChange((0, import_core2.withFilter)(query, id, val || void 0));
988
+ } else if (e.key === "Escape") {
989
+ e.preventDefault();
990
+ setOpenFilterCol(null);
991
+ }
992
+ }
993
+ }
994
+ ),
995
+ meta?.filterOptions && meta.filterOptions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-filter-popover-options", children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
997
+ "div",
998
+ {
999
+ className: !activeFilter ? "tbx-filter-option tbx-filter-option--selected" : "tbx-filter-option",
1000
+ onClick: () => {
1001
+ setOpenFilterCol(null);
1002
+ onQueryChange((0, import_core2.withFilter)(query, id, void 0));
1003
+ },
1004
+ children: locale.filterAll
1005
+ }
1006
+ ),
1007
+ meta.filterOptions.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1008
+ "div",
1009
+ {
1010
+ className: activeFilter === opt ? "tbx-filter-option tbx-filter-option--selected" : "tbx-filter-option",
1011
+ onClick: () => {
1012
+ setOpenFilterCol(null);
1013
+ onQueryChange((0, import_core2.withFilter)(query, id, opt));
1014
+ },
1015
+ children: opt
1016
+ },
1017
+ opt
1018
+ ))
1019
+ ] }) : null,
1020
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-filter-popover-actions", children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1022
+ "button",
1023
+ {
1024
+ type: "button",
1025
+ className: "tbx-filter-popover-btn",
1026
+ onClick: (e) => {
1027
+ e.stopPropagation();
1028
+ setOpenFilterCol(null);
1029
+ onQueryChange((0, import_core2.withFilter)(query, id, void 0));
1030
+ },
1031
+ children: [
1032
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RotateCcwIcon, { className: "tbx-icon" }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: locale.clearFilter })
1034
+ ]
1035
+ }
1036
+ ),
1037
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1038
+ "button",
1039
+ {
1040
+ type: "button",
1041
+ className: "tbx-filter-popover-btn tbx-filter-popover-btn--primary",
1042
+ onClick: (e) => {
1043
+ e.stopPropagation();
1044
+ const parent = e.currentTarget.closest(".tbx-filter-popover");
1045
+ const inputEl = parent?.querySelector("input");
1046
+ const val = inputEl?.value.trim();
1047
+ setOpenFilterCol(null);
1048
+ onQueryChange((0, import_core2.withFilter)(query, id, val || void 0));
1049
+ },
1050
+ children: [
1051
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "tbx-icon" }),
1052
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: locale.applyFilter })
1053
+ ]
1054
+ }
1055
+ )
1056
+ ] })
1057
+ ]
1058
+ }
1059
+ ) : null
1060
+ ] }) : null
1061
+ ] }),
1062
+ enableColumnResize ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-resize-handle", onPointerDown: startResize }) : null
1063
+ ]
690
1064
  },
691
1065
  id || `col-${index}`
692
1066
  );
693
1067
  })
694
1068
  ] }) }),
695
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("td", { className: "nxg-state", colSpan: Math.max(1, columnCount), children: [
696
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "nxg-spinner", "aria-hidden": "true" }),
697
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "aria-live": "polite", children: locale.loadingText })
698
- ] }) }) : data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "nxg-state", colSpan: Math.max(1, columnCount), children: locale.emptyText }) }) : data.map((row, index) => {
1069
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("td", { className: "tbx-state", colSpan: Math.max(1, columnCount), children: [
1070
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-dotted-loader", "aria-hidden": "true", children: [
1071
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" }),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" }),
1074
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" })
1075
+ ] }),
1076
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-loading-text", "aria-live": "polite", children: locale.loadingText })
1077
+ ] }) }) : data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "tbx-state", colSpan: Math.max(1, columnCount), children: locale.emptyText }) }) : data.map((row, index) => {
699
1078
  const id = pageRowIds[index] ?? String(index);
700
1079
  const isSelected = selectedIds.has(id);
701
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
702
- "tr",
703
- {
704
- className: [
705
- "nxg-row",
706
- isSelected ? "nxg-row--selected" : void 0,
707
- rowIsClickable ? "nxg-row--clickable" : void 0
708
- ].filter(Boolean).join(" "),
709
- onClick: rowIsClickable ? activateRow(row) : void 0,
710
- onKeyDown: rowIsClickable ? rowKeyDown(row) : void 0,
711
- tabIndex: rowIsClickable ? 0 : void 0,
712
- children: [
713
- showSerialNumber ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "nxg-td nxg-td--serial", children: (0, import_core2.serialNumber)(currentPage, pageSize, index) }) : null,
714
- enableSelection ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
715
- "td",
716
- {
717
- className: "nxg-td nxg-td--select",
718
- onClick: (event) => event.stopPropagation(),
719
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
720
- "input",
1080
+ const isExpanded = expandedRows.has(id);
1081
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(React4.Fragment, { children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1083
+ "tr",
1084
+ {
1085
+ className: [
1086
+ "tbx-row",
1087
+ isSelected ? "tbx-row--selected" : void 0,
1088
+ isExpanded ? "tbx-row--expanded" : void 0,
1089
+ rowIsClickable ? "tbx-row--clickable" : void 0
1090
+ ].filter(Boolean).join(" "),
1091
+ onClick: rowIsClickable ? activateRow(row) : void 0,
1092
+ onKeyDown: rowIsClickable ? rowKeyDown(row) : void 0,
1093
+ tabIndex: rowIsClickable ? 0 : void 0,
1094
+ children: [
1095
+ renderExpandedRow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1096
+ "td",
1097
+ {
1098
+ className: [
1099
+ "tbx-td tbx-td--expand",
1100
+ leftOffsets.has("__expand") ? "tbx-td--pinned-left" : void 0,
1101
+ lastLeftPinnedId === "__expand" ? "tbx-pinned-border-left" : void 0
1102
+ ].filter(Boolean).join(" "),
1103
+ style: {
1104
+ width: "40px",
1105
+ left: leftOffsets.get("__expand")
1106
+ },
1107
+ onClick: (e) => e.stopPropagation(),
1108
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1109
+ "button",
1110
+ {
1111
+ type: "button",
1112
+ className: isExpanded ? "tbx-expand-btn tbx-expand-btn--open" : "tbx-expand-btn",
1113
+ "aria-label": isExpanded ? "Collapse row" : "Expand row",
1114
+ onClick: (e) => {
1115
+ e.stopPropagation();
1116
+ toggleExpandRow(id);
1117
+ },
1118
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronRightIcon, { className: "tbx-icon" })
1119
+ }
1120
+ )
1121
+ }
1122
+ ) : null,
1123
+ enableSelection ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1124
+ "td",
1125
+ {
1126
+ className: [
1127
+ "tbx-td tbx-td--select",
1128
+ leftOffsets.has("__select") ? "tbx-td--pinned-left" : void 0,
1129
+ lastLeftPinnedId === "__select" ? "tbx-pinned-border-left" : void 0
1130
+ ].filter(Boolean).join(" "),
1131
+ style: {
1132
+ left: leftOffsets.get("__select")
1133
+ },
1134
+ onClick: (event) => event.stopPropagation(),
1135
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1136
+ "input",
1137
+ {
1138
+ type: isSingleSelect ? "radio" : "checkbox",
1139
+ name: isSingleSelect ? `${instanceId}-select` : void 0,
1140
+ className: "tbx-checkbox",
1141
+ checked: isSelected,
1142
+ onChange: () => toggleSelectRow(id),
1143
+ onClick: (event) => event.stopPropagation(),
1144
+ "aria-label": (0, import_core2.formatMessage)(locale.selectRowLabel, { id })
1145
+ }
1146
+ )
1147
+ }
1148
+ ) : null,
1149
+ showSerialNumber ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1150
+ "td",
1151
+ {
1152
+ className: [
1153
+ "tbx-td tbx-td--serial",
1154
+ leftOffsets.has("__serial") ? "tbx-td--pinned-left" : void 0,
1155
+ lastLeftPinnedId === "__serial" ? "tbx-pinned-border-left" : void 0
1156
+ ].filter(Boolean).join(" "),
1157
+ style: {
1158
+ left: leftOffsets.get("__serial")
1159
+ },
1160
+ children: (0, import_core2.serialNumber)(currentPage, pageSize, index)
1161
+ }
1162
+ ) : null,
1163
+ visible.map((col, colIndex) => {
1164
+ const colId = (0, import_core2.getColumnId)(col);
1165
+ const isPinnedLeft = leftOffsets.has(colId);
1166
+ const isPinnedRight = rightOffsets.has(colId);
1167
+ const isLastLeft = lastLeftPinnedId === colId;
1168
+ const isFirstRight = firstRightPinnedId === colId;
1169
+ const isCellEditable = (0, import_core2.isEditable)(col);
1170
+ const isCurrentlyEditing = editingCell?.rowId === id && editingCell?.columnId === colId;
1171
+ const tdStyle = {
1172
+ ...bodyCellStyle(col),
1173
+ ...isPinnedLeft ? { left: leftOffsets.get(colId) } : {},
1174
+ ...isPinnedRight ? { right: rightOffsets.get(colId) } : {}
1175
+ };
1176
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1177
+ "td",
721
1178
  {
722
- type: "checkbox",
723
- className: "nxg-checkbox",
724
- checked: isSelected,
725
- onChange: () => toggleSelectRow(id),
726
- onClick: (event) => event.stopPropagation(),
727
- "aria-label": (0, import_core2.formatMessage)(locale.selectRowLabel, { id })
728
- }
729
- )
730
- }
731
- ) : null,
732
- visible.map((col, colIndex) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
733
- "td",
734
- {
735
- className: "nxg-td",
736
- style: bodyCellStyle(col),
737
- children: renderCellContent(col, row, boolLabels)
738
- },
739
- (0, import_core2.getColumnId)(col) || `col-${colIndex}`
740
- ))
741
- ]
742
- },
743
- id || index
744
- );
1179
+ className: [
1180
+ "tbx-td",
1181
+ isCellEditable ? "tbx-cell--editable" : void 0,
1182
+ isPinnedLeft ? "tbx-td--pinned-left" : void 0,
1183
+ isPinnedRight ? "tbx-td--pinned-right" : void 0,
1184
+ isLastLeft ? "tbx-pinned-border-left" : void 0,
1185
+ isFirstRight ? "tbx-pinned-border-right" : void 0
1186
+ ].filter(Boolean).join(" "),
1187
+ style: tdStyle,
1188
+ onDoubleClick: isCellEditable ? (e) => {
1189
+ e.stopPropagation();
1190
+ setEditingCell({ rowId: id, columnId: colId });
1191
+ } : void 0,
1192
+ children: isCurrentlyEditing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1193
+ "div",
1194
+ {
1195
+ className: "tbx-cell-edit-wrap",
1196
+ onClick: (e) => e.stopPropagation(),
1197
+ children: [
1198
+ col.meta?.editType === "select" && col.meta.editOptions ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1199
+ "select",
1200
+ {
1201
+ autoFocus: true,
1202
+ className: "tbx-cell-edit-select tbx-cell-edit-input",
1203
+ defaultValue: String(row[colId] ?? ""),
1204
+ id: `${instanceId}-edit-${id}-${colId}`,
1205
+ children: col.meta.editOptions.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: opt, children: opt }, opt))
1206
+ }
1207
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1208
+ "input",
1209
+ {
1210
+ autoFocus: true,
1211
+ type: col.meta?.editType === "number" ? "number" : "text",
1212
+ className: "tbx-cell-edit-input",
1213
+ defaultValue: String(row[colId] ?? ""),
1214
+ id: `${instanceId}-edit-${id}-${colId}`,
1215
+ onKeyDown: (e) => {
1216
+ if (e.key === "Enter") {
1217
+ e.preventDefault();
1218
+ const el = e.currentTarget;
1219
+ const val = col.meta?.editType === "number" ? Number(el.value) : el.value;
1220
+ const oldVal = row[colId];
1221
+ row[colId] = val;
1222
+ onCellEdit?.({ row, columnId: colId, oldValue: oldVal, newValue: val });
1223
+ setEditingCell(null);
1224
+ } else if (e.key === "Escape") {
1225
+ e.preventDefault();
1226
+ setEditingCell(null);
1227
+ }
1228
+ }
1229
+ }
1230
+ ),
1231
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-cell-edit-actions", children: [
1232
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1233
+ "button",
1234
+ {
1235
+ type: "button",
1236
+ className: "tbx-cell-edit-btn tbx-cell-edit-btn--save",
1237
+ onClick: (e) => {
1238
+ e.stopPropagation();
1239
+ const el = document.getElementById(`${instanceId}-edit-${id}-${colId}`);
1240
+ if (el) {
1241
+ const val = col.meta?.editType === "number" ? Number(el.value) : el.value;
1242
+ const oldVal = row[colId];
1243
+ row[colId] = val;
1244
+ onCellEdit?.({ row, columnId: colId, oldValue: oldVal, newValue: val });
1245
+ }
1246
+ setEditingCell(null);
1247
+ },
1248
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "tbx-icon" })
1249
+ }
1250
+ ),
1251
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1252
+ "button",
1253
+ {
1254
+ type: "button",
1255
+ className: "tbx-cell-edit-btn tbx-cell-edit-btn--cancel",
1256
+ onClick: (e) => {
1257
+ e.stopPropagation();
1258
+ setEditingCell(null);
1259
+ },
1260
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(XIcon, { className: "tbx-icon" })
1261
+ }
1262
+ )
1263
+ ] })
1264
+ ]
1265
+ }
1266
+ ) : renderCellContent(col, row, boolLabels)
1267
+ },
1268
+ colId || `col-${colIndex}`
1269
+ );
1270
+ })
1271
+ ]
1272
+ }
1273
+ ),
1274
+ isExpanded && renderExpandedRow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { className: "tbx-expanded-row", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "tbx-expanded-cell", colSpan: columnCount, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-detail-panel", children: renderExpandedRow(row) }) }) }) : null
1275
+ ] }, id || index);
745
1276
  }) })
746
1277
  ] }) }),
747
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-cards", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-card", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-state", children: [
748
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "nxg-spinner", "aria-hidden": "true" }),
749
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "aria-live": "polite", children: locale.loadingText })
750
- ] }) }) : data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-card", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "nxg-state", children: locale.emptyText }) }) : data.map((row, index) => {
1278
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-cards", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-card", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-state", children: [
1279
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-dotted-loader", "aria-hidden": "true", children: [
1280
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" }),
1282
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" }),
1283
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-dot" })
1284
+ ] }),
1285
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-loading-text", "aria-live": "polite", children: locale.loadingText })
1286
+ ] }) }) : data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-card", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "tbx-state", children: locale.emptyText }) }) : data.map((row, index) => {
751
1287
  const id = pageRowIds[index] ?? String(index);
752
1288
  const isSelected = selectedIds.has(id);
753
1289
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
754
1290
  "div",
755
1291
  {
756
1292
  className: [
757
- "nxg-card",
758
- isSelected ? "nxg-card--selected" : void 0,
759
- rowIsClickable ? "nxg-card--clickable" : void 0
1293
+ "tbx-card",
1294
+ isSelected ? "tbx-card--selected" : void 0,
1295
+ rowIsClickable ? "tbx-card--clickable" : void 0
760
1296
  ].filter(Boolean).join(" "),
761
1297
  onClick: rowIsClickable ? activateRow(row) : void 0,
762
1298
  onKeyDown: rowIsClickable ? rowKeyDown(row) : void 0,
763
1299
  tabIndex: rowIsClickable ? 0 : void 0,
764
1300
  children: [
765
- showSerialNumber || enableSelection ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-card-head", children: [
766
- showSerialNumber ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "nxg-card-serial", children: [
1301
+ showSerialNumber || enableSelection ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-card-head", children: [
1302
+ showSerialNumber ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "tbx-card-serial", children: [
767
1303
  "#",
768
1304
  (0, import_core2.serialNumber)(currentPage, pageSize, index)
769
1305
  ] }) : null,
770
1306
  enableSelection ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
771
1307
  "span",
772
1308
  {
773
- className: "nxg-card-select",
1309
+ className: "tbx-card-select",
774
1310
  onClick: (event) => event.stopPropagation(),
775
1311
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
776
1312
  "input",
777
1313
  {
778
1314
  type: "checkbox",
779
- className: "nxg-checkbox",
1315
+ className: "tbx-checkbox",
780
1316
  checked: isSelected,
781
1317
  onChange: () => toggleSelectRow(id),
782
1318
  onClick: (event) => event.stopPropagation(),
@@ -786,9 +1322,9 @@ function NexGrid({
786
1322
  }
787
1323
  ) : null
788
1324
  ] }) : null,
789
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dl", { className: "nxg-card-rows", children: visible.map((col, colIndex) => {
1325
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dl", { className: "tbx-card-rows", children: visible.map((col, colIndex) => {
790
1326
  const colId = (0, import_core2.getColumnId)(col);
791
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-card-row", children: [
1327
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-card-row", children: [
792
1328
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: (0, import_core2.getColumnTitle)(col) || colId }),
793
1329
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: renderCellContent(col, row, boolLabels) })
794
1330
  ] }, colId || `col-${colIndex}`);
@@ -798,52 +1334,55 @@ function NexGrid({
798
1334
  id || index
799
1335
  );
800
1336
  }) }),
801
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-footer", children: [
802
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-range", children: [
1337
+ showFooter ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-footer", children: [
1338
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-range", children: [
803
1339
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: renderTemplate(locale.showingRange, {
804
1340
  start: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: range.start }),
805
1341
  end: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: range.end }),
806
- total: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { className: "nxg-range-total", children: total.toLocaleString() })
1342
+ total: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { className: "tbx-range-total", children: total.toLocaleString() })
807
1343
  }) }),
808
- selectedIds.size > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "nxg-selected-badge", children: (0, import_core2.formatMessage)(locale.selectedBadge, { count: selectedIds.size }) }) : null
1344
+ selectedIds.size > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-selected-badge", children: (0, import_core2.formatMessage)(locale.selectedBadge, { count: selectedIds.size }) }) : null
809
1345
  ] }),
810
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-pagination", children: [
811
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-rows-per-page", children: [
1346
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-pagination", children: [
1347
+ isRowsPerPageVisible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-rows-per-page", children: [
812
1348
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: locale.rowsPerPage }),
813
1349
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
814
1350
  "select",
815
1351
  {
816
- className: "nxg-rows-select",
1352
+ className: "tbx-rows-select",
817
1353
  value: String(pageSize),
818
1354
  onChange: (event) => {
819
1355
  onQueryChange((0, import_core2.withPageSize)(query, Number.parseInt(event.target.value, 10)));
820
1356
  },
821
1357
  "aria-label": locale.rowsPerPage,
822
- children: import_core2.PAGE_SIZES.map((size) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: size, children: size }, size))
1358
+ children: import_core2.PAGE_SIZES.map((size) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("option", { value: size, children: [
1359
+ size,
1360
+ " rows"
1361
+ ] }, size))
823
1362
  }
824
1363
  )
825
- ] }),
826
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "nxg-pager", children: [
1364
+ ] }) : null,
1365
+ isPaginationVisible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "tbx-pager", children: [
827
1366
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
828
1367
  "button",
829
1368
  {
830
1369
  type: "button",
831
- className: "nxg-page-nav",
1370
+ className: "tbx-page-nav",
832
1371
  disabled: currentPage <= 1,
833
1372
  onClick: () => onQueryChange((0, import_core2.withPage)(query, currentPage - 1, totalPages)),
834
1373
  "aria-label": locale.previousPage,
835
1374
  children: [
836
1375
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronLeftIcon, { size: 16 }),
837
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "nxg-sr-only", children: locale.previousPage })
1376
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-sr-only", children: locale.previousPage })
838
1377
  ]
839
1378
  }
840
1379
  ),
841
1380
  pageItems.map(
842
- (item, index) => item === "..." ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "nxg-page-ellipsis", children: "\u2026" }, `gap-${index}`) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1381
+ (item, index) => item === "..." ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-page-ellipsis", children: "\u2026" }, `gap-${index}`) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
843
1382
  "button",
844
1383
  {
845
1384
  type: "button",
846
- className: item === currentPage ? "nxg-page-btn nxg-page-btn--current" : "nxg-page-btn",
1385
+ className: item === currentPage ? "tbx-page-btn tbx-page-btn--current" : "tbx-page-btn",
847
1386
  onClick: () => onQueryChange((0, import_core2.withPage)(query, item, totalPages)),
848
1387
  "aria-label": (0, import_core2.formatMessage)(locale.pageLabel, { page: item }),
849
1388
  "aria-current": item === currentPage ? "page" : void 0,
@@ -856,43 +1395,54 @@ function NexGrid({
856
1395
  "button",
857
1396
  {
858
1397
  type: "button",
859
- className: "nxg-page-nav",
1398
+ className: "tbx-page-nav",
860
1399
  disabled: currentPage >= totalPages,
861
1400
  onClick: () => onQueryChange((0, import_core2.withPage)(query, currentPage + 1, totalPages)),
862
1401
  "aria-label": locale.nextPage,
863
1402
  children: [
864
1403
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronRightIcon, { size: 16 }),
865
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "nxg-sr-only", children: locale.nextPage })
1404
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "tbx-sr-only", children: locale.nextPage })
866
1405
  ]
867
1406
  }
868
1407
  )
869
- ] }),
870
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { className: "nxg-jump", onSubmit: submitJump, children: [
871
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "nxg-jump-label", htmlFor: jumpInputId, children: locale.goToPage }),
872
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
873
- "input",
874
- {
875
- id: jumpInputId,
876
- type: "number",
877
- className: "nxg-jump-input",
878
- min: 1,
879
- max: totalPages,
880
- value: jumpText,
881
- onChange: (event) => setJumpText(event.target.value),
882
- onBlur: () => submitJump(),
883
- "aria-label": locale.goToPageOf
884
- }
885
- )
886
- ] })
1408
+ ] }) : null,
1409
+ isJumpToPageVisible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1410
+ "form",
1411
+ {
1412
+ className: "tbx-jump",
1413
+ onSubmit: (event) => {
1414
+ event.preventDefault();
1415
+ submitJump(jumpText);
1416
+ },
1417
+ children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "tbx-jump-label", htmlFor: jumpInputId, children: locale.goToPage }),
1419
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1420
+ "input",
1421
+ {
1422
+ id: jumpInputId,
1423
+ type: "number",
1424
+ className: "tbx-jump-input",
1425
+ min: 1,
1426
+ max: totalPages,
1427
+ value: jumpText,
1428
+ onChange: (event) => setJumpText(event.target.value),
1429
+ onBlur: () => submitJump(jumpText),
1430
+ "aria-label": locale.goToPageOf
1431
+ }
1432
+ )
1433
+ ]
1434
+ }
1435
+ ) : null
887
1436
  ] })
888
- ] })
1437
+ ] }) : null
889
1438
  ] });
890
1439
  }
1440
+ var NexGrid = TableX;
891
1441
 
892
- // src/use-client-nex-grid.ts
1442
+ // src/use-client-table-x.ts
893
1443
  var import_react = require("react");
894
1444
  var import_core3 = require("@nexgrid/core");
895
- function useClientNexGrid(allData, options) {
1445
+ function useClientTableX(allData, options) {
896
1446
  const [query, setQuery] = (0, import_react.useState)(() => ({
897
1447
  ...(0, import_core3.defaultQuery)(),
898
1448
  ...options?.initialQuery
@@ -912,6 +1462,7 @@ function useClientNexGrid(allData, options) {
912
1462
  setQuery
913
1463
  };
914
1464
  }
1465
+ var useClientNexGrid = useClientTableX;
915
1466
 
916
1467
  // src/index.ts
917
1468
  var import_core4 = require("@nexgrid/core");
@@ -921,6 +1472,7 @@ var import_core4 = require("@nexgrid/core");
921
1472
  DEFAULT_PAGE_SIZE,
922
1473
  NexGrid,
923
1474
  PAGE_SIZES,
1475
+ TableX,
924
1476
  buildODataUrl,
925
1477
  buildQueryUrl,
926
1478
  defaultQuery,
@@ -934,11 +1486,13 @@ var import_core4 = require("@nexgrid/core");
934
1486
  toODataParams,
935
1487
  totalPagesFor,
936
1488
  useClientNexGrid,
1489
+ useClientTableX,
937
1490
  withFilter,
938
1491
  withPage,
939
1492
  withPageSize,
940
1493
  withSearch,
941
1494
  withSort,
1495
+ withToggledMultiSort,
942
1496
  withToggledSort
943
1497
  });
944
1498
  //# sourceMappingURL=index.cjs.map