react-glide-table 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -432,6 +432,90 @@ function withCellUpdate(context, commitValue) {
432
432
  };
433
433
  }
434
434
 
435
+ // src/components/ui/table/features/cell-selection/pasteData.ts
436
+ function countLeadingEmptyCells(cells) {
437
+ let depth = 0;
438
+ while (depth < cells.length && cells[depth] === "") {
439
+ depth += 1;
440
+ }
441
+ return depth;
442
+ }
443
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
444
+ if (leadingEmptyCounts.length === 0) return false;
445
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
446
+ if (firstDepth !== 0) return false;
447
+ return leadingEmptyCounts.some((depth) => depth > 0);
448
+ }
449
+ function parseClipboardTSV(text) {
450
+ return parseClipboardTSVWithDepths(text).values;
451
+ }
452
+ function parseClipboardTSVWithDepths(text) {
453
+ if (!text) return { values: [], depths: [] };
454
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
455
+ const withoutTrailing = normalized.replace(/\n+$/, "");
456
+ if (!withoutTrailing) return { values: [], depths: [] };
457
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
458
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
459
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
460
+ const values = [];
461
+ const depths = [];
462
+ for (let index = 0; index < rows.length; index += 1) {
463
+ const cells = rows[index] ?? [];
464
+ const depth = leadingEmptyCounts[index] ?? 0;
465
+ if (treatAsDepth) {
466
+ values.push(cells.slice(depth));
467
+ depths.push(depth);
468
+ } else {
469
+ values.push(cells);
470
+ depths.push(0);
471
+ }
472
+ }
473
+ return { values, depths };
474
+ }
475
+ function resolvePasteColumnIds(rows, startCol, width) {
476
+ if (width <= 0) return [];
477
+ const cells = rows[0]?.getVisibleCells() ?? [];
478
+ const columnIds = [];
479
+ for (let offset = 0; offset < width; offset += 1) {
480
+ const cell = cells[startCol + offset];
481
+ if (!cell) break;
482
+ columnIds.push(cell.column.id);
483
+ }
484
+ return columnIds;
485
+ }
486
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
487
+ const { values, depths } = parseClipboardTSVWithDepths(text);
488
+ if (values.length === 0) return null;
489
+ const width = Math.max(...values.map((row) => row.length), 0);
490
+ if (width === 0) return null;
491
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
492
+ if (columnIds.length === 0) return null;
493
+ const rowIds = [];
494
+ for (let offset = 0; offset < values.length; offset += 1) {
495
+ const row = rows[startRow + offset];
496
+ if (!row) break;
497
+ rowIds.push(row.id);
498
+ }
499
+ const anchorRow = rows[endRow] ?? rows[startRow];
500
+ return {
501
+ mode,
502
+ startRow,
503
+ startCol,
504
+ endRow,
505
+ rowIds,
506
+ anchorRowId: anchorRow?.id ?? "",
507
+ columnIds,
508
+ values,
509
+ depths
510
+ };
511
+ }
512
+ function isEditablePasteTarget(target) {
513
+ if (!(target instanceof HTMLElement)) return false;
514
+ const tag = target.tagName;
515
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
516
+ return Boolean(target.isContentEditable);
517
+ }
518
+
435
519
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
436
520
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
437
521
 
@@ -741,6 +825,219 @@ function hasCellSelectionEdges(style) {
741
825
  }
742
826
 
743
827
  // src/components/ui/table/features/cell-selection/copyData.ts
828
+ import { isValidElement } from "react";
829
+ function isReactNodeIterable(node) {
830
+ return typeof node === "object" && node !== null && !isValidElement(node) && Symbol.iterator in node;
831
+ }
832
+ function getElementTypeName(type) {
833
+ if (typeof type === "string") return type;
834
+ if (typeof type === "function") {
835
+ const fn = type;
836
+ return fn.displayName || fn.name || "";
837
+ }
838
+ if (typeof type === "object" && type !== null) {
839
+ const component = type;
840
+ return component.displayName || component.render?.displayName || component.render?.name || "";
841
+ }
842
+ return "";
843
+ }
844
+ function isButtonReactElement(node) {
845
+ const typeName = getElementTypeName(node.type);
846
+ if (typeName === "button" || /button/i.test(typeName)) return true;
847
+ const props = node.props;
848
+ if (props.role === "button") return true;
849
+ if (typeName === "input" && props.type === "button") return true;
850
+ return false;
851
+ }
852
+ function isImageReactElement(node) {
853
+ const typeName = getElementTypeName(node.type);
854
+ return typeName === "img" || typeName === "image" || /image/i.test(typeName);
855
+ }
856
+ var IMAGE_URL_PROP_KEYS = ["src", "url", "href", "imageUrl", "srcUrl"];
857
+ function isLikelyUrl(value) {
858
+ const trimmed = value.trim();
859
+ if (!trimmed) return false;
860
+ if (/^(https?:|blob:|data:|\/\/)/i.test(trimmed)) return true;
861
+ if (trimmed.startsWith("/") && trimmed.includes("/")) return true;
862
+ return false;
863
+ }
864
+ function pickUrlFromUnknown(value) {
865
+ if (typeof value === "string") {
866
+ return isLikelyUrl(value) ? value.trim() : "";
867
+ }
868
+ if (Array.isArray(value)) {
869
+ return value.map((item) => pickUrlFromUnknown(item)).filter((item) => item.length > 0).join(", ");
870
+ }
871
+ if (value && typeof value === "object") {
872
+ const record = value;
873
+ for (const key of IMAGE_URL_PROP_KEYS) {
874
+ const candidate = record[key];
875
+ if (typeof candidate === "string" && candidate.trim()) {
876
+ return candidate.trim();
877
+ }
878
+ }
879
+ }
880
+ return "";
881
+ }
882
+ function imageElementText(node) {
883
+ const props = node.props;
884
+ for (const key of IMAGE_URL_PROP_KEYS) {
885
+ const candidate = props[key];
886
+ if (typeof candidate === "string" && candidate.trim()) {
887
+ return candidate.trim();
888
+ }
889
+ }
890
+ return "";
891
+ }
892
+ function reactNodeContainsImage(node) {
893
+ if (isValidElement(node)) {
894
+ if (isImageReactElement(node)) return true;
895
+ return reactNodeContainsImage(node.props.children);
896
+ }
897
+ if (isReactNodeIterable(node)) {
898
+ for (const child of node) {
899
+ if (reactNodeContainsImage(child)) return true;
900
+ }
901
+ }
902
+ return false;
903
+ }
904
+ function readImgUrl(img) {
905
+ const attr = img.getAttribute("src")?.trim() ?? "";
906
+ if (attr) return attr;
907
+ if (img instanceof HTMLImageElement) {
908
+ const current = img.currentSrc?.trim() ?? "";
909
+ if (current && current !== img.baseURI) return current;
910
+ }
911
+ return "";
912
+ }
913
+ function readDomImageUrls(rowIndex, colIndex, root) {
914
+ const scope = root ?? (typeof document === "undefined" ? null : document);
915
+ if (!scope) return "";
916
+ const cells = scope.querySelectorAll(
917
+ `[data-row-index="${rowIndex}"][data-col-index="${colIndex}"]`
918
+ );
919
+ for (const cell of cells) {
920
+ const urls = Array.from(cell.querySelectorAll("img")).flatMap((img) => {
921
+ const url = readImgUrl(img);
922
+ return url ? [url] : [];
923
+ });
924
+ if (urls.length > 0) return urls.join(", ");
925
+ }
926
+ return "";
927
+ }
928
+ function reactNodeToText(node) {
929
+ if (node == null || typeof node === "boolean") return "";
930
+ if (typeof node === "string" || typeof node === "number" || typeof node === "bigint") {
931
+ return String(node);
932
+ }
933
+ if (isReactNodeIterable(node)) {
934
+ let text = "";
935
+ for (const child of node) {
936
+ text += reactNodeToText(child);
937
+ }
938
+ return text;
939
+ }
940
+ if (isValidElement(node)) {
941
+ if (isButtonReactElement(node)) return "";
942
+ const props = node.props;
943
+ const childText = reactNodeToText(props.children);
944
+ if (childText) return childText;
945
+ const fromImage = imageElementText(node);
946
+ if (fromImage) return fromImage;
947
+ if (isImageReactElement(node)) return "";
948
+ if (typeof props.alt === "string" && props.alt) return props.alt;
949
+ if (typeof props.title === "string" && props.title) return props.title;
950
+ return "";
951
+ }
952
+ return "";
953
+ }
954
+ function sanitizeClipboardCell(text) {
955
+ return text.replace(/\s+/g, " ").trim();
956
+ }
957
+ function createCopyRenderRow(rowData, index) {
958
+ return {
959
+ id: getOriginalRowId(rowData) || String(index),
960
+ index,
961
+ original: rowData,
962
+ getIsCellDragSelected: () => false
963
+ };
964
+ }
965
+ function buildVisibleRowLookup(visibleRows) {
966
+ const lookup = /* @__PURE__ */ new Map();
967
+ for (const row of visibleRows) {
968
+ lookup.set(row.original, row);
969
+ }
970
+ return lookup;
971
+ }
972
+ function resolveCopyColumnId(cell) {
973
+ if (cell.column.id) return cell.column.id;
974
+ const columnDef = cell.column.columnDef;
975
+ if (columnDef.id) return columnDef.id;
976
+ if (columnDef.accessorKey != null && columnDef.accessorKey !== "") {
977
+ return String(columnDef.accessorKey);
978
+ }
979
+ return "";
980
+ }
981
+ function isPrimitiveCopyValue(value) {
982
+ return value == null || typeof value !== "object";
983
+ }
984
+ function extractRenderedCopyText(node, value, cellPosition, root) {
985
+ const rendered = sanitizeClipboardCell(reactNodeToText(node));
986
+ if (reactNodeContainsImage(node)) {
987
+ const fromDom = cellPosition != null ? sanitizeClipboardCell(
988
+ readDomImageUrls(cellPosition.rowIndex, cellPosition.colIndex, root)
989
+ ) : "";
990
+ if (fromDom) return fromDom;
991
+ if (rendered && isLikelyUrl(rendered)) return rendered;
992
+ return sanitizeClipboardCell(pickUrlFromUnknown(value));
993
+ }
994
+ return rendered;
995
+ }
996
+ function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIndex, sourceCell, cellPosition, options) {
997
+ const meta = columnDef.meta;
998
+ const value = sourceCell ? sourceCell.getValue() : readRowColumnValue(rowData, columnDef);
999
+ const cellRender = meta?.cellRender;
1000
+ if (typeof cellRender === "function") {
1001
+ try {
1002
+ const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
1003
+ const node = cellRender({
1004
+ value,
1005
+ row,
1006
+ index: row.index,
1007
+ columnId,
1008
+ cellProps: meta?.cellProps,
1009
+ update: () => {
1010
+ }
1011
+ });
1012
+ return extractRenderedCopyText(node, value, cellPosition, options?.root);
1013
+ } catch {
1014
+ return formatCellValue(value);
1015
+ }
1016
+ }
1017
+ if (options?.registry && meta?.kind && isPrimitiveCopyValue(value)) {
1018
+ try {
1019
+ const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
1020
+ const ctx = {
1021
+ value,
1022
+ row,
1023
+ index: row.index,
1024
+ columnId,
1025
+ cellProps: meta.cellProps,
1026
+ update: () => {
1027
+ }
1028
+ };
1029
+ const renderer = resolveCellRenderer(options.registry, meta.kind, ctx);
1030
+ if (renderer) {
1031
+ const node = renderer.render(ctx);
1032
+ const rendered = extractRenderedCopyText(node, value, cellPosition, options.root);
1033
+ if (rendered) return rendered;
1034
+ }
1035
+ } catch {
1036
+ return formatCellValue(value);
1037
+ }
1038
+ }
1039
+ return formatCellValue(value);
1040
+ }
744
1041
  function formatPrimitive(value) {
745
1042
  if (value === null || value === void 0) return "";
746
1043
  if (typeof value === "string") return value;
@@ -846,37 +1143,47 @@ function collectCopyRowEntries(visibleRows, bounds, mode = "visible") {
846
1143
  function collectCopyRows(visibleRows, bounds, mode = "visible") {
847
1144
  return collectCopyRowEntries(visibleRows, bounds, mode).map((entry) => entry.row);
848
1145
  }
849
- function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths) {
1146
+ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths, options) {
850
1147
  if (copyRows.length === 0) return "";
851
1148
  const { startCol, endCol } = bounds;
852
1149
  const columnCells = visibleRows[0]?.getVisibleCells().slice(startCol, endCol + 1) ?? [];
853
1150
  if (columnCells.length === 0) return "";
854
1151
  const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
855
1152
  const minDepth = Math.min(...resolvedDepths);
1153
+ const visibleRowByOriginal = buildVisibleRowLookup(visibleRows);
856
1154
  return copyRows.map((rowData, index) => {
857
1155
  const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
858
- const line = columnCells.map(
859
- (cell) => formatCellValue(
860
- readRowColumnValue(
861
- rowData,
862
- cell.column.columnDef
863
- )
864
- )
865
- ).join(" ");
1156
+ const visibleRow = visibleRowByOriginal.get(rowData);
1157
+ const matchingCells = visibleRow?.getVisibleCells().slice(startCol, endCol + 1);
1158
+ const line = columnCells.map((templateCell, colOffset) => {
1159
+ const sourceCell = matchingCells?.[colOffset];
1160
+ const column = sourceCell?.column ?? templateCell.column;
1161
+ return formatCopyCellText(
1162
+ rowData,
1163
+ column.columnDef,
1164
+ resolveCopyColumnId(sourceCell ?? templateCell),
1165
+ visibleRow,
1166
+ visibleRow?.index ?? index,
1167
+ sourceCell,
1168
+ visibleRow != null ? { rowIndex: visibleRow.index, colIndex: startCol + colOffset } : void 0,
1169
+ options
1170
+ );
1171
+ }).join(" ");
866
1172
  return `${" ".repeat(relativeDepth)}${line}`;
867
1173
  }).join("\n");
868
1174
  }
869
- function serializeSelectionToTSV(visibleRows, bounds, mode = "visible") {
1175
+ function serializeSelectionToTSV(visibleRows, bounds, mode = "visible", options) {
870
1176
  const entries = collectCopyRowEntries(visibleRows, bounds, mode);
871
1177
  return serializeCopyRowsToTSV(
872
1178
  entries.map((entry) => entry.row),
873
1179
  visibleRows,
874
1180
  bounds,
875
- entries.map((entry) => entry.depth)
1181
+ entries.map((entry) => entry.depth),
1182
+ options
876
1183
  );
877
1184
  }
878
- async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible") {
879
- const text = serializeSelectionToTSV(visibleRows, bounds, mode);
1185
+ async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible", options) {
1186
+ const text = serializeSelectionToTSV(visibleRows, bounds, mode, options);
880
1187
  if (!text) return false;
881
1188
  try {
882
1189
  await navigator.clipboard.writeText(text);
@@ -942,90 +1249,6 @@ function hasFillExtension(sourceBounds, fillBounds) {
942
1249
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
943
1250
  }
944
1251
 
945
- // src/components/ui/table/features/cell-selection/pasteData.ts
946
- function countLeadingEmptyCells(cells) {
947
- let depth = 0;
948
- while (depth < cells.length && cells[depth] === "") {
949
- depth += 1;
950
- }
951
- return depth;
952
- }
953
- function looksLikeSubtreeIndentation(leadingEmptyCounts) {
954
- if (leadingEmptyCounts.length === 0) return false;
955
- const firstDepth = leadingEmptyCounts[0] ?? 0;
956
- if (firstDepth !== 0) return false;
957
- return leadingEmptyCounts.some((depth) => depth > 0);
958
- }
959
- function parseClipboardTSV(text) {
960
- return parseClipboardTSVWithDepths(text).values;
961
- }
962
- function parseClipboardTSVWithDepths(text) {
963
- if (!text) return { values: [], depths: [] };
964
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
965
- const withoutTrailing = normalized.replace(/\n+$/, "");
966
- if (!withoutTrailing) return { values: [], depths: [] };
967
- const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
968
- const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
969
- const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
970
- const values = [];
971
- const depths = [];
972
- for (let index = 0; index < rows.length; index += 1) {
973
- const cells = rows[index] ?? [];
974
- const depth = leadingEmptyCounts[index] ?? 0;
975
- if (treatAsDepth) {
976
- values.push(cells.slice(depth));
977
- depths.push(depth);
978
- } else {
979
- values.push(cells);
980
- depths.push(0);
981
- }
982
- }
983
- return { values, depths };
984
- }
985
- function resolvePasteColumnIds(rows, startCol, width) {
986
- if (width <= 0) return [];
987
- const cells = rows[0]?.getVisibleCells() ?? [];
988
- const columnIds = [];
989
- for (let offset = 0; offset < width; offset += 1) {
990
- const cell = cells[startCol + offset];
991
- if (!cell) break;
992
- columnIds.push(cell.column.id);
993
- }
994
- return columnIds;
995
- }
996
- function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
997
- const { values, depths } = parseClipboardTSVWithDepths(text);
998
- if (values.length === 0) return null;
999
- const width = Math.max(...values.map((row) => row.length), 0);
1000
- if (width === 0) return null;
1001
- const columnIds = resolvePasteColumnIds(rows, startCol, width);
1002
- if (columnIds.length === 0) return null;
1003
- const rowIds = [];
1004
- for (let offset = 0; offset < values.length; offset += 1) {
1005
- const row = rows[startRow + offset];
1006
- if (!row) break;
1007
- rowIds.push(row.id);
1008
- }
1009
- const anchorRow = rows[endRow] ?? rows[startRow];
1010
- return {
1011
- mode,
1012
- startRow,
1013
- startCol,
1014
- endRow,
1015
- rowIds,
1016
- anchorRowId: anchorRow?.id ?? "",
1017
- columnIds,
1018
- values,
1019
- depths
1020
- };
1021
- }
1022
- function isEditablePasteTarget(target) {
1023
- if (!(target instanceof HTMLElement)) return false;
1024
- const tag = target.tagName;
1025
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
1026
- return Boolean(target.isContentEditable);
1027
- }
1028
-
1029
1252
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
1030
1253
  function useCellSelection({
1031
1254
  data,
@@ -1037,7 +1260,9 @@ function useCellSelection({
1037
1260
  onDataChange,
1038
1261
  onBatchChange,
1039
1262
  onRowsPaste,
1040
- onCellNavigate
1263
+ onCellNavigate,
1264
+ cellRendererRegistry,
1265
+ rootRef
1041
1266
  }) {
1042
1267
  const [dragState, setDragState] = useState2(INITIAL_DRAG_STATE);
1043
1268
  const pendingPasteModeRef = useRef2(null);
@@ -1105,11 +1330,19 @@ function useCellSelection({
1105
1330
  },
1106
1331
  [enabled]
1107
1332
  );
1333
+ const clearSelection = useCallback2(() => {
1334
+ const prev = dragStateRef.current;
1335
+ if (prev.start === null && prev.end === null && !prev.isSelecting && !prev.isFillDragging) {
1336
+ return;
1337
+ }
1338
+ dragStateRef.current = INITIAL_DRAG_STATE;
1339
+ setDragState(INITIAL_DRAG_STATE);
1340
+ }, []);
1108
1341
  useEffect2(() => {
1109
1342
  if (!enabled) {
1110
- setDragState(INITIAL_DRAG_STATE);
1343
+ clearSelection();
1111
1344
  }
1112
- }, [enabled]);
1345
+ }, [clearSelection, enabled]);
1113
1346
  useEffect2(() => {
1114
1347
  if (!enabled) return;
1115
1348
  const handleKeyDown = (e) => {
@@ -1161,15 +1394,28 @@ function useCellSelection({
1161
1394
  async (options) => {
1162
1395
  if (!enabled || !activeSelectionBounds) return false;
1163
1396
  const mode = options?.includeDescendants && enableSubtreeCopy ? "subtree" : "visible";
1164
- return writeSelectionToClipboard(rows, activeSelectionBounds, mode);
1397
+ return writeSelectionToClipboard(rows, activeSelectionBounds, mode, {
1398
+ registry: cellRendererRegistry,
1399
+ root: rootRef?.current
1400
+ });
1165
1401
  },
1166
- [activeSelectionBounds, enableSubtreeCopy, enabled, rows]
1402
+ [
1403
+ activeSelectionBounds,
1404
+ cellRendererRegistry,
1405
+ enableSubtreeCopy,
1406
+ enabled,
1407
+ rootRef,
1408
+ rows
1409
+ ]
1167
1410
  );
1168
1411
  useEffect2(() => {
1169
1412
  if (!enabled) return;
1170
1413
  const handleKeyDown = (e) => {
1171
1414
  if (!activeSelectionBounds) return;
1172
1415
  if (!(e.ctrlKey || e.metaKey)) return;
1416
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1417
+ return;
1418
+ }
1173
1419
  const isSubtreeShortcut = enableSubtreeCopy && e.shiftKey && e.key.toLowerCase() === "c";
1174
1420
  const isVisibleCopyShortcut = !e.shiftKey && e.key.toLowerCase() === "c";
1175
1421
  if (!isSubtreeShortcut && !isVisibleCopyShortcut) return;
@@ -1310,6 +1556,7 @@ function useCellSelection({
1310
1556
  handleCellMouseDown,
1311
1557
  handleCellMouseEnter,
1312
1558
  handleFillHandleMouseDown,
1559
+ clearSelection,
1313
1560
  copySelection
1314
1561
  };
1315
1562
  }
@@ -2418,6 +2665,10 @@ function useGlideTable(options) {
2418
2665
  const [hoveredRowIndex, setHoveredRowIndex] = useState4(null);
2419
2666
  const scrollRef = useRef5(null);
2420
2667
  const rootRef = useRef5(null);
2668
+ const cellRendererRegistry = useMemo3(
2669
+ () => createCellRendererRegistry(cellRenderers),
2670
+ [cellRenderers]
2671
+ );
2421
2672
  const shouldVirtualize = enableVirtualization && !enableRowSpan;
2422
2673
  useEffect5(() => {
2423
2674
  if (enableVirtualization && enableRowSpan) {
@@ -2590,6 +2841,7 @@ function useGlideTable(options) {
2590
2841
  handleCellMouseDown,
2591
2842
  handleCellMouseEnter,
2592
2843
  handleFillHandleMouseDown,
2844
+ clearSelection: clearCellSelection,
2593
2845
  copySelection
2594
2846
  } = useCellSelection({
2595
2847
  data: tableData,
@@ -2601,8 +2853,46 @@ function useGlideTable(options) {
2601
2853
  onDataChange,
2602
2854
  onBatchChange,
2603
2855
  onRowsPaste,
2604
- onCellNavigate: handleCellNavigate
2856
+ onCellNavigate: handleCellNavigate,
2857
+ cellRendererRegistry,
2858
+ rootRef
2605
2859
  });
2860
+ const clearRowSelection = useCallback4(() => {
2861
+ if (rowSelectionMode === "none") return;
2862
+ const hasSelection = Object.values(rowSelection).some(Boolean);
2863
+ if (!hasSelection) return;
2864
+ if (onRowSelectionChange) {
2865
+ onRowSelectionChange(() => ({}));
2866
+ return;
2867
+ }
2868
+ setInternalRowSelection({});
2869
+ }, [onRowSelectionChange, rowSelection, rowSelectionMode]);
2870
+ useEffect5(() => {
2871
+ const clearAllSelections = () => {
2872
+ clearCellSelection();
2873
+ clearRowSelection();
2874
+ };
2875
+ const handleKeyDown = (event) => {
2876
+ if (event.key !== "Escape") return;
2877
+ if (event.defaultPrevented) return;
2878
+ if (isEditablePasteTarget(event.target) || isEditablePasteTarget(document.activeElement)) {
2879
+ return;
2880
+ }
2881
+ clearAllSelections();
2882
+ };
2883
+ const handleMouseDown = (event) => {
2884
+ const root = rootRef.current;
2885
+ if (!root) return;
2886
+ if (event.target instanceof Node && root.contains(event.target)) return;
2887
+ clearAllSelections();
2888
+ };
2889
+ window.addEventListener("keydown", handleKeyDown);
2890
+ document.addEventListener("mousedown", handleMouseDown);
2891
+ return () => {
2892
+ window.removeEventListener("keydown", handleKeyDown);
2893
+ document.removeEventListener("mousedown", handleMouseDown);
2894
+ };
2895
+ }, [clearCellSelection, clearRowSelection]);
2606
2896
  const {
2607
2897
  editingCell,
2608
2898
  draftValue,
@@ -2611,10 +2901,6 @@ function useGlideTable(options) {
2611
2901
  commitEdit,
2612
2902
  cancelEdit
2613
2903
  } = useCellEdit({ data: tableData, rows, onDataChange, onCellChange });
2614
- const cellRendererRegistry = useMemo3(
2615
- () => createCellRendererRegistry(cellRenderers),
2616
- [cellRenderers]
2617
- );
2618
2904
  const commitRenderedCellValue = useCallback4(
2619
2905
  (rowId, columnId, value) => commitCellValue({
2620
2906
  data: tableData,
@@ -2996,7 +3282,9 @@ var DataTableContext = createContext(null);
2996
3282
  function useDataTableRowContext() {
2997
3283
  const context = use(DataTableContext);
2998
3284
  if (!context) {
2999
- throw new Error("useDataTableRowContext must be used within a DataTableContextProvider");
3285
+ throw new Error(
3286
+ "useDataTableRowContext must be used within a DataTableContextProvider"
3287
+ );
3000
3288
  }
3001
3289
  return context;
3002
3290
  }
@@ -3035,8 +3323,105 @@ function ResolvedTableCell({
3035
3323
  }
3036
3324
 
3037
3325
  // src/components/ui/table/features/column-resize/columnResize.ts
3326
+ function clamp(value, min, max) {
3327
+ return Math.min(Math.max(value, min), max);
3328
+ }
3329
+ function floorOf(column) {
3330
+ return column.minWidth ?? 0;
3331
+ }
3332
+ function ceilOf(column) {
3333
+ return column.maxWidth ?? Number.POSITIVE_INFINITY;
3334
+ }
3335
+ function preferOf(column) {
3336
+ const floor = floorOf(column);
3337
+ const ceil = ceilOf(column);
3338
+ const preferred = column.maxWidth ?? column.minWidth ?? 0;
3339
+ return clamp(preferred, floor, Number.isFinite(ceil) ? ceil : preferred);
3340
+ }
3341
+ function resolveColumnLayoutWidths(containerWidth, columns) {
3342
+ const widths = /* @__PURE__ */ new Map();
3343
+ const fixed = [];
3344
+ const bounded = [];
3345
+ let flexCount = 0;
3346
+ for (const column of columns) {
3347
+ if (column.width != null) {
3348
+ fixed.push(column);
3349
+ } else if (column.minWidth != null || column.maxWidth != null) {
3350
+ bounded.push(column);
3351
+ } else {
3352
+ flexCount += 1;
3353
+ }
3354
+ }
3355
+ let used = 0;
3356
+ for (const column of fixed) {
3357
+ let size = column.width;
3358
+ if (column.minWidth != null) size = Math.max(size, column.minWidth);
3359
+ if (column.maxWidth != null) size = Math.min(size, column.maxWidth);
3360
+ widths.set(column.id, size);
3361
+ used += size;
3362
+ }
3363
+ if (bounded.length === 0) {
3364
+ return widths;
3365
+ }
3366
+ const boundedSizes = /* @__PURE__ */ new Map();
3367
+ let preferredSum = 0;
3368
+ let floorSum = 0;
3369
+ for (const column of bounded) {
3370
+ const preferred = preferOf(column);
3371
+ boundedSizes.set(column.id, preferred);
3372
+ preferredSum += preferred;
3373
+ floorSum += floorOf(column);
3374
+ }
3375
+ if (containerWidth > 0) {
3376
+ const remaining = Math.max(0, containerWidth - used);
3377
+ if (remaining >= preferredSum) {
3378
+ } else if (remaining >= floorSum) {
3379
+ let deficit = preferredSum - remaining;
3380
+ const open = bounded.map((column) => ({
3381
+ id: column.id,
3382
+ current: boundedSizes.get(column.id),
3383
+ floor: floorOf(column)
3384
+ }));
3385
+ while (deficit >= 1) {
3386
+ const shrinkable = open.filter((entry) => entry.current > entry.floor);
3387
+ if (shrinkable.length === 0) break;
3388
+ const portion = Math.floor(deficit / shrinkable.length);
3389
+ const rem = deficit % shrinkable.length;
3390
+ let consumed = 0;
3391
+ for (let index = 0; index < shrinkable.length; index += 1) {
3392
+ const entry = shrinkable[index];
3393
+ const reduce = Math.min(
3394
+ entry.current - entry.floor,
3395
+ portion + (index < rem ? 1 : 0)
3396
+ );
3397
+ entry.current -= reduce;
3398
+ consumed += reduce;
3399
+ }
3400
+ if (consumed === 0) break;
3401
+ deficit -= consumed;
3402
+ }
3403
+ for (const entry of open) {
3404
+ boundedSizes.set(entry.id, entry.current);
3405
+ }
3406
+ } else {
3407
+ for (const column of bounded) {
3408
+ boundedSizes.set(column.id, floorOf(column));
3409
+ }
3410
+ }
3411
+ }
3412
+ for (const [id, size] of boundedSizes) {
3413
+ widths.set(id, Math.round(size));
3414
+ }
3415
+ return widths;
3416
+ }
3038
3417
  function getColumnSizeStyle(size, options) {
3039
- const { force = false, lockMax = false, minWidth, maxWidth } = options ?? {};
3418
+ const {
3419
+ force = false,
3420
+ lockMax = false,
3421
+ minWidth,
3422
+ maxWidth,
3423
+ layoutWidth
3424
+ } = options ?? {};
3040
3425
  if (lockMax) {
3041
3426
  return {
3042
3427
  width: size,
@@ -3044,14 +3429,27 @@ function getColumnSizeStyle(size, options) {
3044
3429
  maxWidth: size
3045
3430
  };
3046
3431
  }
3432
+ if (layoutWidth != null) {
3433
+ return {
3434
+ width: layoutWidth,
3435
+ minWidth: layoutWidth,
3436
+ maxWidth: layoutWidth
3437
+ };
3438
+ }
3439
+ const resolvedSize = size;
3047
3440
  const hasExplicitSize = force || size !== DATA_TABLE_COLUMN_SIZE;
3048
3441
  if (!hasExplicitSize && minWidth == null && maxWidth == null) {
3049
3442
  return void 0;
3050
3443
  }
3051
3444
  const style = {};
3052
3445
  if (hasExplicitSize) {
3053
- style.width = size;
3054
- style.minWidth = minWidth ?? size;
3446
+ const used = minWidth != null || maxWidth != null ? clamp(
3447
+ resolvedSize,
3448
+ minWidth ?? Number.NEGATIVE_INFINITY,
3449
+ maxWidth ?? Number.POSITIVE_INFINITY
3450
+ ) : resolvedSize;
3451
+ style.width = used;
3452
+ style.minWidth = minWidth ?? used;
3055
3453
  } else if (minWidth != null) {
3056
3454
  style.minWidth = minWidth;
3057
3455
  }
@@ -3334,6 +3732,7 @@ export {
3334
3732
  previousSearchIndex,
3335
3733
  resolveCellRenderer,
3336
3734
  resolveColumnFreezeSide,
3735
+ resolveColumnLayoutWidths,
3337
3736
  resolveDataTableLabels,
3338
3737
  resolveDropEdge,
3339
3738
  resolveHeaderFreezeOffset,