@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.
- package/LICENSE +21 -0
- package/README.md +46 -0
- package/dist/AddColumnButton.cjs +82 -0
- package/dist/AddColumnButton.js +77 -0
- package/dist/ColumnFilterButton.cjs +68 -0
- package/dist/ColumnFilterButton.js +63 -0
- package/dist/KanbanBoard.cjs +137 -0
- package/dist/KanbanBoard.d.cts +36 -0
- package/dist/KanbanBoard.d.ts +36 -0
- package/dist/KanbanBoard.js +131 -0
- package/dist/KanbanColumn.cjs +142 -0
- package/dist/KanbanColumn.js +134 -0
- package/dist/KanbanColumnCards.cjs +25 -0
- package/dist/KanbanColumnCards.js +23 -0
- package/dist/KanbanDragOverlay.cjs +42 -0
- package/dist/KanbanDragOverlay.js +37 -0
- package/dist/KanbanItem.cjs +58 -0
- package/dist/KanbanItem.js +52 -0
- package/dist/KanbanVirtualColumnCards.cjs +70 -0
- package/dist/KanbanVirtualColumnCards.js +66 -0
- package/dist/_virtual/rolldown_runtime.cjs +25 -0
- package/dist/hooks/use-kanban-board-state.cjs +43 -0
- package/dist/hooks/use-kanban-board-state.js +41 -0
- package/dist/hooks/use-kanban-card-drag.cjs +83 -0
- package/dist/hooks/use-kanban-card-drag.js +81 -0
- package/dist/hooks/use-kanban-collision-detection.cjs +70 -0
- package/dist/hooks/use-kanban-collision-detection.js +67 -0
- package/dist/hooks/use-kanban-column-reorder.cjs +31 -0
- package/dist/hooks/use-kanban-column-reorder.js +28 -0
- package/dist/hooks/use-kanban-drag.cjs +98 -0
- package/dist/hooks/use-kanban-drag.js +95 -0
- package/dist/hooks/use-kanban-filters.cjs +49 -0
- package/dist/hooks/use-kanban-filters.js +47 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/infinite-scroll/InfiniteScrollSentinel.cjs +54 -0
- package/dist/infinite-scroll/InfiniteScrollSentinel.d.cts +2 -0
- package/dist/infinite-scroll/InfiniteScrollSentinel.d.ts +2 -0
- package/dist/infinite-scroll/InfiniteScrollSentinel.js +49 -0
- package/dist/infinite-scroll/index.d.cts +3 -0
- package/dist/infinite-scroll/index.d.ts +3 -0
- package/dist/infinite-scroll/types.d.cts +29 -0
- package/dist/infinite-scroll/types.d.ts +29 -0
- package/dist/infinite-scroll/use-intersection-observer.cjs +46 -0
- package/dist/infinite-scroll/use-intersection-observer.d.cts +1 -0
- package/dist/infinite-scroll/use-intersection-observer.d.ts +1 -0
- package/dist/infinite-scroll/use-intersection-observer.js +44 -0
- package/dist/types.d.cts +251 -0
- package/dist/types.d.ts +251 -0
- package/dist/utils/apply-kanban-drop.cjs +37 -0
- package/dist/utils/apply-kanban-drop.js +35 -0
- package/dist/utils/column-sortable-id.cjs +23 -0
- package/dist/utils/column-sortable-id.js +20 -0
- package/dist/utils/is-below-over-item.cjs +17 -0
- package/dist/utils/is-below-over-item.js +16 -0
- package/dist/utils/items-order-equal.cjs +13 -0
- package/dist/utils/items-order-equal.js +12 -0
- package/dist/utils/kanban-keyboard-coordinates.cjs +64 -0
- package/dist/utils/kanban-keyboard-coordinates.js +62 -0
- package/dist/utils/move-kanban-item.cjs +28 -0
- package/dist/utils/move-kanban-item.js +27 -0
- package/dist/utils/replace-state.cjs +15 -0
- package/dist/utils/replace-state.js +14 -0
- package/dist/utils/resolve-column-id.cjs +25 -0
- package/dist/utils/resolve-column-id.js +24 -0
- 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 };
|