react-glide-table 2.0.2 → 2.1.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
@@ -6,6 +6,7 @@ var DEFAULT_DATA_TABLE_LABELS = {
6
6
  expandRow: "Expand row",
7
7
  collapseRow: "Collapse row",
8
8
  resizeColumn: "Resize column",
9
+ reorderColumn: "Reorder column",
9
10
  searchPlaceholder: "Search\u2026",
10
11
  searchResultHint: "Type to search",
11
12
  searchPrevious: "Previous result",
@@ -47,6 +48,7 @@ var DATA_TABLE_VIRTUAL_OVERSCAN = 8;
47
48
  var DATA_TABLE_COLUMN_SIZE = 150;
48
49
  var DATA_TABLE_COLUMN_MIN_SIZE = 40;
49
50
  var DATA_TABLE_COLUMN_MAX_SIZE = 800;
51
+ var DATA_TABLE_COLUMN_REORDER_THRESHOLD = 4;
50
52
 
51
53
  // src/components/ui/table/features/cell-edit/useCellEdit.ts
52
54
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -1312,6 +1314,129 @@ function useCellSelection({
1312
1314
  };
1313
1315
  }
1314
1316
 
1317
+ // src/components/ui/table/features/column-reorder/columnReorder.ts
1318
+ function getColumnDefId(column) {
1319
+ if (column.id != null && column.id !== "") return column.id;
1320
+ if ("accessorKey" in column && column.accessorKey != null) {
1321
+ return String(column.accessorKey);
1322
+ }
1323
+ return void 0;
1324
+ }
1325
+ function getColumnDefChildren(column) {
1326
+ if (!("columns" in column) || !Array.isArray(column.columns)) return void 0;
1327
+ if (column.columns.length === 0) return void 0;
1328
+ return column.columns;
1329
+ }
1330
+ function collectLeafColumnIds(columns) {
1331
+ const ids = [];
1332
+ for (const column of columns) {
1333
+ const children = getColumnDefChildren(column);
1334
+ if (children) {
1335
+ ids.push(...collectLeafColumnIds(children));
1336
+ continue;
1337
+ }
1338
+ const id = getColumnDefId(column);
1339
+ if (id) ids.push(id);
1340
+ }
1341
+ return ids;
1342
+ }
1343
+ function areColumnOrdersEqual(left, right) {
1344
+ if (left.length !== right.length) return false;
1345
+ return left.every((id, index) => id === right[index]);
1346
+ }
1347
+ function resolveLeafColumnOrder(columns, order) {
1348
+ const leafIds = collectLeafColumnIds(columns);
1349
+ if (!order?.length) return leafIds;
1350
+ const leafSet = new Set(leafIds);
1351
+ const seen = /* @__PURE__ */ new Set();
1352
+ const next = order.filter((id) => {
1353
+ if (!leafSet.has(id) || seen.has(id)) return false;
1354
+ seen.add(id);
1355
+ return true;
1356
+ });
1357
+ for (const id of leafIds) {
1358
+ if (!seen.has(id)) next.push(id);
1359
+ }
1360
+ return next;
1361
+ }
1362
+ function flattenColumnSlots(columns, group) {
1363
+ const slots = [];
1364
+ for (const column of columns) {
1365
+ const id = getColumnDefId(column);
1366
+ const children = getColumnDefChildren(column);
1367
+ if (children) {
1368
+ const nestedGroup = id ? { id, def: column } : group;
1369
+ slots.push(...flattenColumnSlots(children, nestedGroup));
1370
+ continue;
1371
+ }
1372
+ if (!id) continue;
1373
+ slots.push({ id, def: column, group });
1374
+ }
1375
+ return slots;
1376
+ }
1377
+ function rebuildColumnTree(slots) {
1378
+ const result = [];
1379
+ let index = 0;
1380
+ while (index < slots.length) {
1381
+ const slot = slots[index];
1382
+ if (!slot.group) {
1383
+ result.push(slot.def);
1384
+ index += 1;
1385
+ continue;
1386
+ }
1387
+ const groupId = slot.group.id;
1388
+ const children = [];
1389
+ while (index < slots.length && slots[index]?.group?.id === groupId) {
1390
+ children.push(slots[index].def);
1391
+ index += 1;
1392
+ }
1393
+ const firstChildId = children[0] ? getColumnDefId(children[0]) : groupId;
1394
+ result.push({
1395
+ ...slot.group.def,
1396
+ id: `${groupId}::${firstChildId}`,
1397
+ columns: children
1398
+ });
1399
+ }
1400
+ return result;
1401
+ }
1402
+ function applyLeafColumnOrder(columns, order) {
1403
+ const resolved = resolveLeafColumnOrder(columns, order);
1404
+ const defaultOrder = collectLeafColumnIds(columns);
1405
+ if (areColumnOrdersEqual(resolved, defaultOrder)) {
1406
+ return columns;
1407
+ }
1408
+ const byId = new Map(
1409
+ flattenColumnSlots(columns).map((slot) => [
1410
+ slot.id,
1411
+ slot
1412
+ ])
1413
+ );
1414
+ const ordered = [];
1415
+ for (const id of resolved) {
1416
+ const slot = byId.get(id);
1417
+ if (slot) ordered.push(slot);
1418
+ }
1419
+ return rebuildColumnTree(ordered);
1420
+ }
1421
+ function moveColumnIds(order, fromIds, targetIds, edge) {
1422
+ if (fromIds.length === 0 || targetIds.length === 0) return [...order];
1423
+ const fromSet = new Set(fromIds);
1424
+ if (targetIds.some((id) => fromSet.has(id))) return [...order];
1425
+ const rest = order.filter((id) => !fromSet.has(id));
1426
+ const anchorId = edge === "before" ? targetIds[0] : targetIds[targetIds.length - 1];
1427
+ const anchorIndex = rest.indexOf(anchorId);
1428
+ if (anchorIndex < 0) return [...order];
1429
+ const insertAt = edge === "before" ? anchorIndex : anchorIndex + 1;
1430
+ return [...rest.slice(0, insertAt), ...fromIds, ...rest.slice(insertAt)];
1431
+ }
1432
+ function resolveDropEdge(clientX, rect) {
1433
+ return clientX < rect.left + rect.width / 2 ? "before" : "after";
1434
+ }
1435
+ function parseReorderIds(value) {
1436
+ if (!value) return [];
1437
+ return value.split(",").filter(Boolean);
1438
+ }
1439
+
1315
1440
  // src/components/ui/table/features/column-freeze/columnFreeze.ts
1316
1441
  var HEADER_Z_BASE = 30;
1317
1442
  var BODY_Z_BASE = 5;
@@ -2194,6 +2319,9 @@ function useGlideTable(options) {
2194
2319
  columnSizing: controlledColumnSizing,
2195
2320
  onColumnSizingChange,
2196
2321
  columnResizeMode = "onChange",
2322
+ enableColumnReorder = false,
2323
+ columnOrder: controlledColumnOrder,
2324
+ onColumnOrderChange,
2197
2325
  enableColumnFreeze = false,
2198
2326
  enableInlineSearch = false,
2199
2327
  showSearch,
@@ -2216,6 +2344,7 @@ function useGlideTable(options) {
2216
2344
  const resolvedEnableSubtreeCopy = enableSubtreeCopy ?? enableExpand;
2217
2345
  const [internalRowSelection, setInternalRowSelection] = useState4({});
2218
2346
  const [internalColumnSizing, setInternalColumnSizing] = useState4({});
2347
+ const [internalColumnOrder, setInternalColumnOrder] = useState4([]);
2219
2348
  const [internalExpandedRows, setInternalExpandedRows] = useState4(
2220
2349
  () => /* @__PURE__ */ new Set()
2221
2350
  );
@@ -2236,6 +2365,21 @@ function useGlideTable(options) {
2236
2365
  internalRowSelection
2237
2366
  );
2238
2367
  const columnSizing = controlledColumnSizing ?? internalColumnSizing;
2368
+ const columnOrder = controlledColumnOrder ?? internalColumnOrder;
2369
+ const tableColumns = useMemo3(() => {
2370
+ if (!enableColumnReorder) return columns;
2371
+ return applyLeafColumnOrder(columns, columnOrder);
2372
+ }, [columnOrder, columns, enableColumnReorder]);
2373
+ const setColumnOrder = useCallback4(
2374
+ (next) => {
2375
+ if (onColumnOrderChange) {
2376
+ onColumnOrderChange(next);
2377
+ return;
2378
+ }
2379
+ setInternalColumnOrder(next);
2380
+ },
2381
+ [onColumnOrderChange]
2382
+ );
2239
2383
  const expandedRows = controlledExpandedRows ?? internalExpandedRows;
2240
2384
  const handleExpandedRowsChange = useCallback4(
2241
2385
  (next) => {
@@ -2260,7 +2404,7 @@ function useGlideTable(options) {
2260
2404
  });
2261
2405
  const table = useReactTable({
2262
2406
  data: tableData,
2263
- columns,
2407
+ columns: tableColumns,
2264
2408
  ...enableColumnResize ? {
2265
2409
  defaultColumn: {
2266
2410
  minSize: DATA_TABLE_COLUMN_MIN_SIZE,
@@ -2742,6 +2886,7 @@ function useGlideTable(options) {
2742
2886
  selectionLabel: labels.selection,
2743
2887
  enableCellSelection,
2744
2888
  enableColumnResize,
2889
+ enableColumnReorder,
2745
2890
  enableColumnFreeze,
2746
2891
  enableInlineSearch,
2747
2892
  shouldVirtualize,
@@ -2755,6 +2900,7 @@ function useGlideTable(options) {
2755
2900
  getCellContext,
2756
2901
  handleToggleSelect,
2757
2902
  clearHover,
2903
+ setColumnOrder,
2758
2904
  copySelection: stableCopySelection,
2759
2905
  inlineSearch: {
2760
2906
  showSearch: inlineSearch.showSearch,
@@ -2833,6 +2979,219 @@ function getColumnSizeStyle(size, options) {
2833
2979
  ...lockMax ? { maxWidth: size } : {}
2834
2980
  };
2835
2981
  }
2982
+
2983
+ // src/components/ui/table/features/column-reorder/useColumnReorder.ts
2984
+ import {
2985
+ useCallback as useCallback6,
2986
+ useEffect as useEffect6,
2987
+ useRef as useRef6,
2988
+ useState as useState5
2989
+ } from "react";
2990
+ function hitTestReorderHeader(table, clientX, clientY) {
2991
+ const headers = Array.from(
2992
+ table.querySelectorAll(
2993
+ "thead th[data-column-id][data-reorder-ids]"
2994
+ )
2995
+ );
2996
+ const containing = headers.find((element) => {
2997
+ const rect = element.getBoundingClientRect();
2998
+ return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
2999
+ });
3000
+ if (containing) {
3001
+ return {
3002
+ columnId: containing.dataset.columnId ?? "",
3003
+ edge: resolveDropEdge(clientX, containing.getBoundingClientRect())
3004
+ };
3005
+ }
3006
+ const leaves = headers.filter(
3007
+ (element) => element.hasAttribute("data-reorder-leaf")
3008
+ );
3009
+ let match;
3010
+ for (const element of leaves) {
3011
+ const rect = element.getBoundingClientRect();
3012
+ if (clientX >= rect.left && clientX <= rect.right) {
3013
+ match = element;
3014
+ break;
3015
+ }
3016
+ }
3017
+ if (!match && leaves.length > 0) {
3018
+ const first = leaves[0].getBoundingClientRect();
3019
+ const last = leaves[leaves.length - 1].getBoundingClientRect();
3020
+ if (clientX < first.left) match = leaves[0];
3021
+ else if (clientX > last.right) match = leaves[leaves.length - 1];
3022
+ }
3023
+ if (!match) return null;
3024
+ return {
3025
+ columnId: match.dataset.columnId ?? "",
3026
+ edge: resolveDropEdge(clientX, match.getBoundingClientRect())
3027
+ };
3028
+ }
3029
+ function readTargetIds(table, columnId) {
3030
+ const element = table.querySelector(
3031
+ `thead th[data-column-id="${CSS.escape(columnId)}"]`
3032
+ );
3033
+ return parseReorderIds(element?.getAttribute("data-reorder-ids"));
3034
+ }
3035
+ function useColumnReorder(options) {
3036
+ const { enabled, columnOrder, onColumnOrderChange } = options;
3037
+ const sessionRef = useRef6(null);
3038
+ const columnOrderRef = useRef6(columnOrder);
3039
+ const onColumnOrderChangeRef = useRef6(onColumnOrderChange);
3040
+ const [draggingColumnId, setDraggingColumnId] = useState5(null);
3041
+ const [dropTarget, setDropTarget] = useState5(
3042
+ null
3043
+ );
3044
+ const dropTargetRef = useRef6(dropTarget);
3045
+ const previousUserSelectRef = useRef6(null);
3046
+ columnOrderRef.current = columnOrder;
3047
+ onColumnOrderChangeRef.current = onColumnOrderChange;
3048
+ dropTargetRef.current = dropTarget;
3049
+ const resetDrag = useCallback6(() => {
3050
+ sessionRef.current = null;
3051
+ setDraggingColumnId(null);
3052
+ setDropTarget(null);
3053
+ const backup = previousUserSelectRef.current;
3054
+ previousUserSelectRef.current = null;
3055
+ if (backup) {
3056
+ if (backup.value) {
3057
+ document.body.style.setProperty("user-select", backup.value, backup.priority);
3058
+ } else {
3059
+ document.body.style.removeProperty("user-select");
3060
+ }
3061
+ return;
3062
+ }
3063
+ document.body.style.removeProperty("user-select");
3064
+ }, []);
3065
+ useEffect6(() => {
3066
+ if (!enabled) resetDrag();
3067
+ }, [enabled, resetDrag]);
3068
+ useEffect6(() => {
3069
+ return () => {
3070
+ resetDrag();
3071
+ };
3072
+ }, [resetDrag]);
3073
+ const onHeaderPointerDown = useCallback6(
3074
+ (event, meta) => {
3075
+ if (!enabled || !meta.canDrag) return;
3076
+ if (event.button !== 0) return;
3077
+ if (event.pointerType === "mouse" && event.ctrlKey) return;
3078
+ const target = event.target;
3079
+ if (target instanceof Element && target.closest("[data-table-disable-cell-selection]")) {
3080
+ return;
3081
+ }
3082
+ const table = event.currentTarget.closest("table");
3083
+ if (!(table instanceof HTMLTableElement)) return;
3084
+ sessionRef.current = {
3085
+ pointerId: event.pointerId,
3086
+ startX: event.clientX,
3087
+ startY: event.clientY,
3088
+ columnId: meta.columnId,
3089
+ fromIds: meta.leafIds,
3090
+ table,
3091
+ active: false
3092
+ };
3093
+ },
3094
+ [enabled]
3095
+ );
3096
+ useEffect6(() => {
3097
+ if (!enabled) return;
3098
+ const onPointerMove = (event) => {
3099
+ const session = sessionRef.current;
3100
+ if (!session || event.pointerId !== session.pointerId) return;
3101
+ const deltaX = event.clientX - session.startX;
3102
+ const deltaY = event.clientY - session.startY;
3103
+ const distance = Math.hypot(deltaX, deltaY);
3104
+ if (!session.active) {
3105
+ if (distance < DATA_TABLE_COLUMN_REORDER_THRESHOLD) return;
3106
+ session.active = true;
3107
+ if (!previousUserSelectRef.current) {
3108
+ previousUserSelectRef.current = {
3109
+ value: document.body.style.getPropertyValue("user-select"),
3110
+ priority: document.body.style.getPropertyPriority("user-select")
3111
+ };
3112
+ }
3113
+ document.body.style.setProperty("user-select", "none");
3114
+ setDraggingColumnId(session.columnId);
3115
+ }
3116
+ event.preventDefault();
3117
+ const nextTarget = hitTestReorderHeader(
3118
+ session.table,
3119
+ event.clientX,
3120
+ event.clientY
3121
+ );
3122
+ if (!nextTarget || !nextTarget.columnId) {
3123
+ setDropTarget(null);
3124
+ return;
3125
+ }
3126
+ const targetIds = readTargetIds(session.table, nextTarget.columnId);
3127
+ const fromSet = new Set(session.fromIds);
3128
+ if (targetIds.some((id) => fromSet.has(id))) {
3129
+ setDropTarget(null);
3130
+ return;
3131
+ }
3132
+ setDropTarget((previous) => {
3133
+ if (previous?.columnId === nextTarget.columnId && previous.edge === nextTarget.edge) {
3134
+ return previous;
3135
+ }
3136
+ return nextTarget;
3137
+ });
3138
+ };
3139
+ const onPointerUp = (event) => {
3140
+ const session = sessionRef.current;
3141
+ if (!session || event.pointerId !== session.pointerId) {
3142
+ return;
3143
+ }
3144
+ if (session.active) {
3145
+ event.preventDefault();
3146
+ const target = dropTargetRef.current;
3147
+ if (target) {
3148
+ const targetIds = readTargetIds(session.table, target.columnId);
3149
+ const next = moveColumnIds(
3150
+ columnOrderRef.current,
3151
+ session.fromIds,
3152
+ targetIds,
3153
+ target.edge
3154
+ );
3155
+ onColumnOrderChangeRef.current(next);
3156
+ }
3157
+ const suppressClick = (clickEvent) => {
3158
+ clickEvent.preventDefault();
3159
+ clickEvent.stopPropagation();
3160
+ document.removeEventListener("click", suppressClick, true);
3161
+ };
3162
+ document.addEventListener("click", suppressClick, true);
3163
+ window.setTimeout(() => {
3164
+ document.removeEventListener("click", suppressClick, true);
3165
+ }, 0);
3166
+ }
3167
+ resetDrag();
3168
+ };
3169
+ const onPointerCancel = (event) => {
3170
+ const session = sessionRef.current;
3171
+ if (!session || event.pointerId !== session.pointerId) {
3172
+ return;
3173
+ }
3174
+ if (session.active) {
3175
+ event.preventDefault();
3176
+ }
3177
+ resetDrag();
3178
+ };
3179
+ document.addEventListener("pointermove", onPointerMove);
3180
+ document.addEventListener("pointerup", onPointerUp);
3181
+ document.addEventListener("pointercancel", onPointerCancel);
3182
+ return () => {
3183
+ document.removeEventListener("pointermove", onPointerMove);
3184
+ document.removeEventListener("pointerup", onPointerUp);
3185
+ document.removeEventListener("pointercancel", onPointerCancel);
3186
+ };
3187
+ }, [enabled, resetDrag]);
3188
+ return {
3189
+ isReordering: draggingColumnId != null,
3190
+ draggingColumnId,
3191
+ dropTarget,
3192
+ onHeaderPointerDown
3193
+ };
3194
+ }
2836
3195
  export {
2837
3196
  BUILTIN_CELL_RENDERERS,
2838
3197
  CELL_SELECTION_EDGES_CLASS,
@@ -2845,6 +3204,7 @@ export {
2845
3204
  ResolvedTableCell,
2846
3205
  applyCellEdit,
2847
3206
  applyFillData,
3207
+ applyLeafColumnOrder,
2848
3208
  applySelectionUpdater,
2849
3209
  buildColumnFreezeOffsets,
2850
3210
  buildColumnRowSpanMap,
@@ -2859,6 +3219,7 @@ export {
2859
3219
  collectCopyRowEntries,
2860
3220
  collectCopyRows,
2861
3221
  collectFillChanges,
3222
+ collectLeafColumnIds,
2862
3223
  collectRowSpanColumns,
2863
3224
  collectSearchMatchesInRange,
2864
3225
  commitCellValue,
@@ -2883,6 +3244,7 @@ export {
2883
3244
  mapSearchResultToVisibleItem,
2884
3245
  mapSearchResultsToVisibleKeys,
2885
3246
  measureMergedSpanRowHeights,
3247
+ moveColumnIds,
2886
3248
  nextSearchIndex,
2887
3249
  nextSearchStride,
2888
3250
  parseCellEditValue,
@@ -2892,7 +3254,9 @@ export {
2892
3254
  resolveCellRenderer,
2893
3255
  resolveColumnFreezeSide,
2894
3256
  resolveDataTableLabels,
3257
+ resolveDropEdge,
2895
3258
  resolveHeaderFreezeOffset,
3259
+ resolveLeafColumnOrder,
2896
3260
  resolvePasteColumnIds,
2897
3261
  resolveRowSelection,
2898
3262
  resolveRowSpanAt,
@@ -2902,6 +3266,7 @@ export {
2902
3266
  toggleExpandedRowId,
2903
3267
  useCellEdit,
2904
3268
  useCellSelection,
3269
+ useColumnReorder,
2905
3270
  useConvertTreeData,
2906
3271
  useGlideTable,
2907
3272
  useInlineSearch,