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,198 @@
1
+ import type {
2
+ LayoutLocation,
3
+ LayoutState,
4
+ LayoutValidationIssue,
5
+ MoveProposal,
6
+ ProposalResponse,
7
+ } from '../contracts';
8
+ import { validateLayoutState } from '../engine/layoutState';
9
+ import { isCellPosition, isRecord } from '../engine/validation';
10
+
11
+ import {
12
+ createApprovalStore,
13
+ respondToProposal,
14
+ type ApprovalAdapter,
15
+ type ApprovalResult,
16
+ } from './approval';
17
+ import { layoutLocation, sameLayoutLocation } from './locations';
18
+
19
+ export type DndValue<T> = LayoutState<T> & {
20
+ proposalResponse?: ProposalResponse;
21
+ };
22
+ export type DndProposal<T> = MoveProposal<LayoutState<T>, LayoutLocation>;
23
+ export type DndProposalResult<T> = ApprovalResult<
24
+ DndValue<T>,
25
+ LayoutValidationIssue
26
+ >;
27
+
28
+ function isLocation(value: unknown): value is LayoutLocation {
29
+ if (!isRecord(value) || typeof value.zoneId !== 'string') return false;
30
+ return value.kind === 'list'
31
+ ? typeof value.index === 'number' &&
32
+ Number.isSafeInteger(value.index) &&
33
+ value.index >= 0
34
+ : value.kind === 'grid' &&
35
+ isCellPosition(value.position) &&
36
+ value.position.row >= 0 &&
37
+ value.position.col >= 0;
38
+ }
39
+
40
+ function issues<T>(value: LayoutState<T>): LayoutValidationIssue[] | null {
41
+ const checked = validateLayoutState(value);
42
+ return checked.valid ? null : checked.issues;
43
+ }
44
+
45
+ function layoutAdapter<T>(): ApprovalAdapter<
46
+ DndValue<T>,
47
+ LayoutLocation,
48
+ LayoutValidationIssue
49
+ > {
50
+ return {
51
+ validateCurrent: issues,
52
+ validateCandidate: issues,
53
+ issue: message => ({ code: 'invalid-structure', message }),
54
+ isLocation,
55
+ location: layoutLocation,
56
+ sameLocation: sameLayoutLocation,
57
+ hasDestinationZone: (value, to) =>
58
+ value.zones.some(zone => zone.id === to.zoneId && zone.kind === to.kind),
59
+ zonesPreserved: (current, candidate) => {
60
+ const zones = new Map(current.zones.map(zone => [zone.id, zone]));
61
+ return (
62
+ candidate.zones.length === zones.size &&
63
+ candidate.zones.every(zone => {
64
+ const original = zones.get(zone.id);
65
+ return (
66
+ original !== undefined &&
67
+ original.kind === zone.kind &&
68
+ (zone.kind !== 'list' ||
69
+ original.kind !== 'list' ||
70
+ zone.orientation === original.orientation) &&
71
+ (zone.kind !== 'grid' ||
72
+ original.kind !== 'grid' ||
73
+ (zone.rows === original.rows &&
74
+ zone.columns === original.columns &&
75
+ zone.itemSpan?.rows === original.itemSpan?.rows &&
76
+ zone.itemSpan?.cols === original.itemSpan?.cols))
77
+ );
78
+ })
79
+ );
80
+ },
81
+ // computeLayoutMove reuses the baseline items array; only a replaced array
82
+ // needs the per-item identity check.
83
+ itemsPreserved: (current, candidate) => {
84
+ if (candidate.items === current.items) return true;
85
+ const items = new Map(current.items.map(item => [item.id, item]));
86
+ return (
87
+ candidate.items.length === items.size &&
88
+ candidate.items.every(item => items.get(item.id) === item)
89
+ );
90
+ },
91
+ messages: {
92
+ zones:
93
+ 'A movement must preserve zone IDs, kinds, orientations and grid dimensions.',
94
+ items:
95
+ 'A movement must preserve every caller-owned item object and its data.',
96
+ destination:
97
+ 'The destination must match the item location in the complete candidate.',
98
+ },
99
+ accept: (current, candidate, response) => ({
100
+ ...candidate,
101
+ items: current.items,
102
+ proposalResponse: response,
103
+ }),
104
+ };
105
+ }
106
+
107
+ /** Validate the complete atomic proposal before applying a matching revision response. */
108
+ export function respondToDndProposal<T>(
109
+ current: DndValue<T>,
110
+ proposal: DndProposal<T>,
111
+ accepted: boolean,
112
+ ): DndProposalResult<T> {
113
+ return respondToProposal(
114
+ layoutAdapter<T>(),
115
+ current,
116
+ proposal,
117
+ accepted,
118
+ true,
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Internal entry for a caller that has already validated `current` and holds
124
+ * it immutably since (the session keeps its committed value). The proposal and
125
+ * its complete candidate are still checked in full.
126
+ */
127
+ export function respondToValidatedDndProposal<T>(
128
+ current: DndValue<T>,
129
+ proposal: DndProposal<T>,
130
+ accepted: boolean,
131
+ ): DndProposalResult<T> {
132
+ return respondToProposal(
133
+ layoutAdapter<T>(),
134
+ current,
135
+ proposal,
136
+ accepted,
137
+ false,
138
+ );
139
+ }
140
+
141
+ export type DndStateUpdate<T> =
142
+ LayoutState<T> | ((current: DndValue<T>) => LayoutState<T>);
143
+ export type DndStateStore<T> = {
144
+ getSnapshot(): DndValue<T>;
145
+ subscribe(listener: () => void): () => void;
146
+ onChange(next: DndValue<T>, proposal: DndProposal<T>): void;
147
+ /** External edits advance the revision and clear the previous proposal response. */
148
+ setValue(next: DndStateUpdate<T>): void;
149
+ respond(proposal: DndProposal<T>, accepted: boolean): DndProposalResult<T>;
150
+ };
151
+
152
+ function assertValid<T>(value: LayoutState<T>): void {
153
+ const checked = validateLayoutState(value);
154
+ if (!checked.valid)
155
+ throw new TypeError(checked.issues.map(issue => issue.message).join(' '));
156
+ }
157
+
158
+ /** The same synchronous approval path for React state and external stores. */
159
+ export function createDndStateStore<T>(
160
+ initialValue: LayoutState<T>,
161
+ ): DndStateStore<T> {
162
+ assertValid(initialValue);
163
+ const store = createApprovalStore<
164
+ DndValue<T>,
165
+ DndProposal<T>,
166
+ LayoutValidationIssue
167
+ >(
168
+ {
169
+ revision: initialValue.revision,
170
+ items: initialValue.items,
171
+ zones: initialValue.zones,
172
+ },
173
+ respondToDndProposal,
174
+ );
175
+ return {
176
+ getSnapshot: store.getSnapshot,
177
+ subscribe: store.subscribe,
178
+ onChange: (next, proposal) => {
179
+ // The callback carries one complete proposal; two different candidates are ambiguous.
180
+ if (next !== proposal.value) return;
181
+ store.respond(proposal, true);
182
+ },
183
+ setValue: update => {
184
+ const value = store.getSnapshot();
185
+ const next = typeof update === 'function' ? update(value) : update;
186
+ if (next === value) return;
187
+ assertValid(next);
188
+ if (value.revision === Number.MAX_SAFE_INTEGER)
189
+ throw new RangeError('Layout revision is exhausted.');
190
+ store.publish({
191
+ revision: value.revision + 1,
192
+ items: next.items,
193
+ zones: next.zones,
194
+ });
195
+ },
196
+ respond: store.respond,
197
+ };
198
+ }
@@ -0,0 +1,268 @@
1
+ import type {
2
+ DndItem,
3
+ LayoutLocation,
4
+ LayoutState,
5
+ LayoutValidationIssue,
6
+ } from '../contracts';
7
+ import {
8
+ orderedSlots,
9
+ slotIndex,
10
+ slotPosition,
11
+ } from '../engine/gridPlacements';
12
+ import { fromGridValue } from '../engine/layoutState';
13
+ import { hasSameLayout } from '../engine/sameLayout';
14
+ import { validateValue } from '../engine/validation';
15
+ import type {
16
+ DragEndEvent,
17
+ GridChange,
18
+ GridItem,
19
+ GridValue,
20
+ ItemLocation,
21
+ ValidationCode,
22
+ ValidationIssue,
23
+ } from '../types';
24
+
25
+ import type { DndDragEndEvent } from './dndSession';
26
+ import type { DndProposal, DndValue } from './dndState';
27
+
28
+ /**
29
+ * Maps the `GridValue` API of `GridController` onto the layout-state API of
30
+ * the shared provider: values, locations, changes, end events and validation
31
+ * issues. Every function is pure; the component keeps only React state.
32
+ */
33
+
34
+ /** The grid API's location for a layout location; ordered grids use slot indices. */
35
+ export function toItemLocation<T>(
36
+ state: LayoutState<T>,
37
+ location: LayoutLocation,
38
+ ): ItemLocation {
39
+ if (location.kind === 'list')
40
+ return {
41
+ zoneId: location.zoneId,
42
+ strategy: 'ordered',
43
+ index: location.index,
44
+ };
45
+ const zone = state.zones.find(entry => entry.id === location.zoneId);
46
+ if (zone?.kind === 'grid' && zone.itemSpan) {
47
+ const slots = orderedSlots(zone, zone.itemSpan);
48
+ const index = slotIndex(location.position, zone.itemSpan, slots.perRow);
49
+ if (index !== null)
50
+ return { zoneId: location.zoneId, strategy: 'ordered', index };
51
+ }
52
+ return {
53
+ zoneId: location.zoneId,
54
+ strategy: 'spatial',
55
+ position: { row: location.position.row, col: location.position.col },
56
+ };
57
+ }
58
+
59
+ /** The layout location for a grid API location, or null when it names no slot. */
60
+ export function toLayoutLocation<T>(
61
+ state: LayoutState<T>,
62
+ location: ItemLocation,
63
+ ): LayoutLocation | null {
64
+ const zone = state.zones.find(entry => entry.id === location.zoneId);
65
+ if (!zone) return null;
66
+ if (zone.kind === 'list')
67
+ return location.strategy === 'ordered'
68
+ ? { kind: 'list', zoneId: zone.id, index: location.index }
69
+ : null;
70
+ if (location.strategy === 'spatial')
71
+ return zone.itemSpan
72
+ ? null
73
+ : {
74
+ kind: 'grid',
75
+ zoneId: zone.id,
76
+ position: { row: location.position.row, col: location.position.col },
77
+ };
78
+ if (
79
+ !zone.itemSpan ||
80
+ !Number.isSafeInteger(location.index) ||
81
+ location.index < 0
82
+ )
83
+ return null;
84
+ const slots = orderedSlots(zone, zone.itemSpan);
85
+ if (slots.perRow === 0 || location.index >= slots.perRow * slots.rows)
86
+ return null;
87
+ return {
88
+ kind: 'grid',
89
+ zoneId: zone.id,
90
+ position: slotPosition(location.index, zone.itemSpan, slots.perRow),
91
+ };
92
+ }
93
+
94
+ /** The legacy change record of a proposal; the base revision is optional API. */
95
+ export function gridChangeOf<T>(
96
+ proposal: DndProposal<T>,
97
+ includeBaseRevision: boolean,
98
+ ): GridChange {
99
+ return {
100
+ sessionId: proposal.sessionId,
101
+ ...(includeBaseRevision ? { baseRevision: proposal.baseRevision } : {}),
102
+ itemId: proposal.itemId,
103
+ from: toItemLocation(proposal.value, proposal.from),
104
+ to: toItemLocation(proposal.value, proposal.to),
105
+ };
106
+ }
107
+
108
+ export function gridEndEvent<T>(
109
+ event: DndDragEndEvent<T>,
110
+ includeBaseRevision: boolean,
111
+ ): DragEndEvent {
112
+ const base = {
113
+ sessionId: event.sessionId,
114
+ itemId: event.itemId,
115
+ sourceZoneId: event.sourceZoneId,
116
+ ...(event.error === undefined ? {} : { error: event.error }),
117
+ };
118
+ if (event.outcome === 'unchanged') return { ...base, outcome: 'unchanged' };
119
+ if (event.outcome === 'cancelled')
120
+ return {
121
+ ...base,
122
+ outcome: 'cancelled',
123
+ reason: event.reason,
124
+ ...(event.targetZoneId === undefined
125
+ ? {}
126
+ : { targetZoneId: event.targetZoneId }),
127
+ ...(event.policyReason === undefined
128
+ ? {}
129
+ : { policyReason: event.policyReason }),
130
+ };
131
+ const change = gridChangeOf(event.proposal, includeBaseRevision);
132
+ return event.response === 'interrupted'
133
+ ? {
134
+ ...base,
135
+ outcome: 'proposed',
136
+ change,
137
+ targetZoneId: event.targetZoneId,
138
+ response: 'interrupted',
139
+ reason: event.reason,
140
+ }
141
+ : {
142
+ ...base,
143
+ outcome: 'proposed',
144
+ change,
145
+ targetZoneId: event.targetZoneId,
146
+ response: event.response,
147
+ };
148
+ }
149
+
150
+ const GRID_CODES = new Set<string>([
151
+ 'invalid-structure',
152
+ 'duplicate-id',
153
+ 'invalid-dimension',
154
+ 'invalid-span',
155
+ 'invalid-placement',
156
+ 'missing-item-span',
157
+ 'span-mismatch',
158
+ 'invalid-position',
159
+ 'out-of-bounds',
160
+ 'overlap',
161
+ 'capacity-exceeded',
162
+ 'invalid-budget',
163
+ 'invalid-geometry',
164
+ 'invalid-configuration',
165
+ ]);
166
+
167
+ /** Layout-state issues in the grid API's vocabulary. */
168
+ export function gridIssues(
169
+ issues: ReadonlyArray<
170
+ Pick<LayoutValidationIssue, 'message' | 'zoneId' | 'itemId'> & {
171
+ code: string;
172
+ }
173
+ >,
174
+ ): ValidationIssue[] {
175
+ return issues.map(issue => ({
176
+ code: (GRID_CODES.has(issue.code)
177
+ ? issue.code
178
+ : 'invalid-structure') as ValidationCode,
179
+ message: issue.message,
180
+ ...(issue.zoneId === undefined ? {} : { zoneId: issue.zoneId }),
181
+ ...(issue.itemId === undefined ? {} : { itemId: issue.itemId }),
182
+ }));
183
+ }
184
+
185
+ export type ConvertedGridValue<T> = {
186
+ /** Null when the grid value is invalid; `issues` then explains why. */
187
+ state: DndValue<T> | null;
188
+ issues: ValidationIssue[];
189
+ revision: number;
190
+ };
191
+
192
+ export type PreviousConversion<T> = {
193
+ value: GridValue<T>;
194
+ revision: number;
195
+ state: DndValue<T> | null;
196
+ };
197
+
198
+ /**
199
+ * Convert a committed grid value for the provider. Values without a revision
200
+ * receive a synthetic one that advances whenever the layout changes, so the
201
+ * provider's revision protocol still detects edits; a response that omits its
202
+ * base revision is attributed to the proposal it answers. Item objects whose
203
+ * data is unchanged keep their identity, so cells and the approval reducer see
204
+ * stable objects; acceptance itself compares layout and revision only, so an
205
+ * owner may rebuild an item's data for its new zone.
206
+ */
207
+ export function convertGridValue<T>(
208
+ value: GridValue<T>,
209
+ previous: PreviousConversion<T> | null,
210
+ pendingBaseRevision: number | undefined,
211
+ ): ConvertedGridValue<T> {
212
+ const checked = validateValue(value);
213
+ const revision =
214
+ value.revision ??
215
+ (previous === null
216
+ ? 0
217
+ : hasSameLayout(previous.value, value)
218
+ ? previous.revision
219
+ : previous.revision + 1);
220
+ if (!checked.valid) return { state: null, issues: checked.issues, revision };
221
+ const converted = fromGridValue(value, revision);
222
+ if (!converted.valid)
223
+ return { state: null, issues: gridIssues(converted.issues), revision };
224
+ const state = converted.value;
225
+ if (previous?.state)
226
+ state.items = reuseItems(state.items, previous.state.items);
227
+ const response = value.proposalResponse;
228
+ return {
229
+ revision,
230
+ issues: [],
231
+ state: response
232
+ ? {
233
+ ...state,
234
+ proposalResponse: {
235
+ sessionId: response.sessionId,
236
+ accepted: response.accepted,
237
+ baseRevision:
238
+ response.baseRevision ?? pendingBaseRevision ?? revision,
239
+ },
240
+ }
241
+ : state,
242
+ };
243
+ }
244
+
245
+ function reuseItems<T>(
246
+ items: DndItem<T>[],
247
+ previous: DndItem<T>[],
248
+ ): DndItem<T>[] {
249
+ const known = new Map(previous.map(item => [item.id, item]));
250
+ let same = items.length === previous.length;
251
+ const reused = items.map((item, index) => {
252
+ const before = known.get(item.id);
253
+ const kept = before && Object.is(before.data, item.data) ? before : item;
254
+ if (kept !== previous[index]) same = false;
255
+ return kept;
256
+ });
257
+ return same ? previous : reused;
258
+ }
259
+
260
+ /** The caller's own item objects by ID, so render callbacks see stable identities. */
261
+ export function gridItemIndex<T>(
262
+ value: GridValue<T>,
263
+ ): Map<string, GridItem<T>> {
264
+ const items = new Map<string, GridItem<T>>();
265
+ for (const zone of value.zones)
266
+ for (const item of zone.items) items.set(item.id, item);
267
+ return items;
268
+ }
@@ -0,0 +1,187 @@
1
+ import { isCellPosition, isRecord, validateValue } from '../engine/validation';
2
+ import type {
3
+ GridChange,
4
+ GridProposal,
5
+ GridValue,
6
+ ItemLocation,
7
+ RevisionedGridValue,
8
+ ValidationIssue,
9
+ } from '../types';
10
+
11
+ import {
12
+ createApprovalStore,
13
+ respondToProposal,
14
+ type ApprovalAdapter,
15
+ type ApprovalResult,
16
+ } from './approval';
17
+ import { itemLocation, sameItemLocation } from './locations';
18
+
19
+ export type GridProposalResult<T> = ApprovalResult<
20
+ RevisionedGridValue<T>,
21
+ ValidationIssue
22
+ >;
23
+
24
+ function isLocation(value: unknown): value is ItemLocation {
25
+ if (!isRecord(value) || typeof value.zoneId !== 'string') return false;
26
+ return value.strategy === 'ordered'
27
+ ? typeof value.index === 'number' &&
28
+ Number.isSafeInteger(value.index) &&
29
+ value.index >= 0
30
+ : value.strategy === 'spatial' &&
31
+ isCellPosition(value.position) &&
32
+ value.position.row >= 0 &&
33
+ value.position.col >= 0;
34
+ }
35
+
36
+ function issues<T>(value: GridValue<T>): ValidationIssue[] | null {
37
+ const checked = validateValue(value);
38
+ return checked.valid ? null : checked.issues;
39
+ }
40
+
41
+ function gridAdapter<T>(): ApprovalAdapter<
42
+ RevisionedGridValue<T>,
43
+ ItemLocation,
44
+ ValidationIssue
45
+ > {
46
+ return {
47
+ validateCurrent: current =>
48
+ issues(current) ??
49
+ (!Number.isSafeInteger(current.revision) || current.revision < 0
50
+ ? [
51
+ {
52
+ code: 'invalid-structure',
53
+ message:
54
+ 'Current grid state must have a non-negative safe integer revision.',
55
+ },
56
+ ]
57
+ : null),
58
+ validateCandidate: issues,
59
+ issue: message => ({ code: 'invalid-structure', message }),
60
+ isLocation,
61
+ location: itemLocation,
62
+ sameLocation: sameItemLocation,
63
+ hasDestinationZone: (value, to) =>
64
+ value.zones.some(
65
+ zone => zone.id === to.zoneId && zone.strategy === to.strategy,
66
+ ),
67
+ zonesPreserved: (current, candidate) => {
68
+ const zones = new Map(current.zones.map(zone => [zone.id, zone]));
69
+ return (
70
+ candidate.zones.length === zones.size &&
71
+ candidate.zones.every(zone => {
72
+ const original = zones.get(zone.id);
73
+ return (
74
+ original !== undefined &&
75
+ original.strategy === zone.strategy &&
76
+ original.rows === zone.rows &&
77
+ original.columns === zone.columns &&
78
+ (original.strategy !== 'ordered' ||
79
+ zone.strategy !== 'ordered' ||
80
+ (original.itemSpan.rows === zone.itemSpan.rows &&
81
+ original.itemSpan.cols === zone.itemSpan.cols))
82
+ );
83
+ })
84
+ );
85
+ },
86
+ // A movement may change layout, never the caller's item set or opaque data.
87
+ itemsPreserved: (current, candidate) => {
88
+ const items = new Map(
89
+ current.zones.flatMap(zone => zone.items).map(item => [item.id, item]),
90
+ );
91
+ const proposed = candidate.zones.flatMap(zone => zone.items);
92
+ return (
93
+ proposed.length === items.size &&
94
+ proposed.every(item => {
95
+ const original = items.get(item.id);
96
+ return (
97
+ original !== undefined &&
98
+ Object.is(original.data, item.data) &&
99
+ original.span.rows === item.span.rows &&
100
+ original.span.cols === item.span.cols &&
101
+ original.placement === item.placement
102
+ );
103
+ })
104
+ );
105
+ },
106
+ messages: {
107
+ zones:
108
+ 'A grid proposal must preserve zone IDs, strategies, dimensions and ordered item spans.',
109
+ items:
110
+ 'A grid proposal must preserve all item IDs, spans, placement policies and caller data.',
111
+ destination:
112
+ 'The proposed destination must match the item location in the complete candidate.',
113
+ },
114
+ accept: (_current, candidate, response) => ({
115
+ ...candidate,
116
+ proposalResponse: response,
117
+ }),
118
+ };
119
+ }
120
+
121
+ /** A response only changes the exact revision on which the complete proposal was based. */
122
+ export function respondToGridProposal<T>(
123
+ current: RevisionedGridValue<T>,
124
+ proposal: GridProposal<T>,
125
+ accepted: boolean,
126
+ ): GridProposalResult<T> {
127
+ return respondToProposal(gridAdapter<T>(), current, proposal, accepted, true);
128
+ }
129
+
130
+ export type GridStateUpdate<T> =
131
+ GridValue<T> | ((current: RevisionedGridValue<T>) => GridValue<T>);
132
+ export type GridStateStore<T> = {
133
+ getSnapshot(): RevisionedGridValue<T>;
134
+ subscribe(listener: () => void): () => void;
135
+ /** Synchronously accept valid controller proposals. Use canDrop for synchronous rejection. */
136
+ onChange(next: GridValue<T>, change: GridChange): void;
137
+ /** External edits advance the revision and clear any previous response. */
138
+ setValue(next: GridStateUpdate<T>): void;
139
+ respond(proposal: GridProposal<T>, accepted: boolean): GridProposalResult<T>;
140
+ };
141
+
142
+ function assertValid<T>(value: GridValue<T>): void {
143
+ const result = validateValue(value);
144
+ if (!result.valid)
145
+ throw new TypeError(result.issues.map(issue => issue.message).join(' '));
146
+ }
147
+
148
+ /** React-independent store; the state hook and external stores share the response reducer. */
149
+ export function createGridStateStore<T>(
150
+ initialValue: GridValue<T>,
151
+ ): GridStateStore<T> {
152
+ assertValid(initialValue);
153
+ const store = createApprovalStore<
154
+ RevisionedGridValue<T>,
155
+ GridProposal<T>,
156
+ ValidationIssue
157
+ >(
158
+ { zones: initialValue.zones, revision: initialValue.revision ?? 0 },
159
+ respondToGridProposal,
160
+ );
161
+ return {
162
+ getSnapshot: store.getSnapshot,
163
+ subscribe: store.subscribe,
164
+ onChange: (next, change) => {
165
+ if (change.baseRevision === undefined || next.revision === undefined)
166
+ return;
167
+ store.respond(
168
+ {
169
+ ...change,
170
+ baseRevision: change.baseRevision,
171
+ value: { ...next, revision: next.revision },
172
+ },
173
+ true,
174
+ );
175
+ },
176
+ setValue: update => {
177
+ const value = store.getSnapshot();
178
+ const next = typeof update === 'function' ? update(value) : update;
179
+ if (next === value) return;
180
+ assertValid(next);
181
+ if (value.revision === Number.MAX_SAFE_INTEGER)
182
+ throw new RangeError('Grid revision is exhausted.');
183
+ store.publish({ zones: next.zones, revision: value.revision + 1 });
184
+ },
185
+ respond: store.respond,
186
+ };
187
+ }