@symbiote-native/svelte 0.3.0 → 2.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 +8 -10
- package/build/components/{activity-indicator/activity-indicator-props.d.ts → activity-indicator-props.d.ts} +1 -1
- package/build/components/flat-list/index.svelte +4 -4
- package/build/components/{image-background/image-background-props.d.ts → image-background-props.d.ts} +1 -1
- package/build/components/index.d.ts +6 -24
- package/build/components/index.js +0 -18
- package/build/components/keyboard-avoiding-view/index.svelte +6 -6
- package/build/components/modal/index.svelte +7 -7
- package/build/components/pressable/pressable-props.d.ts +1 -0
- package/build/components/refresh-control-props.d.ts +1 -0
- package/build/components/safe-area-view-props.d.ts +1 -0
- package/build/components/text-props.d.ts +1 -0
- package/build/components/virtualized-list/index.svelte +71 -250
- package/build/dom-shim/canonical-prop-names.d.ts +2 -0
- package/build/dom-shim/canonical-prop-names.js +320 -0
- package/build/dom-shim/document.js +1 -1
- package/build/dom-shim/element.d.ts +17 -5
- package/build/dom-shim/element.js +258 -40
- package/build/dom-shim/fold-host-bag.d.ts +1 -0
- package/build/dom-shim/fold-host-bag.js +7 -0
- package/build/dom-shim/patch-globals.js +35 -7
- package/build/dom-shim/shim-node.js +11 -0
- package/build/dom-shim/style-cache.d.ts +1 -0
- package/build/dom-shim/style-cache.js +27 -0
- package/build/dom-shim/text.js +2 -2
- package/build/host-instance.d.ts +2 -3
- package/build/host-instance.js +9 -4
- package/build/index.d.ts +8 -4
- package/build/index.js +21 -5
- package/build/intrinsic-elements.d.ts +63 -0
- package/build/modules/animated/index.d.ts +7 -27
- package/build/modules/animated/index.js +24 -34
- package/build/modules/image/index.d.ts +3 -0
- package/build/modules/image/index.js +15 -0
- package/build/preprocessor/scoped-styles.js +1 -1
- package/build/register.d.ts +1 -0
- package/build/register.js +15 -0
- package/build/root-element.js +1 -1
- package/metro-css-parser.cjs +2 -1
- package/metro-svelte-transformer.cjs +30 -12
- package/package.json +19 -9
- package/build/components/RefreshControl.svelte +0 -40
- package/build/components/RefreshControl.svelte.d.ts +0 -5
- package/build/components/SafeAreaView.svelte +0 -34
- package/build/components/SafeAreaView.svelte.d.ts +0 -5
- package/build/components/Text.svelte +0 -34
- package/build/components/Text.svelte.d.ts +0 -5
- package/build/components/View.svelte +0 -43
- package/build/components/View.svelte.d.ts +0 -5
- package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +0 -3
- package/build/components/activity-indicator/activity-indicator-platform.android.js +0 -5
- package/build/components/activity-indicator/activity-indicator-platform.d.ts +0 -1
- package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +0 -3
- package/build/components/activity-indicator/activity-indicator-platform.ios.js +0 -6
- package/build/components/activity-indicator/activity-indicator-platform.js +0 -5
- package/build/components/activity-indicator/index.svelte +0 -63
- package/build/components/activity-indicator/index.svelte.d.ts +0 -5
- package/build/components/button.svelte +0 -76
- package/build/components/button.svelte.d.ts +0 -5
- package/build/components/image/image-logic.d.ts +0 -2
- package/build/components/image/image-logic.js +0 -135
- package/build/components/image/index.d.ts +0 -3
- package/build/components/image/index.js +0 -10
- package/build/components/image/index.svelte +0 -66
- package/build/components/image/index.svelte.d.ts +0 -5
- package/build/components/image-background/index.svelte +0 -87
- package/build/components/image-background/index.svelte.d.ts +0 -5
- package/build/components/input-accessory-view/index.svelte +0 -51
- package/build/components/input-accessory-view/index.svelte.d.ts +0 -5
- package/build/components/pressable/index.svelte +0 -167
- package/build/components/pressable/index.svelte.d.ts +0 -5
- package/build/components/scroll-view/index.svelte +0 -331
- package/build/components/scroll-view/index.svelte.d.ts +0 -18
- package/build/components/scroll-view/scroll-view-platform-types.d.ts +0 -3
- package/build/components/scroll-view/scroll-view-platform.android.d.ts +0 -3
- package/build/components/scroll-view/scroll-view-platform.android.js +0 -3
- package/build/components/scroll-view/scroll-view-platform.d.ts +0 -1
- package/build/components/scroll-view/scroll-view-platform.ios.d.ts +0 -3
- package/build/components/scroll-view/scroll-view-platform.ios.js +0 -3
- package/build/components/scroll-view/scroll-view-platform.js +0 -4
- package/build/components/scroll-view/scroll-view-sticky-context.d.ts +0 -7
- package/build/components/scroll-view/scroll-view-sticky-context.js +0 -1
- package/build/components/scroll-view/sticky-header-props.d.ts +0 -10
- package/build/components/scroll-view/sticky-header-props.js +0 -1
- package/build/components/scroll-view/sticky-header.svelte +0 -287
- package/build/components/scroll-view/sticky-header.svelte.d.ts +0 -5
- package/build/components/switch/index.svelte +0 -134
- package/build/components/switch/index.svelte.d.ts +0 -5
- package/build/components/text-input/index.svelte +0 -316
- package/build/components/text-input/index.svelte.d.ts +0 -12
- package/build/components/touchable-highlight/index.svelte +0 -138
- package/build/components/touchable-highlight/index.svelte.d.ts +0 -5
- package/build/components/touchable-native-feedback/index.d.ts +0 -9
- package/build/components/touchable-native-feedback/index.js +0 -14
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +0 -49
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +0 -5
- package/build/components/touchable-opacity/index.svelte +0 -180
- package/build/components/touchable-opacity/index.svelte.d.ts +0 -5
- package/build/components/touchable-without-feedback/index.svelte +0 -74
- package/build/components/touchable-without-feedback/index.svelte.d.ts +0 -5
- package/build/modules/animated/animated-component-props.d.ts +0 -7
- package/build/modules/animated/animated-component-props.js +0 -1
- package/build/modules/animated/create-animated-component.d.ts +0 -5
- package/build/modules/animated/create-animated-component.js +0 -131
- /package/build/components/{activity-indicator/activity-indicator-props.js → activity-indicator-props.js} +0 -0
- /package/build/components/{image-background/image-background-props.js → image-background-props.js} +0 -0
- /package/build/{components/scroll-view/scroll-view-platform-types.js → intrinsic-elements.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
// VirtualizedList: real windowing over a hand-authored minimal scroll host. Only cells whose
|
|
3
3
|
// computed offset falls inside the visible window (plus a leading/trailing buffer) render;
|
|
4
|
-
// the rest collapses into two spacer
|
|
4
|
+
// the rest collapses into two spacer view nodes.
|
|
5
5
|
//
|
|
6
6
|
// The orchestration - window recompute, edge-reached, viewability, batch fill, MVCP, imperative
|
|
7
7
|
// scrolls - is the framework-agnostic `reduceList` state machine in @symbiote-native/components
|
|
@@ -10,16 +10,22 @@
|
|
|
10
10
|
// (`listState`), runs the returned EFFECTS with Svelte primitives, and renders the windowed slice
|
|
11
11
|
// with `{#each plan.cells}` (Lists have no Descriptor render fn, per svelte-adapter-dom-shim §15).
|
|
12
12
|
//
|
|
13
|
-
// This file
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// Android RefreshControl wrap style-split, and JS sticky-header wrapping are all wired directly
|
|
18
|
-
// here instead.
|
|
13
|
+
// This file authors the raw `scroll-view` intrinsic directly, and needs to: it walks an indexable
|
|
14
|
+
// `plan.cells` list rather than taking an opaque children Snippet, so it can mark sticky cells
|
|
15
|
+
// itself. (There is no ScrollView component to render instead — it was deleted 2026-09-10 and an
|
|
16
|
+
// app writes the tag too.)
|
|
19
17
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
18
|
+
// It does NOT author the CONTENT node. `registerScrollViewBehavior()` puts a `buildStructure` on
|
|
19
|
+
// the scroll tags, and exactly one thing may build `RCTScrollContentView` — emitting one here as
|
|
20
|
+
// well nests a second one inside the engine's. Everything that used to live here because the
|
|
21
|
+
// content node did (`contentContainerStyle`, the axis base style, `nestedScrollEnabled`, the
|
|
22
|
+
// Android RefreshControl wrap style-split, the JS sticky-header wrapping and its collision map)
|
|
23
|
+
// is the behavior's now; `contentContainerStyle` travels as an OWNER prop and the behavior
|
|
24
|
+
// renames it onto the slot.
|
|
25
|
+
//
|
|
26
|
+
// RefreshControl is written as an ordinary child of the scroll tag on both platforms: the
|
|
27
|
+
// behavior CLAIMS it, keeping it beside the content view on iOS and wrapping the scroll view
|
|
28
|
+
// with it on Android.
|
|
23
29
|
//
|
|
24
30
|
// `<svelte:element>` is forbidden (svelte-adapter-dom-shim skill §4/§7 - its shim surface isn't
|
|
25
31
|
// implemented), so the horizontal/vertical choice is two static branches sharing one
|
|
@@ -44,24 +50,17 @@
|
|
|
44
50
|
FIRST_INDEX,
|
|
45
51
|
INVERTED_X_STYLE,
|
|
46
52
|
INVERTED_Y_STYLE,
|
|
47
|
-
attachStickyScroll,
|
|
48
53
|
buildListPlan,
|
|
49
54
|
buildScrollViewHandle,
|
|
50
55
|
buildViewabilityPairs,
|
|
51
56
|
createInitialListState,
|
|
52
|
-
forwardScrollEvent,
|
|
53
57
|
isSeparatorGapInRange,
|
|
54
58
|
listEffectSignature,
|
|
55
|
-
nextStickyHeaderY,
|
|
56
59
|
readLayoutLength,
|
|
57
60
|
readLayoutOffset,
|
|
58
|
-
readLayoutNumber,
|
|
59
61
|
readScrollOffset,
|
|
60
62
|
reduceList,
|
|
61
63
|
resolveItemKey,
|
|
62
|
-
resolveScrollForwarding,
|
|
63
|
-
selectScrollIntrinsics,
|
|
64
|
-
splitLayoutProps,
|
|
65
64
|
type IListAction,
|
|
66
65
|
type IListEffect,
|
|
67
66
|
type IListReducerInputs,
|
|
@@ -71,24 +70,11 @@
|
|
|
71
70
|
type ISeparators,
|
|
72
71
|
} from '@symbiote-native/components';
|
|
73
72
|
import {
|
|
74
|
-
AnimatedValue,
|
|
75
73
|
dlog,
|
|
76
|
-
event as animatedEvent,
|
|
77
|
-
isNativeAnimatedAvailable,
|
|
78
|
-
resolveClassName,
|
|
79
74
|
type ISymbioteEvent,
|
|
80
75
|
type ISymbioteNode,
|
|
81
76
|
} from '@symbiote-native/engine';
|
|
82
|
-
import { resolveSvelteClass } from '../../class-value';
|
|
83
|
-
import { setContext } from 'svelte';
|
|
84
77
|
import type { ShimElement } from '../../dom-shim';
|
|
85
|
-
import RefreshControl from '../RefreshControl.svelte';
|
|
86
|
-
import { PLATFORM } from '../scroll-view/scroll-view-platform';
|
|
87
|
-
import ScrollViewStickyHeader from '../scroll-view/sticky-header.svelte';
|
|
88
|
-
import {
|
|
89
|
-
SCROLL_VIEW_STICKY_CONTEXT_KEY,
|
|
90
|
-
type IScrollViewStickyContext,
|
|
91
|
-
} from '../scroll-view/scroll-view-sticky-context';
|
|
92
78
|
import {
|
|
93
79
|
pickAccessibilityProps,
|
|
94
80
|
type IVirtualizedListProps as IProps,
|
|
@@ -129,22 +115,6 @@
|
|
|
129
115
|
let viewableTimer: ReturnType<typeof setTimeout> | null = null;
|
|
130
116
|
let batchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
131
117
|
|
|
132
|
-
// Sticky headers: this file hand-rolls the raw scroll intrinsic (see the header comment), so it
|
|
133
|
-
// can't lean on ScrollView.svelte's sticky wiring directly - it mirrors the same
|
|
134
|
-
// scrollAnimatedValue/attachStickyScroll/context wiring here, applied per windowed cell.
|
|
135
|
-
const scrollAnimatedValue = new AnimatedValue(0);
|
|
136
|
-
let viewportHeight = $state<number | undefined>(undefined);
|
|
137
|
-
// y of each measured sticky header, keyed by ORIGINAL list index (RN's _headerLayoutYs) - plain,
|
|
138
|
-
// not $state; stickyVersion (below) drives re-derivation of the collision math that reads it.
|
|
139
|
-
const headerLayoutYs = new Map<number, number>();
|
|
140
|
-
let stickyVersion = $state(0);
|
|
141
|
-
|
|
142
|
-
setContext<IScrollViewStickyContext>(SCROLL_VIEW_STICKY_CONTEXT_KEY, {
|
|
143
|
-
scrollAnimatedValue,
|
|
144
|
-
getInverted: () => narrowed.inverted,
|
|
145
|
-
getViewportHeight: () => viewportHeight,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
118
|
const narrowed = $derived.by(() => {
|
|
149
119
|
// extraData has no field of its own; reading it tracks it so a change forces this derived to
|
|
150
120
|
// re-run (RN's extraData contract).
|
|
@@ -323,58 +293,6 @@
|
|
|
323
293
|
return listEffectSignature(listState);
|
|
324
294
|
});
|
|
325
295
|
|
|
326
|
-
const hasStickyHeaders = $derived(
|
|
327
|
-
narrowed.stickyHeaderIndices !== undefined &&
|
|
328
|
-
narrowed.stickyHeaderIndices.length > 0,
|
|
329
|
-
);
|
|
330
|
-
// Resolved dynamically, exactly like React (adapters/react/.../scroll-view/shared.ts:267). Do
|
|
331
|
-
// not hardcode this false to keep the JS listener alive - RN gates native the same way
|
|
332
|
-
// (AnimatedWithChildren.js:74 `if (!this.__isNative)`) and the pin is the native transform, not
|
|
333
|
-
// the listener, so forcing JS mode only trades away the native driver: header lag on iOS,
|
|
334
|
-
// outright failure on Android (commit debounce 15ms vs iOS's 64ms - render-scroll-sticky.ts).
|
|
335
|
-
const nativeStickyAvailable = $derived(
|
|
336
|
-
hasStickyHeaders && isNativeAnimatedAvailable(),
|
|
337
|
-
);
|
|
338
|
-
// RN's FlatList/SectionList expose no invertStickyHeaders of their own (only ScrollView does) -
|
|
339
|
-
// sticky headers always pin to the top here, matching RN.
|
|
340
|
-
const forwarding = $derived(
|
|
341
|
-
resolveScrollForwarding({
|
|
342
|
-
hasStickyHeaders,
|
|
343
|
-
nativeStickyAvailable,
|
|
344
|
-
invertStickyHeaders: undefined,
|
|
345
|
-
scrollEventThrottle: narrowed.scrollEventThrottle,
|
|
346
|
-
maintainVisibleContentPosition: narrowed.maintainVisibleContentPosition,
|
|
347
|
-
snapToAlignment: undefined,
|
|
348
|
-
}),
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
$effect(() => {
|
|
352
|
-
dlog(
|
|
353
|
-
`VirtualizedList sticky hasStickyHeaders=${hasStickyHeaders} nativeStickyAvailable=${nativeStickyAvailable} mode=${forwarding.mode} scrollEventThrottle=${String(forwarding.scrollEventThrottle)}`,
|
|
354
|
-
);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// Drive the sticky scroll value on the native UI thread once the scroll host commits (mirrors
|
|
358
|
-
// ScrollView.svelte). No-op without the native animated module - the JS fallback below still
|
|
359
|
-
// keeps headers pinned via onScroll.
|
|
360
|
-
$effect(() => {
|
|
361
|
-
if (!nativeStickyAvailable) {
|
|
362
|
-
dlog(
|
|
363
|
-
'VirtualizedList sticky attachStickyScroll skipped: nativeStickyAvailable=false',
|
|
364
|
-
);
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
const node = hostShim?.engineNode;
|
|
368
|
-
if (node === undefined) {
|
|
369
|
-
dlog(
|
|
370
|
-
'VirtualizedList sticky attachStickyScroll skipped: engineNode not ready yet',
|
|
371
|
-
);
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
dlog('VirtualizedList sticky attachStickyScroll attached');
|
|
375
|
-
return attachStickyScroll(node, scrollAnimatedValue);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
296
|
function handleScroll(event: ISymbioteEvent): void {
|
|
379
297
|
const offset = readScrollOffset(event, narrowed.horizontal);
|
|
380
298
|
if (offset === undefined) return;
|
|
@@ -384,66 +302,11 @@
|
|
|
384
302
|
narrowed.userOnScroll?.(event);
|
|
385
303
|
}
|
|
386
304
|
|
|
387
|
-
// sticky-js: no native Animated module, so the scroll value is driven off the JS thread via the
|
|
388
|
-
// same wrapped-listener shape ScrollView.svelte uses. sticky-native/plain: forward untouched.
|
|
389
|
-
const onScroll = $derived.by(() => {
|
|
390
|
-
if (forwarding.mode !== 'sticky-js') return handleScroll;
|
|
391
|
-
return animatedEvent(
|
|
392
|
-
[{ nativeEvent: { contentOffset: { y: scrollAnimatedValue } } }],
|
|
393
|
-
{
|
|
394
|
-
listener: (...args: unknown[]) =>
|
|
395
|
-
forwardScrollEvent(handleScroll, args),
|
|
396
|
-
},
|
|
397
|
-
);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
305
|
function onViewportLayout(event: ISymbioteEvent): void {
|
|
401
306
|
const length = readLayoutLength(event, narrowed.horizontal);
|
|
402
307
|
if (length === undefined) return;
|
|
403
308
|
dlog(`VirtualizedList onLayout viewport=${length}`);
|
|
404
309
|
dispatch({ kind: 'layout', length });
|
|
405
|
-
if (forwarding.capturesViewportHeight) {
|
|
406
|
-
const height = readLayoutNumber(event, 'height');
|
|
407
|
-
if (height !== undefined) viewportHeight = height;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Records a sticky header's measured y (RN's _headerLayoutYs) so the NEXT sticky header down the
|
|
412
|
-
// list can compute its collision point - without this every header would stick indefinitely
|
|
413
|
-
// instead of being pushed off by the one behind it.
|
|
414
|
-
function recordHeaderY(index: number, event: ISymbioteEvent): void {
|
|
415
|
-
const y = readLayoutNumber(event, 'y');
|
|
416
|
-
if (y === undefined) return;
|
|
417
|
-
// This map is the ONLY source of nextHeaderLayoutY for every header ahead of this one; if a
|
|
418
|
-
// later header's onLayout never fires (windowing drops its cell) the collision math upstream
|
|
419
|
-
// falls back to `?? 0` (collisionPoint = -layoutHeight), which is almost certainly wrong.
|
|
420
|
-
dlog(`VirtualizedList recordHeaderY index=${index} y=${y}`);
|
|
421
|
-
headerLayoutYs.set(index, y);
|
|
422
|
-
stickyVersion += 1;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function stickyLayoutFor(index: number): (event: ISymbioteEvent) => void {
|
|
426
|
-
const measure = makeCellMeasure(index);
|
|
427
|
-
return (event: ISymbioteEvent): void => {
|
|
428
|
-
measure(event);
|
|
429
|
-
recordHeaderY(index, event);
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Only a header MOUNTED RIGHT NOW can collide with this one, matching React (its ScrollView
|
|
434
|
-
// receives `renderedStickyIndices`, not the full section list -
|
|
435
|
-
// adapters/react/.../virtualized-list/index.ts:732). Passing the full index list instead let a
|
|
436
|
-
// header collide against a stale y from one that had already scrolled out and unmounted,
|
|
437
|
-
// freezing it in place - proven by sticky-collision-parity.test.ts against the React reference.
|
|
438
|
-
// The map itself is deliberately NOT pruned (React doesn't either): a measured y stays valid for
|
|
439
|
-
// when that header scrolls back in.
|
|
440
|
-
function nextStickyHeaderYFor(index: number): number | undefined {
|
|
441
|
-
void stickyVersion;
|
|
442
|
-
const indices = narrowed.stickyHeaderIndices;
|
|
443
|
-
if (indices === undefined) return undefined;
|
|
444
|
-
const mounted = new Set(allCells.map(cell => cell.index));
|
|
445
|
-
const rendered = indices.filter(stickyIndex => mounted.has(stickyIndex));
|
|
446
|
-
return nextStickyHeaderY(rendered, rendered.indexOf(index), headerLayoutYs);
|
|
447
310
|
}
|
|
448
311
|
|
|
449
312
|
function makeCellMeasure(index: number): (event: ISymbioteEvent) => void {
|
|
@@ -573,10 +436,6 @@
|
|
|
573
436
|
};
|
|
574
437
|
});
|
|
575
438
|
|
|
576
|
-
const scrollViewIntrinsics = $derived(
|
|
577
|
-
selectScrollIntrinsics(narrowed.horizontal, narrowed.contentContainerStyle),
|
|
578
|
-
);
|
|
579
|
-
|
|
580
439
|
const resolvedStyle = $derived(
|
|
581
440
|
narrowed.inverted
|
|
582
441
|
? [
|
|
@@ -585,44 +444,30 @@
|
|
|
585
444
|
]
|
|
586
445
|
: narrowed.style,
|
|
587
446
|
);
|
|
447
|
+
// Travels on the OWNER: `slotProps` renames it onto the content node the behavior built, so it
|
|
448
|
+
// goes through the slot's own routeProp and inherits class resolution and style merging. The
|
|
449
|
+
// horizontal `flexDirection: 'row'` is the behavior's constant and is composed OVER this, so
|
|
450
|
+
// only the measured total width is added here.
|
|
588
451
|
const resolvedContentContainerStyle = $derived(
|
|
589
452
|
narrowed.horizontal
|
|
590
|
-
? [
|
|
591
|
-
:
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
// Android wrap mode only: mirrors ScrollView's own index.svelte - RN's splitLayoutProps routes
|
|
595
|
-
// LAYOUT props (margin/flex/size/position/...) onto the wrapping AndroidSwipeRefreshLayout,
|
|
596
|
-
// VISUAL props (background/padding/border/...) onto the inner scroll view, instead of leaving
|
|
597
|
-
// the wrapper unstyled (which collapses it to zero height).
|
|
598
|
-
const layoutSplit = $derived(
|
|
599
|
-
shouldWrapRefreshControl
|
|
600
|
-
? splitLayoutProps([resolveSvelteClass(narrowed.class), resolvedStyle])
|
|
601
|
-
: undefined,
|
|
453
|
+
? [narrowed.contentContainerStyle, { width: metrics.total }]
|
|
454
|
+
: narrowed.contentContainerStyle,
|
|
602
455
|
);
|
|
603
456
|
|
|
604
457
|
const outerBag = $derived.by(() => {
|
|
605
458
|
const bag: Record<string, unknown> = {
|
|
606
|
-
style:
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
horizontal: narrowed.horizontal,
|
|
611
|
-
// RN defaults nested scrolling ON (ScrollView.js `nestedScrollEnabled ?? true`). This file
|
|
612
|
-
// hand-rolls the raw scroll intrinsic instead of rendering <ScrollView>, so it never
|
|
613
|
-
// inherited that default - on Android, a list nested inside a page ScrollView never got
|
|
614
|
-
// nested-scroll gesture arbitration and only the outer page scrolled.
|
|
615
|
-
nestedScrollEnabled: true,
|
|
616
|
-
onScroll,
|
|
459
|
+
style: resolvedStyle,
|
|
460
|
+
class: narrowed.class,
|
|
461
|
+
contentContainerStyle: resolvedContentContainerStyle,
|
|
462
|
+
onScroll: handleScroll,
|
|
617
463
|
onLayout: onViewportLayout,
|
|
618
464
|
};
|
|
619
|
-
if (layoutSplit === undefined) bag.class = narrowed.class;
|
|
620
465
|
if (commandedOffset !== undefined) bag.contentOffset = commandedOffset;
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
if (
|
|
625
|
-
bag.scrollEventThrottle =
|
|
466
|
+
// The RAW prop. A sticky header raises the throttle from the behavior, once it has actually
|
|
467
|
+
// registered — folding a sticky default in here would hand the behavior a number it reads back
|
|
468
|
+
// as the app's and could never take away again.
|
|
469
|
+
if (narrowed.scrollEventThrottle !== undefined)
|
|
470
|
+
bag.scrollEventThrottle = narrowed.scrollEventThrottle;
|
|
626
471
|
if (narrowed.onScrollBeginDrag !== undefined)
|
|
627
472
|
bag.onScrollBeginDrag = narrowed.onScrollBeginDrag;
|
|
628
473
|
if (narrowed.onScrollEndDrag !== undefined)
|
|
@@ -636,8 +481,11 @@
|
|
|
636
481
|
}
|
|
637
482
|
if (narrowed.keyboardDismissMode !== undefined)
|
|
638
483
|
bag.keyboardDismissMode = narrowed.keyboardDismissMode;
|
|
639
|
-
|
|
640
|
-
|
|
484
|
+
// `stickyHeaderIndices` is deliberately NOT forwarded. The behavior honours it by numbering the
|
|
485
|
+
// owner's own PAINT children, and this list's indices are into the DATA stream — a windowed
|
|
486
|
+
// list paints a spacer, a header and a slice, so index 3 of the data is almost never paint
|
|
487
|
+
// child 3. The cells that should pin carry the `sticky-header` tag directly instead, which is
|
|
488
|
+
// the one form that survives windowing.
|
|
641
489
|
if (narrowed.maintainVisibleContentPosition !== undefined) {
|
|
642
490
|
bag.maintainVisibleContentPosition =
|
|
643
491
|
narrowed.maintainVisibleContentPosition;
|
|
@@ -659,18 +507,6 @@
|
|
|
659
507
|
progressViewOffset: narrowed.progressViewOffset,
|
|
660
508
|
};
|
|
661
509
|
});
|
|
662
|
-
// iOS: RefreshControl is a childless sibling before the content container. Android: it WRAPS the
|
|
663
|
-
// whole scroll view (an Android ScrollView accepts only one child) - same PLATFORM.
|
|
664
|
-
// refreshControlMode ScrollView's own index.svelte reads.
|
|
665
|
-
const shouldWrapRefreshControl = $derived(
|
|
666
|
-
PLATFORM.refreshControlMode === 'wrap' && refreshControlProps !== undefined,
|
|
667
|
-
);
|
|
668
|
-
|
|
669
|
-
const contentBag = $derived({
|
|
670
|
-
style: resolvedContentContainerStyle,
|
|
671
|
-
collapsable: false,
|
|
672
|
-
});
|
|
673
|
-
|
|
674
510
|
const stickySet = $derived(
|
|
675
511
|
narrowed.stickyHeaderIndices !== undefined
|
|
676
512
|
? new Set(narrowed.stickyHeaderIndices)
|
|
@@ -740,19 +576,19 @@
|
|
|
740
576
|
lets virtualized-list.smoke.test.ts still assert an exact windowed child count.
|
|
741
577
|
-->
|
|
742
578
|
{#if hasHeader}
|
|
743
|
-
<
|
|
579
|
+
<view p={{}}>
|
|
744
580
|
{@render props.header?.()}
|
|
745
|
-
</
|
|
581
|
+
</view>
|
|
746
582
|
{/if}
|
|
747
583
|
{#if metrics.count === FIRST_INDEX}
|
|
748
584
|
{#if props.empty}
|
|
749
|
-
<
|
|
585
|
+
<view p={{}}>
|
|
750
586
|
{@render props.empty()}
|
|
751
|
-
</
|
|
587
|
+
</view>
|
|
752
588
|
{/if}
|
|
753
589
|
{:else if plan}
|
|
754
590
|
{#if plan.leadingExtent > EMPTY_OFFSET}
|
|
755
|
-
<
|
|
591
|
+
<view
|
|
756
592
|
p={{
|
|
757
593
|
style: narrowed.horizontal
|
|
758
594
|
? { width: plan.leadingExtent }
|
|
@@ -762,23 +598,24 @@
|
|
|
762
598
|
{/if}
|
|
763
599
|
{#each allCells as cell (cell.key)}
|
|
764
600
|
{#if stickySet?.has(cell.index)}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
601
|
+
<!-- The TAG, not a component: `registerScrollViewBehavior()` registers `sticky-header`
|
|
602
|
+
alongside the scroll tags, and its behavior finds this ScrollView by walking up. The
|
|
603
|
+
collision point comes from the owner's DOCUMENT order, so nothing here computes or
|
|
604
|
+
forwards an index. `onLayout` is forwarded by the behavior, not replaced. -->
|
|
605
|
+
<sticky-header p={{ onLayout: makeCellMeasure(cell.index) }}>
|
|
769
606
|
{@render props.item({
|
|
770
607
|
item: narrowed.getItem(narrowed.data, cell.index),
|
|
771
608
|
index: cell.index,
|
|
772
609
|
separators: makeSeparators(cell.index),
|
|
773
610
|
})}
|
|
774
611
|
{#if props.separator && cell.index < metrics.count - 1}
|
|
775
|
-
<
|
|
612
|
+
<view p={{}}>
|
|
776
613
|
{@render props.separator(separatorPropsFor(cell.index))}
|
|
777
|
-
</
|
|
614
|
+
</view>
|
|
778
615
|
{/if}
|
|
779
|
-
</
|
|
616
|
+
</sticky-header>
|
|
780
617
|
{:else}
|
|
781
|
-
<
|
|
618
|
+
<view
|
|
782
619
|
p={{
|
|
783
620
|
onLayout: makeCellMeasure(cell.index),
|
|
784
621
|
style: cellInvertedStyle,
|
|
@@ -790,14 +627,14 @@
|
|
|
790
627
|
separators: makeSeparators(cell.index),
|
|
791
628
|
})}
|
|
792
629
|
{#if props.separator && cell.index < metrics.count - 1}
|
|
793
|
-
<
|
|
630
|
+
<view p={{}}>
|
|
794
631
|
{@render props.separator(separatorPropsFor(cell.index))}
|
|
795
|
-
</
|
|
632
|
+
</view>
|
|
796
633
|
{/if}
|
|
797
|
-
</
|
|
634
|
+
</view>
|
|
798
635
|
{/if}
|
|
799
636
|
{#if plan.forcedStickyCell && cell.index === plan.forcedStickyCell.index && plan.gapExtent > EMPTY_OFFSET}
|
|
800
|
-
<
|
|
637
|
+
<view
|
|
801
638
|
p={{
|
|
802
639
|
style: narrowed.horizontal
|
|
803
640
|
? { width: plan.gapExtent }
|
|
@@ -807,7 +644,7 @@
|
|
|
807
644
|
{/if}
|
|
808
645
|
{/each}
|
|
809
646
|
{#if plan.trailingExtent > EMPTY_OFFSET}
|
|
810
|
-
<
|
|
647
|
+
<view
|
|
811
648
|
p={{
|
|
812
649
|
style: narrowed.horizontal
|
|
813
650
|
? { width: plan.trailingExtent }
|
|
@@ -817,49 +654,33 @@
|
|
|
817
654
|
{/if}
|
|
818
655
|
{/if}
|
|
819
656
|
{#if props.footer}
|
|
820
|
-
<
|
|
657
|
+
<view p={{}}>
|
|
821
658
|
{@render props.footer()}
|
|
822
|
-
</
|
|
659
|
+
</view>
|
|
823
660
|
{/if}
|
|
824
661
|
{/snippet}
|
|
825
662
|
|
|
826
663
|
{#snippet scrollBody()}
|
|
827
664
|
<!--
|
|
828
|
-
Same as listBody() above:
|
|
829
|
-
|
|
665
|
+
Same as listBody() above: the RefreshControl and the cells are siblings of one parent here, and
|
|
666
|
+
the whitespace between them is dropped before it reaches Fabric.
|
|
667
|
+
|
|
668
|
+
RefreshControl is written as an ordinary child on BOTH platforms — the behavior CLAIMS it, so
|
|
669
|
+
the engine keeps it beside the content view on iOS and inverts the tree on Android. Everything
|
|
670
|
+
after it lands in the content node the behavior built.
|
|
830
671
|
-->
|
|
831
|
-
{#if
|
|
832
|
-
<
|
|
833
|
-
{/if}
|
|
834
|
-
{#if narrowed.horizontal}
|
|
835
|
-
<symbiote-horizontal-scroll-content p={contentBag}>
|
|
836
|
-
{@render listBody()}
|
|
837
|
-
</symbiote-horizontal-scroll-content>
|
|
838
|
-
{:else}
|
|
839
|
-
<symbiote-scroll-content p={contentBag}>
|
|
840
|
-
{@render listBody()}
|
|
841
|
-
</symbiote-scroll-content>
|
|
672
|
+
{#if refreshControlProps !== undefined}
|
|
673
|
+
<refresh-control p={refreshControlProps} />
|
|
842
674
|
{/if}
|
|
675
|
+
{@render listBody()}
|
|
843
676
|
{/snippet}
|
|
844
677
|
|
|
845
|
-
{#if
|
|
846
|
-
<
|
|
847
|
-
{#if narrowed.horizontal}
|
|
848
|
-
<symbiote-horizontal-scroll-view p={outerBag} bind:this={hostShim}>
|
|
849
|
-
{@render scrollBody()}
|
|
850
|
-
</symbiote-horizontal-scroll-view>
|
|
851
|
-
{:else}
|
|
852
|
-
<symbiote-scroll-view p={outerBag} bind:this={hostShim}>
|
|
853
|
-
{@render scrollBody()}
|
|
854
|
-
</symbiote-scroll-view>
|
|
855
|
-
{/if}
|
|
856
|
-
</RefreshControl>
|
|
857
|
-
{:else if narrowed.horizontal}
|
|
858
|
-
<symbiote-horizontal-scroll-view p={outerBag} bind:this={hostShim}>
|
|
678
|
+
{#if narrowed.horizontal}
|
|
679
|
+
<horizontal-scroll-view p={outerBag} bind:this={hostShim}>
|
|
859
680
|
{@render scrollBody()}
|
|
860
|
-
</
|
|
681
|
+
</horizontal-scroll-view>
|
|
861
682
|
{:else}
|
|
862
|
-
<
|
|
683
|
+
<scroll-view p={outerBag} bind:this={hostShim}>
|
|
863
684
|
{@render scrollBody()}
|
|
864
|
-
</
|
|
685
|
+
</scroll-view>
|
|
865
686
|
{/if}
|