@zvndev/yable-react 0.2.0 → 0.3.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
@@ -215,7 +215,15 @@ function useVirtualization({
215
215
  container.scrollTop = offset;
216
216
  }
217
217
  },
218
- [containerRef, totalRows, rowHeight, isFixedHeight, getRowHeight, hasPretextHeights, pretextPrefixSums]
218
+ [
219
+ containerRef,
220
+ totalRows,
221
+ rowHeight,
222
+ isFixedHeight,
223
+ getRowHeight,
224
+ hasPretextHeights,
225
+ pretextPrefixSums
226
+ ]
219
227
  );
220
228
  const totalHeight = React3.useMemo(() => {
221
229
  if (totalRows === 0) return 0;
@@ -269,10 +277,7 @@ function useVirtualization({
269
277
  } else if (isFixedHeight) {
270
278
  const fixedH = rowHeight;
271
279
  startIndex = Math.floor(scrollTop / fixedH);
272
- endIndex = Math.min(
273
- totalRows - 1,
274
- Math.ceil((scrollTop + containerHeight) / fixedH) - 1
275
- );
280
+ endIndex = Math.min(totalRows - 1, Math.ceil((scrollTop + containerHeight) / fixedH) - 1);
276
281
  } else {
277
282
  let accum = 0;
278
283
  let foundStart = false;
@@ -339,6 +344,162 @@ function useVirtualization({
339
344
  scrollTo
340
345
  };
341
346
  }
347
+ function binarySearchOffsets(offsets, target) {
348
+ let low = 0;
349
+ let high = offsets.length - 1;
350
+ while (low < high) {
351
+ const mid = low + high >>> 1;
352
+ if (offsets[mid + 1] <= target) {
353
+ low = mid + 1;
354
+ } else {
355
+ high = mid;
356
+ }
357
+ }
358
+ return low;
359
+ }
360
+ function useColumnVirtualization({
361
+ containerRef,
362
+ columns,
363
+ overscan = 2,
364
+ enabled = true
365
+ }) {
366
+ const [scrollState, setScrollState] = React3.useState({
367
+ scrollLeft: 0,
368
+ containerWidth: 0
369
+ });
370
+ const rafRef = React3.useRef(null);
371
+ const sizes = React3.useMemo(() => columns.map((column) => column.getSize()), [columns]);
372
+ const offsets = React3.useMemo(() => {
373
+ const next = new Array(columns.length + 1);
374
+ next[0] = 0;
375
+ for (let i = 0; i < columns.length; i++) {
376
+ next[i + 1] = next[i] + (sizes[i] ?? 0);
377
+ }
378
+ return next;
379
+ }, [columns.length, sizes]);
380
+ const totalWidth = offsets[offsets.length - 1] ?? 0;
381
+ React3.useEffect(() => {
382
+ const container = containerRef.current;
383
+ if (!container) return;
384
+ setScrollState({
385
+ scrollLeft: container.scrollLeft,
386
+ containerWidth: container.clientWidth
387
+ });
388
+ const handleScroll = () => {
389
+ if (rafRef.current !== null) return;
390
+ rafRef.current = requestAnimationFrame(() => {
391
+ rafRef.current = null;
392
+ const el = containerRef.current;
393
+ if (!el) return;
394
+ setScrollState({
395
+ scrollLeft: el.scrollLeft,
396
+ containerWidth: el.clientWidth
397
+ });
398
+ });
399
+ };
400
+ container.addEventListener("scroll", handleScroll, { passive: true });
401
+ let resizeObserver;
402
+ if (typeof ResizeObserver !== "undefined") {
403
+ resizeObserver = new ResizeObserver(() => {
404
+ const el = containerRef.current;
405
+ if (!el) return;
406
+ setScrollState((prev) => {
407
+ const nextWidth = el.clientWidth;
408
+ if (prev.containerWidth === nextWidth && prev.scrollLeft === el.scrollLeft) {
409
+ return prev;
410
+ }
411
+ return {
412
+ scrollLeft: el.scrollLeft,
413
+ containerWidth: nextWidth
414
+ };
415
+ });
416
+ });
417
+ resizeObserver.observe(container);
418
+ }
419
+ return () => {
420
+ container.removeEventListener("scroll", handleScroll);
421
+ if (rafRef.current !== null) {
422
+ cancelAnimationFrame(rafRef.current);
423
+ rafRef.current = null;
424
+ }
425
+ resizeObserver?.disconnect();
426
+ };
427
+ }, [containerRef]);
428
+ const scrollToIndex = React3.useCallback(
429
+ (index) => {
430
+ const container = containerRef.current;
431
+ if (!container || columns.length === 0) return;
432
+ const clampedIndex = Math.max(0, Math.min(index, columns.length - 1));
433
+ container.scrollLeft = offsets[clampedIndex] ?? 0;
434
+ },
435
+ [columns.length, containerRef, offsets]
436
+ );
437
+ if (!enabled || columns.length === 0) {
438
+ return {
439
+ virtualColumns: columns.map((column, index) => ({
440
+ column,
441
+ index,
442
+ start: offsets[index] ?? 0,
443
+ size: sizes[index] ?? 0
444
+ })),
445
+ startOffset: 0,
446
+ endOffset: 0,
447
+ totalWidth,
448
+ visibleWidth: totalWidth,
449
+ startIndex: 0,
450
+ endIndex: Math.max(columns.length - 1, 0),
451
+ isVirtualized: false,
452
+ scrollToIndex
453
+ };
454
+ }
455
+ const { scrollLeft, containerWidth } = scrollState;
456
+ if (containerWidth <= 0 || totalWidth <= containerWidth) {
457
+ return {
458
+ virtualColumns: columns.map((column, index) => ({
459
+ column,
460
+ index,
461
+ start: offsets[index] ?? 0,
462
+ size: sizes[index] ?? 0
463
+ })),
464
+ startOffset: 0,
465
+ endOffset: 0,
466
+ totalWidth,
467
+ visibleWidth: totalWidth,
468
+ startIndex: 0,
469
+ endIndex: Math.max(columns.length - 1, 0),
470
+ isVirtualized: false,
471
+ scrollToIndex
472
+ };
473
+ }
474
+ const startIndex = binarySearchOffsets(offsets, scrollLeft);
475
+ const endBoundary = scrollLeft + containerWidth;
476
+ const endIndex = binarySearchOffsets(offsets, Math.max(scrollLeft, endBoundary - 1));
477
+ const overscanStart = Math.max(0, startIndex - overscan);
478
+ const overscanEnd = Math.min(columns.length - 1, endIndex + overscan);
479
+ const virtualColumns = [];
480
+ for (let i = overscanStart; i <= overscanEnd; i++) {
481
+ virtualColumns.push({
482
+ column: columns[i],
483
+ index: i,
484
+ start: offsets[i] ?? 0,
485
+ size: sizes[i] ?? 0
486
+ });
487
+ }
488
+ const startOffset = offsets[overscanStart] ?? 0;
489
+ const visibleWidth = (offsets[overscanEnd + 1] ?? totalWidth) - startOffset;
490
+ const endOffset = totalWidth - (offsets[overscanEnd + 1] ?? totalWidth);
491
+ return {
492
+ virtualColumns,
493
+ startOffset,
494
+ endOffset,
495
+ totalWidth,
496
+ visibleWidth,
497
+ startIndex: overscanStart,
498
+ endIndex: overscanEnd,
499
+ isVirtualized: true,
500
+ scrollToIndex
501
+ };
502
+ }
342
503
  var pretextPromise = null;
343
504
  function loadPretext() {
344
505
  if (pretextPromise) return pretextPromise;
@@ -389,7 +550,12 @@ function usePretextMeasurement({
389
550
  }
390
551
  prepareTimeRef.current = performance.now() - start;
391
552
  return result;
392
- }, [pretext, enabled, data, columns.map((c) => `${c.columnId}:${c.font}:${c.fixedHeight ? "F" : "T"}`).join("|")]);
553
+ }, [
554
+ pretext,
555
+ enabled,
556
+ data,
557
+ columns.map((c) => `${c.columnId}:${c.font}:${c.fixedHeight ? "F" : "T"}`).join("|")
558
+ ]);
393
559
  const measurement = React3.useMemo(() => {
394
560
  if (!pretext || !preparedCells) {
395
561
  return { rowHeights: null, prefixSums: null, totalHeight: 0 };
@@ -427,6 +593,7 @@ function usePretextMeasurement({
427
593
  return {
428
594
  rowHeights,
429
595
  prefixSums,
596
+ // Safe: prefixSums has length data.length + 1, so [data.length] always exists
430
597
  totalHeight: prefixSums[data.length]
431
598
  };
432
599
  }, [pretext, preparedCells, columnWidthsKey, minRowHeight, data.length]);
@@ -691,18 +858,10 @@ function CellDate({
691
858
  if (raw == null) return null;
692
859
  const date = raw instanceof Date ? raw : new Date(String(raw));
693
860
  if (isNaN(date.getTime())) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yable-cell-date", children: String(raw) });
694
- const formatter = React3.useMemo(() => {
695
- if (typeof format === "string" && format !== "relative") {
696
- return new Intl.DateTimeFormat(locale, {
697
- ...PRESETS[format],
698
- timeZone: "UTC"
699
- });
700
- }
701
- if (typeof format === "object") {
702
- return new Intl.DateTimeFormat(locale, { ...format, timeZone: "UTC" });
703
- }
704
- return null;
705
- }, [format, locale]);
861
+ const formatter = typeof format === "string" && format !== "relative" ? new Intl.DateTimeFormat(locale, {
862
+ ...PRESETS[format],
863
+ timeZone: "UTC"
864
+ }) : typeof format === "object" ? new Intl.DateTimeFormat(locale, { ...format, timeZone: "UTC" }) : null;
706
865
  const formatted = format === "relative" ? formatRelative(date) : formatter ? formatter.format(date) : date.toLocaleDateString(locale);
707
866
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `yable-cell-date ${className ?? ""}`, title: date.toISOString(), children: formatted });
708
867
  }
@@ -713,8 +872,14 @@ var measureRecipe9 = {
713
872
  padding: 20
714
873
  };
715
874
  function isSafeUrl(url) {
716
- const normalized = String(url).toLowerCase().trim();
717
- return !normalized.startsWith("javascript:") && !normalized.startsWith("data:text/html") && !normalized.startsWith("vbscript:");
875
+ const normalized = String(url).trim();
876
+ if (/^[/#?]/.test(normalized)) return true;
877
+ try {
878
+ const parsed = new URL(normalized, "https://placeholder.invalid");
879
+ return ["http:", "https:", "mailto:", "tel:"].includes(parsed.protocol);
880
+ } catch {
881
+ return false;
882
+ }
718
883
  }
719
884
  function CellLink({
720
885
  context,
@@ -891,7 +1056,7 @@ function useTableContext() {
891
1056
  const ctx = React3.useContext(TableContext);
892
1057
  if (!ctx) {
893
1058
  throw new Error(
894
- "[yable] useTableContext must be used within a <Table> component. Did you forget to pass the `table` prop?"
1059
+ "[yable E001] useTableContext must be used within a <Table> component. Did you forget to pass the `table` prop?"
895
1060
  );
896
1061
  }
897
1062
  return ctx;
@@ -941,12 +1106,234 @@ function SortIndicator({ direction, index }) {
941
1106
  }
942
1107
  );
943
1108
  }
1109
+ function formatValue(value) {
1110
+ if (value == null || value === "") return "(empty)";
1111
+ if (typeof value === "string") return value;
1112
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1113
+ if (value instanceof Date) return value.toISOString();
1114
+ return JSON.stringify(value);
1115
+ }
1116
+ function SetFilter({ column, className }) {
1117
+ const [open, setOpen] = React3.useState(false);
1118
+ const filterValue = column.getFilterValue();
1119
+ const selectedValues = Array.isArray(filterValue) ? filterValue : filterValue == null || filterValue === "" ? [] : [filterValue];
1120
+ const facetedUniqueValues = column.getFacetedUniqueValues();
1121
+ const options = Array.from(facetedUniqueValues.entries()).map(([value, count]) => ({
1122
+ value,
1123
+ count,
1124
+ label: formatValue(value)
1125
+ })).sort((a, b) => a.label.localeCompare(b.label));
1126
+ const selectedSet = new Set(selectedValues);
1127
+ const toggleValue = (value) => {
1128
+ const next = new Set(selectedSet);
1129
+ if (next.has(value)) {
1130
+ next.delete(value);
1131
+ } else {
1132
+ next.add(value);
1133
+ }
1134
+ const nextValues = Array.from(next);
1135
+ column.setFilterValue(nextValues.length > 0 ? nextValues : void 0);
1136
+ };
1137
+ const headerLabel = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1138
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1139
+ "div",
1140
+ {
1141
+ className: ["yable-set-filter", className].filter(Boolean).join(" "),
1142
+ style: { position: "relative" },
1143
+ children: [
1144
+ /* @__PURE__ */ jsxRuntime.jsx(
1145
+ "button",
1146
+ {
1147
+ type: "button",
1148
+ className: "yable-set-filter-trigger",
1149
+ "aria-haspopup": "dialog",
1150
+ "aria-expanded": open,
1151
+ onClick: () => setOpen((prev) => !prev),
1152
+ style: {
1153
+ width: "100%",
1154
+ minHeight: 28,
1155
+ padding: "4px 8px",
1156
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1157
+ borderRadius: 6,
1158
+ background: "transparent",
1159
+ font: "inherit",
1160
+ textAlign: "left",
1161
+ cursor: "pointer"
1162
+ },
1163
+ children: selectedValues.length > 0 ? `${selectedValues.length} selected` : `Filter ${headerLabel}`
1164
+ }
1165
+ ),
1166
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
1167
+ "div",
1168
+ {
1169
+ role: "dialog",
1170
+ "aria-label": `${headerLabel} set filter`,
1171
+ className: "yable-set-filter-popover",
1172
+ style: {
1173
+ position: "absolute",
1174
+ insetInlineStart: 0,
1175
+ top: "calc(100% + 4px)",
1176
+ zIndex: 20,
1177
+ width: 220,
1178
+ maxHeight: 240,
1179
+ overflow: "auto",
1180
+ padding: 8,
1181
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1182
+ borderRadius: 8,
1183
+ background: "var(--yable-color-bg, #fff)",
1184
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.12)"
1185
+ },
1186
+ children: [
1187
+ /* @__PURE__ */ jsxRuntime.jsxs(
1188
+ "div",
1189
+ {
1190
+ style: {
1191
+ display: "flex",
1192
+ alignItems: "center",
1193
+ justifyContent: "space-between",
1194
+ gap: 8,
1195
+ marginBottom: 8
1196
+ },
1197
+ children: [
1198
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: headerLabel }),
1199
+ selectedValues.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1200
+ "button",
1201
+ {
1202
+ type: "button",
1203
+ onClick: () => column.setFilterValue(void 0),
1204
+ style: {
1205
+ border: "none",
1206
+ background: "transparent",
1207
+ padding: 0,
1208
+ cursor: "pointer",
1209
+ fontSize: 12
1210
+ },
1211
+ children: "Clear"
1212
+ }
1213
+ )
1214
+ ]
1215
+ }
1216
+ ),
1217
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": `${headerLabel} options`, children: [
1218
+ options.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, opacity: 0.75 }, children: "No values" }),
1219
+ options.map((option) => {
1220
+ const checked = selectedSet.has(option.value);
1221
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1222
+ "label",
1223
+ {
1224
+ style: {
1225
+ display: "flex",
1226
+ alignItems: "center",
1227
+ gap: 8,
1228
+ padding: "4px 0",
1229
+ fontSize: 12
1230
+ },
1231
+ children: [
1232
+ /* @__PURE__ */ jsxRuntime.jsx(
1233
+ "input",
1234
+ {
1235
+ type: "checkbox",
1236
+ checked,
1237
+ onChange: () => toggleValue(option.value)
1238
+ }
1239
+ ),
1240
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 }, children: option.label }),
1241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.6 }, children: option.count })
1242
+ ]
1243
+ },
1244
+ `${option.label}-${option.count}`
1245
+ );
1246
+ })
1247
+ ] })
1248
+ ]
1249
+ }
1250
+ )
1251
+ ]
1252
+ }
1253
+ );
1254
+ }
1255
+ function isSetCompatibleValue(value) {
1256
+ return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1257
+ }
1258
+ function inferFilterVariant(column) {
1259
+ const meta = column.columnDef.meta ?? {};
1260
+ if (meta.filterVariant) return meta.filterVariant;
1261
+ const uniqueValues = column.getFacetedUniqueValues();
1262
+ if (uniqueValues.size > 0 && uniqueValues.size <= 12) {
1263
+ const allValuesSupported = Array.from(uniqueValues.keys()).every(isSetCompatibleValue);
1264
+ if (allValuesSupported) return "set";
1265
+ }
1266
+ return "text";
1267
+ }
1268
+ function FloatingFilter({ column }) {
1269
+ const variant = inferFilterVariant(column);
1270
+ if (!column.getCanFilter()) {
1271
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { minHeight: 28 }, "aria-hidden": "true" });
1272
+ }
1273
+ if (variant === "set") {
1274
+ return /* @__PURE__ */ jsxRuntime.jsx(SetFilter, { column });
1275
+ }
1276
+ const filterValue = column.getFilterValue();
1277
+ const normalizedValue = typeof filterValue === "string" || typeof filterValue === "number" ? String(filterValue) : "";
1278
+ const label = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1279
+ return /* @__PURE__ */ jsxRuntime.jsx("label", { style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1280
+ "input",
1281
+ {
1282
+ "aria-label": `Filter ${label}`,
1283
+ className: "yable-floating-filter-input",
1284
+ value: normalizedValue,
1285
+ onChange: (event) => {
1286
+ const nextValue = event.target.value;
1287
+ column.setFilterValue(nextValue === "" ? void 0 : nextValue);
1288
+ },
1289
+ placeholder: `Filter ${label}`,
1290
+ style: {
1291
+ width: "100%",
1292
+ minHeight: 28,
1293
+ padding: "4px 8px",
1294
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1295
+ borderRadius: 6,
1296
+ background: "transparent",
1297
+ font: "inherit"
1298
+ }
1299
+ }
1300
+ ) });
1301
+ }
944
1302
  var DRAG_MIME = "application/yable-column";
945
1303
  function TableHeader({
946
- table
1304
+ table,
1305
+ floatingFilters = false
947
1306
  }) {
948
1307
  const headerGroups = table.getHeaderGroups();
949
- return /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "yable-thead", children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-header-row", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsxRuntime.jsx(HeaderCell, { header, table }, header.id)) }, headerGroup.id)) });
1308
+ const visibleColumns = table.getVisibleLeafColumns();
1309
+ return /* @__PURE__ */ jsxRuntime.jsxs("thead", { className: "yable-thead", children: [
1310
+ headerGroups.map((headerGroup) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-header-row", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsxRuntime.jsx(HeaderCell, { header, table }, header.id)) }, headerGroup.id)),
1311
+ floatingFilters && visibleColumns.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-header-row yable-header-row--filters", children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(FloatingFilterCell, { column }, `${column.id}-filter`)) })
1312
+ ] });
1313
+ }
1314
+ function FloatingFilterCell({
1315
+ column
1316
+ }) {
1317
+ const style = React3.useMemo(() => {
1318
+ const next = {
1319
+ width: column.getSize(),
1320
+ minWidth: column.columnDef.minSize,
1321
+ maxWidth: column.columnDef.maxSize,
1322
+ padding: 6,
1323
+ verticalAlign: "top"
1324
+ };
1325
+ const pinned = column.getIsPinned();
1326
+ if (pinned) {
1327
+ next.position = "sticky";
1328
+ if (pinned === "left") {
1329
+ next.left = column.getStart("left");
1330
+ } else {
1331
+ next.right = column.getStart("right");
1332
+ }
1333
+ }
1334
+ return next;
1335
+ }, [column]);
1336
+ return /* @__PURE__ */ jsxRuntime.jsx("th", { className: "yable-th yable-th--filter", role: "columnheader", style, children: /* @__PURE__ */ jsxRuntime.jsx(FloatingFilter, { column }) });
950
1337
  }
951
1338
  function HeaderCell({
952
1339
  header,
@@ -1035,14 +1422,11 @@ function HeaderCell({
1035
1422
  },
1036
1423
  [canReorder]
1037
1424
  );
1038
- const handleDragLeave = React3.useCallback(
1039
- (e) => {
1040
- const next = e.relatedTarget;
1041
- if (next && e.currentTarget.contains(next)) return;
1042
- setDragOver(null);
1043
- },
1044
- []
1045
- );
1425
+ const handleDragLeave = React3.useCallback((e) => {
1426
+ const next = e.relatedTarget;
1427
+ if (next && e.currentTarget.contains(next)) return;
1428
+ setDragOver(null);
1429
+ }, []);
1046
1430
  const handleDragEnd = React3.useCallback(() => {
1047
1431
  setDragOver(null);
1048
1432
  }, []);
@@ -1115,13 +1499,7 @@ function HeaderCell({
1115
1499
  children: [
1116
1500
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yable-th-content", children: [
1117
1501
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: headerContent }),
1118
- canSort && /* @__PURE__ */ jsxRuntime.jsx(
1119
- SortIndicator,
1120
- {
1121
- direction: sortDirection,
1122
- index: sortIndex > 0 ? sortIndex : void 0
1123
- }
1124
- )
1502
+ canSort && /* @__PURE__ */ jsxRuntime.jsx(SortIndicator, { direction: sortDirection, index: sortIndex > 0 ? sortIndex : void 0 })
1125
1503
  ] }),
1126
1504
  canResize && /* @__PURE__ */ jsxRuntime.jsx(
1127
1505
  "div",
@@ -1246,6 +1624,10 @@ function TableCell({
1246
1624
  const cellStatus = table.getCellStatus(cell.row.id, column.id);
1247
1625
  const cellErrorMessage = table.getCellErrorMessage(cell.row.id, column.id);
1248
1626
  const cellConflictWith = table.getCellConflictWith(cell.row.id, column.id);
1627
+ const selectionRange = table.getCellSelectionRange();
1628
+ const selectionEdges = table.getCellSelectionEdges(rowIndex, columnIndex);
1629
+ const isCellSelected = selectionEdges !== null;
1630
+ const isMultiCellSelection = selectionRange !== null && (selectionRange.start.rowIndex !== selectionRange.end.rowIndex || selectionRange.start.columnIndex !== selectionRange.end.columnIndex);
1249
1631
  const overrideValue = cellStatus !== "idle" ? table.getCellRenderValue(cell.row.id, column.id) : void 0;
1250
1632
  let content;
1251
1633
  const cellDef = column.columnDef.cell;
@@ -1269,23 +1651,17 @@ function TableCell({
1269
1651
  }
1270
1652
  const handleClick = React3.useCallback(
1271
1653
  (e) => {
1272
- table.setFocusedCell({ rowIndex, columnIndex });
1273
- const clickTarget = e.target;
1274
- if (!isInteractiveClickTarget(clickTarget)) {
1275
- const currentTarget = e.currentTarget;
1276
- currentTarget.focus({ preventScroll: true });
1277
- }
1278
1654
  table.events.emit("cell:click", {
1279
1655
  cell,
1280
1656
  row: cell.row,
1281
1657
  column: cell.column,
1282
1658
  event: e.nativeEvent
1283
1659
  });
1284
- if (yableCore.canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing) {
1660
+ if (yableCore.canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing && !isMultiCellSelection && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
1285
1661
  table.startEditing(cell.row.id, column.id);
1286
1662
  }
1287
1663
  },
1288
- [cell, column, columnIndex, isAlwaysEditable, isEditing, rowIndex, table]
1664
+ [cell, column, isAlwaysEditable, isEditing, isMultiCellSelection, table]
1289
1665
  );
1290
1666
  const handleDoubleClick = React3.useCallback(
1291
1667
  (e) => {
@@ -1313,9 +1689,34 @@ function TableCell({
1313
1689
  if (!keyboardNavigationEnabled) return;
1314
1690
  table.setFocusedCell({ rowIndex, columnIndex });
1315
1691
  }, [columnIndex, keyboardNavigationEnabled, rowIndex, table]);
1692
+ const handleMouseDown = React3.useCallback(
1693
+ (e) => {
1694
+ if (e.button !== 0) return;
1695
+ const clickTarget = e.target;
1696
+ if (isInteractiveClickTarget(clickTarget)) return;
1697
+ e.preventDefault();
1698
+ table.setFocusedCell({ rowIndex, columnIndex });
1699
+ table.startCellRangeSelection({ rowIndex, columnIndex }, { extend: e.shiftKey });
1700
+ e.currentTarget.focus({ preventScroll: true });
1701
+ },
1702
+ [columnIndex, rowIndex, table]
1703
+ );
1704
+ const handleMouseEnter = React3.useCallback(() => {
1705
+ if (!table.getState().cellSelection?.isDragging) return;
1706
+ table.updateCellRangeSelection({ rowIndex, columnIndex });
1707
+ }, [columnIndex, rowIndex, table]);
1708
+ const handleMouseUp = React3.useCallback(() => {
1709
+ if (!table.getState().cellSelection?.isDragging) return;
1710
+ table.endCellRangeSelection();
1711
+ }, [table]);
1316
1712
  const classNames = [
1317
1713
  "yable-td",
1318
- isFocused && "yable-cell--focused"
1714
+ isFocused && "yable-cell--focused",
1715
+ isCellSelected && "yable-cell--selected",
1716
+ selectionEdges?.top && "yable-cell--selection-top",
1717
+ selectionEdges?.right && "yable-cell--selection-right",
1718
+ selectionEdges?.bottom && "yable-cell--selection-bottom",
1719
+ selectionEdges?.left && "yable-cell--selection-left"
1319
1720
  ].filter(Boolean).join(" ");
1320
1721
  return /* @__PURE__ */ jsxRuntime.jsxs(
1321
1722
  "td",
@@ -1329,10 +1730,14 @@ function TableCell({
1329
1730
  "data-column-id": column.id,
1330
1731
  "data-row-index": rowIndex,
1331
1732
  "data-column-index": columnIndex,
1733
+ "data-cell-selected": isCellSelected || void 0,
1332
1734
  "aria-rowindex": rowIndex + 1,
1333
1735
  "aria-colindex": columnIndex + 1,
1334
1736
  role: "gridcell",
1335
1737
  tabIndex: keyboardNavigationEnabled ? isTabStop ? 0 : -1 : void 0,
1738
+ onMouseDown: handleMouseDown,
1739
+ onMouseEnter: handleMouseEnter,
1740
+ onMouseUp: handleMouseUp,
1336
1741
  onClick: handleClick,
1337
1742
  onDoubleClick: handleDoubleClick,
1338
1743
  onContextMenu: handleContextMenu,
@@ -1457,14 +1862,15 @@ var CellErrorBoundary = class extends React3__default.default.Component {
1457
1862
  return this.props.children;
1458
1863
  }
1459
1864
  };
1460
- function TableBody({
1461
- table,
1462
- clickableRows
1463
- }) {
1865
+ function TableBody({ table, clickableRows }) {
1464
1866
  const rows = table.getRowModel().rows;
1465
1867
  const visibleColumns = table.getVisibleLeafColumns();
1466
1868
  const activeCell = table.getState().editing.activeCell;
1467
1869
  const focusedCell = table.getFocusedCell();
1870
+ const cellSelection = table.getState().cellSelection ?? {
1871
+ range: null,
1872
+ isDragging: false
1873
+ };
1468
1874
  const options = table.options;
1469
1875
  const enableVirtualization = options.enableVirtualization ?? false;
1470
1876
  const scrollContainerRef = React3.useRef(null);
@@ -1482,6 +1888,18 @@ function TableBody({
1482
1888
  pretextHeights,
1483
1889
  pretextPrefixSums
1484
1890
  });
1891
+ const cellSelectionKey = cellSelection.range ? `${cellSelection.range.start.rowIndex}:${cellSelection.range.start.columnIndex}:${cellSelection.range.end.rowIndex}:${cellSelection.range.end.columnIndex}:${cellSelection.isDragging ? "dragging" : "idle"}` : `none:${cellSelection.isDragging ? "dragging" : "idle"}`;
1892
+ React3.useEffect(() => {
1893
+ const handleWindowMouseUp = () => {
1894
+ if (table.getState().cellSelection?.isDragging) {
1895
+ table.endCellRangeSelection();
1896
+ }
1897
+ };
1898
+ window.addEventListener("mouseup", handleWindowMouseUp);
1899
+ return () => {
1900
+ window.removeEventListener("mouseup", handleWindowMouseUp);
1901
+ };
1902
+ }, [table]);
1485
1903
  if (!enableVirtualization) {
1486
1904
  return /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "yable-tbody", children: rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
1487
1905
  MemoizedTableRow,
@@ -1495,6 +1913,7 @@ function TableBody({
1495
1913
  activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1496
1914
  focusedColumnIndex: focusedCell?.rowIndex === rowIndex ? focusedCell.columnIndex : null,
1497
1915
  hasFocusedCell: focusedCell !== null,
1916
+ cellSelectionKey,
1498
1917
  clickable: clickableRows
1499
1918
  },
1500
1919
  row.id
@@ -1503,73 +1922,67 @@ function TableBody({
1503
1922
  const hasPretextData = !!(pretextHeights && pretextPrefixSums);
1504
1923
  const fixedRowHeight = typeof rowHeight === "number" && !hasPretextData ? rowHeight : void 0;
1505
1924
  const containerHeight = hasPretextData ? Math.min(totalHeight, 800) : fixedRowHeight ? Math.min(totalHeight, fixedRowHeight * 20) : 600;
1506
- return /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "yable-tbody", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { style: { height: 0, padding: 0, border: "none" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1507
- "td",
1925
+ return /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "yable-tbody", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { style: { height: 0, padding: 0, border: "none" }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { style: { height: 0, padding: 0, border: "none" }, colSpan: visibleColumns.length, children: /* @__PURE__ */ jsxRuntime.jsx(
1926
+ "div",
1508
1927
  {
1509
- style: { height: 0, padding: 0, border: "none" },
1510
- colSpan: visibleColumns.length,
1928
+ ref: scrollContainerRef,
1929
+ className: "yable-virtual-scroll-container",
1930
+ style: {
1931
+ overflowY: "auto",
1932
+ height: containerHeight,
1933
+ position: "relative"
1934
+ },
1511
1935
  children: /* @__PURE__ */ jsxRuntime.jsx(
1512
1936
  "div",
1513
1937
  {
1514
- ref: scrollContainerRef,
1515
- className: "yable-virtual-scroll-container",
1516
- style: {
1517
- overflowY: "auto",
1518
- height: containerHeight,
1519
- position: "relative"
1520
- },
1938
+ className: "yable-virtual-spacer",
1939
+ style: { height: totalHeight, position: "relative" },
1521
1940
  children: /* @__PURE__ */ jsxRuntime.jsx(
1522
- "div",
1941
+ "table",
1523
1942
  {
1524
- className: "yable-virtual-spacer",
1525
- style: { height: totalHeight, position: "relative" },
1526
- children: /* @__PURE__ */ jsxRuntime.jsx(
1527
- "table",
1528
- {
1529
- style: {
1530
- position: "absolute",
1531
- top: 0,
1532
- left: 0,
1533
- width: "100%",
1534
- tableLayout: "fixed",
1535
- borderCollapse: "collapse"
1943
+ style: {
1944
+ position: "absolute",
1945
+ top: 0,
1946
+ left: 0,
1947
+ width: "100%",
1948
+ tableLayout: "fixed",
1949
+ borderCollapse: "collapse"
1950
+ },
1951
+ children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: virtualRows.map((vRow) => {
1952
+ const row = rows[vRow.index];
1953
+ if (!row) return null;
1954
+ return /* @__PURE__ */ jsxRuntime.jsx(
1955
+ MemoizedTableRow,
1956
+ {
1957
+ row,
1958
+ table,
1959
+ rowIndex: vRow.index,
1960
+ visibleColumns,
1961
+ isSelected: row.getIsSelected(),
1962
+ isExpanded: row.getIsExpanded(),
1963
+ activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1964
+ focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
1965
+ hasFocusedCell: focusedCell !== null,
1966
+ cellSelectionKey,
1967
+ clickable: clickableRows,
1968
+ virtualStyle: {
1969
+ position: "absolute",
1970
+ top: 0,
1971
+ left: 0,
1972
+ width: "100%",
1973
+ height: vRow.size,
1974
+ transform: `translateY(${vRow.start}px)`
1975
+ }
1536
1976
  },
1537
- children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: virtualRows.map((vRow) => {
1538
- const row = rows[vRow.index];
1539
- if (!row) return null;
1540
- return /* @__PURE__ */ jsxRuntime.jsx(
1541
- MemoizedTableRow,
1542
- {
1543
- row,
1544
- table,
1545
- rowIndex: vRow.index,
1546
- visibleColumns,
1547
- isSelected: row.getIsSelected(),
1548
- isExpanded: row.getIsExpanded(),
1549
- activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1550
- focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
1551
- hasFocusedCell: focusedCell !== null,
1552
- clickable: clickableRows,
1553
- virtualStyle: {
1554
- position: "absolute",
1555
- top: 0,
1556
- left: 0,
1557
- width: "100%",
1558
- height: vRow.size,
1559
- transform: `translateY(${vRow.start}px)`
1560
- }
1561
- },
1562
- row.id
1563
- );
1564
- }) })
1565
- }
1566
- )
1977
+ row.id
1978
+ );
1979
+ }) })
1567
1980
  }
1568
1981
  )
1569
1982
  }
1570
1983
  )
1571
1984
  }
1572
- ) }) });
1985
+ ) }) }) });
1573
1986
  }
1574
1987
  function TableRowInner({
1575
1988
  row,
@@ -1581,10 +1994,12 @@ function TableRowInner({
1581
1994
  activeColumnId,
1582
1995
  focusedColumnIndex,
1583
1996
  hasFocusedCell,
1997
+ cellSelectionKey: _cellSelectionKey,
1584
1998
  clickable,
1585
1999
  virtualStyle
1586
2000
  }) {
1587
- const visibleCells = row.getVisibleCells();
2001
+ const allCells = row.getAllCells();
2002
+ const visibleCells = visibleColumns.map((column) => allCells.find((cell) => cell.column.id === column.id)).filter((cell) => cell != null);
1588
2003
  const handleClick = React3.useCallback(
1589
2004
  (e) => {
1590
2005
  if (clickable) {
@@ -1614,6 +2029,8 @@ function TableRowInner({
1614
2029
  },
1615
2030
  [table.events, row]
1616
2031
  );
2032
+ const selectionEnabled = Boolean(table.options.enableRowSelection);
2033
+ const expansionEnabled = Boolean(table.options.enableExpanding);
1617
2034
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1618
2035
  /* @__PURE__ */ jsxRuntime.jsx(
1619
2036
  "tr",
@@ -1625,6 +2042,8 @@ function TableRowInner({
1625
2042
  "data-clickable": clickable || void 0,
1626
2043
  "data-row-id": row.id,
1627
2044
  "data-row-index": rowIndex,
2045
+ "aria-selected": selectionEnabled ? isSelected : void 0,
2046
+ "aria-expanded": expansionEnabled ? isExpanded : void 0,
1628
2047
  onClick: handleClick,
1629
2048
  onDoubleClick: handleDoubleClick,
1630
2049
  onContextMenu: handleContextMenu,
@@ -1666,6 +2085,7 @@ function areRowPropsEqual(prev, next) {
1666
2085
  if (prev.activeColumnId !== next.activeColumnId) return false;
1667
2086
  if (prev.focusedColumnIndex !== next.focusedColumnIndex) return false;
1668
2087
  if (prev.hasFocusedCell !== next.hasFocusedCell) return false;
2088
+ if (prev.cellSelectionKey !== next.cellSelectionKey) return false;
1669
2089
  if (prev.virtualStyle !== next.virtualStyle) {
1670
2090
  if (!prev.virtualStyle || !next.virtualStyle) return false;
1671
2091
  if (prev.virtualStyle.transform !== next.virtualStyle.transform) return false;
@@ -1675,9 +2095,7 @@ function areRowPropsEqual(prev, next) {
1675
2095
  return true;
1676
2096
  }
1677
2097
  var MemoizedTableRow = React3__default.default.memo(TableRowInner, areRowPropsEqual);
1678
- function TableFooter({
1679
- table
1680
- }) {
2098
+ function TableFooter({ table }) {
1681
2099
  const footerGroups = table.getFooterGroups();
1682
2100
  if (!footerGroups.length) return null;
1683
2101
  return /* @__PURE__ */ jsxRuntime.jsx("tfoot", { className: "yable-tfoot", children: footerGroups.map((footerGroup) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "yable-tr", children: footerGroup.headers.map((header) => {
@@ -2440,11 +2858,11 @@ function ContextMenu({
2440
2858
  }
2441
2859
  if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2442
2860
  e.preventDefault();
2443
- const items2 = menuRef.current?.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])');
2444
- if (!items2 || items2.length === 0) return;
2445
- const currentIndex = Array.from(items2).findIndex(
2446
- (el) => el === document.activeElement
2861
+ const items2 = menuRef.current?.querySelectorAll(
2862
+ '[role="menuitem"]:not([aria-disabled="true"])'
2447
2863
  );
2864
+ if (!items2 || items2.length === 0) return;
2865
+ const currentIndex = Array.from(items2).findIndex((el) => el === document.activeElement);
2448
2866
  let nextIndex;
2449
2867
  if (e.key === "ArrowDown") {
2450
2868
  nextIndex = currentIndex < items2.length - 1 ? currentIndex + 1 : 0;
@@ -2489,11 +2907,7 @@ function ContextMenu({
2489
2907
  navigator.clipboard?.readText().then((text) => {
2490
2908
  const editing = table.getState().editing;
2491
2909
  if (editing?.activeCell) {
2492
- table.pasteFromClipboard(
2493
- text,
2494
- editing.activeCell.rowId,
2495
- editing.activeCell.columnId
2496
- );
2910
+ table.pasteFromClipboard(text, editing.activeCell.rowId, editing.activeCell.columnId);
2497
2911
  }
2498
2912
  }).catch(() => {
2499
2913
  });
@@ -2833,6 +3247,12 @@ function isEditableTarget(element) {
2833
3247
  }
2834
3248
  return element.isContentEditable;
2835
3249
  }
3250
+ function filterHeaderGroups(groups, visibleColumnIds) {
3251
+ return groups.map((group) => ({
3252
+ ...group,
3253
+ headers: group.headers.filter((header) => visibleColumnIds.has(header.column.id))
3254
+ })).filter((group) => group.headers.length > 0);
3255
+ }
2836
3256
  function Table({
2837
3257
  table,
2838
3258
  stickyHeader,
@@ -2859,6 +3279,10 @@ function Table({
2859
3279
  sidebar,
2860
3280
  sidebarPanels = ["columns", "filters"],
2861
3281
  defaultSidebarPanel,
3282
+ floatingFilters,
3283
+ columnVirtualization,
3284
+ columnVirtualizationOverscan,
3285
+ ariaLabel,
2862
3286
  ...rest
2863
3287
  }) {
2864
3288
  const [sidebarOpen, setSidebarOpen] = React3.useState(false);
@@ -2866,6 +3290,7 @@ function Table({
2866
3290
  defaultSidebarPanel ?? "columns"
2867
3291
  );
2868
3292
  const containerRef = React3.useRef(null);
3293
+ const horizontalScrollRef = React3.useRef(null);
2869
3294
  const isRtl = direction === "rtl";
2870
3295
  const classNames = [
2871
3296
  "yable",
@@ -2883,8 +3308,86 @@ function Table({
2883
3308
  const hasGlobalFilter = Boolean(table.getState().globalFilter);
2884
3309
  const hasColumnFilters = table.getState().columnFilters.length > 0;
2885
3310
  const isFiltered = hasGlobalFilter || hasColumnFilters;
3311
+ const allVisibleColumns = table.getVisibleLeafColumns();
3312
+ const hasPinnedColumns = table.getLeftVisibleLeafColumns().length > 0 || table.getRightVisibleLeafColumns().length > 0;
3313
+ const hasGroupedHeaders = table.getHeaderGroups().length > 1;
3314
+ const canVirtualizeColumns = Boolean(columnVirtualization) && !hasPinnedColumns && !hasGroupedHeaders && allVisibleColumns.length > 0;
3315
+ const columnVirtualState = useColumnVirtualization({
3316
+ containerRef: horizontalScrollRef,
3317
+ columns: allVisibleColumns,
3318
+ overscan: columnVirtualizationOverscan ?? 2,
3319
+ enabled: canVirtualizeColumns
3320
+ });
3321
+ const renderTable = React3.useMemo(() => {
3322
+ if (!canVirtualizeColumns || !columnVirtualState.isVirtualized) {
3323
+ return table;
3324
+ }
3325
+ const virtualColumns = columnVirtualState.virtualColumns.map((entry) => entry.column);
3326
+ const visibleColumnIds = new Set(virtualColumns.map((column) => column.id));
3327
+ const next = Object.create(table);
3328
+ next.getVisibleFlatColumns = () => virtualColumns;
3329
+ next.getVisibleLeafColumns = () => virtualColumns;
3330
+ next.getCenterVisibleLeafColumns = () => virtualColumns;
3331
+ next.getLeftVisibleLeafColumns = () => [];
3332
+ next.getRightVisibleLeafColumns = () => [];
3333
+ next.getHeaderGroups = () => filterHeaderGroups(table.getHeaderGroups(), visibleColumnIds);
3334
+ next.getCenterHeaderGroups = () => filterHeaderGroups(table.getCenterHeaderGroups(), visibleColumnIds);
3335
+ next.getFooterGroups = () => filterHeaderGroups(table.getFooterGroups(), visibleColumnIds);
3336
+ next.getCenterFooterGroups = () => filterHeaderGroups(table.getCenterFooterGroups(), visibleColumnIds);
3337
+ return next;
3338
+ }, [
3339
+ canVirtualizeColumns,
3340
+ columnVirtualState.isVirtualized,
3341
+ columnVirtualState.virtualColumns,
3342
+ table
3343
+ ]);
3344
+ const showColumnVirtualizationShell = canVirtualizeColumns;
2886
3345
  const contextMenu = useContextMenu();
2887
3346
  useKeyboardNavigation(table, { containerRef });
3347
+ const [announcement, setAnnouncement] = React3.useState("");
3348
+ const prevSortingRef = React3.useRef(table.getState().sorting);
3349
+ const prevFilterCountRef = React3.useRef(rows.length);
3350
+ const prevHasFiltersRef = React3.useRef(isFiltered);
3351
+ const prevPaginationRef = React3.useRef(table.getState().pagination);
3352
+ const isFirstRenderRef = React3.useRef(true);
3353
+ React3.useEffect(() => {
3354
+ if (isFirstRenderRef.current) {
3355
+ isFirstRenderRef.current = false;
3356
+ return;
3357
+ }
3358
+ const currentSorting = table.getState().sorting;
3359
+ const currentPagination = table.getState().pagination;
3360
+ const currentRowCount = rows.length;
3361
+ const currentIsFiltered = isFiltered;
3362
+ if (JSON.stringify(currentSorting) !== JSON.stringify(prevSortingRef.current)) {
3363
+ prevSortingRef.current = currentSorting;
3364
+ const firstSort = currentSorting[0];
3365
+ if (firstSort) {
3366
+ const column = table.getColumn(firstSort.id);
3367
+ const headerDef = column?.columnDef.header;
3368
+ const columnName = typeof headerDef === "string" ? headerDef : firstSort.id;
3369
+ const direction2 = firstSort.desc ? "descending" : "ascending";
3370
+ setAnnouncement(`Sorted by ${columnName} ${direction2}`);
3371
+ return;
3372
+ }
3373
+ }
3374
+ if (currentRowCount !== prevFilterCountRef.current || currentIsFiltered !== prevHasFiltersRef.current) {
3375
+ prevFilterCountRef.current = currentRowCount;
3376
+ prevHasFiltersRef.current = currentIsFiltered;
3377
+ if (currentIsFiltered) {
3378
+ setAnnouncement(`${currentRowCount} rows after filtering`);
3379
+ return;
3380
+ }
3381
+ }
3382
+ if (JSON.stringify(currentPagination) !== JSON.stringify(prevPaginationRef.current)) {
3383
+ prevPaginationRef.current = currentPagination;
3384
+ const pageCount = table.getPageCount();
3385
+ if (pageCount > 0) {
3386
+ setAnnouncement(`Page ${currentPagination.pageIndex + 1} of ${pageCount}`);
3387
+ return;
3388
+ }
3389
+ }
3390
+ });
2888
3391
  const handleContextMenu = React3.useCallback(
2889
3392
  (e) => {
2890
3393
  e.preventDefault();
@@ -2892,6 +3395,24 @@ function Table({
2892
3395
  },
2893
3396
  [contextMenu, table]
2894
3397
  );
3398
+ const tableNode = /* @__PURE__ */ jsxRuntime.jsxs(
3399
+ "table",
3400
+ {
3401
+ className: "yable-table",
3402
+ style: columnVirtualState.isVirtualized ? {
3403
+ width: columnVirtualState.visibleWidth,
3404
+ minWidth: columnVirtualState.visibleWidth,
3405
+ marginLeft: columnVirtualState.startOffset,
3406
+ tableLayout: "fixed"
3407
+ } : void 0,
3408
+ "data-column-virtualized": columnVirtualState.isVirtualized || void 0,
3409
+ children: [
3410
+ /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { table: renderTable, floatingFilters }),
3411
+ /* @__PURE__ */ jsxRuntime.jsx(TableBody, { table: renderTable, clickableRows }),
3412
+ footer && /* @__PURE__ */ jsxRuntime.jsx(TableFooter, { table: renderTable })
3413
+ ]
3414
+ }
3415
+ );
2895
3416
  return /* @__PURE__ */ jsxRuntime.jsx(TableProvider, { value: table, children: /* @__PURE__ */ jsxRuntime.jsxs(
2896
3417
  "div",
2897
3418
  {
@@ -2900,23 +3421,40 @@ function Table({
2900
3421
  "data-theme": theme,
2901
3422
  dir: direction,
2902
3423
  role: "grid",
3424
+ "aria-label": ariaLabel ?? "Data table",
2903
3425
  "aria-rowcount": table.getRowModel().rows.length,
2904
3426
  "aria-colcount": table.getVisibleLeafColumns().length,
2905
3427
  onContextMenu: handleContextMenu,
2906
3428
  ...rest,
2907
3429
  children: [
2908
3430
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yable-main", children: [
2909
- /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "yable-table", children: [
2910
- /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { table }),
2911
- /* @__PURE__ */ jsxRuntime.jsx(
2912
- TableBody,
2913
- {
2914
- table,
2915
- clickableRows
2916
- }
2917
- ),
2918
- footer && /* @__PURE__ */ jsxRuntime.jsx(TableFooter, { table })
2919
- ] }),
3431
+ showColumnVirtualizationShell ? /* @__PURE__ */ jsxRuntime.jsx(
3432
+ "div",
3433
+ {
3434
+ ref: horizontalScrollRef,
3435
+ className: "yable-horizontal-scroll-container",
3436
+ style: {
3437
+ overflowX: "auto",
3438
+ overflowY: "visible",
3439
+ maxWidth: "100%",
3440
+ position: "relative"
3441
+ },
3442
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3443
+ "div",
3444
+ {
3445
+ className: "yable-horizontal-scroll-inner",
3446
+ style: {
3447
+ width: Math.max(columnVirtualState.totalWidth, columnVirtualState.visibleWidth),
3448
+ minWidth: Math.max(
3449
+ columnVirtualState.totalWidth,
3450
+ columnVirtualState.visibleWidth
3451
+ )
3452
+ },
3453
+ children: tableNode
3454
+ }
3455
+ )
3456
+ }
3457
+ ) : tableNode,
2920
3458
  /* @__PURE__ */ jsxRuntime.jsx(
2921
3459
  LoadingOverlay,
2922
3460
  {
@@ -2957,6 +3495,24 @@ function Table({
2957
3495
  onClose: contextMenu.close,
2958
3496
  table
2959
3497
  }
3498
+ ),
3499
+ /* @__PURE__ */ jsxRuntime.jsx(
3500
+ "div",
3501
+ {
3502
+ role: "status",
3503
+ "aria-live": "polite",
3504
+ "aria-atomic": "true",
3505
+ style: {
3506
+ position: "absolute",
3507
+ width: "1px",
3508
+ height: "1px",
3509
+ overflow: "hidden",
3510
+ clip: "rect(0,0,0,0)",
3511
+ whiteSpace: "nowrap",
3512
+ border: 0
3513
+ },
3514
+ children: announcement
3515
+ }
2960
3516
  )
2961
3517
  ]
2962
3518
  }
@@ -3422,14 +3978,7 @@ function formatDateValue(value, type) {
3422
3978
  return String(value);
3423
3979
  }
3424
3980
  function useClipboard(table, options = {}) {
3425
- const {
3426
- enabled = true,
3427
- containerRef,
3428
- onCopy,
3429
- onCut,
3430
- onPaste,
3431
- ...clipboardOptions
3432
- } = options;
3981
+ const { enabled = true, containerRef, onCopy, onCut, onPaste, ...clipboardOptions } = options;
3433
3982
  const handleCopy = React3.useCallback(
3434
3983
  (e) => {
3435
3984
  if (isEditableTarget2(e.target)) return;
@@ -3467,20 +4016,29 @@ function useClipboard(table, options = {}) {
3467
4016
  targetRowId = state.editing.activeCell.rowId;
3468
4017
  targetColumnId = state.editing.activeCell.columnId;
3469
4018
  } else {
3470
- const selectedRowIds = Object.keys(state.rowSelection || {}).filter(
3471
- (id) => state.rowSelection[id]
3472
- );
3473
- if (selectedRowIds.length > 0) {
3474
- targetRowId = selectedRowIds[0];
4019
+ const selectedRange = table.getCellSelectionRange();
4020
+ const rows = table.getRowModel().rows;
4021
+ const columns = table.getVisibleLeafColumns();
4022
+ if (selectedRange) {
4023
+ const startRowIndex = Math.min(selectedRange.start.rowIndex, selectedRange.end.rowIndex);
4024
+ const startColumnIndex = Math.min(
4025
+ selectedRange.start.columnIndex,
4026
+ selectedRange.end.columnIndex
4027
+ );
4028
+ targetRowId = rows[startRowIndex]?.id;
4029
+ targetColumnId = columns[startColumnIndex]?.id;
3475
4030
  } else {
3476
- const rows = table.getRowModel().rows;
3477
- if (rows.length > 0) {
4031
+ const selectedRowIds = Object.keys(state.rowSelection || {}).filter(
4032
+ (id) => state.rowSelection[id]
4033
+ );
4034
+ if (selectedRowIds.length > 0) {
4035
+ targetRowId = selectedRowIds[0];
4036
+ } else if (rows.length > 0) {
3478
4037
  targetRowId = rows[0].id;
3479
4038
  }
3480
- }
3481
- const columns = table.getVisibleLeafColumns();
3482
- if (columns.length > 0) {
3483
- targetColumnId = columns[0].id;
4039
+ if (columns.length > 0) {
4040
+ targetColumnId = columns[0].id;
4041
+ }
3484
4042
  }
3485
4043
  }
3486
4044
  if (targetRowId && targetColumnId) {
@@ -4770,7 +5328,7 @@ function sanitizeCSS(css) {
4770
5328
  sanitized = sanitized.replace(/javascript\s*:/gi, "/* javascript: removed */");
4771
5329
  return sanitized;
4772
5330
  }
4773
- function usePrintLayout(table, options = {}) {
5331
+ function usePrintLayout(_table, options = {}) {
4774
5332
  const { title, additionalCSS } = options;
4775
5333
  const isPrintingRef = React3.useRef(false);
4776
5334
  const preparePrint = React3.useCallback(() => {
@@ -4807,7 +5365,7 @@ function usePrintLayout(table, options = {}) {
4807
5365
  requestAnimationFrame(() => {
4808
5366
  window.print();
4809
5367
  });
4810
- }, [table, title, additionalCSS]);
5368
+ }, [title, additionalCSS]);
4811
5369
  return {
4812
5370
  preparePrint,
4813
5371
  isPrinting: isPrintingRef.current
@@ -4965,6 +5523,7 @@ exports.ExpandIcon = ExpandIcon;
4965
5523
  exports.FillHandle = FillHandle;
4966
5524
  exports.FiltersPanel = FiltersPanel;
4967
5525
  exports.FlashCell = FlashCell;
5526
+ exports.FloatingFilter = FloatingFilter;
4968
5527
  exports.GlobalFilter = GlobalFilter;
4969
5528
  exports.LoadingOverlay = LoadingOverlay;
4970
5529
  exports.MasterDetail = MasterDetail;
@@ -4972,6 +5531,7 @@ exports.NoRowsOverlay = NoRowsOverlay;
4972
5531
  exports.Pagination = Pagination;
4973
5532
  exports.PivotConfigPanel = PivotConfigPanel;
4974
5533
  exports.PrintLayout = PrintLayout;
5534
+ exports.SetFilter = SetFilter;
4975
5535
  exports.Sidebar = Sidebar;
4976
5536
  exports.SortIndicator = SortIndicator;
4977
5537
  exports.StatusBar = StatusBar;
@@ -4990,6 +5550,7 @@ exports.resolveMeasureRecipe = resolveMeasureRecipe;
4990
5550
  exports.useAutoMeasurements = useAutoMeasurements;
4991
5551
  exports.useCellFlash = useCellFlash;
4992
5552
  exports.useClipboard = useClipboard;
5553
+ exports.useColumnVirtualization = useColumnVirtualization;
4993
5554
  exports.useContextMenu = useContextMenu;
4994
5555
  exports.useFillHandle = useFillHandle;
4995
5556
  exports.useKeyboardNavigation = useKeyboardNavigation;