@dxos/react-ui-mosaic 0.6.4 → 0.6.5-staging.097cf0c

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,45 @@ 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 (overItem !== nextOverItem) {
158
+ setOverItem(nextOverItem);
159
+ }
160
+ if (activeItem && nextOverItem && nextOverContainer?.onMove) {
161
+ const { operation: nextOperation, details: nextDetails } = nextOverContainer.onMove({
162
+ active: activeItem,
163
+ over: nextOverItem,
164
+ details: { delta: event.delta },
165
+ });
166
+ setOperation(nextOperation);
167
+ if (
168
+ // always set falsy symbols
169
+ !nextDetails ||
170
+ // always set if details become truthy
171
+ (!moveDetails && nextDetails) ||
172
+ // only update if details change
173
+ Object.entries(nextDetails).find(([key, value]) => !(key in moveDetails!) || moveDetails![key] !== value)
174
+ ) {
175
+ setMoveDetails(nextDetails);
176
+ }
177
+ }
178
+ };
140
179
 
141
180
  const handleDragOver = (event: DragOverEvent) => {
142
181
  const overItem = pick(event.over?.data.current as MosaicDraggedItem, 'path', 'type', 'item', 'position');
182
+ const overContainer = overItem && overItem?.path ? containers[Path.first(overItem.path)] : undefined;
143
183
 
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);
184
+ // TODO(wittjosiah): This is over-prescriptive.
185
+ if (overContainer?.onMove) {
186
+ // The over container will have handled this in its move handler.
148
187
  return;
149
188
  }
150
189
 
151
190
  const activeContainer = activeItem && containers[Path.first(activeItem.path)];
152
- const overContainer = overItem?.path && containers[Path.first(overItem.path)];
191
+
153
192
  if (!event.over || !overItem || !overContainer || !activeItem || !activeContainer) {
154
193
  setOperation('reject');
155
194
  setOverItem(undefined);
@@ -157,10 +196,12 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
157
196
  }
158
197
 
159
198
  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 });
199
+ if (overContainer.onOver) {
200
+ operation = overContainer.onOver({
201
+ active: activeItem,
202
+ over: overItem,
203
+ details: moveDetails,
204
+ });
164
205
  }
165
206
 
166
207
  setOperation(operation);
@@ -176,22 +217,31 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
176
217
  const handleDragEnd = (event: DragEndEvent) => {
177
218
  const overContainer = overItem && containers[Path.first(overItem.path)];
178
219
 
179
- if (
180
- operation !== 'reject' &&
181
- activeItem &&
182
- overItem &&
183
- (activeItem.path !== overItem.path || activeItem.position !== overItem.position)
184
- ) {
220
+ if (operation !== 'reject' && activeItem && overItem) {
221
+ let nextDropAnimation: MosaicDropAnimation = defaultDropAnimation;
185
222
  const activeContainer = containers[Path.first(activeItem.path)];
186
223
  if (activeContainer) {
187
- activeContainer.onDrop?.({ operation, active: activeItem, over: overItem });
224
+ nextDropAnimation = activeContainer.onDrop?.({
225
+ operation,
226
+ active: activeItem,
227
+ over: overItem,
228
+ details: moveDetails,
229
+ });
188
230
 
189
231
  if (overContainer && overContainer !== activeContainer) {
190
- overContainer.onDrop?.({ operation, active: activeItem, over: overItem });
232
+ nextDropAnimation = overContainer.onDrop?.({
233
+ operation,
234
+ active: activeItem,
235
+ over: overItem,
236
+ details: moveDetails,
237
+ });
191
238
  }
192
239
  }
240
+ setDropAnimation(nextDropAnimation);
193
241
  }
194
242
 
243
+ Object.values(containers).forEach((container) => container?.onDragEnd?.(event));
244
+
195
245
  setTimeout(() => {
196
246
  setOperation('reject');
197
247
  setOverItem(undefined);
@@ -200,7 +250,17 @@ export const MosaicRoot: FC<MosaicRootProps> = ({ Component = DefaultComponent,
200
250
  };
201
251
 
202
252
  return (
203
- <MosaicContext.Provider value={{ containers, setContainer: handleSetContainer, activeItem, overItem, operation }}>
253
+ <MosaicContext.Provider
254
+ value={{
255
+ containers,
256
+ setContainer: handleSetContainer,
257
+ activeItem,
258
+ overItem,
259
+ operation,
260
+ moveDetails,
261
+ dropAnimation,
262
+ }}
263
+ >
204
264
  <DndContext
205
265
  collisionDetection={collisionDetection}
206
266
  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),