@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 +72 -28
- package/build/components/virtualized-list/index.js +6 -15
- package/build/intrinsic-elements.js +2 -0
- package/build/renderer/index.js +8 -24
- package/build/runtime-helpers/index.d.ts +0 -16
- package/build/runtime-helpers/index.js +11 -24
- package/intrinsic-tags.cjs +6 -16
- package/metro-vue-transformer.cjs +5 -11
- package/package.json +6 -6
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. **
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
accessibility, and the `VirtualizedList` family
|
|
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
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
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
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
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;
|
package/build/renderer/index.js
CHANGED
|
@@ -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,
|
|
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
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
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
|
|
91
|
-
// `
|
|
92
|
-
//
|
|
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
|
-
//
|
|
41
|
-
// `
|
|
42
|
-
//
|
|
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
|
|
208
|
-
//
|
|
209
|
-
//
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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;
|
package/intrinsic-tags.cjs
CHANGED
|
@@ -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
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
|
23
|
-
//
|
|
24
|
-
// `
|
|
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
|
|
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
|
|
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
|
-
//
|
|
367
|
-
//
|
|
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.
|
|
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.
|
|
74
|
-
"@symbiote-native/css-parser": "0.5.
|
|
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.
|
|
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.
|
|
84
|
-
"@symbiote-native/test-utils": "0.4.
|
|
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",
|