@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.
Files changed (143) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +648 -333
  5. package/dist/index.d.ts +648 -333
  6. package/dist/index.js +13813 -9157
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +13949 -9269
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +10 -22
  11. package/src/__tests__/validateDropIndicator.test.ts +26 -41
  12. package/src/__tests__/workspaceLayout.test.ts +281 -0
  13. package/src/components/ActionMenu.tsx +2 -0
  14. package/src/components/ActivityIndicator.tsx +13 -6
  15. package/src/components/ActivityLog.tsx +197 -0
  16. package/src/components/AnimatePresence.tsx +43 -26
  17. package/src/components/Avatar.tsx +45 -26
  18. package/src/components/Banner.tsx +57 -11
  19. package/src/components/BaseToolbar.tsx +67 -34
  20. package/src/components/Breadcrumbs.tsx +4 -2
  21. package/src/components/Button.tsx +28 -23
  22. package/src/components/Checkbox.tsx +6 -35
  23. package/src/components/Chip.tsx +47 -33
  24. package/src/components/Collection.tsx +20 -12
  25. package/src/components/ColorSwatch.tsx +42 -22
  26. package/src/components/ColorSwatchControl.tsx +7 -5
  27. package/src/components/Combobox.tsx +8 -6
  28. package/src/components/ComboboxMenu.tsx +6 -5
  29. package/src/components/CommandPalette.tsx +3 -1
  30. package/src/components/ContextMenu.tsx +3 -1
  31. package/src/components/Dialog.tsx +9 -13
  32. package/src/components/DimensionInput.tsx +2 -0
  33. package/src/components/Divider.tsx +18 -14
  34. package/src/components/DraggableMenuButton.tsx +11 -5
  35. package/src/components/Drawer.tsx +14 -6
  36. package/src/components/DropdownMenu.tsx +4 -2
  37. package/src/components/EditableText.tsx +4 -2
  38. package/src/components/Fade.tsx +13 -13
  39. package/src/components/FileUploadIndicator.tsx +108 -0
  40. package/src/components/FloatingWindow.tsx +4 -2
  41. package/src/components/Grid.tsx +58 -42
  42. package/src/components/GridView.tsx +65 -36
  43. package/src/components/IVirtualizedList.tsx +5 -0
  44. package/src/components/IconButton.tsx +2 -0
  45. package/src/components/Icons.tsx +24 -4
  46. package/src/components/InputField.tsx +21 -19
  47. package/src/components/InspectorContainer.tsx +2 -2
  48. package/src/components/InspectorPrimitives.tsx +15 -9
  49. package/src/components/Label.tsx +4 -3
  50. package/src/components/LabeledElementView.tsx +9 -5
  51. package/src/components/LabeledField.tsx +8 -6
  52. package/src/components/List.tsx +112 -46
  53. package/src/components/ListMenu.tsx +91 -0
  54. package/src/components/ListNavigator.tsx +106 -0
  55. package/src/components/ListView.tsx +22 -1147
  56. package/src/components/MediaThumbnail.tsx +84 -14
  57. package/src/components/Message.tsx +11 -12
  58. package/src/components/Navigator.tsx +461 -0
  59. package/src/components/NoyaLogo.tsx +1 -1
  60. package/src/components/Popover.tsx +12 -6
  61. package/src/components/Progress.tsx +31 -8
  62. package/src/components/ResizableContainer.tsx +187 -0
  63. package/src/components/RingProgress.tsx +128 -0
  64. package/src/components/ScrollArea.tsx +7 -5
  65. package/src/components/ScrollableSidebar.tsx +10 -0
  66. package/src/components/SearchCompletionMenu.tsx +5 -3
  67. package/src/components/Section.tsx +109 -57
  68. package/src/components/SegmentedControl.tsx +51 -25
  69. package/src/components/SelectMenu.tsx +16 -21
  70. package/src/components/SelectionToolbar.tsx +7 -4
  71. package/src/components/Slider.tsx +12 -10
  72. package/src/components/Spacer.tsx +2 -2
  73. package/src/components/StackNavigator.tsx +378 -0
  74. package/src/components/Stepper.tsx +88 -0
  75. package/src/components/Switch.tsx +7 -5
  76. package/src/components/Tabs.tsx +6 -4
  77. package/src/components/Text.tsx +29 -18
  78. package/src/components/TextArea.tsx +5 -3
  79. package/src/components/Toast.tsx +5 -5
  80. package/src/components/Toolbar.tsx +57 -10
  81. package/src/components/ToolbarDrawer.tsx +29 -0
  82. package/src/components/Tooltip.tsx +2 -2
  83. package/src/components/TreeView.tsx +13 -4
  84. package/src/components/UserPointer.tsx +6 -4
  85. package/src/components/ai-assistant/AIAssistantLayout.tsx +10 -8
  86. package/src/components/blocks/ImageBlockComponent.tsx +162 -0
  87. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +6 -4
  88. package/src/components/file-explorer/FileExplorerLayout.tsx +22 -8
  89. package/src/components/internal/Checkmark.tsx +24 -0
  90. package/src/components/internal/Menu.tsx +24 -19
  91. package/src/components/internal/MenuViewport.tsx +15 -6
  92. package/src/components/internal/SelectItem.tsx +7 -26
  93. package/src/components/internal/TextInput.tsx +7 -1
  94. package/src/components/listView/ListViewContexts.tsx +68 -0
  95. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  96. package/src/components/listView/ListViewRoot.tsx +521 -0
  97. package/src/components/listView/ListViewRow.tsx +475 -0
  98. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  99. package/src/components/listView/types.ts +11 -0
  100. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  101. package/src/components/sorting/DragRegistration.tsx +10 -5
  102. package/src/components/sorting/SharedDragProvider.tsx +134 -27
  103. package/src/components/sorting/Sortable.tsx +90 -27
  104. package/src/components/sorting/createSharedDrag.tsx +7 -6
  105. package/src/components/sorting/sorting.ts +6 -9
  106. package/src/components/workspace/DrawerWorkspaceLayout.tsx +110 -29
  107. package/src/components/workspace/PanelWorkspaceLayout.tsx +148 -99
  108. package/src/components/workspace/VerticalTabMenu.tsx +46 -0
  109. package/src/components/workspace/WorkspaceLayout.tsx +76 -63
  110. package/src/components/workspace/constants.ts +4 -0
  111. package/src/components/workspace/panelStorage.ts +216 -0
  112. package/src/components/workspace/types.ts +11 -4
  113. package/src/contexts/DesignSystemConfiguration.tsx +12 -5
  114. package/src/contexts/DialogContext.tsx +19 -5
  115. package/src/contexts/FloatingWindowContext.tsx +2 -0
  116. package/src/contexts/GlobalInputBlurContext.tsx +2 -0
  117. package/src/contexts/ImageDataContext.tsx +2 -0
  118. package/src/contexts/LinkComponentContext.tsx +17 -0
  119. package/src/contexts/OpenPortalsContext.tsx +2 -0
  120. package/src/contexts/PortalScopeContext.tsx +2 -0
  121. package/src/hooks/mergeEventHandlers.ts +1 -1
  122. package/src/hooks/useHover.ts +30 -17
  123. package/src/hooks/useIndent.ts +5 -8
  124. package/src/hooks/useLabel.ts +2 -0
  125. package/src/hooks/useObjectURL.ts +5 -3
  126. package/src/hooks/usePlatform.ts +5 -3
  127. package/src/hooks/useTheme.ts +3 -1
  128. package/src/index.css +43 -10
  129. package/src/index.tsx +15 -7
  130. package/src/utils/classNames.ts +56 -9
  131. package/src/utils/combobox.ts +2 -0
  132. package/src/utils/editableBlockStyles.ts +2 -2
  133. package/src/utils/formatByteSize.ts +9 -0
  134. package/src/utils/inputs.ts +8 -6
  135. package/src/utils/sketchColor.ts +0 -34
  136. package/src/utils/withSeparatorElements.ts +1 -1
  137. package/tailwind.config.ts +1 -0
  138. package/src/components/FillInputField.tsx +0 -37
  139. package/src/components/FillPreviewBackground.tsx +0 -132
  140. package/src/components/GradientPicker.tsx +0 -88
  141. package/src/hooks/usePreservePanelSize.tsx +0 -183
  142. package/src/mediaQuery.ts +0 -13
  143. 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 { Point, rectContainsPoint, unionRects } from "@noya-app/noya-geometry";
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 { useStableCallback } from "@noya-app/react-utils";
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
- // Both lists have the opportunity to handle the move
174
- sourceList.onMoveItem(parameters);
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
- if (dropTarget.target.listId !== dropTarget.source.listId) {
177
- targetList.onMoveItem(parameters);
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
- (registeredLists: Map<string, RegisteredList>): CollisionDetection =>
290
+ (
291
+ registeredLists: Map<string, RegisteredList>,
292
+ itemHitSlop: Insets | undefined
293
+ ): CollisionDetection =>
266
294
  (parameters) => {
267
- const pointerCollisions = pointerWithin(parameters);
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(parameters);
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 (parameters.pointerCoordinates && listCollision) {
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) => parameters.droppableRects.get(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 (rectContainsPoint(merged, parameters.pointerCoordinates)) {
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 = targetList.acceptsDrop({
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
- absolutePosition,
413
- active,
414
- over,
415
- sourceListId: sourceList.id,
416
- targetListId: targetList.id,
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.Ref<T>;
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 ? undefined : getDropTargetParentIndex?.(target.index);
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: (props: ItemChildrenProps<TElement>) => React.ReactNode;
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 { attributes, listeners, setNodeRef, isDragging, index } = useSortable({
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
- ref,
157
- ...attributes,
158
- ...listeners,
159
- relativeDropPosition,
160
- style: {
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 { absolutePosition, active, over, sourceListId, targetListId } =
207
- parameters;
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" ? over.rect.left : over.rect.top;
212
- const elementSize = axis === "x" ? over.rect.width : over.rect.height;
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
- keys,
217
- activeId: active.id,
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
- { isOverlay }: { isOverlay: boolean }
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
- { isOverlay: true }
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, { isOverlay: false });
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 { SharedDragProvider } from "./SharedDragProvider";
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 = ({ children }: { children: React.ReactNode }) => (
29
- <SharedDragProvider sharedDragProps={sharedDragProps}>
30
- {children}
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
- keys: UniqueIdentifier[]; // Array of all sortable item IDs
87
- activeId: UniqueIdentifier; // ID of item being dragged
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
- keys,
99
- activeId,
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