@stll/ui 0.9.0 → 0.12.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/README.md +90 -15
- package/dist/components/breadcrumb.d.ts +1 -1
- package/dist/components/button-variants.d.ts +2 -2
- package/dist/components/button.d.ts +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/command.d.ts +1 -1
- package/dist/components/command.js +1 -1
- package/dist/components/date-picker-popover.js +1 -1
- package/dist/components/input-group.d.ts +1 -1
- package/dist/components/pagination.d.ts +1 -1
- package/dist/components/pagination.js +1 -1
- package/dist/components/select.d.ts +1 -1
- package/dist/components/select.js +8 -10
- package/dist/components/workspace-shell.d.ts +77 -0
- package/dist/components/workspace-shell.js +106 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +11 -10
- package/dist/inspector/chrome.d.ts +5 -1
- package/dist/inspector/chrome.js +14 -2
- package/dist/inspector/index.d.ts +2 -2
- package/dist/inspector/index.js +2 -2
- package/dist/inspector/layout-tokens.d.ts +5 -1
- package/dist/inspector/layout-tokens.js +5 -1
- package/dist/kanban/grouping.d.ts +17 -17
- package/dist/kanban/index.d.ts +4 -3
- package/dist/kanban/index.js +3 -2
- package/dist/kanban/matrix.d.ts +15 -15
- package/dist/kanban/sortable-interactions.d.ts +75 -13
- package/dist/kanban/sortable-interactions.js +345 -28
- package/dist/kanban/sortable-interactions.logic.d.ts +79 -0
- package/dist/kanban/sortable-interactions.logic.js +205 -0
- package/dist/kanban/virtual-cell.d.ts +19 -2
- package/dist/kanban/virtual-cell.js +53 -7
- package/dist/lib/control-size.d.ts +1 -1
- package/dist/lib/overlay-layer.d.ts +2 -1
- package/dist/lib/overlay-layer.js +2 -1
- package/dist/review/review-comment-card.js +1 -1
- package/package.json +7 -7
- package/dist/components/application-shell.d.ts +0 -27
- package/dist/components/application-shell.js +0 -25
|
@@ -1,19 +1,64 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
+
import "../lib/overlay-layer.js";
|
|
3
4
|
import { Button } from "../components/button.js";
|
|
5
|
+
import { KANBAN_BOARD_COLLISION_DETECTION, KANBAN_DROP_TARGET_TYPES, clearKanbanKeyboardTarget, getKanbanKeyboardTargetState, isKanbanDropSettled, kanbanKeyboardCoordinates } from "./sortable-interactions.logic.js";
|
|
4
6
|
import { isActiveTouchChange } from "./touch-identity.js";
|
|
5
7
|
import { GripVerticalIcon } from "lucide-react";
|
|
6
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
9
|
import * as React$1 from "react";
|
|
8
10
|
import { createPortal } from "react-dom";
|
|
9
|
-
import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
|
|
10
|
-
import { SortableContext,
|
|
11
|
+
import { AutoScrollActivator, DndContext, DragOverlay, KeyboardSensor, MouseSensor, TouchSensor, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
12
|
+
import { SortableContext, useSortable } from "@dnd-kit/sortable";
|
|
11
13
|
//#region src/kanban/sortable-interactions.tsx
|
|
12
14
|
const KANBAN_MOUSE_ACTIVATION_DISTANCE = 8;
|
|
15
|
+
/** Above sticky board chrome, below app-level floating surfaces. */
|
|
16
|
+
const KANBAN_DRAG_OVERLAY_Z_INDEX = 75;
|
|
17
|
+
const KANBAN_BOARD_AUTO_SCROLL_OPTIONS = {
|
|
18
|
+
acceleration: 10,
|
|
19
|
+
activator: AutoScrollActivator.Pointer,
|
|
20
|
+
threshold: {
|
|
21
|
+
x: .15,
|
|
22
|
+
y: .15
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const KANBAN_SORTABLE_ACTIVATION_MODES = {
|
|
26
|
+
CARD: "card",
|
|
27
|
+
HANDLE: "handle",
|
|
28
|
+
ITEM: "item"
|
|
29
|
+
};
|
|
13
30
|
const KANBAN_TOUCH_ACTIVATION_CONSTRAINT = {
|
|
14
31
|
delay: 150,
|
|
15
32
|
tolerance: 8
|
|
16
33
|
};
|
|
34
|
+
const KANBAN_KEYBOARD_CODES = {
|
|
35
|
+
cancel: ["Escape"],
|
|
36
|
+
end: [
|
|
37
|
+
"Space",
|
|
38
|
+
"Enter",
|
|
39
|
+
"Tab"
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
const KANBAN_KEYBOARD_TARGET_RETRY_LIMIT = 60;
|
|
43
|
+
/** A render commit, not a virtual scroll, so the budget stays short. */
|
|
44
|
+
const KANBAN_DROP_SETTLE_FRAME_LIMIT = 10;
|
|
45
|
+
/**
|
|
46
|
+
* Defers a drop until dnd-kit has published the collision target it computed.
|
|
47
|
+
* Every sensor ends a drag from a single input event, so without this wait the
|
|
48
|
+
* drop resolves against the target published before that event.
|
|
49
|
+
*/
|
|
50
|
+
const endWhenDropTargetSettled = (context, end) => {
|
|
51
|
+
let framesWaited = 0;
|
|
52
|
+
const attempt = () => {
|
|
53
|
+
if (framesWaited >= KANBAN_DROP_SETTLE_FRAME_LIMIT || isKanbanDropSettled(context.current)) {
|
|
54
|
+
end();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
framesWaited += 1;
|
|
58
|
+
requestAnimationFrame(attempt);
|
|
59
|
+
};
|
|
60
|
+
attempt();
|
|
61
|
+
};
|
|
17
62
|
const hasTouchLists = (event) => "changedTouches" in event && "touches" in event;
|
|
18
63
|
const getTouchIdentifier = (event) => {
|
|
19
64
|
if (!hasTouchLists(event)) return null;
|
|
@@ -54,7 +99,9 @@ var KanbanTouchSensor = class extends TouchSensor {
|
|
|
54
99
|
},
|
|
55
100
|
onEnd: () => {
|
|
56
101
|
identityListeners.abort();
|
|
57
|
-
props.
|
|
102
|
+
endWhenDropTargetSettled(props.context, () => {
|
|
103
|
+
props.onEnd();
|
|
104
|
+
});
|
|
58
105
|
}
|
|
59
106
|
});
|
|
60
107
|
this.identityListeners = identityListeners;
|
|
@@ -100,10 +147,190 @@ var KanbanTouchSensor = class extends TouchSensor {
|
|
|
100
147
|
this.identityListeners.abort();
|
|
101
148
|
};
|
|
102
149
|
};
|
|
150
|
+
/** The stock mouse sensor, holding its drop until the board target settles. */
|
|
151
|
+
var KanbanMouseSensor = class extends MouseSensor {
|
|
152
|
+
constructor(props) {
|
|
153
|
+
super({
|
|
154
|
+
...props,
|
|
155
|
+
onEnd: () => {
|
|
156
|
+
endWhenDropTargetSettled(props.context, () => {
|
|
157
|
+
props.onEnd();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Applies board coordinates without the stock sensor's one-dimensional source
|
|
165
|
+
* scroll clamping. The coordinate getter moves to the typed target and then
|
|
166
|
+
* scrolls that virtual item into view.
|
|
167
|
+
*/
|
|
168
|
+
var KanbanKeyboardSensor = class {
|
|
169
|
+
static activators = KeyboardSensor.activators;
|
|
170
|
+
autoScrollEnabled = false;
|
|
171
|
+
/** Input handling, released as soon as a drag stops accepting keys. */
|
|
172
|
+
listeners = new AbortController();
|
|
173
|
+
/**
|
|
174
|
+
* The drag itself, released only once it resolves. Ending must not stop the
|
|
175
|
+
* queued retry that still has to bring a pending virtual target into view.
|
|
176
|
+
*/
|
|
177
|
+
lifetime = new AbortController();
|
|
178
|
+
props;
|
|
179
|
+
moveSequence = 0;
|
|
180
|
+
referenceCoordinates = null;
|
|
181
|
+
constructor(props) {
|
|
182
|
+
this.props = props;
|
|
183
|
+
props.onStart({
|
|
184
|
+
x: 0,
|
|
185
|
+
y: 0
|
|
186
|
+
});
|
|
187
|
+
const ownerDocument = getTouchDocument(props.event);
|
|
188
|
+
ownerDocument.addEventListener("visibilitychange", this.handleCancel, { signal: this.listeners.signal });
|
|
189
|
+
ownerDocument.defaultView?.addEventListener("resize", this.handleCancel, { signal: this.listeners.signal });
|
|
190
|
+
ownerDocument.addEventListener("keydown", this.handleKeyDown, { signal: this.listeners.signal });
|
|
191
|
+
}
|
|
192
|
+
handleCancel = () => {
|
|
193
|
+
if (this.lifetime.signal.aborted) return;
|
|
194
|
+
this.moveSequence += 1;
|
|
195
|
+
this.listeners.abort();
|
|
196
|
+
this.finish(() => {
|
|
197
|
+
this.props.onCancel();
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
finish = (resolve) => {
|
|
201
|
+
clearKanbanKeyboardTarget(this.props.context.current.active?.data.current);
|
|
202
|
+
this.lifetime.abort();
|
|
203
|
+
resolve();
|
|
204
|
+
};
|
|
205
|
+
handleKeyDown = (event) => {
|
|
206
|
+
if (event === this.props.event) return;
|
|
207
|
+
const keyboardCodes = this.props.options.keyboardCodes;
|
|
208
|
+
const cancelCodes = keyboardCodes?.cancel ?? KANBAN_KEYBOARD_CODES.cancel;
|
|
209
|
+
const endCodes = keyboardCodes?.end ?? KANBAN_KEYBOARD_CODES.end;
|
|
210
|
+
if (cancelCodes.some((code) => code === event.code)) {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
this.handleCancel();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (endCodes.some((code) => code === event.code)) {
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
this.listeners.abort();
|
|
218
|
+
this.endWhenNavigationResolves(0);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.moveSequence += 1;
|
|
222
|
+
this.move(event, this.moveSequence, 0);
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* A pending target has asked a virtual cell to mount an offscreen row and has
|
|
226
|
+
* published nothing, so the queued move retry, not this keypress, decides
|
|
227
|
+
* where the item lands. Cancel when that retry gives up: the board never
|
|
228
|
+
* reached the row the user navigated to, and the target still published is
|
|
229
|
+
* the one they navigated away from.
|
|
230
|
+
*/
|
|
231
|
+
endWhenNavigationResolves = (framesWaited) => {
|
|
232
|
+
if (this.lifetime.signal.aborted) return;
|
|
233
|
+
const target = getKanbanKeyboardTargetState(this.props.context.current.active?.data.current);
|
|
234
|
+
if (target?.type === "pending") {
|
|
235
|
+
if (framesWaited >= KANBAN_KEYBOARD_TARGET_RETRY_LIMIT) {
|
|
236
|
+
this.finish(() => {
|
|
237
|
+
this.props.onCancel();
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
requestAnimationFrame(() => {
|
|
242
|
+
this.endWhenNavigationResolves(framesWaited + 1);
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (framesWaited > 0 && target?.type !== "ready") {
|
|
247
|
+
this.finish(() => {
|
|
248
|
+
this.props.onCancel();
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
endWhenDropTargetSettled(this.props.context, () => {
|
|
253
|
+
const settled = isKanbanDropSettled(this.props.context.current);
|
|
254
|
+
this.finish(() => {
|
|
255
|
+
if (settled) {
|
|
256
|
+
this.props.onEnd();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
this.props.onCancel();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
move = (event, sequence, retryCount) => {
|
|
264
|
+
const currentContext = this.props.context.current;
|
|
265
|
+
if (currentContext.active?.id !== this.props.active) {
|
|
266
|
+
requestAnimationFrame(() => {
|
|
267
|
+
if (this.listeners.signal.aborted || this.moveSequence !== sequence) return;
|
|
268
|
+
this.move(event, sequence, retryCount);
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const collisionRect = currentContext.collisionRect;
|
|
273
|
+
const currentCoordinates = collisionRect ? {
|
|
274
|
+
x: collisionRect.left,
|
|
275
|
+
y: collisionRect.top
|
|
276
|
+
} : {
|
|
277
|
+
x: 0,
|
|
278
|
+
y: 0
|
|
279
|
+
};
|
|
280
|
+
this.referenceCoordinates ??= currentCoordinates;
|
|
281
|
+
const activeData = currentContext.active.data.current;
|
|
282
|
+
const coordinates = this.props.options.coordinateGetter?.(event, {
|
|
283
|
+
active: this.props.active,
|
|
284
|
+
context: currentContext,
|
|
285
|
+
currentCoordinates
|
|
286
|
+
});
|
|
287
|
+
if (coordinates === void 0) {
|
|
288
|
+
if (getKanbanKeyboardTargetState(activeData)?.type !== "pending") return;
|
|
289
|
+
if (retryCount >= KANBAN_KEYBOARD_TARGET_RETRY_LIMIT) {
|
|
290
|
+
clearKanbanKeyboardTarget(activeData);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
requestAnimationFrame(() => {
|
|
294
|
+
if (this.lifetime.signal.aborted || this.moveSequence !== sequence) return;
|
|
295
|
+
this.move(event, sequence, retryCount + 1);
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this.props.onMove({
|
|
300
|
+
x: coordinates.x - this.referenceCoordinates.x,
|
|
301
|
+
y: coordinates.y - this.referenceCoordinates.y
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
};
|
|
103
305
|
const getTouchDocument = (event) => {
|
|
104
306
|
if (event.target instanceof Node && event.target.ownerDocument) return event.target.ownerDocument;
|
|
105
307
|
return document;
|
|
106
308
|
};
|
|
309
|
+
const KanbanInteractionReadinessContext = React$1.createContext(true);
|
|
310
|
+
/**
|
|
311
|
+
* Register a board-level drop target such as an empty cell or terminal lane.
|
|
312
|
+
*
|
|
313
|
+
* Board consumers should not need to couple their presentation code to the
|
|
314
|
+
* underlying drag-and-drop library just to receive an item from a sortable
|
|
315
|
+
* context.
|
|
316
|
+
*/
|
|
317
|
+
const useKanbanDropTarget = ({ disabled, id, itemIds, navigation, position }) => {
|
|
318
|
+
const interactionReady = React$1.useContext(KanbanInteractionReadinessContext);
|
|
319
|
+
const dropTarget = useDroppable({
|
|
320
|
+
data: {
|
|
321
|
+
itemIds,
|
|
322
|
+
navigation,
|
|
323
|
+
position,
|
|
324
|
+
type: KANBAN_DROP_TARGET_TYPES.CELL
|
|
325
|
+
},
|
|
326
|
+
disabled: !interactionReady || disabled === true,
|
|
327
|
+
id
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
isOver: dropTarget.isOver,
|
|
331
|
+
setNodeRef: dropTarget.setNodeRef
|
|
332
|
+
};
|
|
333
|
+
};
|
|
107
334
|
/**
|
|
108
335
|
* Input-complete drag context for sortable boards.
|
|
109
336
|
*
|
|
@@ -111,9 +338,24 @@ const getTouchDocument = (event) => {
|
|
|
111
338
|
* the sensor activation rules, keyboard navigation, auto-scroll configuration,
|
|
112
339
|
* and overlay lifecycle shared by sortable board UIs.
|
|
113
340
|
*/
|
|
114
|
-
const KanbanSortableBoard = ({ children, onDragEnd, collisionDetection, keyboardCoordinates, autoScroll, sensors, accessibility, onDragStart, onDragCancel, overlay, overlayProps }) => {
|
|
341
|
+
const KanbanSortableBoard = ({ children, onDragEnd, collisionDetection, keyboardCoordinates, autoScroll, sensors, accessibility, onDragStart, onDragOver, onDragCancel, onInteractionReady, overlay, overlayProps }) => {
|
|
115
342
|
const [activeId, setActiveId] = React$1.useState(null);
|
|
343
|
+
const [interactionReady, setInteractionReady] = React$1.useState(false);
|
|
344
|
+
const interactionReadyNotified = React$1.useRef(false);
|
|
116
345
|
const defaultSensors = useKanbanSortableSensors(keyboardCoordinates);
|
|
346
|
+
React$1.useEffect(() => {
|
|
347
|
+
const frame = requestAnimationFrame(() => {
|
|
348
|
+
setInteractionReady(true);
|
|
349
|
+
});
|
|
350
|
+
return () => {
|
|
351
|
+
cancelAnimationFrame(frame);
|
|
352
|
+
};
|
|
353
|
+
}, []);
|
|
354
|
+
React$1.useEffect(() => {
|
|
355
|
+
if (!interactionReady || interactionReadyNotified.current) return;
|
|
356
|
+
interactionReadyNotified.current = true;
|
|
357
|
+
onInteractionReady?.();
|
|
358
|
+
}, [interactionReady, onInteractionReady]);
|
|
117
359
|
const handleDragStart = (event) => {
|
|
118
360
|
setActiveId(event.active.id);
|
|
119
361
|
onDragStart?.(event);
|
|
@@ -126,21 +368,26 @@ const KanbanSortableBoard = ({ children, onDragEnd, collisionDetection, keyboard
|
|
|
126
368
|
setActiveId(null);
|
|
127
369
|
onDragCancel?.(event);
|
|
128
370
|
};
|
|
129
|
-
return /* @__PURE__ */
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
371
|
+
return /* @__PURE__ */ jsx(KanbanInteractionReadinessContext, {
|
|
372
|
+
value: interactionReady,
|
|
373
|
+
children: /* @__PURE__ */ jsxs(DndContext, {
|
|
374
|
+
autoScroll: autoScroll ?? KANBAN_BOARD_AUTO_SCROLL_OPTIONS,
|
|
375
|
+
...accessibility === void 0 ? {} : { accessibility },
|
|
376
|
+
collisionDetection: collisionDetection ?? KANBAN_BOARD_COLLISION_DETECTION,
|
|
377
|
+
onDragCancel: handleDragCancel,
|
|
378
|
+
onDragEnd: handleDragEnd,
|
|
379
|
+
...onDragOver === void 0 ? {} : { onDragOver },
|
|
380
|
+
onDragStart: handleDragStart,
|
|
381
|
+
sensors: sensors ?? defaultSensors,
|
|
382
|
+
children: [children, overlay && typeof document !== "undefined" ? createPortal(/* @__PURE__ */ jsx(DragOverlay, {
|
|
383
|
+
...overlayProps,
|
|
384
|
+
zIndex: overlayProps?.zIndex ?? KANBAN_DRAG_OVERLAY_Z_INDEX,
|
|
385
|
+
children: overlay(activeId)
|
|
386
|
+
}), document.body) : null]
|
|
387
|
+
})
|
|
141
388
|
});
|
|
142
389
|
};
|
|
143
|
-
const useKanbanSortableSensors = (keyboardCoordinates =
|
|
390
|
+
const useKanbanSortableSensors = (keyboardCoordinates = kanbanKeyboardCoordinates) => useSensors(useSensor(KanbanMouseSensor, { activationConstraint: { distance: 8 } }), useSensor(KanbanTouchSensor, { activationConstraint: KANBAN_TOUCH_ACTIVATION_CONSTRAINT }), useSensor(KanbanKeyboardSensor, { coordinateGetter: keyboardCoordinates }));
|
|
144
391
|
/** A vertical card list that preserves native vertical touch scrolling. */
|
|
145
392
|
const KanbanSortableList = ({ className, children, id, items, strategy, disabled, ...props }) => /* @__PURE__ */ jsx(SortableContext, {
|
|
146
393
|
...disabled === void 0 ? {} : { disabled },
|
|
@@ -169,22 +416,48 @@ const KanbanSortableColumns = ({ className, children, id, items, strategy, disab
|
|
|
169
416
|
* Connect a sortable item and its separate drag handle without making the
|
|
170
417
|
* item's content a touch-none activation surface.
|
|
171
418
|
*/
|
|
172
|
-
const useKanbanSortable = ({ id, disabled }) => {
|
|
419
|
+
const useKanbanSortable = ({ activation, id, disabled }) => {
|
|
420
|
+
const interactionReady = React$1.useContext(KanbanInteractionReadinessContext);
|
|
421
|
+
const data = React$1.useMemo(() => ({
|
|
422
|
+
navigation: { current: { type: "idle" } },
|
|
423
|
+
type: KANBAN_DROP_TARGET_TYPES.ITEM
|
|
424
|
+
}), []);
|
|
173
425
|
const sortable = useSortable({
|
|
174
|
-
|
|
175
|
-
|
|
426
|
+
data,
|
|
427
|
+
disabled: !interactionReady || disabled === true,
|
|
428
|
+
id
|
|
176
429
|
});
|
|
430
|
+
const setNodeRef = (element) => {
|
|
431
|
+
sortable.setNodeRef(element);
|
|
432
|
+
if (activation.type === KANBAN_SORTABLE_ACTIVATION_MODES.ITEM) sortable.setActivatorNodeRef(element);
|
|
433
|
+
};
|
|
434
|
+
const bindings = {
|
|
435
|
+
attributes: sortable.attributes,
|
|
436
|
+
listeners: sortable.listeners,
|
|
437
|
+
setActivatorNodeRef: sortable.setActivatorNodeRef
|
|
438
|
+
};
|
|
439
|
+
let activator;
|
|
440
|
+
if (activation.type === KANBAN_SORTABLE_ACTIVATION_MODES.HANDLE) activator = {
|
|
441
|
+
bindings,
|
|
442
|
+
type: KANBAN_SORTABLE_ACTIVATION_MODES.HANDLE
|
|
443
|
+
};
|
|
444
|
+
else if (activation.type === KANBAN_SORTABLE_ACTIVATION_MODES.CARD) activator = {
|
|
445
|
+
bindings,
|
|
446
|
+
type: KANBAN_SORTABLE_ACTIVATION_MODES.CARD
|
|
447
|
+
};
|
|
448
|
+
else activator = {
|
|
449
|
+
attributes: sortable.attributes,
|
|
450
|
+
listeners: sortable.listeners,
|
|
451
|
+
type: KANBAN_SORTABLE_ACTIVATION_MODES.ITEM
|
|
452
|
+
};
|
|
177
453
|
return {
|
|
454
|
+
activator,
|
|
178
455
|
isDragging: sortable.isDragging,
|
|
179
|
-
setNodeRef
|
|
456
|
+
setNodeRef,
|
|
180
457
|
style: {
|
|
458
|
+
touchAction: activation.type === KANBAN_SORTABLE_ACTIVATION_MODES.ITEM ? "none" : void 0,
|
|
181
459
|
transform: sortable.transform ? `translate3d(${sortable.transform.x}px, ${sortable.transform.y}px, 0)` : void 0,
|
|
182
460
|
transition: sortable.transition
|
|
183
|
-
},
|
|
184
|
-
dragHandle: {
|
|
185
|
-
attributes: sortable.attributes,
|
|
186
|
-
listeners: sortable.listeners,
|
|
187
|
-
setActivatorNodeRef: sortable.setActivatorNodeRef
|
|
188
461
|
}
|
|
189
462
|
};
|
|
190
463
|
};
|
|
@@ -198,7 +471,7 @@ const KanbanDragHandle = ({ bindings, label, className, ...props }) => /* @__PUR
|
|
|
198
471
|
...bindings.attributes,
|
|
199
472
|
...bindings.listeners,
|
|
200
473
|
"aria-label": label,
|
|
201
|
-
className: cn("size-11 touch-none sm:size-11", className),
|
|
474
|
+
className: cn("size-11 min-h-11 min-w-11 touch-none sm:size-11", className),
|
|
202
475
|
ref: (element) => bindings.setActivatorNodeRef(element),
|
|
203
476
|
size: "icon-xl",
|
|
204
477
|
tooltip: false,
|
|
@@ -206,5 +479,49 @@ const KanbanDragHandle = ({ bindings, label, className, ...props }) => /* @__PUR
|
|
|
206
479
|
variant: "ghost",
|
|
207
480
|
children: /* @__PURE__ */ jsx(GripVerticalIcon, { "aria-hidden": "true" })
|
|
208
481
|
});
|
|
482
|
+
const KANBAN_CARD_DRAG_SURFACE_INTERACTIVE_SELECTOR = "a,button,input,select,textarea,[contenteditable=true],[role=button],[data-kanban-drag-exempt]";
|
|
483
|
+
const isKanbanCardDragSurfaceInteractiveTarget = (target, activator) => {
|
|
484
|
+
if (!(target instanceof Element)) return false;
|
|
485
|
+
const interactiveTarget = target.closest(KANBAN_CARD_DRAG_SURFACE_INTERACTIVE_SELECTOR);
|
|
486
|
+
return interactiveTarget !== null && interactiveTarget !== activator;
|
|
487
|
+
};
|
|
488
|
+
const getKanbanCardDragSurfaceAttributes = ({ "aria-describedby": _describedBy, "aria-disabled": _disabled, "aria-pressed": _pressed, "aria-roledescription": _roleDescription, role: _role, tabIndex, ...attributes }) => ({
|
|
489
|
+
...attributes,
|
|
490
|
+
tabIndex
|
|
491
|
+
});
|
|
492
|
+
const isKanbanCardDragSurfaceListener = (listener) => typeof listener === "function";
|
|
493
|
+
const getKanbanCardDragSurfaceListeners = (listeners) => {
|
|
494
|
+
const { onKeyDown, onMouseDown, onPointerDown, onTouchStart, ...rest } = listeners;
|
|
495
|
+
const shouldActivate = (event) => !isKanbanCardDragSurfaceInteractiveTarget(event.target, event.currentTarget);
|
|
496
|
+
return {
|
|
497
|
+
...rest,
|
|
498
|
+
...!isKanbanCardDragSurfaceListener(onKeyDown) ? {} : { onKeyDown: (event) => {
|
|
499
|
+
if (shouldActivate(event)) onKeyDown(event);
|
|
500
|
+
} },
|
|
501
|
+
...!isKanbanCardDragSurfaceListener(onMouseDown) ? {} : { onMouseDown: (event) => {
|
|
502
|
+
if (shouldActivate(event)) onMouseDown(event);
|
|
503
|
+
} },
|
|
504
|
+
...!isKanbanCardDragSurfaceListener(onPointerDown) ? {} : { onPointerDown: (event) => {
|
|
505
|
+
if (shouldActivate(event)) onPointerDown(event);
|
|
506
|
+
} },
|
|
507
|
+
...!isKanbanCardDragSurfaceListener(onTouchStart) ? {} : { onTouchStart: (event) => {
|
|
508
|
+
if (shouldActivate(event)) onTouchStart(event);
|
|
509
|
+
} }
|
|
510
|
+
};
|
|
511
|
+
};
|
|
512
|
+
/** A whole-card drag activator that preserves descendant controls and scrolling. */
|
|
513
|
+
const KanbanCardDragSurface = ({ bindings, className, ...props }) => {
|
|
514
|
+
const { listeners, setActivatorNodeRef: setCardActivatorNodeRef } = bindings;
|
|
515
|
+
const setActivatorNodeRef = React$1.useCallback((element) => {
|
|
516
|
+
setCardActivatorNodeRef(element);
|
|
517
|
+
}, [setCardActivatorNodeRef]);
|
|
518
|
+
return /* @__PURE__ */ jsx("div", {
|
|
519
|
+
...props,
|
|
520
|
+
...getKanbanCardDragSurfaceAttributes(bindings.attributes),
|
|
521
|
+
...listeners === void 0 ? {} : getKanbanCardDragSurfaceListeners(listeners),
|
|
522
|
+
className: cn("touch-auto", className),
|
|
523
|
+
ref: setActivatorNodeRef
|
|
524
|
+
});
|
|
525
|
+
};
|
|
209
526
|
//#endregion
|
|
210
|
-
export { KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KanbanDragHandle, KanbanSortableBoard, KanbanSortableColumns, KanbanSortableList, useKanbanSortable, useKanbanSortableSensors };
|
|
527
|
+
export { KANBAN_BOARD_AUTO_SCROLL_OPTIONS, KANBAN_BOARD_COLLISION_DETECTION, KANBAN_DRAG_OVERLAY_Z_INDEX, KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_SORTABLE_ACTIVATION_MODES, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KanbanCardDragSurface, KanbanDragHandle, KanbanSortableBoard, KanbanSortableColumns, KanbanSortableList, kanbanKeyboardCoordinates, useKanbanDropTarget, useKanbanSortable, useKanbanSortableSensors };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CollisionDetection, KeyboardCoordinateGetter, SensorContext, UniqueIdentifier } from "@dnd-kit/core";
|
|
2
|
+
//#region src/kanban/sortable-interactions.logic.d.ts
|
|
3
|
+
declare const KANBAN_DROP_TARGET_TYPES: {
|
|
4
|
+
readonly CELL: "kanban-cell";
|
|
5
|
+
readonly ITEM: "kanban-item";
|
|
6
|
+
};
|
|
7
|
+
type KanbanSortableCellPosition = {
|
|
8
|
+
column: number;
|
|
9
|
+
lane: number;
|
|
10
|
+
};
|
|
11
|
+
type KanbanVirtualScrollRequest = {
|
|
12
|
+
itemId: UniqueIdentifier;
|
|
13
|
+
type: "item";
|
|
14
|
+
};
|
|
15
|
+
type KanbanCellVirtualNavigation = {
|
|
16
|
+
type: "static";
|
|
17
|
+
} | {
|
|
18
|
+
requestScroll: (request: KanbanVirtualScrollRequest) => void;
|
|
19
|
+
type: "virtual";
|
|
20
|
+
};
|
|
21
|
+
type KanbanCellDropData = {
|
|
22
|
+
itemIds: readonly UniqueIdentifier[];
|
|
23
|
+
navigation: KanbanCellVirtualNavigation;
|
|
24
|
+
position: KanbanSortableCellPosition;
|
|
25
|
+
type: "kanban-cell";
|
|
26
|
+
};
|
|
27
|
+
type KanbanKeyboardTargetState = {
|
|
28
|
+
type: "idle";
|
|
29
|
+
} | {
|
|
30
|
+
targetId: UniqueIdentifier;
|
|
31
|
+
type: "pending";
|
|
32
|
+
} | {
|
|
33
|
+
targetId: UniqueIdentifier;
|
|
34
|
+
type: "ready";
|
|
35
|
+
};
|
|
36
|
+
type KanbanItemDropData = {
|
|
37
|
+
navigation: {
|
|
38
|
+
current: KanbanKeyboardTargetState;
|
|
39
|
+
};
|
|
40
|
+
type: "kanban-item";
|
|
41
|
+
};
|
|
42
|
+
type NavigationCell = KanbanCellDropData & {
|
|
43
|
+
id: UniqueIdentifier;
|
|
44
|
+
};
|
|
45
|
+
declare const isKanbanCellDropData: (value: unknown) => value is KanbanCellDropData;
|
|
46
|
+
declare const getKanbanKeyboardTargetState: (value: unknown) => KanbanKeyboardTargetState | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* dnd-kit computes collisions while rendering but publishes the resulting drop
|
|
49
|
+
* target to its sensor context a render later, and resolves a drop from that
|
|
50
|
+
* published value. A drag that ends before the two agree, which every input
|
|
51
|
+
* can do because a single move produces a single render, drops the item on the
|
|
52
|
+
* previously published target.
|
|
53
|
+
*
|
|
54
|
+
* Keyboard navigation names its target outright, so it is compared directly: a
|
|
55
|
+
* pending target is still waiting for a virtual cell to mount an offscreen row
|
|
56
|
+
* and has produced no collision at all, and a ready one is only settled once it
|
|
57
|
+
* is the published target. Until then both the collisions and the published
|
|
58
|
+
* target still describe where the board was before the user navigated away.
|
|
59
|
+
*/
|
|
60
|
+
declare const isKanbanDropSettled: ({ active, collisions, over }: Pick<SensorContext, "active" | "collisions" | "over">) => boolean;
|
|
61
|
+
declare const clearKanbanKeyboardTarget: (value: unknown) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Pointer and touch input must be inside a registered board target. Once that
|
|
64
|
+
* boundary is established, closest corners gives stable ranking across cells.
|
|
65
|
+
*/
|
|
66
|
+
declare const KANBAN_BOARD_COLLISION_DETECTION: CollisionDetection;
|
|
67
|
+
type NavigationDirection = "down" | "left" | "right" | "up";
|
|
68
|
+
type GetKeyboardTargetOptions = {
|
|
69
|
+
activeId: UniqueIdentifier;
|
|
70
|
+
cells: readonly NavigationCell[];
|
|
71
|
+
currentCellId: UniqueIdentifier;
|
|
72
|
+
currentOverId: UniqueIdentifier;
|
|
73
|
+
direction: NavigationDirection;
|
|
74
|
+
};
|
|
75
|
+
declare const getKanbanKeyboardTarget: ({ activeId, cells, currentCellId, currentOverId, direction }: GetKeyboardTargetOptions) => UniqueIdentifier | undefined;
|
|
76
|
+
/** Ordered two-dimensional navigation over item and empty-cell targets. */
|
|
77
|
+
declare const kanbanKeyboardCoordinates: KeyboardCoordinateGetter;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { KANBAN_BOARD_COLLISION_DETECTION, KANBAN_DROP_TARGET_TYPES, KanbanCellDropData, KanbanCellVirtualNavigation, KanbanItemDropData, KanbanSortableCellPosition, KanbanVirtualScrollRequest, clearKanbanKeyboardTarget, getKanbanKeyboardTarget, getKanbanKeyboardTargetState, isKanbanCellDropData, isKanbanDropSettled, kanbanKeyboardCoordinates };
|