@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
@@ -6,17 +6,27 @@
6
6
  // real HTMLElement - we have no DOM, so this module supplies our OWN implementation under the
7
7
  // same export name instead of leaving `v-show` a silent no-op.
8
8
  //
9
- // Scope: Vue's own template directives (v-show, Teleport) and, since host-primitive lowering
10
- // landed, `v-model` on an ELEMENT — see vModelText below for why that stopped being out of scope.
9
+ // Scope: Vue's own template directives (v-show, Teleport) and `v-model` on an ELEMENT — see
10
+ // vModelText below for why that is in scope.
11
11
  export * from '@vue/runtime-core';
12
12
  import { getCurrentInstance, mergeProps as baseMergeProps, normalizeClass, normalizeStyle as baseNormalizeStyle, warn, } from '@vue/runtime-core';
13
- import { requestCommitFor, setNativeProps, setProp, whenCommitted, } from '@symbiote-native/engine';
13
+ import { componentOf, propOf, requestCommitFor, setNativeProps, setProp, whenCommitted, } from '@symbiote-native/engine';
14
14
  import { descriptorFor, SWITCH_TAG } from '@symbiote-native/components';
15
- // Shadows runtime-core's own Teleport for every compiled `from 'vue'` import (see the header):
16
- // same component, plus a runtime guard on `to`. Lives in ../create-portal so the adapter's portal
17
- // sits where every other adapter's does; re-exported here because THIS is the module Metro
18
- // rewrites `vue` to, and an `export *` alone would resolve `Teleport` to the unguarded original.
19
- export { Teleport } from '../create-portal/index.js';
15
+ // Do NOT shadow runtime-core's own Teleport here, however tempting the guarded ../create-portal
16
+ // wrapper looks (same reasoning that shadowed it in the first place, and it is wrong for this
17
+ // file specifically). @vue/compiler-sfc recognises the literal tag name "Teleport" as one of
18
+ // Vue's CORE built-ins at compile time — regardless of which value the name resolves to at
19
+ // runtime — and compiles its children as a raw array with a PROPS-only patchFlag (`dynamicProps:
20
+ // ["to"]`), never the DYNAMIC_SLOTS-flagged shape a real user component gets. Substituting a real
21
+ // `defineComponent` under that name makes it go through Vue's ordinary component update path
22
+ // (shouldUpdateComponent), which — for a PROPS-only patchFlag — checks ONLY the props named in
23
+ // `dynamicProps` and never re-renders on a children/slot change at all. Confirmed by reproducing
24
+ // through the real compiler (compileSfc) + a live mount: the wrapper's render() fires once at
25
+ // mount and never again, so a Teleport whose content is gated by v-if (the ordinary "toast"/
26
+ // "modal" shape) silently stops updating the moment `to` itself stays the same. `export *` above
27
+ // already re-exports the real, unwrapped Teleport for this path — leave it there. The guarded
28
+ // wrapper (../create-portal) is still exported from the adapter's main barrel for anyone who
29
+ // constructs the vnode by hand (`h(Teleport, ...)`, e.g. in TSX), where no such codegen applies.
20
30
  // Shadows @vue/shared's own `normalizeProps` for every compiled `from 'vue'` import, for ONE
21
31
  // divergence: a FUNCTION-valued `style` survives it.
22
32
  //
@@ -71,12 +81,10 @@ export function normalizeProps(props) {
71
81
  // contract reads it (`Pressable.js`: `ViewStyleProp | ((state) => ViewStyleProp)` — the callback is
72
82
  // top-level, never an array element, so `:style="[a, fn]"` is out of contract here exactly as it is
73
83
  // there, and is deliberately NOT rescued).
74
- // The THIRD door, and the one that decides whether the state-style split can leave the transforms
75
- // at all. A `:style` binding on a lowered element compiles to `_normalizeStyle(expr)` whenever the
76
- // compiler cannot keep it on the cheap patch-flag path — an inline arrow and a call expression both
77
- // do, a bare identifier does not. So `<Pressable :style="({pressed}) => …" />` loses its callback
78
- // here, and today nothing notices because the lowering transform rewrites that attribute into a
79
- // resting/active pair before Vue ever emits the helper.
84
+ // The THIRD door. A `:style` binding compiles to `_normalizeStyle(expr)` whenever the compiler
85
+ // cannot keep it on the cheap patch-flag path — an inline arrow and a call expression both do, a
86
+ // bare identifier does not. So `<pressable :style="({pressed}) => …">` would lose its callback here
87
+ // without the rescue below.
80
88
  //
81
89
  // A TOP-LEVEL function is preserved; one nested inside an array is not, and that asymmetry is the
82
90
  // contract rather than an omission. React Native types the prop
@@ -117,6 +125,14 @@ export const vShow = {
117
125
  // Drop a still-pending first-commit wait if the element unmounts before it ever lands.
118
126
  unmounted: el => pendingShowCommits.get(el)?.(),
119
127
  };
128
+ function textFromChangeEvent(event) {
129
+ const text = Reflect.get(event, 'text');
130
+ return typeof text === 'string' ? text : undefined;
131
+ }
132
+ function valueFromChangeEvent(event) {
133
+ const value = Reflect.get(event, 'value');
134
+ return typeof value === 'boolean' ? value : undefined;
135
+ }
120
136
  const modelStates = new WeakMap();
121
137
  // Runtime guards, not casts: both slots are read out of untyped bags — `vnode.props` and
122
138
  // `node.props` are `Record<string, unknown>` — and `typeof x === 'function'` alone narrows to the
@@ -163,9 +179,16 @@ function modelStateFor(el) {
163
179
  appListener: undefined,
164
180
  trim: false,
165
181
  number: false,
166
- listener: (text, event) => {
167
- state.appListener?.(text, event);
168
- state.assign?.(applyModelModifiers(state, text));
182
+ listener: event => {
183
+ state.appListener?.(event);
184
+ const text = textFromChangeEvent(event);
185
+ if (text !== undefined) {
186
+ state.assign?.(applyModelModifiers(state, text));
187
+ return;
188
+ }
189
+ const value = valueFromChangeEvent(event);
190
+ if (value !== undefined)
191
+ state.assign?.(value);
169
192
  },
170
193
  };
171
194
  modelStates.set(el, state);
@@ -175,14 +198,14 @@ function modelStateFor(el) {
175
198
  // by `patchProp` during the same patch, so anything captured earlier is either missing or stale. A
176
199
  // prop that is already OUR listener means nothing overwrote it and the app half is unchanged.
177
200
  function syncModelListener(el, state) {
178
- const current = el.props.onValueChange;
201
+ const current = propOf(el, 'onValueChange');
179
202
  if (current === state.listener)
180
203
  return;
181
204
  state.appListener = isValueChangeListener(current) ? current : undefined;
182
205
  setProp(el, 'onValueChange', state.listener);
183
206
  }
184
207
  // Vue's compiler picks the directive by ELEMENT, and for anything it does not recognise as a DOM
185
- // input it emits `vModelText` — including a lowered `<symbiote-switch>`. Stringifying there is what
208
+ // input it emits `vModelText` — `<switch>` included. Stringifying there is what
186
209
  // upstream must do (a DOM input's value IS a string) and what we must not: the Switch behavior
187
210
  // reads `props.value === true`, so `String(true)` pins the control OFF and no tap can move it.
188
211
  // Device-confirmed on `examples/vue-sfc` 2026-09-02, both switches on `CanaryScreen`.
@@ -192,11 +215,12 @@ function syncModelListener(el, state) {
192
215
  // of carrying a copy of both.
193
216
  const SWITCH_COMPONENT = descriptorFor(SWITCH_TAG).component;
194
217
  function isSwitchNode(el) {
195
- return el.component === SWITCH_COMPONENT;
218
+ return componentOf(el) === SWITCH_COMPONENT;
196
219
  }
197
220
  function syncModelValue(el, value) {
198
- // The READ half needs no branch: the behavior calls `onValueChange(value, event)` for both
199
- // primitives, and `applyModelModifiers` passes a non-string through untouched.
221
+ // The READ half needs no branch: the behavior calls `onValueChange(event)` for both primitives
222
+ // with `text`/`value` carried on the event, and `applyModelModifiers` passes a non-string through
223
+ // untouched.
200
224
  if (isSwitchNode(el)) {
201
225
  setProp(el, 'value', value === true);
202
226
  requestCommitFor(el);
@@ -268,6 +292,16 @@ const MODIFIER_GUARDS = {
268
292
  * typed as required, matching upstream Vue's own declaration — its runtime-only `!fn` guard below
269
293
  * (for a compiler-generated call site that could pass a falsy handler) isn't reflected in the
270
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`.
271
305
  */
272
306
  export function withModifiers(fn, modifiers) {
273
307
  if (!fn)
@@ -12,9 +12,9 @@ const KEBAB_SEGMENT = /-([a-z])/g;
12
12
  function toCamel(key) {
13
13
  return key.replace(KEBAB_SEGMENT, (_match, char) => char.toUpperCase());
14
14
  }
15
- // The per-KEY half, for the path that never sees a whole attrs bag: a host primitive lowered to
16
- // its intrinsic tag by the SFC transformer reaches the renderer one patchProp call at a time,
17
- // with no component in between to fold the bag. Returns the key unchanged (same string identity)
15
+ // The per-KEY half, for the path that never sees a whole attrs bag: an intrinsic tag reaches the
16
+ // renderer one patchProp call at a time, with no component in between to fold the bag. Returns the
17
+ // key unchanged (same string identity)
18
18
  // whenever there is nothing to convert, which is every key on the hot path.
19
19
  export function normalizeVueAttrKey(key) {
20
20
  if (!key.includes('-'))
@@ -0,0 +1,52 @@
1
+ // Every tag both Vue compilers must treat as an ELEMENT rather than as a component to resolve.
2
+ //
3
+ // Vue decides element-vs-component in two unrelated places — `@vue/compiler-sfc`'s
4
+ // `isCustomElement` and `@vue/babel-plugin-jsx`'s option of the same name — so the answer has to
5
+ // come from one set or the two paths drift. That drift is not loud: a tag Vue takes for a component
6
+ // compiles to `resolveComponent("text-input")` with its children as a slot the element path never
7
+ // reads, which renders a blank subtree with no error.
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.
15
+ //
16
+ // SCOPE, stated because it is smaller than `ISymbioteIntrinsic`. That union also holds
17
+ // `scroll-view`, `modal`, `activity-indicator` and the rest, but it is a TypeScript type and these
18
+ // consumers are Babel/Metro plugins that run before TS exists. `HOST_PRIMITIVES` is the only
19
+ // runtime-readable table reachable from here, so a hand-written `<scroll-view>` still resolves as a
20
+ // component. Closing that needs a CJS intrinsic table in `core/components`.
21
+ //
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.
32
+ //
33
+ // The tell that the three are still wrapped rather than merely unlisted: `src/register.ts` does
34
+ // NOT call their behaviors, because a registered behavior plus a surviving wrapper is two owners
35
+ // on one node. Those two facts move together or not at all.
36
+ //
37
+ // .cjs, and its own file rather than a copy in each consumer: two transforms carrying one
38
+ // derivation is the drift the shared spec exists to prevent.
39
+
40
+ const {
41
+ HOST_PRIMITIVES,
42
+ } = require('@symbiote-native/components/host-primitives');
43
+
44
+ // BOTH tags a spec entry can emit. A primitive with an `intrinsicWhen` selects between two native
45
+ // views by one prop, so its second intrinsic is as hand-writable as its first.
46
+ module.exports = new Set(
47
+ Object.values(HOST_PRIMITIVES).flatMap(entry =>
48
+ entry.intrinsicWhen === undefined
49
+ ? [entry.intrinsic]
50
+ : [entry.intrinsic, entry.intrinsicWhen.intrinsic],
51
+ ),
52
+ );