@symbiote-native/react 3.0.0 → 3.0.2
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/build/components/flat-list/index.d.ts +2 -0
- package/build/components/flat-list/index.js +8 -3
- package/build/components/modal/index.js +19 -11
- package/build/components/virtualized-list/index.d.ts +2 -0
- package/build/components/virtualized-list/index.js +5 -1
- package/build/components/virtualized-section-list/index.d.ts +2 -0
- package/build/host-config.js +19 -3
- package/package.json +5 -5
|
@@ -66,6 +66,8 @@ export interface IFlatListProps<ItemT> extends IAccessibilityProps, IAriaProps {
|
|
|
66
66
|
style?: IStyleProp<IViewStyle>;
|
|
67
67
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
68
68
|
className?: string;
|
|
69
|
+
removeClippedSubviews?: boolean;
|
|
70
|
+
nestedScrollEnabled?: boolean;
|
|
69
71
|
}
|
|
70
72
|
export declare function FlatList<ItemT>(props: IFlatListProps<ItemT> & {
|
|
71
73
|
ref?: Ref<IFlatListHandle>;
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// transforms are shared from @symbiote-native/components. This file only adapts to React's lifecycle
|
|
7
7
|
// (element creation + ref threading).
|
|
8
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';
|
|
9
|
+
import { Platform, dlog, resolveClassName, } from '@symbiote-native/engine';
|
|
10
|
+
import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, removeClippedSubviewsOrDefault, rowKeyExtractor, } from '@symbiote-native/components';
|
|
11
11
|
import { VirtualizedList, } from '../virtualized-list/index.js';
|
|
12
12
|
export function FlatList(props) {
|
|
13
13
|
const { ref, data, renderItem, keyExtractor, numColumns = SINGLE_COLUMN, columnWrapperStyle,
|
|
@@ -16,7 +16,12 @@ export function FlatList(props) {
|
|
|
16
16
|
onViewableItemsChanged, viewabilityConfigCallbackPairs,
|
|
17
17
|
// ItemSeparatorComponent is typed on ItemT; the multi-column stream is IRow<ItemT>, so it is
|
|
18
18
|
// wrapped there to unwrap rows back to items, exactly like viewability above.
|
|
19
|
-
ItemSeparatorComponent, ...
|
|
19
|
+
ItemSeparatorComponent, removeClippedSubviews: authoredRemoveClippedSubviews, ...ownRest } = props;
|
|
20
|
+
// FlatList.js always sends it, defaulted per platform.
|
|
21
|
+
const rest = {
|
|
22
|
+
...ownRest,
|
|
23
|
+
removeClippedSubviews: removeClippedSubviewsOrDefault(authoredRemoveClippedSubviews, Platform.OS),
|
|
24
|
+
};
|
|
20
25
|
dlog(`FlatList over ${data.length} items, ${numColumns} column(s)`);
|
|
21
26
|
if (numColumns <= SINGLE_COLUMN) {
|
|
22
27
|
return createElement((VirtualizedList), {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// through the exit transition. The native exit-animation timing is what's deferred, not the
|
|
13
13
|
// callback contract.
|
|
14
14
|
import { createElement, useEffect, useReducer, } from 'react';
|
|
15
|
-
import { dlog } from '@symbiote-native/engine';
|
|
16
|
-
import { createInitialModalState, modalReducer, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
|
|
15
|
+
import { dlog, Platform } from '@symbiote-native/engine';
|
|
16
|
+
import { createInitialModalState, isModalVisible, modalReducer, modalVisibilityAction, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
|
|
17
17
|
export const Modal = rawProps => {
|
|
18
18
|
// Modal owns its host element (modal), so it folds aria/role here; the resolved fields
|
|
19
19
|
// ride the host node via `...passthrough`. The events (onShow/onDismiss/onRequestClose/
|
|
@@ -21,18 +21,26 @@ export const Modal = rawProps => {
|
|
|
21
21
|
// className is pulled out here, like style, and applied to the CONTAINER element below — left in
|
|
22
22
|
// ...passthrough it would land on the outer modal host instead (renderModal composes
|
|
23
23
|
// `style` into the container's style, not the host's).
|
|
24
|
-
const { visible, transparent, backdropColor, animationType, presentationStyle, supportedOrientations, hardwareAccelerated, statusBarTranslucent, navigationBarTranslucent, allowSwipeDismissal, style, className, children, ...passthrough } = resolveAccessibilityProps(rawProps);
|
|
25
|
-
// The iOS keep-alive:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const [state, dispatch] = useReducer(modalReducer, visible === true, createInitialModalState);
|
|
24
|
+
const { visible, transparent, backdropColor, animationType, presentationStyle, supportedOrientations, hardwareAccelerated, statusBarTranslucent, navigationBarTranslucent, allowSwipeDismissal, style, className, children, onDismiss, ...passthrough } = resolveAccessibilityProps(rawProps);
|
|
25
|
+
// The iOS keep-alive (state/modal.ts): armed on show, dropped only by the native dismiss below.
|
|
26
|
+
const isVisible = isModalVisible(visible);
|
|
27
|
+
const [state, dispatch] = useReducer(modalReducer, isVisible, createInitialModalState);
|
|
29
28
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const action = modalVisibilityAction(isVisible);
|
|
30
|
+
if (action !== undefined)
|
|
31
|
+
dispatch(action);
|
|
32
|
+
}, [isVisible]);
|
|
33
|
+
if (!shouldRenderModal(isVisible, state)) {
|
|
33
34
|
dlog('Modal hidden -> no node committed');
|
|
34
35
|
return null;
|
|
35
36
|
}
|
|
37
|
+
// Modal.js: onDismiss is iOS-only — it drops the keep-alive, then tells the app.
|
|
38
|
+
const handleDismiss = () => {
|
|
39
|
+
if (Platform.OS !== 'ios')
|
|
40
|
+
return;
|
|
41
|
+
dispatch({ type: 'hide' });
|
|
42
|
+
onDismiss?.();
|
|
43
|
+
};
|
|
36
44
|
const root = renderModal({
|
|
37
45
|
visible,
|
|
38
46
|
transparent,
|
|
@@ -45,7 +53,7 @@ export const Modal = rawProps => {
|
|
|
45
53
|
navigationBarTranslucent,
|
|
46
54
|
allowSwipeDismissal,
|
|
47
55
|
style,
|
|
48
|
-
passthrough,
|
|
56
|
+
passthrough: { ...passthrough, onDismiss: handleDismiss },
|
|
49
57
|
});
|
|
50
58
|
// root = modal > [container]; the user children nest UNDER the container View, never as
|
|
51
59
|
// a direct sibling of the host (RN's modal content layout).
|
|
@@ -64,6 +64,8 @@ export interface IVirtualizedListProps<ItemT> extends IAccessibilityProps, IAria
|
|
|
64
64
|
scrollEventThrottle?: number;
|
|
65
65
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
66
66
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
67
|
+
removeClippedSubviews?: boolean;
|
|
68
|
+
nestedScrollEnabled?: boolean;
|
|
67
69
|
style?: IStyleProp<IViewStyle>;
|
|
68
70
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
69
71
|
className?: string;
|
|
@@ -491,8 +491,9 @@ export function VirtualizedList(props) {
|
|
|
491
491
|
const resolvedContentContainerStyle = horizontal
|
|
492
492
|
? [contentContainerStyle, { width: total }]
|
|
493
493
|
: contentContainerStyle;
|
|
494
|
+
// VirtualizedList.js: `[inversionStyle, style]` — the app's style can override the flip.
|
|
494
495
|
const resolvedStyle = inverted
|
|
495
|
-
? [
|
|
496
|
+
? [horizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE, style]
|
|
496
497
|
: style;
|
|
497
498
|
const scrollProps = {
|
|
498
499
|
// The list's accessibility surface rides down onto the ScrollView. Spread first so the
|
|
@@ -507,6 +508,9 @@ export function VirtualizedList(props) {
|
|
|
507
508
|
// scrolls the two axes with different native ViewManagers. The behavior's own fold deletes the
|
|
508
509
|
// prop and rewrites it from the tag, so passing it would be at best redundant and at worst a
|
|
509
510
|
// contradiction it has to warn about.
|
|
511
|
+
// VirtualizedList.js:1111 — Android moves the scrollbar back after the `scale: -1` flip.
|
|
512
|
+
if (inverted)
|
|
513
|
+
scrollProps.isInvertedVirtualizedList = true;
|
|
510
514
|
if (onScrollBeginDrag !== undefined)
|
|
511
515
|
scrollProps.onScrollBeginDrag = onScrollBeginDrag;
|
|
512
516
|
if (onScrollEndDrag !== undefined)
|
|
@@ -62,6 +62,8 @@ export interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps
|
|
|
62
62
|
scrollEventThrottle?: number;
|
|
63
63
|
keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
64
64
|
keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
65
|
+
removeClippedSubviews?: boolean;
|
|
66
|
+
nestedScrollEnabled?: boolean;
|
|
65
67
|
style?: IStyleProp<IViewStyle>;
|
|
66
68
|
contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
67
69
|
className?: string;
|
package/build/host-config.js
CHANGED
|
@@ -29,11 +29,24 @@ import { resolveIntrinsicTag } from '@symbiote-native/components/resolve-intrins
|
|
|
29
29
|
function isReservedProp(key) {
|
|
30
30
|
return key === 'children' || key === 'ref' || key === 'key';
|
|
31
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
|
+
*/
|
|
32
45
|
function applyProps(node, props) {
|
|
33
|
-
for (const
|
|
46
|
+
for (const key of Object.keys(props)) {
|
|
34
47
|
if (isReservedProp(key))
|
|
35
48
|
continue;
|
|
36
|
-
routeProp(node, key,
|
|
49
|
+
routeProp(node, key, props[key]);
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
function applyUpdate(node, oldProps, newProps) {
|
|
@@ -43,9 +56,12 @@ function applyUpdate(node, oldProps, newProps) {
|
|
|
43
56
|
if (!Object.hasOwn(newProps, key))
|
|
44
57
|
routeProp(node, key, undefined);
|
|
45
58
|
}
|
|
46
|
-
|
|
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)) {
|
|
47
62
|
if (isReservedProp(key))
|
|
48
63
|
continue;
|
|
64
|
+
const value = newProps[key];
|
|
49
65
|
if (value !== oldProps[key])
|
|
50
66
|
routeProp(node, key, value);
|
|
51
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/react",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
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": "3.
|
|
60
|
+
"@symbiote-native/components": "3.1.0",
|
|
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": "^1.
|
|
66
|
+
"@symbiote-native/engine": "^1.3.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": "1.
|
|
73
|
-
"@symbiote-native/test-utils": "0.
|
|
72
|
+
"@symbiote-native/engine": "1.3.0",
|
|
73
|
+
"@symbiote-native/test-utils": "0.4.2"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"typecheck": "tsc --build",
|