@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.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/build/angular/index.d.ts +3 -0
- package/build/angular/index.js +3 -0
- package/build/angular/navigation-bar-visibility.service.d.ts +5 -0
- package/build/angular/navigation-bar-visibility.service.js +73 -0
- package/build/angular/navigation-bar.d.ts +13 -0
- package/build/angular/navigation-bar.js +107 -0
- package/build/core/entries-stack.d.ts +8 -0
- package/build/core/entries-stack.js +54 -0
- package/build/core/index.d.ts +3 -0
- package/build/core/index.js +2 -0
- package/build/core/native-module/index.android.d.ts +2 -0
- package/build/core/native-module/index.android.js +2 -0
- package/build/core/native-module/index.d.ts +2 -0
- package/build/core/native-module/index.js +3 -0
- package/build/core/native-module/shared.d.ts +8 -0
- package/build/core/native-module/shared.js +1 -0
- package/build/core/navigation-bar.d.ts +16 -0
- package/build/core/navigation-bar.js +69 -0
- package/build/core/types.d.ts +14 -0
- package/build/core/types.js +2 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.js +3 -0
- package/build/react/navigation-bar.d.ts +3 -0
- package/build/react/navigation-bar.js +23 -0
- package/build/react/use-visibility.d.ts +3 -0
- package/build/react/use-visibility.js +22 -0
- package/build/solid/create-visibility.d.ts +4 -0
- package/build/solid/create-visibility.js +26 -0
- package/build/solid/index.d.ts +3 -0
- package/build/solid/index.js +3 -0
- package/build/solid/navigation-bar.d.ts +2 -0
- package/build/solid/navigation-bar.js +24 -0
- package/build/svelte/NavigationBar.svelte +28 -0
- package/build/svelte/NavigationBar.svelte.d.ts +4 -0
- package/build/svelte/index.d.ts +3 -0
- package/build/svelte/index.js +3 -0
- package/build/svelte/use-visibility.svelte.d.ts +4 -0
- package/build/svelte/use-visibility.svelte.js +26 -0
- package/build/vue/index.d.ts +3 -0
- package/build/vue/index.js +3 -0
- package/build/vue/navigation-bar.d.ts +2 -0
- package/build/vue/navigation-bar.js +23 -0
- package/build/vue/use-visibility.d.ts +4 -0
- package/build/vue/use-visibility.js +24 -0
- package/build-ngc/angular/index.d.ts +3 -0
- package/build-ngc/angular/index.js +4 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/navigation-bar-visibility.service.d.ts +8 -0
- package/build-ngc/angular/navigation-bar-visibility.service.js +32 -0
- package/build-ngc/angular/navigation-bar-visibility.service.js.map +1 -0
- package/build-ngc/angular/navigation-bar.d.ts +16 -0
- package/build-ngc/angular/navigation-bar.js +59 -0
- package/build-ngc/angular/navigation-bar.js.map +1 -0
- package/build-ngc/core/entries-stack.d.ts +8 -0
- package/build-ngc/core/entries-stack.js +55 -0
- package/build-ngc/core/entries-stack.js.map +1 -0
- package/build-ngc/core/index.d.ts +3 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module/index.android.d.ts +2 -0
- package/build-ngc/core/native-module/index.android.js +3 -0
- package/build-ngc/core/native-module/index.android.js.map +1 -0
- package/build-ngc/core/native-module/index.d.ts +2 -0
- package/build-ngc/core/native-module/index.js +4 -0
- package/build-ngc/core/native-module/index.js.map +1 -0
- package/build-ngc/core/native-module/shared.d.ts +8 -0
- package/build-ngc/core/native-module/shared.js +2 -0
- package/build-ngc/core/native-module/shared.js.map +1 -0
- package/build-ngc/core/navigation-bar.d.ts +16 -0
- package/build-ngc/core/navigation-bar.js +70 -0
- package/build-ngc/core/navigation-bar.js.map +1 -0
- package/build-ngc/core/types.d.ts +14 -0
- package/build-ngc/core/types.js +3 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +150 -0
- package/src/angular/index.ts +3 -0
- package/src/angular/navigation-bar-visibility.service.ts +29 -0
- package/src/angular/navigation-bar.ts +67 -0
- package/src/core/entries-stack.ts +76 -0
- package/src/core/index.ts +19 -0
- package/src/core/native-module/index.android.ts +5 -0
- package/src/core/native-module/index.ts +5 -0
- package/src/core/native-module/shared.ts +15 -0
- package/src/core/navigation-bar.ts +90 -0
- package/src/core/types.ts +19 -0
- package/src/react/index.ts +3 -0
- package/src/react/navigation-bar.tsx +38 -0
- package/src/react/use-visibility.ts +29 -0
- package/src/solid/create-visibility.ts +32 -0
- package/src/solid/index.ts +3 -0
- package/src/solid/navigation-bar.ts +35 -0
- package/src/svelte/NavigationBar.svelte +28 -0
- package/src/svelte/index.ts +3 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +7 -0
- package/src/svelte/use-visibility.svelte.ts +37 -0
- package/src/vue/index.ts +3 -0
- package/src/vue/navigation-bar.ts +42 -0
- package/src/vue/use-visibility.ts +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# @symbiote-native/navigation-bar
|
|
2
|
+
|
|
3
|
+
Make the Android navigation bar match your screen: light or dark buttons, hidden for an immersive
|
|
4
|
+
view. One API for every [SymbioteNative](../../README.md) adapter (React, Vue, Svelte, Solid and
|
|
5
|
+
Angular).
|
|
6
|
+
|
|
7
|
+
It wraps [`expo-navigation-bar`](https://github.com/expo/expo/tree/main/packages/expo-navigation-bar).
|
|
8
|
+
**Android only**: upstream ships no iOS implementation, so every function throws
|
|
9
|
+
`UnavailabilityError` off Android, matching this repo's normalization for platform-gated packages.
|
|
10
|
+
Built the same way as [`@symbiote-native/intent-launcher`](../intent-launcher): an
|
|
11
|
+
`expo-modules-core`-based wrapper (see the `symbiote-expo-native-module` project skill for the full
|
|
12
|
+
mechanism).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
**New app:**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @symbiote-native/cli new my-app --navigation-bar
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Existing SymbioteNative app:**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @symbiote-native/cli add --navigation-bar
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Either way: installs `@symbiote-native/navigation-bar` and wires the native autolinking
|
|
29
|
+
automatically, see [`@symbiote-native/cli`](../cli).
|
|
30
|
+
|
|
31
|
+
<details>
|
|
32
|
+
<summary>Manual install (no CLI, wiring native autolinking by hand)</summary>
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @symbiote-native/navigation-bar
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`expo-navigation-bar` and `expo-modules-core` come along as regular, pinned dependencies, never
|
|
39
|
+
install either yourself, and never add the `expo` meta-package to this project (it bundles its
|
|
40
|
+
own Metro/Babel pipeline that conflicts with this project's own).
|
|
41
|
+
|
|
42
|
+
### Required one-time step: native autolinking wiring
|
|
43
|
+
|
|
44
|
+
Unlike a plain RN native module, `expo-navigation-bar`'s native code is discovered by
|
|
45
|
+
`expo-modules-autolinking`, wired into the native host app **once**, covering this package and
|
|
46
|
+
every other `expo-modules-core` package with zero further changes. Full mechanics live in the
|
|
47
|
+
`symbiote-expo-native-module` skill.
|
|
48
|
+
|
|
49
|
+
### No manifest permissions, config plugin not carried over
|
|
50
|
+
|
|
51
|
+
- **No runtime permission, no manifest edit.** `expo-navigation-bar`'s own `AndroidManifest.xml`
|
|
52
|
+
declares nothing.
|
|
53
|
+
- **The config plugin was checked and produces no diff with default props.** Upstream's
|
|
54
|
+
`withNavigationBar` only touches `styles.xml` (`android:windowLightNavigationBar`,
|
|
55
|
+
`android:enforceNavigationBarContrast`) when the app passes explicit plugin props
|
|
56
|
+
(`hidden`/`style`/`enforceContrast`); with none given it returns the config unchanged, and this
|
|
57
|
+
repo has no `app.json` for a plugin to read props from anyway. Setting an initial style/hidden
|
|
58
|
+
value before first paint (rather than calling `setStyle`/`setHidden` after mount) needs those
|
|
59
|
+
`styles.xml` edits applied by hand - not something `native-link.json` covers, since it has no
|
|
60
|
+
style-resource field, only manifest attributes and iOS Info.plist array keys.
|
|
61
|
+
|
|
62
|
+
</details>
|
|
63
|
+
|
|
64
|
+
## Shape
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/core/ setStyle + setHidden + addVisibilityListener + setVisibilityAsync +
|
|
68
|
+
getVisibilityAsync + the entries-stack merge core. The native module
|
|
69
|
+
resolution is Android-only (native-module/index.android.ts); the base
|
|
70
|
+
native-module/index.ts is an empty stub, same as upstream's own
|
|
71
|
+
non-Android ExpoNavigationBar.ts
|
|
72
|
+
src/react/ NavigationBar + useVisibility (hooks)
|
|
73
|
+
src/vue/ NavigationBar + useVisibility (composable)
|
|
74
|
+
src/solid/ NavigationBar + createVisibility (primitive)
|
|
75
|
+
src/svelte/ NavigationBar.svelte + use-visibility.svelte.ts (rune)
|
|
76
|
+
src/angular/ NavigationBar + NavigationBarVisibilityService (DI)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Upstream's declarative `<NavigationBar>` component and `useVisibility` hook are ported to
|
|
80
|
+
**every** adapter, not just React: the merge-stack logic lives once in `src/core/entries-stack.ts`
|
|
81
|
+
(`pushStackEntry`/`popStackEntry`/`replaceStackEntry`), and each adapter supplies only its own
|
|
82
|
+
lifecycle glue over that shared core, per this project's `components_split_logic_view_lifecycle`
|
|
83
|
+
convention.
|
|
84
|
+
|
|
85
|
+
## Use it
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { setHidden, setStyle } from '@symbiote-native/navigation-bar';
|
|
89
|
+
|
|
90
|
+
setStyle('dark');
|
|
91
|
+
setHidden(true);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { addVisibilityListener, getVisibilityAsync } from '@symbiote-native/navigation-bar';
|
|
96
|
+
|
|
97
|
+
const visibility = await getVisibilityAsync();
|
|
98
|
+
const subscription = addVisibilityListener(({ visibility }) => {
|
|
99
|
+
console.log(visibility);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Declaratively, per adapter:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// React / Vue / Solid: <NavigationBar style="dark" hidden={false} />
|
|
107
|
+
// Svelte: <NavigationBar style="dark" hidden={false} />
|
|
108
|
+
// Angular: <navigation-bar [style]="'dark'" [hidden]="false" />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The deepest mounted `<NavigationBar>` wins on shared fields (upstream's own priority rule);
|
|
112
|
+
unmounting the last one falls back to `defaultNavigationBarProps`.
|
|
113
|
+
|
|
114
|
+
## API
|
|
115
|
+
|
|
116
|
+
| Export | Signature | Notes |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `setStyle` | `(style: INavigationBarStyle) => void` | `'auto'`/`'inverted'` resolve against `Appearance.getColorScheme()`; skips a redundant native call when the resolved style is unchanged |
|
|
119
|
+
| `setHidden` | `(hidden: boolean) => void` | Skips a redundant native call when the value is unchanged |
|
|
120
|
+
| `addVisibilityListener` | `(listener: (event: INavigationBarVisibilityEvent) => void) => EventSubscription` | |
|
|
121
|
+
| `setVisibilityAsync` | `(visibility: INavigationBarVisibility) => Promise<void>` | Calls the native `setHidden` directly, bypassing `setHidden`'s own dedupe |
|
|
122
|
+
| `getVisibilityAsync` | `() => Promise<INavigationBarVisibility>` | |
|
|
123
|
+
|
|
124
|
+
## Notes
|
|
125
|
+
|
|
126
|
+
- **Every function throws `UnavailabilityError` off Android**, replacing upstream's own
|
|
127
|
+
`console.warn`-and-no-op fallback - the same normalization this repo's other platform-gated
|
|
128
|
+
packages already apply.
|
|
129
|
+
- **`setVisibilityAsync` forwards straight to the native module**, not through this package's own
|
|
130
|
+
`setHidden`, matching upstream's exact observable behavior (and the ported test asserting it).
|
|
131
|
+
|
|
132
|
+
## Common questions
|
|
133
|
+
|
|
134
|
+
- **`setBackgroundColorAsync` has no effect.** Reported on Expo Go; test in a development build.
|
|
135
|
+
- **"The current activity is no longer available".** A setter ran with no activity attached; retry
|
|
136
|
+
once the app is foregrounded.
|
|
137
|
+
- **`PlatformColor` fails.** Pass a plain color string.
|
|
138
|
+
- **Black bar with `inset-swipe`.** Reported on phones that hide the bottom bar by default.
|
|
139
|
+
|
|
140
|
+
Sources: [expo/expo#36814](https://github.com/expo/expo/issues/36814),
|
|
141
|
+
[expo/expo#33950](https://github.com/expo/expo/issues/33950),
|
|
142
|
+
[expo/expo#18515](https://github.com/expo/expo/issues/18515).
|
|
143
|
+
|
|
144
|
+
## Test it
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm vitest run packages/navigation-bar
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The core tests fake the native module in place of `requireNativeModule`'s runtime resolution -
|
|
151
|
+
`ExpoNavigationBar` only exists on a real Android device, so a headless run would otherwise throw
|
|
152
|
+
at import. Upstream's own three tests (`resolves the visibility from the native module`,
|
|
153
|
+
`calls setHidden from setVisibilityAsync`, `adds and removes a visibility listener`) are all
|
|
154
|
+
ported, translated onto this package's own fake-native-module harness; `setStyle`/`setHidden`'s
|
|
155
|
+
resolve-and-dedupe behavior is net-new coverage upstream never exercised directly. Each adapter's
|
|
156
|
+
`NavigationBar`/visibility port has its own lifecycle test (push/replace/pop the stack entry,
|
|
157
|
+
resolve/track/clean up visibility), 53 tests total across the package.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Angular twin of `../react`/`../vue`/`../solid`/`../svelte`'s visibility hook, DI shape
|
|
2
|
+
// matching `ColorSchemeService` (`components_split_logic_view_lifecycle`)
|
|
3
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
4
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
5
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
6
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
7
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
8
|
+
var _, done = false;
|
|
9
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
10
|
+
var context = {};
|
|
11
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
12
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
13
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
14
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
15
|
+
if (kind === "accessor") {
|
|
16
|
+
if (result === void 0) continue;
|
|
17
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
18
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
19
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
20
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
21
|
+
}
|
|
22
|
+
else if (_ = accept(result)) {
|
|
23
|
+
if (kind === "field") initializers.unshift(_);
|
|
24
|
+
else descriptor[key] = _;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
28
|
+
done = true;
|
|
29
|
+
};
|
|
30
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
31
|
+
var useValue = arguments.length > 2;
|
|
32
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
33
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
34
|
+
}
|
|
35
|
+
return useValue ? value : void 0;
|
|
36
|
+
};
|
|
37
|
+
import { DestroyRef, inject, Injectable, signal } from '@angular/core';
|
|
38
|
+
import { addVisibilityListener, getVisibilityAsync } from '../core/index.js';
|
|
39
|
+
let NavigationBarVisibilityService = (() => {
|
|
40
|
+
let _classDecorators = [Injectable({ providedIn: 'root' })];
|
|
41
|
+
let _classDescriptor;
|
|
42
|
+
let _classExtraInitializers = [];
|
|
43
|
+
let _classThis;
|
|
44
|
+
var NavigationBarVisibilityService = class {
|
|
45
|
+
static { _classThis = this; }
|
|
46
|
+
static {
|
|
47
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
48
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
49
|
+
NavigationBarVisibilityService = _classThis = _classDescriptor.value;
|
|
50
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
51
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
52
|
+
}
|
|
53
|
+
visibility = signal(null);
|
|
54
|
+
constructor() {
|
|
55
|
+
const destroyRef = inject(DestroyRef);
|
|
56
|
+
let isMounted = true;
|
|
57
|
+
getVisibilityAsync().then(value => {
|
|
58
|
+
if (isMounted)
|
|
59
|
+
this.visibility.set(value);
|
|
60
|
+
});
|
|
61
|
+
const subscription = addVisibilityListener(({ visibility: next }) => {
|
|
62
|
+
if (isMounted)
|
|
63
|
+
this.visibility.set(next);
|
|
64
|
+
});
|
|
65
|
+
destroyRef.onDestroy(() => {
|
|
66
|
+
subscription.remove();
|
|
67
|
+
isMounted = false;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return NavigationBarVisibilityService = _classThis;
|
|
72
|
+
})();
|
|
73
|
+
export { NavigationBarVisibilityService };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type OnChanges, type OnDestroy, type OnInit } from '@angular/core';
|
|
2
|
+
import { type INavigationBarStyle } from '../core';
|
|
3
|
+
export declare class NavigationBar implements OnInit, OnChanges, OnDestroy {
|
|
4
|
+
style?: INavigationBarStyle;
|
|
5
|
+
hidden?: boolean;
|
|
6
|
+
private readonly colorScheme;
|
|
7
|
+
private stackEntry;
|
|
8
|
+
private isInitialized;
|
|
9
|
+
constructor();
|
|
10
|
+
ngOnInit(): void;
|
|
11
|
+
ngOnChanges(): void;
|
|
12
|
+
ngOnDestroy(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
5
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
6
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
7
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
8
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
9
|
+
var _, done = false;
|
|
10
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
11
|
+
var context = {};
|
|
12
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
13
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
14
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
15
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
16
|
+
if (kind === "accessor") {
|
|
17
|
+
if (result === void 0) continue;
|
|
18
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
19
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
20
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
21
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
22
|
+
}
|
|
23
|
+
else if (_ = accept(result)) {
|
|
24
|
+
if (kind === "field") initializers.unshift(_);
|
|
25
|
+
else descriptor[key] = _;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
29
|
+
done = true;
|
|
30
|
+
};
|
|
31
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
32
|
+
var useValue = arguments.length > 2;
|
|
33
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
34
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
35
|
+
}
|
|
36
|
+
return useValue ? value : void 0;
|
|
37
|
+
};
|
|
38
|
+
import { Component, effect, inject, Input, } from '@angular/core';
|
|
39
|
+
import { ColorSchemeService } from '@symbiote-native/angular';
|
|
40
|
+
import { popStackEntry, pushStackEntry, replaceStackEntry, } from '../core/index.js';
|
|
41
|
+
let NavigationBar = (() => {
|
|
42
|
+
let _classDecorators = [Component({
|
|
43
|
+
selector: 'navigation-bar',
|
|
44
|
+
standalone: true,
|
|
45
|
+
template: '',
|
|
46
|
+
})];
|
|
47
|
+
let _classDescriptor;
|
|
48
|
+
let _classExtraInitializers = [];
|
|
49
|
+
let _classThis;
|
|
50
|
+
let _style_decorators;
|
|
51
|
+
let _style_initializers = [];
|
|
52
|
+
let _style_extraInitializers = [];
|
|
53
|
+
let _hidden_decorators;
|
|
54
|
+
let _hidden_initializers = [];
|
|
55
|
+
let _hidden_extraInitializers = [];
|
|
56
|
+
var NavigationBar = class {
|
|
57
|
+
static { _classThis = this; }
|
|
58
|
+
static {
|
|
59
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
60
|
+
_style_decorators = [Input()];
|
|
61
|
+
_hidden_decorators = [Input()];
|
|
62
|
+
__esDecorate(null, null, _style_decorators, { kind: "field", name: "style", static: false, private: false, access: { has: obj => "style" in obj, get: obj => obj.style, set: (obj, value) => { obj.style = value; } }, metadata: _metadata }, _style_initializers, _style_extraInitializers);
|
|
63
|
+
__esDecorate(null, null, _hidden_decorators, { kind: "field", name: "hidden", static: false, private: false, access: { has: obj => "hidden" in obj, get: obj => obj.hidden, set: (obj, value) => { obj.hidden = value; } }, metadata: _metadata }, _hidden_initializers, _hidden_extraInitializers);
|
|
64
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
65
|
+
NavigationBar = _classThis = _classDescriptor.value;
|
|
66
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
67
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
68
|
+
}
|
|
69
|
+
style = __runInitializers(this, _style_initializers, void 0);
|
|
70
|
+
hidden = (__runInitializers(this, _style_extraInitializers), __runInitializers(this, _hidden_initializers, void 0));
|
|
71
|
+
colorScheme = (__runInitializers(this, _hidden_extraInitializers), inject(ColorSchemeService).colorScheme);
|
|
72
|
+
stackEntry = null;
|
|
73
|
+
isInitialized = false;
|
|
74
|
+
constructor() {
|
|
75
|
+
effect(() => {
|
|
76
|
+
this.colorScheme();
|
|
77
|
+
if (this.stackEntry) {
|
|
78
|
+
this.stackEntry = replaceStackEntry(this.stackEntry, {
|
|
79
|
+
style: this.style,
|
|
80
|
+
hidden: this.hidden,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
ngOnInit() {
|
|
86
|
+
this.stackEntry = pushStackEntry({
|
|
87
|
+
style: this.style,
|
|
88
|
+
hidden: this.hidden,
|
|
89
|
+
});
|
|
90
|
+
this.isInitialized = true;
|
|
91
|
+
}
|
|
92
|
+
ngOnChanges() {
|
|
93
|
+
if (!this.isInitialized || !this.stackEntry)
|
|
94
|
+
return;
|
|
95
|
+
this.stackEntry = replaceStackEntry(this.stackEntry, {
|
|
96
|
+
style: this.style,
|
|
97
|
+
hidden: this.hidden,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
ngOnDestroy() {
|
|
101
|
+
if (this.stackEntry)
|
|
102
|
+
popStackEntry(this.stackEntry);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
return NavigationBar = _classThis;
|
|
106
|
+
})();
|
|
107
|
+
export { NavigationBar };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { INavigationBarProps } from './types';
|
|
2
|
+
export type INavigationBarStackEntry = INavigationBarProps;
|
|
3
|
+
/** Registers a mounted `<NavigationBar>` entry */
|
|
4
|
+
export declare function pushStackEntry(props: INavigationBarProps): INavigationBarStackEntry;
|
|
5
|
+
/** Unregisters an entry, falling back to the previous entries or the last imperative call */
|
|
6
|
+
export declare function popStackEntry(entry: INavigationBarStackEntry): void;
|
|
7
|
+
/** Swaps an entry's props in place, for a mounted component's own prop changes */
|
|
8
|
+
export declare function replaceStackEntry(entry: INavigationBarStackEntry, props: INavigationBarProps): INavigationBarStackEntry;
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
import { defaultNavigationBarProps, setHidden, setStyle, } from './navigation-bar.js';
|
|
5
|
+
const entriesStack = [];
|
|
6
|
+
let updateImmediate = null;
|
|
7
|
+
function mergeEntriesStack(stack) {
|
|
8
|
+
return stack.reduce((prev, cur) => ({
|
|
9
|
+
style: cur.style ?? prev.style,
|
|
10
|
+
hidden: cur.hidden ?? prev.hidden,
|
|
11
|
+
}), {});
|
|
12
|
+
}
|
|
13
|
+
function createStackEntry(props) {
|
|
14
|
+
return { style: props.style, hidden: props.hidden };
|
|
15
|
+
}
|
|
16
|
+
function updateEntriesStack() {
|
|
17
|
+
if (updateImmediate != null)
|
|
18
|
+
clearImmediate(updateImmediate);
|
|
19
|
+
updateImmediate = setImmediate(() => {
|
|
20
|
+
if (entriesStack.length === 0) {
|
|
21
|
+
setStyle(defaultNavigationBarProps.style);
|
|
22
|
+
setHidden(defaultNavigationBarProps.hidden);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { style, hidden } = mergeEntriesStack(entriesStack);
|
|
26
|
+
if (style != null)
|
|
27
|
+
setStyle(style);
|
|
28
|
+
if (hidden != null)
|
|
29
|
+
setHidden(hidden);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Registers a mounted `<NavigationBar>` entry */
|
|
33
|
+
export function pushStackEntry(props) {
|
|
34
|
+
const entry = createStackEntry(props);
|
|
35
|
+
entriesStack.push(entry);
|
|
36
|
+
updateEntriesStack();
|
|
37
|
+
return entry;
|
|
38
|
+
}
|
|
39
|
+
/** Unregisters an entry, falling back to the previous entries or the last imperative call */
|
|
40
|
+
export function popStackEntry(entry) {
|
|
41
|
+
const index = entriesStack.indexOf(entry);
|
|
42
|
+
if (index !== -1)
|
|
43
|
+
entriesStack.splice(index, 1);
|
|
44
|
+
updateEntriesStack();
|
|
45
|
+
}
|
|
46
|
+
/** Swaps an entry's props in place, for a mounted component's own prop changes */
|
|
47
|
+
export function replaceStackEntry(entry, props) {
|
|
48
|
+
const newEntry = createStackEntry(props);
|
|
49
|
+
const index = entriesStack.indexOf(entry);
|
|
50
|
+
if (index !== -1)
|
|
51
|
+
entriesStack[index] = newEntry;
|
|
52
|
+
updateEntriesStack();
|
|
53
|
+
return newEntry;
|
|
54
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { addVisibilityListener, getVisibilityAsync, setHidden, setStyle, setVisibilityAsync, } from './navigation-bar';
|
|
2
|
+
export { popStackEntry, pushStackEntry, replaceStackEntry, type INavigationBarStackEntry, } from './entries-stack';
|
|
3
|
+
export type { INavigationBarProps, INavigationBarStyle, INavigationBarVisibility, INavigationBarVisibilityEvent, } from './types';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { INavigationBarVisibility, INavigationBarVisibilityEvent } from '../types';
|
|
3
|
+
export type INativeNavigationBarModule = {
|
|
4
|
+
addListener(event: 'ExpoNavigationBar.didChange', listener: (event: INavigationBarVisibilityEvent) => void): EventSubscription;
|
|
5
|
+
setStyle(style: 'light' | 'dark'): Promise<void>;
|
|
6
|
+
setHidden(hidden: boolean): Promise<void>;
|
|
7
|
+
getVisibilityAsync(): Promise<INavigationBarVisibility>;
|
|
8
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { INavigationBarStyle, INavigationBarVisibility, INavigationBarVisibilityEvent } from './types';
|
|
3
|
+
export declare const defaultNavigationBarProps: {
|
|
4
|
+
style: INavigationBarStyle;
|
|
5
|
+
hidden: boolean;
|
|
6
|
+
};
|
|
7
|
+
/** Sets the navigation bar buttons' style */
|
|
8
|
+
export declare function setStyle(style: INavigationBarStyle): void;
|
|
9
|
+
/** Shows or hides the navigation bar */
|
|
10
|
+
export declare function setHidden(hidden: boolean): void;
|
|
11
|
+
/** Observes changes to the system navigation bar's visibility */
|
|
12
|
+
export declare function addVisibilityListener(listener: (event: INavigationBarVisibilityEvent) => void): EventSubscription;
|
|
13
|
+
/** Sets the navigation bar's visibility */
|
|
14
|
+
export declare function setVisibilityAsync(visibility: INavigationBarVisibility): Promise<void>;
|
|
15
|
+
/** Gets the navigation bar's current visibility */
|
|
16
|
+
export declare function getVisibilityAsync(): Promise<INavigationBarVisibility>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import { Appearance } from '@symbiote-native/engine';
|
|
3
|
+
import { expoNavigationBar } from './native-module';
|
|
4
|
+
function isLightColorScheme() {
|
|
5
|
+
return (Appearance.getColorScheme() ?? 'light') === 'light';
|
|
6
|
+
}
|
|
7
|
+
function resolveStyle(style) {
|
|
8
|
+
switch (style) {
|
|
9
|
+
case 'auto':
|
|
10
|
+
return isLightColorScheme() ? 'dark' : 'light';
|
|
11
|
+
case 'inverted':
|
|
12
|
+
return isLightColorScheme() ? 'light' : 'dark';
|
|
13
|
+
default:
|
|
14
|
+
return style;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
let currentStyle;
|
|
18
|
+
let currentHidden;
|
|
19
|
+
// The baseline the declarative `<NavigationBar>` stack falls back to once every mounted
|
|
20
|
+
// entry unmounts, kept in sync with every imperative `setStyle`/`setHidden` call too -
|
|
21
|
+
// mirrors upstream's own `defaultProps`, read by ./entries-stack.ts
|
|
22
|
+
export const defaultNavigationBarProps = {
|
|
23
|
+
style: 'light',
|
|
24
|
+
hidden: false,
|
|
25
|
+
};
|
|
26
|
+
/** Sets the navigation bar buttons' style */
|
|
27
|
+
export function setStyle(style) {
|
|
28
|
+
if (!expoNavigationBar.setStyle) {
|
|
29
|
+
throw new UnavailabilityError('NavigationBar', 'setStyle');
|
|
30
|
+
}
|
|
31
|
+
defaultNavigationBarProps.style = style;
|
|
32
|
+
const resolved = resolveStyle(style);
|
|
33
|
+
if (resolved === currentStyle)
|
|
34
|
+
return;
|
|
35
|
+
currentStyle = resolved;
|
|
36
|
+
expoNavigationBar.setStyle(resolved);
|
|
37
|
+
}
|
|
38
|
+
/** Shows or hides the navigation bar */
|
|
39
|
+
export function setHidden(hidden) {
|
|
40
|
+
if (!expoNavigationBar.setHidden) {
|
|
41
|
+
throw new UnavailabilityError('NavigationBar', 'setHidden');
|
|
42
|
+
}
|
|
43
|
+
defaultNavigationBarProps.hidden = hidden;
|
|
44
|
+
if (hidden === currentHidden)
|
|
45
|
+
return;
|
|
46
|
+
currentHidden = hidden;
|
|
47
|
+
expoNavigationBar.setHidden(hidden);
|
|
48
|
+
}
|
|
49
|
+
/** Observes changes to the system navigation bar's visibility */
|
|
50
|
+
export function addVisibilityListener(listener) {
|
|
51
|
+
if (!expoNavigationBar.addListener) {
|
|
52
|
+
throw new UnavailabilityError('NavigationBar', 'addVisibilityListener');
|
|
53
|
+
}
|
|
54
|
+
return expoNavigationBar.addListener('ExpoNavigationBar.didChange', listener);
|
|
55
|
+
}
|
|
56
|
+
/** Sets the navigation bar's visibility */
|
|
57
|
+
export async function setVisibilityAsync(visibility) {
|
|
58
|
+
if (!expoNavigationBar.setHidden) {
|
|
59
|
+
throw new UnavailabilityError('NavigationBar', 'setVisibilityAsync');
|
|
60
|
+
}
|
|
61
|
+
await expoNavigationBar.setHidden(visibility === 'hidden');
|
|
62
|
+
}
|
|
63
|
+
/** Gets the navigation bar's current visibility */
|
|
64
|
+
export async function getVisibilityAsync() {
|
|
65
|
+
if (!expoNavigationBar.getVisibilityAsync) {
|
|
66
|
+
throw new UnavailabilityError('NavigationBar', 'getVisibilityAsync');
|
|
67
|
+
}
|
|
68
|
+
return expoNavigationBar.getVisibilityAsync();
|
|
69
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type INavigationBarVisibility = 'visible' | 'hidden';
|
|
2
|
+
export type INavigationBarVisibilityEvent = {
|
|
3
|
+
/** Current navigation bar visibility */
|
|
4
|
+
visibility: INavigationBarVisibility;
|
|
5
|
+
/** Native Android system UI visibility state, from `setOnSystemUiVisibilityChangeListener` */
|
|
6
|
+
rawVisibility: number;
|
|
7
|
+
};
|
|
8
|
+
/** `auto`/`inverted` resolve against the current color scheme, `light`/`dark` are fixed */
|
|
9
|
+
export type INavigationBarStyle = 'auto' | 'inverted' | 'light' | 'dark';
|
|
10
|
+
export type INavigationBarProps = {
|
|
11
|
+
/** @default 'auto' */
|
|
12
|
+
style?: INavigationBarStyle;
|
|
13
|
+
hidden?: boolean;
|
|
14
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { useColorScheme } from '@symbiote-native/react';
|
|
3
|
+
import { popStackEntry, pushStackEntry, replaceStackEntry, } from '../core/index.js';
|
|
4
|
+
/** Declaratively configures the navigation bar - several mounted instances merge, last wins */
|
|
5
|
+
export function NavigationBar({ style, hidden }) {
|
|
6
|
+
const colorScheme = useColorScheme();
|
|
7
|
+
const stableProps = useMemo(() => ({ style, hidden }), [style, hidden]);
|
|
8
|
+
const stackEntryRef = useRef(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
stackEntryRef.current = pushStackEntry(stableProps);
|
|
11
|
+
return () => {
|
|
12
|
+
if (stackEntryRef.current)
|
|
13
|
+
popStackEntry(stackEntryRef.current);
|
|
14
|
+
};
|
|
15
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
16
|
+
}, []);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (stackEntryRef.current) {
|
|
19
|
+
stackEntryRef.current = replaceStackEntry(stackEntryRef.current, stableProps);
|
|
20
|
+
}
|
|
21
|
+
}, [colorScheme, stableProps]);
|
|
22
|
+
return null;
|
|
23
|
+
}
|