@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.
- 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/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 +9 -3
- package/build/dom-shim/element.js +195 -29
- package/build/dom-shim/patch-globals.js +31 -5
- 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 +7 -1
- package/build/index.d.ts +7 -4
- package/build/index.js +17 -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 -1
- package/build/register.js +13 -31
- package/build/root-element.js +1 -1
- package/metro-css-parser.cjs +2 -1
- package/metro-svelte-transformer.cjs +1 -17
- package/package.json +7 -15
- 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 -185
- package/build/components/pressable/index.svelte.d.ts +0 -9
- 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 -372
- package/build/components/text-input/index.svelte.d.ts +0 -17
- package/build/components/touchable-highlight/index.svelte +0 -139
- 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 -181
- package/build/components/touchable-opacity/index.svelte.d.ts +0 -5
- package/build/components/touchable-without-feedback/index.svelte +0 -75
- 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/preprocessor/lower-host-primitives.d.ts +0 -10
- package/build/preprocessor/lower-host-primitives.js +0 -455
- package/build/state-style.d.ts +0 -1
- package/build/state-style.js +0 -8
- /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,87 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
// ImageBackground: render-only composition (absolute-fill Image behind, children on top).
|
|
3
|
-
// Calls renderImageBackground() straight from core/components for the absolute-fill
|
|
4
|
-
// positioning + wrapper-dimension proxy onto the inner Image — both tags stay literal (the
|
|
5
|
-
// wrapper hosts `{@render children?.()}` as a live sibling after the image, which the
|
|
6
|
-
// generic descriptorToSvelte bridge can't safely own: appending the image inside an $effect
|
|
7
|
-
// would race Svelte's own placement of the live children and could reorder them — see
|
|
8
|
-
// svelte-adapter-dom-shim skill §19), so `.props`/`.children[0].props` are destructured onto
|
|
9
|
-
// the two known host tags, same pattern as Modal/index.svelte.
|
|
10
|
-
import type { IImageBackgroundProps } from './image-background-props';
|
|
11
|
-
|
|
12
|
-
export type { IImageBackgroundProps };
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
|
-
<script lang="ts">
|
|
16
|
-
import {
|
|
17
|
-
renderImageBackground,
|
|
18
|
-
resolveAccessibilityProps,
|
|
19
|
-
} from '@symbiote-native/components';
|
|
20
|
-
import { resolveClassName } from '@symbiote-native/engine';
|
|
21
|
-
import { createAttachmentsSync } from '../../runes/attachments';
|
|
22
|
-
import type { ShimElement } from '../../dom-shim';
|
|
23
|
-
|
|
24
|
-
let {
|
|
25
|
-
children,
|
|
26
|
-
style,
|
|
27
|
-
imageStyle,
|
|
28
|
-
class: className,
|
|
29
|
-
source,
|
|
30
|
-
defaultSource,
|
|
31
|
-
loadingIndicatorSource,
|
|
32
|
-
resizeMode,
|
|
33
|
-
tintColor,
|
|
34
|
-
src,
|
|
35
|
-
srcSet,
|
|
36
|
-
alt,
|
|
37
|
-
width,
|
|
38
|
-
height,
|
|
39
|
-
crossOrigin,
|
|
40
|
-
referrerPolicy,
|
|
41
|
-
...passthrough
|
|
42
|
-
}: IImageBackgroundProps = $props();
|
|
43
|
-
|
|
44
|
-
const resolvedImageStyle = $derived(
|
|
45
|
-
typeof imageStyle === 'string' ? resolveClassName(imageStyle) : imageStyle,
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const descriptor = $derived(
|
|
49
|
-
renderImageBackground({
|
|
50
|
-
style,
|
|
51
|
-
imageStyle: resolvedImageStyle,
|
|
52
|
-
image: {
|
|
53
|
-
source,
|
|
54
|
-
defaultSource,
|
|
55
|
-
loadingIndicatorSource,
|
|
56
|
-
resizeMode,
|
|
57
|
-
tintColor,
|
|
58
|
-
src,
|
|
59
|
-
srcSet,
|
|
60
|
-
alt,
|
|
61
|
-
width,
|
|
62
|
-
height,
|
|
63
|
-
crossOrigin,
|
|
64
|
-
referrerPolicy,
|
|
65
|
-
passthrough: resolveAccessibilityProps(passthrough),
|
|
66
|
-
},
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
69
|
-
const imageBag = $derived.by(() => {
|
|
70
|
-
const child = descriptor.children[0];
|
|
71
|
-
return typeof child === 'string' ? {} : child.props;
|
|
72
|
-
});
|
|
73
|
-
const wrapperBag = $derived({ ...descriptor.props, class: className });
|
|
74
|
-
|
|
75
|
-
// See View.svelte's note on `{@attach}` — the attachment binds to the OUTER wrapper, the node
|
|
76
|
-
// this component's own `class`/`style` land on and the one a caller means by "this component".
|
|
77
|
-
let hostShim = $state.raw<ShimElement | null>(null);
|
|
78
|
-
const syncAttachments = createAttachmentsSync();
|
|
79
|
-
$effect(() => {
|
|
80
|
-
syncAttachments(hostShim, passthrough);
|
|
81
|
-
});
|
|
82
|
-
</script>
|
|
83
|
-
|
|
84
|
-
<symbiote-view p={wrapperBag} bind:this={hostShim}>
|
|
85
|
-
<symbiote-image p={imageBag} />
|
|
86
|
-
{@render children?.()}
|
|
87
|
-
</symbiote-view>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
// InputAccessoryView: render-only host assembly (iOS). Calls the shared
|
|
3
|
-
// core/components/src/view/render-input-accessory-view.ts's renderInputAccessoryView()
|
|
4
|
-
// directly (nativeID/backgroundColor/style -> the host node, everything else passthrough) —
|
|
5
|
-
// the same calling convention as keyboard-avoiding-view/index.svelte's shared-render-fn call,
|
|
6
|
-
// rather than hand-duplicating its prop-assembly logic (per the svelte-adapter-dom-shim
|
|
7
|
-
// skill's §15/§19 correction: a fixed-shape Descriptor still has to be CALLED, not
|
|
8
|
-
// re-derived). The root tag stays literal below since renderInputAccessoryView always paints
|
|
9
|
-
// the same 'symbiote-input-accessory-view' host — no dynamic <svelte:element> needed. The
|
|
10
|
-
// host has no structural children of its own; the user's children nest directly under it.
|
|
11
|
-
import type { IInputAccessoryViewProps } from './input-accessory-view-props';
|
|
12
|
-
|
|
13
|
-
export type { IInputAccessoryViewProps };
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<script lang="ts">
|
|
17
|
-
import {
|
|
18
|
-
renderInputAccessoryView,
|
|
19
|
-
resolveAccessibilityProps,
|
|
20
|
-
} from '@symbiote-native/components';
|
|
21
|
-
import { createAttachmentsSync } from '../../runes/attachments';
|
|
22
|
-
import type { ShimElement } from '../../dom-shim';
|
|
23
|
-
|
|
24
|
-
let {
|
|
25
|
-
nativeID,
|
|
26
|
-
backgroundColor,
|
|
27
|
-
style,
|
|
28
|
-
children,
|
|
29
|
-
...passthrough
|
|
30
|
-
}: IInputAccessoryViewProps = $props();
|
|
31
|
-
|
|
32
|
-
const descriptor = $derived(
|
|
33
|
-
renderInputAccessoryView({
|
|
34
|
-
nativeID,
|
|
35
|
-
backgroundColor,
|
|
36
|
-
style,
|
|
37
|
-
passthrough: resolveAccessibilityProps(passthrough),
|
|
38
|
-
}),
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
// See View.svelte's note on `{@attach}`.
|
|
42
|
-
let hostShim = $state.raw<ShimElement | null>(null);
|
|
43
|
-
const syncAttachments = createAttachmentsSync();
|
|
44
|
-
$effect(() => {
|
|
45
|
-
syncAttachments(hostShim, passthrough);
|
|
46
|
-
});
|
|
47
|
-
</script>
|
|
48
|
-
|
|
49
|
-
<symbiote-input-accessory-view p={descriptor.props} bind:this={hostShim}>
|
|
50
|
-
{@render children?.()}
|
|
51
|
-
</symbiote-input-accessory-view>
|
|
@@ -1,185 +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 { onDestroy } from 'svelte';
|
|
26
|
-
import {
|
|
27
|
-
createPressHandlers,
|
|
28
|
-
createPressRuntime,
|
|
29
|
-
disposePressRuntime,
|
|
30
|
-
rippleProps,
|
|
31
|
-
buildPressableListeners,
|
|
32
|
-
resolveDisabledAccessibilityState,
|
|
33
|
-
resolveAccessibilityProps,
|
|
34
|
-
noteHoverNoop,
|
|
35
|
-
DEFAULT_DELAY_LONG_PRESS_MS,
|
|
36
|
-
DEFAULT_MIN_PRESS_DURATION_MS,
|
|
37
|
-
type IPressHost,
|
|
38
|
-
type IPressState,
|
|
39
|
-
} from '@symbiote-native/components';
|
|
40
|
-
import { measure, type ISymbioteNode } from '@symbiote-native/engine';
|
|
41
|
-
import type { ShimElement } from '../../dom-shim';
|
|
42
|
-
import { createAttachmentsSync } from '../../runes/attachments';
|
|
43
|
-
|
|
44
|
-
let {
|
|
45
|
-
onPress,
|
|
46
|
-
onPressIn,
|
|
47
|
-
onPressOut,
|
|
48
|
-
onPressMove,
|
|
49
|
-
onLongPress,
|
|
50
|
-
delayLongPress = DEFAULT_DELAY_LONG_PRESS_MS,
|
|
51
|
-
disabled,
|
|
52
|
-
cancelable,
|
|
53
|
-
hitSlop,
|
|
54
|
-
pressRetentionOffset,
|
|
55
|
-
unstable_pressDelay = 0,
|
|
56
|
-
__minPressDuration = DEFAULT_MIN_PRESS_DURATION_MS,
|
|
57
|
-
android_ripple,
|
|
58
|
-
android_disableSound,
|
|
59
|
-
onHoverIn,
|
|
60
|
-
onHoverOut,
|
|
61
|
-
delayHoverIn,
|
|
62
|
-
delayHoverOut,
|
|
63
|
-
accessibilityState,
|
|
64
|
-
style,
|
|
65
|
-
class: className,
|
|
66
|
-
children,
|
|
67
|
-
...rest
|
|
68
|
-
}: IPressableProps & {
|
|
69
|
-
/** @internal Touchable* mirrors RN's Pressability minPressDuration: 0 override. */
|
|
70
|
-
__minPressDuration?: number;
|
|
71
|
-
} = $props();
|
|
72
|
-
|
|
73
|
-
let pressed = $state(false);
|
|
74
|
-
// Plain setup-scope object, never `$state`: mutated by the machine on every event, never
|
|
75
|
-
// reactively read — same as Vue's setup-scope runtime.
|
|
76
|
-
const runtime = createPressRuntime();
|
|
77
|
-
// `$state.raw`, NOT `$state`: holds the responder ShimElement by IDENTITY. `$state()` would
|
|
78
|
-
// deep-proxy it, and `measure()` looks the RAW node up in the engine's WeakMap mirror — a Proxy
|
|
79
|
-
// wrapper misses that lookup and the retention-region measure silently no-ops.
|
|
80
|
-
let hostShim = $state.raw<ShimElement | null>(null);
|
|
81
|
-
|
|
82
|
-
const host: IPressHost = {
|
|
83
|
-
setPressed(next: boolean): void {
|
|
84
|
-
pressed = next;
|
|
85
|
-
},
|
|
86
|
-
getMeasureFn: () => {
|
|
87
|
-
const node: ISymbioteNode | undefined = hostShim?.engineNode;
|
|
88
|
-
if (node === undefined) return undefined;
|
|
89
|
-
return callback => measure(node, callback);
|
|
90
|
-
},
|
|
91
|
-
schedule: (callback, ms) => {
|
|
92
|
-
const id = setTimeout(callback, ms);
|
|
93
|
-
return () => clearTimeout(id);
|
|
94
|
-
},
|
|
95
|
-
now: Date.now,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
onDestroy(() => {
|
|
99
|
-
disposePressRuntime(runtime);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
$effect(() => {
|
|
103
|
-
noteHoverNoop(onHoverIn, onHoverOut);
|
|
104
|
-
});
|
|
105
|
-
void delayHoverIn;
|
|
106
|
-
void delayHoverOut;
|
|
107
|
-
|
|
108
|
-
const state: IPressState = $derived({ pressed });
|
|
109
|
-
|
|
110
|
-
// Rebuilt whenever the config it closes over changes (mirrors Vue's per-render rebuild); the
|
|
111
|
-
// runtime persists across rebuilds so in-flight timers/drift state survive.
|
|
112
|
-
const handlers = $derived(
|
|
113
|
-
createPressHandlers(
|
|
114
|
-
{
|
|
115
|
-
onPress,
|
|
116
|
-
onPressIn,
|
|
117
|
-
onPressOut,
|
|
118
|
-
onPressMove,
|
|
119
|
-
onLongPress,
|
|
120
|
-
delayLongPress,
|
|
121
|
-
unstable_pressDelay,
|
|
122
|
-
minPressDuration: __minPressDuration,
|
|
123
|
-
hitSlop,
|
|
124
|
-
pressRetentionOffset,
|
|
125
|
-
},
|
|
126
|
-
runtime,
|
|
127
|
-
host,
|
|
128
|
-
),
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// android_ripple rides a dedicated inner View; on iOS rippleProps() returns undefined, so the
|
|
132
|
-
// child renders unwrapped, no extra node — mirrors React's Pressable + touchable-native-feedback.
|
|
133
|
-
const ripple = $derived(
|
|
134
|
-
android_ripple !== undefined ? rippleProps(android_ripple) : undefined,
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// ONE derived, not four. The accessibility fold and the style resolution used to be their own
|
|
138
|
-
// `$derived`s, each read by exactly this bag and by nothing else — a memo nobody can reuse, in
|
|
139
|
-
// exchange for a reaction-graph node per Pressable instance. A 1 000-row list mounts 2 000 of
|
|
140
|
-
// them, which made this component the largest single allocation site in a create profile.
|
|
141
|
-
// `handlers` and `ripple` stay separate BECAUSE they are read elsewhere: `handlers` must keep
|
|
142
|
-
// its identity across an unrelated bag change or every recompute re-registers native listeners,
|
|
143
|
-
// and `ripple` is read twice (the branch test and the inner view's own bag).
|
|
144
|
-
const bag = $derived.by(() => {
|
|
145
|
-
// Folds aria-*/role into accessibility* — Pressable owns its host node directly (it does not
|
|
146
|
-
// compose View.svelte), so it must fold this itself, exactly like Vue's Pressable and every
|
|
147
|
-
// other Svelte component that hand-authors a raw host tag (RefreshControl.svelte, modal/
|
|
148
|
-
// index.svelte).
|
|
149
|
-
const resolved = resolveAccessibilityProps({
|
|
150
|
-
...rest,
|
|
151
|
-
accessibilityState: resolveDisabledAccessibilityState(
|
|
152
|
-
accessibilityState,
|
|
153
|
-
disabled,
|
|
154
|
-
),
|
|
155
|
-
});
|
|
156
|
-
const next: Record<string, unknown> = {
|
|
157
|
-
...resolved,
|
|
158
|
-
style: typeof style === 'function' ? style(state) : style,
|
|
159
|
-
class: className,
|
|
160
|
-
hitSlop,
|
|
161
|
-
...buildPressableListeners(handlers, { disabled, cancelable }),
|
|
162
|
-
};
|
|
163
|
-
// Assigned rather than conditionally spread: `...(cond ? { x } : {})` allocated an empty
|
|
164
|
-
// object literal on every evaluation for the overwhelmingly common `undefined` case.
|
|
165
|
-
if (android_disableSound !== undefined)
|
|
166
|
-
next.android_disableSound = android_disableSound;
|
|
167
|
-
return next;
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// See View.svelte's note on `{@attach}`.
|
|
171
|
-
const syncAttachments = createAttachmentsSync();
|
|
172
|
-
$effect(() => {
|
|
173
|
-
syncAttachments(hostShim, rest);
|
|
174
|
-
});
|
|
175
|
-
</script>
|
|
176
|
-
|
|
177
|
-
<symbiote-view p={bag} bind:this={hostShim}>
|
|
178
|
-
{#if ripple !== undefined}
|
|
179
|
-
<symbiote-view p={ripple}>
|
|
180
|
-
{@render children?.(state)}
|
|
181
|
-
</symbiote-view>
|
|
182
|
-
{:else}
|
|
183
|
-
{@render children?.(state)}
|
|
184
|
-
{/if}
|
|
185
|
-
</symbiote-view>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { IPressableProps } from './pressable-props';
|
|
2
|
-
export type { IPressableProps };
|
|
3
|
-
type $$ComponentProps = IPressableProps & {
|
|
4
|
-
/** @internal Touchable* mirrors RN's Pressability minPressDuration: 0 override. */
|
|
5
|
-
__minPressDuration?: number;
|
|
6
|
-
};
|
|
7
|
-
declare const Index: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
-
type Index = ReturnType<typeof Index>;
|
|
9
|
-
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}
|