@symbiote-native/react 0.1.0 → 0.2.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/README.md +19 -6
- package/build/bootstrap.d.ts +7 -0
- package/build/bootstrap.js +17 -0
- package/package.json +10 -6
- package/build/components/drawer-layout-android/index.android.d.ts +0 -3
- package/build/components/drawer-layout-android/index.android.js +0 -93
- package/build/components/drawer-layout-android/index.d.ts +0 -3
- package/build/components/drawer-layout-android/index.js +0 -19
- package/build/components/drawer-layout-android/shared.d.ts +0 -19
- package/build/components/drawer-layout-android/shared.js +0 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @symbiote-native/react
|
|
2
2
|
|
|
3
|
-
The **React adapter** for [
|
|
3
|
+
The **React adapter** for [SymbioteNative](../../README.md) — drive real native iOS/Android views from
|
|
4
4
|
React, with React Native's own renderer never in the path. It is a `react-reconciler` host config
|
|
5
5
|
in **mutation mode** (`appendChild` / `insertBefore` / `removeChild` → the engine's four-call
|
|
6
6
|
mutation API); `@symbiote-native/engine` does the clone-on-write commit into Fabric.
|
|
@@ -11,15 +11,28 @@ adapter, not the core.
|
|
|
11
11
|
|
|
12
12
|
<div align="center">
|
|
13
13
|
|
|
14
|
-

|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
> New to
|
|
18
|
+
> New to SymbioteNative? The [root README](../../README.md) has the architecture and the one fact it
|
|
19
19
|
> rests on — React is just *one client* of `nativeFabricUIManager`.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @symbiote-native/react react-native react
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`react-native` and `react` stay your app's own top-level dependencies — SymbioteNative doesn't hide
|
|
30
|
+
them, it only replaces the JS renderer that drives them. There's no `create-symbiote` scaffolder
|
|
31
|
+
yet, so the Metro config and the `index.js` entry seam below come from
|
|
32
|
+
[`examples/react`](../../examples/react) rather than a generator.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
23
36
|
## Use it
|
|
24
37
|
|
|
25
38
|
The app is ordinary React — it imports primitives from `@symbiote-native/react`, not `react-native`:
|
|
@@ -85,7 +98,7 @@ path (R1 + R2 + R3 — decision record 0009):
|
|
|
85
98
|
`interpolate` · `ValueXY` · tracking · `diffClamp`). Native offload is proven by jamming the JS
|
|
86
99
|
thread 1.5 s: the native-driven animations keep moving, the JS-driven one stalls (ADR 0016 · 0017).
|
|
87
100
|
- **Third-party native views** — `@react-native-community/slider` used straight from the package
|
|
88
|
-
with zero
|
|
101
|
+
with zero SymbioteNative metadata; the engine derives its events and prop processors from the library's
|
|
89
102
|
own ViewConfig at runtime — the "install the package, use its component" path.
|
|
90
103
|
- **Gestures & events** — the responder lifecycle (grant/move/release/terminate, LCA-scoped
|
|
91
104
|
re-negotiation), two-phase capture→bubble delivery, `Pressable` press-retention, `Touchable*`
|
|
@@ -100,7 +113,7 @@ path (R1 + R2 + R3 — decision record 0009):
|
|
|
100
113
|
### Android
|
|
101
114
|
|
|
102
115
|
The same canary runs on an Android emulator through the same `@symbiote-native/engine` core. Two signals
|
|
103
|
-
RN ties to a view host
|
|
116
|
+
RN ties to a view host SymbioteNative bypasses — or never shipped on Android — are re-supplied by a small
|
|
104
117
|
`@symbiote-native/android` native package (`KeyboardObserver` host shim; `SettingsManager` →
|
|
105
118
|
`SharedPreferences`). `Platform` and the component-name map are Metro-split per OS (`.ios` /
|
|
106
119
|
`.android`), so there is no `Platform.OS` runtime branch. iOS stays the reference surface (more
|
|
@@ -145,5 +158,5 @@ npm run e2e:test:ios # run the canary journeys on the iOS simulator
|
|
|
145
158
|
# …or the android equivalents: e2e:build:android / e2e:test:android
|
|
146
159
|
```
|
|
147
160
|
|
|
148
|
-
Why these come for free — a
|
|
161
|
+
Why these come for free — a SymbioteNative app is a stock RN app underneath, so RN's whole testing
|
|
149
162
|
ecosystem applies unchanged. See [Testing](../../README.md#testing).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import { type IBootstrapHostOptions } from '@symbiote-native/components/bootstrap';
|
|
3
|
+
export type { IBootstrapHostOptions } from '@symbiote-native/components/bootstrap';
|
|
4
|
+
export type IRegisterAppOptions = IBootstrapHostOptions & {
|
|
5
|
+
appName: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function registerApp(App: ComponentType<object>, options: IRegisterAppOptions): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Zero-config app entry: wires the four RN-backed host seams (via @symbiote-native/components'
|
|
2
|
+
// bootstrapHost) plus this adapter's own AppRegistry host-registrar bridge, then registers the
|
|
3
|
+
// root component — collapsing the canary's manual sequence into one call. Lives OUTSIDE the
|
|
4
|
+
// package's main barrel (see package.json's "./bootstrap" export): it imports react-native
|
|
5
|
+
// directly, which Vitest's Flow-unaware transform can't parse (see
|
|
6
|
+
// @symbiote-native/components/bootstrap for the full reason).
|
|
7
|
+
import { AppRegistry as RNAppRegistry } from 'react-native';
|
|
8
|
+
import { bootstrapHost } from '@symbiote-native/components/bootstrap';
|
|
9
|
+
import { AppRegistry, setHostRegistrar } from './modules/app-registry';
|
|
10
|
+
// Mirrors bare RN's own `AppRegistry.registerComponent(appName, () => App)` idiom, minus the
|
|
11
|
+
// manual host wiring in front of it.
|
|
12
|
+
export function registerApp(App, options) {
|
|
13
|
+
const { appName, ...bootstrapOptions } = options;
|
|
14
|
+
bootstrapHost(bootstrapOptions);
|
|
15
|
+
setHostRegistrar(RNAppRegistry);
|
|
16
|
+
AppRegistry.registerComponent(appName, () => App);
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
7
|
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./build/index.d.ts",
|
|
22
22
|
"default": "./build/index.js"
|
|
23
23
|
},
|
|
24
|
+
"./bootstrap": {
|
|
25
|
+
"types": "./build/bootstrap.d.ts",
|
|
26
|
+
"default": "./build/bootstrap.js"
|
|
27
|
+
},
|
|
24
28
|
"./metro-css-parser": "./metro-css-parser.cjs"
|
|
25
29
|
},
|
|
26
30
|
"files": [
|
|
@@ -32,9 +36,9 @@
|
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
34
38
|
"react-reconciler": "^0.33.0",
|
|
35
|
-
"@symbiote-native/components": "0.
|
|
36
|
-
"@symbiote-native/
|
|
37
|
-
"@symbiote-native/
|
|
39
|
+
"@symbiote-native/components": "0.2.0",
|
|
40
|
+
"@symbiote-native/css-parser": "0.1.1",
|
|
41
|
+
"@symbiote-native/engine": "0.1.1"
|
|
38
42
|
},
|
|
39
43
|
"peerDependencies": {
|
|
40
44
|
"react": ">=19.0.0",
|
|
@@ -44,7 +48,7 @@
|
|
|
44
48
|
"@types/react": "^19.2.0",
|
|
45
49
|
"@types/react-reconciler": "^0.32.2",
|
|
46
50
|
"react": "19.2.3",
|
|
47
|
-
"@symbiote-native/test-utils": "0.
|
|
51
|
+
"@symbiote-native/test-utils": "0.1.1"
|
|
48
52
|
},
|
|
49
53
|
"scripts": {
|
|
50
54
|
"typecheck": "tsc --build",
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { IDrawerLayoutAndroidHandle, IDrawerLayoutAndroidProps } from './shared';
|
|
2
|
-
export type { IDrawerPosition, IDrawerLockMode, IKeyboardDismissMode, IDrawerState, IDrawerSlideEvent, IDrawerLayoutAndroidProps, IDrawerLayoutAndroidHandle, } from './shared';
|
|
3
|
-
export declare const DrawerLayoutAndroid: import("react").ForwardRefExoticComponent<IDrawerLayoutAndroidProps & import("react").RefAttributes<IDrawerLayoutAndroidHandle>>;
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// DrawerLayoutAndroid: the real Android build. AndroidDrawerLayout is an ordinary
|
|
2
|
-
// Fabric host node: it lives in the SAME childSet and commits through the SAME
|
|
3
|
-
// completeRoot as the rest of the tree, exactly like Modal/Switch. There is no
|
|
4
|
-
// per-library glue; shared derives the view's events/processors from its ViewConfig,
|
|
5
|
-
// so we render the RAW Fabric name `AndroidDrawerLayout` (the derive-by-default path:
|
|
6
|
-
// any non-`symbiote-*` createElement type flows through untouched).
|
|
7
|
-
//
|
|
8
|
-
// Behavior mirrors RN's DrawerLayoutAndroid.android.js: the navigation view (from
|
|
9
|
-
// `renderNavigationView()`) and the main content are wrapped in two container Views,
|
|
10
|
-
// and, critically, the child order RN commits is [mainSubview, drawerSubview]: the
|
|
11
|
-
// content wrapper FIRST, the navigation wrapper SECOND (android render:
|
|
12
|
-
// {childrenWrapper}{drawerViewWrapper}). The navigation wrapper is absolutely
|
|
13
|
-
// positioned, drawerWidth-wide, and gated by pointerEvents so it stays untouchable
|
|
14
|
-
// until opened. `openDrawer`/`closeDrawer` are imperative, reached through a ref, and
|
|
15
|
-
// dispatched as the `openDrawer`/`closeDrawer` view commands against the host node,
|
|
16
|
-
// mirroring Switch's dispatchViewCommand path (Commands.openDrawer/closeDrawer in
|
|
17
|
-
// AndroidDrawerLayoutNativeComponent).
|
|
18
|
-
//
|
|
19
|
-
// The platform-invariant math — the host prop bag + the content/navigation wrapper styles, the
|
|
20
|
-
// slide/state event normalization, the imperative open/close handle, and the view/command NAMES —
|
|
21
|
-
// lives in @symbiote/components, shared verbatim with the Vue adapter; here React supplies only the
|
|
22
|
-
// lifecycle: a ref holds the host node, a `drawerOpened` state gates the navigation wrapper's
|
|
23
|
-
// pointerEvents, and useImperativeHandle wires the imperative handle.
|
|
24
|
-
//
|
|
25
|
-
// Metro picks this file on an Android host; off Android the base index.ts
|
|
26
|
-
// renders the fallback. No Platform.OS read; the filename is the selector (ADR 0019).
|
|
27
|
-
// device-verify-pending: the `AndroidDrawerLayout` name + the openDrawer/closeDrawer
|
|
28
|
-
// commands are RN-source-confirmed, not yet exercised on a real Android host.
|
|
29
|
-
import { createElement, forwardRef, useCallback, useImperativeHandle, useRef, useState, } from 'react';
|
|
30
|
-
import { dlog } from '@symbiote/engine';
|
|
31
|
-
import { buildDrawerHandle, DEFAULT_DRAWER_POSITION, offsetFromSlide, resolveAccessibilityProps, resolveDrawerLayout, stateFromChange, } from '@symbiote/components';
|
|
32
|
-
import { View } from '../../components';
|
|
33
|
-
export const DrawerLayoutAndroid = forwardRef((props, ref) => {
|
|
34
|
-
const { drawerWidth, drawerPosition, drawerLockMode, keyboardDismissMode, drawerBackgroundColor, statusBarBackgroundColor, onDrawerOpen, onDrawerClose, onDrawerSlide, onDrawerStateChanged, renderNavigationView, style, children, ...passthrough } = props;
|
|
35
|
-
const node = useRef(null);
|
|
36
|
-
// RN tracks drawerOpened to gate the navigation view's pointerEvents (android
|
|
37
|
-
// _onDrawerOpen/_onDrawerClose setState). Closed -> 'none' so the off-screen drawer
|
|
38
|
-
// never intercepts touches; open -> 'auto' (folded in by resolveDrawerLayout).
|
|
39
|
-
const [drawerOpened, setDrawerOpened] = useState(false);
|
|
40
|
-
// The imperative handle reads the node through a LAZY getter (() => node.current), not the node
|
|
41
|
-
// captured once: it is null until the element commits. The React twin of Vue's
|
|
42
|
-
// expose(buildDrawerHandle(…)).
|
|
43
|
-
useImperativeHandle(ref, () => buildDrawerHandle(() => node.current), []);
|
|
44
|
-
const handleDrawerOpen = useCallback(() => {
|
|
45
|
-
dlog('DrawerLayoutAndroid onDrawerOpen');
|
|
46
|
-
setDrawerOpened(true);
|
|
47
|
-
onDrawerOpen?.();
|
|
48
|
-
}, [onDrawerOpen]);
|
|
49
|
-
const handleDrawerClose = useCallback(() => {
|
|
50
|
-
dlog('DrawerLayoutAndroid onDrawerClose');
|
|
51
|
-
setDrawerOpened(false);
|
|
52
|
-
onDrawerClose?.();
|
|
53
|
-
}, [onDrawerClose]);
|
|
54
|
-
const handleDrawerSlide = useCallback((event) => {
|
|
55
|
-
onDrawerSlide?.({ offset: offsetFromSlide(event) });
|
|
56
|
-
}, [onDrawerSlide]);
|
|
57
|
-
const handleDrawerStateChanged = useCallback((event) => {
|
|
58
|
-
onDrawerStateChanged?.(stateFromChange(event));
|
|
59
|
-
}, [onDrawerStateChanged]);
|
|
60
|
-
const resolved = resolveDrawerLayout({
|
|
61
|
-
drawerWidth,
|
|
62
|
-
drawerPosition,
|
|
63
|
-
drawerLockMode,
|
|
64
|
-
keyboardDismissMode,
|
|
65
|
-
drawerBackgroundColor,
|
|
66
|
-
statusBarBackgroundColor,
|
|
67
|
-
drawerOpened,
|
|
68
|
-
style,
|
|
69
|
-
// The drawer renders a raw host node (not the View FC), so unlike View-backed components nothing
|
|
70
|
-
// else resolves aria-*/role — fold them into accessibility* here before passing through.
|
|
71
|
-
passthrough: resolveAccessibilityProps(passthrough),
|
|
72
|
-
});
|
|
73
|
-
// RN's mainSubview: content wrapped in an absolute box.
|
|
74
|
-
const contentWrapper = createElement(View, { style: resolved.contentWrapperStyle }, children);
|
|
75
|
-
// RN's drawerSubview: the navigation view wrapped, drawerWidth-wide, painted with
|
|
76
|
-
// drawerBackgroundColor, untouchable until opened.
|
|
77
|
-
const navigationWrapper = createElement(View, {
|
|
78
|
-
style: resolved.navigationWrapperStyle,
|
|
79
|
-
pointerEvents: resolved.navigationPointerEvents,
|
|
80
|
-
}, renderNavigationView());
|
|
81
|
-
dlog(`DrawerLayoutAndroid render position=${drawerPosition ?? DEFAULT_DRAWER_POSITION} ` +
|
|
82
|
-
`width=${String(drawerWidth)} opened=${drawerOpened}`);
|
|
83
|
-
// Child order matches RN exactly: content FIRST, navigation SECOND
|
|
84
|
-
// (android render emits {childrenWrapper}{drawerViewWrapper}).
|
|
85
|
-
return createElement(resolved.viewName, {
|
|
86
|
-
...resolved.hostProps,
|
|
87
|
-
ref: node,
|
|
88
|
-
onDrawerOpen: handleDrawerOpen,
|
|
89
|
-
onDrawerClose: handleDrawerClose,
|
|
90
|
-
onDrawerSlide: handleDrawerSlide,
|
|
91
|
-
onDrawerStateChanged: handleDrawerStateChanged,
|
|
92
|
-
}, contentWrapper, navigationWrapper);
|
|
93
|
-
});
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { IDrawerLayoutAndroidHandle, IDrawerLayoutAndroidProps } from './shared';
|
|
2
|
-
export type { IDrawerPosition, IDrawerLockMode, IKeyboardDismissMode, IDrawerState, IDrawerSlideEvent, IDrawerLayoutAndroidProps, IDrawerLayoutAndroidHandle, } from './shared';
|
|
3
|
-
export declare const DrawerLayoutAndroid: import("react").ForwardRefExoticComponent<IDrawerLayoutAndroidProps & import("react").RefAttributes<IDrawerLayoutAndroidHandle>>;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// DrawerLayoutAndroid: base / off-Android fallback. AndroidDrawerLayout is Android-
|
|
2
|
-
// only, so everywhere except an Android host (where Metro picks
|
|
3
|
-
// drawer-layout-android.android.ts) we render the content in a plain container and drop
|
|
4
|
-
// the navigation view (RN's DrawerLayoutAndroidFallback shape). The imperative
|
|
5
|
-
// open/close are silent no-ops; there is no drawer to drive. The filename is the
|
|
6
|
-
// selector; no Platform.OS read. The barrel imports './drawer-layout-android', which
|
|
7
|
-
// resolves here under tsc/tsx and to the .android file under Metro. See ADR 0019.
|
|
8
|
-
import { createElement, forwardRef, useImperativeHandle } from 'react';
|
|
9
|
-
import { dlog } from '@symbiote/engine';
|
|
10
|
-
import { View } from '../../components';
|
|
11
|
-
export const DrawerLayoutAndroid = forwardRef((props, ref) => {
|
|
12
|
-
const { style, children } = props;
|
|
13
|
-
useImperativeHandle(ref, () => ({
|
|
14
|
-
openDrawer: () => dlog('DrawerLayoutAndroid.openDrawer no-op: off Android'),
|
|
15
|
-
closeDrawer: () => dlog('DrawerLayoutAndroid.closeDrawer no-op: off Android'),
|
|
16
|
-
}), []);
|
|
17
|
-
dlog('DrawerLayoutAndroid fallback: off-Android host, rendering content only');
|
|
18
|
-
return createElement(View, { style }, children);
|
|
19
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { IAccessibilityProps, IAriaProps, IDrawerLockMode, IDrawerPosition, IDrawerSlideEvent, IDrawerState, IKeyboardDismissMode } from '@symbiote/components';
|
|
3
|
-
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
4
|
-
export type { IDrawerPosition, IDrawerLockMode, IKeyboardDismissMode, IDrawerState, IDrawerSlideEvent, IDrawerLayoutAndroidHandle, } from '@symbiote/components';
|
|
5
|
-
export interface IDrawerLayoutAndroidProps extends IAccessibilityProps, IAriaProps {
|
|
6
|
-
drawerWidth?: number;
|
|
7
|
-
drawerPosition?: IDrawerPosition;
|
|
8
|
-
drawerLockMode?: IDrawerLockMode;
|
|
9
|
-
keyboardDismissMode?: IKeyboardDismissMode;
|
|
10
|
-
drawerBackgroundColor?: string;
|
|
11
|
-
statusBarBackgroundColor?: string;
|
|
12
|
-
onDrawerOpen?: () => void;
|
|
13
|
-
onDrawerClose?: () => void;
|
|
14
|
-
onDrawerSlide?: (event: IDrawerSlideEvent) => void;
|
|
15
|
-
onDrawerStateChanged?: (state: IDrawerState) => void;
|
|
16
|
-
renderNavigationView: () => ReactNode;
|
|
17
|
-
style?: IStyleProp<IViewStyle>;
|
|
18
|
-
children?: ReactNode;
|
|
19
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
// Shared types for the React DrawerLayoutAndroid: the same Props/Handle contract the real Android
|
|
2
|
-
// build (index.android.ts) and the off-Android fallback (index.ts) both implement. The
|
|
3
|
-
// platform-agnostic enums + the imperative handle live in @symbiote/components (the single source of
|
|
4
|
-
// truth, shared verbatim with the Vue adapter); this file adds only the React-facing prop surface and
|
|
5
|
-
// re-exports the agnostic types so the barrel and both builds keep importing them from one local path.
|
|
6
|
-
// Per ADR 0019 the filename selects the build; no Platform.OS read anywhere.
|
|
7
|
-
export {};
|