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,56 @@
1
+ import type { PositionedItem } from '../types';
2
+
3
+ import type { PlacementSearchResult } from './search';
4
+ import type { SpatialMoveInput } from './spatial';
5
+
6
+ /**
7
+ * Move a full-width exchange item through a vertical band, preserving its holes.
8
+ * Null means this movement belongs to another policy; impossible is terminal.
9
+ */
10
+ export function rotateSpatialRows<T>({
11
+ items,
12
+ active,
13
+ origin,
14
+ columns,
15
+ }: SpatialMoveInput<T>): PlacementSearchResult<T> | null {
16
+ if (
17
+ !origin ||
18
+ active.placement === 'insert' ||
19
+ active.span.cols !== columns ||
20
+ active.position.col !== origin.position.col ||
21
+ active.position.row === origin.position.row
22
+ )
23
+ return null;
24
+
25
+ const firstRow = Math.min(origin.position.row, active.position.row);
26
+ // Both rectangles were validated within the zone, so their ends stay safe.
27
+ const endRow =
28
+ Math.max(origin.position.row, active.position.row) + active.span.rows;
29
+ const offset =
30
+ active.position.row > origin.position.row
31
+ ? -active.span.rows
32
+ : active.span.rows;
33
+ const placements: PositionedItem<T>[] = [];
34
+ let attempts = 0;
35
+ for (const item of items) {
36
+ const itemEnd = item.position.row + item.span.rows;
37
+ if (itemEnd <= firstRow || item.position.row >= endRow) {
38
+ placements.push(item);
39
+ continue;
40
+ }
41
+ attempts = 1;
42
+ if (item.position.row < firstRow || itemEnd > endRow)
43
+ return { status: 'impossible', attempts };
44
+ // The full-width source contains no other item. All touched items therefore
45
+ // occupy the remaining band and translate together by the source height.
46
+ placements.push({
47
+ ...item,
48
+ position: {
49
+ row: item.position.row + offset,
50
+ col: item.position.col,
51
+ },
52
+ });
53
+ }
54
+ placements.push(active);
55
+ return { status: 'ok', placements, attempts };
56
+ }
@@ -0,0 +1,358 @@
1
+ import type {
2
+ CellPosition,
3
+ CellSpan,
4
+ GridValue,
5
+ GridZone,
6
+ PackInput,
7
+ ValidationIssue,
8
+ ValidationResult,
9
+ } from '../types';
10
+
11
+ import { rectanglesOverlap } from './rectangles';
12
+
13
+ type IssueContext = Pick<ValidationIssue, 'zoneId' | 'itemId'>;
14
+ type Rectangle = { position: CellPosition; span: CellSpan; itemId?: string };
15
+
16
+ export function isRecord(value: unknown): value is Record<string, unknown> {
17
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
18
+ }
19
+
20
+ export function isPositiveInteger(value: unknown): value is number {
21
+ return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
22
+ }
23
+
24
+ export function isCellSpan(value: unknown): value is CellSpan {
25
+ return (
26
+ isRecord(value) &&
27
+ isPositiveInteger(value.rows) &&
28
+ isPositiveInteger(value.cols)
29
+ );
30
+ }
31
+
32
+ export function isCellPosition(value: unknown): value is CellPosition {
33
+ return (
34
+ isRecord(value) &&
35
+ typeof value.row === 'number' &&
36
+ Number.isSafeInteger(value.row) &&
37
+ typeof value.col === 'number' &&
38
+ Number.isSafeInteger(value.col)
39
+ );
40
+ }
41
+
42
+ function result(issues: ValidationIssue[]): ValidationResult {
43
+ return issues.length === 0 ? { valid: true } : { valid: false, issues };
44
+ }
45
+
46
+ function addIssue(
47
+ issues: ValidationIssue[],
48
+ code: ValidationIssue['code'],
49
+ message: string,
50
+ context: IssueContext = {},
51
+ ) {
52
+ issues.push({ code, ...context, message });
53
+ }
54
+
55
+ function validateDimensions(
56
+ rows: unknown,
57
+ columns: unknown,
58
+ issues: ValidationIssue[],
59
+ context: IssueContext = {},
60
+ ): boolean {
61
+ if (isPositiveInteger(rows) && isPositiveInteger(columns)) {
62
+ return true;
63
+ }
64
+ addIssue(
65
+ issues,
66
+ 'invalid-dimension',
67
+ 'Rows and columns must be positive safe integers.',
68
+ context,
69
+ );
70
+ return false;
71
+ }
72
+
73
+ function validateItem(
74
+ item: unknown,
75
+ itemIds: Set<string>,
76
+ issues: ValidationIssue[],
77
+ zoneContext: IssueContext,
78
+ ): item is Record<string, unknown> {
79
+ if (!isRecord(item)) {
80
+ addIssue(
81
+ issues,
82
+ 'invalid-structure',
83
+ 'Each item must be an object.',
84
+ zoneContext,
85
+ );
86
+ return false;
87
+ }
88
+ const context = {
89
+ ...zoneContext,
90
+ ...(typeof item.id === 'string' ? { itemId: item.id } : {}),
91
+ };
92
+ if (typeof item.id !== 'string') {
93
+ addIssue(issues, 'invalid-structure', 'Item ID must be a string.', context);
94
+ } else if (itemIds.has(item.id)) {
95
+ addIssue(issues, 'duplicate-id', 'Item ID must be unique.', context);
96
+ } else {
97
+ itemIds.add(item.id);
98
+ }
99
+ if (!isCellSpan(item.span)) {
100
+ addIssue(
101
+ issues,
102
+ 'invalid-span',
103
+ 'Item span must contain positive safe integer rows and cols.',
104
+ context,
105
+ );
106
+ }
107
+ if (
108
+ item.placement !== undefined &&
109
+ item.placement !== 'insert' &&
110
+ item.placement !== 'exchange'
111
+ ) {
112
+ addIssue(
113
+ issues,
114
+ 'invalid-placement',
115
+ 'Item placement must be insert or exchange when provided.',
116
+ context,
117
+ );
118
+ }
119
+ // data is deliberately not read: it is opaque to the layout engine.
120
+ return true;
121
+ }
122
+
123
+ function collectZoneIssues(
124
+ zone: unknown,
125
+ zoneIds: Set<string>,
126
+ itemIds: Set<string>,
127
+ issues: ValidationIssue[],
128
+ ) {
129
+ if (!isRecord(zone)) {
130
+ addIssue(issues, 'invalid-structure', 'Each zone must be an object.');
131
+ return;
132
+ }
133
+ const context = typeof zone.id === 'string' ? { zoneId: zone.id } : {};
134
+ if (typeof zone.id !== 'string') {
135
+ addIssue(issues, 'invalid-structure', 'Zone ID must be a string.');
136
+ } else if (zoneIds.has(zone.id)) {
137
+ addIssue(issues, 'duplicate-id', 'Zone ID must be unique.', context);
138
+ } else {
139
+ zoneIds.add(zone.id);
140
+ }
141
+ const dimensionsValid = validateDimensions(
142
+ zone.rows,
143
+ zone.columns,
144
+ issues,
145
+ context,
146
+ );
147
+ if (zone.strategy !== 'spatial' && zone.strategy !== 'ordered') {
148
+ addIssue(
149
+ issues,
150
+ 'invalid-structure',
151
+ 'Zone strategy must be spatial or ordered.',
152
+ context,
153
+ );
154
+ }
155
+ if (zone.strategy === 'ordered') {
156
+ if (zone.itemSpan === undefined) {
157
+ addIssue(
158
+ issues,
159
+ 'missing-item-span',
160
+ 'Ordered zones require itemSpan.',
161
+ context,
162
+ );
163
+ } else if (!isCellSpan(zone.itemSpan)) {
164
+ addIssue(
165
+ issues,
166
+ 'invalid-span',
167
+ 'Ordered itemSpan must contain positive safe integer rows and cols.',
168
+ context,
169
+ );
170
+ }
171
+ }
172
+ if (!Array.isArray(zone.items)) {
173
+ addIssue(
174
+ issues,
175
+ 'invalid-structure',
176
+ 'Zone items must be an array.',
177
+ context,
178
+ );
179
+ return;
180
+ }
181
+
182
+ const rectangles: Rectangle[] = [];
183
+ for (const item of zone.items) {
184
+ if (!validateItem(item, itemIds, issues, context)) {
185
+ continue;
186
+ }
187
+ const itemContext = {
188
+ ...context,
189
+ ...(typeof item.id === 'string' ? { itemId: item.id } : {}),
190
+ };
191
+ if (zone.strategy === 'ordered') {
192
+ if (
193
+ isCellSpan(zone.itemSpan) &&
194
+ isCellSpan(item.span) &&
195
+ (item.span.rows !== zone.itemSpan.rows ||
196
+ item.span.cols !== zone.itemSpan.cols)
197
+ ) {
198
+ addIssue(
199
+ issues,
200
+ 'span-mismatch',
201
+ 'Every ordered item span must equal the zone itemSpan.',
202
+ itemContext,
203
+ );
204
+ }
205
+ } else if (zone.strategy === 'spatial') {
206
+ if (!isCellPosition(item.position)) {
207
+ addIssue(
208
+ issues,
209
+ 'invalid-position',
210
+ 'Spatial item position must contain safe integer row and col.',
211
+ itemContext,
212
+ );
213
+ continue;
214
+ }
215
+ if (!isCellSpan(item.span) || !dimensionsValid) {
216
+ continue;
217
+ }
218
+ if (
219
+ item.position.row < 0 ||
220
+ item.position.col < 0 ||
221
+ item.position.row > (zone.rows as number) - item.span.rows ||
222
+ item.position.col > (zone.columns as number) - item.span.cols
223
+ ) {
224
+ addIssue(
225
+ issues,
226
+ 'out-of-bounds',
227
+ 'Spatial item rectangle must fit inside the zone.',
228
+ itemContext,
229
+ );
230
+ continue;
231
+ }
232
+ const rectangle = {
233
+ position: item.position,
234
+ span: item.span,
235
+ ...('itemId' in itemContext ? { itemId: itemContext.itemId } : {}),
236
+ };
237
+ for (const previous of rectangles) {
238
+ if (rectanglesOverlap(previous, rectangle)) {
239
+ addIssue(
240
+ issues,
241
+ 'overlap',
242
+ `Spatial item overlaps an earlier item${
243
+ previous.itemId === undefined ? '.' : ` (${previous.itemId}).`
244
+ }`,
245
+ itemContext,
246
+ );
247
+ }
248
+ }
249
+ rectangles.push(rectangle);
250
+ }
251
+ }
252
+
253
+ if (
254
+ zone.strategy === 'ordered' &&
255
+ dimensionsValid &&
256
+ isCellSpan(zone.itemSpan)
257
+ ) {
258
+ const slotsPerRow = Math.floor(
259
+ (zone.columns as number) / zone.itemSpan.cols,
260
+ );
261
+ const slotRows = Math.floor((zone.rows as number) / zone.itemSpan.rows);
262
+ // Avoid multiplying dimensions: their product can overflow despite valid inputs.
263
+ const exceedsCapacity =
264
+ zone.items.length > 0 &&
265
+ (slotsPerRow === 0 ||
266
+ Math.ceil(zone.items.length / slotsPerRow) > slotRows);
267
+ if (exceedsCapacity) {
268
+ addIssue(
269
+ issues,
270
+ 'capacity-exceeded',
271
+ 'Ordered item count exceeds the available slots.',
272
+ context,
273
+ );
274
+ }
275
+ }
276
+ }
277
+
278
+ export function validateZone<T>(zone: GridZone<T>): ValidationResult {
279
+ const issues: ValidationIssue[] = [];
280
+ collectZoneIssues(zone, new Set(), new Set(), issues);
281
+ return result(issues);
282
+ }
283
+
284
+ export function validateValue<T>(value: GridValue<T>): ValidationResult {
285
+ const issues: ValidationIssue[] = [];
286
+ if (!isRecord(value)) {
287
+ return {
288
+ valid: false,
289
+ issues: [
290
+ { code: 'invalid-structure', message: 'Grid value must be an object.' },
291
+ ],
292
+ };
293
+ }
294
+ if (!Array.isArray(value.zones)) {
295
+ addIssue(issues, 'invalid-structure', 'Grid zones must be an array.');
296
+ } else {
297
+ const zoneIds = new Set<string>();
298
+ const itemIds = new Set<string>();
299
+ for (const zone of value.zones) {
300
+ collectZoneIssues(zone, zoneIds, itemIds, issues);
301
+ }
302
+ }
303
+ if (
304
+ value.revision !== undefined &&
305
+ (!Number.isSafeInteger(value.revision) || value.revision < 0)
306
+ ) {
307
+ addIssue(
308
+ issues,
309
+ 'invalid-structure',
310
+ 'Revision must be a non-negative safe integer.',
311
+ );
312
+ }
313
+ if (
314
+ value.proposalResponse !== undefined &&
315
+ (!isRecord(value.proposalResponse) ||
316
+ typeof value.proposalResponse.sessionId !== 'string' ||
317
+ typeof value.proposalResponse.accepted !== 'boolean' ||
318
+ (value.proposalResponse.baseRevision !== undefined &&
319
+ (!Number.isSafeInteger(value.proposalResponse.baseRevision) ||
320
+ value.proposalResponse.baseRevision < 0)))
321
+ ) {
322
+ addIssue(
323
+ issues,
324
+ 'invalid-structure',
325
+ 'Proposal response must contain a string sessionId and boolean accepted.',
326
+ );
327
+ }
328
+ return result(issues);
329
+ }
330
+
331
+ export function validatePackInput<T>(input: PackInput<T>): ValidationResult {
332
+ const issues: ValidationIssue[] = [];
333
+ if (!isRecord(input)) {
334
+ return {
335
+ valid: false,
336
+ issues: [
337
+ { code: 'invalid-structure', message: 'Pack input must be an object.' },
338
+ ],
339
+ };
340
+ }
341
+ validateDimensions(input.rows, input.columns, issues);
342
+ if (!isPositiveInteger(input.searchBudget)) {
343
+ addIssue(
344
+ issues,
345
+ 'invalid-budget',
346
+ 'Search budget must be a positive safe integer.',
347
+ );
348
+ }
349
+ if (!Array.isArray(input.items)) {
350
+ addIssue(issues, 'invalid-structure', 'Pack items must be an array.');
351
+ } else {
352
+ const itemIds = new Set<string>();
353
+ for (const item of input.items) {
354
+ validateItem(item, itemIds, issues, {});
355
+ }
356
+ }
357
+ return result(issues);
358
+ }