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,584 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CellPosition,
|
|
3
|
+
CellSpan,
|
|
4
|
+
GridZone,
|
|
5
|
+
ItemLocation,
|
|
6
|
+
SpatialZone,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import { DEFAULT_GRID_MOVEMENT_POLICY } from '../engine/movementPolicy';
|
|
9
|
+
import { selectSpatialMove } from '../engine/spatialPolicy';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
CellGeometry,
|
|
13
|
+
MeasuredZone,
|
|
14
|
+
UiMotion,
|
|
15
|
+
WindowRect,
|
|
16
|
+
} from '../runtime/gridRuntime';
|
|
17
|
+
import type { ZoneGeometry } from '../components/gridTypes';
|
|
18
|
+
|
|
19
|
+
function clamp(value: number, min: number, max: number): number {
|
|
20
|
+
'worklet';
|
|
21
|
+
return Math.max(min, Math.min(max, value));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The cell an ordered slot starts at. The engine owns the same arithmetic in
|
|
26
|
+
* `slotPosition`; that module stays free of worklet directives, so the pointer
|
|
27
|
+
* keeps a worklet copy and `tests/pointer/orderedSlots.test.ts` pins them
|
|
28
|
+
* together.
|
|
29
|
+
*/
|
|
30
|
+
export function orderedSlotPosition(
|
|
31
|
+
index: number,
|
|
32
|
+
itemSpan: CellSpan,
|
|
33
|
+
columns: number,
|
|
34
|
+
): CellPosition {
|
|
35
|
+
'worklet';
|
|
36
|
+
const perRow = Math.floor(columns / itemSpan.cols);
|
|
37
|
+
return {
|
|
38
|
+
row: Math.floor(index / perRow) * itemSpan.rows,
|
|
39
|
+
col: (index % perRow) * itemSpan.cols,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The ordered slot a cell starts, or null when it is not a slot origin. */
|
|
44
|
+
export function orderedSlotIndex(
|
|
45
|
+
position: CellPosition,
|
|
46
|
+
itemSpan: CellSpan,
|
|
47
|
+
columns: number,
|
|
48
|
+
): number | null {
|
|
49
|
+
'worklet';
|
|
50
|
+
const perRow = Math.floor(columns / itemSpan.cols);
|
|
51
|
+
if (
|
|
52
|
+
perRow <= 0 ||
|
|
53
|
+
position.row < 0 ||
|
|
54
|
+
position.col < 0 ||
|
|
55
|
+
position.row % itemSpan.rows !== 0 ||
|
|
56
|
+
position.col % itemSpan.cols !== 0
|
|
57
|
+
)
|
|
58
|
+
return null;
|
|
59
|
+
const col = position.col / itemSpan.cols;
|
|
60
|
+
if (col >= perRow) return null;
|
|
61
|
+
return (position.row / itemSpan.rows) * perRow + col;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isUsableRect(rect: WindowRect | null): rect is WindowRect {
|
|
65
|
+
'worklet';
|
|
66
|
+
return (
|
|
67
|
+
!!rect &&
|
|
68
|
+
Number.isFinite(rect.x) &&
|
|
69
|
+
Number.isFinite(rect.y) &&
|
|
70
|
+
Number.isFinite(rect.width) &&
|
|
71
|
+
Number.isFinite(rect.height) &&
|
|
72
|
+
rect.width > 0 &&
|
|
73
|
+
rect.height > 0
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function rectContains(rect: WindowRect, x: number, y: number): boolean {
|
|
78
|
+
'worklet';
|
|
79
|
+
return (
|
|
80
|
+
x >= rect.x &&
|
|
81
|
+
y >= rect.y &&
|
|
82
|
+
x <= rect.x + rect.width &&
|
|
83
|
+
y <= rect.y + rect.height
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function containsRectangle(
|
|
88
|
+
outer: WindowRect,
|
|
89
|
+
inner: WindowRect,
|
|
90
|
+
pixelRatio: number,
|
|
91
|
+
): boolean {
|
|
92
|
+
'worklet';
|
|
93
|
+
// Compare actual rendered edges. Native layout uses Float32 and snaps edges
|
|
94
|
+
// relative to the root; widening a hit box would also accept clipped pixels.
|
|
95
|
+
return (
|
|
96
|
+
Math.round(inner.x * pixelRatio) >= Math.round(outer.x * pixelRatio) &&
|
|
97
|
+
Math.round(inner.y * pixelRatio) >= Math.round(outer.y * pixelRatio) &&
|
|
98
|
+
Math.round((inner.x + inner.width) * pixelRatio) <=
|
|
99
|
+
Math.round((outer.x + outer.width) * pixelRatio) &&
|
|
100
|
+
Math.round((inner.y + inner.height) * pixelRatio) <=
|
|
101
|
+
Math.round((outer.y + outer.height) * pixelRatio)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getCellGeometry(
|
|
106
|
+
zone: Pick<GridZone<unknown>, 'rows' | 'columns'>,
|
|
107
|
+
geometry: ZoneGeometry,
|
|
108
|
+
width: number,
|
|
109
|
+
height: number,
|
|
110
|
+
pixelRatio = 1,
|
|
111
|
+
): CellGeometry | null {
|
|
112
|
+
'worklet';
|
|
113
|
+
if (
|
|
114
|
+
!zone ||
|
|
115
|
+
typeof zone !== 'object' ||
|
|
116
|
+
Array.isArray(zone) ||
|
|
117
|
+
!geometry ||
|
|
118
|
+
typeof geometry !== 'object' ||
|
|
119
|
+
Array.isArray(geometry) ||
|
|
120
|
+
(geometry.mode !== 'fixed' && geometry.mode !== 'fit') ||
|
|
121
|
+
!geometry.padding ||
|
|
122
|
+
typeof geometry.padding !== 'object' ||
|
|
123
|
+
Array.isArray(geometry.padding) ||
|
|
124
|
+
!Number.isSafeInteger(zone.rows) ||
|
|
125
|
+
zone.rows <= 0 ||
|
|
126
|
+
!Number.isSafeInteger(zone.columns) ||
|
|
127
|
+
zone.columns <= 0 ||
|
|
128
|
+
!Number.isFinite(width) ||
|
|
129
|
+
width <= 0 ||
|
|
130
|
+
!Number.isFinite(height) ||
|
|
131
|
+
height <= 0 ||
|
|
132
|
+
!Number.isFinite(pixelRatio) ||
|
|
133
|
+
pixelRatio <= 0
|
|
134
|
+
)
|
|
135
|
+
return null;
|
|
136
|
+
const { padding, rowGap, columnGap } = geometry;
|
|
137
|
+
if (
|
|
138
|
+
![
|
|
139
|
+
padding.top,
|
|
140
|
+
padding.right,
|
|
141
|
+
padding.bottom,
|
|
142
|
+
padding.left,
|
|
143
|
+
rowGap,
|
|
144
|
+
columnGap,
|
|
145
|
+
].every(value => Number.isFinite(value) && value >= 0)
|
|
146
|
+
)
|
|
147
|
+
return null;
|
|
148
|
+
const cellWidth =
|
|
149
|
+
geometry.mode === 'fixed'
|
|
150
|
+
? geometry.cellWidth
|
|
151
|
+
: (width -
|
|
152
|
+
padding.left -
|
|
153
|
+
padding.right -
|
|
154
|
+
(zone.columns - 1) * columnGap) /
|
|
155
|
+
zone.columns;
|
|
156
|
+
const cellHeight =
|
|
157
|
+
geometry.mode === 'fixed'
|
|
158
|
+
? geometry.cellHeight
|
|
159
|
+
: (height - padding.top - padding.bottom - (zone.rows - 1) * rowGap) /
|
|
160
|
+
zone.rows;
|
|
161
|
+
const contentWidth =
|
|
162
|
+
zone.columns * cellWidth + (zone.columns - 1) * columnGap;
|
|
163
|
+
const contentHeight = zone.rows * cellHeight + (zone.rows - 1) * rowGap;
|
|
164
|
+
const requiredWidth = contentWidth + padding.left + padding.right;
|
|
165
|
+
const requiredHeight = contentHeight + padding.top + padding.bottom;
|
|
166
|
+
// A native extent is the difference between two rounded root-relative edges.
|
|
167
|
+
// That can be slightly smaller than the requested size (less than one pixel).
|
|
168
|
+
// Preserve explicit cell sizes; final targeting still checks rendered edges.
|
|
169
|
+
const pixelSize = 1 / pixelRatio;
|
|
170
|
+
return Number.isFinite(requiredWidth) &&
|
|
171
|
+
Number.isFinite(requiredHeight) &&
|
|
172
|
+
(geometry.mode !== 'fixed' ||
|
|
173
|
+
(requiredWidth - width < pixelSize &&
|
|
174
|
+
requiredHeight - height < pixelSize)) &&
|
|
175
|
+
Number.isFinite(cellWidth) &&
|
|
176
|
+
cellWidth > 0 &&
|
|
177
|
+
Number.isFinite(cellHeight) &&
|
|
178
|
+
cellHeight > 0
|
|
179
|
+
? { cellWidth, cellHeight }
|
|
180
|
+
: null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Item rectangle relative to its zone, including the zone's content padding. */
|
|
184
|
+
export function itemRect(
|
|
185
|
+
position: CellPosition,
|
|
186
|
+
span: CellSpan,
|
|
187
|
+
geometry: ZoneGeometry,
|
|
188
|
+
cells: CellGeometry,
|
|
189
|
+
): WindowRect {
|
|
190
|
+
'worklet';
|
|
191
|
+
return {
|
|
192
|
+
x:
|
|
193
|
+
geometry.padding.left +
|
|
194
|
+
position.col * (cells.cellWidth + geometry.columnGap),
|
|
195
|
+
y:
|
|
196
|
+
geometry.padding.top +
|
|
197
|
+
position.row * (cells.cellHeight + geometry.rowGap),
|
|
198
|
+
width: span.cols * cells.cellWidth + (span.cols - 1) * geometry.columnGap,
|
|
199
|
+
height: span.rows * cells.cellHeight + (span.rows - 1) * geometry.rowGap,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Check the resolved placement, which a region exchange may shift from the pointer target. */
|
|
204
|
+
export function isPlacementVisible(
|
|
205
|
+
zones: readonly MeasuredZone[],
|
|
206
|
+
outlet: WindowRect,
|
|
207
|
+
target: ItemLocation,
|
|
208
|
+
span: CellSpan,
|
|
209
|
+
): boolean {
|
|
210
|
+
'worklet';
|
|
211
|
+
const measured = zones.find(entry => entry.zone.id === target.zoneId);
|
|
212
|
+
if (
|
|
213
|
+
!measured ||
|
|
214
|
+
measured.zone.strategy !== target.strategy ||
|
|
215
|
+
!isUsableRect(measured.rect) ||
|
|
216
|
+
!isUsableRect(outlet) ||
|
|
217
|
+
!Number.isFinite(measured.pixelRatio) ||
|
|
218
|
+
measured.pixelRatio <= 0 ||
|
|
219
|
+
!Number.isSafeInteger(span.rows) ||
|
|
220
|
+
!Number.isSafeInteger(span.cols) ||
|
|
221
|
+
span.rows <= 0 ||
|
|
222
|
+
span.cols <= 0
|
|
223
|
+
)
|
|
224
|
+
return false;
|
|
225
|
+
const { zone, rect, geometry, cells } = measured;
|
|
226
|
+
let position: CellPosition;
|
|
227
|
+
if (zone.strategy === 'ordered' && target.strategy === 'ordered') {
|
|
228
|
+
if (
|
|
229
|
+
span.rows !== zone.itemSpan.rows ||
|
|
230
|
+
span.cols !== zone.itemSpan.cols ||
|
|
231
|
+
!Number.isSafeInteger(target.index) ||
|
|
232
|
+
target.index < 0 ||
|
|
233
|
+
target.index > zone.items.length
|
|
234
|
+
)
|
|
235
|
+
return false;
|
|
236
|
+
const slotsPerRow = Math.floor(zone.columns / span.cols);
|
|
237
|
+
if (slotsPerRow <= 0) return false;
|
|
238
|
+
position = {
|
|
239
|
+
row: Math.floor(target.index / slotsPerRow) * span.rows,
|
|
240
|
+
col: (target.index % slotsPerRow) * span.cols,
|
|
241
|
+
};
|
|
242
|
+
} else if (target.strategy === 'spatial') {
|
|
243
|
+
position = target.position;
|
|
244
|
+
} else {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (
|
|
248
|
+
!Number.isSafeInteger(position.row) ||
|
|
249
|
+
!Number.isSafeInteger(position.col) ||
|
|
250
|
+
position.row < 0 ||
|
|
251
|
+
position.col < 0 ||
|
|
252
|
+
position.row > zone.rows - span.rows ||
|
|
253
|
+
position.col > zone.columns - span.cols
|
|
254
|
+
)
|
|
255
|
+
return false;
|
|
256
|
+
const anchored = itemRect(position, span, geometry, cells);
|
|
257
|
+
const windowTarget = {
|
|
258
|
+
...anchored,
|
|
259
|
+
x: rect.x + anchored.x,
|
|
260
|
+
y: rect.y + anchored.y,
|
|
261
|
+
};
|
|
262
|
+
return (
|
|
263
|
+
isUsableRect(windowTarget) &&
|
|
264
|
+
containsRectangle(rect, windowTarget, measured.pixelRatio) &&
|
|
265
|
+
containsRectangle(outlet, windowTarget, measured.pixelRatio)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function sameTarget(
|
|
270
|
+
left: ItemLocation | null,
|
|
271
|
+
right: ItemLocation | null,
|
|
272
|
+
): boolean {
|
|
273
|
+
'worklet';
|
|
274
|
+
if (!left || !right) return left === right;
|
|
275
|
+
if (left.zoneId !== right.zoneId || left.strategy !== right.strategy)
|
|
276
|
+
return false;
|
|
277
|
+
return left.strategy === 'ordered' && right.strategy === 'ordered'
|
|
278
|
+
? left.index === right.index
|
|
279
|
+
: left.strategy === 'spatial' &&
|
|
280
|
+
right.strategy === 'spatial' &&
|
|
281
|
+
left.position.row === right.position.row &&
|
|
282
|
+
left.position.col === right.position.col;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function sourceItemTarget(
|
|
286
|
+
measured: MeasuredZone,
|
|
287
|
+
itemId: string,
|
|
288
|
+
): ItemLocation | null {
|
|
289
|
+
'worklet';
|
|
290
|
+
const { zone } = measured;
|
|
291
|
+
if (zone.strategy === 'ordered') {
|
|
292
|
+
const index = zone.items.findIndex(item => item.id === itemId);
|
|
293
|
+
return index < 0 ? null : { zoneId: zone.id, strategy: 'ordered', index };
|
|
294
|
+
}
|
|
295
|
+
const item = zone.items.find(candidate => candidate.id === itemId);
|
|
296
|
+
return item
|
|
297
|
+
? { zoneId: zone.id, strategy: 'spatial', position: { ...item.position } }
|
|
298
|
+
: null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Worklet equivalent of computeOrderedIndex for already validated measured zones. */
|
|
302
|
+
export function orderedIndexForCenter(
|
|
303
|
+
measured: MeasuredZone,
|
|
304
|
+
itemId: string | null,
|
|
305
|
+
centerX: number,
|
|
306
|
+
centerY: number,
|
|
307
|
+
): number | null {
|
|
308
|
+
'worklet';
|
|
309
|
+
const { zone, geometry, cells } = measured;
|
|
310
|
+
if (zone.strategy !== 'ordered') return null;
|
|
311
|
+
const span = zone.itemSpan;
|
|
312
|
+
const slotsPerRow = Math.floor(zone.columns / span.cols);
|
|
313
|
+
const slotRows = Math.floor(zone.rows / span.rows);
|
|
314
|
+
if (slotsPerRow === 0 || slotRows === 0) return null;
|
|
315
|
+
const itemCount = zone.items.reduce(
|
|
316
|
+
(count, item) => count + Number(item.id !== itemId),
|
|
317
|
+
0,
|
|
318
|
+
);
|
|
319
|
+
const slotWidth =
|
|
320
|
+
span.cols * cells.cellWidth + (span.cols - 1) * geometry.columnGap;
|
|
321
|
+
const slotHeight =
|
|
322
|
+
span.rows * cells.cellHeight + (span.rows - 1) * geometry.rowGap;
|
|
323
|
+
const strideX = slotWidth + geometry.columnGap;
|
|
324
|
+
const strideY = slotHeight + geometry.rowGap;
|
|
325
|
+
const width =
|
|
326
|
+
zone.columns * cells.cellWidth + (zone.columns - 1) * geometry.columnGap;
|
|
327
|
+
const height =
|
|
328
|
+
zone.rows * cells.cellHeight + (zone.rows - 1) * geometry.rowGap;
|
|
329
|
+
const x = clamp(centerX, 0, width);
|
|
330
|
+
const y = clamp(centerY, 0, height);
|
|
331
|
+
if (zone.rows % span.rows !== 0 && y >= slotRows * strideY - geometry.rowGap)
|
|
332
|
+
return itemCount;
|
|
333
|
+
const row = clamp(
|
|
334
|
+
Math.floor((y - slotHeight / 2) / strideY + 0.5),
|
|
335
|
+
0,
|
|
336
|
+
slotRows - 1,
|
|
337
|
+
);
|
|
338
|
+
if (
|
|
339
|
+
zone.columns % span.cols !== 0 &&
|
|
340
|
+
x >= slotsPerRow * strideX - geometry.columnGap
|
|
341
|
+
)
|
|
342
|
+
return Math.min(itemCount, (row + 1) * slotsPerRow);
|
|
343
|
+
const col = clamp(
|
|
344
|
+
Math.floor((x - slotWidth / 2) / strideX + 0.5),
|
|
345
|
+
0,
|
|
346
|
+
slotsPerRow - 1,
|
|
347
|
+
);
|
|
348
|
+
const slot = row * slotsPerRow + col;
|
|
349
|
+
if (slot >= itemCount) return itemCount;
|
|
350
|
+
const after =
|
|
351
|
+
slotsPerRow === 1
|
|
352
|
+
? y >= row * strideY + slotHeight / 2
|
|
353
|
+
: x >= col * strideX + slotWidth / 2;
|
|
354
|
+
return Math.min(itemCount, slot + Number(after));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function stableAnchor(
|
|
358
|
+
value: number,
|
|
359
|
+
previous: number | undefined,
|
|
360
|
+
maximum: number,
|
|
361
|
+
hysteresis: number,
|
|
362
|
+
): number {
|
|
363
|
+
'worklet';
|
|
364
|
+
if (
|
|
365
|
+
previous !== undefined &&
|
|
366
|
+
previous >= 0 &&
|
|
367
|
+
previous <= maximum &&
|
|
368
|
+
Math.abs(value - previous) <= 0.5 + hysteresis
|
|
369
|
+
)
|
|
370
|
+
return previous;
|
|
371
|
+
return clamp(Math.round(value), 0, maximum);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function stableExchangeAnchor(
|
|
375
|
+
value: number,
|
|
376
|
+
previous: number,
|
|
377
|
+
maximum: number,
|
|
378
|
+
threshold: number,
|
|
379
|
+
): number {
|
|
380
|
+
'worklet';
|
|
381
|
+
if (Math.abs(value - previous) <= threshold) return previous;
|
|
382
|
+
// Quantize each crossed boundary so a fast jump cannot bypass the threshold.
|
|
383
|
+
return clamp(
|
|
384
|
+
value > previous
|
|
385
|
+
? Math.floor(value + (1 - threshold))
|
|
386
|
+
: Math.ceil(value - (1 - threshold)),
|
|
387
|
+
0,
|
|
388
|
+
maximum,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Use the same explicit placement policy as the engine, without application data. */
|
|
393
|
+
function exchangeOrigin(
|
|
394
|
+
current: UiMotion,
|
|
395
|
+
zone: SpatialZone<undefined>,
|
|
396
|
+
position: CellPosition,
|
|
397
|
+
): CellPosition | null {
|
|
398
|
+
'worklet';
|
|
399
|
+
if (zone.id !== current.sourceZoneId) return null;
|
|
400
|
+
const active = zone.items.find(item => item.id === current.itemId);
|
|
401
|
+
if (!active) return null;
|
|
402
|
+
const { move, kind, colliders } = selectSpatialMove({
|
|
403
|
+
items: zone.items.filter(item => item.id !== active.id),
|
|
404
|
+
active: { ...active, position },
|
|
405
|
+
origin: active,
|
|
406
|
+
rows: zone.rows,
|
|
407
|
+
columns: zone.columns,
|
|
408
|
+
searchBudget: 1,
|
|
409
|
+
movementPolicy: current.movementPolicy,
|
|
410
|
+
});
|
|
411
|
+
if (kind === 'exchange') return colliders.length > 0 ? active.position : null;
|
|
412
|
+
if (kind !== 'row-rotation' || !move.origin) return null;
|
|
413
|
+
const corridorTop = Math.min(
|
|
414
|
+
move.origin.position.row,
|
|
415
|
+
move.active.position.row,
|
|
416
|
+
);
|
|
417
|
+
const corridorBottom =
|
|
418
|
+
Math.max(move.origin.position.row, move.active.position.row) +
|
|
419
|
+
move.active.span.rows;
|
|
420
|
+
const exchanges = move.items.some(
|
|
421
|
+
item =>
|
|
422
|
+
corridorTop < item.position.row + item.span.rows &&
|
|
423
|
+
corridorBottom > item.position.row,
|
|
424
|
+
);
|
|
425
|
+
return exchanges ? active.position : null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function selectZone(
|
|
429
|
+
current: UiMotion,
|
|
430
|
+
x: number,
|
|
431
|
+
y: number,
|
|
432
|
+
): MeasuredZone | null {
|
|
433
|
+
'worklet';
|
|
434
|
+
if (!rectContains(current.outlet, x, y)) return null;
|
|
435
|
+
const previousId =
|
|
436
|
+
current.target?.zoneId ?? (current.seq === 0 ? current.sourceZoneId : null);
|
|
437
|
+
for (const zone of current.zones) {
|
|
438
|
+
if (zone.zone.id === previousId && rectContains(zone.rect, x, y))
|
|
439
|
+
return zone;
|
|
440
|
+
}
|
|
441
|
+
for (const zone of current.zones) {
|
|
442
|
+
const boundary =
|
|
443
|
+
current.movementPolicy?.pointer?.boundaryHysteresis ??
|
|
444
|
+
DEFAULT_GRID_MOVEMENT_POLICY.pointer.boundaryHysteresis;
|
|
445
|
+
const marginX = Math.min(boundary, zone.rect.width / 4);
|
|
446
|
+
const marginY = Math.min(boundary, zone.rect.height / 4);
|
|
447
|
+
if (
|
|
448
|
+
x >= zone.rect.x + marginX &&
|
|
449
|
+
x <= zone.rect.x + zone.rect.width - marginX &&
|
|
450
|
+
y >= zone.rect.y + marginY &&
|
|
451
|
+
y <= zone.rect.y + zone.rect.height - marginY
|
|
452
|
+
)
|
|
453
|
+
return zone;
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
type PointerTarget = Pick<UiMotion, 'target' | 'x' | 'y' | 'width' | 'height'>;
|
|
459
|
+
|
|
460
|
+
/** Preview movement and release use this same hysteresis-adjusted logical target. */
|
|
461
|
+
export function readPointerTarget(
|
|
462
|
+
current: UiMotion,
|
|
463
|
+
absoluteX: number,
|
|
464
|
+
absoluteY: number,
|
|
465
|
+
): PointerTarget {
|
|
466
|
+
'worklet';
|
|
467
|
+
const outside: PointerTarget = {
|
|
468
|
+
target: null,
|
|
469
|
+
x: absoluteX - current.gripX * current.width,
|
|
470
|
+
y: absoluteY - current.gripY * current.height,
|
|
471
|
+
width: current.width,
|
|
472
|
+
height: current.height,
|
|
473
|
+
};
|
|
474
|
+
if (!Number.isFinite(absoluteX) || !Number.isFinite(absoluteY))
|
|
475
|
+
return { ...outside, x: current.x, y: current.y };
|
|
476
|
+
const measured = selectZone(current, absoluteX, absoluteY);
|
|
477
|
+
if (!measured) return outside;
|
|
478
|
+
const { zone, rect, geometry, cells } = measured;
|
|
479
|
+
const { span } = current;
|
|
480
|
+
const sensitivity = current.movementPolicy?.pointer;
|
|
481
|
+
const cellHysteresis =
|
|
482
|
+
sensitivity?.cellHysteresis ??
|
|
483
|
+
DEFAULT_GRID_MOVEMENT_POLICY.pointer.cellHysteresis;
|
|
484
|
+
const exchangeThreshold =
|
|
485
|
+
sensitivity?.exchangeThreshold ??
|
|
486
|
+
DEFAULT_GRID_MOVEMENT_POLICY.pointer.exchangeThreshold;
|
|
487
|
+
const boundaryHysteresis =
|
|
488
|
+
sensitivity?.boundaryHysteresis ??
|
|
489
|
+
DEFAULT_GRID_MOVEMENT_POLICY.pointer.boundaryHysteresis;
|
|
490
|
+
if (span.rows > zone.rows || span.cols > zone.columns) return outside;
|
|
491
|
+
const size = itemRect({ row: 0, col: 0 }, span, geometry, cells);
|
|
492
|
+
if (!isUsableRect(size)) return outside;
|
|
493
|
+
const x = absoluteX - current.gripX * size.width;
|
|
494
|
+
const y = absoluteY - current.gripY * size.height;
|
|
495
|
+
const contentX = x - rect.x - geometry.padding.left;
|
|
496
|
+
const contentY = y - rect.y - geometry.padding.top;
|
|
497
|
+
const previous = current.target?.zoneId === zone.id ? current.target : null;
|
|
498
|
+
let target: ItemLocation;
|
|
499
|
+
let position: CellPosition;
|
|
500
|
+
if (zone.strategy === 'spatial') {
|
|
501
|
+
const old =
|
|
502
|
+
previous?.strategy === 'spatial' ? previous.position : undefined;
|
|
503
|
+
const row = contentY / (cells.cellHeight + geometry.rowGap);
|
|
504
|
+
const col = contentX / (cells.cellWidth + geometry.columnGap);
|
|
505
|
+
position = {
|
|
506
|
+
row: stableAnchor(row, old?.row, zone.rows - span.rows, cellHysteresis),
|
|
507
|
+
col: stableAnchor(
|
|
508
|
+
col,
|
|
509
|
+
old?.col,
|
|
510
|
+
zone.columns - span.cols,
|
|
511
|
+
cellHysteresis,
|
|
512
|
+
),
|
|
513
|
+
};
|
|
514
|
+
// Classify the nearest cell before hysteresis so a configured exchange
|
|
515
|
+
// threshold below the ordinary cell threshold can still take effect.
|
|
516
|
+
const origin = exchangeOrigin(current, zone, {
|
|
517
|
+
row: clamp(Math.round(row), 0, zone.rows - span.rows),
|
|
518
|
+
col: clamp(Math.round(col), 0, zone.columns - span.cols),
|
|
519
|
+
});
|
|
520
|
+
if (origin) {
|
|
521
|
+
// Re-entering the source zone must not bypass the exchange threshold.
|
|
522
|
+
// A full-width widget can move intervening items even at an empty target.
|
|
523
|
+
// Empty corridors and insertion retain their existing immediate targeting.
|
|
524
|
+
const reference = old ?? origin;
|
|
525
|
+
position = {
|
|
526
|
+
row: stableExchangeAnchor(
|
|
527
|
+
row,
|
|
528
|
+
reference.row,
|
|
529
|
+
zone.rows - span.rows,
|
|
530
|
+
exchangeThreshold,
|
|
531
|
+
),
|
|
532
|
+
col: stableExchangeAnchor(
|
|
533
|
+
col,
|
|
534
|
+
reference.col,
|
|
535
|
+
zone.columns - span.cols,
|
|
536
|
+
exchangeThreshold,
|
|
537
|
+
),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
target = { zoneId: zone.id, strategy: 'spatial', position };
|
|
541
|
+
} else {
|
|
542
|
+
const centerX = contentX + size.width / 2;
|
|
543
|
+
const centerY = contentY + size.height / 2;
|
|
544
|
+
let index = orderedIndexForCenter(
|
|
545
|
+
measured,
|
|
546
|
+
current.itemId,
|
|
547
|
+
centerX,
|
|
548
|
+
centerY,
|
|
549
|
+
);
|
|
550
|
+
if (index === null) return outside;
|
|
551
|
+
if (previous?.strategy === 'ordered' && previous.index !== index) {
|
|
552
|
+
const slot = itemRect({ row: 0, col: 0 }, zone.itemSpan, geometry, cells);
|
|
553
|
+
const marginX = Math.min(boundaryHysteresis, slot.width / 4);
|
|
554
|
+
const marginY = Math.min(boundaryHysteresis, slot.height / 4);
|
|
555
|
+
for (const dx of [-marginX, marginX]) {
|
|
556
|
+
for (const dy of [-marginY, marginY]) {
|
|
557
|
+
if (
|
|
558
|
+
orderedIndexForCenter(
|
|
559
|
+
measured,
|
|
560
|
+
current.itemId,
|
|
561
|
+
centerX + dx,
|
|
562
|
+
centerY + dy,
|
|
563
|
+
) === previous.index
|
|
564
|
+
)
|
|
565
|
+
index = previous.index;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
position = orderedSlotPosition(index, zone.itemSpan, zone.columns);
|
|
570
|
+
target = { zoneId: zone.id, strategy: 'ordered', index };
|
|
571
|
+
}
|
|
572
|
+
const anchored = itemRect(position, span, geometry, cells);
|
|
573
|
+
const windowTarget = {
|
|
574
|
+
...anchored,
|
|
575
|
+
x: rect.x + anchored.x,
|
|
576
|
+
y: rect.y + anchored.y,
|
|
577
|
+
};
|
|
578
|
+
if (
|
|
579
|
+
!containsRectangle(rect, windowTarget, measured.pixelRatio) ||
|
|
580
|
+
!containsRectangle(current.outlet, windowTarget, measured.pixelRatio)
|
|
581
|
+
)
|
|
582
|
+
return outside;
|
|
583
|
+
return { target, x, y, width: size.width, height: size.height };
|
|
584
|
+
}
|