@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,204 @@
1
+ // §7 of svelte-adapter-dom-shim (DECIDED 2026-08-11): the constructs that compile cleanly and
2
+ // then silently do nothing under the DOM shim. Everything else in the original §4 table is
3
+ // already closed by "app code can't reach it" or "the TS/Svelte compiler already errors on it for
4
+ // free" (see the skill for the full reachability table). This preprocessor is the guard for what
5
+ // is left.
6
+ //
7
+ // Three families, one mechanism:
8
+ // <svelte:head|window|document|body> — compiler-level special elements with no RN meaning.
9
+ // {@html expr} — compiles to $.html(), which either assigns to an
10
+ // `innerHTML` that ShimElement does not define (a silent no-op producing zero nodes) or
11
+ // reaches create_element('template'), a tag with no descriptorFor entry. There is no
12
+ // meaningful HTML to render into a native tree, so it fails LOUDLY here instead.
13
+ // browser-only IMPORTS from svelte's own subpackages — the same failure one level up. These
14
+ // read browser globals React Native does not define, so every value they hand back is
15
+ // permanently undefined/false with no error anywhere. Measured per subpackage (see §25):
16
+ // `svelte/reactivity/window` and `MediaQuery` are the ONLY browser-dependent members —
17
+ // `svelte/store`, `svelte/easing`, `svelte/motion`, `svelte/events`, `svelte/attachments`
18
+ // and the rest of `svelte/reactivity` (SvelteMap/Set/Date/URL, createSubscriber) are pure
19
+ // and must keep working, so this is a two-name list, not a subpackage ban.
20
+ //
21
+ // Registered in svelte.config.js's `preprocess`, NOT a standalone lint rule: `svelte-check`
22
+ // and the Svelte VS Code/language-server extension run the SAME preprocessor pipeline, so this
23
+ // one mechanism produces both an editor-time diagnostic and a build failure — no second
24
+ // mechanism needed.
25
+ //
26
+ // Verified against the installed svelte@5.56.8: `parse(content, {modern: true})` on throwaway
27
+ // markup confirmed `Root.fragment.nodes[].type` === 'SvelteHead' / 'SvelteWindow' /
28
+ // 'SvelteDocument' / 'SvelteBody' / 'HtmlTag', and running this preprocessor's `markup()`
29
+ // against forbidden vs. clean markup threw/passed as expected.
30
+ import { parse } from 'svelte/compiler';
31
+ const SPECIAL_ELEMENT_ADVICE = 'has no meaning under React Native and is silently inert under the Symbiote DOM shim ' +
32
+ '(svelte-adapter-dom-shim skill §4/§7). Use the matching runtime module instead ' +
33
+ '(Dimensions / AppState / StatusBar / …).';
34
+ const FORBIDDEN = new Map([
35
+ ['SvelteHead', { label: '<svelte:head>', advice: SPECIAL_ELEMENT_ADVICE }],
36
+ ['SvelteWindow', { label: '<svelte:window>', advice: SPECIAL_ELEMENT_ADVICE }],
37
+ ['SvelteDocument', { label: '<svelte:document>', advice: SPECIAL_ELEMENT_ADVICE }],
38
+ ['SvelteBody', { label: '<svelte:body>', advice: SPECIAL_ELEMENT_ADVICE }],
39
+ [
40
+ 'HtmlTag',
41
+ {
42
+ label: '{@html …}',
43
+ advice: 'renders an HTML string, and a native view tree has no HTML to render it into — it ' +
44
+ 'compiles and then paints nothing (svelte-adapter-dom-shim skill §4/§7). Build the ' +
45
+ 'content out of <View>/<Text> from @symbiote-native/svelte instead; for rich text from ' +
46
+ 'a remote source, parse it yourself and map each node onto those primitives.',
47
+ },
48
+ ],
49
+ ]);
50
+ // The real AST shape isn't pinned here (see the VERIFY note above), so this reads it through
51
+ // runtime guards on `unknown` rather than trusting an unverified type — narrower and safer
52
+ // than casting past whatever `parse()`'s declared return type turns out to be.
53
+ function isRecord(value) {
54
+ return typeof value === 'object' && value !== null;
55
+ }
56
+ function nodeType(node) {
57
+ if (!isRecord(node))
58
+ return undefined;
59
+ const type = node.type;
60
+ return typeof type === 'string' ? type : undefined;
61
+ }
62
+ function childNodes(node) {
63
+ if (!isRecord(node))
64
+ return [];
65
+ const fragment = node.fragment;
66
+ if (!isRecord(fragment))
67
+ return [];
68
+ const nodes = fragment.nodes;
69
+ return Array.isArray(nodes) ? nodes : [];
70
+ }
71
+ // `{@html}` can sit anywhere a node can — inside an {#if}/{#each} branch, a snippet body, an
72
+ // element's children — and those hang off different keys than `fragment.nodes`, so the walk
73
+ // descends through every child value rather than the one fragment field the special elements
74
+ // happen to live under.
75
+ function nestedNodes(node) {
76
+ if (!isRecord(node))
77
+ return [];
78
+ const nested = [];
79
+ for (const value of Object.values(node)) {
80
+ if (Array.isArray(value))
81
+ nested.push(...value);
82
+ else if (isRecord(value))
83
+ nested.push(value);
84
+ }
85
+ return nested;
86
+ }
87
+ function walk(nodes, filename, seen) {
88
+ for (const node of nodes) {
89
+ if (!isRecord(node) || seen.has(node))
90
+ continue;
91
+ seen.add(node);
92
+ const type = nodeType(node);
93
+ const forbidden = type === undefined ? undefined : FORBIDDEN.get(type);
94
+ if (forbidden !== undefined) {
95
+ throw new Error(`${filename}: ${forbidden.label} ${forbidden.advice}`);
96
+ }
97
+ walk([...childNodes(node), ...nestedNodes(node)], filename, seen);
98
+ }
99
+ }
100
+ // The two browser-dependent members of svelte's own runtime, and what to reach for instead.
101
+ // Keyed by module specifier; `named` restricts the ban to specific exports of a module whose
102
+ // other exports are fine (all of `svelte/reactivity` except MediaQuery is pure).
103
+ const FORBIDDEN_IMPORT = new Map([
104
+ [
105
+ 'svelte/reactivity/window',
106
+ {
107
+ advice: 'reads browser globals (window.innerWidth / scrollY / navigator.onLine …) that React ' +
108
+ 'Native never defines, so every value it hands back stays undefined with no error. ' +
109
+ 'Import innerWidth / innerHeight / outerWidth / outerHeight / devicePixelRatio from ' +
110
+ '@symbiote-native/svelte instead (svelte-adapter-dom-shim skill §25). There is no ' +
111
+ 'equivalent for scrollX/scrollY/screenLeft/screenTop — a native app has no window-level ' +
112
+ 'scroll offset and no position in a window manager; read scroll from a ScrollView.',
113
+ },
114
+ ],
115
+ [
116
+ 'svelte/reactivity',
117
+ {
118
+ named: new Set(['MediaQuery']),
119
+ advice: 'is built on window.matchMedia, which React Native does not have, so every query ' +
120
+ 'answers false — indistinguishable from a legitimate no. Use orientation or ' +
121
+ 'createWidthQuery from @symbiote-native/svelte, or useColorScheme for ' +
122
+ 'prefers-color-scheme (svelte-adapter-dom-shim skill §25). The rest of this module ' +
123
+ '(SvelteMap / SvelteSet / SvelteDate / SvelteURL / createSubscriber) is pure and fine.',
124
+ },
125
+ ],
126
+ ]);
127
+ // The parsed script halves, read through runtime guards on `unknown` rather than a cast — the
128
+ // same discipline the fragment walk above uses on the same parse() output.
129
+ function scriptBodies(ast) {
130
+ if (!isRecord(ast))
131
+ return [];
132
+ return ['instance', 'module'].flatMap(half => {
133
+ const script = ast[half];
134
+ if (!isRecord(script))
135
+ return [];
136
+ const scriptContent = script.content;
137
+ if (!isRecord(scriptContent))
138
+ return [];
139
+ return Array.isArray(scriptContent.body) ? scriptContent.body : [];
140
+ });
141
+ }
142
+ function importSpecifiers(node) {
143
+ const specifiers = node.specifiers;
144
+ if (!Array.isArray(specifiers))
145
+ return [];
146
+ return specifiers.filter(isRecord);
147
+ }
148
+ // Only an ImportSpecifier carries `imported`; a namespace or default specifier has no member
149
+ // name to read, which is why they are handled separately below.
150
+ function importedNames(node) {
151
+ return importSpecifiers(node).flatMap(specifier => {
152
+ const imported = specifier.imported;
153
+ if (!isRecord(imported))
154
+ return [];
155
+ return typeof imported.name === 'string' ? [imported.name] : [];
156
+ });
157
+ }
158
+ // A default specifier is deliberately NOT treated as a dodge: none of these svelte subpackages
159
+ // has a default export, and a default binding cannot reach a named member anyway.
160
+ function hasNamespaceSpecifier(node) {
161
+ return importSpecifiers(node).some(specifier => specifier.type === 'ImportNamespaceSpecifier');
162
+ }
163
+ function checkImports(ast, filename) {
164
+ for (const node of scriptBodies(ast)) {
165
+ if (!isRecord(node) || node.type !== 'ImportDeclaration')
166
+ continue;
167
+ const source = node.source;
168
+ if (!isRecord(source) || typeof source.value !== 'string')
169
+ continue;
170
+ const rule = FORBIDDEN_IMPORT.get(source.value);
171
+ if (rule === undefined)
172
+ continue;
173
+ if (rule.named === undefined) {
174
+ throw new Error(`${filename}: importing from '${source.value}' ${rule.advice}`);
175
+ }
176
+ // `import * as R from 'svelte/reactivity'` hands the whole module object over under one
177
+ // local name, so whether app code reaches R.MediaQuery is decided at runtime and this check
178
+ // can never see it. A named ban is only enforceable on named imports, so the namespace shape
179
+ // is refused outright instead of being waved through.
180
+ if (hasNamespaceSpecifier(node)) {
181
+ const banned = [...rule.named].map(name => `'${name}'`).join(' / ');
182
+ throw new Error(`${filename}: '${source.value}' is imported as a namespace, which cannot be checked — ` +
183
+ `import the members you need by name so ${banned} is ruled out. ${banned} ` +
184
+ rule.advice);
185
+ }
186
+ for (const name of importedNames(node)) {
187
+ if (rule.named.has(name)) {
188
+ throw new Error(`${filename}: '${name}' from '${source.value}' ${rule.advice}`);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ export function forbidWebOnlyConstructs() {
194
+ return {
195
+ markup({ content, filename }) {
196
+ const ast = parse(content, { filename, modern: true });
197
+ const where = filename ?? '<svelte>';
198
+ walk(childNodes(ast), where, new Set());
199
+ // One parse serves both checks: the fragment walk above, and the script imports here.
200
+ checkImports(ast, where);
201
+ return { code: content };
202
+ },
203
+ };
204
+ }
@@ -0,0 +1,10 @@
1
+ interface IMarkupInput {
2
+ readonly content: string;
3
+ readonly filename?: string;
4
+ }
5
+ export declare function scopedStyles(): {
6
+ markup(input: IMarkupInput): Promise<{
7
+ code: string;
8
+ }>;
9
+ };
10
+ export {};
@@ -0,0 +1,399 @@
1
+ // A `.svelte` file's `<style>` block, compiled to real native styles.
2
+ //
3
+ // WHY SVELTE'S OWN CSS OUTPUT CANNOT BE CONSUMED — measure this before "simplifying" any of the
4
+ // below back to "just wire up `result.css`". Compiled with this adapter's options
5
+ // (`generate:'client', fragments:'tree', css:'external'`), a `<style>` block behaves one of two
6
+ // ways:
7
+ //
8
+ // <symbiote-view class="card"> -> result.css: `.card.svelte-4psua6 { … }`
9
+ // result.js: `$.set_class(node, 1, 'card svelte-4psua6')`
10
+ // <View class="card"> -> warning: css_unused_selector
11
+ // result.css: `/* (unused) .card { … }*/`
12
+ //
13
+ // The first is dead: `set_class` writes `dom.className`, which `ShimElement` does not implement,
14
+ // and app code never authors a host tag anyway (§22a). The second is 100% of real app code —
15
+ // Svelte refuses to scope a parent's styles into a child COMPONENT, so by the time `result.css`
16
+ // exists every rule is commented out. Nothing is left to register, so the scoping has to happen
17
+ // HERE, before the compiler ever sees the block — the same pass `adapters/vue/metro-vue-
18
+ // transformer.cjs` runs for a Vue SFC (symbiote-sfc-style-compiler §5), moved from a compiler
19
+ // node-transform to a source rewrite because Svelte's compiler exposes no AST hook.
20
+ //
21
+ // Four steps, in order:
22
+ // 1. parse the block out and compile it through @symbiote-native/css-parser, exactly like Vue's
23
+ // <style scoped> — every class registers under a per-file-suffixed key, `card` ->
24
+ // `card__svelte-<hash>`, in the same global registry App.css and Vue SFC blocks populate.
25
+ // 2. rewrite `class` in THIS file's own markup to name the suffixed key. Static values are
26
+ // resolved here, at build time; a dynamic `class={expr}` is wrapped in a runtime call to
27
+ // `scopeSvelteClass` (../style-scope) — the only shape that can scope a value the compiler
28
+ // cannot see into.
29
+ // 3. delete the `<style>` block from the source handed on, so Svelte emits no
30
+ // `css_unused_selector` warnings and adds no scope hash of its own.
31
+ // 4. append one `<script module>` line holding the `registerStyles()` call and the two
32
+ // per-file constants step 2's rewrite refers to.
33
+ //
34
+ // SCOPING SEMANTICS, AND THE ONE DELIBERATE DIVERGENCE FROM SVELTE-ON-THE-WEB. Svelte scopes by
35
+ // FILE: only markup written in this file carries the scope, never markup a child component owns
36
+ // — reproduced exactly, step 2 only ever rewrites this file's own source text. Where this
37
+ // diverges is the component boundary: on the web, `<Child class="card"/>` does NOT apply the
38
+ // parent's `.card`, because the hash never lands on the child's element. Here it does, because
39
+ // the scoped NAME travels as an ordinary prop and every Symbiote component forwards `class` down
40
+ // to its host node. Not incidental: a component here renders only other components, so the web
41
+ // rule would make every `<style>` block a no-op — exactly the bug this file exists to fix. An
42
+ // author's mental model still holds: "my `<style>` styles the markup I wrote".
43
+ //
44
+ // Registered in `svelte.config.js`'s `preprocess` AND called directly by
45
+ // `metro-svelte-transformer.cjs`, for the same reason `forbid-web-only-constructs.ts` is — the
46
+ // config covers `svelte-check`/the language server, the transformer covers a consuming app whose
47
+ // own config never registers it.
48
+ //
49
+ // LINE NUMBERS ARE PRESERVED ON PURPOSE (this preprocessor emits no source map): the `<style>`
50
+ // block is replaced by the same number of newlines it occupied rather than deleted outright, and
51
+ // the injected script is one line appended after all original content, or spliced onto the
52
+ // existing `<script module>` tag's own line — so every original line keeps its number and a
53
+ // `svelte-check` diagnostic still points at the right place.
54
+ import { parse } from 'svelte/compiler';
55
+ import { classTokensIn, compile as compilePreprocessor, globalClassNamesIn, globalClassTokensIn, hashFilePath, parseCSS, } from '@symbiote-native/css-parser';
56
+ // Resolved by package SELF-REFERENCE, not the relative `../scope-token` the rest of this package
57
+ // would write — same reason metro-svelte-transformer.cjs reaches for
58
+ // `@symbiote-native/svelte/preprocessor`. `svelte.config.js` is loaded directly by Node
59
+ // (svelte-check, the language server), which strips types off a `.ts` file but still applies its
60
+ // own ESM resolver to what that file imports, where an extensionless relative specifier is
61
+ // ERR_MODULE_NOT_FOUND. The `exports` map supplies the extension at both targets — `src`
62
+ // in this workspace, `build` in a published install — so one line works in both. `scope-token`
63
+ // specifically, never `style-scope`: that one reaches the engine through `class-value`, a whole
64
+ // extensionless graph this build-time file must not pull in.
65
+ import { scopeToken } from '@symbiote-native/svelte/scope-token';
66
+ const SCOPE_ID_PREFIX = 'svelte-';
67
+ const ENGINE_MODULE = '@symbiote-native/engine';
68
+ const RUNTIME_MODULE = '@symbiote-native/svelte/style-scope';
69
+ // Every injected identifier is `__symbiote`-prefixed and import-aliased so a component that
70
+ // happens to declare its own `registerStyles` / `scopeSvelteClass` cannot collide with it.
71
+ const REGISTER_STYLES_LOCAL = '__symbioteRegisterStyles';
72
+ const SCOPE_CLASS_LOCAL = '__symbioteScopeClass';
73
+ const SCOPED_NAMES_LOCAL = '__symbioteScopedNames';
74
+ const SCOPE_ID_LOCAL = '__symbioteScopeId';
75
+ // A `<style lang="…">` names its language directly, unlike a standalone file identified by its
76
+ // extension — so this is its own lookup rather than css-parser's extension-keyed
77
+ // `detectLanguage()`. Same table Vue's transformer keeps for the identical reason.
78
+ const STYLE_LANG_TO_PREPROCESSOR = new Map([
79
+ ['css', 'css'],
80
+ ['scss', 'scss'],
81
+ ['sass', 'scss'],
82
+ ['less', 'less'],
83
+ ['stylus', 'stylus'],
84
+ ['styl', 'stylus'],
85
+ ]);
86
+ // Sass's indented syntax is selected off a `.sass`-suffixed PATH, and an inline block has no path
87
+ // of its own — so one is synthesized from the component's, exactly as Vue's transformer does.
88
+ const INDENTED_SASS_LANG = 'sass';
89
+ export function scopedStyles() {
90
+ return {
91
+ async markup({ content, filename }) {
92
+ // Cheap gate: the overwhelming majority of components carry no <style> block at all, and
93
+ // this saves them a full parse on every Metro transform.
94
+ if (!content.includes('<style'))
95
+ return { code: content };
96
+ const ast = parse(content, { filename, modern: true });
97
+ const styleBlock = readStyleBlock(ast);
98
+ if (styleBlock === undefined)
99
+ return { code: content };
100
+ const path = filename ?? 'component.svelte';
101
+ const css = await compileStyleBlock(styleBlock, path);
102
+ const parsed = parseCSS(css, { filename: path });
103
+ const exemptFromScope = globalClassNamesIn(css);
104
+ const scopeId = SCOPE_ID_PREFIX + hashFilePath(path);
105
+ // A `:global(...)` selector opts out of scoping and registers under its plain name, the
106
+ // same escape hatch Vue's <style scoped> has. Everything else is suffixed and its ORIGINAL
107
+ // name recorded, so the markup rewrite below knows which tokens this file owns.
108
+ //
109
+ // A COMPOUND or DESCENDANT selector registers under one collapsed key (`.card.big` ->
110
+ // `cardBig`) that appears nowhere in the markup — the markup says `class="card big"`. So
111
+ // the key alone is not enough to know what this file owns: its TOKENS are recorded too, or
112
+ // a `.card.big` rule whose parts have no standalone rule of their own leaves both tokens
113
+ // unscoped and the rule unreachable.
114
+ //
115
+ // A token out of a `:global(...)` payload is the one exception: in `.card :global(.reset)`
116
+ // the collapsed KEY is this file's own, because `.card` is, but `reset` was written
117
+ // precisely to name markup this file does not own. Suffixing it along with the rest of its
118
+ // chain scope-mangles the escape hatch into matching nothing.
119
+ const tokensByName = classTokensIn(css, { filename: path });
120
+ const globalTokens = globalClassTokensIn(css, { filename: path });
121
+ const styles = {};
122
+ const localNames = new Set();
123
+ for (const [className, props] of Object.entries(parsed)) {
124
+ const isExempt = exemptFromScope.has(className);
125
+ const registeredName = isExempt ? className : `${className}__${scopeId}`;
126
+ if (!isExempt) {
127
+ localNames.add(className);
128
+ for (const token of tokensByName.get(className) ?? []) {
129
+ if (!globalTokens.has(token))
130
+ localNames.add(token);
131
+ }
132
+ }
133
+ styles[registeredName] = props;
134
+ }
135
+ const edits = [blankOut(styleBlock.start, styleBlock.end, content)];
136
+ collectClassEdits(fragmentNodes(ast), content, localNames, scopeId, edits);
137
+ if (Object.keys(styles).length > 0) {
138
+ edits.push(injectModuleScript(ast, content, styles, localNames, scopeId));
139
+ }
140
+ return { code: applyEdits(content, edits) };
141
+ },
142
+ };
143
+ }
144
+ // ---------------------------------------------------------------------------------------------
145
+ // AST reading. `parse()`'s return type is not pinned here (same call, same reasoning as
146
+ // forbid-web-only-constructs.ts): it is read through runtime guards on `unknown` rather than
147
+ // trusting a shape nothing verifies.
148
+ function isRecord(value) {
149
+ return typeof value === 'object' && value !== null;
150
+ }
151
+ function numberAt(node, key) {
152
+ const value = node[key];
153
+ return typeof value === 'number' ? value : undefined;
154
+ }
155
+ function stringAt(node, key) {
156
+ const value = node[key];
157
+ return typeof value === 'string' ? value : undefined;
158
+ }
159
+ function readStyleBlock(ast) {
160
+ if (!isRecord(ast))
161
+ return undefined;
162
+ const css = ast.css;
163
+ if (!isRecord(css))
164
+ return undefined;
165
+ const start = numberAt(css, 'start');
166
+ const end = numberAt(css, 'end');
167
+ const content = css.content;
168
+ if (start === undefined || end === undefined || !isRecord(content))
169
+ return undefined;
170
+ const source = stringAt(content, 'styles');
171
+ if (source === undefined)
172
+ return undefined;
173
+ return { start, end, source, lang: readLangAttribute(css) };
174
+ }
175
+ function readLangAttribute(block) {
176
+ const attributes = block.attributes;
177
+ if (!Array.isArray(attributes))
178
+ return 'css';
179
+ for (const attribute of attributes) {
180
+ if (!isRecord(attribute) || attribute.name !== 'lang')
181
+ continue;
182
+ const text = staticTextOf(attribute);
183
+ if (text !== undefined)
184
+ return text;
185
+ }
186
+ return 'css';
187
+ }
188
+ async function compileStyleBlock(block, filename) {
189
+ const language = STYLE_LANG_TO_PREPROCESSOR.get(block.lang);
190
+ if (language === undefined) {
191
+ throw new Error(`${filename}: <style lang="${block.lang}"> is not supported — use css, scss, sass, less or stylus.`);
192
+ }
193
+ const path = block.lang === INDENTED_SASS_LANG ? `${filename}.sass` : filename;
194
+ return compilePreprocessor(block.source, language, path);
195
+ }
196
+ function fragmentNodes(ast) {
197
+ if (!isRecord(ast))
198
+ return [];
199
+ const fragment = ast.fragment;
200
+ if (!isRecord(fragment))
201
+ return [];
202
+ return Array.isArray(fragment.nodes) ? fragment.nodes : [];
203
+ }
204
+ // The markup walk descends through every child value rather than one known key: a `class`
205
+ // attribute can sit inside an {#if}/{#each} branch, a snippet body, or a component's children,
206
+ // and those hang off different fields. Deliberately started from `ast.fragment` alone, never the
207
+ // root — the `css` subtree has its own nodes carrying a `name` and would be walked for nothing.
208
+ function nestedNodes(node) {
209
+ const nested = [];
210
+ for (const value of Object.values(node)) {
211
+ if (Array.isArray(value))
212
+ nested.push(...value);
213
+ else if (isRecord(value))
214
+ nested.push(value);
215
+ }
216
+ return nested;
217
+ }
218
+ function collectClassEdits(nodes, content, localNames, scopeId, edits) {
219
+ for (const node of nodes) {
220
+ if (!isRecord(node))
221
+ continue;
222
+ if (node.type === 'Attribute' && node.name === 'class') {
223
+ const edit = classAttributeEdit(node, content, localNames, scopeId);
224
+ if (edit !== undefined)
225
+ edits.push(edit);
226
+ continue;
227
+ }
228
+ collectClassEdits(nestedNodes(node), content, localNames, scopeId, edits);
229
+ }
230
+ }
231
+ // `value` is `true` for a bare `class` attribute, one node for a lone value, or an array mixing
232
+ // Text and ExpressionTag for an interpolated one (`class="card {extra}"`). Normalizing to an
233
+ // array up front removes the per-shape branching everywhere below.
234
+ function attributeValueParts(attribute) {
235
+ const value = attribute.value;
236
+ if (value === true)
237
+ return undefined;
238
+ if (Array.isArray(value))
239
+ return value;
240
+ return isRecord(value) ? [value] : undefined;
241
+ }
242
+ function staticTextOf(attribute) {
243
+ const parts = attributeValueParts(attribute);
244
+ if (parts === undefined)
245
+ return undefined;
246
+ let text = '';
247
+ for (const part of parts) {
248
+ if (!isRecord(part) || part.type !== 'Text')
249
+ return undefined;
250
+ const data = stringAt(part, 'data');
251
+ if (data === undefined)
252
+ return undefined;
253
+ text += data;
254
+ }
255
+ return text;
256
+ }
257
+ function classAttributeEdit(attribute, content, localNames, scopeId) {
258
+ const start = numberAt(attribute, 'start');
259
+ const end = numberAt(attribute, 'end');
260
+ const parts = attributeValueParts(attribute);
261
+ if (start === undefined || end === undefined || parts === undefined)
262
+ return undefined;
263
+ const staticValue = staticTextOf(attribute);
264
+ if (staticValue !== undefined) {
265
+ const scoped = staticValue
266
+ .split(/\s+/)
267
+ .filter(Boolean)
268
+ .map(token => scopeToken(token, localNames, scopeId))
269
+ .join(' ');
270
+ // Nothing this file owns appears in the value — leave the source byte-identical rather than
271
+ // re-quoting it, so a component with only `:global(...)` rules is untouched.
272
+ if (scoped === staticValue)
273
+ return undefined;
274
+ return { start, end, text: `class="${scoped}"` };
275
+ }
276
+ // A dynamic value can only be scoped at runtime, and only if this file defines something to
277
+ // scope against. With no local names there is nothing for the call to do, so it is not emitted
278
+ // at all — and the two constants it would reference stay out of the bundle.
279
+ if (localNames.size === 0)
280
+ return undefined;
281
+ const expression = expressionSourceOf(parts, content);
282
+ if (expression === undefined)
283
+ return undefined;
284
+ return {
285
+ start,
286
+ end,
287
+ text: `class={${SCOPE_CLASS_LOCAL}(${expression}, ${SCOPED_NAMES_LOCAL}, ${SCOPE_ID_LOCAL})}`,
288
+ };
289
+ }
290
+ // A lone `class={expr}` reproduces `expr` verbatim, so an arbitrary expression — a clsx array, a
291
+ // ternary, a function call — passes through untouched and only its RESULT gets scoped. An
292
+ // interpolated `class="a {b} c"` is rebuilt as the template literal Svelte itself would have
293
+ // concatenated, so both shapes reduce to one expression the runtime helper can take.
294
+ function expressionSourceOf(parts, content) {
295
+ if (parts.length === 1 && isRecord(parts[0]) && parts[0].type === 'ExpressionTag') {
296
+ return sliceExpression(parts[0], content);
297
+ }
298
+ let literal = '';
299
+ for (const part of parts) {
300
+ if (!isRecord(part))
301
+ return undefined;
302
+ if (part.type === 'Text') {
303
+ const data = stringAt(part, 'data');
304
+ if (data === undefined)
305
+ return undefined;
306
+ literal += escapeTemplateLiteral(data);
307
+ continue;
308
+ }
309
+ if (part.type !== 'ExpressionTag')
310
+ return undefined;
311
+ const expression = sliceExpression(part, content);
312
+ if (expression === undefined)
313
+ return undefined;
314
+ literal += `\${${expression}}`;
315
+ }
316
+ return `\`${literal}\``;
317
+ }
318
+ function sliceExpression(part, content) {
319
+ const expression = part.expression;
320
+ if (!isRecord(expression))
321
+ return undefined;
322
+ const start = numberAt(expression, 'start');
323
+ const end = numberAt(expression, 'end');
324
+ if (start === undefined || end === undefined)
325
+ return undefined;
326
+ return content.slice(start, end);
327
+ }
328
+ function escapeTemplateLiteral(text) {
329
+ return text.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
330
+ }
331
+ // ---------------------------------------------------------------------------------------------
332
+ // Emitting
333
+ function injectModuleScript(ast, content, styles, localNames, scopeId) {
334
+ const lines = [
335
+ `import { registerStyles as ${REGISTER_STYLES_LOCAL} } from '${ENGINE_MODULE}';`,
336
+ `${REGISTER_STYLES_LOCAL}(${JSON.stringify(styles)});`,
337
+ ];
338
+ if (localNames.size > 0) {
339
+ lines.unshift(`import { scopeSvelteClass as ${SCOPE_CLASS_LOCAL} } from '${RUNTIME_MODULE}';`);
340
+ lines.push(`const ${SCOPED_NAMES_LOCAL} = new Set(${JSON.stringify([...localNames])});`, `const ${SCOPE_ID_LOCAL} = ${JSON.stringify(scopeId)};`);
341
+ }
342
+ const source = lines.join(' ');
343
+ // Svelte rejects a second `<script module>`, so an existing one is spliced into rather than
344
+ // duplicated — at the very start of its body, which is the end of its own opening-tag line, so
345
+ // no line number and no column of real code moves.
346
+ const moduleBody = moduleScriptBodyStart(ast);
347
+ if (moduleBody !== undefined) {
348
+ return { start: moduleBody, end: moduleBody, text: source };
349
+ }
350
+ // No module script: append one after everything. A trailing `<script module>` compiles and its
351
+ // consts are visible to the markup above it (verified against svelte 5.56.8), and appending
352
+ // leaves every original offset untouched.
353
+ const lang = instanceScriptLang(ast) === 'ts' ? ' lang="ts"' : '';
354
+ return {
355
+ start: content.length,
356
+ end: content.length,
357
+ text: `\n<script module${lang}>${source}</script>\n`,
358
+ };
359
+ }
360
+ function moduleScriptBodyStart(ast) {
361
+ if (!isRecord(ast))
362
+ return undefined;
363
+ const moduleScript = ast.module;
364
+ if (!isRecord(moduleScript) || !isRecord(moduleScript.content))
365
+ return undefined;
366
+ return numberAt(moduleScript.content, 'start');
367
+ }
368
+ function instanceScriptLang(ast) {
369
+ if (!isRecord(ast))
370
+ return undefined;
371
+ const instance = ast.instance;
372
+ if (!isRecord(instance) || !Array.isArray(instance.attributes))
373
+ return undefined;
374
+ for (const attribute of instance.attributes) {
375
+ if (isRecord(attribute) && attribute.name === 'lang')
376
+ return staticTextOf(attribute);
377
+ }
378
+ return undefined;
379
+ }
380
+ // Newline-for-newline, so everything below the removed block keeps its line number.
381
+ function blankOut(start, end, content) {
382
+ const removed = content.slice(start, end);
383
+ return { start, end, text: '\n'.repeat(countNewlines(removed)) };
384
+ }
385
+ function countNewlines(text) {
386
+ let count = 0;
387
+ for (const character of text) {
388
+ if (character === '\n')
389
+ count += 1;
390
+ }
391
+ return count;
392
+ }
393
+ // Applied back-to-front so an earlier edit's offsets stay valid. Edits never overlap: each one
394
+ // covers a distinct attribute, the style block, or a zero-width insertion point.
395
+ function applyEdits(content, edits) {
396
+ return [...edits]
397
+ .sort((left, right) => right.start - left.start)
398
+ .reduce((source, edit) => source.slice(0, edit.start) + edit.text + source.slice(edit.end), content);
399
+ }
@@ -0,0 +1,7 @@
1
+ import { type Component } from 'svelte';
2
+ import { type IRootTag, type SymbioteSurface } from '@symbiote-native/engine';
3
+ export declare function mount(rootTag: IRootTag, RootComponent: Component, props?: object): SymbioteSurface;
4
+ export declare function unmount(rootTag: IRootTag): void;
5
+ declare global {
6
+ var RN$stopSurface: ((surfaceId: number) => void) | undefined;
7
+ }
@@ -0,0 +1,46 @@
1
+ // Mount a Svelte app onto a Fabric surface. The native host hands us a rootTag; we create a
2
+ // surface for it, install the DOM shim, and let stock compiled Svelte output drive it while
3
+ // believing it is talking to the real DOM. Decided during Svelte adapter planning
4
+ // (2026-08-11, svelte-adapter-dom-shim skill §10): single root per process, so
5
+ // patchGlobals()/restoreGlobals() need no ref-counting.
6
+ import { mount as svelteMount, unmount as svelteUnmount } from 'svelte';
7
+ import { createSurface, disposeRoot, dlog, } from '@symbiote-native/engine';
8
+ import { patchGlobals, restoreGlobals } from './dom-shim/index.js';
9
+ import { createRootShimElement } from './root-element.js';
10
+ // One Svelte app per surface — same re-mount-starts-clean shape as every other adapter's
11
+ // render.ts (Fast Refresh / focus-lifecycle restarts a surface and reuses the rootTag).
12
+ const apps = new Map();
13
+ function teardown(rootTag) {
14
+ const entry = apps.get(rootTag);
15
+ if (entry === undefined)
16
+ return;
17
+ svelteUnmount(entry.svelteApp);
18
+ apps.delete(rootTag);
19
+ disposeRoot(rootTag);
20
+ }
21
+ export function mount(rootTag, RootComponent, props) {
22
+ teardown(rootTag);
23
+ // Safe to install unconditionally - patchGlobals() is itself idempotent if a surface is
24
+ // already live.
25
+ patchGlobals();
26
+ const surface = createSurface(rootTag);
27
+ const target = createRootShimElement(surface);
28
+ const svelteApp = svelteMount(RootComponent, { target, props: props ?? {} });
29
+ apps.set(rootTag, { svelteApp });
30
+ return surface;
31
+ }
32
+ // Tear down a surface by rootTag: the public pair of `mount`, and the JS half of the
33
+ // bridgeless `RN$stopSurface` contract (see installStopSurfaceGlobal below).
34
+ export function unmount(rootTag) {
35
+ dlog(`svelte unmount root=${rootTag}`);
36
+ teardown(rootTag);
37
+ // No other surface can still need the shim (single root per process, see module header).
38
+ restoreGlobals();
39
+ }
40
+ function installStopSurfaceGlobal() {
41
+ globalThis.RN$stopSurface = (surfaceId) => {
42
+ unmount(surfaceId);
43
+ };
44
+ dlog('installed global.RN$stopSurface');
45
+ }
46
+ installStopSurfaceGlobal();