@symbiote-native/react 0.2.3 → 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.
@@ -19,7 +19,7 @@
19
19
  import { createElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from 'react';
20
20
  import { dlog } from '@symbiote-native/engine';
21
21
  import { DEFAULT_END_REACHED_THRESHOLD, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_START_REACHED_THRESHOLD, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, NO_CONTENT_LENGTH_SENT, averageMeasuredLength, buildListPlan, buildOffsets, buildViewabilityPairs, computeEndReached, computeStartReached, computeViewableSet, computeWindow, diffViewable, highestMeasuredIndex as findHighestMeasuredIndex, maxMinimumViewTime, offsetForIndex as resolveOffsetForIndex, readLayoutLength, readScrollOffset, throttleWindow, } from '@symbiote-native/components';
22
- import { ScrollView } from '../scroll-view/index.js';
22
+ import { ScrollView } from '../scroll-view';
23
23
  import { RefreshControl } from '../refresh-control/index.js';
24
24
  function resolveElement(component) {
25
25
  if (component === undefined)
package/build/index.js CHANGED
@@ -4,14 +4,14 @@
4
4
  export { View, Text } from './components.js';
5
5
  export { Image, setImageSourceResolver } from './components/image/index.js';
6
6
  export { ImageBackground } from './components/image-background/index.js';
7
- export { ScrollView } from './components/scroll-view/index.js';
7
+ export { ScrollView } from './components/scroll-view';
8
8
  export { TextInput } from './components/text-input/index.js';
9
9
  export { InputAccessoryView } from './components/input-accessory-view/index.js';
10
10
  export { Keyboard, KEYBOARD_EVENT } from './modules/keyboard.js';
11
11
  export { KeyboardAvoidingView } from './components/keyboard-avoiding-view/index.js';
12
- export { StatusBar } from './modules/status-bar/index.js';
13
- export { Switch } from './components/switch/index.js';
14
- export { ActivityIndicator } from './components/activity-indicator/index.js';
12
+ export { StatusBar } from './modules/status-bar';
13
+ export { Switch } from './components/switch';
14
+ export { ActivityIndicator } from './components/activity-indicator';
15
15
  export { SafeAreaView } from './components/safe-area-view/index.js';
16
16
  export { RefreshControl } from './components/refresh-control/index.js';
17
17
  export { Modal } from './components/modal/index.js';
@@ -68,7 +68,7 @@ export { ActionSheetIOS } from './modules/action-sheet-ios.js';
68
68
  export { Linking } from './modules/linking.js';
69
69
  export { Vibration } from './modules/vibration.js';
70
70
  export { Share } from './modules/share.js';
71
- export { AccessibilityInfo } from './modules/accessibility-info/index.js';
71
+ export { AccessibilityInfo } from './modules/accessibility-info';
72
72
  export { I18nManager } from './modules/i18n-manager.js';
73
73
  export { Settings } from './modules/settings.js';
74
74
  // Interaction subsystems: gestures, deferred work, and layout transitions.
@@ -6,7 +6,7 @@
6
6
  import { AnimatedValue, AnimatedValueXY, AnimatedColor, Easing, Platform, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, } from '@symbiote-native/engine';
7
7
  import { View, Text } from '../../components.js';
8
8
  import { Image } from '../../components/image/index.js';
9
- import { ScrollView } from '../../components/scroll-view/index.js';
9
+ import { ScrollView } from '../../components/scroll-view';
10
10
  import { FlatList } from '../../components/flat-list/index.js';
11
11
  import { SectionList } from '../../components/section-list/index.js';
12
12
  import { createAnimatedComponent } from './create-animated-component.js';
@@ -1,89 +1,33 @@
1
- // StatusBar on Android: drives the Android StatusBarManager from props (and from the
2
- // static methods). Android's native module is a DIFFERENT shape from iOS: single-arg
3
- // setHidden(hidden) / setStyle(style) plus setColor/setTranslucent, same module name
4
- // ('StatusBarManager'). Metro picks this on an Android host; iOS keeps its own shape.
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
- // The native-call logic stays in the adapter (not the engine): the platform module shape
7
- // diverges iOS/Android and the headless smokes import this file by name (tsx has no platform
8
- // picker, so routing through the engine barrel would resolve the iOS base on every platform).
9
- // The engine owns only the pure shared contract: types, constants, hideTransition.
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 { dlog, getNativeModule, processColor, STATUS_BAR_MANAGER, } from '@symbiote-native/engine';
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, with the single-arg Android setters.
35
- const StatusBarAndroid = props => {
36
- const { barStyle, hidden, animated = false, backgroundColor, translucent } = props;
16
+ // every prop change, same contract as iOS.
17
+ const StatusBarAndroidComponent = props => {
18
+ const { barStyle, hidden, animated, backgroundColor, translucent } = props;
37
19
  useEffect(() => {
38
- const manager = getNativeModule(STATUS_BAR_MANAGER);
39
- if (manager === null) {
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.setBarStyle = style => {
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 (older RN, or a fake that doesn't define getConstants). Read lazily
81
- // (getter, not a value) so nothing touches native at import time.
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: drives the iOS `StatusBarManager` TurboModule from its props
2
- // (and from static methods). The native contract matches React Native's own
3
- // NativeStatusBarManagerIOS spec:
4
- // setStyle(statusBarStyle?: string, animated: boolean)
5
- // setHidden(hidden: boolean, withAnimation: 'none' | 'fade' | 'slide')
6
- // setNetworkActivityIndicatorVisible(visible: boolean)
7
- // Only those three setters are mirrored as the hand-written interface, the typed trust
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
- // The native-call logic stays in the adapter (not the engine): the platform module shape
11
- // diverges iOS/Android and the headless smokes import this file by name (tsx has no
12
- // platform picker, so routing through the engine barrel would resolve the iOS base on every
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 { dlog, getNativeModule, STATUS_BAR_MANAGER, STATIC_HIDE_TRANSITION, hideTransition, } from '@symbiote-native/engine';
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
- export const StatusBar = props => {
21
- const { barStyle, hidden, animated = false, networkActivityIndicatorVisible } = props;
18
+ const StatusBarComponent = props => {
19
+ const { barStyle, hidden, animated, networkActivityIndicatorVisible } = props;
22
20
  useEffect(() => {
23
- // Resolve lazily inside the effect, not at import, keeps this module importable
24
- // headless before a fake __turboModuleProxy is installed. Non-enforcing: a
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 static API mirrors the declarative component: non-throwing. A missing optional
43
- // native module is a no-op.
44
- StatusBar.setBarStyle = (style, animated = false) => {
45
- dlog('StatusBar.setBarStyle');
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",
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.2",
39
+ "@symbiote-native/components": "0.2.4",
40
40
  "@symbiote-native/css-parser": "0.2.1",
41
- "@symbiote-native/engine": "0.1.3"
41
+ "@symbiote-native/engine": "0.1.5"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "react": ">=19.0.0",
@@ -48,7 +48,7 @@
48
48
  "@types/react": "^19.2.0",
49
49
  "@types/react-reconciler": "^0.32.2",
50
50
  "react": "19.2.3",
51
- "@symbiote-native/test-utils": "0.1.3"
51
+ "@symbiote-native/test-utils": "0.1.5"
52
52
  },
53
53
  "scripts": {
54
54
  "typecheck": "tsc --build",