@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.js CHANGED
@@ -210,7 +210,15 @@ function useVirtualization({
210
210
  container.scrollTop = offset;
211
211
  }
212
212
  },
213
- [containerRef, totalRows, rowHeight, isFixedHeight, getRowHeight, hasPretextHeights, pretextPrefixSums]
213
+ [
214
+ containerRef,
215
+ totalRows,
216
+ rowHeight,
217
+ isFixedHeight,
218
+ getRowHeight,
219
+ hasPretextHeights,
220
+ pretextPrefixSums
221
+ ]
214
222
  );
215
223
  const totalHeight = useMemo(() => {
216
224
  if (totalRows === 0) return 0;
@@ -264,10 +272,7 @@ function useVirtualization({
264
272
  } else if (isFixedHeight) {
265
273
  const fixedH = rowHeight;
266
274
  startIndex = Math.floor(scrollTop / fixedH);
267
- endIndex = Math.min(
268
- totalRows - 1,
269
- Math.ceil((scrollTop + containerHeight) / fixedH) - 1
270
- );
275
+ endIndex = Math.min(totalRows - 1, Math.ceil((scrollTop + containerHeight) / fixedH) - 1);
271
276
  } else {
272
277
  let accum = 0;
273
278
  let foundStart = false;
@@ -334,6 +339,162 @@ function useVirtualization({
334
339
  scrollTo
335
340
  };
336
341
  }
342
+ function binarySearchOffsets(offsets, target) {
343
+ let low = 0;
344
+ let high = offsets.length - 1;
345
+ while (low < high) {
346
+ const mid = low + high >>> 1;
347
+ if (offsets[mid + 1] <= target) {
348
+ low = mid + 1;
349
+ } else {
350
+ high = mid;
351
+ }
352
+ }
353
+ return low;
354
+ }
355
+ function useColumnVirtualization({
356
+ containerRef,
357
+ columns,
358
+ overscan = 2,
359
+ enabled = true
360
+ }) {
361
+ const [scrollState, setScrollState] = useState({
362
+ scrollLeft: 0,
363
+ containerWidth: 0
364
+ });
365
+ const rafRef = useRef(null);
366
+ const sizes = useMemo(() => columns.map((column) => column.getSize()), [columns]);
367
+ const offsets = useMemo(() => {
368
+ const next = new Array(columns.length + 1);
369
+ next[0] = 0;
370
+ for (let i = 0; i < columns.length; i++) {
371
+ next[i + 1] = next[i] + (sizes[i] ?? 0);
372
+ }
373
+ return next;
374
+ }, [columns.length, sizes]);
375
+ const totalWidth = offsets[offsets.length - 1] ?? 0;
376
+ useEffect(() => {
377
+ const container = containerRef.current;
378
+ if (!container) return;
379
+ setScrollState({
380
+ scrollLeft: container.scrollLeft,
381
+ containerWidth: container.clientWidth
382
+ });
383
+ const handleScroll = () => {
384
+ if (rafRef.current !== null) return;
385
+ rafRef.current = requestAnimationFrame(() => {
386
+ rafRef.current = null;
387
+ const el = containerRef.current;
388
+ if (!el) return;
389
+ setScrollState({
390
+ scrollLeft: el.scrollLeft,
391
+ containerWidth: el.clientWidth
392
+ });
393
+ });
394
+ };
395
+ container.addEventListener("scroll", handleScroll, { passive: true });
396
+ let resizeObserver;
397
+ if (typeof ResizeObserver !== "undefined") {
398
+ resizeObserver = new ResizeObserver(() => {
399
+ const el = containerRef.current;
400
+ if (!el) return;
401
+ setScrollState((prev) => {
402
+ const nextWidth = el.clientWidth;
403
+ if (prev.containerWidth === nextWidth && prev.scrollLeft === el.scrollLeft) {
404
+ return prev;
405
+ }
406
+ return {
407
+ scrollLeft: el.scrollLeft,
408
+ containerWidth: nextWidth
409
+ };
410
+ });
411
+ });
412
+ resizeObserver.observe(container);
413
+ }
414
+ return () => {
415
+ container.removeEventListener("scroll", handleScroll);
416
+ if (rafRef.current !== null) {
417
+ cancelAnimationFrame(rafRef.current);
418
+ rafRef.current = null;
419
+ }
420
+ resizeObserver?.disconnect();
421
+ };
422
+ }, [containerRef]);
423
+ const scrollToIndex = useCallback(
424
+ (index) => {
425
+ const container = containerRef.current;
426
+ if (!container || columns.length === 0) return;
427
+ const clampedIndex = Math.max(0, Math.min(index, columns.length - 1));
428
+ container.scrollLeft = offsets[clampedIndex] ?? 0;
429
+ },
430
+ [columns.length, containerRef, offsets]
431
+ );
432
+ if (!enabled || columns.length === 0) {
433
+ return {
434
+ virtualColumns: columns.map((column, index) => ({
435
+ column,
436
+ index,
437
+ start: offsets[index] ?? 0,
438
+ size: sizes[index] ?? 0
439
+ })),
440
+ startOffset: 0,
441
+ endOffset: 0,
442
+ totalWidth,
443
+ visibleWidth: totalWidth,
444
+ startIndex: 0,
445
+ endIndex: Math.max(columns.length - 1, 0),
446
+ isVirtualized: false,
447
+ scrollToIndex
448
+ };
449
+ }
450
+ const { scrollLeft, containerWidth } = scrollState;
451
+ if (containerWidth <= 0 || totalWidth <= containerWidth) {
452
+ return {
453
+ virtualColumns: columns.map((column, index) => ({
454
+ column,
455
+ index,
456
+ start: offsets[index] ?? 0,
457
+ size: sizes[index] ?? 0
458
+ })),
459
+ startOffset: 0,
460
+ endOffset: 0,
461
+ totalWidth,
462
+ visibleWidth: totalWidth,
463
+ startIndex: 0,
464
+ endIndex: Math.max(columns.length - 1, 0),
465
+ isVirtualized: false,
466
+ scrollToIndex
467
+ };
468
+ }
469
+ const startIndex = binarySearchOffsets(offsets, scrollLeft);
470
+ const endBoundary = scrollLeft + containerWidth;
471
+ const endIndex = binarySearchOffsets(offsets, Math.max(scrollLeft, endBoundary - 1));
472
+ const overscanStart = Math.max(0, startIndex - overscan);
473
+ const overscanEnd = Math.min(columns.length - 1, endIndex + overscan);
474
+ const virtualColumns = [];
475
+ for (let i = overscanStart; i <= overscanEnd; i++) {
476
+ virtualColumns.push({
477
+ column: columns[i],
478
+ index: i,
479
+ start: offsets[i] ?? 0,
480
+ size: sizes[i] ?? 0
481
+ });
482
+ }
483
+ const startOffset = offsets[overscanStart] ?? 0;
484
+ const visibleWidth = (offsets[overscanEnd + 1] ?? totalWidth) - startOffset;
485
+ const endOffset = totalWidth - (offsets[overscanEnd + 1] ?? totalWidth);
486
+ return {
487
+ virtualColumns,
488
+ startOffset,
489
+ endOffset,
490
+ totalWidth,
491
+ visibleWidth,
492
+ startIndex: overscanStart,
493
+ endIndex: overscanEnd,
494
+ isVirtualized: true,
495
+ scrollToIndex
496
+ };
497
+ }
337
498
  var pretextPromise = null;
338
499
  function loadPretext() {
339
500
  if (pretextPromise) return pretextPromise;
@@ -384,7 +545,12 @@ function usePretextMeasurement({
384
545
  }
385
546
  prepareTimeRef.current = performance.now() - start;
386
547
  return result;
387
- }, [pretext, enabled, data, columns.map((c) => `${c.columnId}:${c.font}:${c.fixedHeight ? "F" : "T"}`).join("|")]);
548
+ }, [
549
+ pretext,
550
+ enabled,
551
+ data,
552
+ columns.map((c) => `${c.columnId}:${c.font}:${c.fixedHeight ? "F" : "T"}`).join("|")
553
+ ]);
388
554
  const measurement = useMemo(() => {
389
555
  if (!pretext || !preparedCells) {
390
556
  return { rowHeights: null, prefixSums: null, totalHeight: 0 };
@@ -422,6 +588,7 @@ function usePretextMeasurement({
422
588
  return {
423
589
  rowHeights,
424
590
  prefixSums,
591
+ // Safe: prefixSums has length data.length + 1, so [data.length] always exists
425
592
  totalHeight: prefixSums[data.length]
426
593
  };
427
594
  }, [pretext, preparedCells, columnWidthsKey, minRowHeight, data.length]);
@@ -686,18 +853,10 @@ function CellDate({
686
853
  if (raw == null) return null;
687
854
  const date = raw instanceof Date ? raw : new Date(String(raw));
688
855
  if (isNaN(date.getTime())) return /* @__PURE__ */ jsx("span", { className: "yable-cell-date", children: String(raw) });
689
- const formatter = useMemo(() => {
690
- if (typeof format === "string" && format !== "relative") {
691
- return new Intl.DateTimeFormat(locale, {
692
- ...PRESETS[format],
693
- timeZone: "UTC"
694
- });
695
- }
696
- if (typeof format === "object") {
697
- return new Intl.DateTimeFormat(locale, { ...format, timeZone: "UTC" });
698
- }
699
- return null;
700
- }, [format, locale]);
856
+ const formatter = typeof format === "string" && format !== "relative" ? new Intl.DateTimeFormat(locale, {
857
+ ...PRESETS[format],
858
+ timeZone: "UTC"
859
+ }) : typeof format === "object" ? new Intl.DateTimeFormat(locale, { ...format, timeZone: "UTC" }) : null;
701
860
  const formatted = format === "relative" ? formatRelative(date) : formatter ? formatter.format(date) : date.toLocaleDateString(locale);
702
861
  return /* @__PURE__ */ jsx("span", { className: `yable-cell-date ${className ?? ""}`, title: date.toISOString(), children: formatted });
703
862
  }
@@ -708,8 +867,14 @@ var measureRecipe9 = {
708
867
  padding: 20
709
868
  };
710
869
  function isSafeUrl(url) {
711
- const normalized = String(url).toLowerCase().trim();
712
- return !normalized.startsWith("javascript:") && !normalized.startsWith("data:text/html") && !normalized.startsWith("vbscript:");
870
+ const normalized = String(url).trim();
871
+ if (/^[/#?]/.test(normalized)) return true;
872
+ try {
873
+ const parsed = new URL(normalized, "https://placeholder.invalid");
874
+ return ["http:", "https:", "mailto:", "tel:"].includes(parsed.protocol);
875
+ } catch {
876
+ return false;
877
+ }
713
878
  }
714
879
  function CellLink({
715
880
  context,
@@ -886,7 +1051,7 @@ function useTableContext() {
886
1051
  const ctx = useContext(TableContext);
887
1052
  if (!ctx) {
888
1053
  throw new Error(
889
- "[yable] useTableContext must be used within a <Table> component. Did you forget to pass the `table` prop?"
1054
+ "[yable E001] useTableContext must be used within a <Table> component. Did you forget to pass the `table` prop?"
890
1055
  );
891
1056
  }
892
1057
  return ctx;
@@ -936,12 +1101,234 @@ function SortIndicator({ direction, index }) {
936
1101
  }
937
1102
  );
938
1103
  }
1104
+ function formatValue(value) {
1105
+ if (value == null || value === "") return "(empty)";
1106
+ if (typeof value === "string") return value;
1107
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1108
+ if (value instanceof Date) return value.toISOString();
1109
+ return JSON.stringify(value);
1110
+ }
1111
+ function SetFilter({ column, className }) {
1112
+ const [open, setOpen] = useState(false);
1113
+ const filterValue = column.getFilterValue();
1114
+ const selectedValues = Array.isArray(filterValue) ? filterValue : filterValue == null || filterValue === "" ? [] : [filterValue];
1115
+ const facetedUniqueValues = column.getFacetedUniqueValues();
1116
+ const options = Array.from(facetedUniqueValues.entries()).map(([value, count]) => ({
1117
+ value,
1118
+ count,
1119
+ label: formatValue(value)
1120
+ })).sort((a, b) => a.label.localeCompare(b.label));
1121
+ const selectedSet = new Set(selectedValues);
1122
+ const toggleValue = (value) => {
1123
+ const next = new Set(selectedSet);
1124
+ if (next.has(value)) {
1125
+ next.delete(value);
1126
+ } else {
1127
+ next.add(value);
1128
+ }
1129
+ const nextValues = Array.from(next);
1130
+ column.setFilterValue(nextValues.length > 0 ? nextValues : void 0);
1131
+ };
1132
+ const headerLabel = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1133
+ return /* @__PURE__ */ jsxs(
1134
+ "div",
1135
+ {
1136
+ className: ["yable-set-filter", className].filter(Boolean).join(" "),
1137
+ style: { position: "relative" },
1138
+ children: [
1139
+ /* @__PURE__ */ jsx(
1140
+ "button",
1141
+ {
1142
+ type: "button",
1143
+ className: "yable-set-filter-trigger",
1144
+ "aria-haspopup": "dialog",
1145
+ "aria-expanded": open,
1146
+ onClick: () => setOpen((prev) => !prev),
1147
+ style: {
1148
+ width: "100%",
1149
+ minHeight: 28,
1150
+ padding: "4px 8px",
1151
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1152
+ borderRadius: 6,
1153
+ background: "transparent",
1154
+ font: "inherit",
1155
+ textAlign: "left",
1156
+ cursor: "pointer"
1157
+ },
1158
+ children: selectedValues.length > 0 ? `${selectedValues.length} selected` : `Filter ${headerLabel}`
1159
+ }
1160
+ ),
1161
+ open && /* @__PURE__ */ jsxs(
1162
+ "div",
1163
+ {
1164
+ role: "dialog",
1165
+ "aria-label": `${headerLabel} set filter`,
1166
+ className: "yable-set-filter-popover",
1167
+ style: {
1168
+ position: "absolute",
1169
+ insetInlineStart: 0,
1170
+ top: "calc(100% + 4px)",
1171
+ zIndex: 20,
1172
+ width: 220,
1173
+ maxHeight: 240,
1174
+ overflow: "auto",
1175
+ padding: 8,
1176
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1177
+ borderRadius: 8,
1178
+ background: "var(--yable-color-bg, #fff)",
1179
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.12)"
1180
+ },
1181
+ children: [
1182
+ /* @__PURE__ */ jsxs(
1183
+ "div",
1184
+ {
1185
+ style: {
1186
+ display: "flex",
1187
+ alignItems: "center",
1188
+ justifyContent: "space-between",
1189
+ gap: 8,
1190
+ marginBottom: 8
1191
+ },
1192
+ children: [
1193
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 12 }, children: headerLabel }),
1194
+ selectedValues.length > 0 && /* @__PURE__ */ jsx(
1195
+ "button",
1196
+ {
1197
+ type: "button",
1198
+ onClick: () => column.setFilterValue(void 0),
1199
+ style: {
1200
+ border: "none",
1201
+ background: "transparent",
1202
+ padding: 0,
1203
+ cursor: "pointer",
1204
+ fontSize: 12
1205
+ },
1206
+ children: "Clear"
1207
+ }
1208
+ )
1209
+ ]
1210
+ }
1211
+ ),
1212
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": `${headerLabel} options`, children: [
1213
+ options.length === 0 && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, opacity: 0.75 }, children: "No values" }),
1214
+ options.map((option) => {
1215
+ const checked = selectedSet.has(option.value);
1216
+ return /* @__PURE__ */ jsxs(
1217
+ "label",
1218
+ {
1219
+ style: {
1220
+ display: "flex",
1221
+ alignItems: "center",
1222
+ gap: 8,
1223
+ padding: "4px 0",
1224
+ fontSize: 12
1225
+ },
1226
+ children: [
1227
+ /* @__PURE__ */ jsx(
1228
+ "input",
1229
+ {
1230
+ type: "checkbox",
1231
+ checked,
1232
+ onChange: () => toggleValue(option.value)
1233
+ }
1234
+ ),
1235
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: option.label }),
1236
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.6 }, children: option.count })
1237
+ ]
1238
+ },
1239
+ `${option.label}-${option.count}`
1240
+ );
1241
+ })
1242
+ ] })
1243
+ ]
1244
+ }
1245
+ )
1246
+ ]
1247
+ }
1248
+ );
1249
+ }
1250
+ function isSetCompatibleValue(value) {
1251
+ return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1252
+ }
1253
+ function inferFilterVariant(column) {
1254
+ const meta = column.columnDef.meta ?? {};
1255
+ if (meta.filterVariant) return meta.filterVariant;
1256
+ const uniqueValues = column.getFacetedUniqueValues();
1257
+ if (uniqueValues.size > 0 && uniqueValues.size <= 12) {
1258
+ const allValuesSupported = Array.from(uniqueValues.keys()).every(isSetCompatibleValue);
1259
+ if (allValuesSupported) return "set";
1260
+ }
1261
+ return "text";
1262
+ }
1263
+ function FloatingFilter({ column }) {
1264
+ const variant = inferFilterVariant(column);
1265
+ if (!column.getCanFilter()) {
1266
+ return /* @__PURE__ */ jsx("div", { style: { minHeight: 28 }, "aria-hidden": "true" });
1267
+ }
1268
+ if (variant === "set") {
1269
+ return /* @__PURE__ */ jsx(SetFilter, { column });
1270
+ }
1271
+ const filterValue = column.getFilterValue();
1272
+ const normalizedValue = typeof filterValue === "string" || typeof filterValue === "number" ? String(filterValue) : "";
1273
+ const label = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1274
+ return /* @__PURE__ */ jsx("label", { style: { display: "block" }, children: /* @__PURE__ */ jsx(
1275
+ "input",
1276
+ {
1277
+ "aria-label": `Filter ${label}`,
1278
+ className: "yable-floating-filter-input",
1279
+ value: normalizedValue,
1280
+ onChange: (event) => {
1281
+ const nextValue = event.target.value;
1282
+ column.setFilterValue(nextValue === "" ? void 0 : nextValue);
1283
+ },
1284
+ placeholder: `Filter ${label}`,
1285
+ style: {
1286
+ width: "100%",
1287
+ minHeight: 28,
1288
+ padding: "4px 8px",
1289
+ border: "1px solid rgba(127, 127, 127, 0.35)",
1290
+ borderRadius: 6,
1291
+ background: "transparent",
1292
+ font: "inherit"
1293
+ }
1294
+ }
1295
+ ) });
1296
+ }
939
1297
  var DRAG_MIME = "application/yable-column";
940
1298
  function TableHeader({
941
- table
1299
+ table,
1300
+ floatingFilters = false
942
1301
  }) {
943
1302
  const headerGroups = table.getHeaderGroups();
944
- return /* @__PURE__ */ jsx("thead", { className: "yable-thead", children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx("tr", { className: "yable-header-row", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(HeaderCell, { header, table }, header.id)) }, headerGroup.id)) });
1303
+ const visibleColumns = table.getVisibleLeafColumns();
1304
+ return /* @__PURE__ */ jsxs("thead", { className: "yable-thead", children: [
1305
+ headerGroups.map((headerGroup) => /* @__PURE__ */ jsx("tr", { className: "yable-header-row", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(HeaderCell, { header, table }, header.id)) }, headerGroup.id)),
1306
+ floatingFilters && visibleColumns.length > 0 && /* @__PURE__ */ jsx("tr", { className: "yable-header-row yable-header-row--filters", children: visibleColumns.map((column) => /* @__PURE__ */ jsx(FloatingFilterCell, { column }, `${column.id}-filter`)) })
1307
+ ] });
1308
+ }
1309
+ function FloatingFilterCell({
1310
+ column
1311
+ }) {
1312
+ const style = useMemo(() => {
1313
+ const next = {
1314
+ width: column.getSize(),
1315
+ minWidth: column.columnDef.minSize,
1316
+ maxWidth: column.columnDef.maxSize,
1317
+ padding: 6,
1318
+ verticalAlign: "top"
1319
+ };
1320
+ const pinned = column.getIsPinned();
1321
+ if (pinned) {
1322
+ next.position = "sticky";
1323
+ if (pinned === "left") {
1324
+ next.left = column.getStart("left");
1325
+ } else {
1326
+ next.right = column.getStart("right");
1327
+ }
1328
+ }
1329
+ return next;
1330
+ }, [column]);
1331
+ return /* @__PURE__ */ jsx("th", { className: "yable-th yable-th--filter", role: "columnheader", style, children: /* @__PURE__ */ jsx(FloatingFilter, { column }) });
945
1332
  }
946
1333
  function HeaderCell({
947
1334
  header,
@@ -1030,14 +1417,11 @@ function HeaderCell({
1030
1417
  },
1031
1418
  [canReorder]
1032
1419
  );
1033
- const handleDragLeave = useCallback(
1034
- (e) => {
1035
- const next = e.relatedTarget;
1036
- if (next && e.currentTarget.contains(next)) return;
1037
- setDragOver(null);
1038
- },
1039
- []
1040
- );
1420
+ const handleDragLeave = useCallback((e) => {
1421
+ const next = e.relatedTarget;
1422
+ if (next && e.currentTarget.contains(next)) return;
1423
+ setDragOver(null);
1424
+ }, []);
1041
1425
  const handleDragEnd = useCallback(() => {
1042
1426
  setDragOver(null);
1043
1427
  }, []);
@@ -1110,13 +1494,7 @@ function HeaderCell({
1110
1494
  children: [
1111
1495
  /* @__PURE__ */ jsxs("div", { className: "yable-th-content", children: [
1112
1496
  /* @__PURE__ */ jsx("span", { children: headerContent }),
1113
- canSort && /* @__PURE__ */ jsx(
1114
- SortIndicator,
1115
- {
1116
- direction: sortDirection,
1117
- index: sortIndex > 0 ? sortIndex : void 0
1118
- }
1119
- )
1497
+ canSort && /* @__PURE__ */ jsx(SortIndicator, { direction: sortDirection, index: sortIndex > 0 ? sortIndex : void 0 })
1120
1498
  ] }),
1121
1499
  canResize && /* @__PURE__ */ jsx(
1122
1500
  "div",
@@ -1241,6 +1619,10 @@ function TableCell({
1241
1619
  const cellStatus = table.getCellStatus(cell.row.id, column.id);
1242
1620
  const cellErrorMessage = table.getCellErrorMessage(cell.row.id, column.id);
1243
1621
  const cellConflictWith = table.getCellConflictWith(cell.row.id, column.id);
1622
+ const selectionRange = table.getCellSelectionRange();
1623
+ const selectionEdges = table.getCellSelectionEdges(rowIndex, columnIndex);
1624
+ const isCellSelected = selectionEdges !== null;
1625
+ const isMultiCellSelection = selectionRange !== null && (selectionRange.start.rowIndex !== selectionRange.end.rowIndex || selectionRange.start.columnIndex !== selectionRange.end.columnIndex);
1244
1626
  const overrideValue = cellStatus !== "idle" ? table.getCellRenderValue(cell.row.id, column.id) : void 0;
1245
1627
  let content;
1246
1628
  const cellDef = column.columnDef.cell;
@@ -1264,23 +1646,17 @@ function TableCell({
1264
1646
  }
1265
1647
  const handleClick = useCallback(
1266
1648
  (e) => {
1267
- table.setFocusedCell({ rowIndex, columnIndex });
1268
- const clickTarget = e.target;
1269
- if (!isInteractiveClickTarget(clickTarget)) {
1270
- const currentTarget = e.currentTarget;
1271
- currentTarget.focus({ preventScroll: true });
1272
- }
1273
1649
  table.events.emit("cell:click", {
1274
1650
  cell,
1275
1651
  row: cell.row,
1276
1652
  column: cell.column,
1277
1653
  event: e.nativeEvent
1278
1654
  });
1279
- if (canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing) {
1655
+ if (canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing && !isMultiCellSelection && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
1280
1656
  table.startEditing(cell.row.id, column.id);
1281
1657
  }
1282
1658
  },
1283
- [cell, column, columnIndex, isAlwaysEditable, isEditing, rowIndex, table]
1659
+ [cell, column, isAlwaysEditable, isEditing, isMultiCellSelection, table]
1284
1660
  );
1285
1661
  const handleDoubleClick = useCallback(
1286
1662
  (e) => {
@@ -1308,9 +1684,34 @@ function TableCell({
1308
1684
  if (!keyboardNavigationEnabled) return;
1309
1685
  table.setFocusedCell({ rowIndex, columnIndex });
1310
1686
  }, [columnIndex, keyboardNavigationEnabled, rowIndex, table]);
1687
+ const handleMouseDown = useCallback(
1688
+ (e) => {
1689
+ if (e.button !== 0) return;
1690
+ const clickTarget = e.target;
1691
+ if (isInteractiveClickTarget(clickTarget)) return;
1692
+ e.preventDefault();
1693
+ table.setFocusedCell({ rowIndex, columnIndex });
1694
+ table.startCellRangeSelection({ rowIndex, columnIndex }, { extend: e.shiftKey });
1695
+ e.currentTarget.focus({ preventScroll: true });
1696
+ },
1697
+ [columnIndex, rowIndex, table]
1698
+ );
1699
+ const handleMouseEnter = useCallback(() => {
1700
+ if (!table.getState().cellSelection?.isDragging) return;
1701
+ table.updateCellRangeSelection({ rowIndex, columnIndex });
1702
+ }, [columnIndex, rowIndex, table]);
1703
+ const handleMouseUp = useCallback(() => {
1704
+ if (!table.getState().cellSelection?.isDragging) return;
1705
+ table.endCellRangeSelection();
1706
+ }, [table]);
1311
1707
  const classNames = [
1312
1708
  "yable-td",
1313
- isFocused && "yable-cell--focused"
1709
+ isFocused && "yable-cell--focused",
1710
+ isCellSelected && "yable-cell--selected",
1711
+ selectionEdges?.top && "yable-cell--selection-top",
1712
+ selectionEdges?.right && "yable-cell--selection-right",
1713
+ selectionEdges?.bottom && "yable-cell--selection-bottom",
1714
+ selectionEdges?.left && "yable-cell--selection-left"
1314
1715
  ].filter(Boolean).join(" ");
1315
1716
  return /* @__PURE__ */ jsxs(
1316
1717
  "td",
@@ -1324,10 +1725,14 @@ function TableCell({
1324
1725
  "data-column-id": column.id,
1325
1726
  "data-row-index": rowIndex,
1326
1727
  "data-column-index": columnIndex,
1728
+ "data-cell-selected": isCellSelected || void 0,
1327
1729
  "aria-rowindex": rowIndex + 1,
1328
1730
  "aria-colindex": columnIndex + 1,
1329
1731
  role: "gridcell",
1330
1732
  tabIndex: keyboardNavigationEnabled ? isTabStop ? 0 : -1 : void 0,
1733
+ onMouseDown: handleMouseDown,
1734
+ onMouseEnter: handleMouseEnter,
1735
+ onMouseUp: handleMouseUp,
1331
1736
  onClick: handleClick,
1332
1737
  onDoubleClick: handleDoubleClick,
1333
1738
  onContextMenu: handleContextMenu,
@@ -1452,14 +1857,15 @@ var CellErrorBoundary = class extends React3.Component {
1452
1857
  return this.props.children;
1453
1858
  }
1454
1859
  };
1455
- function TableBody({
1456
- table,
1457
- clickableRows
1458
- }) {
1860
+ function TableBody({ table, clickableRows }) {
1459
1861
  const rows = table.getRowModel().rows;
1460
1862
  const visibleColumns = table.getVisibleLeafColumns();
1461
1863
  const activeCell = table.getState().editing.activeCell;
1462
1864
  const focusedCell = table.getFocusedCell();
1865
+ const cellSelection = table.getState().cellSelection ?? {
1866
+ range: null,
1867
+ isDragging: false
1868
+ };
1463
1869
  const options = table.options;
1464
1870
  const enableVirtualization = options.enableVirtualization ?? false;
1465
1871
  const scrollContainerRef = useRef(null);
@@ -1477,6 +1883,18 @@ function TableBody({
1477
1883
  pretextHeights,
1478
1884
  pretextPrefixSums
1479
1885
  });
1886
+ 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"}`;
1887
+ useEffect(() => {
1888
+ const handleWindowMouseUp = () => {
1889
+ if (table.getState().cellSelection?.isDragging) {
1890
+ table.endCellRangeSelection();
1891
+ }
1892
+ };
1893
+ window.addEventListener("mouseup", handleWindowMouseUp);
1894
+ return () => {
1895
+ window.removeEventListener("mouseup", handleWindowMouseUp);
1896
+ };
1897
+ }, [table]);
1480
1898
  if (!enableVirtualization) {
1481
1899
  return /* @__PURE__ */ jsx("tbody", { className: "yable-tbody", children: rows.map((row, rowIndex) => /* @__PURE__ */ jsx(
1482
1900
  MemoizedTableRow,
@@ -1490,6 +1908,7 @@ function TableBody({
1490
1908
  activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1491
1909
  focusedColumnIndex: focusedCell?.rowIndex === rowIndex ? focusedCell.columnIndex : null,
1492
1910
  hasFocusedCell: focusedCell !== null,
1911
+ cellSelectionKey,
1493
1912
  clickable: clickableRows
1494
1913
  },
1495
1914
  row.id
@@ -1498,73 +1917,67 @@ function TableBody({
1498
1917
  const hasPretextData = !!(pretextHeights && pretextPrefixSums);
1499
1918
  const fixedRowHeight = typeof rowHeight === "number" && !hasPretextData ? rowHeight : void 0;
1500
1919
  const containerHeight = hasPretextData ? Math.min(totalHeight, 800) : fixedRowHeight ? Math.min(totalHeight, fixedRowHeight * 20) : 600;
1501
- return /* @__PURE__ */ jsx("tbody", { className: "yable-tbody", children: /* @__PURE__ */ jsx("tr", { style: { height: 0, padding: 0, border: "none" }, children: /* @__PURE__ */ jsx(
1502
- "td",
1920
+ return /* @__PURE__ */ jsx("tbody", { className: "yable-tbody", children: /* @__PURE__ */ jsx("tr", { style: { height: 0, padding: 0, border: "none" }, children: /* @__PURE__ */ jsx("td", { style: { height: 0, padding: 0, border: "none" }, colSpan: visibleColumns.length, children: /* @__PURE__ */ jsx(
1921
+ "div",
1503
1922
  {
1504
- style: { height: 0, padding: 0, border: "none" },
1505
- colSpan: visibleColumns.length,
1923
+ ref: scrollContainerRef,
1924
+ className: "yable-virtual-scroll-container",
1925
+ style: {
1926
+ overflowY: "auto",
1927
+ height: containerHeight,
1928
+ position: "relative"
1929
+ },
1506
1930
  children: /* @__PURE__ */ jsx(
1507
1931
  "div",
1508
1932
  {
1509
- ref: scrollContainerRef,
1510
- className: "yable-virtual-scroll-container",
1511
- style: {
1512
- overflowY: "auto",
1513
- height: containerHeight,
1514
- position: "relative"
1515
- },
1933
+ className: "yable-virtual-spacer",
1934
+ style: { height: totalHeight, position: "relative" },
1516
1935
  children: /* @__PURE__ */ jsx(
1517
- "div",
1936
+ "table",
1518
1937
  {
1519
- className: "yable-virtual-spacer",
1520
- style: { height: totalHeight, position: "relative" },
1521
- children: /* @__PURE__ */ jsx(
1522
- "table",
1523
- {
1524
- style: {
1525
- position: "absolute",
1526
- top: 0,
1527
- left: 0,
1528
- width: "100%",
1529
- tableLayout: "fixed",
1530
- borderCollapse: "collapse"
1938
+ style: {
1939
+ position: "absolute",
1940
+ top: 0,
1941
+ left: 0,
1942
+ width: "100%",
1943
+ tableLayout: "fixed",
1944
+ borderCollapse: "collapse"
1945
+ },
1946
+ children: /* @__PURE__ */ jsx("tbody", { children: virtualRows.map((vRow) => {
1947
+ const row = rows[vRow.index];
1948
+ if (!row) return null;
1949
+ return /* @__PURE__ */ jsx(
1950
+ MemoizedTableRow,
1951
+ {
1952
+ row,
1953
+ table,
1954
+ rowIndex: vRow.index,
1955
+ visibleColumns,
1956
+ isSelected: row.getIsSelected(),
1957
+ isExpanded: row.getIsExpanded(),
1958
+ activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1959
+ focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
1960
+ hasFocusedCell: focusedCell !== null,
1961
+ cellSelectionKey,
1962
+ clickable: clickableRows,
1963
+ virtualStyle: {
1964
+ position: "absolute",
1965
+ top: 0,
1966
+ left: 0,
1967
+ width: "100%",
1968
+ height: vRow.size,
1969
+ transform: `translateY(${vRow.start}px)`
1970
+ }
1531
1971
  },
1532
- children: /* @__PURE__ */ jsx("tbody", { children: virtualRows.map((vRow) => {
1533
- const row = rows[vRow.index];
1534
- if (!row) return null;
1535
- return /* @__PURE__ */ jsx(
1536
- MemoizedTableRow,
1537
- {
1538
- row,
1539
- table,
1540
- rowIndex: vRow.index,
1541
- visibleColumns,
1542
- isSelected: row.getIsSelected(),
1543
- isExpanded: row.getIsExpanded(),
1544
- activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1545
- focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
1546
- hasFocusedCell: focusedCell !== null,
1547
- clickable: clickableRows,
1548
- virtualStyle: {
1549
- position: "absolute",
1550
- top: 0,
1551
- left: 0,
1552
- width: "100%",
1553
- height: vRow.size,
1554
- transform: `translateY(${vRow.start}px)`
1555
- }
1556
- },
1557
- row.id
1558
- );
1559
- }) })
1560
- }
1561
- )
1972
+ row.id
1973
+ );
1974
+ }) })
1562
1975
  }
1563
1976
  )
1564
1977
  }
1565
1978
  )
1566
1979
  }
1567
- ) }) });
1980
+ ) }) }) });
1568
1981
  }
1569
1982
  function TableRowInner({
1570
1983
  row,
@@ -1576,10 +1989,12 @@ function TableRowInner({
1576
1989
  activeColumnId,
1577
1990
  focusedColumnIndex,
1578
1991
  hasFocusedCell,
1992
+ cellSelectionKey: _cellSelectionKey,
1579
1993
  clickable,
1580
1994
  virtualStyle
1581
1995
  }) {
1582
- const visibleCells = row.getVisibleCells();
1996
+ const allCells = row.getAllCells();
1997
+ const visibleCells = visibleColumns.map((column) => allCells.find((cell) => cell.column.id === column.id)).filter((cell) => cell != null);
1583
1998
  const handleClick = useCallback(
1584
1999
  (e) => {
1585
2000
  if (clickable) {
@@ -1609,6 +2024,8 @@ function TableRowInner({
1609
2024
  },
1610
2025
  [table.events, row]
1611
2026
  );
2027
+ const selectionEnabled = Boolean(table.options.enableRowSelection);
2028
+ const expansionEnabled = Boolean(table.options.enableExpanding);
1612
2029
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1613
2030
  /* @__PURE__ */ jsx(
1614
2031
  "tr",
@@ -1620,6 +2037,8 @@ function TableRowInner({
1620
2037
  "data-clickable": clickable || void 0,
1621
2038
  "data-row-id": row.id,
1622
2039
  "data-row-index": rowIndex,
2040
+ "aria-selected": selectionEnabled ? isSelected : void 0,
2041
+ "aria-expanded": expansionEnabled ? isExpanded : void 0,
1623
2042
  onClick: handleClick,
1624
2043
  onDoubleClick: handleDoubleClick,
1625
2044
  onContextMenu: handleContextMenu,
@@ -1661,6 +2080,7 @@ function areRowPropsEqual(prev, next) {
1661
2080
  if (prev.activeColumnId !== next.activeColumnId) return false;
1662
2081
  if (prev.focusedColumnIndex !== next.focusedColumnIndex) return false;
1663
2082
  if (prev.hasFocusedCell !== next.hasFocusedCell) return false;
2083
+ if (prev.cellSelectionKey !== next.cellSelectionKey) return false;
1664
2084
  if (prev.virtualStyle !== next.virtualStyle) {
1665
2085
  if (!prev.virtualStyle || !next.virtualStyle) return false;
1666
2086
  if (prev.virtualStyle.transform !== next.virtualStyle.transform) return false;
@@ -1670,9 +2090,7 @@ function areRowPropsEqual(prev, next) {
1670
2090
  return true;
1671
2091
  }
1672
2092
  var MemoizedTableRow = React3.memo(TableRowInner, areRowPropsEqual);
1673
- function TableFooter({
1674
- table
1675
- }) {
2093
+ function TableFooter({ table }) {
1676
2094
  const footerGroups = table.getFooterGroups();
1677
2095
  if (!footerGroups.length) return null;
1678
2096
  return /* @__PURE__ */ jsx("tfoot", { className: "yable-tfoot", children: footerGroups.map((footerGroup) => /* @__PURE__ */ jsx("tr", { className: "yable-tr", children: footerGroup.headers.map((header) => {
@@ -2435,11 +2853,11 @@ function ContextMenu({
2435
2853
  }
2436
2854
  if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2437
2855
  e.preventDefault();
2438
- const items2 = menuRef.current?.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])');
2439
- if (!items2 || items2.length === 0) return;
2440
- const currentIndex = Array.from(items2).findIndex(
2441
- (el) => el === document.activeElement
2856
+ const items2 = menuRef.current?.querySelectorAll(
2857
+ '[role="menuitem"]:not([aria-disabled="true"])'
2442
2858
  );
2859
+ if (!items2 || items2.length === 0) return;
2860
+ const currentIndex = Array.from(items2).findIndex((el) => el === document.activeElement);
2443
2861
  let nextIndex;
2444
2862
  if (e.key === "ArrowDown") {
2445
2863
  nextIndex = currentIndex < items2.length - 1 ? currentIndex + 1 : 0;
@@ -2484,11 +2902,7 @@ function ContextMenu({
2484
2902
  navigator.clipboard?.readText().then((text) => {
2485
2903
  const editing = table.getState().editing;
2486
2904
  if (editing?.activeCell) {
2487
- table.pasteFromClipboard(
2488
- text,
2489
- editing.activeCell.rowId,
2490
- editing.activeCell.columnId
2491
- );
2905
+ table.pasteFromClipboard(text, editing.activeCell.rowId, editing.activeCell.columnId);
2492
2906
  }
2493
2907
  }).catch(() => {
2494
2908
  });
@@ -2828,6 +3242,12 @@ function isEditableTarget(element) {
2828
3242
  }
2829
3243
  return element.isContentEditable;
2830
3244
  }
3245
+ function filterHeaderGroups(groups, visibleColumnIds) {
3246
+ return groups.map((group) => ({
3247
+ ...group,
3248
+ headers: group.headers.filter((header) => visibleColumnIds.has(header.column.id))
3249
+ })).filter((group) => group.headers.length > 0);
3250
+ }
2831
3251
  function Table({
2832
3252
  table,
2833
3253
  stickyHeader,
@@ -2854,6 +3274,10 @@ function Table({
2854
3274
  sidebar,
2855
3275
  sidebarPanels = ["columns", "filters"],
2856
3276
  defaultSidebarPanel,
3277
+ floatingFilters,
3278
+ columnVirtualization,
3279
+ columnVirtualizationOverscan,
3280
+ ariaLabel,
2857
3281
  ...rest
2858
3282
  }) {
2859
3283
  const [sidebarOpen, setSidebarOpen] = useState(false);
@@ -2861,6 +3285,7 @@ function Table({
2861
3285
  defaultSidebarPanel ?? "columns"
2862
3286
  );
2863
3287
  const containerRef = useRef(null);
3288
+ const horizontalScrollRef = useRef(null);
2864
3289
  const isRtl = direction === "rtl";
2865
3290
  const classNames = [
2866
3291
  "yable",
@@ -2878,8 +3303,86 @@ function Table({
2878
3303
  const hasGlobalFilter = Boolean(table.getState().globalFilter);
2879
3304
  const hasColumnFilters = table.getState().columnFilters.length > 0;
2880
3305
  const isFiltered = hasGlobalFilter || hasColumnFilters;
3306
+ const allVisibleColumns = table.getVisibleLeafColumns();
3307
+ const hasPinnedColumns = table.getLeftVisibleLeafColumns().length > 0 || table.getRightVisibleLeafColumns().length > 0;
3308
+ const hasGroupedHeaders = table.getHeaderGroups().length > 1;
3309
+ const canVirtualizeColumns = Boolean(columnVirtualization) && !hasPinnedColumns && !hasGroupedHeaders && allVisibleColumns.length > 0;
3310
+ const columnVirtualState = useColumnVirtualization({
3311
+ containerRef: horizontalScrollRef,
3312
+ columns: allVisibleColumns,
3313
+ overscan: columnVirtualizationOverscan ?? 2,
3314
+ enabled: canVirtualizeColumns
3315
+ });
3316
+ const renderTable = useMemo(() => {
3317
+ if (!canVirtualizeColumns || !columnVirtualState.isVirtualized) {
3318
+ return table;
3319
+ }
3320
+ const virtualColumns = columnVirtualState.virtualColumns.map((entry) => entry.column);
3321
+ const visibleColumnIds = new Set(virtualColumns.map((column) => column.id));
3322
+ const next = Object.create(table);
3323
+ next.getVisibleFlatColumns = () => virtualColumns;
3324
+ next.getVisibleLeafColumns = () => virtualColumns;
3325
+ next.getCenterVisibleLeafColumns = () => virtualColumns;
3326
+ next.getLeftVisibleLeafColumns = () => [];
3327
+ next.getRightVisibleLeafColumns = () => [];
3328
+ next.getHeaderGroups = () => filterHeaderGroups(table.getHeaderGroups(), visibleColumnIds);
3329
+ next.getCenterHeaderGroups = () => filterHeaderGroups(table.getCenterHeaderGroups(), visibleColumnIds);
3330
+ next.getFooterGroups = () => filterHeaderGroups(table.getFooterGroups(), visibleColumnIds);
3331
+ next.getCenterFooterGroups = () => filterHeaderGroups(table.getCenterFooterGroups(), visibleColumnIds);
3332
+ return next;
3333
+ }, [
3334
+ canVirtualizeColumns,
3335
+ columnVirtualState.isVirtualized,
3336
+ columnVirtualState.virtualColumns,
3337
+ table
3338
+ ]);
3339
+ const showColumnVirtualizationShell = canVirtualizeColumns;
2881
3340
  const contextMenu = useContextMenu();
2882
3341
  useKeyboardNavigation(table, { containerRef });
3342
+ const [announcement, setAnnouncement] = useState("");
3343
+ const prevSortingRef = useRef(table.getState().sorting);
3344
+ const prevFilterCountRef = useRef(rows.length);
3345
+ const prevHasFiltersRef = useRef(isFiltered);
3346
+ const prevPaginationRef = useRef(table.getState().pagination);
3347
+ const isFirstRenderRef = useRef(true);
3348
+ useEffect(() => {
3349
+ if (isFirstRenderRef.current) {
3350
+ isFirstRenderRef.current = false;
3351
+ return;
3352
+ }
3353
+ const currentSorting = table.getState().sorting;
3354
+ const currentPagination = table.getState().pagination;
3355
+ const currentRowCount = rows.length;
3356
+ const currentIsFiltered = isFiltered;
3357
+ if (JSON.stringify(currentSorting) !== JSON.stringify(prevSortingRef.current)) {
3358
+ prevSortingRef.current = currentSorting;
3359
+ const firstSort = currentSorting[0];
3360
+ if (firstSort) {
3361
+ const column = table.getColumn(firstSort.id);
3362
+ const headerDef = column?.columnDef.header;
3363
+ const columnName = typeof headerDef === "string" ? headerDef : firstSort.id;
3364
+ const direction2 = firstSort.desc ? "descending" : "ascending";
3365
+ setAnnouncement(`Sorted by ${columnName} ${direction2}`);
3366
+ return;
3367
+ }
3368
+ }
3369
+ if (currentRowCount !== prevFilterCountRef.current || currentIsFiltered !== prevHasFiltersRef.current) {
3370
+ prevFilterCountRef.current = currentRowCount;
3371
+ prevHasFiltersRef.current = currentIsFiltered;
3372
+ if (currentIsFiltered) {
3373
+ setAnnouncement(`${currentRowCount} rows after filtering`);
3374
+ return;
3375
+ }
3376
+ }
3377
+ if (JSON.stringify(currentPagination) !== JSON.stringify(prevPaginationRef.current)) {
3378
+ prevPaginationRef.current = currentPagination;
3379
+ const pageCount = table.getPageCount();
3380
+ if (pageCount > 0) {
3381
+ setAnnouncement(`Page ${currentPagination.pageIndex + 1} of ${pageCount}`);
3382
+ return;
3383
+ }
3384
+ }
3385
+ });
2883
3386
  const handleContextMenu = useCallback(
2884
3387
  (e) => {
2885
3388
  e.preventDefault();
@@ -2887,6 +3390,24 @@ function Table({
2887
3390
  },
2888
3391
  [contextMenu, table]
2889
3392
  );
3393
+ const tableNode = /* @__PURE__ */ jsxs(
3394
+ "table",
3395
+ {
3396
+ className: "yable-table",
3397
+ style: columnVirtualState.isVirtualized ? {
3398
+ width: columnVirtualState.visibleWidth,
3399
+ minWidth: columnVirtualState.visibleWidth,
3400
+ marginLeft: columnVirtualState.startOffset,
3401
+ tableLayout: "fixed"
3402
+ } : void 0,
3403
+ "data-column-virtualized": columnVirtualState.isVirtualized || void 0,
3404
+ children: [
3405
+ /* @__PURE__ */ jsx(TableHeader, { table: renderTable, floatingFilters }),
3406
+ /* @__PURE__ */ jsx(TableBody, { table: renderTable, clickableRows }),
3407
+ footer && /* @__PURE__ */ jsx(TableFooter, { table: renderTable })
3408
+ ]
3409
+ }
3410
+ );
2890
3411
  return /* @__PURE__ */ jsx(TableProvider, { value: table, children: /* @__PURE__ */ jsxs(
2891
3412
  "div",
2892
3413
  {
@@ -2895,23 +3416,40 @@ function Table({
2895
3416
  "data-theme": theme,
2896
3417
  dir: direction,
2897
3418
  role: "grid",
3419
+ "aria-label": ariaLabel ?? "Data table",
2898
3420
  "aria-rowcount": table.getRowModel().rows.length,
2899
3421
  "aria-colcount": table.getVisibleLeafColumns().length,
2900
3422
  onContextMenu: handleContextMenu,
2901
3423
  ...rest,
2902
3424
  children: [
2903
3425
  /* @__PURE__ */ jsxs("div", { className: "yable-main", children: [
2904
- /* @__PURE__ */ jsxs("table", { className: "yable-table", children: [
2905
- /* @__PURE__ */ jsx(TableHeader, { table }),
2906
- /* @__PURE__ */ jsx(
2907
- TableBody,
2908
- {
2909
- table,
2910
- clickableRows
2911
- }
2912
- ),
2913
- footer && /* @__PURE__ */ jsx(TableFooter, { table })
2914
- ] }),
3426
+ showColumnVirtualizationShell ? /* @__PURE__ */ jsx(
3427
+ "div",
3428
+ {
3429
+ ref: horizontalScrollRef,
3430
+ className: "yable-horizontal-scroll-container",
3431
+ style: {
3432
+ overflowX: "auto",
3433
+ overflowY: "visible",
3434
+ maxWidth: "100%",
3435
+ position: "relative"
3436
+ },
3437
+ children: /* @__PURE__ */ jsx(
3438
+ "div",
3439
+ {
3440
+ className: "yable-horizontal-scroll-inner",
3441
+ style: {
3442
+ width: Math.max(columnVirtualState.totalWidth, columnVirtualState.visibleWidth),
3443
+ minWidth: Math.max(
3444
+ columnVirtualState.totalWidth,
3445
+ columnVirtualState.visibleWidth
3446
+ )
3447
+ },
3448
+ children: tableNode
3449
+ }
3450
+ )
3451
+ }
3452
+ ) : tableNode,
2915
3453
  /* @__PURE__ */ jsx(
2916
3454
  LoadingOverlay,
2917
3455
  {
@@ -2952,6 +3490,24 @@ function Table({
2952
3490
  onClose: contextMenu.close,
2953
3491
  table
2954
3492
  }
3493
+ ),
3494
+ /* @__PURE__ */ jsx(
3495
+ "div",
3496
+ {
3497
+ role: "status",
3498
+ "aria-live": "polite",
3499
+ "aria-atomic": "true",
3500
+ style: {
3501
+ position: "absolute",
3502
+ width: "1px",
3503
+ height: "1px",
3504
+ overflow: "hidden",
3505
+ clip: "rect(0,0,0,0)",
3506
+ whiteSpace: "nowrap",
3507
+ border: 0
3508
+ },
3509
+ children: announcement
3510
+ }
2955
3511
  )
2956
3512
  ]
2957
3513
  }
@@ -3417,14 +3973,7 @@ function formatDateValue(value, type) {
3417
3973
  return String(value);
3418
3974
  }
3419
3975
  function useClipboard(table, options = {}) {
3420
- const {
3421
- enabled = true,
3422
- containerRef,
3423
- onCopy,
3424
- onCut,
3425
- onPaste,
3426
- ...clipboardOptions
3427
- } = options;
3976
+ const { enabled = true, containerRef, onCopy, onCut, onPaste, ...clipboardOptions } = options;
3428
3977
  const handleCopy = useCallback(
3429
3978
  (e) => {
3430
3979
  if (isEditableTarget2(e.target)) return;
@@ -3462,20 +4011,29 @@ function useClipboard(table, options = {}) {
3462
4011
  targetRowId = state.editing.activeCell.rowId;
3463
4012
  targetColumnId = state.editing.activeCell.columnId;
3464
4013
  } else {
3465
- const selectedRowIds = Object.keys(state.rowSelection || {}).filter(
3466
- (id) => state.rowSelection[id]
3467
- );
3468
- if (selectedRowIds.length > 0) {
3469
- targetRowId = selectedRowIds[0];
4014
+ const selectedRange = table.getCellSelectionRange();
4015
+ const rows = table.getRowModel().rows;
4016
+ const columns = table.getVisibleLeafColumns();
4017
+ if (selectedRange) {
4018
+ const startRowIndex = Math.min(selectedRange.start.rowIndex, selectedRange.end.rowIndex);
4019
+ const startColumnIndex = Math.min(
4020
+ selectedRange.start.columnIndex,
4021
+ selectedRange.end.columnIndex
4022
+ );
4023
+ targetRowId = rows[startRowIndex]?.id;
4024
+ targetColumnId = columns[startColumnIndex]?.id;
3470
4025
  } else {
3471
- const rows = table.getRowModel().rows;
3472
- if (rows.length > 0) {
4026
+ const selectedRowIds = Object.keys(state.rowSelection || {}).filter(
4027
+ (id) => state.rowSelection[id]
4028
+ );
4029
+ if (selectedRowIds.length > 0) {
4030
+ targetRowId = selectedRowIds[0];
4031
+ } else if (rows.length > 0) {
3473
4032
  targetRowId = rows[0].id;
3474
4033
  }
3475
- }
3476
- const columns = table.getVisibleLeafColumns();
3477
- if (columns.length > 0) {
3478
- targetColumnId = columns[0].id;
4034
+ if (columns.length > 0) {
4035
+ targetColumnId = columns[0].id;
4036
+ }
3479
4037
  }
3480
4038
  }
3481
4039
  if (targetRowId && targetColumnId) {
@@ -4765,7 +5323,7 @@ function sanitizeCSS(css) {
4765
5323
  sanitized = sanitized.replace(/javascript\s*:/gi, "/* javascript: removed */");
4766
5324
  return sanitized;
4767
5325
  }
4768
- function usePrintLayout(table, options = {}) {
5326
+ function usePrintLayout(_table, options = {}) {
4769
5327
  const { title, additionalCSS } = options;
4770
5328
  const isPrintingRef = useRef(false);
4771
5329
  const preparePrint = useCallback(() => {
@@ -4802,7 +5360,7 @@ function usePrintLayout(table, options = {}) {
4802
5360
  requestAnimationFrame(() => {
4803
5361
  window.print();
4804
5362
  });
4805
- }, [table, title, additionalCSS]);
5363
+ }, [title, additionalCSS]);
4806
5364
  return {
4807
5365
  preparePrint,
4808
5366
  isPrinting: isPrintingRef.current
@@ -4854,6 +5412,6 @@ function useTheme(options = {}) {
4854
5412
  };
4855
5413
  }
4856
5414
 
4857
- export { CellBadge, CellBoolean, CellCheckbox, CellCurrency, CellDate, CellDatePicker, CellErrorBoundary, CellInput, CellLink, CellNumeric, CellProgress, CellRating, CellSelect, CellStatus, CellStatusBadge, CellToggle, ColumnsPanel, ContextMenu, ContextMenuItem, DEFAULT_TEXT_RECIPE, DragHandle, ErrorBoundary, ExpandIcon, FillHandle, FiltersPanel, FlashCell, GlobalFilter, LoadingOverlay, MasterDetail, NoRowsOverlay, Pagination, PivotConfigPanel, PrintLayout, Sidebar, SortIndicator, StatusBar, StatusBarPanelComponent, Table, TableBody, TableCell, TableFooter, TableHeader, TableProvider, Tooltip, TreeToggle, getMeasureRecipeForCellType, getRegisteredCellTypes, resolveMeasureRecipe, useAutoMeasurements, useCellFlash, useClipboard, useContextMenu, useFillHandle, useKeyboardNavigation, usePretextMeasurement, usePrintLayout, useRowAnimation, useRowDrag, useTable, useTableContext, useTableRowHeights, useTheme, useTooltip, useVirtualization };
5415
+ export { CellBadge, CellBoolean, CellCheckbox, CellCurrency, CellDate, CellDatePicker, CellErrorBoundary, CellInput, CellLink, CellNumeric, CellProgress, CellRating, CellSelect, CellStatus, CellStatusBadge, CellToggle, ColumnsPanel, ContextMenu, ContextMenuItem, DEFAULT_TEXT_RECIPE, DragHandle, ErrorBoundary, ExpandIcon, FillHandle, FiltersPanel, FlashCell, FloatingFilter, GlobalFilter, LoadingOverlay, MasterDetail, NoRowsOverlay, Pagination, PivotConfigPanel, PrintLayout, SetFilter, Sidebar, SortIndicator, StatusBar, StatusBarPanelComponent, Table, TableBody, TableCell, TableFooter, TableHeader, TableProvider, Tooltip, TreeToggle, getMeasureRecipeForCellType, getRegisteredCellTypes, resolveMeasureRecipe, useAutoMeasurements, useCellFlash, useClipboard, useColumnVirtualization, useContextMenu, useFillHandle, useKeyboardNavigation, usePretextMeasurement, usePrintLayout, useRowAnimation, useRowDrag, useTable, useTableContext, useTableRowHeights, useTheme, useTooltip, useVirtualization };
4858
5416
  //# sourceMappingURL=index.js.map
4859
5417
  //# sourceMappingURL=index.js.map