@dxos/react-ui-mosaic 0.6.4 → 0.6.5-staging.42fccfe

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.
@@ -12,6 +12,7 @@ import {
12
12
  type DragStartEvent,
13
13
  KeyboardSensor,
14
14
  type Modifier,
15
+ type DropAnimation,
15
16
  MouseSensor,
16
17
  pointerWithin,
17
18
  rectIntersection,
@@ -24,7 +25,12 @@ import pick from 'lodash.pick';
24
25
  import React, { createContext, type FC, type PropsWithChildren, useState } from 'react';
25
26
  import { createPortal } from 'react-dom';
26
27
 
27
- import { DEFAULT_TRANSITION, type MosaicContainerProps, type MosaicOperation } from './Container';
28
+ import {
29
+ DEFAULT_TRANSITION,
30
+ type DefaultMoveDetails,
31
+ type MosaicContainerProps,
32
+ type MosaicOperation,
33
+ } from './Container';
28
34
  import { Debug } from './Debug';
29
35
  import { DefaultComponent } from './DefaultComponent';
30
36
  import { type MosaicTileComponent } from './Tile';
@@ -33,12 +39,16 @@ import { Path } from './util';
33
39
 
34
40
  const DEFAULT_COMPONENT_ID = '__default';
35
41
 
42
+ export type MosaicDropAnimation = DropAnimation | null | undefined | void;
43
+
36
44
  export type MosaicContextType = {
37
45
  containers: Record<string, MosaicContainerProps<any> | undefined>;
38
46
  setContainer: (id: string, container?: MosaicContainerProps<any>) => void;
39
47
  activeItem: MosaicDraggedItem | undefined;
40
48
  overItem: MosaicDraggedItem | undefined;
41
49
  operation: MosaicOperation;
50
+ moveDetails?: Record<string, unknown>;
51
+ dropAnimation?: MosaicDropAnimation;
42
52
  };
43
53
 
44
54
  export const MosaicContext = createContext<MosaicContextType | undefined>(undefined);
@@ -51,6 +61,8 @@ export type MosaicRootProps = PropsWithChildren<{
51
61
  debug?: boolean;
52
62
  }>;
53
63
 
64
+ const defaultDropAnimation: MosaicDropAnimation = { duration: 0 };
65
+
54
66
  /**
55
67
  * Root context provider.
56
68
  */
@@ -73,6 +85,8 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
73
85
  const [activeItem, setActiveItem] = useState<MosaicDraggedItem>();
74
86
  const [overItem, setOverItem] = useState<MosaicDraggedItem>();
75
87
  const [operation, setOperation] = useState<MosaicOperation>('reject');
88
+ const [moveDetails, setMoveDetails] = useState<(DefaultMoveDetails & Record<string, any>) | undefined>();
89
+ const [dropAnimation, setDropAnimation] = useState<MosaicDropAnimation>(defaultDropAnimation);
76
90
 
77
91
  //
78
92
  // DndKit Defaults
@@ -136,20 +150,50 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
136
150
  setActiveItem(pick(event.active.data.current as MosaicDraggedItem, 'path', 'type', 'item', 'position'));
137
151
  };
138
152
 
139
- const handleDragMove = (event: DragMoveEvent) => {};
153
+ const handleDragMove = (event: DragMoveEvent) => {
154
+ const nextOverItem = pick(event.over?.data.current as MosaicDraggedItem, 'path', 'type', 'item', 'position');
155
+ const nextOverContainer =
156
+ nextOverItem && nextOverItem?.path ? containers[Path.first(nextOverItem.path)] : undefined;
157
+ if (!event.over || !nextOverItem || !nextOverContainer) {
158
+ setOperation('reject');
159
+ setOverItem(undefined);
160
+ return;
161
+ }
162
+
163
+ if (overItem !== nextOverItem) {
164
+ setOverItem(nextOverItem);
165
+ }
166
+
167
+ if (activeItem && nextOverItem && nextOverContainer?.onMove) {
168
+ const { operation: nextOperation, details: nextDetails } = nextOverContainer.onMove({
169
+ active: activeItem,
170
+ over: nextOverItem,
171
+ details: { delta: event.delta },
172
+ });
173
+ setOperation(nextOperation);
174
+ if (
175
+ // always set falsy symbols
176
+ !nextDetails ||
177
+ // always set if details become truthy
178
+ (!moveDetails && nextDetails) ||
179
+ // only update if details change
180
+ Object.entries(nextDetails).find(([key, value]) => !(key in moveDetails!) || moveDetails![key] !== value)
181
+ ) {
182
+ setMoveDetails(nextDetails);
183
+ }
184
+ }
185
+ };
140
186
 
141
187
  const handleDragOver = (event: DragOverEvent) => {
142
188
  const overItem = pick(event.over?.data.current as MosaicDraggedItem, 'path', 'type', 'item', 'position');
143
-
144
- // If the over item is the same as the active item, do nothing.
145
- // This happens when moving between containers where a placeholder of itself is rendered where it will be dropped.
146
- if (overItem?.item?.id === activeItem?.item.id) {
147
- setOverItem(overItem);
189
+ const overContainer = overItem && overItem?.path ? containers[Path.first(overItem.path)] : undefined;
190
+ // TODO(wittjosiah): This is over-prescriptive.
191
+ if (overContainer?.onMove) {
192
+ // The over container will have handled this in its move handler.
148
193
  return;
149
194
  }
150
195
 
151
196
  const activeContainer = activeItem && containers[Path.first(activeItem.path)];
152
- const overContainer = overItem?.path && containers[Path.first(overItem.path)];
153
197
  if (!event.over || !overItem || !overContainer || !activeItem || !activeContainer) {
154
198
  setOperation('reject');
155
199
  setOverItem(undefined);
@@ -157,10 +201,12 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
157
201
  }
158
202
 
159
203
  let operation: MosaicOperation = 'reject' as const;
160
- if (Path.parent(activeItem.path) === Path.parent(overItem.path)) {
161
- operation = 'rearrange' as const;
162
- } else if (overContainer.onOver) {
163
- operation = overContainer.onOver({ active: activeItem, over: overItem });
204
+ if (overContainer.onOver) {
205
+ operation = overContainer.onOver({
206
+ active: activeItem,
207
+ over: overItem,
208
+ details: moveDetails,
209
+ });
164
210
  }
165
211
 
166
212
  setOperation(operation);
@@ -176,22 +222,31 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
176
222
  const handleDragEnd = (event: DragEndEvent) => {
177
223
  const overContainer = overItem && containers[Path.first(overItem.path)];
178
224
 
179
- if (
180
- operation !== 'reject' &&
181
- activeItem &&
182
- overItem &&
183
- (activeItem.path !== overItem.path || activeItem.position !== overItem.position)
184
- ) {
225
+ if (operation !== 'reject' && activeItem && overItem) {
226
+ let nextDropAnimation: MosaicDropAnimation = defaultDropAnimation;
185
227
  const activeContainer = containers[Path.first(activeItem.path)];
186
228
  if (activeContainer) {
187
- activeContainer.onDrop?.({ operation, active: activeItem, over: overItem });
229
+ nextDropAnimation = activeContainer.onDrop?.({
230
+ operation,
231
+ active: activeItem,
232
+ over: overItem,
233
+ details: moveDetails,
234
+ });
188
235
 
189
236
  if (overContainer && overContainer !== activeContainer) {
190
- overContainer.onDrop?.({ operation, active: activeItem, over: overItem });
237
+ nextDropAnimation = overContainer.onDrop?.({
238
+ operation,
239
+ active: activeItem,
240
+ over: overItem,
241
+ details: moveDetails,
242
+ });
191
243
  }
192
244
  }
245
+ setDropAnimation(nextDropAnimation);
193
246
  }
194
247
 
248
+ Object.values(containers).forEach((container) => container?.onDragEnd?.(event));
249
+
195
250
  setTimeout(() => {
196
251
  setOperation('reject');
197
252
  setOverItem(undefined);
@@ -200,7 +255,17 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
200
255
  };
201
256
 
202
257
  return (
203
- <MosaicContext.Provider value={{ containers, setContainer: handleSetContainer, activeItem, overItem, operation }}>
258
+ <MosaicContext.Provider
259
+ value={{
260
+ containers,
261
+ setContainer: handleSetContainer,
262
+ activeItem,
263
+ overItem,
264
+ operation,
265
+ moveDetails,
266
+ dropAnimation,
267
+ }}
268
+ >
204
269
  <DndContext
205
270
  collisionDetection={collisionDetection}
206
271
  modifiers={[modifiers]}
@@ -35,6 +35,7 @@ export const MosaicSortableContext = ({ id, items = [], direction = 'vertical',
35
35
  activeItem &&
36
36
  activeItem.item.id === item.id &&
37
37
  overItem &&
38
+ overItem.path &&
38
39
  (Path.siblings(overItem.path, path) || Path.hasChild(overItem.path, path)) &&
39
40
  operation !== 'reject';
40
41
  return isPreview ? activeItem.path : path;
@@ -158,6 +158,8 @@ export const SortableTile = ({
158
158
  activeItem &&
159
159
  activeItem.item.id === item.id &&
160
160
  overItem &&
161
+ activeItem.path &&
162
+ overItem.path &&
161
163
  !Path.siblings(activeItem.path, overItem.path) &&
162
164
  (Path.siblings(overItem.path, path) || Path.hasChild(overItem.path, path)) &&
163
165
  operation !== 'reject';
@@ -173,7 +175,7 @@ export const SortableTile = ({
173
175
  if (isDragging) {
174
176
  if (operation === 'rearrange' || operation === 'reject') {
175
177
  active = 'rearrange';
176
- } else if (activeItem && Path.parent(activeItem.path) !== parentPath) {
178
+ } else if (activeItem && activeItem.path && Path.parent(activeItem.path) !== parentPath) {
177
179
  active = 'destination';
178
180
  } else {
179
181
  active = 'origin';
@@ -20,7 +20,7 @@ export const useItemsWithPreview = <T extends MosaicDataItem>({ path, items }: {
20
20
  // This allows items to be appended to the end of the sortable.
21
21
  overItem?.path === path && activeItem && Path.parent(activeItem.path) !== overItem.path
22
22
  ? path
23
- : overItem && Path.parent(overItem.path);
23
+ : overItem && !!overItem.path && Path.parent(overItem.path);
24
24
  const [lastOverParent, setLastOverParent] = useState(overParent);
25
25
 
26
26
  useEffect(() => {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- const SEPARATOR = '/';
5
+ const SEPARATOR = '~';
6
6
 
7
7
  export const Path = {
8
8
  create: (...args: string[]) => args.join(SEPARATOR),