@symbiote-native/svelte 0.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +8 -10
  2. package/build/components/{activity-indicator/activity-indicator-props.d.ts → activity-indicator-props.d.ts} +1 -1
  3. package/build/components/flat-list/index.svelte +4 -4
  4. package/build/components/{image-background/image-background-props.d.ts → image-background-props.d.ts} +1 -1
  5. package/build/components/index.d.ts +6 -24
  6. package/build/components/index.js +0 -18
  7. package/build/components/keyboard-avoiding-view/index.svelte +6 -6
  8. package/build/components/modal/index.svelte +7 -7
  9. package/build/components/pressable/pressable-props.d.ts +1 -0
  10. package/build/components/refresh-control-props.d.ts +1 -0
  11. package/build/components/safe-area-view-props.d.ts +1 -0
  12. package/build/components/text-props.d.ts +1 -0
  13. package/build/components/virtualized-list/index.svelte +71 -250
  14. package/build/dom-shim/canonical-prop-names.d.ts +2 -0
  15. package/build/dom-shim/canonical-prop-names.js +320 -0
  16. package/build/dom-shim/document.js +1 -1
  17. package/build/dom-shim/element.d.ts +17 -5
  18. package/build/dom-shim/element.js +258 -40
  19. package/build/dom-shim/fold-host-bag.d.ts +1 -0
  20. package/build/dom-shim/fold-host-bag.js +7 -0
  21. package/build/dom-shim/patch-globals.js +35 -7
  22. package/build/dom-shim/shim-node.js +11 -0
  23. package/build/dom-shim/style-cache.d.ts +1 -0
  24. package/build/dom-shim/style-cache.js +27 -0
  25. package/build/dom-shim/text.js +2 -2
  26. package/build/host-instance.d.ts +2 -3
  27. package/build/host-instance.js +9 -4
  28. package/build/index.d.ts +8 -4
  29. package/build/index.js +21 -5
  30. package/build/intrinsic-elements.d.ts +63 -0
  31. package/build/modules/animated/index.d.ts +7 -27
  32. package/build/modules/animated/index.js +24 -34
  33. package/build/modules/image/index.d.ts +3 -0
  34. package/build/modules/image/index.js +15 -0
  35. package/build/preprocessor/scoped-styles.js +1 -1
  36. package/build/register.d.ts +1 -0
  37. package/build/register.js +15 -0
  38. package/build/root-element.js +1 -1
  39. package/metro-css-parser.cjs +2 -1
  40. package/metro-svelte-transformer.cjs +30 -12
  41. package/package.json +19 -9
  42. package/build/components/RefreshControl.svelte +0 -40
  43. package/build/components/RefreshControl.svelte.d.ts +0 -5
  44. package/build/components/SafeAreaView.svelte +0 -34
  45. package/build/components/SafeAreaView.svelte.d.ts +0 -5
  46. package/build/components/Text.svelte +0 -34
  47. package/build/components/Text.svelte.d.ts +0 -5
  48. package/build/components/View.svelte +0 -43
  49. package/build/components/View.svelte.d.ts +0 -5
  50. package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +0 -3
  51. package/build/components/activity-indicator/activity-indicator-platform.android.js +0 -5
  52. package/build/components/activity-indicator/activity-indicator-platform.d.ts +0 -1
  53. package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +0 -3
  54. package/build/components/activity-indicator/activity-indicator-platform.ios.js +0 -6
  55. package/build/components/activity-indicator/activity-indicator-platform.js +0 -5
  56. package/build/components/activity-indicator/index.svelte +0 -63
  57. package/build/components/activity-indicator/index.svelte.d.ts +0 -5
  58. package/build/components/button.svelte +0 -76
  59. package/build/components/button.svelte.d.ts +0 -5
  60. package/build/components/image/image-logic.d.ts +0 -2
  61. package/build/components/image/image-logic.js +0 -135
  62. package/build/components/image/index.d.ts +0 -3
  63. package/build/components/image/index.js +0 -10
  64. package/build/components/image/index.svelte +0 -66
  65. package/build/components/image/index.svelte.d.ts +0 -5
  66. package/build/components/image-background/index.svelte +0 -87
  67. package/build/components/image-background/index.svelte.d.ts +0 -5
  68. package/build/components/input-accessory-view/index.svelte +0 -51
  69. package/build/components/input-accessory-view/index.svelte.d.ts +0 -5
  70. package/build/components/pressable/index.svelte +0 -167
  71. package/build/components/pressable/index.svelte.d.ts +0 -5
  72. package/build/components/scroll-view/index.svelte +0 -331
  73. package/build/components/scroll-view/index.svelte.d.ts +0 -18
  74. package/build/components/scroll-view/scroll-view-platform-types.d.ts +0 -3
  75. package/build/components/scroll-view/scroll-view-platform.android.d.ts +0 -3
  76. package/build/components/scroll-view/scroll-view-platform.android.js +0 -3
  77. package/build/components/scroll-view/scroll-view-platform.d.ts +0 -1
  78. package/build/components/scroll-view/scroll-view-platform.ios.d.ts +0 -3
  79. package/build/components/scroll-view/scroll-view-platform.ios.js +0 -3
  80. package/build/components/scroll-view/scroll-view-platform.js +0 -4
  81. package/build/components/scroll-view/scroll-view-sticky-context.d.ts +0 -7
  82. package/build/components/scroll-view/scroll-view-sticky-context.js +0 -1
  83. package/build/components/scroll-view/sticky-header-props.d.ts +0 -10
  84. package/build/components/scroll-view/sticky-header-props.js +0 -1
  85. package/build/components/scroll-view/sticky-header.svelte +0 -287
  86. package/build/components/scroll-view/sticky-header.svelte.d.ts +0 -5
  87. package/build/components/switch/index.svelte +0 -134
  88. package/build/components/switch/index.svelte.d.ts +0 -5
  89. package/build/components/text-input/index.svelte +0 -316
  90. package/build/components/text-input/index.svelte.d.ts +0 -12
  91. package/build/components/touchable-highlight/index.svelte +0 -138
  92. package/build/components/touchable-highlight/index.svelte.d.ts +0 -5
  93. package/build/components/touchable-native-feedback/index.d.ts +0 -9
  94. package/build/components/touchable-native-feedback/index.js +0 -14
  95. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +0 -49
  96. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +0 -5
  97. package/build/components/touchable-opacity/index.svelte +0 -180
  98. package/build/components/touchable-opacity/index.svelte.d.ts +0 -5
  99. package/build/components/touchable-without-feedback/index.svelte +0 -74
  100. package/build/components/touchable-without-feedback/index.svelte.d.ts +0 -5
  101. package/build/modules/animated/animated-component-props.d.ts +0 -7
  102. package/build/modules/animated/animated-component-props.js +0 -1
  103. package/build/modules/animated/create-animated-component.d.ts +0 -5
  104. package/build/modules/animated/create-animated-component.js +0 -131
  105. /package/build/components/{activity-indicator/activity-indicator-props.js → activity-indicator-props.js} +0 -0
  106. /package/build/components/{image-background/image-background-props.js → image-background-props.js} +0 -0
  107. /package/build/{components/scroll-view/scroll-view-platform-types.js → intrinsic-elements.js} +0 -0
@@ -1,180 +0,0 @@
1
- <script lang="ts" module>
2
- // TouchableOpacity: built on Pressable, like the rest of the family. Ported against RN's own
3
- // source (.vendors/react-native/.../Components/Touchable/TouchableOpacity.js) after the
4
- // 2026-08-19 audit found ten divergences shared by every adapter; Solid migrated first, then
5
- // React, Vue and Angular, and this is Svelte.
6
- //
7
- // Shared (framework-agnostic) half: the press-scheduling machine and the resting-opacity math
8
- // (@symbiote-native/components). Svelte owns only the lifecycle — runes, the real timers, and
9
- // the Animated wiring.
10
- //
11
- // The interim `tweenOpacity` stand-in this file used to carry is GONE. Its header claimed no
12
- // Animated binding existed for this adapter; that stopped being true when
13
- // modules/animated landed, and a setTimeout tween can never be native-driven — which is
14
- // exactly what RN asks for here (useNativeDriver: true, TouchableOpacity.js:242).
15
- import type { ITouchableOpacityProps } from './touchable-opacity-props';
16
- import View from '../View.svelte';
17
- import { createAnimatedComponent } from '../../modules/animated/create-animated-component';
18
-
19
- export type { ITouchableOpacityProps };
20
-
21
- // Wrapped here, not imported from modules/animated: that barrel pulls in six `.svelte`
22
- // components (View/Text/Image/ScrollView/FlatList/SectionList), and this package's vitest has
23
- // no `.svelte` loader — a smoke test would have to pre-compile the whole tree to reach one
24
- // Animated.View. Same reasoning, different cause, as sticky-header.svelte's own local wrap.
25
- // Module scope, so every TouchableOpacity instance shares one component identity.
26
- const AnimatedView = createAnimatedComponent(View);
27
- </script>
28
-
29
- <script lang="ts">
30
- import {
31
- createTouchableFeedbackHandlers,
32
- createTouchableFeedbackRuntime,
33
- DEFAULT_ACTIVE_OPACITY,
34
- OPACITY_ACTIVE_GRANT_DURATION_MS,
35
- OPACITY_INACTIVE_DURATION_MS,
36
- TOUCHABLE_MIN_PRESS_DURATION_MS,
37
- restingOpacityFromStyle,
38
- } from '@symbiote-native/components';
39
- import {
40
- AnimatedMock,
41
- AnimatedValue,
42
- Easing,
43
- Platform,
44
- dlog,
45
- timing,
46
- type ISymbioteEvent,
47
- } from '@symbiote-native/engine';
48
- import Pressable from '../pressable/index.svelte';
49
-
50
- // modules/animated's barrel swaps the WHOLE driver namespace for the mock when the host
51
- // reports reduced motion; reaching it here would close the import the module block above
52
- // avoids, so the one driver this component uses is swapped locally on the same flag.
53
- const startTiming = Platform.isDisableAnimations
54
- ? AnimatedMock.timing
55
- : timing;
56
-
57
- function scheduleTimeout(callback: () => void, ms: number): () => void {
58
- const id = setTimeout(callback, ms);
59
- return () => clearTimeout(id);
60
- }
61
-
62
- let {
63
- activeOpacity = DEFAULT_ACTIVE_OPACITY,
64
- style,
65
- class: className,
66
- children: content,
67
- onPressIn,
68
- onPressOut,
69
- delayPressIn = 0,
70
- delayPressOut = 0,
71
- // RN's Touchables override Pressability's own 130ms floor with 0 (TouchableOpacity.js:195);
72
- // what holds the active visual is the fade's duration, not a press-duration floor.
73
- minPressDuration = TOUCHABLE_MIN_PRESS_DURATION_MS,
74
- ...rest
75
- }: ITouchableOpacityProps = $props();
76
-
77
- // RN's _getChildStyleOpacityWithDefault: the fade settles at the opacity the CALLER's style
78
- // asks for, not at a hard 1.
79
- const restingOpacity = $derived(restingOpacityFromStyle(style));
80
-
81
- // One Animated.Value per mount, held by IDENTITY in a plain `const` — never `$state`, which
82
- // would deep-proxy an engine object whose graph bookkeeping is keyed on the raw instance
83
- // (same concern as Pressable's `$state.raw` hostShim).
84
- // svelte-ignore state_referenced_locally -- the SEED is a one-shot read of the initial style;
85
- // later changes are re-settled by the update effect below, not by re-seeding.
86
- const opacity = new AnimatedValue(restingOpacity);
87
-
88
- // Setup-scope only, mutated by the machine — same as Pressable's own runtime, never `$state`.
89
- const runtime = createTouchableFeedbackRuntime();
90
-
91
- function setOpacityTo(toValue: number, duration: number): void {
92
- dlog(`TouchableOpacity opacity -> ${toValue} over ${duration}ms`);
93
- // useNativeDriver: true is RN's own (TouchableOpacity.js:242). opacity is natively drivable,
94
- // so the fade survives a busy JS thread — the whole point of press feedback.
95
- startTiming(opacity, {
96
- toValue,
97
- duration,
98
- easing: Easing.inOut(Easing.quad),
99
- useNativeDriver: true,
100
- }).start();
101
- }
102
-
103
- // Rebuilt whenever the timing config changes (mirrors Vue's per-render rebuild); the runtime
104
- // persists across rebuilds so an in-flight delayPressIn timer / activation clock survives.
105
- const handlers = $derived(
106
- createTouchableFeedbackHandlers(
107
- {
108
- delayPressIn,
109
- delayPressOut,
110
- minPressDuration,
111
- schedule: scheduleTimeout,
112
- now: Date.now,
113
- },
114
- runtime,
115
- {
116
- activate(event: ISymbioteEvent): void {
117
- // 0, not 150 (TouchableOpacity.js:215-220): the duration is chosen by where the
118
- // press-in came from, and Pressability re-dispatches the GRANT event as the delay
119
- // signal when delayPressIn is 0 — so an ordinary tap darkens instantly in RN. The 150ms
120
- // branch is a re-activation (finger returning inside after leaving the bounds), which
121
- // our engine never emits — it sends pressIn from one place, on topTouchStart.
122
- setOpacityTo(activeOpacity, OPACITY_ACTIVE_GRANT_DURATION_MS);
123
- onPressIn?.(event);
124
- },
125
- deactivate(event: ISymbioteEvent): void {
126
- setOpacityTo(restingOpacity, OPACITY_INACTIVE_DURATION_MS);
127
- onPressOut?.(event);
128
- },
129
- },
130
- ),
131
- );
132
-
133
- // Two SEPARATE deriveds, not one object: each memoizes by value, so an unrelated prop change
134
- // cannot re-settle the opacity — the Svelte form of Vue's deliberate two-getter watch source.
135
- const disabledInput = $derived(rest.disabled);
136
-
137
- // RN's componentDidUpdate: a changed `disabled` or a changed style opacity re-settles the view,
138
- // so a Touchable disabled mid-press does not stay stuck at its active opacity. The first run is
139
- // skipped — RN does this on UPDATE only, and firing at mount would animate over the value the
140
- // Animated.Value was just seeded with.
141
- let settled: { disabled: unknown; resting: number } | undefined;
142
- $effect(() => {
143
- const current = { disabled: disabledInput, resting: restingOpacity };
144
- const previous = settled;
145
- settled = current;
146
- if (previous === undefined) return;
147
- if (
148
- previous.disabled === current.disabled &&
149
- previous.resting === current.resting
150
- ) {
151
- return;
152
- }
153
- setOpacityTo(current.resting, OPACITY_INACTIVE_DURATION_MS);
154
- });
155
-
156
- // RN's componentWillUnmount: stop the animation and drop the value back, so a teardown
157
- // mid-fade leaves no driver running against a node that is gone.
158
- $effect(() => {
159
- return () => {
160
- opacity.resetAnimation();
161
- };
162
- });
163
-
164
- // Depends on `style` alone. `opacity` is a stable graph node, so a press changes what the node
165
- // HOLDS, never this array — the per-frame path is setValue -> AnimatedProps.update ->
166
- // setNativeProps, which never re-renders this component.
167
- const feedbackStyle = $derived([style, { opacity }]);
168
- </script>
169
-
170
- <Pressable
171
- {...rest}
172
- onPressIn={handlers.handlePressIn}
173
- onPressOut={handlers.handlePressOut}
174
- >
175
- {#snippet children()}
176
- <AnimatedView style={feedbackStyle} class={className}>
177
- {@render content?.()}
178
- </AnimatedView>
179
- {/snippet}
180
- </Pressable>
@@ -1,5 +0,0 @@
1
- import type { ITouchableOpacityProps } from './touchable-opacity-props';
2
- export type { ITouchableOpacityProps };
3
- declare const Index: import("svelte").Component<ITouchableOpacityProps, {}, "">;
4
- type Index = ReturnType<typeof Index>;
5
- export default Index;
@@ -1,74 +0,0 @@
1
- <script lang="ts" module>
2
- // TouchableWithoutFeedback: built on Pressable, no visual feedback of its own. Ported against
3
- // RN's own source (.vendors/react-native/.../Components/Touchable/TouchableWithoutFeedback.js)
4
- // after the 2026-08-19 audit.
5
- //
6
- // RN builds a FULL Pressability config here — delayPressIn / delayPressOut /
7
- // minPressDuration: 0 — so "without feedback" means no VISUAL, not no TIMING. The previous port
8
- // spread the delay props straight into Pressable, which both ignored them (Pressable owns no
9
- // press-delay scheduling) and forwarded them to the host as unknown Fabric props. The same
10
- // shared machine TouchableOpacity runs now runs here with the visual half left empty.
11
- import type { ITouchableWithoutFeedbackProps } from './touchable-without-feedback-props';
12
-
13
- export type { ITouchableWithoutFeedbackProps };
14
- </script>
15
-
16
- <script lang="ts">
17
- import {
18
- createTouchableFeedbackHandlers,
19
- createTouchableFeedbackRuntime,
20
- TOUCHABLE_MIN_PRESS_DURATION_MS,
21
- } from '@symbiote-native/components';
22
- import type { ISymbioteEvent } from '@symbiote-native/engine';
23
- import Pressable from '../pressable/index.svelte';
24
-
25
- function scheduleTimeout(callback: () => void, ms: number): () => void {
26
- const id = setTimeout(callback, ms);
27
- return () => clearTimeout(id);
28
- }
29
-
30
- let {
31
- children: content,
32
- onPressIn,
33
- onPressOut,
34
- delayPressIn = 0,
35
- delayPressOut = 0,
36
- // RN's Touchables override Pressability's own 130ms floor with 0.
37
- minPressDuration = TOUCHABLE_MIN_PRESS_DURATION_MS,
38
- ...rest
39
- }: ITouchableWithoutFeedbackProps = $props();
40
-
41
- // Setup-scope only, mutated by the machine — never `$state`.
42
- const runtime = createTouchableFeedbackRuntime();
43
-
44
- const handlers = $derived(
45
- createTouchableFeedbackHandlers(
46
- {
47
- delayPressIn,
48
- delayPressOut,
49
- minPressDuration,
50
- schedule: scheduleTimeout,
51
- now: Date.now,
52
- },
53
- runtime,
54
- {
55
- activate(event: ISymbioteEvent): void {
56
- onPressIn?.(event);
57
- },
58
- deactivate(event: ISymbioteEvent): void {
59
- onPressOut?.(event);
60
- },
61
- },
62
- ),
63
- );
64
- </script>
65
-
66
- <Pressable
67
- {...rest}
68
- onPressIn={handlers.handlePressIn}
69
- onPressOut={handlers.handlePressOut}
70
- >
71
- {#snippet children()}
72
- {@render content?.()}
73
- {/snippet}
74
- </Pressable>
@@ -1,5 +0,0 @@
1
- import type { ITouchableWithoutFeedbackProps } from './touchable-without-feedback-props';
2
- export type { ITouchableWithoutFeedbackProps };
3
- declare const Index: import("svelte").Component<import("../touchable-base-props").ITouchableBaseProps, {}, "">;
4
- type Index = ReturnType<typeof Index>;
5
- export default Index;
@@ -1,7 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- export interface IAnimatedComponentProps {
3
- style?: unknown;
4
- children?: Snippet;
5
- passthroughAnimatedPropExplicitValues?: unknown;
6
- [key: string]: unknown;
7
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- import type { Component, ComponentInternals } from 'svelte';
2
- import type { IAnimatedComponentProps } from './animated-component-props';
3
- type IAnimatedBase<TExports extends Record<string, unknown>> = (internals: ComponentInternals, props: never) => TExports;
4
- export declare function createAnimatedComponent<TExports extends Record<string, unknown>>(Base: IAnimatedBase<TExports>): Component<IAnimatedComponentProps, TExports>;
5
- export {};
@@ -1,131 +0,0 @@
1
- // createAnimatedComponent: the Svelte twin of the React/Vue/Solid wrapper. Wraps ANY base
2
- // component so it accepts AnimatedNodes in its props. The framework-agnostic half
3
- // (reduceProps / readPassthroughStyle / resolveHostNode / the AnimatedProps leaf lifecycle)
4
- // comes from @symbiote-native/engine, shared verbatim with every other adapter; only the
5
- // lifecycle below is Svelte's.
6
- //
7
- // WHY IT IS PLAIN TS AND NOT A PARAMETRIZED `.svelte` FILE. The generic wrap expresses fine in
8
- // Svelte — `<Component {...reduced} bind:this={inner} />` over a `component` prop compiles clean
9
- // — but a `.ts` module that imports a `.svelte` file is unparseable by any tool without the
10
- // Svelte plugin, which includes this repo's vitest and every consumer smoke test that builds an
11
- // Animated namespace by hand (packages/navigation's drawer suite). Writing the component body
12
- // against `svelte/internal/client` keeps `createAnimatedComponent` importable from ordinary TS
13
- // everywhere, and leaves `modules/animated/` with no `.svelte` file at all. The internals used
14
- // here are the exact calls the compiler itself emits for the same source — see this package's
15
- // `svelte-internal-client.d.ts`, and §8 of the svelte-adapter-dom-shim skill for the bump
16
- // checklist that covers them.
17
- //
18
- // TWO WAYS TO THE HOST NODE, because Svelte has no ref fall-through. `bind:this` on a COMPONENT
19
- // resolves to that component's exports, never to its element:
20
- // * a scroll container (ScrollView / FlatList / SectionList) exports an imperative handle, so
21
- // resolveHostNode() unwraps it through getScrollNode() — the same call React and Vue make.
22
- // Binding the leaf to the handle itself type-checks and then silently animates nothing.
23
- // * a plain host primitive (View / Text / Image) exports nothing, so the node arrives through
24
- // an `{@attach}` of our own: every Symbiote component forwards attachments onto its host tag
25
- // (runes/attachments.ts), which is the adapter's documented seam to a committed node.
26
- // A base this adapter does not own works if it does either.
27
- //
28
- // EVERY OTHER PROP IS FORWARDED UNTOUCHED, including snippets. Svelte snippets — `children` and
29
- // named scoped ones such as a list's `item` / `sectionHeader` — are ordinary props, so naming any
30
- // of them here would drop the rest: Vue's wrapper forwarded only its default slot and
31
- // Animated.FlatList committed empty cells with no error. The only prop this wrapper consumes is
32
- // `passthroughAnimatedPropExplicitValues`.
33
- import { createAttachmentKey } from 'svelte/attachments';
34
- import { derived, get, pop, push, set, spread_props, state, user_effect, } from 'svelte/internal/client';
35
- import { createAnimatedLeafLifecycle, isNativeAnimatedAvailable, isSymbioteNode, readPassthroughStyle, reduceProps, resolveHostNode, } from '@symbiote-native/engine';
36
- import { ShimElement } from '../../dom-shim/index.js';
37
- // RN's prop carrying explicit (already-rasterized) values that override the animated prop in the
38
- // COMMITTED props (sticky-header passthrough). Named once so reduce and reconcile agree.
39
- const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
40
- // The three keys Svelte's own compiled `rest_props` always drops, plus the prop we consume.
41
- const BASE_EXCLUDES = new Set([
42
- '$$slots',
43
- '$$events',
44
- '$$legacy',
45
- PASSTHROUGH_PROP,
46
- ]);
47
- // The leaf additionally never sees `children`: an AnimatedProps leaf rasterizes its props into
48
- // setNativeProps on every frame, and a snippet is not a Fabric prop. It still reaches the BASE.
49
- const LEAF_EXCLUDES = new Set([...BASE_EXCLUDES, 'children']);
50
- // A fresh plain object each call, never the live props proxy: the leaf lifecycle stores its
51
- // argument as the previous-props SNAPSHOT and compares the next call against it, so handing it
52
- // one live object twice compares that object with itself and skips every reconcile forever.
53
- function collect(props, excludes) {
54
- const out = {};
55
- for (const key of Object.keys(props)) {
56
- if (excludes.has(key))
57
- continue;
58
- out[key] = Reflect.get(props, key);
59
- }
60
- return out;
61
- }
62
- // Reads the base's display name for the dlog label, without a cast, so a reconcile line in a log
63
- // dump says which Animated.* it came from — the per-component labels the six hand-authored files
64
- // used to hardcode.
65
- function baseName(base) {
66
- if (typeof base !== 'function' && typeof base !== 'object')
67
- return 'Anonymous';
68
- if (base === null)
69
- return 'Anonymous';
70
- const name = Reflect.get(base, 'name');
71
- return typeof name === 'string' && name.length > 0 ? name : 'Anonymous';
72
- }
73
- function hostNode(instance, shim) {
74
- const unwrapped = resolveHostNode(instance);
75
- if (isSymbioteNode(unwrapped))
76
- return unwrapped;
77
- return shim?.engineNode ?? null;
78
- }
79
- export function createAnimatedComponent(Base) {
80
- const label = `Animated(${baseName(Base)})`;
81
- return function AnimatedComponent(internals, props) {
82
- // The compiled twin of a component's own `$props()` scope: without it the `$effect`s below
83
- // are created immediately instead of being deferred to mount, and would first run before
84
- // the base has rendered anything.
85
- push(props, true);
86
- const lifecycle = createAnimatedLeafLifecycle(label);
87
- // The `$state.raw` twin: the shim is held by IDENTITY (a deep proxy would miss the engine's
88
- // WeakMap mirror) and read inside the reconcile effect, so the leaf rebinds when it lands.
89
- const hostShim = state(null);
90
- // Rasterized props for the declarative bag: every animated node replaced by its current
91
- // value, so the first paint already carries concrete values. The per-FRAME path is separate
92
- // (AnimatedProps.update -> setNativeProps) and never goes through this.
93
- const reduced = derived(() => {
94
- const out = reduceProps(collect(props, BASE_EXCLUDES));
95
- const passthroughStyle = readPassthroughStyle(Reflect.get(props, PASSTHROUGH_PROP));
96
- if (passthroughStyle !== undefined) {
97
- out.style =
98
- out.style === undefined
99
- ? passthroughStyle
100
- : [out.style, passthroughStyle];
101
- }
102
- return out;
103
- });
104
- // `reduceProps` rebuilds a plain string-keyed Record, so `{@attach}`'s SYMBOL keys do not
105
- // survive it. This one is ours and rides as a second spread source; a user's own attachments
106
- // are re-collected the same way.
107
- const attachments = {
108
- [createAttachmentKey()]: (node) => {
109
- set(hostShim, node instanceof ShimElement ? node : null);
110
- return () => set(hostShim, null);
111
- },
112
- };
113
- for (const key of Object.getOwnPropertySymbols(props)) {
114
- attachments[key] = Reflect.get(props, key);
115
- }
116
- const instance = Base(internals, spread_props(() => get(reduced), attachments));
117
- // `props` and the node are read unconditionally before any branch — a guarded read would
118
- // drop that dependency from later re-runs. reconcile() itself tolerates a null node.
119
- user_effect(() => {
120
- const leafProps = collect(props, LEAF_EXCLUDES);
121
- const node = hostNode(instance, get(hostShim));
122
- const passthrough = Reflect.get(props, PASSTHROUGH_PROP);
123
- lifecycle.reconcile(leafProps, node, passthrough != null && isNativeAnimatedAvailable());
124
- });
125
- user_effect(() => () => lifecycle.teardown());
126
- // The base's own exports ARE the wrapper's: a parent's `bind:this` on Animated.FlatList gets
127
- // the real scrollToOffset/scrollToIndex/getScrollNode surface, and a method added to the base
128
- // later cannot go missing here the way a hand-listed delegation would.
129
- return pop(instance);
130
- };
131
- }