@symbiote-native/react 0.2.4 → 0.2.5
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.
|
@@ -1,89 +1,33 @@
|
|
|
1
|
-
// StatusBar on Android:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// StatusBar on Android: the declarative React half only. The native StatusBarManager driving
|
|
2
|
+
// (applyStatusBarProps), the imperative statics (statusBarImperative), and the Android
|
|
3
|
+
// currentHeight getter (statusBarCurrentHeight) all live in @symbiote-native/engine, shared
|
|
4
|
+
// verbatim with every adapter (Vue's status-bar.ts, Angular's status-bar.ts drive the exact
|
|
5
|
+
// same functions). This file supplies only what's React-specific: an FC that renders null and
|
|
6
|
+
// re-applies props via useEffect on mount + every prop change, with the imperative statics
|
|
7
|
+
// attached to the function object, exactly like RN's own StatusBar.
|
|
5
8
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// History: this used to be a no-op. Driving the window flags from the bridgeless surface
|
|
12
|
-
// blanked the app: a status-bar relayout triggered stopSurface, which threw "Global was
|
|
13
|
-
// not installed" because RN installs global.RN$stopSurface from its own renderer, which this
|
|
14
|
-
// renderer replaces. Now that render.ts installs RN$stopSurface and tears surfaces down cleanly, the
|
|
15
|
-
// relayout survives and the bar updates without blanking (verified on device: show/hide +
|
|
16
|
-
// light/dark text).
|
|
9
|
+
// Metro picks this file on an Android host, resolving '@symbiote-native/engine's relative
|
|
10
|
+
// './status-bar' import to its own index.android.ts in turn (Android's native module is a
|
|
11
|
+
// DIFFERENT shape from iOS — single-arg setHidden(hidden)/setStyle(style) plus
|
|
12
|
+
// setColor/setTranslucent — all handled inside that engine module, not here).
|
|
17
13
|
import { useEffect } from 'react';
|
|
18
|
-
import {
|
|
19
|
-
// processColor returns `unknown` (its result is platform-dependent); narrow to the
|
|
20
|
-
// number Fabric/native expects, like RN's invariant before setColor. A non-number
|
|
21
|
-
// (null for an unparseable color, headless identity passthrough of a string) is dropped.
|
|
22
|
-
function applyBackgroundColor(manager, color, animated) {
|
|
23
|
-
const processed = processColor(color);
|
|
24
|
-
if (typeof processed !== 'number') {
|
|
25
|
-
dlog(`StatusBar android: backgroundColor ${String(color)} did not process to an int — skipping`);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
// RISK: driving the window background flag once blanked the bridgeless surface
|
|
29
|
-
// (see header). Routed cleanly here but DEVICE-VERIFY-PENDING.
|
|
30
|
-
dlog(`StatusBar android setColor -> ${processed} animated=${animated}`);
|
|
31
|
-
manager.setColor(processed, animated);
|
|
32
|
-
}
|
|
14
|
+
import { applyStatusBarProps, statusBarImperative, statusBarCurrentHeight, } from '@symbiote-native/engine';
|
|
33
15
|
// Renders null and applies its props to the native module in an effect, on mount and on
|
|
34
|
-
// every prop change, same contract as iOS
|
|
35
|
-
const
|
|
36
|
-
const { barStyle, hidden, animated
|
|
16
|
+
// every prop change, same contract as iOS.
|
|
17
|
+
const StatusBarAndroidComponent = props => {
|
|
18
|
+
const { barStyle, hidden, animated, backgroundColor, translucent } = props;
|
|
37
19
|
useEffect(() => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
dlog('StatusBar android: StatusBarManager not resolvable — skipping');
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
dlog(`StatusBar android -> barStyle=${barStyle} hidden=${hidden} translucent=${translucent} bg=${String(backgroundColor)}`);
|
|
44
|
-
if (barStyle !== undefined)
|
|
45
|
-
manager.setStyle(barStyle);
|
|
46
|
-
if (hidden !== undefined)
|
|
47
|
-
manager.setHidden(hidden);
|
|
48
|
-
if (translucent !== undefined)
|
|
49
|
-
manager.setTranslucent(translucent);
|
|
50
|
-
if (backgroundColor !== undefined)
|
|
51
|
-
applyBackgroundColor(manager, backgroundColor, animated);
|
|
20
|
+
applyStatusBarProps(props);
|
|
21
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- deps are the primitive fields, not `props` itself, so an unchanged prop shape doesn't re-fire.
|
|
52
22
|
}, [barStyle, hidden, animated, backgroundColor, translucent]);
|
|
53
23
|
return null;
|
|
54
24
|
};
|
|
55
|
-
StatusBarAndroid
|
|
56
|
-
dlog(`StatusBar.setBarStyle android ${style}`);
|
|
57
|
-
getNativeModule(STATUS_BAR_MANAGER)?.setStyle(style);
|
|
58
|
-
};
|
|
59
|
-
StatusBarAndroid.setHidden = hidden => {
|
|
60
|
-
dlog(`StatusBar.setHidden android ${hidden}`);
|
|
61
|
-
getNativeModule(STATUS_BAR_MANAGER)?.setHidden(hidden);
|
|
62
|
-
};
|
|
63
|
-
StatusBarAndroid.setNetworkActivityIndicatorVisible = () => {
|
|
64
|
-
// No Android equivalent, an iOS-only concept.
|
|
65
|
-
dlog('StatusBar.setNetworkActivityIndicatorVisible (android no-op)');
|
|
66
|
-
};
|
|
67
|
-
StatusBarAndroid.setBackgroundColor = (color, animated = false) => {
|
|
68
|
-
dlog(`StatusBar.setBackgroundColor android ${String(color)}`);
|
|
69
|
-
const manager = getNativeModule(STATUS_BAR_MANAGER);
|
|
70
|
-
if (manager === null)
|
|
71
|
-
return;
|
|
72
|
-
applyBackgroundColor(manager, color, animated);
|
|
73
|
-
};
|
|
74
|
-
StatusBarAndroid.setTranslucent = translucent => {
|
|
75
|
-
// RISK: window translucent flag is device-verify-pending (may blank the surface).
|
|
76
|
-
dlog(`StatusBar.setTranslucent android ${translucent}`);
|
|
77
|
-
getNativeModule(STATUS_BAR_MANAGER)?.setTranslucent(translucent);
|
|
78
|
-
};
|
|
25
|
+
const StatusBarAndroid = Object.assign(StatusBarAndroidComponent, statusBarImperative);
|
|
79
26
|
// Android exposes the bar height as a native constant; undefined if the module or the
|
|
80
|
-
// constant is absent (
|
|
81
|
-
//
|
|
27
|
+
// constant is absent. Read lazily (getter, not a value) so nothing touches native at import
|
|
28
|
+
// time — the engine's Android impl resolves it on access.
|
|
82
29
|
Object.defineProperty(StatusBarAndroid, 'currentHeight', {
|
|
83
|
-
get
|
|
84
|
-
return getNativeModule(STATUS_BAR_MANAGER)?.getConstants?.()
|
|
85
|
-
.HEIGHT;
|
|
86
|
-
},
|
|
30
|
+
get: statusBarCurrentHeight,
|
|
87
31
|
enumerable: true,
|
|
88
32
|
});
|
|
89
33
|
export const StatusBar = StatusBarAndroid;
|
|
@@ -1,64 +1,29 @@
|
|
|
1
|
-
// StatusBar on iOS:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// boundary getNativeModule<T> resolves against. Metro picks this on an iOS host.
|
|
1
|
+
// StatusBar on iOS: the declarative React half only. The native `StatusBarManager`
|
|
2
|
+
// TurboModule driving (applyStatusBarProps), the imperative statics (statusBarImperative),
|
|
3
|
+
// and the iOS currentHeight stub all live in @symbiote-native/engine, shared verbatim with
|
|
4
|
+
// every adapter (Vue's status-bar.ts, Angular's status-bar.ts drive the exact same functions).
|
|
5
|
+
// This file supplies only what's React-specific: an FC that renders null and re-applies props
|
|
6
|
+
// via useEffect on mount + every prop change, with the imperative statics attached to the
|
|
7
|
+
// function object, exactly like RN's own StatusBar.
|
|
9
8
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// platform). The engine owns only the pure shared contract: types, constants, hideTransition.
|
|
9
|
+
// Metro picks this file on an iOS host; the headless/tsx base (status-bar/index.ts) re-exports
|
|
10
|
+
// it, which is also what a bare '@symbiote-native/engine' import resolves to under vitest — so
|
|
11
|
+
// this file's own engine import is already vitest-correct, no platform-selection subtlety here.
|
|
14
12
|
import { useEffect } from 'react';
|
|
15
|
-
import {
|
|
13
|
+
import { applyStatusBarProps, statusBarImperative } from '@symbiote-native/engine';
|
|
16
14
|
// StatusBar renders null and applies its props to the native module in an effect,
|
|
17
15
|
// on mount and on every prop change. Simplification vs RN: RN maintains a
|
|
18
16
|
// prop-merge stack so nested StatusBars compose (deepest/last wins). This module
|
|
19
17
|
// direct-applies a single component's props, which is correct for one StatusBar.
|
|
20
|
-
|
|
21
|
-
const { barStyle, hidden, animated
|
|
18
|
+
const StatusBarComponent = props => {
|
|
19
|
+
const { barStyle, hidden, animated, networkActivityIndicatorVisible } = props;
|
|
22
20
|
useEffect(() => {
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
// declarative StatusBar must NOT crash the whole render if the module can't resolve.
|
|
26
|
-
const manager = getNativeModule(STATUS_BAR_MANAGER);
|
|
27
|
-
if (manager === null) {
|
|
28
|
-
dlog('StatusBar: StatusBarManager not resolvable via __turboModuleProxy — skipping');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
dlog('StatusBar -> applying props to StatusBarManager');
|
|
32
|
-
if (barStyle !== undefined)
|
|
33
|
-
manager.setStyle(barStyle, animated);
|
|
34
|
-
if (hidden !== undefined)
|
|
35
|
-
manager.setHidden(hidden, hideTransition(animated));
|
|
36
|
-
if (networkActivityIndicatorVisible !== undefined) {
|
|
37
|
-
manager.setNetworkActivityIndicatorVisible(networkActivityIndicatorVisible);
|
|
38
|
-
}
|
|
21
|
+
applyStatusBarProps(props);
|
|
22
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- deps are the primitive fields, not `props` itself, so an unchanged prop shape doesn't re-fire.
|
|
39
23
|
}, [barStyle, hidden, animated, networkActivityIndicatorVisible]);
|
|
40
24
|
return null;
|
|
41
25
|
};
|
|
42
|
-
// The
|
|
43
|
-
// native module is a no-op.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
getNativeModule(STATUS_BAR_MANAGER)?.setStyle(style, animated);
|
|
47
|
-
};
|
|
48
|
-
StatusBar.setHidden = (hidden, animation = STATIC_HIDE_TRANSITION) => {
|
|
49
|
-
dlog('StatusBar.setHidden');
|
|
50
|
-
getNativeModule(STATUS_BAR_MANAGER)?.setHidden(hidden, animation);
|
|
51
|
-
};
|
|
52
|
-
StatusBar.setNetworkActivityIndicatorVisible = visible => {
|
|
53
|
-
dlog('StatusBar.setNetworkActivityIndicatorVisible');
|
|
54
|
-
getNativeModule(STATUS_BAR_MANAGER)?.setNetworkActivityIndicatorVisible(visible);
|
|
55
|
-
};
|
|
56
|
-
// Android-only on RN, inert on iOS (the iOS status bar has no background color and
|
|
57
|
-
// is never translucent in RN's sense). Present so the contract is platform-uniform.
|
|
58
|
-
StatusBar.setBackgroundColor = () => {
|
|
59
|
-
dlog('StatusBar.setBackgroundColor (ios no-op)');
|
|
60
|
-
};
|
|
61
|
-
StatusBar.setTranslucent = () => {
|
|
62
|
-
dlog('StatusBar.setTranslucent (ios no-op)');
|
|
63
|
-
};
|
|
64
|
-
// currentHeight is Android-only; absent on iOS (RN sets it to null on iOS).
|
|
26
|
+
// The imperative statics mirror the declarative component: non-throwing, a missing optional
|
|
27
|
+
// native module is a no-op. currentHeight is Android-only; absent on iOS (RN sets it to null
|
|
28
|
+
// on iOS), so it's intentionally not attached here.
|
|
29
|
+
export const StatusBar = Object.assign(StatusBarComponent, statusBarImperative);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "SymbioteNative's React adapter — a react-reconciler host config (mutation mode) driving real native iOS/Android views through stock React Native Fabric.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"react-reconciler": "^0.33.0",
|
|
39
|
-
"@symbiote-native/components": "0.2.
|
|
39
|
+
"@symbiote-native/components": "0.2.4",
|
|
40
40
|
"@symbiote-native/css-parser": "0.2.1",
|
|
41
|
-
"@symbiote-native/engine": "0.1.
|
|
41
|
+
"@symbiote-native/engine": "0.1.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": ">=19.0.0",
|