@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.
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/build/bootstrap.d.ts +7 -0
- package/build/bootstrap.js +17 -0
- package/build/class-value.d.ts +6 -0
- package/build/class-value.js +81 -0
- package/build/components/RefreshControl.svelte +38 -0
- package/build/components/RefreshControl.svelte.d.ts +5 -0
- package/build/components/SafeAreaView.svelte +34 -0
- package/build/components/SafeAreaView.svelte.d.ts +5 -0
- package/build/components/Text.svelte +25 -0
- package/build/components/Text.svelte.d.ts +5 -0
- package/build/components/View.svelte +41 -0
- package/build/components/View.svelte.d.ts +5 -0
- package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +3 -0
- package/build/components/activity-indicator/activity-indicator-platform.android.js +5 -0
- package/build/components/activity-indicator/activity-indicator-platform.d.ts +1 -0
- package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +3 -0
- package/build/components/activity-indicator/activity-indicator-platform.ios.js +6 -0
- package/build/components/activity-indicator/activity-indicator-platform.js +5 -0
- package/build/components/activity-indicator/activity-indicator-props.d.ts +5 -0
- package/build/components/activity-indicator/activity-indicator-props.js +1 -0
- package/build/components/activity-indicator/index.svelte +57 -0
- package/build/components/activity-indicator/index.svelte.d.ts +5 -0
- package/build/components/button-props.d.ts +5 -0
- package/build/components/button-props.js +1 -0
- package/build/components/button.svelte +71 -0
- package/build/components/button.svelte.d.ts +5 -0
- package/build/components/flat-list/flat-list-props.d.ts +10 -0
- package/build/components/flat-list/flat-list-props.js +1 -0
- package/build/components/flat-list/index.svelte +254 -0
- package/build/components/flat-list/index.svelte.d.ts +79 -0
- package/build/components/image/image-logic.d.ts +2 -0
- package/build/components/image/image-logic.js +125 -0
- package/build/components/image/image-props.d.ts +5 -0
- package/build/components/image/image-props.js +1 -0
- package/build/components/image/index.d.ts +3 -0
- package/build/components/image/index.js +10 -0
- package/build/components/image/index.svelte +66 -0
- package/build/components/image/index.svelte.d.ts +5 -0
- package/build/components/image-background/image-background-props.d.ts +10 -0
- package/build/components/image-background/image-background-props.js +1 -0
- package/build/components/image-background/index.svelte +81 -0
- package/build/components/image-background/index.svelte.d.ts +5 -0
- package/build/components/index.d.ts +50 -0
- package/build/components/index.js +24 -0
- package/build/components/input-accessory-view/index.svelte +48 -0
- package/build/components/input-accessory-view/index.svelte.d.ts +5 -0
- package/build/components/input-accessory-view/input-accessory-view-props.d.ts +11 -0
- package/build/components/input-accessory-view/input-accessory-view-props.js +1 -0
- package/build/components/keyboard-avoiding-view/index.svelte +135 -0
- package/build/components/keyboard-avoiding-view/index.svelte.d.ts +5 -0
- package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.d.ts +15 -0
- package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.js +1 -0
- package/build/components/modal/index.svelte +126 -0
- package/build/components/modal/index.svelte.d.ts +5 -0
- package/build/components/modal/modal-props.d.ts +24 -0
- package/build/components/modal/modal-props.js +1 -0
- package/build/components/pressable/index.svelte +160 -0
- package/build/components/pressable/index.svelte.d.ts +5 -0
- package/build/components/pressable/pressable-props.d.ts +27 -0
- package/build/components/pressable/pressable-props.js +1 -0
- package/build/components/refresh-control-props.d.ts +19 -0
- package/build/components/refresh-control-props.js +1 -0
- package/build/components/safe-area-view-props.d.ts +10 -0
- package/build/components/safe-area-view-props.js +1 -0
- package/build/components/scroll-view/index.svelte +298 -0
- package/build/components/scroll-view/index.svelte.d.ts +18 -0
- package/build/components/scroll-view/scroll-view-platform-types.d.ts +3 -0
- package/build/components/scroll-view/scroll-view-platform-types.js +1 -0
- package/build/components/scroll-view/scroll-view-platform.android.d.ts +3 -0
- package/build/components/scroll-view/scroll-view-platform.android.js +3 -0
- package/build/components/scroll-view/scroll-view-platform.d.ts +1 -0
- package/build/components/scroll-view/scroll-view-platform.ios.d.ts +3 -0
- package/build/components/scroll-view/scroll-view-platform.ios.js +3 -0
- package/build/components/scroll-view/scroll-view-platform.js +4 -0
- package/build/components/scroll-view/scroll-view-props.d.ts +78 -0
- package/build/components/scroll-view/scroll-view-props.js +1 -0
- package/build/components/scroll-view/scroll-view-sticky-context.d.ts +7 -0
- package/build/components/scroll-view/scroll-view-sticky-context.js +1 -0
- package/build/components/scroll-view/sticky-header-props.d.ts +10 -0
- package/build/components/scroll-view/sticky-header-props.js +1 -0
- package/build/components/scroll-view/sticky-header.svelte +254 -0
- package/build/components/scroll-view/sticky-header.svelte.d.ts +5 -0
- package/build/components/section-list/index.svelte +107 -0
- package/build/components/section-list/index.svelte.d.ts +57 -0
- package/build/components/section-list/section-list-props.d.ts +1 -0
- package/build/components/section-list/section-list-props.js +1 -0
- package/build/components/switch/index.svelte +123 -0
- package/build/components/switch/index.svelte.d.ts +5 -0
- package/build/components/switch/switch-platform-types.d.ts +5 -0
- package/build/components/switch/switch-platform-types.js +1 -0
- package/build/components/switch/switch-platform.android.d.ts +3 -0
- package/build/components/switch/switch-platform.android.js +8 -0
- package/build/components/switch/switch-platform.d.ts +1 -0
- package/build/components/switch/switch-platform.ios.d.ts +3 -0
- package/build/components/switch/switch-platform.ios.js +7 -0
- package/build/components/switch/switch-platform.js +5 -0
- package/build/components/switch/switch-props.d.ts +5 -0
- package/build/components/switch/switch-props.js +1 -0
- package/build/components/text-input/index.svelte +300 -0
- package/build/components/text-input/index.svelte.d.ts +12 -0
- package/build/components/text-input/text-input-props.d.ts +5 -0
- package/build/components/text-input/text-input-props.js +1 -0
- package/build/components/text-props.d.ts +23 -0
- package/build/components/text-props.js +1 -0
- package/build/components/touchable-base-props.d.ts +10 -0
- package/build/components/touchable-base-props.js +1 -0
- package/build/components/touchable-highlight/index.svelte +39 -0
- package/build/components/touchable-highlight/index.svelte.d.ts +5 -0
- package/build/components/touchable-highlight/touchable-highlight-props.d.ts +5 -0
- package/build/components/touchable-highlight/touchable-highlight-props.js +1 -0
- package/build/components/touchable-native-feedback/index.d.ts +9 -0
- package/build/components/touchable-native-feedback/index.js +14 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +44 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +5 -0
- package/build/components/touchable-opacity/index.svelte +124 -0
- package/build/components/touchable-opacity/index.svelte.d.ts +5 -0
- package/build/components/touchable-opacity/touchable-opacity-props.d.ts +5 -0
- package/build/components/touchable-opacity/touchable-opacity-props.js +1 -0
- package/build/components/touchable-without-feedback/index.svelte +19 -0
- package/build/components/touchable-without-feedback/index.svelte.d.ts +5 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +2 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
- package/build/components/view-props.d.ts +29 -0
- package/build/components/view-props.js +1 -0
- package/build/components/virtualized-list/index.svelte +702 -0
- package/build/components/virtualized-list/index.svelte.d.ts +79 -0
- package/build/components/virtualized-list/virtualized-list-props.d.ts +70 -0
- package/build/components/virtualized-list/virtualized-list-props.js +73 -0
- package/build/components/virtualized-section-list/index.svelte +206 -0
- package/build/components/virtualized-section-list/index.svelte.d.ts +57 -0
- package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +62 -0
- package/build/components/virtualized-section-list/virtualized-section-list-props.js +1 -0
- package/build/create-tunnel/index.d.ts +3 -0
- package/build/create-tunnel/index.js +3 -0
- package/build/create-tunnel/tunnel-in.svelte +24 -0
- package/build/create-tunnel/tunnel-in.svelte.d.ts +9 -0
- package/build/create-tunnel/tunnel-out.svelte +14 -0
- package/build/create-tunnel/tunnel-out.svelte.d.ts +7 -0
- package/build/create-tunnel/tunnel.d.ts +9 -0
- package/build/create-tunnel/tunnel.js +9 -0
- package/build/descriptor-to-svelte.d.ts +7 -0
- package/build/descriptor-to-svelte.js +104 -0
- package/build/dom-shim/comment.d.ts +9 -0
- package/build/dom-shim/comment.js +24 -0
- package/build/dom-shim/document-fragment.d.ts +8 -0
- package/build/dom-shim/document-fragment.js +23 -0
- package/build/dom-shim/document.d.ts +23 -0
- package/build/dom-shim/document.js +61 -0
- package/build/dom-shim/element.d.ts +22 -0
- package/build/dom-shim/element.js +130 -0
- package/build/dom-shim/index.d.ts +5 -0
- package/build/dom-shim/index.js +7 -0
- package/build/dom-shim/patch-globals.d.ts +2 -0
- package/build/dom-shim/patch-globals.js +94 -0
- package/build/dom-shim/shim-node.d.ts +30 -0
- package/build/dom-shim/shim-node.js +207 -0
- package/build/dom-shim/text.d.ts +14 -0
- package/build/dom-shim/text.js +97 -0
- package/build/host-instance.d.ts +5 -0
- package/build/host-instance.js +48 -0
- package/build/index.d.ts +28 -0
- package/build/index.js +77 -0
- package/build/modules/accessibility-info/index.android.d.ts +2 -0
- package/build/modules/accessibility-info/index.android.js +4 -0
- package/build/modules/accessibility-info/index.d.ts +1 -0
- package/build/modules/accessibility-info/index.ios.d.ts +2 -0
- package/build/modules/accessibility-info/index.ios.js +3 -0
- package/build/modules/accessibility-info/index.js +5 -0
- package/build/modules/animated/AnimatedImage.svelte +115 -0
- package/build/modules/animated/AnimatedImage.svelte.d.ts +5 -0
- package/build/modules/animated/AnimatedScrollView.svelte +92 -0
- package/build/modules/animated/AnimatedScrollView.svelte.d.ts +17 -0
- package/build/modules/animated/AnimatedText.svelte +58 -0
- package/build/modules/animated/AnimatedText.svelte.d.ts +5 -0
- package/build/modules/animated/AnimatedView.svelte +95 -0
- package/build/modules/animated/AnimatedView.svelte.d.ts +5 -0
- package/build/modules/animated/animated-component-props.d.ts +7 -0
- package/build/modules/animated/animated-component-props.js +1 -0
- package/build/modules/animated/index.d.ts +58 -0
- package/build/modules/animated/index.js +69 -0
- package/build/modules/app-registry/index.d.ts +7 -0
- package/build/modules/app-registry/index.js +28 -0
- package/build/modules/status-bar/index.d.ts +5 -0
- package/build/modules/status-bar/index.js +21 -0
- package/build/modules/status-bar/index.svelte +20 -0
- package/build/modules/status-bar/index.svelte.d.ts +4 -0
- package/build/native-view-bridge.d.ts +4 -0
- package/build/native-view-bridge.js +27 -0
- package/build/preprocessor/forbid-web-only-constructs.d.ts +8 -0
- package/build/preprocessor/forbid-web-only-constructs.js +204 -0
- package/build/preprocessor/scoped-styles.d.ts +10 -0
- package/build/preprocessor/scoped-styles.js +399 -0
- package/build/render.d.ts +7 -0
- package/build/render.js +46 -0
- package/build/root-element.d.ts +3 -0
- package/build/root-element.js +27 -0
- package/build/runes/attachments.d.ts +5 -0
- package/build/runes/attachments.js +73 -0
- package/build/runes/dimensions-value.d.ts +4 -0
- package/build/runes/dimensions-value.js +30 -0
- package/build/runes/media-query.d.ts +8 -0
- package/build/runes/media-query.js +38 -0
- package/build/runes/use-color-scheme.svelte.d.ts +4 -0
- package/build/runes/use-color-scheme.svelte.js +25 -0
- package/build/runes/use-window-dimensions.svelte.d.ts +4 -0
- package/build/runes/use-window-dimensions.svelte.js +43 -0
- package/build/runes/window.d.ts +6 -0
- package/build/runes/window.js +28 -0
- package/build/scope-token.d.ts +2 -0
- package/build/scope-token.js +33 -0
- package/build/style-scope.d.ts +1 -0
- package/build/style-scope.js +29 -0
- package/metro-css-parser.cjs +9 -0
- package/metro-svelte-transformer.cjs +156 -0
- package/package.json +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# @symbiote-native/svelte
|
|
2
|
+
|
|
3
|
+
The **Svelte adapter** for [SymbioteNative](../../README.md) — render real native iOS/Android views
|
|
4
|
+
from Svelte 5, on the _same_ untouched core as React, Vue, and Angular, with React Native's own
|
|
5
|
+
renderer never in the path. Svelte ships no official host-renderer API yet (`createRenderer` is
|
|
6
|
+
still an unmerged PR upstream), so instead of hooking a framework-blessed extension point this
|
|
7
|
+
adapter patches `globalThis`'s DOM classes: stock compiled Svelte client output believes it is
|
|
8
|
+
talking to a real DOM, and every one of those calls routes into `@symbiote-native/engine`'s same
|
|
9
|
+
four-call mutation API underneath, which does the clone-on-write commit into Fabric.
|
|
10
|
+
|
|
11
|
+
Svelte is the **sharpest test yet** that the core is genuinely framework-agnostic — unlike Vue's
|
|
12
|
+
`createRenderer` or Angular's `Renderer2`, there was no host-renderer seam to hook here at all.
|
|
13
|
+
|
|
14
|
+
<div align="center">
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
> New to SymbioteNative? The [root README](../../README.md) has the architecture.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @symbiote-native/svelte react-native svelte
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`react-native` and `svelte` stay your app's own top-level dependencies — this package only
|
|
31
|
+
replaces the JS renderer that drives them. Every component is a `.svelte` SFC — there's no TSX
|
|
32
|
+
alternative the way Vue offers — so the Metro wiring below isn't optional. There's no
|
|
33
|
+
`create-symbiote` scaffolder yet, so it comes from [`examples/svelte`](../../examples/svelte)
|
|
34
|
+
rather than a generator:
|
|
35
|
+
|
|
36
|
+
- `metro.config.js` — point `babelTransformerPath` at
|
|
37
|
+
`@symbiote-native/svelte/metro-svelte-transformer` (compiles `.svelte` on the way into the
|
|
38
|
+
bundle) and disable `inlineRequires` (see [the gotcha
|
|
39
|
+
below](#a-svelte-specific-gotcha--inlinerequires-vs-the-svelte-runtime)).
|
|
40
|
+
- `svelte.config.js` — registers the `forbidWebOnlyConstructs()` and `scopedStyles()`
|
|
41
|
+
preprocessors, so `svelte-check` and the editor catch the same things Metro's transformer
|
|
42
|
+
already guards against at build time, and sets `compilerOptions: { fragments: 'tree', css:
|
|
43
|
+
'external' }`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Use it
|
|
48
|
+
|
|
49
|
+
The native entry reaches the _same_ seam as every other adapter. `createApp(App).mount(appName)`
|
|
50
|
+
wires the native-host seams and RN's own `AppRegistry`, then mounts via `@symbiote-native/engine` —
|
|
51
|
+
RN's own renderer is never in the path:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
// index.js
|
|
55
|
+
import { createApp } from '@symbiote-native/svelte/bootstrap';
|
|
56
|
+
import App from './App.svelte';
|
|
57
|
+
import { name as appName } from './app.json';
|
|
58
|
+
|
|
59
|
+
createApp(App).mount(appName);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The app is ordinary Svelte 5 — it just imports primitives from `@symbiote-native/svelte` instead of
|
|
63
|
+
`react-native`. A tap→increment counter, using runes:
|
|
64
|
+
|
|
65
|
+
```svelte
|
|
66
|
+
<script lang="ts">
|
|
67
|
+
import { View, Text, Pressable } from '@symbiote-native/svelte';
|
|
68
|
+
|
|
69
|
+
let count = $state(0);
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<View style={{ padding: 24 }}>
|
|
73
|
+
<Text>Taps: {count}</Text>
|
|
74
|
+
<Pressable onPress={() => count++}>
|
|
75
|
+
<Text>Tap me</Text>
|
|
76
|
+
</Pressable>
|
|
77
|
+
</View>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The full canary is [`examples/svelte`](../../examples/svelte) — a stock RN 0.86 app whose
|
|
81
|
+
[`App.svelte`](../../examples/svelte/App.svelte) exercises the same surface as the React, Vue, and
|
|
82
|
+
Angular reference canaries.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Parity — and the one gap
|
|
87
|
+
|
|
88
|
+
Svelte reaches the same primitives, runtime modules, `Animated` on both drivers, gestures,
|
|
89
|
+
accessibility, and the `VirtualizedList` family as React, Vue, and Angular, verified on-device on
|
|
90
|
+
iOS and Android. That parity is **structural, not hand-copied**: the component logic (state
|
|
91
|
+
machines + render functions) is written **once** in `@symbiote-native/components`, and Svelte
|
|
92
|
+
supplies only its lifecycle — runes (`$state` / `$derived` / `$effect`) driving hand-authored
|
|
93
|
+
`.svelte` markup that mirrors each `render-*.ts` directly, rather than a generic descriptor bridge
|
|
94
|
+
the way React and Vue use one.
|
|
95
|
+
|
|
96
|
+
The one deliberate gap, same as every other non-React adapter — **third-party React component
|
|
97
|
+
packages** (`@react-native-community/slider` used directly) run only under the React adapter:
|
|
98
|
+
their body calls React hooks off the React dispatcher, which is null under Svelte.
|
|
99
|
+
`@symbiote-native/slider` (this repo's own wrapper) _does_ ship a real Svelte build
|
|
100
|
+
(`@symbiote-native/slider/svelte`) through the same `createNode`-by-ViewConfig path Svelte uses
|
|
101
|
+
for its own primitives — that wrapper is what makes this one third-party native view usable from
|
|
102
|
+
Svelte at all; any _other_ React-only component package stays React-adapter-only until it gets the
|
|
103
|
+
same treatment.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## A Svelte-specific gotcha — inlineRequires vs the Svelte runtime
|
|
108
|
+
|
|
109
|
+
RN's default Metro config turns on `inlineRequires` for every top-level import. Svelte's internal
|
|
110
|
+
client runtime (`svelte/internal/client/**`) is a graph of small files wired through module-scope
|
|
111
|
+
singleton reactivity state (the current-effect/signal graph); under `inlineRequires` that graph
|
|
112
|
+
re-enters itself while mounting and blows the JS stack — `Maximum call stack size exceeded` inside
|
|
113
|
+
`metroRequire`, reproduced on a real device/simulator, not just in theory.
|
|
114
|
+
[`examples/svelte`](../../examples/svelte)'s `metro.config.js` disables it explicitly
|
|
115
|
+
(`experimentalImportSupport: false, inlineRequires: false`); any new Svelte app needs the same
|
|
116
|
+
override, since RN's default Metro config doesn't know to do this on its own.
|
|
117
|
+
|
|
118
|
+
A smaller one: Svelte's compiler only trims LEADING/TRAILING whitespace inside a fragment —
|
|
119
|
+
whitespace strictly _between_ two sibling non-text nodes collapses to a real text node and is
|
|
120
|
+
kept. That's invisible on the web, but a stray text node between siblings under a non-`Text`
|
|
121
|
+
parent is an invalid Fabric child on this DOM shim, so markup inside an `{#each}`/`{#if}` chain
|
|
122
|
+
over host primitives has to avoid it — this repo carries a device-verified regression test
|
|
123
|
+
guarding it.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Run it
|
|
128
|
+
|
|
129
|
+
[`examples/svelte`](../../examples/svelte) is a stock React Native 0.86 app. Requires Node ≥ 22
|
|
130
|
+
and the [RN environment setup](https://reactnative.dev/docs/set-up-your-environment) (Xcode,
|
|
131
|
+
CocoaPods):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
cd examples/svelte
|
|
135
|
+
npm install
|
|
136
|
+
bundle install # first time only — installs CocoaPods itself
|
|
137
|
+
bundle exec pod install # fetch native pods
|
|
138
|
+
|
|
139
|
+
# terminal 1 — Metro. DEBUG=1 turns on diagnostic logs.
|
|
140
|
+
DEBUG=1 npm run dev
|
|
141
|
+
|
|
142
|
+
# terminal 2 — build + launch
|
|
143
|
+
npm run ios # iOS simulator
|
|
144
|
+
npm run android # Android emulator
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Test it
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pnpm test # vitest, from the workspace root — headless, fake Fabric slot
|
|
153
|
+
|
|
154
|
+
cd examples/svelte
|
|
155
|
+
npm run e2e:build:ios # build the app for Detox
|
|
156
|
+
npm run e2e:test:ios # run the canary journeys on the iOS simulator
|
|
157
|
+
# …or the android equivalents: e2e:build:android / e2e:test:android
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Why these come for free — a SymbioteNative app is a stock RN app underneath, so RN's whole testing
|
|
161
|
+
ecosystem applies unchanged. See [Testing](../../README.md#testing).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import { type IBootstrapHostOptions } from '@symbiote-native/components/bootstrap';
|
|
3
|
+
export type { IBootstrapHostOptions } from '@symbiote-native/components/bootstrap';
|
|
4
|
+
export type ISymbioteSvelteApp = {
|
|
5
|
+
mount(appName: string): void;
|
|
6
|
+
};
|
|
7
|
+
export declare function createApp(RootComponent: Component, options?: IBootstrapHostOptions): ISymbioteSvelteApp;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Zero-config app entry, the Svelte twin of adapters/vue/src/bootstrap.ts and
|
|
2
|
+
// adapters/react/src/bootstrap.ts. Lives OUTSIDE the package's main barrel — react-native's
|
|
3
|
+
// own source is Flow syntax Vitest's transform can't parse, so anything importing it directly
|
|
4
|
+
// must stay unreachable from the tested main index.ts.
|
|
5
|
+
import { AppRegistry as RNAppRegistry } from 'react-native';
|
|
6
|
+
import { bootstrapHost } from '@symbiote-native/components/bootstrap';
|
|
7
|
+
import { AppRegistry, setHostRegistrar } from './modules/app-registry/index.js';
|
|
8
|
+
// Mirrors the Vue/React adapters' createApp(App).mount(appName) two-step idiom.
|
|
9
|
+
export function createApp(RootComponent, options = {}) {
|
|
10
|
+
return {
|
|
11
|
+
mount(appName) {
|
|
12
|
+
bootstrapHost(options);
|
|
13
|
+
setHostRegistrar(RNAppRegistry);
|
|
14
|
+
AppRegistry.registerComponent(appName, () => RootComponent);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { resolveClassName, type IClassNameValue } from '@symbiote-native/engine';
|
|
2
|
+
export type IClassMap = Readonly<Record<string, boolean | null | undefined>>;
|
|
3
|
+
export type IClassEntry = IClassNameValue | IClassMap | false;
|
|
4
|
+
export type ISvelteClassValue = IClassEntry | ReadonlyArray<IClassEntry | ReadonlyArray<IClassEntry>>;
|
|
5
|
+
export declare function normalizeSvelteClass(value: unknown): unknown;
|
|
6
|
+
export declare function resolveSvelteClass(value: unknown): ReturnType<typeof resolveClassName>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// clsx-shaped `class` values, normalized to a class string at this adapter's bag boundary.
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS IS THE ADAPTER'S JOB. `class={{ active: isOn }}` and `class={['card', isOn &&
|
|
4
|
+
// 'card-on']}` are idiomatic Svelte, but Svelte only normalizes them on the ELEMENT path:
|
|
5
|
+
// `set_class` runs the value through `clsx()` + `to_class()`
|
|
6
|
+
// (svelte/src/internal/shared/attributes.js). A COMPONENT prop gets no such treatment — it is
|
|
7
|
+
// handed over verbatim. App code here never authors a host element (it composes View/Text/… from
|
|
8
|
+
// @symbiote-native/svelte, skill §7), so every class value in this adapter arrives as a component
|
|
9
|
+
// prop and the normalization Svelte would have done is ours to do.
|
|
10
|
+
//
|
|
11
|
+
// WHY NOT WIDEN THE ENGINE'S resolveClassName INSTEAD. There, a plain object means an
|
|
12
|
+
// ALREADY-RESOLVED STYLE and is returned untouched. That is load-bearing for the other three
|
|
13
|
+
// adapters — React only ever passes a string, and Vue's own `normalizeClass` has already
|
|
14
|
+
// flattened `:class="{ active: x }"` into a string before patchProp is reached — so teaching
|
|
15
|
+
// resolveClassName to read an object as a class map would silently change their behaviour.
|
|
16
|
+
//
|
|
17
|
+
// THE DISAMBIGUATION RULE, and why this one: `{ color: 'red' }` is a resolved style and
|
|
18
|
+
// `{ active: true }` is a clsx map, and the two shapes are only told apart by their VALUES.
|
|
19
|
+
// every own value is boolean / null / undefined -> clsx map
|
|
20
|
+
// anything else (including an empty object) -> resolved style, passed through untouched
|
|
21
|
+
// A style object's values are colours, lengths and numbers — never booleans — while a clsx map
|
|
22
|
+
// is written as `{ name: <condition> }`, and a condition idiomatically evaluates to a boolean.
|
|
23
|
+
// The rule is also expressible in the type below (IClassMap's value type IS the runtime check),
|
|
24
|
+
// so `{ card: someString }` — the one shape that would silently take the style branch — is a
|
|
25
|
+
// compile error at the call site rather than a surprise at runtime. An empty object is
|
|
26
|
+
// deliberately NOT treated as a clsx map: both readings contribute no style, so the cheaper,
|
|
27
|
+
// non-allocating branch is the right one.
|
|
28
|
+
import { isClassNameValue, resolveClassName } from '@symbiote-native/engine';
|
|
29
|
+
// Returns a class STRING when the value is clsx-shaped, and the value UNTOUCHED otherwise, so
|
|
30
|
+
// everything the engine already resolved correctly keeps taking the exact path it took before.
|
|
31
|
+
export function normalizeSvelteClass(value) {
|
|
32
|
+
if (typeof value !== 'object' || value === null)
|
|
33
|
+
return value;
|
|
34
|
+
const parts = [];
|
|
35
|
+
return collectClsxParts(value, parts) ? parts.join(CLASS_SEPARATOR) : value;
|
|
36
|
+
}
|
|
37
|
+
const CLASS_SEPARATOR = ' ';
|
|
38
|
+
// For the handful of components that resolve a class to a style THEMSELVES rather than handing
|
|
39
|
+
// it to the host bag — ScrollView and VirtualizedList split the resolved class into layout vs.
|
|
40
|
+
// visual halves before they know which host tag gets which — so a clsx map reaches the registry
|
|
41
|
+
// there too instead of falling into resolveClassName's "already a style" branch.
|
|
42
|
+
export function resolveSvelteClass(value) {
|
|
43
|
+
const normalized = normalizeSvelteClass(value);
|
|
44
|
+
return resolveClassName(isClassNameValue(normalized) ? normalized : undefined);
|
|
45
|
+
}
|
|
46
|
+
// Mirrors clsx's own `toVal` (node_modules/clsx/dist/clsx.js), which is what Svelte's `clsx()`
|
|
47
|
+
// wrapper delegates to for any object value: a string or number contributes itself, an array
|
|
48
|
+
// recurses over its entries dropping the falsy ones, an object contributes every key whose value
|
|
49
|
+
// is truthy. Returns false the moment it meets a plain object that is not a clsx map — that
|
|
50
|
+
// makes the whole value non-clsx, so the caller hands it on unchanged.
|
|
51
|
+
function collectClsxParts(value, parts) {
|
|
52
|
+
if (value === null || value === undefined || typeof value === 'boolean')
|
|
53
|
+
return true;
|
|
54
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
55
|
+
if (value !== '' && value !== 0)
|
|
56
|
+
parts.push(String(value));
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
for (const entry of value) {
|
|
61
|
+
if (!collectClsxParts(entry, parts))
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (typeof value !== 'object')
|
|
67
|
+
return false;
|
|
68
|
+
const entries = Object.entries(value);
|
|
69
|
+
if (!isClassMapEntries(entries))
|
|
70
|
+
return false;
|
|
71
|
+
for (const [name, applies] of entries) {
|
|
72
|
+
if (applies === true)
|
|
73
|
+
parts.push(name);
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function isClassMapEntries(entries) {
|
|
78
|
+
if (entries.length === 0)
|
|
79
|
+
return false;
|
|
80
|
+
return entries.every(([, applies]) => typeof applies === 'boolean' || applies === null || applies === undefined);
|
|
81
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
// RefreshControl primitive. On iOS this is the PullToRefreshView Fabric node that lives INSIDE
|
|
3
|
+
// a ScrollView (a sibling of the content container), giving the pull-to-refresh gesture. On
|
|
4
|
+
// Android it is AndroidSwipeRefreshLayout and WRAPS the scroll view (a childless-on-iOS,
|
|
5
|
+
// wraps-on-Android shape — see refresh-control-props.ts). `refreshing` is a controlled prop:
|
|
6
|
+
// the parent owns it and pushes it down each commit; native reports the gesture via the direct
|
|
7
|
+
// `topRefresh` event, which the engine routes to the host's `onRefresh` bag entry.
|
|
8
|
+
import type { IRefreshControlProps } from './refresh-control-props';
|
|
9
|
+
|
|
10
|
+
export type { IRefreshControlProps };
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import { dlog } from '@symbiote-native/engine';
|
|
15
|
+
import { resolveAccessibilityProps } from '@symbiote-native/components';
|
|
16
|
+
import { createAttachmentsSync } from '../runes/attachments';
|
|
17
|
+
import type { ShimElement } from '../dom-shim';
|
|
18
|
+
|
|
19
|
+
let { children, ...rest }: IRefreshControlProps = $props();
|
|
20
|
+
|
|
21
|
+
dlog('RefreshControl -> PullToRefreshView');
|
|
22
|
+
dlog(`RefreshControl refreshing=${String(rest.refreshing)}`);
|
|
23
|
+
if (rest.enabled !== undefined) dlog(`RefreshControl enabled=${String(rest.enabled)} (Android-only)`);
|
|
24
|
+
if (rest.onRefresh !== undefined) dlog('RefreshControl onRefresh listener wired');
|
|
25
|
+
|
|
26
|
+
const bag = $derived(resolveAccessibilityProps(rest));
|
|
27
|
+
|
|
28
|
+
// See View.svelte's note on `{@attach}`.
|
|
29
|
+
let hostShim = $state.raw<ShimElement | null>(null);
|
|
30
|
+
const syncAttachments = createAttachmentsSync();
|
|
31
|
+
$effect(() => {
|
|
32
|
+
syncAttachments(hostShim, rest);
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<symbiote-refresh-control p={bag} bind:this={hostShim}>
|
|
37
|
+
{@render children?.()}
|
|
38
|
+
</symbiote-refresh-control>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IRefreshControlProps } from './refresh-control-props';
|
|
2
|
+
export type { IRefreshControlProps };
|
|
3
|
+
declare const RefreshControl: import("svelte").Component<IRefreshControlProps, {}, "">;
|
|
4
|
+
type RefreshControl = ReturnType<typeof RefreshControl>;
|
|
5
|
+
export default RefreshControl;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
// SafeAreaView primitive. A plain view whose native side insets its children to the safe area
|
|
3
|
+
// (notch, rounded corners, system bars). There is no JS-side translation; RN just renders the
|
|
4
|
+
// native RCTSafeAreaView and lets the host do the inset math, so this maps style + children
|
|
5
|
+
// straight onto the intrinsic — same object-bag pattern as View.svelte.
|
|
6
|
+
import type { ISafeAreaViewProps } from './safe-area-view-props';
|
|
7
|
+
|
|
8
|
+
export type { ISafeAreaViewProps };
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { dlog } from '@symbiote-native/engine';
|
|
13
|
+
import { resolveAccessibilityProps } from '@symbiote-native/components';
|
|
14
|
+
|
|
15
|
+
import { createAttachmentsSync } from '../runes/attachments';
|
|
16
|
+
import type { ShimElement } from '../dom-shim';
|
|
17
|
+
|
|
18
|
+
let { children, ...rest }: ISafeAreaViewProps = $props();
|
|
19
|
+
|
|
20
|
+
dlog('SafeAreaView -> SafeAreaView');
|
|
21
|
+
|
|
22
|
+
const bag = $derived(resolveAccessibilityProps(rest));
|
|
23
|
+
|
|
24
|
+
// See View.svelte's note on `{@attach}`.
|
|
25
|
+
let hostShim = $state.raw<ShimElement | null>(null);
|
|
26
|
+
const syncAttachments = createAttachmentsSync();
|
|
27
|
+
$effect(() => {
|
|
28
|
+
syncAttachments(hostShim, rest);
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<symbiote-safe-area-view p={bag} bind:this={hostShim}>
|
|
33
|
+
{@render children?.()}
|
|
34
|
+
</symbiote-safe-area-view>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ISafeAreaViewProps } from './safe-area-view-props';
|
|
2
|
+
export type { ISafeAreaViewProps };
|
|
3
|
+
declare const SafeAreaView: import("svelte").Component<ISafeAreaViewProps, {}, "">;
|
|
4
|
+
type SafeAreaView = ReturnType<typeof SafeAreaView>;
|
|
5
|
+
export default SafeAreaView;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
// See View.svelte's header comment — same object-bag design (svelte-adapter-dom-shim
|
|
3
|
+
// skill §3g(c)). No IResponderProps here, matching every other adapter's ITextProps.
|
|
4
|
+
import type { ITextProps } from './text-props';
|
|
5
|
+
|
|
6
|
+
export type { ITextProps };
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { createAttachmentsSync } from '../runes/attachments';
|
|
11
|
+
import type { ShimElement } from '../dom-shim';
|
|
12
|
+
|
|
13
|
+
let { children, ...bag }: ITextProps = $props();
|
|
14
|
+
|
|
15
|
+
// See View.svelte's note: `{@attach}` arrives as a symbol-keyed entry in the same rest object.
|
|
16
|
+
let hostShim = $state.raw<ShimElement | null>(null);
|
|
17
|
+
const syncAttachments = createAttachmentsSync();
|
|
18
|
+
$effect(() => {
|
|
19
|
+
syncAttachments(hostShim, bag);
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<symbiote-text p={bag} bind:this={hostShim}>
|
|
24
|
+
{@render children?.()}
|
|
25
|
+
</symbiote-text>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
// Host primitive for app code. Per svelte-adapter-dom-shim skill §3g(c): Symbiote's
|
|
3
|
+
// custom-element codegen path stringifies individual attributes, so unlike Vue's View
|
|
4
|
+
// (which spreads props onto the intrinsic tag and lets patchProp route each one), this
|
|
5
|
+
// component assembles every prop into ONE object bag and hands it to `<symbiote-view p={bag}>`
|
|
6
|
+
// — the shim element's `p` setter diffs it per key into routeProp. `children` is pulled out
|
|
7
|
+
// first: it is a Svelte-specific field (a Snippet), never forwarded into the bag, mirroring
|
|
8
|
+
// why every adapter's IViewProps is declared per-adapter (CLAUDE.md
|
|
9
|
+
// <prop_types_split_agnostic_vs_per_adapter>) rather than shared verbatim.
|
|
10
|
+
import type { IViewProps } from './view-props';
|
|
11
|
+
|
|
12
|
+
export type { IViewProps };
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
import { createAttachmentsSync } from '../runes/attachments';
|
|
17
|
+
import type { ShimElement } from '../dom-shim';
|
|
18
|
+
|
|
19
|
+
let { children, id, nativeID, ...rest }: IViewProps = $props();
|
|
20
|
+
|
|
21
|
+
// `{@attach fn}` on a component lands as a SYMBOL-keyed prop, so it rides in `rest` alongside
|
|
22
|
+
// the ordinary bag keys and never reaches the engine (routeProp only walks string keys). This
|
|
23
|
+
// is the one directive-shaped Svelte feature that IS legal on a component, hence the only way
|
|
24
|
+
// an author reaches the committed host node — see runes/attachments.ts.
|
|
25
|
+
let hostShim = $state.raw<ShimElement | null>(null);
|
|
26
|
+
const syncAttachments = createAttachmentsSync();
|
|
27
|
+
$effect(() => {
|
|
28
|
+
syncAttachments(hostShim, rest);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// RN's modern `id` is just a W3C-named alias for `nativeID`: View.js copies it over
|
|
32
|
+
// (`processedProps.nativeID = id`), so `id` wins when both are set. Fold it here and drop
|
|
33
|
+
// the raw `id` key so it never reaches Fabric (every non-function prop in the bag otherwise
|
|
34
|
+
// passes through to the slot as-is) — mirrors React's `resolveId` in
|
|
35
|
+
// `adapters/react/src/components.ts`, adapted to the object-bag idiom.
|
|
36
|
+
const bag = $derived(id === undefined ? { ...rest, nativeID } : { ...rest, nativeID: id });
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<symbiote-view p={bag} bind:this={hostShim}>
|
|
40
|
+
{@render children?.()}
|
|
41
|
+
</symbiote-view>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './activity-indicator-platform.ios';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Base / default: re-exports the iOS build. Metro overrides this with
|
|
2
|
+
// activity-indicator-platform.ios.ts / .android.ts on a real host; under tsc/vitest the host
|
|
3
|
+
// config resolves here. Filename is the selector, no Platform.OS read — same convention as
|
|
4
|
+
// switch/switch-platform.ts.
|
|
5
|
+
export * from './activity-indicator-platform.ios.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IActivityIndicatorProps as IActivityIndicatorBaseProps } from '@symbiote-native/components';
|
|
2
|
+
import type { ISvelteClassValue } from '../../class-value';
|
|
3
|
+
export type IActivityIndicatorProps = IActivityIndicatorBaseProps & {
|
|
4
|
+
class?: ISvelteClassValue;
|
|
5
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
// ActivityIndicator: render-only (no state). Calls the shared renderActivityIndicator()
|
|
3
|
+
// straight from core/components — the size translation, platform default-color fold, and
|
|
4
|
+
// native-extras all live there, once, for every adapter. The root stays a literal template
|
|
5
|
+
// tag (`bind:this` needs a known tag), but its child (the spinner) is materialized and kept
|
|
6
|
+
// in sync via the generic descriptorToSvelte bridge (createDescriptorChildrenSync) instead of
|
|
7
|
+
// a hand-written child tag — see svelte-adapter-dom-shim skill §19 for why this is safe and
|
|
8
|
+
// cheap (create once, update by position, never recreate).
|
|
9
|
+
import type { IActivityIndicatorProps } from './activity-indicator-props';
|
|
10
|
+
|
|
11
|
+
export type { IActivityIndicatorProps };
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
import { renderActivityIndicator, resolveAccessibilityProps } from '@symbiote-native/components';
|
|
16
|
+
import { PLATFORM } from './activity-indicator-platform';
|
|
17
|
+
import { createDescriptorChildrenSync } from '../../descriptor-to-svelte';
|
|
18
|
+
import { createAttachmentsSync } from '../../runes/attachments';
|
|
19
|
+
import type { ShimElement } from '../../dom-shim';
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
animating = true,
|
|
23
|
+
color,
|
|
24
|
+
hidesWhenStopped = true,
|
|
25
|
+
size = 'small',
|
|
26
|
+
style,
|
|
27
|
+
class: className,
|
|
28
|
+
...passthrough
|
|
29
|
+
}: IActivityIndicatorProps = $props();
|
|
30
|
+
|
|
31
|
+
const descriptor = $derived(
|
|
32
|
+
renderActivityIndicator(
|
|
33
|
+
{
|
|
34
|
+
animating,
|
|
35
|
+
hidesWhenStopped,
|
|
36
|
+
size,
|
|
37
|
+
color,
|
|
38
|
+
style,
|
|
39
|
+
passthrough: { ...resolveAccessibilityProps(passthrough), class: className },
|
|
40
|
+
},
|
|
41
|
+
PLATFORM,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
let hostShim = $state.raw<ShimElement | null>(null);
|
|
45
|
+
const syncChildren = createDescriptorChildrenSync();
|
|
46
|
+
|
|
47
|
+
$effect(() => {
|
|
48
|
+
syncChildren(hostShim, descriptor.children);
|
|
49
|
+
});
|
|
50
|
+
// See View.svelte's note on `{@attach}`.
|
|
51
|
+
const syncAttachments = createAttachmentsSync();
|
|
52
|
+
$effect(() => {
|
|
53
|
+
syncAttachments(hostShim, passthrough);
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<symbiote-view p={descriptor.props} bind:this={hostShim} />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|