@pixpilot/shadcn-kanban 0.0.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/dist/AddColumnButton.cjs +82 -0
  4. package/dist/AddColumnButton.js +77 -0
  5. package/dist/ColumnFilterButton.cjs +68 -0
  6. package/dist/ColumnFilterButton.js +63 -0
  7. package/dist/KanbanBoard.cjs +137 -0
  8. package/dist/KanbanBoard.d.cts +36 -0
  9. package/dist/KanbanBoard.d.ts +36 -0
  10. package/dist/KanbanBoard.js +131 -0
  11. package/dist/KanbanColumn.cjs +142 -0
  12. package/dist/KanbanColumn.js +134 -0
  13. package/dist/KanbanColumnCards.cjs +25 -0
  14. package/dist/KanbanColumnCards.js +23 -0
  15. package/dist/KanbanDragOverlay.cjs +42 -0
  16. package/dist/KanbanDragOverlay.js +37 -0
  17. package/dist/KanbanItem.cjs +58 -0
  18. package/dist/KanbanItem.js +52 -0
  19. package/dist/KanbanVirtualColumnCards.cjs +70 -0
  20. package/dist/KanbanVirtualColumnCards.js +66 -0
  21. package/dist/_virtual/rolldown_runtime.cjs +25 -0
  22. package/dist/hooks/use-kanban-board-state.cjs +43 -0
  23. package/dist/hooks/use-kanban-board-state.js +41 -0
  24. package/dist/hooks/use-kanban-card-drag.cjs +83 -0
  25. package/dist/hooks/use-kanban-card-drag.js +81 -0
  26. package/dist/hooks/use-kanban-collision-detection.cjs +70 -0
  27. package/dist/hooks/use-kanban-collision-detection.js +67 -0
  28. package/dist/hooks/use-kanban-column-reorder.cjs +31 -0
  29. package/dist/hooks/use-kanban-column-reorder.js +28 -0
  30. package/dist/hooks/use-kanban-drag.cjs +98 -0
  31. package/dist/hooks/use-kanban-drag.js +95 -0
  32. package/dist/hooks/use-kanban-filters.cjs +49 -0
  33. package/dist/hooks/use-kanban-filters.js +47 -0
  34. package/dist/index.cjs +3 -0
  35. package/dist/index.d.cts +5 -0
  36. package/dist/index.d.ts +5 -0
  37. package/dist/index.js +3 -0
  38. package/dist/infinite-scroll/InfiniteScrollSentinel.cjs +54 -0
  39. package/dist/infinite-scroll/InfiniteScrollSentinel.d.cts +2 -0
  40. package/dist/infinite-scroll/InfiniteScrollSentinel.d.ts +2 -0
  41. package/dist/infinite-scroll/InfiniteScrollSentinel.js +49 -0
  42. package/dist/infinite-scroll/index.d.cts +3 -0
  43. package/dist/infinite-scroll/index.d.ts +3 -0
  44. package/dist/infinite-scroll/types.d.cts +29 -0
  45. package/dist/infinite-scroll/types.d.ts +29 -0
  46. package/dist/infinite-scroll/use-intersection-observer.cjs +46 -0
  47. package/dist/infinite-scroll/use-intersection-observer.d.cts +1 -0
  48. package/dist/infinite-scroll/use-intersection-observer.d.ts +1 -0
  49. package/dist/infinite-scroll/use-intersection-observer.js +44 -0
  50. package/dist/types.d.cts +251 -0
  51. package/dist/types.d.ts +251 -0
  52. package/dist/utils/apply-kanban-drop.cjs +37 -0
  53. package/dist/utils/apply-kanban-drop.js +35 -0
  54. package/dist/utils/column-sortable-id.cjs +23 -0
  55. package/dist/utils/column-sortable-id.js +20 -0
  56. package/dist/utils/is-below-over-item.cjs +17 -0
  57. package/dist/utils/is-below-over-item.js +16 -0
  58. package/dist/utils/items-order-equal.cjs +13 -0
  59. package/dist/utils/items-order-equal.js +12 -0
  60. package/dist/utils/kanban-keyboard-coordinates.cjs +64 -0
  61. package/dist/utils/kanban-keyboard-coordinates.js +62 -0
  62. package/dist/utils/move-kanban-item.cjs +28 -0
  63. package/dist/utils/move-kanban-item.js +27 -0
  64. package/dist/utils/replace-state.cjs +15 -0
  65. package/dist/utils/replace-state.js +14 -0
  66. package/dist/utils/resolve-column-id.cjs +25 -0
  67. package/dist/utils/resolve-column-id.js +24 -0
  68. package/package.json +97 -0
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
5
+ const require_KanbanItem = require('./KanbanItem.cjs');
6
+ let react = require("react");
7
+ react = require_rolldown_runtime.__toESM(react);
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
10
+ let __tanstack_react_virtual = require("@tanstack/react-virtual");
11
+ __tanstack_react_virtual = require_rolldown_runtime.__toESM(__tanstack_react_virtual);
12
+
13
+ //#region src/KanbanVirtualColumnCards.tsx
14
+ const DEFAULT_ESTIMATED_ITEM_HEIGHT = 96;
15
+ const DEFAULT_OVERSCAN = 6;
16
+ /** Matches the `gap-2` the non-virtualized list gets from the scroller. */
17
+ const DEFAULT_GAP = 8;
18
+ /**
19
+ * The card list of a column, virtualized: only the cards near the scroller's
20
+ * window are mounted, positioned absolutely inside a spacer tall enough to
21
+ * keep the scrollbar honest.
22
+ *
23
+ * Each card keeps its own wrapper so the two transforms never collide — the
24
+ * wrapper carries the virtual offset, the card inside carries whatever
25
+ * dnd-kit's sortable is animating.
26
+ */
27
+ function KanbanVirtualColumnCards({ column, items, renderItem, itemClassName, scroller, activeItemId, options, dragDisabled = false }) {
28
+ const { estimateItemHeight = DEFAULT_ESTIMATED_ITEM_HEIGHT, overscan = DEFAULT_OVERSCAN, gap = DEFAULT_GAP } = options;
29
+ const activeIndex = react.default.useMemo(() => activeItemId == null ? -1 : items.findIndex((item) => item.id === String(activeItemId)), [items, activeItemId]);
30
+ const rangeExtractor = react.default.useCallback((range) => {
31
+ const indexes = (0, __tanstack_react_virtual.defaultRangeExtractor)(range);
32
+ if (activeIndex < 0 || indexes.includes(activeIndex)) return indexes;
33
+ return [...indexes, activeIndex].sort((a, b) => a - b);
34
+ }, [activeIndex]);
35
+ const getItemKey = react.default.useCallback((index) => items[index]?.id ?? index, [items]);
36
+ const virtualizer = (0, __tanstack_react_virtual.useVirtualizer)({
37
+ count: items.length,
38
+ getScrollElement: () => scroller,
39
+ estimateSize: () => estimateItemHeight,
40
+ getItemKey,
41
+ rangeExtractor,
42
+ overscan,
43
+ gap
44
+ });
45
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
46
+ "data-testid": `kanban-column-virtual-${column.id}`,
47
+ className: "relative w-full shrink-0",
48
+ style: { height: virtualizer.getTotalSize() },
49
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
50
+ const item = items[virtualItem.index];
51
+ if (!item) return null;
52
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
53
+ "data-index": virtualItem.index,
54
+ ref: virtualizer.measureElement,
55
+ className: "absolute top-0 left-0 w-full",
56
+ style: { transform: `translateY(${virtualItem.start}px)` },
57
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_KanbanItem.KanbanItem, {
58
+ item,
59
+ column,
60
+ renderItem,
61
+ className: itemClassName,
62
+ dragDisabled
63
+ })
64
+ }, virtualItem.key);
65
+ })
66
+ });
67
+ }
68
+
69
+ //#endregion
70
+ exports.KanbanVirtualColumnCards = KanbanVirtualColumnCards;
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+
3
+
4
+ import { KanbanItem } from "./KanbanItem.js";
5
+ import React from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
8
+
9
+ //#region src/KanbanVirtualColumnCards.tsx
10
+ const DEFAULT_ESTIMATED_ITEM_HEIGHT = 96;
11
+ const DEFAULT_OVERSCAN = 6;
12
+ /** Matches the `gap-2` the non-virtualized list gets from the scroller. */
13
+ const DEFAULT_GAP = 8;
14
+ /**
15
+ * The card list of a column, virtualized: only the cards near the scroller's
16
+ * window are mounted, positioned absolutely inside a spacer tall enough to
17
+ * keep the scrollbar honest.
18
+ *
19
+ * Each card keeps its own wrapper so the two transforms never collide — the
20
+ * wrapper carries the virtual offset, the card inside carries whatever
21
+ * dnd-kit's sortable is animating.
22
+ */
23
+ function KanbanVirtualColumnCards({ column, items, renderItem, itemClassName, scroller, activeItemId, options, dragDisabled = false }) {
24
+ const { estimateItemHeight = DEFAULT_ESTIMATED_ITEM_HEIGHT, overscan = DEFAULT_OVERSCAN, gap = DEFAULT_GAP } = options;
25
+ const activeIndex = React.useMemo(() => activeItemId == null ? -1 : items.findIndex((item) => item.id === String(activeItemId)), [items, activeItemId]);
26
+ const rangeExtractor = React.useCallback((range) => {
27
+ const indexes = defaultRangeExtractor(range);
28
+ if (activeIndex < 0 || indexes.includes(activeIndex)) return indexes;
29
+ return [...indexes, activeIndex].sort((a, b) => a - b);
30
+ }, [activeIndex]);
31
+ const getItemKey = React.useCallback((index) => items[index]?.id ?? index, [items]);
32
+ const virtualizer = useVirtualizer({
33
+ count: items.length,
34
+ getScrollElement: () => scroller,
35
+ estimateSize: () => estimateItemHeight,
36
+ getItemKey,
37
+ rangeExtractor,
38
+ overscan,
39
+ gap
40
+ });
41
+ return /* @__PURE__ */ jsx("div", {
42
+ "data-testid": `kanban-column-virtual-${column.id}`,
43
+ className: "relative w-full shrink-0",
44
+ style: { height: virtualizer.getTotalSize() },
45
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
46
+ const item = items[virtualItem.index];
47
+ if (!item) return null;
48
+ return /* @__PURE__ */ jsx("div", {
49
+ "data-index": virtualItem.index,
50
+ ref: virtualizer.measureElement,
51
+ className: "absolute top-0 left-0 w-full",
52
+ style: { transform: `translateY(${virtualItem.start}px)` },
53
+ children: /* @__PURE__ */ jsx(KanbanItem, {
54
+ item,
55
+ column,
56
+ renderItem,
57
+ className: itemClassName,
58
+ dragDisabled
59
+ })
60
+ }, virtualItem.key);
61
+ })
62
+ });
63
+ }
64
+
65
+ //#endregion
66
+ export { KanbanVirtualColumnCards };
@@ -0,0 +1,25 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ exports.__toESM = __toESM;
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
5
+ const require_replace_state = require('../utils/replace-state.cjs');
6
+ let react = require("react");
7
+ react = require_rolldown_runtime.__toESM(react);
8
+
9
+ //#region src/hooks/use-kanban-board-state.ts
10
+ /**
11
+ * Mirrors the controlled `items` and `columns` props into local state so the
12
+ * board can render drag previews the parent has not committed yet.
13
+ */
14
+ function useKanbanBoardState({ externalItems, columns, isDragActiveRef }) {
15
+ const [items, setItems] = react.default.useReducer(require_replace_state.replaceState, externalItems);
16
+ const [internalColumns, setInternalColumns] = react.default.useReducer(require_replace_state.replaceState, columns);
17
+ const itemsRef = react.default.useRef(externalItems);
18
+ const applyItems = react.default.useCallback((next) => {
19
+ itemsRef.current = next;
20
+ setItems(next);
21
+ }, []);
22
+ react.default.useEffect(() => {
23
+ if (isDragActiveRef.current) return;
24
+ applyItems(externalItems);
25
+ }, [
26
+ externalItems,
27
+ applyItems,
28
+ isDragActiveRef
29
+ ]);
30
+ react.default.useEffect(() => {
31
+ setInternalColumns(columns);
32
+ }, [columns]);
33
+ return {
34
+ items,
35
+ itemsRef,
36
+ applyItems,
37
+ internalColumns,
38
+ setInternalColumns
39
+ };
40
+ }
41
+
42
+ //#endregion
43
+ exports.useKanbanBoardState = useKanbanBoardState;
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+
3
+
4
+ import { replaceState } from "../utils/replace-state.js";
5
+ import React from "react";
6
+
7
+ //#region src/hooks/use-kanban-board-state.ts
8
+ /**
9
+ * Mirrors the controlled `items` and `columns` props into local state so the
10
+ * board can render drag previews the parent has not committed yet.
11
+ */
12
+ function useKanbanBoardState({ externalItems, columns, isDragActiveRef }) {
13
+ const [items, setItems] = React.useReducer(replaceState, externalItems);
14
+ const [internalColumns, setInternalColumns] = React.useReducer(replaceState, columns);
15
+ const itemsRef = React.useRef(externalItems);
16
+ const applyItems = React.useCallback((next) => {
17
+ itemsRef.current = next;
18
+ setItems(next);
19
+ }, []);
20
+ React.useEffect(() => {
21
+ if (isDragActiveRef.current) return;
22
+ applyItems(externalItems);
23
+ }, [
24
+ externalItems,
25
+ applyItems,
26
+ isDragActiveRef
27
+ ]);
28
+ React.useEffect(() => {
29
+ setInternalColumns(columns);
30
+ }, [columns]);
31
+ return {
32
+ items,
33
+ itemsRef,
34
+ applyItems,
35
+ internalColumns,
36
+ setInternalColumns
37
+ };
38
+ }
39
+
40
+ //#endregion
41
+ export { useKanbanBoardState };
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
5
+ const require_column_sortable_id = require('../utils/column-sortable-id.cjs');
6
+ const require_move_kanban_item = require('../utils/move-kanban-item.cjs');
7
+ const require_apply_kanban_drop = require('../utils/apply-kanban-drop.cjs');
8
+ const require_is_below_over_item = require('../utils/is-below-over-item.cjs');
9
+ const require_items_order_equal = require('../utils/items-order-equal.cjs');
10
+ const require_resolve_column_id = require('../utils/resolve-column-id.cjs');
11
+ let react = require("react");
12
+ react = require_rolldown_runtime.__toESM(react);
13
+
14
+ //#region src/hooks/use-kanban-card-drag.ts
15
+ /**
16
+ * The card half of the board's drag state machine: cross-column moves are
17
+ * previewed live during `onDragOver`, while same-column reordering is left to
18
+ * the sortable transforms and committed once on `onDragEnd`.
19
+ */
20
+ function useKanbanCardDrag(options) {
21
+ const { columnIds, itemsRef, applyItems, dragStartItemsRef, recentlyMovedToNewColumnRef, onChange } = options;
22
+ return {
23
+ handleCardDragOver: react.default.useCallback((event) => {
24
+ const { active, over } = event;
25
+ if (!over || require_column_sortable_id.isColumnSortableId(active.id)) return;
26
+ const overColumnId = require_resolve_column_id.resolveColumnId(over.id, columnIds, itemsRef.current);
27
+ const activeItem = itemsRef.current.find((item) => item.id === active.id);
28
+ if (overColumnId === void 0 || !activeItem) return;
29
+ if (activeItem.columnId === overColumnId) return;
30
+ recentlyMovedToNewColumnRef.current = true;
31
+ applyItems(require_move_kanban_item.moveKanbanItem({
32
+ items: itemsRef.current,
33
+ activeId: active.id,
34
+ overId: over.id,
35
+ targetColumnId: overColumnId,
36
+ insertAfter: require_is_below_over_item.isBelowOverItem(event),
37
+ overIsColumn: require_resolve_column_id.isColumnDroppableId(over.id, columnIds)
38
+ }));
39
+ }, [
40
+ applyItems,
41
+ columnIds,
42
+ itemsRef,
43
+ recentlyMovedToNewColumnRef
44
+ ]),
45
+ handleCardDragEnd: react.default.useCallback((event) => {
46
+ const { active, over } = event;
47
+ const dragStartItems = dragStartItemsRef.current;
48
+ if (!over) {
49
+ applyItems(dragStartItems);
50
+ return;
51
+ }
52
+ const previousColumnId = dragStartItems.find((item) => item.id === active.id)?.columnId;
53
+ if (previousColumnId === void 0) return;
54
+ const next = require_apply_kanban_drop.applyKanbanDrop({
55
+ items: itemsRef.current,
56
+ activeId: active.id,
57
+ overId: over.id,
58
+ overColumnId: require_resolve_column_id.resolveColumnId(over.id, columnIds, itemsRef.current),
59
+ insertAfter: require_is_below_over_item.isBelowOverItem(event),
60
+ overIsColumn: require_resolve_column_id.isColumnDroppableId(over.id, columnIds)
61
+ });
62
+ applyItems(next);
63
+ if (!onChange || require_items_order_equal.itemsOrderEqual(next, dragStartItems)) return;
64
+ const movedItem = next.find((item) => item.id === active.id);
65
+ if (!movedItem) return;
66
+ onChange({
67
+ item: movedItem,
68
+ previousColumnId,
69
+ nextColumnId: movedItem.columnId,
70
+ items: next
71
+ });
72
+ }, [
73
+ applyItems,
74
+ columnIds,
75
+ dragStartItemsRef,
76
+ itemsRef,
77
+ onChange
78
+ ])
79
+ };
80
+ }
81
+
82
+ //#endregion
83
+ exports.useKanbanCardDrag = useKanbanCardDrag;
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+
3
+
4
+ import { isColumnSortableId } from "../utils/column-sortable-id.js";
5
+ import { moveKanbanItem } from "../utils/move-kanban-item.js";
6
+ import { applyKanbanDrop } from "../utils/apply-kanban-drop.js";
7
+ import { isBelowOverItem } from "../utils/is-below-over-item.js";
8
+ import { itemsOrderEqual } from "../utils/items-order-equal.js";
9
+ import { isColumnDroppableId, resolveColumnId } from "../utils/resolve-column-id.js";
10
+ import React from "react";
11
+
12
+ //#region src/hooks/use-kanban-card-drag.ts
13
+ /**
14
+ * The card half of the board's drag state machine: cross-column moves are
15
+ * previewed live during `onDragOver`, while same-column reordering is left to
16
+ * the sortable transforms and committed once on `onDragEnd`.
17
+ */
18
+ function useKanbanCardDrag(options) {
19
+ const { columnIds, itemsRef, applyItems, dragStartItemsRef, recentlyMovedToNewColumnRef, onChange } = options;
20
+ return {
21
+ handleCardDragOver: React.useCallback((event) => {
22
+ const { active, over } = event;
23
+ if (!over || isColumnSortableId(active.id)) return;
24
+ const overColumnId = resolveColumnId(over.id, columnIds, itemsRef.current);
25
+ const activeItem = itemsRef.current.find((item) => item.id === active.id);
26
+ if (overColumnId === void 0 || !activeItem) return;
27
+ if (activeItem.columnId === overColumnId) return;
28
+ recentlyMovedToNewColumnRef.current = true;
29
+ applyItems(moveKanbanItem({
30
+ items: itemsRef.current,
31
+ activeId: active.id,
32
+ overId: over.id,
33
+ targetColumnId: overColumnId,
34
+ insertAfter: isBelowOverItem(event),
35
+ overIsColumn: isColumnDroppableId(over.id, columnIds)
36
+ }));
37
+ }, [
38
+ applyItems,
39
+ columnIds,
40
+ itemsRef,
41
+ recentlyMovedToNewColumnRef
42
+ ]),
43
+ handleCardDragEnd: React.useCallback((event) => {
44
+ const { active, over } = event;
45
+ const dragStartItems = dragStartItemsRef.current;
46
+ if (!over) {
47
+ applyItems(dragStartItems);
48
+ return;
49
+ }
50
+ const previousColumnId = dragStartItems.find((item) => item.id === active.id)?.columnId;
51
+ if (previousColumnId === void 0) return;
52
+ const next = applyKanbanDrop({
53
+ items: itemsRef.current,
54
+ activeId: active.id,
55
+ overId: over.id,
56
+ overColumnId: resolveColumnId(over.id, columnIds, itemsRef.current),
57
+ insertAfter: isBelowOverItem(event),
58
+ overIsColumn: isColumnDroppableId(over.id, columnIds)
59
+ });
60
+ applyItems(next);
61
+ if (!onChange || itemsOrderEqual(next, dragStartItems)) return;
62
+ const movedItem = next.find((item) => item.id === active.id);
63
+ if (!movedItem) return;
64
+ onChange({
65
+ item: movedItem,
66
+ previousColumnId,
67
+ nextColumnId: movedItem.columnId,
68
+ items: next
69
+ });
70
+ }, [
71
+ applyItems,
72
+ columnIds,
73
+ dragStartItemsRef,
74
+ itemsRef,
75
+ onChange
76
+ ])
77
+ };
78
+ }
79
+
80
+ //#endregion
81
+ export { useKanbanCardDrag };
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
5
+ const require_column_sortable_id = require('../utils/column-sortable-id.cjs');
6
+ let __dnd_kit_core = require("@dnd-kit/core");
7
+ __dnd_kit_core = require_rolldown_runtime.__toESM(__dnd_kit_core);
8
+ let react = require("react");
9
+ react = require_rolldown_runtime.__toESM(react);
10
+
11
+ //#region src/hooks/use-kanban-collision-detection.ts
12
+ /**
13
+ * Collision strategy from dnd-kit's multiple-containers example: pointer hits
14
+ * first, falling back to rectangle intersection for keyboard drags. Hovering a
15
+ * column resolves to the closest card inside it so placement stays stable, and
16
+ * the previous target is reused during the reflow after a column switch.
17
+ *
18
+ * Rect intersection is deliberately limited to drags that have no pointer.
19
+ * It scores the dragged card's whole rect against every column, so a pointer
20
+ * resting outside all of them — in the gap between two columns, or below a
21
+ * short one — can pick a neighbour whose height then changes because of the
22
+ * preview move, which flips the winner back on the next measurement. Each flip
23
+ * moves the card again, and the resulting drag-over/measure feedback loop ends
24
+ * in React's "Maximum update depth exceeded". Pointer drags therefore hold the
25
+ * previous target while the pointer is over nothing at all.
26
+ */
27
+ function useKanbanCollisionDetection(options) {
28
+ const { columnIds, itemsRef, lastOverIdRef, recentlyMovedToNewColumnRef } = options;
29
+ return react.default.useCallback((args) => {
30
+ if (require_column_sortable_id.isColumnSortableId(args.active.id)) return (0, __dnd_kit_core.closestCenter)({
31
+ ...args,
32
+ droppableContainers: args.droppableContainers.filter((container) => require_column_sortable_id.isColumnSortableId(container.id))
33
+ });
34
+ const droppableContainers = args.droppableContainers.filter((container) => !require_column_sortable_id.isColumnSortableId(container.id));
35
+ let collisions = (0, __dnd_kit_core.pointerWithin)({
36
+ ...args,
37
+ droppableContainers
38
+ });
39
+ if (collisions.length === 0 && args.pointerCoordinates == null) collisions = (0, __dnd_kit_core.rectIntersection)({
40
+ ...args,
41
+ droppableContainers
42
+ });
43
+ let overId = (0, __dnd_kit_core.getFirstCollision)(collisions, "id");
44
+ if (overId != null) {
45
+ if (columnIds.has(String(overId))) {
46
+ const columnCards = droppableContainers.filter((container) => {
47
+ if (container.id === overId) return false;
48
+ return itemsRef.current.find(({ id }) => id === container.id)?.columnId === overId;
49
+ });
50
+ const closestCard = (0, __dnd_kit_core.closestCenter)({
51
+ ...args,
52
+ droppableContainers: columnCards
53
+ })[0];
54
+ if (closestCard) overId = closestCard.id;
55
+ }
56
+ lastOverIdRef.current = overId;
57
+ return [{ id: overId }];
58
+ }
59
+ if (recentlyMovedToNewColumnRef.current) lastOverIdRef.current = args.active.id;
60
+ return lastOverIdRef.current == null ? [] : [{ id: lastOverIdRef.current }];
61
+ }, [
62
+ columnIds,
63
+ itemsRef,
64
+ lastOverIdRef,
65
+ recentlyMovedToNewColumnRef
66
+ ]);
67
+ }
68
+
69
+ //#endregion
70
+ exports.useKanbanCollisionDetection = useKanbanCollisionDetection;
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+
3
+
4
+ import { isColumnSortableId } from "../utils/column-sortable-id.js";
5
+ import { closestCenter, getFirstCollision, pointerWithin, rectIntersection } from "@dnd-kit/core";
6
+ import React from "react";
7
+
8
+ //#region src/hooks/use-kanban-collision-detection.ts
9
+ /**
10
+ * Collision strategy from dnd-kit's multiple-containers example: pointer hits
11
+ * first, falling back to rectangle intersection for keyboard drags. Hovering a
12
+ * column resolves to the closest card inside it so placement stays stable, and
13
+ * the previous target is reused during the reflow after a column switch.
14
+ *
15
+ * Rect intersection is deliberately limited to drags that have no pointer.
16
+ * It scores the dragged card's whole rect against every column, so a pointer
17
+ * resting outside all of them — in the gap between two columns, or below a
18
+ * short one — can pick a neighbour whose height then changes because of the
19
+ * preview move, which flips the winner back on the next measurement. Each flip
20
+ * moves the card again, and the resulting drag-over/measure feedback loop ends
21
+ * in React's "Maximum update depth exceeded". Pointer drags therefore hold the
22
+ * previous target while the pointer is over nothing at all.
23
+ */
24
+ function useKanbanCollisionDetection(options) {
25
+ const { columnIds, itemsRef, lastOverIdRef, recentlyMovedToNewColumnRef } = options;
26
+ return React.useCallback((args) => {
27
+ if (isColumnSortableId(args.active.id)) return closestCenter({
28
+ ...args,
29
+ droppableContainers: args.droppableContainers.filter((container) => isColumnSortableId(container.id))
30
+ });
31
+ const droppableContainers = args.droppableContainers.filter((container) => !isColumnSortableId(container.id));
32
+ let collisions = pointerWithin({
33
+ ...args,
34
+ droppableContainers
35
+ });
36
+ if (collisions.length === 0 && args.pointerCoordinates == null) collisions = rectIntersection({
37
+ ...args,
38
+ droppableContainers
39
+ });
40
+ let overId = getFirstCollision(collisions, "id");
41
+ if (overId != null) {
42
+ if (columnIds.has(String(overId))) {
43
+ const columnCards = droppableContainers.filter((container) => {
44
+ if (container.id === overId) return false;
45
+ return itemsRef.current.find(({ id }) => id === container.id)?.columnId === overId;
46
+ });
47
+ const closestCard = closestCenter({
48
+ ...args,
49
+ droppableContainers: columnCards
50
+ })[0];
51
+ if (closestCard) overId = closestCard.id;
52
+ }
53
+ lastOverIdRef.current = overId;
54
+ return [{ id: overId }];
55
+ }
56
+ if (recentlyMovedToNewColumnRef.current) lastOverIdRef.current = args.active.id;
57
+ return lastOverIdRef.current == null ? [] : [{ id: lastOverIdRef.current }];
58
+ }, [
59
+ columnIds,
60
+ itemsRef,
61
+ lastOverIdRef,
62
+ recentlyMovedToNewColumnRef
63
+ ]);
64
+ }
65
+
66
+ //#endregion
67
+ export { useKanbanCollisionDetection };
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
5
+ const require_column_sortable_id = require('../utils/column-sortable-id.cjs');
6
+ let __dnd_kit_sortable = require("@dnd-kit/sortable");
7
+ __dnd_kit_sortable = require_rolldown_runtime.__toESM(__dnd_kit_sortable);
8
+ let react = require("react");
9
+ react = require_rolldown_runtime.__toESM(react);
10
+
11
+ //#region src/hooks/use-kanban-column-reorder.ts
12
+ /** Commits a column drag, ignoring drops that do not land on another column. */
13
+ function useKanbanColumnReorder({ internalColumns, setInternalColumns, onColumnChange }) {
14
+ return react.default.useCallback(({ active, over }) => {
15
+ if (!over || !require_column_sortable_id.isColumnSortableId(over.id) || active.id === over.id) return;
16
+ const indexOf = (sortableId) => internalColumns.findIndex((column) => column.id === require_column_sortable_id.extractColumnId(sortableId));
17
+ const oldIndex = indexOf(active.id);
18
+ const newIndex = indexOf(over.id);
19
+ if (oldIndex === -1 || newIndex === -1) return;
20
+ const next = (0, __dnd_kit_sortable.arrayMove)(internalColumns, oldIndex, newIndex);
21
+ setInternalColumns(next);
22
+ onColumnChange?.(next);
23
+ }, [
24
+ internalColumns,
25
+ onColumnChange,
26
+ setInternalColumns
27
+ ]);
28
+ }
29
+
30
+ //#endregion
31
+ exports.useKanbanColumnReorder = useKanbanColumnReorder;
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+
4
+ import { extractColumnId, isColumnSortableId } from "../utils/column-sortable-id.js";
5
+ import { arrayMove } from "@dnd-kit/sortable";
6
+ import React from "react";
7
+
8
+ //#region src/hooks/use-kanban-column-reorder.ts
9
+ /** Commits a column drag, ignoring drops that do not land on another column. */
10
+ function useKanbanColumnReorder({ internalColumns, setInternalColumns, onColumnChange }) {
11
+ return React.useCallback(({ active, over }) => {
12
+ if (!over || !isColumnSortableId(over.id) || active.id === over.id) return;
13
+ const indexOf = (sortableId) => internalColumns.findIndex((column) => column.id === extractColumnId(sortableId));
14
+ const oldIndex = indexOf(active.id);
15
+ const newIndex = indexOf(over.id);
16
+ if (oldIndex === -1 || newIndex === -1) return;
17
+ const next = arrayMove(internalColumns, oldIndex, newIndex);
18
+ setInternalColumns(next);
19
+ onColumnChange?.(next);
20
+ }, [
21
+ internalColumns,
22
+ onColumnChange,
23
+ setInternalColumns
24
+ ]);
25
+ }
26
+
27
+ //#endregion
28
+ export { useKanbanColumnReorder };