@symbiote-native/vue 1.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +13 -12
  2. package/babel-jsx.cjs +26 -18
  3. package/build/components/activity-indicator-props.d.ts +5 -0
  4. package/build/components/activity-indicator-props.js +1 -0
  5. package/build/components/button-props.d.ts +5 -0
  6. package/build/components/button-props.js +1 -0
  7. package/build/components/flat-list/index.d.ts +3 -3
  8. package/build/components/flat-list/index.js +3 -3
  9. package/build/components/image-background-props.d.ts +7 -0
  10. package/build/components/image-background-props.js +1 -0
  11. package/build/components/image-props.d.ts +5 -0
  12. package/build/components/image-props.js +5 -0
  13. package/build/components/{input-accessory-view.d.ts → input-accessory-view-props.d.ts} +1 -3
  14. package/build/components/input-accessory-view-props.js +5 -0
  15. package/build/components/keyboard-avoiding-view.js +9 -5
  16. package/build/components/modal/index.js +1 -1
  17. package/build/components/pressable-props.d.ts +31 -0
  18. package/build/components/pressable-props.js +12 -0
  19. package/build/components/refresh-control-props.d.ts +16 -0
  20. package/build/components/refresh-control-props.js +9 -0
  21. package/build/components/safe-area-view-props.d.ts +6 -0
  22. package/build/components/safe-area-view-props.js +5 -0
  23. package/build/components/scroll-view/{shared.d.ts → scroll-view-props.d.ts} +4 -27
  24. package/build/components/scroll-view/scroll-view-props.js +1 -0
  25. package/build/components/section-list/index.d.ts +3 -3
  26. package/build/components/switch/switch-props.d.ts +6 -0
  27. package/build/components/switch/switch-props.js +1 -0
  28. package/build/components/text-input/text-input-props.d.ts +5 -0
  29. package/build/components/text-input/text-input-props.js +1 -0
  30. package/build/components/text-props.d.ts +24 -0
  31. package/build/components/text-props.js +6 -0
  32. package/build/components/touchable-native-feedback-props.d.ts +7 -0
  33. package/build/components/touchable-native-feedback-props.js +1 -0
  34. package/build/components/touchable-props.d.ts +17 -0
  35. package/build/components/touchable-props.js +1 -0
  36. package/build/components/touchable-without-feedback-props.d.ts +5 -0
  37. package/build/components/touchable-without-feedback-props.js +1 -0
  38. package/build/components/view-props.d.ts +29 -0
  39. package/build/components/view-props.js +5 -0
  40. package/build/components/virtualized-list/index.d.ts +3 -3
  41. package/build/components/virtualized-list/index.js +64 -46
  42. package/build/components/virtualized-section-list/index.d.ts +3 -3
  43. package/build/descriptor-to-vue.js +2 -2
  44. package/build/host-instance/index.js +2 -2
  45. package/build/index.d.ts +24 -27
  46. package/build/index.js +31 -16
  47. package/build/intrinsic-elements.d.ts +19 -0
  48. package/build/intrinsic-elements.js +1 -0
  49. package/build/jsx-runtime.d.ts +67 -0
  50. package/build/jsx-runtime.js +1 -0
  51. package/build/modules/animated/index.d.ts +41 -20
  52. package/build/modules/animated/index.js +20 -19
  53. package/build/modules/image/index.d.ts +3 -0
  54. package/build/modules/image/index.js +13 -0
  55. package/build/register.d.ts +1 -1
  56. package/build/register.js +13 -33
  57. package/build/renderer/index.js +108 -54
  58. package/build/runtime-helpers/index.d.ts +10 -1
  59. package/build/runtime-helpers/index.js +56 -22
  60. package/build/utils/normalize-attrs.js +3 -3
  61. package/intrinsic-tags.cjs +52 -0
  62. package/metro-vue-transformer.cjs +55 -345
  63. package/package.json +13 -9
  64. package/babel-lower-host-primitives.cjs +0 -225
  65. package/build/components/activity-indicator/index.android.d.ts +0 -4
  66. package/build/components/activity-indicator/index.android.js +0 -10
  67. package/build/components/activity-indicator/index.d.ts +0 -1
  68. package/build/components/activity-indicator/index.ios.d.ts +0 -4
  69. package/build/components/activity-indicator/index.ios.js +0 -10
  70. package/build/components/activity-indicator/index.js +0 -4
  71. package/build/components/activity-indicator/shared.d.ts +0 -8
  72. package/build/components/activity-indicator/shared.js +0 -48
  73. package/build/components/button.d.ts +0 -14
  74. package/build/components/button.js +0 -61
  75. package/build/components/image-background.d.ts +0 -9
  76. package/build/components/image-background.js +0 -103
  77. package/build/components/image.d.ts +0 -9
  78. package/build/components/image.js +0 -90
  79. package/build/components/input-accessory-view.js +0 -41
  80. package/build/components/pressable.d.ts +0 -58
  81. package/build/components/pressable.js +0 -233
  82. package/build/components/refresh-control.d.ts +0 -20
  83. package/build/components/refresh-control.js +0 -47
  84. package/build/components/safe-area-view.d.ts +0 -9
  85. package/build/components/safe-area-view.js +0 -33
  86. package/build/components/scroll-view/index.android.d.ts +0 -4
  87. package/build/components/scroll-view/index.android.js +0 -64
  88. package/build/components/scroll-view/index.d.ts +0 -1
  89. package/build/components/scroll-view/index.ios.d.ts +0 -4
  90. package/build/components/scroll-view/index.ios.js +0 -17
  91. package/build/components/scroll-view/index.js +0 -4
  92. package/build/components/scroll-view/shared.js +0 -271
  93. package/build/components/scroll-view/sticky-header.d.ts +0 -7
  94. package/build/components/scroll-view/sticky-header.js +0 -208
  95. package/build/components/switch/index.android.d.ts +0 -12
  96. package/build/components/switch/index.android.js +0 -13
  97. package/build/components/switch/index.d.ts +0 -2
  98. package/build/components/switch/index.ios.d.ts +0 -12
  99. package/build/components/switch/index.ios.js +0 -11
  100. package/build/components/switch/index.js +0 -5
  101. package/build/components/switch/shared.d.ts +0 -23
  102. package/build/components/switch/shared.js +0 -126
  103. package/build/components/text-input/index.d.ts +0 -24
  104. package/build/components/text-input/index.js +0 -260
  105. package/build/components/touchable-native-feedback.d.ts +0 -24
  106. package/build/components/touchable-native-feedback.js +0 -82
  107. package/build/components/touchable.d.ts +0 -50
  108. package/build/components/touchable.js +0 -281
  109. package/build/components.d.ts +0 -53
  110. package/build/components.js +0 -45
  111. package/build/composables/use-raw-attrs.d.ts +0 -1
  112. package/build/composables/use-raw-attrs.js +0 -26
  113. package/build/state-style.d.ts +0 -1
  114. package/build/state-style.js +0 -13
@@ -5,25 +5,7 @@ export declare const Animated: {
5
5
  Value: typeof AnimatedValue;
6
6
  ValueXY: typeof AnimatedValueXY;
7
7
  Color: typeof AnimatedColor;
8
- Easing: {
9
- step0(n: number): number;
10
- step1(n: number): number;
11
- linear(t: number): number;
12
- ease(t: number): number;
13
- quad(t: number): number;
14
- cubic(t: number): number;
15
- poly(n: number): import("@symbiote-native/engine").IEasingFunction;
16
- sin(t: number): number;
17
- circle(t: number): number;
18
- exp(t: number): number;
19
- elastic(bounciness?: number): import("@symbiote-native/engine").IEasingFunction;
20
- back(s?: number): import("@symbiote-native/engine").IEasingFunction;
21
- bounce(t: number): number;
22
- bezier(x1: number, y1: number, x2: number, y2: number): import("@symbiote-native/engine").IEasingFunction;
23
- in(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
24
- out(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
25
- inOut(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
26
- };
8
+ Easing: import("@symbiote-native/engine").IEasing;
27
9
  timing: typeof timing;
28
10
  spring: typeof spring;
29
11
  decay: typeof decay;
@@ -50,7 +32,46 @@ export declare const Animated: {
50
32
  Image: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
51
33
  [key: string]: any;
52
34
  }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
53
- ScrollView: ReturnType<typeof createAnimatedComponent>;
35
+ ScrollView: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
36
+ [key: string]: any;
37
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
38
+ FlatList: ReturnType<typeof createAnimatedComponent>;
39
+ SectionList: ReturnType<typeof createAnimatedComponent>;
40
+ createAnimatedComponent: typeof createAnimatedComponent;
41
+ } | {
42
+ Value: typeof AnimatedValue;
43
+ ValueXY: typeof AnimatedValueXY;
44
+ Color: typeof AnimatedColor;
45
+ Easing: import("@symbiote-native/engine").IEasing;
46
+ timing: (value: AnimatedValue, config: import("@symbiote-native/engine").ITimingConfig) => import("@symbiote-native/engine").ICompositeAnimation;
47
+ spring: (value: AnimatedValue, config: import("@symbiote-native/engine").ISpringConfig) => import("@symbiote-native/engine").ICompositeAnimation;
48
+ decay: (_value: AnimatedValue, _config: import("@symbiote-native/engine").IDecayConfig) => import("@symbiote-native/engine").ICompositeAnimation;
49
+ parallel: (animations: import("@symbiote-native/engine").ICompositeAnimation[], _config?: import("@symbiote-native/engine").IParallelConfig) => import("@symbiote-native/engine").ICompositeAnimation;
50
+ sequence: (animations: import("@symbiote-native/engine").ICompositeAnimation[]) => import("@symbiote-native/engine").ICompositeAnimation;
51
+ stagger: (_time: number, animations: import("@symbiote-native/engine").ICompositeAnimation[]) => import("@symbiote-native/engine").ICompositeAnimation;
52
+ loop: (_animation: import("@symbiote-native/engine").ICompositeAnimation) => import("@symbiote-native/engine").ICompositeAnimation;
53
+ delay: (_time: number) => import("@symbiote-native/engine").ICompositeAnimation;
54
+ add: typeof add;
55
+ subtract: typeof subtract;
56
+ multiply: typeof multiply;
57
+ divide: typeof divide;
58
+ modulo: typeof modulo;
59
+ diffClamp: typeof diffClamp;
60
+ event: typeof event;
61
+ forkEvent: typeof forkEvent;
62
+ unforkEvent: typeof unforkEvent;
63
+ View: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
64
+ [key: string]: any;
65
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
66
+ Text: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
67
+ [key: string]: any;
68
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
69
+ Image: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
70
+ [key: string]: any;
71
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
72
+ ScrollView: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
73
+ [key: string]: any;
74
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
54
75
  FlatList: ReturnType<typeof createAnimatedComponent>;
55
76
  SectionList: ReturnType<typeof createAnimatedComponent>;
56
77
  createAnimatedComponent: typeof createAnimatedComponent;
@@ -4,25 +4,29 @@
4
4
  // meet here so `Animated.timing(new Animated.Value(0), ...).start()` works against the
5
5
  // Vue-driven engine.
6
6
  import { AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedMock, Easing, Platform, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, } from '@symbiote-native/engine';
7
- import { View, Text } from '../../components.js';
8
- import { Image } from '../../components/image.js';
9
- import { ScrollView } from '../../components/scroll-view';
10
7
  import { FlatList } from '../../components/flat-list/index.js';
11
8
  import { SectionList } from '../../components/section-list/index.js';
12
9
  import { createAnimatedComponent } from './create-animated-component.js';
13
10
  export { createAnimatedComponent } from './create-animated-component.js';
14
- // View/Text are pure host primitives; Image is the functional renderImage wrapper, and all expose
15
- // their host node via ref fall-through, so wrap them eagerly.
16
- const AnimatedView = createAnimatedComponent(View);
17
- const AnimatedText = createAnimatedComponent(Text);
18
- const AnimatedImage = createAnimatedComponent(Image);
19
- // LAZY, memoized getters, mirroring RN's `get ScrollView()`. Every scrolling container reaches
20
- // scroll-view/sticky-header, which imports this Animated namespace back, so a wrap at module
21
- // scope captures whatever the half-evaluated cycle holds at that instant. Under a bundler's ESM
22
- // interop that is `undefined`, not a ReferenceError - the wrapper builds fine and then renders
23
- // nothing. Deferring past module init is what keeps it from firing. FlatList and SectionList
24
- // reach ScrollView through VirtualizedList, so they sit on the same cycle and take the same shape.
25
- let animatedScrollView;
11
+ // The TAGS, not components — there are no View/Text/Image components left to alias, and a string
12
+ // base is what `createAnimatedComponent` already handles (`animated-tag-base.test.ts`): it renders
13
+ // the intrinsic and the ref falls through to the raw engine node, the same fall-through the
14
+ // functional wrappers used to provide.
15
+ //
16
+ // Wrapping is itself vestigial — the engine resolves an AnimatedNode written into any prop of any
17
+ // host node (`core/engine/src/animated/host-binding.ts`), so `<view :style="{ opacity: v }">` needs
18
+ // no wrapper at all. These stay as RN-compatible aliases until the namespace is retired.
19
+ const AnimatedView = createAnimatedComponent('view');
20
+ const AnimatedText = createAnimatedComponent('text');
21
+ const AnimatedImage = createAnimatedComponent('image');
22
+ // `scroll-view` is a TAG now too (no more component, no more scroll-view/sticky-header cycle to
23
+ // dodge), so it takes the same eager shape as View/Text/Image above.
24
+ const AnimatedScrollView = createAnimatedComponent('scroll-view');
25
+ // LAZY, memoized getters, mirroring RN's `get FlatList()`. Kept deferred past module init even
26
+ // though the scroll-view cycle these once rode is gone (ScrollView above is eager now) — neither
27
+ // FlatList nor VirtualizedList imports this module, so there is no proven cycle left, but nothing
28
+ // forces one to stay absent either. Cheap insurance against a half-evaluated module handing back
29
+ // `undefined` and rendering nothing.
26
30
  let animatedFlatList;
27
31
  let animatedSectionList;
28
32
  // The live, JS-driven driver namespace (real frames). RN's AnimatedImplementation.
@@ -58,10 +62,7 @@ export const Animated = {
58
62
  View: AnimatedView,
59
63
  Text: AnimatedText,
60
64
  Image: AnimatedImage,
61
- get ScrollView() {
62
- animatedScrollView ??= createAnimatedComponent(ScrollView);
63
- return animatedScrollView;
64
- },
65
+ ScrollView: AnimatedScrollView,
65
66
  get FlatList() {
66
67
  animatedFlatList ??= createAnimatedComponent(FlatList);
67
68
  return animatedFlatList;
@@ -0,0 +1,3 @@
1
+ import { type IImageStatics } from '@symbiote-native/components';
2
+ export type { IImageStatics };
3
+ export declare const Image: IImageStatics;
@@ -0,0 +1,13 @@
1
+ // RN's Image STATICS — `getSize`, `prefetch`, `queryCache`, `abortPrefetch`, `resolveAssetSource`.
2
+ //
3
+ // IN `modules/`, NOT `components/`. The primitive is the `<image>` tag and its fold runs in the
4
+ // engine (`behaviors/image.ts`); what is left under the name `Image` is an imperative API with no
5
+ // view, which is what this bucket is for. Sitting in `components/` made a statics object read as a
6
+ // component.
7
+ //
8
+ // A NAMESPACE OBJECT rather than five bare functions, deliberately: `Alert`, `Share`, `Linking`
9
+ // and `Vibration` are all `export const X = {…}`, and an app porting from RN writes
10
+ // `Image.getSize(...)` verbatim. Shared verbatim via `imageStatics`, the same source every other
11
+ // adapter's Image uses.
12
+ import { imageStatics } from '@symbiote-native/components';
13
+ export const Image = imageStatics;
@@ -1 +1 @@
1
- export {};
1
+ import '@symbiote-native/components/register';
package/build/register.js CHANGED
@@ -1,35 +1,15 @@
1
- // Side-effect ONLY. This module exports nothing, on purpose.
1
+ // Side-effect ONLY. Exports nothing, and must never be re-exported from the barrel.
2
2
  //
3
- // A lowered `<symbiote-pressable>` is an element, so the press machine lives on the engine node
4
- // instead of inside a component — and it gets there by `registerPressableBehavior()` having run
5
- // before the first node of that tag is created. The entry imports this file as a bare
6
- // `import './register';`.
3
+ // The fourteen calls live once, in `@symbiote-native/components/register` — they were identical in
4
+ // all five adapters, and each copy carried a migration ledger justifying a call against a wrapper
5
+ // that no longer exists. What is per-adapter is only the SPELLING of the import below: every
6
+ // `*-tag.test.*` here reaches the registry through `import './register';`, and this adapter's own
7
+ // barrel guard pins that line.
7
8
  //
8
- // WHY IT CANNOT BE A RE-EXPORT, and why this file has no exports to re-export in the first place.
9
- // Metro turns on `inlineRequires` for production only: it moves a `require()` down to the first
10
- // place its binding is USED, and a barrel's `export { X } from './x'` compiles to a lazy getter. A
11
- // module reached only that way never evaluates unless something names X as a VALUE, so its
12
- // registration silently never happens — in RELEASE builds only, invisible to tsc, to vitest, and
13
- // even to grepping the bundle, because the code IS bundled and simply never runs. A bare
14
- // `import './register';` placed NEXT TO a re-export of the same specifier does not help either:
15
- // Babel merges the two into one dependency and the merged one stays lazy. See CLAUDE.md, "Never
16
- // make correctness depend on a module's load-time side effect".
17
- //
18
- // `adapters/vue/src/index.test.ts` pins the barrel against both halves of that.
19
- import { registerImageBehavior, registerInputAccessoryViewBehavior, registerPressableBehavior, registerSwitchBehavior, registerTextInputBehavior, } from '@symbiote-native/components';
20
- registerPressableBehavior();
21
- // Only the LOWERED tags carry this — the wrapper renders `symbiote-text-input-managed` and
22
- // keeps running its own lifecycle. One owner per node; see `component-names/shared.ts`.
23
- registerTextInputBehavior();
24
- // Same reason as TextInput: the wrapper renders `symbiote-switch-managed` and runs its own
25
- // lastNativeReport/snap-back lifecycle, so the engine's copy attaches only to the bare tag.
26
- registerSwitchBehavior();
27
- // Image owns no runtime — its behavior is a prop FOLD and nothing else, and it is registered on the
28
- // same `symbiote-image` the wrapper already emits rather than on a `-managed` twin. That is safe
29
- // only because the mapping is idempotent, which `core/components/src/behaviors/image.test.ts`
30
- // asserts rather than assumes; a wrapper-built node simply folds a second time and nothing moves.
31
- registerImageBehavior();
32
- // Fold-only, and it shares the wrapper's tag for the same reason Image does: the mapping has no
33
- // aliasing at all, so a wrapper-built node folding a second time moves nothing.
34
- // `core/components/src/behaviors/input-accessory-view.test.ts` asserts that rather than assuming it.
35
- registerInputAccessoryViewBehavior();
9
+ // WHY A SIDE-EFFECT SUBPATH AND NOT A BARREL EXPORT. Metro turns on `inlineRequires` for PRODUCTION
10
+ // only: it moves a `require` down to the first place its binding is used as a VALUE, and
11
+ // `export { X } from './x'` compiles to a lazy getter. A registration reached that way never
12
+ // evaluates in a Release build — invisible to tsc, to vitest, and to grepping the bundle. A bare
13
+ // `import` placed NEXT TO a re-export of the same specifier does not help either: Babel merges the
14
+ // two into one dependency and the merged dependency stays lazy.
15
+ import '@symbiote-native/components/register';
@@ -2,47 +2,67 @@
2
2
  // the engine's tiny mutation API; the engine owns all Fabric clone-on-write, so Vue
3
3
  // drives the exact same retained tree React does: the proof the core is framework-
4
4
  // agnostic.
5
- import { createRenderer } from '@vue/runtime-core';
6
- import { appendChild, createAnchor, createElement, createRawText, dlog, insertBefore, removeChild, routeProp, setProp, setText, toPublicInstance, RAW_TEXT_COMPONENT, SymbioteSurface, } from '@symbiote-native/engine';
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';
7
7
  import { descriptorFor } from '@symbiote-native/components';
8
8
  import { normalizeVueAttrKey } from '../utils/normalize-attrs.js';
9
9
  function isSurface(parent) {
10
10
  return parent instanceof SymbioteSurface;
11
11
  }
12
12
  function isRawText(node) {
13
- return node.component === RAW_TEXT_COMPONENT;
13
+ return isRawTextNode(node);
14
14
  }
15
- // RN's Text.js applies two defaults on the way to native (core/components/src/text-props.ts:
16
- // ellipsizeMode 'tail', allowFontScaling true unless literally false). The Vue <Text> wrapper
17
- // folded them with resolveTextProps; a template that the SFC transformer lowered to the
18
- // intrinsic `symbiote-text` has no wrapper, so the renderer seeds them instead. Without this a
19
- // numberOfLines={1} line clips mid-word with no ellipsis — device-observed, and silent.
20
- const TEXT_DEFAULTS = new Map([
21
- ['ellipsizeMode', 'tail'],
22
- ['allowFontScaling', true],
23
- ]);
24
- function seedTextDefaults(node) {
25
- for (const [key, value] of TEXT_DEFAULTS)
26
- setProp(node, key, value);
27
- }
28
- // RN's `id` is the modern W3C-named alias for `nativeID` — View.js copies it over
29
- // (`processedProps.nativeID = id`), so the two name ONE native prop. React folds it in its
30
- // component wrapper and Svelte and Solid in their transforms; Vue had it nowhere, so `<View
31
- // id="x">` reached Fabric with an unknown `id` and no `nativeID`, silently and on device only.
32
- // It lives in the renderer rather than in a transform because that covers all four Vue paths at
33
- // once — lowered SFC, lowered TSX, the component wrapper, and a hand-written
34
- // `h('symbiote-view', { id })` no compiler ever sees.
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.
29
+ // RN-style event prop naming ('onPress', 'onValueChange', ...), the same convention JSX itself
30
+ // uses to separate an event from a plain value prop — good enough to decide whether to wrap,
31
+ // without duplicating routeProp's own ViewConfig-derived event lists here.
32
+ const EVENT_PROP_NAME = /^on[A-Z]/;
33
+ // A native event (a Fabric touch, a responder gesture) invokes a Pressable/TextInput/etc.
34
+ // listener straight out of the engine's own event dispatch (core/engine/src/dispatch.ts,
35
+ // core/components/src/behaviors/pressable.ts's `dispatch`), entirely outside anything Vue ever
36
+ // wraps — no render, no watcher, no lifecycle hook sits between the native event and the app's
37
+ // callback. So a throw inside `onPress={throwNow}` was a genuinely UNCAUGHT JS exception: it
38
+ // skipped `onErrorCaptured`, skipped `app.config.errorHandler`, skipped even this adapter's own
39
+ // default reporter (render.ts's `reportToHost`) and reached Hermes's own top-level handler
40
+ // directly — the raw, component-stack-less redbox this was device-reported as, not the "vue
41
+ // render (...)" framed one `reportToHost` produces. Real Vue DOM has the identical seam
42
+ // (`patchEvent`'s `createInvoker`) for the identical reason: a DOM event is native too.
35
43
  //
36
- // Caveat, and it matches what Solid's compile-time rename already does: with BOTH `id` and
37
- // `nativeID` on one element the last patchProp wins, where upstream gives `id` priority
38
- // unconditionally. Honouring that needs per-node state to remember an `id` arrived; no example or
39
- // test sets both, so the state is not worth carrying.
40
- const PROP_ALIASES = new Map([['id', 'nativeID']]);
41
- // An explicit `undefined` must NOT clear one of those defaults: RN treats a missing prop and an
42
- // explicit undefined alike, and only a literal `false` opts out of allowFontScaling. Reached
43
- // only when a value is already undefined, so it costs nothing on the hot path.
44
- function textDefaultFor(node, key) {
45
- return node.isText ? TEXT_DEFAULTS.get(key) : undefined;
44
+ // Wrapping here, at `patchProp`, needs the OWNING instance — and `getCurrentInstance()` cannot
45
+ // supply it: Vue resets `currentRenderingInstance` at the END of `renderComponentRoot`, BEFORE
46
+ // the subsequent `patch()` call that actually reaches `mountElement`/`patchProp`, so by the time
47
+ // this runs it always reads null (measured via a real thrown press listener — `handleError` never
48
+ // found the `onErrorCaptured` ancestor because `if (instance)` was false). Real Vue DOM does not
49
+ // use `getCurrentInstance()` for this either: `hostPatchProp` is called with an explicit 6th
50
+ // argument, `parentComponent`, threaded down through `patch`/`mountElement` from the component
51
+ // whose render produced the vnode — the same instance the DOM renderer's own `patchEvent` passes
52
+ // to `callWithAsyncErrorHandling`. That parameter, not the global pointer, is the correct capture.
53
+ //
54
+ // `callWithErrorHandling` (the SYNC form, not `callWithAsyncErrorHandling`) is deliberate: a
55
+ // responder-negotiation callback (`onStartShouldSetResponder`, ...) returns a boolean the engine
56
+ // reads synchronously, and the sync form is the one that returns the call's result directly
57
+ // rather than folding it into promise-handling for the multi-hook case.
58
+ function wrapListenerForErrorHandling(listener, instance) {
59
+ const wrapper = (...args) => callWithErrorHandling(listener, instance, ErrorCodes.NATIVE_EVENT_HANDLER, args);
60
+ // Some listeners are marker-carrying functions, not plain callbacks — Animated.event's
61
+ // __getHandler() (core/engine/src/animated/event.ts) hands back exactly this shape,
62
+ // `Object.assign((...) => {...}, { __getEvent: () => this })`, and the engine's native-driver
63
+ // attach path reads `__getEvent` off the prop value it was given. A fresh closure with none of
64
+ // the original's own properties silently breaks that — carry them forward.
65
+ return Object.assign(wrapper, listener);
46
66
  }
47
67
  // One renderer per mounted surface: the options close over the surface so every mutation
48
68
  // can ask it to (microtask-coalesced) recommit. Vue has no resetAfterCommit; instead
@@ -53,12 +73,10 @@ export function createSymbioteRenderer(surface) {
53
73
  createElement(type) {
54
74
  const descriptor = descriptorFor(type);
55
75
  // `type` as the third argument, not just `descriptor.component`: the behavior registry is
56
- // keyed by the INTRINSIC TAG (`symbiote-pressable`), while a node only ever carries the
57
- // resolved Fabric name (`RCTView`). This is the one place that still holds both, so a
58
- // lowered primitive whose machine lives on the engine node can be matched at all.
76
+ // keyed by the INTRINSIC TAG (`pressable`), while a node only ever carries the
77
+ // resolved Fabric name (`RCTView`). This is the one place that holds both, so a primitive
78
+ // whose machine lives on the engine node can be matched at all.
59
79
  const node = createElement(descriptor.component, descriptor.isText, type);
60
- if (descriptor.isText)
61
- seedTextDefaults(node);
62
80
  // The imperative public-instance API (measure / setNativeProps / focus / …) is already on
63
81
  // the node's prototype, so a template/function ref to a host element exposes it exactly
64
82
  // like React's getPublicInstance and toPublicInstance is the identity. The ref must keep
@@ -69,7 +87,16 @@ export function createSymbioteRenderer(surface) {
69
87
  // one benchmark press — and a literal is built at the call site before dlog can decide
70
88
  // anything (see core/engine/src/debug.ts).
71
89
  dlog(() => `vue createElement ${descriptor.component} -> public instance`);
72
- return toPublicInstance(node);
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.
99
+ return markRaw(toPublicInstance(node));
73
100
  },
74
101
  createText(text) {
75
102
  // Vue mounts Fragment boundaries (v-for / v-if lists / multi-root) as EMPTY text
@@ -98,25 +125,26 @@ export function createSymbioteRenderer(surface) {
98
125
  // this instead of insert() when an element's children collapse to a single string, so
99
126
  // without the check a raw text lands under a non-Text parent - an invalid Fabric tree
100
127
  // built silently, which is worse than the throw insert() would have given.
101
- if (!el.isText) {
128
+ if (!isTextContainer(el)) {
102
129
  throw new Error(`Text string "${text}" must be rendered inside a <Text>`);
103
130
  }
104
131
  // An RCTText carries its string as a single raw-text child. Reuse a lone existing
105
132
  // one to avoid churn; otherwise replace all children with a fresh raw-text node.
106
- const [first] = el.children;
107
- if (el.children.length === 1 && first !== undefined && isRawText(first)) {
133
+ const existing = childrenOf(el);
134
+ const [first] = existing;
135
+ if (existing.length === 1 && first !== undefined && isRawText(first)) {
108
136
  setText(first, text);
109
137
  }
110
138
  else {
111
- for (const child of el.children.slice())
139
+ for (const child of existing.slice())
112
140
  removeChild(el, child);
113
141
  appendChild(el, createRawText(text));
114
142
  }
115
143
  surface.requestCommit();
116
144
  },
117
145
  insert(child, parent, anchor) {
118
- if (isRawText(child) && (isSurface(parent) || !parent.isText)) {
119
- throw new Error(`Text string "${String(child.props.text)}" must be rendered inside a <Text>`);
146
+ if (isRawText(child) && (isSurface(parent) || !isTextContainer(parent))) {
147
+ throw new Error(`Text string "${textOf(child) ?? ''}" must be rendered inside a <Text>`);
120
148
  }
121
149
  if (isSurface(parent)) {
122
150
  if (anchor)
@@ -135,7 +163,7 @@ export function createSymbioteRenderer(surface) {
135
163
  remove(child) {
136
164
  // A top-level node has no parent (it lives in surface.children); everything else
137
165
  // detaches from its retained parent.
138
- const parent = child.parent;
166
+ const parent = parentOf(child);
139
167
  if (parent !== undefined)
140
168
  removeChild(parent, child);
141
169
  else
@@ -143,27 +171,53 @@ export function createSymbioteRenderer(surface) {
143
171
  surface.requestCommit();
144
172
  },
145
173
  parentNode(node) {
146
- return node.parent ?? surface;
174
+ return parentOf(node) ?? surface;
147
175
  },
148
176
  nextSibling(node) {
149
- const siblings = node.parent !== undefined ? node.parent.children : surface.children;
150
- const index = siblings.indexOf(node);
151
- return index >= 0 ? (siblings[index + 1] ?? null) : null;
177
+ // `?? null` because Vue's RendererOptions types the miss as null, not undefined; the
178
+ // engine answers undefined uniformly and the surface fallback lives there now.
179
+ return nextSiblingOf(node, surface) ?? null;
152
180
  },
153
- patchProp(el, key, _prev, next) {
181
+ patchProp(el, key, prev, next, _namespace, parentComponent) {
154
182
  if (isSurface(el))
155
183
  return;
156
184
  // Kebab -> camel happens HERE, not only inside a component wrapper: the SFC transformer
157
185
  // lowers View/Text to their intrinsic tags (metro-vue-transformer.cjs), so those props
158
186
  // arrive one key at a time with no component to fold the bag. Idempotent for the wrapped
159
187
  // path, which already normalized.
160
- const normalized = normalizeVueAttrKey(key);
161
- const name = PROP_ALIASES.get(normalized) ?? normalized;
188
+ const name = normalizeVueAttrKey(key);
189
+ const value = next;
190
+ const routed = EVENT_PROP_NAME.test(name) && typeof value === 'function'
191
+ ? wrapListenerForErrorHandling(value, parentComponent ?? null)
192
+ : value;
162
193
  // routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on a
163
194
  // View becomes a listener; onTintColor on a Switch stays a prop), shared with React. The
164
195
  // class/style merge (explicit :style always winning, regardless of which of Vue's two
165
196
  // independent patchProp calls lands last) is centralized there too (core/engine/src/node.ts).
166
- routeProp(el, name, next === undefined ? textDefaultFor(el, name) : next);
197
+ //
198
+ // `value` REACHES HERE UNCHANGED, on every re-render, and that is upstream by design:
199
+ // `patchProps` excludes it from its own diff and then patches it on its own line
200
+ // (@vue/runtime-core 3.5.39 — `if (next !== prev && key !== "value")`, then
201
+ // `if ("value" in newProps)`). The reason is a DOM one: typing mutates `el.value` directly,
202
+ // so what Vue last SET is not what the element now HOLDS, and only the element can say.
203
+ // Upstream pairs it with a guard in the patcher, and that half we did not have — `patchDOMProp`
204
+ // writes only on a difference against the element's live value (@vue/runtime-dom,
205
+ // `if (oldValue !== newValue || !("_value" in el))`). So every `<text-input>` re-routed its
206
+ // value on every re-render of its parent: 1 000 writes and ~6 000 wire slots per relabel of
207
+ // the benchmark list, against solid's 0 for the identical tree.
208
+ //
209
+ // On `prev` rather than on the node's own prop: the divergence upstream protects against is
210
+ // not visible from here — native text lives on the far side and TextInput's behavior owns the
211
+ // mirror — and asking the host would cost a crossing per input per render to be told the
212
+ // declarative value, which is what `prev` already is.
213
+ //
214
+ // The COMMIT REQUEST still goes out, which is why this skips the route and does not return.
215
+ // The controlled handshake hangs off the commit beat, not off the write: `afterCommit` reads
216
+ // the node's own `value` against its native mirror. An idle commit is O(1) (F-12) and its
217
+ // post-commit hooks run whether or not the commit reached the host (F-7).
218
+ if (key !== 'value' || prev !== next) {
219
+ routeProp(el, name, routed);
220
+ }
167
221
  surface.requestCommit();
168
222
  },
169
223
  // RN has no querySelector / scope-id / innerHTML. The first two are inert; static
@@ -1,7 +1,6 @@
1
1
  export * from '@vue/runtime-core';
2
2
  import { type ObjectDirective } from '@vue/runtime-core';
3
3
  import { type ISymbioteNode } from '@symbiote-native/engine';
4
- export { Teleport, type ITeleportTarget } from '../create-portal';
5
4
  export declare function normalizeProps(props: Record<string, unknown> | null | undefined): Record<string, unknown> | null;
6
5
  export declare function normalizeStyle(value: unknown): unknown;
7
6
  export declare function mergeProps(...args: Record<string, unknown>[]): Record<string, unknown>;
@@ -28,6 +27,16 @@ type IEventHandler<TEvent> = ((event: TEvent, ...args: never[]) => unknown) & {
28
27
  * typed as required, matching upstream Vue's own declaration — its runtime-only `!fn` guard below
29
28
  * (for a compiler-generated call site that could pass a falsy handler) isn't reflected in the
30
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`.
31
40
  */
32
41
  export declare function withModifiers<TEvent extends IModifierGuardableEvent>(fn: IEventHandler<TEvent>, modifiers: readonly IEventModifier[]): IEventHandler<TEvent>;
33
42
  type IKeyedEvent = {