@symbiote-native/navigation-bar 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/build/angular/index.d.ts +3 -0
  4. package/build/angular/index.js +3 -0
  5. package/build/angular/navigation-bar-visibility.service.d.ts +5 -0
  6. package/build/angular/navigation-bar-visibility.service.js +73 -0
  7. package/build/angular/navigation-bar.d.ts +13 -0
  8. package/build/angular/navigation-bar.js +107 -0
  9. package/build/core/entries-stack.d.ts +8 -0
  10. package/build/core/entries-stack.js +54 -0
  11. package/build/core/index.d.ts +3 -0
  12. package/build/core/index.js +2 -0
  13. package/build/core/native-module/index.android.d.ts +2 -0
  14. package/build/core/native-module/index.android.js +2 -0
  15. package/build/core/native-module/index.d.ts +2 -0
  16. package/build/core/native-module/index.js +3 -0
  17. package/build/core/native-module/shared.d.ts +8 -0
  18. package/build/core/native-module/shared.js +1 -0
  19. package/build/core/navigation-bar.d.ts +16 -0
  20. package/build/core/navigation-bar.js +69 -0
  21. package/build/core/types.d.ts +14 -0
  22. package/build/core/types.js +2 -0
  23. package/build/react/index.d.ts +3 -0
  24. package/build/react/index.js +3 -0
  25. package/build/react/navigation-bar.d.ts +3 -0
  26. package/build/react/navigation-bar.js +23 -0
  27. package/build/react/use-visibility.d.ts +3 -0
  28. package/build/react/use-visibility.js +22 -0
  29. package/build/solid/create-visibility.d.ts +4 -0
  30. package/build/solid/create-visibility.js +26 -0
  31. package/build/solid/index.d.ts +3 -0
  32. package/build/solid/index.js +3 -0
  33. package/build/solid/navigation-bar.d.ts +2 -0
  34. package/build/solid/navigation-bar.js +24 -0
  35. package/build/svelte/NavigationBar.svelte +28 -0
  36. package/build/svelte/NavigationBar.svelte.d.ts +4 -0
  37. package/build/svelte/index.d.ts +3 -0
  38. package/build/svelte/index.js +3 -0
  39. package/build/svelte/use-visibility.svelte.d.ts +4 -0
  40. package/build/svelte/use-visibility.svelte.js +26 -0
  41. package/build/vue/index.d.ts +3 -0
  42. package/build/vue/index.js +3 -0
  43. package/build/vue/navigation-bar.d.ts +2 -0
  44. package/build/vue/navigation-bar.js +23 -0
  45. package/build/vue/use-visibility.d.ts +4 -0
  46. package/build/vue/use-visibility.js +24 -0
  47. package/build-ngc/angular/index.d.ts +3 -0
  48. package/build-ngc/angular/index.js +4 -0
  49. package/build-ngc/angular/index.js.map +1 -0
  50. package/build-ngc/angular/navigation-bar-visibility.service.d.ts +8 -0
  51. package/build-ngc/angular/navigation-bar-visibility.service.js +32 -0
  52. package/build-ngc/angular/navigation-bar-visibility.service.js.map +1 -0
  53. package/build-ngc/angular/navigation-bar.d.ts +16 -0
  54. package/build-ngc/angular/navigation-bar.js +59 -0
  55. package/build-ngc/angular/navigation-bar.js.map +1 -0
  56. package/build-ngc/core/entries-stack.d.ts +8 -0
  57. package/build-ngc/core/entries-stack.js +55 -0
  58. package/build-ngc/core/entries-stack.js.map +1 -0
  59. package/build-ngc/core/index.d.ts +3 -0
  60. package/build-ngc/core/index.js +3 -0
  61. package/build-ngc/core/index.js.map +1 -0
  62. package/build-ngc/core/native-module/index.android.d.ts +2 -0
  63. package/build-ngc/core/native-module/index.android.js +3 -0
  64. package/build-ngc/core/native-module/index.android.js.map +1 -0
  65. package/build-ngc/core/native-module/index.d.ts +2 -0
  66. package/build-ngc/core/native-module/index.js +4 -0
  67. package/build-ngc/core/native-module/index.js.map +1 -0
  68. package/build-ngc/core/native-module/shared.d.ts +8 -0
  69. package/build-ngc/core/native-module/shared.js +2 -0
  70. package/build-ngc/core/native-module/shared.js.map +1 -0
  71. package/build-ngc/core/navigation-bar.d.ts +16 -0
  72. package/build-ngc/core/navigation-bar.js +70 -0
  73. package/build-ngc/core/navigation-bar.js.map +1 -0
  74. package/build-ngc/core/types.d.ts +14 -0
  75. package/build-ngc/core/types.js +3 -0
  76. package/build-ngc/core/types.js.map +1 -0
  77. package/native-link.json +12 -0
  78. package/package.json +150 -0
  79. package/src/angular/index.ts +3 -0
  80. package/src/angular/navigation-bar-visibility.service.ts +29 -0
  81. package/src/angular/navigation-bar.ts +67 -0
  82. package/src/core/entries-stack.ts +76 -0
  83. package/src/core/index.ts +19 -0
  84. package/src/core/native-module/index.android.ts +5 -0
  85. package/src/core/native-module/index.ts +5 -0
  86. package/src/core/native-module/shared.ts +15 -0
  87. package/src/core/navigation-bar.ts +90 -0
  88. package/src/core/types.ts +19 -0
  89. package/src/react/index.ts +3 -0
  90. package/src/react/navigation-bar.tsx +38 -0
  91. package/src/react/use-visibility.ts +29 -0
  92. package/src/solid/create-visibility.ts +32 -0
  93. package/src/solid/index.ts +3 -0
  94. package/src/solid/navigation-bar.ts +35 -0
  95. package/src/svelte/NavigationBar.svelte +28 -0
  96. package/src/svelte/index.ts +3 -0
  97. package/src/svelte/svelte-ambient.d.ts +3 -0
  98. package/src/svelte/svelte-compile.test-helper.ts +7 -0
  99. package/src/svelte/use-visibility.svelte.ts +37 -0
  100. package/src/vue/index.ts +3 -0
  101. package/src/vue/navigation-bar.ts +42 -0
  102. package/src/vue/use-visibility.ts +28 -0
@@ -0,0 +1,3 @@
1
+ // Ported from expo-navigation-bar's NavigationBar.types.ts (sdk-57)
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,oEAAoE"}
@@ -0,0 +1,12 @@
1
+ {
2
+ "android": {
3
+ "gradleProjectName": "expo-navigation-bar",
4
+ "modules": [
5
+ {
6
+ "importPath": "expo.modules.navigationbar.NavigationBarModule",
7
+ "className": "NavigationBarModule",
8
+ "nativeName": "ExpoNavigationBar"
9
+ }
10
+ ]
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,150 @@
1
+ {
2
+ "name": "@symbiote-native/navigation-bar",
3
+ "version": "0.1.0",
4
+ "description": "expo-navigation-bar wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Sets the Android navigation bar's style and visibility. Android only.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "react-native",
8
+ "symbiote-native",
9
+ "expo-navigation-bar",
10
+ "react",
11
+ "vue",
12
+ "svelte",
13
+ "solid",
14
+ "angular",
15
+ "android",
16
+ "navigation-bar"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
21
+ "directory": "packages/navigation-bar"
22
+ },
23
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/navigation-bar#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
26
+ },
27
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
28
+ "type": "module",
29
+ "main": "./build/core/index.js",
30
+ "module": "./build/core/index.js",
31
+ "types": "./build/core/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./build/core/index.d.ts",
35
+ "default": "./build/core/index.js"
36
+ },
37
+ "./vue": {
38
+ "types": "./build/vue/index.d.ts",
39
+ "default": "./build/vue/index.js"
40
+ },
41
+ "./react": {
42
+ "types": "./build/react/index.d.ts",
43
+ "default": "./build/react/index.js"
44
+ },
45
+ "./svelte": {
46
+ "types": "./build/svelte/index.d.ts",
47
+ "default": "./build/svelte/index.js"
48
+ },
49
+ "./solid": {
50
+ "types": "./build/solid/index.d.ts",
51
+ "default": "./build/solid/index.js"
52
+ },
53
+ "./angular": {
54
+ "types": "./build-ngc/angular/index.d.ts",
55
+ "react-native": "./build-ngc/angular/index.js",
56
+ "default": "./src/angular/index.ts"
57
+ }
58
+ },
59
+ "files": [
60
+ "src",
61
+ "build",
62
+ "build-ngc",
63
+ "native-link.json",
64
+ "!src/**/*.test.*",
65
+ "!src/**/*.spec.*",
66
+ "!src/**/*.detox.*"
67
+ ],
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "dependencies": {
72
+ "expo-navigation-bar": "57.0.2",
73
+ "expo-modules-core": "57.0.20"
74
+ },
75
+ "peerDependencies": {
76
+ "@angular/core": ">=20",
77
+ "@vue/runtime-core": "^3.5.13",
78
+ "react": ">=19.0.0",
79
+ "react-native": ">=0.86",
80
+ "solid-js": ">=1.9.0",
81
+ "svelte": ">=5.56.0",
82
+ "vue": ">=3.5.0",
83
+ "@symbiote-native/angular": "^3.1.3",
84
+ "@symbiote-native/engine": "^1.4.0",
85
+ "@symbiote-native/react": "^3.1.0",
86
+ "@symbiote-native/solid": "^3.0.5",
87
+ "@symbiote-native/vue": "^3.1.0",
88
+ "@symbiote-native/svelte": "^3.0.5"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "@symbiote-native/angular": {
92
+ "optional": true
93
+ },
94
+ "@symbiote-native/react": {
95
+ "optional": true
96
+ },
97
+ "@symbiote-native/solid": {
98
+ "optional": true
99
+ },
100
+ "@symbiote-native/svelte": {
101
+ "optional": true
102
+ },
103
+ "@symbiote-native/vue": {
104
+ "optional": true
105
+ },
106
+ "@angular/core": {
107
+ "optional": true
108
+ },
109
+ "@vue/runtime-core": {
110
+ "optional": true
111
+ },
112
+ "react": {
113
+ "optional": true
114
+ },
115
+ "solid-js": {
116
+ "optional": true
117
+ },
118
+ "svelte": {
119
+ "optional": true
120
+ },
121
+ "vue": {
122
+ "optional": true
123
+ }
124
+ },
125
+ "devDependencies": {
126
+ "@angular/compiler": "~22.0.8",
127
+ "@angular/compiler-cli": "~22.0.8",
128
+ "@angular/core": "~22.0.8",
129
+ "@types/node": "^26.0.0",
130
+ "@types/react": "^19.2.0",
131
+ "@vue/runtime-core": "^3.5.13",
132
+ "react": "19.2.3",
133
+ "solid-js": "^1.9.14",
134
+ "svelte": "^5.56.0",
135
+ "typescript": "~6.0.0",
136
+ "@symbiote-native/angular": "3.1.3",
137
+ "@symbiote-native/engine": "1.4.0",
138
+ "@symbiote-native/react": "3.1.0",
139
+ "@symbiote-native/solid": "3.0.5",
140
+ "@symbiote-native/vue": "3.1.0",
141
+ "@symbiote-native/svelte": "3.0.5",
142
+ "@symbiote-native/test-utils": "0.4.5"
143
+ },
144
+ "scripts": {
145
+ "typecheck": "tsc --build",
146
+ "clean": "rm -rf build-ngc",
147
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
148
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
149
+ }
150
+ }
@@ -0,0 +1,3 @@
1
+ export * from '../core';
2
+ export { NavigationBar } from './navigation-bar';
3
+ export { NavigationBarVisibilityService } from './navigation-bar-visibility.service';
@@ -0,0 +1,29 @@
1
+ // Angular twin of `../react`/`../vue`/`../solid`/`../svelte`'s visibility hook, DI shape
2
+ // matching `ColorSchemeService` (`components_split_logic_view_lifecycle`)
3
+
4
+ import { DestroyRef, inject, Injectable, signal } from '@angular/core';
5
+ import { addVisibilityListener, getVisibilityAsync } from '../core';
6
+ import type { INavigationBarVisibility } from '../core';
7
+
8
+ @Injectable({ providedIn: 'root' })
9
+ export class NavigationBarVisibilityService {
10
+ readonly visibility = signal<INavigationBarVisibility | null>(null);
11
+
12
+ constructor() {
13
+ const destroyRef = inject(DestroyRef);
14
+ let isMounted = true;
15
+
16
+ getVisibilityAsync().then(value => {
17
+ if (isMounted) this.visibility.set(value);
18
+ });
19
+
20
+ const subscription = addVisibilityListener(({ visibility: next }) => {
21
+ if (isMounted) this.visibility.set(next);
22
+ });
23
+
24
+ destroyRef.onDestroy(() => {
25
+ subscription.remove();
26
+ isMounted = false;
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,67 @@
1
+ // Angular twin of `../react`/`../vue`/`../solid`/`../svelte`'s `NavigationBar`, same shared
2
+ // stack core - only the lifecycle wiring differs per adapter
3
+ // (`components_split_logic_view_lifecycle`)
4
+
5
+ import {
6
+ Component,
7
+ effect,
8
+ inject,
9
+ Input,
10
+ type OnChanges,
11
+ type OnDestroy,
12
+ type OnInit,
13
+ } from '@angular/core';
14
+ import { ColorSchemeService } from '@symbiote-native/angular';
15
+ import {
16
+ popStackEntry,
17
+ pushStackEntry,
18
+ replaceStackEntry,
19
+ type INavigationBarStackEntry,
20
+ type INavigationBarStyle,
21
+ } from '../core';
22
+
23
+ @Component({
24
+ selector: 'navigation-bar',
25
+ standalone: true,
26
+ template: '',
27
+ })
28
+ export class NavigationBar implements OnInit, OnChanges, OnDestroy {
29
+ @Input() style?: INavigationBarStyle;
30
+ @Input() hidden?: boolean;
31
+
32
+ private readonly colorScheme = inject(ColorSchemeService).colorScheme;
33
+ private stackEntry: INavigationBarStackEntry | null = null;
34
+ private isInitialized = false;
35
+
36
+ constructor() {
37
+ effect(() => {
38
+ this.colorScheme();
39
+ if (this.stackEntry) {
40
+ this.stackEntry = replaceStackEntry(this.stackEntry, {
41
+ style: this.style,
42
+ hidden: this.hidden,
43
+ });
44
+ }
45
+ });
46
+ }
47
+
48
+ ngOnInit(): void {
49
+ this.stackEntry = pushStackEntry({
50
+ style: this.style,
51
+ hidden: this.hidden,
52
+ });
53
+ this.isInitialized = true;
54
+ }
55
+
56
+ ngOnChanges(): void {
57
+ if (!this.isInitialized || !this.stackEntry) return;
58
+ this.stackEntry = replaceStackEntry(this.stackEntry, {
59
+ style: this.style,
60
+ hidden: this.hidden,
61
+ });
62
+ }
63
+
64
+ ngOnDestroy(): void {
65
+ if (this.stackEntry) popStackEntry(this.stackEntry);
66
+ }
67
+ }
@@ -0,0 +1,76 @@
1
+ // Framework-agnostic merge-stack over multiple mounted `<NavigationBar>` declarative components,
2
+ // ported from expo-navigation-bar's NavigationBar.android.ts - the last pushed (or deepest in the
3
+ // view hierarchy) entry wins on shared fields, matching upstream's own priority rule
4
+
5
+ import {
6
+ defaultNavigationBarProps,
7
+ setHidden,
8
+ setStyle,
9
+ } from './navigation-bar';
10
+ import type { INavigationBarProps } from './types';
11
+
12
+ export type INavigationBarStackEntry = INavigationBarProps;
13
+
14
+ const entriesStack: INavigationBarStackEntry[] = [];
15
+ let updateImmediate: ReturnType<typeof setImmediate> | null = null;
16
+
17
+ function mergeEntriesStack(
18
+ stack: readonly INavigationBarStackEntry[],
19
+ ): INavigationBarProps {
20
+ return stack.reduce<INavigationBarProps>(
21
+ (prev, cur) => ({
22
+ style: cur.style ?? prev.style,
23
+ hidden: cur.hidden ?? prev.hidden,
24
+ }),
25
+ {},
26
+ );
27
+ }
28
+
29
+ function createStackEntry(
30
+ props: INavigationBarProps,
31
+ ): INavigationBarStackEntry {
32
+ return { style: props.style, hidden: props.hidden };
33
+ }
34
+
35
+ function updateEntriesStack(): void {
36
+ if (updateImmediate != null) clearImmediate(updateImmediate);
37
+ updateImmediate = setImmediate(() => {
38
+ if (entriesStack.length === 0) {
39
+ setStyle(defaultNavigationBarProps.style);
40
+ setHidden(defaultNavigationBarProps.hidden);
41
+ return;
42
+ }
43
+ const { style, hidden } = mergeEntriesStack(entriesStack);
44
+ if (style != null) setStyle(style);
45
+ if (hidden != null) setHidden(hidden);
46
+ });
47
+ }
48
+
49
+ /** Registers a mounted `<NavigationBar>` entry */
50
+ export function pushStackEntry(
51
+ props: INavigationBarProps,
52
+ ): INavigationBarStackEntry {
53
+ const entry = createStackEntry(props);
54
+ entriesStack.push(entry);
55
+ updateEntriesStack();
56
+ return entry;
57
+ }
58
+
59
+ /** Unregisters an entry, falling back to the previous entries or the last imperative call */
60
+ export function popStackEntry(entry: INavigationBarStackEntry): void {
61
+ const index = entriesStack.indexOf(entry);
62
+ if (index !== -1) entriesStack.splice(index, 1);
63
+ updateEntriesStack();
64
+ }
65
+
66
+ /** Swaps an entry's props in place, for a mounted component's own prop changes */
67
+ export function replaceStackEntry(
68
+ entry: INavigationBarStackEntry,
69
+ props: INavigationBarProps,
70
+ ): INavigationBarStackEntry {
71
+ const newEntry = createStackEntry(props);
72
+ const index = entriesStack.indexOf(entry);
73
+ if (index !== -1) entriesStack[index] = newEntry;
74
+ updateEntriesStack();
75
+ return newEntry;
76
+ }
@@ -0,0 +1,19 @@
1
+ export {
2
+ addVisibilityListener,
3
+ getVisibilityAsync,
4
+ setHidden,
5
+ setStyle,
6
+ setVisibilityAsync,
7
+ } from './navigation-bar';
8
+ export {
9
+ popStackEntry,
10
+ pushStackEntry,
11
+ replaceStackEntry,
12
+ type INavigationBarStackEntry,
13
+ } from './entries-stack';
14
+ export type {
15
+ INavigationBarProps,
16
+ INavigationBarStyle,
17
+ INavigationBarVisibility,
18
+ INavigationBarVisibilityEvent,
19
+ } from './types';
@@ -0,0 +1,5 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ import type { INativeNavigationBarModule } from './shared';
3
+
4
+ export const expoNavigationBar =
5
+ requireNativeModule<INativeNavigationBarModule>('ExpoNavigationBar');
@@ -0,0 +1,5 @@
1
+ import type { INativeNavigationBarModule } from './shared';
2
+
3
+ // Android-only native module - upstream's own non-android entry types an empty stub, so every
4
+ // function guards a missing method with UnavailabilityError rather than calling this
5
+ export const expoNavigationBar: Partial<INativeNavigationBarModule> = {};
@@ -0,0 +1,15 @@
1
+ import type { EventSubscription } from 'expo-modules-core';
2
+ import type {
3
+ INavigationBarVisibility,
4
+ INavigationBarVisibilityEvent,
5
+ } from '../types';
6
+
7
+ export type INativeNavigationBarModule = {
8
+ addListener(
9
+ event: 'ExpoNavigationBar.didChange',
10
+ listener: (event: INavigationBarVisibilityEvent) => void,
11
+ ): EventSubscription;
12
+ setStyle(style: 'light' | 'dark'): Promise<void>;
13
+ setHidden(hidden: boolean): Promise<void>;
14
+ getVisibilityAsync(): Promise<INavigationBarVisibility>;
15
+ };
@@ -0,0 +1,90 @@
1
+ import { UnavailabilityError, type EventSubscription } from 'expo-modules-core';
2
+ import { Appearance } from '@symbiote-native/engine';
3
+ import { expoNavigationBar } from './native-module';
4
+ import type {
5
+ INavigationBarStyle,
6
+ INavigationBarVisibility,
7
+ INavigationBarVisibilityEvent,
8
+ } from './types';
9
+
10
+ type IResolvedNavigationBarStyle = 'light' | 'dark';
11
+
12
+ function isLightColorScheme(): boolean {
13
+ return (Appearance.getColorScheme() ?? 'light') === 'light';
14
+ }
15
+
16
+ function resolveStyle(style: INavigationBarStyle): IResolvedNavigationBarStyle {
17
+ switch (style) {
18
+ case 'auto':
19
+ return isLightColorScheme() ? 'dark' : 'light';
20
+ case 'inverted':
21
+ return isLightColorScheme() ? 'light' : 'dark';
22
+ default:
23
+ return style;
24
+ }
25
+ }
26
+
27
+ let currentStyle: IResolvedNavigationBarStyle | undefined;
28
+ let currentHidden: boolean | undefined;
29
+
30
+ // The baseline the declarative `<NavigationBar>` stack falls back to once every mounted
31
+ // entry unmounts, kept in sync with every imperative `setStyle`/`setHidden` call too -
32
+ // mirrors upstream's own `defaultProps`, read by ./entries-stack.ts
33
+ export const defaultNavigationBarProps: {
34
+ style: INavigationBarStyle;
35
+ hidden: boolean;
36
+ } = {
37
+ style: 'light',
38
+ hidden: false,
39
+ };
40
+
41
+ /** Sets the navigation bar buttons' style */
42
+ export function setStyle(style: INavigationBarStyle): void {
43
+ if (!expoNavigationBar.setStyle) {
44
+ throw new UnavailabilityError('NavigationBar', 'setStyle');
45
+ }
46
+ defaultNavigationBarProps.style = style;
47
+ const resolved = resolveStyle(style);
48
+ if (resolved === currentStyle) return;
49
+ currentStyle = resolved;
50
+ expoNavigationBar.setStyle(resolved);
51
+ }
52
+
53
+ /** Shows or hides the navigation bar */
54
+ export function setHidden(hidden: boolean): void {
55
+ if (!expoNavigationBar.setHidden) {
56
+ throw new UnavailabilityError('NavigationBar', 'setHidden');
57
+ }
58
+ defaultNavigationBarProps.hidden = hidden;
59
+ if (hidden === currentHidden) return;
60
+ currentHidden = hidden;
61
+ expoNavigationBar.setHidden(hidden);
62
+ }
63
+
64
+ /** Observes changes to the system navigation bar's visibility */
65
+ export function addVisibilityListener(
66
+ listener: (event: INavigationBarVisibilityEvent) => void,
67
+ ): EventSubscription {
68
+ if (!expoNavigationBar.addListener) {
69
+ throw new UnavailabilityError('NavigationBar', 'addVisibilityListener');
70
+ }
71
+ return expoNavigationBar.addListener('ExpoNavigationBar.didChange', listener);
72
+ }
73
+
74
+ /** Sets the navigation bar's visibility */
75
+ export async function setVisibilityAsync(
76
+ visibility: INavigationBarVisibility,
77
+ ): Promise<void> {
78
+ if (!expoNavigationBar.setHidden) {
79
+ throw new UnavailabilityError('NavigationBar', 'setVisibilityAsync');
80
+ }
81
+ await expoNavigationBar.setHidden(visibility === 'hidden');
82
+ }
83
+
84
+ /** Gets the navigation bar's current visibility */
85
+ export async function getVisibilityAsync(): Promise<INavigationBarVisibility> {
86
+ if (!expoNavigationBar.getVisibilityAsync) {
87
+ throw new UnavailabilityError('NavigationBar', 'getVisibilityAsync');
88
+ }
89
+ return expoNavigationBar.getVisibilityAsync();
90
+ }
@@ -0,0 +1,19 @@
1
+ // Ported from expo-navigation-bar's NavigationBar.types.ts (sdk-57)
2
+
3
+ export type INavigationBarVisibility = 'visible' | 'hidden';
4
+
5
+ export type INavigationBarVisibilityEvent = {
6
+ /** Current navigation bar visibility */
7
+ visibility: INavigationBarVisibility;
8
+ /** Native Android system UI visibility state, from `setOnSystemUiVisibilityChangeListener` */
9
+ rawVisibility: number;
10
+ };
11
+
12
+ /** `auto`/`inverted` resolve against the current color scheme, `light`/`dark` are fixed */
13
+ export type INavigationBarStyle = 'auto' | 'inverted' | 'light' | 'dark';
14
+
15
+ export type INavigationBarProps = {
16
+ /** @default 'auto' */
17
+ style?: INavigationBarStyle;
18
+ hidden?: boolean;
19
+ };
@@ -0,0 +1,3 @@
1
+ export * from '../core';
2
+ export { NavigationBar } from './navigation-bar';
3
+ export { useVisibility } from './use-visibility';
@@ -0,0 +1,38 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import { useColorScheme } from '@symbiote-native/react';
3
+ import {
4
+ popStackEntry,
5
+ pushStackEntry,
6
+ replaceStackEntry,
7
+ type INavigationBarProps,
8
+ type INavigationBarStackEntry,
9
+ } from '../core';
10
+
11
+ /** Declaratively configures the navigation bar - several mounted instances merge, last wins */
12
+ export function NavigationBar({ style, hidden }: INavigationBarProps): null {
13
+ const colorScheme = useColorScheme();
14
+ const stableProps = useMemo<INavigationBarProps>(
15
+ () => ({ style, hidden }),
16
+ [style, hidden],
17
+ );
18
+ const stackEntryRef = useRef<INavigationBarStackEntry | null>(null);
19
+
20
+ useEffect(() => {
21
+ stackEntryRef.current = pushStackEntry(stableProps);
22
+ return () => {
23
+ if (stackEntryRef.current) popStackEntry(stackEntryRef.current);
24
+ };
25
+ // eslint-disable-next-line react-hooks/exhaustive-deps
26
+ }, []);
27
+
28
+ useEffect(() => {
29
+ if (stackEntryRef.current) {
30
+ stackEntryRef.current = replaceStackEntry(
31
+ stackEntryRef.current,
32
+ stableProps,
33
+ );
34
+ }
35
+ }, [colorScheme, stableProps]);
36
+
37
+ return null;
38
+ }
@@ -0,0 +1,29 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { addVisibilityListener, getVisibilityAsync } from '../core';
3
+ import type { INavigationBarVisibility } from '../core';
4
+
5
+ /** Statefully tracks the system navigation bar's visibility, `null` during async initialization */
6
+ export function useVisibility(): INavigationBarVisibility | null {
7
+ const [visibility, setVisibility] = useState<INavigationBarVisibility | null>(
8
+ null,
9
+ );
10
+
11
+ useEffect(() => {
12
+ let isMounted = true;
13
+
14
+ getVisibilityAsync().then(value => {
15
+ if (isMounted) setVisibility(value);
16
+ });
17
+
18
+ const subscription = addVisibilityListener(({ visibility: next }) => {
19
+ if (isMounted) setVisibility(next);
20
+ });
21
+
22
+ return () => {
23
+ subscription.remove();
24
+ isMounted = false;
25
+ };
26
+ }, []);
27
+
28
+ return visibility;
29
+ }
@@ -0,0 +1,32 @@
1
+ // Solid twin of `../react`/`../vue`'s visibility hook, returning an accessor instead of a
2
+ // snapshot/ref (`components_split_logic_view_lifecycle`)
3
+
4
+ import { createSignal, onCleanup, onMount, type Accessor } from 'solid-js';
5
+ import type { EventSubscription } from 'expo-modules-core';
6
+ import { addVisibilityListener, getVisibilityAsync } from '../core';
7
+ import type { INavigationBarVisibility } from '../core';
8
+
9
+ /** Statefully tracks the system navigation bar's visibility, `null` during async initialization */
10
+ export function createVisibility(): Accessor<INavigationBarVisibility | null> {
11
+ const [visibility, setVisibility] =
12
+ createSignal<INavigationBarVisibility | null>(null);
13
+ let subscription: EventSubscription | undefined;
14
+ let isMounted = false;
15
+
16
+ onMount(() => {
17
+ isMounted = true;
18
+ getVisibilityAsync().then(value => {
19
+ if (isMounted) setVisibility(value);
20
+ });
21
+ subscription = addVisibilityListener(({ visibility: next }) => {
22
+ if (isMounted) setVisibility(next);
23
+ });
24
+ });
25
+
26
+ onCleanup(() => {
27
+ subscription?.remove();
28
+ isMounted = false;
29
+ });
30
+
31
+ return visibility;
32
+ }
@@ -0,0 +1,3 @@
1
+ export * from '../core';
2
+ export { NavigationBar } from './navigation-bar';
3
+ export { createVisibility } from './create-visibility';
@@ -0,0 +1,35 @@
1
+ // Solid twin of `../react`/`../vue`'s `NavigationBar`, same shared stack core - only the
2
+ // lifecycle wiring differs per adapter (`components_split_logic_view_lifecycle`)
3
+
4
+ import { createEffect, onCleanup, onMount } from 'solid-js';
5
+ import { createColorScheme } from '@symbiote-native/solid';
6
+ import {
7
+ popStackEntry,
8
+ pushStackEntry,
9
+ replaceStackEntry,
10
+ type INavigationBarProps,
11
+ type INavigationBarStackEntry,
12
+ } from '../core';
13
+
14
+ export function NavigationBar(props: INavigationBarProps): null {
15
+ const colorScheme = createColorScheme();
16
+ let stackEntry: INavigationBarStackEntry | null = null;
17
+
18
+ onMount(() => {
19
+ stackEntry = pushStackEntry({ style: props.style, hidden: props.hidden });
20
+ });
21
+
22
+ onCleanup(() => {
23
+ if (stackEntry) popStackEntry(stackEntry);
24
+ });
25
+
26
+ createEffect(() => {
27
+ const style = props.style;
28
+ const hidden = props.hidden;
29
+ colorScheme();
30
+ if (stackEntry)
31
+ stackEntry = replaceStackEntry(stackEntry, { style, hidden });
32
+ });
33
+
34
+ return null;
35
+ }