@symbiote-native/svelte 0.2.1 → 1.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/build/bootstrap.js +1 -1
- package/build/class-value.js +1 -1
- package/build/components/RefreshControl.svelte +4 -2
- package/build/components/Text.svelte +11 -2
- package/build/components/View.svelte +3 -1
- package/build/components/activity-indicator/index.svelte +8 -2
- package/build/components/button.svelte +8 -3
- package/build/components/flat-list/index.svelte +46 -11
- package/build/components/image/image-logic.js +13 -3
- package/build/components/image-background/index.svelte +8 -2
- package/build/components/index.d.ts +2 -2
- package/build/components/input-accessory-view/index.svelte +4 -1
- package/build/components/keyboard-avoiding-view/index.svelte +34 -11
- package/build/components/modal/index.svelte +4 -1
- package/build/components/pressable/index.svelte +48 -23
- package/build/components/pressable/index.svelte.d.ts +5 -1
- package/build/components/pressable/pressable-props.d.ts +1 -1
- package/build/components/safe-area-view-props.d.ts +1 -0
- package/build/components/scroll-view/index.svelte +54 -21
- package/build/components/scroll-view/sticky-header.svelte +49 -16
- package/build/components/section-list/index.svelte +6 -1
- package/build/components/switch/index.svelte +16 -5
- package/build/components/text-input/index.svelte +80 -8
- package/build/components/text-input/index.svelte.d.ts +5 -0
- package/build/components/touchable-highlight/index.svelte +115 -15
- package/build/components/touchable-highlight/touchable-highlight-props.d.ts +2 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +8 -3
- package/build/components/touchable-opacity/index.svelte +113 -56
- package/build/components/touchable-without-feedback/index.svelte +60 -4
- package/build/components/virtualized-list/index.svelte +233 -70
- package/build/components/virtualized-list/virtualized-list-props.js +8 -4
- package/build/components/virtualized-section-list/index.svelte +56 -9
- package/build/components/virtualized-section-list/index.svelte.d.ts +1 -1
- package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +5 -0
- package/build/create-portal/index.d.ts +22 -0
- package/build/create-portal/index.js +160 -0
- package/build/descriptor-to-svelte.js +15 -21
- package/build/dom-shim/element.d.ts +9 -3
- package/build/dom-shim/element.js +77 -25
- 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/index.d.ts +1 -0
- package/build/dom-shim/index.js +3 -0
- package/build/dom-shim/patch-globals.js +4 -2
- package/build/dom-shim/shim-node.js +11 -0
- package/build/dom-shim/text.js +38 -1
- package/build/host-instance.js +9 -5
- package/build/index.d.ts +6 -3
- package/build/index.js +20 -11
- package/build/modules/animated/create-animated-component.d.ts +5 -0
- package/build/modules/animated/create-animated-component.js +131 -0
- package/build/modules/animated/index.d.ts +24 -14
- package/build/modules/animated/index.js +30 -30
- package/build/modules/status-bar/index.js +1 -1
- package/build/modules/status-bar/index.svelte +4 -1
- package/build/native-view-bridge.d.ts +1 -1
- package/build/native-view-bridge.js +1 -1
- package/build/preprocessor/collapse-text-whitespace.js +20 -7
- package/build/preprocessor/forbid-web-only-constructs.js +8 -2
- package/build/preprocessor/lower-host-primitives.d.ts +10 -0
- package/build/preprocessor/lower-host-primitives.js +455 -0
- package/build/preprocessor/scoped-styles.js +141 -104
- package/build/register.d.ts +1 -0
- package/build/register.js +33 -0
- package/build/render.js +48 -3
- package/build/scope-token.d.ts +3 -2
- package/build/scope-token.js +6 -17
- package/build/state-style.d.ts +1 -0
- package/build/state-style.js +8 -0
- package/build/style-scope.d.ts +2 -1
- package/build/style-scope.js +10 -8
- package/metro-svelte-transformer.cjs +48 -14
- package/package.json +28 -10
- package/build/modules/animated/AnimatedImage.svelte +0 -115
- package/build/modules/animated/AnimatedImage.svelte.d.ts +0 -5
- package/build/modules/animated/AnimatedScrollView.svelte +0 -92
- package/build/modules/animated/AnimatedScrollView.svelte.d.ts +0 -17
- package/build/modules/animated/AnimatedText.svelte +0 -58
- package/build/modules/animated/AnimatedText.svelte.d.ts +0 -5
- package/build/modules/animated/AnimatedView.svelte +0 -95
- package/build/modules/animated/AnimatedView.svelte.d.ts +0 -5
|
@@ -35,7 +35,11 @@ const upstreamTransformer = resolveUpstreamTransformer();
|
|
|
35
35
|
// svelte-check, the language server, Vite), while this file is `require()`d by Metro directly
|
|
36
36
|
// and must stay pure CommonJS with no build step of its own, exactly like
|
|
37
37
|
// metro-vue-transformer.cjs.
|
|
38
|
-
const COMPILER_OPTIONS = {
|
|
38
|
+
const COMPILER_OPTIONS = {
|
|
39
|
+
fragments: 'tree',
|
|
40
|
+
css: 'external',
|
|
41
|
+
generate: 'client',
|
|
42
|
+
};
|
|
39
43
|
|
|
40
44
|
// The web-only-construct guard (svelte-adapter-dom-shim skill §7/§22) RUNS HERE, on every
|
|
41
45
|
// `.svelte` Metro compiles — not just via svelte-check/the language server. `{@html}` compiles
|
|
@@ -50,21 +54,21 @@ const COMPILER_OPTIONS = { fragments: 'tree', css: 'external', generate: 'client
|
|
|
50
54
|
// `require()` cannot pull an ESM/TS module, but `transform` is already async.
|
|
51
55
|
let preprocessorPromise;
|
|
52
56
|
function webOnlyConstructGuard() {
|
|
53
|
-
preprocessorPromise ??= import('@symbiote-native/svelte/preprocessor').then(
|
|
54
|
-
mod.forbidWebOnlyConstructs(),
|
|
57
|
+
preprocessorPromise ??= import('@symbiote-native/svelte/preprocessor').then(
|
|
58
|
+
mod => mod.forbidWebOnlyConstructs(),
|
|
55
59
|
);
|
|
56
60
|
return preprocessorPromise;
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
// The `<style>` preprocessor, loaded the same lazy way and for the same reason. Unlike the guard
|
|
60
64
|
// above (which only throws and hands the source back untouched), this one REWRITES the source —
|
|
61
|
-
// it compiles the style block into
|
|
65
|
+
// it compiles the style block into registerRules() output and scopes every `class` in the
|
|
62
66
|
// component's own markup — so its returned `code` is what compile() must be given. See
|
|
63
67
|
// src/preprocessor/scoped-styles.ts for why Svelte's own `result.css` cannot be used instead.
|
|
64
68
|
let scopedStylesPromise;
|
|
65
69
|
function scopedStylesPreprocessor() {
|
|
66
|
-
scopedStylesPromise ??= import('@symbiote-native/svelte/scoped-styles').then(
|
|
67
|
-
mod.scopedStyles(),
|
|
70
|
+
scopedStylesPromise ??= import('@symbiote-native/svelte/scoped-styles').then(
|
|
71
|
+
mod => mod.scopedStyles(),
|
|
68
72
|
);
|
|
69
73
|
return scopedStylesPromise;
|
|
70
74
|
}
|
|
@@ -75,11 +79,24 @@ function scopedStylesPreprocessor() {
|
|
|
75
79
|
// renders as a forced line break on device. Loaded lazily for the same reason as the two above.
|
|
76
80
|
let collapseTextWhitespacePromise;
|
|
77
81
|
function collapseTextWhitespacePreprocessor() {
|
|
78
|
-
collapseTextWhitespacePromise ??=
|
|
79
|
-
mod =>
|
|
80
|
-
|
|
82
|
+
collapseTextWhitespacePromise ??=
|
|
83
|
+
import('@symbiote-native/svelte/collapse-text-whitespace').then(mod =>
|
|
84
|
+
mod.collapseTextWhitespace(),
|
|
85
|
+
);
|
|
81
86
|
return collapseTextWhitespacePromise;
|
|
82
87
|
}
|
|
88
|
+
|
|
89
|
+
// Lowers <View>/<Text> to their intrinsic tags so a primitive stops costing a Svelte component
|
|
90
|
+
// boundary — which in Svelte is paid in ANCHOR NODES, 12 per benchmark row (see the file header).
|
|
91
|
+
// Loaded lazily for the same reason as the three above.
|
|
92
|
+
let lowerHostPrimitivesPromise;
|
|
93
|
+
function lowerHostPrimitivesPreprocessor() {
|
|
94
|
+
lowerHostPrimitivesPromise ??=
|
|
95
|
+
import('@symbiote-native/svelte/lower-host-primitives').then(mod =>
|
|
96
|
+
mod.lowerHostPrimitives(),
|
|
97
|
+
);
|
|
98
|
+
return lowerHostPrimitivesPromise;
|
|
99
|
+
}
|
|
83
100
|
//
|
|
84
101
|
// Svelte 5's compiler strips <script lang="ts"> types structurally, with no external type
|
|
85
102
|
// resolution needed (unlike @vue/compiler-sfc's compileScript, which needs registerTS + a real
|
|
@@ -108,13 +125,19 @@ function compileSvelteFile(src, filename) {
|
|
|
108
125
|
function stripTypeScript(src, filename) {
|
|
109
126
|
const { outputText } = ts.transpileModule(src, {
|
|
110
127
|
fileName: filename,
|
|
111
|
-
compilerOptions: {
|
|
128
|
+
compilerOptions: {
|
|
129
|
+
module: ts.ModuleKind.ESNext,
|
|
130
|
+
target: ts.ScriptTarget.ESNext,
|
|
131
|
+
},
|
|
112
132
|
});
|
|
113
133
|
return outputText;
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
function compileSvelteModuleFile(src, filename) {
|
|
117
|
-
const { js } = compileModule(stripTypeScript(src, filename), {
|
|
137
|
+
const { js } = compileModule(stripTypeScript(src, filename), {
|
|
138
|
+
generate: 'client',
|
|
139
|
+
filename,
|
|
140
|
+
});
|
|
118
141
|
return js.code;
|
|
119
142
|
}
|
|
120
143
|
|
|
@@ -127,14 +150,22 @@ module.exports.transform = async function transform(params) {
|
|
|
127
150
|
if (params.filename.endsWith('.svelte')) {
|
|
128
151
|
// Throws with a message naming the RN alternative. Deliberately BEFORE compile(), so the
|
|
129
152
|
// author sees the real diagnosis rather than a downstream symptom.
|
|
130
|
-
(await webOnlyConstructGuard()).markup({
|
|
153
|
+
(await webOnlyConstructGuard()).markup({
|
|
154
|
+
content: params.src,
|
|
155
|
+
filename: params.filename,
|
|
156
|
+
});
|
|
131
157
|
const stylePreprocessed = await (
|
|
132
158
|
await scopedStylesPreprocessor()
|
|
133
159
|
).markup({ content: params.src, filename: params.filename });
|
|
134
160
|
const preprocessed = await (
|
|
135
161
|
await collapseTextWhitespacePreprocessor()
|
|
136
162
|
).markup({ content: stylePreprocessed.code, filename: params.filename });
|
|
137
|
-
|
|
163
|
+
// LAST, and after scopedStyles specifically — see svelte.config.js for why the order is
|
|
164
|
+
// load-bearing rather than stylistic.
|
|
165
|
+
const lowered = await (
|
|
166
|
+
await lowerHostPrimitivesPreprocessor()
|
|
167
|
+
).markup({ content: preprocessed.code, filename: params.filename });
|
|
168
|
+
const code = compileSvelteFile(lowered.code, params.filename);
|
|
138
169
|
// Re-label as .tsx so RN's transformer processes the module exactly like app source; Metro
|
|
139
170
|
// tracks the real path separately. Matches metro-vue-transformer.cjs's identical trick.
|
|
140
171
|
return upstreamTransformer.transform({
|
|
@@ -143,7 +174,10 @@ module.exports.transform = async function transform(params) {
|
|
|
143
174
|
filename: params.filename + '.tsx',
|
|
144
175
|
});
|
|
145
176
|
}
|
|
146
|
-
if (
|
|
177
|
+
if (
|
|
178
|
+
params.filename.endsWith('.svelte.ts') ||
|
|
179
|
+
params.filename.endsWith('.svelte.js')
|
|
180
|
+
) {
|
|
147
181
|
const code = compileSvelteModuleFile(params.src, params.filename);
|
|
148
182
|
return upstreamTransformer.transform({
|
|
149
183
|
...params,
|
package/package.json
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "SymbioteNative's Svelte adapter — a DOM shim over stock compiled Svelte output driving real native iOS/Android views through the same engine as every other adapter.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react-native",
|
|
7
|
+
"symbiote-native",
|
|
8
|
+
"svelte",
|
|
9
|
+
"fabric",
|
|
10
|
+
"cross-platform",
|
|
11
|
+
"ios",
|
|
12
|
+
"android",
|
|
13
|
+
"renderer"
|
|
14
|
+
],
|
|
5
15
|
"license": "MIT",
|
|
6
16
|
"repository": {
|
|
7
17
|
"type": "git",
|
|
@@ -42,6 +52,10 @@
|
|
|
42
52
|
"types": "./build/preprocessor/collapse-text-whitespace.d.ts",
|
|
43
53
|
"default": "./build/preprocessor/collapse-text-whitespace.js"
|
|
44
54
|
},
|
|
55
|
+
"./lower-host-primitives": {
|
|
56
|
+
"types": "./build/preprocessor/lower-host-primitives.d.ts",
|
|
57
|
+
"default": "./build/preprocessor/lower-host-primitives.js"
|
|
58
|
+
},
|
|
45
59
|
"./style-scope": {
|
|
46
60
|
"types": "./build/style-scope.d.ts",
|
|
47
61
|
"default": "./build/style-scope.js"
|
|
@@ -51,7 +65,11 @@
|
|
|
51
65
|
"default": "./build/scope-token.js"
|
|
52
66
|
},
|
|
53
67
|
"./metro-svelte-transformer": "./metro-svelte-transformer.cjs",
|
|
54
|
-
"./metro-css-parser": "./metro-css-parser.cjs"
|
|
68
|
+
"./metro-css-parser": "./metro-css-parser.cjs",
|
|
69
|
+
"./state-style": {
|
|
70
|
+
"types": "./build/state-style.d.ts",
|
|
71
|
+
"default": "./build/state-style.js"
|
|
72
|
+
}
|
|
55
73
|
},
|
|
56
74
|
"files": [
|
|
57
75
|
"build",
|
|
@@ -63,22 +81,22 @@
|
|
|
63
81
|
},
|
|
64
82
|
"dependencies": {
|
|
65
83
|
"typescript": "~6.0.0",
|
|
66
|
-
"@symbiote-native/components": "0.
|
|
67
|
-
"@symbiote-native/css-parser": "0.
|
|
84
|
+
"@symbiote-native/components": "1.0.0",
|
|
85
|
+
"@symbiote-native/css-parser": "0.5.0"
|
|
68
86
|
},
|
|
69
87
|
"peerDependencies": {
|
|
70
|
-
"@symbiote-native/engine": ">=0.1.7",
|
|
71
88
|
"svelte": ">=5.56.0",
|
|
72
|
-
"react-native": ">=0.86"
|
|
89
|
+
"react-native": ">=0.86",
|
|
90
|
+
"@symbiote-native/engine": "^0.4.0"
|
|
73
91
|
},
|
|
74
92
|
"devDependencies": {
|
|
75
93
|
"@vue/runtime-core": "^3.5.13",
|
|
76
94
|
"react": "19.2.3",
|
|
77
95
|
"svelte": "^5.56.0",
|
|
78
|
-
"@symbiote-native/
|
|
79
|
-
"@symbiote-native/
|
|
80
|
-
"@symbiote-native/test-utils": "0.
|
|
81
|
-
"@symbiote-native/vue": "0.
|
|
96
|
+
"@symbiote-native/engine": "0.4.0",
|
|
97
|
+
"@symbiote-native/react": "1.0.0",
|
|
98
|
+
"@symbiote-native/test-utils": "0.3.0",
|
|
99
|
+
"@symbiote-native/vue": "1.0.0"
|
|
82
100
|
},
|
|
83
101
|
"scripts": {
|
|
84
102
|
"typecheck": "tsc --build",
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
// Animated.Image — unlike View/Text, Image.svelte does REAL prop transformation
|
|
3
|
-
// (buildImageBag: source resolution, resizeMode/tintColor read off style, width/height fold,
|
|
4
|
-
// alt->accessibility). Hand-authoring a bare pass-through bag here (like AnimatedView/Text)
|
|
5
|
-
// would silently drop all of that — the exact copy-paste-instead-of-calling bug class the
|
|
6
|
-
// svelte-adapter-dom-shim skill's §15/§19 already caught on Switch/ActivityIndicator/
|
|
7
|
-
// TextInput/ImageBackground. So this component calls buildImageBag itself (same as
|
|
8
|
-
// image/index.svelte), fed with rasterized (reduceProps'd) field values.
|
|
9
|
-
//
|
|
10
|
-
// Known, inherited (not Svelte-specific) limitation: the PER-FRAME animated path
|
|
11
|
-
// (AnimatedProps.update -> setNativeProps) pushes the raw `rest` fields directly, bypassing
|
|
12
|
-
// buildImageBag's mapping — same as Vue's/React's Animated.Image, since AnimatedProps is
|
|
13
|
-
// framework-agnostic and has no notion of a component's own prop transform. In practice this
|
|
14
|
-
// only matters if a non-`style` field (e.g. `source`) is itself animated, which is not a
|
|
15
|
-
// supported use — animating Image is expected to go through `style` (opacity/transform),
|
|
16
|
-
// which round-trips correctly since buildImageBag keeps `style` under the same key name.
|
|
17
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
18
|
-
|
|
19
|
-
export type { IAnimatedComponentProps };
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<script lang="ts">
|
|
23
|
-
import {
|
|
24
|
-
createAnimatedLeafLifecycle,
|
|
25
|
-
isNativeAnimatedAvailable,
|
|
26
|
-
reduceProps,
|
|
27
|
-
readPassthroughStyle,
|
|
28
|
-
} from '@symbiote-native/engine';
|
|
29
|
-
import { resolveAccessibilityProps } from '@symbiote-native/components';
|
|
30
|
-
import type { IAccessibilityProps, IAriaProps, IImageViewProps } from '@symbiote-native/components';
|
|
31
|
-
import { buildImageBag } from '../../components/image/image-logic';
|
|
32
|
-
import { createAttachmentsSync } from '../../runes/attachments';
|
|
33
|
-
import type { ShimElement } from '../../dom-shim';
|
|
34
|
-
|
|
35
|
-
let { passthroughAnimatedPropExplicitValues: passthrough, ...rest }: IAnimatedComponentProps =
|
|
36
|
-
$props();
|
|
37
|
-
|
|
38
|
-
let hostShim = $state.raw<ShimElement | null>(null);
|
|
39
|
-
|
|
40
|
-
const wantsNative = $derived(passthrough != null && isNativeAnimatedAvailable());
|
|
41
|
-
|
|
42
|
-
// Rasterize every field (style's animated entries resolved to plain numbers, any other
|
|
43
|
-
// animated field to its current value), then re-run Image's own field split + buildImageBag
|
|
44
|
-
// over the resolved values — the same shape image/index.svelte builds, just fed reduced
|
|
45
|
-
// values instead of raw $props().
|
|
46
|
-
const reduced = $derived.by(() => {
|
|
47
|
-
const resolved = reduceProps(rest);
|
|
48
|
-
const {
|
|
49
|
-
source,
|
|
50
|
-
defaultSource,
|
|
51
|
-
loadingIndicatorSource,
|
|
52
|
-
style,
|
|
53
|
-
resizeMode,
|
|
54
|
-
tintColor,
|
|
55
|
-
src,
|
|
56
|
-
srcSet,
|
|
57
|
-
alt,
|
|
58
|
-
width,
|
|
59
|
-
height,
|
|
60
|
-
crossOrigin,
|
|
61
|
-
referrerPolicy,
|
|
62
|
-
...imagePassthrough
|
|
63
|
-
} = resolved;
|
|
64
|
-
// reduceProps returns a flat Record<string, unknown> — every field above is `unknown`
|
|
65
|
-
// even though it is, at runtime, exactly the shape IImageViewProps/IAccessibilityProps
|
|
66
|
-
// declare. Bridging that back into a typed shape without an `as` cast uses the same
|
|
67
|
-
// `Object.assign(Object.create(null), …)` pattern React's create-animated-component.tsx
|
|
68
|
-
// already establishes: Object.create(null) types as `any`, so Object.assign's result does
|
|
69
|
-
// too, and TS allows assigning `any` into a specifically-typed binding.
|
|
70
|
-
const accessibility: IAccessibilityProps & IAriaProps = Object.assign(
|
|
71
|
-
Object.create(null),
|
|
72
|
-
imagePassthrough,
|
|
73
|
-
);
|
|
74
|
-
const view: IImageViewProps = Object.assign(Object.create(null), {
|
|
75
|
-
source,
|
|
76
|
-
defaultSource,
|
|
77
|
-
loadingIndicatorSource,
|
|
78
|
-
style,
|
|
79
|
-
resizeMode,
|
|
80
|
-
tintColor,
|
|
81
|
-
src,
|
|
82
|
-
srcSet,
|
|
83
|
-
alt,
|
|
84
|
-
width,
|
|
85
|
-
height,
|
|
86
|
-
crossOrigin,
|
|
87
|
-
referrerPolicy,
|
|
88
|
-
passthrough: resolveAccessibilityProps(accessibility),
|
|
89
|
-
});
|
|
90
|
-
const bag = buildImageBag(view);
|
|
91
|
-
const passthroughStyle = readPassthroughStyle(passthrough);
|
|
92
|
-
if (passthroughStyle !== undefined) {
|
|
93
|
-
bag.style = bag.style === undefined ? passthroughStyle : [bag.style, passthroughStyle];
|
|
94
|
-
}
|
|
95
|
-
return bag;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const runtime = createAnimatedLeafLifecycle('AnimatedImage');
|
|
99
|
-
|
|
100
|
-
$effect(() => {
|
|
101
|
-
const currentRest = rest;
|
|
102
|
-
const node = hostShim?.engineNode ?? null;
|
|
103
|
-
runtime.reconcile(currentRest, node, wantsNative);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
$effect(() => () => runtime.teardown());
|
|
107
|
-
|
|
108
|
-
// See View.svelte's note on `{@attach}`.
|
|
109
|
-
const syncAttachments = createAttachmentsSync();
|
|
110
|
-
$effect(() => {
|
|
111
|
-
syncAttachments(hostShim, rest);
|
|
112
|
-
});
|
|
113
|
-
</script>
|
|
114
|
-
|
|
115
|
-
<symbiote-image p={reduced} bind:this={hostShim} />
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
2
|
-
export type { IAnimatedComponentProps };
|
|
3
|
-
declare const AnimatedImage: import("svelte").Component<IAnimatedComponentProps, {}, "">;
|
|
4
|
-
type AnimatedImage = ReturnType<typeof AnimatedImage>;
|
|
5
|
-
export default AnimatedImage;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
// Animated.ScrollView — unlike View/Text/Image, ScrollView.svelte already exposes an
|
|
3
|
-
// imperative handle (scrollTo/scrollToEnd/flashScrollIndicators/getScrollNode, the SAME
|
|
4
|
-
// IScrollViewHandle shape createAnimatedComponent's resolveHostNode expects on Vue/React) via
|
|
5
|
-
// its own top-level `export function`s. That is exactly what `bind:this` needs, so this
|
|
6
|
-
// component WRAPS the real ScrollView.svelte directly rather than hand-authoring a reduced
|
|
7
|
-
// host tag — the only way to keep ScrollView's full feature surface (sticky headers,
|
|
8
|
-
// RefreshControl, maintainVisibleContentPosition, native scroll-attach) per
|
|
9
|
-
// <adapters_reach_full_feature_parity>; a hand-authored duplicate would silently lose all of
|
|
10
|
-
// it. getScrollNode() IS this adapter's resolveHostNode() equivalent for a scroll container.
|
|
11
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
12
|
-
|
|
13
|
-
export type { IAnimatedComponentProps };
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<script lang="ts">
|
|
17
|
-
import {
|
|
18
|
-
createAnimatedLeafLifecycle,
|
|
19
|
-
isNativeAnimatedAvailable,
|
|
20
|
-
reduceProps,
|
|
21
|
-
readPassthroughStyle,
|
|
22
|
-
} from '@symbiote-native/engine';
|
|
23
|
-
import type { ISymbioteNode } from '@symbiote-native/engine';
|
|
24
|
-
import type { IScrollViewHandle } from '@symbiote-native/components';
|
|
25
|
-
import ScrollView from '../../components/scroll-view/index.svelte';
|
|
26
|
-
import type { IScrollViewProps } from '../../components/scroll-view/scroll-view-props';
|
|
27
|
-
import { pickAttachmentProps } from '../../runes/attachments';
|
|
28
|
-
|
|
29
|
-
let {
|
|
30
|
-
children,
|
|
31
|
-
passthroughAnimatedPropExplicitValues: passthrough,
|
|
32
|
-
...rest
|
|
33
|
-
}: IAnimatedComponentProps = $props();
|
|
34
|
-
|
|
35
|
-
// The wrapped ScrollView's own instance — its exported functions ARE its public handle
|
|
36
|
-
// (svelte-adapter-dom-shim skill's ScrollView precedent), captured by IDENTITY via bind:this,
|
|
37
|
-
// never $state (a deep proxy would still work here since it's just function references, but
|
|
38
|
-
// $state.raw stays consistent with every other imperative-handle capture in this adapter).
|
|
39
|
-
let scrollRef = $state.raw<IScrollViewHandle | null>(null);
|
|
40
|
-
|
|
41
|
-
const wantsNative = $derived(passthrough != null && isNativeAnimatedAvailable());
|
|
42
|
-
|
|
43
|
-
// reduceProps returns Record<string, unknown> — every field is `unknown` even though it is,
|
|
44
|
-
// at runtime, exactly IScrollViewProps' shape. Bridged back without an `as` cast via the same
|
|
45
|
-
// Object.assign(Object.create(null), …) pattern AnimatedImage.svelte uses (and React's
|
|
46
|
-
// create-animated-component.tsx already establishes): Object.create(null) types as `any`, so
|
|
47
|
-
// the assignment result does too, and TS allows binding `any` into a typed variable.
|
|
48
|
-
const reduced = $derived.by(() => {
|
|
49
|
-
const resolved = reduceProps(rest);
|
|
50
|
-
const passthroughStyle = readPassthroughStyle(passthrough);
|
|
51
|
-
if (passthroughStyle !== undefined) {
|
|
52
|
-
resolved.style =
|
|
53
|
-
resolved.style === undefined ? passthroughStyle : [resolved.style, passthroughStyle];
|
|
54
|
-
}
|
|
55
|
-
const props: IScrollViewProps = Object.assign(Object.create(null), resolved);
|
|
56
|
-
return props;
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const runtime = createAnimatedLeafLifecycle('AnimatedScrollView');
|
|
60
|
-
|
|
61
|
-
$effect(() => {
|
|
62
|
-
const currentRest = rest;
|
|
63
|
-
const node: ISymbioteNode | null = scrollRef?.getScrollNode() ?? null;
|
|
64
|
-
runtime.reconcile(currentRest, node, wantsNative);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
$effect(() => () => runtime.teardown());
|
|
68
|
-
|
|
69
|
-
// Forward the imperative handle: a parent's `bind:this` on <Animated.ScrollView> gets the
|
|
70
|
-
// same scrollTo/scrollToEnd/flashScrollIndicators/getScrollNode surface plain ScrollView
|
|
71
|
-
// exposes — the Svelte twin of Vue's delegating Proxy `expose()`.
|
|
72
|
-
export function scrollTo(options?: { x?: number; y?: number; animated?: boolean }): void {
|
|
73
|
-
scrollRef?.scrollTo(options);
|
|
74
|
-
}
|
|
75
|
-
export function scrollToEnd(options?: { animated?: boolean }): void {
|
|
76
|
-
scrollRef?.scrollToEnd(options);
|
|
77
|
-
}
|
|
78
|
-
export function flashScrollIndicators(): void {
|
|
79
|
-
scrollRef?.flashScrollIndicators();
|
|
80
|
-
}
|
|
81
|
-
export function getScrollNode(): ISymbioteNode | null {
|
|
82
|
-
return scrollRef?.getScrollNode() ?? null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// `reduceProps` rebuilds a plain string-keyed Record, so `{@attach}`'s symbol keys do not
|
|
86
|
-
// survive it — forward them explicitly onto the wrapped ScrollView, which owns the host node.
|
|
87
|
-
const attachments = $derived(pickAttachmentProps(rest));
|
|
88
|
-
</script>
|
|
89
|
-
|
|
90
|
-
<ScrollView {...reduced} {...attachments} bind:this={scrollRef}>
|
|
91
|
-
{@render children?.()}
|
|
92
|
-
</ScrollView>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
2
|
-
export type { IAnimatedComponentProps };
|
|
3
|
-
import type { ISymbioteNode } from '@symbiote-native/engine';
|
|
4
|
-
declare const AnimatedScrollView: import("svelte").Component<IAnimatedComponentProps, {
|
|
5
|
-
scrollTo: (options?: {
|
|
6
|
-
x?: number;
|
|
7
|
-
y?: number;
|
|
8
|
-
animated?: boolean;
|
|
9
|
-
}) => void;
|
|
10
|
-
scrollToEnd: (options?: {
|
|
11
|
-
animated?: boolean;
|
|
12
|
-
}) => void;
|
|
13
|
-
flashScrollIndicators: () => void;
|
|
14
|
-
getScrollNode: () => ISymbioteNode | null;
|
|
15
|
-
}, "">;
|
|
16
|
-
type AnimatedScrollView = ReturnType<typeof AnimatedScrollView>;
|
|
17
|
-
export default AnimatedScrollView;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
// Animated.Text — see AnimatedView.svelte's header comment for the shared design rationale
|
|
3
|
-
// (hand-authored root tag, shared reconcile runtime). Text.svelte is likewise a pure
|
|
4
|
-
// pass-through bag, so hand-authoring loses no logic.
|
|
5
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
6
|
-
|
|
7
|
-
export type { IAnimatedComponentProps };
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<script lang="ts">
|
|
11
|
-
import {
|
|
12
|
-
createAnimatedLeafLifecycle,
|
|
13
|
-
isNativeAnimatedAvailable,
|
|
14
|
-
reduceProps,
|
|
15
|
-
readPassthroughStyle,
|
|
16
|
-
} from '@symbiote-native/engine';
|
|
17
|
-
import { createAttachmentsSync } from '../../runes/attachments';
|
|
18
|
-
import type { ShimElement } from '../../dom-shim';
|
|
19
|
-
|
|
20
|
-
let {
|
|
21
|
-
children,
|
|
22
|
-
passthroughAnimatedPropExplicitValues: passthrough,
|
|
23
|
-
...rest
|
|
24
|
-
}: IAnimatedComponentProps = $props();
|
|
25
|
-
|
|
26
|
-
let hostShim = $state.raw<ShimElement | null>(null);
|
|
27
|
-
|
|
28
|
-
const wantsNative = $derived(passthrough != null && isNativeAnimatedAvailable());
|
|
29
|
-
|
|
30
|
-
const reduced = $derived.by(() => {
|
|
31
|
-
const out = reduceProps(rest);
|
|
32
|
-
const passthroughStyle = readPassthroughStyle(passthrough);
|
|
33
|
-
if (passthroughStyle !== undefined) {
|
|
34
|
-
out.style = out.style === undefined ? passthroughStyle : [out.style, passthroughStyle];
|
|
35
|
-
}
|
|
36
|
-
return out;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const runtime = createAnimatedLeafLifecycle('AnimatedText');
|
|
40
|
-
|
|
41
|
-
$effect(() => {
|
|
42
|
-
const currentRest = rest;
|
|
43
|
-
const node = hostShim?.engineNode ?? null;
|
|
44
|
-
runtime.reconcile(currentRest, node, wantsNative);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
$effect(() => () => runtime.teardown());
|
|
48
|
-
|
|
49
|
-
// See View.svelte's note on `{@attach}`.
|
|
50
|
-
const syncAttachments = createAttachmentsSync();
|
|
51
|
-
$effect(() => {
|
|
52
|
-
syncAttachments(hostShim, rest);
|
|
53
|
-
});
|
|
54
|
-
</script>
|
|
55
|
-
|
|
56
|
-
<symbiote-text p={reduced} bind:this={hostShim}>
|
|
57
|
-
{@render children?.()}
|
|
58
|
-
</symbiote-text>
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
2
|
-
export type { IAnimatedComponentProps };
|
|
3
|
-
declare const AnimatedText: import("svelte").Component<IAnimatedComponentProps, {}, "">;
|
|
4
|
-
type AnimatedText = ReturnType<typeof AnimatedText>;
|
|
5
|
-
export default AnimatedText;
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
// Animated.View: wraps @symbiote-native/engine's Animated value graph around a
|
|
3
|
-
// symbiote-view host tag. Hand-authors its own root tag rather than composing View.svelte —
|
|
4
|
-
// View.svelte exposes no host-node escape hatch (no bind:this on its own root, nothing
|
|
5
|
-
// exported), so there is nothing to capture a ref onto; the pattern instead mirrors
|
|
6
|
-
// Pressable/ScrollView, the adapter's existing precedent for a component that needs the raw
|
|
7
|
-
// ShimElement (svelte-adapter-dom-shim skill). Since View.svelte does no prop transformation
|
|
8
|
-
// of its own (it is a pure pass-through bag), hand-authoring here loses no logic — unlike
|
|
9
|
-
// AnimatedImage, which reuses Image's buildImageBag for exactly that reason.
|
|
10
|
-
//
|
|
11
|
-
// The reactive plumbing (leaf lifecycle, native-driver opt-in, native event attach) is shared
|
|
12
|
-
// with AnimatedText/AnimatedImage/AnimatedScrollView via animated-props-runtime.ts — see this
|
|
13
|
-
// module's index.ts for why that is a shared HELPER rather than one generic
|
|
14
|
-
// createAnimatedComponent(Component) the way Vue/React have: Svelte has no h()/createElement
|
|
15
|
-
// to parametrize an arbitrary base component at runtime.
|
|
16
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
17
|
-
|
|
18
|
-
export type { IAnimatedComponentProps };
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<script lang="ts">
|
|
22
|
-
import {
|
|
23
|
-
createAnimatedLeafLifecycle,
|
|
24
|
-
dlog,
|
|
25
|
-
isNativeAnimatedAvailable,
|
|
26
|
-
reduceProps,
|
|
27
|
-
readPassthroughStyle,
|
|
28
|
-
} from '@symbiote-native/engine';
|
|
29
|
-
import { createAttachmentsSync } from '../../runes/attachments';
|
|
30
|
-
import type { ShimElement } from '../../dom-shim';
|
|
31
|
-
|
|
32
|
-
let {
|
|
33
|
-
children,
|
|
34
|
-
passthroughAnimatedPropExplicitValues: passthrough,
|
|
35
|
-
...rest
|
|
36
|
-
}: IAnimatedComponentProps = $props();
|
|
37
|
-
|
|
38
|
-
// $state.raw, NOT $state — same identity rule every stateful component in this adapter
|
|
39
|
-
// follows (Switch/Pressable/ScrollView): a deep $state proxy would make the engine's
|
|
40
|
-
// WeakMap-keyed node lookups miss.
|
|
41
|
-
let hostShim = $state.raw<ShimElement | null>(null);
|
|
42
|
-
|
|
43
|
-
const wantsNative = $derived(passthrough != null && isNativeAnimatedAvailable());
|
|
44
|
-
|
|
45
|
-
// Reduced (rasterized) props for the declarative bag: every animated node replaced by its
|
|
46
|
-
// current value, so the first paint (and every non-animated re-render) carries concrete
|
|
47
|
-
// values — the per-FRAME path is separate (the runtime's reconcile below), driven straight
|
|
48
|
-
// off the raw `rest` object, never through this derivation.
|
|
49
|
-
let reducedCallCount = 0;
|
|
50
|
-
const reduced = $derived.by(() => {
|
|
51
|
-
const out = reduceProps(rest);
|
|
52
|
-
const passthroughStyle = readPassthroughStyle(passthrough);
|
|
53
|
-
if (passthroughStyle !== undefined) {
|
|
54
|
-
out.style = out.style === undefined ? passthroughStyle : [out.style, passthroughStyle];
|
|
55
|
-
}
|
|
56
|
-
dlog(`AnimatedView reduced#${++reducedCallCount} wantsNative=${wantsNative}`);
|
|
57
|
-
return out;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const runtime = createAnimatedLeafLifecycle('AnimatedView');
|
|
61
|
-
|
|
62
|
-
// DIAGNOSTIC (2026-08-13, tracking the effect_update_depth_exceeded device crash): isolates
|
|
63
|
-
// whether `bind:this={hostShim}` below EVER re-fires after the initial mount. If it does — a
|
|
64
|
-
// custom-element `p` prop commit re-triggering the ref callback — that reassignment would make
|
|
65
|
-
// THIS effect's `hostShim?.engineNode` read see a "new" dependency and re-run, independent of
|
|
66
|
-
// whatever else changed, which is a plausible source of a same-flush synchronous loop that has
|
|
67
|
-
// no equivalent in React (ref callbacks)/Vue (template refs)/Angular (ViewChild). Reads ONLY
|
|
68
|
-
// hostShim, deliberately excluding `rest`, so it is unambiguous which one retriggered it.
|
|
69
|
-
let hostShimChangeCount = 0;
|
|
70
|
-
$effect(() => {
|
|
71
|
-
void hostShim;
|
|
72
|
-
dlog(`AnimatedView hostShim identity change #${++hostShimChangeCount} isNull=${hostShim === null}`);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// `rest` and `hostShim` are read unconditionally before any branch — the dependency-tracking
|
|
76
|
-
// discipline every $effect in this adapter follows (a guarded read drops that dependency from
|
|
77
|
-
// future re-runs; svelte-adapter-dom-shim skill). reconcile() itself handles a null node.
|
|
78
|
-
$effect(() => {
|
|
79
|
-
const currentRest = rest;
|
|
80
|
-
const node = hostShim?.engineNode ?? null;
|
|
81
|
-
runtime.reconcile(currentRest, node, wantsNative);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
$effect(() => () => runtime.teardown());
|
|
85
|
-
|
|
86
|
-
// See View.svelte's note on `{@attach}`.
|
|
87
|
-
const syncAttachments = createAttachmentsSync();
|
|
88
|
-
$effect(() => {
|
|
89
|
-
syncAttachments(hostShim, rest);
|
|
90
|
-
});
|
|
91
|
-
</script>
|
|
92
|
-
|
|
93
|
-
<symbiote-view p={reduced} bind:this={hostShim}>
|
|
94
|
-
{@render children?.()}
|
|
95
|
-
</symbiote-view>
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { IAnimatedComponentProps } from './animated-component-props';
|
|
2
|
-
export type { IAnimatedComponentProps };
|
|
3
|
-
declare const AnimatedView: import("svelte").Component<IAnimatedComponentProps, {}, "">;
|
|
4
|
-
type AnimatedView = ReturnType<typeof AnimatedView>;
|
|
5
|
-
export default AnimatedView;
|