@ankhorage/zora 25.7.0 → 25.8.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 (311) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -1
  3. package/dist/capabilities/index.d.ts +2 -74
  4. package/dist/capabilities/index.d.ts.map +1 -1
  5. package/dist/capabilities/index.js +6170 -59
  6. package/dist/capabilities/index.js.map +119 -1
  7. package/dist/cli/index.d.ts +2 -74
  8. package/dist/cli/index.d.ts.map +1 -1
  9. package/dist/cli/index.js +2 -1
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.d.ts +5 -0
  12. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.d.ts.map +1 -0
  13. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js +40 -0
  14. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js.map +1 -0
  15. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.d.ts +2 -0
  16. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.d.ts.map +1 -0
  17. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.js +2 -0
  18. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.js.map +1 -0
  19. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.d.ts +11 -0
  20. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.d.ts.map +1 -0
  21. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.js +5 -0
  22. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.js.map +1 -0
  23. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.d.ts +11 -0
  24. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.d.ts.map +1 -0
  25. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js +20 -0
  26. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js.map +1 -0
  27. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.d.ts +17 -0
  28. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.d.ts.map +1 -0
  29. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.js +76 -0
  30. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.js.map +1 -0
  31. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.d.ts +21 -0
  32. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.d.ts.map +1 -0
  33. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.js +20 -0
  34. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.js.map +1 -0
  35. package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts +4 -0
  36. package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts.map +1 -0
  37. package/dist/features/grid-interactions/application/createGridInteractionsController.js +191 -0
  38. package/dist/features/grid-interactions/application/createGridInteractionsController.js.map +1 -0
  39. package/dist/features/grid-interactions/meta.d.ts +11 -0
  40. package/dist/features/grid-interactions/meta.d.ts.map +1 -0
  41. package/dist/features/grid-interactions/meta.js +11 -0
  42. package/dist/features/grid-interactions/meta.js.map +1 -0
  43. package/dist/features/grid-interactions/public.d.ts +4 -0
  44. package/dist/features/grid-interactions/public.d.ts.map +1 -0
  45. package/dist/features/grid-interactions/public.js +3 -0
  46. package/dist/features/grid-interactions/public.js.map +1 -0
  47. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.d.ts.map +1 -1
  48. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js +73 -19
  49. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js.map +1 -1
  50. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.d.ts +9 -0
  51. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.d.ts.map +1 -0
  52. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.js +5 -0
  53. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.js.map +1 -0
  54. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.d.ts +9 -0
  55. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.d.ts.map +1 -0
  56. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.js +5 -0
  57. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.js.map +1 -0
  58. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.d.ts +9 -0
  59. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.d.ts.map +1 -0
  60. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.js +28 -0
  61. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.js.map +1 -0
  62. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts +14 -0
  63. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts.map +1 -0
  64. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js +50 -0
  65. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js.map +1 -0
  66. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.d.ts +18 -0
  67. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.d.ts.map +1 -0
  68. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.js +56 -0
  69. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.js.map +1 -0
  70. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.d.ts +4 -0
  71. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.d.ts.map +1 -0
  72. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.js +65 -0
  73. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.js.map +1 -0
  74. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts +8 -1
  75. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts.map +1 -1
  76. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js +15 -4
  77. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js.map +1 -1
  78. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.d.ts.map +1 -1
  79. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js +2 -0
  80. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js.map +1 -1
  81. package/dist/features/registry/componentMeta.d.ts.map +1 -1
  82. package/dist/features/registry/componentMeta.js +2 -0
  83. package/dist/features/registry/componentMeta.js.map +1 -1
  84. package/dist/features/registry/createEventCapabilities.d.ts +4 -0
  85. package/dist/features/registry/createEventCapabilities.d.ts.map +1 -0
  86. package/dist/features/registry/createEventCapabilities.js +76 -0
  87. package/dist/features/registry/createEventCapabilities.js.map +1 -0
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +1 -0
  91. package/dist/index.js.map +1 -1
  92. package/dist/types/grid-interactions.d.ts +58 -0
  93. package/dist/types/grid-interactions.d.ts.map +1 -0
  94. package/dist/types/grid-interactions.js +2 -0
  95. package/dist/types/grid-interactions.js.map +1 -0
  96. package/dist/types/matrix-grid.d.ts +2 -0
  97. package/dist/types/matrix-grid.d.ts.map +1 -1
  98. package/dist/types/matrix-grid.js.map +1 -1
  99. package/package.json +1825 -7
  100. package/src/capabilities/index.test.ts +87 -15
  101. package/src/capabilities/index.ts +5 -2
  102. package/src/cli/index.test.ts +5 -3
  103. package/src/cli/index.ts +5 -2
  104. package/src/features/grid-interactions/adapters/inbound/GridInteractions.test.tsx +143 -0
  105. package/src/features/grid-interactions/adapters/inbound/GridInteractions.tsx +79 -0
  106. package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.tsx +14 -0
  107. package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.tsx +1 -0
  108. package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx +41 -0
  109. package/src/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.test.ts +98 -0
  110. package/src/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.ts +94 -0
  111. package/src/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.test.ts +33 -0
  112. package/src/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.ts +45 -0
  113. package/src/features/grid-interactions/application/createGridInteractionsController.test.ts +115 -0
  114. package/src/features/grid-interactions/application/createGridInteractionsController.ts +231 -0
  115. package/src/features/grid-interactions/meta.ts +12 -0
  116. package/src/features/grid-interactions/public.ts +11 -0
  117. package/src/features/matrix-grid/adapters/inbound/MatrixGrid.test.tsx +280 -2
  118. package/src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx +121 -31
  119. package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.tsx +11 -0
  120. package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.tsx +11 -0
  121. package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.tsx +43 -0
  122. package/src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts +70 -0
  123. package/src/features/matrix-grid/application/createMatrixGridSparseIndex.test.ts +35 -0
  124. package/src/features/matrix-grid/application/createMatrixGridSparseIndex.ts +105 -0
  125. package/src/features/matrix-grid/application/getMatrixGridNextFocusId.test.ts +93 -0
  126. package/src/features/matrix-grid/application/getMatrixGridNextFocusId.ts +112 -0
  127. package/src/features/matrix-grid/application/resolveMatrixGridSelection.test.ts +63 -2
  128. package/src/features/matrix-grid/application/resolveMatrixGridSelection.ts +27 -4
  129. package/src/features/registry/ZORA_COMPONENT_REGISTRY.ts +2 -0
  130. package/src/features/registry/componentMeta.ts +2 -0
  131. package/src/features/registry/createEventCapabilities.ts +108 -0
  132. package/src/index.ts +11 -0
  133. package/src/structureOwnership.test.ts +5 -0
  134. package/src/types/grid-interactions.ts +72 -0
  135. package/src/types/matrix-grid.ts +2 -0
  136. package/web-dist/chunks/{Accordion-5g05kfmx.js → Accordion-0q2wzjdd.js} +4 -4
  137. package/web-dist/chunks/{Accordion-b96evb46.js → Accordion-1fsrnq73.js} +1 -1
  138. package/web-dist/chunks/{Accordion-h3y8qev2.js → Accordion-38ts3gnn.js} +2 -2
  139. package/web-dist/chunks/{Accordion-jnc315h7.js → Accordion-8pkw32q9.js} +2 -2
  140. package/web-dist/chunks/{Accordion-h7zsx9eg.js → Accordion-9jrxktne.js} +3 -3
  141. package/web-dist/chunks/{Accordion-9w8wg9mf.js → Accordion-9rnsg9pk.js} +1 -1
  142. package/web-dist/chunks/{Accordion-4zqd7twp.js → Accordion-9ygakvwk.js} +5 -5
  143. package/web-dist/chunks/{Accordion-fhnazn3n.js → Accordion-b74j4hdt.js} +1 -1
  144. package/web-dist/chunks/{Accordion-nssr5gep.js → Accordion-c50pywj3.js} +2 -2
  145. package/web-dist/chunks/{Accordion-pvj5npzc.js → Accordion-ddrn94v3.js} +3 -3
  146. package/web-dist/chunks/{Accordion-090y18cv.js → Accordion-dk0dtyc0.js} +3 -3
  147. package/web-dist/chunks/{Accordion-59pa0677.js → Accordion-fcczvwgg.js} +0 -1
  148. package/web-dist/chunks/{Accordion-sp4k08kw.js → Accordion-geah5s4f.js} +4 -4
  149. package/web-dist/chunks/{Accordion-6rrqpb2h.js → Accordion-h4ekm5cp.js} +2 -2
  150. package/web-dist/chunks/{Accordion-3d4wkzg7.js → Accordion-hecpczme.js} +5 -5
  151. package/web-dist/chunks/{Accordion-a6551mh4.js → Accordion-hn43wt45.js} +1 -1
  152. package/web-dist/chunks/{Accordion-sw7ep29d.js → Accordion-hnx7hx55.js} +2 -2
  153. package/web-dist/chunks/{Accordion-rsvphbe4.js → Accordion-j1chy29e.js} +1 -1
  154. package/web-dist/chunks/{Accordion-ewf1zen9.js → Accordion-k9cxpg08.js} +5 -5
  155. package/web-dist/chunks/{Accordion-5df6cgyf.js → Accordion-md1g54wg.js} +1 -1
  156. package/web-dist/chunks/{Accordion-kjtcbe09.js → Accordion-p1ebrt17.js} +3 -3
  157. package/web-dist/chunks/{Accordion-qn398m1x.js → Accordion-r2g18vxa.js} +1 -1
  158. package/web-dist/chunks/{Accordion-zt9yghdz.js → Accordion-rctfgyfv.js} +4 -4
  159. package/web-dist/chunks/{Accordion-88t8fgxv.js → Accordion-rfc5mdbv.js} +1 -1
  160. package/web-dist/chunks/{Accordion-fmzxat3v.js → Accordion-sdnzqktc.js} +4 -4
  161. package/web-dist/chunks/{Accordion-v0grdh6q.js → Accordion-sq7ya2ye.js} +2 -2
  162. package/web-dist/chunks/{Accordion-ejx5gcf9.js → Accordion-t5scffdr.js} +9 -9
  163. package/web-dist/chunks/{Accordion-sy1cc5my.js → Accordion-ts788t7b.js} +3 -3
  164. package/web-dist/chunks/{Accordion-4s68t3h7.js → Accordion-v3rzy273.js} +6 -6
  165. package/web-dist/chunks/{Accordion-7tjz25jh.js → Accordion-vbzjq3z8.js} +1 -1
  166. package/web-dist/chunks/{Accordion-jh72azm2.js → Accordion-vg337250.js} +2 -2
  167. package/web-dist/chunks/{Accordion-42jfy1a1.js → Accordion-vj1m2wck.js} +1 -1
  168. package/web-dist/chunks/{Accordion-tqpsmdrk.js → Accordion-vxg5sw01.js} +4 -4
  169. package/web-dist/chunks/{Accordion-61ntvh14.js → Accordion-w59y0jdf.js} +4 -4
  170. package/web-dist/chunks/{Accordion-fjxcn6g9.js → Accordion-wcmrasqx.js} +8 -8
  171. package/web-dist/chunks/{Accordion-r1cj2w9e.js → Accordion-wpk8dxqp.js} +3 -3
  172. package/web-dist/chunks/{Accordion-w6qedeba.js → Accordion-x055n3b3.js} +1 -1
  173. package/web-dist/chunks/{Accordion-dbf93gxh.js → Accordion-xcm1qc39.js} +1 -1
  174. package/web-dist/chunks/{Accordion-7kq500r1.js → Accordion-xgxxg1hz.js} +1 -1
  175. package/web-dist/chunks/{Accordion-a4cma5b6.js → Accordion-y3083394.js} +3 -3
  176. package/web-dist/chunks/{Accordion-40tenzcg.js → Accordion-yexfbrzg.js} +4 -4
  177. package/web-dist/chunks/{Accordion-2r0gqk3x.js → Accordion-ztw7zp6v.js} +5 -5
  178. package/web-dist/chunks/{Accordion-bz04casa.js → Accordion-zypyr9xw.js} +211 -6
  179. package/web-dist/components/accordion/Accordion.js +5 -5
  180. package/web-dist/components/accordion-item/AccordionItem.js +5 -5
  181. package/web-dist/components/activity-indicator/ActivityIndicator.js +1 -1
  182. package/web-dist/components/app-bar/AppBar.js +7 -7
  183. package/web-dist/components/app-header/AppHeader.js +7 -7
  184. package/web-dist/components/app-shell/AppShell.js +2 -2
  185. package/web-dist/components/auth-screen/AuthScreen.js +10 -10
  186. package/web-dist/components/avatar/Avatar.js +5 -5
  187. package/web-dist/components/avatar-group/AvatarGroup.js +5 -5
  188. package/web-dist/components/badge/Badge.js +2 -2
  189. package/web-dist/components/barcode-scanner-view/BarcodeScannerView.js +10 -10
  190. package/web-dist/components/bottom-sheet/BottomSheet.js +3 -3
  191. package/web-dist/components/bottom-sheet-flat-list/BottomSheetFlatList.js +3 -3
  192. package/web-dist/components/bottom-sheet-scroll-view/BottomSheetScrollView.js +3 -3
  193. package/web-dist/components/bottom-sheet-section-list/BottomSheetSectionList.js +3 -3
  194. package/web-dist/components/bottom-sheet-view/BottomSheetView.js +3 -3
  195. package/web-dist/components/bottom-sheet-virtualized-list/BottomSheetVirtualizedList.js +3 -3
  196. package/web-dist/components/breadcrumbs/Breadcrumbs.js +5 -5
  197. package/web-dist/components/button/Button.js +3 -3
  198. package/web-dist/components/button-group/ButtonGroup.js +3 -3
  199. package/web-dist/components/camera-permission-view/CameraPermissionView.js +10 -10
  200. package/web-dist/components/card/Card.js +9 -9
  201. package/web-dist/components/card-back/CardBack.js +2 -2
  202. package/web-dist/components/card-hand/CardHand.js +2 -2
  203. package/web-dist/components/chat-list-item/ChatListItem.js +7 -7
  204. package/web-dist/components/checkbox/Checkbox.js +4 -4
  205. package/web-dist/components/checkbox-group/CheckboxGroup.js +4 -4
  206. package/web-dist/components/chess-board/ChessBoard.js +2 -2
  207. package/web-dist/components/chip/Chip.js +5 -5
  208. package/web-dist/components/chip-group/ChipGroup.js +5 -5
  209. package/web-dist/components/collection-editor/CollectionEditor.js +9 -9
  210. package/web-dist/components/content-rail/ContentRail.js +4 -4
  211. package/web-dist/components/data-table/DataTable.js +14 -14
  212. package/web-dist/components/date-picker/DatePicker.js +5 -5
  213. package/web-dist/components/dialog/Dialog.js +4 -4
  214. package/web-dist/components/divider/Divider.js +2 -2
  215. package/web-dist/components/empty-state/EmptyState.js +10 -10
  216. package/web-dist/components/explorer/Explorer.js +7 -7
  217. package/web-dist/components/field/Field.js +4 -4
  218. package/web-dist/components/file-explorer/FileExplorer.js +7 -7
  219. package/web-dist/components/flat-list/FlatList.js +5 -5
  220. package/web-dist/components/forgot-password-form/ForgotPasswordForm.js +10 -10
  221. package/web-dist/components/form/Form.js +6 -6
  222. package/web-dist/components/form-actions/FormActions.js +6 -6
  223. package/web-dist/components/form-error/FormError.js +6 -6
  224. package/web-dist/components/game/Game.js +3 -3
  225. package/web-dist/components/game-entity/GameEntity.js +3 -3
  226. package/web-dist/components/game-field/GameField.js +3 -3
  227. package/web-dist/components/game-input-zone/GameInputZone.js +3 -3
  228. package/web-dist/components/game-measurement-probe/GameMeasurementProbe.js +3 -3
  229. package/web-dist/components/game-overlay/GameOverlay.js +3 -3
  230. package/web-dist/components/gradient/Gradient.js +3 -3
  231. package/web-dist/components/gradient-renderer-provider/GradientRendererProvider.js +3 -3
  232. package/web-dist/components/graph-view/GraphView.js +2 -2
  233. package/web-dist/components/grid/Grid.js +2 -2
  234. package/web-dist/components/grid-interactions/GridInteractions.d.ts +3 -0
  235. package/web-dist/components/grid-interactions/GridInteractions.js +371 -0
  236. package/web-dist/components/grid-interactions/index.d.ts +1 -0
  237. package/web-dist/components/grid-interactions/index.js +2 -0
  238. package/web-dist/components/grid-line-overlay/GridLineOverlay.js +2 -2
  239. package/web-dist/components/grid-ruler/GridRuler.js +2 -2
  240. package/web-dist/components/grid-view/GridView.js +1 -1
  241. package/web-dist/components/heading/Heading.js +2 -2
  242. package/web-dist/components/hero/Hero.js +9 -9
  243. package/web-dist/components/icon/Icon.js +2 -2
  244. package/web-dist/components/icon-button/IconButton.js +3 -3
  245. package/web-dist/components/image/Image.js +4 -4
  246. package/web-dist/components/keyboard-avoiding-view/KeyboardAvoidingView.js +2 -2
  247. package/web-dist/components/list/List.js +5 -5
  248. package/web-dist/components/list-item/ListItem.js +5 -5
  249. package/web-dist/components/list-section/ListSection.js +5 -5
  250. package/web-dist/components/matrix-grid/MatrixGrid.js +296 -47
  251. package/web-dist/components/media-card/MediaCard.js +9 -9
  252. package/web-dist/components/media-explorer/MediaExplorer.js +7 -7
  253. package/web-dist/components/message-bubble/MessageBubble.js +7 -7
  254. package/web-dist/components/metric-card/MetricCard.js +9 -9
  255. package/web-dist/components/missing-element/MissingElement.js +4 -4
  256. package/web-dist/components/o-auth-provider-button/OAuthProviderButton.js +10 -10
  257. package/web-dist/components/o-auth-provider-list/OAuthProviderList.js +10 -10
  258. package/web-dist/components/opening-book/OpeningBook.js +2 -2
  259. package/web-dist/components/otp-form/OtpForm.js +10 -10
  260. package/web-dist/components/pagination/Pagination.js +4 -4
  261. package/web-dist/components/palette-item/PaletteItem.js +9 -9
  262. package/web-dist/components/playing-card/PlayingCard.js +2 -2
  263. package/web-dist/components/poker-training-table/PokerTrainingTable.js +2 -2
  264. package/web-dist/components/popover-menu/PopoverMenu.js +4 -4
  265. package/web-dist/components/post-card/PostCard.js +9 -9
  266. package/web-dist/components/product-card/ProductCard.js +9 -9
  267. package/web-dist/components/progress/Progress.js +4 -4
  268. package/web-dist/components/progress-ring/ProgressRing.js +4 -4
  269. package/web-dist/components/radio/Radio.js +5 -5
  270. package/web-dist/components/radio-group/RadioGroup.js +5 -5
  271. package/web-dist/components/rating/Rating.js +3 -3
  272. package/web-dist/components/reader/Reader.js +10 -10
  273. package/web-dist/components/scan-overlay/ScanOverlay.js +10 -10
  274. package/web-dist/components/screen/Screen.js +2 -2
  275. package/web-dist/components/screen-section/ScreenSection.js +4 -4
  276. package/web-dist/components/scroll-view/ScrollView.js +2 -2
  277. package/web-dist/components/search-input/SearchInput.js +4 -4
  278. package/web-dist/components/section-header/SectionHeader.js +4 -4
  279. package/web-dist/components/section-list/SectionList.js +5 -5
  280. package/web-dist/components/select/Select.js +4 -4
  281. package/web-dist/components/selectable-item/SelectableItem.js +2 -2
  282. package/web-dist/components/selection-provider/SelectionProvider.js +2 -2
  283. package/web-dist/components/sign-in-form/SignInForm.js +10 -10
  284. package/web-dist/components/sign-up-form/SignUpForm.js +10 -10
  285. package/web-dist/components/skeleton/Skeleton.js +12 -12
  286. package/web-dist/components/skeleton-card/SkeletonCard.js +12 -12
  287. package/web-dist/components/skeleton-list/SkeletonList.js +12 -12
  288. package/web-dist/components/skeleton-text/SkeletonText.js +12 -12
  289. package/web-dist/components/spatial-grid/SpatialGrid.js +1 -1
  290. package/web-dist/components/surface/Surface.js +2 -2
  291. package/web-dist/components/switch/Switch.js +1 -1
  292. package/web-dist/components/tab/Tab.js +2 -2
  293. package/web-dist/components/tab-list/TabList.js +2 -2
  294. package/web-dist/components/tab-panel/TabPanel.js +2 -2
  295. package/web-dist/components/tabletop-table/TabletopTable.js +2 -2
  296. package/web-dist/components/tabs/Tabs.js +2 -2
  297. package/web-dist/components/text/Text.js +2 -2
  298. package/web-dist/components/text-input/TextInput.js +4 -4
  299. package/web-dist/components/tile-grid/TileGrid.js +1 -1
  300. package/web-dist/components/time-grid/TimeGrid.js +2 -2
  301. package/web-dist/components/time-picker/TimePicker.js +5 -5
  302. package/web-dist/components/timeline/Timeline.js +4 -4
  303. package/web-dist/components/toast/Toast.js +2 -2
  304. package/web-dist/components/toast-provider/ToastProvider.js +2 -2
  305. package/web-dist/components/toolbar/Toolbar.js +9 -9
  306. package/web-dist/components/tree-item/TreeItem.js +9 -9
  307. package/web-dist/components/tree-view/TreeView.js +1 -1
  308. package/web-dist/components/uploader/Uploader.js +12 -12
  309. package/web-dist/components/view/View.js +2 -2
  310. package/web-dist/manifest.json +787 -772
  311. package/web-dist/runtime/ZoraProvider.js +3 -3
@@ -1,9 +1,11 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
 
3
- import { isCapability } from '@ankhorage/contracts/capabilities';
3
+ import { isCapability, isCapabilityId } from '@ankhorage/capability';
4
+ import type { Capability } from '@ankhorage/contracts/capability';
4
5
  import { isRecord } from '@ankhorage/utility/object';
5
6
  import { expect, test } from 'bun:test';
6
7
 
8
+ import { ZORA_COMPONENT_META } from '../features/registry';
7
9
  import { CAPABILITIES } from './index';
8
10
 
9
11
  const packageJson: unknown = JSON.parse(
@@ -13,11 +15,66 @@ const packageCapabilities =
13
15
  isRecord(packageJson) && isRecord(packageJson.ankh) ? packageJson.ankh.capabilities : undefined;
14
16
 
15
17
  test('publishes valid, uniquely identified canonical ZORA capabilities', () => {
16
- expect(CAPABILITIES).toHaveLength(2);
17
18
  expect(CAPABILITIES.every(isCapability)).toBe(true);
18
19
  expect(new Set(CAPABILITIES.map((capability) => capability.id)).size).toBe(CAPABILITIES.length);
19
20
  });
20
21
 
22
+ test('projects each direct-manifest event metadata entry into one canonical event capability', () => {
23
+ const metadataEvents = Object.values(ZORA_COMPONENT_META)
24
+ .filter((component) => component.directManifestNode)
25
+ .flatMap((component) => Object.values(component.events ?? {}));
26
+ const eventCapabilities = CAPABILITIES.filter((capability) => capability.access.includes('emit'));
27
+
28
+ expect(eventCapabilities).toHaveLength(
29
+ new Set(metadataEvents.map((event) => event.eventType)).size,
30
+ );
31
+ const eventCapabilitiesById = new Map<Capability['id'], Capability>(
32
+ eventCapabilities.map((capability) => [capability.id, capability]),
33
+ );
34
+
35
+ for (const event of metadataEvents) {
36
+ if (!isCapabilityId(event.eventType)) throw new Error(`Invalid event id: ${event.eventType}`);
37
+ const capability = eventCapabilitiesById.get(event.eventType);
38
+ expect(capability?.owner).toBe('@ankhorage/zora');
39
+ expect(capability?.access).toEqual(['emit']);
40
+ expect(capability?.binding).toEqual({ kind: 'event', bindableAs: ['source'] });
41
+ expect(capability?.label).toBe(event.label);
42
+ expect(capability?.description).toBe(event.description);
43
+ }
44
+ });
45
+
46
+ test('projects event payload metadata into matching output schemas', () => {
47
+ const eventCapabilities = new Map<Capability['id'], Capability>(
48
+ CAPABILITIES.filter((capability) => capability.access.includes('emit')).map((capability) => [
49
+ capability.id,
50
+ capability,
51
+ ]),
52
+ );
53
+
54
+ for (const component of Object.values(ZORA_COMPONENT_META)) {
55
+ if (!component.directManifestNode) continue;
56
+ for (const event of Object.values(component.events ?? {})) {
57
+ if (!isCapabilityId(event.eventType)) throw new Error(`Invalid event id: ${event.eventType}`);
58
+ const capability = eventCapabilities.get(event.eventType);
59
+ const schema = capability?.output?.schema;
60
+ expect(schema?.type).toBe('object');
61
+
62
+ for (const field of event.payloadFields ?? []) {
63
+ if (field.path.includes('.')) continue;
64
+ const fieldSchema = Object.entries(schema?.properties ?? {}).find(
65
+ ([fieldName]) => fieldName === field.path,
66
+ )?.[1];
67
+ expect(fieldSchema?.type).toBe(
68
+ field.type === 'record' ? 'object' : field.type === 'unknown' ? undefined : field.type,
69
+ );
70
+ expect(fieldSchema?.additionalProperties).toBe(field.type === 'record' ? true : undefined);
71
+ expect(fieldSchema?.title).toBe(field.label);
72
+ expect(fieldSchema?.description).toBe(field.description);
73
+ }
74
+ }
75
+ }
76
+ });
77
+
21
78
  test('keeps Ankh package metadata identical to the canonical catalog', () => {
22
79
  expect(Array.isArray(packageCapabilities)).toBe(true);
23
80
  if (!Array.isArray(packageCapabilities)) {
@@ -37,18 +94,33 @@ test('exports the catalog from the public capabilities subpath', async () => {
37
94
  });
38
95
 
39
96
  test('preserves the executable action semantics of ZORA provider capabilities', () => {
40
- expect(CAPABILITIES).toEqual([
41
- expect.objectContaining({
42
- id: 'zora.create',
43
- owner: '@ankhorage/zora',
44
- access: ['invoke'],
45
- binding: { kind: 'action', bindableAs: ['target'] },
46
- }),
47
- expect.objectContaining({
48
- id: 'zora.sync',
49
- owner: '@ankhorage/zora',
50
- access: ['invoke'],
51
- binding: { kind: 'action', bindableAs: ['target'] },
52
- }),
97
+ const actions = CAPABILITIES.filter((capability) => capability.access.includes('invoke'));
98
+ expect(actions.map((capability) => capability.id)).toEqual(['zora.create', 'zora.sync']);
99
+ expect(actions.every((capability) => capability.binding.kind === 'action')).toBe(true);
100
+ expect(actions.every((capability) => capability.binding.bindableAs.includes('target'))).toBe(
101
+ true,
102
+ );
103
+ });
104
+
105
+ test('built public capability entrypoint imports under Node ESM', async () => {
106
+ const subprocess = Bun.spawn(
107
+ [
108
+ 'node',
109
+ '--input-type=module',
110
+ '-e',
111
+ "import('./dist/capabilities/index.js').then(({ CAPABILITIES }) => { if (!Array.isArray(CAPABILITIES)) process.exit(1); });",
112
+ ],
113
+ {
114
+ cwd: process.cwd(),
115
+ stderr: 'pipe',
116
+ stdout: 'pipe',
117
+ },
118
+ );
119
+ const [exitCode, stderr] = await Promise.all([
120
+ subprocess.exited,
121
+ new Response(subprocess.stderr).text(),
53
122
  ]);
123
+
124
+ expect(stderr).toBe('');
125
+ expect(exitCode).toBe(0);
54
126
  });
@@ -1,4 +1,6 @@
1
- import type { Capability } from '@ankhorage/contracts/capabilities';
1
+ import type { Capability } from '@ankhorage/contracts/capability';
2
+
3
+ import { createEventCapabilities } from '../features/registry/createEventCapabilities.js';
2
4
 
3
5
  export const CAPABILITIES = [
4
6
  {
@@ -60,4 +62,5 @@ export const CAPABILITIES = [
60
62
  },
61
63
  },
62
64
  },
63
- ] as const satisfies readonly Capability[];
65
+ ...createEventCapabilities(),
66
+ ] satisfies readonly Capability[];
@@ -6,7 +6,9 @@ import provider from './index';
6
6
  test('publishes ZORA create and sync through the Ankh provider', () => {
7
7
  expect(provider.id).toBe('@ankhorage/zora');
8
8
  expect(provider.category).toBe('zora');
9
- expect(provider.capabilities).toBe(CAPABILITIES);
9
+ expect(provider.capabilities).toEqual(
10
+ CAPABILITIES.filter((capability) => capability.access.includes('invoke')),
11
+ );
10
12
  expect(provider.commands).toEqual([
11
13
  {
12
14
  path: ['create'],
@@ -24,9 +26,9 @@ test('publishes ZORA create and sync through the Ankh provider', () => {
24
26
  expect(provider.handlers[1]?.path).toEqual(['sync']);
25
27
  });
26
28
 
27
- test('maps every provider command to one canonical capability independent of catalog order', () => {
29
+ test('maps every provider command to one executable canonical capability independent of catalog order', () => {
28
30
  const commandCapabilityIds = provider.commands.map((command) => command.capability);
29
- const capabilityIds = CAPABILITIES.map((capability) => capability.id);
31
+ const capabilityIds = provider.capabilities.map((capability) => capability.id);
30
32
 
31
33
  expect(new Set(commandCapabilityIds).size).toBe(commandCapabilityIds.length);
32
34
  expect(new Set(commandCapabilityIds)).toEqual(new Set(capabilityIds));
package/src/cli/index.ts CHANGED
@@ -3,7 +3,7 @@ import { dirname, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
5
  import type { AnkhRuntimeCommandProvider } from '@ankhorage/ankh';
6
- import type { Capability } from '@ankhorage/contracts/capabilities';
6
+ import type { Capability } from '@ankhorage/contracts/capability';
7
7
 
8
8
  import { CAPABILITIES } from '../capabilities';
9
9
  import { create } from './commands/create';
@@ -19,12 +19,15 @@ const SYNC_COMMAND = {
19
19
  capability: 'zora.sync' satisfies Capability['id'],
20
20
  summary: 'Regenerate the declared ZORA web materialization.',
21
21
  } as const;
22
+ const COMMAND_CAPABILITIES = CAPABILITIES.filter((capability) =>
23
+ capability.access.includes('invoke'),
24
+ );
22
25
 
23
26
  const provider = {
24
27
  id: '@ankhorage/zora',
25
28
  category: 'zora',
26
29
  version: readPackageVersion(),
27
- capabilities: CAPABILITIES,
30
+ capabilities: COMMAND_CAPABILITIES,
28
31
  commands: [CREATE_COMMAND, SYNC_COMMAND],
29
32
  handlers: [
30
33
  {
@@ -0,0 +1,143 @@
1
+ import { join } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ import { expect, test } from 'bun:test';
5
+ import { Window } from 'happy-dom';
6
+ import React, { act } from 'react';
7
+ import { createRoot } from 'react-dom/client';
8
+
9
+ import type { GridInteractions as GridInteractionsComponent } from './GridInteractions';
10
+
11
+ const webDistRoot = join(import.meta.dir, '../../../../../web-dist');
12
+
13
+ /*** Loads the independently bundled RNW interaction adapter through its public artifact path. */
14
+ function loadGridInteractions() {
15
+ return import(
16
+ pathToFileURL(join(webDistRoot, 'components/grid-interactions/index.js')).href
17
+ ) as Promise<{ GridInteractions: typeof GridInteractionsComponent }>;
18
+ }
19
+
20
+ test('keeps a mounted RNW keyboard boundary current across parent rerenders', async () => {
21
+ const browser = new Window();
22
+ const restore = installBrowserGlobals(browser);
23
+ const host = document.createElement('div');
24
+ document.body.append(host);
25
+ const root = createRoot(host);
26
+ const { GridInteractions } = await loadGridInteractions();
27
+ const firstIntents: unknown[] = [];
28
+ const secondIntents: unknown[] = [];
29
+ const render = (
30
+ onIntent: (intent: unknown) => void,
31
+ interactionPolicy?: 'passive',
32
+ resizeHandle?: 'bottom-right',
33
+ ) => (
34
+ <GridInteractions
35
+ interactionPolicy={interactionPolicy}
36
+ items={[{ height: 2, id: 'a', width: 3, x: 1, y: 1 }]}
37
+ onIntent={onIntent}
38
+ selectedIds={['a']}
39
+ testID="grid-interactions"
40
+ resizeHandle={resizeHandle}
41
+ viewport={{
42
+ height: 100,
43
+ offsetX: 0,
44
+ offsetY: 0,
45
+ pixelsPerUnitX: 2,
46
+ pixelsPerUnitY: 4,
47
+ width: 100,
48
+ }}
49
+ >
50
+ <span>nested target</span>
51
+ </GridInteractions>
52
+ );
53
+
54
+ try {
55
+ await renderGridInteractions(
56
+ root,
57
+ render((intent) => firstIntents.push(intent)),
58
+ );
59
+ await renderGridInteractions(
60
+ root,
61
+ render((intent) => secondIntents.push(intent)),
62
+ );
63
+
64
+ const keyboard = host.querySelector<HTMLElement>(
65
+ '[aria-label="Grid interaction keyboard controls"]',
66
+ );
67
+ if (!keyboard) throw new Error('Missing keyboard interaction proxy');
68
+ const resize = new browser.KeyboardEvent('keydown', {
69
+ altKey: true,
70
+ bubbles: true,
71
+ cancelable: true,
72
+ key: 'ArrowRight',
73
+ });
74
+ await dispatch(keyboard, resize);
75
+ const acceleratedMove = new browser.KeyboardEvent('keydown', {
76
+ bubbles: true,
77
+ cancelable: true,
78
+ key: 'ArrowDown',
79
+ shiftKey: true,
80
+ });
81
+ await dispatch(keyboard, acceleratedMove);
82
+ await renderGridInteractions(
83
+ root,
84
+ render((intent) => secondIntents.push(intent), 'passive'),
85
+ );
86
+ const passiveKey = new browser.KeyboardEvent('keydown', { bubbles: true, key: 'ArrowLeft' });
87
+ await dispatch(keyboard, passiveKey);
88
+ expect(keyboard.getAttribute('tabindex')).toBe('-1');
89
+
90
+ expect(firstIntents).toEqual([]);
91
+ expect(secondIntents).toEqual([
92
+ { type: 'resize', itemIds: ['a'], rects: [{ height: 2, id: 'a', width: 4, x: 1, y: 1 }] },
93
+ { type: 'move', itemIds: ['a'], rects: [{ height: 2, id: 'a', width: 3, x: 1, y: 11 }] },
94
+ ]);
95
+ expect(resize.defaultPrevented).toBe(true);
96
+ expect(acceleratedMove.defaultPrevented).toBe(true);
97
+ expect(passiveKey.defaultPrevented).toBe(false);
98
+ } finally {
99
+ await act(() => Promise.resolve().then(() => root.unmount()));
100
+ host.remove();
101
+ browser.close();
102
+ restore();
103
+ }
104
+ });
105
+
106
+ /*** Installs the DOM globals that the independently bundled RNW adapter requires. */
107
+ function installBrowserGlobals(browser: Window): () => void {
108
+ const keys = [
109
+ 'window',
110
+ 'document',
111
+ 'Node',
112
+ 'navigator',
113
+ 'ShadowRoot',
114
+ 'IS_REACT_ACT_ENVIRONMENT',
115
+ ] as const;
116
+ const previous = keys.map(
117
+ (key) => [key, Object.getOwnPropertyDescriptor(globalThis, key)] as const,
118
+ );
119
+ Object.assign(globalThis, {
120
+ IS_REACT_ACT_ENVIRONMENT: true,
121
+ Node: browser.Node,
122
+ ShadowRoot: browser.ShadowRoot,
123
+ document: browser.document,
124
+ navigator: browser.navigator,
125
+ window: browser,
126
+ });
127
+ return () => {
128
+ for (const [key, descriptor] of previous) {
129
+ if (descriptor) Object.defineProperty(globalThis, key, descriptor);
130
+ else Reflect.deleteProperty(globalThis, key);
131
+ }
132
+ };
133
+ }
134
+
135
+ /*** Flushes one mounted RNW render before the next synthetic input event. */
136
+ function renderGridInteractions(root: ReturnType<typeof createRoot>, component: React.ReactNode) {
137
+ return act(() => Promise.resolve().then(() => root.render(component)));
138
+ }
139
+
140
+ /*** Dispatches one browser input event through React's RNW adapter boundary. */
141
+ function dispatch(target: HTMLElement, event: Event) {
142
+ return act(() => Promise.resolve().then(() => target.dispatchEvent(event)));
143
+ }
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import type {
5
+ GridInteractionsController,
6
+ GridInteractionsProps,
7
+ } from '../../../../types/grid-interactions';
8
+ import { createGridInteractionsController } from '../../application/createGridInteractionsController';
9
+ import { createGridInteractionsPointerQueue } from './createGridInteractionsPointerQueue';
10
+ import { GridInteractionsKeyboardProxy } from './GridInteractionsKeyboardProxy';
11
+ import { resolveGridInteractionPointer } from './resolveGridInteractionPointer';
12
+
13
+ /*** Adapts native responder and keyboard events to the controlled grid interaction boundary. */
14
+ export function GridInteractions({
15
+ children,
16
+ interactionPolicy,
17
+ resizeHandle,
18
+ testID,
19
+ ...props
20
+ }: GridInteractionsProps) {
21
+ const controllerProps = React.useMemo(
22
+ () => ({ ...props, interactionPolicy }),
23
+ [interactionPolicy, props],
24
+ );
25
+ const [controller] = React.useState<GridInteractionsController>(() =>
26
+ createGridInteractionsController(controllerProps),
27
+ );
28
+ const surfaceRef = React.useRef<View>(null);
29
+ const [pointerQueue] = React.useState(() => createGridInteractionsPointerQueue());
30
+ React.useLayoutEffect(() => controller.update(controllerProps), [controller, controllerProps]);
31
+ React.useLayoutEffect(() => {
32
+ pointerQueue.activate();
33
+ return () => {
34
+ pointerQueue.dispose();
35
+ controller.cancel();
36
+ };
37
+ }, [controller, pointerQueue]);
38
+ const withPointer = (
39
+ event: Parameters<typeof resolveGridInteractionPointer>[0],
40
+ handlePointer: (pointer: ReturnType<typeof resolveGridInteractionPointer>) => void,
41
+ ) => {
42
+ const surface = surfaceRef.current;
43
+ if (!surface) return;
44
+ const eventSnapshot = { nativeEvent: { ...event.nativeEvent } };
45
+ pointerQueue.enqueue(surface, (origin) =>
46
+ handlePointer(resolveGridInteractionPointer(eventSnapshot, origin)),
47
+ );
48
+ };
49
+ const enabled = interactionPolicy !== 'passive';
50
+
51
+ return (
52
+ <View
53
+ accessible
54
+ accessibilityLabel="Grid interaction surface"
55
+ accessibilityRole="adjustable"
56
+ ref={surfaceRef}
57
+ testID={testID}
58
+ onResponderGrant={(event) =>
59
+ withPointer(event, (pointer) => controller.begin(pointer, resizeHandle))
60
+ }
61
+ onResponderMove={(event) => withPointer(event, controller.move)}
62
+ onResponderRelease={(event) => withPointer(event, controller.end)}
63
+ onResponderTerminate={() => {
64
+ pointerQueue.cancel();
65
+ controller.cancel();
66
+ }}
67
+ onStartShouldSetResponder={() => enabled}
68
+ >
69
+ <GridInteractionsKeyboardProxy
70
+ enabled={enabled}
71
+ onKeyDown={(key, keyPointer) => {
72
+ return controller.keyDown(key, keyPointer);
73
+ }}
74
+ >
75
+ {children}
76
+ </GridInteractionsKeyboardProxy>
77
+ </View>
78
+ );
79
+ }
@@ -0,0 +1,14 @@
1
+ import type React from 'react';
2
+
3
+ import type { GridInteractionPointer } from '../../../../types/grid-interactions';
4
+
5
+ /*** Leaves native accessibility focus untouched where browser keyboard events are unavailable. */
6
+ export function GridInteractionsKeyboardProxy({ children }: GridInteractionsKeyboardProxyProps) {
7
+ return <>{children}</>;
8
+ }
9
+
10
+ interface GridInteractionsKeyboardProxyProps {
11
+ readonly children: React.ReactNode;
12
+ readonly enabled: boolean;
13
+ readonly onKeyDown: (key: string, pointer: GridInteractionPointer) => boolean;
14
+ }
@@ -0,0 +1 @@
1
+ export { GridInteractionsKeyboardProxy } from './GridInteractionsKeyboardProxy.native';
@@ -0,0 +1,41 @@
1
+ import type React from 'react';
2
+
3
+ import type { GridInteractionPointer } from '../../../../types/grid-interactions';
4
+
5
+ /*** Adapts web keyboard movement and resize commands without changing native responder behavior. */
6
+ export function GridInteractionsKeyboardProxy({
7
+ children,
8
+ enabled,
9
+ onKeyDown,
10
+ }: GridInteractionsKeyboardProxyProps) {
11
+ return (
12
+ <div
13
+ aria-label="Grid interaction keyboard controls"
14
+ onKeyDown={(event) => {
15
+ if (!enabled) return;
16
+ if (
17
+ onKeyDown(event.key, {
18
+ altKey: event.altKey,
19
+ ctrlKey: event.ctrlKey,
20
+ metaKey: event.metaKey,
21
+ shiftKey: event.shiftKey,
22
+ x: 0,
23
+ y: 0,
24
+ })
25
+ ) {
26
+ event.preventDefault();
27
+ }
28
+ }}
29
+ style={{ display: 'contents' }}
30
+ tabIndex={enabled ? 0 : -1}
31
+ >
32
+ {children}
33
+ </div>
34
+ );
35
+ }
36
+
37
+ interface GridInteractionsKeyboardProxyProps {
38
+ readonly children: React.ReactNode;
39
+ readonly enabled: boolean;
40
+ readonly onKeyDown: (key: string, pointer: GridInteractionPointer) => boolean;
41
+ }
@@ -0,0 +1,98 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ import { createGridInteractionsPointerQueue } from './createGridInteractionsPointerQueue';
4
+ import { resolveGridInteractionPointer } from './resolveGridInteractionPointer';
5
+
6
+ test('invalidates pending native responder callbacks after cancellation', () => {
7
+ const callbacks: ((x: number, y: number) => void)[] = [];
8
+ const origins: unknown[] = [];
9
+ const queue = createGridInteractionsPointerQueue();
10
+ const surface = {
11
+ measureInWindow: (callback: (x: number, y: number) => void) => callbacks.push(callback),
12
+ };
13
+
14
+ queue.enqueue(surface, (origin) => origins.push(origin));
15
+ queue.enqueue(surface, (origin) => origins.push(origin));
16
+ queue.cancel();
17
+ callbacks[0]?.(10, 20);
18
+
19
+ expect(origins).toEqual([]);
20
+ expect(callbacks).toHaveLength(1);
21
+ });
22
+
23
+ test('invalidates an active native responder measurement across unmount and remount', () => {
24
+ const callbacks: ((x: number, y: number) => void)[] = [];
25
+ const origins: unknown[] = [];
26
+ const queue = createGridInteractionsPointerQueue();
27
+ const surface = {
28
+ measureInWindow: (callback: (x: number, y: number) => void) => callbacks.push(callback),
29
+ };
30
+
31
+ queue.enqueue(surface, (origin) => origins.push(origin));
32
+ queue.dispose();
33
+ queue.activate();
34
+ queue.enqueue(surface, (origin) => origins.push(origin));
35
+ callbacks[1]?.(30, 40);
36
+ callbacks[0]?.(10, 20);
37
+
38
+ expect(origins).toEqual([{ x: 30, y: 40 }]);
39
+ });
40
+
41
+ test('retires a canceled measurement slot without waiting for its callback', () => {
42
+ const callbacks: ((x: number, y: number) => void)[] = [];
43
+ const origins: unknown[] = [];
44
+ const queue = createGridInteractionsPointerQueue();
45
+ const surface = {
46
+ measureInWindow: (callback: (x: number, y: number) => void) => callbacks.push(callback),
47
+ };
48
+
49
+ queue.enqueue(surface, (origin) => origins.push(origin));
50
+ queue.cancel();
51
+ queue.enqueue(surface, (origin) => origins.push(origin));
52
+ callbacks[1]?.(30, 40);
53
+ queue.enqueue(surface, (origin) => origins.push(origin));
54
+ callbacks[0]?.(10, 20);
55
+ callbacks[2]?.(50, 60);
56
+
57
+ expect(origins).toEqual([
58
+ { x: 30, y: 40 },
59
+ { x: 50, y: 60 },
60
+ ]);
61
+ });
62
+
63
+ test('uses the latest RNW surface origin for each serialized pointer', () => {
64
+ const origins = [
65
+ { left: 10, top: 20 },
66
+ { left: 15, top: 25 },
67
+ ];
68
+ const resolvedOrigins: unknown[] = [];
69
+ const queue = createGridInteractionsPointerQueue();
70
+ const surface = {
71
+ getBoundingClientRect: () => origins.shift(),
72
+ };
73
+
74
+ queue.enqueue(surface, (origin) => resolvedOrigins.push(origin));
75
+ queue.enqueue(surface, (origin) => resolvedOrigins.push(origin));
76
+
77
+ expect(resolvedOrigins).toEqual([
78
+ { x: 10, y: 20 },
79
+ { x: 15, y: 25 },
80
+ ]);
81
+ });
82
+
83
+ test('preserves native responder-local coordinates while measuring asynchronously', () => {
84
+ const callbacks: ((x: number, y: number) => void)[] = [];
85
+ const pointers: unknown[] = [];
86
+ const queue = createGridInteractionsPointerQueue();
87
+ const surface = {
88
+ measureInWindow: (callback: (x: number, y: number) => void) => callbacks.push(callback),
89
+ };
90
+ const event = { nativeEvent: { locationX: 7, locationY: 9 } };
91
+
92
+ queue.enqueue(surface, (origin) => {
93
+ pointers.push(resolveGridInteractionPointer(event, origin));
94
+ });
95
+ callbacks[0]?.(10, 20);
96
+
97
+ expect(pointers).toEqual([{ x: 7, y: 9 }]);
98
+ });
@@ -0,0 +1,94 @@
1
+ /*** Serializes native responder measurements and invalidates work from canceled interaction lifecycles. */
2
+ export function createGridInteractionsPointerQueue() {
3
+ let active = true;
4
+ let generation = 0;
5
+ let measuring = false;
6
+ let measurementId = 0;
7
+ let activeMeasurementId: number | undefined;
8
+ let pending: readonly GridInteractionPointerWork[] = [];
9
+
10
+ const processNext = () => {
11
+ if (measuring) return;
12
+ const pointerWork = pending.at(0);
13
+ if (pointerWork === undefined) return;
14
+ pending = pending.slice(1);
15
+ measuring = true;
16
+ const currentMeasurementId = ++measurementId;
17
+ activeMeasurementId = currentMeasurementId;
18
+ let settled = false;
19
+ measureSurfaceOrigin(pointerWork.surface, (origin) => {
20
+ if (settled) return;
21
+ settled = true;
22
+ if (activeMeasurementId !== currentMeasurementId) return;
23
+ try {
24
+ if (active && pointerWork.generation === generation) {
25
+ pointerWork.handleOrigin(origin);
26
+ }
27
+ } finally {
28
+ activeMeasurementId = undefined;
29
+ measuring = false;
30
+ processNext();
31
+ }
32
+ });
33
+ };
34
+
35
+ return {
36
+ activate: () => {
37
+ active = true;
38
+ },
39
+ enqueue: (
40
+ surface: GridInteractionSurface,
41
+ handleOrigin: (origin: GridInteractionSurfaceOrigin) => void,
42
+ ) => {
43
+ if (!active) return;
44
+ pending = [...pending, { generation, handleOrigin, surface }];
45
+ processNext();
46
+ },
47
+ cancel: () => {
48
+ generation += 1;
49
+ pending = [];
50
+ activeMeasurementId = undefined;
51
+ measuring = false;
52
+ },
53
+ dispose: () => {
54
+ active = false;
55
+ generation += 1;
56
+ pending = [];
57
+ activeMeasurementId = undefined;
58
+ measuring = false;
59
+ },
60
+ };
61
+ }
62
+
63
+ interface GridInteractionPointerWork {
64
+ readonly generation: number;
65
+ readonly handleOrigin: (origin: GridInteractionSurfaceOrigin) => void;
66
+ readonly surface: GridInteractionSurface;
67
+ }
68
+
69
+ interface GridInteractionSurface {
70
+ readonly getBoundingClientRect?: () => Pick<DOMRect, 'left' | 'top'>;
71
+ readonly measureInWindow?: (callback: (x: number, y: number) => void) => void;
72
+ }
73
+
74
+ interface GridInteractionSurfaceOrigin {
75
+ readonly x: number;
76
+ readonly y: number;
77
+ }
78
+
79
+ /*** Resolves the current surface origin through RNW DOM geometry or native asynchronous measurement. */
80
+ function measureSurfaceOrigin(
81
+ surface: GridInteractionSurface,
82
+ onOrigin: (origin: GridInteractionSurfaceOrigin) => void,
83
+ ) {
84
+ const rect = surface.getBoundingClientRect?.();
85
+ if (rect) {
86
+ onOrigin({ x: rect.left, y: rect.top });
87
+ return;
88
+ }
89
+ if (surface.measureInWindow !== undefined) {
90
+ surface.measureInWindow((x, y) => onOrigin({ x, y }));
91
+ return;
92
+ }
93
+ onOrigin({ x: 0, y: 0 });
94
+ }