@stll/ui 0.17.1 → 0.19.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/dist/components/button-variants.d.ts +2 -2
- package/dist/components/button-variants.js +1 -0
- package/dist/components/composer.d.ts +108 -0
- package/dist/components/composer.js +132 -0
- package/dist/components/landing.d.ts +61 -0
- package/dist/components/landing.js +97 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/inspector/tabs.d.ts +1 -1
- package/dist/kanban/band-peek.d.ts +59 -0
- package/dist/kanban/band-peek.js +103 -0
- package/dist/kanban/column-band-header.d.ts +10 -2
- package/dist/kanban/column-band-header.js +1 -1
- package/dist/kanban/column-header.d.ts +8 -1
- package/dist/kanban/column-header.js +9 -2
- package/dist/kanban/drag-interactions.d.ts +9 -1
- package/dist/kanban/drag-interactions.js +10 -1
- package/dist/kanban/index.d.ts +5 -4
- package/dist/kanban/index.js +4 -3
- package/dist/kanban/sortable-interactions.d.ts +12 -0
- package/dist/kanban/subgroup-board.d.ts +38 -7
- package/dist/kanban/subgroup-board.js +83 -39
- package/dist/lib/control-size.d.ts +1 -1
- package/dist/styles/theme.css +8 -29
- package/package.json +9 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//#region src/kanban/band-peek.ts
|
|
2
|
+
/**
|
|
3
|
+
* The peek a collapsed band opens while a dragged card rests on its folded
|
|
4
|
+
* slot, as a state machine with an injectable scheduler so its timing rules
|
|
5
|
+
* can be tested without a DOM. A plain hover never peeks: the peek exists so
|
|
6
|
+
* a drag can still land on a specific column inside a folded band, and the
|
|
7
|
+
* board only feeds the controller drag events.
|
|
8
|
+
*
|
|
9
|
+
* One rule keeps a peek from fighting the drag: it ends only after the drag
|
|
10
|
+
* has left every part of the open band for a short linger. The band renders
|
|
11
|
+
* as separate elements (its caption, then its columns in each lane), so
|
|
12
|
+
* moving from the caption down into a column crosses an element boundary;
|
|
13
|
+
* without the linger the band would fold under the card and the slot would
|
|
14
|
+
* peek it straight back open. The end of the drag, wherever it lands, ends
|
|
15
|
+
* the peek at once.
|
|
16
|
+
*/
|
|
17
|
+
/** How long a dragged card rests on a folded slot before the band peeks open. */
|
|
18
|
+
const KANBAN_BAND_PEEK_DELAY_MS = 400;
|
|
19
|
+
/** How long the drag may be outside an open band before the peek ends. */
|
|
20
|
+
const KANBAN_BAND_PEEK_LINGER_MS = 150;
|
|
21
|
+
const hostScheduler = (callback, ms) => {
|
|
22
|
+
const timer = setTimeout(callback, ms);
|
|
23
|
+
return () => clearTimeout(timer);
|
|
24
|
+
};
|
|
25
|
+
const createBandPeekController = ({ onChange, delayMs = 400, lingerMs = 150, schedule = hostScheduler }) => {
|
|
26
|
+
let peekingBandId = null;
|
|
27
|
+
let cancelOpen = null;
|
|
28
|
+
let openingBandId = null;
|
|
29
|
+
let cancelEnd = null;
|
|
30
|
+
const clearOpenTimer = () => {
|
|
31
|
+
if (cancelOpen !== null) {
|
|
32
|
+
cancelOpen();
|
|
33
|
+
cancelOpen = null;
|
|
34
|
+
openingBandId = null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const clearEndTimer = () => {
|
|
38
|
+
if (cancelEnd !== null) {
|
|
39
|
+
cancelEnd();
|
|
40
|
+
cancelEnd = null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const setPeeking = (bandId) => {
|
|
44
|
+
if (peekingBandId === bandId) return;
|
|
45
|
+
peekingBandId = bandId;
|
|
46
|
+
onChange(bandId);
|
|
47
|
+
};
|
|
48
|
+
const bandFolded = (bandId) => {
|
|
49
|
+
if (openingBandId === bandId) clearOpenTimer();
|
|
50
|
+
if (peekingBandId === bandId) {
|
|
51
|
+
clearEndTimer();
|
|
52
|
+
setPeeking(null);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
slotDragOver: (bandId) => {
|
|
57
|
+
if (peekingBandId === bandId || openingBandId === bandId) return;
|
|
58
|
+
clearOpenTimer();
|
|
59
|
+
openingBandId = bandId;
|
|
60
|
+
cancelOpen = schedule(() => {
|
|
61
|
+
cancelOpen = null;
|
|
62
|
+
openingBandId = null;
|
|
63
|
+
clearEndTimer();
|
|
64
|
+
setPeeking(bandId);
|
|
65
|
+
}, delayMs);
|
|
66
|
+
},
|
|
67
|
+
slotDragLeave: (bandId) => {
|
|
68
|
+
if (openingBandId === bandId) clearOpenTimer();
|
|
69
|
+
},
|
|
70
|
+
openDragEnter: (bandId) => {
|
|
71
|
+
if (peekingBandId === bandId) clearEndTimer();
|
|
72
|
+
},
|
|
73
|
+
openDragLeave: (bandId) => {
|
|
74
|
+
if (peekingBandId !== bandId || cancelEnd !== null) return;
|
|
75
|
+
cancelEnd = schedule(() => {
|
|
76
|
+
cancelEnd = null;
|
|
77
|
+
setPeeking(null);
|
|
78
|
+
}, lingerMs);
|
|
79
|
+
},
|
|
80
|
+
bandFolded,
|
|
81
|
+
bandExpanded: (bandId) => {
|
|
82
|
+
if (openingBandId === bandId) clearOpenTimer();
|
|
83
|
+
if (peekingBandId === bandId) {
|
|
84
|
+
clearEndTimer();
|
|
85
|
+
setPeeking(null);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
slotUnmounted: (bandId) => {
|
|
89
|
+
if (openingBandId === bandId) clearOpenTimer();
|
|
90
|
+
},
|
|
91
|
+
dragEnded: () => {
|
|
92
|
+
clearOpenTimer();
|
|
93
|
+
clearEndTimer();
|
|
94
|
+
setPeeking(null);
|
|
95
|
+
},
|
|
96
|
+
dispose: () => {
|
|
97
|
+
clearOpenTimer();
|
|
98
|
+
clearEndTimer();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { KANBAN_BAND_PEEK_DELAY_MS, KANBAN_BAND_PEEK_LINGER_MS, createBandPeekController };
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
//#region src/kanban/column-band-header.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* How a band toggle was activated. A pointer activation leaves the pointer
|
|
5
|
+
* over whatever replaces the caption (the folded slot), which matters to the
|
|
6
|
+
* board's peek behaviour; a keyboard activation does not.
|
|
7
|
+
*/
|
|
8
|
+
type KanbanBandToggleActivation = {
|
|
9
|
+
viaPointer: boolean;
|
|
10
|
+
};
|
|
3
11
|
type KanbanColumnBandHeaderProps = {
|
|
4
12
|
/** The band name, or the control that has taken its place. */
|
|
5
13
|
title: ReactNode;
|
|
@@ -17,7 +25,7 @@ type KanbanColumnBandHeaderProps = {
|
|
|
17
25
|
compact?: boolean | undefined;
|
|
18
26
|
/** Accessible name for the toggle, such as "Collapse To do". */
|
|
19
27
|
toggleLabel: string;
|
|
20
|
-
onCollapsedChange: (collapsed: boolean) => void;
|
|
28
|
+
onCollapsedChange: (collapsed: boolean, activation: KanbanBandToggleActivation) => void;
|
|
21
29
|
/** Band menu or other controls, after the toggle. */
|
|
22
30
|
actions?: ReactNode;
|
|
23
31
|
};
|
|
@@ -29,4 +37,4 @@ type KanbanColumnBandHeaderProps = {
|
|
|
29
37
|
*/
|
|
30
38
|
declare const KanbanColumnBandHeader: ({ title, swatch, meta, collapsed, compact, toggleLabel, onCollapsedChange, actions }: KanbanColumnBandHeaderProps) => import("react").JSX.Element;
|
|
31
39
|
//#endregion
|
|
32
|
-
export { KanbanColumnBandHeader, KanbanColumnBandHeaderProps };
|
|
40
|
+
export { KanbanBandToggleActivation, KanbanColumnBandHeader, KanbanColumnBandHeaderProps };
|
|
@@ -18,7 +18,7 @@ const KanbanColumnBandHeader = ({ title, swatch, meta, collapsed, compact = coll
|
|
|
18
18
|
"aria-expanded": !collapsed,
|
|
19
19
|
"aria-label": toggleLabel,
|
|
20
20
|
className: "hover:bg-muted/60 text-muted-foreground hover:text-foreground flex size-6 shrink-0 items-center justify-center rounded-md transition-[background-color]",
|
|
21
|
-
onClick: () => onCollapsedChange(!collapsed),
|
|
21
|
+
onClick: (event) => onCollapsedChange(!collapsed, { viaPointer: event.detail > 0 }),
|
|
22
22
|
title: toggleLabel,
|
|
23
23
|
type: "button",
|
|
24
24
|
children: /* @__PURE__ */ jsx(DirectionalIcon, {
|
|
@@ -13,11 +13,18 @@ type KanbanColumnHeaderProps = {
|
|
|
13
13
|
dragHandle?: ReactNode;
|
|
14
14
|
/** Column menu. */
|
|
15
15
|
actions?: ReactNode;
|
|
16
|
+
/** Extra classes, e.g. to opt a caller out of the default row height. */
|
|
17
|
+
className?: string;
|
|
16
18
|
};
|
|
17
19
|
/**
|
|
18
20
|
* The column header row: one rhythm for the swatch, the name, the count, the
|
|
19
21
|
* calculation, and the column's controls, so every board's header lines up.
|
|
22
|
+
*
|
|
23
|
+
* Fixed at `TOOLBAR_ROW_HEIGHT`, the same height as the page header, the view
|
|
24
|
+
* switcher, and the inspector rail's cells, so a column's top edge lines up
|
|
25
|
+
* with every other chrome row above the board. The title clips instead of
|
|
26
|
+
* wrapping so a long name can never grow the row past that height.
|
|
20
27
|
*/
|
|
21
|
-
declare const KanbanColumnHeader: ({ swatch, title, meta, calculation, dragHandle, actions }: KanbanColumnHeaderProps) => import("react").JSX.Element;
|
|
28
|
+
declare const KanbanColumnHeader: ({ swatch, title, meta, calculation, dragHandle, actions, className }: KanbanColumnHeaderProps) => import("react").JSX.Element;
|
|
22
29
|
//#endregion
|
|
23
30
|
export { KanbanColumnHeader, KanbanColumnHeaderProps };
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { TOOLBAR_ROW_HEIGHT } from "../inspector/layout-tokens.js";
|
|
1
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
4
|
//#region src/kanban/column-header.tsx
|
|
3
5
|
/**
|
|
4
6
|
* The column header row: one rhythm for the swatch, the name, the count, the
|
|
5
7
|
* calculation, and the column's controls, so every board's header lines up.
|
|
8
|
+
*
|
|
9
|
+
* Fixed at `TOOLBAR_ROW_HEIGHT`, the same height as the page header, the view
|
|
10
|
+
* switcher, and the inspector rail's cells, so a column's top edge lines up
|
|
11
|
+
* with every other chrome row above the board. The title clips instead of
|
|
12
|
+
* wrapping so a long name can never grow the row past that height.
|
|
6
13
|
*/
|
|
7
|
-
const KanbanColumnHeader = ({ swatch, title, meta, calculation, dragHandle, actions }) => /* @__PURE__ */ jsxs("div", {
|
|
8
|
-
className: "flex items-center gap-2 px-3
|
|
14
|
+
const KanbanColumnHeader = ({ swatch, title, meta, calculation, dragHandle, actions, className }) => /* @__PURE__ */ jsxs("div", {
|
|
15
|
+
className: cn("flex items-center gap-2 px-3", TOOLBAR_ROW_HEIGHT, className),
|
|
9
16
|
children: [
|
|
10
17
|
swatch,
|
|
11
18
|
/* @__PURE__ */ jsxs("span", {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
2
2
|
//#region src/kanban/drag-interactions.d.ts
|
|
3
3
|
type AtlaskitDraggableOptions = Parameters<typeof draggable>[0];
|
|
4
|
+
/**
|
|
5
|
+
* Marks a card drag on the native `DataTransfer` so the board's native
|
|
6
|
+
* `dragover`/`dragenter`/`dragleave` listeners (which also see reorders,
|
|
7
|
+
* file drops, and any other native drag passing over the board) can tell a
|
|
8
|
+
* kanban card drag from everything else. Pragmatic's `getInitialData` never
|
|
9
|
+
* reaches those native events; only data attached this way does.
|
|
10
|
+
*/
|
|
11
|
+
declare const KANBAN_CARD_DRAG_MIME: "application/x-stella-kanban-card";
|
|
4
12
|
type RegisterKanbanCardDragOptions = {
|
|
5
13
|
/** The drag wrapper rendered by `KanbanCardShell`. */
|
|
6
14
|
element: AtlaskitDraggableOptions["element"];
|
|
@@ -32,4 +40,4 @@ type RegisterKanbanBoardAutoScrollOptions = {
|
|
|
32
40
|
/** Register horizontal auto-scroll at the board's overflow boundary. */
|
|
33
41
|
declare const registerKanbanBoardAutoScroll: ({ element, sources }: RegisterKanbanBoardAutoScrollOptions) => (() => void);
|
|
34
42
|
//#endregion
|
|
35
|
-
export { KANBAN_BOARD_AUTO_SCROLL_SOURCES, RegisterKanbanBoardAutoScrollOptions, RegisterKanbanCardDragOptions, registerKanbanBoardAutoScroll, registerKanbanCardDrag };
|
|
43
|
+
export { KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_CARD_DRAG_MIME, RegisterKanbanBoardAutoScrollOptions, RegisterKanbanCardDragOptions, registerKanbanBoardAutoScroll, registerKanbanCardDrag };
|
|
@@ -6,6 +6,14 @@ import { centerUnderPointer } from "@atlaskit/pragmatic-drag-and-drop/element/ce
|
|
|
6
6
|
import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
|
|
7
7
|
//#region src/kanban/drag-interactions.ts
|
|
8
8
|
/**
|
|
9
|
+
* Marks a card drag on the native `DataTransfer` so the board's native
|
|
10
|
+
* `dragover`/`dragenter`/`dragleave` listeners (which also see reorders,
|
|
11
|
+
* file drops, and any other native drag passing over the board) can tell a
|
|
12
|
+
* kanban card drag from everything else. Pragmatic's `getInitialData` never
|
|
13
|
+
* reaches those native events; only data attached this way does.
|
|
14
|
+
*/
|
|
15
|
+
const KANBAN_CARD_DRAG_MIME = "application/x-stella-kanban-card";
|
|
16
|
+
/**
|
|
9
17
|
* Register the standard kanban card drag source and native preview.
|
|
10
18
|
*
|
|
11
19
|
* Movement remains with the caller: the package does not inspect the drag data
|
|
@@ -15,6 +23,7 @@ import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/el
|
|
|
15
23
|
const registerKanbanCardDrag = ({ element, getInitialData, canDrag, onDragStart, onDrop }) => draggable({
|
|
16
24
|
element,
|
|
17
25
|
getInitialData,
|
|
26
|
+
getInitialDataForExternal: () => ({ [KANBAN_CARD_DRAG_MIME]: "" }),
|
|
18
27
|
...canDrag === void 0 ? {} : { canDrag },
|
|
19
28
|
...onDragStart === void 0 ? {} : { onDragStart },
|
|
20
29
|
...onDrop === void 0 ? {} : { onDrop },
|
|
@@ -50,4 +59,4 @@ const registerKanbanBoardAutoScroll = ({ element, sources }) => {
|
|
|
50
59
|
}));
|
|
51
60
|
};
|
|
52
61
|
//#endregion
|
|
53
|
-
export { KANBAN_BOARD_AUTO_SCROLL_SOURCES, registerKanbanBoardAutoScroll, registerKanbanCardDrag };
|
|
62
|
+
export { KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_CARD_DRAG_MIME, registerKanbanBoardAutoScroll, registerKanbanCardDrag };
|
package/dist/kanban/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { KanbanCardFieldSelection, selectKanbanCardFieldIds } from "./card-properties.js";
|
|
2
2
|
import { KanbanCardShell, KanbanCardShellProps } from "./card-shell.js";
|
|
3
3
|
import { KanbanCellAction, KanbanCellActionProps } from "./cell-action.js";
|
|
4
|
-
import { KanbanColumnBandHeader, KanbanColumnBandHeaderProps } from "./column-band-header.js";
|
|
4
|
+
import { KanbanBandToggleActivation, KanbanColumnBandHeader, KanbanColumnBandHeaderProps } from "./column-band-header.js";
|
|
5
5
|
import { KanbanBuiltInGroup, KanbanColumnBand, KanbanGroup, KanbanGroupOption, KanbanGrouping, KanbanSchema, ResolveKanbanGroupingParams, getKanbanGroupingPropertyId, getKanbanGroups, isKanbanGroupingRenderable, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanRows } from "./grouping.js";
|
|
6
6
|
import { BuildKanbanBoardMatrixParams, CreateKanbanDropIntentParams, KANBAN_BOARD_AXES, KanbanBoardAxis, KanbanBoardCell, KanbanBoardColumn, KanbanBoardCoordinate, KanbanBoardDestination, KanbanBoardLane, KanbanBoardMatrix, KanbanDropAxisChange, KanbanDropIntent, OrderKanbanCellsByColumnsParams, ResolveKanbanGroupValueParams, buildKanbanBoardMatrix, createKanbanDropIntent, getKanbanBoardColumnIdentity, getKanbanBoardLaneIdentity, orderKanbanCellsByColumns } from "./matrix.js";
|
|
7
7
|
import { KANBAN_COLLAPSED_BAND_WIDTH_CLASS, KANBAN_COLLAPSED_BAND_WIDTH_PX, KANBAN_COLUMN_GAP_PX, KANBAN_COLUMN_WIDTH_CLASS, KANBAN_COLUMN_WIDTH_PX, KanbanColumnBandSpan, hasKanbanColumnBands, resolveKanbanColumnBands } from "./column-bands.js";
|
|
@@ -9,7 +9,8 @@ import { KanbanColumnHeader, KanbanColumnHeaderProps } from "./column-header.js"
|
|
|
9
9
|
import { KANBAN_BOARD_COLLISION_DETECTION, KanbanCellVirtualNavigation, KanbanSortableCellPosition, KanbanVirtualScrollRequest, kanbanKeyboardCoordinates } from "./sortable-interactions.logic.js";
|
|
10
10
|
import { KANBAN_BOARD_AUTO_SCROLL_OPTIONS, KANBAN_DRAG_OVERLAY_Z_INDEX, KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_SORTABLE_ACTIVATION_MODES, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KanbanCardDragSurface, KanbanCardDragSurfaceProps, KanbanDragCancelEvent, KanbanDragEndEvent, KanbanDragHandle, KanbanDragHandleProps, KanbanDragOverEvent, KanbanDragStartEvent, KanbanSortableActivationMode, KanbanSortableBindings, KanbanSortableBoard, KanbanSortableBoardProps, KanbanSortableColumns, KanbanSortableColumnsProps, KanbanSortableList, KanbanSortableListProps, UseKanbanDropTargetOptions, UseKanbanSortableOptions, useKanbanDropTarget, useKanbanSortable, useKanbanSortableSensors } from "./sortable-interactions.js";
|
|
11
11
|
import { KANBAN_DIRECTIONS, KANBAN_HORIZONTAL_EDGES, KanbanDirection, KanbanHorizontalEdge, getKanbanHorizontalEdge } from "./sortable-edge.js";
|
|
12
|
-
import { KANBAN_BOARD_AUTO_SCROLL_SOURCES, RegisterKanbanBoardAutoScrollOptions, RegisterKanbanCardDragOptions, registerKanbanBoardAutoScroll, registerKanbanCardDrag } from "./drag-interactions.js";
|
|
13
|
-
import {
|
|
12
|
+
import { KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_CARD_DRAG_MIME, RegisterKanbanBoardAutoScrollOptions, RegisterKanbanCardDragOptions, registerKanbanBoardAutoScroll, registerKanbanCardDrag } from "./drag-interactions.js";
|
|
13
|
+
import { KanbanSubgroupBandHeaderContext, KanbanSubgroupBoard, KanbanSubgroupBoardProps, KanbanSubgroupCellContext, KanbanSubgroupCollapsedBandCellContext, KanbanSubgroupColumnHeaderContext, KanbanSubgroupLaneIdentityContext } from "./subgroup-board.js";
|
|
14
|
+
import { KANBAN_BAND_PEEK_DELAY_MS, KANBAN_BAND_PEEK_LINGER_MS } from "./band-peek.js";
|
|
14
15
|
import { KANBAN_VIRTUAL_CELL_PAGINATION, KanbanVirtualCell, KanbanVirtualCellPagination, KanbanVirtualCellProps, KanbanVirtualCellSortableContext } from "./virtual-cell.js";
|
|
15
|
-
export { type BuildKanbanBoardMatrixParams, type CreateKanbanDropIntentParams, KANBAN_BAND_PEEK_DELAY_MS, KANBAN_BOARD_AUTO_SCROLL_OPTIONS, KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_BOARD_AXES, KANBAN_BOARD_COLLISION_DETECTION, KANBAN_COLLAPSED_BAND_WIDTH_CLASS, KANBAN_COLLAPSED_BAND_WIDTH_PX, KANBAN_COLUMN_GAP_PX, KANBAN_COLUMN_WIDTH_CLASS, KANBAN_COLUMN_WIDTH_PX, KANBAN_DIRECTIONS, KANBAN_DRAG_OVERLAY_Z_INDEX, KANBAN_HORIZONTAL_EDGES, KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_SORTABLE_ACTIVATION_MODES, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KANBAN_VIRTUAL_CELL_PAGINATION, type KanbanBoardAxis, type KanbanBoardCell, type KanbanBoardColumn, type KanbanBoardCoordinate, type KanbanBoardDestination, type KanbanBoardLane, type KanbanBoardMatrix, type KanbanBuiltInGroup, KanbanCardDragSurface, type KanbanCardDragSurfaceProps, type KanbanCardFieldSelection, KanbanCardShell, type KanbanCardShellProps, KanbanCellAction, type KanbanCellActionProps, type KanbanCellVirtualNavigation, type KanbanColumnBand, KanbanColumnBandHeader, type KanbanColumnBandHeaderProps, type KanbanColumnBandSpan, KanbanColumnHeader, type KanbanColumnHeaderProps, type KanbanDirection, type KanbanDragCancelEvent, type KanbanDragEndEvent, KanbanDragHandle, type KanbanDragHandleProps, type KanbanDragOverEvent, type KanbanDragStartEvent, type KanbanDropAxisChange, type KanbanDropIntent, type KanbanGroup, type KanbanGroupOption, type KanbanGrouping, type KanbanHorizontalEdge, type KanbanSchema, type KanbanSortableActivationMode, type KanbanSortableBindings, KanbanSortableBoard, type KanbanSortableBoardProps, type KanbanSortableCellPosition, KanbanSortableColumns, type KanbanSortableColumnsProps, KanbanSortableList, type KanbanSortableListProps, type KanbanSubgroupBandHeaderContext, KanbanSubgroupBoard, type KanbanSubgroupBoardProps, type KanbanSubgroupCellContext, type KanbanSubgroupCollapsedBandCellContext, type KanbanSubgroupColumnHeaderContext, type KanbanSubgroupLaneIdentityContext, KanbanVirtualCell, type KanbanVirtualCellPagination, type KanbanVirtualCellProps, type KanbanVirtualCellSortableContext, type KanbanVirtualScrollRequest, type OrderKanbanCellsByColumnsParams, type RegisterKanbanBoardAutoScrollOptions, type RegisterKanbanCardDragOptions, type ResolveKanbanGroupValueParams, type ResolveKanbanGroupingParams, type UseKanbanDropTargetOptions, type UseKanbanSortableOptions, buildKanbanBoardMatrix, createKanbanDropIntent, getKanbanBoardColumnIdentity, getKanbanBoardLaneIdentity, getKanbanGroupingPropertyId, getKanbanGroups, getKanbanHorizontalEdge, hasKanbanColumnBands, isKanbanGroupingRenderable, kanbanKeyboardCoordinates, orderKanbanCellsByColumns, registerKanbanBoardAutoScroll, registerKanbanCardDrag, resolveKanbanColumnBands, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanCardFieldIds, selectKanbanRows, useKanbanDropTarget, useKanbanSortable, useKanbanSortableSensors };
|
|
16
|
+
export { type BuildKanbanBoardMatrixParams, type CreateKanbanDropIntentParams, KANBAN_BAND_PEEK_DELAY_MS, KANBAN_BAND_PEEK_LINGER_MS, KANBAN_BOARD_AUTO_SCROLL_OPTIONS, KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_BOARD_AXES, KANBAN_BOARD_COLLISION_DETECTION, KANBAN_CARD_DRAG_MIME, KANBAN_COLLAPSED_BAND_WIDTH_CLASS, KANBAN_COLLAPSED_BAND_WIDTH_PX, KANBAN_COLUMN_GAP_PX, KANBAN_COLUMN_WIDTH_CLASS, KANBAN_COLUMN_WIDTH_PX, KANBAN_DIRECTIONS, KANBAN_DRAG_OVERLAY_Z_INDEX, KANBAN_HORIZONTAL_EDGES, KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_SORTABLE_ACTIVATION_MODES, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KANBAN_VIRTUAL_CELL_PAGINATION, type KanbanBandToggleActivation, type KanbanBoardAxis, type KanbanBoardCell, type KanbanBoardColumn, type KanbanBoardCoordinate, type KanbanBoardDestination, type KanbanBoardLane, type KanbanBoardMatrix, type KanbanBuiltInGroup, KanbanCardDragSurface, type KanbanCardDragSurfaceProps, type KanbanCardFieldSelection, KanbanCardShell, type KanbanCardShellProps, KanbanCellAction, type KanbanCellActionProps, type KanbanCellVirtualNavigation, type KanbanColumnBand, KanbanColumnBandHeader, type KanbanColumnBandHeaderProps, type KanbanColumnBandSpan, KanbanColumnHeader, type KanbanColumnHeaderProps, type KanbanDirection, type KanbanDragCancelEvent, type KanbanDragEndEvent, KanbanDragHandle, type KanbanDragHandleProps, type KanbanDragOverEvent, type KanbanDragStartEvent, type KanbanDropAxisChange, type KanbanDropIntent, type KanbanGroup, type KanbanGroupOption, type KanbanGrouping, type KanbanHorizontalEdge, type KanbanSchema, type KanbanSortableActivationMode, type KanbanSortableBindings, KanbanSortableBoard, type KanbanSortableBoardProps, type KanbanSortableCellPosition, KanbanSortableColumns, type KanbanSortableColumnsProps, KanbanSortableList, type KanbanSortableListProps, type KanbanSubgroupBandHeaderContext, KanbanSubgroupBoard, type KanbanSubgroupBoardProps, type KanbanSubgroupCellContext, type KanbanSubgroupCollapsedBandCellContext, type KanbanSubgroupColumnHeaderContext, type KanbanSubgroupLaneIdentityContext, KanbanVirtualCell, type KanbanVirtualCellPagination, type KanbanVirtualCellProps, type KanbanVirtualCellSortableContext, type KanbanVirtualScrollRequest, type OrderKanbanCellsByColumnsParams, type RegisterKanbanBoardAutoScrollOptions, type RegisterKanbanCardDragOptions, type ResolveKanbanGroupValueParams, type ResolveKanbanGroupingParams, type UseKanbanDropTargetOptions, type UseKanbanSortableOptions, buildKanbanBoardMatrix, createKanbanDropIntent, getKanbanBoardColumnIdentity, getKanbanBoardLaneIdentity, getKanbanGroupingPropertyId, getKanbanGroups, getKanbanHorizontalEdge, hasKanbanColumnBands, isKanbanGroupingRenderable, kanbanKeyboardCoordinates, orderKanbanCellsByColumns, registerKanbanBoardAutoScroll, registerKanbanCardDrag, resolveKanbanColumnBands, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanCardFieldIds, selectKanbanRows, useKanbanDropTarget, useKanbanSortable, useKanbanSortableSensors };
|
package/dist/kanban/index.js
CHANGED
|
@@ -9,7 +9,8 @@ import { KanbanColumnHeader } from "./column-header.js";
|
|
|
9
9
|
import { KANBAN_BOARD_COLLISION_DETECTION, kanbanKeyboardCoordinates } from "./sortable-interactions.logic.js";
|
|
10
10
|
import { KANBAN_BOARD_AUTO_SCROLL_OPTIONS, KANBAN_DRAG_OVERLAY_Z_INDEX, KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_SORTABLE_ACTIVATION_MODES, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KanbanCardDragSurface, KanbanDragHandle, KanbanSortableBoard, KanbanSortableColumns, KanbanSortableList, useKanbanDropTarget, useKanbanSortable, useKanbanSortableSensors } from "./sortable-interactions.js";
|
|
11
11
|
import { KANBAN_DIRECTIONS, KANBAN_HORIZONTAL_EDGES, getKanbanHorizontalEdge } from "./sortable-edge.js";
|
|
12
|
-
import { KANBAN_BOARD_AUTO_SCROLL_SOURCES, registerKanbanBoardAutoScroll, registerKanbanCardDrag } from "./drag-interactions.js";
|
|
13
|
-
import { KANBAN_BAND_PEEK_DELAY_MS,
|
|
12
|
+
import { KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_CARD_DRAG_MIME, registerKanbanBoardAutoScroll, registerKanbanCardDrag } from "./drag-interactions.js";
|
|
13
|
+
import { KANBAN_BAND_PEEK_DELAY_MS, KANBAN_BAND_PEEK_LINGER_MS } from "./band-peek.js";
|
|
14
|
+
import { KanbanSubgroupBoard } from "./subgroup-board.js";
|
|
14
15
|
import { KANBAN_VIRTUAL_CELL_PAGINATION, KanbanVirtualCell } from "./virtual-cell.js";
|
|
15
|
-
export { KANBAN_BAND_PEEK_DELAY_MS, KANBAN_BOARD_AUTO_SCROLL_OPTIONS, KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_BOARD_AXES, KANBAN_BOARD_COLLISION_DETECTION, KANBAN_COLLAPSED_BAND_WIDTH_CLASS, KANBAN_COLLAPSED_BAND_WIDTH_PX, KANBAN_COLUMN_GAP_PX, KANBAN_COLUMN_WIDTH_CLASS, KANBAN_COLUMN_WIDTH_PX, KANBAN_DIRECTIONS, KANBAN_DRAG_OVERLAY_Z_INDEX, KANBAN_HORIZONTAL_EDGES, KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_SORTABLE_ACTIVATION_MODES, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KANBAN_VIRTUAL_CELL_PAGINATION, KanbanCardDragSurface, KanbanCardShell, KanbanCellAction, KanbanColumnBandHeader, KanbanColumnHeader, KanbanDragHandle, KanbanSortableBoard, KanbanSortableColumns, KanbanSortableList, KanbanSubgroupBoard, KanbanVirtualCell, buildKanbanBoardMatrix, createKanbanDropIntent, getKanbanBoardColumnIdentity, getKanbanBoardLaneIdentity, getKanbanGroupingPropertyId, getKanbanGroups, getKanbanHorizontalEdge, hasKanbanColumnBands, isKanbanGroupingRenderable, kanbanKeyboardCoordinates, orderKanbanCellsByColumns, registerKanbanBoardAutoScroll, registerKanbanCardDrag, resolveKanbanColumnBands, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanCardFieldIds, selectKanbanRows, useKanbanDropTarget, useKanbanSortable, useKanbanSortableSensors };
|
|
16
|
+
export { KANBAN_BAND_PEEK_DELAY_MS, KANBAN_BAND_PEEK_LINGER_MS, KANBAN_BOARD_AUTO_SCROLL_OPTIONS, KANBAN_BOARD_AUTO_SCROLL_SOURCES, KANBAN_BOARD_AXES, KANBAN_BOARD_COLLISION_DETECTION, KANBAN_CARD_DRAG_MIME, KANBAN_COLLAPSED_BAND_WIDTH_CLASS, KANBAN_COLLAPSED_BAND_WIDTH_PX, KANBAN_COLUMN_GAP_PX, KANBAN_COLUMN_WIDTH_CLASS, KANBAN_COLUMN_WIDTH_PX, KANBAN_DIRECTIONS, KANBAN_DRAG_OVERLAY_Z_INDEX, KANBAN_HORIZONTAL_EDGES, KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_SORTABLE_ACTIVATION_MODES, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KANBAN_VIRTUAL_CELL_PAGINATION, KanbanCardDragSurface, KanbanCardShell, KanbanCellAction, KanbanColumnBandHeader, KanbanColumnHeader, KanbanDragHandle, KanbanSortableBoard, KanbanSortableColumns, KanbanSortableList, KanbanSubgroupBoard, KanbanVirtualCell, buildKanbanBoardMatrix, createKanbanDropIntent, getKanbanBoardColumnIdentity, getKanbanBoardLaneIdentity, getKanbanGroupingPropertyId, getKanbanGroups, getKanbanHorizontalEdge, hasKanbanColumnBands, isKanbanGroupingRenderable, kanbanKeyboardCoordinates, orderKanbanCellsByColumns, registerKanbanBoardAutoScroll, registerKanbanCardDrag, resolveKanbanColumnBands, resolveKanbanGroupOptions, resolveKanbanGrouping, selectKanbanCardFieldIds, selectKanbanRows, useKanbanDropTarget, useKanbanSortable, useKanbanSortableSensors };
|
|
@@ -40,7 +40,19 @@ type KanbanSortableBoardProps = {
|
|
|
40
40
|
sensors?: DndContextProps["sensors"] | undefined;
|
|
41
41
|
/** Overrides dnd-kit's screen-reader announcements when supplied. */
|
|
42
42
|
accessibility?: DndContextProps["accessibility"] | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Fires on drag start and on every drag end or cancel; a board over a
|
|
45
|
+
* `KanbanSubgroupBoard` combines the two into that board's `isDragging`
|
|
46
|
+
* (`true` here, `false` on `onDragEnd`/`onDragCancel`).
|
|
47
|
+
*/
|
|
43
48
|
onDragStart?: ((event: KanbanDragStartEvent) => void) | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Fires whenever dnd-kit's collision detection changes the drop target
|
|
51
|
+
* the drag is over, `event.over` included when it becomes none. A board
|
|
52
|
+
* over a `KanbanSubgroupBoard` maps `event.over?.id ?? null` to a band id
|
|
53
|
+
* (via `KanbanSubgroupCellContext.band`, or a droppable registered for a
|
|
54
|
+
* folded band's slot) to drive that board's `dragOverBandId`.
|
|
55
|
+
*/
|
|
44
56
|
onDragOver?: ((event: KanbanDragOverEvent) => void) | undefined;
|
|
45
57
|
onDragCancel?: ((event: KanbanDragCancelEvent) => void) | undefined;
|
|
46
58
|
/** Called once mounted child drop targets can safely receive input. */
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { KanbanBandToggleActivation } from "./column-band-header.js";
|
|
1
2
|
import { KanbanColumnBand, KanbanGroup } from "./grouping.js";
|
|
2
3
|
import { KanbanBoardCell, KanbanBoardColumn, KanbanBoardMatrix } from "./matrix.js";
|
|
3
4
|
import { ReactNode } from "react";
|
|
4
5
|
//#region src/kanban/subgroup-board.d.ts
|
|
5
|
-
/** How long a pointer rests on a collapsed band before it peeks open. */
|
|
6
|
-
declare const KANBAN_BAND_PEEK_DELAY_MS = 400;
|
|
7
6
|
type KanbanSubgroupColumnHeaderContext = {
|
|
8
7
|
column: KanbanBoardColumn;
|
|
9
8
|
count: number;
|
|
@@ -17,6 +16,12 @@ type KanbanSubgroupCellContext<TRow> = {
|
|
|
17
16
|
/** Number of rows in this lane/column intersection, including zero. */
|
|
18
17
|
count: number;
|
|
19
18
|
laneValue: string | null;
|
|
19
|
+
/**
|
|
20
|
+
* The band this cell's column belongs to, or `null` outside any band. Lets
|
|
21
|
+
* a host that drives its own drag-and-drop (see `dragOverBandId` on the
|
|
22
|
+
* board) map a cell's droppable id back to the band it should report.
|
|
23
|
+
*/
|
|
24
|
+
band: KanbanColumnBand | null;
|
|
20
25
|
};
|
|
21
26
|
type KanbanSubgroupBandHeaderContext = {
|
|
22
27
|
band: KanbanColumnBand;
|
|
@@ -31,7 +36,12 @@ type KanbanSubgroupBandHeaderContext = {
|
|
|
31
36
|
* name and offer to pin the band open.
|
|
32
37
|
*/
|
|
33
38
|
folded: boolean;
|
|
34
|
-
|
|
39
|
+
/**
|
|
40
|
+
* A custom caption may report which way it was activated (pointer vs.
|
|
41
|
+
* keyboard); the board itself has no use for the distinction and passes
|
|
42
|
+
* it through unread, purely as information for a host's own header.
|
|
43
|
+
*/
|
|
44
|
+
onCollapsedChange: (collapsed: boolean, activation?: KanbanBandToggleActivation) => void;
|
|
35
45
|
};
|
|
36
46
|
/**
|
|
37
47
|
* One lane's slot for a collapsed band: the cells it hides, so a host can
|
|
@@ -78,6 +88,27 @@ type KanbanSubgroupBoardProps<TRow> = {
|
|
|
78
88
|
formatCount?: ((count: number) => ReactNode) | undefined;
|
|
79
89
|
footer?: ReactNode;
|
|
80
90
|
className?: string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* The band whose folded slot (or, for a peeked band, whose open part) the
|
|
93
|
+
* host's own drag is currently over, or `null` when it is over neither.
|
|
94
|
+
* Omit (`undefined`) when the host does not drive drags itself — the
|
|
95
|
+
* board's native `dragover`/`dragenter`/`dragleave` listeners already feed
|
|
96
|
+
* the peek controller in that case. A host drives its own drag when it
|
|
97
|
+
* builds the drop targets itself (for example a `KanbanSortableBoard`,
|
|
98
|
+
* whose `onDragOver` reports the active `over` droppable); it maps that
|
|
99
|
+
* droppable back to a band id — a folded cell's context and an open
|
|
100
|
+
* cell's context (see `KanbanSubgroupCellContext.band`) both carry the
|
|
101
|
+
* band — and passes it here. The controller treats "over the band" as
|
|
102
|
+
* entering the open band when it is already peeked, and as hovering its
|
|
103
|
+
* folded slot otherwise; leaving it is reported the same way.
|
|
104
|
+
*/
|
|
105
|
+
dragOverBandId?: string | null | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Whether the host's own drag (see `dragOverBandId`) is in progress.
|
|
108
|
+
* Flipping to `false` ends any peek immediately, mirroring the native
|
|
109
|
+
* `dragend`/`drop` listeners the board installs for its own drags.
|
|
110
|
+
*/
|
|
111
|
+
isDragging?: boolean | undefined;
|
|
81
112
|
} & KanbanSubgroupCollapseControl;
|
|
82
113
|
/**
|
|
83
114
|
* Reusable swimlane layout over the canonical two-axis Kanban matrix.
|
|
@@ -85,12 +116,12 @@ type KanbanSubgroupBoardProps<TRow> = {
|
|
|
85
116
|
* Columns that carry band metadata render under a one-line band caption and
|
|
86
117
|
* can be collapsed as a run: the band folds into one narrow slot in every
|
|
87
118
|
* row, whose cells stay reachable (a host renders its drop target in them),
|
|
88
|
-
* and peeks open while a
|
|
89
|
-
* specific column inside. Every row is laid out span by span with the same
|
|
119
|
+
* and peeks open while a dragged card rests on it, so a drag can still land
|
|
120
|
+
* on a specific column inside; a plain hover never opens it. Every row is laid out span by span with the same
|
|
90
121
|
* widths, which is what keeps captions, headers, counts, and cells aligned
|
|
91
122
|
* in every state; the caption line never grows past its own height, so a
|
|
92
123
|
* folded band costs no vertical space.
|
|
93
124
|
*/
|
|
94
|
-
declare const KanbanSubgroupBoard: <TRow>({ matrix, renderColumnHeader, renderLaneIdentity, renderCell, renderBandHeader, renderCollapsedBandCell, formatBandToggleLabel, formatCount, isLaneCollapsed, onLaneCollapsedChange, isBandCollapsed, onBandCollapsedChange, footer, className }: KanbanSubgroupBoardProps<TRow>) => import("react").JSX.Element;
|
|
125
|
+
declare const KanbanSubgroupBoard: <TRow>({ matrix, renderColumnHeader, renderLaneIdentity, renderCell, renderBandHeader, renderCollapsedBandCell, formatBandToggleLabel, formatCount, isLaneCollapsed, onLaneCollapsedChange, isBandCollapsed, onBandCollapsedChange, footer, className, dragOverBandId, isDragging }: KanbanSubgroupBoardProps<TRow>) => import("react").JSX.Element;
|
|
95
126
|
//#endregion
|
|
96
|
-
export {
|
|
127
|
+
export { KanbanSubgroupBandHeaderContext, KanbanSubgroupBoard, KanbanSubgroupBoardProps, KanbanSubgroupCellContext, KanbanSubgroupCollapsedBandCellContext, KanbanSubgroupColumnHeaderContext, KanbanSubgroupLaneIdentityContext };
|
|
@@ -2,6 +2,8 @@ import { cn } from "../lib/utils.js";
|
|
|
2
2
|
import { DirectionalIcon } from "../components/directional-icon.js";
|
|
3
3
|
import { KanbanColumnBandHeader } from "./column-band-header.js";
|
|
4
4
|
import { KANBAN_COLLAPSED_BAND_WIDTH_CLASS, KANBAN_COLUMN_WIDTH_CLASS, resolveKanbanColumnBands } from "./column-bands.js";
|
|
5
|
+
import { KANBAN_CARD_DRAG_MIME } from "./drag-interactions.js";
|
|
6
|
+
import { createBandPeekController } from "./band-peek.js";
|
|
5
7
|
import { ChevronDownIcon } from "lucide-react";
|
|
6
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
9
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -10,25 +12,54 @@ const groupValueKey = (value) => value === null ? "null" : `value:${value.length
|
|
|
10
12
|
const columnKey = (column) => column.type === "group" ? `group:${groupValueKey(column.group.value)}` : `destination:${column.destination.id}`;
|
|
11
13
|
const spanKey = (span) => span.band === null ? `single:${span.columns.map(columnKey).join("|")}` : `band:${span.band.id}`;
|
|
12
14
|
const rowsIn = (cells) => cells.reduce((sum, cell) => sum + cell.rows.length, 0);
|
|
13
|
-
/** How long a pointer rests on a collapsed band before it peeks open. */
|
|
14
|
-
const KANBAN_BAND_PEEK_DELAY_MS = 400;
|
|
15
15
|
/**
|
|
16
16
|
* Reusable swimlane layout over the canonical two-axis Kanban matrix.
|
|
17
17
|
*
|
|
18
18
|
* Columns that carry band metadata render under a one-line band caption and
|
|
19
19
|
* can be collapsed as a run: the band folds into one narrow slot in every
|
|
20
20
|
* row, whose cells stay reachable (a host renders its drop target in them),
|
|
21
|
-
* and peeks open while a
|
|
22
|
-
* specific column inside. Every row is laid out span by span with the same
|
|
21
|
+
* and peeks open while a dragged card rests on it, so a drag can still land
|
|
22
|
+
* on a specific column inside; a plain hover never opens it. Every row is laid out span by span with the same
|
|
23
23
|
* widths, which is what keeps captions, headers, counts, and cells aligned
|
|
24
24
|
* in every state; the caption line never grows past its own height, so a
|
|
25
25
|
* folded band costs no vertical space.
|
|
26
26
|
*/
|
|
27
|
-
const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, renderCell, renderBandHeader, renderCollapsedBandCell, formatBandToggleLabel, formatCount = String, isLaneCollapsed, onLaneCollapsedChange, isBandCollapsed, onBandCollapsedChange, footer, className }) => {
|
|
27
|
+
const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, renderCell, renderBandHeader, renderCollapsedBandCell, formatBandToggleLabel, formatCount = String, isLaneCollapsed, onLaneCollapsedChange, isBandCollapsed, onBandCollapsedChange, footer, className, dragOverBandId, isDragging }) => {
|
|
28
28
|
const [collapsedLaneValues, setCollapsedLaneValues] = useState(() => /* @__PURE__ */ new Set());
|
|
29
29
|
const [expandedEmptyLaneValues, setExpandedEmptyLaneValues] = useState(() => /* @__PURE__ */ new Set());
|
|
30
30
|
const [collapsedBandIds, setCollapsedBandIds] = useState(() => /* @__PURE__ */ new Set());
|
|
31
31
|
const [peekingBandId, setPeekingBandId] = useState(null);
|
|
32
|
+
const [peek] = useState(() => createBandPeekController({ onChange: setPeekingBandId }));
|
|
33
|
+
useEffect(() => () => peek.dispose(), [peek]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const end = () => peek.dragEnded();
|
|
36
|
+
window.addEventListener("dragend", end);
|
|
37
|
+
window.addEventListener("drop", end);
|
|
38
|
+
return () => {
|
|
39
|
+
window.removeEventListener("dragend", end);
|
|
40
|
+
window.removeEventListener("drop", end);
|
|
41
|
+
};
|
|
42
|
+
}, [peek]);
|
|
43
|
+
const previousDragOverBandId = useRef(null);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (dragOverBandId === void 0 || dragOverBandId === previousDragOverBandId.current) return;
|
|
46
|
+
const previous = previousDragOverBandId.current;
|
|
47
|
+
previousDragOverBandId.current = dragOverBandId;
|
|
48
|
+
if (previous !== null) if (peekingBandId === previous) peek.openDragLeave(previous);
|
|
49
|
+
else peek.slotDragLeave(previous);
|
|
50
|
+
if (dragOverBandId !== null) if (peekingBandId === dragOverBandId) peek.openDragEnter(dragOverBandId);
|
|
51
|
+
else peek.slotDragOver(dragOverBandId);
|
|
52
|
+
}, [
|
|
53
|
+
dragOverBandId,
|
|
54
|
+
peek,
|
|
55
|
+
peekingBandId
|
|
56
|
+
]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (isDragging === false) {
|
|
59
|
+
previousDragOverBandId.current = null;
|
|
60
|
+
peek.dragEnded();
|
|
61
|
+
}
|
|
62
|
+
}, [isDragging, peek]);
|
|
32
63
|
const { cellsByLaneValue, countByColumnValue, ungroupedCells } = useMemo(() => {
|
|
33
64
|
const laneCells = /* @__PURE__ */ new Map();
|
|
34
65
|
const columnCounts = /* @__PURE__ */ new Map();
|
|
@@ -83,8 +114,14 @@ const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, r
|
|
|
83
114
|
* itself collapsed and its toggle pins it open rather than closing it.
|
|
84
115
|
*/
|
|
85
116
|
const isBandFolded = (band) => peekingBandId !== band.id && isBandCollapsedNow(band);
|
|
117
|
+
const peekedBand = peekingBandId === null ? null : spans.find((span) => span.band?.id === peekingBandId)?.band ?? null;
|
|
118
|
+
const staleBandId = peekingBandId !== null && (peekedBand === null || !isBandCollapsedNow(peekedBand)) ? peekingBandId : null;
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (staleBandId !== null) peek.bandExpanded(staleBandId);
|
|
121
|
+
}, [peek, staleBandId]);
|
|
86
122
|
const setBandCollapsed = (band, collapsed) => {
|
|
87
|
-
|
|
123
|
+
if (collapsed) peek.bandFolded(band.id);
|
|
124
|
+
else peek.bandExpanded(band.id);
|
|
88
125
|
if (onBandCollapsedChange) {
|
|
89
126
|
onBandCollapsedChange(band, collapsed);
|
|
90
127
|
return;
|
|
@@ -96,7 +133,6 @@ const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, r
|
|
|
96
133
|
return next;
|
|
97
134
|
});
|
|
98
135
|
};
|
|
99
|
-
const endPeek = () => setPeekingBandId(null);
|
|
100
136
|
const columnCount = (column) => countByColumnValue.get(columnKey(column)) ?? 0;
|
|
101
137
|
const cellsOf = (span, cells) => span.columns.flatMap((column) => cells.filter((cell) => columnKey(cell.coordinate.column) === columnKey(column)));
|
|
102
138
|
const spanWidth = (span) => span.columns.length * 300 + (span.columns.length - 1) * 12;
|
|
@@ -112,17 +148,21 @@ const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, r
|
|
|
112
148
|
if (band !== null && isBandFolded(band)) return /* @__PURE__ */ jsx(FoldedBandSlot, {
|
|
113
149
|
band,
|
|
114
150
|
className: KANBAN_COLLAPSED_BAND_WIDTH_CLASS,
|
|
115
|
-
|
|
116
|
-
onPeekEnd: endPeek,
|
|
151
|
+
peek,
|
|
117
152
|
children: renderFoldedBand(band, span)
|
|
118
153
|
}, spanKey(span));
|
|
119
154
|
return /* @__PURE__ */ jsx("div", {
|
|
120
155
|
className: "flex gap-3",
|
|
121
156
|
"data-kanban-band": band?.id,
|
|
122
|
-
|
|
157
|
+
onDragEnter: band === null ? void 0 : (event) => {
|
|
158
|
+
if (isKanbanCardDragEvent(event)) peek.openDragEnter(band.id);
|
|
159
|
+
},
|
|
160
|
+
onDragLeave: band === null ? void 0 : (event) => {
|
|
161
|
+
if (leavesElement(event) && isKanbanCardDragEvent(event)) peek.openDragLeave(band.id);
|
|
162
|
+
},
|
|
123
163
|
children: span.columns.map((column) => /* @__PURE__ */ jsx("div", {
|
|
124
164
|
className: KANBAN_COLUMN_WIDTH_CLASS,
|
|
125
|
-
children: renderColumn(column)
|
|
165
|
+
children: renderColumn(column, band)
|
|
126
166
|
}, columnKey(column)))
|
|
127
167
|
}, spanKey(span));
|
|
128
168
|
})
|
|
@@ -199,15 +239,19 @@ const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, r
|
|
|
199
239
|
if (isBandFolded(band)) return /* @__PURE__ */ jsx(FoldedBandSlot, {
|
|
200
240
|
band,
|
|
201
241
|
className: cn("border-border/60 border-b", KANBAN_COLLAPSED_BAND_WIDTH_CLASS),
|
|
202
|
-
|
|
203
|
-
onPeekEnd: endPeek,
|
|
242
|
+
peek,
|
|
204
243
|
children: bandHeader(band, span)
|
|
205
244
|
}, spanKey(span));
|
|
206
245
|
return /* @__PURE__ */ jsx("div", {
|
|
207
246
|
className: "border-border/60 shrink-0 border-b",
|
|
208
247
|
"data-kanban-band": band.id,
|
|
209
248
|
style: { width: `${String(spanWidth(span))}px` },
|
|
210
|
-
|
|
249
|
+
onDragEnter: (event) => {
|
|
250
|
+
if (isKanbanCardDragEvent(event)) peek.openDragEnter(band.id);
|
|
251
|
+
},
|
|
252
|
+
onDragLeave: (event) => {
|
|
253
|
+
if (leavesElement(event) && isKanbanCardDragEvent(event)) peek.openDragLeave(band.id);
|
|
254
|
+
},
|
|
211
255
|
children: bandHeader(band, span)
|
|
212
256
|
}, spanKey(span));
|
|
213
257
|
})
|
|
@@ -221,9 +265,10 @@ const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, r
|
|
|
221
265
|
}),
|
|
222
266
|
ungroupedCells.length === 0 ? null : renderRow({
|
|
223
267
|
className: "pb-1",
|
|
224
|
-
renderColumn: (column) => {
|
|
268
|
+
renderColumn: (column, band) => {
|
|
225
269
|
const cell = ungroupedCells.find((candidate) => columnKey(candidate.coordinate.column) === columnKey(column));
|
|
226
270
|
return cell === void 0 ? null : /* @__PURE__ */ jsx(Fragment, { children: renderCell({
|
|
271
|
+
band,
|
|
227
272
|
cell,
|
|
228
273
|
count: cell.rows.length,
|
|
229
274
|
laneValue: null
|
|
@@ -283,9 +328,10 @@ const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, r
|
|
|
283
328
|
}),
|
|
284
329
|
!collapsed && renderRow({
|
|
285
330
|
className: "pb-1",
|
|
286
|
-
renderColumn: (column) => {
|
|
331
|
+
renderColumn: (column, band) => {
|
|
287
332
|
const cell = cellFor(column);
|
|
288
333
|
return cell === void 0 ? null : /* @__PURE__ */ jsx(Fragment, { children: renderCell({
|
|
334
|
+
band,
|
|
289
335
|
cell,
|
|
290
336
|
count: cell.rows.length,
|
|
291
337
|
laneValue: group.value
|
|
@@ -302,37 +348,35 @@ const KanbanSubgroupBoard = ({ matrix, renderColumnHeader, renderLaneIdentity, r
|
|
|
302
348
|
});
|
|
303
349
|
};
|
|
304
350
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
* and cancels a pending one, so a drag passing over it does not unfold it.
|
|
308
|
-
* The pointer must enter the slot after it appeared: a band folded under a
|
|
309
|
-
* resting pointer does not peek straight back open.
|
|
351
|
+
* Whether a drag-leave event actually leaves `currentTarget` rather than
|
|
352
|
+
* moving between its descendants, which fire leave/enter pairs of their own.
|
|
310
353
|
*/
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
354
|
+
const leavesElement = (event) => !(event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget));
|
|
355
|
+
/**
|
|
356
|
+
* Whether a native drag event is a kanban card drag, rather than some other
|
|
357
|
+
* native drag (a column reorder, a file, text) passing over the board. Only
|
|
358
|
+
* a card drag should ever open or hold a band's peek.
|
|
359
|
+
*/
|
|
360
|
+
const isKanbanCardDragEvent = (event) => event.dataTransfer.types.includes(KANBAN_CARD_DRAG_MIME);
|
|
361
|
+
/**
|
|
362
|
+
* The narrow slot a folded band occupies in a row. Its drag events go to
|
|
363
|
+
* the board's peek controller, which decides when a resting drag peeks the
|
|
364
|
+
* band open.
|
|
365
|
+
*/
|
|
366
|
+
const FoldedBandSlot = ({ band, children, className, peek }) => {
|
|
367
|
+
useEffect(() => () => peek.slotUnmounted(band.id), [peek, band.id]);
|
|
316
368
|
return /* @__PURE__ */ jsx("div", {
|
|
317
369
|
className,
|
|
318
370
|
"data-kanban-band": band.id,
|
|
319
371
|
"data-kanban-band-collapsed": "",
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
timer.current = setTimeout(() => {
|
|
323
|
-
timer.current = null;
|
|
324
|
-
onPeek(band.id);
|
|
325
|
-
}, 400);
|
|
372
|
+
onDragOver: (event) => {
|
|
373
|
+
if (isKanbanCardDragEvent(event)) peek.slotDragOver(band.id);
|
|
326
374
|
},
|
|
327
|
-
|
|
328
|
-
if (
|
|
329
|
-
clearTimeout(timer.current);
|
|
330
|
-
timer.current = null;
|
|
331
|
-
}
|
|
332
|
-
onPeekEnd();
|
|
375
|
+
onDragLeave: (event) => {
|
|
376
|
+
if (leavesElement(event) && isKanbanCardDragEvent(event)) peek.slotDragLeave(band.id);
|
|
333
377
|
},
|
|
334
378
|
children
|
|
335
379
|
});
|
|
336
380
|
};
|
|
337
381
|
//#endregion
|
|
338
|
-
export {
|
|
382
|
+
export { KanbanSubgroupBoard };
|
|
@@ -5,6 +5,6 @@ declare const CONTROL_SIZE: Readonly<{
|
|
|
5
5
|
readonly lg: "lg";
|
|
6
6
|
}>;
|
|
7
7
|
type ControlSize = (typeof CONTROL_SIZE)[keyof typeof CONTROL_SIZE];
|
|
8
|
-
declare const CONTROL_SIZES: readonly ("
|
|
8
|
+
declare const CONTROL_SIZES: readonly ("default" | "lg" | "sm")[];
|
|
9
9
|
//#endregion
|
|
10
10
|
export { CONTROL_SIZE, CONTROL_SIZES, type ControlSize };
|