@noya-app/noya-designsystem 0.1.55 → 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.
@@ -0,0 +1,41 @@
1
+ import * as React from "react";
2
+ import {
3
+ ActiveDragContextType,
4
+ DragRegistrationContextType,
5
+ SharedDragProps,
6
+ } from "./DragRegistration";
7
+ import { SharedDragProvider } from "./SharedDragProvider";
8
+ import { Sortable, SortableProps } from "./Sortable";
9
+
10
+ export function createSharedDrag(): {
11
+ Provider: typeof SharedDragProvider;
12
+ Sortable: <TItem, TElement extends HTMLElement>(
13
+ props: SortableProps<TItem, TElement>
14
+ ) => React.ReactNode;
15
+ props: SharedDragProps;
16
+ } {
17
+ const dragRegistrationContext =
18
+ React.createContext<DragRegistrationContextType | null>(null);
19
+ const activeDragContext = React.createContext<ActiveDragContextType | null>(
20
+ null
21
+ );
22
+
23
+ const sharedDragProps: SharedDragProps = {
24
+ dragRegistrationContext,
25
+ activeDragContext,
26
+ };
27
+
28
+ const Provider = ({ children }: { children: React.ReactNode }) => (
29
+ <SharedDragProvider sharedDragProps={sharedDragProps}>
30
+ {children}
31
+ </SharedDragProvider>
32
+ );
33
+
34
+ return {
35
+ Provider,
36
+ Sortable: <TItem, TElement extends HTMLElement>(
37
+ props: SortableProps<TItem, TElement>
38
+ ) => <Sortable sharedDragProps={sharedDragProps} {...props} />,
39
+ props: sharedDragProps,
40
+ };
41
+ }
@@ -0,0 +1,215 @@
1
+ import { UniqueIdentifier } from "@dnd-kit/core";
2
+ import * as React from "react";
3
+
4
+ export type RelativeDropPosition = "above" | "below" | "inside";
5
+
6
+ export type DropValidator = (
7
+ sourceIndex: number,
8
+ targetIndex: number,
9
+ position: RelativeDropPosition,
10
+ sourceListId: string,
11
+ targetListId: string
12
+ ) => boolean;
13
+
14
+ export type AcceptsDrop = (
15
+ sourceIndex: number,
16
+ targetIndex: number,
17
+ position: RelativeDropPosition,
18
+ sourceListId: string,
19
+ targetListId: string
20
+ ) => boolean;
21
+
22
+ export type MoveDragItemHandler = (
23
+ sourceIndex: number,
24
+ targetIndex: number,
25
+ position: RelativeDropPosition,
26
+ sourceListId: string,
27
+ targetListId: string
28
+ ) => void;
29
+
30
+ export type AcceptsFromList = ({
31
+ sourceListId,
32
+ targetListId,
33
+ }: {
34
+ sourceListId: string;
35
+ targetListId: string;
36
+ }) => boolean;
37
+
38
+ export interface DragItem {
39
+ itemId: UniqueIdentifier;
40
+ listId: string;
41
+ index: number;
42
+ }
43
+
44
+ export type DragState = {
45
+ source: DragItem | null;
46
+ target: DragItem | null;
47
+ indicator: RelativeDropPosition | null;
48
+ };
49
+
50
+ export const normalizeListTargetIndex = (
51
+ index: number,
52
+ position: "above" | "below"
53
+ ): number => {
54
+ return position === "above" ? index : index + 1;
55
+ };
56
+
57
+ export const defaultAcceptsDrop: DropValidator = (
58
+ sourceIndex,
59
+ targetIndex,
60
+ position
61
+ ) => {
62
+ if (position === "inside") return false;
63
+
64
+ const normalized = normalizeListTargetIndex(targetIndex, position);
65
+
66
+ if (sourceIndex === normalized || sourceIndex + 1 === normalized) {
67
+ return false;
68
+ }
69
+
70
+ return true;
71
+ };
72
+
73
+ export type SortableItemContextProps = {
74
+ keys: UniqueIdentifier[];
75
+ acceptsDrop: DropValidator;
76
+ axis: "x" | "y";
77
+ position: { x: number; y: number };
78
+ setActivatorEvent: (event: PointerEvent) => void;
79
+ getDropTargetParentIndex?: (index: number) => number | undefined;
80
+ listId: string;
81
+ };
82
+
83
+ export const SortableItemContext =
84
+ React.createContext<SortableItemContextProps>({
85
+ keys: [],
86
+ acceptsDrop: defaultAcceptsDrop,
87
+ axis: "y",
88
+ position: { x: 0, y: 0 },
89
+ setActivatorEvent: () => {},
90
+ getDropTargetParentIndex: undefined,
91
+ listId: "",
92
+ });
93
+
94
+ export type ValidateDropIndicatorParams = {
95
+ acceptsDrop: DropValidator; // Function that validates if a drop is allowed
96
+ keys: UniqueIdentifier[]; // Array of all sortable item IDs
97
+ activeId: UniqueIdentifier; // ID of item being dragged
98
+ overId: UniqueIdentifier; // ID of item being dragged over
99
+ offsetStart: number; // Current drag position (x or y coordinate)
100
+ elementStart: number; // Start position of target element (left or top)
101
+ elementSize: number; // Size of target element (width or height)
102
+ sourceListId: string; // ID of the list the item is being dragged from
103
+ targetListId: string; // ID of the list the item is being dragged to
104
+ };
105
+
106
+ export function validateDropIndicator({
107
+ acceptsDrop,
108
+ keys,
109
+ activeId,
110
+ overId,
111
+ offsetStart,
112
+ elementStart,
113
+ elementSize,
114
+ sourceListId,
115
+ targetListId,
116
+ }: ValidateDropIndicatorParams): RelativeDropPosition | undefined {
117
+ const activeIndex = keys.findIndex((id) => id === activeId); // index of item being dragged
118
+ const overIndex = keys.findIndex((id) => id === overId); // index of item being dragged over
119
+
120
+ if (overIndex === -1) return undefined;
121
+ if (activeIndex === -1 && sourceListId === targetListId) return undefined;
122
+
123
+ const acceptsDropInside = acceptsDrop(
124
+ activeIndex,
125
+ overIndex,
126
+ "inside",
127
+ sourceListId,
128
+ targetListId
129
+ );
130
+
131
+ // Drop into the middle third if possible
132
+ if (
133
+ isContainedInMiddleThird(elementStart, elementSize, offsetStart) &&
134
+ acceptsDropInside
135
+ ) {
136
+ return "inside";
137
+ }
138
+
139
+ const containingHalf = getContainingHalf(
140
+ elementStart,
141
+ elementSize,
142
+ offsetStart
143
+ );
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
+
158
+ // Drop above or below if possible
159
+ const acceptedHalf = acceptsDrop(
160
+ activeIndex,
161
+ overIndex,
162
+ containingHalf,
163
+ sourceListId,
164
+ targetListId
165
+ );
166
+
167
+ return acceptedHalf
168
+ ? containingHalf
169
+ : // Lastly, check if inside but not middle third
170
+ acceptsDropInside
171
+ ? "inside"
172
+ : undefined;
173
+ }
174
+
175
+ function isContainedInMiddleThird(
176
+ elementStart: number,
177
+ elementSize: number,
178
+ offsetStart: number
179
+ ): boolean {
180
+ return (
181
+ offsetStart >= elementStart + elementSize / 3 &&
182
+ offsetStart <= elementStart + (elementSize * 2) / 3
183
+ );
184
+ }
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
+
196
+ function getContainingHalf(
197
+ elementStart: number,
198
+ elementSize: number,
199
+ offsetStart: number
200
+ ): "above" | "below" {
201
+ if (offsetStart < elementStart + elementSize / 2) return "above";
202
+ return "below";
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
+ }
package/src/index.tsx CHANGED
@@ -60,8 +60,10 @@ export * from "./components/SegmentedControl";
60
60
  export * from "./components/SelectionToolbar";
61
61
  export * from "./components/SelectMenu";
62
62
  export * from "./components/Slider";
63
- export * from "./components/Sortable";
64
- export type { RelativeDropPosition } from "./components/Sortable";
63
+ export * from "./components/sorting/createSharedDrag";
64
+ export * from "./components/sorting/SharedDragProvider";
65
+ export * from "./components/sorting/Sortable";
66
+ export * from "./components/sorting/sorting";
65
67
  export * from "./components/Spacer";
66
68
  export * from "./components/Switch";
67
69
  export * from "./components/Text";
@@ -2,7 +2,7 @@ import { isDeepEqual } from "@noya-app/noya-utils";
2
2
  import {
3
3
  defaultAcceptsDrop,
4
4
  RelativeDropPosition,
5
- } from "../components/Sortable";
5
+ } from "../components/sorting/sorting";
6
6
 
7
7
  type MoveOptions = {
8
8
  paths: number[][];
@@ -91,7 +91,9 @@ export function acceptsDrop({
91
91
  return defaultAcceptsDrop(
92
92
  sourceIndexPath.at(-1)!,
93
93
  targetIndexPath.at(-1)!,
94
- position
94
+ position,
95
+ "",
96
+ ""
95
97
  );
96
98
  }
97
99