@noya-app/noya-designsystem 0.1.56 → 0.1.57
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 +10 -10
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +48 -42
- package/dist/index.d.ts +48 -42
- package/dist/index.js +236 -189
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +235 -190
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Collection.tsx +3 -0
- package/src/components/List.tsx +4 -0
- package/src/components/ListView.tsx +7 -0
- package/src/components/sorting/DragRegistration.tsx +3 -6
- package/src/components/sorting/SharedDragProvider.tsx +200 -131
- package/src/components/sorting/Sortable.tsx +57 -104
- package/src/components/sorting/createSharedDrag.tsx +10 -15
- package/src/components/sorting/sorting.ts +41 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UniqueIdentifier,
|
|
1
|
+
import { UniqueIdentifier, useDroppable } from "@dnd-kit/core";
|
|
2
2
|
import {
|
|
3
3
|
horizontalListSortingStrategy,
|
|
4
4
|
SortableContext,
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
ActiveDragContextType,
|
|
12
12
|
DragRegistrationContext,
|
|
13
13
|
RegisteredList,
|
|
14
|
-
|
|
14
|
+
SharedDragProps,
|
|
15
15
|
useActiveDrag,
|
|
16
16
|
useDragRegistration,
|
|
17
17
|
} from "./DragRegistration";
|
|
18
18
|
import { withDragProvider } from "./SharedDragProvider";
|
|
19
19
|
import {
|
|
20
20
|
AcceptsFromList,
|
|
21
|
+
createDragItemKey,
|
|
21
22
|
defaultAcceptsDrop,
|
|
22
23
|
DropValidator,
|
|
23
24
|
MoveDragItemHandler,
|
|
@@ -35,18 +36,14 @@ type ItemChildrenProps<T> = {
|
|
|
35
36
|
|
|
36
37
|
// Context for drop indicators within a sortable list
|
|
37
38
|
interface SortableItemContextProps {
|
|
38
|
-
keys: UniqueIdentifier[];
|
|
39
39
|
acceptsDrop: DropValidator;
|
|
40
|
-
axis: "x" | "y";
|
|
41
40
|
listId: string;
|
|
42
41
|
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
43
42
|
activeDragContext: React.Context<ActiveDragContextType | null>;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
const SortableItemContext = React.createContext<SortableItemContextProps>({
|
|
47
|
-
keys: [],
|
|
48
46
|
acceptsDrop: defaultAcceptsDrop,
|
|
49
|
-
axis: "y",
|
|
50
47
|
listId: "",
|
|
51
48
|
getDropTargetParentIndex: undefined,
|
|
52
49
|
activeDragContext: ActiveDragContext,
|
|
@@ -54,95 +51,60 @@ const SortableItemContext = React.createContext<SortableItemContextProps>({
|
|
|
54
51
|
|
|
55
52
|
// Custom hook for drop indicators in sortable lists
|
|
56
53
|
function useSortableDropIndicator({
|
|
57
|
-
id,
|
|
58
54
|
index,
|
|
59
|
-
|
|
60
|
-
overIndex,
|
|
61
|
-
activeIndex,
|
|
62
|
-
keys,
|
|
63
|
-
acceptsDrop,
|
|
64
|
-
axis,
|
|
55
|
+
itemId,
|
|
65
56
|
listId,
|
|
57
|
+
acceptsDrop,
|
|
66
58
|
getDropTargetParentIndex,
|
|
67
59
|
}: {
|
|
68
|
-
id: UniqueIdentifier;
|
|
69
60
|
index: number;
|
|
70
|
-
|
|
71
|
-
overIndex: number;
|
|
72
|
-
activeIndex: number;
|
|
73
|
-
keys: UniqueIdentifier[];
|
|
74
|
-
acceptsDrop: DropValidator;
|
|
75
|
-
axis: "x" | "y";
|
|
61
|
+
itemId: UniqueIdentifier;
|
|
76
62
|
listId: string;
|
|
63
|
+
acceptsDrop: DropValidator;
|
|
77
64
|
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
78
65
|
}): RelativeDropPosition | undefined {
|
|
79
|
-
const { active, over, activatorEvent } = useDndContext();
|
|
80
66
|
const { activeDragContext } = React.useContext(SortableItemContext);
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// Cross-list drop: check if we're hovering over this specific item
|
|
94
|
-
(activeItem.listId !== listId && over.id === id)
|
|
95
|
-
)
|
|
96
|
-
)
|
|
97
|
-
) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const isCrossContainer = activeItem.listId !== listId;
|
|
102
|
-
|
|
103
|
-
// For cross-list drops, use combined keys like MultiSortable does
|
|
104
|
-
const validationKeys = isCrossContainer ? [active.id, ...keys] : keys;
|
|
105
|
-
|
|
106
|
-
const eventX = (activatorEvent as PointerEvent | null)?.clientX ?? 0;
|
|
107
|
-
const eventY = (activatorEvent as PointerEvent | null)?.clientY ?? 0;
|
|
108
|
-
const offsetLeft = eventX + position.x;
|
|
109
|
-
const offsetTop = eventY + position.y;
|
|
110
|
-
|
|
111
|
-
const parentIndex =
|
|
112
|
-
overIndex === -1 ? undefined : getDropTargetParentIndex?.(overIndex);
|
|
67
|
+
const activeDrag = useActiveDrag(activeDragContext);
|
|
68
|
+
const { source, target } = activeDrag;
|
|
69
|
+
|
|
70
|
+
if (!source || !target) return;
|
|
71
|
+
|
|
72
|
+
const indicator =
|
|
73
|
+
target.listId === listId && target.itemId === itemId
|
|
74
|
+
? activeDrag.indicator
|
|
75
|
+
: undefined;
|
|
76
|
+
|
|
77
|
+
const targetParentIndex =
|
|
78
|
+
target.index === -1 ? undefined : getDropTargetParentIndex?.(target.index);
|
|
113
79
|
|
|
114
80
|
const isValidParent =
|
|
115
|
-
|
|
81
|
+
targetParentIndex === undefined || targetParentIndex === -1
|
|
116
82
|
? false
|
|
117
|
-
: acceptsDrop(
|
|
83
|
+
: acceptsDrop(
|
|
84
|
+
source.index,
|
|
85
|
+
targetParentIndex,
|
|
86
|
+
"inside",
|
|
87
|
+
source.listId,
|
|
88
|
+
target.listId
|
|
89
|
+
);
|
|
118
90
|
|
|
119
91
|
const overIdAcceptsDrop =
|
|
120
|
-
|
|
92
|
+
target.index === -1
|
|
121
93
|
? undefined
|
|
122
|
-
: acceptsDrop(
|
|
94
|
+
: acceptsDrop(
|
|
95
|
+
source.index,
|
|
96
|
+
target.index,
|
|
97
|
+
"inside",
|
|
98
|
+
source.listId,
|
|
99
|
+
target.listId
|
|
100
|
+
);
|
|
123
101
|
|
|
124
|
-
const
|
|
125
|
-
index >= 0 && index === overIndex && !isDragging && active && over
|
|
126
|
-
? validateDropIndicator({
|
|
127
|
-
acceptsDrop,
|
|
128
|
-
keys: validationKeys,
|
|
129
|
-
activeId: active.id,
|
|
130
|
-
overId: over.id,
|
|
131
|
-
offsetStart: axis === "x" ? offsetLeft : offsetTop,
|
|
132
|
-
elementStart: axis === "x" ? over.rect.left : over.rect.top,
|
|
133
|
-
elementSize: axis === "x" ? over.rect.width : over.rect.height,
|
|
134
|
-
sourceListId: listId,
|
|
135
|
-
targetListId: listId,
|
|
136
|
-
})
|
|
137
|
-
: undefined;
|
|
102
|
+
const thisItemIsTargetParent = targetParentIndex === index;
|
|
138
103
|
|
|
139
104
|
const showDragInsideIndicatorOnParent =
|
|
140
|
-
!overIdAcceptsDrop && isValidParent &&
|
|
105
|
+
!overIdAcceptsDrop && isValidParent && thisItemIsTargetParent;
|
|
141
106
|
|
|
142
|
-
return (
|
|
143
|
-
relativeDropPosition ??
|
|
144
|
-
(showDragInsideIndicatorOnParent ? "inside" : undefined)
|
|
145
|
-
);
|
|
107
|
+
return indicator ?? (showDragInsideIndicatorOnParent ? "inside" : undefined);
|
|
146
108
|
}
|
|
147
109
|
|
|
148
110
|
interface RootProps {
|
|
@@ -155,7 +117,7 @@ interface RootProps {
|
|
|
155
117
|
acceptsFromList?: AcceptsFromList;
|
|
156
118
|
acceptsDrop?: DropValidator;
|
|
157
119
|
getDropTargetParentIndex?: (index: number) => number | undefined;
|
|
158
|
-
|
|
120
|
+
sharedDragProps?: SharedDragProps;
|
|
159
121
|
containerRef?: React.RefObject<HTMLElement>;
|
|
160
122
|
}
|
|
161
123
|
|
|
@@ -168,11 +130,15 @@ function SortableItem<TElement extends HTMLElement>({
|
|
|
168
130
|
disabled?: boolean;
|
|
169
131
|
children: (props: ItemChildrenProps<TElement>) => React.ReactNode;
|
|
170
132
|
}) {
|
|
171
|
-
const {
|
|
133
|
+
const { acceptsDrop, listId, getDropTargetParentIndex } =
|
|
172
134
|
React.useContext(SortableItemContext);
|
|
173
135
|
|
|
174
|
-
const
|
|
175
|
-
|
|
136
|
+
const dragItemKey = createDragItemKey(listId, id);
|
|
137
|
+
|
|
138
|
+
const { attributes, listeners, setNodeRef, isDragging, index } = useSortable({
|
|
139
|
+
id: dragItemKey,
|
|
140
|
+
disabled,
|
|
141
|
+
});
|
|
176
142
|
|
|
177
143
|
const ref = React.useCallback(
|
|
178
144
|
(node: TElement | null) => setNodeRef(node),
|
|
@@ -180,15 +146,10 @@ function SortableItem<TElement extends HTMLElement>({
|
|
|
180
146
|
);
|
|
181
147
|
|
|
182
148
|
const relativeDropPosition = useSortableDropIndicator({
|
|
183
|
-
id,
|
|
184
149
|
index,
|
|
185
|
-
|
|
186
|
-
overIndex,
|
|
187
|
-
keys,
|
|
188
|
-
acceptsDrop,
|
|
189
|
-
axis,
|
|
150
|
+
itemId: dragItemKey,
|
|
190
151
|
listId,
|
|
191
|
-
|
|
152
|
+
acceptsDrop,
|
|
192
153
|
getDropTargetParentIndex,
|
|
193
154
|
});
|
|
194
155
|
|
|
@@ -205,7 +166,7 @@ function SortableItem<TElement extends HTMLElement>({
|
|
|
205
166
|
|
|
206
167
|
function SortableRoot_({
|
|
207
168
|
id: idProp,
|
|
208
|
-
keys,
|
|
169
|
+
keys: keysProp,
|
|
209
170
|
onMoveItem,
|
|
210
171
|
renderOverlay,
|
|
211
172
|
acceptsFromList,
|
|
@@ -213,15 +174,17 @@ function SortableRoot_({
|
|
|
213
174
|
axis = "y",
|
|
214
175
|
children,
|
|
215
176
|
getDropTargetParentIndex,
|
|
216
|
-
|
|
177
|
+
sharedDragProps,
|
|
217
178
|
containerRef,
|
|
218
179
|
}: RootProps) {
|
|
219
180
|
const defaultId = React.useId();
|
|
220
181
|
const id = idProp ?? defaultId;
|
|
182
|
+
const keys = React.useMemo(() => {
|
|
183
|
+
return keysProp.map((key) => createDragItemKey(id, key));
|
|
184
|
+
}, [keysProp, id]);
|
|
221
185
|
|
|
222
186
|
const { registerList, unregisterList } = useDragRegistration(
|
|
223
|
-
|
|
224
|
-
DragRegistrationContext
|
|
187
|
+
sharedDragProps?.dragRegistrationContext ?? DragRegistrationContext
|
|
225
188
|
);
|
|
226
189
|
|
|
227
190
|
const { setNodeRef: setDroppableRef } = useDroppable({
|
|
@@ -244,9 +207,7 @@ function SortableRoot_({
|
|
|
244
207
|
const index = keys.findIndex((key) => key === id);
|
|
245
208
|
return renderOverlay?.(index) ?? null;
|
|
246
209
|
},
|
|
247
|
-
acceptsFromList:
|
|
248
|
-
acceptsFromList ??
|
|
249
|
-
(({ sourceListId, targetListId }) => sourceListId === targetListId),
|
|
210
|
+
acceptsFromList: acceptsFromList ?? (() => true),
|
|
250
211
|
acceptsDrop,
|
|
251
212
|
getDropIndicator: (parameters) => {
|
|
252
213
|
const { absolutePosition, active, over, sourceListId, targetListId } =
|
|
@@ -294,20 +255,12 @@ function SortableRoot_({
|
|
|
294
255
|
() => ({
|
|
295
256
|
keys,
|
|
296
257
|
acceptsDrop,
|
|
297
|
-
axis,
|
|
298
258
|
listId: id,
|
|
299
259
|
getDropTargetParentIndex,
|
|
300
260
|
activeDragContext:
|
|
301
|
-
|
|
261
|
+
sharedDragProps?.activeDragContext ?? ActiveDragContext,
|
|
302
262
|
}),
|
|
303
|
-
[
|
|
304
|
-
keys,
|
|
305
|
-
acceptsDrop,
|
|
306
|
-
axis,
|
|
307
|
-
id,
|
|
308
|
-
getDropTargetParentIndex,
|
|
309
|
-
sharedDragProviderContexts,
|
|
310
|
-
]
|
|
263
|
+
[keys, acceptsDrop, id, getDropTargetParentIndex, sharedDragProps]
|
|
311
264
|
);
|
|
312
265
|
|
|
313
266
|
// Set the droppable ref after every render.
|
|
@@ -2,17 +2,17 @@ import * as React from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
ActiveDragContextType,
|
|
4
4
|
DragRegistrationContextType,
|
|
5
|
-
|
|
5
|
+
SharedDragProps,
|
|
6
6
|
} from "./DragRegistration";
|
|
7
7
|
import { SharedDragProvider } from "./SharedDragProvider";
|
|
8
8
|
import { Sortable, SortableProps } from "./Sortable";
|
|
9
9
|
|
|
10
10
|
export function createSharedDrag(): {
|
|
11
11
|
Provider: typeof SharedDragProvider;
|
|
12
|
-
props: SharedDragProviderContexts;
|
|
13
12
|
Sortable: <TItem, TElement extends HTMLElement>(
|
|
14
13
|
props: SortableProps<TItem, TElement>
|
|
15
14
|
) => React.ReactNode;
|
|
15
|
+
props: SharedDragProps;
|
|
16
16
|
} {
|
|
17
17
|
const dragRegistrationContext =
|
|
18
18
|
React.createContext<DragRegistrationContextType | null>(null);
|
|
@@ -20,27 +20,22 @@ export function createSharedDrag(): {
|
|
|
20
20
|
null
|
|
21
21
|
);
|
|
22
22
|
|
|
23
|
+
const sharedDragProps: SharedDragProps = {
|
|
24
|
+
dragRegistrationContext,
|
|
25
|
+
activeDragContext,
|
|
26
|
+
};
|
|
27
|
+
|
|
23
28
|
const Provider = ({ children }: { children: React.ReactNode }) => (
|
|
24
|
-
<SharedDragProvider
|
|
25
|
-
contexts={{
|
|
26
|
-
dragRegistrationContext,
|
|
27
|
-
activeDragContext,
|
|
28
|
-
}}
|
|
29
|
-
>
|
|
29
|
+
<SharedDragProvider sharedDragProps={sharedDragProps}>
|
|
30
30
|
{children}
|
|
31
31
|
</SharedDragProvider>
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
const dragProps = {
|
|
35
|
-
dragRegistrationContext,
|
|
36
|
-
activeDragContext,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
34
|
return {
|
|
40
35
|
Provider,
|
|
41
36
|
Sortable: <TItem, TElement extends HTMLElement>(
|
|
42
37
|
props: SortableProps<TItem, TElement>
|
|
43
|
-
) => <Sortable
|
|
44
|
-
props:
|
|
38
|
+
) => <Sortable sharedDragProps={sharedDragProps} {...props} />,
|
|
39
|
+
props: sharedDragProps,
|
|
45
40
|
};
|
|
46
41
|
}
|
|
@@ -36,16 +36,16 @@ export type AcceptsFromList = ({
|
|
|
36
36
|
}) => boolean;
|
|
37
37
|
|
|
38
38
|
export interface DragItem {
|
|
39
|
-
|
|
39
|
+
itemId: UniqueIdentifier;
|
|
40
40
|
listId: string;
|
|
41
41
|
index: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
44
|
+
export type DragState = {
|
|
45
|
+
source: DragItem | null;
|
|
46
|
+
target: DragItem | null;
|
|
47
|
+
indicator: RelativeDropPosition | null;
|
|
48
|
+
};
|
|
49
49
|
|
|
50
50
|
export const normalizeListTargetIndex = (
|
|
51
51
|
index: number,
|
|
@@ -142,6 +142,19 @@ export function validateDropIndicator({
|
|
|
142
142
|
offsetStart
|
|
143
143
|
);
|
|
144
144
|
|
|
145
|
+
// For moving within the same list, we are more strict about the drop position.
|
|
146
|
+
// The source item must actually be contained in the target element.
|
|
147
|
+
// Otherwise the user may unintentionally drop the item into target,
|
|
148
|
+
// since it’s difficult to select no target at all.
|
|
149
|
+
// When dropping into a different list, the user definitely wants the item within that list,
|
|
150
|
+
// and it also handles the empty list case.
|
|
151
|
+
if (
|
|
152
|
+
!isContained(elementStart, elementSize, offsetStart) &&
|
|
153
|
+
sourceListId === targetListId
|
|
154
|
+
) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
145
158
|
// Drop above or below if possible
|
|
146
159
|
const acceptedHalf = acceptsDrop(
|
|
147
160
|
activeIndex,
|
|
@@ -170,6 +183,16 @@ function isContainedInMiddleThird(
|
|
|
170
183
|
);
|
|
171
184
|
}
|
|
172
185
|
|
|
186
|
+
function isContained(
|
|
187
|
+
elementStart: number,
|
|
188
|
+
elementSize: number,
|
|
189
|
+
offsetStart: number
|
|
190
|
+
): boolean {
|
|
191
|
+
return (
|
|
192
|
+
offsetStart >= elementStart && offsetStart <= elementStart + elementSize
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
function getContainingHalf(
|
|
174
197
|
elementStart: number,
|
|
175
198
|
elementSize: number,
|
|
@@ -178,3 +201,15 @@ function getContainingHalf(
|
|
|
178
201
|
if (offsetStart < elementStart + elementSize / 2) return "above";
|
|
179
202
|
return "below";
|
|
180
203
|
}
|
|
204
|
+
|
|
205
|
+
export function createDragItemKey(listId: string, itemId: UniqueIdentifier) {
|
|
206
|
+
return `${listId}-~-${itemId}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function parseDragItemKey(key: string): {
|
|
210
|
+
listId: string;
|
|
211
|
+
itemId: UniqueIdentifier;
|
|
212
|
+
} {
|
|
213
|
+
const [listId, itemId] = key.split("-~-");
|
|
214
|
+
return { listId, itemId };
|
|
215
|
+
}
|