@symbiote-native/svelte 3.0.2 → 3.0.3

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @symbiote-native/svelte
2
2
 
3
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
4
+ from Svelte 5, on the _same_ untouched core as React, Vue, Solid, and Angular, with React Native's own
5
5
  renderer never in the path. Svelte ships no official host-renderer API yet (`createRenderer` is
6
6
  still an unmerged PR upstream), so instead of hooking a framework-blessed extension point this
7
7
  adapter patches `globalThis`'s DOM classes: stock compiled Svelte client output believes it is
@@ -23,35 +23,84 @@ Svelte is the **sharpest test yet** that the core is genuinely framework-agnosti
23
23
 
24
24
  ## Install
25
25
 
26
+ ```bash
27
+ npx @symbiote-native/cli new my-app --framework svelte
28
+ ```
29
+
30
+ One command, nothing to wire by hand: every component here is a `.svelte` SFC with no TSX
31
+ alternative, so the Metro wiring below isn't optional — the generator sets it up, plus
32
+ `@symbiote-native/svelte`/`react-native`/`svelte` as your app's own dependencies.
33
+
34
+ <details>
35
+ <summary>Manual install (no generator — an existing app, or you want to wire it yourself)</summary>
36
+
26
37
  ```bash
27
38
  npm install @symbiote-native/svelte react-native svelte
28
39
  ```
29
40
 
30
41
  `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. `npx @symbiote-native/cli new`
33
- sets this up for a new app; wiring it into an existing one still follows
34
- [`examples/svelte`](../../examples/svelte) rather than a generator:
42
+ replaces the JS renderer that drives them. Follow [`examples/svelte`](../../examples/svelte) for
43
+ both pieces of Metro wiring below; there is no wiring script for an existing app:
35
44
 
36
45
  - `metro.config.js` — point `babelTransformerPath` at
37
46
  `@symbiote-native/svelte/metro-svelte-transformer` (compiles `.svelte` on the way into the
38
47
  bundle) and disable `inlineRequires` (see [the gotcha
39
48
  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' }`.
49
+ - `svelte.config.js` — registers the `forbidWebOnlyConstructs()`, `scopedStyles()`, and
50
+ `collapseTextWhitespace()` preprocessors, so `svelte-check` and the editor catch the same things
51
+ Metro's transformer already guards against at build time (the third one matters beyond
52
+ diagnostics: without it, a sentence wrapped across source lines for readability ships a literal
53
+ newline into the native text content — Svelte doesn't collapse it the way a browser or Vue's
54
+ compiler would). Also sets `compilerOptions: { fragments: 'tree', css: 'external' }` and a
55
+ `warningFilter` that silences `element_invalid_self_closing_tag` for `<pressable />`-style
56
+ self-closing primitives.
57
+
58
+ </details>
44
59
 
45
60
  ---
46
61
 
47
62
  ## Use it
48
63
 
64
+ The app is ordinary Svelte 5 — the native primitives are plain intrinsic tags, no import needed.
65
+ Styling is a CSS class against the component's own `<style>` block — the convention every example
66
+ app here follows. A tap→increment counter, using runes:
67
+
68
+ ```svelte
69
+ <script lang="ts">
70
+ let count = $state(0);
71
+ </script>
72
+
73
+ <safe-area-view class="screen">
74
+ <text>Taps: {count}</text>
75
+ <pressable onPress={() => count++}>
76
+ <text>Tap me</text>
77
+ </pressable>
78
+ </safe-area-view>
79
+
80
+ <style>
81
+ .screen {
82
+ flex: 1;
83
+ align-items: center;
84
+ justify-content: center;
85
+ padding: 24px;
86
+ }
87
+ </style>
88
+ ```
89
+
90
+ <details>
91
+ <summary>Native entry point (index.js) — already scaffolded by <code>npx @symbiote-native/cli new --framework svelte</code></summary>
92
+
49
93
  The native entry reaches the _same_ seam as every other adapter. `createApp(App).mount(appName)`
50
94
  wires the native-host seams and RN's own `AppRegistry`, then mounts via `@symbiote-native/engine` —
51
95
  RN's own renderer is never in the path:
52
96
 
53
97
  ```js
54
98
  // index.js
99
+
100
+ // Registers host behaviors (Image, Pressable, Switch, ...) that /bootstrap alone doesn't
101
+ // reach; deleting this breaks them silently (Metro's production inlineRequires makes a
102
+ // side-effect-only barrel import go lazy, see register.ts).
103
+ import '@symbiote-native/svelte';
55
104
  import { createApp } from '@symbiote-native/svelte/bootstrap';
56
105
  import App from './App.svelte';
57
106
  import { name as appName } from './app.json';
@@ -59,21 +108,7 @@ import { name as appName } from './app.json';
59
108
  createApp(App).mount(appName);
60
109
  ```
61
110
 
62
- The app is ordinary Svelte 5 — the native primitives are plain intrinsic tags, no import needed.
63
- A tap→increment counter, using runes:
64
-
65
- ```svelte
66
- <script lang="ts">
67
- let count = $state(0);
68
- </script>
69
-
70
- <view style={{ padding: 24 }}>
71
- <text>Taps: {count}</text>
72
- <pressable onPress={() => count++}>
73
- <text>Tap me</text>
74
- </pressable>
75
- </view>
76
- ```
111
+ </details>
77
112
 
78
113
  The full canary is [`examples/svelte`](../../examples/svelte) — a stock RN 0.86 app whose
79
114
  [`App.svelte`](../../examples/svelte/App.svelte) exercises the same surface as the React, Vue, and
@@ -84,8 +119,8 @@ Angular reference canaries.
84
119
  ## Parity — and the one gap
85
120
 
86
121
  Svelte reaches the same primitives, runtime modules, `Animated` on both drivers, gestures,
87
- accessibility, and the `VirtualizedList` family as React, Vue, and Angular, verified on-device on
88
- iOS and Android. That parity is **structural, not hand-copied**: the component logic (state
122
+ accessibility, and the `VirtualizedList` family as React, Vue, Solid, and Angular, verified
123
+ on-device on iOS and Android. That parity is **structural, not hand-copied**: the component logic (state
89
124
  machines + render functions) is written **once** in `@symbiote-native/components`, and Svelte
90
125
  supplies only its lifecycle — runes (`$state` / `$derived` / `$effect`) driving hand-authored
91
126
  `.svelte` markup that mirrors each `render-*.ts` directly, rather than a generic descriptor bridge
@@ -124,9 +159,9 @@ guarding it.
124
159
 
125
160
  ## Run it
126
161
 
127
- [`examples/svelte`](../../examples/svelte) is a stock React Native 0.86 app. Requires Node ≥ 22
128
- and the [RN environment setup](https://reactnative.dev/docs/set-up-your-environment) (Xcode,
129
- CocoaPods):
162
+ [`examples/svelte`](../../examples/svelte) is a stock React Native 0.86 app. Requires Node ≥ 22.13
163
+ (react-native 0.86's own `package.json#engines`) and the [RN environment
164
+ setup](https://reactnative.dev/docs/set-up-your-environment) (Xcode, CocoaPods):
130
165
 
131
166
  ```bash
132
167
  cd examples/svelte
@@ -35,23 +35,26 @@
35
35
 
36
36
  let rawProps: IModalProps = $props();
37
37
 
38
- let state = $state(createInitialModalState(isModalVisible(rawProps.visible)));
38
+ // Not a $derived candidate despite the $state+$effect shape below: modalReducer folds over the
39
+ // PREVIOUS `state` (self-referential — $derived can't read the value it's replacing) and must
40
+ // run POST-render (see the effect's own comment) so the keep-alive frame survives a commit.
41
+ let localState = $state(createInitialModalState(rawProps.visible === true));
39
42
 
40
43
  const resolved = $derived(resolveAccessibilityProps(rawProps));
41
44
  const isVisible = $derived(isModalVisible(resolved.visible));
42
- const shouldRender = $derived(shouldRenderModal(isVisible, state));
45
+ const shouldRender = $derived(shouldRenderModal(isVisible, localState));
43
46
 
44
47
  // Arms the iOS keep-alive on show; a hide is left to the native dismiss (state/modal.ts). The
45
48
  // reducer is identity-stable, so the mount run triggers no extra render.
46
49
  $effect(() => {
47
50
  const action = modalVisibilityAction(isVisible);
48
- if (action !== undefined) state = modalReducer(state, action);
51
+ if (action !== undefined) localState = modalReducer(localState, action);
49
52
  });
50
53
 
51
54
  // Modal.js: onDismiss is iOS-only — it drops the keep-alive, then tells the app.
52
55
  function handleDismiss(): void {
53
56
  if (Platform.OS !== 'ios') return;
54
- state = modalReducer(state, { type: 'hide' });
57
+ localState = modalReducer(localState, { type: 'hide' });
55
58
  rawProps.onDismiss?.();
56
59
  }
57
60
 
@@ -109,6 +109,10 @@
109
109
 
110
110
  // The one folded state cell — the Svelte twin of Vue's plain listState / React's stateRef.
111
111
  const listState: IListState<ItemT> = createInitialListState<ItemT>();
112
+ // Plain Map by design, like listState above: separatorVersion (below) is the manual
113
+ // invalidation signal a read tracks — a SvelteMap would double-track and still miss nothing,
114
+ // but adds reactive overhead this hot path doesn't need.
115
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
112
116
  const separatorOverrides = new Map<number, Partial<ISeparatorProps<ItemT>>>();
113
117
  let viewableTimer: ReturnType<typeof setTimeout> | null = null;
114
118
  let batchTimer: ReturnType<typeof setTimeout> | null = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/svelte",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "SymbioteNative's Svelte adapter — a DOM shim over stock compiled Svelte output driving real native iOS/Android views through the same engine as every other adapter.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -73,8 +73,8 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "typescript": "~6.0.0",
76
- "@symbiote-native/components": "3.1.0",
77
- "@symbiote-native/css-parser": "0.5.0"
76
+ "@symbiote-native/components": "3.1.1",
77
+ "@symbiote-native/css-parser": "0.5.1"
78
78
  },
79
79
  "peerDependencies": {
80
80
  "svelte": ">=5.56.0",
@@ -86,9 +86,9 @@
86
86
  "react": "19.2.3",
87
87
  "svelte": "^5.56.0",
88
88
  "@symbiote-native/engine": "1.3.0",
89
- "@symbiote-native/test-utils": "0.4.2",
90
- "@symbiote-native/react": "3.0.2",
91
- "@symbiote-native/vue": "3.0.2"
89
+ "@symbiote-native/react": "3.0.3",
90
+ "@symbiote-native/test-utils": "0.4.3",
91
+ "@symbiote-native/vue": "3.0.3"
92
92
  },
93
93
  "scripts": {
94
94
  "typecheck": "tsc --build",