@symbiote-native/svelte 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/build/bootstrap.d.ts +7 -0
  4. package/build/bootstrap.js +17 -0
  5. package/build/class-value.d.ts +6 -0
  6. package/build/class-value.js +81 -0
  7. package/build/components/RefreshControl.svelte +38 -0
  8. package/build/components/RefreshControl.svelte.d.ts +5 -0
  9. package/build/components/SafeAreaView.svelte +34 -0
  10. package/build/components/SafeAreaView.svelte.d.ts +5 -0
  11. package/build/components/Text.svelte +25 -0
  12. package/build/components/Text.svelte.d.ts +5 -0
  13. package/build/components/View.svelte +41 -0
  14. package/build/components/View.svelte.d.ts +5 -0
  15. package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +3 -0
  16. package/build/components/activity-indicator/activity-indicator-platform.android.js +5 -0
  17. package/build/components/activity-indicator/activity-indicator-platform.d.ts +1 -0
  18. package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +3 -0
  19. package/build/components/activity-indicator/activity-indicator-platform.ios.js +6 -0
  20. package/build/components/activity-indicator/activity-indicator-platform.js +5 -0
  21. package/build/components/activity-indicator/activity-indicator-props.d.ts +5 -0
  22. package/build/components/activity-indicator/activity-indicator-props.js +1 -0
  23. package/build/components/activity-indicator/index.svelte +57 -0
  24. package/build/components/activity-indicator/index.svelte.d.ts +5 -0
  25. package/build/components/button-props.d.ts +5 -0
  26. package/build/components/button-props.js +1 -0
  27. package/build/components/button.svelte +71 -0
  28. package/build/components/button.svelte.d.ts +5 -0
  29. package/build/components/flat-list/flat-list-props.d.ts +10 -0
  30. package/build/components/flat-list/flat-list-props.js +1 -0
  31. package/build/components/flat-list/index.svelte +254 -0
  32. package/build/components/flat-list/index.svelte.d.ts +79 -0
  33. package/build/components/image/image-logic.d.ts +2 -0
  34. package/build/components/image/image-logic.js +125 -0
  35. package/build/components/image/image-props.d.ts +5 -0
  36. package/build/components/image/image-props.js +1 -0
  37. package/build/components/image/index.d.ts +3 -0
  38. package/build/components/image/index.js +10 -0
  39. package/build/components/image/index.svelte +66 -0
  40. package/build/components/image/index.svelte.d.ts +5 -0
  41. package/build/components/image-background/image-background-props.d.ts +10 -0
  42. package/build/components/image-background/image-background-props.js +1 -0
  43. package/build/components/image-background/index.svelte +81 -0
  44. package/build/components/image-background/index.svelte.d.ts +5 -0
  45. package/build/components/index.d.ts +50 -0
  46. package/build/components/index.js +24 -0
  47. package/build/components/input-accessory-view/index.svelte +48 -0
  48. package/build/components/input-accessory-view/index.svelte.d.ts +5 -0
  49. package/build/components/input-accessory-view/input-accessory-view-props.d.ts +11 -0
  50. package/build/components/input-accessory-view/input-accessory-view-props.js +1 -0
  51. package/build/components/keyboard-avoiding-view/index.svelte +135 -0
  52. package/build/components/keyboard-avoiding-view/index.svelte.d.ts +5 -0
  53. package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.d.ts +15 -0
  54. package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.js +1 -0
  55. package/build/components/modal/index.svelte +126 -0
  56. package/build/components/modal/index.svelte.d.ts +5 -0
  57. package/build/components/modal/modal-props.d.ts +24 -0
  58. package/build/components/modal/modal-props.js +1 -0
  59. package/build/components/pressable/index.svelte +160 -0
  60. package/build/components/pressable/index.svelte.d.ts +5 -0
  61. package/build/components/pressable/pressable-props.d.ts +27 -0
  62. package/build/components/pressable/pressable-props.js +1 -0
  63. package/build/components/refresh-control-props.d.ts +19 -0
  64. package/build/components/refresh-control-props.js +1 -0
  65. package/build/components/safe-area-view-props.d.ts +10 -0
  66. package/build/components/safe-area-view-props.js +1 -0
  67. package/build/components/scroll-view/index.svelte +298 -0
  68. package/build/components/scroll-view/index.svelte.d.ts +18 -0
  69. package/build/components/scroll-view/scroll-view-platform-types.d.ts +3 -0
  70. package/build/components/scroll-view/scroll-view-platform-types.js +1 -0
  71. package/build/components/scroll-view/scroll-view-platform.android.d.ts +3 -0
  72. package/build/components/scroll-view/scroll-view-platform.android.js +3 -0
  73. package/build/components/scroll-view/scroll-view-platform.d.ts +1 -0
  74. package/build/components/scroll-view/scroll-view-platform.ios.d.ts +3 -0
  75. package/build/components/scroll-view/scroll-view-platform.ios.js +3 -0
  76. package/build/components/scroll-view/scroll-view-platform.js +4 -0
  77. package/build/components/scroll-view/scroll-view-props.d.ts +78 -0
  78. package/build/components/scroll-view/scroll-view-props.js +1 -0
  79. package/build/components/scroll-view/scroll-view-sticky-context.d.ts +7 -0
  80. package/build/components/scroll-view/scroll-view-sticky-context.js +1 -0
  81. package/build/components/scroll-view/sticky-header-props.d.ts +10 -0
  82. package/build/components/scroll-view/sticky-header-props.js +1 -0
  83. package/build/components/scroll-view/sticky-header.svelte +254 -0
  84. package/build/components/scroll-view/sticky-header.svelte.d.ts +5 -0
  85. package/build/components/section-list/index.svelte +107 -0
  86. package/build/components/section-list/index.svelte.d.ts +57 -0
  87. package/build/components/section-list/section-list-props.d.ts +1 -0
  88. package/build/components/section-list/section-list-props.js +1 -0
  89. package/build/components/switch/index.svelte +123 -0
  90. package/build/components/switch/index.svelte.d.ts +5 -0
  91. package/build/components/switch/switch-platform-types.d.ts +5 -0
  92. package/build/components/switch/switch-platform-types.js +1 -0
  93. package/build/components/switch/switch-platform.android.d.ts +3 -0
  94. package/build/components/switch/switch-platform.android.js +8 -0
  95. package/build/components/switch/switch-platform.d.ts +1 -0
  96. package/build/components/switch/switch-platform.ios.d.ts +3 -0
  97. package/build/components/switch/switch-platform.ios.js +7 -0
  98. package/build/components/switch/switch-platform.js +5 -0
  99. package/build/components/switch/switch-props.d.ts +5 -0
  100. package/build/components/switch/switch-props.js +1 -0
  101. package/build/components/text-input/index.svelte +300 -0
  102. package/build/components/text-input/index.svelte.d.ts +12 -0
  103. package/build/components/text-input/text-input-props.d.ts +5 -0
  104. package/build/components/text-input/text-input-props.js +1 -0
  105. package/build/components/text-props.d.ts +23 -0
  106. package/build/components/text-props.js +1 -0
  107. package/build/components/touchable-base-props.d.ts +10 -0
  108. package/build/components/touchable-base-props.js +1 -0
  109. package/build/components/touchable-highlight/index.svelte +39 -0
  110. package/build/components/touchable-highlight/index.svelte.d.ts +5 -0
  111. package/build/components/touchable-highlight/touchable-highlight-props.d.ts +5 -0
  112. package/build/components/touchable-highlight/touchable-highlight-props.js +1 -0
  113. package/build/components/touchable-native-feedback/index.d.ts +9 -0
  114. package/build/components/touchable-native-feedback/index.js +14 -0
  115. package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
  116. package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
  117. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +44 -0
  118. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +5 -0
  119. package/build/components/touchable-opacity/index.svelte +124 -0
  120. package/build/components/touchable-opacity/index.svelte.d.ts +5 -0
  121. package/build/components/touchable-opacity/touchable-opacity-props.d.ts +5 -0
  122. package/build/components/touchable-opacity/touchable-opacity-props.js +1 -0
  123. package/build/components/touchable-without-feedback/index.svelte +19 -0
  124. package/build/components/touchable-without-feedback/index.svelte.d.ts +5 -0
  125. package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +2 -0
  126. package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
  127. package/build/components/view-props.d.ts +29 -0
  128. package/build/components/view-props.js +1 -0
  129. package/build/components/virtualized-list/index.svelte +702 -0
  130. package/build/components/virtualized-list/index.svelte.d.ts +79 -0
  131. package/build/components/virtualized-list/virtualized-list-props.d.ts +70 -0
  132. package/build/components/virtualized-list/virtualized-list-props.js +73 -0
  133. package/build/components/virtualized-section-list/index.svelte +206 -0
  134. package/build/components/virtualized-section-list/index.svelte.d.ts +57 -0
  135. package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +62 -0
  136. package/build/components/virtualized-section-list/virtualized-section-list-props.js +1 -0
  137. package/build/create-tunnel/index.d.ts +3 -0
  138. package/build/create-tunnel/index.js +3 -0
  139. package/build/create-tunnel/tunnel-in.svelte +24 -0
  140. package/build/create-tunnel/tunnel-in.svelte.d.ts +9 -0
  141. package/build/create-tunnel/tunnel-out.svelte +14 -0
  142. package/build/create-tunnel/tunnel-out.svelte.d.ts +7 -0
  143. package/build/create-tunnel/tunnel.d.ts +9 -0
  144. package/build/create-tunnel/tunnel.js +9 -0
  145. package/build/descriptor-to-svelte.d.ts +7 -0
  146. package/build/descriptor-to-svelte.js +104 -0
  147. package/build/dom-shim/comment.d.ts +9 -0
  148. package/build/dom-shim/comment.js +24 -0
  149. package/build/dom-shim/document-fragment.d.ts +8 -0
  150. package/build/dom-shim/document-fragment.js +23 -0
  151. package/build/dom-shim/document.d.ts +23 -0
  152. package/build/dom-shim/document.js +61 -0
  153. package/build/dom-shim/element.d.ts +22 -0
  154. package/build/dom-shim/element.js +130 -0
  155. package/build/dom-shim/index.d.ts +5 -0
  156. package/build/dom-shim/index.js +7 -0
  157. package/build/dom-shim/patch-globals.d.ts +2 -0
  158. package/build/dom-shim/patch-globals.js +94 -0
  159. package/build/dom-shim/shim-node.d.ts +30 -0
  160. package/build/dom-shim/shim-node.js +207 -0
  161. package/build/dom-shim/text.d.ts +14 -0
  162. package/build/dom-shim/text.js +97 -0
  163. package/build/host-instance.d.ts +5 -0
  164. package/build/host-instance.js +48 -0
  165. package/build/index.d.ts +28 -0
  166. package/build/index.js +77 -0
  167. package/build/modules/accessibility-info/index.android.d.ts +2 -0
  168. package/build/modules/accessibility-info/index.android.js +4 -0
  169. package/build/modules/accessibility-info/index.d.ts +1 -0
  170. package/build/modules/accessibility-info/index.ios.d.ts +2 -0
  171. package/build/modules/accessibility-info/index.ios.js +3 -0
  172. package/build/modules/accessibility-info/index.js +5 -0
  173. package/build/modules/animated/AnimatedImage.svelte +115 -0
  174. package/build/modules/animated/AnimatedImage.svelte.d.ts +5 -0
  175. package/build/modules/animated/AnimatedScrollView.svelte +92 -0
  176. package/build/modules/animated/AnimatedScrollView.svelte.d.ts +17 -0
  177. package/build/modules/animated/AnimatedText.svelte +58 -0
  178. package/build/modules/animated/AnimatedText.svelte.d.ts +5 -0
  179. package/build/modules/animated/AnimatedView.svelte +95 -0
  180. package/build/modules/animated/AnimatedView.svelte.d.ts +5 -0
  181. package/build/modules/animated/animated-component-props.d.ts +7 -0
  182. package/build/modules/animated/animated-component-props.js +1 -0
  183. package/build/modules/animated/index.d.ts +58 -0
  184. package/build/modules/animated/index.js +69 -0
  185. package/build/modules/app-registry/index.d.ts +7 -0
  186. package/build/modules/app-registry/index.js +28 -0
  187. package/build/modules/status-bar/index.d.ts +5 -0
  188. package/build/modules/status-bar/index.js +21 -0
  189. package/build/modules/status-bar/index.svelte +20 -0
  190. package/build/modules/status-bar/index.svelte.d.ts +4 -0
  191. package/build/native-view-bridge.d.ts +4 -0
  192. package/build/native-view-bridge.js +27 -0
  193. package/build/preprocessor/forbid-web-only-constructs.d.ts +8 -0
  194. package/build/preprocessor/forbid-web-only-constructs.js +204 -0
  195. package/build/preprocessor/scoped-styles.d.ts +10 -0
  196. package/build/preprocessor/scoped-styles.js +399 -0
  197. package/build/render.d.ts +7 -0
  198. package/build/render.js +46 -0
  199. package/build/root-element.d.ts +3 -0
  200. package/build/root-element.js +27 -0
  201. package/build/runes/attachments.d.ts +5 -0
  202. package/build/runes/attachments.js +73 -0
  203. package/build/runes/dimensions-value.d.ts +4 -0
  204. package/build/runes/dimensions-value.js +30 -0
  205. package/build/runes/media-query.d.ts +8 -0
  206. package/build/runes/media-query.js +38 -0
  207. package/build/runes/use-color-scheme.svelte.d.ts +4 -0
  208. package/build/runes/use-color-scheme.svelte.js +25 -0
  209. package/build/runes/use-window-dimensions.svelte.d.ts +4 -0
  210. package/build/runes/use-window-dimensions.svelte.js +43 -0
  211. package/build/runes/window.d.ts +6 -0
  212. package/build/runes/window.js +28 -0
  213. package/build/scope-token.d.ts +2 -0
  214. package/build/scope-token.js +33 -0
  215. package/build/style-scope.d.ts +1 -0
  216. package/build/style-scope.js +29 -0
  217. package/metro-css-parser.cjs +9 -0
  218. package/metro-svelte-transformer.cjs +156 -0
  219. package/package.json +83 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { AnimatedValue } from '@symbiote-native/engine';
2
+ export declare const SCROLL_VIEW_STICKY_CONTEXT_KEY: unique symbol;
3
+ export type IScrollViewStickyContext = {
4
+ scrollAnimatedValue: AnimatedValue;
5
+ getInverted: () => boolean | undefined;
6
+ getViewportHeight: () => number | undefined;
7
+ };
@@ -0,0 +1 @@
1
+ export const SCROLL_VIEW_STICKY_CONTEXT_KEY = Symbol('symbiote-scroll-view-sticky');
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { AnimatedValue, ISymbioteEvent } from '@symbiote-native/engine';
3
+ export type IStickyHeaderComponentProps = {
4
+ nextHeaderLayoutY?: number;
5
+ onLayout?: (event: ISymbioteEvent) => void;
6
+ scrollAnimatedValue?: AnimatedValue;
7
+ inverted?: boolean;
8
+ scrollViewHeight?: number;
9
+ children?: Snippet;
10
+ };
@@ -0,0 +1,254 @@
1
+ <script lang="ts" module>
2
+ // ScrollViewStickyHeader: the Svelte twin of React's/Vue's per-header sticky component
3
+ // (adapters/react/src/components/scroll-view/sticky-header.tsx,
4
+ // adapters/vue/src/components/scroll-view/sticky-header.ts). Drives the shared `reduceSticky`
5
+ // state machine (@symbiote-native/components/state/sticky-header-reducer); this component
6
+ // supplies only Svelte's lifecycle: the folded state cell, the interpolation + listener wiring,
7
+ // the debounce setTimeout, and a reactive re-render trigger.
8
+ //
9
+ // NOT auto-wrapped by `stickyHeaderIndices` on ScrollView (it only ever sees an opaque children
10
+ // Snippet, nothing to index/wrap - see scroll-view-props.ts's KNOWN GAP). App code composes
11
+ // this directly around the section that should stick:
12
+ //
13
+ // <ScrollView>
14
+ // <ScrollViewStickyHeader>
15
+ // <Text>Section A</Text>
16
+ // </ScrollViewStickyHeader>
17
+ // <Row />
18
+ // ...
19
+ // </ScrollView>
20
+ //
21
+ // (VirtualizedList's own stickyHeaderIndices IS auto-wrapped, since it walks an indexable cell
22
+ // list - see virtualized-list/index.svelte.)
23
+ //
24
+ // `nextHeaderLayoutY` (the collision point where this header gets pushed off by the next sticky
25
+ // header) has no automatic cross-talk wiring for a manually-composed header here (no
26
+ // headerLayoutYs map exists without auto-wrapping). Left undefined, a header sticks
27
+ // indefinitely once pinned - correct for the common single-header case. An app with multiple
28
+ // manually-composed headers can pass it explicitly if it tracks the next header's own y.
29
+ //
30
+ // Native-Animated-driven, like React's ScrollViewStickyHeader.js: the pinned translateY rides
31
+ // as the raw AnimatedInterpolation node in style.transform on a real Animated.View
32
+ // (AnimatedView.svelte), whose `__makeNative()` connects it straight to the native shadow tree,
33
+ // so once native the pin tracks scroll on the UI thread with zero JS per frame - the
34
+ // interpolation's own .addListener() below only drives the separate debounced
35
+ // `passthroughAnimatedPropExplicitValues` (RN's own hit-testing sync).
36
+ //
37
+ // reduceSticky's 'layout' case carries an `alreadyAtThisGeometry` guard (sticky-header-reducer.ts):
38
+ // without it, a redundant onLayout reporting the SAME y/height (which every onLayout consumer
39
+ // must tolerate) rebuilds the interpolation graph and reconnects it natively on every pass, and
40
+ // each such commit can itself provoke another relayout - an unbounded same-tick ping-pong that
41
+ // crashes with Svelte's `effect_update_depth_exceeded`. Fixed at the shared layer so every
42
+ // adapter gets it, not just this one.
43
+ //
44
+ // Bootstrap note: the FIRST tick must reach this component's interpolation via JS (the parent's
45
+ // own scroll-forwarding, NOT `attachStickyScroll`'s native event attach) — `__makeNative()`
46
+ // cascades to its PARENT first (interpolation-node.ts), so once `wantsNative` flips true off
47
+ // the first settled debounce, the whole chain promotes to native automatically. If
48
+ // `scrollAnimatedValue` were made native UP FRONT, its JS listener cascade would be permanently
49
+ // disabled (AnimatedWithChildren.__callListeners skips children once isNative) before a single
50
+ // tick ever reached this component - a bootstrap deadlock, not a flaky native module. This is
51
+ // why ScrollView/VirtualizedList force `nativeStickyAvailable = false`: the correct bootstrap
52
+ // path, not a workaround.
53
+ import type { IStickyHeaderComponentProps } from './sticky-header-props';
54
+
55
+ export type { IStickyHeaderComponentProps };
56
+
57
+ // Diagnostic-only: a module-scope counter so every instance across every ScrollViewStickyHeader
58
+ // in a device log is individually identifiable (see the mount/unmount dlog calls below).
59
+ let stickyHeaderInstanceSeq = 0;
60
+ </script>
61
+
62
+ <script lang="ts">
63
+ import { getContext } from 'svelte';
64
+ import {
65
+ createInitialStickyState,
66
+ reduceSticky,
67
+ readLayoutNumber,
68
+ STICKY_HEADER_Z_INDEX,
69
+ type IStickyAction,
70
+ type IStickyEffect,
71
+ type IStickyReducerInputs,
72
+ } from '@symbiote-native/components';
73
+ import { AnimatedValue, Platform, dlog, type AnimatedInterpolation, type ISymbioteEvent } from '@symbiote-native/engine';
74
+ import AnimatedView from '../../modules/animated/AnimatedView.svelte';
75
+ import {
76
+ SCROLL_VIEW_STICKY_CONTEXT_KEY,
77
+ type IScrollViewStickyContext,
78
+ } from './scroll-view-sticky-context';
79
+ import { pickAttachmentProps } from '../../runes/attachments';
80
+
81
+ let {
82
+ nextHeaderLayoutY,
83
+ onLayout: onLayoutProp,
84
+ scrollAnimatedValue: scrollAnimatedValueProp,
85
+ inverted: invertedProp,
86
+ scrollViewHeight: scrollViewHeightProp,
87
+ children,
88
+ ...passthrough
89
+ }: IStickyHeaderComponentProps = $props();
90
+
91
+ // Resolve the scroll wiring from the parent ScrollView's context when not given explicitly.
92
+ // A fresh standalone AnimatedValue is the fallback for a header used outside a ScrollView
93
+ // (never sticks, but never crashes either).
94
+ const stickyContext = getContext<IScrollViewStickyContext | undefined>(SCROLL_VIEW_STICKY_CONTEXT_KEY);
95
+ $effect(() => {
96
+ if (stickyContext === undefined && scrollAnimatedValueProp === undefined) {
97
+ dlog('ScrollViewStickyHeader used outside a ScrollView — sticky positioning is a no-op');
98
+ }
99
+ });
100
+ // svelte-ignore state_referenced_locally -- captured ONCE by identity; it does not change
101
+ // after mount, so re-deriving it would be wrong.
102
+ const scrollAnimatedValue =
103
+ scrollAnimatedValueProp ?? stickyContext?.scrollAnimatedValue ?? new AnimatedValue(0);
104
+ const inverted = $derived(invertedProp ?? stickyContext?.getInverted());
105
+ const scrollViewHeight = $derived(scrollViewHeightProp ?? stickyContext?.getViewportHeight());
106
+
107
+ // The one folded state cell (RN's scattered useState/useRef collapsed into IStickyHeaderState),
108
+ // mutated in place by reduceSticky. Named `stickyState`, not `state`: a local binding literally
109
+ // called `state` collides with the `$state` rune (Svelte's store_rune_conflict warning).
110
+ const stickyState = createInitialStickyState();
111
+ // Bumped on every 'apply-passthrough' effect so the passthrough $derived below re-reads
112
+ // stickyState.translateY (RN's forceRender()).
113
+ let version = $state(0);
114
+
115
+ // The interpolation node currently bound into style.transform — held by IDENTITY ($state.raw),
116
+ // rebuilt on every 'rebuild-interpolation' effect.
117
+ let animatedTranslateY = $state.raw<AnimatedInterpolation>(
118
+ scrollAnimatedValue.interpolate({ inputRange: [-1, 0], outputRange: [0, 0] }),
119
+ );
120
+ let listenerId: string | undefined;
121
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined;
122
+
123
+ function inputs(): IStickyReducerInputs {
124
+ return { os: Platform.OS, inverted, scrollViewHeight, nextHeaderLayoutY };
125
+ }
126
+
127
+ function runEffects(effects: IStickyEffect[]): void {
128
+ for (const effect of effects) {
129
+ switch (effect.kind) {
130
+ case 'rebuild-interpolation': {
131
+ // Logs the actual input/output ranges (not just `changedKeys=style`) so a device log
132
+ // shows what gets built right at a header collision; own layoutY identifies which
133
+ // header this is.
134
+ dlog(
135
+ `ScrollViewStickyHeader[y=${stickyState.layoutY}] rebuild-interpolation ` +
136
+ `inputRange=${JSON.stringify(effect.inputRange)} outputRange=${JSON.stringify(effect.outputRange)} ` +
137
+ `nextHeaderLayoutY=${String(nextHeaderLayoutY)}`,
138
+ );
139
+ // Detach the old listener, build a fresh interpolation onto the shared scroll value, and
140
+ // wire the settled-value listener (drives ONLY the debounced hit-testing passthrough
141
+ // below — the visible pin rides the native AnimatedProps connection in the markup).
142
+ if (listenerId !== undefined) animatedTranslateY.removeListener(listenerId);
143
+ const next = scrollAnimatedValue.interpolate({
144
+ inputRange: effect.inputRange,
145
+ outputRange: effect.outputRange,
146
+ });
147
+ listenerId = next.addListener(({ value }: { value: number | string }): void => {
148
+ if (typeof value === 'number') dispatch({ kind: 'animated-tick', value });
149
+ });
150
+ animatedTranslateY = next;
151
+ break;
152
+ }
153
+ case 'schedule-debounce':
154
+ if (debounceTimer !== undefined) clearTimeout(debounceTimer);
155
+ debounceTimer = setTimeout(() => {
156
+ debounceTimer = undefined;
157
+ dispatch({ kind: 'debounce-fired', value: effect.value });
158
+ }, effect.delay);
159
+ break;
160
+ case 'apply-passthrough':
161
+ // The actual committed translateY — a freeze at a huge/wrong number here is the
162
+ // visible-disappearance symptom to watch for.
163
+ dlog(`ScrollViewStickyHeader[y=${stickyState.layoutY}] apply-passthrough translateY=${effect.translateY}`);
164
+ version += 1;
165
+ break;
166
+ case 'record-header-y':
167
+ // No auto-wrap cross-talk map in this adapter (see the module header comment) — nothing
168
+ // to record into.
169
+ break;
170
+ }
171
+ }
172
+ }
173
+
174
+ function dispatch(action: IStickyAction): void {
175
+ runEffects(reduceSticky(stickyState, action, inputs()).effects);
176
+ }
177
+
178
+ // Rebuild when the collision/viewport inputs change; also does the initial identity build.
179
+ $effect(() => {
180
+ void inverted;
181
+ void scrollViewHeight;
182
+ void nextHeaderLayoutY;
183
+ dispatch({ kind: 'inputs-changed' });
184
+ });
185
+
186
+ // Logs mount/unmount so a device log shows whether a header index gets destroyed+recreated (a
187
+ // fresh instance id right after an unmount of the same index) as it crosses the
188
+ // forcedStickyCell/windowed-cell boundary in VirtualizedList's {#each}, or stays one instance.
189
+ const instanceId = ++stickyHeaderInstanceSeq;
190
+ dlog(`ScrollViewStickyHeader#${instanceId} mount`);
191
+
192
+ // Detach the listener + clear the debounce on unmount.
193
+ $effect(() => {
194
+ return (): void => {
195
+ dlog(`ScrollViewStickyHeader#${instanceId}[y=${stickyState.layoutY}] unmount`);
196
+ if (listenerId !== undefined) animatedTranslateY.removeListener(listenerId);
197
+ if (debounceTimer !== undefined) clearTimeout(debounceTimer);
198
+ };
199
+ });
200
+
201
+ function handleLayout(event: ISymbioteEvent): void {
202
+ const y = readLayoutNumber(event, 'y');
203
+ const height = readLayoutNumber(event, 'height');
204
+ // Keep the previous value when a field is absent (RN sets state only on a defined read).
205
+ dispatch({
206
+ kind: 'layout',
207
+ y: y ?? stickyState.layoutY,
208
+ height: height ?? stickyState.layoutHeight,
209
+ });
210
+ dlog(`ScrollViewStickyHeader layout y=${y} height=${height}`);
211
+ onLayoutProp?.(event);
212
+ }
213
+
214
+ // The EXPLICIT debounced translateY overrides the committed transform for hit-testing, while
215
+ // `animatedTranslateY` (in the markup below) does the smooth native-driven pin — per RN
216
+ // ScrollViewStickyHeader.js, and AnimatedView.svelte's own passthroughAnimatedPropExplicitValues
217
+ // contract (readPassthroughStyle).
218
+ const passthroughAnimatedPropExplicitValues = $derived.by(() => {
219
+ void version;
220
+ return stickyState.translateY !== null
221
+ ? { style: { transform: [{ translateY: stickyState.translateY }] } }
222
+ : null;
223
+ });
224
+
225
+ // Kept as ITS OWN derived, reading only `animatedTranslateY` — NOT folded into `bag` below.
226
+ // `bag` also carries `passthroughAnimatedPropExplicitValues`, which changes value on every
227
+ // 'apply-passthrough' tick (every scroll frame); if `style` lived in that same derived it would
228
+ // get a fresh object identity on every tick even though the interpolation node never changed.
229
+ // animated-props-runtime.ts's reconcile() relies on `style`'s reference staying stable across
230
+ // passthrough-only ticks to skip rebuilding the native AnimatedProps graph — without this
231
+ // split, every scroll frame tore down and reconnected the native node, and a reconnect right as
232
+ // scrolling stopped left the header frozen at its post-reset default until the next tick.
233
+ const animatedStyle = $derived.by(() => ({
234
+ transform: [{ translateY: animatedTranslateY }],
235
+ zIndex: STICKY_HEADER_Z_INDEX,
236
+ }));
237
+
238
+ // collapsable:false keeps the wrapper a real Yoga node; zIndex makes the pinned header paint
239
+ // OVER the rows scrolling under it.
240
+ const bag = $derived.by(() => ({
241
+ style: animatedStyle,
242
+ onLayout: handleLayout,
243
+ collapsable: false,
244
+ passthroughAnimatedPropExplicitValues,
245
+ }));
246
+
247
+ // `{@attach}` rides in the rest object as a symbol-keyed entry; forwarded onto AnimatedView,
248
+ // which owns this component's actual host node.
249
+ const attachments = $derived(pickAttachmentProps(passthrough));
250
+ </script>
251
+
252
+ <AnimatedView {...bag} {...attachments}>
253
+ {@render children?.()}
254
+ </AnimatedView>
@@ -0,0 +1,5 @@
1
+ import type { IStickyHeaderComponentProps } from './sticky-header-props';
2
+ export type { IStickyHeaderComponentProps };
3
+ declare const StickyHeader: import("svelte").Component<IStickyHeaderComponentProps, {}, "">;
4
+ type StickyHeader = ReturnType<typeof StickyHeader>;
5
+ export default StickyHeader;
@@ -0,0 +1,107 @@
1
+ <script lang="ts" module>
2
+ // SectionList: the public list-of-sections component. A thin wrapper over
3
+ // VirtualizedSectionList, mirroring RN's layering (SectionList -> VirtualizedSectionList ->
4
+ // VirtualizedList). All section-flattening / windowing / imperative-scroll logic lives below;
5
+ // this layer re-exposes the same surface under the SectionList name and delegates the handle
6
+ // (a Svelte `bind:this` on THIS component resolves to whatever THIS component exports, so the
7
+ // wrapper must re-declare + delegate rather than re-exporting the child's exports directly) —
8
+ // the Svelte twin of the React/Vue SectionList.
9
+ import type { ISectionListProps, ISectionListHandle, ISection } from './section-list-props';
10
+
11
+ export type { ISectionListProps, ISectionListHandle, ISection };
12
+ </script>
13
+
14
+ <script lang="ts" generics="ItemT">
15
+ import VirtualizedSectionList from '../virtualized-section-list/index.svelte';
16
+ import { pickAccessibilityProps } from '../virtualized-list/virtualized-list-props';
17
+ import type {
18
+ IVirtualizedSectionListHandle,
19
+ IScrollViewHandle,
20
+ } from '../virtualized-list/virtualized-list-props';
21
+ import type { ISectionListProps as IProps } from './section-list-props';
22
+ import { pickAttachmentProps } from '../../runes/attachments';
23
+ import type { ISymbioteNode } from '@symbiote-native/engine';
24
+
25
+ let props: IProps<ItemT> = $props();
26
+
27
+ let inner = $state.raw<IVirtualizedSectionListHandle | null>(null);
28
+
29
+ // Forwarded as a component-prop spread onto VirtualizedSectionList (a compiled Svelte
30
+ // component, not a symbiote-* host tag — see flat-list/index.svelte's identical comment).
31
+ const accessibilityProps = $derived(pickAccessibilityProps(props));
32
+
33
+ export function scrollToLocation(params: {
34
+ sectionIndex: number;
35
+ itemIndex: number;
36
+ viewOffset?: number;
37
+ viewPosition?: number;
38
+ animated?: boolean;
39
+ }): void {
40
+ inner?.scrollToLocation(params);
41
+ }
42
+ export function flashScrollIndicators(): void {
43
+ inner?.flashScrollIndicators();
44
+ }
45
+ export function getNativeScrollRef(): IScrollViewHandle | null {
46
+ return inner?.getNativeScrollRef() ?? null;
47
+ }
48
+ export function getScrollableNode(): IScrollViewHandle | null {
49
+ return inner?.getScrollableNode() ?? null;
50
+ }
51
+ export function getScrollResponder(): IScrollViewHandle | null {
52
+ return inner?.getScrollResponder() ?? null;
53
+ }
54
+ export function getScrollNode(): ISymbioteNode | null {
55
+ return inner?.getScrollNode() ?? null;
56
+ }
57
+ export function recordInteraction(): void {
58
+ inner?.recordInteraction();
59
+ }
60
+
61
+ // `{@attach}` arrives as a symbol-keyed prop, which naming individual props below drops.
62
+ // Re-spread just those onto the inner list, which owns the real host node.
63
+ const attachments = $derived(pickAttachmentProps(props));
64
+ </script>
65
+
66
+ <VirtualizedSectionList
67
+ bind:this={inner}
68
+ {...accessibilityProps}
69
+ {...attachments}
70
+ sections={props.sections}
71
+ item={props.item}
72
+ sectionHeader={props.sectionHeader}
73
+ sectionFooter={props.sectionFooter}
74
+ sectionSeparator={props.sectionSeparator}
75
+ separator={props.separator}
76
+ header={props.header}
77
+ footer={props.footer}
78
+ empty={props.empty}
79
+ keyExtractor={props.keyExtractor}
80
+ stickySectionHeadersEnabled={props.stickySectionHeadersEnabled}
81
+ inverted={props.inverted}
82
+ extraData={props.extraData}
83
+ onEndReached={props.onEndReached}
84
+ onEndReachedThreshold={props.onEndReachedThreshold}
85
+ onStartReached={props.onStartReached}
86
+ onStartReachedThreshold={props.onStartReachedThreshold}
87
+ onRefresh={props.onRefresh}
88
+ refreshing={props.refreshing}
89
+ progressViewOffset={props.progressViewOffset}
90
+ initialNumToRender={props.initialNumToRender}
91
+ initialScrollIndex={props.initialScrollIndex}
92
+ maxToRenderPerBatch={props.maxToRenderPerBatch}
93
+ updateCellsBatchingPeriod={props.updateCellsBatchingPeriod}
94
+ windowSize={props.windowSize}
95
+ maintainVisibleContentPosition={props.maintainVisibleContentPosition}
96
+ onScroll={props.onScroll}
97
+ onScrollBeginDrag={props.onScrollBeginDrag}
98
+ onScrollEndDrag={props.onScrollEndDrag}
99
+ onMomentumScrollBegin={props.onMomentumScrollBegin}
100
+ onMomentumScrollEnd={props.onMomentumScrollEnd}
101
+ scrollEventThrottle={props.scrollEventThrottle}
102
+ keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
103
+ keyboardDismissMode={props.keyboardDismissMode}
104
+ style={props.style}
105
+ contentContainerStyle={props.contentContainerStyle}
106
+ class={props.class}
107
+ />
@@ -0,0 +1,57 @@
1
+ import type { ISectionListProps, ISectionListHandle, ISection } from './section-list-props';
2
+ export type { ISectionListProps, ISectionListHandle, ISection };
3
+ import type { IScrollViewHandle } from '../virtualized-list/virtualized-list-props';
4
+ import type { ISectionListProps as IProps } from './section-list-props';
5
+ import type { ISymbioteNode } from '@symbiote-native/engine';
6
+ declare function $$render<ItemT>(): {
7
+ props: IProps<ItemT>;
8
+ exports: {
9
+ scrollToLocation: (params: {
10
+ sectionIndex: number;
11
+ itemIndex: number;
12
+ viewOffset?: number;
13
+ viewPosition?: number;
14
+ animated?: boolean;
15
+ }) => void;
16
+ flashScrollIndicators: () => void;
17
+ getNativeScrollRef: () => IScrollViewHandle | null;
18
+ getScrollableNode: () => IScrollViewHandle | null;
19
+ getScrollResponder: () => IScrollViewHandle | null;
20
+ getScrollNode: () => ISymbioteNode | null;
21
+ recordInteraction: () => void;
22
+ };
23
+ bindings: "";
24
+ slots: {};
25
+ events: {};
26
+ };
27
+ declare class __sveltets_Render<ItemT> {
28
+ props(): ReturnType<typeof $$render<ItemT>>['props'];
29
+ events(): ReturnType<typeof $$render<ItemT>>['events'];
30
+ slots(): ReturnType<typeof $$render<ItemT>>['slots'];
31
+ bindings(): "";
32
+ exports(): {
33
+ scrollToLocation: (params: {
34
+ sectionIndex: number;
35
+ itemIndex: number;
36
+ viewOffset?: number;
37
+ viewPosition?: number;
38
+ animated?: boolean;
39
+ }) => void;
40
+ flashScrollIndicators: () => void;
41
+ getNativeScrollRef: () => IScrollViewHandle | null;
42
+ getScrollableNode: () => IScrollViewHandle | null;
43
+ getScrollResponder: () => IScrollViewHandle | null;
44
+ getScrollNode: () => ISymbioteNode | null;
45
+ recordInteraction: () => void;
46
+ };
47
+ }
48
+ interface $$IsomorphicComponent {
49
+ new <ItemT>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<ItemT>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<ItemT>['props']>, ReturnType<__sveltets_Render<ItemT>['events']>, ReturnType<__sveltets_Render<ItemT>['slots']>> & {
50
+ $$bindings?: ReturnType<__sveltets_Render<ItemT>['bindings']>;
51
+ } & ReturnType<__sveltets_Render<ItemT>['exports']>;
52
+ <ItemT>(internal: unknown, props: ReturnType<__sveltets_Render<ItemT>['props']> & {}): ReturnType<__sveltets_Render<ItemT>['exports']>;
53
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
54
+ }
55
+ declare const Index: $$IsomorphicComponent;
56
+ type Index<ItemT> = InstanceType<typeof Index<ItemT>>;
57
+ export default Index;
@@ -0,0 +1 @@
1
+ export type { IVirtualizedSectionListProps as ISectionListProps, IVirtualizedSectionListHandle as ISectionListHandle, ISection, } from '../virtualized-section-list/virtualized-section-list-props';
@@ -0,0 +1,123 @@
1
+ <script lang="ts" module>
2
+ // Switch: the first STATE-machine component in the Svelte adapter (View/Text are
3
+ // render-only). Reuses the shared logic verbatim (switchReducer / valueFromChange /
4
+ // shouldSnapBack, exactly like React's useReducer + Vue's ref-based reducer wiring) and
5
+ // calls renderSwitch() itself for the prop assembly — its Descriptor is always exactly one
6
+ // `symbiote-switch` node with zero children (core/components/src/view/render-switch.ts). Root
7
+ // stays a literal tag (bind:this needs a known tag); `createDescriptorChildrenSync` is still
8
+ // wired for its (always-empty) children, matching every other category-1 component uniformly
9
+ // — the same reason React still routes a childless Switch through descriptorToReact rather
10
+ // than special-casing it. See svelte-adapter-dom-shim skill §19.
11
+ //
12
+ // `value` is `$bindable()` (2026-08-15): `<Switch bind:value={x}>` round-trips a native toggle
13
+ // into `x` with no extra plumbing at the call site. See handleChange's own comment for exactly
14
+ // when the echo fires and why it is gated on `onValueChange` being absent — the full reasoning
15
+ // (and why an ungated echo would silently defeat the existing snap-back correction) lives next
16
+ // to TextInput's identical mechanism in text-input/index.svelte, plus the skill's §7 note.
17
+ import type { ISwitchProps } from './switch-props';
18
+
19
+ export type { ISwitchProps };
20
+ </script>
21
+
22
+ <script lang="ts">
23
+ import {
24
+ switchReducer,
25
+ createInitialSwitchState,
26
+ shouldSnapBack,
27
+ valueFromChange,
28
+ renderSwitch,
29
+ } from '@symbiote-native/components';
30
+ import { dispatchViewCommand, dlog, type ISymbioteEvent } from '@symbiote-native/engine';
31
+ import { PLATFORM } from './switch-platform';
32
+ import { createDescriptorChildrenSync } from '../../descriptor-to-svelte';
33
+ import { createAttachmentsSync } from '../../runes/attachments';
34
+ import type { ShimElement } from '../../dom-shim';
35
+
36
+ let {
37
+ value = $bindable(),
38
+ onValueChange,
39
+ disabled,
40
+ trackColor,
41
+ thumbColor,
42
+ ios_backgroundColor,
43
+ style,
44
+ class: className,
45
+ ...passthrough
46
+ }: ISwitchProps = $props();
47
+
48
+ // $state.raw, NOT $state: this holds the shim element by IDENTITY (the same
49
+ // shallowRef-not-ref concern Vue's switch/shared.ts documents). $state() would deep-proxy
50
+ // the object, and every imperative command (dispatchViewCommand) reads `.engineNode` off
51
+ // the RAW ShimElement the engine's WeakMap-keyed mirror actually knows about — a Proxy
52
+ // wrapper would miss on every lookup and silently no-op.
53
+ let hostShim = $state.raw<ShimElement | null>(null);
54
+ let switchState = $state(createInitialSwitchState());
55
+
56
+ const fabricValue = $derived(value === true);
57
+
58
+ function handleChange(event: ISymbioteEvent): void {
59
+ const next = valueFromChange(event);
60
+ dlog(
61
+ `Switch onChange value=${String(next)} eventCount=${String(event.nativeEvent.eventCount)}`,
62
+ );
63
+ if (next === undefined) return;
64
+ onValueChange?.(next, event);
65
+ // $bindable() sugar: with no onValueChange nothing could reject this report, so mirror it
66
+ // straight into the bound value. A caller that ALSO supplies onValueChange keeps full
67
+ // accept/reject control via the snap-back effect below — see TextInput's identical
68
+ // mechanism (text-input/index.svelte) for why echoing unconditionally would break that.
69
+ if (onValueChange === undefined) value = next;
70
+ switchState = switchReducer(switchState, { type: 'native-reported', value: next });
71
+ }
72
+
73
+ // Snap-back: when native reported a value the parent rejected (the value prop did not
74
+ // change), command the JS value back down — the controlled-Switch correction RN does via
75
+ // SwitchCommands.setValue/setNativeValue. Mirrors React's useLayoutEffect / Vue's post-flush
76
+ // watch; Svelte's $effect reruns whenever switchState or fabricValue changes, which is the
77
+ // same trigger set. Verified against a real compiled mount
78
+ // (mount-pipeline.smoke.test.ts + switch.smoke.test.ts): the shim's insertOne() calls
79
+ // makeLive() synchronously as part of the SAME appendChild the compiler emits before
80
+ // bind:this fires, so hostShim.engineNode is always populated by the time this first runs.
81
+ $effect(() => {
82
+ const engineNode = hostShim?.engineNode;
83
+ if (engineNode === undefined) return;
84
+ if (!shouldSnapBack(switchState, fabricValue)) {
85
+ dlog(
86
+ `Switch snap-back no-op reported=${String(switchState.lastNativeReport)} value=${fabricValue}`,
87
+ );
88
+ return;
89
+ }
90
+ dlog(
91
+ `Switch ${PLATFORM.snapBackCommand} snap-back reported=${String(switchState.lastNativeReport)} value=${fabricValue}`,
92
+ );
93
+ dispatchViewCommand(engineNode, PLATFORM.snapBackCommand, [fabricValue]);
94
+ });
95
+
96
+ const descriptor = $derived(
97
+ renderSwitch(
98
+ {
99
+ value: fabricValue,
100
+ disabled,
101
+ trackColor,
102
+ thumbColor,
103
+ ios_backgroundColor,
104
+ style,
105
+ passthrough: { ...passthrough, class: className, onChange: handleChange },
106
+ },
107
+ PLATFORM,
108
+ ),
109
+ );
110
+
111
+ const syncChildren = createDescriptorChildrenSync();
112
+ $effect(() => {
113
+ syncChildren(hostShim, descriptor.children);
114
+ });
115
+
116
+ // See View.svelte's note on `{@attach}`.
117
+ const syncAttachments = createAttachmentsSync();
118
+ $effect(() => {
119
+ syncAttachments(hostShim, passthrough);
120
+ });
121
+ </script>
122
+
123
+ <symbiote-switch p={descriptor.props} bind:this={hostShim} />
@@ -0,0 +1,5 @@
1
+ import type { ISwitchProps } from './switch-props';
2
+ export type { ISwitchProps };
3
+ declare const Index: import("svelte").Component<ISwitchProps, {}, "value">;
4
+ type Index = ReturnType<typeof Index>;
5
+ export default Index;
@@ -0,0 +1,5 @@
1
+ import type { ISwitchTrackColor } from '@symbiote-native/components';
2
+ export type ISwitchHostPlatform = {
3
+ snapBackCommand: string;
4
+ trackColorProps: (value: boolean, trackColor?: ISwitchTrackColor) => Record<string, unknown>;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { ISwitchHostPlatform } from './switch-platform-types';
2
+ export type { ISwitchHostPlatform };
3
+ export declare const PLATFORM: ISwitchHostPlatform;
@@ -0,0 +1,8 @@
1
+ export const PLATFORM = {
2
+ snapBackCommand: 'setNativeValue',
3
+ trackColorProps: (value, trackColor) => ({
4
+ trackColorForFalse: trackColor?.false,
5
+ trackColorForTrue: trackColor?.true,
6
+ trackTintColor: value ? trackColor?.true : trackColor?.false,
7
+ }),
8
+ };
@@ -0,0 +1 @@
1
+ export * from './switch-platform.ios';
@@ -0,0 +1,3 @@
1
+ import type { ISwitchHostPlatform } from './switch-platform-types';
2
+ export type { ISwitchHostPlatform };
3
+ export declare const PLATFORM: ISwitchHostPlatform;
@@ -0,0 +1,7 @@
1
+ export const PLATFORM = {
2
+ snapBackCommand: 'setValue',
3
+ trackColorProps: (_value, trackColor) => ({
4
+ onTintColor: trackColor?.true,
5
+ tintColor: trackColor?.false,
6
+ }),
7
+ };
@@ -0,0 +1,5 @@
1
+ // Base / default: re-exports the iOS build. Metro overrides this with switch-platform.ios.ts /
2
+ // switch-platform.android.ts on a real host; under tsc/vitest the host config resolves here.
3
+ // Filename is the selector, no Platform.OS read — same convention as every other adapter's
4
+ // switch platform split.
5
+ export * from './switch-platform.ios.js';
@@ -0,0 +1,5 @@
1
+ import type { ISwitchProps as ISwitchBaseProps } from '@symbiote-native/components';
2
+ import type { ISvelteClassValue } from '../../class-value';
3
+ export type ISwitchProps = ISwitchBaseProps & {
4
+ class?: ISvelteClassValue;
5
+ };
@@ -0,0 +1 @@
1
+ export {};