@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.
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/safe-area-view-props.d.ts +1 -0
  12. package/build/components/text-props.d.ts +1 -0
  13. package/build/components/virtualized-list/index.svelte +71 -250
  14. package/build/dom-shim/canonical-prop-names.d.ts +2 -0
  15. package/build/dom-shim/canonical-prop-names.js +320 -0
  16. package/build/dom-shim/document.js +1 -1
  17. package/build/dom-shim/element.d.ts +17 -5
  18. package/build/dom-shim/element.js +258 -40
  19. package/build/dom-shim/fold-host-bag.d.ts +1 -0
  20. package/build/dom-shim/fold-host-bag.js +7 -0
  21. package/build/dom-shim/patch-globals.js +35 -7
  22. package/build/dom-shim/shim-node.js +11 -0
  23. package/build/dom-shim/style-cache.d.ts +1 -0
  24. package/build/dom-shim/style-cache.js +27 -0
  25. package/build/dom-shim/text.js +2 -2
  26. package/build/host-instance.d.ts +2 -3
  27. package/build/host-instance.js +9 -4
  28. package/build/index.d.ts +8 -4
  29. package/build/index.js +21 -5
  30. package/build/intrinsic-elements.d.ts +63 -0
  31. package/build/modules/animated/index.d.ts +7 -27
  32. package/build/modules/animated/index.js +24 -34
  33. package/build/modules/image/index.d.ts +3 -0
  34. package/build/modules/image/index.js +15 -0
  35. package/build/preprocessor/scoped-styles.js +1 -1
  36. package/build/register.d.ts +1 -0
  37. package/build/register.js +15 -0
  38. package/build/root-element.js +1 -1
  39. package/metro-css-parser.cjs +2 -1
  40. package/metro-svelte-transformer.cjs +30 -12
  41. package/package.json +19 -9
  42. package/build/components/RefreshControl.svelte +0 -40
  43. package/build/components/RefreshControl.svelte.d.ts +0 -5
  44. package/build/components/SafeAreaView.svelte +0 -34
  45. package/build/components/SafeAreaView.svelte.d.ts +0 -5
  46. package/build/components/Text.svelte +0 -34
  47. package/build/components/Text.svelte.d.ts +0 -5
  48. package/build/components/View.svelte +0 -43
  49. package/build/components/View.svelte.d.ts +0 -5
  50. package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +0 -3
  51. package/build/components/activity-indicator/activity-indicator-platform.android.js +0 -5
  52. package/build/components/activity-indicator/activity-indicator-platform.d.ts +0 -1
  53. package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +0 -3
  54. package/build/components/activity-indicator/activity-indicator-platform.ios.js +0 -6
  55. package/build/components/activity-indicator/activity-indicator-platform.js +0 -5
  56. package/build/components/activity-indicator/index.svelte +0 -63
  57. package/build/components/activity-indicator/index.svelte.d.ts +0 -5
  58. package/build/components/button.svelte +0 -76
  59. package/build/components/button.svelte.d.ts +0 -5
  60. package/build/components/image/image-logic.d.ts +0 -2
  61. package/build/components/image/image-logic.js +0 -135
  62. package/build/components/image/index.d.ts +0 -3
  63. package/build/components/image/index.js +0 -10
  64. package/build/components/image/index.svelte +0 -66
  65. package/build/components/image/index.svelte.d.ts +0 -5
  66. package/build/components/image-background/index.svelte +0 -87
  67. package/build/components/image-background/index.svelte.d.ts +0 -5
  68. package/build/components/input-accessory-view/index.svelte +0 -51
  69. package/build/components/input-accessory-view/index.svelte.d.ts +0 -5
  70. package/build/components/pressable/index.svelte +0 -167
  71. package/build/components/pressable/index.svelte.d.ts +0 -5
  72. package/build/components/scroll-view/index.svelte +0 -331
  73. package/build/components/scroll-view/index.svelte.d.ts +0 -18
  74. package/build/components/scroll-view/scroll-view-platform-types.d.ts +0 -3
  75. package/build/components/scroll-view/scroll-view-platform.android.d.ts +0 -3
  76. package/build/components/scroll-view/scroll-view-platform.android.js +0 -3
  77. package/build/components/scroll-view/scroll-view-platform.d.ts +0 -1
  78. package/build/components/scroll-view/scroll-view-platform.ios.d.ts +0 -3
  79. package/build/components/scroll-view/scroll-view-platform.ios.js +0 -3
  80. package/build/components/scroll-view/scroll-view-platform.js +0 -4
  81. package/build/components/scroll-view/scroll-view-sticky-context.d.ts +0 -7
  82. package/build/components/scroll-view/scroll-view-sticky-context.js +0 -1
  83. package/build/components/scroll-view/sticky-header-props.d.ts +0 -10
  84. package/build/components/scroll-view/sticky-header-props.js +0 -1
  85. package/build/components/scroll-view/sticky-header.svelte +0 -287
  86. package/build/components/scroll-view/sticky-header.svelte.d.ts +0 -5
  87. package/build/components/switch/index.svelte +0 -134
  88. package/build/components/switch/index.svelte.d.ts +0 -5
  89. package/build/components/text-input/index.svelte +0 -316
  90. package/build/components/text-input/index.svelte.d.ts +0 -12
  91. package/build/components/touchable-highlight/index.svelte +0 -138
  92. package/build/components/touchable-highlight/index.svelte.d.ts +0 -5
  93. package/build/components/touchable-native-feedback/index.d.ts +0 -9
  94. package/build/components/touchable-native-feedback/index.js +0 -14
  95. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +0 -49
  96. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +0 -5
  97. package/build/components/touchable-opacity/index.svelte +0 -180
  98. package/build/components/touchable-opacity/index.svelte.d.ts +0 -5
  99. package/build/components/touchable-without-feedback/index.svelte +0 -74
  100. package/build/components/touchable-without-feedback/index.svelte.d.ts +0 -5
  101. package/build/modules/animated/animated-component-props.d.ts +0 -7
  102. package/build/modules/animated/animated-component-props.js +0 -1
  103. package/build/modules/animated/create-animated-component.d.ts +0 -5
  104. package/build/modules/animated/create-animated-component.js +0 -131
  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,167 +0,0 @@
1
- <script lang="ts" module>
2
- // Pressable: the Svelte lifecycle half. The press lifecycle (the long-press timer,
3
- // unstable_pressDelay deferral, the pressRetentionOffset drift test, the suppression flags)
4
- // lives in @symbiote-native/components/state as a pure machine over a runtime + host; the
5
- // render decisions (the responder listeners, the disabled->accessibilityState fold, the
6
- // ripple prop) in @symbiote-native/components/view, shared verbatim with React and Vue. Here
7
- // runes supply the reactivity: `$state` holds `pressed`, a plain setup-scope object (created
8
- // once, like Vue's) holds the press runtime, and `$state.raw` holds the responder's ShimElement
9
- // by IDENTITY so the machine can measure through it (same shallowRef-not-ref concern as Vue's
10
- // pressable.ts / Switch's own hostShim — a deep-proxied `$state` object would miss the engine's
11
- // WeakMap mirror lookup).
12
- //
13
- // Pressable owns no `renderPressable()` Descriptor factory (unlike Switch's fixed-shape
14
- // `renderSwitch()`) — core/components/src/view/render-pressable only resolves the responder-
15
- // listener bag and the accessibilityState fold, so this hand-authors the host node directly,
16
- // close to View.svelte's own shape, wiring the press responder listeners onto it instead of
17
- // importing View.svelte (which has no `bind:this` escape hatch for the raw ShimElement the
18
- // measure handle needs).
19
- import type { IPressableProps } from './pressable-props';
20
-
21
- export type { IPressableProps };
22
- </script>
23
-
24
- <script lang="ts">
25
- import {
26
- createPressHandlers,
27
- createPressRuntime,
28
- rippleProps,
29
- buildPressableListeners,
30
- resolveDisabledAccessibilityState,
31
- resolveAccessibilityProps,
32
- noteHoverNoop,
33
- DEFAULT_DELAY_LONG_PRESS_MS,
34
- type IPressHost,
35
- type IPressState,
36
- } from '@symbiote-native/components';
37
- import { measure, type ISymbioteNode } from '@symbiote-native/engine';
38
- import type { ShimElement } from '../../dom-shim';
39
- import { createAttachmentsSync } from '../../runes/attachments';
40
-
41
- let {
42
- onPress,
43
- onPressIn,
44
- onPressOut,
45
- onPressMove,
46
- onLongPress,
47
- delayLongPress = DEFAULT_DELAY_LONG_PRESS_MS,
48
- disabled,
49
- cancelable,
50
- hitSlop,
51
- pressRetentionOffset,
52
- unstable_pressDelay = 0,
53
- android_ripple,
54
- android_disableSound,
55
- onHoverIn,
56
- onHoverOut,
57
- delayHoverIn,
58
- delayHoverOut,
59
- accessibilityState,
60
- style,
61
- class: className,
62
- children,
63
- ...rest
64
- }: IPressableProps = $props();
65
-
66
- let pressed = $state(false);
67
- // Plain setup-scope object, never `$state`: mutated by the machine on every event, never
68
- // reactively read — same as Vue's setup-scope runtime.
69
- const runtime = createPressRuntime();
70
- // `$state.raw`, NOT `$state`: holds the responder ShimElement by IDENTITY. `$state()` would
71
- // deep-proxy it, and `measure()` looks the RAW node up in the engine's WeakMap mirror — a Proxy
72
- // wrapper misses that lookup and the retention-region measure silently no-ops.
73
- let hostShim = $state.raw<ShimElement | null>(null);
74
-
75
- const host: IPressHost = {
76
- setPressed(next: boolean): void {
77
- pressed = next;
78
- },
79
- getMeasureFn: () => {
80
- const node: ISymbioteNode | undefined = hostShim?.engineNode;
81
- if (node === undefined) return undefined;
82
- return callback => measure(node, callback);
83
- },
84
- schedule: (callback, ms) => {
85
- const id = setTimeout(callback, ms);
86
- return () => clearTimeout(id);
87
- },
88
- };
89
-
90
- $effect(() => {
91
- noteHoverNoop(onHoverIn, onHoverOut);
92
- });
93
- void delayHoverIn;
94
- void delayHoverOut;
95
-
96
- const state: IPressState = $derived({ pressed });
97
-
98
- // Rebuilt whenever the config it closes over changes (mirrors Vue's per-render rebuild); the
99
- // runtime persists across rebuilds so in-flight timers/drift state survive.
100
- const handlers = $derived(
101
- createPressHandlers(
102
- {
103
- onPress,
104
- onPressIn,
105
- onPressOut,
106
- onPressMove,
107
- onLongPress,
108
- delayLongPress,
109
- unstable_pressDelay,
110
- hitSlop,
111
- pressRetentionOffset,
112
- },
113
- runtime,
114
- host,
115
- ),
116
- );
117
-
118
- const resolvedAccessibilityState = $derived(
119
- resolveDisabledAccessibilityState(accessibilityState, disabled),
120
- );
121
-
122
- // Folds aria-*/role into accessibility* — Pressable owns its host node directly (it does not
123
- // compose View.svelte), so it must fold this itself, exactly like Vue's Pressable and every
124
- // other Svelte component that hand-authors a raw host tag (RefreshControl.svelte, modal/
125
- // index.svelte).
126
- const resolved = $derived(
127
- resolveAccessibilityProps({
128
- ...rest,
129
- accessibilityState: resolvedAccessibilityState,
130
- }),
131
- );
132
-
133
- const resolvedStyle = $derived(
134
- typeof style === 'function' ? style(state) : style,
135
- );
136
-
137
- // android_ripple rides a dedicated inner View; on iOS rippleProps() returns undefined, so the
138
- // child renders unwrapped, no extra node — mirrors React's Pressable + touchable-native-feedback.
139
- const ripple = $derived(
140
- android_ripple !== undefined ? rippleProps(android_ripple) : undefined,
141
- );
142
-
143
- const bag = $derived({
144
- ...resolved,
145
- style: resolvedStyle,
146
- class: className,
147
- hitSlop,
148
- ...(android_disableSound !== undefined ? { android_disableSound } : {}),
149
- ...buildPressableListeners(handlers, { disabled, cancelable }),
150
- });
151
-
152
- // See View.svelte's note on `{@attach}`.
153
- const syncAttachments = createAttachmentsSync();
154
- $effect(() => {
155
- syncAttachments(hostShim, rest);
156
- });
157
- </script>
158
-
159
- <symbiote-view p={bag} bind:this={hostShim}>
160
- {#if ripple !== undefined}
161
- <symbiote-view p={ripple}>
162
- {@render children?.(state)}
163
- </symbiote-view>
164
- {:else}
165
- {@render children?.(state)}
166
- {/if}
167
- </symbiote-view>
@@ -1,5 +0,0 @@
1
- import type { IPressableProps } from './pressable-props';
2
- export type { IPressableProps };
3
- declare const Index: import("svelte").Component<IPressableProps, {}, "">;
4
- type Index = ReturnType<typeof Index>;
5
- export default Index;
@@ -1,331 +0,0 @@
1
- <script lang="ts" module>
2
- // ScrollView: sticky headers, RefreshControl, the imperative scroll handle,
3
- // maintainVisibleContentPosition, native scroll-attach. There is NO renderScrollView Descriptor
4
- // factory (no 3-layer split, no descriptorToSvelte bridge exists anywhere in this adapter — see
5
- // svelte-adapter-dom-shim skill §15's fixed-shape-render note). This component hand-assembles
6
- // markup and wires refs/effects directly, calling the SAME framework-agnostic helpers React's
7
- // usePreparedScrollView / Vue's createScrollView call: resolveDecelerationRate,
8
- // selectScrollIntrinsics, readLayoutDimension, didContentSizeChange, resolveScrollForwarding,
9
- // buildScrollViewHandle, attachStickyScroll, forwardScrollEvent, resolveAccessibilityProps.
10
- //
11
- // Fabric tree shape: a scroll view wraps a content view holding the children (RN's own
12
- // ScrollView.js shape). Svelte cannot pick a host tag name dynamically without
13
- // `<svelte:element>` (unverified under the DOM shim, not exercised elsewhere in this adapter —
14
- // see svelte-adapter-dom-shim skill §4), so the horizontal/vertical tag choice is a static
15
- // `{#if isHorizontal}` branch instead of a data-driven `createElement(scrollViewIntrinsic, …)`
16
- // call the way React/Vue do it.
17
- //
18
- // RefreshControl: iOS renders the real `RefreshControl.svelte` as a childless SIBLING before
19
- // the content container. Android WRAPS the scroll view with it (`refreshControl` becomes the
20
- // parent, scroll view nested inside) — structurally possible in Svelte (unlike React's
21
- // cloneElement / Vue's VNode re-invocation) because `refreshControl` is typed as
22
- // RefreshControl's OWN PROPS BAG here (scroll-view-props.ts), not a pre-rendered
23
- // element/snippet: ScrollView itself instantiates `<RefreshControl>` in the right position and
24
- // puts the scroll view INSIDE it on Android via plain markup nesting.
25
- //
26
- // KNOWN GAPS:
27
- // 1. `stickyHeaderIndices` / `invertStickyHeaders` are NOT auto-honored (see scroll-view-props.ts
28
- // and sticky-header.svelte's header comments for the full reasoning). Compose the exported
29
- // `ScrollViewStickyHeader` manually instead; it auto-wires to THIS ScrollView's scroll offset
30
- // via Svelte context (scroll-view-sticky-context.ts), so no extra props are usually needed.
31
- // 2. `maintainVisibleContentPosition` is forwarded to the native node and `collapsableChildren`
32
- // is set correctly (via resolveScrollForwarding), but not otherwise exercised or tested here.
33
- import type { IScrollViewProps } from './scroll-view-props';
34
- import type { IScrollViewHandle } from '@symbiote-native/components';
35
-
36
- export type { IScrollViewProps, IScrollViewHandle };
37
- </script>
38
-
39
- <script lang="ts">
40
- import { setContext } from 'svelte';
41
- import {
42
- attachStickyScroll,
43
- buildScrollViewHandle,
44
- didContentSizeChange,
45
- forwardScrollEvent,
46
- readLayoutDimension,
47
- resolveAccessibilityProps,
48
- resolveDecelerationRate,
49
- resolveScrollForwarding,
50
- selectScrollIntrinsics,
51
- splitLayoutProps,
52
- type IContentSize,
53
- } from '@symbiote-native/components';
54
- import {
55
- AnimatedValue,
56
- dlog,
57
- event as animatedEvent,
58
- isNativeAnimatedAvailable,
59
- resolveClassName,
60
- type ISymbioteEvent,
61
- type ISymbioteNode,
62
- } from '@symbiote-native/engine';
63
- import { resolveSvelteClass } from '../../class-value';
64
- import { PLATFORM } from './scroll-view-platform';
65
- import { SCROLL_VIEW_STICKY_CONTEXT_KEY } from './scroll-view-sticky-context';
66
- import RefreshControl from '../RefreshControl.svelte';
67
- import type { ShimElement } from '../../dom-shim';
68
- import { createAttachmentsSync } from '../../runes/attachments';
69
-
70
- let {
71
- style,
72
- class: className,
73
- contentContainerStyle,
74
- horizontal,
75
- decelerationRate,
76
- refreshControl,
77
- onContentSizeChange,
78
- stickyHeaderIndices,
79
- invertStickyHeaders,
80
- onLayout,
81
- onScroll,
82
- scrollEventThrottle,
83
- children,
84
- ...passthrough
85
- }: IScrollViewProps = $props();
86
-
87
- $effect(() => {
88
- if (stickyHeaderIndices === undefined || stickyHeaderIndices.length === 0)
89
- return;
90
- dlog(
91
- 'ScrollView.stickyHeaderIndices is not auto-honored on Svelte (no index-wrap mechanism — ' +
92
- 'see scroll-view-props.ts KNOWN GAP); compose ScrollViewStickyHeader manually instead',
93
- );
94
- });
95
-
96
- // $state.raw, NOT $state: holds the shim element by IDENTITY — a deep $state proxy would make
97
- // dispatchViewCommand miss the engine's WeakMap mirror and every
98
- // scrollTo/scrollToEnd/flashScrollIndicators would silently no-op.
99
- let hostShim = $state.raw<ShimElement | null>(null);
100
- const handle: IScrollViewHandle = buildScrollViewHandle(
101
- (): ISymbioteNode | null => hostShim?.engineNode ?? null,
102
- );
103
-
104
- // Plain exported functions on the instance script are what a parent's `bind:this={ref}` sees —
105
- // the Svelte mechanism for exposing an imperative handle, the twin of React's
106
- // useImperativeHandle / Vue's expose().
107
- export function scrollTo(options?: {
108
- x?: number;
109
- y?: number;
110
- animated?: boolean;
111
- }): void {
112
- handle.scrollTo(options);
113
- }
114
- export function scrollToEnd(options?: { animated?: boolean }): void {
115
- handle.scrollToEnd(options);
116
- }
117
- export function flashScrollIndicators(): void {
118
- handle.flashScrollIndicators();
119
- }
120
- export function getScrollNode(): ISymbioteNode | null {
121
- return handle.getScrollNode();
122
- }
123
-
124
- const isHorizontal = $derived(horizontal === true);
125
- const hasStickyHeaders = $derived(
126
- stickyHeaderIndices !== undefined && stickyHeaderIndices.length > 0,
127
- );
128
- const shouldWrapRefreshControl = $derived(
129
- PLATFORM.refreshControlMode === 'wrap' && refreshControl !== undefined,
130
- );
131
-
132
- // Android wrap mode only: RN's ScrollView.js splits the flattened style across the two boxes —
133
- // LAYOUT props (margin/flex/size/position/...) drive the outer AndroidSwipeRefreshLayout frame,
134
- // VISUAL props (background/padding/border/...) paint the inner scroll view (mirrors React's/
135
- // Vue's index.android.ts splitLayoutProps). Splitting on the resolved [class, style] pair, not
136
- // `style` alone: a class-only layout prop (flex/height/gap/...) is invisible to `style` until
137
- // resolveClassName runs, so splitting on `style` alone would starve the wrapper of its layout
138
- // style and collapse it to nothing — e.g. App.svelte's `class="screen"` (flex:1) on the
139
- // top-level ScrollView, which left the wrapper with no height for its content to grow into.
140
- const layoutSplit = $derived(
141
- shouldWrapRefreshControl
142
- ? splitLayoutProps([resolveSvelteClass(className), style])
143
- : undefined,
144
- );
145
-
146
- // A single AnimatedValue tracks the scroll offset (RN's _scrollAnimatedValue), allocated once
147
- // per instance — held by IDENTITY (never wrapped in $state, the same reactivity rule the shim
148
- // node follows), shared with any manually-composed ScrollViewStickyHeader via context below.
149
- const scrollAnimatedValue = new AnimatedValue(0);
150
-
151
- // Inverted sticky headers stick to the BOTTOM, needing the viewport height (RN _handleLayout).
152
- let viewportHeight = $state<number | undefined>(undefined);
153
-
154
- // Context handoff for a manually-composed ScrollViewStickyHeader (see scroll-view-props.ts's
155
- // KNOWN GAP note) — getter functions so a header reads the LIVE value, not a snapshot from
156
- // whenever it first called getContext().
157
- setContext(SCROLL_VIEW_STICKY_CONTEXT_KEY, {
158
- scrollAnimatedValue,
159
- getInverted: (): boolean | undefined => invertStickyHeaders,
160
- getViewportHeight: (): number | undefined => viewportHeight,
161
- });
162
-
163
- // Resolved dynamically, exactly like React (adapters/react/.../scroll-view/shared.ts:267). Do
164
- // not hardcode this false to keep the JS listener alive: `attachStickyScroll` makes
165
- // `scrollAnimatedValue` native up front, and once native AnimatedWithChildren stops cascading
166
- // listeners into its subtree — but RN carries the SAME gate (AnimatedWithChildren.js:74) and
167
- // streams values back only for AnimatedValue, never an interpolation, so the listener is silent
168
- // under RN too and sticky headers still work: the pin IS the native transform, the listener
169
- // only feeds the debounced committed transform for hit-testing (ScrollViewStickyHeader.js adds
170
- // it solely `if (isFabric)`). Forcing the JS path to keep that listener alive only gives up the
171
- // native driver, putting the pin on the JS thread — drift on iOS, outright failure on Android
172
- // (commit debounce 15ms vs iOS's 64ms — render-scroll-sticky.ts).
173
- const nativeStickyAvailable = $derived(
174
- hasStickyHeaders && isNativeAnimatedAvailable(),
175
- );
176
-
177
- // Native sticky-scroll attach (RN attachNativeEvent / _updateAnimatedNodeAttachment) — NOT used
178
- // for sticky headers (see the comment above); kept for other native-event-attach consumers.
179
- $effect(() => {
180
- if (!nativeStickyAvailable) return;
181
- const node = hostShim?.engineNode;
182
- if (node === undefined) return;
183
- return attachStickyScroll(node, scrollAnimatedValue);
184
- });
185
-
186
- const resolvedContentContainerStyle = $derived(
187
- typeof contentContainerStyle === 'string'
188
- ? resolveClassName(contentContainerStyle)
189
- : contentContainerStyle,
190
- );
191
-
192
- const intrinsics = $derived(
193
- selectScrollIntrinsics(isHorizontal, resolvedContentContainerStyle),
194
- );
195
-
196
- const forwarding = $derived(
197
- resolveScrollForwarding({
198
- hasStickyHeaders,
199
- nativeStickyAvailable,
200
- invertStickyHeaders,
201
- scrollEventThrottle,
202
- maintainVisibleContentPosition:
203
- passthrough.maintainVisibleContentPosition,
204
- snapToAlignment: passthrough.snapToAlignment,
205
- }),
206
- );
207
-
208
- let lastContentSize = $state.raw<IContentSize | null>(null);
209
-
210
- function handleContentLayout(event: ISymbioteEvent): void {
211
- const width = readLayoutDimension(event, 'width');
212
- const height = readLayoutDimension(event, 'height');
213
- if (width === undefined || height === undefined) return;
214
- if (!didContentSizeChange(lastContentSize, { width, height })) return;
215
- lastContentSize = { width, height };
216
- dlog(`ScrollView onContentSizeChange ${width}x${height}`);
217
- onContentSizeChange?.(width, height);
218
- }
219
-
220
- function handleScrollLayout(event: ISymbioteEvent): void {
221
- if (forwarding.capturesViewportHeight) {
222
- const height = readLayoutDimension(event, 'height');
223
- if (height !== undefined) viewportHeight = height;
224
- }
225
- onLayout?.(event);
226
- }
227
-
228
- // onScroll: the JS-fallback path wraps the user's handler in Animated.event so the offset drives
229
- // the AnimatedValue each frame (RN _scrollAnimatedValueAttachment); the native + plain paths
230
- // forward the user's handler as-is (the native driver attaches the value on the UI thread).
231
- const resolvedScrollHandler = $derived.by(() => {
232
- if (forwarding.mode !== 'sticky-js') return onScroll;
233
- return animatedEvent(
234
- [{ nativeEvent: { contentOffset: { y: scrollAnimatedValue } } }],
235
- onScroll === undefined
236
- ? undefined
237
- : {
238
- listener: (...args: unknown[]) =>
239
- forwardScrollEvent(onScroll, args),
240
- },
241
- );
242
- });
243
-
244
- // Not wrapping: the full [base, style] pair stays on the scroll view, unchanged. Wrapping: only
245
- // the VISUAL half (layoutSplit.inner) paints it — the LAYOUT half moved to the wrapper below.
246
- const scrollStyle = $derived([
247
- intrinsics.scrollViewBaseStyle,
248
- layoutSplit !== undefined ? layoutSplit.inner : style,
249
- ]);
250
-
251
- const outerBag = $derived.by(() => {
252
- const forwarded = resolveAccessibilityProps(passthrough);
253
- return {
254
- ...forwarded,
255
- nestedScrollEnabled: passthrough.nestedScrollEnabled ?? true,
256
- ...(horizontal !== undefined ? { horizontal } : {}),
257
- ...(decelerationRate !== undefined
258
- ? { decelerationRate: resolveDecelerationRate(decelerationRate) }
259
- : {}),
260
- // layoutSplit already folded className's resolved value into inner/outer above — forwarding
261
- // the raw class here too would re-apply its LAYOUT half onto the inner scroll view a second
262
- // time (on top of the wrapper).
263
- ...(className !== undefined && layoutSplit === undefined
264
- ? { class: className }
265
- : {}),
266
- style: scrollStyle,
267
- onScroll: resolvedScrollHandler,
268
- onLayout: handleScrollLayout,
269
- ...(forwarding.scrollEventThrottle !== undefined
270
- ? { scrollEventThrottle: forwarding.scrollEventThrottle }
271
- : {}),
272
- };
273
- });
274
-
275
- // `collapsable: false` is load-bearing on Android: the content container is a layout-only view
276
- // Android Fabric would otherwise view-flatten away, hoisting the cells up as direct children of
277
- // the scroll view (which strictly hosts one child — an addViewAt crash). No-op on iOS.
278
- const contentBag = $derived.by(() => ({
279
- style: intrinsics.contentStyle,
280
- collapsable: false,
281
- ...(forwarding.collapsableChildren ? { collapsableChildren: false } : {}),
282
- ...(onContentSizeChange !== undefined
283
- ? { onLayout: handleContentLayout }
284
- : {}),
285
- }));
286
- // See View.svelte's note on `{@attach}`.
287
- const syncAttachments = createAttachmentsSync();
288
- $effect(() => {
289
- syncAttachments(hostShim, passthrough);
290
- });
291
- </script>
292
-
293
- {#snippet scrollBody()}
294
- <!-- The gap between these two sibling blocks survives clean_nodes as a ' ' text node, but
295
- the shim drops a whitespace-only node whose parent takes no raw text, so it never
296
- reaches Fabric (dom-shim/text.ts, svelte-adapter-dom-shim §16b). -->
297
- {#if !shouldWrapRefreshControl && refreshControl !== undefined}
298
- <RefreshControl {...refreshControl} />
299
- {/if}
300
- {#if isHorizontal}
301
- <symbiote-horizontal-scroll-content p={contentBag}>
302
- {@render children?.()}
303
- </symbiote-horizontal-scroll-content>
304
- {:else}
305
- <symbiote-scroll-content p={contentBag}>
306
- {@render children?.()}
307
- </symbiote-scroll-content>
308
- {/if}
309
- {/snippet}
310
-
311
- {#if shouldWrapRefreshControl && refreshControl !== undefined}
312
- <RefreshControl {...refreshControl} style={layoutSplit?.outer}>
313
- {#if isHorizontal}
314
- <symbiote-horizontal-scroll-view p={outerBag} bind:this={hostShim}>
315
- {@render scrollBody()}
316
- </symbiote-horizontal-scroll-view>
317
- {:else}
318
- <symbiote-scroll-view p={outerBag} bind:this={hostShim}>
319
- {@render scrollBody()}
320
- </symbiote-scroll-view>
321
- {/if}
322
- </RefreshControl>
323
- {:else if isHorizontal}
324
- <symbiote-horizontal-scroll-view p={outerBag} bind:this={hostShim}>
325
- {@render scrollBody()}
326
- </symbiote-horizontal-scroll-view>
327
- {:else}
328
- <symbiote-scroll-view p={outerBag} bind:this={hostShim}>
329
- {@render scrollBody()}
330
- </symbiote-scroll-view>
331
- {/if}
@@ -1,18 +0,0 @@
1
- import type { IScrollViewProps } from './scroll-view-props';
2
- import type { IScrollViewHandle } from '@symbiote-native/components';
3
- export type { IScrollViewProps, IScrollViewHandle };
4
- import { type ISymbioteNode } from '@symbiote-native/engine';
5
- declare const Index: import("svelte").Component<IScrollViewProps, {
6
- scrollTo: (options?: {
7
- x?: number;
8
- y?: number;
9
- animated?: boolean;
10
- }) => void;
11
- scrollToEnd: (options?: {
12
- animated?: boolean;
13
- }) => void;
14
- flashScrollIndicators: () => void;
15
- getScrollNode: () => ISymbioteNode | null;
16
- }, "">;
17
- type Index = ReturnType<typeof Index>;
18
- export default Index;
@@ -1,3 +0,0 @@
1
- export type IScrollViewHostPlatform = {
2
- refreshControlMode: 'sibling' | 'wrap';
3
- };
@@ -1,3 +0,0 @@
1
- import type { IScrollViewHostPlatform } from './scroll-view-platform-types';
2
- export type { IScrollViewHostPlatform };
3
- export declare const PLATFORM: IScrollViewHostPlatform;
@@ -1,3 +0,0 @@
1
- export const PLATFORM = {
2
- refreshControlMode: 'wrap',
3
- };
@@ -1 +0,0 @@
1
- export * from './scroll-view-platform.ios';
@@ -1,3 +0,0 @@
1
- import type { IScrollViewHostPlatform } from './scroll-view-platform-types';
2
- export type { IScrollViewHostPlatform };
3
- export declare const PLATFORM: IScrollViewHostPlatform;
@@ -1,3 +0,0 @@
1
- export const PLATFORM = {
2
- refreshControlMode: 'sibling',
3
- };
@@ -1,4 +0,0 @@
1
- // Base / default: re-exports the iOS build. Metro overrides this with scroll-view-platform.ios.ts /
2
- // scroll-view-platform.android.ts on a real host; under tsc/vitest the host config resolves here.
3
- // Filename is the selector, no Platform.OS read — same convention as switch-platform.ts.
4
- export * from './scroll-view-platform.ios.js';
@@ -1,7 +0,0 @@
1
- import type { AnimatedValue } from '@symbiote-native/engine';
2
- export declare const SCROLL_VIEW_STICKY_CONTEXT_KEY: unique symbol;
3
- export type IScrollViewStickyContext = {
4
- scrollAnimatedValue: AnimatedValue;
5
- getInverted: () => boolean | undefined;
6
- getViewportHeight: () => number | undefined;
7
- };
@@ -1 +0,0 @@
1
- export const SCROLL_VIEW_STICKY_CONTEXT_KEY = Symbol('symbiote-scroll-view-sticky');
@@ -1,10 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- import type { AnimatedValue, ISymbioteEvent } from '@symbiote-native/engine';
3
- export type IStickyHeaderComponentProps = {
4
- nextHeaderLayoutY?: number;
5
- onLayout?: (event: ISymbioteEvent) => void;
6
- scrollAnimatedValue?: AnimatedValue;
7
- inverted?: boolean;
8
- scrollViewHeight?: number;
9
- children?: Snippet;
10
- };
@@ -1 +0,0 @@
1
- export {};