@ankhorage/zora 25.7.0 → 25.8.1

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 +37 -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 +200 -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 +229 -0
  114. package/src/features/grid-interactions/application/createGridInteractionsController.ts +248 -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 +379 -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
@@ -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']>;
@@ -1,19 +1,23 @@
1
1
  import {
2
+ getMatrixCellPlacement,
2
3
  getVisibleMatrixCells,
3
4
  type GridMatrixCellPlacement,
4
5
  type GridViewport,
5
6
  } from '@ankhorage/grid-view';
6
- import { Pressable } from '@ankhorage/surface';
7
+ import { isRecord } from '@ankhorage/utility/object';
7
8
  import React from 'react';
8
- import { StyleSheet, View } from 'react-native';
9
+ import { Platform, Pressable as NativePressable, StyleSheet, View } from 'react-native';
9
10
 
10
11
  import type { MatrixGridProps } from '../../../../types/matrix-grid';
11
12
  import { GridView } from '../../../grid-view/public';
12
13
  import { resolveSelectionEventIntent } from '../../../selection/public';
13
14
  import { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThemeScope';
14
15
  import { useZoraTheme } from '../../../theme/composition/useZoraTheme';
16
+ import { createMatrixGridSparseIndex } from '../../application/createMatrixGridSparseIndex';
15
17
  import { resolveMatrixGridContentSize } from '../../application/resolveMatrixGridContentSize';
16
18
  import { resolveMatrixGridSelection } from '../../application/resolveMatrixGridSelection';
19
+ import { MatrixGridKeyboardProxy } from './MatrixGridKeyboardProxy';
20
+ import { useMatrixGridKeyboardFocus } from './useMatrixGridKeyboardFocus';
17
21
 
18
22
  /*** Compose the canonical GridView around released sparse matrix placement and culling geometry. */
19
23
  function MatrixGridInner({
@@ -25,6 +29,7 @@ function MatrixGridInner({
25
29
  onSelectionChange,
26
30
  onViewportChange,
27
31
  overscanPixels = 160,
32
+ readOnly = false,
28
33
  renderCell,
29
34
  selectedCellIds = [],
30
35
  selectionMode = 'single',
@@ -42,15 +47,16 @@ function MatrixGridInner({
42
47
  );
43
48
  const [uncontrolledViewport, setUncontrolledViewport] = React.useState(initialViewport);
44
49
  const viewport = controlledViewport ?? uncontrolledViewport;
45
- const visibleCells = React.useMemo(
50
+ const sparseIndex = React.useMemo(
51
+ () => createMatrixGridSparseIndex(layout, cells),
52
+ [cells, layout],
53
+ );
54
+ const cellPlacements = React.useMemo(
46
55
  () => getVisibleMatrixCells(layout, cells, viewport, overscanPixels),
47
56
  [cells, layout, overscanPixels, viewport],
48
57
  );
49
-
50
- const visibleCellById = React.useMemo(
51
- () => new Map(visibleCells.map((cell) => [cell.id, cell])),
52
- [visibleCells],
53
- );
58
+ const [visibleIds, setVisibleIds] = React.useState<readonly string[]>([]);
59
+ const anchorId = React.useRef<string | null>(null);
54
60
 
55
61
  const handleViewportChange = React.useCallback(
56
62
  (proposal: GridViewport) => {
@@ -62,14 +68,63 @@ function MatrixGridInner({
62
68
 
63
69
  const activateCell = React.useCallback(
64
70
  (cell: GridMatrixCellPlacement, event: unknown) => {
65
- if (interactionPolicy === 'passive') return;
66
- const intent = resolveSelectionEventIntent(event, 'pointer');
71
+ if (interactionPolicy === 'passive' || readOnly) return;
72
+ const intent = isRangeSelectionEvent(event)
73
+ ? 'range'
74
+ : resolveSelectionEventIntent(event, 'pointer');
67
75
  onSelectionChange?.(
68
- resolveMatrixGridSelection(selectedCellIds, cell.id, intent, selectionMode),
76
+ resolveMatrixGridSelection(
77
+ sparseIndex.entries,
78
+ selectedCellIds,
79
+ cell.id,
80
+ anchorId.current,
81
+ intent,
82
+ selectionMode,
83
+ ),
69
84
  );
85
+ if (intent !== 'range') anchorId.current = cell.id;
70
86
  },
71
- [interactionPolicy, onSelectionChange, selectedCellIds, selectionMode],
87
+ [
88
+ interactionPolicy,
89
+ onSelectionChange,
90
+ readOnly,
91
+ selectedCellIds,
92
+ selectionMode,
93
+ sparseIndex.entries,
94
+ ],
95
+ );
96
+ const handleKeyboardNavigation = React.useCallback(
97
+ (targetId: string, originId: string, shiftKey: boolean) => {
98
+ anchorId.current ??= originId;
99
+ if (!shiftKey) return;
100
+ const cell = sparseIndex.cellById.get(targetId);
101
+ if (cell) activateCell(getMatrixCellPlacement(layout, cell), { shiftKey: true });
102
+ },
103
+ [activateCell, layout, sparseIndex],
104
+ );
105
+ const keyboard = useMatrixGridKeyboardFocus(
106
+ sparseIndex,
107
+ interactionPolicy === 'passive',
108
+ visibleIds,
109
+ handleKeyboardNavigation,
110
+ );
111
+ const focusedPlacement = React.useMemo(() => {
112
+ const focusedCell =
113
+ keyboard.focusedId === null ? undefined : sparseIndex.cellById.get(keyboard.focusedId);
114
+ return focusedCell ? getMatrixCellPlacement(layout, focusedCell) : undefined;
115
+ }, [keyboard.focusedId, layout, sparseIndex.cellById]);
116
+ const gridItems = React.useMemo(
117
+ () =>
118
+ focusedPlacement && !cellPlacements.some((cell) => cell.id === focusedPlacement.id)
119
+ ? [...cellPlacements, focusedPlacement]
120
+ : cellPlacements,
121
+ [cellPlacements, focusedPlacement],
72
122
  );
123
+ const cellById = React.useMemo(
124
+ () => new Map(gridItems.map((cell) => [cell.id, cell])),
125
+ [gridItems],
126
+ );
127
+
73
128
  return (
74
129
  <View accessibilityLabel={accessibilityLabel}>
75
130
  <GridView
@@ -77,10 +132,12 @@ function MatrixGridInner({
77
132
  contentWidth={contentSize.width}
78
133
  height={height}
79
134
  interactionPolicy={interactionPolicy}
80
- items={visibleCells}
135
+ items={gridItems}
136
+ focusedItemId={keyboard.focusedId ?? undefined}
81
137
  overscanPixels={0}
138
+ onVisibleItemIdsChange={setVisibleIds}
82
139
  renderItem={(item) => {
83
- const cell = visibleCellById.get(item.id);
140
+ const cell = cellById.get(item.id);
84
141
  if (!cell) return null;
85
142
  return renderMatrixCell(
86
143
  cell,
@@ -89,6 +146,9 @@ function MatrixGridInner({
89
146
  theme.colors.primary,
90
147
  renderCell,
91
148
  activateCell,
149
+ keyboard,
150
+ interactionPolicy === 'passive',
151
+ readOnly,
92
152
  testID,
93
153
  );
94
154
  }}
@@ -109,28 +169,58 @@ function renderMatrixCell(
109
169
  selectedColor: string,
110
170
  renderCell: MatrixGridProps['renderCell'],
111
171
  activateCell: (cell: GridMatrixCellPlacement, event: unknown) => void,
172
+ keyboard: ReturnType<typeof useMatrixGridKeyboardFocus>,
173
+ passive: boolean,
174
+ readOnly: boolean,
112
175
  testID: string | undefined,
113
176
  ) {
114
177
  const selected = selectedCellIds.includes(cell.id);
178
+ const canInteract = keyboard.tabStopId === cell.id;
115
179
  return (
116
- <Pressable
117
- key={cell.id}
118
- accessibilityLabel={`Cell ${cell.id}`}
119
- accessibilityRole="button"
120
- accessibilityState={{ selected }}
121
- style={[
122
- styles.cell,
123
- {
124
- borderColor: selected ? selectedColor : dividerColor,
125
- height: '100%',
126
- width: '100%',
127
- },
128
- ]}
129
- testID={testID === undefined ? undefined : `${testID}-cell-${cell.id}`}
130
- onPress={(event) => activateCell(cell, event)}
180
+ <MatrixGridKeyboardProxy
181
+ onKeyDown={(event) => {
182
+ if (keyboard.onKeyDown(cell.id, event.key, event.shiftKey)) return true;
183
+ if (!['Enter', ' '].includes(event.key)) return false;
184
+ if (passive || readOnly) return false;
185
+ activateCell(cell, event);
186
+ return true;
187
+ }}
131
188
  >
132
- {renderCell(cell)}
133
- </Pressable>
189
+ <NativePressable
190
+ key={cell.id}
191
+ accessibilityActions={[{ name: 'activate' }]}
192
+ accessibilityLabel={`Cell ${cell.id}`}
193
+ accessibilityRole="button"
194
+ accessibilityState={{ disabled: passive, selected }}
195
+ disabled={passive}
196
+ focusable={Platform.OS !== 'web' || canInteract}
197
+ style={[
198
+ styles.cell,
199
+ {
200
+ borderColor: selected || keyboard.focusedId === cell.id ? selectedColor : dividerColor,
201
+ height: '100%',
202
+ width: '100%',
203
+ },
204
+ ]}
205
+ testID={testID === undefined ? undefined : `${testID}-cell-${cell.id}`}
206
+ onAccessibilityAction={() => activateCell(cell, {})}
207
+ onFocus={() => keyboard.onFocus(cell.id)}
208
+ onPress={(event) => activateCell(cell, event)}
209
+ ref={(node) => keyboard.registerCell(cell.id, node)}
210
+ >
211
+ {renderCell(cell)}
212
+ </NativePressable>
213
+ </MatrixGridKeyboardProxy>
214
+ );
215
+ }
216
+
217
+ /*** Recognize range intent without changing the shared replace-or-toggle selection contract. */
218
+ function isRangeSelectionEvent(event: unknown): boolean {
219
+ if (!isRecord(event)) return false;
220
+ if (event.shiftKey === true) return true;
221
+ return (
222
+ (isRecord(event.nativeEvent) && event.nativeEvent.shiftKey === true) ||
223
+ (isRecord(event.originalEvent) && event.originalEvent.shiftKey === true)
134
224
  );
135
225
  }
136
226
 
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+
3
+ /*** Retain native accessibility focus while physical-key dispatch remains a web host concern. */
4
+ export function MatrixGridKeyboardProxy({ children }: MatrixGridKeyboardProxyProps) {
5
+ return <>{children}</>;
6
+ }
7
+
8
+ interface MatrixGridKeyboardProxyProps {
9
+ readonly children: React.ReactNode;
10
+ readonly onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => boolean;
11
+ }
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+
3
+ /*** Provides the native fallback type surface; RNW resolves the web adapter at runtime. */
4
+ export function MatrixGridKeyboardProxy({ children }: MatrixGridKeyboardProxyProps) {
5
+ return <>{children}</>;
6
+ }
7
+
8
+ interface MatrixGridKeyboardProxyProps {
9
+ readonly children: React.ReactNode;
10
+ readonly onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => boolean;
11
+ }
@@ -0,0 +1,43 @@
1
+ import type React from 'react';
2
+
3
+ /*** Bridge browser keyboard events into the matrix's platform-neutral focus policy. */
4
+ export function MatrixGridKeyboardProxy({ children, onKeyDown }: MatrixGridKeyboardProxyProps) {
5
+ return (
6
+ <div
7
+ onKeyDownCapture={(event) => {
8
+ if (isNestedInteractiveElement(event.target, event.currentTarget)) return;
9
+ if (onKeyDown(event)) event.preventDefault();
10
+ }}
11
+ style={{ display: 'flex', flex: 1 }}
12
+ >
13
+ {children}
14
+ </div>
15
+ );
16
+ }
17
+
18
+ /*** Leave editable and independently interactive cell content in control of its own keyboard events. */
19
+ function isNestedInteractiveElement(target: EventTarget, boundary: EventTarget): boolean {
20
+ if (target === boundary || !hasClosest(target)) return false;
21
+ const interactiveElement = target.closest(interactiveSelector);
22
+ return interactiveElement !== null && interactiveElement !== getFirstElementChild(boundary);
23
+ }
24
+
25
+ /*** Recognize the browser-only closest capability without leaking DOM globals into shared code. */
26
+ function hasClosest(
27
+ target: EventTarget,
28
+ ): target is EventTarget & { closest: (selector: string) => unknown } {
29
+ return 'closest' in target && typeof target.closest === 'function';
30
+ }
31
+
32
+ /*** Exempt the cell's own Pressable while preserving keyboard ownership for nested controls. */
33
+ function getFirstElementChild(boundary: EventTarget): unknown {
34
+ return 'firstElementChild' in boundary ? boundary.firstElementChild : undefined;
35
+ }
36
+
37
+ const interactiveSelector =
38
+ 'a, button, input, select, textarea, [contenteditable="true"], [role="button"], [role="checkbox"], [role="combobox"], [role="link"], [role="menuitem"], [role="option"], [role="slider"], [role="spinbutton"], [role="switch"], [role="tab"]';
39
+
40
+ interface MatrixGridKeyboardProxyProps {
41
+ readonly children: React.ReactNode;
42
+ readonly onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => boolean;
43
+ }
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+
3
+ import type { MatrixGridSparseIndex } from '../../application/createMatrixGridSparseIndex';
4
+ import { getMatrixGridNextFocusId } from '../../application/getMatrixGridNextFocusId';
5
+
6
+ /*** Keep one roving focus target and restore it after GridView virtualizes a sparse cell. */
7
+ export function useMatrixGridKeyboardFocus(
8
+ index: MatrixGridSparseIndex,
9
+ disabled: boolean,
10
+ visibleIds: readonly string[],
11
+ onNavigate: (targetId: string, originId: string, shiftKey: boolean) => void,
12
+ ) {
13
+ const [focusedId, setFocusedId] = React.useState<string | null>(null);
14
+ const focusedRef = React.useRef<string | null>(null);
15
+ const pendingFocusRef = React.useRef<string | null>(null);
16
+ const cellRefs = React.useRef(new Map<string, FocusableCell>());
17
+ const visibleIdSet = React.useMemo(() => new Set(visibleIds), [visibleIds]);
18
+ const tabStopId =
19
+ focusedId !== null && visibleIdSet.has(focusedId)
20
+ ? focusedId
21
+ : visibleIds.find((id) => index.positionById.has(id));
22
+
23
+ const onFocus = React.useCallback((id: string) => {
24
+ pendingFocusRef.current = null;
25
+ focusedRef.current = id;
26
+ setFocusedId(id);
27
+ }, []);
28
+
29
+ const registerCell = React.useCallback((id: string, node: FocusableCell | null) => {
30
+ if (node === null) {
31
+ cellRefs.current.delete(id);
32
+ return;
33
+ }
34
+ cellRefs.current.set(id, node);
35
+ if (pendingFocusRef.current === id) {
36
+ pendingFocusRef.current = null;
37
+ node.focus();
38
+ }
39
+ }, []);
40
+
41
+ const onKeyDown = React.useCallback(
42
+ (id: string, key: string, shiftKey: boolean): boolean => {
43
+ if (disabled || !['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(key)) {
44
+ return false;
45
+ }
46
+ const originId = focusedRef.current ?? id;
47
+ const nextId = getMatrixGridNextFocusId(index, originId, key);
48
+ if (nextId === null || nextId === originId) return true;
49
+
50
+ focusedRef.current = nextId;
51
+ pendingFocusRef.current = nextId;
52
+ setFocusedId(nextId);
53
+ onNavigate(nextId, originId, shiftKey);
54
+
55
+ const mounted = cellRefs.current.get(nextId);
56
+ if (mounted) {
57
+ pendingFocusRef.current = null;
58
+ mounted.focus();
59
+ }
60
+ return true;
61
+ },
62
+ [disabled, index, onNavigate],
63
+ );
64
+
65
+ return { focusedId, onFocus, onKeyDown, registerCell, tabStopId };
66
+ }
67
+
68
+ interface FocusableCell {
69
+ focus: () => void;
70
+ }