react-native-layout-dnd 0.1.0-alpha.0
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.
- package/LICENSE +20 -0
- package/README.md +262 -0
- package/lib/module/adapters/coordinates.js +135 -0
- package/lib/module/adapters/coordinates.js.map +1 -0
- package/lib/module/adapters/scroll.js +81 -0
- package/lib/module/adapters/scroll.js.map +1 -0
- package/lib/module/adapters/virtualizedList.js +69 -0
- package/lib/module/adapters/virtualizedList.js.map +1 -0
- package/lib/module/components/DndProvider.js +1144 -0
- package/lib/module/components/DndProvider.js.map +1 -0
- package/lib/module/components/DragHandle.js +65 -0
- package/lib/module/components/DragHandle.js.map +1 -0
- package/lib/module/components/GridController.js +151 -0
- package/lib/module/components/GridController.js.map +1 -0
- package/lib/module/components/GridDragHandle.js +15 -0
- package/lib/module/components/GridDragHandle.js.map +1 -0
- package/lib/module/components/GridZoneView.js +59 -0
- package/lib/module/components/GridZoneView.js.map +1 -0
- package/lib/module/components/SortableGrid.js +305 -0
- package/lib/module/components/SortableGrid.js.map +1 -0
- package/lib/module/components/SortableList.js +714 -0
- package/lib/module/components/SortableList.js.map +1 -0
- package/lib/module/components/dndTypes.js +4 -0
- package/lib/module/components/dndTypes.js.map +1 -0
- package/lib/module/components/gridTypes.js +4 -0
- package/lib/module/components/gridTypes.js.map +1 -0
- package/lib/module/components/handleViewProps.js +13 -0
- package/lib/module/components/handleViewProps.js.map +1 -0
- package/lib/module/contracts.js +4 -0
- package/lib/module/contracts.js.map +1 -0
- package/lib/module/controller/approval.js +98 -0
- package/lib/module/controller/approval.js.map +1 -0
- package/lib/module/controller/coordinatorBase.js +93 -0
- package/lib/module/controller/coordinatorBase.js.map +1 -0
- package/lib/module/controller/dndSession.js +621 -0
- package/lib/module/controller/dndSession.js.map +1 -0
- package/lib/module/controller/dndState.js +103 -0
- package/lib/module/controller/dndState.js.map +1 -0
- package/lib/module/controller/gridAdapter.js +191 -0
- package/lib/module/controller/gridAdapter.js.map +1 -0
- package/lib/module/controller/gridState.js +101 -0
- package/lib/module/controller/gridState.js.map +1 -0
- package/lib/module/controller/locations.js +48 -0
- package/lib/module/controller/locations.js.map +1 -0
- package/lib/module/controller/previewPath.js +73 -0
- package/lib/module/controller/previewPath.js.map +1 -0
- package/lib/module/controller/sessionCore.js +222 -0
- package/lib/module/controller/sessionCore.js.map +1 -0
- package/lib/module/controller/useDndState.js +9 -0
- package/lib/module/controller/useDndState.js.map +1 -0
- package/lib/module/controller/useGridState.js +9 -0
- package/lib/module/controller/useGridState.js.map +1 -0
- package/lib/module/controller/useStoreState.js +15 -0
- package/lib/module/controller/useStoreState.js.map +1 -0
- package/lib/module/defaults.js +7 -0
- package/lib/module/defaults.js.map +1 -0
- package/lib/module/diagnostics.js +16 -0
- package/lib/module/diagnostics.js.map +1 -0
- package/lib/module/engine/gridPlacements.js +54 -0
- package/lib/module/engine/gridPlacements.js.map +1 -0
- package/lib/module/engine/index.js +19 -0
- package/lib/module/engine/index.js.map +1 -0
- package/lib/module/engine/layout.js +36 -0
- package/lib/module/engine/layout.js.map +1 -0
- package/lib/module/engine/layoutMove.js +326 -0
- package/lib/module/engine/layoutMove.js.map +1 -0
- package/lib/module/engine/layoutState.js +344 -0
- package/lib/module/engine/layoutState.js.map +1 -0
- package/lib/module/engine/list.js +172 -0
- package/lib/module/engine/list.js.map +1 -0
- package/lib/module/engine/listIndex.js +125 -0
- package/lib/module/engine/listIndex.js.map +1 -0
- package/lib/module/engine/move.js +216 -0
- package/lib/module/engine/move.js.map +1 -0
- package/lib/module/engine/movementPolicy.js +77 -0
- package/lib/module/engine/movementPolicy.js.map +1 -0
- package/lib/module/engine/orderedTarget.js +71 -0
- package/lib/module/engine/orderedTarget.js.map +1 -0
- package/lib/module/engine/pack.js +30 -0
- package/lib/module/engine/pack.js.map +1 -0
- package/lib/module/engine/rectangles.js +12 -0
- package/lib/module/engine/rectangles.js.map +1 -0
- package/lib/module/engine/sameLayout.js +29 -0
- package/lib/module/engine/sameLayout.js.map +1 -0
- package/lib/module/engine/search.js +159 -0
- package/lib/module/engine/search.js.map +1 -0
- package/lib/module/engine/spatial.js +27 -0
- package/lib/module/engine/spatial.js.map +1 -0
- package/lib/module/engine/spatialAppRotation.js +39 -0
- package/lib/module/engine/spatialAppRotation.js.map +1 -0
- package/lib/module/engine/spatialExchange.js +164 -0
- package/lib/module/engine/spatialExchange.js.map +1 -0
- package/lib/module/engine/spatialInsertion.js +135 -0
- package/lib/module/engine/spatialInsertion.js.map +1 -0
- package/lib/module/engine/spatialPolicy.js +41 -0
- package/lib/module/engine/spatialPolicy.js.map +1 -0
- package/lib/module/engine/spatialRotation.js +48 -0
- package/lib/module/engine/spatialRotation.js.map +1 -0
- package/lib/module/engine/validation.js +205 -0
- package/lib/module/engine/validation.js.map +1 -0
- package/lib/module/gesture/DndGestureGroup.js +268 -0
- package/lib/module/gesture/DndGestureGroup.js.map +1 -0
- package/lib/module/gesture/DndGestureHost.js +32 -0
- package/lib/module/gesture/DndGestureHost.js.map +1 -0
- package/lib/module/gesture/gestureConfig.js +81 -0
- package/lib/module/gesture/gestureConfig.js.map +1 -0
- package/lib/module/gesture/gestureGrouping.js +122 -0
- package/lib/module/gesture/gestureGrouping.js.map +1 -0
- package/lib/module/gesture/gestureHost.js +106 -0
- package/lib/module/gesture/gestureHost.js.map +1 -0
- package/lib/module/index.js +23 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/pointer/dndTarget.js +101 -0
- package/lib/module/pointer/dndTarget.js.map +1 -0
- package/lib/module/pointer/geometry.js +363 -0
- package/lib/module/pointer/geometry.js.map +1 -0
- package/lib/module/pointer/layoutTarget.js +307 -0
- package/lib/module/pointer/layoutTarget.js.map +1 -0
- package/lib/module/pointer/listCellMotion.js +99 -0
- package/lib/module/pointer/listCellMotion.js.map +1 -0
- package/lib/module/pointer/listTarget.js +42 -0
- package/lib/module/pointer/listTarget.js.map +1 -0
- package/lib/module/pointer/motion.js +37 -0
- package/lib/module/pointer/motion.js.map +1 -0
- package/lib/module/runtime/dndPresentation.js +81 -0
- package/lib/module/runtime/dndPresentation.js.map +1 -0
- package/lib/module/runtime/dndRenderStore.js +129 -0
- package/lib/module/runtime/dndRenderStore.js.map +1 -0
- package/lib/module/runtime/dndRuntime.js +135 -0
- package/lib/module/runtime/dndRuntime.js.map +1 -0
- package/lib/module/runtime/dndSettlement.js +77 -0
- package/lib/module/runtime/dndSettlement.js.map +1 -0
- package/lib/module/runtime/gridRuntime.js +37 -0
- package/lib/module/runtime/gridRuntime.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/adapters/coordinates.d.ts +9 -0
- package/lib/typescript/src/adapters/coordinates.d.ts.map +1 -0
- package/lib/typescript/src/adapters/scroll.d.ts +49 -0
- package/lib/typescript/src/adapters/scroll.d.ts.map +1 -0
- package/lib/typescript/src/adapters/virtualizedList.d.ts +22 -0
- package/lib/typescript/src/adapters/virtualizedList.d.ts.map +1 -0
- package/lib/typescript/src/components/DndProvider.d.ts +4 -0
- package/lib/typescript/src/components/DndProvider.d.ts.map +1 -0
- package/lib/typescript/src/components/DragHandle.d.ts +4 -0
- package/lib/typescript/src/components/DragHandle.d.ts.map +1 -0
- package/lib/typescript/src/components/GridController.d.ts +21 -0
- package/lib/typescript/src/components/GridController.d.ts.map +1 -0
- package/lib/typescript/src/components/GridDragHandle.d.ts +8 -0
- package/lib/typescript/src/components/GridDragHandle.d.ts.map +1 -0
- package/lib/typescript/src/components/GridZoneView.d.ts +8 -0
- package/lib/typescript/src/components/GridZoneView.d.ts.map +1 -0
- package/lib/typescript/src/components/SortableGrid.d.ts +4 -0
- package/lib/typescript/src/components/SortableGrid.d.ts.map +1 -0
- package/lib/typescript/src/components/SortableList.d.ts +4 -0
- package/lib/typescript/src/components/SortableList.d.ts.map +1 -0
- package/lib/typescript/src/components/dndTypes.d.ts +94 -0
- package/lib/typescript/src/components/dndTypes.d.ts.map +1 -0
- package/lib/typescript/src/components/gridTypes.d.ts +84 -0
- package/lib/typescript/src/components/gridTypes.d.ts.map +1 -0
- package/lib/typescript/src/components/handleViewProps.d.ts +6 -0
- package/lib/typescript/src/components/handleViewProps.d.ts.map +1 -0
- package/lib/typescript/src/contracts.d.ts +145 -0
- package/lib/typescript/src/contracts.d.ts.map +1 -0
- package/lib/typescript/src/controller/approval.d.ts +66 -0
- package/lib/typescript/src/controller/approval.d.ts.map +1 -0
- package/lib/typescript/src/controller/coordinatorBase.d.ts +37 -0
- package/lib/typescript/src/controller/coordinatorBase.d.ts.map +1 -0
- package/lib/typescript/src/controller/dndSession.d.ts +152 -0
- package/lib/typescript/src/controller/dndSession.d.ts.map +1 -0
- package/lib/typescript/src/controller/dndState.d.ts +27 -0
- package/lib/typescript/src/controller/dndState.d.ts.map +1 -0
- package/lib/typescript/src/controller/gridAdapter.d.ts +44 -0
- package/lib/typescript/src/controller/gridAdapter.d.ts.map +1 -0
- package/lib/typescript/src/controller/gridState.d.ts +18 -0
- package/lib/typescript/src/controller/gridState.d.ts.map +1 -0
- package/lib/typescript/src/controller/locations.d.ts +9 -0
- package/lib/typescript/src/controller/locations.d.ts.map +1 -0
- package/lib/typescript/src/controller/previewPath.d.ts +49 -0
- package/lib/typescript/src/controller/previewPath.d.ts.map +1 -0
- package/lib/typescript/src/controller/sessionCore.d.ts +106 -0
- package/lib/typescript/src/controller/sessionCore.d.ts.map +1 -0
- package/lib/typescript/src/controller/useDndState.d.ts +8 -0
- package/lib/typescript/src/controller/useDndState.d.ts.map +1 -0
- package/lib/typescript/src/controller/useGridState.d.ts +8 -0
- package/lib/typescript/src/controller/useGridState.d.ts.map +1 -0
- package/lib/typescript/src/controller/useStoreState.d.ts +16 -0
- package/lib/typescript/src/controller/useStoreState.d.ts.map +1 -0
- package/lib/typescript/src/defaults.d.ts +5 -0
- package/lib/typescript/src/defaults.d.ts.map +1 -0
- package/lib/typescript/src/diagnostics.d.ts +9 -0
- package/lib/typescript/src/diagnostics.d.ts.map +1 -0
- package/lib/typescript/src/engine/gridPlacements.d.ts +22 -0
- package/lib/typescript/src/engine/gridPlacements.d.ts.map +1 -0
- package/lib/typescript/src/engine/index.d.ts +28 -0
- package/lib/typescript/src/engine/index.d.ts.map +1 -0
- package/lib/typescript/src/engine/layout.d.ts +4 -0
- package/lib/typescript/src/engine/layout.d.ts.map +1 -0
- package/lib/typescript/src/engine/layoutMove.d.ts +46 -0
- package/lib/typescript/src/engine/layoutMove.d.ts.map +1 -0
- package/lib/typescript/src/engine/layoutState.d.ts +22 -0
- package/lib/typescript/src/engine/layoutState.d.ts.map +1 -0
- package/lib/typescript/src/engine/list.d.ts +68 -0
- package/lib/typescript/src/engine/list.d.ts.map +1 -0
- package/lib/typescript/src/engine/listIndex.d.ts +32 -0
- package/lib/typescript/src/engine/listIndex.d.ts.map +1 -0
- package/lib/typescript/src/engine/move.d.ts +10 -0
- package/lib/typescript/src/engine/move.d.ts.map +1 -0
- package/lib/typescript/src/engine/movementPolicy.d.ts +34 -0
- package/lib/typescript/src/engine/movementPolicy.d.ts.map +1 -0
- package/lib/typescript/src/engine/orderedTarget.d.ts +32 -0
- package/lib/typescript/src/engine/orderedTarget.d.ts.map +1 -0
- package/lib/typescript/src/engine/pack.d.ts +3 -0
- package/lib/typescript/src/engine/pack.d.ts.map +1 -0
- package/lib/typescript/src/engine/rectangles.d.ts +9 -0
- package/lib/typescript/src/engine/rectangles.d.ts.map +1 -0
- package/lib/typescript/src/engine/sameLayout.d.ts +4 -0
- package/lib/typescript/src/engine/sameLayout.d.ts.map +1 -0
- package/lib/typescript/src/engine/search.d.ts +28 -0
- package/lib/typescript/src/engine/search.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatial.d.ts +16 -0
- package/lib/typescript/src/engine/spatial.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialAppRotation.d.ts +4 -0
- package/lib/typescript/src/engine/spatialAppRotation.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialExchange.d.ts +5 -0
- package/lib/typescript/src/engine/spatialExchange.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialInsertion.d.ts +5 -0
- package/lib/typescript/src/engine/spatialInsertion.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialPolicy.d.ts +9 -0
- package/lib/typescript/src/engine/spatialPolicy.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialRotation.d.ts +8 -0
- package/lib/typescript/src/engine/spatialRotation.d.ts.map +1 -0
- package/lib/typescript/src/engine/validation.d.ts +9 -0
- package/lib/typescript/src/engine/validation.d.ts.map +1 -0
- package/lib/typescript/src/gesture/DndGestureGroup.d.ts +33 -0
- package/lib/typescript/src/gesture/DndGestureGroup.d.ts.map +1 -0
- package/lib/typescript/src/gesture/DndGestureHost.d.ts +12 -0
- package/lib/typescript/src/gesture/DndGestureHost.d.ts.map +1 -0
- package/lib/typescript/src/gesture/gestureConfig.d.ts +15 -0
- package/lib/typescript/src/gesture/gestureConfig.d.ts.map +1 -0
- package/lib/typescript/src/gesture/gestureGrouping.d.ts +66 -0
- package/lib/typescript/src/gesture/gestureGrouping.d.ts.map +1 -0
- package/lib/typescript/src/gesture/gestureHost.d.ts +23 -0
- package/lib/typescript/src/gesture/gestureHost.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +38 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/pointer/dndTarget.d.ts +14 -0
- package/lib/typescript/src/pointer/dndTarget.d.ts.map +1 -0
- package/lib/typescript/src/pointer/geometry.d.ts +28 -0
- package/lib/typescript/src/pointer/geometry.d.ts.map +1 -0
- package/lib/typescript/src/pointer/layoutTarget.d.ts +43 -0
- package/lib/typescript/src/pointer/layoutTarget.d.ts.map +1 -0
- package/lib/typescript/src/pointer/listCellMotion.d.ts +43 -0
- package/lib/typescript/src/pointer/listCellMotion.d.ts.map +1 -0
- package/lib/typescript/src/pointer/listTarget.d.ts +7 -0
- package/lib/typescript/src/pointer/listTarget.d.ts.map +1 -0
- package/lib/typescript/src/pointer/motion.d.ts +20 -0
- package/lib/typescript/src/pointer/motion.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndPresentation.d.ts +37 -0
- package/lib/typescript/src/runtime/dndPresentation.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndRenderStore.d.ts +46 -0
- package/lib/typescript/src/runtime/dndRenderStore.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndRuntime.d.ts +138 -0
- package/lib/typescript/src/runtime/dndRuntime.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndSettlement.d.ts +18 -0
- package/lib/typescript/src/runtime/dndSettlement.d.ts.map +1 -0
- package/lib/typescript/src/runtime/gridRuntime.d.ts +54 -0
- package/lib/typescript/src/runtime/gridRuntime.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +180 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +136 -0
- package/src/adapters/coordinates.ts +181 -0
- package/src/adapters/scroll.ts +147 -0
- package/src/adapters/virtualizedList.ts +112 -0
- package/src/components/DndProvider.tsx +1836 -0
- package/src/components/DragHandle.tsx +89 -0
- package/src/components/GridController.tsx +213 -0
- package/src/components/GridDragHandle.tsx +11 -0
- package/src/components/GridZoneView.tsx +57 -0
- package/src/components/SortableGrid.tsx +449 -0
- package/src/components/SortableList.tsx +1028 -0
- package/src/components/dndTypes.ts +102 -0
- package/src/components/gridTypes.ts +90 -0
- package/src/components/handleViewProps.ts +35 -0
- package/src/contracts.ts +149 -0
- package/src/controller/approval.ts +179 -0
- package/src/controller/coordinatorBase.ts +118 -0
- package/src/controller/dndSession.ts +1058 -0
- package/src/controller/dndState.ts +198 -0
- package/src/controller/gridAdapter.ts +268 -0
- package/src/controller/gridState.ts +187 -0
- package/src/controller/locations.ts +67 -0
- package/src/controller/previewPath.ts +90 -0
- package/src/controller/sessionCore.ts +390 -0
- package/src/controller/useDndState.ts +26 -0
- package/src/controller/useGridState.ts +22 -0
- package/src/controller/useStoreState.ts +27 -0
- package/src/defaults.ts +4 -0
- package/src/diagnostics.ts +23 -0
- package/src/engine/gridPlacements.ts +85 -0
- package/src/engine/index.ts +99 -0
- package/src/engine/layout.ts +29 -0
- package/src/engine/layoutMove.ts +401 -0
- package/src/engine/layoutState.ts +490 -0
- package/src/engine/list.ts +307 -0
- package/src/engine/listIndex.ts +174 -0
- package/src/engine/move.ts +274 -0
- package/src/engine/movementPolicy.ts +151 -0
- package/src/engine/orderedTarget.ts +131 -0
- package/src/engine/pack.ts +24 -0
- package/src/engine/rectangles.ts +18 -0
- package/src/engine/sameLayout.ts +50 -0
- package/src/engine/search.ts +174 -0
- package/src/engine/spatial.ts +36 -0
- package/src/engine/spatialAppRotation.ts +53 -0
- package/src/engine/spatialExchange.ts +212 -0
- package/src/engine/spatialInsertion.ts +157 -0
- package/src/engine/spatialPolicy.ts +48 -0
- package/src/engine/spatialRotation.ts +56 -0
- package/src/engine/validation.ts +358 -0
- package/src/gesture/DndGestureGroup.tsx +357 -0
- package/src/gesture/DndGestureHost.tsx +50 -0
- package/src/gesture/gestureConfig.ts +123 -0
- package/src/gesture/gestureGrouping.ts +210 -0
- package/src/gesture/gestureHost.tsx +163 -0
- package/src/index.ts +162 -0
- package/src/pointer/dndTarget.ts +213 -0
- package/src/pointer/geometry.ts +584 -0
- package/src/pointer/layoutTarget.ts +515 -0
- package/src/pointer/listCellMotion.ts +174 -0
- package/src/pointer/listTarget.ts +56 -0
- package/src/pointer/motion.ts +66 -0
- package/src/runtime/dndPresentation.ts +113 -0
- package/src/runtime/dndRenderStore.ts +246 -0
- package/src/runtime/dndRuntime.ts +311 -0
- package/src/runtime/dndSettlement.ts +117 -0
- package/src/runtime/gridRuntime.ts +73 -0
- package/src/types.ts +189 -0
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DndItem,
|
|
3
|
+
GridLayoutZone,
|
|
4
|
+
LayoutLocation,
|
|
5
|
+
LayoutState,
|
|
6
|
+
LayoutValidationIssue,
|
|
7
|
+
} from '../contracts';
|
|
8
|
+
import { samePlacement } from '../engine/gridPlacements';
|
|
9
|
+
import {
|
|
10
|
+
computeValidatedLayoutMove,
|
|
11
|
+
type GridItemLayout,
|
|
12
|
+
type LayoutMoveResult,
|
|
13
|
+
} from '../engine/layoutMove';
|
|
14
|
+
import { validateLayoutState } from '../engine/layoutState';
|
|
15
|
+
import { isCellSpan, isPositiveInteger, isRecord } from '../engine/validation';
|
|
16
|
+
import { DEFAULT_SEARCH_BUDGET } from '../defaults';
|
|
17
|
+
import {
|
|
18
|
+
resolveGridMovementPolicy,
|
|
19
|
+
validateGridMovementPolicy,
|
|
20
|
+
type GridMovementPolicy,
|
|
21
|
+
type ResolvedGridMovementPolicy,
|
|
22
|
+
} from '../engine/movementPolicy';
|
|
23
|
+
import type {
|
|
24
|
+
CancelReason,
|
|
25
|
+
DragStartEvent,
|
|
26
|
+
DropDecision,
|
|
27
|
+
GridDiagnosticEvent,
|
|
28
|
+
} from '../types';
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
respondToValidatedDndProposal,
|
|
32
|
+
type DndProposal,
|
|
33
|
+
type DndValue,
|
|
34
|
+
} from './dndState';
|
|
35
|
+
import { now, reportCallbackError as reportFailure } from './coordinatorBase';
|
|
36
|
+
import { layoutLocation, sameLayoutLocation } from './locations';
|
|
37
|
+
import { PreviewPath } from './previewPath';
|
|
38
|
+
import {
|
|
39
|
+
type CandidateResult,
|
|
40
|
+
SessionCore,
|
|
41
|
+
validatedDecision,
|
|
42
|
+
} from './sessionCore';
|
|
43
|
+
|
|
44
|
+
export type DndCancelReason = CancelReason;
|
|
45
|
+
export type DndDropFailureReason =
|
|
46
|
+
| 'outside-zones'
|
|
47
|
+
| 'invalid-target'
|
|
48
|
+
| 'missing-grid-item-layout'
|
|
49
|
+
| 'invalid-grid-item-layout'
|
|
50
|
+
| 'unresolvable'
|
|
51
|
+
| 'search-budget'
|
|
52
|
+
| 'policy-rejected';
|
|
53
|
+
export type GridItemLayoutResolverArgs<T> = {
|
|
54
|
+
item: DndItem<T>;
|
|
55
|
+
from: LayoutLocation;
|
|
56
|
+
target: GridLayoutZone;
|
|
57
|
+
value: LayoutState<T>;
|
|
58
|
+
};
|
|
59
|
+
export type DndDragEndEvent<T> = DragStartEvent & { error?: unknown } & (
|
|
60
|
+
| { outcome: 'unchanged' }
|
|
61
|
+
| {
|
|
62
|
+
outcome: 'cancelled';
|
|
63
|
+
reason: DndCancelReason;
|
|
64
|
+
targetZoneId?: string;
|
|
65
|
+
policyReason?: string;
|
|
66
|
+
}
|
|
67
|
+
| {
|
|
68
|
+
outcome: 'proposed';
|
|
69
|
+
proposal: DndProposal<T>;
|
|
70
|
+
targetZoneId: string;
|
|
71
|
+
response: 'accepted' | 'rejected' | 'expired' | 'mismatch';
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
outcome: 'proposed';
|
|
75
|
+
proposal: DndProposal<T>;
|
|
76
|
+
targetZoneId: string;
|
|
77
|
+
response: 'interrupted';
|
|
78
|
+
reason: DndCancelReason | 'response-missing' | 'change-handler-error';
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export type DndSessionSnapshot<T> = {
|
|
83
|
+
value: DndValue<T> | null;
|
|
84
|
+
displayValue: DndValue<T> | null;
|
|
85
|
+
candidate: LayoutState<T> | null;
|
|
86
|
+
phase: 'idle' | 'dragging' | 'awaiting-response';
|
|
87
|
+
sessionId: string | null;
|
|
88
|
+
itemId: string | null;
|
|
89
|
+
sourceZoneId: string | null;
|
|
90
|
+
targetZoneId: string | null;
|
|
91
|
+
target: LayoutLocation | null;
|
|
92
|
+
seq: number;
|
|
93
|
+
validity: 'pending' | 'valid' | 'invalid';
|
|
94
|
+
disabled: boolean;
|
|
95
|
+
/** Span and placement captured once for each grid target at activation. */
|
|
96
|
+
gridItemLayouts: ReadonlyMap<string, GridItemLayout>;
|
|
97
|
+
movementPolicy: ResolvedGridMovementPolicy;
|
|
98
|
+
failureReason: DndDropFailureReason | null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type DndSessionOptions<T> = {
|
|
102
|
+
value: DndValue<T>;
|
|
103
|
+
onChange(next: DndValue<T>, proposal: DndProposal<T>): void;
|
|
104
|
+
canDrop?(proposal: DndProposal<T>): DropDecision;
|
|
105
|
+
onDragStart?(event: DragStartEvent): void;
|
|
106
|
+
onDragEnd?(event: DndDragEndEvent<T>): void;
|
|
107
|
+
onValidationError?(issues: LayoutValidationIssue[]): void;
|
|
108
|
+
/** Optional JS-only observation. Exceptions are ignored; omit to disable timing. */
|
|
109
|
+
onDiagnostic?(event: GridDiagnosticEvent): void;
|
|
110
|
+
responseTimeoutMs?: number;
|
|
111
|
+
disabled?: boolean;
|
|
112
|
+
movementPolicy?: GridMovementPolicy;
|
|
113
|
+
searchBudget?: number;
|
|
114
|
+
/** Required for list-to-grid; returning null disables that target for this session. */
|
|
115
|
+
getGridItemLayout?(
|
|
116
|
+
args: GridItemLayoutResolverArgs<T>,
|
|
117
|
+
): GridItemLayout | null;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
type ActiveSession<T> = DragStartEvent & {
|
|
121
|
+
baseRevision: number;
|
|
122
|
+
seq: number;
|
|
123
|
+
target: LayoutLocation | null;
|
|
124
|
+
/** Same-zone grid previews the next candidate builds on. */
|
|
125
|
+
path: PreviewPath<LayoutLocation, LayoutState<T>>;
|
|
126
|
+
proposal?: DndProposal<T>;
|
|
127
|
+
proposalKey?: string;
|
|
128
|
+
deadline?: number;
|
|
129
|
+
gridItemLayouts: Map<string, GridItemLayout>;
|
|
130
|
+
gridItemLayoutErrors: Map<
|
|
131
|
+
string,
|
|
132
|
+
'missing-grid-item-layout' | 'invalid-grid-item-layout'
|
|
133
|
+
>;
|
|
134
|
+
movementPolicy: ResolvedGridMovementPolicy;
|
|
135
|
+
searchBudget: number;
|
|
136
|
+
responseTimeoutMs: number;
|
|
137
|
+
settingsKey: string;
|
|
138
|
+
getGridItemLayout: DndSessionOptions<T>['getGridItemLayout'];
|
|
139
|
+
resolving: boolean;
|
|
140
|
+
previewSeq: number | null;
|
|
141
|
+
previewInputVersion: number;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
let instanceCounter = 0;
|
|
145
|
+
|
|
146
|
+
/** Capture structure by value so an in-place same-revision edit still invalidates a drag. */
|
|
147
|
+
function layoutKey<T>(value: LayoutState<T>): string {
|
|
148
|
+
return JSON.stringify([
|
|
149
|
+
value.revision,
|
|
150
|
+
value.items.map(item => item.id).sort(),
|
|
151
|
+
value.zones
|
|
152
|
+
.map(zone =>
|
|
153
|
+
zone.kind === 'list'
|
|
154
|
+
? [zone.id, zone.kind, zone.orientation, [...zone.itemIds]]
|
|
155
|
+
: [
|
|
156
|
+
zone.id,
|
|
157
|
+
zone.kind,
|
|
158
|
+
zone.rows,
|
|
159
|
+
zone.columns,
|
|
160
|
+
zone.placements
|
|
161
|
+
.map(item => [
|
|
162
|
+
item.itemId,
|
|
163
|
+
item.position.row,
|
|
164
|
+
item.position.col,
|
|
165
|
+
item.span.rows,
|
|
166
|
+
item.span.cols,
|
|
167
|
+
item.placement,
|
|
168
|
+
])
|
|
169
|
+
.sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
.sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function sameTarget(
|
|
177
|
+
first: LayoutLocation | null,
|
|
178
|
+
second: LayoutLocation | null,
|
|
179
|
+
): boolean {
|
|
180
|
+
if (!first || !second) return first === second;
|
|
181
|
+
if (first.zoneId !== second.zoneId) return false;
|
|
182
|
+
return first.kind === 'list' && second.kind === 'list'
|
|
183
|
+
? first.index === second.index
|
|
184
|
+
: first.kind === 'grid' &&
|
|
185
|
+
second.kind === 'grid' &&
|
|
186
|
+
first.position?.row === second.position?.row &&
|
|
187
|
+
first.position?.col === second.position?.col;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
type PreviewStructure =
|
|
191
|
+
| 'state'
|
|
192
|
+
| 'zones'
|
|
193
|
+
| 'zone'
|
|
194
|
+
| 'placements'
|
|
195
|
+
| 'placement'
|
|
196
|
+
| 'coordinates'
|
|
197
|
+
| 'itemIds'
|
|
198
|
+
| 'target';
|
|
199
|
+
|
|
200
|
+
/** Compare generated layout containers without reading or copying opaque data. */
|
|
201
|
+
function samePreviewStructure(
|
|
202
|
+
first: unknown,
|
|
203
|
+
second: unknown,
|
|
204
|
+
structure: PreviewStructure,
|
|
205
|
+
): boolean {
|
|
206
|
+
if (Object.is(first, second)) return true;
|
|
207
|
+
if (
|
|
208
|
+
first === null ||
|
|
209
|
+
second === null ||
|
|
210
|
+
typeof first !== 'object' ||
|
|
211
|
+
typeof second !== 'object' ||
|
|
212
|
+
Object.getPrototypeOf(first) !== Object.getPrototypeOf(second)
|
|
213
|
+
)
|
|
214
|
+
return false;
|
|
215
|
+
const keys = Reflect.ownKeys(first);
|
|
216
|
+
const otherKeys = Reflect.ownKeys(second);
|
|
217
|
+
return (
|
|
218
|
+
keys.length === otherKeys.length &&
|
|
219
|
+
keys.every((key, index) => {
|
|
220
|
+
if (key !== otherKeys[index]) return false;
|
|
221
|
+
const a = Object.getOwnPropertyDescriptor(first, key)!;
|
|
222
|
+
const b = Object.getOwnPropertyDescriptor(second, key)!;
|
|
223
|
+
if (!('value' in a) || !('value' in b) || a.enumerable !== b.enumerable)
|
|
224
|
+
return false;
|
|
225
|
+
let child: PreviewStructure | undefined;
|
|
226
|
+
if (structure === 'state' && key === 'zones') child = 'zones';
|
|
227
|
+
else if (structure === 'zone' && key === 'itemIds') child = 'itemIds';
|
|
228
|
+
else if (structure === 'zone' && key === 'placements')
|
|
229
|
+
child = 'placements';
|
|
230
|
+
else if (
|
|
231
|
+
(structure === 'placement' && (key === 'position' || key === 'span')) ||
|
|
232
|
+
(structure === 'target' && key === 'position')
|
|
233
|
+
)
|
|
234
|
+
child = 'coordinates';
|
|
235
|
+
else if (
|
|
236
|
+
(structure === 'zones' || structure === 'placements') &&
|
|
237
|
+
typeof key === 'string' &&
|
|
238
|
+
/^(0|[1-9]\d*)$/.test(key)
|
|
239
|
+
)
|
|
240
|
+
child = structure === 'zones' ? 'zone' : 'placement';
|
|
241
|
+
// Item arrays and extra metadata retain their exact identity. In
|
|
242
|
+
// particular, equality never traverses caller-owned item.data objects.
|
|
243
|
+
return child
|
|
244
|
+
? samePreviewStructure(a.value, b.value, child)
|
|
245
|
+
: Object.is(a.value, b.value);
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function sameVisibleSnapshot<T>(
|
|
251
|
+
first: DndSessionSnapshot<T>,
|
|
252
|
+
second: DndSessionSnapshot<T>,
|
|
253
|
+
): boolean {
|
|
254
|
+
return (Object.keys(first) as Array<keyof DndSessionSnapshot<T>>).every(
|
|
255
|
+
key =>
|
|
256
|
+
key === 'seq' ||
|
|
257
|
+
(key === 'candidate' || key === 'displayValue'
|
|
258
|
+
? samePreviewStructure(first[key], second[key], 'state')
|
|
259
|
+
: key === 'target'
|
|
260
|
+
? samePreviewStructure(first[key], second[key], 'target')
|
|
261
|
+
: Object.is(first[key], second[key])),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function settingsKey<T>(options: DndSessionOptions<T>): string {
|
|
266
|
+
return JSON.stringify([
|
|
267
|
+
options.searchBudget ?? DEFAULT_SEARCH_BUDGET,
|
|
268
|
+
options.responseTimeoutMs ?? 2000,
|
|
269
|
+
resolveGridMovementPolicy(options.movementPolicy),
|
|
270
|
+
]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function copyMovementPolicy(
|
|
274
|
+
policy?: GridMovementPolicy,
|
|
275
|
+
): ResolvedGridMovementPolicy {
|
|
276
|
+
const resolved = resolveGridMovementPolicy(policy);
|
|
277
|
+
return Object.freeze({
|
|
278
|
+
...resolved,
|
|
279
|
+
candidateOrder: Object.freeze([...resolved.candidateOrder]),
|
|
280
|
+
pointer: Object.freeze({ ...resolved.pointer }),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function copyGridItemLayout(value: unknown): GridItemLayout | null {
|
|
285
|
+
if (
|
|
286
|
+
!isRecord(value) ||
|
|
287
|
+
!isCellSpan(value.span) ||
|
|
288
|
+
(value.placement !== undefined &&
|
|
289
|
+
value.placement !== 'insert' &&
|
|
290
|
+
value.placement !== 'exchange')
|
|
291
|
+
)
|
|
292
|
+
return null;
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
span: Object.freeze({ rows: value.span.rows, cols: value.span.cols }),
|
|
295
|
+
...(value.placement === undefined ? {} : { placement: value.placement }),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function checkOptions<T>(options: DndSessionOptions<T>) {
|
|
300
|
+
const checked = validateLayoutState(options.value);
|
|
301
|
+
const issues: LayoutValidationIssue[] = checked.valid
|
|
302
|
+
? []
|
|
303
|
+
: [...checked.issues];
|
|
304
|
+
const policy = validateGridMovementPolicy(options.movementPolicy);
|
|
305
|
+
if (!policy.valid) issues.push(...policy.issues);
|
|
306
|
+
const budgetValid = isPositiveInteger(
|
|
307
|
+
options.searchBudget === undefined
|
|
308
|
+
? DEFAULT_SEARCH_BUDGET
|
|
309
|
+
: options.searchBudget,
|
|
310
|
+
);
|
|
311
|
+
const timeoutValid = isPositiveInteger(options.responseTimeoutMs ?? 2000);
|
|
312
|
+
if (!budgetValid)
|
|
313
|
+
issues.push({
|
|
314
|
+
code: 'invalid-budget',
|
|
315
|
+
message: 'searchBudget must be a positive safe integer.',
|
|
316
|
+
});
|
|
317
|
+
if (
|
|
318
|
+
(options.responseTimeoutMs !== undefined &&
|
|
319
|
+
!isPositiveInteger(options.responseTimeoutMs)) ||
|
|
320
|
+
(options.disabled !== undefined && typeof options.disabled !== 'boolean') ||
|
|
321
|
+
typeof options.onChange !== 'function' ||
|
|
322
|
+
[
|
|
323
|
+
'canDrop',
|
|
324
|
+
'onDragStart',
|
|
325
|
+
'onDragEnd',
|
|
326
|
+
'onValidationError',
|
|
327
|
+
'getGridItemLayout',
|
|
328
|
+
].some(key => {
|
|
329
|
+
const callback = options[key as keyof DndSessionOptions<T>];
|
|
330
|
+
return callback !== undefined && typeof callback !== 'function';
|
|
331
|
+
})
|
|
332
|
+
) {
|
|
333
|
+
issues.push({
|
|
334
|
+
code: 'invalid-configuration',
|
|
335
|
+
message: 'Session callbacks, disabled or response timeout are invalid.',
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
layoutValid: checked.valid,
|
|
340
|
+
issues,
|
|
341
|
+
movementPolicy: copyMovementPolicy(
|
|
342
|
+
policy.valid ? options.movementPolicy : undefined,
|
|
343
|
+
),
|
|
344
|
+
settingsKey:
|
|
345
|
+
policy.valid && budgetValid && timeoutValid ? settingsKey(options) : null,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function reportCallbackError(error: unknown): void {
|
|
350
|
+
reportFailure('LayoutDnD', error);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Give a stored preview the item objects of a same-layout recommit. */
|
|
354
|
+
function rebaseLayoutState<T>(
|
|
355
|
+
value: LayoutState<T>,
|
|
356
|
+
committed: LayoutState<T>,
|
|
357
|
+
): LayoutState<T> {
|
|
358
|
+
return value.items === committed.items
|
|
359
|
+
? value
|
|
360
|
+
: { ...value, items: committed.items };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Compare zone layouts by ID; revision and caller items are not layout. */
|
|
364
|
+
function sameLayoutZones<T>(
|
|
365
|
+
first: LayoutState<T>,
|
|
366
|
+
second: LayoutState<T>,
|
|
367
|
+
): boolean {
|
|
368
|
+
if (first.zones.length !== second.zones.length) return false;
|
|
369
|
+
const zones = new Map(second.zones.map(zone => [zone.id, zone]));
|
|
370
|
+
return first.zones.every(zone => {
|
|
371
|
+
const other = zones.get(zone.id);
|
|
372
|
+
if (!other) return false;
|
|
373
|
+
if (zone === other) return true;
|
|
374
|
+
if (zone.kind === 'list')
|
|
375
|
+
return (
|
|
376
|
+
other.kind === 'list' &&
|
|
377
|
+
zone.itemIds.length === other.itemIds.length &&
|
|
378
|
+
zone.itemIds.every((id, index) => id === other.itemIds[index])
|
|
379
|
+
);
|
|
380
|
+
if (
|
|
381
|
+
other.kind !== 'grid' ||
|
|
382
|
+
zone.placements.length !== other.placements.length
|
|
383
|
+
)
|
|
384
|
+
return false;
|
|
385
|
+
const placements = new Map(
|
|
386
|
+
other.placements.map(item => [item.itemId, item]),
|
|
387
|
+
);
|
|
388
|
+
return zone.placements.every(item => {
|
|
389
|
+
const match = placements.get(item.itemId);
|
|
390
|
+
return match !== undefined && samePlacement(item, match);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Express a candidate built on an earlier preview relative to the committed value. */
|
|
396
|
+
function relativeToCommitted<T>(
|
|
397
|
+
committed: LayoutState<T>,
|
|
398
|
+
from: LayoutLocation,
|
|
399
|
+
result: Extract<LayoutMoveResult<T>, { status: 'ok' }>,
|
|
400
|
+
): LayoutMoveResult<T> {
|
|
401
|
+
if (sameLayoutZones(committed, result.value))
|
|
402
|
+
return { ...result, value: committed, from, changed: false };
|
|
403
|
+
if (committed.revision === Number.MAX_SAFE_INTEGER)
|
|
404
|
+
return {
|
|
405
|
+
status: 'invalid',
|
|
406
|
+
issues: [
|
|
407
|
+
{
|
|
408
|
+
code: 'invalid-revision',
|
|
409
|
+
message: 'The layout revision cannot exceed a safe integer.',
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
const revision = committed.revision + 1;
|
|
414
|
+
const value =
|
|
415
|
+
result.value.revision === revision && result.value.items === committed.items
|
|
416
|
+
? result.value
|
|
417
|
+
: { ...result.value, revision, items: committed.items };
|
|
418
|
+
return { ...result, value, from, changed: true };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** UI-independent normalized session; screen/scroll measurements stay in the adapter. */
|
|
422
|
+
export class DndSessionCoordinator<T> extends SessionCore<
|
|
423
|
+
DndValue<T>,
|
|
424
|
+
LayoutLocation,
|
|
425
|
+
DndSessionSnapshot<T>,
|
|
426
|
+
ActiveSession<T>,
|
|
427
|
+
DndProposal<T>
|
|
428
|
+
> {
|
|
429
|
+
private readonly instanceId = ++instanceCounter;
|
|
430
|
+
private sessionCounter = 0;
|
|
431
|
+
private options: DndSessionOptions<T>;
|
|
432
|
+
private lastLayoutKey: string | null;
|
|
433
|
+
private validationNotification: string | null = null;
|
|
434
|
+
private previewInputVersion = 0;
|
|
435
|
+
private operationVersion = 0;
|
|
436
|
+
|
|
437
|
+
constructor(options: DndSessionOptions<T>) {
|
|
438
|
+
const checked = checkOptions(options);
|
|
439
|
+
const lastValidValue = checked.layoutValid ? options.value : null;
|
|
440
|
+
super(
|
|
441
|
+
{
|
|
442
|
+
value: lastValidValue,
|
|
443
|
+
displayValue: lastValidValue,
|
|
444
|
+
candidate: null,
|
|
445
|
+
phase: 'idle',
|
|
446
|
+
sessionId: null,
|
|
447
|
+
itemId: null,
|
|
448
|
+
sourceZoneId: null,
|
|
449
|
+
targetZoneId: null,
|
|
450
|
+
target: null,
|
|
451
|
+
seq: 0,
|
|
452
|
+
validity: checked.issues.length ? 'invalid' : 'valid',
|
|
453
|
+
disabled: checked.issues.length > 0 || options.disabled === true,
|
|
454
|
+
gridItemLayouts: new Map(),
|
|
455
|
+
movementPolicy: checked.movementPolicy,
|
|
456
|
+
failureReason: null,
|
|
457
|
+
},
|
|
458
|
+
lastValidValue,
|
|
459
|
+
);
|
|
460
|
+
this.options = options;
|
|
461
|
+
this.lastLayoutKey = checked.layoutValid ? layoutKey(options.value) : null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Every batch is one operation; a refresh may only fold into its own. */
|
|
465
|
+
protected override batchStarted(): void {
|
|
466
|
+
this.operationVersion++;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
protected diagnosticObserver() {
|
|
470
|
+
return typeof this.options.onDiagnostic === 'function'
|
|
471
|
+
? this.options.onDiagnostic
|
|
472
|
+
: undefined;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** Commit owner state after render; ordinary scroll updates do not enter this contract. */
|
|
476
|
+
commit(options: DndSessionOptions<T>): void {
|
|
477
|
+
this.batch(() => {
|
|
478
|
+
this.disposed = false;
|
|
479
|
+
const previousValue = this.lastValidValue;
|
|
480
|
+
const checked = checkOptions(options);
|
|
481
|
+
const key = checked.layoutValid ? layoutKey(options.value) : null;
|
|
482
|
+
const layoutChanged = key !== this.lastLayoutKey;
|
|
483
|
+
if (
|
|
484
|
+
options.value !== this.options.value ||
|
|
485
|
+
options.canDrop !== this.options.canDrop
|
|
486
|
+
)
|
|
487
|
+
this.previewInputVersion++;
|
|
488
|
+
this.options = options;
|
|
489
|
+
this.lastLayoutKey = key;
|
|
490
|
+
if (checked.layoutValid) this.lastValidValue = options.value;
|
|
491
|
+
const disabled = checked.issues.length > 0 || options.disabled === true;
|
|
492
|
+
this.update({ value: this.lastValidValue, disabled });
|
|
493
|
+
const session = this.session;
|
|
494
|
+
const settingsChanged =
|
|
495
|
+
!!session &&
|
|
496
|
+
(checked.settingsKey !== session.settingsKey ||
|
|
497
|
+
options.getGridItemLayout !== session.getGridItemLayout);
|
|
498
|
+
if (!session) {
|
|
499
|
+
this.update({
|
|
500
|
+
displayValue: this.lastValidValue,
|
|
501
|
+
validity: disabled ? 'invalid' : 'valid',
|
|
502
|
+
...(JSON.stringify(this.snapshot.movementPolicy) !==
|
|
503
|
+
JSON.stringify(checked.movementPolicy)
|
|
504
|
+
? { movementPolicy: checked.movementPolicy }
|
|
505
|
+
: {}),
|
|
506
|
+
});
|
|
507
|
+
} else if (this.snapshot.phase === 'awaiting-response') {
|
|
508
|
+
const response = isRecord(options.value)
|
|
509
|
+
? options.value.proposalResponse
|
|
510
|
+
: undefined;
|
|
511
|
+
const matches =
|
|
512
|
+
isRecord(response) &&
|
|
513
|
+
response.sessionId === session.sessionId &&
|
|
514
|
+
response.baseRevision === session.baseRevision &&
|
|
515
|
+
typeof response.accepted === 'boolean';
|
|
516
|
+
if (session.deadline !== undefined && now() >= session.deadline) {
|
|
517
|
+
this.settle(session, 'expired');
|
|
518
|
+
} else if (settingsChanged) {
|
|
519
|
+
this.interrupt(session, 'input-changed');
|
|
520
|
+
} else if (matches) {
|
|
521
|
+
// Acceptance is judged by layout and revision (the layout key). The
|
|
522
|
+
// owner may rebuild item objects or data in the accepting commit,
|
|
523
|
+
// as a store that derives a dock item from a page item does.
|
|
524
|
+
this.settle(
|
|
525
|
+
session,
|
|
526
|
+
response.accepted === false
|
|
527
|
+
? 'rejected'
|
|
528
|
+
: checked.layoutValid && key === session.proposalKey
|
|
529
|
+
? 'accepted'
|
|
530
|
+
: 'mismatch',
|
|
531
|
+
);
|
|
532
|
+
} else if (layoutChanged) {
|
|
533
|
+
this.interrupt(
|
|
534
|
+
session,
|
|
535
|
+
key === session.proposalKey ? 'response-missing' : 'input-changed',
|
|
536
|
+
);
|
|
537
|
+
} else if (disabled) {
|
|
538
|
+
this.interrupt(session, 'disabled');
|
|
539
|
+
}
|
|
540
|
+
} else if (layoutChanged) {
|
|
541
|
+
this.cancelWith('input-changed');
|
|
542
|
+
} else if (disabled) {
|
|
543
|
+
this.cancelWith('disabled');
|
|
544
|
+
} else if (settingsChanged) {
|
|
545
|
+
this.cancelWith('input-changed');
|
|
546
|
+
} else if (this.snapshot.displayValue === previousValue) {
|
|
547
|
+
this.update({ displayValue: this.lastValidValue });
|
|
548
|
+
}
|
|
549
|
+
const notification = checked.issues.length
|
|
550
|
+
? JSON.stringify(checked.issues)
|
|
551
|
+
: null;
|
|
552
|
+
if (notification !== this.validationNotification) {
|
|
553
|
+
this.validationNotification = notification;
|
|
554
|
+
if (notification && typeof options.onValidationError === 'function') {
|
|
555
|
+
try {
|
|
556
|
+
options.onValidationError(checked.issues);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
reportCallbackError(error);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
start(itemId: string, baseRevision?: number): string | null {
|
|
566
|
+
return this.batch(() => {
|
|
567
|
+
if (
|
|
568
|
+
this.disposed ||
|
|
569
|
+
this.session ||
|
|
570
|
+
this.snapshot.disabled ||
|
|
571
|
+
!this.lastValidValue ||
|
|
572
|
+
(baseRevision !== undefined &&
|
|
573
|
+
baseRevision !== this.lastValidValue.revision)
|
|
574
|
+
)
|
|
575
|
+
return null;
|
|
576
|
+
const checked = checkOptions(this.options);
|
|
577
|
+
if (checked.issues.length) {
|
|
578
|
+
this.commit(this.options);
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
const source = this.lastValidValue.zones.find(zone =>
|
|
582
|
+
zone.kind === 'list'
|
|
583
|
+
? zone.itemIds.includes(itemId)
|
|
584
|
+
: zone.placements.some(item => item.itemId === itemId),
|
|
585
|
+
);
|
|
586
|
+
if (!source) return null;
|
|
587
|
+
const base = this.lastValidValue;
|
|
588
|
+
const gridSource =
|
|
589
|
+
source.kind === 'grid'
|
|
590
|
+
? source.placements.find(item => item.itemId === itemId)!
|
|
591
|
+
: null;
|
|
592
|
+
const from: LayoutLocation =
|
|
593
|
+
source.kind === 'list'
|
|
594
|
+
? {
|
|
595
|
+
kind: 'list',
|
|
596
|
+
zoneId: source.id,
|
|
597
|
+
index: source.itemIds.indexOf(itemId),
|
|
598
|
+
}
|
|
599
|
+
: {
|
|
600
|
+
kind: 'grid',
|
|
601
|
+
zoneId: source.id,
|
|
602
|
+
position: { ...gridSource!.position },
|
|
603
|
+
};
|
|
604
|
+
const session: ActiveSession<T> = {
|
|
605
|
+
sessionId: `layout-dnd:${this.instanceId}:${++this.sessionCounter}`,
|
|
606
|
+
itemId,
|
|
607
|
+
sourceZoneId: source.id,
|
|
608
|
+
baseRevision: this.lastValidValue.revision,
|
|
609
|
+
seq: -1,
|
|
610
|
+
target: null,
|
|
611
|
+
path: new PreviewPath(sameLayoutLocation, rebaseLayoutState),
|
|
612
|
+
gridItemLayouts: new Map(),
|
|
613
|
+
gridItemLayoutErrors: new Map(),
|
|
614
|
+
movementPolicy: checked.movementPolicy,
|
|
615
|
+
searchBudget: this.options.searchBudget ?? DEFAULT_SEARCH_BUDGET,
|
|
616
|
+
responseTimeoutMs: this.options.responseTimeoutMs ?? 2000,
|
|
617
|
+
settingsKey: settingsKey(this.options),
|
|
618
|
+
getGridItemLayout: this.options.getGridItemLayout,
|
|
619
|
+
resolving: true,
|
|
620
|
+
previewSeq: null,
|
|
621
|
+
previewInputVersion: this.previewInputVersion,
|
|
622
|
+
};
|
|
623
|
+
this.session = session;
|
|
624
|
+
this.update({
|
|
625
|
+
phase: 'dragging',
|
|
626
|
+
sessionId: session.sessionId,
|
|
627
|
+
itemId,
|
|
628
|
+
sourceZoneId: source.id,
|
|
629
|
+
targetZoneId: null,
|
|
630
|
+
target: null,
|
|
631
|
+
candidate: null,
|
|
632
|
+
seq: -1,
|
|
633
|
+
displayValue: this.lastValidValue,
|
|
634
|
+
validity: 'pending',
|
|
635
|
+
failureReason: null,
|
|
636
|
+
gridItemLayouts: new Map(),
|
|
637
|
+
movementPolicy: session.movementPolicy,
|
|
638
|
+
});
|
|
639
|
+
try {
|
|
640
|
+
const item = base.items.find(candidate => candidate.id === itemId)!;
|
|
641
|
+
for (const target of base.zones) {
|
|
642
|
+
if (target.kind !== 'grid') continue;
|
|
643
|
+
const descriptor =
|
|
644
|
+
target.id === source.id
|
|
645
|
+
? gridSource
|
|
646
|
+
: session.getGridItemLayout
|
|
647
|
+
? session.getGridItemLayout({ item, from, target, value: base })
|
|
648
|
+
: gridSource;
|
|
649
|
+
if (this.session !== session) return null;
|
|
650
|
+
const copied = copyGridItemLayout(descriptor);
|
|
651
|
+
if (copied) session.gridItemLayouts.set(target.id, copied);
|
|
652
|
+
else
|
|
653
|
+
session.gridItemLayoutErrors.set(
|
|
654
|
+
target.id,
|
|
655
|
+
descriptor === null || descriptor === undefined
|
|
656
|
+
? 'missing-grid-item-layout'
|
|
657
|
+
: 'invalid-grid-item-layout',
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (this.session === session) this.cancelWith('policy-error', error);
|
|
662
|
+
else reportCallbackError(error);
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
session.resolving = false;
|
|
666
|
+
this.update({ gridItemLayouts: new Map(session.gridItemLayouts) });
|
|
667
|
+
try {
|
|
668
|
+
this.options.onDragStart?.(this.event(session));
|
|
669
|
+
} catch (error) {
|
|
670
|
+
if (this.session === session)
|
|
671
|
+
this.cancelWith('start-handler-error', error);
|
|
672
|
+
else reportCallbackError(error);
|
|
673
|
+
}
|
|
674
|
+
return this.session === session ? session.sessionId : null;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
requestTarget(
|
|
679
|
+
sessionId: string,
|
|
680
|
+
seq: number,
|
|
681
|
+
to: LayoutLocation | null,
|
|
682
|
+
options?: { refresh?: boolean },
|
|
683
|
+
): void {
|
|
684
|
+
this.batch(() => {
|
|
685
|
+
const session = this.acceptsRequest(sessionId, seq, false);
|
|
686
|
+
if (!session || session.resolving) return;
|
|
687
|
+
// The adapter can certify that measurements and policy inputs have not
|
|
688
|
+
// changed. Pointer movement within one target then needs only the private
|
|
689
|
+
// sequence guard; publishing it would rerender every sortable child.
|
|
690
|
+
if (
|
|
691
|
+
options?.refresh === false &&
|
|
692
|
+
session.previewSeq === session.seq &&
|
|
693
|
+
session.previewInputVersion === this.previewInputVersion &&
|
|
694
|
+
sameTarget(session.target, to)
|
|
695
|
+
) {
|
|
696
|
+
session.seq = seq;
|
|
697
|
+
session.previewSeq = seq;
|
|
698
|
+
// Pointer movement inside one logical cell. Reported so an observer can
|
|
699
|
+
// tell a coalesced packet from one that never arrived.
|
|
700
|
+
this.lifecycle('request-coalesced', session);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const inputVersion = this.previewInputVersion;
|
|
704
|
+
const previous = this.snapshot;
|
|
705
|
+
const previousChanged = this.changed;
|
|
706
|
+
const operationVersion = this.operationVersion;
|
|
707
|
+
this.lifecycle('request', session);
|
|
708
|
+
this.calculate(session, seq, to, false);
|
|
709
|
+
// Reentrant callbacks may already have completed a newer request. Never
|
|
710
|
+
// mark that request as reusable using this older request's inputs.
|
|
711
|
+
if (
|
|
712
|
+
this.session === session &&
|
|
713
|
+
session.seq === seq &&
|
|
714
|
+
this.snapshot.phase === 'dragging'
|
|
715
|
+
) {
|
|
716
|
+
session.previewSeq = seq;
|
|
717
|
+
session.previewInputVersion = inputVersion;
|
|
718
|
+
if (
|
|
719
|
+
options?.refresh === true &&
|
|
720
|
+
this.batchDepth === 1 &&
|
|
721
|
+
this.operationVersion === operationVersion &&
|
|
722
|
+
this.previewInputVersion === inputVersion &&
|
|
723
|
+
sameTarget(previous.target, to) &&
|
|
724
|
+
sameVisibleSnapshot(previous, this.snapshot)
|
|
725
|
+
) {
|
|
726
|
+
// Scrolling can require a fresh visibility/canDrop check without
|
|
727
|
+
// changing anything React displays. Keep sequence ownership private,
|
|
728
|
+
// just as for refresh:false, after every validation has completed.
|
|
729
|
+
this.restore(previous, previousChanged);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
release(sessionId: string, seq: number, to: LayoutLocation | null): void {
|
|
736
|
+
this.batch(() => {
|
|
737
|
+
const session = this.acceptsRequest(sessionId, seq, true);
|
|
738
|
+
if (!session || session.resolving) return;
|
|
739
|
+
// This is receipt on JS, not the physical touch-release timestamp.
|
|
740
|
+
this.lifecycle('release', session);
|
|
741
|
+
this.calculate(session, seq, to, true);
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private calculate(
|
|
746
|
+
session: ActiveSession<T>,
|
|
747
|
+
seq: number,
|
|
748
|
+
target: LayoutLocation | null,
|
|
749
|
+
final: boolean,
|
|
750
|
+
): void {
|
|
751
|
+
// The path accumulates inside one grid zone only; any other target builds
|
|
752
|
+
// on the committed value again, as the display does.
|
|
753
|
+
if (
|
|
754
|
+
!target ||
|
|
755
|
+
target.kind !== 'grid' ||
|
|
756
|
+
target.zoneId !== session.target?.zoneId
|
|
757
|
+
)
|
|
758
|
+
session.path.reset();
|
|
759
|
+
session.seq = seq;
|
|
760
|
+
session.target =
|
|
761
|
+
target &&
|
|
762
|
+
(target.kind === 'grid'
|
|
763
|
+
? { ...target, position: { ...target.position } }
|
|
764
|
+
: { ...target });
|
|
765
|
+
this.update({
|
|
766
|
+
seq,
|
|
767
|
+
target: session.target,
|
|
768
|
+
targetZoneId: target?.zoneId ?? null,
|
|
769
|
+
validity: 'pending',
|
|
770
|
+
failureReason: null,
|
|
771
|
+
});
|
|
772
|
+
this.calculateCandidate(session, final);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
protected location(value: DndValue<T>, itemId: string) {
|
|
776
|
+
return layoutLocation(value, itemId);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
protected isSpatial(target: LayoutLocation) {
|
|
780
|
+
return target.kind === 'grid';
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/** Grid destinations need the span captured at activation. */
|
|
784
|
+
protected precheck(session: ActiveSession<T>, target: LayoutLocation) {
|
|
785
|
+
if (target.kind !== 'grid' || session.gridItemLayouts.has(target.zoneId))
|
|
786
|
+
return null;
|
|
787
|
+
return session.gridItemLayoutErrors.get(target.zoneId) ?? 'invalid-target';
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
protected compute(
|
|
791
|
+
session: ActiveSession<T>,
|
|
792
|
+
target: LayoutLocation,
|
|
793
|
+
baseline: { base: DndValue<T>; cumulative: boolean },
|
|
794
|
+
committed: DndValue<T>,
|
|
795
|
+
from: LayoutLocation | null,
|
|
796
|
+
): CandidateResult<DndValue<T>, LayoutLocation> {
|
|
797
|
+
// lastValidValue passed validateLayoutState when it was committed, the
|
|
798
|
+
// API requires immutable values, and an earlier preview was validated as
|
|
799
|
+
// a complete candidate. Pointer updates therefore validate only the new
|
|
800
|
+
// candidate instead of the whole baseline again.
|
|
801
|
+
const result = this.timed(
|
|
802
|
+
session,
|
|
803
|
+
'compute-move',
|
|
804
|
+
() =>
|
|
805
|
+
computeValidatedLayoutMove({
|
|
806
|
+
value: baseline.cumulative
|
|
807
|
+
? { ...baseline.base, revision: committed.revision }
|
|
808
|
+
: committed,
|
|
809
|
+
itemId: session.itemId,
|
|
810
|
+
to: target,
|
|
811
|
+
gridItem:
|
|
812
|
+
target.kind === 'grid'
|
|
813
|
+
? session.gridItemLayouts.get(target.zoneId)
|
|
814
|
+
: undefined,
|
|
815
|
+
movementPolicy: session.movementPolicy,
|
|
816
|
+
searchBudget: session.searchBudget,
|
|
817
|
+
}),
|
|
818
|
+
computed => computed.status,
|
|
819
|
+
computed => ('attempts' in computed ? computed.attempts : undefined),
|
|
820
|
+
);
|
|
821
|
+
if (result.status !== 'ok') return { status: result.status };
|
|
822
|
+
const relative =
|
|
823
|
+
baseline.cumulative && from
|
|
824
|
+
? relativeToCommitted(committed, from, result)
|
|
825
|
+
: result;
|
|
826
|
+
return relative.status === 'ok' ? relative : { status: relative.status };
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// A transient gap between legal positions inside the source grid must not
|
|
830
|
+
// bounce neighbors back to the committed layout (the same rule as the grid
|
|
831
|
+
// controller). This affects display only; release still computes its own
|
|
832
|
+
// candidate and reports the failure reason.
|
|
833
|
+
protected keepsPreview(
|
|
834
|
+
session: ActiveSession<T>,
|
|
835
|
+
target: LayoutLocation,
|
|
836
|
+
status: 'impossible' | 'unresolved' | 'invalid',
|
|
837
|
+
) {
|
|
838
|
+
return (
|
|
839
|
+
target.kind === 'grid' &&
|
|
840
|
+
target.zoneId === session.sourceZoneId &&
|
|
841
|
+
(status === 'impossible' || status === 'unresolved')
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
protected prepareProposal(
|
|
846
|
+
session: ActiveSession<T>,
|
|
847
|
+
result: { value: DndValue<T>; from: LayoutLocation; to: LayoutLocation },
|
|
848
|
+
): DndProposal<T> {
|
|
849
|
+
return {
|
|
850
|
+
sessionId: session.sessionId,
|
|
851
|
+
baseRevision: session.baseRevision,
|
|
852
|
+
itemId: session.itemId,
|
|
853
|
+
from: result.from,
|
|
854
|
+
to: result.to,
|
|
855
|
+
value: result.value,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
protected askPolicy(
|
|
860
|
+
_session: ActiveSession<T>,
|
|
861
|
+
_committed: DndValue<T>,
|
|
862
|
+
proposal: DndProposal<T>,
|
|
863
|
+
): DropDecision {
|
|
864
|
+
return validatedDecision(
|
|
865
|
+
this.options.canDrop ? this.options.canDrop(proposal) : { allowed: true },
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
protected policyFailed(
|
|
870
|
+
session: ActiveSession<T>,
|
|
871
|
+
seq: number,
|
|
872
|
+
error: unknown,
|
|
873
|
+
) {
|
|
874
|
+
if (
|
|
875
|
+
this.session === session &&
|
|
876
|
+
session.seq === seq &&
|
|
877
|
+
this.snapshot.phase === 'dragging'
|
|
878
|
+
)
|
|
879
|
+
this.cancelWith('policy-error', error);
|
|
880
|
+
else reportCallbackError(error);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Policy callbacks receive the complete candidate. Validate it again if a
|
|
884
|
+
// callback was allowed to run before issuing an atomic state proposal.
|
|
885
|
+
protected revalidate(
|
|
886
|
+
_session: ActiveSession<T>,
|
|
887
|
+
committed: DndValue<T>,
|
|
888
|
+
proposal: DndProposal<T>,
|
|
889
|
+
) {
|
|
890
|
+
return (
|
|
891
|
+
!this.options.canDrop ||
|
|
892
|
+
respondToValidatedDndProposal(
|
|
893
|
+
this.lastValidValue ?? committed,
|
|
894
|
+
proposal,
|
|
895
|
+
true,
|
|
896
|
+
).status === 'accepted'
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
protected showCandidate(result: { value: DndValue<T> }) {
|
|
901
|
+
this.update({
|
|
902
|
+
validity: 'valid',
|
|
903
|
+
candidate: result.value,
|
|
904
|
+
displayValue: result.value,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
protected showFailure(
|
|
909
|
+
committed: DndValue<T> | null,
|
|
910
|
+
failure: string,
|
|
911
|
+
keepPreview: boolean,
|
|
912
|
+
) {
|
|
913
|
+
this.update({
|
|
914
|
+
validity: 'invalid',
|
|
915
|
+
failureReason: failure as DndDropFailureReason,
|
|
916
|
+
...(keepPreview ? {} : { candidate: null, displayValue: committed }),
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
protected expectedPhase() {
|
|
921
|
+
return 'dragging';
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
protected sessionEnded() {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
protected finishUnchanged(session: ActiveSession<T>) {
|
|
929
|
+
this.finish(session, { ...this.event(session), outcome: 'unchanged' });
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
protected registerProposal(
|
|
933
|
+
session: ActiveSession<T>,
|
|
934
|
+
proposal: DndProposal<T>,
|
|
935
|
+
result: { value: DndValue<T> },
|
|
936
|
+
) {
|
|
937
|
+
session.proposal = proposal;
|
|
938
|
+
session.proposalKey = layoutKey(result.value);
|
|
939
|
+
session.deadline = now() + session.responseTimeoutMs;
|
|
940
|
+
this.update({ phase: 'awaiting-response' });
|
|
941
|
+
this.armTimer(session);
|
|
942
|
+
this.lifecycle('proposal', session);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
protected emitChange(
|
|
946
|
+
proposal: DndProposal<T>,
|
|
947
|
+
result: { value: DndValue<T> },
|
|
948
|
+
) {
|
|
949
|
+
this.options.onChange(result.value, proposal);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
protected changeFailed(session: ActiveSession<T>, error: unknown) {
|
|
953
|
+
if (this.session === session)
|
|
954
|
+
this.interrupt(session, 'change-handler-error', error);
|
|
955
|
+
else reportCallbackError(error);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
private armTimer(session: ActiveSession<T>): void {
|
|
959
|
+
this.armDeadline(
|
|
960
|
+
session.deadline,
|
|
961
|
+
() => this.session === session,
|
|
962
|
+
() => this.settle(session, 'expired'),
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
private settle(
|
|
967
|
+
session: ActiveSession<T>,
|
|
968
|
+
response: 'accepted' | 'rejected' | 'expired' | 'mismatch',
|
|
969
|
+
): void {
|
|
970
|
+
if (!session.proposal) return;
|
|
971
|
+
this.finish(session, {
|
|
972
|
+
...this.event(session),
|
|
973
|
+
outcome: 'proposed',
|
|
974
|
+
proposal: session.proposal,
|
|
975
|
+
targetZoneId: session.proposal.to.zoneId,
|
|
976
|
+
response,
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private interrupt(
|
|
981
|
+
session: ActiveSession<T>,
|
|
982
|
+
reason: DndCancelReason | 'response-missing' | 'change-handler-error',
|
|
983
|
+
error?: unknown,
|
|
984
|
+
): void {
|
|
985
|
+
if (!session.proposal) return;
|
|
986
|
+
this.finish(session, {
|
|
987
|
+
...this.event(session),
|
|
988
|
+
outcome: 'proposed',
|
|
989
|
+
proposal: session.proposal,
|
|
990
|
+
targetZoneId: session.proposal.to.zoneId,
|
|
991
|
+
response: 'interrupted',
|
|
992
|
+
reason,
|
|
993
|
+
...(error === undefined ? {} : { error }),
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
protected cancelWith(
|
|
998
|
+
reason: DndCancelReason,
|
|
999
|
+
error?: unknown,
|
|
1000
|
+
policyReason?: string,
|
|
1001
|
+
): void {
|
|
1002
|
+
const session = this.session;
|
|
1003
|
+
if (!session) return;
|
|
1004
|
+
if (session.proposal) {
|
|
1005
|
+
this.interrupt(session, reason, error);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
this.finish(session, {
|
|
1009
|
+
...this.event(session),
|
|
1010
|
+
outcome: 'cancelled',
|
|
1011
|
+
reason,
|
|
1012
|
+
...(session.target ? { targetZoneId: session.target.zoneId } : {}),
|
|
1013
|
+
...(error === undefined ? {} : { error }),
|
|
1014
|
+
...(policyReason === undefined ? {} : { policyReason }),
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
cancel(reason: DndCancelReason): void {
|
|
1019
|
+
this.batch(() => this.cancelWith(reason));
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
private finish(session: ActiveSession<T>, event: DndDragEndEvent<T>): void {
|
|
1023
|
+
if (this.session !== session) return;
|
|
1024
|
+
if (event.outcome === 'proposed') this.lifecycle('response', session);
|
|
1025
|
+
this.lifecycle('end', session);
|
|
1026
|
+
this.clearDeadline();
|
|
1027
|
+
// Clear before callbacks so reentrant end handlers can safely start a new drag.
|
|
1028
|
+
this.session = null;
|
|
1029
|
+
this.update({
|
|
1030
|
+
phase: 'idle',
|
|
1031
|
+
sessionId: null,
|
|
1032
|
+
itemId: null,
|
|
1033
|
+
sourceZoneId: null,
|
|
1034
|
+
targetZoneId: null,
|
|
1035
|
+
target: null,
|
|
1036
|
+
candidate: null,
|
|
1037
|
+
seq: 0,
|
|
1038
|
+
displayValue: this.lastValidValue,
|
|
1039
|
+
validity: this.snapshot.disabled ? 'invalid' : 'valid',
|
|
1040
|
+
gridItemLayouts: new Map(),
|
|
1041
|
+
failureReason: null,
|
|
1042
|
+
});
|
|
1043
|
+
try {
|
|
1044
|
+
this.options.onDragEnd?.(event);
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
reportCallbackError(error);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
dispose(): void {
|
|
1051
|
+
this.batch(() => {
|
|
1052
|
+
this.disposed = true;
|
|
1053
|
+
this.clearDeadline();
|
|
1054
|
+
this.update({ disabled: true });
|
|
1055
|
+
this.cancelWith('unmounted');
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
}
|