@dxos/react-ui-mosaic 0.8.4-main.bc674ce → 0.8.4-main.c85a9c8dae

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 (186) hide show
  1. package/dist/lib/browser/chunk-7S4PIFKU.mjs +953 -0
  2. package/dist/lib/browser/chunk-7S4PIFKU.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +596 -70
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/playwright/index.mjs +138 -0
  7. package/dist/lib/browser/playwright/index.mjs.map +7 -0
  8. package/dist/lib/browser/testing/index.mjs +61 -232
  9. package/dist/lib/browser/testing/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/chunk-MK6KU66S.mjs +955 -0
  11. package/dist/lib/node-esm/chunk-MK6KU66S.mjs.map +7 -0
  12. package/dist/lib/node-esm/index.mjs +596 -70
  13. package/dist/lib/node-esm/index.mjs.map +4 -4
  14. package/dist/lib/node-esm/meta.json +1 -1
  15. package/dist/lib/node-esm/playwright/index.mjs +140 -0
  16. package/dist/lib/node-esm/playwright/index.mjs.map +7 -0
  17. package/dist/lib/node-esm/testing/index.mjs +61 -232
  18. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  19. package/dist/types/src/components/Board/Board.d.ts +81 -0
  20. package/dist/types/src/components/Board/Board.d.ts.map +1 -0
  21. package/dist/types/src/components/{Mosaic/Mosaic.stories.d.ts → Board/Board.stories.d.ts} +15 -1
  22. package/dist/types/src/components/Board/Board.stories.d.ts.map +1 -0
  23. package/dist/types/src/components/Board/Column.d.ts +40 -0
  24. package/dist/types/src/components/Board/Column.d.ts.map +1 -0
  25. package/dist/types/src/components/Board/Item.d.ts +9 -0
  26. package/dist/types/src/components/Board/Item.d.ts.map +1 -0
  27. package/dist/types/src/components/Board/index.d.ts +2 -0
  28. package/dist/types/src/components/Board/index.d.ts.map +1 -0
  29. package/dist/types/src/components/Focus/Focus.d.ts +5 -4
  30. package/dist/types/src/components/Focus/Focus.d.ts.map +1 -1
  31. package/dist/types/src/components/Focus/index.d.ts +0 -1
  32. package/dist/types/src/components/Focus/index.d.ts.map +1 -1
  33. package/dist/types/src/components/Mosaic/Container.d.ts +42 -0
  34. package/dist/types/src/components/Mosaic/Container.d.ts.map +1 -0
  35. package/dist/types/src/components/Mosaic/Mosaic.d.ts +35 -118
  36. package/dist/types/src/components/Mosaic/Mosaic.d.ts.map +1 -1
  37. package/dist/types/src/components/Mosaic/Placeholder.d.ts +21 -0
  38. package/dist/types/src/components/Mosaic/Placeholder.d.ts.map +1 -0
  39. package/dist/types/src/components/Mosaic/Root.d.ts +36 -0
  40. package/dist/types/src/components/Mosaic/Root.d.ts.map +1 -0
  41. package/dist/types/src/components/Mosaic/Stack.d.ts +23 -0
  42. package/dist/types/src/components/Mosaic/Stack.d.ts.map +1 -0
  43. package/dist/types/src/components/{Stack → Mosaic}/Stack.stories.d.ts +2 -2
  44. package/dist/types/src/components/Mosaic/Stack.stories.d.ts.map +1 -0
  45. package/dist/types/src/components/Mosaic/Tile.d.ts +48 -0
  46. package/dist/types/src/components/Mosaic/Tile.d.ts.map +1 -0
  47. package/dist/types/src/components/Mosaic/styles.d.ts.map +1 -1
  48. package/dist/types/src/components/Mosaic/types.d.ts +9 -2
  49. package/dist/types/src/components/Mosaic/types.d.ts.map +1 -1
  50. package/dist/types/src/components/index.d.ts +1 -4
  51. package/dist/types/src/components/index.d.ts.map +1 -1
  52. package/dist/types/src/hooks/index.d.ts +1 -0
  53. package/dist/types/src/hooks/index.d.ts.map +1 -1
  54. package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts +15 -0
  55. package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts.map +1 -0
  56. package/dist/types/src/hooks/useEventHandlerAdapter.d.ts +1 -1
  57. package/dist/types/src/hooks/useEventHandlerAdapter.d.ts.map +1 -1
  58. package/dist/types/src/hooks/useVisibleItems.d.ts +2 -2
  59. package/dist/types/src/hooks/useVisibleItems.d.ts.map +1 -1
  60. package/dist/types/src/playwright/board-manager.d.ts +45 -0
  61. package/dist/types/src/playwright/board-manager.d.ts.map +1 -0
  62. package/dist/types/src/playwright/index.d.ts +9 -0
  63. package/dist/types/src/playwright/index.d.ts.map +1 -0
  64. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  65. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  66. package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
  67. package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
  68. package/dist/types/src/testing/CardContainer.d.ts +5 -4
  69. package/dist/types/src/testing/CardContainer.d.ts.map +1 -1
  70. package/dist/types/src/testing/DefaultStackTile.d.ts +2 -2
  71. package/dist/types/src/testing/DefaultStackTile.d.ts.map +1 -1
  72. package/dist/types/src/testing/index.d.ts +1 -1
  73. package/dist/types/src/testing/index.d.ts.map +1 -1
  74. package/dist/types/src/testing/types.d.ts +21 -0
  75. package/dist/types/src/testing/types.d.ts.map +1 -0
  76. package/dist/types/src/translations.d.ts +5 -1
  77. package/dist/types/src/translations.d.ts.map +1 -1
  78. package/dist/types/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +43 -28
  80. package/src/components/Board/Board.stories.tsx +273 -0
  81. package/src/components/Board/Board.tsx +191 -0
  82. package/src/components/Board/Column.tsx +300 -0
  83. package/src/components/Board/Item.tsx +116 -0
  84. package/src/components/{Layout → Board}/index.ts +1 -1
  85. package/src/components/Focus/Focus.tsx +26 -17
  86. package/src/components/Focus/index.ts +0 -2
  87. package/src/components/Mosaic/Container.tsx +289 -0
  88. package/src/components/Mosaic/Mosaic.tsx +35 -913
  89. package/src/components/Mosaic/Placeholder.tsx +133 -0
  90. package/src/components/Mosaic/Root.tsx +292 -0
  91. package/src/components/{Stack → Mosaic}/Stack.stories.tsx +30 -26
  92. package/src/components/Mosaic/Stack.tsx +246 -0
  93. package/src/components/Mosaic/Tile.tsx +242 -0
  94. package/src/components/Mosaic/extension.ts +1 -1
  95. package/src/components/Mosaic/styles.ts +9 -8
  96. package/src/components/Mosaic/types.ts +15 -3
  97. package/src/components/index.ts +1 -4
  98. package/src/hooks/index.ts +1 -0
  99. package/src/hooks/useContainerDebug.tsx +4 -2
  100. package/src/hooks/useDefaultColumnEventHandler.ts +35 -0
  101. package/src/hooks/useEventHandlerAdapter.ts +15 -6
  102. package/src/hooks/useVisibleItems.ts +7 -2
  103. package/src/playwright/board-manager.ts +154 -0
  104. package/src/playwright/index.ts +13 -0
  105. package/src/playwright/playwright.config.ts +17 -0
  106. package/src/playwright/smoke.spec.ts +138 -0
  107. package/src/testing/CardContainer.tsx +30 -18
  108. package/src/testing/DefaultStackTile.tsx +27 -16
  109. package/src/testing/index.ts +1 -1
  110. package/src/testing/types.ts +38 -0
  111. package/src/translations.ts +5 -1
  112. package/dist/lib/browser/chunk-UMBPEG4F.mjs +0 -1637
  113. package/dist/lib/browser/chunk-UMBPEG4F.mjs.map +0 -7
  114. package/dist/lib/browser/index.css +0 -13
  115. package/dist/lib/browser/index.css.map +0 -7
  116. package/dist/lib/browser/testing/index.css +0 -13
  117. package/dist/lib/browser/testing/index.css.map +0 -7
  118. package/dist/lib/node-esm/chunk-X6GV7SMK.mjs +0 -1639
  119. package/dist/lib/node-esm/chunk-X6GV7SMK.mjs.map +0 -7
  120. package/dist/lib/node-esm/index.css +0 -13
  121. package/dist/lib/node-esm/index.css.map +0 -7
  122. package/dist/lib/node-esm/testing/index.css +0 -13
  123. package/dist/lib/node-esm/testing/index.css.map +0 -7
  124. package/dist/types/src/components/Card/Card.d.ts +0 -121
  125. package/dist/types/src/components/Card/Card.d.ts.map +0 -1
  126. package/dist/types/src/components/Card/Card.stories.d.ts +0 -21
  127. package/dist/types/src/components/Card/Card.stories.d.ts.map +0 -1
  128. package/dist/types/src/components/Card/CardDragPreview.d.ts +0 -6
  129. package/dist/types/src/components/Card/CardDragPreview.d.ts.map +0 -1
  130. package/dist/types/src/components/Card/index.d.ts +0 -4
  131. package/dist/types/src/components/Card/index.d.ts.map +0 -1
  132. package/dist/types/src/components/Card/styles.d.ts +0 -13
  133. package/dist/types/src/components/Card/styles.d.ts.map +0 -1
  134. package/dist/types/src/components/Focus/styles.d.ts +0 -6
  135. package/dist/types/src/components/Focus/styles.d.ts.map +0 -1
  136. package/dist/types/src/components/Image/Image.d.ts +0 -14
  137. package/dist/types/src/components/Image/Image.d.ts.map +0 -1
  138. package/dist/types/src/components/Image/Image.stories.d.ts +0 -33
  139. package/dist/types/src/components/Image/Image.stories.d.ts.map +0 -1
  140. package/dist/types/src/components/Image/index.d.ts +0 -2
  141. package/dist/types/src/components/Image/index.d.ts.map +0 -1
  142. package/dist/types/src/components/Layout/Layout.d.ts +0 -33
  143. package/dist/types/src/components/Layout/Layout.d.ts.map +0 -1
  144. package/dist/types/src/components/Layout/Layout.stories.d.ts +0 -10
  145. package/dist/types/src/components/Layout/Layout.stories.d.ts.map +0 -1
  146. package/dist/types/src/components/Layout/index.d.ts +0 -2
  147. package/dist/types/src/components/Layout/index.d.ts.map +0 -1
  148. package/dist/types/src/components/Mosaic/Mosaic.stories.d.ts.map +0 -1
  149. package/dist/types/src/components/Scrollable/Scrollable.d.ts +0 -24
  150. package/dist/types/src/components/Scrollable/Scrollable.d.ts.map +0 -1
  151. package/dist/types/src/components/Scrollable/Scrollable.stories.d.ts +0 -10
  152. package/dist/types/src/components/Scrollable/Scrollable.stories.d.ts.map +0 -1
  153. package/dist/types/src/components/Scrollable/index.d.ts +0 -2
  154. package/dist/types/src/components/Scrollable/index.d.ts.map +0 -1
  155. package/dist/types/src/components/Stack/Stack.d.ts +0 -22
  156. package/dist/types/src/components/Stack/Stack.d.ts.map +0 -1
  157. package/dist/types/src/components/Stack/Stack.stories.d.ts.map +0 -1
  158. package/dist/types/src/components/Stack/index.d.ts +0 -2
  159. package/dist/types/src/components/Stack/index.d.ts.map +0 -1
  160. package/dist/types/src/testing/Board.d.ts +0 -45
  161. package/dist/types/src/testing/Board.d.ts.map +0 -1
  162. package/dist/types/src/testing/Slot.stories.d.ts +0 -14
  163. package/dist/types/src/testing/Slot.stories.d.ts.map +0 -1
  164. package/dist/types/src/testing/Virtualizer.stories.d.ts +0 -12
  165. package/dist/types/src/testing/Virtualizer.stories.d.ts.map +0 -1
  166. package/src/components/Card/Card.stories.tsx +0 -148
  167. package/src/components/Card/Card.tsx +0 -458
  168. package/src/components/Card/CardDragPreview.tsx +0 -23
  169. package/src/components/Card/index.ts +0 -8
  170. package/src/components/Card/styles.ts +0 -23
  171. package/src/components/Focus/styles.ts +0 -24
  172. package/src/components/Image/Image.stories.tsx +0 -86
  173. package/src/components/Image/Image.tsx +0 -223
  174. package/src/components/Image/index.ts +0 -5
  175. package/src/components/Layout/Layout.stories.tsx +0 -59
  176. package/src/components/Layout/Layout.tsx +0 -108
  177. package/src/components/Mosaic/Mosaic.stories.tsx +0 -113
  178. package/src/components/Scrollable/Scrollable.stories.tsx +0 -59
  179. package/src/components/Scrollable/Scrollable.tsx +0 -88
  180. package/src/components/Scrollable/index.ts +0 -5
  181. package/src/components/Scrollable/scrollable.css +0 -20
  182. package/src/components/Stack/Stack.tsx +0 -210
  183. package/src/components/Stack/index.ts +0 -5
  184. package/src/testing/Board.tsx +0 -278
  185. package/src/testing/Slot.stories.tsx +0 -101
  186. package/src/testing/Virtualizer.stories.tsx +0 -133
@@ -0,0 +1,133 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
+ import {
7
+ DropIndicator as NaturalDropIndicator,
8
+ type DropIndicatorProps as NaturalDropIndicatorProps,
9
+ } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box';
10
+ import { Primitive } from '@radix-ui/react-primitive';
11
+ import { Slot } from '@radix-ui/react-slot';
12
+ import React, { type PropsWithChildren, useLayoutEffect, useMemo, useRef } from 'react';
13
+
14
+ import { type Axis, type ThemedClassName } from '@dxos/react-ui';
15
+ import { mx } from '@dxos/ui-theme';
16
+
17
+ import { useMosaicContainerContext } from './Container';
18
+ import { useMosaicTileContext } from './Tile';
19
+ import { type LocationType, type MosaicPlaceholderData, getSourceData } from './types';
20
+
21
+ //
22
+ // Placeholder
23
+ //
24
+
25
+ const MOSAIC_PLACEHOLDER_NAME = 'Mosaic.Placeholder';
26
+
27
+ // Orientation: data-[mosaic-placeholder-orientation=vertical]
28
+ const MOSAIC_PLACEHOLDER_ORIENTATION_ATTR = 'mosaic-placeholder-orientation';
29
+
30
+ // State attribute: data-[mosaic-placeholder-state=active]
31
+ const MOSAIC_PLACEHOLDER_STATE_ATTR = 'mosaic-placeholder-state';
32
+
33
+ type MosaicPlaceholderProps<Location = LocationType> = ThemedClassName<
34
+ PropsWithChildren<{
35
+ asChild?: boolean;
36
+ orientation?: Axis;
37
+ location: Location;
38
+ }>
39
+ >;
40
+
41
+ const MosaicPlaceholder = <Location extends LocationType = LocationType>({
42
+ classNames,
43
+ children,
44
+ asChild,
45
+ orientation = 'vertical',
46
+ location,
47
+ }: MosaicPlaceholderProps<Location>) => {
48
+ const rootRef = useRef<HTMLDivElement>(null);
49
+ const Comp = asChild ? Slot : Primitive.div;
50
+ const {
51
+ id: containerId,
52
+ eventHandler,
53
+ scrolling,
54
+ activeLocation,
55
+ setActiveLocation,
56
+ } = useMosaicContainerContext(MOSAIC_PLACEHOLDER_NAME);
57
+ const data = useMemo<MosaicPlaceholderData<Location>>(
58
+ () =>
59
+ ({
60
+ type: 'placeholder',
61
+ containerId,
62
+ location,
63
+ }) satisfies MosaicPlaceholderData<Location>,
64
+ [containerId, location],
65
+ );
66
+
67
+ useLayoutEffect(() => {
68
+ const root = rootRef.current;
69
+ if (!root || scrolling) {
70
+ return;
71
+ }
72
+
73
+ return dropTargetForElements({
74
+ element: root,
75
+ getData: () => data,
76
+ canDrop: ({ source }) => {
77
+ const data = getSourceData(source);
78
+ return (data && eventHandler.canDrop?.({ source: data })) || false;
79
+ },
80
+ onDragEnter: () => {
81
+ setActiveLocation(data.location);
82
+ },
83
+ onDragLeave: () => {
84
+ setActiveLocation(undefined);
85
+ },
86
+ onDrop: () => {
87
+ setActiveLocation(undefined);
88
+ },
89
+ });
90
+ }, [rootRef, data, scrolling, setActiveLocation]);
91
+
92
+ return (
93
+ <Comp
94
+ {...{
95
+ [`data-${MOSAIC_PLACEHOLDER_ORIENTATION_ATTR}`]: orientation,
96
+ [`data-${MOSAIC_PLACEHOLDER_STATE_ATTR}`]: data.location === activeLocation ? 'active' : 'idle',
97
+ }}
98
+ role='none'
99
+ className={mx('relative', classNames)}
100
+ ref={rootRef}
101
+ >
102
+ {children}
103
+ </Comp>
104
+ );
105
+ };
106
+
107
+ MosaicPlaceholder.displayName = MOSAIC_PLACEHOLDER_NAME;
108
+
109
+ //
110
+ // DropIndicator
111
+ // TODO(burdon): Support DropIndicator or Placeholder variants.
112
+ //
113
+
114
+ const DROP_INDICATOR_NAME = 'Mosaic.DropIndicator';
115
+
116
+ type MosaicDropIndicatorProps = Omit<NaturalDropIndicatorProps, 'edge'>;
117
+
118
+ const MosaicDropIndicator = (props: MosaicDropIndicatorProps) => {
119
+ const { state } = useMosaicTileContext(DROP_INDICATOR_NAME);
120
+ return state.type === 'target' && state.closestEdge ? (
121
+ <NaturalDropIndicator {...props} edge={state.closestEdge} />
122
+ ) : null;
123
+ };
124
+
125
+ MosaicDropIndicator.displayName = DROP_INDICATOR_NAME;
126
+
127
+ //
128
+ // Exports
129
+ //
130
+
131
+ export { MosaicPlaceholder, MosaicDropIndicator };
132
+
133
+ export type { MosaicPlaceholderProps, MosaicDropIndicatorProps };
@@ -0,0 +1,292 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
6
+ import { type ElementDragPayload, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
7
+ import { type DragLocationHistory } from '@atlaskit/pragmatic-drag-and-drop/types';
8
+ import { useComposedRefs } from '@radix-ui/react-compose-refs';
9
+ import { createContext } from '@radix-ui/react-context';
10
+ import { Primitive } from '@radix-ui/react-primitive';
11
+ import { Slot } from '@radix-ui/react-slot';
12
+ import React, { type PropsWithChildren, forwardRef, useCallback, useEffect, useRef, useState } from 'react';
13
+
14
+ import { log } from '@dxos/log';
15
+ import { type ThemedClassName } from '@dxos/react-ui';
16
+ import { mx } from '@dxos/ui-theme';
17
+
18
+ import { type MosaicData, type MosaicEventHandler, type MosaicTileData } from './types';
19
+
20
+ //
21
+ // Context
22
+ //
23
+
24
+ type MosaicDraggingSource = {
25
+ data: MosaicTileData;
26
+ handler?: MosaicEventHandler;
27
+ container?: Element;
28
+ };
29
+
30
+ type MosaicDraggingTarget = {
31
+ data: MosaicData;
32
+ handler?: MosaicEventHandler;
33
+ };
34
+
35
+ type MosaicDraggingState = {
36
+ source: MosaicDraggingSource;
37
+ target?: MosaicDraggingTarget;
38
+ };
39
+
40
+ type MosaicRootContextValue = {
41
+ containers: Record<string, MosaicEventHandler>;
42
+ addContainer: (container: MosaicEventHandler) => void;
43
+ removeContainer: (id: string) => void;
44
+ dragging?: MosaicDraggingState;
45
+ };
46
+
47
+ const [MosaicRootContextProvider, useMosaicRootContext] = createContext<MosaicRootContextValue>('MosaicRoot');
48
+
49
+ //
50
+ // Root
51
+ //
52
+
53
+ const MOSAIC_ROOT_NAME = 'Mosaic.Root';
54
+
55
+ // State attribute: [&:has(>_[data-mosaic-debug=true])]
56
+ const MOSAIC_ROOT_DEBUG_ATTR = 'mosaic-debug';
57
+
58
+ type MosaicRootProps = ThemedClassName<
59
+ PropsWithChildren<{
60
+ asChild?: boolean;
61
+ debug?: boolean;
62
+ }>
63
+ >;
64
+
65
+ const MosaicRoot = forwardRef<HTMLDivElement, MosaicRootProps>(
66
+ ({ classNames, children, asChild, debug }, forwardedRef) => {
67
+ const rootRef = useRef<HTMLDivElement>(null);
68
+ const composedRef = useComposedRefs<HTMLDivElement>(rootRef, forwardedRef);
69
+ const Comp = asChild ? Slot : Primitive.div;
70
+
71
+ const [handlers, setHandlers] = useState<Record<string, MosaicEventHandler>>({});
72
+ const [dragging, setDragging] = useState<MosaicDraggingState | undefined>();
73
+
74
+ const getSourceHandler = useCallback(
75
+ (source: ElementDragPayload): { data: MosaicTileData; handler?: MosaicEventHandler } => {
76
+ const data = source.data as MosaicTileData;
77
+ return { data, handler: handlers[data.containerId] };
78
+ },
79
+ [handlers],
80
+ );
81
+
82
+ const getTargetHandler = useCallback(
83
+ (location: DragLocationHistory): { data?: MosaicData; handler?: MosaicEventHandler } => {
84
+ for (const target of location.current.dropTargets) {
85
+ const data = target.data as MosaicData;
86
+ let containerId: string;
87
+ switch (data.type) {
88
+ case 'tile':
89
+ case 'placeholder':
90
+ containerId = data.containerId;
91
+ break;
92
+ case 'container':
93
+ containerId = data.id;
94
+ break;
95
+ }
96
+
97
+ const handler = handlers[containerId];
98
+ if (handler) {
99
+ return { data, handler };
100
+ }
101
+ }
102
+
103
+ return {};
104
+ },
105
+ [handlers],
106
+ );
107
+
108
+ useEffect(() => {
109
+ const handleChange = ({ source, location }: { source: ElementDragPayload; location: DragLocationHistory }) => {
110
+ const { data: sourceData } = getSourceHandler(source);
111
+ const { data: targetData, handler } = getTargetHandler(location);
112
+ setDragging((dragging) => {
113
+ dragging?.target?.handler?.onCancel?.();
114
+ return {
115
+ source: {
116
+ data: sourceData,
117
+ handler: handlers[sourceData.containerId],
118
+ // TOOD(burdon): Check id matches.
119
+ container: location.initial.dropTargets.find((target) => target.data.type === 'container')?.element,
120
+ },
121
+ target: targetData && {
122
+ data: targetData,
123
+ handler,
124
+ },
125
+ };
126
+ });
127
+ };
128
+
129
+ const handleCancel = () => {
130
+ setDragging((dragging) => {
131
+ requestAnimationFrame(() => {
132
+ dragging?.target?.handler?.onCancel?.();
133
+ dragging?.source?.container?.dispatchEvent(new CustomEvent('dnd:cancel', { bubbles: true }));
134
+ });
135
+
136
+ return undefined;
137
+ });
138
+ };
139
+
140
+ let noTargetTimeout: NodeJS.Timeout;
141
+
142
+ // Main controller.
143
+ return combine(
144
+ monitorForElements({
145
+ /**
146
+ * Dragging started within any container.
147
+ */
148
+ onDragStart: ({ source, location }) => {
149
+ log('Root.onDragStart', {
150
+ source: source.data,
151
+ location: location.current.dropTargets.map((target) => target.data),
152
+ });
153
+
154
+ handleChange({ source, location });
155
+ },
156
+
157
+ /**
158
+ * Dragging entered a new container.
159
+ */
160
+ onDropTargetChange: ({ source, location }) => {
161
+ clearTimeout(noTargetTimeout);
162
+ log('Root.onDropTargetChange', {
163
+ source: source.data,
164
+ location: location.current.dropTargets.map((target) => target.data),
165
+ });
166
+
167
+ // Stop dragging if there are no drop targets (or we are cancelling).
168
+ if (location.current.dropTargets.length === 0) {
169
+ noTargetTimeout = setTimeout(() => setDragging(undefined), 1_000);
170
+ } else {
171
+ handleChange({ source, location });
172
+ }
173
+ },
174
+
175
+ /**
176
+ * Dragging within any container.
177
+ */
178
+ onDrag: ({ source, location }) => {
179
+ const { data } = getSourceHandler(source);
180
+ const { handler } = getTargetHandler(location);
181
+ if (handler) {
182
+ const { clientX: x, clientY: y } = location.current.input;
183
+ handler.onDrag?.({ source: data, position: { x, y } });
184
+ }
185
+ },
186
+
187
+ /**
188
+ * Dragging ended.
189
+ */
190
+ onDrop: ({ source, location }) => {
191
+ log.info('Root.onDrop', {
192
+ source: source.data,
193
+ location: location.current.dropTargets.map((target) => target.data),
194
+ });
195
+
196
+ // Get the source container.
197
+ const { data: sourceData, handler: sourceHandler } = getSourceHandler(source);
198
+ if (!sourceHandler) {
199
+ log.warn('invalid source', {
200
+ source: sourceData,
201
+ handlers: Object.keys(handlers),
202
+ });
203
+ return;
204
+ }
205
+
206
+ // NOTE: When dragging is cancelled (e.g., user presses ESC) onDrop is only called after a subsequent event.
207
+ // NOTE: pDND blocks ESC event propagation while dragging.
208
+ // - ESC only flips internal state.
209
+ // - Completion happens on the next processed input event.
210
+ // - This avoids reentrancy and keeps pointer/keyboard behavior consistent.
211
+ // - We set dragging to undefined in onDropTargetChange after a delay if there are no drop targets.
212
+ try {
213
+ if (location.current.dropTargets.length === 0) {
214
+ log.info('cancelled');
215
+ } else {
216
+ // Get the target container.
217
+ const { data: targetData, handler: targetHandler } = getTargetHandler(location);
218
+ if (!targetHandler) {
219
+ log.warn('invalid target', {
220
+ source: sourceData,
221
+ location,
222
+ handlers: Object.keys(handlers),
223
+ });
224
+ return;
225
+ }
226
+
227
+ // TODO(burdon): Check object doesn't already exist in the collection.
228
+ if (sourceHandler === targetHandler) {
229
+ targetHandler.onDrop?.({
230
+ source: sourceData,
231
+ target: targetData,
232
+ });
233
+ } else {
234
+ if (!sourceHandler.onTake) {
235
+ log.warn('invalid source', { source: sourceData });
236
+ return;
237
+ }
238
+
239
+ sourceHandler.onTake?.({ source: sourceData }, async (object) => {
240
+ targetHandler.onDrop?.({
241
+ source: { ...sourceData, data: object },
242
+ target: targetData,
243
+ });
244
+
245
+ return true;
246
+ });
247
+ }
248
+ }
249
+ } finally {
250
+ handleCancel();
251
+ }
252
+ },
253
+ }),
254
+ );
255
+ }, [handlers, getSourceHandler, getTargetHandler]);
256
+
257
+ return (
258
+ <MosaicRootContextProvider
259
+ containers={handlers}
260
+ addContainer={(container) =>
261
+ setHandlers((containers) => ({
262
+ ...containers,
263
+ [container.id]: container,
264
+ }))
265
+ }
266
+ removeContainer={(id) =>
267
+ setHandlers((containers) => {
268
+ delete containers[id];
269
+ return { ...containers };
270
+ })
271
+ }
272
+ dragging={dragging}
273
+ >
274
+ <Comp
275
+ className={mx('group', classNames)}
276
+ {...{
277
+ [`data-${MOSAIC_ROOT_DEBUG_ATTR}`]: debug,
278
+ }}
279
+ ref={composedRef}
280
+ >
281
+ {children}
282
+ </Comp>
283
+ </MosaicRootContextProvider>
284
+ );
285
+ },
286
+ );
287
+
288
+ MosaicRoot.displayName = MOSAIC_ROOT_NAME;
289
+
290
+ export { MosaicRoot, useMosaicRootContext };
291
+
292
+ export type { MosaicRootProps, MosaicDraggingState };
@@ -8,14 +8,14 @@ import { useMemo } from 'react';
8
8
 
9
9
  import { Obj } from '@dxos/echo';
10
10
  import { faker } from '@dxos/random';
11
- import { Toolbar } from '@dxos/react-ui';
11
+ import { ScrollArea, Toolbar } from '@dxos/react-ui';
12
12
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
13
13
 
14
14
  import { useContainerDebug } from '../../hooks';
15
15
  import { DefaultStackTile, TestItem } from '../../testing';
16
- import { Mosaic } from '../Mosaic';
17
16
 
18
- import { Stack } from './Stack';
17
+ import { Mosaic } from './Mosaic';
18
+ import { MosaicStack } from './Stack';
19
19
 
20
20
  faker.seed(999);
21
21
 
@@ -30,15 +30,15 @@ const createTestItems = (n: number) =>
30
30
  }),
31
31
  );
32
32
 
33
- const meta: Meta<typeof Stack<Obj.Any>> = {
33
+ const meta: Meta<typeof MosaicStack<Obj.Any>> = {
34
34
  title: 'ui/react-ui-mosaic/Stack',
35
- component: Stack,
36
- decorators: [withLayout({ layout: 'column' }), withTheme],
35
+ component: MosaicStack,
36
+ decorators: [withLayout({ layout: 'column' }), withTheme()],
37
37
  parameters: {
38
38
  layout: 'fullscreen',
39
39
  },
40
40
  args: {
41
- axis: 'vertical',
41
+ orientation: 'vertical',
42
42
  getId: (item) => item.id,
43
43
  Tile: DefaultStackTile,
44
44
  // debug: true,
@@ -56,22 +56,24 @@ export const Default: Story = {
56
56
  const [DebugInfo, debugHandler] = useContainerDebug(props.debug);
57
57
  const [viewport, setViewport] = useState<HTMLElement | null>(null);
58
58
  return (
59
- <Mosaic.Root debug={props.debug} classNames='bs-full grid grid-rows-[min-content_1fr_min-content]'>
60
- <Toolbar.Root classNames='border-be border-separator'>
59
+ <Mosaic.Root debug={props.debug} classNames='h-full grid grid-rows-[min-content_1fr_min-content]'>
60
+ <Toolbar.Root classNames='border-b border-separator'>
61
61
  <div className='flex grow justify-center'>Items: {items.length}</div>
62
62
  </Toolbar.Root>
63
63
  <Mosaic.Container
64
64
  asChild
65
- axis='vertical'
65
+ orientation='vertical'
66
66
  autoScroll={viewport}
67
67
  eventHandler={{ id: 'test', canDrop: () => true }}
68
68
  debug={debugHandler}
69
69
  >
70
- <Mosaic.Viewport axis='vertical' padding viewportRef={setViewport}>
71
- <Mosaic.Stack {...props} items={items} />
72
- </Mosaic.Viewport>
70
+ <ScrollArea.Root orientation='vertical'>
71
+ <ScrollArea.Viewport classNames='p-2' ref={setViewport}>
72
+ <Mosaic.Stack {...props} items={items} />
73
+ </ScrollArea.Viewport>
74
+ </ScrollArea.Root>
73
75
  </Mosaic.Container>
74
- <DebugInfo classNames='border-bs border-separator' />
76
+ <DebugInfo classNames='border-t border-separator' />
75
77
  </Mosaic.Root>
76
78
  );
77
79
  },
@@ -91,22 +93,24 @@ export const Virtual: Story = {
91
93
  </Toolbar.Root>
92
94
  <Mosaic.Container
93
95
  asChild
94
- axis='vertical'
96
+ orientation='vertical'
95
97
  autoScroll={viewport}
96
98
  eventHandler={{ id: 'test', canDrop: () => true }}
97
99
  debug={debugHandler}
98
100
  >
99
- <Mosaic.Viewport axis='vertical' padding viewportRef={setViewport}>
100
- <Mosaic.VirtualStack
101
- {...props}
102
- items={items}
103
- getScrollElement={() => viewport}
104
- estimateSize={() => 40}
105
- onChange={(virtualizer) => {
106
- setInfo({ range: virtualizer.range });
107
- }}
108
- />
109
- </Mosaic.Viewport>
101
+ <ScrollArea.Root orientation='vertical'>
102
+ <ScrollArea.Viewport classNames='p-2' ref={setViewport}>
103
+ <Mosaic.VirtualStack
104
+ {...props}
105
+ items={items}
106
+ getScrollElement={() => viewport}
107
+ estimateSize={() => 40}
108
+ onChange={(virtualizer) => {
109
+ setInfo({ range: virtualizer.range });
110
+ }}
111
+ />
112
+ </ScrollArea.Viewport>
113
+ </ScrollArea.Root>
110
114
  </Mosaic.Container>
111
115
  <DebugInfo />
112
116
  </Mosaic.Root>