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,490 @@
1
+ import type {
2
+ DndItem,
3
+ GridLayoutZone,
4
+ GridPlacement,
5
+ LayoutConversionResult,
6
+ LayoutState,
7
+ LayoutValidationIssue,
8
+ LayoutValidationResult,
9
+ } from '../contracts';
10
+ import type { GridValue } from '../types';
11
+
12
+ import { orderedSlots, slotPosition } from './gridPlacements';
13
+ import { computeZoneLayout } from './layout';
14
+ import { rectanglesOverlap } from './rectangles';
15
+ import {
16
+ isCellPosition,
17
+ isCellSpan,
18
+ isPositiveInteger,
19
+ isRecord,
20
+ validateValue,
21
+ } from './validation';
22
+
23
+ type IssueContext = Pick<LayoutValidationIssue, 'zoneId' | 'itemId'>;
24
+
25
+ function addIssue(
26
+ issues: LayoutValidationIssue[],
27
+ code: LayoutValidationIssue['code'],
28
+ message: string,
29
+ context: IssueContext = {},
30
+ ) {
31
+ issues.push({ code, ...context, message });
32
+ }
33
+
34
+ function isRevision(value: unknown): value is number {
35
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
36
+ }
37
+
38
+ function collectReference(
39
+ itemId: unknown,
40
+ itemIds: Set<string>,
41
+ references: Set<string>,
42
+ issues: LayoutValidationIssue[],
43
+ context: IssueContext,
44
+ ) {
45
+ if (typeof itemId !== 'string') {
46
+ addIssue(
47
+ issues,
48
+ 'invalid-structure',
49
+ 'Item reference must be a string.',
50
+ context,
51
+ );
52
+ return;
53
+ }
54
+ const itemContext = { ...context, itemId };
55
+ if (!itemIds.has(itemId)) {
56
+ addIssue(
57
+ issues,
58
+ 'unknown-item',
59
+ 'Item reference must resolve to a declared item.',
60
+ itemContext,
61
+ );
62
+ }
63
+ if (references.has(itemId)) {
64
+ addIssue(
65
+ issues,
66
+ 'duplicate-reference',
67
+ 'An item must appear in exactly one placement or list position.',
68
+ itemContext,
69
+ );
70
+ }
71
+ references.add(itemId);
72
+ }
73
+
74
+ function collectGridIssues(
75
+ zone: Record<string, unknown>,
76
+ itemIds: Set<string>,
77
+ references: Set<string>,
78
+ issues: LayoutValidationIssue[],
79
+ context: IssueContext,
80
+ ) {
81
+ const dimensionsValid =
82
+ isPositiveInteger(zone.rows) && isPositiveInteger(zone.columns);
83
+ if (!dimensionsValid) {
84
+ addIssue(
85
+ issues,
86
+ 'invalid-dimension',
87
+ 'Rows and columns must be positive safe integers.',
88
+ context,
89
+ );
90
+ }
91
+ if (!Array.isArray(zone.placements)) {
92
+ addIssue(
93
+ issues,
94
+ 'invalid-structure',
95
+ 'Grid placements must be an array.',
96
+ context,
97
+ );
98
+ return;
99
+ }
100
+ const rectangles: GridPlacement[] = [];
101
+ for (const placement of zone.placements) {
102
+ if (!isRecord(placement)) {
103
+ addIssue(
104
+ issues,
105
+ 'invalid-structure',
106
+ 'Each grid placement must be an object.',
107
+ context,
108
+ );
109
+ continue;
110
+ }
111
+ collectReference(placement.itemId, itemIds, references, issues, context);
112
+ const itemContext = {
113
+ ...context,
114
+ ...(typeof placement.itemId === 'string'
115
+ ? { itemId: placement.itemId }
116
+ : {}),
117
+ };
118
+ if (
119
+ placement.placement !== undefined &&
120
+ placement.placement !== 'insert' &&
121
+ placement.placement !== 'exchange'
122
+ ) {
123
+ addIssue(
124
+ issues,
125
+ 'invalid-placement',
126
+ 'Placement must be insert or exchange when provided.',
127
+ itemContext,
128
+ );
129
+ }
130
+ const spanValid = isCellSpan(placement.span);
131
+ const positionValid = isCellPosition(placement.position);
132
+ if (!spanValid) {
133
+ addIssue(
134
+ issues,
135
+ 'invalid-span',
136
+ 'Span must contain positive safe integer rows and cols.',
137
+ itemContext,
138
+ );
139
+ }
140
+ if (!positionValid) {
141
+ addIssue(
142
+ issues,
143
+ 'invalid-position',
144
+ 'Position must contain safe integer row and col.',
145
+ itemContext,
146
+ );
147
+ }
148
+ if (!spanValid || !positionValid || !dimensionsValid) continue;
149
+ const rectangle = placement as GridPlacement;
150
+ if (
151
+ rectangle.position.row < 0 ||
152
+ rectangle.position.col < 0 ||
153
+ rectangle.position.row > (zone.rows as number) - rectangle.span.rows ||
154
+ rectangle.position.col > (zone.columns as number) - rectangle.span.cols
155
+ ) {
156
+ addIssue(
157
+ issues,
158
+ 'out-of-bounds',
159
+ 'Grid placement must fit inside its zone.',
160
+ itemContext,
161
+ );
162
+ continue;
163
+ }
164
+ for (const previous of rectangles) {
165
+ if (rectanglesOverlap(previous, rectangle)) {
166
+ addIssue(
167
+ issues,
168
+ 'overlap',
169
+ `Grid placement overlaps an earlier item (${previous.itemId}).`,
170
+ itemContext,
171
+ );
172
+ }
173
+ }
174
+ rectangles.push(rectangle);
175
+ }
176
+ if (zone.itemSpan !== undefined)
177
+ collectOrderedIssues(zone, dimensionsValid, issues, context);
178
+ }
179
+
180
+ /** An ordered grid keeps one span per item, reading-order slots and its capacity. */
181
+ function collectOrderedIssues(
182
+ zone: Record<string, unknown>,
183
+ dimensionsValid: boolean,
184
+ issues: LayoutValidationIssue[],
185
+ context: IssueContext,
186
+ ) {
187
+ const itemSpan = zone.itemSpan;
188
+ if (!isCellSpan(itemSpan)) {
189
+ addIssue(
190
+ issues,
191
+ 'invalid-span',
192
+ 'Ordered itemSpan must contain positive safe integer rows and cols.',
193
+ context,
194
+ );
195
+ return;
196
+ }
197
+ const placements = zone.placements as unknown[];
198
+ for (const placement of placements) {
199
+ if (!isRecord(placement) || !isCellSpan(placement.span)) continue;
200
+ if (
201
+ placement.span.rows !== itemSpan.rows ||
202
+ placement.span.cols !== itemSpan.cols
203
+ )
204
+ addIssue(
205
+ issues,
206
+ 'span-mismatch',
207
+ 'Every ordered item span must equal the zone itemSpan.',
208
+ {
209
+ ...context,
210
+ ...(typeof placement.itemId === 'string'
211
+ ? { itemId: placement.itemId }
212
+ : {}),
213
+ },
214
+ );
215
+ }
216
+ if (!dimensionsValid) return;
217
+ const slots = orderedSlots(
218
+ { rows: zone.rows as number, columns: zone.columns as number },
219
+ itemSpan,
220
+ );
221
+ // Avoid multiplying dimensions: their product can overflow despite valid inputs.
222
+ if (
223
+ placements.length > 0 &&
224
+ (slots.perRow === 0 ||
225
+ Math.ceil(placements.length / slots.perRow) > slots.rows)
226
+ ) {
227
+ addIssue(
228
+ issues,
229
+ 'capacity-exceeded',
230
+ 'Ordered item count exceeds the available slots.',
231
+ context,
232
+ );
233
+ return;
234
+ }
235
+ placements.forEach((placement, index) => {
236
+ if (!isRecord(placement) || !isCellPosition(placement.position)) return;
237
+ const expected = slotPosition(index, itemSpan, slots.perRow);
238
+ if (
239
+ placement.position.row !== expected.row ||
240
+ placement.position.col !== expected.col
241
+ )
242
+ addIssue(
243
+ issues,
244
+ 'invalid-position',
245
+ 'Ordered placements must occupy reading-order slots.',
246
+ {
247
+ ...context,
248
+ ...(typeof placement.itemId === 'string'
249
+ ? { itemId: placement.itemId }
250
+ : {}),
251
+ },
252
+ );
253
+ });
254
+ }
255
+
256
+ /** Validates the complete candidate without inspecting data or modifying inputs. */
257
+ export function validateLayoutState<T>(
258
+ state: LayoutState<T>,
259
+ ): LayoutValidationResult {
260
+ const issues: LayoutValidationIssue[] = [];
261
+ if (!isRecord(state)) {
262
+ return {
263
+ valid: false,
264
+ issues: [
265
+ {
266
+ code: 'invalid-structure',
267
+ message: 'Layout state must be an object.',
268
+ },
269
+ ],
270
+ };
271
+ }
272
+ if (!isRevision(state.revision)) {
273
+ addIssue(
274
+ issues,
275
+ 'invalid-revision',
276
+ 'Layout revision must be a nonnegative safe integer.',
277
+ );
278
+ }
279
+ const itemIds = new Set<string>();
280
+ if (!Array.isArray(state.items)) {
281
+ addIssue(issues, 'invalid-structure', 'Layout items must be an array.');
282
+ } else {
283
+ for (const item of state.items) {
284
+ if (!isRecord(item) || typeof item.id !== 'string') {
285
+ addIssue(
286
+ issues,
287
+ 'invalid-structure',
288
+ 'Each item must be an object with a string ID.',
289
+ );
290
+ } else if (itemIds.has(item.id)) {
291
+ addIssue(issues, 'duplicate-id', 'Item ID must be globally unique.', {
292
+ itemId: item.id,
293
+ });
294
+ } else {
295
+ itemIds.add(item.id);
296
+ }
297
+ // Do not read item.data, including getters or serialization hooks.
298
+ }
299
+ }
300
+ const zoneIds = new Set<string>();
301
+ const references = new Set<string>();
302
+ if (!Array.isArray(state.zones)) {
303
+ addIssue(issues, 'invalid-structure', 'Layout zones must be an array.');
304
+ } else {
305
+ for (const zone of state.zones) {
306
+ if (!isRecord(zone)) {
307
+ addIssue(issues, 'invalid-structure', 'Each zone must be an object.');
308
+ continue;
309
+ }
310
+ const context = typeof zone.id === 'string' ? { zoneId: zone.id } : {};
311
+ if (typeof zone.id !== 'string') {
312
+ addIssue(issues, 'invalid-structure', 'Zone ID must be a string.');
313
+ } else if (zoneIds.has(zone.id)) {
314
+ addIssue(
315
+ issues,
316
+ 'duplicate-id',
317
+ 'Zone ID must be globally unique.',
318
+ context,
319
+ );
320
+ } else {
321
+ zoneIds.add(zone.id);
322
+ }
323
+ if (zone.kind === 'grid') {
324
+ collectGridIssues(zone, itemIds, references, issues, context);
325
+ } else if (zone.kind === 'list') {
326
+ if (
327
+ zone.orientation !== 'vertical' &&
328
+ zone.orientation !== 'horizontal'
329
+ ) {
330
+ addIssue(
331
+ issues,
332
+ 'invalid-orientation',
333
+ 'List orientation must be vertical or horizontal.',
334
+ context,
335
+ );
336
+ }
337
+ if (!Array.isArray(zone.itemIds)) {
338
+ addIssue(
339
+ issues,
340
+ 'invalid-structure',
341
+ 'List item IDs must be an array.',
342
+ context,
343
+ );
344
+ } else {
345
+ for (const itemId of zone.itemIds) {
346
+ collectReference(itemId, itemIds, references, issues, context);
347
+ }
348
+ }
349
+ } else {
350
+ addIssue(
351
+ issues,
352
+ 'invalid-structure',
353
+ 'Zone kind must be grid or list.',
354
+ context,
355
+ );
356
+ }
357
+ }
358
+ }
359
+ for (const itemId of itemIds) {
360
+ if (!references.has(itemId)) {
361
+ addIssue(
362
+ issues,
363
+ 'unplaced-item',
364
+ 'Every declared item must belong to a zone.',
365
+ { itemId },
366
+ );
367
+ }
368
+ }
369
+ return issues.length === 0 ? { valid: true } : { valid: false, issues };
370
+ }
371
+
372
+ /**
373
+ * Imports existing spatial/ordered grids into explicit cell placements. Ordered
374
+ * grids keep their rendered positions and their `itemSpan`, so `toGridValue`
375
+ * restores the ordered strategy; they do not become one-dimensional lists.
376
+ * Proposal responses are session metadata and are not copied into layout state.
377
+ */
378
+ export function fromGridValue<T>(
379
+ value: GridValue<T>,
380
+ revision?: number,
381
+ ): LayoutConversionResult<LayoutState<T>> {
382
+ const validation = validateValue(value);
383
+ if (!validation.valid) return validation;
384
+ const nextRevision = revision ?? value.revision ?? 0;
385
+ if (!isRevision(nextRevision)) {
386
+ return {
387
+ valid: false,
388
+ issues: [
389
+ {
390
+ code: 'invalid-revision',
391
+ message: 'Layout revision must be a nonnegative safe integer.',
392
+ },
393
+ ],
394
+ };
395
+ }
396
+ const items: DndItem<T>[] = [];
397
+ const zones: GridLayoutZone[] = [];
398
+ for (const zone of value.zones) {
399
+ const layout = computeZoneLayout(zone);
400
+ if (!layout.valid) return layout;
401
+ zones.push({
402
+ id: zone.id,
403
+ kind: 'grid',
404
+ rows: zone.rows,
405
+ columns: zone.columns,
406
+ ...(zone.strategy === 'ordered'
407
+ ? { itemSpan: { rows: zone.itemSpan.rows, cols: zone.itemSpan.cols } }
408
+ : {}),
409
+ placements: layout.items.map(item => {
410
+ items.push({ id: item.id, data: item.data });
411
+ return {
412
+ itemId: item.id,
413
+ position: { ...item.position },
414
+ span: { ...item.span },
415
+ ...(item.placement === undefined
416
+ ? {}
417
+ : { placement: item.placement }),
418
+ };
419
+ }),
420
+ });
421
+ }
422
+ return { valid: true, value: { revision: nextRevision, items, zones } };
423
+ }
424
+
425
+ /**
426
+ * Exports all-grid state: zones with `itemSpan` become ordered grids again and
427
+ * the rest spatial grids; list zones require the future list adapter.
428
+ */
429
+ export function toGridValue<T>(
430
+ state: LayoutState<T>,
431
+ ): LayoutConversionResult<GridValue<T>> {
432
+ const validation = validateLayoutState(state);
433
+ if (!validation.valid) return validation;
434
+ const unsupported = state.zones.filter(zone => zone.kind !== 'grid');
435
+ if (unsupported.length > 0) {
436
+ return {
437
+ valid: false,
438
+ issues: unsupported.map(zone => ({
439
+ code: 'unsupported-zone',
440
+ zoneId: zone.id,
441
+ message: 'The grid adapter cannot convert list zones.',
442
+ })),
443
+ };
444
+ }
445
+ return { valid: true, value: layoutToGridValue(state) };
446
+ }
447
+
448
+ /**
449
+ * Internal unchecked export for a state that only contains grid zones and was
450
+ * validated on the way in (the controller adapter converts every candidate).
451
+ */
452
+ export function layoutToGridValue<T>(state: LayoutState<T>): GridValue<T> {
453
+ const items = new Map(state.items.map(item => [item.id, item]));
454
+ return {
455
+ revision: state.revision,
456
+ zones: (state.zones as GridLayoutZone[]).map(zone =>
457
+ zone.itemSpan
458
+ ? {
459
+ id: zone.id,
460
+ strategy: 'ordered',
461
+ rows: zone.rows,
462
+ columns: zone.columns,
463
+ itemSpan: { rows: zone.itemSpan.rows, cols: zone.itemSpan.cols },
464
+ items: zone.placements.map(placement => ({
465
+ id: placement.itemId,
466
+ data: items.get(placement.itemId)!.data,
467
+ span: { ...placement.span },
468
+ ...(placement.placement === undefined
469
+ ? {}
470
+ : { placement: placement.placement }),
471
+ })),
472
+ }
473
+ : {
474
+ id: zone.id,
475
+ strategy: 'spatial',
476
+ rows: zone.rows,
477
+ columns: zone.columns,
478
+ items: zone.placements.map(placement => ({
479
+ id: placement.itemId,
480
+ data: items.get(placement.itemId)!.data,
481
+ position: { ...placement.position },
482
+ span: { ...placement.span },
483
+ ...(placement.placement === undefined
484
+ ? {}
485
+ : { placement: placement.placement }),
486
+ })),
487
+ },
488
+ ),
489
+ };
490
+ }