@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
@@ -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
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { createMatrixGridSparseIndex } from './createMatrixGridSparseIndex';
4
+ import { getMatrixGridNextFocusId } from './getMatrixGridNextFocusId';
5
+
6
+ describe('createMatrixGridSparseIndex', () => {
7
+ test('indexes more than 10k sparse cells without creating placement rectangles', () => {
8
+ const layout = {
9
+ columns: Array.from({ length: 1_000 }, (_, index) => ({ id: `column-${index}`, size: 10 })),
10
+ rows: Array.from({ length: 1_000 }, (_, index) => ({ id: `row-${index}`, size: 10 })),
11
+ };
12
+ const cells = layout.rows.flatMap((row) =>
13
+ layout.columns.slice(0, 10).map((column) => ({
14
+ columnId: column.id,
15
+ id: `${row.id}-${column.id}`,
16
+ rowId: row.id,
17
+ })),
18
+ );
19
+ const index = createMatrixGridSparseIndex(layout, [...cells].reverse());
20
+
21
+ expect(index.entries).toHaveLength(10_000);
22
+ expect(index.entries[0]).toEqual({
23
+ cell: cells.at(-1),
24
+ columnIndex: 9,
25
+ id: 'row-999-column-9',
26
+ rowIndex: 999,
27
+ });
28
+ expect(getMatrixGridNextFocusId(index, 'row-500-column-5', 'ArrowRight')).toBe(
29
+ 'row-500-column-6',
30
+ );
31
+ expect(getMatrixGridNextFocusId(index, 'row-500-column-5', 'ArrowDown')).toBe(
32
+ 'row-501-column-5',
33
+ );
34
+ });
35
+ });
@@ -0,0 +1,105 @@
1
+ import type { GridMatrixCell, GridMatrixLayout } from '@ankhorage/grid-view';
2
+
3
+ /*** Build a geometry-independent lookup for sparse matrix navigation, selection, and focus recovery. */
4
+ export function createMatrixGridSparseIndex(
5
+ layout: GridMatrixLayout,
6
+ cells: readonly GridMatrixCell[],
7
+ ): MatrixGridSparseIndex {
8
+ const rowIndexes = new Map(layout.rows.map((row, index) => [row.id, index]));
9
+ const columnIndexes = new Map(layout.columns.map((column, index) => [column.id, index]));
10
+ const entries = cells.map((cell) => createSparseEntry(cell, rowIndexes, columnIndexes));
11
+ const entriesByRow = groupEntries(entries, 'columnIndex');
12
+ const entriesByColumn = groupEntries(entries, 'rowIndex');
13
+ const orderedEntries = [...entries].sort(compareRowThenColumn);
14
+
15
+ return {
16
+ cellById: new Map(entries.map((entry) => [entry.id, entry.cell])),
17
+ entries,
18
+ entriesByColumn,
19
+ entriesByRow,
20
+ firstId: orderedEntries.at(0)?.id ?? null,
21
+ positionById: new Map(entries.map((entry) => [entry.id, entry])),
22
+ };
23
+ }
24
+
25
+ /*** Resolve validated matrix coordinates once without materializing every cell rectangle. */
26
+ function createSparseEntry(
27
+ cell: GridMatrixCell,
28
+ rowIndexes: ReadonlyMap<string, number>,
29
+ columnIndexes: ReadonlyMap<string, number>,
30
+ ): MatrixGridSparseIndexEntry {
31
+ const rowIndex = rowIndexes.get(cell.rowId);
32
+ const columnIndex = columnIndexes.get(cell.columnId);
33
+ if (rowIndex === undefined || columnIndex === undefined) {
34
+ throw new Error(`Matrix cell ${cell.id} references an unknown row or column.`);
35
+ }
36
+ return { cell, columnIndex, id: cell.id, rowIndex };
37
+ }
38
+
39
+ /*** Group and sort directional neighbours so arrow navigation is logarithmic within one axis. */
40
+ function groupEntries(
41
+ entries: readonly MatrixGridSparseIndexEntry[],
42
+ axis: 'columnIndex' | 'rowIndex',
43
+ ): ReadonlyMap<number, readonly MatrixGridSparseIndexEntry[]> {
44
+ const groups = new Map<number, MatrixGridSparseIndexEntry[]>();
45
+ for (const entry of entries) {
46
+ const groupKey = axis === 'columnIndex' ? entry.rowIndex : entry.columnIndex;
47
+ const group = groups.get(groupKey) ?? [];
48
+ group.push(entry);
49
+ groups.set(groupKey, group);
50
+ }
51
+ return new Map(
52
+ [...groups].map(([key, group]) => [
53
+ key,
54
+ group.sort((left, right) => compareAxisThenId(left, right, axis)),
55
+ ]),
56
+ );
57
+ }
58
+
59
+ /*** Keep the initial roving target deterministic even when sparse input arrives in arbitrary order. */
60
+ function compareRowThenColumn(
61
+ left: MatrixGridSparseIndexEntry,
62
+ right: MatrixGridSparseIndexEntry,
63
+ ): number {
64
+ return (
65
+ left.rowIndex - right.rowIndex ||
66
+ left.columnIndex - right.columnIndex ||
67
+ left.id.localeCompare(right.id)
68
+ );
69
+ }
70
+
71
+ /*** Sort a row or column by its navigated coordinate and stable identity. */
72
+ function compareAxisThenId(
73
+ left: MatrixGridSparseIndexEntry,
74
+ right: MatrixGridSparseIndexEntry,
75
+ axis: 'columnIndex' | 'rowIndex',
76
+ ): number {
77
+ return (
78
+ getAxisCoordinate(left, axis) - getAxisCoordinate(right, axis) ||
79
+ left.id.localeCompare(right.id)
80
+ );
81
+ }
82
+
83
+ /*** Read a selected coordinate without dynamically indexing an object. */
84
+ function getAxisCoordinate(
85
+ entry: MatrixGridSparseIndexEntry,
86
+ axis: 'columnIndex' | 'rowIndex',
87
+ ): number {
88
+ return axis === 'columnIndex' ? entry.columnIndex : entry.rowIndex;
89
+ }
90
+
91
+ export type MatrixGridSparseIndex = Readonly<{
92
+ cellById: ReadonlyMap<string, GridMatrixCell>;
93
+ entries: readonly MatrixGridSparseIndexEntry[];
94
+ entriesByColumn: ReadonlyMap<number, readonly MatrixGridSparseIndexEntry[]>;
95
+ entriesByRow: ReadonlyMap<number, readonly MatrixGridSparseIndexEntry[]>;
96
+ firstId: string | null;
97
+ positionById: ReadonlyMap<string, MatrixGridSparseIndexEntry>;
98
+ }>;
99
+
100
+ export type MatrixGridSparseIndexEntry = Readonly<{
101
+ cell: GridMatrixCell;
102
+ columnIndex: number;
103
+ id: string;
104
+ rowIndex: number;
105
+ }>;
@@ -0,0 +1,93 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { createMatrixGridSparseIndex } from './createMatrixGridSparseIndex';
4
+ import { getMatrixGridNextFocusId } from './getMatrixGridNextFocusId';
5
+
6
+ const cells = [
7
+ {
8
+ columnId: 'a',
9
+ columnIndex: 0,
10
+ height: 1,
11
+ id: 'a1',
12
+ rowId: '1',
13
+ rowIndex: 0,
14
+ width: 1,
15
+ x: 0,
16
+ y: 0,
17
+ },
18
+ {
19
+ columnId: 'c',
20
+ columnIndex: 2,
21
+ height: 1,
22
+ id: 'c1',
23
+ rowId: '1',
24
+ rowIndex: 0,
25
+ width: 1,
26
+ x: 2,
27
+ y: 0,
28
+ },
29
+ {
30
+ columnId: 'a',
31
+ columnIndex: 0,
32
+ height: 1,
33
+ id: 'a3',
34
+ rowId: '3',
35
+ rowIndex: 2,
36
+ width: 1,
37
+ x: 0,
38
+ y: 2,
39
+ },
40
+ {
41
+ columnId: 'c',
42
+ columnIndex: 2,
43
+ height: 1,
44
+ id: 'c3',
45
+ rowId: '3',
46
+ rowIndex: 2,
47
+ width: 1,
48
+ x: 2,
49
+ y: 2,
50
+ },
51
+ ] as const;
52
+
53
+ const layout = {
54
+ columns: [
55
+ { id: 'a', size: 1 },
56
+ { id: 'b', size: 1 },
57
+ { id: 'c', size: 1 },
58
+ ],
59
+ rows: [
60
+ { id: '1', size: 1 },
61
+ { id: '2', size: 1 },
62
+ { id: '3', size: 1 },
63
+ ],
64
+ } as const;
65
+
66
+ const index = createMatrixGridSparseIndex(layout, cells);
67
+
68
+ describe('getMatrixGridNextFocusId', () => {
69
+ test('moves over sparse rows and columns without materializing missing cells', () => {
70
+ expect(getMatrixGridNextFocusId(index, 'a1', 'ArrowRight')).toBe('c1');
71
+ expect(getMatrixGridNextFocusId(index, 'c3', 'ArrowLeft')).toBe('a3');
72
+ expect(getMatrixGridNextFocusId(index, 'a1', 'ArrowDown')).toBe('a3');
73
+ expect(getMatrixGridNextFocusId(index, 'c3', 'ArrowUp')).toBe('c1');
74
+ });
75
+
76
+ test('retains focus at a sparse edge and initializes from the first real cell', () => {
77
+ expect(getMatrixGridNextFocusId(index, 'a1', 'ArrowLeft')).toBe('a1');
78
+ expect(getMatrixGridNextFocusId(index, null, 'ArrowRight')).toBe('a1');
79
+ expect(
80
+ getMatrixGridNextFocusId(createMatrixGridSparseIndex(layout, []), null, 'ArrowRight'),
81
+ ).toBeNull();
82
+ });
83
+
84
+ test('chooses geometric neighbours from an intentionally unsorted sparse input', () => {
85
+ const unsorted = createMatrixGridSparseIndex(layout, [cells[3], cells[1], cells[2], cells[0]]);
86
+
87
+ expect(getMatrixGridNextFocusId(unsorted, 'a1', 'ArrowRight')).toBe('c1');
88
+ expect(getMatrixGridNextFocusId(unsorted, 'c3', 'ArrowLeft')).toBe('a3');
89
+ expect(getMatrixGridNextFocusId(unsorted, 'a1', 'ArrowDown')).toBe('a3');
90
+ expect(getMatrixGridNextFocusId(unsorted, 'c3', 'ArrowUp')).toBe('c1');
91
+ expect(getMatrixGridNextFocusId(unsorted, null, 'ArrowRight')).toBe('a1');
92
+ });
93
+ });