@symbiote-native/vue 3.0.2 → 3.0.4

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
@@ -20,20 +20,79 @@ mutation-oriented framework driving the already-validated engine.
20
20
 
21
21
  ## Install
22
22
 
23
+ ```bash
24
+ npx @symbiote-native/cli new my-app --framework vue --vue-flavor sfc # or --vue-flavor tsx
25
+ ```
26
+
27
+ One command, nothing to wire by hand: scaffolds the Metro config (the SFC transformer, or the TSX
28
+ Babel plugin below), the entry seam below, and `@symbiote-native/vue`/`react-native`/`vue` as your
29
+ app's own dependencies.
30
+
31
+ <details>
32
+ <summary>Manual install (no generator — an existing app, or you want to wire it yourself)</summary>
33
+
23
34
  ```bash
24
35
  npm install @symbiote-native/vue react-native vue
25
36
  ```
26
37
 
27
38
  `react-native` and `vue` stay your app's own top-level dependencies — this package only replaces
28
- the JS renderer that drives them. **TSX** needs nothing beyond the install above; **SFC**
29
- additionally needs a Metro transformer for `.vue` files (see
30
- [`examples/vue-sfc`](../../examples/vue-sfc)'s `metro.config.js`) — `npx @symbiote-native/cli new`
31
- wires this for a new app; an existing app still needs it copied in by hand.
39
+ the JS renderer that drives them. **SFC** additionally needs a Metro transformer for `.vue` files
40
+ — see [`examples/vue-sfc`](../../examples/vue-sfc)'s `metro.config.js`.
41
+
42
+ **TSX** needs one more Babel plugin beyond the install above, or a dev-mode Vue warning can crash
43
+ the mount to a blank screen. RN's dev React-JSX transform tags every `JSXElement` with `__self`/
44
+ `__source` (inert under React); `@vue/babel-plugin-jsx` copies them verbatim into the vnode's
45
+ props, and at module scope `__self`'s `this` is the Hermes global `HostObject` — the first Vue dev
46
+ warning that formats that prop for a component trace throws reading `Symbol.toStringTag` off it,
47
+ unwinding the whole mount. Strip both attributes on `JSXOpeningElement` exit, after Babel's
48
+ `__self`/`__source` plugins add them but before `@vue/babel-plugin-jsx` reads them (see
49
+ [`examples/vue-tsx`](../../examples/vue-tsx)'s `babel.config.js`, `stripReactJsxDevAttrs`) — SFC
50
+ doesn't need this, since its compiler never sees RN's React-JSX transform at all.
51
+
52
+ </details>
32
53
 
33
54
  ---
34
55
 
35
56
  ## Use it
36
57
 
58
+ The app is ordinary Vue. The primitives are TAGS — `<view>`, `<text>`, `<pressable>` — so there is
59
+ nothing to import for them at all; both Vue compilers resolve them as elements and the renderer
60
+ maps each to its Fabric view. Styling is a CSS class (a plain `.css` file in TSX, an SFC's own
61
+ `<style>` block otherwise) — the convention every example app here follows. A tap→increment
62
+ counter, authored in TSX:
63
+
64
+ ```jsx
65
+ import { ref } from '@vue/runtime-core';
66
+ import './App.css';
67
+
68
+ export default {
69
+ setup() {
70
+ const count = ref(0);
71
+ return () => (
72
+ <safe-area-view class="screen">
73
+ <text>Taps: {count.value}</text>
74
+ <pressable onPress={() => count.value++}>
75
+ <text>Tap me</text>
76
+ </pressable>
77
+ </safe-area-view>
78
+ );
79
+ },
80
+ };
81
+ ```
82
+
83
+ ```css
84
+ /* App.css */
85
+ .screen {
86
+ flex: 1;
87
+ align-items: center;
88
+ justify-content: center;
89
+ padding: 24px;
90
+ }
91
+ ```
92
+
93
+ <details>
94
+ <summary>Native entry point (index.js) — already scaffolded by <code>npx @symbiote-native/cli new --framework vue</code></summary>
95
+
37
96
  The zero-config entry mirrors real Vue's own `createApp(App).mount(...)` idiom and wires the same
38
97
  RN-backed host seams React's `registerApp` does — this is what
39
98
  [`examples/vue-tsx`](../../examples/vue-tsx) and [`examples/vue-sfc`](../../examples/vue-sfc)
@@ -41,6 +100,11 @@ actually use:
41
100
 
42
101
  ```js
43
102
  // index.js
103
+
104
+ // Registers host behaviors (Image, Pressable, Switch, ...) that /bootstrap alone doesn't
105
+ // reach; deleting this breaks them silently (Metro's production inlineRequires makes a
106
+ // side-effect-only barrel import go lazy, see register.ts).
107
+ import '@symbiote-native/vue';
44
108
  import { createApp } from '@symbiote-native/vue/bootstrap';
45
109
  import App from './App';
46
110
  import { name as appName } from './app.json';
@@ -66,27 +130,7 @@ RNAppRegistry.registerRunnable(appName, ({ rootTag }) => {
66
130
  });
67
131
  ```
68
132
 
69
- The app is ordinary Vue. The primitives are TAGS — `<view>`, `<text>`, `<pressable>` — so there is
70
- nothing to import for them at all; both Vue compilers resolve them as elements and the renderer
71
- maps each to its Fabric view. A tap→increment counter, authored in TSX:
72
-
73
- ```jsx
74
- import { ref } from '@vue/runtime-core';
75
-
76
- export default {
77
- setup() {
78
- const count = ref(0);
79
- return () => (
80
- <view style={{ padding: 24 }}>
81
- <text>Taps: {count.value}</text>
82
- <pressable onPress={() => count.value++}>
83
- <text>Tap me</text>
84
- </pressable>
85
- </view>
86
- );
87
- },
88
- };
89
- ```
133
+ </details>
90
134
 
91
135
  ### Two example apps
92
136
 
@@ -101,9 +145,9 @@ the demo above is the first one running on the iOS simulator:
101
145
 
102
146
  ## Parity — and the one gap
103
147
 
104
- Both adapters reach the same primitives, runtime modules, `Animated` on both drivers, gestures,
105
- accessibility, and the `VirtualizedList` family, verified on-device on iOS and Android. That
106
- parity is **structural, not hand-copied**: the
148
+ Vue reaches the same primitives, runtime modules, `Animated` on both drivers, gestures,
149
+ accessibility, and the `VirtualizedList` family as React, Svelte, Solid, and Angular, verified
150
+ on-device on iOS and Android. That parity is **structural, not hand-copied**: the
107
151
  component logic (state machines + render functions) is written **once** in `@symbiote-native/components`,
108
152
  and each adapter supplies only its lifecycle (Vue's `ref`/`watch` + the descriptor→`h()` bridge).
109
153
 
@@ -492,16 +492,9 @@ export const VirtualizedList = defineComponent((props, { attrs, expose, emit, sl
492
492
  ? INVERTED_X_STYLE
493
493
  : INVERTED_Y_STYLE
494
494
  : undefined;
495
- // Wraps one cell identically whether it's the force-mounted sticky cell ahead of the
496
- // window or an in-window cell from the loop below — same measuring View, same key shape.
497
- // RN renders the separator INSIDE the cell's own measuring wrapper
498
- // (VirtualizedListCellRenderer.js:218-221), and that placement is load-bearing rather than
499
- // cosmetic. As a SIBLING it is an extra flex child, so the chrome between two cells becomes
500
- // gap + separator + gap while a spacer collapsing that region replaces it with one gap —
501
- // the leading spacer then lands every cell below it short by (separator + gap), and the
502
- // content visibly jumps by that amount each time the window's first index moves. Measured
503
- // at exactly 17px on device 2026-08-19 (a 1px divider under a 16px container gap); see
504
- // .claude/rules/list-geometry-feedback-loop.md.
495
+ // The separator lives INSIDE the cell's own measuring wrapper, not as a sibling: a sibling
496
+ // is an extra flex child, so a leading-spacer collapse would land every cell below it
497
+ // short by (separator + gap). See .claude/rules/list-geometry-feedback-loop.md.
505
498
  const pushCell = (cell) => {
506
499
  const item = p.getItem(p.data, cell.index);
507
500
  const content = p.renderItem?.({
@@ -510,11 +503,9 @@ export const VirtualizedList = defineComponent((props, { attrs, expose, emit, sl
510
503
  separators: makeSeparators(cell.index),
511
504
  });
512
505
  const separator =
513
- // RN gates the separator on the last index of the DATA, not of the WINDOW
514
- // (VirtualizedList.js:793 `const end = getItemCount(data) - 1`), and now that the separator
515
- // lives INSIDE the measuring wrapper that distinction is load-bearing: gating on the window
516
- // would make a cell's own measured height change as the window slides past it. Device-measured
517
- // 2026-08-19 as a run of cells all shifting by exactly the divider's 1px.
506
+ // Gated on the last index of the DATA, not the WINDOW: since the separator lives
507
+ // inside the measuring wrapper, gating on the window would make a cell's own
508
+ // measured height change as the window slides past it.
518
509
  cell.index < m.count - 1
519
510
  ? renderSeparatorElement(p.itemSeparatorComponent, item, p.getItem(p.data, cell.index + 1), separatorOverrides.get(cell.index))
520
511
  : undefined;
@@ -1 +1,3 @@
1
+ // The tag alphabet, declared to vue-tsc and Volar. Vue's template checker resolves an unknown
2
+ // lowercase tag as a component, so declaring it here makes the bare spelling first-class.
1
3
  export {};
@@ -3,7 +3,7 @@
3
3
  // drives the exact same retained tree React does: the proof the core is framework-
4
4
  // agnostic.
5
5
  import { callWithErrorHandling, createRenderer, ErrorCodes, markRaw, } from '@vue/runtime-core';
6
- import { appendChild, createAnchor, createElement, createRawText, childrenOf, dlog, insertBefore, isRawTextNode, isTextContainer, nextSiblingOf, parentOf, removeChild, routeProp, setProp, setText, textOf, toPublicInstance, SymbioteSurface, } from '@symbiote-native/engine';
6
+ import { appendChild, createAnchor, createElement, createRawText, childrenOf, dlog, insertBefore, isRawTextNode, isTextContainer, nextSiblingOf, parentOf, removeChild, routeProp, setText, textOf, toPublicInstance, SymbioteSurface, } from '@symbiote-native/engine';
7
7
  import { descriptorFor } from '@symbiote-native/components';
8
8
  import { normalizeVueAttrKey } from '../utils/normalize-attrs.js';
9
9
  function isSurface(parent) {
@@ -12,20 +12,10 @@ function isSurface(parent) {
12
12
  function isRawText(node) {
13
13
  return isRawTextNode(node);
14
14
  }
15
- // RN's two Text defaults left this renderer entirely on 2026-09-18, in two steps a month apart. The
16
- // CREATE seed went first, because writing them as props cost a crossing every time the app authored
17
- // the same value — 6 000 per 1 000-row create, measured with `writesOfUnchanged`. What stayed was a
18
- // clear-back: an explicit `undefined` at patch time was substituted for the default, since RN treats
19
- // a missing prop and an explicit `undefined` alike.
20
- //
21
- // That substitution is gone too, and it was redundant rather than wrong. The rule reads the AUTHORED
22
- // bag at payload time (`foldTextDefaults`, `SymbioteFabricProps.cpp`), so it cannot tell a cleared
23
- // prop from one never written — both are absent by the time it looks, and both get the default. The
24
- // adapter was re-supplying an answer the layer below already had.
25
- // `PROP_ALIASES` (`id` -> `nativeID`) left this renderer on 2026-09-18 — `routeProp` resolves it
26
- // for every adapter now, and carries the per-node state the caveat here said was not worth it: with
27
- // both names on one element upstream gives `id` unconditional priority, where a per-key rename
28
- // resolved it by write order.
15
+ // RN's two Text defaults are `foldTextDefaults`'s rule now (`SymbioteFabricProps.cpp`), reading
16
+ // the AUTHORED bag at payload time — this renderer keeps no seed or clear-back of either default.
17
+ // `id` -> `nativeID` is `routeProp`'s fold for every adapter now; with both names on one element
18
+ // it gives `id` unconditional priority.
29
19
  // RN-style event prop naming ('onPress', 'onValueChange', ...), the same convention JSX itself
30
20
  // uses to separate an event from a plain value prop — good enough to decide whether to wrap,
31
21
  // without duplicating routeProp's own ViewConfig-derived event lists here.
@@ -87,15 +77,9 @@ export function createSymbioteRenderer(surface) {
87
77
  // one benchmark press — and a literal is built at the call site before dlog can decide
88
78
  // anything (see core/engine/src/debug.ts).
89
79
  dlog(() => `vue createElement ${descriptor.component} -> public instance`);
90
- // markRaw is load-bearing, not an optimization: useTemplateRef()'s return value is
91
- // `readonly(shallowRef(null))` (runtime-core.cjs.js), and Vue's readonly() wraps ANY
92
- // `.value` whose Object.prototype.toString reads "[object Object]" — true of a plain class
93
- // instance, unlike a real DOM Element, which fails that check for free. Unmarked, a node
94
- // reached through useTemplateRef() (not a plain ref()/shallowRef(), both of which skip the
95
- // wrap) came back as a deep-readonly Proxy: reads worked, so `committedOf`/`whenCommitted`
96
- // saw a "committed" node, but `setNativeProps`'s `node.props.style = …` silently no-op'd
97
- // with a dev-only "Set operation… target is readonly" warning — device-reported 2026-09-11
98
- // as "flash the right chip" doing nothing on press.
80
+ // markRaw is load-bearing: useTemplateRef()'s `readonly(shallowRef(null))` wraps any
81
+ // `.value` that isn't a real DOM Element, and unmarked, `setNativeProps` silently
82
+ // no-ops against the resulting readonly Proxy.
99
83
  return markRaw(toPublicInstance(node));
100
84
  },
101
85
  createText(text) {
@@ -22,22 +22,6 @@ type IModifierGuardableEvent = {
22
22
  type IEventHandler<TEvent> = ((event: TEvent, ...args: never[]) => unknown) & {
23
23
  _withMods?: Record<string, IEventHandler<TEvent>>;
24
24
  };
25
- /**
26
- * Render-function/compiled-template equivalent of `v-on.stop`/`.prevent`/`.self`/etc. `fn` is
27
- * typed as required, matching upstream Vue's own declaration — its runtime-only `!fn` guard below
28
- * (for a compiler-generated call site that could pass a falsy handler) isn't reflected in the
29
- * type there either.
30
- *
31
- * `@press.self` on an element reaches this, and both halves were measured 2026-09-11 rather
32
- * than assumed — the question arose because a modifier on a COMPONENT takes Vue's own event path
33
- * and only an element emits the helper. `<pressable @press.self>` compiles to
34
- * `_withModifiers(fn, ["self"])` imported `from "@symbiote-native/vue/runtime-helpers"` (the Metro
35
- * transformer retargets every compiled `from 'vue'`), so the compiler-emitted call lands here and
36
- * not on @vue/runtime-dom's. And `ISymbioteEvent` (`core/engine/src/node.ts`) declares `target`,
37
- * `currentTarget` and `stopPropagation` as REQUIRED fields with the DOM semantics these guards
38
- * read, so `.self` genuinely filters a bubbled press instead of degenerating to
39
- * `undefined !== undefined`.
40
- */
41
25
  export declare function withModifiers<TEvent extends IModifierGuardableEvent>(fn: IEventHandler<TEvent>, modifiers: readonly IEventModifier[]): IEventHandler<TEvent>;
42
26
  type IKeyedEvent = {
43
27
  key?: string;
@@ -37,9 +37,9 @@ import { descriptorFor, SWITCH_TAG } from '@symbiote-native/components';
37
37
  // idiom the engine resolves in `routeProp` (`isStyleCallback`); Vue's normaliser is DOM-shaped and
38
38
  // cannot know that.
39
39
  //
40
- // Measured 2026-09-01 through the real SFC pipeline, which is the ONLY path that reaches this:
41
- // `h(Component, { ...bag })` skips `normalizeProps` entirely and shows the style arriving intact,
42
- // so a probe written that way reports the bug as absent.
40
+ // Reachable only through the real SFC pipeline: `h(Component, { ...bag })` skips
41
+ // `normalizeProps` entirely and shows the style arriving intact, so a probe written that way
42
+ // reports the bug as absent.
43
43
  //
44
44
  // <View v-bind="bag"> fn style committed {testID} <- style gone
45
45
  // <Pressable v-bind="bag"> fn style committed {testID} <- style gone
@@ -204,11 +204,9 @@ function syncModelListener(el, state) {
204
204
  state.appListener = isValueChangeListener(current) ? current : undefined;
205
205
  setProp(el, 'onValueChange', state.listener);
206
206
  }
207
- // Vue's compiler picks the directive by ELEMENT, and for anything it does not recognise as a DOM
208
- // input it emits `vModelText` — `<switch>` included. Stringifying there is what
209
- // upstream must do (a DOM input's value IS a string) and what we must not: the Switch behavior
210
- // reads `props.value === true`, so `String(true)` pins the control OFF and no tap can move it.
211
- // Device-confirmed on `examples/vue-sfc` 2026-09-02, both switches on `CanaryScreen`.
207
+ // Vue's compiler picks the directive by ELEMENT: anything not recognised as a DOM input gets
208
+ // `vModelText` — `<switch>` included. Stringifying is fatal here: Switch reads
209
+ // `props.value === true`, so `String(true)` pins the control OFF and no tap can move it.
212
210
  //
213
211
  // Resolved through the shared descriptor table rather than a literal Fabric name, so the check
214
212
  // follows the platform the bundle actually loaded (iOS `Switch`, Android `AndroidSwitch`) instead
@@ -287,22 +285,11 @@ const MODIFIER_GUARDS = {
287
285
  right: event => event.button !== 2,
288
286
  exact: (event, modifiers) => SYSTEM_MODIFIERS.some(modifier => event[`${modifier}Key`] && !modifiers.includes(modifier)),
289
287
  };
290
- /**
291
- * Render-function/compiled-template equivalent of `v-on.stop`/`.prevent`/`.self`/etc. `fn` is
292
- * typed as required, matching upstream Vue's own declaration — its runtime-only `!fn` guard below
293
- * (for a compiler-generated call site that could pass a falsy handler) isn't reflected in the
294
- * type there either.
295
- *
296
- * `@press.self` on an element reaches this, and both halves were measured 2026-09-11 rather
297
- * than assumed — the question arose because a modifier on a COMPONENT takes Vue's own event path
298
- * and only an element emits the helper. `<pressable @press.self>` compiles to
299
- * `_withModifiers(fn, ["self"])` imported `from "@symbiote-native/vue/runtime-helpers"` (the Metro
300
- * transformer retargets every compiled `from 'vue'`), so the compiler-emitted call lands here and
301
- * not on @vue/runtime-dom's. And `ISymbioteEvent` (`core/engine/src/node.ts`) declares `target`,
302
- * `currentTarget` and `stopPropagation` as REQUIRED fields with the DOM semantics these guards
303
- * read, so `.self` genuinely filters a bubbled press instead of degenerating to
304
- * `undefined !== undefined`.
305
- */
288
+ // Render-function/compiled-template equivalent of `v-on.stop`/`.prevent`/`.self`/etc. `fn` is
289
+ // typed as required, matching upstream Vue's own declaration.
290
+ // `@press.self` on an element reaches this — a modifier on a COMPONENT takes Vue's own event
291
+ // path, only an element emits the helper. `ISymbioteEvent` declares `target`/`currentTarget`/
292
+ // `stopPropagation` as REQUIRED fields with DOM semantics, so `.self` genuinely filters.
306
293
  export function withModifiers(fn, modifiers) {
307
294
  if (!fn)
308
295
  return fn;
@@ -6,12 +6,9 @@
6
6
  // compiles to `resolveComponent("text-input")` with its children as a slot the element path never
7
7
  // reads, which renders a blank subtree with no error.
8
8
  //
9
- // DERIVED, never enumerated. Until 2026-09-07 this was the prefix test `tag.startsWith('symbiote-')`
10
- // in ./babel-jsx.cjs — correct while every intrinsic carried that marker, and answering FALSE for
11
- // every tag since the prefix was dropped (`core/components/src/component-names/shared.ts` says why
12
- // it went). What hid it is the tag alphabet: `view` / `text` / `image` / `switch` are real SVG
13
- // element names, so both compilers resolve those four as elements with no configuration at all, and
14
- // only the hyphenated half degraded.
9
+ // DERIVED, never enumerated — the tag alphabet has no shared marker to test for.
10
+ // `view`/`text`/`image`/`switch` are real SVG element names, so both compilers resolve those four
11
+ // as elements with no configuration at all; only the hyphenated half needs this table.
15
12
  //
16
13
  // SCOPE, stated because it is smaller than `ISymbioteIntrinsic`. That union also holds
17
14
  // `scroll-view`, `modal`, `activity-indicator` and the rest, but it is a TypeScript type and these
@@ -19,16 +16,9 @@
19
16
  // runtime-readable table reachable from here, so a hand-written `<scroll-view>` still resolves as a
20
17
  // component. Closing that needs a CJS intrinsic table in `core/components`.
21
18
  //
22
- // WHAT THAT GAP COSTS, measured 2026-09-11 rather than reasoned, because the first guess was that
23
- // the tag simply would not work. It works: `resolveComponent` falls back to the tag STRING, and
24
- // `normalizeChildren` then unwraps the `withCtx` default slot for an element shapeFlag, so
25
- // `<scroll-view><text>hi</text></scroll-view>` committed `RCTView > RCTScrollView > RCTText >
26
- // RCTRawText`. What it costs is a dev-mode `[Vue warn]: Failed to resolve component: scroll-view`
27
- // on every such element plus the component codegen path (a slot closure per element) instead of
28
- // `_createElementBlock`. That is a divergence from every other tag, so `ScrollView`,
29
- // `TouchableOpacity` and `TouchableHighlight` stay COMPONENTS on Vue until their
30
- // `HOST_PRIMITIVES` entries land — and `intrinsic-elements.ts` already types all three as valid
31
- // markup off `ISymbioteIntrinsic`, so vue-tsc accepts what the runtime compiler warns about.
19
+ // WHAT THAT GAP COSTS: `resolveComponent` falls back to the tag STRING and still commits the
20
+ // right tree, paying only a dev-mode `[Vue warn]` plus the component codegen path — so
21
+ // `ScrollView`/`TouchableOpacity`/`TouchableHighlight` stay COMPONENTS until they join this table.
32
22
  //
33
23
  // The tell that the three are still wrapped rather than merely unlisted: `src/register.ts` does
34
24
  // NOT call their behaviors, because a registered behavior plus a surviving wrapper is two owners
@@ -280,11 +280,9 @@ async function compileSfc(src, filename) {
280
280
  // re-parse, which is what we need anyway.
281
281
 
282
282
  parseCache.clear();
283
- // element-vs-component is decided by the PARSER, not by the later `isCustomElement` in
283
+ // element-vs-component is decided by the PARSER, not the later `isCustomElement` in
284
284
  // templateOptions — `compileScript` reuses this descriptor's already-parsed AST, where `tagType`
285
- // is already fixed. So a hand-written `<pressable>` needs the answer here or it stays a component
286
- // whatever templateOptions says, which is how it read until 2026-09-07:
287
- // `resolveComponent("pressable")`, a blank subtree with no error.
285
+ // is fixed. Without the answer here, `<pressable>` stays a component: `resolveComponent(...)`.
288
286
  const { descriptor, errors } = parse(src, {
289
287
  filename,
290
288
  templateParseOptions: { isCustomElement: tag => INTRINSIC_TAGS.has(tag) },
@@ -362,13 +360,9 @@ async function compileSfc(src, filename) {
362
360
  ];
363
361
  if (scopedClassNames.size > 0)
364
362
  nodeTransforms.push(createScopeClassNodeTransform(scopedClassNames));
365
- // UNCONDITIONAL, and covering EVERY intrinsic rather than the ones this file happens to import.
366
- // Both narrowings were live until 2026-09-07 and both broke a hand-written tag: a file importing
367
- // no wrapper got no `isCustomElement` at all, and a file importing one got an answer scoped to
368
- // that import, so `<pressable>` written by hand compiled to `resolveComponent("pressable")` —
369
- // a blank subtree with no error — whichever way the file was written. `<switch v-model>` failed
370
- // louder: `@vue/compiler-dom`'s transformModel rejects v-model on an element that is neither
371
- // input/textarea/select nor a custom element, so it was a hard compile error.
363
+ // UNCONDITIONAL, and covering EVERY intrinsic rather than the ones this file happens to import:
364
+ // a narrower `isCustomElement` leaves `<pressable>` compiling to `resolveComponent(...)` (blank
365
+ // subtree, no error), and `<switch v-model>` a hard compile error (non-custom, non-form element).
372
366
  const templateOptions = {
373
367
  compilerOptions: {
374
368
  ...(nodeTransforms.length > 0 ? { nodeTransforms } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/vue",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "SymbioteNative's Vue adapter — a @vue/runtime-core createRenderer driving real native iOS/Android views through the same engine as the React adapter.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,18 +70,18 @@
70
70
  "@vue/compiler-sfc": "^3.5.13",
71
71
  "@vue/runtime-core": "^3.5.13",
72
72
  "typescript": "~6.0.0",
73
- "@symbiote-native/components": "3.1.0",
74
- "@symbiote-native/css-parser": "0.5.0"
73
+ "@symbiote-native/components": "3.1.2",
74
+ "@symbiote-native/css-parser": "0.5.2"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "vue": ">=3.5.0",
78
78
  "react-native": ">=0.86",
79
- "@symbiote-native/engine": "^1.3.0"
79
+ "@symbiote-native/engine": "^1.3.1"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@babel/core": "^7.25.2",
83
- "@symbiote-native/engine": "1.3.0",
84
- "@symbiote-native/test-utils": "0.4.2"
83
+ "@symbiote-native/engine": "1.3.1",
84
+ "@symbiote-native/test-utils": "0.4.4"
85
85
  },
86
86
  "scripts": {
87
87
  "typecheck": "tsc --build",