@noya-app/noya-designsystem 0.1.63 → 0.1.65
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/.turbo/turbo-build.log +13 -10
- package/CHANGELOG.md +25 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +648 -333
- package/dist/index.d.ts +648 -333
- package/dist/index.js +13813 -9157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13949 -9269
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -22
- package/src/__tests__/validateDropIndicator.test.ts +26 -41
- package/src/__tests__/workspaceLayout.test.ts +281 -0
- package/src/components/ActionMenu.tsx +2 -0
- package/src/components/ActivityIndicator.tsx +13 -6
- package/src/components/ActivityLog.tsx +197 -0
- package/src/components/AnimatePresence.tsx +43 -26
- package/src/components/Avatar.tsx +45 -26
- package/src/components/Banner.tsx +57 -11
- package/src/components/BaseToolbar.tsx +67 -34
- package/src/components/Breadcrumbs.tsx +4 -2
- package/src/components/Button.tsx +28 -23
- package/src/components/Checkbox.tsx +6 -35
- package/src/components/Chip.tsx +47 -33
- package/src/components/Collection.tsx +20 -12
- package/src/components/ColorSwatch.tsx +42 -22
- package/src/components/ColorSwatchControl.tsx +7 -5
- package/src/components/Combobox.tsx +8 -6
- package/src/components/ComboboxMenu.tsx +6 -5
- package/src/components/CommandPalette.tsx +3 -1
- package/src/components/ContextMenu.tsx +3 -1
- package/src/components/Dialog.tsx +9 -13
- package/src/components/DimensionInput.tsx +2 -0
- package/src/components/Divider.tsx +18 -14
- package/src/components/DraggableMenuButton.tsx +11 -5
- package/src/components/Drawer.tsx +14 -6
- package/src/components/DropdownMenu.tsx +4 -2
- package/src/components/EditableText.tsx +4 -2
- package/src/components/Fade.tsx +13 -13
- package/src/components/FileUploadIndicator.tsx +108 -0
- package/src/components/FloatingWindow.tsx +4 -2
- package/src/components/Grid.tsx +58 -42
- package/src/components/GridView.tsx +65 -36
- package/src/components/IVirtualizedList.tsx +5 -0
- package/src/components/IconButton.tsx +2 -0
- package/src/components/Icons.tsx +24 -4
- package/src/components/InputField.tsx +21 -19
- package/src/components/InspectorContainer.tsx +2 -2
- package/src/components/InspectorPrimitives.tsx +15 -9
- package/src/components/Label.tsx +4 -3
- package/src/components/LabeledElementView.tsx +9 -5
- package/src/components/LabeledField.tsx +8 -6
- package/src/components/List.tsx +112 -46
- package/src/components/ListMenu.tsx +91 -0
- package/src/components/ListNavigator.tsx +106 -0
- package/src/components/ListView.tsx +22 -1147
- package/src/components/MediaThumbnail.tsx +84 -14
- package/src/components/Message.tsx +11 -12
- package/src/components/Navigator.tsx +461 -0
- package/src/components/NoyaLogo.tsx +1 -1
- package/src/components/Popover.tsx +12 -6
- package/src/components/Progress.tsx +31 -8
- package/src/components/ResizableContainer.tsx +187 -0
- package/src/components/RingProgress.tsx +128 -0
- package/src/components/ScrollArea.tsx +7 -5
- package/src/components/ScrollableSidebar.tsx +10 -0
- package/src/components/SearchCompletionMenu.tsx +5 -3
- package/src/components/Section.tsx +109 -57
- package/src/components/SegmentedControl.tsx +51 -25
- package/src/components/SelectMenu.tsx +16 -21
- package/src/components/SelectionToolbar.tsx +7 -4
- package/src/components/Slider.tsx +12 -10
- package/src/components/Spacer.tsx +2 -2
- package/src/components/StackNavigator.tsx +378 -0
- package/src/components/Stepper.tsx +88 -0
- package/src/components/Switch.tsx +7 -5
- package/src/components/Tabs.tsx +6 -4
- package/src/components/Text.tsx +29 -18
- package/src/components/TextArea.tsx +5 -3
- package/src/components/Toast.tsx +5 -5
- package/src/components/Toolbar.tsx +57 -10
- package/src/components/ToolbarDrawer.tsx +29 -0
- package/src/components/Tooltip.tsx +2 -2
- package/src/components/TreeView.tsx +13 -4
- package/src/components/UserPointer.tsx +6 -4
- package/src/components/ai-assistant/AIAssistantLayout.tsx +10 -8
- package/src/components/blocks/ImageBlockComponent.tsx +162 -0
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +6 -4
- package/src/components/file-explorer/FileExplorerLayout.tsx +22 -8
- package/src/components/internal/Checkmark.tsx +24 -0
- package/src/components/internal/Menu.tsx +24 -19
- package/src/components/internal/MenuViewport.tsx +15 -6
- package/src/components/internal/SelectItem.tsx +7 -26
- package/src/components/internal/TextInput.tsx +7 -1
- package/src/components/listView/ListViewContexts.tsx +68 -0
- package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
- package/src/components/listView/ListViewRoot.tsx +521 -0
- package/src/components/listView/ListViewRow.tsx +475 -0
- package/src/components/listView/ListViewRowTitle.tsx +21 -0
- package/src/components/listView/types.ts +11 -0
- package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
- package/src/components/sorting/DragRegistration.tsx +10 -5
- package/src/components/sorting/SharedDragProvider.tsx +134 -27
- package/src/components/sorting/Sortable.tsx +90 -27
- package/src/components/sorting/createSharedDrag.tsx +7 -6
- package/src/components/sorting/sorting.ts +6 -9
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +110 -29
- package/src/components/workspace/PanelWorkspaceLayout.tsx +148 -99
- package/src/components/workspace/VerticalTabMenu.tsx +46 -0
- package/src/components/workspace/WorkspaceLayout.tsx +76 -63
- package/src/components/workspace/constants.ts +4 -0
- package/src/components/workspace/panelStorage.ts +216 -0
- package/src/components/workspace/types.ts +11 -4
- package/src/contexts/DesignSystemConfiguration.tsx +12 -5
- package/src/contexts/DialogContext.tsx +19 -5
- package/src/contexts/FloatingWindowContext.tsx +2 -0
- package/src/contexts/GlobalInputBlurContext.tsx +2 -0
- package/src/contexts/ImageDataContext.tsx +2 -0
- package/src/contexts/LinkComponentContext.tsx +17 -0
- package/src/contexts/OpenPortalsContext.tsx +2 -0
- package/src/contexts/PortalScopeContext.tsx +2 -0
- package/src/hooks/mergeEventHandlers.ts +1 -1
- package/src/hooks/useHover.ts +30 -17
- package/src/hooks/useIndent.ts +5 -8
- package/src/hooks/useLabel.ts +2 -0
- package/src/hooks/useObjectURL.ts +5 -3
- package/src/hooks/usePlatform.ts +5 -3
- package/src/hooks/useTheme.ts +3 -1
- package/src/index.css +43 -10
- package/src/index.tsx +15 -7
- package/src/utils/classNames.ts +56 -9
- package/src/utils/combobox.ts +2 -0
- package/src/utils/editableBlockStyles.ts +2 -2
- package/src/utils/formatByteSize.ts +9 -0
- package/src/utils/inputs.ts +8 -6
- package/src/utils/sketchColor.ts +0 -34
- package/src/utils/withSeparatorElements.ts +1 -1
- package/tailwind.config.ts +1 -0
- package/src/components/FillInputField.tsx +0 -37
- package/src/components/FillPreviewBackground.tsx +0 -132
- package/src/components/GradientPicker.tsx +0 -88
- package/src/hooks/usePreservePanelSize.tsx +0 -183
- package/src/mediaQuery.ts +0 -13
- package/src/utils/getGradientBackground.tsx +0 -31
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
Active,
|
|
5
|
+
ClientRect,
|
|
3
6
|
closestCenter,
|
|
4
7
|
CollisionDetection,
|
|
5
8
|
DndContext,
|
|
@@ -13,9 +16,14 @@ import {
|
|
|
13
16
|
useSensor,
|
|
14
17
|
useSensors,
|
|
15
18
|
} from "@dnd-kit/core";
|
|
16
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
Insets,
|
|
21
|
+
Point,
|
|
22
|
+
rectContainsPoint,
|
|
23
|
+
unionRects,
|
|
24
|
+
} from "@noya-app/noya-geometry";
|
|
17
25
|
import { windowsOf } from "@noya-app/noya-utils";
|
|
18
|
-
import {
|
|
26
|
+
import { useIsomorphicLayoutEffect } from "@noya-app/react-utils";
|
|
19
27
|
import * as React from "react";
|
|
20
28
|
import { useMemo } from "react";
|
|
21
29
|
import { createPortal } from "react-dom";
|
|
@@ -30,6 +38,7 @@ import {
|
|
|
30
38
|
useDragRegistrationManager,
|
|
31
39
|
} from "./DragRegistration";
|
|
32
40
|
import {
|
|
41
|
+
AcceptsDrop,
|
|
33
42
|
DragItem,
|
|
34
43
|
DragState,
|
|
35
44
|
MoveDragItemHandler,
|
|
@@ -37,19 +46,23 @@ import {
|
|
|
37
46
|
RelativeDropPosition,
|
|
38
47
|
} from "./sorting";
|
|
39
48
|
|
|
40
|
-
type SharedDragProviderProps = {
|
|
49
|
+
export type SharedDragProviderProps = {
|
|
41
50
|
children: React.ReactNode;
|
|
51
|
+
acceptsDrop?: AcceptsDrop;
|
|
42
52
|
onMoveItem?: MoveDragItemHandler;
|
|
43
53
|
sharedDragProps?: SharedDragProps;
|
|
54
|
+
itemHitSlop?: Insets;
|
|
44
55
|
};
|
|
45
56
|
|
|
46
57
|
export function SharedDragProvider({
|
|
47
58
|
children,
|
|
59
|
+
acceptsDrop,
|
|
48
60
|
onMoveItem,
|
|
49
61
|
sharedDragProps = {
|
|
50
62
|
dragRegistrationContext: DragRegistrationContext,
|
|
51
63
|
activeDragContext: ActiveDragContext,
|
|
52
64
|
},
|
|
65
|
+
itemHitSlop,
|
|
53
66
|
}: SharedDragProviderProps) {
|
|
54
67
|
const sensors = useSensors(
|
|
55
68
|
useSensor(PointerSensor, {
|
|
@@ -66,6 +79,16 @@ export function SharedDragProvider({
|
|
|
66
79
|
indicator: null,
|
|
67
80
|
});
|
|
68
81
|
|
|
82
|
+
const stableOnMoveItem = React.useRef(onMoveItem);
|
|
83
|
+
useIsomorphicLayoutEffect(() => {
|
|
84
|
+
stableOnMoveItem.current = onMoveItem;
|
|
85
|
+
}, [onMoveItem]);
|
|
86
|
+
|
|
87
|
+
const stableAcceptsDrop = React.useRef(acceptsDrop);
|
|
88
|
+
useIsomorphicLayoutEffect(() => {
|
|
89
|
+
stableAcceptsDrop.current = acceptsDrop;
|
|
90
|
+
}, [acceptsDrop]);
|
|
91
|
+
|
|
69
92
|
// console.log("dragState", dragState.target);
|
|
70
93
|
|
|
71
94
|
const { registerList, unregisterList, registeredLists } =
|
|
@@ -117,6 +140,7 @@ export function SharedDragProvider({
|
|
|
117
140
|
active,
|
|
118
141
|
source: dragState.source,
|
|
119
142
|
registeredLists,
|
|
143
|
+
acceptsDrop: stableAcceptsDrop.current,
|
|
120
144
|
});
|
|
121
145
|
|
|
122
146
|
setDragState({
|
|
@@ -128,8 +152,6 @@ export function SharedDragProvider({
|
|
|
128
152
|
[registeredLists, dragState.source]
|
|
129
153
|
);
|
|
130
154
|
|
|
131
|
-
const stableOnMoveItem = useStableCallback(onMoveItem);
|
|
132
|
-
|
|
133
155
|
const handleDragEnd = React.useCallback(
|
|
134
156
|
(event: DragEndEvent) => {
|
|
135
157
|
const { active, over } = event;
|
|
@@ -146,6 +168,7 @@ export function SharedDragProvider({
|
|
|
146
168
|
active,
|
|
147
169
|
source: dragState.source,
|
|
148
170
|
registeredLists,
|
|
171
|
+
acceptsDrop: stableAcceptsDrop.current,
|
|
149
172
|
});
|
|
150
173
|
|
|
151
174
|
setDragState({
|
|
@@ -170,14 +193,16 @@ export function SharedDragProvider({
|
|
|
170
193
|
targetListId: dropTarget.target.listId,
|
|
171
194
|
};
|
|
172
195
|
|
|
173
|
-
|
|
174
|
-
|
|
196
|
+
if (stableOnMoveItem.current) {
|
|
197
|
+
stableOnMoveItem.current(parameters);
|
|
198
|
+
} else {
|
|
199
|
+
// Both lists have the opportunity to handle the move
|
|
200
|
+
sourceList.onMoveItem(parameters);
|
|
175
201
|
|
|
176
|
-
|
|
177
|
-
|
|
202
|
+
if (dropTarget.target.listId !== dropTarget.source.listId) {
|
|
203
|
+
targetList.onMoveItem(parameters);
|
|
204
|
+
}
|
|
178
205
|
}
|
|
179
|
-
|
|
180
|
-
stableOnMoveItem?.(parameters);
|
|
181
206
|
},
|
|
182
207
|
[registeredLists, dragState.source, stableOnMoveItem]
|
|
183
208
|
);
|
|
@@ -199,8 +224,8 @@ export function SharedDragProvider({
|
|
|
199
224
|
);
|
|
200
225
|
|
|
201
226
|
const collisionDetection = useMemo(() => {
|
|
202
|
-
return getItemFirstCollisionDetection(registeredLists);
|
|
203
|
-
}, [registeredLists]);
|
|
227
|
+
return getItemFirstCollisionDetection(registeredLists, itemHitSlop);
|
|
228
|
+
}, [registeredLists, itemHitSlop]);
|
|
204
229
|
|
|
205
230
|
return (
|
|
206
231
|
<AnyDragContext.Provider value={true}>
|
|
@@ -262,9 +287,18 @@ export function withDragProvider<T extends object>(
|
|
|
262
287
|
|
|
263
288
|
// Custom collision detection that prioritizes items over containers
|
|
264
289
|
export const getItemFirstCollisionDetection =
|
|
265
|
-
(
|
|
290
|
+
(
|
|
291
|
+
registeredLists: Map<string, RegisteredList>,
|
|
292
|
+
itemHitSlop: Insets | undefined
|
|
293
|
+
): CollisionDetection =>
|
|
266
294
|
(parameters) => {
|
|
267
|
-
const
|
|
295
|
+
const parametersWithHitSlop = getCollisionParametersWithHitSlop(
|
|
296
|
+
registeredLists,
|
|
297
|
+
parameters,
|
|
298
|
+
itemHitSlop
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const pointerCollisions = pointerWithin(parametersWithHitSlop);
|
|
268
302
|
|
|
269
303
|
if (pointerCollisions.length > 0) {
|
|
270
304
|
const itemCollisions = pointerCollisions.filter((collision) => {
|
|
@@ -283,11 +317,11 @@ export const getItemFirstCollisionDetection =
|
|
|
283
317
|
}
|
|
284
318
|
|
|
285
319
|
const listCollision = pointerCollisions.at(0);
|
|
286
|
-
const nearestCenters = closestCenter(
|
|
320
|
+
const nearestCenters = closestCenter(parametersWithHitSlop);
|
|
287
321
|
|
|
288
322
|
// Handle a pointer between items by checking if the pointer is within
|
|
289
323
|
// a window of two items.
|
|
290
|
-
if (
|
|
324
|
+
if (parametersWithHitSlop.pointerCoordinates && listCollision) {
|
|
291
325
|
const list = registeredLists.get(listCollision.id as string);
|
|
292
326
|
|
|
293
327
|
if (list) {
|
|
@@ -295,7 +329,7 @@ export const getItemFirstCollisionDetection =
|
|
|
295
329
|
|
|
296
330
|
for (const pair of pairs) {
|
|
297
331
|
const clientRects = pair.map(
|
|
298
|
-
(key) =>
|
|
332
|
+
(key) => parametersWithHitSlop.droppableRects.get(key)!
|
|
299
333
|
);
|
|
300
334
|
|
|
301
335
|
const merged = unionRects(
|
|
@@ -307,7 +341,9 @@ export const getItemFirstCollisionDetection =
|
|
|
307
341
|
}))
|
|
308
342
|
);
|
|
309
343
|
|
|
310
|
-
if (
|
|
344
|
+
if (
|
|
345
|
+
rectContainsPoint(merged, parametersWithHitSlop.pointerCoordinates)
|
|
346
|
+
) {
|
|
311
347
|
return nearestCenters.filter((center) =>
|
|
312
348
|
pair.some((id) => id === center.id)
|
|
313
349
|
);
|
|
@@ -320,6 +356,69 @@ export const getItemFirstCollisionDetection =
|
|
|
320
356
|
return pointerCollisions;
|
|
321
357
|
};
|
|
322
358
|
|
|
359
|
+
type CollisionDetectionParameters = Parameters<CollisionDetection>[0];
|
|
360
|
+
|
|
361
|
+
function getCollisionParametersWithHitSlop(
|
|
362
|
+
registeredLists: Map<string, RegisteredList>,
|
|
363
|
+
parameters: CollisionDetectionParameters,
|
|
364
|
+
defaultHitSlop: Insets | undefined
|
|
365
|
+
): CollisionDetectionParameters {
|
|
366
|
+
const adjustedDroppableRects = createDroppableRectsWithHitSlop(
|
|
367
|
+
registeredLists,
|
|
368
|
+
parameters.droppableRects,
|
|
369
|
+
defaultHitSlop
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (!adjustedDroppableRects) {
|
|
373
|
+
return parameters;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
...parameters,
|
|
378
|
+
droppableRects: adjustedDroppableRects,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function createDroppableRectsWithHitSlop(
|
|
383
|
+
registeredLists: Map<string, RegisteredList>,
|
|
384
|
+
droppableRects: CollisionDetectionParameters["droppableRects"],
|
|
385
|
+
defaultHitSlop: Insets | undefined
|
|
386
|
+
): Map<string | number, ClientRect> | undefined {
|
|
387
|
+
let adjustedRects: Map<string | number, ClientRect> | undefined;
|
|
388
|
+
|
|
389
|
+
for (const [, list] of registeredLists) {
|
|
390
|
+
const hitSlop = list.itemHitSlop ?? defaultHitSlop;
|
|
391
|
+
|
|
392
|
+
if (!hitSlop) continue;
|
|
393
|
+
|
|
394
|
+
for (const key of list.keys) {
|
|
395
|
+
const rect = droppableRects.get(key);
|
|
396
|
+
|
|
397
|
+
if (!rect) continue;
|
|
398
|
+
|
|
399
|
+
if (!adjustedRects) {
|
|
400
|
+
adjustedRects = new Map(droppableRects);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
adjustedRects.set(key, expandRectByInsets(rect, hitSlop));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return adjustedRects;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function expandRectByInsets(rect: ClientRect, hitSlop: Insets): ClientRect {
|
|
411
|
+
return {
|
|
412
|
+
...rect,
|
|
413
|
+
left: rect.left - hitSlop.left,
|
|
414
|
+
right: rect.right + hitSlop.right,
|
|
415
|
+
top: rect.top - hitSlop.top,
|
|
416
|
+
bottom: rect.bottom + hitSlop.bottom,
|
|
417
|
+
width: rect.width + hitSlop.left + hitSlop.right,
|
|
418
|
+
height: rect.height + hitSlop.top + hitSlop.bottom,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
323
422
|
function findDropTarget({
|
|
324
423
|
activationPoint,
|
|
325
424
|
delta,
|
|
@@ -327,6 +426,7 @@ function findDropTarget({
|
|
|
327
426
|
active,
|
|
328
427
|
source,
|
|
329
428
|
registeredLists,
|
|
429
|
+
acceptsDrop: acceptsDropProp,
|
|
330
430
|
}: {
|
|
331
431
|
activationPoint: Point;
|
|
332
432
|
delta: Point;
|
|
@@ -334,6 +434,7 @@ function findDropTarget({
|
|
|
334
434
|
active: Active;
|
|
335
435
|
source: DragItem;
|
|
336
436
|
registeredLists: Map<string, RegisteredList>;
|
|
437
|
+
acceptsDrop?: AcceptsDrop;
|
|
337
438
|
}):
|
|
338
439
|
| {
|
|
339
440
|
source: DragItem;
|
|
@@ -379,9 +480,11 @@ function findDropTarget({
|
|
|
379
480
|
return;
|
|
380
481
|
}
|
|
381
482
|
|
|
483
|
+
const acceptsDrop = acceptsDropProp ?? targetList.acceptsDrop;
|
|
484
|
+
|
|
382
485
|
// Dropping on the list container itself
|
|
383
486
|
if (over.id === targetList.id) {
|
|
384
|
-
const canDrop =
|
|
487
|
+
const canDrop = acceptsDrop({
|
|
385
488
|
sourceIndex: source.index,
|
|
386
489
|
targetIndex: Math.max(0, targetList.keys.length - 1), // Use last valid index or 0 for empty lists
|
|
387
490
|
position: "below",
|
|
@@ -408,13 +511,17 @@ function findDropTarget({
|
|
|
408
511
|
const currentY = eventY + delta.y;
|
|
409
512
|
const absolutePosition = { x: currentX, y: currentY };
|
|
410
513
|
|
|
411
|
-
const indicator = targetList.getDropIndicator(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
514
|
+
const indicator = targetList.getDropIndicator(
|
|
515
|
+
{
|
|
516
|
+
absolutePosition,
|
|
517
|
+
activeIndex: source.index,
|
|
518
|
+
overIndex: targetIndex,
|
|
519
|
+
overRect: over.rect,
|
|
520
|
+
sourceListId: sourceList.id,
|
|
521
|
+
targetListId: targetList.id,
|
|
522
|
+
},
|
|
523
|
+
acceptsDrop
|
|
524
|
+
);
|
|
418
525
|
|
|
419
526
|
if (indicator) {
|
|
420
527
|
return {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { UniqueIdentifier, useDroppable } from "@dnd-kit/core";
|
|
2
4
|
import {
|
|
3
5
|
horizontalListSortingStrategy,
|
|
@@ -5,6 +7,7 @@ import {
|
|
|
5
7
|
useSortable,
|
|
6
8
|
verticalListSortingStrategy,
|
|
7
9
|
} from "@dnd-kit/sortable";
|
|
10
|
+
import { Insets } from "@noya-app/noya-geometry";
|
|
8
11
|
import * as React from "react";
|
|
9
12
|
import {
|
|
10
13
|
ActiveDragContext,
|
|
@@ -28,12 +31,21 @@ import {
|
|
|
28
31
|
|
|
29
32
|
type UseSortableReturnType = ReturnType<typeof useSortable>;
|
|
30
33
|
|
|
31
|
-
type ItemChildrenProps<T> = {
|
|
32
|
-
ref: React.
|
|
33
|
-
relativeDropPosition?: RelativeDropPosition;
|
|
34
|
+
export type ItemChildrenProps<T> = {
|
|
35
|
+
ref: React.RefCallback<T>;
|
|
34
36
|
style?: React.CSSProperties;
|
|
37
|
+
onPointerDown?: React.PointerEventHandler;
|
|
35
38
|
} & UseSortableReturnType["attributes"];
|
|
36
39
|
|
|
40
|
+
export type ItemChildrenExtraProps = {
|
|
41
|
+
isDragging: boolean;
|
|
42
|
+
isOverlay: boolean;
|
|
43
|
+
relativeDropPosition: RelativeDropPosition | undefined;
|
|
44
|
+
setActivatorNodeRef: React.RefCallback<HTMLElement>;
|
|
45
|
+
setDraggableNodeRef: React.RefCallback<HTMLElement>;
|
|
46
|
+
setDroppableNodeRef: React.RefCallback<HTMLElement>;
|
|
47
|
+
};
|
|
48
|
+
|
|
37
49
|
// Context for drop indicators within a sortable list
|
|
38
50
|
interface SortableItemContextProps {
|
|
39
51
|
acceptsDrop: DropValidator;
|
|
@@ -74,7 +86,9 @@ function useSortableDropIndicator({
|
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
const targetParentIndex =
|
|
77
|
-
target.index === -1
|
|
89
|
+
listId !== target.listId || target.index === -1
|
|
90
|
+
? undefined
|
|
91
|
+
: getDropTargetParentIndex?.(target.index);
|
|
78
92
|
|
|
79
93
|
const isValidParent =
|
|
80
94
|
targetParentIndex === undefined || targetParentIndex === -1
|
|
@@ -113,11 +127,13 @@ interface RootProps {
|
|
|
113
127
|
children?: React.ReactNode;
|
|
114
128
|
renderOverlay?: (index: number) => React.ReactNode;
|
|
115
129
|
onMoveItem?: MoveDragItemHandler;
|
|
130
|
+
onIsDraggingChange?: (isDragging: boolean) => void;
|
|
116
131
|
acceptsFromList?: AcceptsFromList;
|
|
117
132
|
acceptsDrop?: DropValidator;
|
|
118
133
|
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
119
134
|
sharedDragProps?: SharedDragProps;
|
|
120
|
-
containerRef?: React.RefObject<HTMLElement>;
|
|
135
|
+
containerRef?: React.RefObject<HTMLElement | null>;
|
|
136
|
+
itemHitSlop?: Insets;
|
|
121
137
|
}
|
|
122
138
|
|
|
123
139
|
function SortableItem<TElement extends HTMLElement>({
|
|
@@ -127,14 +143,26 @@ function SortableItem<TElement extends HTMLElement>({
|
|
|
127
143
|
}: {
|
|
128
144
|
id: UniqueIdentifier;
|
|
129
145
|
disabled?: boolean;
|
|
130
|
-
children: (
|
|
146
|
+
children: (
|
|
147
|
+
props: ItemChildrenProps<TElement>,
|
|
148
|
+
extraProps: Omit<ItemChildrenExtraProps, "isOverlay">
|
|
149
|
+
) => React.ReactNode;
|
|
131
150
|
}) {
|
|
132
151
|
const { acceptsDrop, listId, getDropTargetParentIndex } =
|
|
133
152
|
React.useContext(SortableItemContext);
|
|
134
153
|
|
|
135
154
|
const dragItemKey = createDragItemKey(listId, id);
|
|
136
155
|
|
|
137
|
-
const {
|
|
156
|
+
const {
|
|
157
|
+
attributes,
|
|
158
|
+
listeners,
|
|
159
|
+
setNodeRef,
|
|
160
|
+
isDragging,
|
|
161
|
+
index,
|
|
162
|
+
setActivatorNodeRef,
|
|
163
|
+
setDraggableNodeRef,
|
|
164
|
+
setDroppableNodeRef,
|
|
165
|
+
} = useSortable({
|
|
138
166
|
id: dragItemKey,
|
|
139
167
|
disabled,
|
|
140
168
|
});
|
|
@@ -152,21 +180,28 @@ function SortableItem<TElement extends HTMLElement>({
|
|
|
152
180
|
getDropTargetParentIndex,
|
|
153
181
|
});
|
|
154
182
|
|
|
155
|
-
return children(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
opacity: isDragging ? 0.5 : 1,
|
|
183
|
+
return children(
|
|
184
|
+
{
|
|
185
|
+
ref,
|
|
186
|
+
...attributes,
|
|
187
|
+
...listeners,
|
|
188
|
+
style: { opacity: isDragging ? 0.5 : 1 },
|
|
162
189
|
},
|
|
163
|
-
|
|
190
|
+
{
|
|
191
|
+
isDragging,
|
|
192
|
+
setActivatorNodeRef,
|
|
193
|
+
setDraggableNodeRef,
|
|
194
|
+
setDroppableNodeRef,
|
|
195
|
+
relativeDropPosition,
|
|
196
|
+
}
|
|
197
|
+
);
|
|
164
198
|
}
|
|
165
199
|
|
|
166
200
|
function SortableRoot_({
|
|
167
201
|
id: idProp,
|
|
168
202
|
keys: keysProp,
|
|
169
203
|
onMoveItem,
|
|
204
|
+
onIsDraggingChange,
|
|
170
205
|
renderOverlay,
|
|
171
206
|
acceptsFromList,
|
|
172
207
|
acceptsDrop = defaultAcceptsDrop,
|
|
@@ -175,6 +210,7 @@ function SortableRoot_({
|
|
|
175
210
|
getDropTargetParentIndex,
|
|
176
211
|
sharedDragProps,
|
|
177
212
|
containerRef,
|
|
213
|
+
itemHitSlop,
|
|
178
214
|
}: RootProps) {
|
|
179
215
|
const defaultId = React.useId();
|
|
180
216
|
const id = idProp ?? defaultId;
|
|
@@ -202,20 +238,25 @@ function SortableRoot_({
|
|
|
202
238
|
},
|
|
203
239
|
acceptsFromList: acceptsFromList ?? (() => true),
|
|
204
240
|
acceptsDrop,
|
|
205
|
-
getDropIndicator: (parameters) => {
|
|
206
|
-
const {
|
|
207
|
-
|
|
241
|
+
getDropIndicator: (parameters, acceptsDrop) => {
|
|
242
|
+
const {
|
|
243
|
+
absolutePosition,
|
|
244
|
+
activeIndex,
|
|
245
|
+
overIndex,
|
|
246
|
+
overRect,
|
|
247
|
+
sourceListId,
|
|
248
|
+
targetListId,
|
|
249
|
+
} = parameters;
|
|
208
250
|
|
|
209
251
|
const offsetStart =
|
|
210
252
|
axis === "x" ? absolutePosition.x : absolutePosition.y;
|
|
211
|
-
const elementStart = axis === "x" ?
|
|
212
|
-
const elementSize = axis === "x" ?
|
|
253
|
+
const elementStart = axis === "x" ? overRect.left : overRect.top;
|
|
254
|
+
const elementSize = axis === "x" ? overRect.width : overRect.height;
|
|
213
255
|
|
|
214
256
|
const relativeDropPosition = validateDropIndicator({
|
|
215
257
|
acceptsDrop,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
overId: over.id,
|
|
258
|
+
activeIndex,
|
|
259
|
+
overIndex,
|
|
219
260
|
offsetStart,
|
|
220
261
|
elementStart,
|
|
221
262
|
elementSize,
|
|
@@ -225,6 +266,7 @@ function SortableRoot_({
|
|
|
225
266
|
|
|
226
267
|
return relativeDropPosition;
|
|
227
268
|
},
|
|
269
|
+
itemHitSlop,
|
|
228
270
|
};
|
|
229
271
|
|
|
230
272
|
registerList(listConfig);
|
|
@@ -242,6 +284,7 @@ function SortableRoot_({
|
|
|
242
284
|
registerList,
|
|
243
285
|
unregisterList,
|
|
244
286
|
keys,
|
|
287
|
+
itemHitSlop,
|
|
245
288
|
]);
|
|
246
289
|
|
|
247
290
|
const contextValue: SortableItemContextProps = React.useMemo(
|
|
@@ -256,6 +299,16 @@ function SortableRoot_({
|
|
|
256
299
|
[keys, acceptsDrop, id, getDropTargetParentIndex, sharedDragProps]
|
|
257
300
|
);
|
|
258
301
|
|
|
302
|
+
// Notify drag state changes to consumers if requested
|
|
303
|
+
const activeDragContext =
|
|
304
|
+
sharedDragProps?.activeDragContext ?? ActiveDragContext;
|
|
305
|
+
const dragState = React.useContext(activeDragContext);
|
|
306
|
+
const isDragging = !!dragState?.source;
|
|
307
|
+
|
|
308
|
+
React.useEffect(() => {
|
|
309
|
+
onIsDraggingChange?.(isDragging);
|
|
310
|
+
}, [isDragging, onIsDraggingChange]);
|
|
311
|
+
|
|
259
312
|
// Set the droppable ref after every render.
|
|
260
313
|
// Since we don't render the dom node internally, we aggressively set it
|
|
261
314
|
// after every render to make sure we are using the latest.
|
|
@@ -291,7 +344,7 @@ export interface SortableProps<TItem, TElement extends HTMLElement>
|
|
|
291
344
|
renderItem: (
|
|
292
345
|
item: TItem,
|
|
293
346
|
props: ItemChildrenProps<TElement>,
|
|
294
|
-
|
|
347
|
+
extraProps: { isOverlay: boolean } & ItemChildrenExtraProps
|
|
295
348
|
) => React.ReactNode;
|
|
296
349
|
}
|
|
297
350
|
|
|
@@ -321,7 +374,14 @@ const Sortable_ = function Sortable_<TItem, TElement extends HTMLElement>({
|
|
|
321
374
|
"aria-describedby": "Item being dragged",
|
|
322
375
|
role: "button",
|
|
323
376
|
},
|
|
324
|
-
{
|
|
377
|
+
{
|
|
378
|
+
isOverlay: true,
|
|
379
|
+
setActivatorNodeRef: () => {},
|
|
380
|
+
setDraggableNodeRef: () => {},
|
|
381
|
+
setDroppableNodeRef: () => {},
|
|
382
|
+
relativeDropPosition: undefined,
|
|
383
|
+
isDragging: true,
|
|
384
|
+
}
|
|
325
385
|
);
|
|
326
386
|
},
|
|
327
387
|
[renderItem, items]
|
|
@@ -335,8 +395,11 @@ const Sortable_ = function Sortable_<TItem, TElement extends HTMLElement>({
|
|
|
335
395
|
>
|
|
336
396
|
{keys.map((key, index) => (
|
|
337
397
|
<SortableItem<TElement> key={key} id={key}>
|
|
338
|
-
{(props) => {
|
|
339
|
-
return renderItem(items[index], props, {
|
|
398
|
+
{(props, extraProps) => {
|
|
399
|
+
return renderItem(items[index], props, {
|
|
400
|
+
isOverlay: false,
|
|
401
|
+
...extraProps,
|
|
402
|
+
});
|
|
340
403
|
}}
|
|
341
404
|
</SortableItem>
|
|
342
405
|
))}
|
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
DragRegistrationContextType,
|
|
5
5
|
SharedDragProps,
|
|
6
6
|
} from "./DragRegistration";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
SharedDragProvider,
|
|
9
|
+
SharedDragProviderProps,
|
|
10
|
+
} from "./SharedDragProvider";
|
|
8
11
|
import { Sortable, SortableProps } from "./Sortable";
|
|
9
12
|
|
|
10
13
|
export function createSharedDrag(): {
|
|
@@ -25,11 +28,9 @@ export function createSharedDrag(): {
|
|
|
25
28
|
activeDragContext,
|
|
26
29
|
};
|
|
27
30
|
|
|
28
|
-
const Provider = (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
</SharedDragProvider>
|
|
32
|
-
);
|
|
31
|
+
const Provider = (
|
|
32
|
+
props: Omit<SharedDragProviderProps, "sharedDragProps">
|
|
33
|
+
) => <SharedDragProvider sharedDragProps={sharedDragProps} {...props} />;
|
|
33
34
|
|
|
34
35
|
return {
|
|
35
36
|
Provider,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { UniqueIdentifier } from "@dnd-kit/core";
|
|
2
4
|
import * as React from "react";
|
|
3
5
|
|
|
@@ -83,9 +85,8 @@ export const SortableItemContext =
|
|
|
83
85
|
|
|
84
86
|
export type ValidateDropIndicatorParams = {
|
|
85
87
|
acceptsDrop: DropValidator; // Function that validates if a drop is allowed
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
overId: UniqueIdentifier; // ID of item being dragged over
|
|
88
|
+
activeIndex: number; // ID of item being dragged
|
|
89
|
+
overIndex: number; // ID of item being dragged over
|
|
89
90
|
offsetStart: number; // Current drag position (x or y coordinate)
|
|
90
91
|
elementStart: number; // Start position of target element (left or top)
|
|
91
92
|
elementSize: number; // Size of target element (width or height)
|
|
@@ -95,18 +96,14 @@ export type ValidateDropIndicatorParams = {
|
|
|
95
96
|
|
|
96
97
|
export function validateDropIndicator({
|
|
97
98
|
acceptsDrop,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
overId,
|
|
99
|
+
activeIndex,
|
|
100
|
+
overIndex,
|
|
101
101
|
offsetStart,
|
|
102
102
|
elementStart,
|
|
103
103
|
elementSize,
|
|
104
104
|
sourceListId,
|
|
105
105
|
targetListId,
|
|
106
106
|
}: ValidateDropIndicatorParams): RelativeDropPosition | undefined {
|
|
107
|
-
const activeIndex = keys.findIndex((id) => id === activeId); // index of item being dragged
|
|
108
|
-
const overIndex = keys.findIndex((id) => id === overId); // index of item being dragged over
|
|
109
|
-
|
|
110
107
|
if (overIndex === -1) return undefined;
|
|
111
108
|
if (activeIndex === -1 && sourceListId === targetListId) return undefined;
|
|
112
109
|
|