@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,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,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 +0,0 @@
|
|
|
1
|
-
export * from './scroll-view-platform.ios';
|
|
@@ -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 {};
|