@symbiote-native/solid 0.0.1 → 1.0.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 +25 -14
- package/babel-lower-host-primitives.cjs +323 -0
- package/babel-preset.cjs +5 -0
- package/build/bootstrap.d.ts +7 -0
- package/build/bootstrap.js +18 -0
- package/build/components/activity-indicator/index.android.d.ts +2 -0
- package/build/components/activity-indicator/index.android.js +11 -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 +8 -0
- package/build/components/activity-indicator/shared.js +51 -0
- package/build/components/button.d.ts +7 -0
- package/build/components/button.jsx +45 -0
- package/build/components/flat-list/index.d.ts +10 -0
- package/build/components/flat-list/index.jsx +118 -0
- package/build/components/image-background.d.ts +10 -0
- package/build/components/image-background.jsx +74 -0
- package/build/components/image.d.ts +9 -0
- package/build/components/image.js +75 -0
- package/build/components/input-accessory-view.d.ts +11 -0
- package/build/components/input-accessory-view.jsx +39 -0
- package/build/components/keyboard-avoiding-view.d.ts +15 -0
- package/build/components/keyboard-avoiding-view.jsx +152 -0
- package/build/components/modal.d.ts +24 -0
- package/build/components/modal.jsx +125 -0
- package/build/components/pressable.d.ts +31 -0
- package/build/components/pressable.jsx +211 -0
- package/build/components/refresh-control.d.ts +18 -0
- package/build/components/refresh-control.jsx +36 -0
- package/build/components/safe-area-view.d.ts +11 -0
- package/build/components/safe-area-view.jsx +24 -0
- package/build/components/scroll-view/index.android.d.ts +2 -0
- package/build/components/scroll-view/index.android.js +11 -0
- package/build/components/scroll-view/index.d.ts +2 -0
- package/build/components/scroll-view/index.ios.d.ts +2 -0
- package/build/components/scroll-view/index.ios.js +8 -0
- package/build/components/scroll-view/index.js +9 -0
- package/build/components/scroll-view/shared.d.ts +85 -0
- package/build/components/scroll-view/shared.jsx +334 -0
- package/build/components/scroll-view/sticky-header.d.ts +18 -0
- package/build/components/scroll-view/sticky-header.jsx +182 -0
- package/build/components/section-list/index.d.ts +6 -0
- package/build/components/section-list/index.jsx +8 -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 +11 -0
- package/build/components/switch/index.js +4 -0
- package/build/components/switch/shared.d.ts +11 -0
- package/build/components/switch/shared.js +87 -0
- package/build/components/text-input.d.ts +10 -0
- package/build/components/text-input.js +290 -0
- package/build/components/text.d.ts +26 -0
- package/build/components/text.jsx +27 -0
- package/build/components/touchable/index.d.ts +22 -0
- package/build/components/touchable/index.jsx +247 -0
- package/build/components/touchable-native-feedback/index.d.ts +16 -0
- package/build/components/touchable-native-feedback/index.jsx +68 -0
- package/build/components/view.d.ts +32 -0
- package/build/components/view.jsx +49 -0
- package/build/components/virtualized-list/index.android.d.ts +2 -0
- package/build/components/virtualized-list/index.android.js +9 -0
- package/build/components/virtualized-list/index.d.ts +1 -0
- package/build/components/virtualized-list/index.ios.d.ts +2 -0
- package/build/components/virtualized-list/index.ios.js +9 -0
- package/build/components/virtualized-list/index.js +4 -0
- package/build/components/virtualized-list/shared.d.ts +75 -0
- package/build/components/virtualized-list/shared.jsx +782 -0
- package/build/components/virtualized-section-list/index.d.ts +62 -0
- package/build/components/virtualized-section-list/index.jsx +145 -0
- package/build/components.d.ts +43 -0
- package/build/components.js +54 -0
- package/build/create-portal/index.d.ts +11 -0
- package/build/create-portal/index.jsx +88 -0
- package/build/create-tunnel/index.d.ts +13 -0
- package/build/create-tunnel/index.jsx +53 -0
- package/build/descriptor-to-solid.d.ts +3 -0
- package/build/descriptor-to-solid.js +91 -0
- package/build/host-instance.d.ts +2 -0
- package/build/host-instance.js +36 -0
- package/build/index.d.ts +31 -0
- package/build/index.js +98 -0
- package/build/jsx-runtime.d.ts +26 -0
- package/build/jsx-runtime.js +46 -0
- package/build/modules/animated/create-animated-component.d.ts +10 -0
- package/build/modules/animated/create-animated-component.jsx +127 -0
- package/build/modules/animated/index.d.ts +54 -0
- package/build/modules/animated/index.js +78 -0
- package/build/modules/app-registry/index.d.ts +7 -0
- package/build/modules/app-registry/index.js +37 -0
- package/build/modules/status-bar.d.ts +7 -0
- package/build/modules/status-bar.js +45 -0
- package/build/primitives/create-color-scheme.d.ts +3 -0
- package/build/primitives/create-color-scheme.js +33 -0
- package/build/primitives/create-window-dimensions.d.ts +3 -0
- package/build/primitives/create-window-dimensions.js +34 -0
- package/build/register.d.ts +1 -0
- package/build/register.js +31 -0
- package/build/render.d.ts +7 -0
- package/build/render.js +91 -0
- package/build/renderer.d.ts +8 -0
- package/build/renderer.js +259 -0
- package/build/state-style.d.ts +1 -0
- package/build/state-style.js +13 -0
- package/build/utils/host-ref.d.ts +3 -0
- package/build/utils/host-ref.js +22 -0
- package/build/utils/stable-keys.d.ts +1 -0
- package/build/utils/stable-keys.js +31 -0
- package/package.json +24 -8
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// ScrollView — the Solid lifecycle half. The Fabric tree is nested: a scroll view wraps a content
|
|
2
|
+
// view that holds the children (RN's own ScrollView.js shape). Everything platform- and
|
|
3
|
+
// framework-invariant already exists in @symbiote-native/components and is CALLED, never re-derived:
|
|
4
|
+
// `selectScrollIntrinsics` (per-axis tags + base styles), `resolveDecelerationRate`,
|
|
5
|
+
// `resolveScrollForwarding` (which onScroll path, the 1/16 throttle defaults, the inverted viewport
|
|
6
|
+
// capture, collapsableChildren), `didContentSizeChange`, `buildScrollViewHandle`,
|
|
7
|
+
// `splitLayoutProps`, `attachStickyScroll`, `forwardScrollEvent`, `resolveAccessibilityProps`.
|
|
8
|
+
// Solid supplies signals, effects and the element assembly.
|
|
9
|
+
//
|
|
10
|
+
// THREE THINGS ARE SOLID-SPECIFIC AND NONE OF THEM IS COSMETIC.
|
|
11
|
+
//
|
|
12
|
+
// 1. There is no reconciler between what this file returns and the host nodes. React diffs fibers,
|
|
13
|
+
// Vue diffs vnodes, Svelte patches in place; Solid's `insert` REPLACES. So the tree is built ONCE
|
|
14
|
+
// and every prop rides a `spread` render effect on the SAME node — the identity
|
|
15
|
+
// dispatchViewCommand, the commit mirror and native-owned scroll offset all key on. The only
|
|
16
|
+
// rebuild is when the host TAG itself has to change (see `treeShape` below).
|
|
17
|
+
// 2. A JSX element prop is a GETTER that CREATES the element on read, so `refreshControl` is read
|
|
18
|
+
// exactly once. A second read would build a second refresh-control node.
|
|
19
|
+
// 3. The commit is microtask-coalesced (`requestCommit`), so at mount time the scroll node has no
|
|
20
|
+
// Fabric tag. Anything native wired then — here, the sticky scroll attach — goes through
|
|
21
|
+
// `whenCommitted` or it silently no-ops with no retry. The imperative handle is safe without it
|
|
22
|
+
// for a different reason: `buildScrollViewHandle` takes a LAZY node getter and every command
|
|
23
|
+
// re-reads it, so a handle handed out at setup keeps working once the node commits.
|
|
24
|
+
//
|
|
25
|
+
// NOTHING here destructures `props`. Solid props are getters and a component body runs ONCE;
|
|
26
|
+
// `splitProps` is the idiomatic split that keeps the rest reactive, and every read below sits inside
|
|
27
|
+
// an accessor, a memo, or an event handler.
|
|
28
|
+
import { children, createEffect, createMemo, createRenderEffect, createSignal, on, onCleanup, splitProps, untrack, } from 'solid-js';
|
|
29
|
+
import { attachStickyScroll, buildScrollViewHandle, didContentSizeChange, forwardScrollEvent, nextStickyHeaderY, readLayoutDimension, resolveAccessibilityProps, resolveDecelerationRate, resolveScrollForwarding, selectScrollIntrinsics, splitLayoutProps, } from '@symbiote-native/components';
|
|
30
|
+
import { AnimatedValue, dlog, event as animatedEvent, isNativeAnimatedAvailable, isSymbioteNode, resolveClassName, whenCommitted, } from '@symbiote-native/engine';
|
|
31
|
+
import { createElement, insert, insertNode, setProp, spread, } from '../../renderer.js';
|
|
32
|
+
import { withStableKeys } from '../../utils/stable-keys.js';
|
|
33
|
+
import { wrapStickyHeaders, } from './sticky-header.jsx';
|
|
34
|
+
// Consumed by the lifecycle itself; everything else forwards onto the scroll-view host node.
|
|
35
|
+
// onContentSizeChange is synthesized from the content view's onLayout and must never reach Fabric —
|
|
36
|
+
// a function prop crashes Android's folly::dynamic serializer. Same for the sticky props, which
|
|
37
|
+
// native ignores, and `refreshControl`, which is an element.
|
|
38
|
+
const HANDLED_PROPS = [
|
|
39
|
+
'style',
|
|
40
|
+
'class',
|
|
41
|
+
'contentContainerStyle',
|
|
42
|
+
'horizontal',
|
|
43
|
+
'decelerationRate',
|
|
44
|
+
'refreshControl',
|
|
45
|
+
'stickyHeaderIndices',
|
|
46
|
+
'invertStickyHeaders',
|
|
47
|
+
'StickyHeaderComponent',
|
|
48
|
+
'onContentSizeChange',
|
|
49
|
+
'onLayout',
|
|
50
|
+
'onScroll',
|
|
51
|
+
'scrollEventThrottle',
|
|
52
|
+
'children',
|
|
53
|
+
'ref',
|
|
54
|
+
];
|
|
55
|
+
// `maintainVisibleContentPosition`, `snapToAlignment` and `nestedScrollEnabled` are deliberately NOT
|
|
56
|
+
// in that list: the lifecycle READS them (for resolveScrollForwarding and RN's nested-scroll
|
|
57
|
+
// default) while native also needs them, so they forward through `rest` untouched.
|
|
58
|
+
function hostElement(tag) {
|
|
59
|
+
const node = createElement(tag);
|
|
60
|
+
// Narrowing, not defensive: the renderer types createElement over its IHostNode union (which
|
|
61
|
+
// includes the surface), while everything below needs a real host node.
|
|
62
|
+
if (!isSymbioteNode(node))
|
|
63
|
+
throw new Error(`ScrollView: ${tag} did not create a host node`);
|
|
64
|
+
return node;
|
|
65
|
+
}
|
|
66
|
+
export function createScrollView(platform) {
|
|
67
|
+
return function ScrollView(props) {
|
|
68
|
+
const [local, rest] = splitProps(props, HANDLED_PROPS);
|
|
69
|
+
// The scroll-view host node, held by IDENTITY in a plain variable. A store or any proxy wrapper
|
|
70
|
+
// would become a different key than the one the engine's commit mirror holds, and every
|
|
71
|
+
// imperative command would silently no-op (symbiote-engine-core §3).
|
|
72
|
+
let scrollNode = null;
|
|
73
|
+
// A LAZY getter, not the node captured once: it is null until buildTree runs, and null again
|
|
74
|
+
// under it if the tree is ever rebuilt.
|
|
75
|
+
const handle = buildScrollViewHandle(() => scrollNode);
|
|
76
|
+
// Read ONCE and untracked — see the module header, point 2. Nothing reactive is lost: a
|
|
77
|
+
// RefreshControl's live state is `refreshing`, which is a prop of the element itself.
|
|
78
|
+
const refreshControlElement = untrack(() => local.refreshControl);
|
|
79
|
+
const refreshControlNode = isSymbioteNode(refreshControlElement)
|
|
80
|
+
? refreshControlElement
|
|
81
|
+
: undefined;
|
|
82
|
+
const isWrappingRefreshControl = platform.refreshControlMode === 'wrap' &&
|
|
83
|
+
refreshControlNode !== undefined;
|
|
84
|
+
// Drives every sticky header's translateY (RN's _scrollAnimatedValue). Allocated
|
|
85
|
+
// unconditionally, exactly like React's unconditional hook, and held by identity.
|
|
86
|
+
const scrollAnimatedValue = new AnimatedValue(0);
|
|
87
|
+
// Inverted sticky headers stick to the BOTTOM, so they need the viewport height (RN
|
|
88
|
+
// _handleLayout). Captured off the scroll view's own onLayout.
|
|
89
|
+
const [viewportHeight, setViewportHeight] = createSignal(undefined);
|
|
90
|
+
// Sticky-header cross-talk (RN's _headerLayoutYs): a child-index -> measured-y map so each
|
|
91
|
+
// header learns where the NEXT one starts — its push-off collision point. Mutated imperatively
|
|
92
|
+
// from each header's onLayout; the version bump is what lets the PREVIOUS header re-read it.
|
|
93
|
+
const headerLayoutYs = new Map();
|
|
94
|
+
const [headerLayoutVersion, setHeaderLayoutVersion] = createSignal(0);
|
|
95
|
+
// RN fires the content onLayout on every layout pass; only a real size change emits.
|
|
96
|
+
let lastContentSize = null;
|
|
97
|
+
const isHorizontal = () => local.horizontal === true;
|
|
98
|
+
const stickyHeaderIndices = createMemo(() => local.stickyHeaderIndices);
|
|
99
|
+
const hasStickyHeaders = () => {
|
|
100
|
+
const indices = stickyHeaderIndices();
|
|
101
|
+
return indices !== undefined && indices.length > 0;
|
|
102
|
+
};
|
|
103
|
+
const nativeStickyAvailable = () => hasStickyHeaders() && isNativeAnimatedAvailable();
|
|
104
|
+
// A class-name string resolves through the shared registry before it reaches the intrinsic
|
|
105
|
+
// selector, which only understands style objects/arrays.
|
|
106
|
+
const resolvedContentContainerStyle = () => {
|
|
107
|
+
const style = local.contentContainerStyle;
|
|
108
|
+
return typeof style === 'string' ? resolveClassName(style) : style;
|
|
109
|
+
};
|
|
110
|
+
const intrinsics = createMemo(() => selectScrollIntrinsics(isHorizontal(), resolvedContentContainerStyle()));
|
|
111
|
+
// `class` is normally forwarded raw and resolved per-node at commit time, but the Android
|
|
112
|
+
// RefreshControl wrap has to splitLayoutProps() BEFORE that, so a class-only layout prop
|
|
113
|
+
// (flex, height, gap, …) would otherwise never reach the wrapper and it would collapse to
|
|
114
|
+
// nothing.
|
|
115
|
+
const layoutSplitStyle = () => [
|
|
116
|
+
resolveClassName(local.class),
|
|
117
|
+
local.style,
|
|
118
|
+
];
|
|
119
|
+
const splitStyles = createMemo(() => splitLayoutProps(layoutSplitStyle()));
|
|
120
|
+
const forwarding = createMemo(() => resolveScrollForwarding({
|
|
121
|
+
hasStickyHeaders: hasStickyHeaders(),
|
|
122
|
+
nativeStickyAvailable: nativeStickyAvailable(),
|
|
123
|
+
invertStickyHeaders: local.invertStickyHeaders,
|
|
124
|
+
scrollEventThrottle: local.scrollEventThrottle,
|
|
125
|
+
maintainVisibleContentPosition: props.maintainVisibleContentPosition,
|
|
126
|
+
snapToAlignment: props.snapToAlignment,
|
|
127
|
+
}));
|
|
128
|
+
// onScroll: the JS-fallback path wraps the user's handler in Animated.event so the offset drives
|
|
129
|
+
// the AnimatedValue each frame (RN's _scrollAnimatedValueAttachment); the native and plain paths
|
|
130
|
+
// forward it as-is, because the native driver attaches the value on the UI thread instead.
|
|
131
|
+
const scrollHandler = createMemo(() => {
|
|
132
|
+
const userOnScroll = local.onScroll;
|
|
133
|
+
if (forwarding().mode !== 'sticky-js')
|
|
134
|
+
return userOnScroll;
|
|
135
|
+
return animatedEvent([{ nativeEvent: { contentOffset: { y: scrollAnimatedValue } } }], userOnScroll === undefined
|
|
136
|
+
? undefined
|
|
137
|
+
: {
|
|
138
|
+
listener: (...args) => forwardScrollEvent(userOnScroll, args),
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
// onLayout on the scroll-view node: capture the viewport height for inverted sticky headers
|
|
142
|
+
// (RN _handleLayout), then call the user's handler. Passed through unchanged otherwise, so a
|
|
143
|
+
// ScrollView with neither never raises the onLayout flag prop and native never measures it.
|
|
144
|
+
const scrollLayoutHandler = createMemo(() => {
|
|
145
|
+
const userOnLayout = local.onLayout;
|
|
146
|
+
if (!forwarding().capturesViewportHeight)
|
|
147
|
+
return userOnLayout;
|
|
148
|
+
return (event) => {
|
|
149
|
+
const height = readLayoutDimension(event, 'height');
|
|
150
|
+
if (height !== undefined)
|
|
151
|
+
setViewportHeight(height);
|
|
152
|
+
userOnLayout?.(event);
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
const handleContentLayout = (event) => {
|
|
156
|
+
const width = readLayoutDimension(event, 'width');
|
|
157
|
+
const height = readLayoutDimension(event, 'height');
|
|
158
|
+
if (width === undefined || height === undefined)
|
|
159
|
+
return;
|
|
160
|
+
if (!didContentSizeChange(lastContentSize, { width, height }))
|
|
161
|
+
return;
|
|
162
|
+
lastContentSize = { width, height };
|
|
163
|
+
dlog(`Solid ScrollView contentSizeChange ${width}x${height}`);
|
|
164
|
+
local.onContentSizeChange?.(width, height);
|
|
165
|
+
};
|
|
166
|
+
const onHeaderLayoutY = (index, y) => {
|
|
167
|
+
if (headerLayoutYs.get(index) === y)
|
|
168
|
+
return;
|
|
169
|
+
headerLayoutYs.set(index, y);
|
|
170
|
+
dlog(`Solid ScrollView sticky-header layoutY index=${index} y=${y}`);
|
|
171
|
+
setHeaderLayoutVersion(tick => tick + 1);
|
|
172
|
+
};
|
|
173
|
+
const readNextHeaderLayoutY = (indexOfIndex) => {
|
|
174
|
+
// Read the bump FIRST so this stays a dependency of whichever header calls it — that is what
|
|
175
|
+
// turns "a later header measured" into "the previous header rebuilds its collision range".
|
|
176
|
+
headerLayoutVersion();
|
|
177
|
+
const indices = stickyHeaderIndices();
|
|
178
|
+
if (indices === undefined)
|
|
179
|
+
return undefined;
|
|
180
|
+
return nextStickyHeaderY(indices, indexOfIndex, headerLayoutYs);
|
|
181
|
+
};
|
|
182
|
+
// withStableKeys on BOTH bags: resolveAccessibilityProps has two branches with different key
|
|
183
|
+
// sets, and several keys below are conditional. Solid's `spread` walks only the CURRENT keys and
|
|
184
|
+
// has no removal pass, so a vanished key would keep its last value on the native view forever
|
|
185
|
+
// (.claude/rules/solid-descriptor-bridge.md §1).
|
|
186
|
+
const outerBag = withStableKeys(() => {
|
|
187
|
+
const bag = {
|
|
188
|
+
...resolveAccessibilityProps(rest),
|
|
189
|
+
};
|
|
190
|
+
// RN defaults nested scrolling ON (ScrollView.js `nestedScrollEnabled ?? true`): Android needs
|
|
191
|
+
// it for a scrollable nested inside another to scroll independently; iOS handles nesting
|
|
192
|
+
// natively. The Android RefreshControl wrap forces it, so the inner scroll view takes the
|
|
193
|
+
// gesture before the refresh parent.
|
|
194
|
+
bag.nestedScrollEnabled = isWrappingRefreshControl
|
|
195
|
+
? true
|
|
196
|
+
: (props.nestedScrollEnabled ?? true);
|
|
197
|
+
// Load-bearing on iOS (it flips RCTScrollView's axis), ignored by Android's dedicated
|
|
198
|
+
// horizontal manager — so forward it whenever it is defined.
|
|
199
|
+
if (local.horizontal !== undefined)
|
|
200
|
+
bag.horizontal = local.horizontal;
|
|
201
|
+
const rate = local.decelerationRate;
|
|
202
|
+
if (rate !== undefined)
|
|
203
|
+
bag.decelerationRate = resolveDecelerationRate(rate);
|
|
204
|
+
// Base style UNDER the user style, so an explicit height / flexDirection still wins. Under the
|
|
205
|
+
// Android wrap only the VISUAL half stays here; the LAYOUT half moved to the wrapper.
|
|
206
|
+
bag.style = [
|
|
207
|
+
intrinsics().scrollViewBaseStyle,
|
|
208
|
+
isWrappingRefreshControl ? splitStyles().inner : local.style,
|
|
209
|
+
];
|
|
210
|
+
// Stripped under the wrap: layoutSplitStyle already folded the resolved class into
|
|
211
|
+
// outer/inner, so forwarding it raw too would re-apply its LAYOUT half a second time.
|
|
212
|
+
if (!isWrappingRefreshControl)
|
|
213
|
+
bag.class = local.class;
|
|
214
|
+
bag.onScroll = scrollHandler();
|
|
215
|
+
bag.onLayout = scrollLayoutHandler();
|
|
216
|
+
bag.scrollEventThrottle = forwarding().scrollEventThrottle;
|
|
217
|
+
return bag;
|
|
218
|
+
});
|
|
219
|
+
// `collapsable: false` is load-bearing on Android: the content container is a layout-only view
|
|
220
|
+
// that Fabric would otherwise flatten away, hoisting the cells up as DIRECT children of the
|
|
221
|
+
// scroll view — which hosts exactly one ("addViewAt" crash). iOS never flattens.
|
|
222
|
+
const contentBag = withStableKeys(() => {
|
|
223
|
+
const bag = {
|
|
224
|
+
style: intrinsics().contentStyle,
|
|
225
|
+
collapsable: false,
|
|
226
|
+
};
|
|
227
|
+
// maintainVisibleContentPosition (and Android snapToAlignment) anchor against the metrics of
|
|
228
|
+
// MOUNTED cell views, so RN keeps them un-flattened too (ScrollView.js preserveChildren).
|
|
229
|
+
if (forwarding().collapsableChildren)
|
|
230
|
+
bag.collapsableChildren = false;
|
|
231
|
+
if (local.onContentSizeChange !== undefined)
|
|
232
|
+
bag.onLayout = handleContentLayout;
|
|
233
|
+
return bag;
|
|
234
|
+
});
|
|
235
|
+
function mountChildren(content) {
|
|
236
|
+
if (!hasStickyHeaders()) {
|
|
237
|
+
// The plain path hands `insert` the raw children accessor, so its own nested-effect
|
|
238
|
+
// machinery keeps updates fine-grained — a <For> inside re-runs only its own insert.
|
|
239
|
+
insert(content, () => local.children);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Sticky wrapping needs an INDEXABLE list, which is what solid's `children()` helper resolves
|
|
243
|
+
// to. It costs a coarser update (any child change re-runs the whole wrap), which is why it is
|
|
244
|
+
// created ONLY here and not for every ScrollView — and it must not coexist with the accessor
|
|
245
|
+
// above, since reading `local.children` twice would build the subtree twice.
|
|
246
|
+
const resolved = children(() => local.children);
|
|
247
|
+
insert(content, () => wrapStickyHeaders(resolved.toArray(), {
|
|
248
|
+
stickyHeaderIndices: stickyHeaderIndices() ?? [],
|
|
249
|
+
scrollAnimatedValue,
|
|
250
|
+
readInverted: () => local.invertStickyHeaders,
|
|
251
|
+
readScrollViewHeight: viewportHeight,
|
|
252
|
+
readNextHeaderLayoutY,
|
|
253
|
+
StickyHeaderComponent: local.StickyHeaderComponent,
|
|
254
|
+
onHeaderLayoutY,
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
function buildTree() {
|
|
258
|
+
const { scrollViewIntrinsic, contentIntrinsic } = intrinsics();
|
|
259
|
+
dlog(`Solid ScrollView -> ${scrollViewIntrinsic} (horizontal=${String(isHorizontal())} sticky=${String(hasStickyHeaders())})`);
|
|
260
|
+
const content = hostElement(contentIntrinsic);
|
|
261
|
+
spread(content, contentBag, true);
|
|
262
|
+
mountChildren(content);
|
|
263
|
+
const scroll = hostElement(scrollViewIntrinsic);
|
|
264
|
+
scrollNode = scroll;
|
|
265
|
+
spread(scroll, outerBag, true);
|
|
266
|
+
// iOS: the RefreshControl is a childless SIBLING placed BEFORE the content container (RN
|
|
267
|
+
// ScrollView.js: {refreshControl}{contentContainer}).
|
|
268
|
+
if (refreshControlNode !== undefined && !isWrappingRefreshControl) {
|
|
269
|
+
insertNode(scroll, refreshControlNode);
|
|
270
|
+
}
|
|
271
|
+
insertNode(scroll, content);
|
|
272
|
+
if (refreshControlNode === undefined || !isWrappingRefreshControl)
|
|
273
|
+
return scroll;
|
|
274
|
+
// Android: the RefreshControl WRAPS the scroll view. React does this with cloneElement and Vue
|
|
275
|
+
// by re-invoking the VNode's type; Solid has neither, because the element arrived already
|
|
276
|
+
// built — so the scroll view is nested into the existing node and the outer style is written
|
|
277
|
+
// onto it. A render effect, not a one-shot set, so a later style/class change still moves.
|
|
278
|
+
//
|
|
279
|
+
// This OVERRIDES a `style` the app put on the RefreshControl itself, exactly as React's
|
|
280
|
+
// cloneElement({style}) does. Unlike React's, the two writers are independent render effects,
|
|
281
|
+
// so an app that styles BOTH boxes on Android has no defined winner — pass the ScrollView's
|
|
282
|
+
// layout through `style`/`class` there and leave the control's own `style` alone.
|
|
283
|
+
createRenderEffect(() => {
|
|
284
|
+
setProp(refreshControlNode, 'style', splitStyles().outer);
|
|
285
|
+
});
|
|
286
|
+
insertNode(refreshControlNode, scroll);
|
|
287
|
+
return refreshControlNode;
|
|
288
|
+
}
|
|
289
|
+
// The scroll AXIS picks a different host TAG (horizontal is a separate ViewManager on Android)
|
|
290
|
+
// and sticky headers pick a different children pipeline. Solid cannot swap a tag under a live
|
|
291
|
+
// node, so either flip REBUILDS — which is exactly what React does when an element type changes.
|
|
292
|
+
// `on()` runs buildTree untracked, so every other read inside it (style, class, handlers,
|
|
293
|
+
// contentContainerStyle) re-props the SAME nodes through `spread` instead of rebuilding them.
|
|
294
|
+
const treeShape = createMemo(() => `${String(isHorizontal())}:${String(hasStickyHeaders())}`);
|
|
295
|
+
const tree = createMemo(on(treeShape, () => buildTree()));
|
|
296
|
+
// Solid's `ref` is a COMPILE-TIME construct: by the time a component body reads `props.ref`, a
|
|
297
|
+
// `ref={scroller}` call site has already been rewritten into a callback. The full rationale (and
|
|
298
|
+
// why the declared type still has to be the `Ref<T>` union) is in utils/host-ref.ts, whose
|
|
299
|
+
// helper is typed for a host node — this hands back the imperative handle instead.
|
|
300
|
+
//
|
|
301
|
+
// Called AFTER `tree` has built at least once, not right after `handle` is constructed.
|
|
302
|
+
// `createAnimatedComponent`'s captureRef (resolveHostNode) reads handle.getScrollNode()
|
|
303
|
+
// EAGERLY, once, at ref-call time - it never re-reads the lazy getter later. React's
|
|
304
|
+
// useImperativeHandle and Vue's expose() fire only after the child's own mount work, so they
|
|
305
|
+
// never saw a null handle; Solid's `ref` is called by the component itself and has to be
|
|
306
|
+
// sequenced by hand. Calling it before `buildTree()` gave captureRef a permanently-null node,
|
|
307
|
+
// so Animated.ScrollView's onScroll never native-attached - a scroll-linked header fade froze
|
|
308
|
+
// at its initial value on this adapter only.
|
|
309
|
+
if (typeof local.ref === 'function')
|
|
310
|
+
local.ref(handle);
|
|
311
|
+
// Drive the sticky scroll value on the native UI thread (RN's attachNativeEvent /
|
|
312
|
+
// _updateAnimatedNodeAttachment), so the header interpolations ride scroll natively with no JS
|
|
313
|
+
// jitter. `tree()` is read for its dependency: a rebuild means a NEW scroll node to attach to.
|
|
314
|
+
createEffect(() => {
|
|
315
|
+
tree();
|
|
316
|
+
if (!nativeStickyAvailable())
|
|
317
|
+
return;
|
|
318
|
+
const node = scrollNode;
|
|
319
|
+
if (node === null)
|
|
320
|
+
return;
|
|
321
|
+
let detach;
|
|
322
|
+
// The engine commits on a microtask, so this node has no Fabric tag yet on the first run and
|
|
323
|
+
// the attach would no-op with no retry. whenCommitted is that retry.
|
|
324
|
+
const cancel = whenCommitted(node, () => {
|
|
325
|
+
detach = attachStickyScroll(node, scrollAnimatedValue);
|
|
326
|
+
});
|
|
327
|
+
onCleanup(() => {
|
|
328
|
+
cancel();
|
|
329
|
+
detach?.();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
return tree;
|
|
333
|
+
};
|
|
334
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JSX } from '../../jsx-runtime';
|
|
2
|
+
import { type IStickyHeaderProps } from '@symbiote-native/components';
|
|
3
|
+
import { type AnimatedValue } from '@symbiote-native/engine';
|
|
4
|
+
export type IStickyHeaderComponentProps = IStickyHeaderProps & {
|
|
5
|
+
children?: JSX.Element;
|
|
6
|
+
};
|
|
7
|
+
export type IStickyHeaderComponentType = (props: IStickyHeaderComponentProps) => JSX.Element;
|
|
8
|
+
export declare function ScrollViewStickyHeader(props: IStickyHeaderComponentProps): JSX.Element;
|
|
9
|
+
export interface IStickyWrapInputs {
|
|
10
|
+
stickyHeaderIndices: number[];
|
|
11
|
+
scrollAnimatedValue: AnimatedValue;
|
|
12
|
+
readInverted: () => boolean | undefined;
|
|
13
|
+
readScrollViewHeight: () => number | undefined;
|
|
14
|
+
readNextHeaderLayoutY: (indexOfIndex: number) => number | undefined;
|
|
15
|
+
StickyHeaderComponent: IStickyHeaderComponentType | undefined;
|
|
16
|
+
onHeaderLayoutY: (index: number, y: number) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function wrapStickyHeaders(children: readonly JSX.Element[], inputs: IStickyWrapInputs): JSX.Element[];
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Sticky headers — the Solid lifecycle half of the JS layer RN implements in ScrollView.js /
|
|
2
|
+
// ScrollViewStickyHeader.js.
|
|
3
|
+
//
|
|
4
|
+
// RN does stickiness PURELY IN JS: ScrollView wraps each flagged child in a ScrollViewStickyHeader
|
|
5
|
+
// fed by ONE scroll AnimatedValue, and the native scroll view ignores `stickyHeaderIndices`
|
|
6
|
+
// entirely — forwarding that array to Fabric is a silent no-op, so we replicate the JS layer
|
|
7
|
+
// instead. The math (top and inverted input/output ranges) is `computeStickyInterpolation` and the
|
|
8
|
+
// DECISIONS (the zero-swallow gate, the debounce window, when to rebuild) are `reduceSticky`, both
|
|
9
|
+
// in @symbiote-native/components and both shared verbatim with React, Vue and Angular. This file
|
|
10
|
+
// contributes only Solid's reactivity: one signal for the interpolation node, one version counter,
|
|
11
|
+
// the listener/debounce wiring, and the child-wrapping.
|
|
12
|
+
//
|
|
13
|
+
// WHY THE WRAPPER TAKES ITS REACTIVE INPUTS AS PROPS AND NOT AS RESOLVED VALUES. Every prop below
|
|
14
|
+
// that changes over time — `nextHeaderLayoutY` (cross-talk as a later header measures),
|
|
15
|
+
// `scrollViewHeight` (the inverted viewport capture), `inverted` — is written into the JSX as a
|
|
16
|
+
// CALL, so the Solid compiler emits it as a getter and the header reads it inside its OWN effect.
|
|
17
|
+
// That is not a style choice: the wrap runs inside the content view's `insert` render effect, and
|
|
18
|
+
// `insert` REPLACES a subtree rather than diffing one, so a value read there would put those
|
|
19
|
+
// signals in the insert effect's dependency set and every header layout would tear down and rebuild
|
|
20
|
+
// every header. React/Vue/Angular can pass plain values because each has a node-reusing layer
|
|
21
|
+
// underneath; Solid does not (.claude/rules/solid-descriptor-bridge.md §4).
|
|
22
|
+
import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
|
|
23
|
+
import { createInitialStickyState, readLayoutNumber, reduceSticky, STICKY_HEADER_Z_INDEX, } from '@symbiote-native/components';
|
|
24
|
+
import { AnimatedInterpolation, Platform, dlog, isSymbioteNode, } from '@symbiote-native/engine';
|
|
25
|
+
import { Animated } from '../../modules/animated/index.js';
|
|
26
|
+
// One sticky header. Measures its own y/height through onLayout, interpolates the shared scroll
|
|
27
|
+
// offset into a translateY that pins it to the top (or the bottom, inverted) until the next header
|
|
28
|
+
// collides with it, and drives that translate through the native driver when available so the pin
|
|
29
|
+
// tracks scroll on the UI thread with no JS per frame.
|
|
30
|
+
export function ScrollViewStickyHeader(props) {
|
|
31
|
+
// The ONE folded state cell (RN's scattered useState/useRef, collapsed into IStickyHeaderState),
|
|
32
|
+
// mutated in place by reduceSticky. A plain object, never a signal: nothing reads it reactively —
|
|
33
|
+
// the two signals below are what render depends on.
|
|
34
|
+
const state = createInitialStickyState();
|
|
35
|
+
// Bumped on every committed translateY so the passthrough memo re-reads state.translateY (RN's
|
|
36
|
+
// forceRender()).
|
|
37
|
+
const [version, setVersion] = createSignal(0);
|
|
38
|
+
// Read ONCE and held by identity: the value is stable for the header's lifetime by contract (the
|
|
39
|
+
// parent allocates exactly one per ScrollView).
|
|
40
|
+
const scrollAnimatedValue = props.scrollAnimatedValue;
|
|
41
|
+
// The interpolation node currently bound into style.transform — the un-measured identity stub
|
|
42
|
+
// until the first rebuild-interpolation effect replaces it. A signal, not a plain variable:
|
|
43
|
+
// render has to repaint when it is swapped.
|
|
44
|
+
const [animatedTranslateY, setAnimatedTranslateY] = createSignal(scrollAnimatedValue.interpolate({
|
|
45
|
+
inputRange: [-1, 0],
|
|
46
|
+
outputRange: [0, 0],
|
|
47
|
+
}));
|
|
48
|
+
// Held so the next rebuild can detach the previous settled-value listener (an engine call the
|
|
49
|
+
// reducer does not own) and cleanup can drop both.
|
|
50
|
+
let interpolation;
|
|
51
|
+
let listenerId;
|
|
52
|
+
let debounceTimer;
|
|
53
|
+
const inputs = () => ({
|
|
54
|
+
os: Platform.OS,
|
|
55
|
+
inverted: props.inverted,
|
|
56
|
+
scrollViewHeight: props.scrollViewHeight,
|
|
57
|
+
nextHeaderLayoutY: props.nextHeaderLayoutY,
|
|
58
|
+
});
|
|
59
|
+
function runEffects(effects) {
|
|
60
|
+
for (const effect of effects) {
|
|
61
|
+
switch (effect.kind) {
|
|
62
|
+
case 'rebuild-interpolation': {
|
|
63
|
+
dlog(`Solid ScrollViewStickyHeader[y=${state.layoutY}] rebuild-interpolation ` +
|
|
64
|
+
`inputRange=${JSON.stringify(effect.inputRange)} outputRange=${JSON.stringify(effect.outputRange)}`);
|
|
65
|
+
if (interpolation !== undefined && listenerId !== undefined) {
|
|
66
|
+
interpolation.removeListener(listenerId);
|
|
67
|
+
listenerId = undefined;
|
|
68
|
+
}
|
|
69
|
+
const next = scrollAnimatedValue.interpolate({
|
|
70
|
+
inputRange: effect.inputRange,
|
|
71
|
+
outputRange: effect.outputRange,
|
|
72
|
+
});
|
|
73
|
+
// Feeds ONLY the debounced committed transform below (RN adds this listener solely on
|
|
74
|
+
// Fabric, for hit-testing); the visible pin is the native AnimatedProps connection.
|
|
75
|
+
listenerId = next.addListener(({ value }) => {
|
|
76
|
+
if (typeof value === 'number')
|
|
77
|
+
dispatch({ kind: 'animated-tick', value });
|
|
78
|
+
});
|
|
79
|
+
interpolation = next;
|
|
80
|
+
setAnimatedTranslateY(next);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'schedule-debounce':
|
|
84
|
+
// The animated value updates several times per frame; debounce the settled value into the
|
|
85
|
+
// committed transform so hit detection stays current (a Fabric issue, worse on Android).
|
|
86
|
+
if (debounceTimer !== undefined)
|
|
87
|
+
clearTimeout(debounceTimer);
|
|
88
|
+
debounceTimer = setTimeout(() => {
|
|
89
|
+
debounceTimer = undefined;
|
|
90
|
+
dispatch({ kind: 'debounce-fired', value: effect.value });
|
|
91
|
+
}, effect.delay);
|
|
92
|
+
break;
|
|
93
|
+
case 'apply-passthrough':
|
|
94
|
+
dlog(`Solid ScrollViewStickyHeader[y=${state.layoutY}] apply-passthrough translateY=${effect.translateY}`);
|
|
95
|
+
setVersion(tick => tick + 1);
|
|
96
|
+
break;
|
|
97
|
+
case 'record-header-y':
|
|
98
|
+
// Solid records through the wrapper's own onLayout closure (the public
|
|
99
|
+
// IStickyHeaderProps.onLayout contract, same as React/Vue), so the reducer never emits an
|
|
100
|
+
// index for it.
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function dispatch(action) {
|
|
106
|
+
runEffects(reduceSticky(state, action, inputs()).effects);
|
|
107
|
+
}
|
|
108
|
+
// Rebuild whenever a collision/viewport input changes (RN's [inverted, scrollViewHeight,
|
|
109
|
+
// nextHeaderLayoutY] dep array); also does the initial identity build on mount. All three are read
|
|
110
|
+
// through inputs() INSIDE the effect, which is what makes them dependencies — a guarded or
|
|
111
|
+
// deferred read would silently drop one.
|
|
112
|
+
createEffect(() => {
|
|
113
|
+
runEffects(reduceSticky(state, { kind: 'inputs-changed' }, inputs()).effects);
|
|
114
|
+
});
|
|
115
|
+
onCleanup(() => {
|
|
116
|
+
if (interpolation !== undefined && listenerId !== undefined) {
|
|
117
|
+
interpolation.removeListener(listenerId);
|
|
118
|
+
}
|
|
119
|
+
if (debounceTimer !== undefined)
|
|
120
|
+
clearTimeout(debounceTimer);
|
|
121
|
+
});
|
|
122
|
+
// RN's ScrollViewStickyHeader._onLayout additionally re-invokes the wrapped child's OWN onLayout.
|
|
123
|
+
// Deliberately not replicated: by the time a Solid component sees `children` they are already
|
|
124
|
+
// real engine nodes, and a node's listeners live in the engine's event map, not as a readable JS
|
|
125
|
+
// prop — there is nothing to re-invoke. Nothing is lost, because the child node is committed in
|
|
126
|
+
// its own right and Fabric fires its onLayout at it directly; React/Vue's forwarding actually
|
|
127
|
+
// makes a child handler run twice.
|
|
128
|
+
const onLayout = (event) => {
|
|
129
|
+
const y = readLayoutNumber(event, 'y');
|
|
130
|
+
const height = readLayoutNumber(event, 'height');
|
|
131
|
+
// Keep the previous value when a field is absent (RN sets state only on a defined read).
|
|
132
|
+
dispatch({
|
|
133
|
+
kind: 'layout',
|
|
134
|
+
y: y ?? state.layoutY,
|
|
135
|
+
height: height ?? state.layoutHeight,
|
|
136
|
+
});
|
|
137
|
+
props.onLayout(event);
|
|
138
|
+
};
|
|
139
|
+
// The EXPLICIT debounced translateY overrides the committed transform for hit-testing, while
|
|
140
|
+
// animatedTranslateY does the smooth (native-driven) pin.
|
|
141
|
+
const passthrough = createMemo(() => {
|
|
142
|
+
// Read the bump so a committed translateY repaints.
|
|
143
|
+
version();
|
|
144
|
+
if (state.translateY === null)
|
|
145
|
+
return null;
|
|
146
|
+
return { style: { transform: [{ translateY: state.translateY }] } };
|
|
147
|
+
});
|
|
148
|
+
// collapsable:false keeps the wrapper a real Yoga node; zIndex makes the pinned header paint OVER
|
|
149
|
+
// the rows scrolling up under it.
|
|
150
|
+
return (<Animated.View style={{
|
|
151
|
+
transform: [{ translateY: animatedTranslateY() }],
|
|
152
|
+
zIndex: STICKY_HEADER_Z_INDEX,
|
|
153
|
+
}} onLayout={onLayout} collapsable={false} passthroughAnimatedPropExplicitValues={passthrough()}>
|
|
154
|
+
{props.children}
|
|
155
|
+
</Animated.View>);
|
|
156
|
+
}
|
|
157
|
+
// Wrap each child flagged by `stickyHeaderIndices` in the sticky header component, fed by the shared
|
|
158
|
+
// scroll AnimatedValue (RN ScrollView.js's render-time children.map). Unflagged children pass
|
|
159
|
+
// through untouched.
|
|
160
|
+
//
|
|
161
|
+
// Cross-talk (RN's _headerLayoutYs + _onStickyHeaderLayout): each header reports its own y through
|
|
162
|
+
// `onHeaderLayoutY`, and every header is fed the y of the NEXT flagged one — the point past which it
|
|
163
|
+
// gets pushed off. The LAST flagged header has no successor, so its nextHeaderLayoutY stays
|
|
164
|
+
// undefined and it sticks indefinitely, which is correct.
|
|
165
|
+
export function wrapStickyHeaders(children, inputs) {
|
|
166
|
+
const Wrapper = inputs.StickyHeaderComponent ?? ScrollViewStickyHeader;
|
|
167
|
+
return children.map((child, index) => {
|
|
168
|
+
const indexOfIndex = inputs.stickyHeaderIndices.indexOf(index);
|
|
169
|
+
// A non-node child (a bare string or number in a scroll view) has no frame to pin and cannot be
|
|
170
|
+
// wrapped meaningfully, so it passes through like an unflagged one.
|
|
171
|
+
if (indexOfIndex === -1 || !isSymbioteNode(child))
|
|
172
|
+
return child;
|
|
173
|
+
dlog(`Solid ScrollView sticky-header wrap index=${index}`);
|
|
174
|
+
return (<Wrapper nextHeaderLayoutY={inputs.readNextHeaderLayoutY(indexOfIndex)} onLayout={(event) => {
|
|
175
|
+
const y = readLayoutNumber(event, 'y');
|
|
176
|
+
if (y !== undefined)
|
|
177
|
+
inputs.onHeaderLayoutY(index, y);
|
|
178
|
+
}} scrollAnimatedValue={inputs.scrollAnimatedValue} inverted={inputs.readInverted()} scrollViewHeight={inputs.readScrollViewHeight()}>
|
|
179
|
+
{child}
|
|
180
|
+
</Wrapper>);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JSX } from '../../jsx-runtime';
|
|
2
|
+
import { type IVirtualizedSectionListHandle, type IVirtualizedSectionListProps } from '../virtualized-section-list';
|
|
3
|
+
export type { ISection } from '../virtualized-section-list';
|
|
4
|
+
export type ISectionListProps<ItemT> = IVirtualizedSectionListProps<ItemT>;
|
|
5
|
+
export type ISectionListHandle = IVirtualizedSectionListHandle;
|
|
6
|
+
export declare function SectionList<ItemT>(props: ISectionListProps<ItemT>): JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SectionList: the public preset over VirtualizedSectionList, mirroring RN's
|
|
2
|
+
// SectionList -> VirtualizedSectionList -> VirtualizedList layering. All section flattening,
|
|
3
|
+
// windowing and imperative-scroll logic lives one layer down; this re-exposes the same surface
|
|
4
|
+
// under the SectionList name, threading the imperative ref straight through.
|
|
5
|
+
import { VirtualizedSectionList, } from '../virtualized-section-list/index.jsx';
|
|
6
|
+
export function SectionList(props) {
|
|
7
|
+
return <VirtualizedSectionList {...props}/>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Switch, Android host binding. AndroidSwitch takes trackColorForTrue / trackColorForFalse plus
|
|
2
|
+
// trackTintColor (the color for the CURRENT value, which RN computes as value ? true : false), and
|
|
3
|
+
// snaps native back via the `setNativeValue` command (Switch.js:221-225).
|
|
4
|
+
import { createSwitch } from './shared.js';
|
|
5
|
+
export const Switch = createSwitch({
|
|
6
|
+
snapBackCommand: 'setNativeValue',
|
|
7
|
+
trackColorProps: (value, trackColor) => ({
|
|
8
|
+
trackColorForFalse: trackColor?.false,
|
|
9
|
+
trackColorForTrue: trackColor?.true,
|
|
10
|
+
trackTintColor: value ? trackColor?.true : trackColor?.false,
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.ios';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Switch, iOS host binding. iOS's native Switch takes onTintColor (ON-track) / tintColor
|
|
2
|
+
// (OFF-track), and snaps native back via the `setValue` command (Switch.js:221-225) — the same
|
|
3
|
+
// facts adapters/react's, adapters/vue's and adapters/svelte's iOS bindings encode.
|
|
4
|
+
import { createSwitch } from './shared.js';
|
|
5
|
+
export const Switch = createSwitch({
|
|
6
|
+
snapBackCommand: 'setValue',
|
|
7
|
+
trackColorProps: (_value, trackColor) => ({
|
|
8
|
+
onTintColor: trackColor?.true,
|
|
9
|
+
tintColor: trackColor?.false,
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JSX } from '../../jsx-runtime';
|
|
2
|
+
import { type ISwitchPlatform, type ISwitchProps as ISwitchBaseProps } from '@symbiote-native/components';
|
|
3
|
+
import { type IClassNameValue } from '@symbiote-native/engine';
|
|
4
|
+
export type { ISwitchTrackColor } from '@symbiote-native/components';
|
|
5
|
+
export type ISwitchProps = ISwitchBaseProps & {
|
|
6
|
+
class?: IClassNameValue;
|
|
7
|
+
};
|
|
8
|
+
export type ISwitchHostPlatform = ISwitchPlatform & {
|
|
9
|
+
snapBackCommand: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function createSwitch(platform: ISwitchHostPlatform): (props: ISwitchProps) => JSX.Element;
|