react-glide-table 2.2.0 → 2.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/core.js CHANGED
@@ -741,6 +741,219 @@ function hasCellSelectionEdges(style) {
741
741
  }
742
742
 
743
743
  // src/components/ui/table/features/cell-selection/copyData.ts
744
+ import { isValidElement } from "react";
745
+ function isReactNodeIterable(node) {
746
+ return typeof node === "object" && node !== null && !isValidElement(node) && Symbol.iterator in node;
747
+ }
748
+ function getElementTypeName(type) {
749
+ if (typeof type === "string") return type;
750
+ if (typeof type === "function") {
751
+ const fn = type;
752
+ return fn.displayName || fn.name || "";
753
+ }
754
+ if (typeof type === "object" && type !== null) {
755
+ const component = type;
756
+ return component.displayName || component.render?.displayName || component.render?.name || "";
757
+ }
758
+ return "";
759
+ }
760
+ function isButtonReactElement(node) {
761
+ const typeName = getElementTypeName(node.type);
762
+ if (typeName === "button" || /button/i.test(typeName)) return true;
763
+ const props = node.props;
764
+ if (props.role === "button") return true;
765
+ if (typeName === "input" && props.type === "button") return true;
766
+ return false;
767
+ }
768
+ function isImageReactElement(node) {
769
+ const typeName = getElementTypeName(node.type);
770
+ return typeName === "img" || typeName === "image" || /image/i.test(typeName);
771
+ }
772
+ var IMAGE_URL_PROP_KEYS = ["src", "url", "href", "imageUrl", "srcUrl"];
773
+ function isLikelyUrl(value) {
774
+ const trimmed = value.trim();
775
+ if (!trimmed) return false;
776
+ if (/^(https?:|blob:|data:|\/\/)/i.test(trimmed)) return true;
777
+ if (trimmed.startsWith("/") && trimmed.includes("/")) return true;
778
+ return false;
779
+ }
780
+ function pickUrlFromUnknown(value) {
781
+ if (typeof value === "string") {
782
+ return isLikelyUrl(value) ? value.trim() : "";
783
+ }
784
+ if (Array.isArray(value)) {
785
+ return value.map((item) => pickUrlFromUnknown(item)).filter((item) => item.length > 0).join(", ");
786
+ }
787
+ if (value && typeof value === "object") {
788
+ const record = value;
789
+ for (const key of IMAGE_URL_PROP_KEYS) {
790
+ const candidate = record[key];
791
+ if (typeof candidate === "string" && candidate.trim()) {
792
+ return candidate.trim();
793
+ }
794
+ }
795
+ }
796
+ return "";
797
+ }
798
+ function imageElementText(node) {
799
+ const props = node.props;
800
+ for (const key of IMAGE_URL_PROP_KEYS) {
801
+ const candidate = props[key];
802
+ if (typeof candidate === "string" && candidate.trim()) {
803
+ return candidate.trim();
804
+ }
805
+ }
806
+ return "";
807
+ }
808
+ function reactNodeContainsImage(node) {
809
+ if (isValidElement(node)) {
810
+ if (isImageReactElement(node)) return true;
811
+ return reactNodeContainsImage(node.props.children);
812
+ }
813
+ if (isReactNodeIterable(node)) {
814
+ for (const child of node) {
815
+ if (reactNodeContainsImage(child)) return true;
816
+ }
817
+ }
818
+ return false;
819
+ }
820
+ function readImgUrl(img) {
821
+ const attr = img.getAttribute("src")?.trim() ?? "";
822
+ if (attr) return attr;
823
+ if (img instanceof HTMLImageElement) {
824
+ const current = img.currentSrc?.trim() ?? "";
825
+ if (current && current !== img.baseURI) return current;
826
+ }
827
+ return "";
828
+ }
829
+ function readDomImageUrls(rowIndex, colIndex, root) {
830
+ const scope = root ?? (typeof document === "undefined" ? null : document);
831
+ if (!scope) return "";
832
+ const cells = scope.querySelectorAll(
833
+ `[data-row-index="${rowIndex}"][data-col-index="${colIndex}"]`
834
+ );
835
+ for (const cell of cells) {
836
+ const urls = Array.from(cell.querySelectorAll("img")).flatMap((img) => {
837
+ const url = readImgUrl(img);
838
+ return url ? [url] : [];
839
+ });
840
+ if (urls.length > 0) return urls.join(", ");
841
+ }
842
+ return "";
843
+ }
844
+ function reactNodeToText(node) {
845
+ if (node == null || typeof node === "boolean") return "";
846
+ if (typeof node === "string" || typeof node === "number" || typeof node === "bigint") {
847
+ return String(node);
848
+ }
849
+ if (isReactNodeIterable(node)) {
850
+ let text = "";
851
+ for (const child of node) {
852
+ text += reactNodeToText(child);
853
+ }
854
+ return text;
855
+ }
856
+ if (isValidElement(node)) {
857
+ if (isButtonReactElement(node)) return "";
858
+ const props = node.props;
859
+ const childText = reactNodeToText(props.children);
860
+ if (childText) return childText;
861
+ const fromImage = imageElementText(node);
862
+ if (fromImage) return fromImage;
863
+ if (isImageReactElement(node)) return "";
864
+ if (typeof props.alt === "string" && props.alt) return props.alt;
865
+ if (typeof props.title === "string" && props.title) return props.title;
866
+ return "";
867
+ }
868
+ return "";
869
+ }
870
+ function sanitizeClipboardCell(text) {
871
+ return text.replace(/\s+/g, " ").trim();
872
+ }
873
+ function createCopyRenderRow(rowData, index) {
874
+ return {
875
+ id: getOriginalRowId(rowData) || String(index),
876
+ index,
877
+ original: rowData,
878
+ getIsCellDragSelected: () => false
879
+ };
880
+ }
881
+ function buildVisibleRowLookup(visibleRows) {
882
+ const lookup = /* @__PURE__ */ new Map();
883
+ for (const row of visibleRows) {
884
+ lookup.set(row.original, row);
885
+ }
886
+ return lookup;
887
+ }
888
+ function resolveCopyColumnId(cell) {
889
+ if (cell.column.id) return cell.column.id;
890
+ const columnDef = cell.column.columnDef;
891
+ if (columnDef.id) return columnDef.id;
892
+ if (columnDef.accessorKey != null && columnDef.accessorKey !== "") {
893
+ return String(columnDef.accessorKey);
894
+ }
895
+ return "";
896
+ }
897
+ function isPrimitiveCopyValue(value) {
898
+ return value == null || typeof value !== "object";
899
+ }
900
+ function extractRenderedCopyText(node, value, cellPosition, root) {
901
+ const rendered = sanitizeClipboardCell(reactNodeToText(node));
902
+ if (reactNodeContainsImage(node)) {
903
+ const fromDom = cellPosition != null ? sanitizeClipboardCell(
904
+ readDomImageUrls(cellPosition.rowIndex, cellPosition.colIndex, root)
905
+ ) : "";
906
+ if (fromDom) return fromDom;
907
+ if (rendered && isLikelyUrl(rendered)) return rendered;
908
+ return sanitizeClipboardCell(pickUrlFromUnknown(value));
909
+ }
910
+ return rendered;
911
+ }
912
+ function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIndex, sourceCell, cellPosition, options) {
913
+ const meta = columnDef.meta;
914
+ const value = sourceCell ? sourceCell.getValue() : readRowColumnValue(rowData, columnDef);
915
+ const cellRender = meta?.cellRender;
916
+ if (typeof cellRender === "function") {
917
+ try {
918
+ const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
919
+ const node = cellRender({
920
+ value,
921
+ row,
922
+ index: row.index,
923
+ columnId,
924
+ cellProps: meta?.cellProps,
925
+ update: () => {
926
+ }
927
+ });
928
+ return extractRenderedCopyText(node, value, cellPosition, options?.root);
929
+ } catch {
930
+ return formatCellValue(value);
931
+ }
932
+ }
933
+ if (options?.registry && meta?.kind && isPrimitiveCopyValue(value)) {
934
+ try {
935
+ const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
936
+ const ctx = {
937
+ value,
938
+ row,
939
+ index: row.index,
940
+ columnId,
941
+ cellProps: meta.cellProps,
942
+ update: () => {
943
+ }
944
+ };
945
+ const renderer = resolveCellRenderer(options.registry, meta.kind, ctx);
946
+ if (renderer) {
947
+ const node = renderer.render(ctx);
948
+ const rendered = extractRenderedCopyText(node, value, cellPosition, options.root);
949
+ if (rendered) return rendered;
950
+ }
951
+ } catch {
952
+ return formatCellValue(value);
953
+ }
954
+ }
955
+ return formatCellValue(value);
956
+ }
744
957
  function formatPrimitive(value) {
745
958
  if (value === null || value === void 0) return "";
746
959
  if (typeof value === "string") return value;
@@ -846,37 +1059,47 @@ function collectCopyRowEntries(visibleRows, bounds, mode = "visible") {
846
1059
  function collectCopyRows(visibleRows, bounds, mode = "visible") {
847
1060
  return collectCopyRowEntries(visibleRows, bounds, mode).map((entry) => entry.row);
848
1061
  }
849
- function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths) {
1062
+ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths, options) {
850
1063
  if (copyRows.length === 0) return "";
851
1064
  const { startCol, endCol } = bounds;
852
1065
  const columnCells = visibleRows[0]?.getVisibleCells().slice(startCol, endCol + 1) ?? [];
853
1066
  if (columnCells.length === 0) return "";
854
1067
  const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
855
1068
  const minDepth = Math.min(...resolvedDepths);
1069
+ const visibleRowByOriginal = buildVisibleRowLookup(visibleRows);
856
1070
  return copyRows.map((rowData, index) => {
857
1071
  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(" ");
1072
+ const visibleRow = visibleRowByOriginal.get(rowData);
1073
+ const matchingCells = visibleRow?.getVisibleCells().slice(startCol, endCol + 1);
1074
+ const line = columnCells.map((templateCell, colOffset) => {
1075
+ const sourceCell = matchingCells?.[colOffset];
1076
+ const column = sourceCell?.column ?? templateCell.column;
1077
+ return formatCopyCellText(
1078
+ rowData,
1079
+ column.columnDef,
1080
+ resolveCopyColumnId(sourceCell ?? templateCell),
1081
+ visibleRow,
1082
+ visibleRow?.index ?? index,
1083
+ sourceCell,
1084
+ visibleRow != null ? { rowIndex: visibleRow.index, colIndex: startCol + colOffset } : void 0,
1085
+ options
1086
+ );
1087
+ }).join(" ");
866
1088
  return `${" ".repeat(relativeDepth)}${line}`;
867
1089
  }).join("\n");
868
1090
  }
869
- function serializeSelectionToTSV(visibleRows, bounds, mode = "visible") {
1091
+ function serializeSelectionToTSV(visibleRows, bounds, mode = "visible", options) {
870
1092
  const entries = collectCopyRowEntries(visibleRows, bounds, mode);
871
1093
  return serializeCopyRowsToTSV(
872
1094
  entries.map((entry) => entry.row),
873
1095
  visibleRows,
874
1096
  bounds,
875
- entries.map((entry) => entry.depth)
1097
+ entries.map((entry) => entry.depth),
1098
+ options
876
1099
  );
877
1100
  }
878
- async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible") {
879
- const text = serializeSelectionToTSV(visibleRows, bounds, mode);
1101
+ async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible", options) {
1102
+ const text = serializeSelectionToTSV(visibleRows, bounds, mode, options);
880
1103
  if (!text) return false;
881
1104
  try {
882
1105
  await navigator.clipboard.writeText(text);
@@ -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);
@@ -1161,15 +1386,28 @@ function useCellSelection({
1161
1386
  async (options) => {
1162
1387
  if (!enabled || !activeSelectionBounds) return false;
1163
1388
  const mode = options?.includeDescendants && enableSubtreeCopy ? "subtree" : "visible";
1164
- return writeSelectionToClipboard(rows, activeSelectionBounds, mode);
1389
+ return writeSelectionToClipboard(rows, activeSelectionBounds, mode, {
1390
+ registry: cellRendererRegistry,
1391
+ root: rootRef?.current
1392
+ });
1165
1393
  },
1166
- [activeSelectionBounds, enableSubtreeCopy, enabled, rows]
1394
+ [
1395
+ activeSelectionBounds,
1396
+ cellRendererRegistry,
1397
+ enableSubtreeCopy,
1398
+ enabled,
1399
+ rootRef,
1400
+ rows
1401
+ ]
1167
1402
  );
1168
1403
  useEffect2(() => {
1169
1404
  if (!enabled) return;
1170
1405
  const handleKeyDown = (e) => {
1171
1406
  if (!activeSelectionBounds) return;
1172
1407
  if (!(e.ctrlKey || e.metaKey)) return;
1408
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1409
+ return;
1410
+ }
1173
1411
  const isSubtreeShortcut = enableSubtreeCopy && e.shiftKey && e.key.toLowerCase() === "c";
1174
1412
  const isVisibleCopyShortcut = !e.shiftKey && e.key.toLowerCase() === "c";
1175
1413
  if (!isSubtreeShortcut && !isVisibleCopyShortcut) return;
@@ -2418,6 +2656,10 @@ function useGlideTable(options) {
2418
2656
  const [hoveredRowIndex, setHoveredRowIndex] = useState4(null);
2419
2657
  const scrollRef = useRef5(null);
2420
2658
  const rootRef = useRef5(null);
2659
+ const cellRendererRegistry = useMemo3(
2660
+ () => createCellRendererRegistry(cellRenderers),
2661
+ [cellRenderers]
2662
+ );
2421
2663
  const shouldVirtualize = enableVirtualization && !enableRowSpan;
2422
2664
  useEffect5(() => {
2423
2665
  if (enableVirtualization && enableRowSpan) {
@@ -2601,7 +2843,9 @@ function useGlideTable(options) {
2601
2843
  onDataChange,
2602
2844
  onBatchChange,
2603
2845
  onRowsPaste,
2604
- onCellNavigate: handleCellNavigate
2846
+ onCellNavigate: handleCellNavigate,
2847
+ cellRendererRegistry,
2848
+ rootRef
2605
2849
  });
2606
2850
  const {
2607
2851
  editingCell,
@@ -2611,10 +2855,6 @@ function useGlideTable(options) {
2611
2855
  commitEdit,
2612
2856
  cancelEdit
2613
2857
  } = useCellEdit({ data: tableData, rows, onDataChange, onCellChange });
2614
- const cellRendererRegistry = useMemo3(
2615
- () => createCellRendererRegistry(cellRenderers),
2616
- [cellRenderers]
2617
- );
2618
2858
  const commitRenderedCellValue = useCallback4(
2619
2859
  (rowId, columnId, value) => commitCellValue({
2620
2860
  data: tableData,
@@ -2996,7 +3236,9 @@ var DataTableContext = createContext(null);
2996
3236
  function useDataTableRowContext() {
2997
3237
  const context = use(DataTableContext);
2998
3238
  if (!context) {
2999
- throw new Error("useDataTableRowContext must be used within a DataTableContextProvider");
3239
+ throw new Error(
3240
+ "useDataTableRowContext must be used within a DataTableContextProvider"
3241
+ );
3000
3242
  }
3001
3243
  return context;
3002
3244
  }
@@ -3035,16 +3277,140 @@ function ResolvedTableCell({
3035
3277
  }
3036
3278
 
3037
3279
  // src/components/ui/table/features/column-resize/columnResize.ts
3280
+ function clamp(value, min, max) {
3281
+ return Math.min(Math.max(value, min), max);
3282
+ }
3283
+ function floorOf(column) {
3284
+ return column.minWidth ?? 0;
3285
+ }
3286
+ function ceilOf(column) {
3287
+ return column.maxWidth ?? Number.POSITIVE_INFINITY;
3288
+ }
3289
+ function preferOf(column) {
3290
+ const floor = floorOf(column);
3291
+ const ceil = ceilOf(column);
3292
+ const preferred = column.maxWidth ?? column.minWidth ?? 0;
3293
+ return clamp(preferred, floor, Number.isFinite(ceil) ? ceil : preferred);
3294
+ }
3295
+ function resolveColumnLayoutWidths(containerWidth, columns) {
3296
+ const widths = /* @__PURE__ */ new Map();
3297
+ const fixed = [];
3298
+ const bounded = [];
3299
+ let flexCount = 0;
3300
+ for (const column of columns) {
3301
+ if (column.width != null) {
3302
+ fixed.push(column);
3303
+ } else if (column.minWidth != null || column.maxWidth != null) {
3304
+ bounded.push(column);
3305
+ } else {
3306
+ flexCount += 1;
3307
+ }
3308
+ }
3309
+ let used = 0;
3310
+ for (const column of fixed) {
3311
+ let size = column.width;
3312
+ if (column.minWidth != null) size = Math.max(size, column.minWidth);
3313
+ if (column.maxWidth != null) size = Math.min(size, column.maxWidth);
3314
+ widths.set(column.id, size);
3315
+ used += size;
3316
+ }
3317
+ if (bounded.length === 0) {
3318
+ return widths;
3319
+ }
3320
+ const boundedSizes = /* @__PURE__ */ new Map();
3321
+ let preferredSum = 0;
3322
+ let floorSum = 0;
3323
+ for (const column of bounded) {
3324
+ const preferred = preferOf(column);
3325
+ boundedSizes.set(column.id, preferred);
3326
+ preferredSum += preferred;
3327
+ floorSum += floorOf(column);
3328
+ }
3329
+ if (containerWidth > 0) {
3330
+ const remaining = Math.max(0, containerWidth - used);
3331
+ if (remaining >= preferredSum) {
3332
+ } else if (remaining >= floorSum) {
3333
+ let deficit = preferredSum - remaining;
3334
+ const open = bounded.map((column) => ({
3335
+ id: column.id,
3336
+ current: boundedSizes.get(column.id),
3337
+ floor: floorOf(column)
3338
+ }));
3339
+ while (deficit >= 1) {
3340
+ const shrinkable = open.filter((entry) => entry.current > entry.floor);
3341
+ if (shrinkable.length === 0) break;
3342
+ const portion = Math.floor(deficit / shrinkable.length);
3343
+ const rem = deficit % shrinkable.length;
3344
+ let consumed = 0;
3345
+ for (let index = 0; index < shrinkable.length; index += 1) {
3346
+ const entry = shrinkable[index];
3347
+ const reduce = Math.min(
3348
+ entry.current - entry.floor,
3349
+ portion + (index < rem ? 1 : 0)
3350
+ );
3351
+ entry.current -= reduce;
3352
+ consumed += reduce;
3353
+ }
3354
+ if (consumed === 0) break;
3355
+ deficit -= consumed;
3356
+ }
3357
+ for (const entry of open) {
3358
+ boundedSizes.set(entry.id, entry.current);
3359
+ }
3360
+ } else {
3361
+ for (const column of bounded) {
3362
+ boundedSizes.set(column.id, floorOf(column));
3363
+ }
3364
+ }
3365
+ }
3366
+ for (const [id, size] of boundedSizes) {
3367
+ widths.set(id, Math.round(size));
3368
+ }
3369
+ return widths;
3370
+ }
3038
3371
  function getColumnSizeStyle(size, options) {
3039
- const { force = false, lockMax = false } = options ?? {};
3040
- if (!force && size === DATA_TABLE_COLUMN_SIZE) {
3372
+ const {
3373
+ force = false,
3374
+ lockMax = false,
3375
+ minWidth,
3376
+ maxWidth,
3377
+ layoutWidth
3378
+ } = options ?? {};
3379
+ if (lockMax) {
3380
+ return {
3381
+ width: size,
3382
+ minWidth: size,
3383
+ maxWidth: size
3384
+ };
3385
+ }
3386
+ if (layoutWidth != null) {
3387
+ return {
3388
+ width: layoutWidth,
3389
+ minWidth: layoutWidth,
3390
+ maxWidth: layoutWidth
3391
+ };
3392
+ }
3393
+ const resolvedSize = size;
3394
+ const hasExplicitSize = force || size !== DATA_TABLE_COLUMN_SIZE;
3395
+ if (!hasExplicitSize && minWidth == null && maxWidth == null) {
3041
3396
  return void 0;
3042
3397
  }
3043
- return {
3044
- width: size,
3045
- minWidth: size,
3046
- ...lockMax ? { maxWidth: size } : {}
3047
- };
3398
+ const style = {};
3399
+ if (hasExplicitSize) {
3400
+ const used = minWidth != null || maxWidth != null ? clamp(
3401
+ resolvedSize,
3402
+ minWidth ?? Number.NEGATIVE_INFINITY,
3403
+ maxWidth ?? Number.POSITIVE_INFINITY
3404
+ ) : resolvedSize;
3405
+ style.width = used;
3406
+ style.minWidth = minWidth ?? used;
3407
+ } else if (minWidth != null) {
3408
+ style.minWidth = minWidth;
3409
+ }
3410
+ if (maxWidth != null) {
3411
+ style.maxWidth = maxWidth;
3412
+ }
3413
+ return style;
3048
3414
  }
3049
3415
 
3050
3416
  // src/components/ui/table/features/column-reorder/useColumnReorder.ts
@@ -3320,6 +3686,7 @@ export {
3320
3686
  previousSearchIndex,
3321
3687
  resolveCellRenderer,
3322
3688
  resolveColumnFreezeSide,
3689
+ resolveColumnLayoutWidths,
3323
3690
  resolveDataTableLabels,
3324
3691
  resolveDropEdge,
3325
3692
  resolveHeaderFreezeOffset,