@symbiote-native/svelte 1.0.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.
Files changed (107) hide show
  1. package/README.md +8 -10
  2. package/build/components/{activity-indicator/activity-indicator-props.d.ts → activity-indicator-props.d.ts} +1 -1
  3. package/build/components/flat-list/index.svelte +4 -4
  4. package/build/components/{image-background/image-background-props.d.ts → image-background-props.d.ts} +1 -1
  5. package/build/components/index.d.ts +6 -24
  6. package/build/components/index.js +0 -18
  7. package/build/components/keyboard-avoiding-view/index.svelte +6 -6
  8. package/build/components/modal/index.svelte +7 -7
  9. package/build/components/pressable/pressable-props.d.ts +1 -0
  10. package/build/components/refresh-control-props.d.ts +1 -0
  11. package/build/components/text-props.d.ts +1 -0
  12. package/build/components/virtualized-list/index.svelte +71 -250
  13. package/build/dom-shim/canonical-prop-names.d.ts +2 -0
  14. package/build/dom-shim/canonical-prop-names.js +320 -0
  15. package/build/dom-shim/document.js +1 -1
  16. package/build/dom-shim/element.d.ts +9 -3
  17. package/build/dom-shim/element.js +195 -29
  18. package/build/dom-shim/patch-globals.js +31 -5
  19. package/build/dom-shim/style-cache.d.ts +1 -0
  20. package/build/dom-shim/style-cache.js +27 -0
  21. package/build/dom-shim/text.js +2 -2
  22. package/build/host-instance.d.ts +2 -3
  23. package/build/host-instance.js +7 -1
  24. package/build/index.d.ts +7 -4
  25. package/build/index.js +17 -5
  26. package/build/intrinsic-elements.d.ts +63 -0
  27. package/build/modules/animated/index.d.ts +7 -27
  28. package/build/modules/animated/index.js +24 -34
  29. package/build/modules/image/index.d.ts +3 -0
  30. package/build/modules/image/index.js +15 -0
  31. package/build/preprocessor/scoped-styles.js +1 -1
  32. package/build/register.d.ts +1 -1
  33. package/build/register.js +13 -31
  34. package/build/root-element.js +1 -1
  35. package/metro-css-parser.cjs +2 -1
  36. package/metro-svelte-transformer.cjs +1 -17
  37. package/package.json +7 -15
  38. package/build/components/RefreshControl.svelte +0 -40
  39. package/build/components/RefreshControl.svelte.d.ts +0 -5
  40. package/build/components/SafeAreaView.svelte +0 -34
  41. package/build/components/SafeAreaView.svelte.d.ts +0 -5
  42. package/build/components/Text.svelte +0 -34
  43. package/build/components/Text.svelte.d.ts +0 -5
  44. package/build/components/View.svelte +0 -43
  45. package/build/components/View.svelte.d.ts +0 -5
  46. package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +0 -3
  47. package/build/components/activity-indicator/activity-indicator-platform.android.js +0 -5
  48. package/build/components/activity-indicator/activity-indicator-platform.d.ts +0 -1
  49. package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +0 -3
  50. package/build/components/activity-indicator/activity-indicator-platform.ios.js +0 -6
  51. package/build/components/activity-indicator/activity-indicator-platform.js +0 -5
  52. package/build/components/activity-indicator/index.svelte +0 -63
  53. package/build/components/activity-indicator/index.svelte.d.ts +0 -5
  54. package/build/components/button.svelte +0 -76
  55. package/build/components/button.svelte.d.ts +0 -5
  56. package/build/components/image/image-logic.d.ts +0 -2
  57. package/build/components/image/image-logic.js +0 -135
  58. package/build/components/image/index.d.ts +0 -3
  59. package/build/components/image/index.js +0 -10
  60. package/build/components/image/index.svelte +0 -66
  61. package/build/components/image/index.svelte.d.ts +0 -5
  62. package/build/components/image-background/index.svelte +0 -87
  63. package/build/components/image-background/index.svelte.d.ts +0 -5
  64. package/build/components/input-accessory-view/index.svelte +0 -51
  65. package/build/components/input-accessory-view/index.svelte.d.ts +0 -5
  66. package/build/components/pressable/index.svelte +0 -185
  67. package/build/components/pressable/index.svelte.d.ts +0 -9
  68. package/build/components/scroll-view/index.svelte +0 -331
  69. package/build/components/scroll-view/index.svelte.d.ts +0 -18
  70. package/build/components/scroll-view/scroll-view-platform-types.d.ts +0 -3
  71. package/build/components/scroll-view/scroll-view-platform.android.d.ts +0 -3
  72. package/build/components/scroll-view/scroll-view-platform.android.js +0 -3
  73. package/build/components/scroll-view/scroll-view-platform.d.ts +0 -1
  74. package/build/components/scroll-view/scroll-view-platform.ios.d.ts +0 -3
  75. package/build/components/scroll-view/scroll-view-platform.ios.js +0 -3
  76. package/build/components/scroll-view/scroll-view-platform.js +0 -4
  77. package/build/components/scroll-view/scroll-view-sticky-context.d.ts +0 -7
  78. package/build/components/scroll-view/scroll-view-sticky-context.js +0 -1
  79. package/build/components/scroll-view/sticky-header-props.d.ts +0 -10
  80. package/build/components/scroll-view/sticky-header-props.js +0 -1
  81. package/build/components/scroll-view/sticky-header.svelte +0 -287
  82. package/build/components/scroll-view/sticky-header.svelte.d.ts +0 -5
  83. package/build/components/switch/index.svelte +0 -134
  84. package/build/components/switch/index.svelte.d.ts +0 -5
  85. package/build/components/text-input/index.svelte +0 -372
  86. package/build/components/text-input/index.svelte.d.ts +0 -17
  87. package/build/components/touchable-highlight/index.svelte +0 -139
  88. package/build/components/touchable-highlight/index.svelte.d.ts +0 -5
  89. package/build/components/touchable-native-feedback/index.d.ts +0 -9
  90. package/build/components/touchable-native-feedback/index.js +0 -14
  91. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +0 -49
  92. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +0 -5
  93. package/build/components/touchable-opacity/index.svelte +0 -181
  94. package/build/components/touchable-opacity/index.svelte.d.ts +0 -5
  95. package/build/components/touchable-without-feedback/index.svelte +0 -75
  96. package/build/components/touchable-without-feedback/index.svelte.d.ts +0 -5
  97. package/build/modules/animated/animated-component-props.d.ts +0 -7
  98. package/build/modules/animated/animated-component-props.js +0 -1
  99. package/build/modules/animated/create-animated-component.d.ts +0 -5
  100. package/build/modules/animated/create-animated-component.js +0 -131
  101. package/build/preprocessor/lower-host-primitives.d.ts +0 -10
  102. package/build/preprocessor/lower-host-primitives.js +0 -455
  103. package/build/state-style.d.ts +0 -1
  104. package/build/state-style.js +0 -8
  105. /package/build/components/{activity-indicator/activity-indicator-props.js → activity-indicator-props.js} +0 -0
  106. /package/build/components/{image-background/image-background-props.js → image-background-props.js} +0 -0
  107. /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 symbiote-view nodes.
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 hand-authors the raw `symbiote-scroll-view`/`symbiote-scroll-content` intrinsics
14
- // directly rather than rendering <ScrollView>: unlike ScrollView.svelte, it walks an indexable
15
- // `plan.cells` list instead of an opaque children Snippet, so it can auto-wrap sticky cells
16
- // itself. `onContentSizeChange` remains the one gap left from that; nestedScrollEnabled, the
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
- // RefreshControl attaches directly to these raw scroll intrinsics: a sibling before the content
21
- // on iOS, wrapping the whole scroll view on Android (PLATFORM.refreshControlMode) - the same
22
- // shape ScrollView's own index.svelte uses.
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
- ? [scrollViewIntrinsics.contentStyle, { width: metrics.total }]
591
- : scrollViewIntrinsics.contentStyle,
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
- scrollViewIntrinsics.scrollViewBaseStyle,
608
- layoutSplit !== undefined ? layoutSplit.inner : resolvedStyle,
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
- // forwarding.scrollEventThrottle (not the raw prop): folds in the sticky-mode default
622
- // (1 native / 16 JS-fallback) when unset - without it a sticky header rebuilt off too-sparse
623
- // scroll events pins/collides late.
624
- if (forwarding.scrollEventThrottle !== undefined)
625
- bag.scrollEventThrottle = forwarding.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
- if (narrowed.stickyHeaderIndices !== undefined)
640
- bag.stickyHeaderIndices = narrowed.stickyHeaderIndices;
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
- <symbiote-view p={{}}>
579
+ <view p={{}}>
744
580
  {@render props.header?.()}
745
- </symbiote-view>
581
+ </view>
746
582
  {/if}
747
583
  {#if metrics.count === FIRST_INDEX}
748
584
  {#if props.empty}
749
- <symbiote-view p={{}}>
585
+ <view p={{}}>
750
586
  {@render props.empty()}
751
- </symbiote-view>
587
+ </view>
752
588
  {/if}
753
589
  {:else if plan}
754
590
  {#if plan.leadingExtent > EMPTY_OFFSET}
755
- <symbiote-view
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
- <ScrollViewStickyHeader
766
- onLayout={stickyLayoutFor(cell.index)}
767
- nextHeaderLayoutY={nextStickyHeaderYFor(cell.index)}
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
- <symbiote-view p={{}}>
612
+ <view p={{}}>
776
613
  {@render props.separator(separatorPropsFor(cell.index))}
777
- </symbiote-view>
614
+ </view>
778
615
  {/if}
779
- </ScrollViewStickyHeader>
616
+ </sticky-header>
780
617
  {:else}
781
- <symbiote-view
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
- <symbiote-view p={{}}>
630
+ <view p={{}}>
794
631
  {@render props.separator(separatorPropsFor(cell.index))}
795
- </symbiote-view>
632
+ </view>
796
633
  {/if}
797
- </symbiote-view>
634
+ </view>
798
635
  {/if}
799
636
  {#if plan.forcedStickyCell && cell.index === plan.forcedStickyCell.index && plan.gapExtent > EMPTY_OFFSET}
800
- <symbiote-view
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
- <symbiote-view
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
- <symbiote-view p={{}}>
657
+ <view p={{}}>
821
658
  {@render props.footer()}
822
- </symbiote-view>
659
+ </view>
823
660
  {/if}
824
661
  {/snippet}
825
662
 
826
663
  {#snippet scrollBody()}
827
664
  <!--
828
- Same as listBody() above: on iOS the non-wrapping RefreshControl and the content container are
829
- siblings of one parent, and the gap between them is dropped before it reaches Fabric.
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 !shouldWrapRefreshControl && refreshControlProps !== undefined}
832
- <RefreshControl {...refreshControlProps} />
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 shouldWrapRefreshControl && refreshControlProps !== undefined}
846
- <RefreshControl {...refreshControlProps} style={layoutSplit?.outer}>
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
- </symbiote-horizontal-scroll-view>
681
+ </horizontal-scroll-view>
861
682
  {:else}
862
- <symbiote-scroll-view p={outerBag} bind:this={hostShim}>
683
+ <scroll-view p={outerBag} bind:this={hostShim}>
863
684
  {@render scrollBody()}
864
- </symbiote-scroll-view>
685
+ </scroll-view>
865
686
  {/if}
@@ -0,0 +1,2 @@
1
+ export declare const CANONICAL_PROP_NAMES: readonly string[];
2
+ export declare const CANONICAL_BY_LOWER: ReadonlyMap<string, string>;