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,56 @@
|
|
|
1
|
+
import { screenToContent } from '../adapters/coordinates';
|
|
2
|
+
import type { ListLocation } from '../contracts';
|
|
3
|
+
import { computeListIndex } from '../engine/list';
|
|
4
|
+
import { findListTargetIndex } from '../engine/listIndex';
|
|
5
|
+
import type { DndMotion, ListRegistration } from '../runtime/dndRuntime';
|
|
6
|
+
import { rectContains } from './geometry';
|
|
7
|
+
|
|
8
|
+
/** Target from committed order; candidate reflow must not move its own thresholds. */
|
|
9
|
+
export function listTarget(
|
|
10
|
+
state: DndMotion,
|
|
11
|
+
registrations: ReadonlyMap<
|
|
12
|
+
string,
|
|
13
|
+
ListRegistration & { layoutVersion: number }
|
|
14
|
+
>,
|
|
15
|
+
): ListLocation | null {
|
|
16
|
+
if (!rectContains(state.root, state.pointer.x, state.pointer.y)) return null;
|
|
17
|
+
for (const measured of state.zones) {
|
|
18
|
+
const registration = registrations.get(measured.zoneId);
|
|
19
|
+
if (
|
|
20
|
+
!registration ||
|
|
21
|
+
registration.revision !== state.revision ||
|
|
22
|
+
registration.layout.revision !== state.revision ||
|
|
23
|
+
registration.layoutVersion !== measured.layoutVersion ||
|
|
24
|
+
!rectContains(measured.viewport.rect, state.pointer.x, state.pointer.y)
|
|
25
|
+
)
|
|
26
|
+
continue;
|
|
27
|
+
const vertical = registration.orientation === 'vertical';
|
|
28
|
+
const center = {
|
|
29
|
+
x: state.pointer.x - state.grip.x + state.width / 2,
|
|
30
|
+
y: state.pointer.y - state.grip.y + state.height / 2,
|
|
31
|
+
};
|
|
32
|
+
const content = screenToContent(
|
|
33
|
+
center,
|
|
34
|
+
{
|
|
35
|
+
viewport: measured.viewport,
|
|
36
|
+
scrollOffset: vertical
|
|
37
|
+
? { x: 0, y: measured.offset }
|
|
38
|
+
: { x: measured.offset, y: 0 },
|
|
39
|
+
},
|
|
40
|
+
{ revision: state.revision },
|
|
41
|
+
);
|
|
42
|
+
if (!content.valid) return null;
|
|
43
|
+
const mainAxisCenter = vertical ? content.value.y : content.value.x;
|
|
44
|
+
const index = registration.targetIndex
|
|
45
|
+
? findListTargetIndex(
|
|
46
|
+
registration.targetIndex,
|
|
47
|
+
state.itemId,
|
|
48
|
+
mainAxisCenter,
|
|
49
|
+
)
|
|
50
|
+
: computeListIndex(registration.layout, state.itemId, mainAxisCenter);
|
|
51
|
+
return index === null
|
|
52
|
+
? null
|
|
53
|
+
: { kind: 'list', zoneId: measured.zoneId, index };
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ValidationIssue } from '../types';
|
|
2
|
+
|
|
3
|
+
export type GridMotionConfig = {
|
|
4
|
+
/** Duration in milliseconds for item movement and preview settling. Defaults to 180. */
|
|
5
|
+
durationMs?: number;
|
|
6
|
+
/** Respect the device accessibility setting by default. */
|
|
7
|
+
reduceMotion?: 'system' | 'always' | 'never';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type ResolvedGridMotionConfig = Required<GridMotionConfig>;
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_GRID_MOTION: Readonly<ResolvedGridMotionConfig> =
|
|
13
|
+
Object.freeze({ durationMs: 180, reduceMotion: 'system' });
|
|
14
|
+
|
|
15
|
+
type GridMotionResult =
|
|
16
|
+
| { valid: true; motion: ResolvedGridMotionConfig }
|
|
17
|
+
| { valid: false; issues: ValidationIssue[] };
|
|
18
|
+
|
|
19
|
+
/** Validate before sharing animation settings with the UI runtime. */
|
|
20
|
+
export function resolveGridMotion(value: unknown): GridMotionResult {
|
|
21
|
+
const fail = (message: string): GridMotionResult => ({
|
|
22
|
+
valid: false,
|
|
23
|
+
issues: [{ code: 'invalid-configuration', message }],
|
|
24
|
+
});
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return { valid: true, motion: { ...DEFAULT_GRID_MOTION } };
|
|
27
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
28
|
+
return fail('motion must be an object with durationMs and reduceMotion.');
|
|
29
|
+
const config = value as Record<string, unknown>;
|
|
30
|
+
if (
|
|
31
|
+
Object.keys(config).some(
|
|
32
|
+
key => key !== 'durationMs' && key !== 'reduceMotion',
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
return fail('motion supports only durationMs and reduceMotion.');
|
|
36
|
+
|
|
37
|
+
const durationMs =
|
|
38
|
+
config.durationMs === undefined
|
|
39
|
+
? DEFAULT_GRID_MOTION.durationMs
|
|
40
|
+
: config.durationMs;
|
|
41
|
+
if (
|
|
42
|
+
typeof durationMs !== 'number' ||
|
|
43
|
+
!Number.isFinite(durationMs) ||
|
|
44
|
+
durationMs < 0
|
|
45
|
+
)
|
|
46
|
+
return fail('motion.durationMs must be a finite non-negative number.');
|
|
47
|
+
|
|
48
|
+
const reduceMotion =
|
|
49
|
+
config.reduceMotion === undefined
|
|
50
|
+
? DEFAULT_GRID_MOTION.reduceMotion
|
|
51
|
+
: config.reduceMotion;
|
|
52
|
+
if (
|
|
53
|
+
reduceMotion !== 'system' &&
|
|
54
|
+
reduceMotion !== 'always' &&
|
|
55
|
+
reduceMotion !== 'never'
|
|
56
|
+
)
|
|
57
|
+
return fail('motion.reduceMotion must be system, always or never.');
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
valid: true,
|
|
61
|
+
motion: {
|
|
62
|
+
durationMs: reduceMotion === 'always' ? 0 : durationMs,
|
|
63
|
+
reduceMotion,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { LayoutLocation, LayoutRect } from '../contracts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Only validated numeric geometry crosses into the UI runtime. Coordinates
|
|
5
|
+
* form one typed array so a zone table crosses runtimes as a single buffer;
|
|
6
|
+
* the slot map is reused while a zone keeps the same items, so repeated
|
|
7
|
+
* candidates serialize only the coordinates.
|
|
8
|
+
*/
|
|
9
|
+
export type DndZonePresentation = {
|
|
10
|
+
/** Prefixed item ID → slot; four coordinates per slot in `rects`. */
|
|
11
|
+
slots: Readonly<Record<string, number>>;
|
|
12
|
+
/** x, y, width and height of each slot, in zone content coordinates. */
|
|
13
|
+
rects: Float64Array;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type DndPresentation = {
|
|
17
|
+
token: number;
|
|
18
|
+
revision: number;
|
|
19
|
+
zones: Record<string, DndZonePresentation>;
|
|
20
|
+
/** Last policy-approved preview, usable for motion before JS acknowledges release. */
|
|
21
|
+
releaseTarget?: {
|
|
22
|
+
target: LayoutLocation;
|
|
23
|
+
policyRevision: number;
|
|
24
|
+
/** Final pointer already validated by JS; controlled state may still be pending. */
|
|
25
|
+
releasedSeq?: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const EMPTY_DND_PRESENTATION: DndPresentation = {
|
|
30
|
+
token: 0,
|
|
31
|
+
revision: 0,
|
|
32
|
+
zones: {},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function presentationRect(
|
|
36
|
+
presentation: DndPresentation | undefined,
|
|
37
|
+
zoneId: string,
|
|
38
|
+
itemId: string,
|
|
39
|
+
): LayoutRect | undefined {
|
|
40
|
+
'worklet';
|
|
41
|
+
const zone = presentation?.zones[`#${zoneId}`];
|
|
42
|
+
if (zone === undefined) return undefined;
|
|
43
|
+
const slot = zone.slots[`#${itemId}`];
|
|
44
|
+
if (slot === undefined) return undefined;
|
|
45
|
+
const offset = slot * 4;
|
|
46
|
+
return {
|
|
47
|
+
x: zone.rects[offset],
|
|
48
|
+
y: zone.rects[offset + 1],
|
|
49
|
+
width: zone.rects[offset + 2],
|
|
50
|
+
height: zone.rects[offset + 3],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build a zone table from item rects. The previous table's slot map is kept
|
|
56
|
+
* when it lists exactly these items, so unchanged maps keep their identity.
|
|
57
|
+
*/
|
|
58
|
+
export function createZonePresentation(
|
|
59
|
+
entries: ReadonlyArray<readonly [itemId: string, rect: LayoutRect]>,
|
|
60
|
+
previous?: DndZonePresentation | null,
|
|
61
|
+
): DndZonePresentation {
|
|
62
|
+
let slots = previous?.slots;
|
|
63
|
+
let count = slots ? Object.keys(slots).length : 0;
|
|
64
|
+
if (
|
|
65
|
+
!slots ||
|
|
66
|
+
count !== entries.length ||
|
|
67
|
+
entries.some(([itemId]) => slots![`#${itemId}`] === undefined)
|
|
68
|
+
) {
|
|
69
|
+
const next: Record<string, number> = {};
|
|
70
|
+
count = 0;
|
|
71
|
+
for (const [itemId] of entries) {
|
|
72
|
+
const key = `#${itemId}`;
|
|
73
|
+
if (next[key] === undefined) next[key] = count++;
|
|
74
|
+
}
|
|
75
|
+
slots = next;
|
|
76
|
+
}
|
|
77
|
+
const rects = new Float64Array(count * 4);
|
|
78
|
+
for (const [itemId, rect] of entries) {
|
|
79
|
+
const offset = slots[`#${itemId}`] * 4;
|
|
80
|
+
rects[offset] = rect.x;
|
|
81
|
+
rects[offset + 1] = rect.y;
|
|
82
|
+
rects[offset + 2] = rect.width;
|
|
83
|
+
rects[offset + 3] = rect.height;
|
|
84
|
+
}
|
|
85
|
+
return { slots, rects };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Build a zone table from a `#itemId` keyed rect record. */
|
|
89
|
+
export function zonePresentationFromRects(
|
|
90
|
+
rects: Readonly<Record<string, LayoutRect>>,
|
|
91
|
+
): DndZonePresentation {
|
|
92
|
+
return createZonePresentation(
|
|
93
|
+
Object.entries(rects).map(([key, rect]) => [key.slice(1), rect] as const),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Read a zone table back as a `#itemId` keyed rect record. */
|
|
98
|
+
export function zonePresentationRects(
|
|
99
|
+
zone: DndZonePresentation | undefined,
|
|
100
|
+
): Record<string, LayoutRect> {
|
|
101
|
+
const result: Record<string, LayoutRect> = {};
|
|
102
|
+
if (!zone) return result;
|
|
103
|
+
for (const [key, slot] of Object.entries(zone.slots)) {
|
|
104
|
+
const offset = slot * 4;
|
|
105
|
+
result[key] = {
|
|
106
|
+
x: zone.rects[offset],
|
|
107
|
+
y: zone.rects[offset + 1],
|
|
108
|
+
width: zone.rects[offset + 2],
|
|
109
|
+
height: zone.rects[offset + 3],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { DndItem, LayoutZone } from '../contracts';
|
|
2
|
+
import type { DndSessionSnapshot } from '../controller/dndSession';
|
|
3
|
+
|
|
4
|
+
type DndSnapshotSource<T = unknown> = {
|
|
5
|
+
getSnapshot(): DndSessionSnapshot<T>;
|
|
6
|
+
subscribe(listener: () => void): () => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type DndZoneSnapshot = {
|
|
10
|
+
zone: LayoutZone | undefined;
|
|
11
|
+
display: LayoutZone | undefined;
|
|
12
|
+
items: readonly DndItem<unknown>[] | undefined;
|
|
13
|
+
revision: number;
|
|
14
|
+
displayRevision: number;
|
|
15
|
+
activeItemId: string | null;
|
|
16
|
+
scrollEnabled: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type DndItemSnapshot = {
|
|
20
|
+
index: number;
|
|
21
|
+
row?: number;
|
|
22
|
+
col?: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Answers once per drag session whether a zone keeps its native slots in
|
|
27
|
+
* committed order while `activeItemId` is dragged. Adapters whose mounted set
|
|
28
|
+
* depends on slot order (windowed lists) answer from their render window.
|
|
29
|
+
*/
|
|
30
|
+
export type RetainSlotsRule = (activeItemId: string) => boolean;
|
|
31
|
+
|
|
32
|
+
/** Native cells keep their slots during a preview. Shared geometry moves them. */
|
|
33
|
+
function retainNativeSlots(
|
|
34
|
+
previous: LayoutZone | undefined,
|
|
35
|
+
next: LayoutZone | undefined,
|
|
36
|
+
): LayoutZone | undefined {
|
|
37
|
+
if (!previous || !next || previous.kind !== next.kind) return next;
|
|
38
|
+
if (previous === next) return previous;
|
|
39
|
+
if (previous.kind === 'list' && next.kind === 'list') {
|
|
40
|
+
if (
|
|
41
|
+
previous.orientation !== next.orientation ||
|
|
42
|
+
previous.itemIds.length !== next.itemIds.length
|
|
43
|
+
)
|
|
44
|
+
return next;
|
|
45
|
+
const ids = new Set(previous.itemIds);
|
|
46
|
+
return next.itemIds.every(id => ids.has(id)) ? previous : next;
|
|
47
|
+
}
|
|
48
|
+
if (previous.kind === 'grid' && next.kind === 'grid') {
|
|
49
|
+
if (
|
|
50
|
+
previous.rows !== next.rows ||
|
|
51
|
+
previous.columns !== next.columns ||
|
|
52
|
+
previous.placements.length !== next.placements.length
|
|
53
|
+
)
|
|
54
|
+
return next;
|
|
55
|
+
const placements = new Map(
|
|
56
|
+
previous.placements.map(placement => [placement.itemId, placement]),
|
|
57
|
+
);
|
|
58
|
+
return next.placements.every(placement => {
|
|
59
|
+
const old = placements.get(placement.itemId);
|
|
60
|
+
return (
|
|
61
|
+
old &&
|
|
62
|
+
old.span.rows === placement.span.rows &&
|
|
63
|
+
old.span.cols === placement.span.cols &&
|
|
64
|
+
old.placement === placement.placement
|
|
65
|
+
);
|
|
66
|
+
})
|
|
67
|
+
? previous
|
|
68
|
+
: next;
|
|
69
|
+
}
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Separate React subscriptions from frame-by-frame approved presentation. */
|
|
74
|
+
export class DndRenderStore<T = unknown> {
|
|
75
|
+
private provider: DndSessionSnapshot<T> | undefined;
|
|
76
|
+
private zones = new Map<string, DndZoneSnapshot>();
|
|
77
|
+
private itemIndices = new WeakMap<LayoutZone, Map<string, DndItemSnapshot>>();
|
|
78
|
+
private items = new Map<string, Map<string, DndItemSnapshot>>();
|
|
79
|
+
private retainDecisions = new Map<
|
|
80
|
+
string,
|
|
81
|
+
{ sessionId: string | null; itemId: string; retain: boolean }
|
|
82
|
+
>();
|
|
83
|
+
|
|
84
|
+
constructor(private readonly source: DndSnapshotSource<T>) {}
|
|
85
|
+
|
|
86
|
+
getSnapshot = (): DndSessionSnapshot<T> => this.source.getSnapshot();
|
|
87
|
+
|
|
88
|
+
getProviderSnapshot = (): DndSessionSnapshot<T> => {
|
|
89
|
+
const next = this.source.getSnapshot();
|
|
90
|
+
const previous = this.provider;
|
|
91
|
+
if (
|
|
92
|
+
previous &&
|
|
93
|
+
previous.value === next.value &&
|
|
94
|
+
previous.phase === next.phase &&
|
|
95
|
+
previous.sessionId === next.sessionId &&
|
|
96
|
+
previous.itemId === next.itemId &&
|
|
97
|
+
previous.sourceZoneId === next.sourceZoneId &&
|
|
98
|
+
previous.targetZoneId === next.targetZoneId &&
|
|
99
|
+
previous.validity === next.validity &&
|
|
100
|
+
previous.disabled === next.disabled
|
|
101
|
+
)
|
|
102
|
+
return previous;
|
|
103
|
+
return (this.provider = next);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
getZoneSnapshot = (
|
|
107
|
+
zoneId: string,
|
|
108
|
+
preserveSlots: boolean | RetainSlotsRule = true,
|
|
109
|
+
): DndZoneSnapshot => {
|
|
110
|
+
const current = this.source.getSnapshot();
|
|
111
|
+
const zone = current.value?.zones.find(entry => entry.id === zoneId);
|
|
112
|
+
const items = current.value?.items;
|
|
113
|
+
const revision = current.value?.revision ?? 0;
|
|
114
|
+
const key = `${preserveSlots === false ? 'ordered' : 'stable'}:${zoneId}`;
|
|
115
|
+
const previous = this.zones.get(key);
|
|
116
|
+
const candidate = current.displayValue?.zones.find(
|
|
117
|
+
entry => entry.id === zoneId,
|
|
118
|
+
);
|
|
119
|
+
const display =
|
|
120
|
+
current.phase !== 'idle' && this.retainSlots(key, current, preserveSlots)
|
|
121
|
+
? retainNativeSlots(
|
|
122
|
+
previous &&
|
|
123
|
+
previous.zone === zone &&
|
|
124
|
+
previous.items === items &&
|
|
125
|
+
previous.revision === revision
|
|
126
|
+
? previous.display
|
|
127
|
+
: zone,
|
|
128
|
+
candidate,
|
|
129
|
+
)
|
|
130
|
+
: candidate;
|
|
131
|
+
// The revision of the value the display comes from: candidate geometry is
|
|
132
|
+
// prepared under the candidate's revision, exactly as the provider asks
|
|
133
|
+
// the adapter to prepare it for the UI runtime.
|
|
134
|
+
const displayRevision =
|
|
135
|
+
display === zone
|
|
136
|
+
? revision
|
|
137
|
+
: display === candidate
|
|
138
|
+
? (current.displayValue?.revision ?? revision)
|
|
139
|
+
: (previous?.displayRevision ?? revision);
|
|
140
|
+
const next: DndZoneSnapshot = {
|
|
141
|
+
zone,
|
|
142
|
+
display,
|
|
143
|
+
items,
|
|
144
|
+
revision,
|
|
145
|
+
displayRevision,
|
|
146
|
+
activeItemId: current.itemId,
|
|
147
|
+
scrollEnabled: current.phase === 'idle',
|
|
148
|
+
};
|
|
149
|
+
if (
|
|
150
|
+
previous &&
|
|
151
|
+
(Object.keys(next) as Array<keyof DndZoneSnapshot>).every(
|
|
152
|
+
field => previous[field] === next[field],
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
return previous;
|
|
156
|
+
this.zones.set(key, next);
|
|
157
|
+
return next;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** A rule is consulted once per drag session so its answer cannot flip mid-drag. */
|
|
161
|
+
private retainSlots(
|
|
162
|
+
key: string,
|
|
163
|
+
current: DndSessionSnapshot<T>,
|
|
164
|
+
preserveSlots: boolean | RetainSlotsRule,
|
|
165
|
+
): boolean {
|
|
166
|
+
if (typeof preserveSlots !== 'function') return preserveSlots;
|
|
167
|
+
if (current.itemId === null) return true;
|
|
168
|
+
const decision = this.retainDecisions.get(key);
|
|
169
|
+
if (
|
|
170
|
+
decision &&
|
|
171
|
+
decision.sessionId === current.sessionId &&
|
|
172
|
+
decision.itemId === current.itemId
|
|
173
|
+
)
|
|
174
|
+
return decision.retain;
|
|
175
|
+
const retain = preserveSlots(current.itemId);
|
|
176
|
+
this.retainDecisions.set(key, {
|
|
177
|
+
sessionId: current.sessionId,
|
|
178
|
+
itemId: current.itemId,
|
|
179
|
+
retain,
|
|
180
|
+
});
|
|
181
|
+
return retain;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getItemSnapshot = (
|
|
185
|
+
zoneId: string,
|
|
186
|
+
itemId: string,
|
|
187
|
+
): DndItemSnapshot | undefined => {
|
|
188
|
+
const zone = this.source
|
|
189
|
+
.getSnapshot()
|
|
190
|
+
.displayValue?.zones.find(entry => entry.id === zoneId);
|
|
191
|
+
let indices = zone && this.itemIndices.get(zone);
|
|
192
|
+
if (zone && !indices) {
|
|
193
|
+
const prepared = new Map<string, DndItemSnapshot>();
|
|
194
|
+
if (zone.kind === 'list')
|
|
195
|
+
zone.itemIds.forEach((id, index) => prepared.set(id, { index }));
|
|
196
|
+
else
|
|
197
|
+
zone.placements.forEach((entry, index) =>
|
|
198
|
+
prepared.set(entry.itemId, {
|
|
199
|
+
index,
|
|
200
|
+
row: entry.position.row,
|
|
201
|
+
col: entry.position.col,
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
// Many mounted cells read the same approved display in one notification.
|
|
205
|
+
// Build its ID index once instead of scanning the zone for every cell.
|
|
206
|
+
this.itemIndices.set(zone, prepared);
|
|
207
|
+
indices = prepared;
|
|
208
|
+
}
|
|
209
|
+
const next = indices?.get(itemId);
|
|
210
|
+
let selected = this.items.get(zoneId);
|
|
211
|
+
if (!next) {
|
|
212
|
+
selected?.delete(itemId);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const previous = selected?.get(itemId);
|
|
216
|
+
if (
|
|
217
|
+
previous &&
|
|
218
|
+
previous.index === next.index &&
|
|
219
|
+
previous.row === next.row &&
|
|
220
|
+
previous.col === next.col
|
|
221
|
+
)
|
|
222
|
+
return previous;
|
|
223
|
+
if (!selected) {
|
|
224
|
+
selected = new Map();
|
|
225
|
+
this.items.set(zoneId, selected);
|
|
226
|
+
}
|
|
227
|
+
selected.set(itemId, next);
|
|
228
|
+
return next;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
subscribeSelection<Selection>(
|
|
232
|
+
get: () => Selection,
|
|
233
|
+
listener: () => void,
|
|
234
|
+
): () => void {
|
|
235
|
+
let previous = get();
|
|
236
|
+
return this.source.subscribe(() => {
|
|
237
|
+
const next = get();
|
|
238
|
+
if (Object.is(previous, next)) return;
|
|
239
|
+
previous = next;
|
|
240
|
+
listener();
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
subscribeProvider = (listener: () => void): (() => void) =>
|
|
245
|
+
this.subscribeSelection(this.getProviderSnapshot, listener);
|
|
246
|
+
}
|