react-glide-table 2.0.2 → 2.2.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
@@ -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",
@@ -56,6 +57,7 @@ var DATA_TABLE_VIRTUAL_OVERSCAN = 8;
56
57
  var DATA_TABLE_COLUMN_SIZE = 150;
57
58
  var DATA_TABLE_COLUMN_MIN_SIZE = 40;
58
59
  var DATA_TABLE_COLUMN_MAX_SIZE = 800;
60
+ var DATA_TABLE_COLUMN_REORDER_THRESHOLD = 4;
59
61
 
60
62
  // src/components/ui/table/features/cell-edit/useCellEdit.ts
61
63
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -1321,6 +1323,135 @@ function useCellSelection({
1321
1323
  };
1322
1324
  }
1323
1325
 
1326
+ // src/components/ui/table/features/column-reorder/columnReorder.ts
1327
+ function getColumnDefId(column) {
1328
+ if (column.id != null && column.id !== "") return column.id;
1329
+ if ("accessorKey" in column && column.accessorKey != null) {
1330
+ return String(column.accessorKey);
1331
+ }
1332
+ return void 0;
1333
+ }
1334
+ function getColumnDefChildren(column) {
1335
+ if (!("columns" in column) || !Array.isArray(column.columns)) return void 0;
1336
+ if (column.columns.length === 0) return void 0;
1337
+ return column.columns;
1338
+ }
1339
+ function collectLeafColumnIds(columns) {
1340
+ const ids = [];
1341
+ for (const column of columns) {
1342
+ const children = getColumnDefChildren(column);
1343
+ if (children) {
1344
+ ids.push(...collectLeafColumnIds(children));
1345
+ continue;
1346
+ }
1347
+ const id = getColumnDefId(column);
1348
+ if (id) ids.push(id);
1349
+ }
1350
+ return ids;
1351
+ }
1352
+ function areColumnOrdersEqual(left, right) {
1353
+ if (left.length !== right.length) return false;
1354
+ return left.every((id, index) => id === right[index]);
1355
+ }
1356
+ function resolveLeafColumnOrder(columns, order) {
1357
+ const leafIds = collectLeafColumnIds(columns);
1358
+ if (!order?.length) return leafIds;
1359
+ const leafSet = new Set(leafIds);
1360
+ const seen = /* @__PURE__ */ new Set();
1361
+ const next = order.filter((id) => {
1362
+ if (!leafSet.has(id) || seen.has(id)) return false;
1363
+ seen.add(id);
1364
+ return true;
1365
+ });
1366
+ for (const id of leafIds) {
1367
+ if (!seen.has(id)) next.push(id);
1368
+ }
1369
+ return next;
1370
+ }
1371
+ function flattenColumnSlots(columns, group) {
1372
+ const slots = [];
1373
+ for (const column of columns) {
1374
+ const id = getColumnDefId(column);
1375
+ const children = getColumnDefChildren(column);
1376
+ if (children) {
1377
+ const nestedGroup = id ? { id, def: column } : group;
1378
+ slots.push(...flattenColumnSlots(children, nestedGroup));
1379
+ continue;
1380
+ }
1381
+ if (!id) continue;
1382
+ slots.push({ id, def: column, group });
1383
+ }
1384
+ return slots;
1385
+ }
1386
+ function rebuildColumnTree(slots) {
1387
+ const result = [];
1388
+ let index = 0;
1389
+ while (index < slots.length) {
1390
+ const slot = slots[index];
1391
+ if (!slot.group) {
1392
+ result.push(slot.def);
1393
+ index += 1;
1394
+ continue;
1395
+ }
1396
+ const groupId = slot.group.id;
1397
+ const children = [];
1398
+ while (index < slots.length && slots[index]?.group?.id === groupId) {
1399
+ children.push(slots[index].def);
1400
+ index += 1;
1401
+ }
1402
+ const firstChildId = children[0] ? getColumnDefId(children[0]) : groupId;
1403
+ result.push({
1404
+ ...slot.group.def,
1405
+ id: `${groupId}::${firstChildId}`,
1406
+ columns: children
1407
+ });
1408
+ }
1409
+ return result;
1410
+ }
1411
+ function applyLeafColumnOrder(columns, order) {
1412
+ const resolved = resolveLeafColumnOrder(columns, order);
1413
+ const defaultOrder = collectLeafColumnIds(columns);
1414
+ if (areColumnOrdersEqual(resolved, defaultOrder)) {
1415
+ return columns;
1416
+ }
1417
+ const byId = new Map(
1418
+ flattenColumnSlots(columns).map((slot) => [
1419
+ slot.id,
1420
+ slot
1421
+ ])
1422
+ );
1423
+ const ordered = [];
1424
+ for (const id of resolved) {
1425
+ const slot = byId.get(id);
1426
+ if (slot) ordered.push(slot);
1427
+ }
1428
+ return rebuildColumnTree(ordered);
1429
+ }
1430
+ function moveColumnIds(order, fromIds, targetIds, edge) {
1431
+ if (fromIds.length === 0 || targetIds.length === 0) return [...order];
1432
+ const fromSet = new Set(fromIds);
1433
+ if (targetIds.some((id) => fromSet.has(id))) return [...order];
1434
+ const rest = order.filter((id) => !fromSet.has(id));
1435
+ const anchorId = edge === "before" ? targetIds[0] : targetIds[targetIds.length - 1];
1436
+ const anchorIndex = rest.indexOf(anchorId);
1437
+ if (anchorIndex < 0) return [...order];
1438
+ const insertAt = edge === "before" ? anchorIndex : anchorIndex + 1;
1439
+ return [...rest.slice(0, insertAt), ...fromIds, ...rest.slice(insertAt)];
1440
+ }
1441
+ function resolveDropEdge(clientX, rect) {
1442
+ return clientX < rect.left + rect.width / 2 ? "before" : "after";
1443
+ }
1444
+ function parseReorderIds(value) {
1445
+ if (!value) return [];
1446
+ return value.split(",").filter(Boolean);
1447
+ }
1448
+ function serializeReorderIds(ids) {
1449
+ return ids.join(",");
1450
+ }
1451
+ function isColumnReorderable(meta) {
1452
+ return meta?.reorderable !== false;
1453
+ }
1454
+
1324
1455
  // src/components/ui/table/features/column-freeze/columnFreeze.ts
1325
1456
  var HEADER_Z_BASE = 30;
1326
1457
  var BODY_Z_BASE = 5;
@@ -2094,23 +2225,49 @@ function applySelectionUpdater(mode, updater, previous) {
2094
2225
  function getRowFieldValue(row, key) {
2095
2226
  return row[key];
2096
2227
  }
2097
- function computeRowSpans(data, rowSpanKey) {
2228
+ function normalizeRowSpanParent(value) {
2229
+ if (!value) return [];
2230
+ return typeof value === "string" ? [value] : [...value];
2231
+ }
2232
+ function toParentSpanList(parentSpans) {
2233
+ if (!parentSpans?.length) return [];
2234
+ const first = parentSpans[0];
2235
+ if (!Array.isArray(first)) {
2236
+ return [parentSpans];
2237
+ }
2238
+ return parentSpans;
2239
+ }
2240
+ function buildStartRowLookup(spans) {
2241
+ const startRows = new Array(spans.length);
2242
+ let origin = 0;
2243
+ for (let i = 0; i < spans.length; i++) {
2244
+ if ((spans[i]?.rowSpan ?? 1) > 0) origin = i;
2245
+ startRows[i] = origin;
2246
+ }
2247
+ return startRows;
2248
+ }
2249
+ function sharesParentGroup(parentStartRows, rowIndex) {
2250
+ if (parentStartRows.length === 0 || rowIndex <= 0) return true;
2251
+ return parentStartRows.every(
2252
+ (startRows) => startRows[rowIndex - 1] === startRows[rowIndex]
2253
+ );
2254
+ }
2255
+ function computeRowSpans(data, rowSpanKey, parentSpans) {
2098
2256
  if (data.length === 0) return [];
2257
+ const parentStartRows = toParentSpanList(parentSpans).map(buildStartRowLookup);
2099
2258
  const result = [];
2100
2259
  for (let index = 0; index < data.length; index++) {
2101
2260
  const currentValue = getRowFieldValue(data[index], rowSpanKey);
2102
2261
  const previousValue = index > 0 ? getRowFieldValue(data[index - 1], rowSpanKey) : void 0;
2103
- if (index > 0 && currentValue === previousValue) {
2262
+ if (index > 0 && currentValue === previousValue && sharesParentGroup(parentStartRows, index)) {
2104
2263
  result.push({ rowSpan: 0, isFirstInGroup: false });
2105
2264
  continue;
2106
2265
  }
2107
2266
  let span = 1;
2108
2267
  for (let nextIndex = index + 1; nextIndex < data.length; nextIndex++) {
2109
- if (getRowFieldValue(data[nextIndex], rowSpanKey) === currentValue) {
2110
- span++;
2111
- } else {
2112
- break;
2113
- }
2268
+ if (getRowFieldValue(data[nextIndex], rowSpanKey) !== currentValue) break;
2269
+ if (!sharesParentGroup(parentStartRows, nextIndex)) break;
2270
+ span++;
2114
2271
  }
2115
2272
  result.push({ rowSpan: span, isFirstInGroup: true });
2116
2273
  }
@@ -2132,10 +2289,50 @@ function resolveRowSpanAt(rowSpans, rowIndex) {
2132
2289
  }
2133
2290
  return { startRow: rowIndex, rowSpan: 1 };
2134
2291
  }
2292
+ function findRowSpanColumn(spec, ref) {
2293
+ return spec.find((column) => column.columnId === ref) ?? spec.find((column) => column.rowSpanKey === ref);
2294
+ }
2135
2295
  function buildColumnRowSpanMap(data, columnKeys) {
2136
2296
  const map = /* @__PURE__ */ new Map();
2137
- for (const { columnId, rowSpanKey } of columnKeys) {
2138
- map.set(columnId, computeRowSpans(data, rowSpanKey));
2297
+ const visiting = /* @__PURE__ */ new Set();
2298
+ const warnedCycles = /* @__PURE__ */ new Set();
2299
+ const virtualParents = /* @__PURE__ */ new Map();
2300
+ const spansForColumn = (columnId) => {
2301
+ const cached = map.get(columnId);
2302
+ if (cached !== void 0) return cached;
2303
+ const column = columnKeys.find((item) => item.columnId === columnId);
2304
+ if (!column) return void 0;
2305
+ if (visiting.has(columnId)) {
2306
+ if (!warnedCycles.has(columnId)) {
2307
+ warnedCycles.add(columnId);
2308
+ console.warn(
2309
+ `[rowSpan] rowSpanParent cycle detected at column "${columnId}"; dropping the cyclic parent reference.`
2310
+ );
2311
+ }
2312
+ return void 0;
2313
+ }
2314
+ visiting.add(columnId);
2315
+ const parentSpans = (column.rowSpanParent ?? []).map((ref) => spansForParentRef(ref)).filter((spans2) => Boolean(spans2));
2316
+ visiting.delete(columnId);
2317
+ const spans = computeRowSpans(
2318
+ data,
2319
+ column.rowSpanKey,
2320
+ parentSpans.length > 0 ? parentSpans : void 0
2321
+ );
2322
+ map.set(columnId, spans);
2323
+ return spans;
2324
+ };
2325
+ const spansForParentRef = (ref) => {
2326
+ const parentColumn = findRowSpanColumn(columnKeys, ref);
2327
+ if (parentColumn) return spansForColumn(parentColumn.columnId);
2328
+ const cached = virtualParents.get(ref);
2329
+ if (cached !== void 0) return cached;
2330
+ const spans = computeRowSpans(data, ref);
2331
+ virtualParents.set(ref, spans);
2332
+ return spans;
2333
+ };
2334
+ for (const column of columnKeys) {
2335
+ spansForColumn(column.columnId);
2139
2336
  }
2140
2337
  return map;
2141
2338
  }
@@ -2151,7 +2348,8 @@ function collectRowSpanColumns(columns) {
2151
2348
  if (!columnId || !columnDef.meta?.rowSpan) continue;
2152
2349
  result.push({
2153
2350
  columnId,
2154
- rowSpanKey: columnDef.meta.rowSpanKey ?? columnId
2351
+ rowSpanKey: columnDef.meta.rowSpanKey ?? columnId,
2352
+ rowSpanParent: normalizeRowSpanParent(columnDef.meta.rowSpanParent)
2155
2353
  });
2156
2354
  }
2157
2355
  };
@@ -2203,6 +2401,9 @@ function useGlideTable(options) {
2203
2401
  columnSizing: controlledColumnSizing,
2204
2402
  onColumnSizingChange,
2205
2403
  columnResizeMode = "onChange",
2404
+ enableColumnReorder = false,
2405
+ columnOrder: controlledColumnOrder,
2406
+ onColumnOrderChange,
2206
2407
  enableColumnFreeze = false,
2207
2408
  enableInlineSearch = false,
2208
2409
  showSearch,
@@ -2225,6 +2426,7 @@ function useGlideTable(options) {
2225
2426
  const resolvedEnableSubtreeCopy = enableSubtreeCopy ?? enableExpand;
2226
2427
  const [internalRowSelection, setInternalRowSelection] = useState4({});
2227
2428
  const [internalColumnSizing, setInternalColumnSizing] = useState4({});
2429
+ const [internalColumnOrder, setInternalColumnOrder] = useState4([]);
2228
2430
  const [internalExpandedRows, setInternalExpandedRows] = useState4(
2229
2431
  () => /* @__PURE__ */ new Set()
2230
2432
  );
@@ -2245,6 +2447,21 @@ function useGlideTable(options) {
2245
2447
  internalRowSelection
2246
2448
  );
2247
2449
  const columnSizing = controlledColumnSizing ?? internalColumnSizing;
2450
+ const columnOrder = controlledColumnOrder ?? internalColumnOrder;
2451
+ const tableColumns = useMemo3(() => {
2452
+ if (!enableColumnReorder) return columns;
2453
+ return applyLeafColumnOrder(columns, columnOrder);
2454
+ }, [columnOrder, columns, enableColumnReorder]);
2455
+ const setColumnOrder = useCallback4(
2456
+ (next) => {
2457
+ if (onColumnOrderChange) {
2458
+ onColumnOrderChange(next);
2459
+ return;
2460
+ }
2461
+ setInternalColumnOrder(next);
2462
+ },
2463
+ [onColumnOrderChange]
2464
+ );
2248
2465
  const expandedRows = controlledExpandedRows ?? internalExpandedRows;
2249
2466
  const handleExpandedRowsChange = useCallback4(
2250
2467
  (next) => {
@@ -2269,7 +2486,7 @@ function useGlideTable(options) {
2269
2486
  });
2270
2487
  const table = useReactTable({
2271
2488
  data: tableData,
2272
- columns,
2489
+ columns: tableColumns,
2273
2490
  ...enableColumnResize ? {
2274
2491
  defaultColumn: {
2275
2492
  minSize: DATA_TABLE_COLUMN_MIN_SIZE,
@@ -2751,6 +2968,7 @@ function useGlideTable(options) {
2751
2968
  selectionLabel: labels.selection,
2752
2969
  enableCellSelection,
2753
2970
  enableColumnResize,
2971
+ enableColumnReorder,
2754
2972
  enableColumnFreeze,
2755
2973
  enableInlineSearch,
2756
2974
  shouldVirtualize,
@@ -2764,6 +2982,7 @@ function useGlideTable(options) {
2764
2982
  getCellContext,
2765
2983
  handleToggleSelect,
2766
2984
  clearHover,
2985
+ setColumnOrder,
2767
2986
  copySelection: stableCopySelection,
2768
2987
  inlineSearch: {
2769
2988
  showSearch: inlineSearch.showSearch,
@@ -2849,13 +3068,226 @@ function getColumnSizeStyle(size, options) {
2849
3068
  };
2850
3069
  }
2851
3070
 
3071
+ // src/components/ui/table/features/column-reorder/useColumnReorder.ts
3072
+ import {
3073
+ useCallback as useCallback6,
3074
+ useEffect as useEffect6,
3075
+ useRef as useRef6,
3076
+ useState as useState5
3077
+ } from "react";
3078
+ function hitTestReorderHeader(table, clientX, clientY) {
3079
+ const headers = Array.from(
3080
+ table.querySelectorAll(
3081
+ "thead th[data-column-id][data-reorder-ids]"
3082
+ )
3083
+ );
3084
+ const containing = headers.find((element) => {
3085
+ const rect = element.getBoundingClientRect();
3086
+ return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
3087
+ });
3088
+ if (containing) {
3089
+ return {
3090
+ columnId: containing.dataset.columnId ?? "",
3091
+ edge: resolveDropEdge(clientX, containing.getBoundingClientRect())
3092
+ };
3093
+ }
3094
+ const leaves = headers.filter(
3095
+ (element) => element.hasAttribute("data-reorder-leaf")
3096
+ );
3097
+ let match;
3098
+ for (const element of leaves) {
3099
+ const rect = element.getBoundingClientRect();
3100
+ if (clientX >= rect.left && clientX <= rect.right) {
3101
+ match = element;
3102
+ break;
3103
+ }
3104
+ }
3105
+ if (!match && leaves.length > 0) {
3106
+ const first = leaves[0].getBoundingClientRect();
3107
+ const last = leaves[leaves.length - 1].getBoundingClientRect();
3108
+ if (clientX < first.left) match = leaves[0];
3109
+ else if (clientX > last.right) match = leaves[leaves.length - 1];
3110
+ }
3111
+ if (!match) return null;
3112
+ return {
3113
+ columnId: match.dataset.columnId ?? "",
3114
+ edge: resolveDropEdge(clientX, match.getBoundingClientRect())
3115
+ };
3116
+ }
3117
+ function readTargetIds(table, columnId) {
3118
+ const element = table.querySelector(
3119
+ `thead th[data-column-id="${CSS.escape(columnId)}"]`
3120
+ );
3121
+ return parseReorderIds(element?.getAttribute("data-reorder-ids"));
3122
+ }
3123
+ function useColumnReorder(options) {
3124
+ const { enabled, columnOrder, onColumnOrderChange } = options;
3125
+ const sessionRef = useRef6(null);
3126
+ const columnOrderRef = useRef6(columnOrder);
3127
+ const onColumnOrderChangeRef = useRef6(onColumnOrderChange);
3128
+ const [draggingColumnId, setDraggingColumnId] = useState5(null);
3129
+ const [dropTarget, setDropTarget] = useState5(
3130
+ null
3131
+ );
3132
+ const dropTargetRef = useRef6(dropTarget);
3133
+ const previousUserSelectRef = useRef6(null);
3134
+ columnOrderRef.current = columnOrder;
3135
+ onColumnOrderChangeRef.current = onColumnOrderChange;
3136
+ dropTargetRef.current = dropTarget;
3137
+ const resetDrag = useCallback6(() => {
3138
+ sessionRef.current = null;
3139
+ setDraggingColumnId(null);
3140
+ setDropTarget(null);
3141
+ const backup = previousUserSelectRef.current;
3142
+ previousUserSelectRef.current = null;
3143
+ if (backup) {
3144
+ if (backup.value) {
3145
+ document.body.style.setProperty("user-select", backup.value, backup.priority);
3146
+ } else {
3147
+ document.body.style.removeProperty("user-select");
3148
+ }
3149
+ return;
3150
+ }
3151
+ document.body.style.removeProperty("user-select");
3152
+ }, []);
3153
+ useEffect6(() => {
3154
+ if (!enabled) resetDrag();
3155
+ }, [enabled, resetDrag]);
3156
+ useEffect6(() => {
3157
+ return () => {
3158
+ resetDrag();
3159
+ };
3160
+ }, [resetDrag]);
3161
+ const onHeaderPointerDown = useCallback6(
3162
+ (event, meta) => {
3163
+ if (!enabled || !meta.canDrag) return;
3164
+ if (event.button !== 0) return;
3165
+ if (event.pointerType === "mouse" && event.ctrlKey) return;
3166
+ const target = event.target;
3167
+ if (target instanceof Element && target.closest("[data-table-disable-cell-selection]")) {
3168
+ return;
3169
+ }
3170
+ const table = event.currentTarget.closest("table");
3171
+ if (!(table instanceof HTMLTableElement)) return;
3172
+ sessionRef.current = {
3173
+ pointerId: event.pointerId,
3174
+ startX: event.clientX,
3175
+ startY: event.clientY,
3176
+ columnId: meta.columnId,
3177
+ fromIds: meta.leafIds,
3178
+ table,
3179
+ active: false
3180
+ };
3181
+ },
3182
+ [enabled]
3183
+ );
3184
+ useEffect6(() => {
3185
+ if (!enabled) return;
3186
+ const onPointerMove = (event) => {
3187
+ const session = sessionRef.current;
3188
+ if (!session || event.pointerId !== session.pointerId) return;
3189
+ const deltaX = event.clientX - session.startX;
3190
+ const deltaY = event.clientY - session.startY;
3191
+ const distance = Math.hypot(deltaX, deltaY);
3192
+ if (!session.active) {
3193
+ if (distance < DATA_TABLE_COLUMN_REORDER_THRESHOLD) return;
3194
+ session.active = true;
3195
+ if (!previousUserSelectRef.current) {
3196
+ previousUserSelectRef.current = {
3197
+ value: document.body.style.getPropertyValue("user-select"),
3198
+ priority: document.body.style.getPropertyPriority("user-select")
3199
+ };
3200
+ }
3201
+ document.body.style.setProperty("user-select", "none");
3202
+ setDraggingColumnId(session.columnId);
3203
+ }
3204
+ event.preventDefault();
3205
+ const nextTarget = hitTestReorderHeader(
3206
+ session.table,
3207
+ event.clientX,
3208
+ event.clientY
3209
+ );
3210
+ if (!nextTarget || !nextTarget.columnId) {
3211
+ setDropTarget(null);
3212
+ return;
3213
+ }
3214
+ const targetIds = readTargetIds(session.table, nextTarget.columnId);
3215
+ const fromSet = new Set(session.fromIds);
3216
+ if (targetIds.some((id) => fromSet.has(id))) {
3217
+ setDropTarget(null);
3218
+ return;
3219
+ }
3220
+ setDropTarget((previous) => {
3221
+ if (previous?.columnId === nextTarget.columnId && previous.edge === nextTarget.edge) {
3222
+ return previous;
3223
+ }
3224
+ return nextTarget;
3225
+ });
3226
+ };
3227
+ const onPointerUp = (event) => {
3228
+ const session = sessionRef.current;
3229
+ if (!session || event.pointerId !== session.pointerId) {
3230
+ return;
3231
+ }
3232
+ if (session.active) {
3233
+ event.preventDefault();
3234
+ const target = dropTargetRef.current;
3235
+ if (target) {
3236
+ const targetIds = readTargetIds(session.table, target.columnId);
3237
+ const next = moveColumnIds(
3238
+ columnOrderRef.current,
3239
+ session.fromIds,
3240
+ targetIds,
3241
+ target.edge
3242
+ );
3243
+ onColumnOrderChangeRef.current(next);
3244
+ }
3245
+ const suppressClick = (clickEvent) => {
3246
+ clickEvent.preventDefault();
3247
+ clickEvent.stopPropagation();
3248
+ document.removeEventListener("click", suppressClick, true);
3249
+ };
3250
+ document.addEventListener("click", suppressClick, true);
3251
+ window.setTimeout(() => {
3252
+ document.removeEventListener("click", suppressClick, true);
3253
+ }, 0);
3254
+ }
3255
+ resetDrag();
3256
+ };
3257
+ const onPointerCancel = (event) => {
3258
+ const session = sessionRef.current;
3259
+ if (!session || event.pointerId !== session.pointerId) {
3260
+ return;
3261
+ }
3262
+ if (session.active) {
3263
+ event.preventDefault();
3264
+ }
3265
+ resetDrag();
3266
+ };
3267
+ document.addEventListener("pointermove", onPointerMove);
3268
+ document.addEventListener("pointerup", onPointerUp);
3269
+ document.addEventListener("pointercancel", onPointerCancel);
3270
+ return () => {
3271
+ document.removeEventListener("pointermove", onPointerMove);
3272
+ document.removeEventListener("pointerup", onPointerUp);
3273
+ document.removeEventListener("pointercancel", onPointerCancel);
3274
+ };
3275
+ }, [enabled, resetDrag]);
3276
+ return {
3277
+ isReordering: draggingColumnId != null,
3278
+ draggingColumnId,
3279
+ dropTarget,
3280
+ onHeaderPointerDown
3281
+ };
3282
+ }
3283
+
2852
3284
  // src/components/ui/table/components/DataTable/DataTable.tsx
2853
3285
  import { flexRender as flexRender2 } from "@tanstack/react-table";
2854
3286
  import { useMemo as useMemo4 } from "react";
2855
3287
 
2856
3288
  // src/components/ui/table/components/DataTable/DataTableRow.tsx
2857
3289
  import { flexRender } from "@tanstack/react-table";
2858
- import { useEffect as useEffect6, useRef as useRef6 } from "react";
3290
+ import { useEffect as useEffect7, useRef as useRef7 } from "react";
2859
3291
 
2860
3292
  // src/components/ui/table/components/icons.tsx
2861
3293
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -3153,9 +3585,9 @@ function DataTableRow({
3153
3585
  const expandKey = toggleField && rowData[toggleField] !== null && rowData[toggleField] !== void 0 ? String(rowData[toggleField]) : null;
3154
3586
  const isExpanded = expandKey !== null && Boolean(expandedRows?.has(expandKey));
3155
3587
  const rowLevel = typeof rowData.level === "number" ? rowData.level : 0;
3156
- const editInputRef = useRef6(null);
3588
+ const editInputRef = useRef7(null);
3157
3589
  const isRowEditing = editingCell?.rowIndex === rowIndex;
3158
- useEffect6(() => {
3590
+ useEffect7(() => {
3159
3591
  if (!isRowEditing) return;
3160
3592
  editInputRef.current?.focus();
3161
3593
  editInputRef.current?.select();
@@ -3753,6 +4185,7 @@ function DataTable({
3753
4185
  selectionLabel,
3754
4186
  enableCellSelection,
3755
4187
  enableColumnResize,
4188
+ enableColumnReorder,
3756
4189
  enableColumnFreeze,
3757
4190
  enableInlineSearch,
3758
4191
  shouldVirtualize,
@@ -3765,6 +4198,7 @@ function DataTable({
3765
4198
  rowContextValue,
3766
4199
  handleToggleSelect,
3767
4200
  clearHover,
4201
+ setColumnOrder,
3768
4202
  inlineSearch
3769
4203
  } = useGlideTable(glideOptions);
3770
4204
  const ToolbarSlot = slots?.Toolbar ?? DataTableToolbar;
@@ -3774,6 +4208,12 @@ function DataTable({
3774
4208
  const EmptySlot = slots?.Empty ?? DefaultEmpty;
3775
4209
  const freezeOffsets = rowContextValue.columnFreeze.offsets;
3776
4210
  const headerGroups = getMergedHeaderGroups(table.getHeaderGroups());
4211
+ const leafColumnIds = table.getVisibleLeafColumns().map((column) => column.id);
4212
+ const { isReordering, draggingColumnId, dropTarget, onHeaderPointerDown } = useColumnReorder({
4213
+ enabled: enableColumnReorder,
4214
+ columnOrder: leafColumnIds,
4215
+ onColumnOrderChange: setColumnOrder
4216
+ });
3777
4217
  const contextValue = useMemo4(
3778
4218
  () => ({ ...rowContextValue, classNames }),
3779
4219
  [rowContextValue, classNames]
@@ -3796,6 +4236,8 @@ function DataTable({
3796
4236
  "DataTableJSX",
3797
4237
  !enableCellSelection && "DataTableJSX--no-cell-selection",
3798
4238
  enableColumnResize && "DataTableJSX--column-resize",
4239
+ enableColumnReorder && "DataTableJSX--column-reorder",
4240
+ isReordering && "DataTableJSX--column-reordering",
3799
4241
  enableColumnFreeze && "DataTableJSX--column-freeze",
3800
4242
  enableInlineSearch && "DataTableJSX--inline-search",
3801
4243
  classNames?.root,
@@ -3864,20 +4306,43 @@ function DataTable({
3864
4306
  ...sizeStyle,
3865
4307
  ...freezeStyle
3866
4308
  };
4309
+ const isPlaceholder = header.isPlaceholder;
4310
+ const leafColumns = header.column.getLeafColumns();
4311
+ const leafIds = leafColumns.map((leafColumn) => leafColumn.id);
4312
+ const isLeafHeader = !isPlaceholder && header.subHeaders.length === 0;
4313
+ const canDrag = enableColumnReorder && !isPlaceholder && leafIds.length > 0 && leafColumns.every(
4314
+ (leafColumn) => isColumnReorderable(leafColumn.columnDef.meta)
4315
+ );
4316
+ const isDragging = draggingColumnId === header.column.id;
4317
+ const dropEdge = !isPlaceholder && dropTarget?.columnId === header.column.id ? dropTarget.edge : void 0;
3867
4318
  return /* @__PURE__ */ jsxs6(
3868
4319
  "th",
3869
4320
  {
3870
4321
  colSpan: header.colSpan,
3871
4322
  rowSpan: header.mergedRowSpan,
4323
+ "data-column-id": enableColumnReorder && !isPlaceholder ? header.column.id : void 0,
4324
+ "data-reorder-ids": enableColumnReorder && !isPlaceholder ? serializeReorderIds(leafIds) : void 0,
4325
+ "data-reorder-leaf": enableColumnReorder && isLeafHeader ? "" : void 0,
4326
+ "data-reorderable": canDrag ? "" : void 0,
4327
+ "data-reordering": isDragging ? "" : void 0,
4328
+ "data-drop-edge": dropEdge,
3872
4329
  "data-resizing": header.column.getIsResizing() ? "" : void 0,
3873
4330
  "data-frozen": freezeOffset?.side,
3874
4331
  "data-freeze-edge": getColumnFreezeEdgeAttr(freezeOffset),
4332
+ "aria-grabbed": isDragging ? true : void 0,
4333
+ title: canDrag ? labels.reorderColumn : void 0,
4334
+ onPointerDown: enableColumnReorder ? (event) => onHeaderPointerDown(event, {
4335
+ columnId: header.column.id,
4336
+ leafIds,
4337
+ canDrag
4338
+ }) : void 0,
3875
4339
  style: Object.keys(headerStyle).length > 0 ? headerStyle : void 0,
3876
4340
  className: cn(
3877
4341
  "data-table-head-cell",
3878
4342
  freezeOffset && `data-table-head-cell--frozen-${freezeOffset.side}`,
3879
4343
  CELL_ALIGN_CLASS[align],
3880
4344
  classNames?.headCell,
4345
+ dropEdge && classNames?.dropEdge,
3881
4346
  headerClassName
3882
4347
  ),
3883
4348
  children: [
@@ -3999,7 +4464,7 @@ function DataTable({
3999
4464
  }
4000
4465
 
4001
4466
  // src/components/ui/table/components/Table/Table.tsx
4002
- import { useCallback as useCallback6, useMemo as useMemo5, useState as useState5 } from "react";
4467
+ import { useCallback as useCallback7, useMemo as useMemo5, useState as useState6 } from "react";
4003
4468
 
4004
4469
  // src/components/ui/table/components/Table/buildColumnDef.tsx
4005
4470
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -4037,10 +4502,12 @@ function buildColumnDef(props, sort, onSort) {
4037
4502
  minWidth,
4038
4503
  maxWidth,
4039
4504
  resizable,
4505
+ reorderable,
4040
4506
  frozen,
4041
4507
  align,
4042
4508
  rowSpan,
4043
4509
  rowSpanKey,
4510
+ rowSpanParent,
4044
4511
  editable,
4045
4512
  editType,
4046
4513
  editInputProps,
@@ -4075,6 +4542,7 @@ function buildColumnDef(props, sort, onSort) {
4075
4542
  align,
4076
4543
  rowSpan,
4077
4544
  rowSpanKey,
4545
+ rowSpanParent,
4078
4546
  editable,
4079
4547
  editType,
4080
4548
  editInputProps,
@@ -4082,6 +4550,7 @@ function buildColumnDef(props, sort, onSort) {
4082
4550
  cellProps,
4083
4551
  cellRender: render,
4084
4552
  frozen,
4553
+ reorderable,
4085
4554
  className,
4086
4555
  headerClassName
4087
4556
  }
@@ -4329,8 +4798,8 @@ function TableRoot({
4329
4798
  () => parseTableChildren(children),
4330
4799
  [children]
4331
4800
  );
4332
- const [sort, setSort] = useState5(null);
4333
- const handleSort = useCallback6((field) => {
4801
+ const [sort, setSort] = useState6(null);
4802
+ const handleSort = useCallback7((field) => {
4334
4803
  setSort((previous) => {
4335
4804
  if (previous?.field !== field) {
4336
4805
  return { field, direction: "asc" };
@@ -4427,6 +4896,7 @@ export {
4427
4896
  Table,
4428
4897
  applyCellEdit,
4429
4898
  applyFillData,
4899
+ applyLeafColumnOrder,
4430
4900
  applySelectionUpdater,
4431
4901
  buildColumnFreezeOffsets,
4432
4902
  buildColumnRowSpanMap,
@@ -4441,6 +4911,7 @@ export {
4441
4911
  collectCopyRowEntries,
4442
4912
  collectCopyRows,
4443
4913
  collectFillChanges,
4914
+ collectLeafColumnIds,
4444
4915
  collectRowSpanColumns,
4445
4916
  collectSearchMatchesInRange,
4446
4917
  commitCellValue,
@@ -4466,6 +4937,7 @@ export {
4466
4937
  mapSearchResultToVisibleItem,
4467
4938
  mapSearchResultsToVisibleKeys,
4468
4939
  measureMergedSpanRowHeights,
4940
+ moveColumnIds,
4469
4941
  nextSearchIndex,
4470
4942
  nextSearchStride,
4471
4943
  parseCellEditValue,
@@ -4475,7 +4947,9 @@ export {
4475
4947
  resolveCellRenderer,
4476
4948
  resolveColumnFreezeSide,
4477
4949
  resolveDataTableLabels,
4950
+ resolveDropEdge,
4478
4951
  resolveHeaderFreezeOffset,
4952
+ resolveLeafColumnOrder,
4479
4953
  resolvePasteColumnIds,
4480
4954
  resolveRowSelection,
4481
4955
  resolveRowSpanAt,
@@ -4485,6 +4959,7 @@ export {
4485
4959
  toggleExpandedRowId,
4486
4960
  useCellEdit,
4487
4961
  useCellSelection,
4962
+ useColumnReorder,
4488
4963
  useConvertTreeData,
4489
4964
  useGlideTable,
4490
4965
  useInlineSearch,