@symbiote-native/react 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 +149 -0
- package/build/components/activity-indicator/index.android.d.ts +2 -0
- package/build/components/activity-indicator/index.android.js +13 -0
- package/build/components/activity-indicator/index.d.ts +1 -0
- package/build/components/activity-indicator/index.ios.d.ts +2 -0
- package/build/components/activity-indicator/index.ios.js +10 -0
- package/build/components/activity-indicator/index.js +4 -0
- package/build/components/activity-indicator/shared.d.ts +7 -0
- package/build/components/activity-indicator/shared.js +20 -0
- package/build/components/button.d.ts +6 -0
- package/build/components/button.js +42 -0
- package/build/components/drawer-layout-android/index.android.d.ts +3 -0
- package/build/components/drawer-layout-android/index.android.js +93 -0
- package/build/components/drawer-layout-android/index.d.ts +3 -0
- package/build/components/drawer-layout-android/index.js +19 -0
- package/build/components/drawer-layout-android/shared.d.ts +19 -0
- package/build/components/drawer-layout-android/shared.js +7 -0
- package/build/components/flat-list/index.d.ts +73 -0
- package/build/components/flat-list/index.js +86 -0
- package/build/components/image/index.d.ts +9 -0
- package/build/components/image/index.js +28 -0
- package/build/components/image-background/index.d.ts +10 -0
- package/build/components/image-background/index.js +46 -0
- package/build/components/input-accessory-view/index.d.ts +11 -0
- package/build/components/input-accessory-view/index.js +14 -0
- package/build/components/keyboard-avoiding-view/index.d.ts +17 -0
- package/build/components/keyboard-avoiding-view/index.js +85 -0
- package/build/components/modal/index.d.ts +24 -0
- package/build/components/modal/index.js +56 -0
- package/build/components/pressable/index.d.ts +30 -0
- package/build/components/pressable/index.js +100 -0
- package/build/components/refresh-control/index.d.ts +17 -0
- package/build/components/refresh-control/index.js +21 -0
- package/build/components/safe-area-view/index.d.ts +11 -0
- package/build/components/safe-area-view/index.js +18 -0
- package/build/components/scroll-view/index.android.d.ts +3 -0
- package/build/components/scroll-view/index.android.js +53 -0
- package/build/components/scroll-view/index.d.ts +1 -0
- package/build/components/scroll-view/index.ios.d.ts +3 -0
- package/build/components/scroll-view/index.ios.js +27 -0
- package/build/components/scroll-view/index.js +4 -0
- package/build/components/scroll-view/shared.d.ts +98 -0
- package/build/components/scroll-view/shared.js +195 -0
- package/build/components/scroll-view/sticky-header.d.ts +9 -0
- package/build/components/scroll-view/sticky-header.js +161 -0
- package/build/components/section-list/index.d.ts +8 -0
- package/build/components/section-list/index.js +12 -0
- package/build/components/switch/index.android.d.ts +2 -0
- package/build/components/switch/index.android.js +12 -0
- package/build/components/switch/index.d.ts +1 -0
- package/build/components/switch/index.ios.d.ts +2 -0
- package/build/components/switch/index.ios.js +10 -0
- package/build/components/switch/index.js +6 -0
- package/build/components/switch/shared.d.ts +10 -0
- package/build/components/switch/shared.js +59 -0
- package/build/components/text-input/index.d.ts +50 -0
- package/build/components/text-input/index.js +167 -0
- package/build/components/touchable/index.d.ts +20 -0
- package/build/components/touchable/index.js +95 -0
- package/build/components/touchable-native-feedback/index.d.ts +18 -0
- package/build/components/touchable-native-feedback/index.js +32 -0
- package/build/components/virtualized-list/index.d.ts +73 -0
- package/build/components/virtualized-list/index.js +552 -0
- package/build/components/virtualized-section-list/index.d.ts +66 -0
- package/build/components/virtualized-section-list/index.js +99 -0
- package/build/components.d.ts +54 -0
- package/build/components.js +17 -0
- package/build/create-portal.d.ts +4 -0
- package/build/create-portal.js +23 -0
- package/build/create-tunnel.d.ts +12 -0
- package/build/create-tunnel.js +60 -0
- package/build/descriptor-to-react/index.d.ts +3 -0
- package/build/descriptor-to-react/index.js +15 -0
- package/build/hooks/use-color-scheme.d.ts +2 -0
- package/build/hooks/use-color-scheme.js +13 -0
- package/build/hooks/use-window-dimensions.d.ts +2 -0
- package/build/hooks/use-window-dimensions.js +30 -0
- package/build/host-config.d.ts +7 -0
- package/build/host-config.js +169 -0
- package/build/host-instance.d.ts +3 -0
- package/build/host-instance.js +20 -0
- package/build/index.d.ts +98 -0
- package/build/index.js +85 -0
- package/build/modules/accessibility-info/index.android.d.ts +2 -0
- package/build/modules/accessibility-info/index.android.js +4 -0
- package/build/modules/accessibility-info/index.d.ts +1 -0
- package/build/modules/accessibility-info/index.ios.d.ts +2 -0
- package/build/modules/accessibility-info/index.ios.js +3 -0
- package/build/modules/accessibility-info/index.js +5 -0
- package/build/modules/accessibility-info/shared.d.ts +1 -0
- package/build/modules/accessibility-info/shared.js +1 -0
- package/build/modules/action-sheet-ios.d.ts +2 -0
- package/build/modules/action-sheet-ios.js +4 -0
- package/build/modules/alert.d.ts +2 -0
- package/build/modules/alert.js +5 -0
- package/build/modules/animated/create-animated-component.d.ts +12 -0
- package/build/modules/animated/create-animated-component.js +125 -0
- package/build/modules/animated/index.d.ts +52 -0
- package/build/modules/animated/index.js +84 -0
- package/build/modules/app-registry/index.d.ts +7 -0
- package/build/modules/app-registry/index.js +26 -0
- package/build/modules/app-state.d.ts +2 -0
- package/build/modules/app-state.js +3 -0
- package/build/modules/appearance.d.ts +2 -0
- package/build/modules/appearance.js +3 -0
- package/build/modules/back-handler.d.ts +2 -0
- package/build/modules/back-handler.js +3 -0
- package/build/modules/dimensions.d.ts +2 -0
- package/build/modules/dimensions.js +3 -0
- package/build/modules/i18n-manager.d.ts +2 -0
- package/build/modules/i18n-manager.js +4 -0
- package/build/modules/keyboard.d.ts +2 -0
- package/build/modules/keyboard.js +3 -0
- package/build/modules/layout-animation.d.ts +2 -0
- package/build/modules/layout-animation.js +3 -0
- package/build/modules/linking.d.ts +2 -0
- package/build/modules/linking.js +4 -0
- package/build/modules/permissions-android.d.ts +2 -0
- package/build/modules/permissions-android.js +3 -0
- package/build/modules/settings.d.ts +1 -0
- package/build/modules/settings.js +4 -0
- package/build/modules/share.d.ts +2 -0
- package/build/modules/share.js +4 -0
- package/build/modules/status-bar/index.android.d.ts +3 -0
- package/build/modules/status-bar/index.android.js +89 -0
- package/build/modules/status-bar/index.d.ts +1 -0
- package/build/modules/status-bar/index.ios.d.ts +3 -0
- package/build/modules/status-bar/index.ios.js +64 -0
- package/build/modules/status-bar/index.js +4 -0
- package/build/modules/status-bar/shared.d.ts +6 -0
- package/build/modules/status-bar/shared.js +6 -0
- package/build/modules/text-input-state.d.ts +1 -0
- package/build/modules/text-input-state.js +3 -0
- package/build/modules/toast-android.d.ts +1 -0
- package/build/modules/toast-android.js +4 -0
- package/build/modules/vibration.d.ts +1 -0
- package/build/modules/vibration.js +4 -0
- package/build/reconciler-constants.d.ts +7 -0
- package/build/reconciler-constants.js +12 -0
- package/build/render.d.ts +7 -0
- package/build/render.js +60 -0
- package/build/utils/responder-props.d.ts +1 -0
- package/build/utils/responder-props.js +1 -0
- package/build/utils/styles.d.ts +1 -0
- package/build/utils/styles.js +1 -0
- package/metro-css-parser.cjs +11 -0
- package/package.json +53 -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,149 @@
|
|
|
1
|
+
# @symbiote-native/react
|
|
2
|
+
|
|
3
|
+
The **React adapter** for [SymbioteJS](../../README.md) — drive real native iOS/Android views from
|
|
4
|
+
React, with React Native's own renderer never in the path. It is a `react-reconciler` host config
|
|
5
|
+
in **mutation mode** (`appendChild` / `insertBefore` / `removeChild` → the engine's four-call
|
|
6
|
+
mutation API); `@symbiote-native/engine` does the clone-on-write commit into Fabric.
|
|
7
|
+
|
|
8
|
+
This is the **reference adapter**: the known-good driver used to validate the native pipe and the
|
|
9
|
+
commit engine before any other framework lands, so a break in Vue/Svelte/Solid isolates to *that*
|
|
10
|
+
adapter, not the core.
|
|
11
|
+
|
|
12
|
+
<div align="center">
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
> New to SymbioteJS? The [root README](../../README.md) has the architecture and the one fact it
|
|
19
|
+
> rests on — React is just *one client* of `nativeFabricUIManager`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Use it
|
|
24
|
+
|
|
25
|
+
The app is ordinary React — it imports primitives from `@symbiote-native/react`, not `react-native`:
|
|
26
|
+
|
|
27
|
+
```jsx
|
|
28
|
+
import { useState } from 'react';
|
|
29
|
+
import { View, Text, Pressable } from '@symbiote-native/react';
|
|
30
|
+
|
|
31
|
+
export default function App() {
|
|
32
|
+
const [count, setCount] = useState(0);
|
|
33
|
+
return (
|
|
34
|
+
<View style={{ padding: 24 }}>
|
|
35
|
+
<Text>Taps: {count}</Text>
|
|
36
|
+
<Pressable onPress={() => setCount((c) => c + 1)}>
|
|
37
|
+
<Text>Tap me</Text>
|
|
38
|
+
</Pressable>
|
|
39
|
+
</View>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The native entry registers a low-level *runnable* instead of a React component: RN's Fabric host
|
|
45
|
+
calls it with the surface's `rootTag`, and the renderer takes over from there — `nativeFabric-
|
|
46
|
+
UIManager` is driven directly, RN's own renderer never runs.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// index.js
|
|
50
|
+
import { AppRegistry, processColor } from 'react-native';
|
|
51
|
+
import { createElement } from 'react';
|
|
52
|
+
import { mount } from '@symbiote-native/react';
|
|
53
|
+
import { setColorProcessor } from '@symbiote-native/engine';
|
|
54
|
+
import App from './App';
|
|
55
|
+
import { name as appName } from './app.json';
|
|
56
|
+
|
|
57
|
+
// Colors reach Fabric as platform ints; let the engine use RN's own converter.
|
|
58
|
+
setColorProcessor(processColor);
|
|
59
|
+
|
|
60
|
+
// registerRunnable (not registerComponent): RN stores a raw mount callback and never renders
|
|
61
|
+
// it with its own renderer.
|
|
62
|
+
AppRegistry.registerRunnable(appName, ({ rootTag }) => {
|
|
63
|
+
mount(rootTag, createElement(App));
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The runnable example is [`examples/react`](../../examples/react) — a stock RN 0.86 app whose full
|
|
68
|
+
canary ([`App.tsx`](../../examples/react/App.tsx)) exercises every block of the surface below.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Surface — verified on device, iOS + Android
|
|
73
|
+
|
|
74
|
+
Every interaction commits through `@symbiote-native/engine` into Fabric, with RN's renderer never in the
|
|
75
|
+
path (R1 + R2 + R3 — decision record 0009):
|
|
76
|
+
|
|
77
|
+
- **Primitives** — `View` · `Text` · `Image` · `ImageBackground` · `ScrollView` · `TextInput` ·
|
|
78
|
+
`Pressable` · `Touchable*` · `Button` · `Switch` · `Modal` · `ActivityIndicator` ·
|
|
79
|
+
`SafeAreaView` · `RefreshControl` · `FlatList` · `SectionList` · `VirtualizedList`.
|
|
80
|
+
- **Runtime modules** — `Platform` · `StyleSheet` (incl. `hairlineWidth`) · `Dimensions` ·
|
|
81
|
+
`Appearance` · `PixelRatio` · `AppState`, plus imperative `Alert` · `ActionSheetIOS` · `Share` ·
|
|
82
|
+
`Linking` · `Vibration` · `Keyboard` · `StatusBar` — each reaching its real native module on the
|
|
83
|
+
bridgeless host.
|
|
84
|
+
- **`Animated`, both drivers** — JS *and* native driver side by side (`timing` · `spring` · `loop` ·
|
|
85
|
+
`interpolate` · `ValueXY` · tracking · `diffClamp`). Native offload is proven by jamming the JS
|
|
86
|
+
thread 1.5 s: the native-driven animations keep moving, the JS-driven one stalls (ADR 0016 · 0017).
|
|
87
|
+
- **Third-party native views** — `@react-native-community/slider` used straight from the package
|
|
88
|
+
with zero SymbioteJS metadata; the engine derives its events and prop processors from the library's
|
|
89
|
+
own ViewConfig at runtime — the "install the package, use its component" path.
|
|
90
|
+
- **Gestures & events** — the responder lifecycle (grant/move/release/terminate, LCA-scoped
|
|
91
|
+
re-negotiation), two-phase capture→bubble delivery, `Pressable` press-retention, `Touchable*`
|
|
92
|
+
delays, and `PanResponder` — all in the engine.
|
|
93
|
+
- **Accessibility** — the `accessibility*` / ARIA prop layer (roles, labels, states, focus,
|
|
94
|
+
grouping) verified against the platform a11y tree on both platforms.
|
|
95
|
+
- **Modern styling** — RN's JS style processors (`boxShadow` · `filter` · `transform` ·
|
|
96
|
+
`transformOrigin` · `aspectRatio` · `fontVariant`) and color resolution run in the engine, so
|
|
97
|
+
CSS-style props commit correctly on **both** platforms — not just iOS tolerating a raw string
|
|
98
|
+
while Android's strict native converter crashes on it.
|
|
99
|
+
|
|
100
|
+
### Android
|
|
101
|
+
|
|
102
|
+
The same canary runs on an Android emulator through the same `@symbiote-native/engine` core. Two signals
|
|
103
|
+
RN ties to a view host SymbioteJS bypasses — or never shipped on Android — are re-supplied by a small
|
|
104
|
+
`@symbiote-native/android` native package (`KeyboardObserver` host shim; `SettingsManager` →
|
|
105
|
+
`SharedPreferences`). `Platform` and the component-name map are Metro-split per OS (`.ios` /
|
|
106
|
+
`.android`), so there is no `Platform.OS` runtime branch. iOS stays the reference surface (more
|
|
107
|
+
real-hardware time, wider prop-edge coverage); `ActionSheetIOS` is iOS-only by design, and
|
|
108
|
+
`Vibration` needs the app to declare the `VIBRATE` permission (RN's standard requirement, owned by
|
|
109
|
+
the future scaffolder).
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Run it
|
|
114
|
+
|
|
115
|
+
[`examples/react`](../../examples/react) is a stock React Native 0.86 app. Requires Node ≥ 22 and
|
|
116
|
+
the [RN environment setup](https://reactnative.dev/docs/set-up-your-environment) (Xcode, CocoaPods):
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd examples/react
|
|
120
|
+
npm install
|
|
121
|
+
bundle install # first time only — installs CocoaPods itself
|
|
122
|
+
bundle exec pod install # (cd ios && pod install) — fetch native pods
|
|
123
|
+
|
|
124
|
+
# terminal 1 — Metro. DEBUG=1 turns on diagnostic logs (Babel inlines it).
|
|
125
|
+
DEBUG=1 npm start --reset-cache
|
|
126
|
+
|
|
127
|
+
# terminal 2 — build + launch
|
|
128
|
+
npm run ios # iOS simulator (the reference surface)
|
|
129
|
+
npm run android # Android emulator (manual-links @symbiote-native/android)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Press <kbd>R</kbd> in the simulator to reload. Because `DEBUG` is Babel-inlined into the bundle,
|
|
133
|
+
changing it requires restarting Metro with `--reset-cache`.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Test it
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm test # vitest, from the workspace root — headless, fake Fabric slot
|
|
141
|
+
|
|
142
|
+
cd examples/react
|
|
143
|
+
npm run e2e:build:ios # build the app for Detox (once per native change)
|
|
144
|
+
npm run e2e:test:ios # run the canary journeys on the iOS simulator
|
|
145
|
+
# …or the android equivalents: e2e:build:android / e2e:test:android
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Why these come for free — a SymbioteJS app is a stock RN app underneath, so RN's whole testing
|
|
149
|
+
ecosystem applies unchanged. See [Testing](../../README.md#testing).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// ActivityIndicator on Android: AndroidProgressBar needs `styleAttr` (it drives the
|
|
2
|
+
// ProgressBar's setStyle(); without it the view throws "setStyle() not called") and
|
|
3
|
+
// `indeterminate: true`, and its default color is the theme (null), per RN's
|
|
4
|
+
// ActivityIndicator.js android branch ({styleAttr:'Normal', indeterminate:true}). Metro
|
|
5
|
+
// picks this on an Android host; no Platform.OS read. See ADR 0020.
|
|
6
|
+
// device-verify-pending: prop names mirror RN's AndroidProgressBar, proven on a real
|
|
7
|
+
// host by the absence of the "setStyle() not called" red box.
|
|
8
|
+
import { createActivityIndicator } from './shared';
|
|
9
|
+
export const ActivityIndicator = createActivityIndicator({
|
|
10
|
+
// RN: `color = Platform.OS === 'ios' ? GRAY : null`; Android lets the theme color it.
|
|
11
|
+
defaultColor: null,
|
|
12
|
+
nativeExtras: { styleAttr: 'Normal', indeterminate: true },
|
|
13
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.ios';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// ActivityIndicator on iOS: RCTActivityIndicatorView takes the size enum + a GRAY
|
|
2
|
+
// default color and no extra native props. Also the base (activity-indicator.ts
|
|
3
|
+
// re-exports it) for headless / web. See ADR 0020.
|
|
4
|
+
import { createActivityIndicator } from './shared';
|
|
5
|
+
// RN's iOS default spinner color (Libraries/.../ActivityIndicator.js GRAY).
|
|
6
|
+
const IOS_DEFAULT_COLOR = '#999999';
|
|
7
|
+
export const ActivityIndicator = createActivityIndicator({
|
|
8
|
+
defaultColor: IOS_DEFAULT_COLOR,
|
|
9
|
+
nativeExtras: {},
|
|
10
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Base / default ActivityIndicator: re-exports the iOS build. Metro overrides this with
|
|
2
|
+
// activity-indicator.ios.ts / .android.ts on a real host; under tsx / tsc / web the host
|
|
3
|
+
// config resolves here. Filename is the selector, no Platform.OS read. See ADR 0020.
|
|
4
|
+
export * from './index.ios';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { IActivityIndicatorPlatform, IActivityIndicatorProps as IActivityIndicatorBaseProps } from '@symbiote-native/components';
|
|
3
|
+
export type { IActivityIndicatorPlatform } from '@symbiote-native/components';
|
|
4
|
+
export type IActivityIndicatorProps = IActivityIndicatorBaseProps & {
|
|
5
|
+
className?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function createActivityIndicator(platform: IActivityIndicatorPlatform): FC<IActivityIndicatorProps>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ActivityIndicator is the React lifecycle half. The render (size translation, wrapper +
|
|
2
|
+
// spinner, color omission) lives framework-agnostic in @symbiote-native/components; here the
|
|
3
|
+
// adapter only resolves props (accessibility, defaults), calls the render fn, and bridges
|
|
4
|
+
// the Descriptor to a React element. No state, no effects: this component is render-only.
|
|
5
|
+
//
|
|
6
|
+
// The per-platform bits (iOS GRAY default + no extras; Android theme/null + styleAttr +
|
|
7
|
+
// indeterminate) are supplied by the .ios/.android files via `IActivityIndicatorPlatform`.
|
|
8
|
+
import { renderActivityIndicator, resolveAccessibilityProps } from '@symbiote-native/components';
|
|
9
|
+
import { descriptorToReact } from '../../descriptor-to-react';
|
|
10
|
+
export function createActivityIndicator(platform) {
|
|
11
|
+
return rawProps => {
|
|
12
|
+
// The wrapper is a raw symbiote-view, not the View FC, so it never runs
|
|
13
|
+
// resolveAccessibilityProps itself, so fold aria/role here, then forward the resolved
|
|
14
|
+
// accessibility* surface (plus testID / onLayout) onto the wrapper via `passthrough`.
|
|
15
|
+
const props = resolveAccessibilityProps(rawProps);
|
|
16
|
+
const { animating = true, color, hidesWhenStopped = true, size = 'small', style, ...passthrough } = props;
|
|
17
|
+
const descriptor = renderActivityIndicator({ animating, color, hidesWhenStopped, size, style, passthrough }, platform);
|
|
18
|
+
return descriptorToReact(descriptor);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Button is the minimal cross-platform button, rendered in its iOS shape per RN's Button.js: a
|
|
2
|
+
// TouchableOpacity wrapping a Text. The base text style, the role constant, and the color fold
|
|
3
|
+
// (caller color tints the label; disabled greys it) are shared in @symbiote-native/components/view; here
|
|
4
|
+
// React only composes its TouchableOpacity + Text and forwards the native-only props.
|
|
5
|
+
import { createElement } from 'react';
|
|
6
|
+
import { Text } from '../components';
|
|
7
|
+
import { TouchableOpacity } from './touchable';
|
|
8
|
+
import { BUTTON_ACCESSIBILITY_ROLE, resolveButtonTextStyle } from '@symbiote-native/components';
|
|
9
|
+
export const Button = props => {
|
|
10
|
+
const { title, onPress, color, disabled, touchSoundDisabled, testID, hasTVPreferredFocus, nextFocusDown, nextFocusForward, nextFocusLeft, nextFocusRight, nextFocusUp, ...accessibilityRest } = props;
|
|
11
|
+
const textStyle = resolveButtonTextStyle(color, disabled);
|
|
12
|
+
// The pressable / native View props TouchableOpacity does not type but forwards to Fabric
|
|
13
|
+
// (testID + TV-focus). Carried as a plain record (the pass-through idiom Image uses) so
|
|
14
|
+
// excess-property typing does not reject the native-only keys. TV-focus is inert on a phone.
|
|
15
|
+
const nativeForward = { testID };
|
|
16
|
+
if (hasTVPreferredFocus !== undefined)
|
|
17
|
+
nativeForward.hasTVPreferredFocus = hasTVPreferredFocus;
|
|
18
|
+
if (nextFocusDown !== undefined)
|
|
19
|
+
nativeForward.nextFocusDown = nextFocusDown;
|
|
20
|
+
if (nextFocusForward !== undefined)
|
|
21
|
+
nativeForward.nextFocusForward = nextFocusForward;
|
|
22
|
+
if (nextFocusLeft !== undefined)
|
|
23
|
+
nativeForward.nextFocusLeft = nextFocusLeft;
|
|
24
|
+
if (nextFocusRight !== undefined)
|
|
25
|
+
nativeForward.nextFocusRight = nextFocusRight;
|
|
26
|
+
if (nextFocusUp !== undefined)
|
|
27
|
+
nativeForward.nextFocusUp = nextFocusUp;
|
|
28
|
+
// RN's Button sets role=button, is accessible, and propagates the disabled accessibility state.
|
|
29
|
+
// The caller's accessibility props pass through, but Button's fixed role / accessible /
|
|
30
|
+
// disabled-state win, applied after the spread. touchSoundDisabled maps to the pressable's
|
|
31
|
+
// android_disableSound.
|
|
32
|
+
return createElement(TouchableOpacity, {
|
|
33
|
+
...accessibilityRest,
|
|
34
|
+
...nativeForward,
|
|
35
|
+
onPress,
|
|
36
|
+
disabled,
|
|
37
|
+
android_disableSound: touchSoundDisabled,
|
|
38
|
+
accessibilityRole: BUTTON_ACCESSIBILITY_ROLE,
|
|
39
|
+
accessible: true,
|
|
40
|
+
accessibilityState: { disabled },
|
|
41
|
+
}, createElement(Text, { style: textStyle }, title));
|
|
42
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
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>>;
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
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>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ComponentType, type ReactElement, type ReactNode, type Ref } from 'react';
|
|
2
|
+
import { type ISymbioteEvent } from '@symbiote-native/engine';
|
|
3
|
+
import { type ISeparators, type ISeparatorProps, type IViewabilityConfig, type IViewabilityConfigCallbackPair, type IViewableItemsChangedInfo, type IVirtualizedListHandle } from '../virtualized-list';
|
|
4
|
+
import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
|
|
5
|
+
import type { IStyleProp, IViewStyle } from '../../utils/styles';
|
|
6
|
+
type IRenderItem<ItemT> = (info: {
|
|
7
|
+
item: ItemT;
|
|
8
|
+
index: number;
|
|
9
|
+
separators: ISeparators;
|
|
10
|
+
}) => ReactNode;
|
|
11
|
+
export type IFlatListHandle = IVirtualizedListHandle;
|
|
12
|
+
export interface IFlatListProps<ItemT> extends IAccessibilityProps, IAriaProps {
|
|
13
|
+
data: readonly ItemT[];
|
|
14
|
+
renderItem: IRenderItem<ItemT>;
|
|
15
|
+
keyExtractor?: (item: ItemT, index: number) => string;
|
|
16
|
+
getItemLayout?: (data: unknown, index: number) => {
|
|
17
|
+
length: number;
|
|
18
|
+
offset: number;
|
|
19
|
+
index: number;
|
|
20
|
+
};
|
|
21
|
+
numColumns?: number;
|
|
22
|
+
columnWrapperStyle?: IStyleProp<IViewStyle> | string;
|
|
23
|
+
ItemSeparatorComponent?: ComponentType<ISeparatorProps<ItemT>>;
|
|
24
|
+
ListHeaderComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
25
|
+
ListFooterComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
26
|
+
ListEmptyComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
27
|
+
horizontal?: boolean;
|
|
28
|
+
inverted?: boolean;
|
|
29
|
+
extraData?: unknown;
|
|
30
|
+
onEndReached?: (info: {
|
|
31
|
+
distanceFromEnd: number;
|
|
32
|
+
}) => void;
|
|
33
|
+
onEndReachedThreshold?: number;
|
|
34
|
+
onStartReached?: (info: {
|
|
35
|
+
distanceFromStart: number;
|
|
36
|
+
}) => void;
|
|
37
|
+
onStartReachedThreshold?: number;
|
|
38
|
+
onRefresh?: () => void;
|
|
39
|
+
refreshing?: boolean | null;
|
|
40
|
+
progressViewOffset?: number;
|
|
41
|
+
onViewableItemsChanged?: (info: IViewableItemsChangedInfo<ItemT>) => void;
|
|
42
|
+
viewabilityConfig?: IViewabilityConfig;
|
|
43
|
+
viewabilityConfigCallbackPairs?: IViewabilityConfigCallbackPair<ItemT>[];
|
|
44
|
+
onScrollToIndexFailed?: (info: {
|
|
45
|
+
index: number;
|
|
46
|
+
highestMeasuredFrameIndex: number;
|
|
47
|
+
averageItemLength: number;
|
|
48
|
+
}) => void;
|
|
49
|
+
initialNumToRender?: number;
|
|
50
|
+
initialScrollIndex?: number;
|
|
51
|
+
maxToRenderPerBatch?: number;
|
|
52
|
+
updateCellsBatchingPeriod?: number;
|
|
53
|
+
windowSize?: number;
|
|
54
|
+
maintainVisibleContentPosition?: {
|
|
55
|
+
minIndexForVisible: number;
|
|
56
|
+
autoscrollToTopThreshold?: number;
|
|
57
|
+
};
|
|
58
|
+
onScroll?: (event: ISymbioteEvent) => void;
|
|
59
|
+
onScrollBeginDrag?: (event: ISymbioteEvent) => void;
|
|
60
|
+
onScrollEndDrag?: (event: ISymbioteEvent) => void;
|
|
61
|
+
onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
|
|
62
|
+
onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
|
|
63
|
+
scrollEventThrottle?: number;
|
|
64
|
+
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
65
|
+
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
66
|
+
style?: IStyleProp<IViewStyle>;
|
|
67
|
+
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
68
|
+
className?: string;
|
|
69
|
+
}
|
|
70
|
+
export declare function FlatList<ItemT>(props: IFlatListProps<ItemT> & {
|
|
71
|
+
ref?: Ref<IFlatListHandle>;
|
|
72
|
+
}): ReactElement;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// FlatList: the convenience surface over VirtualizedList. It takes a plain `data` array and
|
|
2
|
+
// derives getItem/getItemCount, so callers never touch the VirtualizedList data-access protocol.
|
|
3
|
+
// `numColumns` packs that many items into each row (a horizontal sub-View), so the virtualized
|
|
4
|
+
// stream is rows, not items (RN's FlatList). All windowing, viewability, batching, and imperative
|
|
5
|
+
// scrolling are inherited from VirtualizedList; the data shaping and the row/viewability/separator
|
|
6
|
+
// transforms are shared from @symbiote-native/components. This file only adapts to React's lifecycle
|
|
7
|
+
// (element creation + ref threading).
|
|
8
|
+
import { createElement, } from 'react';
|
|
9
|
+
import { dlog, resolveClassName } from '@symbiote-native/engine';
|
|
10
|
+
import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, rowKeyExtractor, } from '@symbiote-native/components';
|
|
11
|
+
import { VirtualizedList, } from '../virtualized-list';
|
|
12
|
+
export function FlatList(props) {
|
|
13
|
+
const { ref, data, renderItem, keyExtractor, numColumns = SINGLE_COLUMN, columnWrapperStyle,
|
|
14
|
+
// onViewableItemsChanged/viewability are typed against ItemT here; in the multi-column path
|
|
15
|
+
// the underlying stream is IRow<ItemT>, so they are dropped from `rest` and pulled out.
|
|
16
|
+
onViewableItemsChanged, viewabilityConfigCallbackPairs,
|
|
17
|
+
// ItemSeparatorComponent is typed on ItemT; the multi-column stream is IRow<ItemT>, so it is
|
|
18
|
+
// wrapped there to unwrap rows back to items, exactly like viewability above.
|
|
19
|
+
ItemSeparatorComponent, ...rest } = props;
|
|
20
|
+
dlog(`FlatList over ${data.length} items, ${numColumns} column(s)`);
|
|
21
|
+
if (numColumns <= SINGLE_COLUMN) {
|
|
22
|
+
return createElement((VirtualizedList), {
|
|
23
|
+
ref,
|
|
24
|
+
data,
|
|
25
|
+
getItem: (_source, index) => data[index],
|
|
26
|
+
getItemCount: () => data.length,
|
|
27
|
+
renderItem,
|
|
28
|
+
keyExtractor,
|
|
29
|
+
onViewableItemsChanged,
|
|
30
|
+
viewabilityConfigCallbackPairs,
|
|
31
|
+
ItemSeparatorComponent,
|
|
32
|
+
...rest,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// Multi-column: the virtualized stream is rows. Each cell renders its items side by side in a
|
|
36
|
+
// flex-row View so windowing accounts for whole rows.
|
|
37
|
+
const rows = chunkIntoRows(data, numColumns);
|
|
38
|
+
const resolvedColumnWrapperStyle = typeof columnWrapperStyle === 'string'
|
|
39
|
+
? resolveClassName(columnWrapperStyle)
|
|
40
|
+
: columnWrapperStyle;
|
|
41
|
+
const rowStyle = [{ flexDirection: 'row' }, resolvedColumnWrapperStyle];
|
|
42
|
+
const renderRow = (info) => {
|
|
43
|
+
const cells = info.item.items.map((item, column) => {
|
|
44
|
+
const index = info.item.startIndex + column;
|
|
45
|
+
const key = keyExtractor ? keyExtractor(item, index) : String(index);
|
|
46
|
+
// The row IS the virtualized cell, so every item in it shares the row's separators handle
|
|
47
|
+
// (the divider sits between rows, not columns), like RN's multi-column FlatList.
|
|
48
|
+
return createElement('symbiote-view', { key, style: { flex: 1 } }, renderItem({ item, index, separators: info.separators }));
|
|
49
|
+
});
|
|
50
|
+
return createElement('symbiote-view', { style: rowStyle }, ...cells);
|
|
51
|
+
};
|
|
52
|
+
// Viewability over rows expands back to per-item tokens so the caller sees item-level
|
|
53
|
+
// visibility, not row-level (shared expandRowViewability).
|
|
54
|
+
const rowOnViewableItemsChanged = onViewableItemsChanged !== undefined
|
|
55
|
+
? (rowInfo) => {
|
|
56
|
+
onViewableItemsChanged(expandRowViewability(rowInfo, keyExtractor));
|
|
57
|
+
}
|
|
58
|
+
: undefined;
|
|
59
|
+
const rowViewabilityPairs = viewabilityConfigCallbackPairs?.map(pair => ({
|
|
60
|
+
viewabilityConfig: pair.viewabilityConfig,
|
|
61
|
+
onViewableItemsChanged: (rowInfo) => {
|
|
62
|
+
pair.onViewableItemsChanged(expandRowViewability(rowInfo, keyExtractor));
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
// The divider between rows shows real items (last of the row above, first of the row below), so
|
|
66
|
+
// the user's separator, typed on ItemT, sees items rather than the IRow wrapper.
|
|
67
|
+
const rowSeparatorComponent = ItemSeparatorComponent === undefined
|
|
68
|
+
? undefined
|
|
69
|
+
: (rowProps) => createElement(ItemSeparatorComponent, {
|
|
70
|
+
...rowProps,
|
|
71
|
+
leadingItem: lastItemOfRow(rowProps.leadingItem),
|
|
72
|
+
trailingItem: firstItemOfRow(rowProps.trailingItem),
|
|
73
|
+
});
|
|
74
|
+
return createElement((VirtualizedList), {
|
|
75
|
+
ref,
|
|
76
|
+
data: rows,
|
|
77
|
+
getItem: (_source, index) => rows[index],
|
|
78
|
+
getItemCount: () => rows.length,
|
|
79
|
+
renderItem: renderRow,
|
|
80
|
+
keyExtractor: rowKeyExtractor,
|
|
81
|
+
onViewableItemsChanged: rowOnViewableItemsChanged,
|
|
82
|
+
viewabilityConfigCallbackPairs: rowViewabilityPairs,
|
|
83
|
+
ItemSeparatorComponent: rowSeparatorComponent,
|
|
84
|
+
...rest,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import { type IImageStatics, type IImageProps as IImageBaseProps } from '@symbiote-native/components';
|
|
3
|
+
export type IImageProps = IImageBaseProps & {
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
export type IImageWithStatics = FC<IImageProps> & IImageStatics;
|
|
7
|
+
export declare const Image: IImageWithStatics;
|
|
8
|
+
export { setImageSourceResolver } from '@symbiote-native/components';
|
|
9
|
+
export type { IImageSource, IImageSourceProp, IResizeMode, IImageSize, IImageCacheStatus, } from '@symbiote-native/components';
|