@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.
- package/CHANGELOG.md +37 -0
- package/README.md +1 -1
- package/dist/capabilities/index.d.ts +2 -74
- package/dist/capabilities/index.d.ts.map +1 -1
- package/dist/capabilities/index.js +6170 -59
- package/dist/capabilities/index.js.map +119 -1
- package/dist/cli/index.d.ts +2 -74
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/features/grid-interactions/adapters/inbound/GridInteractions.d.ts +5 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractions.d.ts.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js +40 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.d.ts +2 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.d.ts.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.js +2 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.js.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.d.ts +11 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.d.ts.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.js +5 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.js.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.d.ts +11 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.d.ts.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js +20 -0
- package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.d.ts +17 -0
- package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.d.ts.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.js +76 -0
- package/dist/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.js.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.d.ts +21 -0
- package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.d.ts.map +1 -0
- package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.js +20 -0
- package/dist/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.js.map +1 -0
- package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts +4 -0
- package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts.map +1 -0
- package/dist/features/grid-interactions/application/createGridInteractionsController.js +200 -0
- package/dist/features/grid-interactions/application/createGridInteractionsController.js.map +1 -0
- package/dist/features/grid-interactions/meta.d.ts +11 -0
- package/dist/features/grid-interactions/meta.d.ts.map +1 -0
- package/dist/features/grid-interactions/meta.js +11 -0
- package/dist/features/grid-interactions/meta.js.map +1 -0
- package/dist/features/grid-interactions/public.d.ts +4 -0
- package/dist/features/grid-interactions/public.d.ts.map +1 -0
- package/dist/features/grid-interactions/public.js +3 -0
- package/dist/features/grid-interactions/public.js.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.d.ts.map +1 -1
- package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js +73 -19
- package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js.map +1 -1
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.d.ts +9 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.d.ts.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.js +5 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.js.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.d.ts +9 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.d.ts.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.js +5 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.js.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.d.ts +9 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.d.ts.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.js +28 -0
- package/dist/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.js.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts +14 -0
- package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts.map +1 -0
- package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js +50 -0
- package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js.map +1 -0
- package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.d.ts +18 -0
- package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.d.ts.map +1 -0
- package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.js +56 -0
- package/dist/features/matrix-grid/application/createMatrixGridSparseIndex.js.map +1 -0
- package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.d.ts +4 -0
- package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.d.ts.map +1 -0
- package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.js +65 -0
- package/dist/features/matrix-grid/application/getMatrixGridNextFocusId.js.map +1 -0
- package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts +8 -1
- package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts.map +1 -1
- package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js +15 -4
- package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js.map +1 -1
- package/dist/features/registry/ZORA_COMPONENT_REGISTRY.d.ts.map +1 -1
- package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js +2 -0
- package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js.map +1 -1
- package/dist/features/registry/componentMeta.d.ts.map +1 -1
- package/dist/features/registry/componentMeta.js +2 -0
- package/dist/features/registry/componentMeta.js.map +1 -1
- package/dist/features/registry/createEventCapabilities.d.ts +4 -0
- package/dist/features/registry/createEventCapabilities.d.ts.map +1 -0
- package/dist/features/registry/createEventCapabilities.js +76 -0
- package/dist/features/registry/createEventCapabilities.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types/grid-interactions.d.ts +58 -0
- package/dist/types/grid-interactions.d.ts.map +1 -0
- package/dist/types/grid-interactions.js +2 -0
- package/dist/types/grid-interactions.js.map +1 -0
- package/dist/types/matrix-grid.d.ts +2 -0
- package/dist/types/matrix-grid.d.ts.map +1 -1
- package/dist/types/matrix-grid.js.map +1 -1
- package/package.json +1825 -7
- package/src/capabilities/index.test.ts +87 -15
- package/src/capabilities/index.ts +5 -2
- package/src/cli/index.test.ts +5 -3
- package/src/cli/index.ts +5 -2
- package/src/features/grid-interactions/adapters/inbound/GridInteractions.test.tsx +143 -0
- package/src/features/grid-interactions/adapters/inbound/GridInteractions.tsx +79 -0
- package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.native.tsx +14 -0
- package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.tsx +1 -0
- package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx +41 -0
- package/src/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.test.ts +98 -0
- package/src/features/grid-interactions/adapters/inbound/createGridInteractionsPointerQueue.ts +94 -0
- package/src/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.test.ts +33 -0
- package/src/features/grid-interactions/adapters/inbound/resolveGridInteractionPointer.ts +45 -0
- package/src/features/grid-interactions/application/createGridInteractionsController.test.ts +229 -0
- package/src/features/grid-interactions/application/createGridInteractionsController.ts +248 -0
- package/src/features/grid-interactions/meta.ts +12 -0
- package/src/features/grid-interactions/public.ts +11 -0
- package/src/features/matrix-grid/adapters/inbound/MatrixGrid.test.tsx +280 -2
- package/src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx +121 -31
- package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.native.tsx +11 -0
- package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.tsx +11 -0
- package/src/features/matrix-grid/adapters/inbound/MatrixGridKeyboardProxy.web.tsx +43 -0
- package/src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts +70 -0
- package/src/features/matrix-grid/application/createMatrixGridSparseIndex.test.ts +35 -0
- package/src/features/matrix-grid/application/createMatrixGridSparseIndex.ts +105 -0
- package/src/features/matrix-grid/application/getMatrixGridNextFocusId.test.ts +93 -0
- package/src/features/matrix-grid/application/getMatrixGridNextFocusId.ts +112 -0
- package/src/features/matrix-grid/application/resolveMatrixGridSelection.test.ts +63 -2
- package/src/features/matrix-grid/application/resolveMatrixGridSelection.ts +27 -4
- package/src/features/registry/ZORA_COMPONENT_REGISTRY.ts +2 -0
- package/src/features/registry/componentMeta.ts +2 -0
- package/src/features/registry/createEventCapabilities.ts +108 -0
- package/src/index.ts +11 -0
- package/src/structureOwnership.test.ts +5 -0
- package/src/types/grid-interactions.ts +72 -0
- package/src/types/matrix-grid.ts +2 -0
- package/web-dist/chunks/{Accordion-5g05kfmx.js → Accordion-0q2wzjdd.js} +4 -4
- package/web-dist/chunks/{Accordion-b96evb46.js → Accordion-1fsrnq73.js} +1 -1
- package/web-dist/chunks/{Accordion-h3y8qev2.js → Accordion-38ts3gnn.js} +2 -2
- package/web-dist/chunks/{Accordion-jnc315h7.js → Accordion-8pkw32q9.js} +2 -2
- package/web-dist/chunks/{Accordion-h7zsx9eg.js → Accordion-9jrxktne.js} +3 -3
- package/web-dist/chunks/{Accordion-9w8wg9mf.js → Accordion-9rnsg9pk.js} +1 -1
- package/web-dist/chunks/{Accordion-4zqd7twp.js → Accordion-9ygakvwk.js} +5 -5
- package/web-dist/chunks/{Accordion-fhnazn3n.js → Accordion-b74j4hdt.js} +1 -1
- package/web-dist/chunks/{Accordion-nssr5gep.js → Accordion-c50pywj3.js} +2 -2
- package/web-dist/chunks/{Accordion-pvj5npzc.js → Accordion-ddrn94v3.js} +3 -3
- package/web-dist/chunks/{Accordion-090y18cv.js → Accordion-dk0dtyc0.js} +3 -3
- package/web-dist/chunks/{Accordion-59pa0677.js → Accordion-fcczvwgg.js} +0 -1
- package/web-dist/chunks/{Accordion-sp4k08kw.js → Accordion-geah5s4f.js} +4 -4
- package/web-dist/chunks/{Accordion-6rrqpb2h.js → Accordion-h4ekm5cp.js} +2 -2
- package/web-dist/chunks/{Accordion-3d4wkzg7.js → Accordion-hecpczme.js} +5 -5
- package/web-dist/chunks/{Accordion-a6551mh4.js → Accordion-hn43wt45.js} +1 -1
- package/web-dist/chunks/{Accordion-sw7ep29d.js → Accordion-hnx7hx55.js} +2 -2
- package/web-dist/chunks/{Accordion-rsvphbe4.js → Accordion-j1chy29e.js} +1 -1
- package/web-dist/chunks/{Accordion-ewf1zen9.js → Accordion-k9cxpg08.js} +5 -5
- package/web-dist/chunks/{Accordion-5df6cgyf.js → Accordion-md1g54wg.js} +1 -1
- package/web-dist/chunks/{Accordion-kjtcbe09.js → Accordion-p1ebrt17.js} +3 -3
- package/web-dist/chunks/{Accordion-qn398m1x.js → Accordion-r2g18vxa.js} +1 -1
- package/web-dist/chunks/{Accordion-zt9yghdz.js → Accordion-rctfgyfv.js} +4 -4
- package/web-dist/chunks/{Accordion-88t8fgxv.js → Accordion-rfc5mdbv.js} +1 -1
- package/web-dist/chunks/{Accordion-fmzxat3v.js → Accordion-sdnzqktc.js} +4 -4
- package/web-dist/chunks/{Accordion-v0grdh6q.js → Accordion-sq7ya2ye.js} +2 -2
- package/web-dist/chunks/{Accordion-ejx5gcf9.js → Accordion-t5scffdr.js} +9 -9
- package/web-dist/chunks/{Accordion-sy1cc5my.js → Accordion-ts788t7b.js} +3 -3
- package/web-dist/chunks/{Accordion-4s68t3h7.js → Accordion-v3rzy273.js} +6 -6
- package/web-dist/chunks/{Accordion-7tjz25jh.js → Accordion-vbzjq3z8.js} +1 -1
- package/web-dist/chunks/{Accordion-jh72azm2.js → Accordion-vg337250.js} +2 -2
- package/web-dist/chunks/{Accordion-42jfy1a1.js → Accordion-vj1m2wck.js} +1 -1
- package/web-dist/chunks/{Accordion-tqpsmdrk.js → Accordion-vxg5sw01.js} +4 -4
- package/web-dist/chunks/{Accordion-61ntvh14.js → Accordion-w59y0jdf.js} +4 -4
- package/web-dist/chunks/{Accordion-fjxcn6g9.js → Accordion-wcmrasqx.js} +8 -8
- package/web-dist/chunks/{Accordion-r1cj2w9e.js → Accordion-wpk8dxqp.js} +3 -3
- package/web-dist/chunks/{Accordion-w6qedeba.js → Accordion-x055n3b3.js} +1 -1
- package/web-dist/chunks/{Accordion-dbf93gxh.js → Accordion-xcm1qc39.js} +1 -1
- package/web-dist/chunks/{Accordion-7kq500r1.js → Accordion-xgxxg1hz.js} +1 -1
- package/web-dist/chunks/{Accordion-a4cma5b6.js → Accordion-y3083394.js} +3 -3
- package/web-dist/chunks/{Accordion-40tenzcg.js → Accordion-yexfbrzg.js} +4 -4
- package/web-dist/chunks/{Accordion-2r0gqk3x.js → Accordion-ztw7zp6v.js} +5 -5
- package/web-dist/chunks/{Accordion-bz04casa.js → Accordion-zypyr9xw.js} +211 -6
- package/web-dist/components/accordion/Accordion.js +5 -5
- package/web-dist/components/accordion-item/AccordionItem.js +5 -5
- package/web-dist/components/activity-indicator/ActivityIndicator.js +1 -1
- package/web-dist/components/app-bar/AppBar.js +7 -7
- package/web-dist/components/app-header/AppHeader.js +7 -7
- package/web-dist/components/app-shell/AppShell.js +2 -2
- package/web-dist/components/auth-screen/AuthScreen.js +10 -10
- package/web-dist/components/avatar/Avatar.js +5 -5
- package/web-dist/components/avatar-group/AvatarGroup.js +5 -5
- package/web-dist/components/badge/Badge.js +2 -2
- package/web-dist/components/barcode-scanner-view/BarcodeScannerView.js +10 -10
- package/web-dist/components/bottom-sheet/BottomSheet.js +3 -3
- package/web-dist/components/bottom-sheet-flat-list/BottomSheetFlatList.js +3 -3
- package/web-dist/components/bottom-sheet-scroll-view/BottomSheetScrollView.js +3 -3
- package/web-dist/components/bottom-sheet-section-list/BottomSheetSectionList.js +3 -3
- package/web-dist/components/bottom-sheet-view/BottomSheetView.js +3 -3
- package/web-dist/components/bottom-sheet-virtualized-list/BottomSheetVirtualizedList.js +3 -3
- package/web-dist/components/breadcrumbs/Breadcrumbs.js +5 -5
- package/web-dist/components/button/Button.js +3 -3
- package/web-dist/components/button-group/ButtonGroup.js +3 -3
- package/web-dist/components/camera-permission-view/CameraPermissionView.js +10 -10
- package/web-dist/components/card/Card.js +9 -9
- package/web-dist/components/card-back/CardBack.js +2 -2
- package/web-dist/components/card-hand/CardHand.js +2 -2
- package/web-dist/components/chat-list-item/ChatListItem.js +7 -7
- package/web-dist/components/checkbox/Checkbox.js +4 -4
- package/web-dist/components/checkbox-group/CheckboxGroup.js +4 -4
- package/web-dist/components/chess-board/ChessBoard.js +2 -2
- package/web-dist/components/chip/Chip.js +5 -5
- package/web-dist/components/chip-group/ChipGroup.js +5 -5
- package/web-dist/components/collection-editor/CollectionEditor.js +9 -9
- package/web-dist/components/content-rail/ContentRail.js +4 -4
- package/web-dist/components/data-table/DataTable.js +14 -14
- package/web-dist/components/date-picker/DatePicker.js +5 -5
- package/web-dist/components/dialog/Dialog.js +4 -4
- package/web-dist/components/divider/Divider.js +2 -2
- package/web-dist/components/empty-state/EmptyState.js +10 -10
- package/web-dist/components/explorer/Explorer.js +7 -7
- package/web-dist/components/field/Field.js +4 -4
- package/web-dist/components/file-explorer/FileExplorer.js +7 -7
- package/web-dist/components/flat-list/FlatList.js +5 -5
- package/web-dist/components/forgot-password-form/ForgotPasswordForm.js +10 -10
- package/web-dist/components/form/Form.js +6 -6
- package/web-dist/components/form-actions/FormActions.js +6 -6
- package/web-dist/components/form-error/FormError.js +6 -6
- package/web-dist/components/game/Game.js +3 -3
- package/web-dist/components/game-entity/GameEntity.js +3 -3
- package/web-dist/components/game-field/GameField.js +3 -3
- package/web-dist/components/game-input-zone/GameInputZone.js +3 -3
- package/web-dist/components/game-measurement-probe/GameMeasurementProbe.js +3 -3
- package/web-dist/components/game-overlay/GameOverlay.js +3 -3
- package/web-dist/components/gradient/Gradient.js +3 -3
- package/web-dist/components/gradient-renderer-provider/GradientRendererProvider.js +3 -3
- package/web-dist/components/graph-view/GraphView.js +2 -2
- package/web-dist/components/grid/Grid.js +2 -2
- package/web-dist/components/grid-interactions/GridInteractions.d.ts +3 -0
- package/web-dist/components/grid-interactions/GridInteractions.js +379 -0
- package/web-dist/components/grid-interactions/index.d.ts +1 -0
- package/web-dist/components/grid-interactions/index.js +2 -0
- package/web-dist/components/grid-line-overlay/GridLineOverlay.js +2 -2
- package/web-dist/components/grid-ruler/GridRuler.js +2 -2
- package/web-dist/components/grid-view/GridView.js +1 -1
- package/web-dist/components/heading/Heading.js +2 -2
- package/web-dist/components/hero/Hero.js +9 -9
- package/web-dist/components/icon/Icon.js +2 -2
- package/web-dist/components/icon-button/IconButton.js +3 -3
- package/web-dist/components/image/Image.js +4 -4
- package/web-dist/components/keyboard-avoiding-view/KeyboardAvoidingView.js +2 -2
- package/web-dist/components/list/List.js +5 -5
- package/web-dist/components/list-item/ListItem.js +5 -5
- package/web-dist/components/list-section/ListSection.js +5 -5
- package/web-dist/components/matrix-grid/MatrixGrid.js +296 -47
- package/web-dist/components/media-card/MediaCard.js +9 -9
- package/web-dist/components/media-explorer/MediaExplorer.js +7 -7
- package/web-dist/components/message-bubble/MessageBubble.js +7 -7
- package/web-dist/components/metric-card/MetricCard.js +9 -9
- package/web-dist/components/missing-element/MissingElement.js +4 -4
- package/web-dist/components/o-auth-provider-button/OAuthProviderButton.js +10 -10
- package/web-dist/components/o-auth-provider-list/OAuthProviderList.js +10 -10
- package/web-dist/components/opening-book/OpeningBook.js +2 -2
- package/web-dist/components/otp-form/OtpForm.js +10 -10
- package/web-dist/components/pagination/Pagination.js +4 -4
- package/web-dist/components/palette-item/PaletteItem.js +9 -9
- package/web-dist/components/playing-card/PlayingCard.js +2 -2
- package/web-dist/components/poker-training-table/PokerTrainingTable.js +2 -2
- package/web-dist/components/popover-menu/PopoverMenu.js +4 -4
- package/web-dist/components/post-card/PostCard.js +9 -9
- package/web-dist/components/product-card/ProductCard.js +9 -9
- package/web-dist/components/progress/Progress.js +4 -4
- package/web-dist/components/progress-ring/ProgressRing.js +4 -4
- package/web-dist/components/radio/Radio.js +5 -5
- package/web-dist/components/radio-group/RadioGroup.js +5 -5
- package/web-dist/components/rating/Rating.js +3 -3
- package/web-dist/components/reader/Reader.js +10 -10
- package/web-dist/components/scan-overlay/ScanOverlay.js +10 -10
- package/web-dist/components/screen/Screen.js +2 -2
- package/web-dist/components/screen-section/ScreenSection.js +4 -4
- package/web-dist/components/scroll-view/ScrollView.js +2 -2
- package/web-dist/components/search-input/SearchInput.js +4 -4
- package/web-dist/components/section-header/SectionHeader.js +4 -4
- package/web-dist/components/section-list/SectionList.js +5 -5
- package/web-dist/components/select/Select.js +4 -4
- package/web-dist/components/selectable-item/SelectableItem.js +2 -2
- package/web-dist/components/selection-provider/SelectionProvider.js +2 -2
- package/web-dist/components/sign-in-form/SignInForm.js +10 -10
- package/web-dist/components/sign-up-form/SignUpForm.js +10 -10
- package/web-dist/components/skeleton/Skeleton.js +12 -12
- package/web-dist/components/skeleton-card/SkeletonCard.js +12 -12
- package/web-dist/components/skeleton-list/SkeletonList.js +12 -12
- package/web-dist/components/skeleton-text/SkeletonText.js +12 -12
- package/web-dist/components/spatial-grid/SpatialGrid.js +1 -1
- package/web-dist/components/surface/Surface.js +2 -2
- package/web-dist/components/switch/Switch.js +1 -1
- package/web-dist/components/tab/Tab.js +2 -2
- package/web-dist/components/tab-list/TabList.js +2 -2
- package/web-dist/components/tab-panel/TabPanel.js +2 -2
- package/web-dist/components/tabletop-table/TabletopTable.js +2 -2
- package/web-dist/components/tabs/Tabs.js +2 -2
- package/web-dist/components/text/Text.js +2 -2
- package/web-dist/components/text-input/TextInput.js +4 -4
- package/web-dist/components/tile-grid/TileGrid.js +1 -1
- package/web-dist/components/time-grid/TimeGrid.js +2 -2
- package/web-dist/components/time-picker/TimePicker.js +5 -5
- package/web-dist/components/timeline/Timeline.js +4 -4
- package/web-dist/components/toast/Toast.js +2 -2
- package/web-dist/components/toast-provider/ToastProvider.js +2 -2
- package/web-dist/components/toolbar/Toolbar.js +9 -9
- package/web-dist/components/tree-item/TreeItem.js +9 -9
- package/web-dist/components/tree-view/TreeView.js +1 -1
- package/web-dist/components/uploader/Uploader.js +12 -12
- package/web-dist/components/view/View.js +2 -2
- package/web-dist/manifest.json +787 -772
- 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 {
|
|
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
|
|
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
|
|
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 =
|
|
71
|
+
if (interactionPolicy === 'passive' || readOnly) return;
|
|
72
|
+
const intent = isRangeSelectionEvent(event)
|
|
73
|
+
? 'range'
|
|
74
|
+
: resolveSelectionEventIntent(event, 'pointer');
|
|
67
75
|
onSelectionChange?.(
|
|
68
|
-
resolveMatrixGridSelection(
|
|
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
|
-
[
|
|
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={
|
|
135
|
+
items={gridItems}
|
|
136
|
+
focusedItemId={keyboard.focusedId ?? undefined}
|
|
81
137
|
overscanPixels={0}
|
|
138
|
+
onVisibleItemIdsChange={setVisibleIds}
|
|
82
139
|
renderItem={(item) => {
|
|
83
|
-
const cell =
|
|
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
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
+
}
|