@ai-matrx/design-system 0.16.0 → 0.17.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +6 -0
  3. package/dist/data-table/copy-helpers.d.cts +1 -1
  4. package/dist/data-table/copy-helpers.d.ts +1 -1
  5. package/dist/data-table/filter-engine.d.cts +1 -1
  6. package/dist/data-table/filter-engine.d.ts +1 -1
  7. package/dist/data-table/index.cjs +321 -75
  8. package/dist/data-table/index.cjs.map +1 -1
  9. package/dist/data-table/index.d.cts +2 -2
  10. package/dist/data-table/index.d.ts +2 -2
  11. package/dist/data-table/index.js +317 -71
  12. package/dist/data-table/index.js.map +1 -1
  13. package/dist/data-table/infer-filter.d.cts +1 -1
  14. package/dist/data-table/infer-filter.d.ts +1 -1
  15. package/dist/data-table/layered-filters.d.cts +1 -1
  16. package/dist/data-table/layered-filters.d.ts +1 -1
  17. package/dist/data-table/query-control.d.cts +1 -1
  18. package/dist/data-table/query-control.d.ts +1 -1
  19. package/dist/data-table/scroll-pagination.cjs +18 -7
  20. package/dist/data-table/scroll-pagination.cjs.map +1 -1
  21. package/dist/data-table/scroll-pagination.js +18 -7
  22. package/dist/data-table/scroll-pagination.js.map +1 -1
  23. package/dist/data-table/types.cjs.map +1 -1
  24. package/dist/data-table/types.d.cts +1 -1
  25. package/dist/data-table/types.d.ts +1 -1
  26. package/dist/data-table/url-state.d.cts +1 -1
  27. package/dist/data-table/url-state.d.ts +1 -1
  28. package/dist/data-table/xlsx.d.cts +1 -1
  29. package/dist/data-table/xlsx.d.ts +1 -1
  30. package/dist/{types-XXbVB1sh.d.cts → types-4fmLG5AN.d.cts} +16 -1
  31. package/dist/{types-ClaxTaXq.d.ts → types-pZ95iFk5.d.ts} +16 -1
  32. package/package.json +1 -1
@@ -460,7 +460,7 @@ module.exports = __toCommonJS(data_table_exports);
460
460
 
461
461
  // src/data-table/MatrxDataTable.tsx
462
462
  var import_react14 = require("react");
463
- var import_core = require("@dnd-kit/core");
463
+ var import_core2 = require("@dnd-kit/core");
464
464
 
465
465
  // src/data-table/icons.tsx
466
466
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -1197,6 +1197,7 @@ function useScrollPagination({
1197
1197
  const inFlightGenerationRef = (0, import_react.useRef)(null);
1198
1198
  const requestErrorRef = (0, import_react.useRef)(null);
1199
1199
  const mountedRef = (0, import_react.useRef)(false);
1200
+ const observedTopRef = (0, import_react.useRef)(/* @__PURE__ */ new WeakMap());
1200
1201
  const effectiveError = error ?? requestError;
1201
1202
  (0, import_react.useEffect)(() => {
1202
1203
  mountedRef.current = true;
@@ -1230,6 +1231,7 @@ function useScrollPagination({
1230
1231
  };
1231
1232
  (0, import_react.useEffect)(() => {
1232
1233
  intentRef.current = null;
1234
+ observedTopRef.current = /* @__PURE__ */ new WeakMap();
1233
1235
  consumedSequenceRef.current = inputSequenceRef.current;
1234
1236
  requestErrorRef.current = null;
1235
1237
  setRequestError(null);
@@ -1239,14 +1241,18 @@ function useScrollPagination({
1239
1241
  (0, import_react.useEffect)(() => {
1240
1242
  if (mode !== "scroll" || !enabled || typeof window === "undefined") return;
1241
1243
  const attachable = containers.map(resolveContainer).filter((container) => container !== null);
1242
- const setIntent = (container) => {
1244
+ const setIntent = (container, startTop) => {
1243
1245
  const current = latestRef.current;
1244
1246
  if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;
1245
1247
  inputSequenceRef.current += 1;
1246
1248
  intentRef.current = {
1247
1249
  queryKey: latestRef.current.queryKey,
1248
1250
  container,
1249
- startTop: container.scrollTop,
1251
+ // The compositor can advance downward before the passive input
1252
+ // listener, while DOM/test callers can move upward before its scroll
1253
+ // notification. Either way, the lower position is the only safe
1254
+ // baseline for proving that this downward intent caused movement.
1255
+ startTop: Math.min(startTop, container.scrollTop),
1250
1256
  sequence: inputSequenceRef.current,
1251
1257
  expiresAt: Date.now() + intentTimeoutMs
1252
1258
  };
@@ -1261,9 +1267,11 @@ function useScrollPagination({
1261
1267
  void requestNextPage();
1262
1268
  };
1263
1269
  const cleanups = attachable.map((container) => {
1270
+ if (!observedTopRef.current.has(container)) observedTopRef.current.set(container, container.scrollTop);
1271
+ const lastObservedTop = () => observedTopRef.current.get(container) ?? container.scrollTop;
1264
1272
  const onWheel = (event) => {
1265
1273
  if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
1266
- setIntent(container);
1274
+ setIntent(container, lastObservedTop());
1267
1275
  }
1268
1276
  };
1269
1277
  const onTouchStart = (event) => {
@@ -1274,19 +1282,22 @@ function useScrollPagination({
1274
1282
  const touch = event.touches.item(0);
1275
1283
  const startY = Number(container.dataset.matrxPaginationTouchY);
1276
1284
  if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {
1277
- setIntent(container);
1285
+ setIntent(container, lastObservedTop());
1278
1286
  }
1279
1287
  };
1280
1288
  const onTouchEnd = () => {
1281
1289
  delete container.dataset.matrxPaginationTouchY;
1282
1290
  };
1283
1291
  const onKeyDown = (event) => {
1284
- if (isDownwardKeyboardInput(event)) setIntent(container);
1292
+ if (isDownwardKeyboardInput(event)) setIntent(container, lastObservedTop());
1285
1293
  };
1286
1294
  const onPointerDown = (event) => {
1287
- if (isNativeScrollbarPointer(event, container)) setIntent(container);
1295
+ if (isNativeScrollbarPointer(event, container)) setIntent(container, lastObservedTop());
1296
+ };
1297
+ const onScroll = () => {
1298
+ handleScroll(container);
1299
+ observedTopRef.current.set(container, container.scrollTop);
1288
1300
  };
1289
- const onScroll = () => handleScroll(container);
1290
1301
  container.addEventListener("wheel", onWheel, { passive: true });
1291
1302
  container.addEventListener("touchstart", onTouchStart, { passive: true });
1292
1303
  container.addEventListener("touchmove", onTouchMove, { passive: true });
@@ -3416,6 +3427,7 @@ function parseOptionalNumber(raw) {
3416
3427
 
3417
3428
  // src/data-table/ColumnManager.tsx
3418
3429
  var import_react9 = require("react");
3430
+ var import_core = require("@dnd-kit/core");
3419
3431
  init_filter_engine();
3420
3432
  var import_jsx_runtime23 = require("react/jsx-runtime");
3421
3433
  function ColumnManager({
@@ -3432,6 +3444,8 @@ function ColumnManager({
3432
3444
  );
3433
3445
  const [draftOrder, setDraftOrder] = (0, import_react9.useState)(order);
3434
3446
  const [draftHidden, setDraftHidden] = (0, import_react9.useState)(() => new Set(hidden));
3447
+ const [draggedColumnId, setDraggedColumnId] = (0, import_react9.useState)(null);
3448
+ const [dropTarget, setDropTarget] = (0, import_react9.useState)(null);
3435
3449
  (0, import_react9.useEffect)(() => {
3436
3450
  if (!open) return;
3437
3451
  setDraftOrder(order);
@@ -3450,6 +3464,45 @@ function ColumnManager({
3450
3464
  return next;
3451
3465
  });
3452
3466
  };
3467
+ const moveTo = (id, targetId) => {
3468
+ if (id === targetId) return;
3469
+ setDraftOrder((current) => {
3470
+ const from = current.indexOf(id);
3471
+ const target = current.indexOf(targetId);
3472
+ if (from < 0 || target < 0) return current;
3473
+ const next = [...current];
3474
+ next.splice(from, 1);
3475
+ const targetAfterRemoval = next.indexOf(targetId);
3476
+ next.splice(targetAfterRemoval, 0, id);
3477
+ return next;
3478
+ });
3479
+ };
3480
+ (0, import_core.useDndMonitor)({
3481
+ onDragStart(event) {
3482
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3483
+ setDraggedColumnId(String(event.active.data.current.columnId));
3484
+ },
3485
+ onDragOver(event) {
3486
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3487
+ const overData = event.over?.data.current;
3488
+ const target = overData?.matrxDragKind === "column-manager" ? overData.columnId : null;
3489
+ setDropTarget(target ? String(target) : null);
3490
+ },
3491
+ onDragCancel(event) {
3492
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3493
+ setDraggedColumnId(null);
3494
+ setDropTarget(null);
3495
+ },
3496
+ onDragEnd(event) {
3497
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3498
+ const dragged = String(event.active.data.current.columnId);
3499
+ const overData = event.over?.data.current;
3500
+ const target = overData?.matrxDragKind === "column-manager" ? overData.columnId : null;
3501
+ setDraggedColumnId(null);
3502
+ setDropTarget(null);
3503
+ if (target) moveTo(dragged, String(target));
3504
+ }
3505
+ });
3453
3506
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Dialog, { open, onOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DialogContent, { className: "max-w-md", children: [
3454
3507
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DialogHeader, { children: [
3455
3508
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DialogTitle, { children: "Columns" }),
@@ -3462,14 +3515,16 @@ function ColumnManager({
3462
3515
  const hideable = column.hideable !== false;
3463
3516
  const visible = !draftHidden.has(id);
3464
3517
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
3465
- "div",
3518
+ ColumnManagerDropTarget,
3466
3519
  {
3520
+ id,
3467
3521
  className: cn(
3468
3522
  "flex items-center gap-2 px-2 py-2",
3469
- !visible && "bg-muted/30 text-muted-foreground"
3523
+ !visible && "bg-muted/30 text-muted-foreground",
3524
+ dropTarget === id && draggedColumnId !== id && "border-t-2 border-primary"
3470
3525
  ),
3471
3526
  children: [
3472
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(GripVertical, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3527
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ColumnManagerDragHandle, { id, label }),
3473
3528
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3474
3529
  Checkbox,
3475
3530
  {
@@ -3535,6 +3590,36 @@ function ColumnManager({
3535
3590
  ] })
3536
3591
  ] }) });
3537
3592
  }
3593
+ function ColumnManagerDropTarget({
3594
+ id,
3595
+ className,
3596
+ children
3597
+ }) {
3598
+ const { setNodeRef } = (0, import_core.useDroppable)({
3599
+ id: `matrx-column-manager:${id}`,
3600
+ data: { matrxDragKind: "column-manager", columnId: id }
3601
+ });
3602
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: setNodeRef, "data-matrx-column-manager-drop-target": id, className, children });
3603
+ }
3604
+ function ColumnManagerDragHandle({ id, label }) {
3605
+ const { attributes, listeners, setNodeRef } = (0, import_core.useDraggable)({
3606
+ id: `matrx-column-manager:${id}`,
3607
+ data: { matrxDragKind: "column-manager", columnId: id }
3608
+ });
3609
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3610
+ "button",
3611
+ {
3612
+ ref: setNodeRef,
3613
+ type: "button",
3614
+ className: "flex h-10 w-10 shrink-0 touch-none cursor-grab items-center justify-center text-muted-foreground hover:text-foreground active:cursor-grabbing",
3615
+ "aria-label": `Reorder ${label}`,
3616
+ title: `Drag to reorder ${label}`,
3617
+ ...attributes,
3618
+ ...listeners,
3619
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(GripVertical, { className: "h-4 w-4" })
3620
+ }
3621
+ );
3622
+ }
3538
3623
 
3539
3624
  // src/data-table/MatrxUuidCell.tsx
3540
3625
  var import_react10 = require("react");
@@ -5004,6 +5089,9 @@ function safeQueryPage(requestedPage, totalItems, pageSize) {
5004
5089
 
5005
5090
  // src/data-table/MatrxDataTable.tsx
5006
5091
  var import_jsx_runtime32 = require("react/jsx-runtime");
5092
+ function hasSameRowReferences(previous, next) {
5093
+ return previous.length === next.length && previous.every((row, index) => row === next[index]);
5094
+ }
5007
5095
  function TableRowCopyControl({
5008
5096
  copy,
5009
5097
  row,
@@ -5121,6 +5209,9 @@ function MatrxDataTableWithUrlState({
5121
5209
  function MatrxDataTableCore({
5122
5210
  data: suppliedData,
5123
5211
  columns,
5212
+ columnState,
5213
+ reorderableColumns = false,
5214
+ columnManager: controlledColumnManager,
5124
5215
  getRowId,
5125
5216
  searchText,
5126
5217
  processLocalRows,
@@ -5186,8 +5277,8 @@ function MatrxDataTableCore({
5186
5277
  );
5187
5278
  const [internalSearch, setInternalSearch] = (0, import_react14.useState)("");
5188
5279
  const [draggedRowId, setDraggedRowId] = (0, import_react14.useState)(null);
5189
- const hierarchySensors = (0, import_core.useSensors)(
5190
- (0, import_core.useSensor)(import_core.PointerSensor, { activationConstraint: { distance: 6 } })
5280
+ const hierarchySensors = (0, import_core2.useSensors)(
5281
+ (0, import_core2.useSensor)(import_core2.PointerSensor, { activationConstraint: { distance: 6 } })
5191
5282
  );
5192
5283
  const toolbarSearchControlled = toolbar?.searchValue !== void 0;
5193
5284
  const searchValue = controlledQuery ? controlledQuery.state.search : toolbarSearchControlled ? toolbar?.searchValue ?? "" : internalSearch;
@@ -5424,13 +5515,67 @@ function MatrxDataTableCore({
5424
5515
  observer?.disconnect();
5425
5516
  };
5426
5517
  }, [updateScrollHint, data, columns]);
5427
- const [columnOrder, setColumnOrder] = (0, import_react14.useState)(() => columns.map(columnId));
5518
+ const [columnOrder, setColumnOrder] = (0, import_react14.useState)(() => columnState?.order ?? columns.map(columnId));
5428
5519
  const [hiddenColumnIds, setHiddenColumnIds] = (0, import_react14.useState)(
5429
- () => new Set(columns.filter((column) => column.hidden).map(columnId))
5520
+ () => new Set(columnState?.hidden ?? columns.filter((column) => column.hidden).map(columnId))
5430
5521
  );
5431
- const [columnManagerOpen, setColumnManagerOpen] = (0, import_react14.useState)(false);
5522
+ const draggedColumnId = (0, import_react14.useRef)(null);
5523
+ const columnDragActive = (0, import_react14.useRef)(false);
5524
+ const columnDragRaf = (0, import_react14.useRef)(null);
5525
+ const columnDragScrollDirection = (0, import_react14.useRef)(0);
5526
+ const [columnDropTarget, setColumnDropTarget] = (0, import_react14.useState)(null);
5527
+ const stopColumnDragScroll = (0, import_react14.useCallback)(() => {
5528
+ if (columnDragRaf.current !== null) cancelAnimationFrame(columnDragRaf.current);
5529
+ columnDragRaf.current = null;
5530
+ columnDragScrollDirection.current = 0;
5531
+ }, []);
5532
+ (0, import_react14.useEffect)(() => () => {
5533
+ columnDragActive.current = false;
5534
+ stopColumnDragScroll();
5535
+ }, [stopColumnDragScroll]);
5536
+ const startColumnDragScroll = (0, import_react14.useCallback)(() => {
5537
+ if (!columnDragActive.current || columnDragRaf.current !== null) return;
5538
+ const scrollStep = () => {
5539
+ const scroll = scrollRef.current;
5540
+ const direction = columnDragScrollDirection.current;
5541
+ if (!columnDragActive.current || !scroll || direction === 0) {
5542
+ columnDragRaf.current = null;
5543
+ return;
5544
+ }
5545
+ const nextScrollLeft = Math.max(
5546
+ 0,
5547
+ Math.min(scroll.scrollLeft + direction * 18, scroll.scrollWidth - scroll.clientWidth)
5548
+ );
5549
+ if (nextScrollLeft === scroll.scrollLeft) {
5550
+ columnDragRaf.current = null;
5551
+ columnDragScrollDirection.current = 0;
5552
+ return;
5553
+ }
5554
+ scroll.scrollLeft = nextScrollLeft;
5555
+ if (!columnDragActive.current || columnDragScrollDirection.current === 0) {
5556
+ columnDragRaf.current = null;
5557
+ return;
5558
+ }
5559
+ columnDragRaf.current = requestAnimationFrame(scrollStep);
5560
+ };
5561
+ columnDragRaf.current = requestAnimationFrame(scrollStep);
5562
+ }, []);
5563
+ const commitColumnState = (0, import_react14.useCallback)((order, hidden) => {
5564
+ if (columnState) columnState.onChange({ order, hidden: [...hidden] });
5565
+ else {
5566
+ setColumnOrder(order);
5567
+ setHiddenColumnIds(hidden);
5568
+ }
5569
+ }, [columnState]);
5570
+ const [internalColumnManagerOpen, setInternalColumnManagerOpen] = (0, import_react14.useState)(false);
5571
+ const columnManagerOpen = controlledColumnManager?.open ?? internalColumnManagerOpen;
5572
+ const setColumnManagerOpen = controlledColumnManager?.onOpenChange ?? setInternalColumnManagerOpen;
5573
+ const columnIds = (0, import_react14.useMemo)(() => columns.map(columnId), [columns]);
5574
+ const effectiveColumnOrder = (0, import_react14.useMemo)(() => columnState ? [...columnState.order.filter((id, index, values) => columnIds.includes(id) && values.indexOf(id) === index), ...columnIds.filter((id) => !columnState.order.includes(id))] : columnOrder, [columnIds, columnOrder, columnState]);
5575
+ const effectiveHiddenColumnIds = (0, import_react14.useMemo)(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
5432
5576
  (0, import_react14.useEffect)(() => {
5433
5577
  const ids = columns.map(columnId);
5578
+ if (columnState) return;
5434
5579
  setColumnOrder((current) => [
5435
5580
  ...current.filter((id) => ids.includes(id)),
5436
5581
  ...ids.filter((id) => !current.includes(id))
@@ -5442,13 +5587,13 @@ function MatrxDataTableCore({
5442
5587
  }
5443
5588
  return next;
5444
5589
  });
5445
- }, [columns]);
5590
+ }, [columns, columnState]);
5446
5591
  const visibleColumns = (0, import_react14.useMemo)(() => {
5447
5592
  const byId = new Map(columns.map((column) => [columnId(column), column]));
5448
- return columnOrder.map((id) => byId.get(id)).filter(
5449
- (column) => column !== void 0 && !hiddenColumnIds.has(columnId(column))
5593
+ return effectiveColumnOrder.map((id) => byId.get(id)).filter(
5594
+ (column) => column !== void 0 && !effectiveHiddenColumnIds.has(columnId(column))
5450
5595
  );
5451
- }, [columns, columnOrder, hiddenColumnIds]);
5596
+ }, [columns, effectiveColumnOrder, effectiveHiddenColumnIds]);
5452
5597
  const copySubsetVariant = useCopySubsetVariant();
5453
5598
  const copySubsetColumns = () => visibleColumns.filter((c) => c.filter !== false || c.accessorKey || c.accessorFn).map((c) => ({
5454
5599
  id: columnId(c),
@@ -5522,8 +5667,15 @@ function MatrxDataTableCore({
5522
5667
  sourceOwnsColumnFilters,
5523
5668
  sourceOwnsSort
5524
5669
  ]);
5670
+ const lastNotifiedViewRows = (0, import_react14.useRef)(null);
5525
5671
  (0, import_react14.useEffect)(() => {
5526
- onViewChange?.(processed);
5672
+ if (!onViewChange) {
5673
+ lastNotifiedViewRows.current = null;
5674
+ return;
5675
+ }
5676
+ const changed = lastNotifiedViewRows.current === null || !hasSameRowReferences(lastNotifiedViewRows.current, processed);
5677
+ lastNotifiedViewRows.current = processed;
5678
+ if (changed) onViewChange(processed);
5527
5679
  }, [processed, onViewChange]);
5528
5680
  (0, import_react14.useEffect)(() => {
5529
5681
  if (controlledQuery) return;
@@ -5953,16 +6105,77 @@ function MatrxDataTableCore({
5953
6105
  } : facet
5954
6106
  ) : toolbar?.facets;
5955
6107
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
5956
- import_core.DndContext,
6108
+ import_core2.DndContext,
5957
6109
  {
5958
6110
  sensors: hierarchySensors,
5959
- collisionDetection: import_core.pointerWithin,
5960
- measuring: { droppable: { strategy: import_core.MeasuringStrategy.Always } },
6111
+ collisionDetection: import_core2.pointerWithin,
6112
+ measuring: { droppable: { strategy: import_core2.MeasuringStrategy.Always } },
5961
6113
  onDragStart: (event) => {
6114
+ const kind = event.active.data.current?.matrxDragKind;
6115
+ if (kind === "column-header") {
6116
+ draggedColumnId.current = String(event.active.data.current?.columnId);
6117
+ columnDragActive.current = true;
6118
+ return;
6119
+ }
6120
+ if (kind === "column-manager") return;
5962
6121
  setDraggedRowId(String(event.active.id));
5963
6122
  },
5964
- onDragCancel: () => setDraggedRowId(null),
5965
- onDragEnd: endHierarchyDrag,
6123
+ onDragMove: (event) => {
6124
+ if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
6125
+ const overData = event.over?.data.current;
6126
+ const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
6127
+ const activeRect = event.active.rect.current.translated;
6128
+ const overRect = event.over?.rect;
6129
+ if (!overId || !activeRect || !overRect) {
6130
+ setColumnDropTarget(null);
6131
+ columnDragScrollDirection.current = 0;
6132
+ stopColumnDragScroll();
6133
+ return;
6134
+ }
6135
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
6136
+ setColumnDropTarget({ id: String(overId), after });
6137
+ const scroll = scrollRef.current;
6138
+ if (!scroll) return;
6139
+ const bounds = scroll.getBoundingClientRect();
6140
+ const center = activeRect.left + activeRect.width / 2;
6141
+ columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
6142
+ if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
6143
+ else startColumnDragScroll();
6144
+ },
6145
+ onDragCancel: () => {
6146
+ columnDragActive.current = false;
6147
+ draggedColumnId.current = null;
6148
+ setColumnDropTarget(null);
6149
+ stopColumnDragScroll();
6150
+ setDraggedRowId(null);
6151
+ },
6152
+ onDragEnd: (event) => {
6153
+ const kind = event.active.data.current?.matrxDragKind;
6154
+ if (kind === "column-manager") return;
6155
+ if (kind !== "column-header") {
6156
+ endHierarchyDrag(event);
6157
+ return;
6158
+ }
6159
+ const from = String(event.active.data.current?.columnId);
6160
+ const targetData = event.over?.data.current;
6161
+ const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
6162
+ const activeRect = event.active.rect.current.translated;
6163
+ const overRect = event.over?.rect;
6164
+ columnDragActive.current = false;
6165
+ draggedColumnId.current = null;
6166
+ setColumnDropTarget(null);
6167
+ stopColumnDragScroll();
6168
+ if (!target || from === target || !activeRect || !overRect) return;
6169
+ const order = [...effectiveColumnOrder];
6170
+ const fromIndex = order.indexOf(from);
6171
+ const targetIndex = order.indexOf(String(target));
6172
+ if (fromIndex < 0 || targetIndex < 0) return;
6173
+ order.splice(fromIndex, 1);
6174
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
6175
+ const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
6176
+ order.splice(insertion, 0, from);
6177
+ commitColumnState(order, effectiveHiddenColumnIds);
6178
+ },
5966
6179
  children: [
5967
6180
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
5968
6181
  "div",
@@ -6184,8 +6397,8 @@ function MatrxDataTableCore({
6184
6397
  onClick: () => setColumnManagerOpen(true),
6185
6398
  children: [
6186
6399
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(SlidersHorizontal, { className: "h-4 w-4" }),
6187
- hiddenColumnIds.size > 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "sr-only", children: [
6188
- hiddenColumnIds.size,
6400
+ effectiveHiddenColumnIds.size > 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "sr-only", children: [
6401
+ effectiveHiddenColumnIds.size,
6189
6402
  " hidden"
6190
6403
  ] }) : null
6191
6404
  ]
@@ -6439,46 +6652,50 @@ function MatrxDataTableCore({
6439
6652
  // horizontal-scroll surface.
6440
6653
  col.width !== void 0 && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
6441
6654
  col.headerClassName,
6655
+ columnDropTarget?.id === id && (columnDropTarget.after ? "border-r-2 border-primary" : "border-l-2 border-primary"),
6442
6656
  col.align === "center" && "text-center",
6443
6657
  col.align === "right" && "text-right"
6444
6658
  ),
6445
6659
  style: columnWidthVar(col.width),
6446
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6447
- ColumnHeaderCell,
6448
- {
6449
- label: col.header,
6450
- labelText: headerLabel,
6451
- sortable: col.sortable !== false,
6452
- isSorted: Boolean(isSorted),
6453
- sortDirection: sort?.direction ?? defaultSortDirection,
6454
- onSortAsc: () => setSort({ id, direction: "asc" }),
6455
- onSortDesc: () => setSort({ id, direction: "desc" }),
6456
- onClearSort: () => setSort(null),
6457
- onHeaderSortClick: () => {
6458
- if (!isSorted) {
6459
- setSort({ id, direction: defaultSortDirection });
6460
- return;
6461
- }
6462
- if (sort?.direction === "asc") {
6463
- setSort({ id, direction: "desc" });
6464
- return;
6465
- }
6466
- setSort(null);
6467
- },
6468
- filterKind: meta?.kind ?? null,
6469
- filterValue: columnFilters[id],
6470
- onFilterChange: (next) => setColumnFilter(id, next),
6471
- selectOptions: meta?.options,
6472
- selectSingle: col.filterSingle,
6473
- filterApplyLabel: filterUi?.applyLabel,
6474
- filterShowCancel: filterUi?.showCancel,
6475
- filterFooterHint: filterUi?.footerHint,
6476
- allowNegation: filterUi?.allowNegation,
6477
- allowNullModes: filterUi?.allowNullModes,
6478
- align: col.align,
6479
- compact: col.compact
6480
- }
6481
- )
6660
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: [
6661
+ reorderableColumns ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ColumnReorderHandle, { id, label: headerLabel }) : null,
6662
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6663
+ ColumnHeaderCell,
6664
+ {
6665
+ label: col.header,
6666
+ labelText: headerLabel,
6667
+ sortable: col.sortable !== false,
6668
+ isSorted: Boolean(isSorted),
6669
+ sortDirection: sort?.direction ?? defaultSortDirection,
6670
+ onSortAsc: () => setSort({ id, direction: "asc" }),
6671
+ onSortDesc: () => setSort({ id, direction: "desc" }),
6672
+ onClearSort: () => setSort(null),
6673
+ onHeaderSortClick: () => {
6674
+ if (!isSorted) {
6675
+ setSort({ id, direction: defaultSortDirection });
6676
+ return;
6677
+ }
6678
+ if (sort?.direction === "asc") {
6679
+ setSort({ id, direction: "desc" });
6680
+ return;
6681
+ }
6682
+ setSort(null);
6683
+ },
6684
+ filterKind: meta?.kind ?? null,
6685
+ filterValue: columnFilters[id],
6686
+ onFilterChange: (next) => setColumnFilter(id, next),
6687
+ selectOptions: meta?.options,
6688
+ selectSingle: col.filterSingle,
6689
+ filterApplyLabel: filterUi?.applyLabel,
6690
+ filterShowCancel: filterUi?.showCancel,
6691
+ filterFooterHint: filterUi?.footerHint,
6692
+ allowNegation: filterUi?.allowNegation,
6693
+ allowNullModes: filterUi?.allowNullModes,
6694
+ align: col.align,
6695
+ compact: col.compact
6696
+ }
6697
+ )
6698
+ ] })
6482
6699
  },
6483
6700
  id
6484
6701
  );
@@ -6777,12 +6994,9 @@ function MatrxDataTableCore({
6777
6994
  open: columnManagerOpen,
6778
6995
  onOpenChange: setColumnManagerOpen,
6779
6996
  columns,
6780
- order: columnOrder,
6781
- hidden: hiddenColumnIds,
6782
- onApply: (order, hidden) => {
6783
- setColumnOrder(order);
6784
- setHiddenColumnIds(hidden);
6785
- }
6997
+ order: effectiveColumnOrder,
6998
+ hidden: effectiveHiddenColumnIds,
6999
+ onApply: commitColumnState
6786
7000
  }
6787
7001
  ),
6788
7002
  detailEnabled && selectedRow ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
@@ -6981,7 +7195,7 @@ function MatrxDataTableCore({
6981
7195
  ]
6982
7196
  }
6983
7197
  ),
6984
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_core.DragOverlay, { children: draggedRow ? (
7198
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_core2.DragOverlay, { children: draggedRow ? (
6985
7199
  // w-max: the overlay inherits the ACTIVE node's size, and the active
6986
7200
  // node is the 16px grip handle — without it the label wraps to one
6987
7201
  // character per line.
@@ -6996,7 +7210,7 @@ function HierarchyDragHandle({
6996
7210
  label,
6997
7211
  disabled
6998
7212
  }) {
6999
- const { attributes, listeners, setNodeRef } = (0, import_core.useDraggable)({
7213
+ const { attributes, listeners, setNodeRef } = (0, import_core2.useDraggable)({
7000
7214
  id,
7001
7215
  disabled
7002
7216
  });
@@ -7020,7 +7234,7 @@ function HierarchyRootDropTarget({
7020
7234
  label,
7021
7235
  draggedLabel
7022
7236
  }) {
7023
- const { isOver, setNodeRef } = (0, import_core.useDroppable)({ id: "__root__" });
7237
+ const { isOver, setNodeRef } = (0, import_core2.useDroppable)({ id: "__root__" });
7024
7238
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7025
7239
  "div",
7026
7240
  {
@@ -7044,7 +7258,7 @@ function HierarchyDragIndicatorLayer({
7044
7258
  const [indicator, setIndicator] = (0, import_react14.useState)(
7045
7259
  null
7046
7260
  );
7047
- (0, import_core.useDndMonitor)({
7261
+ (0, import_core2.useDndMonitor)({
7048
7262
  onDragMove(event) {
7049
7263
  const container = scrollRef.current;
7050
7264
  const overId = event.over ? String(event.over.id) : null;
@@ -7144,7 +7358,7 @@ function HierarchyDroppableRow({
7144
7358
  disabled,
7145
7359
  children
7146
7360
  }) {
7147
- const { setNodeRef } = (0, import_core.useDroppable)({ id, disabled });
7361
+ const { setNodeRef } = (0, import_core2.useDroppable)({ id, disabled });
7148
7362
  if (!(0, import_react14.isValidElement)(children) || children.type !== "tr") return children;
7149
7363
  const row = children;
7150
7364
  return (0, import_react14.cloneElement)(row, {
@@ -7152,6 +7366,38 @@ function HierarchyDroppableRow({
7152
7366
  className: row.props.className
7153
7367
  });
7154
7368
  }
7369
+ function ColumnHeaderDropZone({
7370
+ id,
7371
+ enabled,
7372
+ children
7373
+ }) {
7374
+ const { setNodeRef } = (0, import_core2.useDroppable)({
7375
+ id: `matrx-column-header:${id}`,
7376
+ disabled: !enabled,
7377
+ data: { matrxDragKind: "column-header", columnId: id }
7378
+ });
7379
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: setNodeRef, className: "flex min-w-0 items-center", children });
7380
+ }
7381
+ function ColumnReorderHandle({ id, label }) {
7382
+ const { attributes, listeners, setNodeRef } = (0, import_core2.useDraggable)({
7383
+ id: `matrx-column-header:${id}`,
7384
+ data: { matrxDragKind: "column-header", columnId: id }
7385
+ });
7386
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7387
+ "button",
7388
+ {
7389
+ ref: setNodeRef,
7390
+ type: "button",
7391
+ className: "mr-1 flex h-7 w-5 shrink-0 touch-none cursor-grab items-center justify-center text-muted-foreground hover:text-foreground active:cursor-grabbing",
7392
+ "aria-label": `Reorder ${label}`,
7393
+ title: `Drag to reorder ${label}`,
7394
+ onClick: (event) => event.stopPropagation(),
7395
+ ...attributes,
7396
+ ...listeners,
7397
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(GripVertical, { className: "h-3.5 w-3.5" })
7398
+ }
7399
+ );
7400
+ }
7155
7401
  function columnWidthVar(width) {
7156
7402
  if (width === void 0) return void 0;
7157
7403
  return {