@symbiote-native/navigation 1.0.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.
- package/LICENSE +21 -0
- package/build/angular/drawer/index.d.ts +76 -0
- package/build/angular/drawer/index.js +508 -0
- package/build/angular/drawer-screen.directive.d.ts +14 -0
- package/build/angular/drawer-screen.directive.js +87 -0
- package/build/angular/index.d.ts +23 -0
- package/build/angular/index.js +22 -0
- package/build/angular/injectors/index.d.ts +13 -0
- package/build/angular/injectors/index.js +8 -0
- package/build/angular/injectors/inject-drawer-navigation.d.ts +4 -0
- package/build/angular/injectors/inject-drawer-navigation.js +12 -0
- package/build/angular/injectors/inject-focus-effect.d.ts +2 -0
- package/build/angular/injectors/inject-focus-effect.js +29 -0
- package/build/angular/injectors/inject-is-focused.d.ts +2 -0
- package/build/angular/injectors/inject-is-focused.js +20 -0
- package/build/angular/injectors/inject-navigation-state.d.ts +3 -0
- package/build/angular/injectors/inject-navigation-state.js +35 -0
- package/build/angular/injectors/inject-navigation.d.ts +7 -0
- package/build/angular/injectors/inject-navigation.js +16 -0
- package/build/angular/injectors/inject-route.d.ts +3 -0
- package/build/angular/injectors/inject-route.js +20 -0
- package/build/angular/injectors/inject-stack-navigation.d.ts +4 -0
- package/build/angular/injectors/inject-stack-navigation.js +15 -0
- package/build/angular/injectors/inject-tab-navigation.d.ts +4 -0
- package/build/angular/injectors/inject-tab-navigation.js +11 -0
- package/build/angular/linking.d.ts +3 -0
- package/build/angular/linking.js +43 -0
- package/build/angular/navigation-context.service.d.ts +13 -0
- package/build/angular/navigation-context.service.js +105 -0
- package/build/angular/navigation-scope.directive.d.ts +10 -0
- package/build/angular/navigation-scope.directive.js +90 -0
- package/build/angular/screen.directive.d.ts +22 -0
- package/build/angular/screen.directive.js +90 -0
- package/build/angular/search-bar-ref.directive.d.ts +10 -0
- package/build/angular/search-bar-ref.directive.js +88 -0
- package/build/angular/stack/index.d.ts +55 -0
- package/build/angular/stack/index.js +386 -0
- package/build/angular/tab-screen.directive.d.ts +14 -0
- package/build/angular/tab-screen.directive.js +87 -0
- package/build/angular/tabs/index.d.ts +42 -0
- package/build/angular/tabs/index.js +263 -0
- package/build/core/constants.d.ts +27 -0
- package/build/core/constants.js +61 -0
- package/build/core/drawer-options/index.d.ts +64 -0
- package/build/core/drawer-options/index.js +200 -0
- package/build/core/drawer-router-state/index.d.ts +21 -0
- package/build/core/drawer-router-state/index.js +40 -0
- package/build/core/guards.d.ts +2 -0
- package/build/core/guards.js +22 -0
- package/build/core/header-bar-buttons.d.ts +9 -0
- package/build/core/header-bar-buttons.js +163 -0
- package/build/core/index.d.ts +29 -0
- package/build/core/index.js +32 -0
- package/build/core/linking-config/index.d.ts +13 -0
- package/build/core/linking-config/index.js +171 -0
- package/build/core/navigation-events/index.d.ts +16 -0
- package/build/core/navigation-events/index.js +57 -0
- package/build/core/navigator-handles/index.d.ts +30 -0
- package/build/core/navigator-handles/index.js +15 -0
- package/build/core/navigator-props.d.ts +216 -0
- package/build/core/navigator-props.js +7 -0
- package/build/core/navigator-state/index.d.ts +36 -0
- package/build/core/navigator-state/index.js +81 -0
- package/build/core/render-drawer.d.ts +13 -0
- package/build/core/render-drawer.js +88 -0
- package/build/core/render-stack.d.ts +35 -0
- package/build/core/render-stack.js +294 -0
- package/build/core/render-tabs.d.ts +20 -0
- package/build/core/render-tabs.js +97 -0
- package/build/core/screen-options.d.ts +4 -0
- package/build/core/screen-options.js +133 -0
- package/build/core/search-bar-commands.d.ts +10 -0
- package/build/core/search-bar-commands.js +42 -0
- package/build/core/state-persistence/index.d.ts +3 -0
- package/build/core/state-persistence/index.js +23 -0
- package/build/core/tab-options.d.ts +13 -0
- package/build/core/tab-options.js +10 -0
- package/build/core/tab-router-state/index.d.ts +19 -0
- package/build/core/tab-router-state/index.js +51 -0
- package/build/react/collect-registry.d.ts +4 -0
- package/build/react/collect-registry.js +15 -0
- package/build/react/drawer/index.d.ts +29 -0
- package/build/react/drawer/index.js +277 -0
- package/build/react/drawer-screen.d.ts +14 -0
- package/build/react/drawer-screen.js +8 -0
- package/build/react/hooks/index.d.ts +12 -0
- package/build/react/hooks/index.js +8 -0
- package/build/react/hooks/use-drawer-navigation.d.ts +4 -0
- package/build/react/hooks/use-drawer-navigation.js +12 -0
- package/build/react/hooks/use-focus-effect.d.ts +2 -0
- package/build/react/hooks/use-focus-effect.js +29 -0
- package/build/react/hooks/use-is-focused.d.ts +1 -0
- package/build/react/hooks/use-is-focused.js +22 -0
- package/build/react/hooks/use-navigation-state/index.d.ts +2 -0
- package/build/react/hooks/use-navigation-state/index.js +39 -0
- package/build/react/hooks/use-navigation.d.ts +7 -0
- package/build/react/hooks/use-navigation.js +16 -0
- package/build/react/hooks/use-route.d.ts +2 -0
- package/build/react/hooks/use-route.js +8 -0
- package/build/react/hooks/use-stack-navigation.d.ts +4 -0
- package/build/react/hooks/use-stack-navigation.js +14 -0
- package/build/react/hooks/use-tab-navigation.d.ts +4 -0
- package/build/react/hooks/use-tab-navigation.js +12 -0
- package/build/react/index.d.ts +24 -0
- package/build/react/index.js +21 -0
- package/build/react/linking.d.ts +3 -0
- package/build/react/linking.js +47 -0
- package/build/react/navigation-context.d.ts +10 -0
- package/build/react/navigation-context.js +22 -0
- package/build/react/screen.d.ts +22 -0
- package/build/react/screen.js +7 -0
- package/build/react/stack/index.d.ts +13 -0
- package/build/react/stack/index.js +202 -0
- package/build/react/tab-screen.d.ts +15 -0
- package/build/react/tab-screen.js +8 -0
- package/build/react/tabs/index.d.ts +12 -0
- package/build/react/tabs/index.js +116 -0
- package/build/register.d.ts +7 -0
- package/build/register.js +58 -0
- package/build/vue/composables/index.d.ts +12 -0
- package/build/vue/composables/index.js +8 -0
- package/build/vue/composables/use-drawer-navigation.d.ts +5 -0
- package/build/vue/composables/use-drawer-navigation.js +15 -0
- package/build/vue/composables/use-focus-effect.d.ts +1 -0
- package/build/vue/composables/use-focus-effect.js +31 -0
- package/build/vue/composables/use-is-focused.d.ts +2 -0
- package/build/vue/composables/use-is-focused.js +28 -0
- package/build/vue/composables/use-navigation-state.d.ts +3 -0
- package/build/vue/composables/use-navigation-state.js +29 -0
- package/build/vue/composables/use-navigation.d.ts +8 -0
- package/build/vue/composables/use-navigation.js +13 -0
- package/build/vue/composables/use-route.d.ts +3 -0
- package/build/vue/composables/use-route.js +9 -0
- package/build/vue/composables/use-stack-navigation.d.ts +5 -0
- package/build/vue/composables/use-stack-navigation.js +18 -0
- package/build/vue/composables/use-tab-navigation.d.ts +5 -0
- package/build/vue/composables/use-tab-navigation.js +15 -0
- package/build/vue/drawer/index.d.ts +19 -0
- package/build/vue/drawer/index.js +390 -0
- package/build/vue/drawer-screen.d.ts +14 -0
- package/build/vue/drawer-screen.js +12 -0
- package/build/vue/index.d.ts +21 -0
- package/build/vue/index.js +19 -0
- package/build/vue/linking.d.ts +3 -0
- package/build/vue/linking.js +57 -0
- package/build/vue/navigation-context.d.ts +16 -0
- package/build/vue/navigation-context.js +65 -0
- package/build/vue/screen.d.ts +21 -0
- package/build/vue/screen.js +12 -0
- package/build/vue/stack/index.d.ts +9 -0
- package/build/vue/stack/index.js +279 -0
- package/build/vue/tab-screen.d.ts +14 -0
- package/build/vue/tab-screen.js +11 -0
- package/build/vue/tabs/index.d.ts +10 -0
- package/build/vue/tabs/index.js +209 -0
- package/build-ngc/angular/drawer/index.d.ts +79 -0
- package/build-ngc/angular/drawer/index.js +479 -0
- package/build-ngc/angular/drawer/index.js.map +1 -0
- package/build-ngc/angular/drawer-screen.directive.d.ts +17 -0
- package/build-ngc/angular/drawer-screen.directive.js +32 -0
- package/build-ngc/angular/drawer-screen.directive.js.map +1 -0
- package/build-ngc/angular/drawer.d.ts +79 -0
- package/build-ngc/angular/drawer.js +475 -0
- package/build-ngc/angular/drawer.js.map +1 -0
- package/build-ngc/angular/index.d.ts +23 -0
- package/build-ngc/angular/index.js +23 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/injectors/index.d.ts +13 -0
- package/build-ngc/angular/injectors/index.js +9 -0
- package/build-ngc/angular/injectors/index.js.map +1 -0
- package/build-ngc/angular/injectors/inject-drawer-navigation.d.ts +4 -0
- package/build-ngc/angular/injectors/inject-drawer-navigation.js +13 -0
- package/build-ngc/angular/injectors/inject-drawer-navigation.js.map +1 -0
- package/build-ngc/angular/injectors/inject-focus-effect.d.ts +2 -0
- package/build-ngc/angular/injectors/inject-focus-effect.js +30 -0
- package/build-ngc/angular/injectors/inject-focus-effect.js.map +1 -0
- package/build-ngc/angular/injectors/inject-is-focused.d.ts +2 -0
- package/build-ngc/angular/injectors/inject-is-focused.js +22 -0
- package/build-ngc/angular/injectors/inject-is-focused.js.map +1 -0
- package/build-ngc/angular/injectors/inject-navigation-state.d.ts +3 -0
- package/build-ngc/angular/injectors/inject-navigation-state.js +37 -0
- package/build-ngc/angular/injectors/inject-navigation-state.js.map +1 -0
- package/build-ngc/angular/injectors/inject-navigation.d.ts +7 -0
- package/build-ngc/angular/injectors/inject-navigation.js +17 -0
- package/build-ngc/angular/injectors/inject-navigation.js.map +1 -0
- package/build-ngc/angular/injectors/inject-route.d.ts +3 -0
- package/build-ngc/angular/injectors/inject-route.js +21 -0
- package/build-ngc/angular/injectors/inject-route.js.map +1 -0
- package/build-ngc/angular/injectors/inject-stack-navigation.d.ts +4 -0
- package/build-ngc/angular/injectors/inject-stack-navigation.js +16 -0
- package/build-ngc/angular/injectors/inject-stack-navigation.js.map +1 -0
- package/build-ngc/angular/injectors/inject-tab-navigation.d.ts +4 -0
- package/build-ngc/angular/injectors/inject-tab-navigation.js +12 -0
- package/build-ngc/angular/injectors/inject-tab-navigation.js.map +1 -0
- package/build-ngc/angular/linking.d.ts +3 -0
- package/build-ngc/angular/linking.js +44 -0
- package/build-ngc/angular/linking.js.map +1 -0
- package/build-ngc/angular/navigation-context.service.d.ts +16 -0
- package/build-ngc/angular/navigation-context.service.js +63 -0
- package/build-ngc/angular/navigation-context.service.js.map +1 -0
- package/build-ngc/angular/navigation-scope.directive.d.ts +13 -0
- package/build-ngc/angular/navigation-scope.directive.js +42 -0
- package/build-ngc/angular/navigation-scope.directive.js.map +1 -0
- package/build-ngc/angular/screen.directive.d.ts +25 -0
- package/build-ngc/angular/screen.directive.js +35 -0
- package/build-ngc/angular/screen.directive.js.map +1 -0
- package/build-ngc/angular/search-bar-ref.directive.d.ts +13 -0
- package/build-ngc/angular/search-bar-ref.directive.js +44 -0
- package/build-ngc/angular/search-bar-ref.directive.js.map +1 -0
- package/build-ngc/angular/stack/index.d.ts +58 -0
- package/build-ngc/angular/stack/index.js +406 -0
- package/build-ngc/angular/stack/index.js.map +1 -0
- package/build-ngc/angular/stack.d.ts +58 -0
- package/build-ngc/angular/stack.js +402 -0
- package/build-ngc/angular/stack.js.map +1 -0
- package/build-ngc/angular/tab-screen.directive.d.ts +17 -0
- package/build-ngc/angular/tab-screen.directive.js +32 -0
- package/build-ngc/angular/tab-screen.directive.js.map +1 -0
- package/build-ngc/angular/tabs/index.d.ts +45 -0
- package/build-ngc/angular/tabs/index.js +231 -0
- package/build-ngc/angular/tabs/index.js.map +1 -0
- package/build-ngc/angular/tabs.d.ts +45 -0
- package/build-ngc/angular/tabs.js +231 -0
- package/build-ngc/angular/tabs.js.map +1 -0
- package/build-ngc/core/constants.d.ts +27 -0
- package/build-ngc/core/constants.js +62 -0
- package/build-ngc/core/constants.js.map +1 -0
- package/build-ngc/core/drawer-options/index.d.ts +64 -0
- package/build-ngc/core/drawer-options/index.js +201 -0
- package/build-ngc/core/drawer-options/index.js.map +1 -0
- package/build-ngc/core/drawer-options.d.ts +64 -0
- package/build-ngc/core/drawer-options.js +201 -0
- package/build-ngc/core/drawer-options.js.map +1 -0
- package/build-ngc/core/drawer-router-state/index.d.ts +21 -0
- package/build-ngc/core/drawer-router-state/index.js +41 -0
- package/build-ngc/core/drawer-router-state/index.js.map +1 -0
- package/build-ngc/core/drawer-router-state.d.ts +21 -0
- package/build-ngc/core/drawer-router-state.js +41 -0
- package/build-ngc/core/drawer-router-state.js.map +1 -0
- package/build-ngc/core/guards.d.ts +2 -0
- package/build-ngc/core/guards.js +23 -0
- package/build-ngc/core/guards.js.map +1 -0
- package/build-ngc/core/header-bar-buttons.d.ts +9 -0
- package/build-ngc/core/header-bar-buttons.js +164 -0
- package/build-ngc/core/header-bar-buttons.js.map +1 -0
- package/build-ngc/core/href.d.ts +16 -0
- package/build-ngc/core/href.js +55 -0
- package/build-ngc/core/href.js.map +1 -0
- package/build-ngc/core/index.d.ts +29 -0
- package/build-ngc/core/index.js +33 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/linking-config/index.d.ts +13 -0
- package/build-ngc/core/linking-config/index.js +172 -0
- package/build-ngc/core/linking-config/index.js.map +1 -0
- package/build-ngc/core/linking-config.d.ts +13 -0
- package/build-ngc/core/linking-config.js +172 -0
- package/build-ngc/core/linking-config.js.map +1 -0
- package/build-ngc/core/navigation-events/index.d.ts +16 -0
- package/build-ngc/core/navigation-events/index.js +58 -0
- package/build-ngc/core/navigation-events/index.js.map +1 -0
- package/build-ngc/core/navigation-events.d.ts +16 -0
- package/build-ngc/core/navigation-events.js +58 -0
- package/build-ngc/core/navigation-events.js.map +1 -0
- package/build-ngc/core/navigator-handles/index.d.ts +30 -0
- package/build-ngc/core/navigator-handles/index.js +16 -0
- package/build-ngc/core/navigator-handles/index.js.map +1 -0
- package/build-ngc/core/navigator-handles.d.ts +30 -0
- package/build-ngc/core/navigator-handles.js +16 -0
- package/build-ngc/core/navigator-handles.js.map +1 -0
- package/build-ngc/core/navigator-props.d.ts +216 -0
- package/build-ngc/core/navigator-props.js +8 -0
- package/build-ngc/core/navigator-props.js.map +1 -0
- package/build-ngc/core/navigator-state/index.d.ts +36 -0
- package/build-ngc/core/navigator-state/index.js +82 -0
- package/build-ngc/core/navigator-state/index.js.map +1 -0
- package/build-ngc/core/navigator-state.d.ts +36 -0
- package/build-ngc/core/navigator-state.js +82 -0
- package/build-ngc/core/navigator-state.js.map +1 -0
- package/build-ngc/core/render-drawer.d.ts +13 -0
- package/build-ngc/core/render-drawer.js +89 -0
- package/build-ngc/core/render-drawer.js.map +1 -0
- package/build-ngc/core/render-stack.d.ts +35 -0
- package/build-ngc/core/render-stack.js +295 -0
- package/build-ngc/core/render-stack.js.map +1 -0
- package/build-ngc/core/render-tabs.d.ts +20 -0
- package/build-ngc/core/render-tabs.js +98 -0
- package/build-ngc/core/render-tabs.js.map +1 -0
- package/build-ngc/core/screen-options.d.ts +4 -0
- package/build-ngc/core/screen-options.js +134 -0
- package/build-ngc/core/screen-options.js.map +1 -0
- package/build-ngc/core/search-bar-commands.d.ts +10 -0
- package/build-ngc/core/search-bar-commands.js +43 -0
- package/build-ngc/core/search-bar-commands.js.map +1 -0
- package/build-ngc/core/search-params.d.ts +9 -0
- package/build-ngc/core/search-params.js +60 -0
- package/build-ngc/core/search-params.js.map +1 -0
- package/build-ngc/core/state-persistence/index.d.ts +3 -0
- package/build-ngc/core/state-persistence/index.js +24 -0
- package/build-ngc/core/state-persistence/index.js.map +1 -0
- package/build-ngc/core/state-persistence.d.ts +3 -0
- package/build-ngc/core/state-persistence.js +24 -0
- package/build-ngc/core/state-persistence.js.map +1 -0
- package/build-ngc/core/tab-options.d.ts +13 -0
- package/build-ngc/core/tab-options.js +11 -0
- package/build-ngc/core/tab-options.js.map +1 -0
- package/build-ngc/core/tab-router-state/index.d.ts +19 -0
- package/build-ngc/core/tab-router-state/index.js +52 -0
- package/build-ngc/core/tab-router-state/index.js.map +1 -0
- package/build-ngc/core/tab-router-state.d.ts +19 -0
- package/build-ngc/core/tab-router-state.js +52 -0
- package/build-ngc/core/tab-router-state.js.map +1 -0
- package/build-ngc/register.d.ts +7 -0
- package/build-ngc/register.js +59 -0
- package/build-ngc/register.js.map +1 -0
- package/codegen-specs/FullWindowOverlayNativeComponent.ts +13 -0
- package/codegen-specs/ModalScreenNativeComponent.ts +129 -0
- package/codegen-specs/NativeScreensModule.ts +8 -0
- package/codegen-specs/NativeScreensModule.web.ts +1 -0
- package/codegen-specs/ScreenContainerNativeComponent.ts +8 -0
- package/codegen-specs/ScreenContentWrapperNativeComponent.ts +9 -0
- package/codegen-specs/ScreenFooterNativeComponent.ts +6 -0
- package/codegen-specs/ScreenNativeComponent.ts +133 -0
- package/codegen-specs/ScreenNavigationContainerNativeComponent.ts +11 -0
- package/codegen-specs/ScreenStackHeaderConfigNativeComponent.ts +99 -0
- package/codegen-specs/ScreenStackHeaderSubviewNativeComponent.ts +25 -0
- package/codegen-specs/ScreenStackNativeComponent.ts +17 -0
- package/codegen-specs/SearchBarNativeComponent.ts +101 -0
- package/codegen-specs/codegenUtils.ts +6 -0
- package/codegen-specs/gamma/ScrollViewMarkerNativeComponent.ts +15 -0
- package/codegen-specs/gamma/modals/form-sheet/FormSheetContentWrapperNativeComponent.ts +10 -0
- package/codegen-specs/gamma/modals/form-sheet/FormSheetHostNativeComponent.ts +47 -0
- package/codegen-specs/gamma/split/SplitHostNativeComponent.ts +124 -0
- package/codegen-specs/gamma/split/SplitScreenNativeComponent.ts +24 -0
- package/codegen-specs/gamma/stack/StackHeaderConfigAndroidNativeComponent.ts +144 -0
- package/codegen-specs/gamma/stack/StackHeaderConfigIOSNativeComponent.ts +34 -0
- package/codegen-specs/gamma/stack/StackHeaderItemIOSNativeComponent.ts +47 -0
- package/codegen-specs/gamma/stack/StackHeaderItemSpacerIOSNativeComponent.ts +20 -0
- package/codegen-specs/gamma/stack/StackHeaderSubviewAndroidNativeComponent.ts +27 -0
- package/codegen-specs/gamma/stack/StackHostNativeComponent.ts +8 -0
- package/codegen-specs/gamma/stack/StackScreenNativeComponent.ts +42 -0
- package/codegen-specs/safe-area/SafeAreaViewNativeComponent.ts +24 -0
- package/codegen-specs/tabs/TabsBottomAccessoryContentNativeComponent.ts +15 -0
- package/codegen-specs/tabs/TabsBottomAccessoryNativeComponent.ts +19 -0
- package/codegen-specs/tabs/TabsHostAndroidNativeComponent.ts +69 -0
- package/codegen-specs/tabs/TabsHostIOSNativeComponent.ts +92 -0
- package/codegen-specs/tabs/TabsScreenAndroidNativeComponent.ts +112 -0
- package/codegen-specs/tabs/TabsScreenIOSNativeComponent.ts +171 -0
- package/package.json +201 -0
- package/react-native.config.cjs +58 -0
- package/src/angular/drawer/drawer.test.ts +299 -0
- package/src/angular/drawer/index.ts +546 -0
- package/src/angular/drawer-screen.directive.ts +28 -0
- package/src/angular/index.ts +63 -0
- package/src/angular/injectors/index.ts +13 -0
- package/src/angular/injectors/inject-drawer-navigation.ts +21 -0
- package/src/angular/injectors/inject-focus-effect.ts +37 -0
- package/src/angular/injectors/inject-is-focused.ts +28 -0
- package/src/angular/injectors/inject-navigation-state.ts +46 -0
- package/src/angular/injectors/inject-navigation.ts +26 -0
- package/src/angular/injectors/inject-route.ts +25 -0
- package/src/angular/injectors/inject-stack-navigation.ts +24 -0
- package/src/angular/injectors/inject-tab-navigation.ts +20 -0
- package/src/angular/injectors/injectors.test.ts +247 -0
- package/src/angular/injectors/use-typed-navigation.test.ts +229 -0
- package/src/angular/linking.ts +55 -0
- package/src/angular/navigation-context.service.ts +68 -0
- package/src/angular/navigation-scope.directive.ts +31 -0
- package/src/angular/nested-navigation.test.ts +189 -0
- package/src/angular/screen.directive.ts +48 -0
- package/src/angular/search-bar-ref.directive.ts +38 -0
- package/src/angular/stack/index.ts +401 -0
- package/src/angular/stack/stack.test.ts +419 -0
- package/src/angular/tab-screen.directive.ts +26 -0
- package/src/angular/tabs/index.ts +254 -0
- package/src/angular/tabs/tabs.test.ts +249 -0
- package/src/core/constants.ts +68 -0
- package/src/core/drawer-options/drawer-options.test.ts +415 -0
- package/src/core/drawer-options/index.ts +331 -0
- package/src/core/drawer-router-state/drawer-router-state.test.ts +116 -0
- package/src/core/drawer-router-state/index.ts +71 -0
- package/src/core/guards.ts +23 -0
- package/src/core/header-bar-buttons.ts +212 -0
- package/src/core/index.ts +170 -0
- package/src/core/linking-config/index.ts +209 -0
- package/src/core/linking-config/linking-config.test.ts +109 -0
- package/src/core/navigation-events/index.ts +94 -0
- package/src/core/navigation-events/navigation-events.test.ts +56 -0
- package/src/core/navigator-handles/index.ts +63 -0
- package/src/core/navigator-handles/navigator-handles.test.ts +46 -0
- package/src/core/navigator-props.ts +323 -0
- package/src/core/navigator-state/index.ts +112 -0
- package/src/core/navigator-state/navigator-state.test.ts +80 -0
- package/src/core/render-drawer.ts +123 -0
- package/src/core/render-stack.ts +414 -0
- package/src/core/render-tabs.ts +138 -0
- package/src/core/screen-options.ts +181 -0
- package/src/core/search-bar-commands.ts +57 -0
- package/src/core/state-persistence/index.ts +30 -0
- package/src/core/state-persistence/state-persistence.test.ts +42 -0
- package/src/core/tab-options.ts +25 -0
- package/src/core/tab-router-state/index.ts +75 -0
- package/src/core/tab-router-state/tab-router-state.test.ts +34 -0
- package/src/globals.d.ts +4 -0
- package/src/react/collect-registry.ts +19 -0
- package/src/react/drawer/drawer.test.tsx +472 -0
- package/src/react/drawer/index.ts +451 -0
- package/src/react/drawer-screen.ts +29 -0
- package/src/react/hooks/hooks.test.tsx +199 -0
- package/src/react/hooks/index.ts +12 -0
- package/src/react/hooks/use-drawer-navigation.ts +21 -0
- package/src/react/hooks/use-focus-effect.ts +36 -0
- package/src/react/hooks/use-is-focused.ts +26 -0
- package/src/react/hooks/use-navigation-state/index.ts +47 -0
- package/src/react/hooks/use-navigation-state/use-navigation-state.test.ts +85 -0
- package/src/react/hooks/use-navigation.ts +36 -0
- package/src/react/hooks/use-route.ts +11 -0
- package/src/react/hooks/use-stack-navigation.ts +23 -0
- package/src/react/hooks/use-tab-navigation.ts +21 -0
- package/src/react/hooks/use-typed-navigation.test.tsx +187 -0
- package/src/react/index.ts +52 -0
- package/src/react/linking.ts +59 -0
- package/src/react/navigation-context.ts +40 -0
- package/src/react/nested-navigation.test.tsx +145 -0
- package/src/react/screen.ts +44 -0
- package/src/react/stack/index.ts +323 -0
- package/src/react/stack/stack.test.tsx +514 -0
- package/src/react/tab-screen.ts +30 -0
- package/src/react/tabs/index.ts +204 -0
- package/src/react/tabs/tabs.test.tsx +256 -0
- package/src/register.ts +59 -0
- package/src/vue/composables/composables.test.ts +221 -0
- package/src/vue/composables/index.ts +12 -0
- package/src/vue/composables/use-drawer-navigation.ts +25 -0
- package/src/vue/composables/use-focus-effect.ts +37 -0
- package/src/vue/composables/use-is-focused.ts +34 -0
- package/src/vue/composables/use-navigation-state.ts +38 -0
- package/src/vue/composables/use-navigation.ts +30 -0
- package/src/vue/composables/use-route.ts +13 -0
- package/src/vue/composables/use-stack-navigation.ts +28 -0
- package/src/vue/composables/use-tab-navigation.ts +25 -0
- package/src/vue/composables/use-typed-navigation.test.ts +205 -0
- package/src/vue/drawer/drawer.test.ts +451 -0
- package/src/vue/drawer/index.ts +550 -0
- package/src/vue/drawer-screen.ts +32 -0
- package/src/vue/index.ts +70 -0
- package/src/vue/linking.ts +69 -0
- package/src/vue/navigation-context.ts +87 -0
- package/src/vue/nested-navigation.test.ts +155 -0
- package/src/vue/screen.ts +52 -0
- package/src/vue/stack/index.ts +376 -0
- package/src/vue/stack/stack.test.ts +501 -0
- package/src/vue/tab-screen.ts +29 -0
- package/src/vue/tabs/index.ts +302 -0
- package/src/vue/tabs/tabs.test.ts +278 -0
- package/symbiote-navigation.podspec +84 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Tab, the Vue lifecycle half. The focused-index router (tab-router-state) and the tab-bar
|
|
2
|
+
// Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim with
|
|
3
|
+
// the React/Angular adapters; here Vue supplies the lifecycle - a plain ref for the focused index
|
|
4
|
+
// (Vue's twin of useReducer), useId for route-key generation, expose() for the jumpTo/setParams
|
|
5
|
+
// handle - plus the descriptor bridge for the tab-bar leaf, exactly like Stack bridges its header
|
|
6
|
+
// config (stack.ts). Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary
|
|
7
|
+
// `symbiote-view`/`symbiote-text` primitives via the shared render fn, so there is no
|
|
8
|
+
// react-native-screens ViewConfig to register here - Tab needs no `../register` import.
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
defineComponent,
|
|
12
|
+
h,
|
|
13
|
+
nextTick,
|
|
14
|
+
onMounted,
|
|
15
|
+
onUnmounted,
|
|
16
|
+
shallowRef,
|
|
17
|
+
useId,
|
|
18
|
+
watch,
|
|
19
|
+
} from '@vue/runtime-core';
|
|
20
|
+
import type { VNode } from '@vue/runtime-core';
|
|
21
|
+
import { descriptorToVue, normalizeVueAttrs } from '@symbiote-native/vue';
|
|
22
|
+
import { dlog } from '@symbiote-native/engine';
|
|
23
|
+
import {
|
|
24
|
+
NAVIGATION_EVENT_BLUR,
|
|
25
|
+
NAVIGATION_EVENT_FOCUS,
|
|
26
|
+
createInitialTabState,
|
|
27
|
+
createNavigationEmitter,
|
|
28
|
+
diffFocusedRoute,
|
|
29
|
+
isFocusedRoute,
|
|
30
|
+
isRecord,
|
|
31
|
+
renderTabBar,
|
|
32
|
+
tabRouterReducer,
|
|
33
|
+
} from '../../core';
|
|
34
|
+
import type {
|
|
35
|
+
INavigationEmitter,
|
|
36
|
+
IRoute,
|
|
37
|
+
ITabBarItemView,
|
|
38
|
+
ITabNavigatorHandle,
|
|
39
|
+
ITabOptions,
|
|
40
|
+
} from '../../core';
|
|
41
|
+
import { NavigationScope, injectNavigationScope } from '../navigation-context';
|
|
42
|
+
import { TabScreen } from '../tab-screen';
|
|
43
|
+
import type { ITabScreenOptionsArgs, ITabScreenProps } from '../tab-screen';
|
|
44
|
+
|
|
45
|
+
export type { ITabNavigatorHandle } from '../../core';
|
|
46
|
+
|
|
47
|
+
// React's `children?: ReactNode` becomes Vue's default slot instead (registered screens, read via
|
|
48
|
+
// collectRegistry below).
|
|
49
|
+
export type ITabProps = {
|
|
50
|
+
initialRouteName?: string;
|
|
51
|
+
screenOptions?: ITabOptions;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type ITabRegistryEntry = {
|
|
55
|
+
component: ITabScreenProps['component'];
|
|
56
|
+
options: ITabScreenProps['options'];
|
|
57
|
+
initialParams: unknown;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function asString(value: unknown): string | undefined {
|
|
61
|
+
return typeof value === 'string' ? value : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function asComponent(value: unknown): ITabScreenProps['component'] | undefined {
|
|
65
|
+
if (typeof value === 'function') return value as ITabScreenProps['component'];
|
|
66
|
+
return isRecord(value) ? (value as ITabScreenProps['component']) : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ITabOptions carries a handful of independently-optional fields forwarded wholesale into the
|
|
70
|
+
// shared render fn below - same "narrow the object-ness, cast at the exact I/O edge" rationale
|
|
71
|
+
// stack.ts's asScreenOptions documents (CLAUDE.md's ts-js rule).
|
|
72
|
+
function asTabOptions(value: unknown): ITabOptions | undefined {
|
|
73
|
+
return isRecord(value) ? (value as ITabOptions) : undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function asTabOptionsOrResolver(value: unknown): ITabScreenProps['options'] {
|
|
77
|
+
if (typeof value === 'function') return value as ITabScreenProps['options'];
|
|
78
|
+
return asTabOptions(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function collectRegistry(vnodes: readonly VNode[]): Map<string, ITabRegistryEntry> {
|
|
82
|
+
const registry = new Map<string, ITabRegistryEntry>();
|
|
83
|
+
for (const vnode of vnodes) {
|
|
84
|
+
if (vnode.type !== TabScreen || !isRecord(vnode.props)) continue;
|
|
85
|
+
const name = asString(vnode.props.name);
|
|
86
|
+
const component = asComponent(vnode.props.component);
|
|
87
|
+
if (name === undefined || component === undefined) continue;
|
|
88
|
+
registry.set(name, {
|
|
89
|
+
component,
|
|
90
|
+
options: asTabOptionsOrResolver(vnode.props.options),
|
|
91
|
+
initialParams: vnode.props.initialParams,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return registry;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveTabOptions(
|
|
98
|
+
entry: ITabRegistryEntry,
|
|
99
|
+
screenComponentProps: ITabScreenOptionsArgs,
|
|
100
|
+
screenOptions: ITabOptions | undefined,
|
|
101
|
+
): ITabOptions {
|
|
102
|
+
const own =
|
|
103
|
+
typeof entry.options === 'function' ? entry.options(screenComponentProps) : entry.options;
|
|
104
|
+
return { ...screenOptions, ...own };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const TAB_CONTENT_STYLE = { flex: 1 };
|
|
108
|
+
const TAB_ROOT_STYLE = { flex: 1 };
|
|
109
|
+
|
|
110
|
+
const TabImpl = defineComponent<ITabProps>(
|
|
111
|
+
(_props, { attrs: rawAttrs, slots, expose }) => {
|
|
112
|
+
const attrs = normalizeVueAttrs(rawAttrs);
|
|
113
|
+
|
|
114
|
+
// Read BEFORE this Tab establishes its own per-screen NavigationScope below - becomes the
|
|
115
|
+
// `parent` link a nested screen's useNavigation().getParent() walks (e.g. this Tab rendered
|
|
116
|
+
// as a Stack screen's content reaches that Stack via this value). undefined when this Tab is
|
|
117
|
+
// the nesting root.
|
|
118
|
+
const ambientScopeRef = injectNavigationScope();
|
|
119
|
+
const routeIdPrefix = useId();
|
|
120
|
+
|
|
121
|
+
function buildRoutes(registry: Map<string, ITabRegistryEntry>): IRoute<unknown>[] {
|
|
122
|
+
return Array.from(registry.entries()).map(([name, entry]) => ({
|
|
123
|
+
key: `${routeIdPrefix}-${name}`,
|
|
124
|
+
name,
|
|
125
|
+
params: entry.initialParams,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const initialRegistry = collectRegistry(slots.default?.() ?? []);
|
|
130
|
+
const initialRoutes = buildRoutes(initialRegistry);
|
|
131
|
+
if (initialRoutes.length === 0) dlog('Tab: no <Tab.Screen> children registered');
|
|
132
|
+
|
|
133
|
+
const state = shallowRef(
|
|
134
|
+
createInitialTabState(initialRoutes, asString(attrs.initialRouteName)),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
function dispatch(action: Parameters<typeof tabRouterReducer>[1]): void {
|
|
138
|
+
state.value = tabRouterReducer(state.value, action);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const jumpTo = (name: string, params?: unknown): void =>
|
|
142
|
+
dispatch({ type: 'jumpTo', name, params });
|
|
143
|
+
const setParams = (params: unknown, key: string): void =>
|
|
144
|
+
dispatch({ type: 'setParams', key, params });
|
|
145
|
+
|
|
146
|
+
const handle: ITabNavigatorHandle = { jumpTo, setParams };
|
|
147
|
+
expose(handle);
|
|
148
|
+
|
|
149
|
+
// Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
|
|
150
|
+
// Stack's RNSScreen), so focus/blur is synthesized here: emit 'focus' once the newly-focused
|
|
151
|
+
// route's content has mounted, 'blur' when it's about to be replaced or Tab itself unmounts.
|
|
152
|
+
// Keyed on the route KEY (not the route object) so a setParams-only change (new route object,
|
|
153
|
+
// same key) doesn't spuriously re-fire focus/blur - mirrors tabs.ts's React twin exactly,
|
|
154
|
+
// just expressed as a `watch` over the focused key instead of a useEffect dependency array.
|
|
155
|
+
function focusedKeyOf(current: typeof state.value): string | undefined {
|
|
156
|
+
return current.routes[current.index]?.key;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// One emitter per route.key, created lazily and cached for the navigator's whole lifetime -
|
|
160
|
+
// mirrors stack.ts's own `emitters` map. This decouples emitter IDENTITY (stable, looked up
|
|
161
|
+
// by key, read by the render closure below when it builds a route's NavigationScope) from
|
|
162
|
+
// emit TIMING (must wait until the focused screen has actually mounted and subscribed): a
|
|
163
|
+
// scheme that instead tried to recreate/swap `routeEmitter` synchronously on each focus
|
|
164
|
+
// change raced Vue's own render/mount cycle no matter which watch `flush` timing it used -
|
|
165
|
+
// 'pre' emits before the new screen's onMounted subscribes, 'post' emits AFTER the render
|
|
166
|
+
// closure already needed the (still up-to-date) emitter to build that screen's NavigationScope
|
|
167
|
+
// in the FIRST place. A stable per-key lookup sidesteps the race entirely.
|
|
168
|
+
const emitters = new Map<string, INavigationEmitter>();
|
|
169
|
+
function emitterFor(routeKey: string): INavigationEmitter {
|
|
170
|
+
let emitter = emitters.get(routeKey);
|
|
171
|
+
if (emitter === undefined) {
|
|
172
|
+
emitter = createNavigationEmitter();
|
|
173
|
+
emitters.set(routeKey, emitter);
|
|
174
|
+
}
|
|
175
|
+
return emitter;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
|
|
179
|
+
// Stack's RNSScreen), so focus/blur is synthesized here: emit 'focus' once the newly-focused
|
|
180
|
+
// route's content has mounted, 'blur' when it's about to be replaced or Tab itself unmounts.
|
|
181
|
+
// Keyed on the route KEY (not the route object) so a setParams-only change (new route object,
|
|
182
|
+
// same key) doesn't spuriously re-fire focus/blur - mirrors tabs.ts's React twin exactly,
|
|
183
|
+
// just expressed as a `watch` over the focused key instead of a useEffect dependency array.
|
|
184
|
+
let focusedRouteKey = focusedKeyOf(state.value);
|
|
185
|
+
|
|
186
|
+
onMounted(() => {
|
|
187
|
+
if (focusedRouteKey !== undefined) {
|
|
188
|
+
dlog(`Tab: route "${focusedRouteKey}" focused at t=${Date.now()}`);
|
|
189
|
+
emitterFor(focusedRouteKey).emit(NAVIGATION_EVENT_FOCUS);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// The bookkeeping (which key is focused) updates immediately so the render closure below
|
|
194
|
+
// always reads the right route; the actual emit is deferred to nextTick(), which resolves
|
|
195
|
+
// only after the CURRENT flush cycle fully drains - including the newly-focused screen's own
|
|
196
|
+
// onMounted (subscribing its useIsFocused/useFocusEffect listeners). flush:'post' alone is
|
|
197
|
+
// NOT enough here: it only guarantees this callback runs after THIS component's own render
|
|
198
|
+
// effect, not after an arbitrary sibling post-flush job (a just-mounted child's onMounted)
|
|
199
|
+
// that may be queued after it in the same batch - nextTick is the one API that waits for the
|
|
200
|
+
// WHOLE cycle, not just this watcher's own slot in it.
|
|
201
|
+
watch(
|
|
202
|
+
() => focusedKeyOf(state.value),
|
|
203
|
+
nextKey => {
|
|
204
|
+
const { blurKey, focusKey } = diffFocusedRoute(focusedRouteKey, nextKey);
|
|
205
|
+
if (blurKey === undefined && focusKey === undefined) return;
|
|
206
|
+
focusedRouteKey = nextKey;
|
|
207
|
+
nextTick(() => {
|
|
208
|
+
if (blurKey !== undefined) {
|
|
209
|
+
dlog(`Tab: route "${blurKey}" blurred at t=${Date.now()}`);
|
|
210
|
+
emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
|
|
211
|
+
}
|
|
212
|
+
if (focusKey !== undefined) {
|
|
213
|
+
dlog(`Tab: route "${focusKey}" focused at t=${Date.now()}`);
|
|
214
|
+
emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
onUnmounted(() => {
|
|
220
|
+
if (focusedRouteKey !== undefined) emitterFor(focusedRouteKey).emit(NAVIGATION_EVENT_BLUR);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return () => {
|
|
224
|
+
const registry = collectRegistry(slots.default?.() ?? []);
|
|
225
|
+
const screenOptions = asTabOptions(attrs.screenOptions);
|
|
226
|
+
|
|
227
|
+
if (registry.size === 0) dlog('Tab: no <Tab.Screen> children registered');
|
|
228
|
+
|
|
229
|
+
const focusedRoute: IRoute<unknown> | undefined = state.value.routes[state.value.index];
|
|
230
|
+
|
|
231
|
+
const items: ITabBarItemView[] = state.value.routes.map((route, index) => {
|
|
232
|
+
const entry = registry.get(route.name);
|
|
233
|
+
const focused = isFocusedRoute(index, state.value.index);
|
|
234
|
+
if (entry === undefined) {
|
|
235
|
+
dlog(`Tab: no screen registered for route name "${route.name}"`);
|
|
236
|
+
return { key: route.key, focused, label: route.name, passthrough: {} };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const options = resolveTabOptions(entry, { route, navigation: handle }, screenOptions);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
key: route.key,
|
|
243
|
+
focused,
|
|
244
|
+
label: options.tabBarLabel ?? options.title ?? route.name,
|
|
245
|
+
icon: options.tabBarIcon,
|
|
246
|
+
badge: options.tabBarBadge,
|
|
247
|
+
activeTintColor: options.tabBarActiveTintColor,
|
|
248
|
+
inactiveTintColor: options.tabBarInactiveTintColor,
|
|
249
|
+
passthrough: {
|
|
250
|
+
onPress: () => jumpTo(route.name),
|
|
251
|
+
accessibilityRole: 'tab',
|
|
252
|
+
accessibilityState: { selected: focused },
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
|
|
258
|
+
const focusedOptions: ITabOptions | undefined =
|
|
259
|
+
focusedEntry && focusedRoute
|
|
260
|
+
? resolveTabOptions(
|
|
261
|
+
focusedEntry,
|
|
262
|
+
{ route: focusedRoute, navigation: handle },
|
|
263
|
+
screenOptions,
|
|
264
|
+
)
|
|
265
|
+
: screenOptions;
|
|
266
|
+
|
|
267
|
+
const tabBar = descriptorToVue(
|
|
268
|
+
renderTabBar({ items, style: focusedOptions?.tabBarStyle, passthrough: {} }),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Only the focused route's screen is ever mounted (unlike Stack, which keeps every pushed
|
|
272
|
+
// route alive), so a fresh NavigationScope per focus change is sufficient - the previous
|
|
273
|
+
// screen's whole subtree (and any listeners it registered) is torn down by an ordinary Vue
|
|
274
|
+
// unmount when focus moves on.
|
|
275
|
+
const content =
|
|
276
|
+
focusedEntry && focusedRoute
|
|
277
|
+
? h(
|
|
278
|
+
NavigationScope,
|
|
279
|
+
{
|
|
280
|
+
value: {
|
|
281
|
+
route: focusedRoute,
|
|
282
|
+
navigation: handle,
|
|
283
|
+
emitter: emitterFor(focusedRoute.key),
|
|
284
|
+
parent: ambientScopeRef?.value,
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
// No route/navigation props: the screen reads both through composables off the
|
|
288
|
+
// NavigationScope provided just above (useRoute / useTabNavigation).
|
|
289
|
+
() => h(focusedEntry.component),
|
|
290
|
+
)
|
|
291
|
+
: null;
|
|
292
|
+
|
|
293
|
+
return h('symbiote-view', { style: TAB_ROOT_STYLE }, [
|
|
294
|
+
h('symbiote-view', { style: TAB_CONTENT_STYLE }, content === null ? [] : [content]),
|
|
295
|
+
tabBar,
|
|
296
|
+
]);
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
{ name: 'Tab', inheritAttrs: false },
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
export const Tab = Object.assign(TabImpl, { Screen: TabScreen });
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// Co-located Vue-driven pipeline test, the Vue twin of react/tabs.test.tsx. Unlike Stack (which
|
|
2
|
+
// drives real native RNSScreen views and needs an injected codegen-shaped ViewConfig - see
|
|
3
|
+
// stack.test.ts), the tab bar is a PURE-JS UI painted from ordinary `symbiote-view`/`symbiote-text`
|
|
4
|
+
// primitives, so no ViewConfig source is needed here at all. Proves: only the focused route's
|
|
5
|
+
// screen mounts, jumpTo() moves focus, a tap (synthesized by the engine from a
|
|
6
|
+
// topTouchStart/topTouchEnd pair on the tab button) drives the same jumpTo, and per-tab options
|
|
7
|
+
// (label/badge/tint) reach the tab bar.
|
|
8
|
+
|
|
9
|
+
import { defineComponent, h, ref } from '@vue/runtime-core';
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
+
import { mount, unmount } from '@symbiote-native/vue';
|
|
12
|
+
import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
|
|
13
|
+
import { Tab } from './index';
|
|
14
|
+
import type { ITabNavigatorHandle } from './index';
|
|
15
|
+
import { useFocusEffect, useIsFocused, useNavigation, useRoute } from '../composables';
|
|
16
|
+
|
|
17
|
+
const ROOT_TAG = 4640;
|
|
18
|
+
const TOUCH_START = 'topTouchStart';
|
|
19
|
+
const TOUCH_END = 'topTouchEnd';
|
|
20
|
+
|
|
21
|
+
const fabric = installFabric();
|
|
22
|
+
const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
|
|
23
|
+
|
|
24
|
+
beforeEach(() => fabric.reset());
|
|
25
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
26
|
+
|
|
27
|
+
function findAllText(nodes: readonly IFakeNode[]): string[] {
|
|
28
|
+
const found: string[] = [];
|
|
29
|
+
const collect = (list: readonly IFakeNode[]): void => {
|
|
30
|
+
for (const node of list) {
|
|
31
|
+
if (node.viewName === 'RCTRawText' && typeof node.props.text === 'string')
|
|
32
|
+
found.push(node.props.text);
|
|
33
|
+
collect(node.children);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
collect(nodes);
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The tab bar row: the second child of Tab's root `symbiote-view` (content wrapper first, bar
|
|
41
|
+
// second - see tabs.ts's final h() call).
|
|
42
|
+
function tabBarRow(): IFakeNode {
|
|
43
|
+
const root = fabric.appRoot();
|
|
44
|
+
const tabRoot = root.children[0];
|
|
45
|
+
const bar = tabRoot?.children[1];
|
|
46
|
+
if (!bar) throw new Error('no tab bar row was committed');
|
|
47
|
+
return bar;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function tapItem(index: number): Promise<void> {
|
|
51
|
+
const item = tabBarRow().children[index];
|
|
52
|
+
if (!item) throw new Error(`no tab item at index ${index}`);
|
|
53
|
+
fabric.fireEvent(item.instanceHandle, TOUCH_START, {
|
|
54
|
+
touches: [{ identifier: 1, pageX: 0, pageY: 0 }],
|
|
55
|
+
changedTouches: [{ identifier: 1, pageX: 0, pageY: 0 }],
|
|
56
|
+
});
|
|
57
|
+
fabric.fireEvent(item.instanceHandle, TOUCH_END, {
|
|
58
|
+
touches: [],
|
|
59
|
+
changedTouches: [{ identifier: 1, pageX: 0, pageY: 0 }],
|
|
60
|
+
});
|
|
61
|
+
await tick();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function HomeScreen() {
|
|
65
|
+
return h('symbiote-text', {}, 'home');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ProfileScreen() {
|
|
69
|
+
return h('symbiote-text', {}, 'profile');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe('Vue Tab navigator', () => {
|
|
73
|
+
it('mounts only the initial focused route content, and the tab bar for every registered route', async () => {
|
|
74
|
+
mount(
|
|
75
|
+
ROOT_TAG,
|
|
76
|
+
defineComponent({
|
|
77
|
+
setup: () => () =>
|
|
78
|
+
h(Tab, { initialRouteName: 'Home' }, () => [
|
|
79
|
+
h(Tab.Screen, {
|
|
80
|
+
name: 'Home',
|
|
81
|
+
component: HomeScreen,
|
|
82
|
+
options: { tabBarLabel: 'Home' },
|
|
83
|
+
}),
|
|
84
|
+
h(Tab.Screen, {
|
|
85
|
+
name: 'Profile',
|
|
86
|
+
component: ProfileScreen,
|
|
87
|
+
options: { tabBarLabel: 'Profile' },
|
|
88
|
+
}),
|
|
89
|
+
]),
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
await tick();
|
|
93
|
+
expect(findAllText(fabric.committed)).toContain('home');
|
|
94
|
+
expect(findAllText(fabric.committed)).not.toContain('profile');
|
|
95
|
+
expect(tabBarRow().children).toHaveLength(2);
|
|
96
|
+
expect(findAllText(tabBarRow().children)).toEqual(['Home', 'Profile']);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('jumpTo() switches the focused screen content', async () => {
|
|
100
|
+
const handleRef = ref<ITabNavigatorHandle | null>(null);
|
|
101
|
+
mount(
|
|
102
|
+
ROOT_TAG,
|
|
103
|
+
defineComponent({
|
|
104
|
+
setup: () => () =>
|
|
105
|
+
h(Tab, { ref: handleRef, initialRouteName: 'Home' }, () => [
|
|
106
|
+
h(Tab.Screen, { name: 'Home', component: HomeScreen }),
|
|
107
|
+
h(Tab.Screen, { name: 'Profile', component: ProfileScreen }),
|
|
108
|
+
]),
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
await tick();
|
|
112
|
+
handleRef.value?.jumpTo('Profile');
|
|
113
|
+
await tick();
|
|
114
|
+
expect(findAllText(fabric.committed)).toContain('profile');
|
|
115
|
+
expect(findAllText(fabric.committed)).not.toContain('home');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('a tap on a tab bar item drives the same jumpTo as the imperative handle', async () => {
|
|
119
|
+
mount(
|
|
120
|
+
ROOT_TAG,
|
|
121
|
+
defineComponent({
|
|
122
|
+
setup: () => () =>
|
|
123
|
+
h(Tab, { initialRouteName: 'Home' }, () => [
|
|
124
|
+
h(Tab.Screen, { name: 'Home', component: HomeScreen }),
|
|
125
|
+
h(Tab.Screen, { name: 'Profile', component: ProfileScreen }),
|
|
126
|
+
]),
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
await tick();
|
|
130
|
+
expect(findAllText(fabric.committed)).toContain('home');
|
|
131
|
+
await tapItem(1);
|
|
132
|
+
expect(findAllText(fabric.committed)).toContain('profile');
|
|
133
|
+
expect(findAllText(fabric.committed)).not.toContain('home');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('jumpTo() to an unknown route name is a no-op', async () => {
|
|
137
|
+
const handleRef = ref<ITabNavigatorHandle | null>(null);
|
|
138
|
+
mount(
|
|
139
|
+
ROOT_TAG,
|
|
140
|
+
defineComponent({
|
|
141
|
+
setup: () => () =>
|
|
142
|
+
h(Tab, { ref: handleRef, initialRouteName: 'Home' }, () => [
|
|
143
|
+
h(Tab.Screen, { name: 'Home', component: HomeScreen }),
|
|
144
|
+
h(Tab.Screen, { name: 'Profile', component: ProfileScreen }),
|
|
145
|
+
]),
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
await tick();
|
|
149
|
+
handleRef.value?.jumpTo('Nowhere');
|
|
150
|
+
await tick();
|
|
151
|
+
expect(findAllText(fabric.committed)).toContain('home');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('exposes route.params to the focused screen via useRoute() after jumpTo', async () => {
|
|
155
|
+
let receivedParams: unknown;
|
|
156
|
+
const ParamsScreen = defineComponent(() => {
|
|
157
|
+
const route = useRoute();
|
|
158
|
+
return () => {
|
|
159
|
+
receivedParams = route.value.params;
|
|
160
|
+
return h('symbiote-text', {}, 'params');
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
const handleRef = ref<ITabNavigatorHandle | null>(null);
|
|
164
|
+
mount(
|
|
165
|
+
ROOT_TAG,
|
|
166
|
+
defineComponent({
|
|
167
|
+
setup: () => () =>
|
|
168
|
+
h(Tab, { ref: handleRef, initialRouteName: 'Home' }, () => [
|
|
169
|
+
h(Tab.Screen, { name: 'Home', component: HomeScreen }),
|
|
170
|
+
h(Tab.Screen, { name: 'Profile', component: ParamsScreen }),
|
|
171
|
+
]),
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
await tick();
|
|
175
|
+
handleRef.value?.jumpTo('Profile', { id: 42 });
|
|
176
|
+
await tick();
|
|
177
|
+
expect(receivedParams).toEqual({ id: 42 });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('resolves tabBarBadge onto the focused-agnostic tab item', async () => {
|
|
181
|
+
mount(
|
|
182
|
+
ROOT_TAG,
|
|
183
|
+
defineComponent({
|
|
184
|
+
setup: () => () =>
|
|
185
|
+
h(Tab, { initialRouteName: 'Home' }, () => [
|
|
186
|
+
h(Tab.Screen, { name: 'Home', component: HomeScreen, options: { tabBarBadge: 3 } }),
|
|
187
|
+
h(Tab.Screen, { name: 'Profile', component: ProfileScreen }),
|
|
188
|
+
]),
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
await tick();
|
|
192
|
+
expect(findAllText(tabBarRow().children)).toContain('3');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Before this fix, Tab never wrapped its focused screen in a NavigationScope at all, so every
|
|
196
|
+
// one of these composables threw "must be used within a screen rendered by <Stack>" the moment
|
|
197
|
+
// a Tab screen called them. These cases prove the scope is now provided and the focus semantics
|
|
198
|
+
// are wired.
|
|
199
|
+
it('useNavigation()/useRoute() are usable inside a Tab screen, and useIsFocused() reflects the focused tab', async () => {
|
|
200
|
+
let homeIsFocused: boolean | undefined;
|
|
201
|
+
let homeRouteName: string | undefined;
|
|
202
|
+
let profileIsFocused: boolean | undefined;
|
|
203
|
+
|
|
204
|
+
// Plain functions used as `component:` are stateless functional components - Vue calls them
|
|
205
|
+
// fresh on every render and treats their return value as vnodes directly, NOT as a "setup
|
|
206
|
+
// returns a render fn" component. A screen calling a composable needs a real setup-based
|
|
207
|
+
// component (onMounted/inject require a persistent instance across renders), hence
|
|
208
|
+
// defineComponent here instead of a bare function.
|
|
209
|
+
const TrackedHomeScreen = defineComponent(() => {
|
|
210
|
+
const navigation = useNavigation();
|
|
211
|
+
const isFocused = useIsFocused();
|
|
212
|
+
const route = useRoute();
|
|
213
|
+
expect(typeof navigation.value.jumpTo).toBe('function');
|
|
214
|
+
return () => {
|
|
215
|
+
homeIsFocused = isFocused.value;
|
|
216
|
+
homeRouteName = route.value.name;
|
|
217
|
+
return h('symbiote-text', {}, 'home');
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
const TrackedProfileScreen = defineComponent(() => {
|
|
221
|
+
const isFocused = useIsFocused();
|
|
222
|
+
return () => {
|
|
223
|
+
profileIsFocused = isFocused.value;
|
|
224
|
+
return h('symbiote-text', {}, 'profile');
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const handleRef = ref<ITabNavigatorHandle | null>(null);
|
|
229
|
+
mount(
|
|
230
|
+
ROOT_TAG,
|
|
231
|
+
defineComponent({
|
|
232
|
+
setup: () => () =>
|
|
233
|
+
h(Tab, { ref: handleRef, initialRouteName: 'Home' }, () => [
|
|
234
|
+
h(Tab.Screen, { name: 'Home', component: TrackedHomeScreen }),
|
|
235
|
+
h(Tab.Screen, { name: 'Profile', component: TrackedProfileScreen }),
|
|
236
|
+
]),
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
await tick();
|
|
240
|
+
expect(homeIsFocused).toBe(true);
|
|
241
|
+
expect(homeRouteName).toBe('Home');
|
|
242
|
+
|
|
243
|
+
handleRef.value?.jumpTo('Profile');
|
|
244
|
+
await tick();
|
|
245
|
+
expect(profileIsFocused).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('useFocusEffect runs on Tab focus and its cleanup once jumpTo moves focus away', async () => {
|
|
249
|
+
const events: string[] = [];
|
|
250
|
+
const TrackedHomeScreen = defineComponent(() => {
|
|
251
|
+
useFocusEffect(() => {
|
|
252
|
+
events.push('effect');
|
|
253
|
+
return () => events.push('cleanup');
|
|
254
|
+
});
|
|
255
|
+
return () => h('symbiote-text', {}, 'home');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const handleRef = ref<ITabNavigatorHandle | null>(null);
|
|
259
|
+
mount(
|
|
260
|
+
ROOT_TAG,
|
|
261
|
+
defineComponent({
|
|
262
|
+
setup: () => () =>
|
|
263
|
+
h(Tab, { ref: handleRef, initialRouteName: 'Home' }, () => [
|
|
264
|
+
h(Tab.Screen, { name: 'Home', component: TrackedHomeScreen }),
|
|
265
|
+
h(Tab.Screen, { name: 'Profile', component: ProfileScreen }),
|
|
266
|
+
]),
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
await tick();
|
|
270
|
+
// Tab paints no native RNSScreen (unlike Stack), so there is no onAppear to wait for - the
|
|
271
|
+
// focused screen's useFocusEffect runs as soon as it mounts.
|
|
272
|
+
expect(events).toEqual(['effect']);
|
|
273
|
+
|
|
274
|
+
handleRef.value?.jumpTo('Profile');
|
|
275
|
+
await tick();
|
|
276
|
+
expect(events).toEqual(['effect', 'cleanup']);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
5
|
+
|
|
6
|
+
native_screens_package_json = `node --print "require.resolve('react-native-screens/package.json', { paths: [process.argv[1]] })" "#{__dir__}"`.strip
|
|
7
|
+
native_screens_root = File.dirname(native_screens_package_json)
|
|
8
|
+
|
|
9
|
+
# CocoaPods enumerates a pod's files with ONE recursive `Dir.glob(root + '**/*')` walk
|
|
10
|
+
# (Sandbox::PathList#read_file_system), and Ruby's `**` never descends into a symlinked
|
|
11
|
+
# subdirectory it encounters mid-walk (confirmed: it lists the symlink as an entry but
|
|
12
|
+
# never recurses into it) — true whether the symlink crosses the app's node_modules or
|
|
13
|
+
# points straight at the vendored library. react-native-screens lives behind pnpm's own
|
|
14
|
+
# .pnpm-store symlinks, so no relative-path or same-directory-symlink trick can reach it;
|
|
15
|
+
# the files must be REAL entries under this pod's own directory. Physically vendor them
|
|
16
|
+
# (directories stay real, so `**` still recurses through them normally) instead of trying
|
|
17
|
+
# to glob through any symlink. If this glob ever matches zero files, thirdPartyFabricComponents
|
|
18
|
+
# silently gets a nil class entry and Fabric crashes building that dictionary at app startup —
|
|
19
|
+
# not a build-time error, since NSClassFromString only resolves at runtime.
|
|
20
|
+
vendor_dir = File.join(__dir__, '.rn-screens')
|
|
21
|
+
FileUtils.rm_rf(vendor_dir)
|
|
22
|
+
FileUtils.mkdir_p(vendor_dir)
|
|
23
|
+
%w[ios common cpp].each do |subdir|
|
|
24
|
+
src = File.join(native_screens_root, subdir)
|
|
25
|
+
FileUtils.cp_r(src, File.join(vendor_dir, subdir)) if File.directory?(src)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Swift is excluded: react-native-screens' own podspec only sets DEFINES_MODULE (required
|
|
29
|
+
# for its Swift files to build) under an opt-in "gamma" flag, precisely because it conflicts
|
|
30
|
+
# with React-RCTImage (a dependency both that podspec and this one carry) not defining a
|
|
31
|
+
# module — their own comment says as much ("we can not have Swift code in stable package").
|
|
32
|
+
# The only non-gamma Swift file (ios/utils/RNSLog.swift) is a logging helper unused by
|
|
33
|
+
# RNSScreen/RNSScreenStack/RNSScreenStackHeaderConfig; everything that imports the Swift
|
|
34
|
+
# bridging header lives under ios/gamma/, which is already excluded below. So the stable
|
|
35
|
+
# native-stack surface never needs Swift compiled at all — matching upstream's own default.
|
|
36
|
+
source_files_exts = '{h,m,mm,cpp}'
|
|
37
|
+
|
|
38
|
+
Pod::Spec.new do |s|
|
|
39
|
+
s.name = 'symbiote-navigation'
|
|
40
|
+
s.version = package['version']
|
|
41
|
+
s.summary = 'Symbiote wrapper for react-native-screens native stack primitives.'
|
|
42
|
+
s.license = package['license'] || 'MIT'
|
|
43
|
+
s.authors = package['author'] || 'symbiote'
|
|
44
|
+
s.homepage = package['homepage'] || 'https://github.com/symbiote/symbiote'
|
|
45
|
+
s.platforms = { :ios => '15.1', :visionos => '1.0' }
|
|
46
|
+
s.source = { :git => 'https://github.com/symbiote/symbiote.git', :tag => "v#{s.version}" }
|
|
47
|
+
|
|
48
|
+
s.source_files = ".rn-screens/ios/**/*.#{source_files_exts}"
|
|
49
|
+
s.exclude_files = ".rn-screens/ios/gamma/**/*.#{source_files_exts}"
|
|
50
|
+
|
|
51
|
+
s.subspec 'common' do |ss|
|
|
52
|
+
ss.source_files = [
|
|
53
|
+
'.rn-screens/common/cpp/**/*.{cpp,h}',
|
|
54
|
+
'.rn-screens/cpp/**/*.{cpp,h}',
|
|
55
|
+
]
|
|
56
|
+
ss.header_dir = 'rnscreens'
|
|
57
|
+
# Must point at the vendored copy, not native_screens_root: an include resolved via
|
|
58
|
+
# this search path pulls in a second, physically distinct copy of the same headers
|
|
59
|
+
# already compiled from .rn-screens/common/cpp, and two distinct files defining the
|
|
60
|
+
# same class have no shared include guard — "redefinition of 'RNSSafeAreaViewState'".
|
|
61
|
+
ss.pod_target_xcconfig = {
|
|
62
|
+
'HEADER_SEARCH_PATHS' => "\"#{File.join(vendor_dir, 'common/cpp')}\""
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if defined?(install_modules_dependencies)
|
|
67
|
+
install_modules_dependencies(s)
|
|
68
|
+
else
|
|
69
|
+
s.dependency 'React-Core'
|
|
70
|
+
s.compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32 -DRCT_NEW_ARCH_ENABLED=1'
|
|
71
|
+
s.pod_target_xcconfig = {
|
|
72
|
+
'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/boost"',
|
|
73
|
+
'OTHER_CPLUSPLUSFLAGS' => '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1',
|
|
74
|
+
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17'
|
|
75
|
+
}
|
|
76
|
+
s.dependency 'React-RCTFabric'
|
|
77
|
+
s.dependency 'React-Codegen'
|
|
78
|
+
s.dependency 'RCT-Folly'
|
|
79
|
+
s.dependency 'RCTRequired'
|
|
80
|
+
s.dependency 'RCTTypeSafety'
|
|
81
|
+
s.dependency 'ReactCommon/turbomodule/core'
|
|
82
|
+
end
|
|
83
|
+
s.dependency 'React-RCTImage'
|
|
84
|
+
end
|