@ai-matrx/design-system 0.15.2 → 0.17.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/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0 — 2026-09-12
4
+
5
+ - `MatrxDataTable` accepts opt-in controlled `columnState` and `reorderableColumns`, allowing URL-backed order/visibility and package-owned header drag without a host renderer fork. Header grips and the column manager use the existing pointer sensor for mouse/touch ordering; the manager retains arrow controls and protected visibility. Headers show insertion feedback and cancel edge scrolling when the drag ends or leaves a valid target.
6
+ - `onViewChange` notifies when a subscriber attaches or row references/order change, while callback identity changes alone do not trigger a feedback loop.
7
+
8
+ ### Consumer action
9
+
10
+ Update to 0.17.0 before using `columnState`, `reorderableColumns`, or the
11
+ controlled `columnManager` open state. These column ports are opt-in: existing
12
+ column-order and visibility behavior is unchanged until a consumer supplies
13
+ them. `onViewChange` subscribers receive the corrected attach and identity
14
+ semantics described above.
15
+
16
+ ## 0.16.0 — 2026-09-11
17
+
18
+ `ConfirmDialogHost` now rides the shared opener primitive instead of its own
19
+ copy of the machinery: `useOpenerHost` from `@ai-matrx/kit/opener-react`
20
+ (kit 0.10.0) supplies the host registration, the ref-backed request queue, the
21
+ drain effect and the settle-once callback, and this package keeps only what was
22
+ ever its own — the dialog body, its motion layer and the themed scrim.
23
+
24
+ The same twenty lines lived here AND in three matrx-frontend dialog hosts;
25
+ package logic is never duplicated outside the package, and a host registry in
26
+ particular must never be re-implemented, because module-level host state
27
+ silently splits across ESM/CJS loader graphs.
28
+
29
+ Behaviour is unchanged and proven unchanged: all 17 confirm-surface tests —
30
+ imperative queueing, pre-mount calls, sequential presentation, the
31
+ never-resolve-silently rule, the destructive variant and the consequence-first
32
+ copy contract — pass untouched against the new host.
33
+
34
+ ### Consumer action
35
+
36
+ None. `<ConfirmDialogHost />` mounts and behaves exactly as before. Requires
37
+ `@ai-matrx/kit` 0.10.0 or later.
38
+
3
39
  ## 0.15.2 — 2026-09-11
4
40
 
5
41
  Editable append tables retain unsaved drafts by source identity. Returning to the
package/README.md CHANGED
@@ -150,6 +150,12 @@ const pagination = usePaginatedData({ queryKey, initialCursor: 0, loadPage, getR
150
150
  scroll: { mode: "scroll", thresholdPx: 96, intentTimeoutMs: 1200 } }} />;
151
151
  ```
152
152
 
153
+ URL/source owners can control columns without rebuilding the table: pass
154
+ `columnState={{ order, hidden, onChange }}` and `reorderableColumns`. The
155
+ package then commits both the Columns dialog and header drag through `onChange`.
156
+ The dialog provides drag ordering and labelled arrow controls; columns marked
157
+ `hideable: false` remain visible.
158
+
153
159
  Use application-resolved preferences for `scroll`; `manual` always retains Load
154
160
  more. Numbered exact-page sources use `controlled` with `totalItems`; fully loaded
155
161
  client arrays use local mode or `controlled-local`. Partial server results must
@@ -1,5 +1,5 @@
1
1
  import { AgentPayloadInput } from './copy-types.cjs';
2
- import { f as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-XXbVB1sh.cjs';
2
+ import { f as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-4fmLG5AN.cjs';
3
3
  import 'react';
4
4
 
5
5
  /** Human markdown table of the current view (visible columns × rows). */
@@ -1,5 +1,5 @@
1
1
  import { AgentPayloadInput } from './copy-types.js';
2
- import { f as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-ClaxTaXq.js';
2
+ import { f as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-pZ95iFk5.js';
3
3
  import 'react';
4
4
 
5
5
  /** Human markdown table of the current view (visible columns × rows). */
@@ -1,4 +1,4 @@
1
- import { M as MatrxColumnDef, C as ColumnFiltersState, S as SortState, L as LayeredFilterRule, T as TableSearchMatchMode, a as ColumnFilterValue } from '../types-XXbVB1sh.cjs';
1
+ import { M as MatrxColumnDef, C as ColumnFiltersState, S as SortState, L as LayeredFilterRule, T as TableSearchMatchMode, a as ColumnFilterValue } from '../types-4fmLG5AN.cjs';
2
2
  import 'react';
3
3
  import './copy-types.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { M as MatrxColumnDef, C as ColumnFiltersState, S as SortState, L as LayeredFilterRule, T as TableSearchMatchMode, a as ColumnFilterValue } from '../types-ClaxTaXq.js';
1
+ import { M as MatrxColumnDef, C as ColumnFiltersState, S as SortState, L as LayeredFilterRule, T as TableSearchMatchMode, a as ColumnFilterValue } from '../types-pZ95iFk5.js';
2
2
  import 'react';
3
3
  import './copy-types.js';
4
4
 
@@ -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");
@@ -3416,6 +3416,7 @@ function parseOptionalNumber(raw) {
3416
3416
 
3417
3417
  // src/data-table/ColumnManager.tsx
3418
3418
  var import_react9 = require("react");
3419
+ var import_core = require("@dnd-kit/core");
3419
3420
  init_filter_engine();
3420
3421
  var import_jsx_runtime23 = require("react/jsx-runtime");
3421
3422
  function ColumnManager({
@@ -3432,6 +3433,8 @@ function ColumnManager({
3432
3433
  );
3433
3434
  const [draftOrder, setDraftOrder] = (0, import_react9.useState)(order);
3434
3435
  const [draftHidden, setDraftHidden] = (0, import_react9.useState)(() => new Set(hidden));
3436
+ const [draggedColumnId, setDraggedColumnId] = (0, import_react9.useState)(null);
3437
+ const [dropTarget, setDropTarget] = (0, import_react9.useState)(null);
3435
3438
  (0, import_react9.useEffect)(() => {
3436
3439
  if (!open) return;
3437
3440
  setDraftOrder(order);
@@ -3450,6 +3453,45 @@ function ColumnManager({
3450
3453
  return next;
3451
3454
  });
3452
3455
  };
3456
+ const moveTo = (id, targetId) => {
3457
+ if (id === targetId) return;
3458
+ setDraftOrder((current) => {
3459
+ const from = current.indexOf(id);
3460
+ const target = current.indexOf(targetId);
3461
+ if (from < 0 || target < 0) return current;
3462
+ const next = [...current];
3463
+ next.splice(from, 1);
3464
+ const targetAfterRemoval = next.indexOf(targetId);
3465
+ next.splice(targetAfterRemoval, 0, id);
3466
+ return next;
3467
+ });
3468
+ };
3469
+ (0, import_core.useDndMonitor)({
3470
+ onDragStart(event) {
3471
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3472
+ setDraggedColumnId(String(event.active.data.current.columnId));
3473
+ },
3474
+ onDragOver(event) {
3475
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3476
+ const overData = event.over?.data.current;
3477
+ const target = overData?.matrxDragKind === "column-manager" ? overData.columnId : null;
3478
+ setDropTarget(target ? String(target) : null);
3479
+ },
3480
+ onDragCancel(event) {
3481
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3482
+ setDraggedColumnId(null);
3483
+ setDropTarget(null);
3484
+ },
3485
+ onDragEnd(event) {
3486
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3487
+ const dragged = String(event.active.data.current.columnId);
3488
+ const overData = event.over?.data.current;
3489
+ const target = overData?.matrxDragKind === "column-manager" ? overData.columnId : null;
3490
+ setDraggedColumnId(null);
3491
+ setDropTarget(null);
3492
+ if (target) moveTo(dragged, String(target));
3493
+ }
3494
+ });
3453
3495
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Dialog, { open, onOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DialogContent, { className: "max-w-md", children: [
3454
3496
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(DialogHeader, { children: [
3455
3497
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DialogTitle, { children: "Columns" }),
@@ -3462,14 +3504,16 @@ function ColumnManager({
3462
3504
  const hideable = column.hideable !== false;
3463
3505
  const visible = !draftHidden.has(id);
3464
3506
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
3465
- "div",
3507
+ ColumnManagerDropTarget,
3466
3508
  {
3509
+ id,
3467
3510
  className: cn(
3468
3511
  "flex items-center gap-2 px-2 py-2",
3469
- !visible && "bg-muted/30 text-muted-foreground"
3512
+ !visible && "bg-muted/30 text-muted-foreground",
3513
+ dropTarget === id && draggedColumnId !== id && "border-t-2 border-primary"
3470
3514
  ),
3471
3515
  children: [
3472
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(GripVertical, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3516
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ColumnManagerDragHandle, { id, label }),
3473
3517
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3474
3518
  Checkbox,
3475
3519
  {
@@ -3535,6 +3579,36 @@ function ColumnManager({
3535
3579
  ] })
3536
3580
  ] }) });
3537
3581
  }
3582
+ function ColumnManagerDropTarget({
3583
+ id,
3584
+ className,
3585
+ children
3586
+ }) {
3587
+ const { setNodeRef } = (0, import_core.useDroppable)({
3588
+ id: `matrx-column-manager:${id}`,
3589
+ data: { matrxDragKind: "column-manager", columnId: id }
3590
+ });
3591
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: setNodeRef, "data-matrx-column-manager-drop-target": id, className, children });
3592
+ }
3593
+ function ColumnManagerDragHandle({ id, label }) {
3594
+ const { attributes, listeners, setNodeRef } = (0, import_core.useDraggable)({
3595
+ id: `matrx-column-manager:${id}`,
3596
+ data: { matrxDragKind: "column-manager", columnId: id }
3597
+ });
3598
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3599
+ "button",
3600
+ {
3601
+ ref: setNodeRef,
3602
+ type: "button",
3603
+ 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",
3604
+ "aria-label": `Reorder ${label}`,
3605
+ title: `Drag to reorder ${label}`,
3606
+ ...attributes,
3607
+ ...listeners,
3608
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(GripVertical, { className: "h-4 w-4" })
3609
+ }
3610
+ );
3611
+ }
3538
3612
 
3539
3613
  // src/data-table/MatrxUuidCell.tsx
3540
3614
  var import_react10 = require("react");
@@ -5004,6 +5078,9 @@ function safeQueryPage(requestedPage, totalItems, pageSize) {
5004
5078
 
5005
5079
  // src/data-table/MatrxDataTable.tsx
5006
5080
  var import_jsx_runtime32 = require("react/jsx-runtime");
5081
+ function hasSameRowReferences(previous, next) {
5082
+ return previous.length === next.length && previous.every((row, index) => row === next[index]);
5083
+ }
5007
5084
  function TableRowCopyControl({
5008
5085
  copy,
5009
5086
  row,
@@ -5121,6 +5198,9 @@ function MatrxDataTableWithUrlState({
5121
5198
  function MatrxDataTableCore({
5122
5199
  data: suppliedData,
5123
5200
  columns,
5201
+ columnState,
5202
+ reorderableColumns = false,
5203
+ columnManager: controlledColumnManager,
5124
5204
  getRowId,
5125
5205
  searchText,
5126
5206
  processLocalRows,
@@ -5186,8 +5266,8 @@ function MatrxDataTableCore({
5186
5266
  );
5187
5267
  const [internalSearch, setInternalSearch] = (0, import_react14.useState)("");
5188
5268
  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 } })
5269
+ const hierarchySensors = (0, import_core2.useSensors)(
5270
+ (0, import_core2.useSensor)(import_core2.PointerSensor, { activationConstraint: { distance: 6 } })
5191
5271
  );
5192
5272
  const toolbarSearchControlled = toolbar?.searchValue !== void 0;
5193
5273
  const searchValue = controlledQuery ? controlledQuery.state.search : toolbarSearchControlled ? toolbar?.searchValue ?? "" : internalSearch;
@@ -5424,13 +5504,67 @@ function MatrxDataTableCore({
5424
5504
  observer?.disconnect();
5425
5505
  };
5426
5506
  }, [updateScrollHint, data, columns]);
5427
- const [columnOrder, setColumnOrder] = (0, import_react14.useState)(() => columns.map(columnId));
5507
+ const [columnOrder, setColumnOrder] = (0, import_react14.useState)(() => columnState?.order ?? columns.map(columnId));
5428
5508
  const [hiddenColumnIds, setHiddenColumnIds] = (0, import_react14.useState)(
5429
- () => new Set(columns.filter((column) => column.hidden).map(columnId))
5509
+ () => new Set(columnState?.hidden ?? columns.filter((column) => column.hidden).map(columnId))
5430
5510
  );
5431
- const [columnManagerOpen, setColumnManagerOpen] = (0, import_react14.useState)(false);
5511
+ const draggedColumnId = (0, import_react14.useRef)(null);
5512
+ const columnDragActive = (0, import_react14.useRef)(false);
5513
+ const columnDragRaf = (0, import_react14.useRef)(null);
5514
+ const columnDragScrollDirection = (0, import_react14.useRef)(0);
5515
+ const [columnDropTarget, setColumnDropTarget] = (0, import_react14.useState)(null);
5516
+ const stopColumnDragScroll = (0, import_react14.useCallback)(() => {
5517
+ if (columnDragRaf.current !== null) cancelAnimationFrame(columnDragRaf.current);
5518
+ columnDragRaf.current = null;
5519
+ columnDragScrollDirection.current = 0;
5520
+ }, []);
5521
+ (0, import_react14.useEffect)(() => () => {
5522
+ columnDragActive.current = false;
5523
+ stopColumnDragScroll();
5524
+ }, [stopColumnDragScroll]);
5525
+ const startColumnDragScroll = (0, import_react14.useCallback)(() => {
5526
+ if (!columnDragActive.current || columnDragRaf.current !== null) return;
5527
+ const scrollStep = () => {
5528
+ const scroll = scrollRef.current;
5529
+ const direction = columnDragScrollDirection.current;
5530
+ if (!columnDragActive.current || !scroll || direction === 0) {
5531
+ columnDragRaf.current = null;
5532
+ return;
5533
+ }
5534
+ const nextScrollLeft = Math.max(
5535
+ 0,
5536
+ Math.min(scroll.scrollLeft + direction * 18, scroll.scrollWidth - scroll.clientWidth)
5537
+ );
5538
+ if (nextScrollLeft === scroll.scrollLeft) {
5539
+ columnDragRaf.current = null;
5540
+ columnDragScrollDirection.current = 0;
5541
+ return;
5542
+ }
5543
+ scroll.scrollLeft = nextScrollLeft;
5544
+ if (!columnDragActive.current || columnDragScrollDirection.current === 0) {
5545
+ columnDragRaf.current = null;
5546
+ return;
5547
+ }
5548
+ columnDragRaf.current = requestAnimationFrame(scrollStep);
5549
+ };
5550
+ columnDragRaf.current = requestAnimationFrame(scrollStep);
5551
+ }, []);
5552
+ const commitColumnState = (0, import_react14.useCallback)((order, hidden) => {
5553
+ if (columnState) columnState.onChange({ order, hidden: [...hidden] });
5554
+ else {
5555
+ setColumnOrder(order);
5556
+ setHiddenColumnIds(hidden);
5557
+ }
5558
+ }, [columnState]);
5559
+ const [internalColumnManagerOpen, setInternalColumnManagerOpen] = (0, import_react14.useState)(false);
5560
+ const columnManagerOpen = controlledColumnManager?.open ?? internalColumnManagerOpen;
5561
+ const setColumnManagerOpen = controlledColumnManager?.onOpenChange ?? setInternalColumnManagerOpen;
5562
+ const columnIds = (0, import_react14.useMemo)(() => columns.map(columnId), [columns]);
5563
+ 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]);
5564
+ const effectiveHiddenColumnIds = (0, import_react14.useMemo)(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
5432
5565
  (0, import_react14.useEffect)(() => {
5433
5566
  const ids = columns.map(columnId);
5567
+ if (columnState) return;
5434
5568
  setColumnOrder((current) => [
5435
5569
  ...current.filter((id) => ids.includes(id)),
5436
5570
  ...ids.filter((id) => !current.includes(id))
@@ -5442,13 +5576,13 @@ function MatrxDataTableCore({
5442
5576
  }
5443
5577
  return next;
5444
5578
  });
5445
- }, [columns]);
5579
+ }, [columns, columnState]);
5446
5580
  const visibleColumns = (0, import_react14.useMemo)(() => {
5447
5581
  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))
5582
+ return effectiveColumnOrder.map((id) => byId.get(id)).filter(
5583
+ (column) => column !== void 0 && !effectiveHiddenColumnIds.has(columnId(column))
5450
5584
  );
5451
- }, [columns, columnOrder, hiddenColumnIds]);
5585
+ }, [columns, effectiveColumnOrder, effectiveHiddenColumnIds]);
5452
5586
  const copySubsetVariant = useCopySubsetVariant();
5453
5587
  const copySubsetColumns = () => visibleColumns.filter((c) => c.filter !== false || c.accessorKey || c.accessorFn).map((c) => ({
5454
5588
  id: columnId(c),
@@ -5522,8 +5656,15 @@ function MatrxDataTableCore({
5522
5656
  sourceOwnsColumnFilters,
5523
5657
  sourceOwnsSort
5524
5658
  ]);
5659
+ const lastNotifiedViewRows = (0, import_react14.useRef)(null);
5525
5660
  (0, import_react14.useEffect)(() => {
5526
- onViewChange?.(processed);
5661
+ if (!onViewChange) {
5662
+ lastNotifiedViewRows.current = null;
5663
+ return;
5664
+ }
5665
+ const changed = lastNotifiedViewRows.current === null || !hasSameRowReferences(lastNotifiedViewRows.current, processed);
5666
+ lastNotifiedViewRows.current = processed;
5667
+ if (changed) onViewChange(processed);
5527
5668
  }, [processed, onViewChange]);
5528
5669
  (0, import_react14.useEffect)(() => {
5529
5670
  if (controlledQuery) return;
@@ -5953,16 +6094,77 @@ function MatrxDataTableCore({
5953
6094
  } : facet
5954
6095
  ) : toolbar?.facets;
5955
6096
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
5956
- import_core.DndContext,
6097
+ import_core2.DndContext,
5957
6098
  {
5958
6099
  sensors: hierarchySensors,
5959
- collisionDetection: import_core.pointerWithin,
5960
- measuring: { droppable: { strategy: import_core.MeasuringStrategy.Always } },
6100
+ collisionDetection: import_core2.pointerWithin,
6101
+ measuring: { droppable: { strategy: import_core2.MeasuringStrategy.Always } },
5961
6102
  onDragStart: (event) => {
6103
+ const kind = event.active.data.current?.matrxDragKind;
6104
+ if (kind === "column-header") {
6105
+ draggedColumnId.current = String(event.active.data.current?.columnId);
6106
+ columnDragActive.current = true;
6107
+ return;
6108
+ }
6109
+ if (kind === "column-manager") return;
5962
6110
  setDraggedRowId(String(event.active.id));
5963
6111
  },
5964
- onDragCancel: () => setDraggedRowId(null),
5965
- onDragEnd: endHierarchyDrag,
6112
+ onDragMove: (event) => {
6113
+ if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
6114
+ const overData = event.over?.data.current;
6115
+ const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
6116
+ const activeRect = event.active.rect.current.translated;
6117
+ const overRect = event.over?.rect;
6118
+ if (!overId || !activeRect || !overRect) {
6119
+ setColumnDropTarget(null);
6120
+ columnDragScrollDirection.current = 0;
6121
+ stopColumnDragScroll();
6122
+ return;
6123
+ }
6124
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
6125
+ setColumnDropTarget({ id: String(overId), after });
6126
+ const scroll = scrollRef.current;
6127
+ if (!scroll) return;
6128
+ const bounds = scroll.getBoundingClientRect();
6129
+ const center = activeRect.left + activeRect.width / 2;
6130
+ columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
6131
+ if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
6132
+ else startColumnDragScroll();
6133
+ },
6134
+ onDragCancel: () => {
6135
+ columnDragActive.current = false;
6136
+ draggedColumnId.current = null;
6137
+ setColumnDropTarget(null);
6138
+ stopColumnDragScroll();
6139
+ setDraggedRowId(null);
6140
+ },
6141
+ onDragEnd: (event) => {
6142
+ const kind = event.active.data.current?.matrxDragKind;
6143
+ if (kind === "column-manager") return;
6144
+ if (kind !== "column-header") {
6145
+ endHierarchyDrag(event);
6146
+ return;
6147
+ }
6148
+ const from = String(event.active.data.current?.columnId);
6149
+ const targetData = event.over?.data.current;
6150
+ const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
6151
+ const activeRect = event.active.rect.current.translated;
6152
+ const overRect = event.over?.rect;
6153
+ columnDragActive.current = false;
6154
+ draggedColumnId.current = null;
6155
+ setColumnDropTarget(null);
6156
+ stopColumnDragScroll();
6157
+ if (!target || from === target || !activeRect || !overRect) return;
6158
+ const order = [...effectiveColumnOrder];
6159
+ const fromIndex = order.indexOf(from);
6160
+ const targetIndex = order.indexOf(String(target));
6161
+ if (fromIndex < 0 || targetIndex < 0) return;
6162
+ order.splice(fromIndex, 1);
6163
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
6164
+ const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
6165
+ order.splice(insertion, 0, from);
6166
+ commitColumnState(order, effectiveHiddenColumnIds);
6167
+ },
5966
6168
  children: [
5967
6169
  /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
5968
6170
  "div",
@@ -6184,8 +6386,8 @@ function MatrxDataTableCore({
6184
6386
  onClick: () => setColumnManagerOpen(true),
6185
6387
  children: [
6186
6388
  /* @__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,
6389
+ effectiveHiddenColumnIds.size > 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "sr-only", children: [
6390
+ effectiveHiddenColumnIds.size,
6189
6391
  " hidden"
6190
6392
  ] }) : null
6191
6393
  ]
@@ -6439,46 +6641,50 @@ function MatrxDataTableCore({
6439
6641
  // horizontal-scroll surface.
6440
6642
  col.width !== void 0 && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
6441
6643
  col.headerClassName,
6644
+ columnDropTarget?.id === id && (columnDropTarget.after ? "border-r-2 border-primary" : "border-l-2 border-primary"),
6442
6645
  col.align === "center" && "text-center",
6443
6646
  col.align === "right" && "text-right"
6444
6647
  ),
6445
6648
  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
- )
6649
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: [
6650
+ reorderableColumns ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ColumnReorderHandle, { id, label: headerLabel }) : null,
6651
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6652
+ ColumnHeaderCell,
6653
+ {
6654
+ label: col.header,
6655
+ labelText: headerLabel,
6656
+ sortable: col.sortable !== false,
6657
+ isSorted: Boolean(isSorted),
6658
+ sortDirection: sort?.direction ?? defaultSortDirection,
6659
+ onSortAsc: () => setSort({ id, direction: "asc" }),
6660
+ onSortDesc: () => setSort({ id, direction: "desc" }),
6661
+ onClearSort: () => setSort(null),
6662
+ onHeaderSortClick: () => {
6663
+ if (!isSorted) {
6664
+ setSort({ id, direction: defaultSortDirection });
6665
+ return;
6666
+ }
6667
+ if (sort?.direction === "asc") {
6668
+ setSort({ id, direction: "desc" });
6669
+ return;
6670
+ }
6671
+ setSort(null);
6672
+ },
6673
+ filterKind: meta?.kind ?? null,
6674
+ filterValue: columnFilters[id],
6675
+ onFilterChange: (next) => setColumnFilter(id, next),
6676
+ selectOptions: meta?.options,
6677
+ selectSingle: col.filterSingle,
6678
+ filterApplyLabel: filterUi?.applyLabel,
6679
+ filterShowCancel: filterUi?.showCancel,
6680
+ filterFooterHint: filterUi?.footerHint,
6681
+ allowNegation: filterUi?.allowNegation,
6682
+ allowNullModes: filterUi?.allowNullModes,
6683
+ align: col.align,
6684
+ compact: col.compact
6685
+ }
6686
+ )
6687
+ ] })
6482
6688
  },
6483
6689
  id
6484
6690
  );
@@ -6777,12 +6983,9 @@ function MatrxDataTableCore({
6777
6983
  open: columnManagerOpen,
6778
6984
  onOpenChange: setColumnManagerOpen,
6779
6985
  columns,
6780
- order: columnOrder,
6781
- hidden: hiddenColumnIds,
6782
- onApply: (order, hidden) => {
6783
- setColumnOrder(order);
6784
- setHiddenColumnIds(hidden);
6785
- }
6986
+ order: effectiveColumnOrder,
6987
+ hidden: effectiveHiddenColumnIds,
6988
+ onApply: commitColumnState
6786
6989
  }
6787
6990
  ),
6788
6991
  detailEnabled && selectedRow ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
@@ -6981,7 +7184,7 @@ function MatrxDataTableCore({
6981
7184
  ]
6982
7185
  }
6983
7186
  ),
6984
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_core.DragOverlay, { children: draggedRow ? (
7187
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_core2.DragOverlay, { children: draggedRow ? (
6985
7188
  // w-max: the overlay inherits the ACTIVE node's size, and the active
6986
7189
  // node is the 16px grip handle — without it the label wraps to one
6987
7190
  // character per line.
@@ -6996,7 +7199,7 @@ function HierarchyDragHandle({
6996
7199
  label,
6997
7200
  disabled
6998
7201
  }) {
6999
- const { attributes, listeners, setNodeRef } = (0, import_core.useDraggable)({
7202
+ const { attributes, listeners, setNodeRef } = (0, import_core2.useDraggable)({
7000
7203
  id,
7001
7204
  disabled
7002
7205
  });
@@ -7020,7 +7223,7 @@ function HierarchyRootDropTarget({
7020
7223
  label,
7021
7224
  draggedLabel
7022
7225
  }) {
7023
- const { isOver, setNodeRef } = (0, import_core.useDroppable)({ id: "__root__" });
7226
+ const { isOver, setNodeRef } = (0, import_core2.useDroppable)({ id: "__root__" });
7024
7227
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
7025
7228
  "div",
7026
7229
  {
@@ -7044,7 +7247,7 @@ function HierarchyDragIndicatorLayer({
7044
7247
  const [indicator, setIndicator] = (0, import_react14.useState)(
7045
7248
  null
7046
7249
  );
7047
- (0, import_core.useDndMonitor)({
7250
+ (0, import_core2.useDndMonitor)({
7048
7251
  onDragMove(event) {
7049
7252
  const container = scrollRef.current;
7050
7253
  const overId = event.over ? String(event.over.id) : null;
@@ -7144,7 +7347,7 @@ function HierarchyDroppableRow({
7144
7347
  disabled,
7145
7348
  children
7146
7349
  }) {
7147
- const { setNodeRef } = (0, import_core.useDroppable)({ id, disabled });
7350
+ const { setNodeRef } = (0, import_core2.useDroppable)({ id, disabled });
7148
7351
  if (!(0, import_react14.isValidElement)(children) || children.type !== "tr") return children;
7149
7352
  const row = children;
7150
7353
  return (0, import_react14.cloneElement)(row, {
@@ -7152,6 +7355,38 @@ function HierarchyDroppableRow({
7152
7355
  className: row.props.className
7153
7356
  });
7154
7357
  }
7358
+ function ColumnHeaderDropZone({
7359
+ id,
7360
+ enabled,
7361
+ children
7362
+ }) {
7363
+ const { setNodeRef } = (0, import_core2.useDroppable)({
7364
+ id: `matrx-column-header:${id}`,
7365
+ disabled: !enabled,
7366
+ data: { matrxDragKind: "column-header", columnId: id }
7367
+ });
7368
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref: setNodeRef, className: "flex min-w-0 items-center", children });
7369
+ }
7370
+ function ColumnReorderHandle({ id, label }) {
7371
+ const { attributes, listeners, setNodeRef } = (0, import_core2.useDraggable)({
7372
+ id: `matrx-column-header:${id}`,
7373
+ data: { matrxDragKind: "column-header", columnId: id }
7374
+ });
7375
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
7376
+ "button",
7377
+ {
7378
+ ref: setNodeRef,
7379
+ type: "button",
7380
+ 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",
7381
+ "aria-label": `Reorder ${label}`,
7382
+ title: `Drag to reorder ${label}`,
7383
+ onClick: (event) => event.stopPropagation(),
7384
+ ...attributes,
7385
+ ...listeners,
7386
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(GripVertical, { className: "h-3.5 w-3.5" })
7387
+ }
7388
+ );
7389
+ }
7155
7390
  function columnWidthVar(width) {
7156
7391
  if (width === void 0) return void 0;
7157
7392
  return {