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,67 @@
|
|
|
1
|
+
import type { LayoutLocation, LayoutState } from '../contracts';
|
|
2
|
+
import type { GridValue, ItemLocation } from '../types';
|
|
3
|
+
|
|
4
|
+
/** Where a validated grid value places the item, or null when it is absent. */
|
|
5
|
+
export function itemLocation<T>(
|
|
6
|
+
value: GridValue<T>,
|
|
7
|
+
itemId: string,
|
|
8
|
+
): ItemLocation | null {
|
|
9
|
+
const zone = value.zones.find(candidate =>
|
|
10
|
+
candidate.items.some(item => item.id === itemId),
|
|
11
|
+
);
|
|
12
|
+
if (!zone) return null;
|
|
13
|
+
const index = zone.items.findIndex(item => item.id === itemId);
|
|
14
|
+
return zone.strategy === 'ordered'
|
|
15
|
+
? { zoneId: zone.id, strategy: 'ordered', index }
|
|
16
|
+
: {
|
|
17
|
+
zoneId: zone.id,
|
|
18
|
+
strategy: 'spatial',
|
|
19
|
+
position: zone.items[index].position,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function sameItemLocation(
|
|
24
|
+
first: ItemLocation,
|
|
25
|
+
second: ItemLocation | null,
|
|
26
|
+
): boolean {
|
|
27
|
+
if (!second || first.zoneId !== second.zoneId) return false;
|
|
28
|
+
return first.strategy === 'ordered' && second.strategy === 'ordered'
|
|
29
|
+
? first.index === second.index
|
|
30
|
+
: first.strategy === 'spatial' &&
|
|
31
|
+
second.strategy === 'spatial' &&
|
|
32
|
+
first.position.row === second.position.row &&
|
|
33
|
+
first.position.col === second.position.col;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Where a validated layout state places the item, or null when it is absent. */
|
|
37
|
+
export function layoutLocation<T>(
|
|
38
|
+
value: LayoutState<T>,
|
|
39
|
+
itemId: string,
|
|
40
|
+
): LayoutLocation | null {
|
|
41
|
+
for (const zone of value.zones) {
|
|
42
|
+
if (zone.kind === 'list') {
|
|
43
|
+
const index = zone.itemIds.indexOf(itemId);
|
|
44
|
+
if (index !== -1) return { kind: 'list', zoneId: zone.id, index };
|
|
45
|
+
} else {
|
|
46
|
+
const item = zone.placements.find(
|
|
47
|
+
placement => placement.itemId === itemId,
|
|
48
|
+
);
|
|
49
|
+
if (item)
|
|
50
|
+
return { kind: 'grid', zoneId: zone.id, position: item.position };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function sameLayoutLocation(
|
|
57
|
+
first: LayoutLocation,
|
|
58
|
+
second: LayoutLocation | null,
|
|
59
|
+
): boolean {
|
|
60
|
+
if (!second || first.zoneId !== second.zoneId) return false;
|
|
61
|
+
return first.kind === 'list' && second.kind === 'list'
|
|
62
|
+
? first.index === second.index
|
|
63
|
+
: first.kind === 'grid' &&
|
|
64
|
+
second.kind === 'grid' &&
|
|
65
|
+
first.position.row === second.position.row &&
|
|
66
|
+
first.position.col === second.position.col;
|
|
67
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cumulative preview path of one drag session inside one zone.
|
|
3
|
+
*
|
|
4
|
+
* Every valid, permitted candidate the session displays becomes the baseline
|
|
5
|
+
* for the next target in the same zone, so an exchange made on the way stays
|
|
6
|
+
* where the user saw it (moving a widget A → B → C swaps at C against the
|
|
7
|
+
* layout shown at B, as launchers do). Returning to a target visited earlier
|
|
8
|
+
* restores exactly that layout, and the item's committed location always
|
|
9
|
+
* restores the committed layout. The path is dropped whenever the display
|
|
10
|
+
* returns to the committed value: leaving every zone, entering another zone,
|
|
11
|
+
* a rejected or failed candidate, or a candidate equal to the committed layout.
|
|
12
|
+
*/
|
|
13
|
+
type PreviewStep<Location, Value> = {
|
|
14
|
+
/** The logical target that produced the step. */
|
|
15
|
+
target: Location;
|
|
16
|
+
/** The item's committed location, reported as the origin of every candidate. */
|
|
17
|
+
from: Location;
|
|
18
|
+
/** Where the candidate placed the dragged item. */
|
|
19
|
+
to: Location;
|
|
20
|
+
value: Value;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type PreviewBaseline<Location, Value> =
|
|
24
|
+
| { kind: 'restored'; step: PreviewStep<Location, Value> }
|
|
25
|
+
| {
|
|
26
|
+
kind: 'compute';
|
|
27
|
+
base: Value;
|
|
28
|
+
/** True when the base is an earlier preview rather than the committed value. */
|
|
29
|
+
cumulative: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class PreviewPath<Location, Value> {
|
|
33
|
+
private steps: PreviewStep<Location, Value>[] = [];
|
|
34
|
+
private committed: Value | null = null;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly sameLocation: (
|
|
38
|
+
first: Location,
|
|
39
|
+
second: Location,
|
|
40
|
+
) => boolean,
|
|
41
|
+
/** Re-attach a stored layout to a recommit that changed only item data. */
|
|
42
|
+
private readonly rebase: (value: Value, committed: Value) => Value,
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
get length(): number {
|
|
46
|
+
return this.steps.length;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
reset(): void {
|
|
50
|
+
this.steps.length = 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Choose the layout a new target builds on, or the earlier step it restores. */
|
|
54
|
+
resolve(
|
|
55
|
+
committed: Value,
|
|
56
|
+
origin: Location,
|
|
57
|
+
target: Location,
|
|
58
|
+
): PreviewBaseline<Location, Value> {
|
|
59
|
+
if (this.sameLocation(target, origin)) {
|
|
60
|
+
this.reset();
|
|
61
|
+
return { kind: 'compute', base: committed, cumulative: false };
|
|
62
|
+
}
|
|
63
|
+
this.sync(committed);
|
|
64
|
+
const index = this.steps.findIndex(step =>
|
|
65
|
+
this.sameLocation(step.target, target),
|
|
66
|
+
);
|
|
67
|
+
if (index !== -1) {
|
|
68
|
+
this.steps.length = index + 1;
|
|
69
|
+
return { kind: 'restored', step: this.steps[index] };
|
|
70
|
+
}
|
|
71
|
+
const newest = this.steps[this.steps.length - 1];
|
|
72
|
+
return newest
|
|
73
|
+
? { kind: 'compute', base: newest.value, cumulative: true }
|
|
74
|
+
: { kind: 'compute', base: committed, cumulative: false };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Record a displayed candidate that differs from the committed layout. */
|
|
78
|
+
push(committed: Value, step: PreviewStep<Location, Value>): void {
|
|
79
|
+
this.sync(committed);
|
|
80
|
+
this.steps.push(step);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private sync(committed: Value): void {
|
|
84
|
+
if (this.committed === committed) return;
|
|
85
|
+
if (this.committed !== null)
|
|
86
|
+
for (const step of this.steps)
|
|
87
|
+
step.value = this.rebase(step.value, committed);
|
|
88
|
+
this.committed = committed;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import {
|
|
2
|
+
diagnosticNow,
|
|
3
|
+
type DiagnosticObserver,
|
|
4
|
+
emitDiagnostic,
|
|
5
|
+
} from '../diagnostics';
|
|
6
|
+
import type {
|
|
7
|
+
CancelReason,
|
|
8
|
+
DragStartEvent,
|
|
9
|
+
DropDecision,
|
|
10
|
+
GridDiagnosticEvent,
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
13
|
+
import { CoordinatorBase } from './coordinatorBase';
|
|
14
|
+
import { type PreviewBaseline, PreviewPath } from './previewPath';
|
|
15
|
+
|
|
16
|
+
/** Engine outcome normalized for both value shapes. */
|
|
17
|
+
export type CandidateResult<Value, Location> =
|
|
18
|
+
| {
|
|
19
|
+
status: 'ok';
|
|
20
|
+
value: Value;
|
|
21
|
+
from: Location;
|
|
22
|
+
to: Location;
|
|
23
|
+
changed: boolean;
|
|
24
|
+
}
|
|
25
|
+
| { status: 'impossible' | 'unresolved' | 'invalid' };
|
|
26
|
+
|
|
27
|
+
type OkCandidate<Value, Location> = Extract<
|
|
28
|
+
CandidateResult<Value, Location>,
|
|
29
|
+
{ status: 'ok' }
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
export type CandidateFailure =
|
|
33
|
+
'unresolvable' | 'search-budget' | 'invalid-target';
|
|
34
|
+
|
|
35
|
+
export function candidateFailure(
|
|
36
|
+
status: 'impossible' | 'unresolved' | 'invalid',
|
|
37
|
+
): CandidateFailure {
|
|
38
|
+
return status === 'impossible'
|
|
39
|
+
? 'unresolvable'
|
|
40
|
+
: status === 'unresolved'
|
|
41
|
+
? 'search-budget'
|
|
42
|
+
: 'invalid-target';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Snapshot fields every coordinator publishes; subclasses add their own. */
|
|
46
|
+
export type CoreSnapshot<Value> = {
|
|
47
|
+
value: Value | null;
|
|
48
|
+
displayValue: Value | null;
|
|
49
|
+
phase: string;
|
|
50
|
+
sessionId: string | null;
|
|
51
|
+
itemId: string | null;
|
|
52
|
+
sourceZoneId: string | null;
|
|
53
|
+
targetZoneId: string | null;
|
|
54
|
+
seq: number;
|
|
55
|
+
validity: 'pending' | 'valid' | 'invalid';
|
|
56
|
+
disabled: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Session fields the shared candidate flow reads and writes. */
|
|
60
|
+
export type CoreSession<Value, Location> = DragStartEvent & {
|
|
61
|
+
seq: number;
|
|
62
|
+
target: Location | null;
|
|
63
|
+
path: PreviewPath<Location, Value>;
|
|
64
|
+
deadline?: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The drag session logic both coordinators share: request guards, the
|
|
69
|
+
* cumulative preview baseline, engine dispatch, the permission callback with
|
|
70
|
+
* its reentrancy guard, candidate publication and the final proposal. The
|
|
71
|
+
* value shape, engine call, snapshot fields and terminal transitions
|
|
72
|
+
* (cancel, unchanged, handoff) come from the subclass.
|
|
73
|
+
*/
|
|
74
|
+
export abstract class SessionCore<
|
|
75
|
+
Value,
|
|
76
|
+
Location extends { zoneId: string },
|
|
77
|
+
Snapshot extends CoreSnapshot<Value>,
|
|
78
|
+
Session extends CoreSession<Value, Location>,
|
|
79
|
+
Proposal,
|
|
80
|
+
> extends CoordinatorBase<Snapshot> {
|
|
81
|
+
protected session: Session | null = null;
|
|
82
|
+
protected lastValidValue: Value | null;
|
|
83
|
+
protected disposed = false;
|
|
84
|
+
private diagnosticEvents:
|
|
85
|
+
{ observer: DiagnosticObserver; event: GridDiagnosticEvent }[] | undefined;
|
|
86
|
+
private flushingDiagnostics = false;
|
|
87
|
+
|
|
88
|
+
protected constructor(initial: Snapshot, lastValidValue: Value | null) {
|
|
89
|
+
super(initial);
|
|
90
|
+
this.lastValidValue = lastValidValue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The consumer's optional JS-only observer; exceptions never affect a drag. */
|
|
94
|
+
protected abstract diagnosticObserver(): DiagnosticObserver | undefined;
|
|
95
|
+
|
|
96
|
+
protected queueDiagnostic(
|
|
97
|
+
observer: DiagnosticObserver,
|
|
98
|
+
event: GridDiagnosticEvent,
|
|
99
|
+
): void {
|
|
100
|
+
(this.diagnosticEvents ??= []).push({ observer, event });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Diagnostics queued during a batch are delivered after its subscribers. */
|
|
104
|
+
protected override batchFinished(): void {
|
|
105
|
+
if (this.diagnosticEvents) this.flushDiagnostics();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private flushDiagnostics(): void {
|
|
109
|
+
if (this.flushingDiagnostics) return;
|
|
110
|
+
this.flushingDiagnostics = true;
|
|
111
|
+
try {
|
|
112
|
+
while (this.diagnosticEvents) {
|
|
113
|
+
const pending = this.diagnosticEvents;
|
|
114
|
+
this.diagnosticEvents = undefined;
|
|
115
|
+
for (const { observer, event } of pending)
|
|
116
|
+
emitDiagnostic(observer, event);
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
this.flushingDiagnostics = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected lifecycle(
|
|
124
|
+
name: Extract<GridDiagnosticEvent, { type: 'lifecycle' }>['name'],
|
|
125
|
+
session: Session,
|
|
126
|
+
): void {
|
|
127
|
+
const observer = this.diagnosticObserver();
|
|
128
|
+
if (!observer) return;
|
|
129
|
+
this.queueDiagnostic(observer, {
|
|
130
|
+
type: 'lifecycle',
|
|
131
|
+
name,
|
|
132
|
+
timestampMs: diagnosticNow(),
|
|
133
|
+
sessionId: session.sessionId,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Time one engine or policy step for the observer, including failures. */
|
|
138
|
+
protected timed<R>(
|
|
139
|
+
session: Session,
|
|
140
|
+
name: 'compute-move' | 'policy',
|
|
141
|
+
run: () => R,
|
|
142
|
+
status: (result: R) => string,
|
|
143
|
+
attempts: (result: R) => number | undefined = () => undefined,
|
|
144
|
+
): R {
|
|
145
|
+
const observer = this.diagnosticObserver();
|
|
146
|
+
if (!observer) return run();
|
|
147
|
+
const startedAt = diagnosticNow();
|
|
148
|
+
let result: R | undefined;
|
|
149
|
+
try {
|
|
150
|
+
result = run();
|
|
151
|
+
return result;
|
|
152
|
+
} finally {
|
|
153
|
+
const count = result === undefined ? undefined : attempts(result);
|
|
154
|
+
this.queueDiagnostic(observer, {
|
|
155
|
+
type: 'timing',
|
|
156
|
+
name,
|
|
157
|
+
durationMs: diagnosticNow() - startedAt,
|
|
158
|
+
status: result === undefined ? 'error' : status(result),
|
|
159
|
+
...(count === undefined ? {} : { attempts: count }),
|
|
160
|
+
sessionId: session.sessionId,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
protected event(session: Session): DragStartEvent {
|
|
166
|
+
return {
|
|
167
|
+
sessionId: session.sessionId,
|
|
168
|
+
itemId: session.itemId,
|
|
169
|
+
sourceZoneId: session.sourceZoneId,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The live dragging session when the request sequence is acceptable. */
|
|
174
|
+
protected acceptsRequest(
|
|
175
|
+
sessionId: string,
|
|
176
|
+
seq: number,
|
|
177
|
+
final: boolean,
|
|
178
|
+
): Session | null {
|
|
179
|
+
const session = this.session;
|
|
180
|
+
if (
|
|
181
|
+
!session ||
|
|
182
|
+
session.sessionId !== sessionId ||
|
|
183
|
+
this.snapshot.phase !== 'dragging' ||
|
|
184
|
+
!Number.isSafeInteger(seq) ||
|
|
185
|
+
seq < 0 ||
|
|
186
|
+
(final ? seq < session.seq : seq <= session.seq)
|
|
187
|
+
)
|
|
188
|
+
return null;
|
|
189
|
+
return session;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected abstract location(value: Value, itemId: string): Location | null;
|
|
193
|
+
/** Spatial targets accumulate previews; ordered/list targets never do. */
|
|
194
|
+
protected abstract isSpatial(target: Location): boolean;
|
|
195
|
+
/** A failure detail that rejects the target before any computation. */
|
|
196
|
+
protected abstract precheck(
|
|
197
|
+
session: Session,
|
|
198
|
+
target: Location,
|
|
199
|
+
): string | null;
|
|
200
|
+
protected abstract compute(
|
|
201
|
+
session: Session,
|
|
202
|
+
target: Location,
|
|
203
|
+
baseline: { base: Value; cumulative: boolean },
|
|
204
|
+
committed: Value,
|
|
205
|
+
from: Location | null,
|
|
206
|
+
): CandidateResult<Value, Location>;
|
|
207
|
+
/** Whether a failed candidate leaves the earlier preview on screen. */
|
|
208
|
+
protected abstract keepsPreview(
|
|
209
|
+
session: Session,
|
|
210
|
+
target: Location,
|
|
211
|
+
status: 'impossible' | 'unresolved' | 'invalid',
|
|
212
|
+
): boolean;
|
|
213
|
+
protected abstract prepareProposal(
|
|
214
|
+
session: Session,
|
|
215
|
+
result: OkCandidate<Value, Location>,
|
|
216
|
+
): Proposal;
|
|
217
|
+
/** Runs the permission callback and validates its shape; may throw. */
|
|
218
|
+
protected abstract askPolicy(
|
|
219
|
+
session: Session,
|
|
220
|
+
committed: Value,
|
|
221
|
+
proposal: Proposal,
|
|
222
|
+
result: OkCandidate<Value, Location>,
|
|
223
|
+
): DropDecision;
|
|
224
|
+
protected abstract policyFailed(
|
|
225
|
+
session: Session,
|
|
226
|
+
seq: number,
|
|
227
|
+
error: unknown,
|
|
228
|
+
): void;
|
|
229
|
+
/** Re-check a changed candidate after the callback ran; true when it stands. */
|
|
230
|
+
protected abstract revalidate(
|
|
231
|
+
session: Session,
|
|
232
|
+
committed: Value,
|
|
233
|
+
proposal: Proposal,
|
|
234
|
+
result: OkCandidate<Value, Location>,
|
|
235
|
+
): boolean;
|
|
236
|
+
protected abstract showCandidate(result: OkCandidate<Value, Location>): void;
|
|
237
|
+
protected abstract showFailure(
|
|
238
|
+
committed: Value | null,
|
|
239
|
+
failure: string,
|
|
240
|
+
keepPreview: boolean,
|
|
241
|
+
): void;
|
|
242
|
+
protected abstract expectedPhase(final: boolean): string;
|
|
243
|
+
protected abstract sessionEnded(session: Session): boolean;
|
|
244
|
+
protected abstract cancelWith(
|
|
245
|
+
reason: CancelReason,
|
|
246
|
+
error?: unknown,
|
|
247
|
+
policyReason?: string,
|
|
248
|
+
): void;
|
|
249
|
+
protected abstract finishUnchanged(session: Session): void;
|
|
250
|
+
/** Store the proposal, arm the deadline and enter awaiting-response. */
|
|
251
|
+
protected abstract registerProposal(
|
|
252
|
+
session: Session,
|
|
253
|
+
proposal: Proposal,
|
|
254
|
+
result: OkCandidate<Value, Location>,
|
|
255
|
+
): void;
|
|
256
|
+
protected abstract emitChange(
|
|
257
|
+
proposal: Proposal,
|
|
258
|
+
result: OkCandidate<Value, Location>,
|
|
259
|
+
): void;
|
|
260
|
+
protected abstract changeFailed(session: Session, error: unknown): void;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Compute, permit and publish the candidate for `session.target`. The
|
|
264
|
+
* subclass has already stored the target and its sequence on the session.
|
|
265
|
+
*/
|
|
266
|
+
protected calculateCandidate(session: Session, final: boolean): void {
|
|
267
|
+
const seq = session.seq;
|
|
268
|
+
const committed = this.lastValidValue;
|
|
269
|
+
const target = session.target;
|
|
270
|
+
if (!target || !committed) {
|
|
271
|
+
if (final) this.cancelWith('outside-zones');
|
|
272
|
+
else this.showFailure(committed, 'outside-zones', false);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const rejected = this.precheck(session, target);
|
|
276
|
+
if (rejected !== null) {
|
|
277
|
+
session.path.reset();
|
|
278
|
+
if (final) this.cancelWith('invalid-target', undefined, rejected);
|
|
279
|
+
else this.showFailure(committed, rejected, false);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const from = this.location(committed, session.itemId);
|
|
283
|
+
// Previews accumulate only inside the source zone: ordered targets are
|
|
284
|
+
// path-independent, and other zones keep computing from the committed
|
|
285
|
+
// value, so a cross-zone exchange stays impossible.
|
|
286
|
+
const accumulates =
|
|
287
|
+
from !== null &&
|
|
288
|
+
this.isSpatial(target) &&
|
|
289
|
+
target.zoneId === session.sourceZoneId;
|
|
290
|
+
let baseline: PreviewBaseline<Location, Value>;
|
|
291
|
+
if (accumulates) baseline = session.path.resolve(committed, from, target);
|
|
292
|
+
else {
|
|
293
|
+
session.path.reset();
|
|
294
|
+
baseline = { kind: 'compute', base: committed, cumulative: false };
|
|
295
|
+
}
|
|
296
|
+
const result: CandidateResult<Value, Location> =
|
|
297
|
+
baseline.kind === 'restored'
|
|
298
|
+
? {
|
|
299
|
+
status: 'ok',
|
|
300
|
+
value: baseline.step.value,
|
|
301
|
+
from: baseline.step.from,
|
|
302
|
+
to: baseline.step.to,
|
|
303
|
+
changed: true,
|
|
304
|
+
}
|
|
305
|
+
: this.compute(session, target, baseline, committed, from);
|
|
306
|
+
if (result.status !== 'ok') {
|
|
307
|
+
const failure = candidateFailure(result.status);
|
|
308
|
+
if (final) this.cancelWith(failure);
|
|
309
|
+
else {
|
|
310
|
+
const keepPreview = this.keepsPreview(session, target, result.status);
|
|
311
|
+
if (!keepPreview) session.path.reset();
|
|
312
|
+
this.showFailure(committed, failure, keepPreview);
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const proposal = this.prepareProposal(session, result);
|
|
317
|
+
let decision: DropDecision;
|
|
318
|
+
try {
|
|
319
|
+
decision = this.timed(
|
|
320
|
+
session,
|
|
321
|
+
'policy',
|
|
322
|
+
() => this.askPolicy(session, committed, proposal, result),
|
|
323
|
+
chosen => (chosen.allowed ? 'allowed' : 'rejected'),
|
|
324
|
+
);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
this.policyFailed(session, seq, error);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// A consumer callback may have synchronously changed or disposed the input.
|
|
330
|
+
if (
|
|
331
|
+
this.session !== session ||
|
|
332
|
+
this.sessionEnded(session) ||
|
|
333
|
+
session.seq !== seq ||
|
|
334
|
+
this.snapshot.phase !== this.expectedPhase(final)
|
|
335
|
+
)
|
|
336
|
+
return;
|
|
337
|
+
if (!decision.allowed) {
|
|
338
|
+
if (final) this.cancelWith('policy-rejected', undefined, decision.reason);
|
|
339
|
+
else {
|
|
340
|
+
session.path.reset();
|
|
341
|
+
this.showFailure(committed, 'policy-rejected', false);
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (
|
|
346
|
+
result.changed &&
|
|
347
|
+
!this.revalidate(session, committed, proposal, result)
|
|
348
|
+
) {
|
|
349
|
+
if (final) this.cancelWith('invalid-target');
|
|
350
|
+
else {
|
|
351
|
+
session.path.reset();
|
|
352
|
+
this.showFailure(committed, 'invalid-target', false);
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
this.showCandidate(result);
|
|
357
|
+
if (baseline.kind === 'compute') {
|
|
358
|
+
if (accumulates && result.changed)
|
|
359
|
+
session.path.push(committed, {
|
|
360
|
+
target,
|
|
361
|
+
from,
|
|
362
|
+
to: result.to,
|
|
363
|
+
value: result.value,
|
|
364
|
+
});
|
|
365
|
+
else session.path.reset();
|
|
366
|
+
}
|
|
367
|
+
if (!final) return;
|
|
368
|
+
if (!result.changed) {
|
|
369
|
+
this.finishUnchanged(session);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
this.registerProposal(session, proposal, result);
|
|
373
|
+
try {
|
|
374
|
+
this.emitChange(proposal, result);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
this.changeFailed(session, error);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Shape-check a permission callback result; the caller reports a throw. */
|
|
382
|
+
export function validatedDecision(decision: unknown): DropDecision {
|
|
383
|
+
if (
|
|
384
|
+
typeof decision !== 'object' ||
|
|
385
|
+
decision === null ||
|
|
386
|
+
typeof (decision as { allowed?: unknown }).allowed !== 'boolean'
|
|
387
|
+
)
|
|
388
|
+
throw new TypeError('canDrop must return an allowed boolean decision.');
|
|
389
|
+
return decision as DropDecision;
|
|
390
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LayoutState } from '../contracts';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createDndStateStore,
|
|
5
|
+
type DndStateStore,
|
|
6
|
+
type DndValue,
|
|
7
|
+
} from './dndState';
|
|
8
|
+
import { useStoreState } from './useStoreState';
|
|
9
|
+
|
|
10
|
+
export type DndState<T> = Pick<
|
|
11
|
+
DndStateStore<T>,
|
|
12
|
+
'onChange' | 'setValue' | 'respond'
|
|
13
|
+
> & {
|
|
14
|
+
value: DndValue<T>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Own normalized state and synchronously acknowledge proposals; initialValue is read once. */
|
|
18
|
+
export function useDndState<T>(
|
|
19
|
+
initialValue: LayoutState<T> | (() => LayoutState<T>),
|
|
20
|
+
): DndState<T> {
|
|
21
|
+
return useStoreState(() =>
|
|
22
|
+
createDndStateStore(
|
|
23
|
+
typeof initialValue === 'function' ? initialValue() : initialValue,
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GridValue, RevisionedGridValue } from '../types';
|
|
2
|
+
|
|
3
|
+
import { createGridStateStore, type GridStateStore } from './gridState';
|
|
4
|
+
import { useStoreState } from './useStoreState';
|
|
5
|
+
|
|
6
|
+
export type GridState<T> = Pick<
|
|
7
|
+
GridStateStore<T>,
|
|
8
|
+
'onChange' | 'setValue' | 'respond'
|
|
9
|
+
> & {
|
|
10
|
+
value: RevisionedGridValue<T>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Own grid state and acknowledge proposals synchronously; initialValue is read once. */
|
|
14
|
+
export function useGridState<T>(
|
|
15
|
+
initialValue: GridValue<T> | (() => GridValue<T>),
|
|
16
|
+
): GridState<T> {
|
|
17
|
+
return useStoreState(() =>
|
|
18
|
+
createGridStateStore(
|
|
19
|
+
typeof initialValue === 'function' ? initialValue() : initialValue,
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useState, useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
type StateStore<Value, OnChange, SetValue, Respond> = {
|
|
4
|
+
getSnapshot(): Value;
|
|
5
|
+
subscribe(listener: () => void): () => void;
|
|
6
|
+
onChange: OnChange;
|
|
7
|
+
setValue: SetValue;
|
|
8
|
+
respond: Respond;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Own one approval store for the component's lifetime and subscribe to it. */
|
|
12
|
+
export function useStoreState<Value, OnChange, SetValue, Respond>(
|
|
13
|
+
create: () => StateStore<Value, OnChange, SetValue, Respond>,
|
|
14
|
+
): { value: Value; onChange: OnChange; setValue: SetValue; respond: Respond } {
|
|
15
|
+
const [store] = useState(create);
|
|
16
|
+
const value = useSyncExternalStore(
|
|
17
|
+
store.subscribe,
|
|
18
|
+
store.getSnapshot,
|
|
19
|
+
store.getSnapshot,
|
|
20
|
+
);
|
|
21
|
+
return {
|
|
22
|
+
value,
|
|
23
|
+
onChange: store.onChange,
|
|
24
|
+
setValue: store.setValue,
|
|
25
|
+
respond: store.respond,
|
|
26
|
+
};
|
|
27
|
+
}
|
package/src/defaults.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GridDiagnosticEvent } from './types';
|
|
2
|
+
|
|
3
|
+
export type DiagnosticObserver = (event: GridDiagnosticEvent) => void;
|
|
4
|
+
export type TimingDiagnostic = Extract<GridDiagnosticEvent, { type: 'timing' }>;
|
|
5
|
+
|
|
6
|
+
export function diagnosticNow(): number {
|
|
7
|
+
// RN provides this JS monotonic clock; ambient native declarations omit it.
|
|
8
|
+
return (
|
|
9
|
+
globalThis as typeof globalThis & { performance: { now(): number } }
|
|
10
|
+
).performance.now();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Observers receive newly-created scalar payloads and cannot fail a drag. */
|
|
14
|
+
export function emitDiagnostic(
|
|
15
|
+
observer: DiagnosticObserver,
|
|
16
|
+
event: GridDiagnosticEvent,
|
|
17
|
+
): void {
|
|
18
|
+
try {
|
|
19
|
+
observer(event);
|
|
20
|
+
} catch {
|
|
21
|
+
// Diagnostics must not alter placement, response or cleanup behavior.
|
|
22
|
+
}
|
|
23
|
+
}
|