@symbiote-native/react 2.0.0 → 3.0.1
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 +3 -3
- package/build/components/flat-list/index.js +1 -1
- package/build/components/keyboard-avoiding-view/index.js +6 -1
- package/build/components/pressable/pressable-props.d.ts +2 -0
- package/build/components/touchable/touchable-props.d.ts +2 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +1 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +1 -0
- package/build/components/virtualized-list/index.js +8 -4
- package/build/host-config.js +24 -15
- package/build/modules/animated/index.d.ts +29 -19
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -27,9 +27,9 @@ npm install @symbiote-native/react react-native react
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
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.
|
|
31
|
-
|
|
32
|
-
[`examples/react`](../../examples/react) rather than a generator.
|
|
30
|
+
them, it only replaces the JS renderer that drives them. `npx @symbiote-native/cli new --framework react`
|
|
31
|
+
scaffolds the Metro config and the `index.js` entry seam below for a new app; wiring them into an
|
|
32
|
+
existing one still follows [`examples/react`](../../examples/react) rather than a generator.
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
@@ -80,7 +80,7 @@ export function FlatList(props) {
|
|
|
80
80
|
getItem: (_source, index) => rows[index],
|
|
81
81
|
getItemCount: () => rows.length,
|
|
82
82
|
renderItem: renderRow,
|
|
83
|
-
keyExtractor: rowKeyExtractor,
|
|
83
|
+
keyExtractor: (row) => rowKeyExtractor(row, keyExtractor),
|
|
84
84
|
onViewableItemsChanged: rowOnViewableItemsChanged,
|
|
85
85
|
viewabilityConfigCallbackPairs: rowViewabilityPairs,
|
|
86
86
|
ItemSeparatorComponent: rowSeparatorComponent,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// around its children.
|
|
11
11
|
import { createElement, useEffect, useRef, useState, } from 'react';
|
|
12
12
|
import { dlog, Keyboard, Platform, } from '@symbiote-native/engine';
|
|
13
|
-
import { computeInset, keyboardAvoidingEventNamesFor, readKeyboardFrame, readLayoutFrame, readPrefersCrossFadeTransitions, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
|
|
13
|
+
import { computeInset, configureKeyboardAvoidingAnimation, keyboardAvoidingEventNamesFor, readKeyboardAnimationTiming, readKeyboardFrame, readLayoutFrame, readPrefersCrossFadeTransitions, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
|
|
14
14
|
import {} from '../../components.js';
|
|
15
15
|
// The two notifications to subscribe to, per host (iOS takes the `will` pair so the view rides
|
|
16
16
|
// up with the keyboard animation; Android has no will-notifications). Resolved ONCE at module
|
|
@@ -47,6 +47,11 @@ export const KeyboardAvoidingView = props => {
|
|
|
47
47
|
previousInset,
|
|
48
48
|
prefersCrossFadeTransitions: prefersCrossFadeRef.current,
|
|
49
49
|
});
|
|
50
|
+
// RN's `_updateBottomIfNecessary` skips the animation entirely when the inset did not
|
|
51
|
+
// change (`if (this._bottom === height) return;`) — arming a transition for a value that
|
|
52
|
+
// moves nowhere would only ever animate other props the app changed this frame.
|
|
53
|
+
if (next !== previousInset)
|
|
54
|
+
configureKeyboardAvoidingAnimation(readKeyboardAnimationTiming(payload), enabled);
|
|
50
55
|
dlog(`KeyboardAvoidingView show -> inset ${next}`);
|
|
51
56
|
return next;
|
|
52
57
|
});
|
|
@@ -14,7 +14,9 @@ export interface IPressableProps extends IAccessibilityProps, IAriaProps {
|
|
|
14
14
|
disabled?: boolean;
|
|
15
15
|
focusable?: boolean;
|
|
16
16
|
cancelable?: boolean;
|
|
17
|
+
blockNativeResponder?: boolean;
|
|
17
18
|
hitSlop?: IRectOffset;
|
|
19
|
+
testOnly_pressed?: boolean;
|
|
18
20
|
pressRetentionOffset?: IRectOffset;
|
|
19
21
|
unstable_pressDelay?: number;
|
|
20
22
|
minPressDuration?: number;
|
|
@@ -12,6 +12,8 @@ export interface ITouchableOpacityProps extends ITouchableBaseProps {
|
|
|
12
12
|
export interface ITouchableHighlightProps extends ITouchableBaseProps {
|
|
13
13
|
activeOpacity?: number;
|
|
14
14
|
underlayColor?: string;
|
|
15
|
+
touchSoundDisabled?: boolean;
|
|
16
|
+
testOnly_pressed?: boolean;
|
|
15
17
|
onShowUnderlay?: () => void;
|
|
16
18
|
onHideUnderlay?: () => void;
|
|
17
19
|
}
|
|
@@ -3,4 +3,5 @@ import type { IPressTimingProps } from '@symbiote-native/components';
|
|
|
3
3
|
import type { IPressableProps } from '../pressable/pressable-props';
|
|
4
4
|
export type ITouchableWithoutFeedbackProps = Omit<IPressableProps, 'style' | 'children'> & IPressTimingProps & {
|
|
5
5
|
children?: ReactNode;
|
|
6
|
+
touchSoundDisabled?: boolean;
|
|
6
7
|
};
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
// nothing else about it.
|
|
39
39
|
import { createElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, } from 'react';
|
|
40
40
|
import { dlog, } from '@symbiote-native/engine';
|
|
41
|
-
import {
|
|
41
|
+
import { DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, buildListPlan, buildScrollViewHandle, buildViewabilityPairs, createInitialListState, isSeparatorGapInRange, listEffectSignature, readLayoutLength, readLayoutOffset, readScrollOffset, reduceList, resolveItemKey, } from '@symbiote-native/components';
|
|
42
42
|
function resolveElement(component) {
|
|
43
43
|
if (component === undefined)
|
|
44
44
|
return undefined;
|
|
@@ -65,7 +65,7 @@ function renderSeparatorElement(component, leadingItem, trailingItem, overrides)
|
|
|
65
65
|
// generic). The ref is destructured here and wired through useImperativeHandle.
|
|
66
66
|
export function VirtualizedList(props) {
|
|
67
67
|
const forwardedRef = props.ref;
|
|
68
|
-
const { data, getItem, getItemCount, renderItem, keyExtractor, getItemLayout, ItemSeparatorComponent, ListHeaderComponent, ListFooterComponent, ListEmptyComponent, horizontal = false, inverted = false, extraData, onEndReached, onEndReachedThreshold
|
|
68
|
+
const { data, getItem, getItemCount, renderItem, keyExtractor, getItemLayout, ItemSeparatorComponent, ListHeaderComponent, ListFooterComponent, ListEmptyComponent, horizontal = false, inverted = false, extraData, onEndReached, onEndReachedThreshold, onStartReached, onStartReachedThreshold, onRefresh, refreshing, progressViewOffset, onViewableItemsChanged, viewabilityConfig, viewabilityConfigCallbackPairs, onScrollToIndexFailed, initialNumToRender = DEFAULT_INITIAL_NUM_TO_RENDER, initialScrollIndex, maxToRenderPerBatch = DEFAULT_MAX_TO_RENDER_PER_BATCH, updateCellsBatchingPeriod = DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, windowSize = DEFAULT_WINDOW_SIZE, stickyHeaderIndices, maintainVisibleContentPosition, style, contentContainerStyle,
|
|
69
69
|
// Pulled out of the rest so the user's onScroll does NOT arrive raw via
|
|
70
70
|
// ...accessibilityRest and overwrite the internal windowing handler. Composed below.
|
|
71
71
|
onScroll: userOnScroll, onScrollBeginDrag, onScrollEndDrag, onMomentumScrollBegin, onMomentumScrollEnd, scrollEventThrottle, keyboardShouldPersistTaps, keyboardDismissMode,
|
|
@@ -188,8 +188,12 @@ export function VirtualizedList(props) {
|
|
|
188
188
|
const info = effect.info;
|
|
189
189
|
const map = effect.map;
|
|
190
190
|
const fire = () => {
|
|
191
|
-
for (const pair of pairs)
|
|
192
|
-
pair.onViewableItemsChanged(
|
|
191
|
+
for (const pair of pairs) {
|
|
192
|
+
pair.onViewableItemsChanged({
|
|
193
|
+
...info,
|
|
194
|
+
viewabilityConfig: pair.viewabilityConfig,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
193
197
|
dispatchRef.current({ kind: 'viewable-fired', map });
|
|
194
198
|
};
|
|
195
199
|
if (viewableTimerRef.current !== null) {
|
package/build/host-config.js
CHANGED
|
@@ -16,10 +16,9 @@ import { descriptorFor } from '@symbiote-native/components';
|
|
|
16
16
|
// A bare intrinsic tag has no wrapper to apply RN's per-primitive prop folds (id -> nativeID,
|
|
17
17
|
// Text's ellipsizeMode / allowFontScaling defaults), so the renderer is the layer that must.
|
|
18
18
|
// Shared with every other adapter, driven by the same HOST_PRIMITIVES spec.
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// where the runtime value is known. Identity for every primitive that declares no alternative.
|
|
19
|
+
// WHICH native view a primitive commits can depend on a prop (TextInput's `multiline`), and that
|
|
20
|
+
// prop can be a runtime value — so the choice is made here, where it is known. Identity for every
|
|
21
|
+
// primitive that declares no alternative.
|
|
23
22
|
import { resolveIntrinsicTag } from '@symbiote-native/components/resolve-intrinsic';
|
|
24
23
|
// React-reserved prop keys that must never reach Fabric. `children` is the element
|
|
25
24
|
// tree (handled via appendChild). `ref`/`key` are React 19 plain props: the reconciler
|
|
@@ -30,11 +29,24 @@ import { resolveIntrinsicTag } from '@symbiote-native/components/resolve-intrins
|
|
|
30
29
|
function isReservedProp(key) {
|
|
31
30
|
return key === 'children' || key === 'ref' || key === 'key';
|
|
32
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* `Object.keys`, not `Object.entries`, and it is measured rather than stylistic.
|
|
34
|
+
*
|
|
35
|
+
* `entries` allocates the outer array AND a two-element array per key, eagerly, before the loop
|
|
36
|
+
* starts; `keys` allocates one array of strings and the lookup is a property read the engine is
|
|
37
|
+
* about to do anyway. This runs ONCE PER NODE CREATED — ten thousand times on a thousand-row create.
|
|
38
|
+
* Priced on `-O` Hermes by `object-iteration-cost.itest.ts` over a four-key bag: 0.42 us against
|
|
39
|
+
* 0.23, so **0.19 us per node, ~1.9 ms of a 102 ms create**.
|
|
40
|
+
*
|
|
41
|
+
* `for…in` is cheaper still (0.14) and is NOT used: it walks the prototype chain, and an inherited
|
|
42
|
+
* key would be routed as a prop. A reconciler's props object has no prototype today, which is a fact
|
|
43
|
+
* about today rather than a contract.
|
|
44
|
+
*/
|
|
33
45
|
function applyProps(node, props) {
|
|
34
|
-
for (const
|
|
46
|
+
for (const key of Object.keys(props)) {
|
|
35
47
|
if (isReservedProp(key))
|
|
36
48
|
continue;
|
|
37
|
-
routeProp(node, key,
|
|
49
|
+
routeProp(node, key, props[key]);
|
|
38
50
|
}
|
|
39
51
|
}
|
|
40
52
|
function applyUpdate(node, oldProps, newProps) {
|
|
@@ -44,9 +56,12 @@ function applyUpdate(node, oldProps, newProps) {
|
|
|
44
56
|
if (!Object.hasOwn(newProps, key))
|
|
45
57
|
routeProp(node, key, undefined);
|
|
46
58
|
}
|
|
47
|
-
|
|
59
|
+
// Same `Object.keys` reason as `applyProps` above — this walk runs once per UPDATED node, which on
|
|
60
|
+
// a `partial` step is every row on the screen.
|
|
61
|
+
for (const key of Object.keys(newProps)) {
|
|
48
62
|
if (isReservedProp(key))
|
|
49
63
|
continue;
|
|
64
|
+
const value = newProps[key];
|
|
50
65
|
if (value !== oldProps[key])
|
|
51
66
|
routeProp(node, key, value);
|
|
52
67
|
}
|
|
@@ -117,12 +132,8 @@ const reconciler = createReconciler({
|
|
|
117
132
|
// other four adapters all pass it (vue `renderer/index.ts`, svelte `dom-shim/element.ts`,
|
|
118
133
|
// solid `renderer.ts`, angular `renderer/index.ts`), and React is the one that did not.
|
|
119
134
|
//
|
|
120
|
-
// Inert on landing and deliberately so — nothing registers a behavior under a tag React emits.
|
|
121
|
-
// The `-managed` split is what keeps it inert once something does: React's TextInput and
|
|
122
|
-
// Switch wrappers emit `text-input-managed` / `switch-managed`, so a machine
|
|
123
|
-
// registered on the lowered tag cannot also attach to a wrapper-built node and run twice.
|
|
124
135
|
const node = createElement(descriptor.component, descriptor.isText, tag);
|
|
125
|
-
applyProps(node,
|
|
136
|
+
applyProps(node, props);
|
|
126
137
|
return node;
|
|
127
138
|
},
|
|
128
139
|
createTextInstance(text, _container, hostContext) {
|
|
@@ -159,9 +170,7 @@ const reconciler = createReconciler({
|
|
|
159
170
|
// an update. The node keeps its identity — an app's ref stays live — and the commit walk
|
|
160
171
|
// re-creates the Fabric node because its viewName no longer matches the committed one.
|
|
161
172
|
setNodeComponent(node, descriptorFor(resolveIntrinsicTag(type, newProps)).component);
|
|
162
|
-
|
|
163
|
-
// the alias twice while never clearing the raw key.
|
|
164
|
-
applyUpdate(node, foldHostBag(type, oldProps), foldHostBag(type, newProps));
|
|
173
|
+
applyUpdate(node, oldProps, newProps);
|
|
165
174
|
},
|
|
166
175
|
commitTextUpdate(node, _oldText, newText) {
|
|
167
176
|
setText(node, newText);
|
|
@@ -6,25 +6,7 @@ export declare const Animated: {
|
|
|
6
6
|
Value: typeof AnimatedValue;
|
|
7
7
|
ValueXY: typeof AnimatedValueXY;
|
|
8
8
|
Color: typeof AnimatedColor;
|
|
9
|
-
Easing:
|
|
10
|
-
step0(n: number): number;
|
|
11
|
-
step1(n: number): number;
|
|
12
|
-
linear(t: number): number;
|
|
13
|
-
ease(t: number): number;
|
|
14
|
-
quad(t: number): number;
|
|
15
|
-
cubic(t: number): number;
|
|
16
|
-
poly(n: number): import("@symbiote-native/engine").IEasingFunction;
|
|
17
|
-
sin(t: number): number;
|
|
18
|
-
circle(t: number): number;
|
|
19
|
-
exp(t: number): number;
|
|
20
|
-
elastic(bounciness?: number): import("@symbiote-native/engine").IEasingFunction;
|
|
21
|
-
back(s?: number): import("@symbiote-native/engine").IEasingFunction;
|
|
22
|
-
bounce(t: number): number;
|
|
23
|
-
bezier(x1: number, y1: number, x2: number, y2: number): import("@symbiote-native/engine").IEasingFunction;
|
|
24
|
-
in(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
|
|
25
|
-
out(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
|
|
26
|
-
inOut(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
|
|
27
|
-
};
|
|
9
|
+
Easing: import("@symbiote-native/engine").IEasing;
|
|
28
10
|
timing: typeof timing;
|
|
29
11
|
spring: typeof spring;
|
|
30
12
|
decay: typeof decay;
|
|
@@ -48,4 +30,32 @@ export declare const Animated: {
|
|
|
48
30
|
FlatList: ReturnType<typeof createAnimatedComponent>;
|
|
49
31
|
SectionList: ReturnType<typeof createAnimatedComponent>;
|
|
50
32
|
createAnimatedComponent: typeof createAnimatedComponent;
|
|
33
|
+
} | {
|
|
34
|
+
Value: typeof AnimatedValue;
|
|
35
|
+
ValueXY: typeof AnimatedValueXY;
|
|
36
|
+
Color: typeof AnimatedColor;
|
|
37
|
+
Easing: import("@symbiote-native/engine").IEasing;
|
|
38
|
+
timing: (value: AnimatedValue, config: import("@symbiote-native/engine").ITimingConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
39
|
+
spring: (value: AnimatedValue, config: import("@symbiote-native/engine").ISpringConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
40
|
+
decay: (_value: AnimatedValue, _config: import("@symbiote-native/engine").IDecayConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
41
|
+
parallel: (animations: import("@symbiote-native/engine").ICompositeAnimation[], _config?: import("@symbiote-native/engine").IParallelConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
42
|
+
sequence: (animations: import("@symbiote-native/engine").ICompositeAnimation[]) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
43
|
+
stagger: (_time: number, animations: import("@symbiote-native/engine").ICompositeAnimation[]) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
44
|
+
loop: (_animation: import("@symbiote-native/engine").ICompositeAnimation) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
45
|
+
delay: (_time: number) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
46
|
+
add: typeof add;
|
|
47
|
+
subtract: typeof subtract;
|
|
48
|
+
multiply: typeof multiply;
|
|
49
|
+
divide: typeof divide;
|
|
50
|
+
modulo: typeof modulo;
|
|
51
|
+
diffClamp: typeof diffClamp;
|
|
52
|
+
event: typeof event;
|
|
53
|
+
forkEvent: typeof forkEvent;
|
|
54
|
+
unforkEvent: typeof unforkEvent;
|
|
55
|
+
View: import("react").ComponentType<import("./create-animated-component").IAnimatedComponentProps>;
|
|
56
|
+
Text: import("react").ComponentType<import("./create-animated-component").IAnimatedComponentProps>;
|
|
57
|
+
Image: import("react").ComponentType<import("./create-animated-component").IAnimatedComponentProps>;
|
|
58
|
+
FlatList: ReturnType<typeof createAnimatedComponent>;
|
|
59
|
+
SectionList: ReturnType<typeof createAnimatedComponent>;
|
|
60
|
+
createAnimatedComponent: typeof createAnimatedComponent;
|
|
51
61
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
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
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -57,20 +57,20 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"react-reconciler": "^0.33.0",
|
|
60
|
-
"@symbiote-native/components": "
|
|
60
|
+
"@symbiote-native/components": "3.0.1",
|
|
61
61
|
"@symbiote-native/css-parser": "0.5.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"react": ">=19.0.0",
|
|
65
65
|
"react-native": ">=0.86",
|
|
66
|
-
"@symbiote-native/engine": "^
|
|
66
|
+
"@symbiote-native/engine": "^1.2.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/react": "^19.2.0",
|
|
70
70
|
"@types/react-reconciler": "^0.32.2",
|
|
71
71
|
"react": "19.2.3",
|
|
72
|
-
"@symbiote-native/engine": "
|
|
73
|
-
"@symbiote-native/test-utils": "0.
|
|
72
|
+
"@symbiote-native/engine": "1.2.0",
|
|
73
|
+
"@symbiote-native/test-utils": "0.4.1"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"typecheck": "tsc --build",
|