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.
Files changed (340) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +262 -0
  3. package/lib/module/adapters/coordinates.js +135 -0
  4. package/lib/module/adapters/coordinates.js.map +1 -0
  5. package/lib/module/adapters/scroll.js +81 -0
  6. package/lib/module/adapters/scroll.js.map +1 -0
  7. package/lib/module/adapters/virtualizedList.js +69 -0
  8. package/lib/module/adapters/virtualizedList.js.map +1 -0
  9. package/lib/module/components/DndProvider.js +1144 -0
  10. package/lib/module/components/DndProvider.js.map +1 -0
  11. package/lib/module/components/DragHandle.js +65 -0
  12. package/lib/module/components/DragHandle.js.map +1 -0
  13. package/lib/module/components/GridController.js +151 -0
  14. package/lib/module/components/GridController.js.map +1 -0
  15. package/lib/module/components/GridDragHandle.js +15 -0
  16. package/lib/module/components/GridDragHandle.js.map +1 -0
  17. package/lib/module/components/GridZoneView.js +59 -0
  18. package/lib/module/components/GridZoneView.js.map +1 -0
  19. package/lib/module/components/SortableGrid.js +305 -0
  20. package/lib/module/components/SortableGrid.js.map +1 -0
  21. package/lib/module/components/SortableList.js +714 -0
  22. package/lib/module/components/SortableList.js.map +1 -0
  23. package/lib/module/components/dndTypes.js +4 -0
  24. package/lib/module/components/dndTypes.js.map +1 -0
  25. package/lib/module/components/gridTypes.js +4 -0
  26. package/lib/module/components/gridTypes.js.map +1 -0
  27. package/lib/module/components/handleViewProps.js +13 -0
  28. package/lib/module/components/handleViewProps.js.map +1 -0
  29. package/lib/module/contracts.js +4 -0
  30. package/lib/module/contracts.js.map +1 -0
  31. package/lib/module/controller/approval.js +98 -0
  32. package/lib/module/controller/approval.js.map +1 -0
  33. package/lib/module/controller/coordinatorBase.js +93 -0
  34. package/lib/module/controller/coordinatorBase.js.map +1 -0
  35. package/lib/module/controller/dndSession.js +621 -0
  36. package/lib/module/controller/dndSession.js.map +1 -0
  37. package/lib/module/controller/dndState.js +103 -0
  38. package/lib/module/controller/dndState.js.map +1 -0
  39. package/lib/module/controller/gridAdapter.js +191 -0
  40. package/lib/module/controller/gridAdapter.js.map +1 -0
  41. package/lib/module/controller/gridState.js +101 -0
  42. package/lib/module/controller/gridState.js.map +1 -0
  43. package/lib/module/controller/locations.js +48 -0
  44. package/lib/module/controller/locations.js.map +1 -0
  45. package/lib/module/controller/previewPath.js +73 -0
  46. package/lib/module/controller/previewPath.js.map +1 -0
  47. package/lib/module/controller/sessionCore.js +222 -0
  48. package/lib/module/controller/sessionCore.js.map +1 -0
  49. package/lib/module/controller/useDndState.js +9 -0
  50. package/lib/module/controller/useDndState.js.map +1 -0
  51. package/lib/module/controller/useGridState.js +9 -0
  52. package/lib/module/controller/useGridState.js.map +1 -0
  53. package/lib/module/controller/useStoreState.js +15 -0
  54. package/lib/module/controller/useStoreState.js.map +1 -0
  55. package/lib/module/defaults.js +7 -0
  56. package/lib/module/defaults.js.map +1 -0
  57. package/lib/module/diagnostics.js +16 -0
  58. package/lib/module/diagnostics.js.map +1 -0
  59. package/lib/module/engine/gridPlacements.js +54 -0
  60. package/lib/module/engine/gridPlacements.js.map +1 -0
  61. package/lib/module/engine/index.js +19 -0
  62. package/lib/module/engine/index.js.map +1 -0
  63. package/lib/module/engine/layout.js +36 -0
  64. package/lib/module/engine/layout.js.map +1 -0
  65. package/lib/module/engine/layoutMove.js +326 -0
  66. package/lib/module/engine/layoutMove.js.map +1 -0
  67. package/lib/module/engine/layoutState.js +344 -0
  68. package/lib/module/engine/layoutState.js.map +1 -0
  69. package/lib/module/engine/list.js +172 -0
  70. package/lib/module/engine/list.js.map +1 -0
  71. package/lib/module/engine/listIndex.js +125 -0
  72. package/lib/module/engine/listIndex.js.map +1 -0
  73. package/lib/module/engine/move.js +216 -0
  74. package/lib/module/engine/move.js.map +1 -0
  75. package/lib/module/engine/movementPolicy.js +77 -0
  76. package/lib/module/engine/movementPolicy.js.map +1 -0
  77. package/lib/module/engine/orderedTarget.js +71 -0
  78. package/lib/module/engine/orderedTarget.js.map +1 -0
  79. package/lib/module/engine/pack.js +30 -0
  80. package/lib/module/engine/pack.js.map +1 -0
  81. package/lib/module/engine/rectangles.js +12 -0
  82. package/lib/module/engine/rectangles.js.map +1 -0
  83. package/lib/module/engine/sameLayout.js +29 -0
  84. package/lib/module/engine/sameLayout.js.map +1 -0
  85. package/lib/module/engine/search.js +159 -0
  86. package/lib/module/engine/search.js.map +1 -0
  87. package/lib/module/engine/spatial.js +27 -0
  88. package/lib/module/engine/spatial.js.map +1 -0
  89. package/lib/module/engine/spatialAppRotation.js +39 -0
  90. package/lib/module/engine/spatialAppRotation.js.map +1 -0
  91. package/lib/module/engine/spatialExchange.js +164 -0
  92. package/lib/module/engine/spatialExchange.js.map +1 -0
  93. package/lib/module/engine/spatialInsertion.js +135 -0
  94. package/lib/module/engine/spatialInsertion.js.map +1 -0
  95. package/lib/module/engine/spatialPolicy.js +41 -0
  96. package/lib/module/engine/spatialPolicy.js.map +1 -0
  97. package/lib/module/engine/spatialRotation.js +48 -0
  98. package/lib/module/engine/spatialRotation.js.map +1 -0
  99. package/lib/module/engine/validation.js +205 -0
  100. package/lib/module/engine/validation.js.map +1 -0
  101. package/lib/module/gesture/DndGestureGroup.js +268 -0
  102. package/lib/module/gesture/DndGestureGroup.js.map +1 -0
  103. package/lib/module/gesture/DndGestureHost.js +32 -0
  104. package/lib/module/gesture/DndGestureHost.js.map +1 -0
  105. package/lib/module/gesture/gestureConfig.js +81 -0
  106. package/lib/module/gesture/gestureConfig.js.map +1 -0
  107. package/lib/module/gesture/gestureGrouping.js +122 -0
  108. package/lib/module/gesture/gestureGrouping.js.map +1 -0
  109. package/lib/module/gesture/gestureHost.js +106 -0
  110. package/lib/module/gesture/gestureHost.js.map +1 -0
  111. package/lib/module/index.js +23 -0
  112. package/lib/module/index.js.map +1 -0
  113. package/lib/module/package.json +1 -0
  114. package/lib/module/pointer/dndTarget.js +101 -0
  115. package/lib/module/pointer/dndTarget.js.map +1 -0
  116. package/lib/module/pointer/geometry.js +363 -0
  117. package/lib/module/pointer/geometry.js.map +1 -0
  118. package/lib/module/pointer/layoutTarget.js +307 -0
  119. package/lib/module/pointer/layoutTarget.js.map +1 -0
  120. package/lib/module/pointer/listCellMotion.js +99 -0
  121. package/lib/module/pointer/listCellMotion.js.map +1 -0
  122. package/lib/module/pointer/listTarget.js +42 -0
  123. package/lib/module/pointer/listTarget.js.map +1 -0
  124. package/lib/module/pointer/motion.js +37 -0
  125. package/lib/module/pointer/motion.js.map +1 -0
  126. package/lib/module/runtime/dndPresentation.js +81 -0
  127. package/lib/module/runtime/dndPresentation.js.map +1 -0
  128. package/lib/module/runtime/dndRenderStore.js +129 -0
  129. package/lib/module/runtime/dndRenderStore.js.map +1 -0
  130. package/lib/module/runtime/dndRuntime.js +135 -0
  131. package/lib/module/runtime/dndRuntime.js.map +1 -0
  132. package/lib/module/runtime/dndSettlement.js +77 -0
  133. package/lib/module/runtime/dndSettlement.js.map +1 -0
  134. package/lib/module/runtime/gridRuntime.js +37 -0
  135. package/lib/module/runtime/gridRuntime.js.map +1 -0
  136. package/lib/module/types.js +4 -0
  137. package/lib/module/types.js.map +1 -0
  138. package/lib/typescript/package.json +1 -0
  139. package/lib/typescript/src/adapters/coordinates.d.ts +9 -0
  140. package/lib/typescript/src/adapters/coordinates.d.ts.map +1 -0
  141. package/lib/typescript/src/adapters/scroll.d.ts +49 -0
  142. package/lib/typescript/src/adapters/scroll.d.ts.map +1 -0
  143. package/lib/typescript/src/adapters/virtualizedList.d.ts +22 -0
  144. package/lib/typescript/src/adapters/virtualizedList.d.ts.map +1 -0
  145. package/lib/typescript/src/components/DndProvider.d.ts +4 -0
  146. package/lib/typescript/src/components/DndProvider.d.ts.map +1 -0
  147. package/lib/typescript/src/components/DragHandle.d.ts +4 -0
  148. package/lib/typescript/src/components/DragHandle.d.ts.map +1 -0
  149. package/lib/typescript/src/components/GridController.d.ts +21 -0
  150. package/lib/typescript/src/components/GridController.d.ts.map +1 -0
  151. package/lib/typescript/src/components/GridDragHandle.d.ts +8 -0
  152. package/lib/typescript/src/components/GridDragHandle.d.ts.map +1 -0
  153. package/lib/typescript/src/components/GridZoneView.d.ts +8 -0
  154. package/lib/typescript/src/components/GridZoneView.d.ts.map +1 -0
  155. package/lib/typescript/src/components/SortableGrid.d.ts +4 -0
  156. package/lib/typescript/src/components/SortableGrid.d.ts.map +1 -0
  157. package/lib/typescript/src/components/SortableList.d.ts +4 -0
  158. package/lib/typescript/src/components/SortableList.d.ts.map +1 -0
  159. package/lib/typescript/src/components/dndTypes.d.ts +94 -0
  160. package/lib/typescript/src/components/dndTypes.d.ts.map +1 -0
  161. package/lib/typescript/src/components/gridTypes.d.ts +84 -0
  162. package/lib/typescript/src/components/gridTypes.d.ts.map +1 -0
  163. package/lib/typescript/src/components/handleViewProps.d.ts +6 -0
  164. package/lib/typescript/src/components/handleViewProps.d.ts.map +1 -0
  165. package/lib/typescript/src/contracts.d.ts +145 -0
  166. package/lib/typescript/src/contracts.d.ts.map +1 -0
  167. package/lib/typescript/src/controller/approval.d.ts +66 -0
  168. package/lib/typescript/src/controller/approval.d.ts.map +1 -0
  169. package/lib/typescript/src/controller/coordinatorBase.d.ts +37 -0
  170. package/lib/typescript/src/controller/coordinatorBase.d.ts.map +1 -0
  171. package/lib/typescript/src/controller/dndSession.d.ts +152 -0
  172. package/lib/typescript/src/controller/dndSession.d.ts.map +1 -0
  173. package/lib/typescript/src/controller/dndState.d.ts +27 -0
  174. package/lib/typescript/src/controller/dndState.d.ts.map +1 -0
  175. package/lib/typescript/src/controller/gridAdapter.d.ts +44 -0
  176. package/lib/typescript/src/controller/gridAdapter.d.ts.map +1 -0
  177. package/lib/typescript/src/controller/gridState.d.ts +18 -0
  178. package/lib/typescript/src/controller/gridState.d.ts.map +1 -0
  179. package/lib/typescript/src/controller/locations.d.ts +9 -0
  180. package/lib/typescript/src/controller/locations.d.ts.map +1 -0
  181. package/lib/typescript/src/controller/previewPath.d.ts +49 -0
  182. package/lib/typescript/src/controller/previewPath.d.ts.map +1 -0
  183. package/lib/typescript/src/controller/sessionCore.d.ts +106 -0
  184. package/lib/typescript/src/controller/sessionCore.d.ts.map +1 -0
  185. package/lib/typescript/src/controller/useDndState.d.ts +8 -0
  186. package/lib/typescript/src/controller/useDndState.d.ts.map +1 -0
  187. package/lib/typescript/src/controller/useGridState.d.ts +8 -0
  188. package/lib/typescript/src/controller/useGridState.d.ts.map +1 -0
  189. package/lib/typescript/src/controller/useStoreState.d.ts +16 -0
  190. package/lib/typescript/src/controller/useStoreState.d.ts.map +1 -0
  191. package/lib/typescript/src/defaults.d.ts +5 -0
  192. package/lib/typescript/src/defaults.d.ts.map +1 -0
  193. package/lib/typescript/src/diagnostics.d.ts +9 -0
  194. package/lib/typescript/src/diagnostics.d.ts.map +1 -0
  195. package/lib/typescript/src/engine/gridPlacements.d.ts +22 -0
  196. package/lib/typescript/src/engine/gridPlacements.d.ts.map +1 -0
  197. package/lib/typescript/src/engine/index.d.ts +28 -0
  198. package/lib/typescript/src/engine/index.d.ts.map +1 -0
  199. package/lib/typescript/src/engine/layout.d.ts +4 -0
  200. package/lib/typescript/src/engine/layout.d.ts.map +1 -0
  201. package/lib/typescript/src/engine/layoutMove.d.ts +46 -0
  202. package/lib/typescript/src/engine/layoutMove.d.ts.map +1 -0
  203. package/lib/typescript/src/engine/layoutState.d.ts +22 -0
  204. package/lib/typescript/src/engine/layoutState.d.ts.map +1 -0
  205. package/lib/typescript/src/engine/list.d.ts +68 -0
  206. package/lib/typescript/src/engine/list.d.ts.map +1 -0
  207. package/lib/typescript/src/engine/listIndex.d.ts +32 -0
  208. package/lib/typescript/src/engine/listIndex.d.ts.map +1 -0
  209. package/lib/typescript/src/engine/move.d.ts +10 -0
  210. package/lib/typescript/src/engine/move.d.ts.map +1 -0
  211. package/lib/typescript/src/engine/movementPolicy.d.ts +34 -0
  212. package/lib/typescript/src/engine/movementPolicy.d.ts.map +1 -0
  213. package/lib/typescript/src/engine/orderedTarget.d.ts +32 -0
  214. package/lib/typescript/src/engine/orderedTarget.d.ts.map +1 -0
  215. package/lib/typescript/src/engine/pack.d.ts +3 -0
  216. package/lib/typescript/src/engine/pack.d.ts.map +1 -0
  217. package/lib/typescript/src/engine/rectangles.d.ts +9 -0
  218. package/lib/typescript/src/engine/rectangles.d.ts.map +1 -0
  219. package/lib/typescript/src/engine/sameLayout.d.ts +4 -0
  220. package/lib/typescript/src/engine/sameLayout.d.ts.map +1 -0
  221. package/lib/typescript/src/engine/search.d.ts +28 -0
  222. package/lib/typescript/src/engine/search.d.ts.map +1 -0
  223. package/lib/typescript/src/engine/spatial.d.ts +16 -0
  224. package/lib/typescript/src/engine/spatial.d.ts.map +1 -0
  225. package/lib/typescript/src/engine/spatialAppRotation.d.ts +4 -0
  226. package/lib/typescript/src/engine/spatialAppRotation.d.ts.map +1 -0
  227. package/lib/typescript/src/engine/spatialExchange.d.ts +5 -0
  228. package/lib/typescript/src/engine/spatialExchange.d.ts.map +1 -0
  229. package/lib/typescript/src/engine/spatialInsertion.d.ts +5 -0
  230. package/lib/typescript/src/engine/spatialInsertion.d.ts.map +1 -0
  231. package/lib/typescript/src/engine/spatialPolicy.d.ts +9 -0
  232. package/lib/typescript/src/engine/spatialPolicy.d.ts.map +1 -0
  233. package/lib/typescript/src/engine/spatialRotation.d.ts +8 -0
  234. package/lib/typescript/src/engine/spatialRotation.d.ts.map +1 -0
  235. package/lib/typescript/src/engine/validation.d.ts +9 -0
  236. package/lib/typescript/src/engine/validation.d.ts.map +1 -0
  237. package/lib/typescript/src/gesture/DndGestureGroup.d.ts +33 -0
  238. package/lib/typescript/src/gesture/DndGestureGroup.d.ts.map +1 -0
  239. package/lib/typescript/src/gesture/DndGestureHost.d.ts +12 -0
  240. package/lib/typescript/src/gesture/DndGestureHost.d.ts.map +1 -0
  241. package/lib/typescript/src/gesture/gestureConfig.d.ts +15 -0
  242. package/lib/typescript/src/gesture/gestureConfig.d.ts.map +1 -0
  243. package/lib/typescript/src/gesture/gestureGrouping.d.ts +66 -0
  244. package/lib/typescript/src/gesture/gestureGrouping.d.ts.map +1 -0
  245. package/lib/typescript/src/gesture/gestureHost.d.ts +23 -0
  246. package/lib/typescript/src/gesture/gestureHost.d.ts.map +1 -0
  247. package/lib/typescript/src/index.d.ts +38 -0
  248. package/lib/typescript/src/index.d.ts.map +1 -0
  249. package/lib/typescript/src/pointer/dndTarget.d.ts +14 -0
  250. package/lib/typescript/src/pointer/dndTarget.d.ts.map +1 -0
  251. package/lib/typescript/src/pointer/geometry.d.ts +28 -0
  252. package/lib/typescript/src/pointer/geometry.d.ts.map +1 -0
  253. package/lib/typescript/src/pointer/layoutTarget.d.ts +43 -0
  254. package/lib/typescript/src/pointer/layoutTarget.d.ts.map +1 -0
  255. package/lib/typescript/src/pointer/listCellMotion.d.ts +43 -0
  256. package/lib/typescript/src/pointer/listCellMotion.d.ts.map +1 -0
  257. package/lib/typescript/src/pointer/listTarget.d.ts +7 -0
  258. package/lib/typescript/src/pointer/listTarget.d.ts.map +1 -0
  259. package/lib/typescript/src/pointer/motion.d.ts +20 -0
  260. package/lib/typescript/src/pointer/motion.d.ts.map +1 -0
  261. package/lib/typescript/src/runtime/dndPresentation.d.ts +37 -0
  262. package/lib/typescript/src/runtime/dndPresentation.d.ts.map +1 -0
  263. package/lib/typescript/src/runtime/dndRenderStore.d.ts +46 -0
  264. package/lib/typescript/src/runtime/dndRenderStore.d.ts.map +1 -0
  265. package/lib/typescript/src/runtime/dndRuntime.d.ts +138 -0
  266. package/lib/typescript/src/runtime/dndRuntime.d.ts.map +1 -0
  267. package/lib/typescript/src/runtime/dndSettlement.d.ts +18 -0
  268. package/lib/typescript/src/runtime/dndSettlement.d.ts.map +1 -0
  269. package/lib/typescript/src/runtime/gridRuntime.d.ts +54 -0
  270. package/lib/typescript/src/runtime/gridRuntime.d.ts.map +1 -0
  271. package/lib/typescript/src/types.d.ts +180 -0
  272. package/lib/typescript/src/types.d.ts.map +1 -0
  273. package/package.json +136 -0
  274. package/src/adapters/coordinates.ts +181 -0
  275. package/src/adapters/scroll.ts +147 -0
  276. package/src/adapters/virtualizedList.ts +112 -0
  277. package/src/components/DndProvider.tsx +1836 -0
  278. package/src/components/DragHandle.tsx +89 -0
  279. package/src/components/GridController.tsx +213 -0
  280. package/src/components/GridDragHandle.tsx +11 -0
  281. package/src/components/GridZoneView.tsx +57 -0
  282. package/src/components/SortableGrid.tsx +449 -0
  283. package/src/components/SortableList.tsx +1028 -0
  284. package/src/components/dndTypes.ts +102 -0
  285. package/src/components/gridTypes.ts +90 -0
  286. package/src/components/handleViewProps.ts +35 -0
  287. package/src/contracts.ts +149 -0
  288. package/src/controller/approval.ts +179 -0
  289. package/src/controller/coordinatorBase.ts +118 -0
  290. package/src/controller/dndSession.ts +1058 -0
  291. package/src/controller/dndState.ts +198 -0
  292. package/src/controller/gridAdapter.ts +268 -0
  293. package/src/controller/gridState.ts +187 -0
  294. package/src/controller/locations.ts +67 -0
  295. package/src/controller/previewPath.ts +90 -0
  296. package/src/controller/sessionCore.ts +390 -0
  297. package/src/controller/useDndState.ts +26 -0
  298. package/src/controller/useGridState.ts +22 -0
  299. package/src/controller/useStoreState.ts +27 -0
  300. package/src/defaults.ts +4 -0
  301. package/src/diagnostics.ts +23 -0
  302. package/src/engine/gridPlacements.ts +85 -0
  303. package/src/engine/index.ts +99 -0
  304. package/src/engine/layout.ts +29 -0
  305. package/src/engine/layoutMove.ts +401 -0
  306. package/src/engine/layoutState.ts +490 -0
  307. package/src/engine/list.ts +307 -0
  308. package/src/engine/listIndex.ts +174 -0
  309. package/src/engine/move.ts +274 -0
  310. package/src/engine/movementPolicy.ts +151 -0
  311. package/src/engine/orderedTarget.ts +131 -0
  312. package/src/engine/pack.ts +24 -0
  313. package/src/engine/rectangles.ts +18 -0
  314. package/src/engine/sameLayout.ts +50 -0
  315. package/src/engine/search.ts +174 -0
  316. package/src/engine/spatial.ts +36 -0
  317. package/src/engine/spatialAppRotation.ts +53 -0
  318. package/src/engine/spatialExchange.ts +212 -0
  319. package/src/engine/spatialInsertion.ts +157 -0
  320. package/src/engine/spatialPolicy.ts +48 -0
  321. package/src/engine/spatialRotation.ts +56 -0
  322. package/src/engine/validation.ts +358 -0
  323. package/src/gesture/DndGestureGroup.tsx +357 -0
  324. package/src/gesture/DndGestureHost.tsx +50 -0
  325. package/src/gesture/gestureConfig.ts +123 -0
  326. package/src/gesture/gestureGrouping.ts +210 -0
  327. package/src/gesture/gestureHost.tsx +163 -0
  328. package/src/index.ts +162 -0
  329. package/src/pointer/dndTarget.ts +213 -0
  330. package/src/pointer/geometry.ts +584 -0
  331. package/src/pointer/layoutTarget.ts +515 -0
  332. package/src/pointer/listCellMotion.ts +174 -0
  333. package/src/pointer/listTarget.ts +56 -0
  334. package/src/pointer/motion.ts +66 -0
  335. package/src/runtime/dndPresentation.ts +113 -0
  336. package/src/runtime/dndRenderStore.ts +246 -0
  337. package/src/runtime/dndRuntime.ts +311 -0
  338. package/src/runtime/dndSettlement.ts +117 -0
  339. package/src/runtime/gridRuntime.ts +73 -0
  340. 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
+ }