@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
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { b as MatrxDataTableProps } from '../types-XXbVB1sh.cjs';
3
- export { A as AnyOfColumnSearch, c as CellEditType, d as CellEditsMap, e as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, M as MatrxColumnDef, f as MatrxDataTableCopyConfig, g as MatrxDataTableDetailConfig, h as MatrxDataTableEditConfig, i as MatrxDataTableEmptyState, j as MatrxDataTableFilterUi, k as MatrxDataTableHierarchyConfig, l as MatrxDataTableHierarchyMove, m as MatrxDataTableMobileCardControls, n as MatrxDataTableQueryControl, o as MatrxDataTableQueryState, p as MatrxDataTableRecordControls, q as MatrxDataTableSelectionConfig, r as MatrxDataTableSourceProcessing, s as MatrxDataTableToolbar, t as MatrxDataTableUrlStateConfig, u as MatrxDataTableWindowConfig, v as MatrxTablePagination, w as SortDirection, S as SortState, T as TableSearchMatchMode, x as TextFilterMode, y as ToolbarFacet } from '../types-XXbVB1sh.cjs';
2
+ import { b as MatrxDataTableProps } from '../types-4fmLG5AN.cjs';
3
+ export { A as AnyOfColumnSearch, c as CellEditType, d as CellEditsMap, e as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, M as MatrxColumnDef, f as MatrxDataTableCopyConfig, g as MatrxDataTableDetailConfig, h as MatrxDataTableEditConfig, i as MatrxDataTableEmptyState, j as MatrxDataTableFilterUi, k as MatrxDataTableHierarchyConfig, l as MatrxDataTableHierarchyMove, m as MatrxDataTableMobileCardControls, n as MatrxDataTableQueryControl, o as MatrxDataTableQueryState, p as MatrxDataTableRecordControls, q as MatrxDataTableSelectionConfig, r as MatrxDataTableSourceProcessing, s as MatrxDataTableToolbar, t as MatrxDataTableUrlStateConfig, u as MatrxDataTableWindowConfig, v as MatrxTablePagination, w as SortDirection, S as SortState, T as TableSearchMatchMode, x as TextFilterMode, y as ToolbarFacet } from '../types-4fmLG5AN.cjs';
4
4
  export { MatrxDataTableHost, MatrxDataTableProvider, TableDoors, TableEntityRefProps, TableLinkProps, TablePeekProps, TableSidePanelProps, TableWindowPanelProps, useMatrxDataTableHost } from './host.cjs';
5
5
  export { DataRowInspector } from './data-row-inspector.cjs';
6
6
  export { DataRowWindow, DataRowWindowProps, DataRowWindowTab } from './data-row-window.cjs';
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { b as MatrxDataTableProps } from '../types-ClaxTaXq.js';
3
- export { A as AnyOfColumnSearch, c as CellEditType, d as CellEditsMap, e as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, M as MatrxColumnDef, f as MatrxDataTableCopyConfig, g as MatrxDataTableDetailConfig, h as MatrxDataTableEditConfig, i as MatrxDataTableEmptyState, j as MatrxDataTableFilterUi, k as MatrxDataTableHierarchyConfig, l as MatrxDataTableHierarchyMove, m as MatrxDataTableMobileCardControls, n as MatrxDataTableQueryControl, o as MatrxDataTableQueryState, p as MatrxDataTableRecordControls, q as MatrxDataTableSelectionConfig, r as MatrxDataTableSourceProcessing, s as MatrxDataTableToolbar, t as MatrxDataTableUrlStateConfig, u as MatrxDataTableWindowConfig, v as MatrxTablePagination, w as SortDirection, S as SortState, T as TableSearchMatchMode, x as TextFilterMode, y as ToolbarFacet } from '../types-ClaxTaXq.js';
2
+ import { b as MatrxDataTableProps } from '../types-pZ95iFk5.js';
3
+ export { A as AnyOfColumnSearch, c as CellEditType, d as CellEditsMap, e as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, M as MatrxColumnDef, f as MatrxDataTableCopyConfig, g as MatrxDataTableDetailConfig, h as MatrxDataTableEditConfig, i as MatrxDataTableEmptyState, j as MatrxDataTableFilterUi, k as MatrxDataTableHierarchyConfig, l as MatrxDataTableHierarchyMove, m as MatrxDataTableMobileCardControls, n as MatrxDataTableQueryControl, o as MatrxDataTableQueryState, p as MatrxDataTableRecordControls, q as MatrxDataTableSelectionConfig, r as MatrxDataTableSourceProcessing, s as MatrxDataTableToolbar, t as MatrxDataTableUrlStateConfig, u as MatrxDataTableWindowConfig, v as MatrxTablePagination, w as SortDirection, S as SortState, T as TableSearchMatchMode, x as TextFilterMode, y as ToolbarFacet } from '../types-pZ95iFk5.js';
4
4
  export { MatrxDataTableHost, MatrxDataTableProvider, TableDoors, TableEntityRefProps, TableLinkProps, TablePeekProps, TableSidePanelProps, TableWindowPanelProps, useMatrxDataTableHost } from './host.js';
5
5
  export { DataRowInspector } from './data-row-inspector.js';
6
6
  export { DataRowWindow, DataRowWindowProps, DataRowWindowTab } from './data-row-window.js';
@@ -425,9 +425,9 @@ import {
425
425
  MeasuringStrategy,
426
426
  PointerSensor,
427
427
  pointerWithin,
428
- useDndMonitor,
429
- useDraggable,
430
- useDroppable,
428
+ useDndMonitor as useDndMonitor2,
429
+ useDraggable as useDraggable2,
430
+ useDroppable as useDroppable2,
431
431
  useSensor,
432
432
  useSensors
433
433
  } from "@dnd-kit/core";
@@ -1167,6 +1167,7 @@ function useScrollPagination({
1167
1167
  const inFlightGenerationRef = useRef(null);
1168
1168
  const requestErrorRef = useRef(null);
1169
1169
  const mountedRef = useRef(false);
1170
+ const observedTopRef = useRef(/* @__PURE__ */ new WeakMap());
1170
1171
  const effectiveError = error ?? requestError;
1171
1172
  useEffect(() => {
1172
1173
  mountedRef.current = true;
@@ -1200,6 +1201,7 @@ function useScrollPagination({
1200
1201
  };
1201
1202
  useEffect(() => {
1202
1203
  intentRef.current = null;
1204
+ observedTopRef.current = /* @__PURE__ */ new WeakMap();
1203
1205
  consumedSequenceRef.current = inputSequenceRef.current;
1204
1206
  requestErrorRef.current = null;
1205
1207
  setRequestError(null);
@@ -1209,14 +1211,18 @@ function useScrollPagination({
1209
1211
  useEffect(() => {
1210
1212
  if (mode !== "scroll" || !enabled || typeof window === "undefined") return;
1211
1213
  const attachable = containers.map(resolveContainer).filter((container) => container !== null);
1212
- const setIntent = (container) => {
1214
+ const setIntent = (container, startTop) => {
1213
1215
  const current = latestRef.current;
1214
1216
  if (!isVisibleContainer(container) || current.loading || current.isFetchingNextPage || inFlightGenerationRef.current !== null) return;
1215
1217
  inputSequenceRef.current += 1;
1216
1218
  intentRef.current = {
1217
1219
  queryKey: latestRef.current.queryKey,
1218
1220
  container,
1219
- startTop: container.scrollTop,
1221
+ // The compositor can advance downward before the passive input
1222
+ // listener, while DOM/test callers can move upward before its scroll
1223
+ // notification. Either way, the lower position is the only safe
1224
+ // baseline for proving that this downward intent caused movement.
1225
+ startTop: Math.min(startTop, container.scrollTop),
1220
1226
  sequence: inputSequenceRef.current,
1221
1227
  expiresAt: Date.now() + intentTimeoutMs
1222
1228
  };
@@ -1231,9 +1237,11 @@ function useScrollPagination({
1231
1237
  void requestNextPage();
1232
1238
  };
1233
1239
  const cleanups = attachable.map((container) => {
1240
+ if (!observedTopRef.current.has(container)) observedTopRef.current.set(container, container.scrollTop);
1241
+ const lastObservedTop = () => observedTopRef.current.get(container) ?? container.scrollTop;
1234
1242
  const onWheel = (event) => {
1235
1243
  if (event.deltaY > 0 && Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
1236
- setIntent(container);
1244
+ setIntent(container, lastObservedTop());
1237
1245
  }
1238
1246
  };
1239
1247
  const onTouchStart = (event) => {
@@ -1244,19 +1252,22 @@ function useScrollPagination({
1244
1252
  const touch = event.touches.item(0);
1245
1253
  const startY = Number(container.dataset.matrxPaginationTouchY);
1246
1254
  if (touch && Number.isFinite(startY) && startY - touch.clientY > 0) {
1247
- setIntent(container);
1255
+ setIntent(container, lastObservedTop());
1248
1256
  }
1249
1257
  };
1250
1258
  const onTouchEnd = () => {
1251
1259
  delete container.dataset.matrxPaginationTouchY;
1252
1260
  };
1253
1261
  const onKeyDown = (event) => {
1254
- if (isDownwardKeyboardInput(event)) setIntent(container);
1262
+ if (isDownwardKeyboardInput(event)) setIntent(container, lastObservedTop());
1255
1263
  };
1256
1264
  const onPointerDown = (event) => {
1257
- if (isNativeScrollbarPointer(event, container)) setIntent(container);
1265
+ if (isNativeScrollbarPointer(event, container)) setIntent(container, lastObservedTop());
1266
+ };
1267
+ const onScroll = () => {
1268
+ handleScroll(container);
1269
+ observedTopRef.current.set(container, container.scrollTop);
1258
1270
  };
1259
- const onScroll = () => handleScroll(container);
1260
1271
  container.addEventListener("wheel", onWheel, { passive: true });
1261
1272
  container.addEventListener("touchstart", onTouchStart, { passive: true });
1262
1273
  container.addEventListener("touchmove", onTouchMove, { passive: true });
@@ -3393,6 +3404,7 @@ function parseOptionalNumber(raw) {
3393
3404
 
3394
3405
  // src/data-table/ColumnManager.tsx
3395
3406
  import { useEffect as useEffect6, useMemo as useMemo3, useState as useState9 } from "react";
3407
+ import { useDndMonitor, useDraggable, useDroppable } from "@dnd-kit/core";
3396
3408
  init_filter_engine();
3397
3409
  import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
3398
3410
  function ColumnManager({
@@ -3409,6 +3421,8 @@ function ColumnManager({
3409
3421
  );
3410
3422
  const [draftOrder, setDraftOrder] = useState9(order);
3411
3423
  const [draftHidden, setDraftHidden] = useState9(() => new Set(hidden));
3424
+ const [draggedColumnId, setDraggedColumnId] = useState9(null);
3425
+ const [dropTarget, setDropTarget] = useState9(null);
3412
3426
  useEffect6(() => {
3413
3427
  if (!open) return;
3414
3428
  setDraftOrder(order);
@@ -3427,6 +3441,45 @@ function ColumnManager({
3427
3441
  return next;
3428
3442
  });
3429
3443
  };
3444
+ const moveTo = (id, targetId) => {
3445
+ if (id === targetId) return;
3446
+ setDraftOrder((current) => {
3447
+ const from = current.indexOf(id);
3448
+ const target = current.indexOf(targetId);
3449
+ if (from < 0 || target < 0) return current;
3450
+ const next = [...current];
3451
+ next.splice(from, 1);
3452
+ const targetAfterRemoval = next.indexOf(targetId);
3453
+ next.splice(targetAfterRemoval, 0, id);
3454
+ return next;
3455
+ });
3456
+ };
3457
+ useDndMonitor({
3458
+ onDragStart(event) {
3459
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3460
+ setDraggedColumnId(String(event.active.data.current.columnId));
3461
+ },
3462
+ onDragOver(event) {
3463
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3464
+ const overData = event.over?.data.current;
3465
+ const target = overData?.matrxDragKind === "column-manager" ? overData.columnId : null;
3466
+ setDropTarget(target ? String(target) : null);
3467
+ },
3468
+ onDragCancel(event) {
3469
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3470
+ setDraggedColumnId(null);
3471
+ setDropTarget(null);
3472
+ },
3473
+ onDragEnd(event) {
3474
+ if (event.active.data.current?.matrxDragKind !== "column-manager") return;
3475
+ const dragged = String(event.active.data.current.columnId);
3476
+ const overData = event.over?.data.current;
3477
+ const target = overData?.matrxDragKind === "column-manager" ? overData.columnId : null;
3478
+ setDraggedColumnId(null);
3479
+ setDropTarget(null);
3480
+ if (target) moveTo(dragged, String(target));
3481
+ }
3482
+ });
3430
3483
  return /* @__PURE__ */ jsx23(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs16(DialogContent, { className: "max-w-md", children: [
3431
3484
  /* @__PURE__ */ jsxs16(DialogHeader, { children: [
3432
3485
  /* @__PURE__ */ jsx23(DialogTitle, { children: "Columns" }),
@@ -3439,14 +3492,16 @@ function ColumnManager({
3439
3492
  const hideable = column.hideable !== false;
3440
3493
  const visible = !draftHidden.has(id);
3441
3494
  return /* @__PURE__ */ jsxs16(
3442
- "div",
3495
+ ColumnManagerDropTarget,
3443
3496
  {
3497
+ id,
3444
3498
  className: cn(
3445
3499
  "flex items-center gap-2 px-2 py-2",
3446
- !visible && "bg-muted/30 text-muted-foreground"
3500
+ !visible && "bg-muted/30 text-muted-foreground",
3501
+ dropTarget === id && draggedColumnId !== id && "border-t-2 border-primary"
3447
3502
  ),
3448
3503
  children: [
3449
- /* @__PURE__ */ jsx23(GripVertical, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3504
+ /* @__PURE__ */ jsx23(ColumnManagerDragHandle, { id, label }),
3450
3505
  /* @__PURE__ */ jsx23(
3451
3506
  Checkbox,
3452
3507
  {
@@ -3512,6 +3567,36 @@ function ColumnManager({
3512
3567
  ] })
3513
3568
  ] }) });
3514
3569
  }
3570
+ function ColumnManagerDropTarget({
3571
+ id,
3572
+ className,
3573
+ children
3574
+ }) {
3575
+ const { setNodeRef } = useDroppable({
3576
+ id: `matrx-column-manager:${id}`,
3577
+ data: { matrxDragKind: "column-manager", columnId: id }
3578
+ });
3579
+ return /* @__PURE__ */ jsx23("div", { ref: setNodeRef, "data-matrx-column-manager-drop-target": id, className, children });
3580
+ }
3581
+ function ColumnManagerDragHandle({ id, label }) {
3582
+ const { attributes, listeners, setNodeRef } = useDraggable({
3583
+ id: `matrx-column-manager:${id}`,
3584
+ data: { matrxDragKind: "column-manager", columnId: id }
3585
+ });
3586
+ return /* @__PURE__ */ jsx23(
3587
+ "button",
3588
+ {
3589
+ ref: setNodeRef,
3590
+ type: "button",
3591
+ 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",
3592
+ "aria-label": `Reorder ${label}`,
3593
+ title: `Drag to reorder ${label}`,
3594
+ ...attributes,
3595
+ ...listeners,
3596
+ children: /* @__PURE__ */ jsx23(GripVertical, { className: "h-4 w-4" })
3597
+ }
3598
+ );
3599
+ }
3515
3600
 
3516
3601
  // src/data-table/MatrxUuidCell.tsx
3517
3602
  import {
@@ -4985,6 +5070,9 @@ function safeQueryPage(requestedPage, totalItems, pageSize) {
4985
5070
 
4986
5071
  // src/data-table/MatrxDataTable.tsx
4987
5072
  import { Fragment as Fragment5, jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
5073
+ function hasSameRowReferences(previous, next) {
5074
+ return previous.length === next.length && previous.every((row, index) => row === next[index]);
5075
+ }
4988
5076
  function TableRowCopyControl({
4989
5077
  copy,
4990
5078
  row,
@@ -5102,6 +5190,9 @@ function MatrxDataTableWithUrlState({
5102
5190
  function MatrxDataTableCore({
5103
5191
  data: suppliedData,
5104
5192
  columns,
5193
+ columnState,
5194
+ reorderableColumns = false,
5195
+ columnManager: controlledColumnManager,
5105
5196
  getRowId,
5106
5197
  searchText,
5107
5198
  processLocalRows,
@@ -5405,13 +5496,67 @@ function MatrxDataTableCore({
5405
5496
  observer?.disconnect();
5406
5497
  };
5407
5498
  }, [updateScrollHint, data, columns]);
5408
- const [columnOrder, setColumnOrder] = useState14(() => columns.map(columnId));
5499
+ const [columnOrder, setColumnOrder] = useState14(() => columnState?.order ?? columns.map(columnId));
5409
5500
  const [hiddenColumnIds, setHiddenColumnIds] = useState14(
5410
- () => new Set(columns.filter((column) => column.hidden).map(columnId))
5501
+ () => new Set(columnState?.hidden ?? columns.filter((column) => column.hidden).map(columnId))
5411
5502
  );
5412
- const [columnManagerOpen, setColumnManagerOpen] = useState14(false);
5503
+ const draggedColumnId = useRef4(null);
5504
+ const columnDragActive = useRef4(false);
5505
+ const columnDragRaf = useRef4(null);
5506
+ const columnDragScrollDirection = useRef4(0);
5507
+ const [columnDropTarget, setColumnDropTarget] = useState14(null);
5508
+ const stopColumnDragScroll = useCallback3(() => {
5509
+ if (columnDragRaf.current !== null) cancelAnimationFrame(columnDragRaf.current);
5510
+ columnDragRaf.current = null;
5511
+ columnDragScrollDirection.current = 0;
5512
+ }, []);
5513
+ useEffect9(() => () => {
5514
+ columnDragActive.current = false;
5515
+ stopColumnDragScroll();
5516
+ }, [stopColumnDragScroll]);
5517
+ const startColumnDragScroll = useCallback3(() => {
5518
+ if (!columnDragActive.current || columnDragRaf.current !== null) return;
5519
+ const scrollStep = () => {
5520
+ const scroll = scrollRef.current;
5521
+ const direction = columnDragScrollDirection.current;
5522
+ if (!columnDragActive.current || !scroll || direction === 0) {
5523
+ columnDragRaf.current = null;
5524
+ return;
5525
+ }
5526
+ const nextScrollLeft = Math.max(
5527
+ 0,
5528
+ Math.min(scroll.scrollLeft + direction * 18, scroll.scrollWidth - scroll.clientWidth)
5529
+ );
5530
+ if (nextScrollLeft === scroll.scrollLeft) {
5531
+ columnDragRaf.current = null;
5532
+ columnDragScrollDirection.current = 0;
5533
+ return;
5534
+ }
5535
+ scroll.scrollLeft = nextScrollLeft;
5536
+ if (!columnDragActive.current || columnDragScrollDirection.current === 0) {
5537
+ columnDragRaf.current = null;
5538
+ return;
5539
+ }
5540
+ columnDragRaf.current = requestAnimationFrame(scrollStep);
5541
+ };
5542
+ columnDragRaf.current = requestAnimationFrame(scrollStep);
5543
+ }, []);
5544
+ const commitColumnState = useCallback3((order, hidden) => {
5545
+ if (columnState) columnState.onChange({ order, hidden: [...hidden] });
5546
+ else {
5547
+ setColumnOrder(order);
5548
+ setHiddenColumnIds(hidden);
5549
+ }
5550
+ }, [columnState]);
5551
+ const [internalColumnManagerOpen, setInternalColumnManagerOpen] = useState14(false);
5552
+ const columnManagerOpen = controlledColumnManager?.open ?? internalColumnManagerOpen;
5553
+ const setColumnManagerOpen = controlledColumnManager?.onOpenChange ?? setInternalColumnManagerOpen;
5554
+ const columnIds = useMemo4(() => columns.map(columnId), [columns]);
5555
+ const effectiveColumnOrder = useMemo4(() => 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]);
5556
+ const effectiveHiddenColumnIds = useMemo4(() => columnState ? new Set(columnState.hidden.filter((id) => columnIds.includes(id))) : hiddenColumnIds, [columnIds, columnState, hiddenColumnIds]);
5413
5557
  useEffect9(() => {
5414
5558
  const ids = columns.map(columnId);
5559
+ if (columnState) return;
5415
5560
  setColumnOrder((current) => [
5416
5561
  ...current.filter((id) => ids.includes(id)),
5417
5562
  ...ids.filter((id) => !current.includes(id))
@@ -5423,13 +5568,13 @@ function MatrxDataTableCore({
5423
5568
  }
5424
5569
  return next;
5425
5570
  });
5426
- }, [columns]);
5571
+ }, [columns, columnState]);
5427
5572
  const visibleColumns = useMemo4(() => {
5428
5573
  const byId = new Map(columns.map((column) => [columnId(column), column]));
5429
- return columnOrder.map((id) => byId.get(id)).filter(
5430
- (column) => column !== void 0 && !hiddenColumnIds.has(columnId(column))
5574
+ return effectiveColumnOrder.map((id) => byId.get(id)).filter(
5575
+ (column) => column !== void 0 && !effectiveHiddenColumnIds.has(columnId(column))
5431
5576
  );
5432
- }, [columns, columnOrder, hiddenColumnIds]);
5577
+ }, [columns, effectiveColumnOrder, effectiveHiddenColumnIds]);
5433
5578
  const copySubsetVariant = useCopySubsetVariant();
5434
5579
  const copySubsetColumns = () => visibleColumns.filter((c) => c.filter !== false || c.accessorKey || c.accessorFn).map((c) => ({
5435
5580
  id: columnId(c),
@@ -5503,8 +5648,15 @@ function MatrxDataTableCore({
5503
5648
  sourceOwnsColumnFilters,
5504
5649
  sourceOwnsSort
5505
5650
  ]);
5651
+ const lastNotifiedViewRows = useRef4(null);
5506
5652
  useEffect9(() => {
5507
- onViewChange?.(processed);
5653
+ if (!onViewChange) {
5654
+ lastNotifiedViewRows.current = null;
5655
+ return;
5656
+ }
5657
+ const changed = lastNotifiedViewRows.current === null || !hasSameRowReferences(lastNotifiedViewRows.current, processed);
5658
+ lastNotifiedViewRows.current = processed;
5659
+ if (changed) onViewChange(processed);
5508
5660
  }, [processed, onViewChange]);
5509
5661
  useEffect9(() => {
5510
5662
  if (controlledQuery) return;
@@ -5940,10 +6092,71 @@ function MatrxDataTableCore({
5940
6092
  collisionDetection: pointerWithin,
5941
6093
  measuring: { droppable: { strategy: MeasuringStrategy.Always } },
5942
6094
  onDragStart: (event) => {
6095
+ const kind = event.active.data.current?.matrxDragKind;
6096
+ if (kind === "column-header") {
6097
+ draggedColumnId.current = String(event.active.data.current?.columnId);
6098
+ columnDragActive.current = true;
6099
+ return;
6100
+ }
6101
+ if (kind === "column-manager") return;
5943
6102
  setDraggedRowId(String(event.active.id));
5944
6103
  },
5945
- onDragCancel: () => setDraggedRowId(null),
5946
- onDragEnd: endHierarchyDrag,
6104
+ onDragMove: (event) => {
6105
+ if (!columnDragActive.current || event.active.data.current?.matrxDragKind !== "column-header") return;
6106
+ const overData = event.over?.data.current;
6107
+ const overId = overData?.matrxDragKind === "column-header" ? overData.columnId : null;
6108
+ const activeRect = event.active.rect.current.translated;
6109
+ const overRect = event.over?.rect;
6110
+ if (!overId || !activeRect || !overRect) {
6111
+ setColumnDropTarget(null);
6112
+ columnDragScrollDirection.current = 0;
6113
+ stopColumnDragScroll();
6114
+ return;
6115
+ }
6116
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
6117
+ setColumnDropTarget({ id: String(overId), after });
6118
+ const scroll = scrollRef.current;
6119
+ if (!scroll) return;
6120
+ const bounds = scroll.getBoundingClientRect();
6121
+ const center = activeRect.left + activeRect.width / 2;
6122
+ columnDragScrollDirection.current = center - bounds.left < 80 ? -1 : bounds.right - center < 80 ? 1 : 0;
6123
+ if (columnDragScrollDirection.current === 0) stopColumnDragScroll();
6124
+ else startColumnDragScroll();
6125
+ },
6126
+ onDragCancel: () => {
6127
+ columnDragActive.current = false;
6128
+ draggedColumnId.current = null;
6129
+ setColumnDropTarget(null);
6130
+ stopColumnDragScroll();
6131
+ setDraggedRowId(null);
6132
+ },
6133
+ onDragEnd: (event) => {
6134
+ const kind = event.active.data.current?.matrxDragKind;
6135
+ if (kind === "column-manager") return;
6136
+ if (kind !== "column-header") {
6137
+ endHierarchyDrag(event);
6138
+ return;
6139
+ }
6140
+ const from = String(event.active.data.current?.columnId);
6141
+ const targetData = event.over?.data.current;
6142
+ const target = targetData?.matrxDragKind === "column-header" ? targetData.columnId : null;
6143
+ const activeRect = event.active.rect.current.translated;
6144
+ const overRect = event.over?.rect;
6145
+ columnDragActive.current = false;
6146
+ draggedColumnId.current = null;
6147
+ setColumnDropTarget(null);
6148
+ stopColumnDragScroll();
6149
+ if (!target || from === target || !activeRect || !overRect) return;
6150
+ const order = [...effectiveColumnOrder];
6151
+ const fromIndex = order.indexOf(from);
6152
+ const targetIndex = order.indexOf(String(target));
6153
+ if (fromIndex < 0 || targetIndex < 0) return;
6154
+ order.splice(fromIndex, 1);
6155
+ const after = activeRect.left + activeRect.width / 2 >= overRect.left + overRect.width / 2;
6156
+ const insertion = order.indexOf(String(target)) + (after ? 1 : 0);
6157
+ order.splice(insertion, 0, from);
6158
+ commitColumnState(order, effectiveHiddenColumnIds);
6159
+ },
5947
6160
  children: [
5948
6161
  /* @__PURE__ */ jsxs24(
5949
6162
  "div",
@@ -6165,8 +6378,8 @@ function MatrxDataTableCore({
6165
6378
  onClick: () => setColumnManagerOpen(true),
6166
6379
  children: [
6167
6380
  /* @__PURE__ */ jsx32(SlidersHorizontal, { className: "h-4 w-4" }),
6168
- hiddenColumnIds.size > 0 ? /* @__PURE__ */ jsxs24("span", { className: "sr-only", children: [
6169
- hiddenColumnIds.size,
6381
+ effectiveHiddenColumnIds.size > 0 ? /* @__PURE__ */ jsxs24("span", { className: "sr-only", children: [
6382
+ effectiveHiddenColumnIds.size,
6170
6383
  " hidden"
6171
6384
  ] }) : null
6172
6385
  ]
@@ -6420,46 +6633,50 @@ function MatrxDataTableCore({
6420
6633
  // horizontal-scroll surface.
6421
6634
  col.width !== void 0 && "sm:w-[var(--matrx-col-w)] sm:min-w-[var(--matrx-col-w)]",
6422
6635
  col.headerClassName,
6636
+ columnDropTarget?.id === id && (columnDropTarget.after ? "border-r-2 border-primary" : "border-l-2 border-primary"),
6423
6637
  col.align === "center" && "text-center",
6424
6638
  col.align === "right" && "text-right"
6425
6639
  ),
6426
6640
  style: columnWidthVar(col.width),
6427
- children: /* @__PURE__ */ jsx32(
6428
- ColumnHeaderCell,
6429
- {
6430
- label: col.header,
6431
- labelText: headerLabel,
6432
- sortable: col.sortable !== false,
6433
- isSorted: Boolean(isSorted),
6434
- sortDirection: sort?.direction ?? defaultSortDirection,
6435
- onSortAsc: () => setSort({ id, direction: "asc" }),
6436
- onSortDesc: () => setSort({ id, direction: "desc" }),
6437
- onClearSort: () => setSort(null),
6438
- onHeaderSortClick: () => {
6439
- if (!isSorted) {
6440
- setSort({ id, direction: defaultSortDirection });
6441
- return;
6442
- }
6443
- if (sort?.direction === "asc") {
6444
- setSort({ id, direction: "desc" });
6445
- return;
6446
- }
6447
- setSort(null);
6448
- },
6449
- filterKind: meta?.kind ?? null,
6450
- filterValue: columnFilters[id],
6451
- onFilterChange: (next) => setColumnFilter(id, next),
6452
- selectOptions: meta?.options,
6453
- selectSingle: col.filterSingle,
6454
- filterApplyLabel: filterUi?.applyLabel,
6455
- filterShowCancel: filterUi?.showCancel,
6456
- filterFooterHint: filterUi?.footerHint,
6457
- allowNegation: filterUi?.allowNegation,
6458
- allowNullModes: filterUi?.allowNullModes,
6459
- align: col.align,
6460
- compact: col.compact
6461
- }
6462
- )
6641
+ children: /* @__PURE__ */ jsxs24(ColumnHeaderDropZone, { id, enabled: reorderableColumns, children: [
6642
+ reorderableColumns ? /* @__PURE__ */ jsx32(ColumnReorderHandle, { id, label: headerLabel }) : null,
6643
+ /* @__PURE__ */ jsx32(
6644
+ ColumnHeaderCell,
6645
+ {
6646
+ label: col.header,
6647
+ labelText: headerLabel,
6648
+ sortable: col.sortable !== false,
6649
+ isSorted: Boolean(isSorted),
6650
+ sortDirection: sort?.direction ?? defaultSortDirection,
6651
+ onSortAsc: () => setSort({ id, direction: "asc" }),
6652
+ onSortDesc: () => setSort({ id, direction: "desc" }),
6653
+ onClearSort: () => setSort(null),
6654
+ onHeaderSortClick: () => {
6655
+ if (!isSorted) {
6656
+ setSort({ id, direction: defaultSortDirection });
6657
+ return;
6658
+ }
6659
+ if (sort?.direction === "asc") {
6660
+ setSort({ id, direction: "desc" });
6661
+ return;
6662
+ }
6663
+ setSort(null);
6664
+ },
6665
+ filterKind: meta?.kind ?? null,
6666
+ filterValue: columnFilters[id],
6667
+ onFilterChange: (next) => setColumnFilter(id, next),
6668
+ selectOptions: meta?.options,
6669
+ selectSingle: col.filterSingle,
6670
+ filterApplyLabel: filterUi?.applyLabel,
6671
+ filterShowCancel: filterUi?.showCancel,
6672
+ filterFooterHint: filterUi?.footerHint,
6673
+ allowNegation: filterUi?.allowNegation,
6674
+ allowNullModes: filterUi?.allowNullModes,
6675
+ align: col.align,
6676
+ compact: col.compact
6677
+ }
6678
+ )
6679
+ ] })
6463
6680
  },
6464
6681
  id
6465
6682
  );
@@ -6758,12 +6975,9 @@ function MatrxDataTableCore({
6758
6975
  open: columnManagerOpen,
6759
6976
  onOpenChange: setColumnManagerOpen,
6760
6977
  columns,
6761
- order: columnOrder,
6762
- hidden: hiddenColumnIds,
6763
- onApply: (order, hidden) => {
6764
- setColumnOrder(order);
6765
- setHiddenColumnIds(hidden);
6766
- }
6978
+ order: effectiveColumnOrder,
6979
+ hidden: effectiveHiddenColumnIds,
6980
+ onApply: commitColumnState
6767
6981
  }
6768
6982
  ),
6769
6983
  detailEnabled && selectedRow ? /* @__PURE__ */ jsx32(
@@ -6977,7 +7191,7 @@ function HierarchyDragHandle({
6977
7191
  label,
6978
7192
  disabled
6979
7193
  }) {
6980
- const { attributes, listeners, setNodeRef } = useDraggable({
7194
+ const { attributes, listeners, setNodeRef } = useDraggable2({
6981
7195
  id,
6982
7196
  disabled
6983
7197
  });
@@ -7001,7 +7215,7 @@ function HierarchyRootDropTarget({
7001
7215
  label,
7002
7216
  draggedLabel
7003
7217
  }) {
7004
- const { isOver, setNodeRef } = useDroppable({ id: "__root__" });
7218
+ const { isOver, setNodeRef } = useDroppable2({ id: "__root__" });
7005
7219
  return /* @__PURE__ */ jsxs24(
7006
7220
  "div",
7007
7221
  {
@@ -7025,7 +7239,7 @@ function HierarchyDragIndicatorLayer({
7025
7239
  const [indicator, setIndicator] = useState14(
7026
7240
  null
7027
7241
  );
7028
- useDndMonitor({
7242
+ useDndMonitor2({
7029
7243
  onDragMove(event) {
7030
7244
  const container = scrollRef.current;
7031
7245
  const overId = event.over ? String(event.over.id) : null;
@@ -7125,7 +7339,7 @@ function HierarchyDroppableRow({
7125
7339
  disabled,
7126
7340
  children
7127
7341
  }) {
7128
- const { setNodeRef } = useDroppable({ id, disabled });
7342
+ const { setNodeRef } = useDroppable2({ id, disabled });
7129
7343
  if (!isValidElement(children) || children.type !== "tr") return children;
7130
7344
  const row = children;
7131
7345
  return cloneElement(row, {
@@ -7133,6 +7347,38 @@ function HierarchyDroppableRow({
7133
7347
  className: row.props.className
7134
7348
  });
7135
7349
  }
7350
+ function ColumnHeaderDropZone({
7351
+ id,
7352
+ enabled,
7353
+ children
7354
+ }) {
7355
+ const { setNodeRef } = useDroppable2({
7356
+ id: `matrx-column-header:${id}`,
7357
+ disabled: !enabled,
7358
+ data: { matrxDragKind: "column-header", columnId: id }
7359
+ });
7360
+ return /* @__PURE__ */ jsx32("div", { ref: setNodeRef, className: "flex min-w-0 items-center", children });
7361
+ }
7362
+ function ColumnReorderHandle({ id, label }) {
7363
+ const { attributes, listeners, setNodeRef } = useDraggable2({
7364
+ id: `matrx-column-header:${id}`,
7365
+ data: { matrxDragKind: "column-header", columnId: id }
7366
+ });
7367
+ return /* @__PURE__ */ jsx32(
7368
+ "button",
7369
+ {
7370
+ ref: setNodeRef,
7371
+ type: "button",
7372
+ 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",
7373
+ "aria-label": `Reorder ${label}`,
7374
+ title: `Drag to reorder ${label}`,
7375
+ onClick: (event) => event.stopPropagation(),
7376
+ ...attributes,
7377
+ ...listeners,
7378
+ children: /* @__PURE__ */ jsx32(GripVertical, { className: "h-3.5 w-3.5" })
7379
+ }
7380
+ );
7381
+ }
7136
7382
  function columnWidthVar(width) {
7137
7383
  if (width === void 0) return void 0;
7138
7384
  return {