@symbiote-native/react 0.3.0 → 0.4.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 +5 -5
- package/build/bootstrap.js +1 -1
- package/build/components/activity-indicator/shared.js +1 -1
- package/build/components/button.js +1 -1
- package/build/components/flat-list/index.js +5 -2
- package/build/components/input-accessory-view/index.js +6 -1
- package/build/components/keyboard-avoiding-view/index.js +32 -12
- package/build/components/modal/index.js +1 -1
- package/build/components/pressable/index.d.ts +1 -1
- package/build/components/pressable/index.js +1 -1
- package/build/components/scroll-view/index.android.js +16 -6
- package/build/components/scroll-view/index.ios.js +3 -1
- package/build/components/scroll-view/shared.js +56 -7
- package/build/components/scroll-view/sticky-header.d.ts +11 -2
- package/build/components/scroll-view/sticky-header.js +53 -11
- package/build/components/text-input/index.js +7 -2
- package/build/components/touchable/index.d.ts +2 -0
- package/build/components/touchable/index.js +130 -28
- package/build/components/virtualized-list/index.js +64 -24
- package/build/components/virtualized-section-list/index.d.ts +5 -0
- package/build/components/virtualized-section-list/index.js +24 -4
- package/build/components.js +2 -2
- package/build/create-portal/index.js +1 -1
- package/build/create-tunnel/index.js +1 -1
- package/build/hooks/use-window-dimensions.js +1 -1
- package/build/host-config.js +13 -5
- package/build/index.d.ts +7 -7
- package/build/index.js +1 -1
- package/build/modules/animated/create-animated-component.js +3 -1
- package/build/modules/animated/index.d.ts +1 -1
- package/build/modules/animated/index.js +1 -1
- package/build/modules/status-bar/index.ios.js +1 -1
- package/build/render.js +14 -4
- package/package.json +5 -5
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
// The Touchable* family, all built on Pressable.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// TouchableOpacity:
|
|
6
|
-
//
|
|
7
|
-
// TouchableHighlight:
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
// The Touchable* family, all built on Pressable. Ported against RN's own sources
|
|
2
|
+
// (.vendors/react-native/.../Components/Touchable/*.js) after the 2026-08-19 audit found ten
|
|
3
|
+
// divergences; the shared halves live in @symbiote-native/components and React owns only the
|
|
4
|
+
// lifecycle — refs, effects, the real timers, the Animated wiring, and the child clone:
|
|
5
|
+
// TouchableOpacity: an Animated.Value fades toward activeOpacity on press-in and back to the
|
|
6
|
+
// style's own resting opacity on press-out, driven from onPressIn/onPressOut.
|
|
7
|
+
// TouchableHighlight: the underlay machine drives a `shown` flag (NOT `pressed` — RN holds the
|
|
8
|
+
// underlay for delayPressOut past the tap); the container takes the underlay color and the
|
|
9
|
+
// child takes the lowered opacity, RN's split.
|
|
10
|
+
// TouchableWithoutFeedback: the same press-timing machine with the visual half left empty —
|
|
11
|
+
// "without feedback" means no VISUAL, not no timing.
|
|
12
|
+
import { Children, cloneElement, createElement, isValidElement, useEffect, useRef, useState, } from 'react';
|
|
13
|
+
import { createHighlightUnderlayHandlers, createHighlightUnderlayRuntime, createTouchableFeedbackHandlers, createTouchableFeedbackRuntime, hasTouchablePressHandler, resolveHighlightExtraStyles, restingOpacityFromStyle, DEFAULT_ACTIVE_OPACITY, OPACITY_ACTIVE_GRANT_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, TOUCHABLE_MIN_PRESS_DURATION_MS, } from '@symbiote-native/components';
|
|
12
14
|
import { Pressable } from '../pressable/index.js';
|
|
13
15
|
import { Animated } from '../../modules/animated/index.js';
|
|
14
|
-
// The real setTimeout the shared
|
|
15
|
-
//
|
|
16
|
+
// The real setTimeout the shared machines schedule on (core/components has no timer globals).
|
|
17
|
+
// Returns a canceller so an early release flushes the timer.
|
|
16
18
|
function scheduleTimeout(callback, ms) {
|
|
17
19
|
const id = setTimeout(callback, ms);
|
|
18
20
|
return () => clearTimeout(id);
|
|
@@ -22,42 +24,142 @@ export const TouchableOpacity = props => {
|
|
|
22
24
|
// className is pulled out here, like style, and applied to the inner Animated.View below —
|
|
23
25
|
// left in ...rest it would land on the outer Pressable instead, which is not where `style`
|
|
24
26
|
// (the opacity-fade node) goes.
|
|
25
|
-
className, children, onPressIn, onPressOut, delayPressIn = 0, delayPressOut = 0,
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
27
|
+
className, children, disabled, onPressIn, onPressOut, delayPressIn = 0, delayPressOut = 0,
|
|
28
|
+
// RN's Touchables override Pressability's 130ms floor with 0 (TouchableOpacity.js:195); the
|
|
29
|
+
// active visual is held by the fade's own duration instead.
|
|
30
|
+
minPressDuration = TOUCHABLE_MIN_PRESS_DURATION_MS, ...rest } = props;
|
|
31
|
+
// RN's _getChildStyleOpacityWithDefault: the fade settles at the opacity the CALLER's style
|
|
32
|
+
// asks for, not at a hard 1 — and must START there, or the first paint jumps to fully opaque.
|
|
33
|
+
const restingOpacity = restingOpacityFromStyle(style);
|
|
34
|
+
// One Animated.Value per mount. The Animated.View leaf commits its current value every frame,
|
|
35
|
+
// so timing it animates the real view.
|
|
36
|
+
const opacity = useRef(new Animated.Value(restingOpacity)).current;
|
|
29
37
|
// The shared press-scheduling cell (delayPressIn timer + activation clock), held across renders.
|
|
30
38
|
const runtime = useRef(createTouchableFeedbackRuntime()).current;
|
|
31
39
|
function setOpacityTo(toValue, duration) {
|
|
40
|
+
// useNativeDriver: true is RN's own (TouchableOpacity.js:242). opacity is natively drivable
|
|
41
|
+
// and the fade then survives a busy JS thread, which is the whole point of the feedback.
|
|
32
42
|
Animated.timing(opacity, {
|
|
33
43
|
toValue,
|
|
34
44
|
duration,
|
|
35
45
|
easing: Animated.Easing.inOut(Animated.Easing.quad),
|
|
36
|
-
useNativeDriver:
|
|
46
|
+
useNativeDriver: true,
|
|
37
47
|
}).start();
|
|
38
48
|
}
|
|
49
|
+
// RN's componentDidUpdate: a changed `disabled` or a changed style opacity re-settles the view,
|
|
50
|
+
// so a Touchable disabled mid-press does not stay stuck at its active opacity.
|
|
51
|
+
const hasRendered = useRef(false);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
// RN runs this on UPDATE only. Firing at mount would animate over the value the
|
|
54
|
+
// Animated.Value was just constructed with.
|
|
55
|
+
if (!hasRendered.current) {
|
|
56
|
+
hasRendered.current = true;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
setOpacityTo(restingOpacity, OPACITY_INACTIVE_DURATION_MS);
|
|
60
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- setOpacityTo is re-made per render
|
|
61
|
+
}, [disabled, restingOpacity]);
|
|
62
|
+
// RN's componentWillUnmount: stop the fade so a teardown mid-animation leaves no driver running
|
|
63
|
+
// against a node that is gone.
|
|
64
|
+
useEffect(() => () => {
|
|
65
|
+
opacity.resetAnimation();
|
|
66
|
+
}, [opacity]);
|
|
39
67
|
// The shared machine owns the delayPressIn/minPressDuration scheduling; the adapter supplies only
|
|
40
68
|
// the native seam — the Animated opacity fade + the framework emit — as activate/deactivate.
|
|
41
|
-
const { handlePressIn, handlePressOut } = createTouchableFeedbackHandlers({
|
|
69
|
+
const { handlePressIn, handlePressOut } = createTouchableFeedbackHandlers({
|
|
70
|
+
delayPressIn,
|
|
71
|
+
delayPressOut,
|
|
72
|
+
minPressDuration,
|
|
73
|
+
schedule: scheduleTimeout,
|
|
74
|
+
now: Date.now,
|
|
75
|
+
}, runtime, {
|
|
42
76
|
activate(event) {
|
|
43
|
-
setOpacityTo(activeOpacity,
|
|
77
|
+
setOpacityTo(activeOpacity, OPACITY_ACTIVE_GRANT_DURATION_MS);
|
|
44
78
|
onPressIn?.(event);
|
|
45
79
|
},
|
|
46
80
|
deactivate(event) {
|
|
47
|
-
setOpacityTo(
|
|
81
|
+
setOpacityTo(restingOpacity, OPACITY_INACTIVE_DURATION_MS);
|
|
48
82
|
onPressOut?.(event);
|
|
49
83
|
},
|
|
50
84
|
});
|
|
51
|
-
return createElement(Pressable, { ...rest, onPressIn: handlePressIn, onPressOut: handlePressOut }, createElement(Animated.View, { style: [style, { opacity }], className }, children));
|
|
85
|
+
return createElement(Pressable, { ...rest, disabled, onPressIn: handlePressIn, onPressOut: handlePressOut }, createElement(Animated.View, { style: [style, { opacity }], className }, children));
|
|
52
86
|
};
|
|
87
|
+
// RN's render: the lowered opacity is cloned onto the CHILD while the container keeps an opaque
|
|
88
|
+
// underlay (TouchableHighlight.js). Fold both onto the container instead and the opacity fades the
|
|
89
|
+
// very underlay it is meant to reveal — `underlayColor: 'black'` paints grey. React is the one
|
|
90
|
+
// adapter that can reach the child without a wrapper view perturbing the flex chain.
|
|
91
|
+
// A non-element child (text, a fragment, nothing at all) has no style prop to compose onto, so it
|
|
92
|
+
// passes through untouched rather than throwing the way RN's Children.only would.
|
|
93
|
+
function withChildStyle(children, extra) {
|
|
94
|
+
const child = Children.count(children) === 1 ? children : undefined;
|
|
95
|
+
if (!isValidElement(child))
|
|
96
|
+
return children;
|
|
97
|
+
return cloneElement(child, { style: [child.props.style, extra] });
|
|
98
|
+
}
|
|
53
99
|
export const TouchableHighlight = props => {
|
|
54
|
-
const { activeOpacity
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
const { activeOpacity, underlayColor, style, children, onPress, onPressIn, onPressOut, onShowUnderlay, onHideUnderlay, delayPressOut = 0, ...rest } = props;
|
|
101
|
+
// `shown` is NOT `pressed`: RN holds the underlay for delayPressOut past the tap so a press too
|
|
102
|
+
// fast to see still flashes, and the pressed flag is already false by then.
|
|
103
|
+
const [shown, setShown] = useState(false);
|
|
104
|
+
const runtime = useRef(createHighlightUnderlayRuntime()).current;
|
|
105
|
+
// RN's _hasPressHandler gate: no press handler, no underlay. onLongPress is only READ here —
|
|
106
|
+
// it stays in `rest` for Pressable to fire directly.
|
|
107
|
+
const hasPressHandler = hasTouchablePressHandler({
|
|
108
|
+
onPress,
|
|
109
|
+
onPressIn,
|
|
110
|
+
onPressOut,
|
|
111
|
+
onLongPress: rest.onLongPress,
|
|
112
|
+
});
|
|
113
|
+
const underlay = createHighlightUnderlayHandlers({ delayPressOut, hasPressHandler, schedule: scheduleTimeout }, runtime, { setShown, onShowUnderlay, onHideUnderlay });
|
|
114
|
+
const extraStyles = resolveHighlightExtraStyles({
|
|
115
|
+
shown,
|
|
116
|
+
hasPressHandler,
|
|
117
|
+
underlayColor,
|
|
118
|
+
activeOpacity,
|
|
119
|
+
});
|
|
120
|
+
// Visual first, then the caller's callback — RN's order in _createPressabilityConfig.
|
|
121
|
+
function handlePressIn(event) {
|
|
122
|
+
underlay.handlePressIn(event);
|
|
123
|
+
onPressIn?.(event);
|
|
124
|
+
}
|
|
125
|
+
function handlePress(event) {
|
|
126
|
+
underlay.handlePress(event);
|
|
127
|
+
onPress?.(event);
|
|
57
128
|
}
|
|
58
|
-
|
|
129
|
+
function handlePressOut(event) {
|
|
130
|
+
underlay.handlePressOut(event);
|
|
131
|
+
onPressOut?.(event);
|
|
132
|
+
}
|
|
133
|
+
return createElement(Pressable, {
|
|
134
|
+
...rest,
|
|
135
|
+
style: extraStyles === undefined ? style : [style, extraStyles.underlay],
|
|
136
|
+
onPress: handlePress,
|
|
137
|
+
onPressIn: handlePressIn,
|
|
138
|
+
onPressOut: handlePressOut,
|
|
139
|
+
}, extraStyles === undefined
|
|
140
|
+
? children
|
|
141
|
+
: withChildStyle(children, extraStyles.child));
|
|
59
142
|
};
|
|
60
143
|
export const TouchableWithoutFeedback = props => {
|
|
61
|
-
const { children, ...rest } = props;
|
|
62
|
-
|
|
144
|
+
const { children, onPressIn, onPressOut, delayPressIn = 0, delayPressOut = 0, minPressDuration = TOUCHABLE_MIN_PRESS_DURATION_MS, ...rest } = props;
|
|
145
|
+
// RN's TouchableWithoutFeedback builds a FULL Pressability config with delayPressIn /
|
|
146
|
+
// delayPressOut / minPressDuration: 0 — the same shared machine TouchableOpacity uses, with the
|
|
147
|
+
// visual half left empty. Spreading the delay props onto Pressable instead did nothing: it does
|
|
148
|
+
// not read them, and they leaked to the host as unknown props.
|
|
149
|
+
const runtime = useRef(createTouchableFeedbackRuntime()).current;
|
|
150
|
+
const { handlePressIn, handlePressOut } = createTouchableFeedbackHandlers({
|
|
151
|
+
delayPressIn,
|
|
152
|
+
delayPressOut,
|
|
153
|
+
minPressDuration,
|
|
154
|
+
schedule: scheduleTimeout,
|
|
155
|
+
now: Date.now,
|
|
156
|
+
}, runtime, {
|
|
157
|
+
activate(event) {
|
|
158
|
+
onPressIn?.(event);
|
|
159
|
+
},
|
|
160
|
+
deactivate(event) {
|
|
161
|
+
onPressOut?.(event);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
return createElement(Pressable, { ...rest, onPressIn: handlePressIn, onPressOut: handlePressOut }, children);
|
|
63
165
|
};
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
// scrollTo command via its handle ref, animated by default. The `contentOffset` prop is only a
|
|
19
19
|
// fallback for the pre-mount window (handle not yet attached).
|
|
20
20
|
import { createElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, } from 'react';
|
|
21
|
-
import { dlog } from '@symbiote-native/engine';
|
|
22
|
-
import { DEFAULT_END_REACHED_THRESHOLD, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_START_REACHED_THRESHOLD, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, buildListPlan, buildViewabilityPairs, createInitialListState, isSeparatorGapInRange, listEffectSignature, readLayoutLength, readScrollOffset, reduceList, resolveItemKey, } from '@symbiote-native/components';
|
|
23
|
-
import { ScrollView } from '../scroll-view';
|
|
21
|
+
import { dlog, } from '@symbiote-native/engine';
|
|
22
|
+
import { DEFAULT_END_REACHED_THRESHOLD, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_START_REACHED_THRESHOLD, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, buildListPlan, buildViewabilityPairs, createInitialListState, isSeparatorGapInRange, listEffectSignature, readLayoutLength, readLayoutOffset, readScrollOffset, reduceList, resolveItemKey, } from '@symbiote-native/components';
|
|
23
|
+
import { ScrollView, } from '../scroll-view';
|
|
24
24
|
import { RefreshControl } from '../refresh-control/index.js';
|
|
25
25
|
function resolveElement(component) {
|
|
26
26
|
if (component === undefined)
|
|
@@ -88,7 +88,12 @@ export function VirtualizedList(props) {
|
|
|
88
88
|
onScrollToIndexFailed,
|
|
89
89
|
viewabilityPairs,
|
|
90
90
|
});
|
|
91
|
-
handlersRef.current = {
|
|
91
|
+
handlersRef.current = {
|
|
92
|
+
onEndReached,
|
|
93
|
+
onStartReached,
|
|
94
|
+
onScrollToIndexFailed,
|
|
95
|
+
viewabilityPairs,
|
|
96
|
+
};
|
|
92
97
|
// The one folded state cell (RN's scattered refs collapsed into IListState). Lazily created once.
|
|
93
98
|
const stateRef = useRef(null);
|
|
94
99
|
const state = (stateRef.current ??= createInitialListState());
|
|
@@ -109,7 +114,9 @@ export function VirtualizedList(props) {
|
|
|
109
114
|
const scrollToPixel = useCallback((offset, animated) => {
|
|
110
115
|
const clamped = Math.max(EMPTY_OFFSET, offset);
|
|
111
116
|
const isHorizontal = inputsRef.current.horizontal;
|
|
112
|
-
const target = isHorizontal
|
|
117
|
+
const target = isHorizontal
|
|
118
|
+
? { x: clamped, y: EMPTY_OFFSET }
|
|
119
|
+
: { x: EMPTY_OFFSET, y: clamped };
|
|
113
120
|
if (scrollViewRef.current !== null) {
|
|
114
121
|
dlog(`VirtualizedList scrollTo offset=${clamped} animated=${animated} (horizontal=${isHorizontal})`);
|
|
115
122
|
scrollViewRef.current.scrollTo({ x: target.x, y: target.y, animated });
|
|
@@ -129,10 +136,14 @@ export function VirtualizedList(props) {
|
|
|
129
136
|
scrollToPixel(effect.offset, effect.animated);
|
|
130
137
|
break;
|
|
131
138
|
case 'fire-end-reached':
|
|
132
|
-
handlers.onEndReached?.({
|
|
139
|
+
handlers.onEndReached?.({
|
|
140
|
+
distanceFromEnd: effect.distanceFromEnd,
|
|
141
|
+
});
|
|
133
142
|
break;
|
|
134
143
|
case 'fire-start-reached':
|
|
135
|
-
handlers.onStartReached?.({
|
|
144
|
+
handlers.onStartReached?.({
|
|
145
|
+
distanceFromStart: effect.distanceFromStart,
|
|
146
|
+
});
|
|
136
147
|
break;
|
|
137
148
|
case 'fire-scroll-to-index-failed':
|
|
138
149
|
handlers.onScrollToIndexFailed?.({
|
|
@@ -217,8 +228,9 @@ export function VirtualizedList(props) {
|
|
|
217
228
|
const length = readLayoutLength(event, horizontal);
|
|
218
229
|
if (length === undefined)
|
|
219
230
|
return;
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
const offset = readLayoutOffset(event, horizontal);
|
|
232
|
+
dlog(`VirtualizedList cell ${index} measured length=${length} offset=${offset ?? 'none'}`);
|
|
233
|
+
dispatch({ kind: 'measure', index, length, offset });
|
|
222
234
|
}, [horizontal, dispatch]);
|
|
223
235
|
// Merge an override onto the separator at a given gap and request a re-render. A gap index
|
|
224
236
|
// outside [0, count-2] has no separator, so the write is a no-op (RN bails the same way).
|
|
@@ -313,7 +325,9 @@ export function VirtualizedList(props) {
|
|
|
313
325
|
// extraData) already re-renders and re-runs renderItem. Voided to mark the deliberate no-op.
|
|
314
326
|
void extraData;
|
|
315
327
|
const children = [];
|
|
316
|
-
const stickySet = stickyHeaderIndices !== undefined
|
|
328
|
+
const stickySet = stickyHeaderIndices !== undefined
|
|
329
|
+
? new Set(stickyHeaderIndices)
|
|
330
|
+
: undefined;
|
|
317
331
|
let renderedStickyIndices = [];
|
|
318
332
|
const header = resolveElement(ListHeaderComponent);
|
|
319
333
|
if (header !== undefined) {
|
|
@@ -337,13 +351,14 @@ export function VirtualizedList(props) {
|
|
|
337
351
|
keyFor: keyForIndex,
|
|
338
352
|
stickyIndices: stickySet,
|
|
339
353
|
hasHeader: header !== undefined,
|
|
340
|
-
hasSeparators: ItemSeparatorComponent !== undefined,
|
|
341
354
|
});
|
|
342
355
|
renderedStickyIndices = plan.stickyChildPositions;
|
|
343
356
|
if (plan.leadingExtent > EMPTY_OFFSET) {
|
|
344
357
|
children.push(createElement('symbiote-view', {
|
|
345
358
|
key: 'spacer-leading',
|
|
346
|
-
style: horizontal
|
|
359
|
+
style: horizontal
|
|
360
|
+
? { width: plan.leadingExtent }
|
|
361
|
+
: { height: plan.leadingExtent },
|
|
347
362
|
}));
|
|
348
363
|
}
|
|
349
364
|
// The force-mounted sticky cell (RN _ensureClosestStickyHeader): the nearest sticky index
|
|
@@ -362,13 +377,19 @@ export function VirtualizedList(props) {
|
|
|
362
377
|
children.push(createElement('symbiote-view', {
|
|
363
378
|
key: `cell-${plan.forcedStickyCell.key}`,
|
|
364
379
|
onLayout: makeCellMeasure(forcedIndex),
|
|
365
|
-
style: inverted
|
|
380
|
+
style: inverted
|
|
381
|
+
? horizontal
|
|
382
|
+
? INVERTED_X_STYLE
|
|
383
|
+
: INVERTED_Y_STYLE
|
|
384
|
+
: undefined,
|
|
366
385
|
}, forcedCell));
|
|
367
386
|
}
|
|
368
387
|
if (plan.gapExtent > EMPTY_OFFSET) {
|
|
369
388
|
children.push(createElement('symbiote-view', {
|
|
370
389
|
key: 'spacer-gap',
|
|
371
|
-
style: horizontal
|
|
390
|
+
style: horizontal
|
|
391
|
+
? { width: plan.gapExtent }
|
|
392
|
+
: { height: plan.gapExtent },
|
|
372
393
|
}));
|
|
373
394
|
}
|
|
374
395
|
for (const planCell of plan.cells) {
|
|
@@ -379,24 +400,42 @@ export function VirtualizedList(props) {
|
|
|
379
400
|
index: planCell.index,
|
|
380
401
|
separators: makeSeparators(planCell.index),
|
|
381
402
|
});
|
|
403
|
+
// RN renders the separator INSIDE the cell's own measuring wrapper
|
|
404
|
+
// (VirtualizedListCellRenderer.js:218-221), and that placement is load-bearing rather than
|
|
405
|
+
// cosmetic. As a SIBLING it is an extra flex child, so the chrome between two cells becomes
|
|
406
|
+
// gap + separator + gap while a spacer collapsing that region replaces it with one gap — the
|
|
407
|
+
// leading spacer then lands every cell below it short by (separator + gap), and the content
|
|
408
|
+
// visibly jumps by that amount each time the window's first index moves. Measured at exactly
|
|
409
|
+
// 17px on device 2026-08-19 (a 1px divider under a 16px container gap); see
|
|
410
|
+
// .claude/rules/list-geometry-feedback-loop.md. Inside the wrapper it is part of the cell's
|
|
411
|
+
// measured length instead, and every cell stays exactly one child.
|
|
412
|
+
const separator =
|
|
413
|
+
// RN gates the separator on the last index of the DATA, not of the WINDOW
|
|
414
|
+
// (VirtualizedList.js:793 `const end = getItemCount(data) - 1`), and now that the separator
|
|
415
|
+
// lives INSIDE the measuring wrapper that distinction is load-bearing: gating on the window
|
|
416
|
+
// would make a cell's own measured height change as the window slides past it. Device-measured
|
|
417
|
+
// 2026-08-19 as a run of cells all shifting by exactly the divider's 1px.
|
|
418
|
+
planCell.index < count - 1
|
|
419
|
+
? renderSeparatorElement(ItemSeparatorComponent, item, getItem(data, planCell.index + 1), separatorOverridesRef.current.get(planCell.index))
|
|
420
|
+
: undefined;
|
|
382
421
|
// Wrap each cell in a measuring View. When inverted, each cell carries the counter-flip so
|
|
383
422
|
// its content reads upright inside the flipped content container.
|
|
384
423
|
children.push(createElement('symbiote-view', {
|
|
385
424
|
key: `cell-${planCell.key}`,
|
|
386
425
|
onLayout: makeCellMeasure(planCell.index),
|
|
387
|
-
style: inverted
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
children.push(createElement('symbiote-view', { key: `sep-${planCell.key}` }, separator));
|
|
394
|
-
}
|
|
426
|
+
style: inverted
|
|
427
|
+
? horizontal
|
|
428
|
+
? INVERTED_X_STYLE
|
|
429
|
+
: INVERTED_Y_STYLE
|
|
430
|
+
: undefined,
|
|
431
|
+
}, cell, separator));
|
|
395
432
|
}
|
|
396
433
|
if (plan.trailingExtent > EMPTY_OFFSET) {
|
|
397
434
|
children.push(createElement('symbiote-view', {
|
|
398
435
|
key: 'spacer-trailing',
|
|
399
|
-
style: horizontal
|
|
436
|
+
style: horizontal
|
|
437
|
+
? { width: plan.trailingExtent }
|
|
438
|
+
: { height: plan.trailingExtent },
|
|
400
439
|
}));
|
|
401
440
|
}
|
|
402
441
|
}
|
|
@@ -448,7 +487,8 @@ export function VirtualizedList(props) {
|
|
|
448
487
|
if (maintainVisibleContentPosition !== undefined) {
|
|
449
488
|
scrollProps.maintainVisibleContentPosition = {
|
|
450
489
|
...maintainVisibleContentPosition,
|
|
451
|
-
minIndexForVisible: maintainVisibleContentPosition.minIndexForVisible +
|
|
490
|
+
minIndexForVisible: maintainVisibleContentPosition.minIndexForVisible +
|
|
491
|
+
(header !== undefined ? 1 : 0),
|
|
452
492
|
};
|
|
453
493
|
}
|
|
454
494
|
// Pull-to-refresh: when onRefresh is set, build a RefreshControl for the ScrollView's
|
|
@@ -22,6 +22,11 @@ export interface IVirtualizedSectionListProps<ItemT> extends IAccessibilityProps
|
|
|
22
22
|
}) => ReactNode;
|
|
23
23
|
SectionSeparatorComponent?: ComponentType<Record<string, never>> | ReactElement;
|
|
24
24
|
keyExtractor?: (item: ItemT, index: number) => string;
|
|
25
|
+
getItemLayout?: (data: ReadonlyArray<ISection<ItemT>> | null, index: number) => {
|
|
26
|
+
length: number;
|
|
27
|
+
offset: number;
|
|
28
|
+
index: number;
|
|
29
|
+
};
|
|
25
30
|
stickySectionHeadersEnabled?: boolean;
|
|
26
31
|
extraData?: unknown;
|
|
27
32
|
ItemSeparatorComponent?: ComponentType<ISeparatorProps<ItemT>>;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// useImperativeHandle + the per-entry render dispatch). Lower layer in RN's
|
|
7
7
|
// SectionList -> VirtualizedSectionList -> VirtualizedList stack.
|
|
8
8
|
import { createElement, useImperativeHandle, useRef, } from 'react';
|
|
9
|
-
import { dlog, Platform } from '@symbiote-native/engine';
|
|
9
|
+
import { dlog, Platform, } from '@symbiote-native/engine';
|
|
10
10
|
import { flattenSections, resolveStickySectionHeaders, scrollLocationToFlatIndex, sectionEntryKey, unwrapEntryItem, } from '@symbiote-native/components';
|
|
11
11
|
import { VirtualizedList, } from '../virtualized-list/index.js';
|
|
12
12
|
function resolveSeparator(component) {
|
|
@@ -20,7 +20,10 @@ export function VirtualizedSectionList(props) {
|
|
|
20
20
|
const { ref, sections, renderItem, renderSectionHeader, renderSectionFooter, SectionSeparatorComponent,
|
|
21
21
|
// Pulled out of `rest`: the user's separator is typed on ItemT, but the inner VirtualizedList
|
|
22
22
|
// streams ISectionEntry<ItemT>, so we wrap it to unwrap each entry back to its ItemT.
|
|
23
|
-
ItemSeparatorComponent, keyExtractor,
|
|
23
|
+
ItemSeparatorComponent, keyExtractor,
|
|
24
|
+
// Also pulled out of `rest`: the inner list streams entries, the user's callback expects
|
|
25
|
+
// sections. See entryItemLayout below.
|
|
26
|
+
getItemLayout, stickySectionHeadersEnabled, ...rest } = props;
|
|
24
27
|
const { entries, headerIndices } = flattenSections(sections, SectionSeparatorComponent !== undefined);
|
|
25
28
|
// RN sticks section headers by default only on iOS; Android does not unless asked. headerIndices
|
|
26
29
|
// are the flat positions of every section header; VirtualizedList forwards the in-window ones.
|
|
@@ -58,10 +61,14 @@ export function VirtualizedSectionList(props) {
|
|
|
58
61
|
const renderEntry = (info) => {
|
|
59
62
|
const entry = info.item;
|
|
60
63
|
if (entry.kind === 'header') {
|
|
61
|
-
return renderSectionHeader
|
|
64
|
+
return renderSectionHeader
|
|
65
|
+
? renderSectionHeader({ section: entry.section })
|
|
66
|
+
: undefined;
|
|
62
67
|
}
|
|
63
68
|
if (entry.kind === 'footer') {
|
|
64
|
-
return renderSectionFooter
|
|
69
|
+
return renderSectionFooter
|
|
70
|
+
? renderSectionFooter({ section: entry.section })
|
|
71
|
+
: undefined;
|
|
65
72
|
}
|
|
66
73
|
if (entry.kind === 'section-separator') {
|
|
67
74
|
return resolveSeparator(SectionSeparatorComponent);
|
|
@@ -83,6 +90,18 @@ export function VirtualizedSectionList(props) {
|
|
|
83
90
|
trailingItem: unwrapEntryItem(entryProps.trailingItem),
|
|
84
91
|
});
|
|
85
92
|
const entryKeyExtractor = (entry, index) => sectionEntryKey(entry, index, keyExtractor);
|
|
93
|
+
// Hand the callback `sections`, not the entries: RN's inner VirtualizedList gets
|
|
94
|
+
// `data={this.props.sections}` (VirtualizedSectionList.js:216) while ours streams the FLATTENED
|
|
95
|
+
// entries, so the same user code would otherwise see a different argument here than on RN.
|
|
96
|
+
//
|
|
97
|
+
// UPSTREAM-DIVERGENCE(react-native): the flat INDEX matches RN's (two rows per section, header
|
|
98
|
+
// and footer) only while SectionSeparatorComponent is unset. With it, flattenSections emits an
|
|
99
|
+
// extra 'section-separator' row per boundary that RN renders inside the neighbouring cell, so
|
|
100
|
+
// indices shift by one per boundary from the second section on. Deliberate - that row is how this
|
|
101
|
+
// adapter paints the separator; a caller combining the two must account for it.
|
|
102
|
+
const entryItemLayout = getItemLayout === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: (_entries, index) => getItemLayout(sections, index);
|
|
86
105
|
return createElement((VirtualizedList), {
|
|
87
106
|
ref: listRef,
|
|
88
107
|
data: entries,
|
|
@@ -90,6 +109,7 @@ export function VirtualizedSectionList(props) {
|
|
|
90
109
|
getItemCount: () => entries.length,
|
|
91
110
|
renderItem: renderEntry,
|
|
92
111
|
keyExtractor: entryKeyExtractor,
|
|
112
|
+
getItemLayout: entryItemLayout,
|
|
93
113
|
stickyHeaderIndices,
|
|
94
114
|
ItemSeparatorComponent: entrySeparatorComponent,
|
|
95
115
|
...rest,
|
package/build/components.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// reconciler maps those to shared's mutation API, which resolves them to Fabric
|
|
4
4
|
// view names at commit.
|
|
5
5
|
import { createElement } from 'react';
|
|
6
|
-
import { resolveAccessibilityProps, } from '@symbiote-native/components';
|
|
6
|
+
import { resolveAccessibilityProps, resolveTextProps, } from '@symbiote-native/components';
|
|
7
7
|
// RN's modern `id` is just a W3C-named alias for `nativeID`: View.js copies it over
|
|
8
8
|
// (`processedProps.nativeID = id`), so `id` wins when both are set. We fold it here and
|
|
9
9
|
// blank the alias so a raw `id` never reaches Fabric (every non-function prop passes
|
|
@@ -14,4 +14,4 @@ function resolveId({ id, ...rest }) {
|
|
|
14
14
|
return { ...rest, nativeID: id };
|
|
15
15
|
}
|
|
16
16
|
export const View = props => createElement('symbiote-view', resolveAccessibilityProps(resolveId(props)));
|
|
17
|
-
export const Text = props => createElement('symbiote-text', resolveAccessibilityProps(props));
|
|
17
|
+
export const Text = props => createElement('symbiote-text', resolveTextProps(resolveAccessibilityProps(props)));
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// call site (e.g. a ref to a persistent "overlay host" View near your app root) — not a second,
|
|
11
11
|
// independently-mounted SymbioteSurface. Cross-surface targets aren't wired: resetAfterCommit only
|
|
12
12
|
// fires for the primary root's own container, so a different surface's tree would never repaint.
|
|
13
|
-
import { isSymbioteNode, SymbioteSurface } from '@symbiote-native/engine';
|
|
13
|
+
import { isSymbioteNode, SymbioteSurface, } from '@symbiote-native/engine';
|
|
14
14
|
import reconciler from '../host-config.js';
|
|
15
15
|
export function createPortal(children, container, key) {
|
|
16
16
|
if (!(container instanceof SymbioteSurface) && !isSymbioteNode(container)) {
|
|
@@ -15,7 +15,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
15
15
|
// nowhere to bounce back to. `Out` lives in whichever surface should PAINT the content; `In`
|
|
16
16
|
// lives anywhere - same surface or a different one - and never touches a Fabric node
|
|
17
17
|
// directly, so there's no "target must already be mounted" guard to satisfy.
|
|
18
|
-
import { Fragment, useEffect, useId, useSyncExternalStore } from 'react';
|
|
18
|
+
import { Fragment, useEffect, useId, useSyncExternalStore, } from 'react';
|
|
19
19
|
export function createTunnel() {
|
|
20
20
|
const items = new Map();
|
|
21
21
|
const listeners = new Set();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// subscribing to close the gap between the render-time get and the effect-time
|
|
5
5
|
// listener. Only the window metrics changing triggers a re-render.
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
7
|
-
import { Dimensions } from '@symbiote-native/engine';
|
|
7
|
+
import { Dimensions, } from '@symbiote-native/engine';
|
|
8
8
|
export function useWindowDimensions() {
|
|
9
9
|
const [dimensions, setDimensions] = useState(() => Dimensions.get('window'));
|
|
10
10
|
useEffect(() => {
|
package/build/host-config.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// canary: a known-good driver exercising shared end to end.
|
|
5
5
|
import createReconciler from 'react-reconciler';
|
|
6
6
|
import { createContext } from 'react';
|
|
7
|
-
import { appendChild, createElement, createRawText, insertBefore, removeChild, routeProp, setText, SymbioteSurface, } from '@symbiote-native/engine';
|
|
7
|
+
import { appendChild, createElement, createRawText, insertBefore, removeChild, routeProp, setNodeHidden, setText, SymbioteSurface, } from '@symbiote-native/engine';
|
|
8
8
|
import { DefaultEventPriority, DiscreteEventPriority, NoEventPriority, } from './reconciler-constants.js';
|
|
9
9
|
import { toPublicInstance } from './host-instance.js';
|
|
10
10
|
// Intrinsic JSX type -> Fabric component name. The name table + resolver live once in
|
|
@@ -70,7 +70,9 @@ const reconciler = createReconciler({
|
|
|
70
70
|
getRootHostContext: () => ({ isInsideText: false }),
|
|
71
71
|
getChildHostContext(parentHostContext, type) {
|
|
72
72
|
const isInsideText = descriptorFor(type).isText;
|
|
73
|
-
return parentHostContext.isInsideText === isInsideText
|
|
73
|
+
return parentHostContext.isInsideText === isInsideText
|
|
74
|
+
? parentHostContext
|
|
75
|
+
: { isInsideText };
|
|
74
76
|
},
|
|
75
77
|
getPublicInstance: instance => toPublicInstance(instance),
|
|
76
78
|
prepareForCommit: () => null,
|
|
@@ -134,8 +136,12 @@ const reconciler = createReconciler({
|
|
|
134
136
|
resetTextContent: () => { },
|
|
135
137
|
hideTextInstance: node => setText(node, ''),
|
|
136
138
|
unhideTextInstance: (node, text) => setText(node, text),
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
// `Activity mode="hidden"` and a re-suspending `Suspense` both reach the tree through these.
|
|
140
|
+
// They were no-ops, so hidden content went on painting — stacked on top of whatever replaced
|
|
141
|
+
// it — and only the mount/unmount half of Activity appeared to work. The engine owns the
|
|
142
|
+
// reversible half (see setNodeHidden): the author's own style stays untouched underneath.
|
|
143
|
+
hideInstance: node => setNodeHidden(node, true),
|
|
144
|
+
unhideInstance: node => setNodeHidden(node, false),
|
|
139
145
|
beforeActiveInstanceBlur: () => { },
|
|
140
146
|
afterActiveInstanceBlur: () => { },
|
|
141
147
|
detachDeletedInstance: () => { },
|
|
@@ -146,7 +152,9 @@ const reconciler = createReconciler({
|
|
|
146
152
|
currentUpdatePriority = priority;
|
|
147
153
|
},
|
|
148
154
|
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
149
|
-
resolveUpdatePriority: () => currentUpdatePriority !== NoEventPriority
|
|
155
|
+
resolveUpdatePriority: () => currentUpdatePriority !== NoEventPriority
|
|
156
|
+
? currentUpdatePriority
|
|
157
|
+
: DefaultEventPriority,
|
|
150
158
|
maySuspendCommit: () => false,
|
|
151
159
|
NotPendingTransition: null,
|
|
152
160
|
// react's Context IS the runtime value react-reconciler's ReactContext wants
|
package/build/index.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ export type { IImageProps, IImageSource, IImageSourceProp, IResizeMode, IImageSi
|
|
|
7
7
|
export { ImageBackground } from './components/image-background';
|
|
8
8
|
export type { IImageBackgroundProps } from './components/image-background';
|
|
9
9
|
export { ScrollView } from './components/scroll-view';
|
|
10
|
-
export type { IScrollViewProps, IScrollViewHandle } from './components/scroll-view';
|
|
10
|
+
export type { IScrollViewProps, IScrollViewHandle, } from './components/scroll-view';
|
|
11
11
|
export { TextInput } from './components/text-input';
|
|
12
|
-
export type { ITextInputProps, ITextInputHandle } from './components/text-input';
|
|
12
|
+
export type { ITextInputProps, ITextInputHandle, } from './components/text-input';
|
|
13
13
|
export { InputAccessoryView } from './components/input-accessory-view';
|
|
14
14
|
export type { IInputAccessoryViewProps } from './components/input-accessory-view';
|
|
15
15
|
export { KeyboardAvoidingView } from './components/keyboard-avoiding-view';
|
|
@@ -35,12 +35,12 @@ export type { IButtonProps } from './components/button';
|
|
|
35
35
|
export { FlatList } from './components/flat-list';
|
|
36
36
|
export type { IFlatListProps, IFlatListHandle } from './components/flat-list';
|
|
37
37
|
export { SectionList } from './components/section-list';
|
|
38
|
-
export type { ISectionListProps, ISectionListHandle, ISection } from './components/section-list';
|
|
38
|
+
export type { ISectionListProps, ISectionListHandle, ISection, } from './components/section-list';
|
|
39
39
|
export { VirtualizedSectionList } from './components/virtualized-section-list';
|
|
40
40
|
export type { IVirtualizedSectionListProps, IVirtualizedSectionListHandle, } from './components/virtualized-section-list';
|
|
41
41
|
export { VirtualizedList } from './components/virtualized-list';
|
|
42
42
|
export type { IVirtualizedListProps, IVirtualizedListHandle, IViewToken, IViewableItemsChangedInfo, IViewabilityConfig, IViewabilityConfigCallbackPair, } from './components/virtualized-list';
|
|
43
|
-
export type { IViewStyle, ITextStyle, IFlexAlign, IFlexJustify } from './utils/styles';
|
|
43
|
+
export type { IViewStyle, ITextStyle, IFlexAlign, IFlexJustify, } from './utils/styles';
|
|
44
44
|
export { mount, unmount } from './render';
|
|
45
45
|
export { createPortal, type IPortalContainer } from './create-portal';
|
|
46
46
|
export { createTunnel, type ITunnel } from './create-tunnel';
|
|
@@ -51,11 +51,11 @@ export { AppRegistry, setHostRegistrar } from './modules/app-registry';
|
|
|
51
51
|
export type { IComponentProvider, IAppParameters, IRunnable, IHostRegistrar, IWrapperComponentProvider, IRegistry, IHeadlessTask, ITaskProvider, ITaskCanceller, ITaskCancelProvider, } from './modules/app-registry';
|
|
52
52
|
export { Animated, createAnimatedComponent } from './modules/animated';
|
|
53
53
|
export { Platform, StyleSheet } from '@symbiote-native/engine';
|
|
54
|
-
export { PlatformColor, DynamicColorIOS, processColor } from '@symbiote-native/engine';
|
|
54
|
+
export { PlatformColor, DynamicColorIOS, processColor, } from '@symbiote-native/engine';
|
|
55
55
|
export type { IColorValue, IOpaqueColorValue, IDynamicColorIOSTuple, } from '@symbiote-native/engine';
|
|
56
56
|
export { setNativeViewConfigSource, setColorProcessor, setDeviceEventSource, } from '@symbiote-native/engine';
|
|
57
57
|
export { dlog, isDebug } from '@symbiote-native/engine';
|
|
58
|
-
export type { INativeViewConfig, INativeViewConfigSource } from '@symbiote-native/engine';
|
|
58
|
+
export type { INativeViewConfig, INativeViewConfigSource, } from '@symbiote-native/engine';
|
|
59
59
|
export type { IPlatformStatic, IPlatformOSType, IPlatformConstantsIOS, IPlatformConstantsAndroid, IPlatformSelectSpec, } from '@symbiote-native/engine';
|
|
60
60
|
export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Settings, I18nManager, Dimensions, Appearance, AppState, Keyboard, KEYBOARD_EVENT, BackHandler, PermissionsAndroid, PERMISSIONS, RESULTS, LayoutAnimation, PixelRatio, } from '@symbiote-native/engine';
|
|
61
61
|
export type { IAlertType, IAlertButtonStyle, IAlertButton, IAlertButtons, IAlertOptions, IShareContent, IShareOptions, IShareAction, IActionSheetIOSOptions, IShareActionSheetIOSOptions, IShareActionSheetError, IUrlEvent, II18nManagerConstants, IDisplayMetrics, IDisplayMetricsAndroid, IDimensionsPayload, IDimensionsSet, IDimensionsKey, IDimensionsChangeListener, IDimensionsStatic, IColorSchemeName, IColorSchemePreference, IAppStateStatus, IAppStateEvent, IKeyboardEventName, IKeyboardEvent, IKeyboardMetrics, IBackPressEventName, IBackPressHandler, IPermission, IPermissionStatus, IRationale, ILayoutAnimationType, ILayoutAnimationProperty, ILayoutAnimationConfig, ILayoutAnimationAnim, IPixelRatioStatic, } from '@symbiote-native/engine';
|
|
@@ -69,5 +69,5 @@ export { InteractionManager } from '@symbiote-native/engine';
|
|
|
69
69
|
export type { IInteractionEvent, ISimpleTask, IPromiseTask, ITask, IHandle, } from '@symbiote-native/engine';
|
|
70
70
|
export { TouchableNativeFeedback } from './components/touchable-native-feedback';
|
|
71
71
|
export type { ITouchableNativeFeedbackProps, INativeFeedbackBackground, IThemeAttrBackground, IRippleBackground, } from './components/touchable-native-feedback';
|
|
72
|
-
export type { ISymbioteEvent, ISymbioteNode, IRootTag } from '@symbiote-native/engine';
|
|
72
|
+
export type { ISymbioteEvent, ISymbioteNode, IRootTag, } from '@symbiote-native/engine';
|
|
73
73
|
export type { ICellLayout, ISeparatorProps, ISeparators, IModalOrientationChangeEvent, IPressableAndroidRippleConfig, IEnterKeyHint, IInputMode, ISubmitBehavior, ITextInputSelection, IImageStatics, } from '@symbiote-native/components';
|
package/build/index.js
CHANGED
|
@@ -49,7 +49,7 @@ export { Platform, StyleSheet } from '@symbiote-native/engine';
|
|
|
49
49
|
// Color utilities: PlatformColor / DynamicColorIOS build opaque platform colors;
|
|
50
50
|
// processColor runs a color through the injected platform processor. All pure /
|
|
51
51
|
// seam-backed, so they live in shared and the adapter re-exports them.
|
|
52
|
-
export { PlatformColor, DynamicColorIOS, processColor } from '@symbiote-native/engine';
|
|
52
|
+
export { PlatformColor, DynamicColorIOS, processColor, } from '@symbiote-native/engine';
|
|
53
53
|
// The three app-entry seams, wired once on a real host, so the barrel exposes them together.
|
|
54
54
|
// setNativeViewConfigSource hands the engine RN's ViewConfig registry, which is how third-party
|
|
55
55
|
// Fabric views auto-derive their metadata:
|
|
@@ -74,7 +74,9 @@ export function createAnimatedComponent(Component) {
|
|
|
74
74
|
const passthroughStyle = readPassthroughStyle(passthrough);
|
|
75
75
|
if (passthroughStyle !== undefined) {
|
|
76
76
|
reduced.style =
|
|
77
|
-
reduced.style === undefined
|
|
77
|
+
reduced.style === undefined
|
|
78
|
+
? passthroughStyle
|
|
79
|
+
: [reduced.style, passthroughStyle];
|
|
78
80
|
}
|
|
79
81
|
const childProps = Object.assign(Object.create(null), reduced, { ref: captureRef });
|
|
80
82
|
return createElement(Component, childProps);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AnimatedValue, AnimatedValueXY, AnimatedColor, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent } from '@symbiote-native/engine';
|
|
2
2
|
import { createAnimatedComponent } from './create-animated-component';
|
|
3
3
|
export { createAnimatedComponent } from './create-animated-component';
|
|
4
|
-
export { AnimatedProps, AnimatedStyle, AnimatedTransform } from '@symbiote-native/engine';
|
|
4
|
+
export { AnimatedProps, AnimatedStyle, AnimatedTransform, } from '@symbiote-native/engine';
|
|
5
5
|
export declare const Animated: {
|
|
6
6
|
Value: typeof AnimatedValue;
|
|
7
7
|
ValueXY: typeof AnimatedValueXY;
|