@ankhorage/zora 25.6.2 → 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 (295) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +1 -1
  3. package/dist/capabilities/index.js +13 -1
  4. package/dist/capabilities/index.js.map +5 -4
  5. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.d.ts +5 -0
  6. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.d.ts.map +1 -0
  7. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js +40 -0
  8. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js.map +1 -0
  9. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.d.ts +2 -0
  10. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.d.ts.map +1 -0
  11. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.js +2 -0
  12. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.js.map +1 -0
  13. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.d.ts +11 -0
  14. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.d.ts.map +1 -0
  15. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.js +5 -0
  16. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.js.map +1 -0
  17. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.d.ts +11 -0
  18. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.d.ts.map +1 -0
  19. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js +20 -0
  20. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js.map +1 -0
  21. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.d.ts +17 -0
  22. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.d.ts.map +1 -0
  23. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.js +76 -0
  24. package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.js.map +1 -0
  25. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.d.ts +21 -0
  26. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.d.ts.map +1 -0
  27. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.js +20 -0
  28. package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.js.map +1 -0
  29. package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts +4 -0
  30. package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts.map +1 -0
  31. package/dist/features/grid-interactions/application/createGridInteractionsController.js +191 -0
  32. package/dist/features/grid-interactions/application/createGridInteractionsController.js.map +1 -0
  33. package/dist/features/grid-interactions/meta.d.ts +11 -0
  34. package/dist/features/grid-interactions/meta.d.ts.map +1 -0
  35. package/dist/features/grid-interactions/meta.js +11 -0
  36. package/dist/features/grid-interactions/meta.js.map +1 -0
  37. package/dist/features/grid-interactions/public.d.ts +4 -0
  38. package/dist/features/grid-interactions/public.d.ts.map +1 -0
  39. package/dist/features/grid-interactions/public.js +3 -0
  40. package/dist/features/grid-interactions/public.js.map +1 -0
  41. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.d.ts.map +1 -1
  42. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js +73 -19
  43. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js.map +1 -1
  44. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.d.ts +9 -0
  45. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.d.ts.map +1 -0
  46. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.js +5 -0
  47. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.js.map +1 -0
  48. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.d.ts +9 -0
  49. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.d.ts.map +1 -0
  50. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.js +5 -0
  51. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.js.map +1 -0
  52. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.d.ts +9 -0
  53. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.d.ts.map +1 -0
  54. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.js +28 -0
  55. package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.js.map +1 -0
  56. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts +14 -0
  57. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts.map +1 -0
  58. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js +50 -0
  59. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js.map +1 -0
  60. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.d.ts +18 -0
  61. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.d.ts.map +1 -0
  62. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.js +56 -0
  63. package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.js.map +1 -0
  64. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.d.ts +4 -0
  65. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.d.ts.map +1 -0
  66. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.js +65 -0
  67. package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.js.map +1 -0
  68. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts +8 -1
  69. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts.map +1 -1
  70. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js +15 -4
  71. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js.map +1 -1
  72. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.d.ts.map +1 -1
  73. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js +2 -0
  74. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js.map +1 -1
  75. package/dist/features/registry/componentMeta.d.ts.map +1 -1
  76. package/dist/features/registry/componentMeta.js +2 -0
  77. package/dist/features/registry/componentMeta.js.map +1 -1
  78. package/dist/index.d.ts +1 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +1 -0
  81. package/dist/index.js.map +1 -1
  82. package/dist/types/grid-interactions.d.ts +58 -0
  83. package/dist/types/grid-interactions.d.ts.map +1 -0
  84. package/dist/types/grid-interactions.js +2 -0
  85. package/dist/types/grid-interactions.js.map +1 -0
  86. package/dist/types/matrix-grid.d.ts +2 -0
  87. package/dist/types/matrix-grid.d.ts.map +1 -1
  88. package/dist/types/matrix-grid.js.map +1 -1
  89. package/package.json +5 -5
  90. package/src/features/grid-interactions/adapters/inbound/GridInteractions.test.tsx +143 -0
  91. package/src/features/grid-interactions/adapters/inbound/GridInteractions.tsx +79 -0
  92. package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.tsx +14 -0
  93. package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.tsx +1 -0
  94. package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx +41 -0
  95. package/src/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.test.ts +98 -0
  96. package/src/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.ts +94 -0
  97. package/src/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.test.ts +33 -0
  98. package/src/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.ts +45 -0
  99. package/src/features/grid-interactions/application/createGridInteractionsController.test.ts +115 -0
  100. package/src/features/grid-interactions/application/createGridInteractionsController.ts +231 -0
  101. package/src/features/grid-interactions/meta.ts +12 -0
  102. package/src/features/grid-interactions/public.ts +11 -0
  103. package/src/features/matrix-grid/adapters/inbound/MatrixGrid.test.tsx +280 -2
  104. package/src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx +121 -31
  105. package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.tsx +11 -0
  106. package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.tsx +11 -0
  107. package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.tsx +43 -0
  108. package/src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts +70 -0
  109. package/src/features/matrix-grid/application/createMatrixGridSparseIndex.test.ts +35 -0
  110. package/src/features/matrix-grid/application/createMatrixGridSparseIndex.ts +105 -0
  111. package/src/features/matrix-grid/application/getMatrixGridNextFocusId.test.ts +93 -0
  112. package/src/features/matrix-grid/application/getMatrixGridNextFocusId.ts +112 -0
  113. package/src/features/matrix-grid/application/resolveMatrixGridSelection.test.ts +63 -2
  114. package/src/features/matrix-grid/application/resolveMatrixGridSelection.ts +27 -4
  115. package/src/features/registry/ZORA_COMPONENT_REGISTRY.ts +2 -0
  116. package/src/features/registry/componentMeta.ts +2 -0
  117. package/src/index.ts +11 -0
  118. package/src/types/grid-interactions.ts +72 -0
  119. package/src/types/matrix-grid.ts +2 -0
  120. package/web-dist/chunks/{Accordion-5g05kfmx.js → Accordion-0q2wzjdd.js} +4 -4
  121. package/web-dist/chunks/{Accordion-b96evb46.js → Accordion-1fsrnq73.js} +1 -1
  122. package/web-dist/chunks/{Accordion-h3y8qev2.js → Accordion-38ts3gnn.js} +2 -2
  123. package/web-dist/chunks/{Accordion-jnc315h7.js → Accordion-8pkw32q9.js} +2 -2
  124. package/web-dist/chunks/{Accordion-h7zsx9eg.js → Accordion-9jrxktne.js} +3 -3
  125. package/web-dist/chunks/{Accordion-9w8wg9mf.js → Accordion-9rnsg9pk.js} +1 -1
  126. package/web-dist/chunks/{Accordion-4zqd7twp.js → Accordion-9ygakvwk.js} +5 -5
  127. package/web-dist/chunks/{Accordion-fhnazn3n.js → Accordion-b74j4hdt.js} +1 -1
  128. package/web-dist/chunks/{Accordion-nssr5gep.js → Accordion-c50pywj3.js} +2 -2
  129. package/web-dist/chunks/{Accordion-pvj5npzc.js → Accordion-ddrn94v3.js} +3 -3
  130. package/web-dist/chunks/{Accordion-090y18cv.js → Accordion-dk0dtyc0.js} +3 -3
  131. package/web-dist/chunks/{Accordion-59pa0677.js → Accordion-fcczvwgg.js} +0 -1
  132. package/web-dist/chunks/{Accordion-sp4k08kw.js → Accordion-geah5s4f.js} +4 -4
  133. package/web-dist/chunks/{Accordion-6rrqpb2h.js → Accordion-h4ekm5cp.js} +2 -2
  134. package/web-dist/chunks/{Accordion-3d4wkzg7.js → Accordion-hecpczme.js} +5 -5
  135. package/web-dist/chunks/{Accordion-a6551mh4.js → Accordion-hn43wt45.js} +1 -1
  136. package/web-dist/chunks/{Accordion-sw7ep29d.js → Accordion-hnx7hx55.js} +2 -2
  137. package/web-dist/chunks/{Accordion-rsvphbe4.js → Accordion-j1chy29e.js} +1 -1
  138. package/web-dist/chunks/{Accordion-ewf1zen9.js → Accordion-k9cxpg08.js} +5 -5
  139. package/web-dist/chunks/{Accordion-5df6cgyf.js → Accordion-md1g54wg.js} +1 -1
  140. package/web-dist/chunks/{Accordion-kjtcbe09.js → Accordion-p1ebrt17.js} +3 -3
  141. package/web-dist/chunks/{Accordion-qn398m1x.js → Accordion-r2g18vxa.js} +1 -1
  142. package/web-dist/chunks/{Accordion-zt9yghdz.js → Accordion-rctfgyfv.js} +4 -4
  143. package/web-dist/chunks/{Accordion-88t8fgxv.js → Accordion-rfc5mdbv.js} +1 -1
  144. package/web-dist/chunks/{Accordion-fmzxat3v.js → Accordion-sdnzqktc.js} +4 -4
  145. package/web-dist/chunks/{Accordion-v0grdh6q.js → Accordion-sq7ya2ye.js} +2 -2
  146. package/web-dist/chunks/{Accordion-ejx5gcf9.js → Accordion-t5scffdr.js} +9 -9
  147. package/web-dist/chunks/{Accordion-sy1cc5my.js → Accordion-ts788t7b.js} +3 -3
  148. package/web-dist/chunks/{Accordion-4s68t3h7.js → Accordion-v3rzy273.js} +6 -6
  149. package/web-dist/chunks/{Accordion-7tjz25jh.js → Accordion-vbzjq3z8.js} +1 -1
  150. package/web-dist/chunks/{Accordion-jh72azm2.js → Accordion-vg337250.js} +2 -2
  151. package/web-dist/chunks/{Accordion-42jfy1a1.js → Accordion-vj1m2wck.js} +1 -1
  152. package/web-dist/chunks/{Accordion-tqpsmdrk.js → Accordion-vxg5sw01.js} +4 -4
  153. package/web-dist/chunks/{Accordion-61ntvh14.js → Accordion-w59y0jdf.js} +4 -4
  154. package/web-dist/chunks/{Accordion-fjxcn6g9.js → Accordion-wcmrasqx.js} +8 -8
  155. package/web-dist/chunks/{Accordion-r1cj2w9e.js → Accordion-wpk8dxqp.js} +3 -3
  156. package/web-dist/chunks/{Accordion-w6qedeba.js → Accordion-x055n3b3.js} +1 -1
  157. package/web-dist/chunks/{Accordion-dbf93gxh.js → Accordion-xcm1qc39.js} +1 -1
  158. package/web-dist/chunks/{Accordion-7kq500r1.js → Accordion-xgxxg1hz.js} +1 -1
  159. package/web-dist/chunks/{Accordion-a4cma5b6.js → Accordion-y3083394.js} +3 -3
  160. package/web-dist/chunks/{Accordion-40tenzcg.js → Accordion-yexfbrzg.js} +4 -4
  161. package/web-dist/chunks/{Accordion-2r0gqk3x.js → Accordion-ztw7zp6v.js} +5 -5
  162. package/web-dist/chunks/{Accordion-bz04casa.js → Accordion-zypyr9xw.js} +211 -6
  163. package/web-dist/components/accordion/Accordion.js +5 -5
  164. package/web-dist/components/accordion-item/AccordionItem.js +5 -5
  165. package/web-dist/components/activity-indicator/ActivityIndicator.js +1 -1
  166. package/web-dist/components/app-bar/AppBar.js +7 -7
  167. package/web-dist/components/app-header/AppHeader.js +7 -7
  168. package/web-dist/components/app-shell/AppShell.js +2 -2
  169. package/web-dist/components/auth-screen/AuthScreen.js +10 -10
  170. package/web-dist/components/avatar/Avatar.js +5 -5
  171. package/web-dist/components/avatar-group/AvatarGroup.js +5 -5
  172. package/web-dist/components/badge/Badge.js +2 -2
  173. package/web-dist/components/barcode-scanner-view/BarcodeScannerView.js +10 -10
  174. package/web-dist/components/bottom-sheet/BottomSheet.js +3 -3
  175. package/web-dist/components/bottom-sheet-flat-list/BottomSheetFlatList.js +3 -3
  176. package/web-dist/components/bottom-sheet-scroll-view/BottomSheetScrollView.js +3 -3
  177. package/web-dist/components/bottom-sheet-section-list/BottomSheetSectionList.js +3 -3
  178. package/web-dist/components/bottom-sheet-view/BottomSheetView.js +3 -3
  179. package/web-dist/components/bottom-sheet-virtualized-list/BottomSheetVirtualizedList.js +3 -3
  180. package/web-dist/components/breadcrumbs/Breadcrumbs.js +5 -5
  181. package/web-dist/components/button/Button.js +3 -3
  182. package/web-dist/components/button-group/ButtonGroup.js +3 -3
  183. package/web-dist/components/camera-permission-view/CameraPermissionView.js +10 -10
  184. package/web-dist/components/card/Card.js +9 -9
  185. package/web-dist/components/card-back/CardBack.js +2 -2
  186. package/web-dist/components/card-hand/CardHand.js +2 -2
  187. package/web-dist/components/chat-list-item/ChatListItem.js +7 -7
  188. package/web-dist/components/checkbox/Checkbox.js +4 -4
  189. package/web-dist/components/checkbox-group/CheckboxGroup.js +4 -4
  190. package/web-dist/components/chess-board/ChessBoard.js +2 -2
  191. package/web-dist/components/chip/Chip.js +5 -5
  192. package/web-dist/components/chip-group/ChipGroup.js +5 -5
  193. package/web-dist/components/collection-editor/CollectionEditor.js +9 -9
  194. package/web-dist/components/content-rail/ContentRail.js +4 -4
  195. package/web-dist/components/data-table/DataTable.js +14 -14
  196. package/web-dist/components/date-picker/DatePicker.js +5 -5
  197. package/web-dist/components/dialog/Dialog.js +4 -4
  198. package/web-dist/components/divider/Divider.js +2 -2
  199. package/web-dist/components/empty-state/EmptyState.js +10 -10
  200. package/web-dist/components/explorer/Explorer.js +7 -7
  201. package/web-dist/components/field/Field.js +4 -4
  202. package/web-dist/components/file-explorer/FileExplorer.js +7 -7
  203. package/web-dist/components/flat-list/FlatList.js +5 -5
  204. package/web-dist/components/forgot-password-form/ForgotPasswordForm.js +10 -10
  205. package/web-dist/components/form/Form.js +6 -6
  206. package/web-dist/components/form-actions/FormActions.js +6 -6
  207. package/web-dist/components/form-error/FormError.js +6 -6
  208. package/web-dist/components/game/Game.js +3 -3
  209. package/web-dist/components/game-entity/GameEntity.js +3 -3
  210. package/web-dist/components/game-field/GameField.js +3 -3
  211. package/web-dist/components/game-input-zone/GameInputZone.js +3 -3
  212. package/web-dist/components/game-measurement-probe/GameMeasurementProbe.js +3 -3
  213. package/web-dist/components/game-overlay/GameOverlay.js +3 -3
  214. package/web-dist/components/gradient/Gradient.js +3 -3
  215. package/web-dist/components/gradient-renderer-provider/GradientRendererProvider.js +3 -3
  216. package/web-dist/components/graph-view/GraphView.js +2 -2
  217. package/web-dist/components/grid/Grid.js +2 -2
  218. package/web-dist/components/grid-interactions/GridInteractions.d.ts +3 -0
  219. package/web-dist/components/grid-interactions/GridInteractions.js +371 -0
  220. package/web-dist/components/grid-interactions/index.d.ts +1 -0
  221. package/web-dist/components/grid-interactions/index.js +2 -0
  222. package/web-dist/components/grid-line-overlay/GridLineOverlay.js +2 -2
  223. package/web-dist/components/grid-ruler/GridRuler.js +2 -2
  224. package/web-dist/components/grid-view/GridView.js +1 -1
  225. package/web-dist/components/heading/Heading.js +2 -2
  226. package/web-dist/components/hero/Hero.js +9 -9
  227. package/web-dist/components/icon/Icon.js +2 -2
  228. package/web-dist/components/icon-button/IconButton.js +3 -3
  229. package/web-dist/components/image/Image.js +4 -4
  230. package/web-dist/components/keyboard-avoiding-view/KeyboardAvoidingView.js +2 -2
  231. package/web-dist/components/list/List.js +5 -5
  232. package/web-dist/components/list-item/ListItem.js +5 -5
  233. package/web-dist/components/list-section/ListSection.js +5 -5
  234. package/web-dist/components/matrix-grid/MatrixGrid.js +296 -47
  235. package/web-dist/components/media-card/MediaCard.js +9 -9
  236. package/web-dist/components/media-explorer/MediaExplorer.js +7 -7
  237. package/web-dist/components/message-bubble/MessageBubble.js +7 -7
  238. package/web-dist/components/metric-card/MetricCard.js +9 -9
  239. package/web-dist/components/missing-element/MissingElement.js +4 -4
  240. package/web-dist/components/o-auth-provider-button/OAuthProviderButton.js +10 -10
  241. package/web-dist/components/o-auth-provider-list/OAuthProviderList.js +10 -10
  242. package/web-dist/components/opening-book/OpeningBook.js +2 -2
  243. package/web-dist/components/otp-form/OtpForm.js +10 -10
  244. package/web-dist/components/pagination/Pagination.js +4 -4
  245. package/web-dist/components/palette-item/PaletteItem.js +9 -9
  246. package/web-dist/components/playing-card/PlayingCard.js +2 -2
  247. package/web-dist/components/poker-training-table/PokerTrainingTable.js +2 -2
  248. package/web-dist/components/popover-menu/PopoverMenu.js +4 -4
  249. package/web-dist/components/post-card/PostCard.js +9 -9
  250. package/web-dist/components/product-card/ProductCard.js +9 -9
  251. package/web-dist/components/progress/Progress.js +4 -4
  252. package/web-dist/components/progress-ring/ProgressRing.js +4 -4
  253. package/web-dist/components/radio/Radio.js +5 -5
  254. package/web-dist/components/radio-group/RadioGroup.js +5 -5
  255. package/web-dist/components/rating/Rating.js +3 -3
  256. package/web-dist/components/reader/Reader.js +10 -10
  257. package/web-dist/components/scan-overlay/ScanOverlay.js +10 -10
  258. package/web-dist/components/screen/Screen.js +2 -2
  259. package/web-dist/components/screen-section/ScreenSection.js +4 -4
  260. package/web-dist/components/scroll-view/ScrollView.js +2 -2
  261. package/web-dist/components/search-input/SearchInput.js +4 -4
  262. package/web-dist/components/section-header/SectionHeader.js +4 -4
  263. package/web-dist/components/section-list/SectionList.js +5 -5
  264. package/web-dist/components/select/Select.js +4 -4
  265. package/web-dist/components/selectable-item/SelectableItem.js +2 -2
  266. package/web-dist/components/selection-provider/SelectionProvider.js +2 -2
  267. package/web-dist/components/sign-in-form/SignInForm.js +10 -10
  268. package/web-dist/components/sign-up-form/SignUpForm.js +10 -10
  269. package/web-dist/components/skeleton/Skeleton.js +12 -12
  270. package/web-dist/components/skeleton-card/SkeletonCard.js +12 -12
  271. package/web-dist/components/skeleton-list/SkeletonList.js +12 -12
  272. package/web-dist/components/skeleton-text/SkeletonText.js +12 -12
  273. package/web-dist/components/spatial-grid/SpatialGrid.js +1 -1
  274. package/web-dist/components/surface/Surface.js +2 -2
  275. package/web-dist/components/switch/Switch.js +1 -1
  276. package/web-dist/components/tab/Tab.js +2 -2
  277. package/web-dist/components/tab-list/TabList.js +2 -2
  278. package/web-dist/components/tab-panel/TabPanel.js +2 -2
  279. package/web-dist/components/tabletop-table/TabletopTable.js +2 -2
  280. package/web-dist/components/tabs/Tabs.js +2 -2
  281. package/web-dist/components/text/Text.js +2 -2
  282. package/web-dist/components/text-input/TextInput.js +4 -4
  283. package/web-dist/components/tile-grid/TileGrid.js +1 -1
  284. package/web-dist/components/time-grid/TimeGrid.js +2 -2
  285. package/web-dist/components/time-picker/TimePicker.js +5 -5
  286. package/web-dist/components/timeline/Timeline.js +4 -4
  287. package/web-dist/components/toast/Toast.js +2 -2
  288. package/web-dist/components/toast-provider/ToastProvider.js +2 -2
  289. package/web-dist/components/toolbar/Toolbar.js +9 -9
  290. package/web-dist/components/tree-item/TreeItem.js +9 -9
  291. package/web-dist/components/tree-view/TreeView.js +1 -1
  292. package/web-dist/components/uploader/Uploader.js +12 -12
  293. package/web-dist/components/view/View.js +2 -2
  294. package/web-dist/manifest.json +787 -772
  295. package/web-dist/runtime/ZoraProvider.js +3 -3
@@ -0,0 +1,231 @@
1
+ import {
2
+ type GridPoint,
3
+ type GridRect,
4
+ type GridResizeHandle,
5
+ hitTestWorldRects,
6
+ moveWorldRects,
7
+ panViewport,
8
+ resizeWorldRect,
9
+ resolveWorldSnapCandidate,
10
+ selectWorldRects,
11
+ viewportToWorld,
12
+ } from '@ankhorage/grid-view';
13
+
14
+ import type {
15
+ GridInteractionIntent,
16
+ GridInteractionItem,
17
+ GridInteractionPointer,
18
+ GridInteractionsController,
19
+ GridInteractionsControllerProps,
20
+ } from '../../../types/grid-interactions';
21
+
22
+ type InteractionState =
23
+ | { readonly kind: 'idle' }
24
+ | { readonly kind: 'marquee'; readonly start: GridPoint }
25
+ | { readonly kind: 'pan'; readonly start: GridPoint }
26
+ | { readonly kind: 'move'; readonly start: GridPoint; readonly itemIds: readonly string[] }
27
+ | {
28
+ readonly kind: 'resize';
29
+ readonly start: GridPoint;
30
+ readonly item: GridInteractionItem;
31
+ readonly handle: GridResizeHandle;
32
+ };
33
+
34
+ /*** Create a platform-neutral controlled interaction controller using grid-view geometry only. */
35
+ export function createGridInteractionsController(
36
+ props: GridInteractionsControllerProps,
37
+ ): GridInteractionsController {
38
+ let currentProps = props;
39
+ let state: InteractionState = { kind: 'idle' };
40
+ let previousIntent: GridInteractionIntent | undefined;
41
+ const point = (pointer: GridInteractionPointer) =>
42
+ Number.isFinite(pointer.x) && Number.isFinite(pointer.y)
43
+ ? viewportToWorld(pointer, currentProps.viewport)
44
+ : undefined;
45
+ const emit = (intent: GridInteractionIntent) => {
46
+ if (JSON.stringify(previousIntent) === JSON.stringify(intent)) return;
47
+ previousIntent = intent;
48
+ currentProps.onIntent(intent);
49
+ };
50
+
51
+ return {
52
+ begin: (pointer, handle) => {
53
+ if (!canEdit(currentProps)) return;
54
+ const start = point(pointer);
55
+ if (start === undefined) return;
56
+ if (pointer.spaceKey === true || pointer.altKey === true) {
57
+ state = { kind: 'pan', start };
58
+ return;
59
+ }
60
+ const target = hitTestWorldRects(currentProps.items, start);
61
+ if (target === undefined || isProtected(target)) {
62
+ state = { kind: 'marquee', start };
63
+ return;
64
+ }
65
+ if (handle !== undefined && target.resizable !== false) {
66
+ state = { kind: 'resize', start, item: target, handle };
67
+ return;
68
+ }
69
+ const itemIds =
70
+ currentProps.selectedIds?.includes(target.id) === true
71
+ ? currentProps.selectedIds
72
+ : [target.id];
73
+ state = { kind: 'move', start, itemIds };
74
+ },
75
+ move: (pointer) => {
76
+ if (!canEdit(currentProps)) return;
77
+ const current = point(pointer);
78
+ if (current !== undefined) emitPreview(state, current, currentProps, emit);
79
+ },
80
+ end: (pointer) => {
81
+ if (!canEdit(currentProps)) {
82
+ state = { kind: 'idle' };
83
+ return;
84
+ }
85
+ const current = point(pointer);
86
+ if (current !== undefined) emitPreview(state, current, currentProps, emit);
87
+ state = { kind: 'idle' };
88
+ },
89
+ cancel: () => {
90
+ state = { kind: 'idle' };
91
+ },
92
+ keyDown: (key, pointer = { x: 0, y: 0 }) => {
93
+ if (!canEdit(currentProps)) return false;
94
+ const focusedId = currentProps.selectedIds?.at(-1);
95
+ const focused = currentProps.items.find((item) => item.id === focusedId);
96
+ if (!focused || isProtected(focused)) return false;
97
+ const increment = pointer.shiftKey === true ? 10 : 1;
98
+ const delta = keyToDelta(key, increment);
99
+ if (delta === undefined) return false;
100
+ if (pointer.altKey === true) {
101
+ emit({
102
+ type: 'resize',
103
+ itemIds: [focused.id],
104
+ rects: [resizeWorldRect(focused, resizeHandleForDelta(delta), delta)],
105
+ });
106
+ return true;
107
+ }
108
+ emit({ type: 'move', itemIds: [focused.id], rects: moveWorldRects([focused], delta) });
109
+ return true;
110
+ },
111
+ update: (nextProps) => {
112
+ currentProps = nextProps;
113
+ },
114
+ };
115
+ }
116
+
117
+ /*** Determines whether the component-wide interaction policy permits edit intents. */
118
+ function canEdit(props: GridInteractionsControllerProps): boolean {
119
+ return props.interactionPolicy !== 'passive';
120
+ }
121
+
122
+ /*** Emit one controlled preview/intention without retaining or mutating caller-owned item data. */
123
+ function emitPreview(
124
+ state: InteractionState,
125
+ current: GridPoint,
126
+ props: GridInteractionsControllerProps,
127
+ emit: (intent: GridInteractionIntent) => void,
128
+ ) {
129
+ if (state.kind === 'idle') return;
130
+ if (state.kind === 'pan') {
131
+ emit({
132
+ type: 'pan',
133
+ itemIds: [],
134
+ viewport: panViewport(props.viewport, {
135
+ x: state.start.x - current.x,
136
+ y: state.start.y - current.y,
137
+ }),
138
+ });
139
+ return;
140
+ }
141
+ if (state.kind === 'marquee') {
142
+ const marquee = normalizeRect(state.start, current);
143
+ const itemIds = selectWorldRects(props.items, marquee, {
144
+ mode: props.marqueeMode ?? 'intersect',
145
+ }).filter((id) => {
146
+ const item = props.items.find((candidate) => candidate.id === id);
147
+ return item !== undefined && !isProtected(item);
148
+ });
149
+ emit({ type: 'marquee', itemIds, marquee });
150
+ return;
151
+ }
152
+ const delta = resolveSnapDelta(state, current, props);
153
+ if (state.kind === 'move') {
154
+ const selected = props.items.filter((item) => state.itemIds.includes(item.id));
155
+ emit({ type: 'move', itemIds: state.itemIds, rects: moveWorldRects(selected, delta) });
156
+ return;
157
+ }
158
+ emit({
159
+ type: 'resize',
160
+ itemIds: [state.item.id],
161
+ rects: [resizeWorldRect(state.item, state.handle, delta)],
162
+ });
163
+ }
164
+
165
+ /*** Resolve explicit per-axis candidate snapping without inventing geometry outside grid-view. */
166
+ function resolveSnapDelta(
167
+ state: InteractionState,
168
+ current: GridPoint,
169
+ props: GridInteractionsControllerProps,
170
+ ): GridPoint {
171
+ if (state.kind === 'idle' || state.kind === 'marquee' || state.kind === 'pan') {
172
+ return { x: 0, y: 0 };
173
+ }
174
+ const raw = { x: current.x - state.start.x, y: current.y - state.start.y };
175
+ if (props.snap?.enabled !== true) return raw;
176
+ const anchor =
177
+ state.kind === 'resize' ? state.item : props.items.find((item) => item.id === state.itemIds[0]);
178
+ if (anchor === undefined) return raw;
179
+ const options = {
180
+ enabled: true,
181
+ tolerancePixels: props.snap.tolerancePixels,
182
+ priorities: props.snap.priorities,
183
+ };
184
+ return {
185
+ x:
186
+ resolveWorldSnapCandidate(anchor.x + raw.x, props.snap.x ?? [], {
187
+ ...options,
188
+ pixelsPerUnit: props.viewport.pixelsPerUnitX,
189
+ }) - anchor.x,
190
+ y:
191
+ resolveWorldSnapCandidate(anchor.y + raw.y, props.snap.y ?? [], {
192
+ ...options,
193
+ pixelsPerUnit: props.viewport.pixelsPerUnitY,
194
+ }) - anchor.y,
195
+ };
196
+ }
197
+
198
+ /*** Keep all caller-declared protected items out of selection and edit intents. */
199
+ function isProtected(item: GridInteractionItem): boolean {
200
+ return (
201
+ item.disabled === true ||
202
+ item.locked === true ||
203
+ item.readOnly === true ||
204
+ item.passive === true
205
+ );
206
+ }
207
+
208
+ /*** Normalize an arbitrary-direction drag into the world rectangle expected by grid-view selection. */
209
+ function normalizeRect(start: GridPoint, end: GridPoint): GridRect {
210
+ return {
211
+ x: Math.min(start.x, end.x),
212
+ y: Math.min(start.y, end.y),
213
+ width: Math.abs(end.x - start.x),
214
+ height: Math.abs(end.y - start.y),
215
+ };
216
+ }
217
+
218
+ /** Maps accessible arrow keys to caller-controlled world-space edit increments. */
219
+ function keyToDelta(key: string, increment: number): GridPoint | undefined {
220
+ if (key === 'ArrowUp') return { x: 0, y: -increment };
221
+ if (key === 'ArrowRight') return { x: increment, y: 0 };
222
+ if (key === 'ArrowDown') return { x: 0, y: increment };
223
+ if (key === 'ArrowLeft') return { x: -increment, y: 0 };
224
+ return undefined;
225
+ }
226
+
227
+ /** Chooses the physical edge matching an accessible resize direction. */
228
+ function resizeHandleForDelta(delta: GridPoint): GridResizeHandle {
229
+ if (delta.x < 0) return delta.y < 0 ? 'top-left' : delta.y > 0 ? 'bottom-left' : 'left';
230
+ return delta.y < 0 ? 'top-right' : delta.y > 0 ? 'bottom-right' : 'right';
231
+ }
@@ -0,0 +1,12 @@
1
+ import type { ZoraComponentMeta } from '../../types/authoring';
2
+
3
+ /** Grid input is a code composition boundary because it accepts live geometry and callbacks. */
4
+ export const gridInteractionsMeta = {
5
+ name: 'GridInteractions',
6
+ category: 'foundation',
7
+ description: 'Maps native and web input to controlled world-space grid interaction intents.',
8
+ directManifestNode: false,
9
+ note: 'Requires live viewport geometry, item data, and an executable intent callback; available through code APIs only.',
10
+ allowedChildren: [],
11
+ props: {},
12
+ } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,11 @@
1
+ export type {
2
+ GridInteractionIntent,
3
+ GridInteractionItem,
4
+ GridInteractionPointer,
5
+ GridInteractionsController,
6
+ GridInteractionsControllerProps,
7
+ GridInteractionSnap,
8
+ GridInteractionsProps,
9
+ } from '../../types/grid-interactions';
10
+ export { GridInteractions } from './adapters/inbound/GridInteractions';
11
+ export { createGridInteractionsController } from './application/createGridInteractionsController';
@@ -63,10 +63,12 @@ const cells = layout.rows.flatMap((row) =>
63
63
  type MatrixGridComponent = React.ComponentType<{
64
64
  readonly cells: typeof cells;
65
65
  readonly height: number;
66
+ readonly interactionPolicy?: 'passive';
66
67
  readonly layout: typeof layout;
67
68
  readonly onSelectionChange?: (ids: readonly string[]) => void;
68
69
  readonly onViewportChange?: (viewport: unknown) => void;
69
70
  readonly renderCell: (cell: { readonly id: string }) => React.ReactNode;
71
+ readonly readOnly?: boolean;
70
72
  readonly selectedCellIds?: readonly string[];
71
73
  readonly selectionMode?: 'multi';
72
74
  readonly testID: string;
@@ -127,8 +129,8 @@ test('mounts only visible sparse cells with independent axis zoom and preserves
127
129
  expect(mountedCells.length).toBeLessThan(100);
128
130
  const target = host.querySelector('[data-testid="matrix-cell-row-1-column-1"]');
129
131
  expect(target).not.toBeNull();
130
- expect(target?.parentElement?.getAttribute('style')).toContain('left: 20px');
131
- expect(target?.parentElement?.getAttribute('style')).toContain('top: 24px');
132
+ expect(target?.parentElement?.parentElement?.getAttribute('style')).toContain('left: 20px');
133
+ expect(target?.parentElement?.parentElement?.getAttribute('style')).toContain('top: 24px');
132
134
  void act(() => target?.dispatchEvent(new browser.MouseEvent('click', { bubbles: true })));
133
135
  expect(selected).toEqual([['row-1-column-1']]);
134
136
  } finally {
@@ -230,3 +232,279 @@ test('proposes an uncontrolled horizontal pan in world units while retaining ind
230
232
  }
231
233
  });
232
234
  });
235
+
236
+ test('navigates sparse cells by keyboard and applies range and modifier selection intents', async () => {
237
+ await withMatrixDom(async (host, browser) => {
238
+ const selections: (readonly string[])[] = [];
239
+ const root = createRoot(host);
240
+ const { MatrixGrid } = (await load('components/matrix-grid/MatrixGrid.js')) as {
241
+ MatrixGrid: MatrixGridComponent;
242
+ };
243
+ const { ZoraProvider } = (await load('runtime/ZoraProvider.js')) as {
244
+ ZoraProvider: ZoraProviderComponent;
245
+ };
246
+ const sparseLayout = {
247
+ columns: [
248
+ { id: 'column-0', size: 20 },
249
+ { id: 'column-1', size: 20 },
250
+ { id: 'column-2', size: 20 },
251
+ ],
252
+ rows: [
253
+ { id: 'row-0', size: 20 },
254
+ { id: 'row-1', size: 20 },
255
+ { id: 'row-2', size: 20 },
256
+ ],
257
+ };
258
+ const sparseCells = [
259
+ { columnId: 'column-0', id: 'a1', rowId: 'row-0' },
260
+ { columnId: 'column-2', id: 'c1', rowId: 'row-0' },
261
+ { columnId: 'column-0', id: 'a3', rowId: 'row-2' },
262
+ { columnId: 'column-2', id: 'c3', rowId: 'row-2' },
263
+ ];
264
+
265
+ function KeyboardMatrix() {
266
+ const [selectedIds, setSelectedIds] = React.useState<readonly string[]>(['a1']);
267
+ return (
268
+ <ZoraProvider>
269
+ <MatrixGrid
270
+ cells={sparseCells}
271
+ height={80}
272
+ layout={sparseLayout}
273
+ selectedCellIds={selectedIds}
274
+ selectionMode="multi"
275
+ testID="keyboard-matrix"
276
+ width={80}
277
+ onSelectionChange={(ids) => {
278
+ selections.push(ids);
279
+ setSelectedIds(ids);
280
+ }}
281
+ renderCell={(cell) => <span>{cell.id}</span>}
282
+ />
283
+ </ZoraProvider>
284
+ );
285
+ }
286
+
287
+ try {
288
+ act(() => root.render(<KeyboardMatrix />));
289
+ const a1 = host.querySelector<HTMLElement>('[data-testid="keyboard-matrix-cell-a1"]');
290
+ if (!a1) throw new Error('Missing initial sparse matrix cell.');
291
+ act(() => a1.focus());
292
+ act(() => {
293
+ void a1.dispatchEvent(
294
+ new browser.KeyboardEvent('keydown', { bubbles: true, key: 'ArrowRight' }),
295
+ );
296
+ });
297
+ const c1 = host.querySelector<HTMLElement>('[data-testid="keyboard-matrix-cell-c1"]');
298
+ expect(c1).not.toBeNull();
299
+ expect(document.activeElement).toBe(c1);
300
+ act(() => {
301
+ void c1?.dispatchEvent(
302
+ new browser.KeyboardEvent('keydown', { bubbles: true, key: 'ArrowDown', shiftKey: true }),
303
+ );
304
+ });
305
+ expect(selections).toEqual([['a1', 'c1', 'a3', 'c3']]);
306
+ const c3 = host.querySelector<HTMLElement>('[data-testid="keyboard-matrix-cell-c3"]');
307
+ act(() => {
308
+ void c3?.dispatchEvent(
309
+ new browser.KeyboardEvent('keydown', { bubbles: true, ctrlKey: true, key: 'Enter' }),
310
+ );
311
+ });
312
+ expect(selections.at(-1)).toEqual(['a1', 'c1', 'a3']);
313
+ act(() => {
314
+ void c3?.dispatchEvent(new browser.KeyboardEvent('keydown', { bubbles: true, key: ' ' }));
315
+ });
316
+ expect(selections.at(-1)).toEqual(['c3']);
317
+ } finally {
318
+ act(() => root.unmount());
319
+ }
320
+ });
321
+ });
322
+
323
+ test('does not capture keyboard input from interactive cell content and respects read-only selection', async () => {
324
+ await withMatrixDom(async (host, browser) => {
325
+ const selections: (readonly string[])[] = [];
326
+ const root = createRoot(host);
327
+ const { MatrixGrid } = (await load('components/matrix-grid/MatrixGrid.js')) as {
328
+ MatrixGrid: MatrixGridComponent;
329
+ };
330
+ const { ZoraProvider } = (await load('runtime/ZoraProvider.js')) as {
331
+ ZoraProvider: ZoraProviderComponent;
332
+ };
333
+ const oneCellLayout = {
334
+ columns: [{ id: 'column-0', size: 20 }],
335
+ rows: [{ id: 'row-0', size: 20 }],
336
+ };
337
+ const oneCell = [{ columnId: 'column-0', id: 'a1', rowId: 'row-0' }];
338
+
339
+ try {
340
+ act(() =>
341
+ root.render(
342
+ <ZoraProvider>
343
+ <MatrixGrid
344
+ cells={oneCell}
345
+ height={40}
346
+ layout={oneCellLayout}
347
+ readOnly
348
+ testID="interactive-matrix"
349
+ width={40}
350
+ onSelectionChange={(ids) => selections.push(ids)}
351
+ renderCell={() => <input data-testid="matrix-editor" />}
352
+ />
353
+ </ZoraProvider>,
354
+ ),
355
+ );
356
+ const editor = host.querySelector<HTMLInputElement>('[data-testid="matrix-editor"]');
357
+ const cell = host.querySelector<HTMLElement>('[data-testid="interactive-matrix-cell-a1"]');
358
+ if (!editor) throw new Error('Missing embedded matrix editor.');
359
+ if (!cell) throw new Error('Missing read-only matrix cell.');
360
+ act(() => {
361
+ void cell.dispatchEvent(new browser.MouseEvent('click', { bubbles: true }));
362
+ });
363
+ act(() => editor.focus());
364
+ act(() => {
365
+ void editor.dispatchEvent(
366
+ new browser.KeyboardEvent('keydown', { bubbles: true, key: 'ArrowRight' }),
367
+ );
368
+ void editor.dispatchEvent(
369
+ new browser.KeyboardEvent('keydown', { bubbles: true, key: 'Enter' }),
370
+ );
371
+ void editor.dispatchEvent(
372
+ new browser.KeyboardEvent('keydown', { bubbles: true, key: ' ' }),
373
+ );
374
+ });
375
+ expect(document.activeElement).toBe(editor);
376
+ expect(selections).toEqual([]);
377
+ } finally {
378
+ act(() => root.unmount());
379
+ }
380
+ });
381
+ });
382
+
383
+ test('suppresses selection from passive matrix cells', async () => {
384
+ await withMatrixDom(async (host, browser) => {
385
+ const selections: (readonly string[])[] = [];
386
+ const root = createRoot(host);
387
+ const { MatrixGrid } = (await load('components/matrix-grid/MatrixGrid.js')) as {
388
+ MatrixGrid: MatrixGridComponent;
389
+ };
390
+ const { ZoraProvider } = (await load('runtime/ZoraProvider.js')) as {
391
+ ZoraProvider: ZoraProviderComponent;
392
+ };
393
+ const oneCellLayout = {
394
+ columns: [{ id: 'column-0', size: 20 }],
395
+ rows: [{ id: 'row-0', size: 20 }],
396
+ };
397
+ const oneCell = [{ columnId: 'column-0', id: 'a1', rowId: 'row-0' }];
398
+
399
+ try {
400
+ act(() =>
401
+ root.render(
402
+ <ZoraProvider>
403
+ <MatrixGrid
404
+ cells={oneCell}
405
+ height={40}
406
+ interactionPolicy="passive"
407
+ layout={oneCellLayout}
408
+ testID="passive-matrix"
409
+ width={40}
410
+ onSelectionChange={(ids) => selections.push(ids)}
411
+ renderCell={(cell) => <span>{cell.id}</span>}
412
+ />
413
+ </ZoraProvider>,
414
+ ),
415
+ );
416
+ const cell = host.querySelector<HTMLElement>('[data-testid="passive-matrix-cell-a1"]');
417
+ if (!cell) throw new Error('Missing passive matrix cell.');
418
+ act(() => {
419
+ void cell.dispatchEvent(new browser.MouseEvent('click', { bubbles: true }));
420
+ void cell.dispatchEvent(
421
+ new browser.KeyboardEvent('keydown', { bubbles: true, key: 'ArrowRight' }),
422
+ );
423
+ });
424
+ expect(selections).toEqual([]);
425
+ } finally {
426
+ act(() => root.unmount());
427
+ }
428
+ });
429
+ });
430
+
431
+ test('reveals an offscreen keyboard target through a controlled viewport with independent axis zoom', async () => {
432
+ await withMatrixDom(async (host, browser) => {
433
+ const root = createRoot(host);
434
+ const { MatrixGrid } = (await load('components/matrix-grid/MatrixGrid.js')) as {
435
+ MatrixGrid: MatrixGridComponent;
436
+ };
437
+ const { ZoraProvider } = (await load('runtime/ZoraProvider.js')) as {
438
+ ZoraProvider: ZoraProviderComponent;
439
+ };
440
+ const sparseLayout = {
441
+ columns: [
442
+ { id: 'column-0', size: 20 },
443
+ { id: 'column-1', size: 20 },
444
+ { id: 'column-2', size: 20 },
445
+ ],
446
+ rows: [{ id: 'row-0', size: 20 }],
447
+ };
448
+ const sparseCells = [
449
+ { columnId: 'column-0', id: 'a1', rowId: 'row-0' },
450
+ { columnId: 'column-2', id: 'c1', rowId: 'row-0' },
451
+ ];
452
+ function ControlledMatrix() {
453
+ const [viewport, setViewport] = React.useState<MatrixGridViewport>({
454
+ height: 20,
455
+ offsetX: 0,
456
+ offsetY: 0,
457
+ pixelsPerUnitX: 2,
458
+ pixelsPerUnitY: 3,
459
+ width: 20,
460
+ });
461
+ return (
462
+ <MatrixGrid
463
+ cells={sparseCells}
464
+ height={20}
465
+ layout={sparseLayout}
466
+ overscanPixels={0}
467
+ testID="controlled-keyboard-matrix"
468
+ viewport={viewport}
469
+ width={20}
470
+ onViewportChange={(proposal) => {
471
+ const nextViewport = proposal as MatrixGridViewport;
472
+ setViewport(nextViewport);
473
+ }}
474
+ renderCell={(cell) => <span>{cell.id}</span>}
475
+ />
476
+ );
477
+ }
478
+
479
+ try {
480
+ act(() =>
481
+ root.render(
482
+ <ZoraProvider>
483
+ <ControlledMatrix />
484
+ </ZoraProvider>,
485
+ ),
486
+ );
487
+ const a1 = host.querySelector<HTMLElement>(
488
+ '[data-testid="controlled-keyboard-matrix-cell-a1"]',
489
+ );
490
+ if (!a1) throw new Error('Missing controlled focus origin.');
491
+ await act(async () => {
492
+ await new Promise<void>((resolve) => browser.setTimeout(resolve, 0));
493
+ a1.focus();
494
+ void a1.dispatchEvent(
495
+ new browser.KeyboardEvent('keydown', { bubbles: true, key: 'ArrowRight' }),
496
+ );
497
+ await new Promise<void>((resolve) => browser.setTimeout(resolve, 20));
498
+ });
499
+ const c1 = host.querySelector<HTMLElement>(
500
+ '[data-testid="controlled-keyboard-matrix-cell-c1"]',
501
+ );
502
+ expect(c1).not.toBeNull();
503
+ expect(document.activeElement).toBe(c1);
504
+ } finally {
505
+ act(() => root.unmount());
506
+ }
507
+ });
508
+ });
509
+
510
+ type MatrixGridViewport = NonNullable<React.ComponentProps<MatrixGridComponent>['viewport']>;