@symbiote-native/vue 0.3.8 → 0.4.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.
@@ -1,14 +1,12 @@
1
- // Switch, the Vue lifecycle half. The logic (the lastNativeReport reducer, valueFromChange,
2
- // the snap-back decision) lives in @symbiote-native/components/state and the render in
3
- // @symbiote-native/components/view, both shared verbatim with the React adapter. Here Vue supplies
4
- // the reactivity: a ref holds what native last reported, a function ref grabs the host node,
5
- // and a post-flush watch snaps native back when the parent rejects a toggle. This is the Vue
6
- // twin of the React adapter's useReducer + useLayoutEffect + dispatchViewCommand.
1
+ // Switch, the Vue lifecycle half. The logic (lastNativeReport reducer, valueFromChange, the
2
+ // snap-back decision) lives in @symbiote-native/components/state and the render in
3
+ // @symbiote-native/components/view, both shared verbatim with React. Vue supplies the
4
+ // reactivity: a ref holds what native last reported, a function ref grabs the host node, and a
5
+ // post-flush watch snaps native back when the parent rejects a toggle.
7
6
  //
8
- // Inputs arrive as attrs (untyped), so each is narrowed with a runtime guard rather than a
9
- // cast. onValueChange MUST be stripped from the forwarded attrs: it is not a ViewConfig event
10
- // (it's derived from the native onChange), so leaking it would reach Fabric as a function prop
11
- // and crash Android's folly::dynamic.
7
+ // Inputs arrive as attrs (untyped), so each is narrowed with a runtime guard, never a cast.
8
+ // onValueChange MUST be stripped from the forwarded attrs: it is not a ViewConfig event, so
9
+ // leaking it would reach Fabric as a function prop and crash Android's folly::dynamic.
12
10
  import { defineComponent, ref, shallowRef, watch } from '@vue/runtime-core';
13
11
  import { renderSwitch, switchReducer, createInitialSwitchState, shouldSnapBack, valueFromChange, } from '@symbiote-native/components';
14
12
  import { dispatchViewCommand, isSymbioteNode, dlog, } from '@symbiote-native/engine';
@@ -24,7 +22,6 @@ function asString(value) {
24
22
  function isBoolean(value) {
25
23
  return typeof value === 'boolean';
26
24
  }
27
- // trackColor arrives untyped; keep only the string false/true entries the render fn reads.
28
25
  function normalizeTrackColor(value) {
29
26
  if (!isRecord(value))
30
27
  return undefined;
@@ -36,12 +33,11 @@ function normalizeTrackColor(value) {
36
33
  return trackColor;
37
34
  }
38
35
  // The render fn's style param is a plain object; array/registered styles degrade to undefined
39
- // (the engine flattens those on its own). Same narrowing the Vue ActivityIndicator uses.
36
+ // (the engine flattens those on its own).
40
37
  function isViewStyleObject(value) {
41
38
  return typeof value === 'object' && value !== null && !Array.isArray(value);
42
39
  }
43
- // The prop/handler keys the lifecycle consumes itself; everything else (accessibility,
44
- // testID, …) forwards onto the host node. onValueChange is pure JS and must never reach Fabric.
40
+ // Everything else forwards onto the host node. onValueChange is pure JS and must never reach Fabric.
45
41
  const HANDLED_ATTRS = [
46
42
  'value',
47
43
  'modelValue',
@@ -62,22 +58,15 @@ function forwardAttrs(attrs) {
62
58
  }
63
59
  export function createSwitch(platform) {
64
60
  return defineComponent((_props, { attrs: rawAttrs, emit }) => {
65
- // shallowRef, NOT ref: the engine node must be held by IDENTITY. A plain ref() runs the
66
- // assigned object through Vue's toReactive(), so reading nodeRef.value back yields a
67
- // reactive Proxy, a different object than the raw node. The engine's mirror is a WeakMap
68
- // keyed on the raw node, so every imperative command (dispatchViewCommand / measure /
69
- // setNativeProps) would miss on the Proxy and silently no-op. shallowRef stores the raw
70
- // node untouched. General rule for this adapter: host nodes live in shallowRef / markRaw.
61
+ // shallowRef, NOT ref: a plain ref() would hand back a reactive Proxy the engine's WeakMap
62
+ // mirror doesn't recognize, so every imperative command would silently no-op. General rule
63
+ // for this adapter: host nodes live in shallowRef / markRaw.
71
64
  const nodeRef = shallowRef(null);
72
65
  const state = ref(createInitialSwitchState());
73
- // The host node our Vue renderer creates IS an engine SymbioteNode; Vue hands it to this
74
- // function ref on mount. A stable function ref (defined once) isn't re-invoked per patch.
75
66
  const setNodeRef = (el) => {
76
67
  nodeRef.value = isSymbioteNode(el) ? el : null;
77
68
  };
78
69
  const handleChange = (event) => {
79
- // Vue-facing event is emitted here; the host still receives this internal onChange
80
- // handler so the controlled snap-back state stays synchronized with native.
81
70
  const next = valueFromChange(event);
82
71
  dlog(`Switch onChange value=${String(next)} eventCount=${String(event.nativeEvent.eventCount)}`);
83
72
  if (next === undefined)
@@ -86,11 +75,10 @@ export function createSwitch(platform) {
86
75
  emitModelUpdate(emit, next);
87
76
  state.value = switchReducer(state.value, { type: 'native-reported', value: next });
88
77
  };
89
- // Snap-back: when native reported a value the parent rejected (the value prop did not
90
- // change), command the JS value back down: the controlled-Switch correction RN does via
91
- // SwitchCommands.setValue. The decision is shared with React (shouldSnapBack); only the
92
- // command name is platform-imperative. flush:'post' so the engine has committed the node
93
- // before the command reads its Fabric handle.
78
+ // Snap-back: when native reported a value the parent rejected, command the JS value back
79
+ // down (the controlled-Switch correction RN does via SwitchCommands.setValue). The
80
+ // decision is shared with React (shouldSnapBack); only the command name is platform-
81
+ // imperative. flush:'post' so the node is committed before the command reads its Fabric handle.
94
82
  watch(() => ({
95
83
  fabricValue: resolveModelValue(rawAttrs, isBoolean) === true,
96
84
  switchState: state.value,
@@ -1,17 +1,13 @@
1
- // TextInput, the Vue lifecycle half. The folds/maps (value->text, the W3C/alias resolution)
2
- // and the controlled-write predicate live in @symbiote-native/components/state, the render (intrinsic
3
- // + native-prop mapping) in @symbiote-native/components/view, both shared verbatim with the React
4
- // adapter. Here Vue supplies only the reactivity: a shallowRef holds the host node, a ref holds
5
- // the acknowledged event count (so the exposed handle echoes the latest), setup-scope `let`s
6
- // hold the last native text + focus flag, a post-flush watch runs the controlled-write command,
7
- // and expose() wires the imperative handle. The Vue twin of the React useState/useRef +
8
- // useLayoutEffect + useImperativeHandle.
1
+ // TextInput, the Vue lifecycle half. The folds/maps (value->text, W3C/alias resolution) and the
2
+ // controlled-write predicate live in @symbiote-native/components/state, the render in
3
+ // @symbiote-native/components/view, both shared verbatim with React. Vue supplies only the
4
+ // reactivity: a shallowRef holds the host node, a ref holds the acknowledged event count, setup-
5
+ // scope `let`s hold the last native text + focus flag, a post-flush watch runs the
6
+ // controlled-write command, and expose() wires the imperative handle.
9
7
  //
10
- // Inputs arrive as attrs (untyped), so each is narrowed with a runtime guard rather than a cast.
11
- // onValueChange MUST be stripped from the forwarded attrs: it is not a ViewConfig event (it's
12
- // derived from the native onChange), so leaking it would reach Fabric as a function prop and
13
- // crash Android's folly::dynamic. The imperative module (blurTextInput / setInput*) is imported
14
- // from @symbiote-native/engine, the same framework-agnostic singleton both adapters share.
8
+ // Inputs arrive as attrs (untyped), so each is narrowed with a runtime guard, never a cast.
9
+ // onValueChange MUST be stripped from the forwarded attrs: it is not a ViewConfig event, so
10
+ // leaking it would reach Fabric as a function prop and crash Android's folly::dynamic.
15
11
  import { defineComponent, onBeforeUnmount, ref, shallowRef, watch } from '@vue/runtime-core';
16
12
  import { resolveAccessibilityProps, resolveTextInputProps, renderTextInput, foldText, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from '@symbiote-native/components';
17
13
  import { dispatchViewCommand, isSymbioteNode, dlog, blurTextInput, setInputFocused, setInputBlurred, whenCommitted, } from '@symbiote-native/engine';
@@ -30,7 +26,6 @@ function isString(value) {
30
26
  function asBoolean(value) {
31
27
  return typeof value === 'boolean' ? value : undefined;
32
28
  }
33
- // selection arrives untyped; keep only the numeric start/end the render fn + controlled write read.
34
29
  function normalizeSelection(value) {
35
30
  if (!isRecord(value))
36
31
  return undefined;
@@ -41,9 +36,7 @@ function normalizeSelection(value) {
41
36
  selection.end = value.end;
42
37
  return selection;
43
38
  }
44
- // The prop/handler keys the lifecycle consumes itself (mirrors the React adapter's destructure);
45
- // everything else (placeholder, secureTextEntry, the remaining native events, accessibility,
46
- // testID, style…) forwards onto the host node. onFocus/onBlur are re-supplied as our wrapped
39
+ // Everything else forwards onto the host node. onFocus/onBlur are re-supplied as our wrapped
47
40
  // handlers; onValueChange is pure JS and must never reach Fabric.
48
41
  const HANDLED_ATTRS = [
49
42
  'value',
@@ -80,38 +73,30 @@ function forwardAttrs(attrs) {
80
73
  return result;
81
74
  }
82
75
  export const TextInput = defineComponent((_props, { attrs: rawAttrs, emit, expose }) => {
83
- // shallowRef, NOT ref: the engine node must be held by IDENTITY. A plain ref() runs the node
84
- // through Vue's toReactive(), handing back a reactive Proxy, a different object than the raw
85
- // node the engine's WeakMap mirror is keyed on, so every imperative command
86
- // (dispatchViewCommand / focus / blur / setTextAndSelection) would miss and silently no-op.
87
- // Same rule applies to the Switch / ScrollView host node.
76
+ // shallowRef, NOT ref: a plain ref() would hand back a reactive Proxy the engine's WeakMap
77
+ // mirror doesn't recognize, so every imperative command would silently no-op. Same rule as
78
+ // the Switch / ScrollView host node.
88
79
  const nodeRef = shallowRef(null);
89
80
  const setNodeRef = (el) => {
90
81
  nodeRef.value = isSymbioteNode(el) ? el : null;
91
82
  };
92
- // The count native last acknowledged. A ref so the render echoes it back as
93
- // mostRecentEventCount and the exposed handle reads the latest; the controlled write commands
94
- // it so native's eventLag lands on 0.
83
+ // The count native last acknowledged. A ref so render echoes it as mostRecentEventCount; the
84
+ // controlled write commands it so native's eventLag lands on 0.
95
85
  const mostRecentEventCount = ref(INITIAL_EVENT_COUNT);
96
- // The last text native holds, as far as JS knows. Seeded from the mount-time value (the `text`
97
- // prop already carries it down via createNode, so the FIRST controlled value is not a
98
- // divergence and must NOT re-command). A setup-scope `let`: no render needed when it changes.
86
+ // Seeded from the mount-time value (the `text` prop already carries it down via createNode,
87
+ // so the FIRST controlled value is not a divergence and must NOT re-command).
99
88
  let lastNativeText = foldText(resolveModelValue(rawAttrs, isString), asString(rawAttrs.defaultValue));
100
- // JS-side focus state, mirrored from the focus/blur events for isFocused(): native exposes no
101
- // synchronous focus getter (RN's TextInputState holds the same).
89
+ // Mirrored from focus/blur events for isFocused(): native exposes no synchronous focus getter.
102
90
  let focused = false;
103
- // autoFocus fires once when the node first commits; guard so a later node identity change does
104
- // not re-focus.
91
+ // Guard so a later node identity change does not re-focus.
105
92
  let autoFocused = false;
106
93
  const handleChange = (event) => {
107
- // Event seam: the controlled handshake hinges on the change payload carrying `text`
108
- // (+ `eventCount`). iOS and Android Fabric can key these differently, so log the actual shape.
94
+ // iOS and Android Fabric can key the change payload differently, so log the actual shape.
109
95
  dlog(`TextInput change keys=[${Object.keys(event.nativeEvent).join(',')}] ` +
110
96
  `text=${JSON.stringify(event.nativeEvent.text)} count=${JSON.stringify(event.nativeEvent.eventCount)}`);
111
97
  const text = textFromChange(event);
112
98
  if (text !== undefined) {
113
- // Record the text first, then the count, so the count never runs ahead of the text it
114
- // stands for.
99
+ // Record the text first, so the count never runs ahead of the text it stands for.
115
100
  lastNativeText = text;
116
101
  emit('valueChange', text, event);
117
102
  emitModelUpdate(emit, text);
@@ -136,10 +121,8 @@ export const TextInput = defineComponent((_props, { attrs: rawAttrs, emit, expos
136
121
  emit('blur', event);
137
122
  };
138
123
  // Controlled write: when JS-side `value` diverges from what native reported, command the new
139
- // text down with the acknowledged count; a plain prop re-push would race the user's
140
- // keystrokes. Watching `value` covers every divergence (the parent only rewrites `value` after
141
- // a change). flush:'post' so the engine has committed the node before the command reads its
142
- // Fabric handle; the predicate makes it a no-op on mount (value === the seed).
124
+ // text down with the acknowledged count - a plain prop re-push would race the user's
125
+ // keystrokes. flush:'post' so the node is committed before the command reads its Fabric handle.
143
126
  watch(() => resolveModelValue(rawAttrs, isString), value => {
144
127
  const node = nodeRef.value;
145
128
  if (node === null)
@@ -158,11 +141,10 @@ export const TextInput = defineComponent((_props, { attrs: rawAttrs, emit, expos
158
141
  ]);
159
142
  lastNativeText = value;
160
143
  }, { flush: 'post' });
161
- // autoFocus is driven in JS, not as a native prop: once the node first commits, command `focus`
162
- // down once (RN does the same via TextInputState.focusInput). The watch fires when nodeRef is
163
- // set (mount), but under Vue's async-batched commit the node has no Fabric tag yet at post-flush,
164
- // so the focus command would be skipped with no retry. whenCommitted defers it to the commit that
165
- // assigns the tag. Cancelled on unmount so an un-committed pending focus can't leak.
144
+ // autoFocus is driven in JS: once the node first commits, command `focus` down once. Under
145
+ // Vue's async-batched commit the node has no Fabric tag yet at post-flush, so
146
+ // whenCommitted defers it to the commit that assigns the tag. Cancelled on unmount so an
147
+ // un-committed pending focus can't leak.
166
148
  let cancelAutoFocus;
167
149
  watch(nodeRef, node => {
168
150
  if (autoFocused || node === null || rawAttrs.autoFocus !== true)
@@ -172,10 +154,8 @@ export const TextInput = defineComponent((_props, { attrs: rawAttrs, emit, expos
172
154
  cancelAutoFocus = whenCommitted(node, () => dispatchViewCommand(node, 'focus', []));
173
155
  }, { flush: 'post' });
174
156
  onBeforeUnmount(() => cancelAutoFocus?.());
175
- // The imperative API RN exposes on the ref. The methods read nodeRef.value / the count LIVE,
176
- // so no stale capture: the Vue twin of React's useImperativeHandle. focus/blur drive native
177
- // view commands; clear and setSelection reuse setTextAndSelection (the same stale-safe path as
178
- // a controlled write) echoing the acknowledged event count.
157
+ // Methods read nodeRef.value / the count LIVE, so no stale capture. clear/setSelection reuse
158
+ // setTextAndSelection, the same path as a controlled write.
179
159
  expose({
180
160
  focus: () => {
181
161
  const node = nodeRef.value;
@@ -477,8 +477,9 @@ export const VirtualizedList = defineComponent((props, { attrs, expose, emit, sl
477
477
  ? INVERTED_X_STYLE
478
478
  : INVERTED_Y_STYLE
479
479
  : undefined;
480
- for (let cellPos = 0; cellPos < plan.cells.length; cellPos += 1) {
481
- const cell = plan.cells[cellPos];
480
+ // Wraps one cell identically whether it's the force-mounted sticky cell ahead of the
481
+ // window or an in-window cell from the loop below — same measuring View, same key shape.
482
+ const pushCell = (cell) => {
482
483
  const item = p.getItem(p.data, cell.index);
483
484
  const content = p.renderItem?.({
484
485
  item,
@@ -490,8 +491,24 @@ export const VirtualizedList = defineComponent((props, { attrs, expose, emit, sl
490
491
  onLayout: makeCellMeasure(cell.index),
491
492
  style: cellInverted,
492
493
  }, [content]));
494
+ };
495
+ // The nearest sticky section header below the window (RN _ensureClosestStickyHeader):
496
+ // force-mounted so it never gets destroyed/recreated while pinned off-window. Rendered
497
+ // ahead of the main window loop, with its own gap spacer to the window's first cell.
498
+ if (plan.forcedStickyCell !== undefined) {
499
+ pushCell(plan.forcedStickyCell);
500
+ if (plan.gapExtent > EMPTY_OFFSET) {
501
+ children.push(h('symbiote-view', {
502
+ key: 'spacer-gap',
503
+ style: p.horizontal ? { width: plan.gapExtent } : { height: plan.gapExtent },
504
+ }));
505
+ }
506
+ }
507
+ for (let cellPos = 0; cellPos < plan.cells.length; cellPos += 1) {
508
+ const cell = plan.cells[cellPos];
509
+ pushCell(cell);
493
510
  if (cell.index < m.last) {
494
- const separator = renderSeparatorElement(p.itemSeparatorComponent, item, p.getItem(p.data, cell.index + 1), separatorOverrides.get(cell.index));
511
+ const separator = renderSeparatorElement(p.itemSeparatorComponent, p.getItem(p.data, cell.index), p.getItem(p.data, cell.index + 1), separatorOverrides.get(cell.index));
495
512
  if (separator !== undefined) {
496
513
  children.push(h('symbiote-view', { key: `sep-${cell.key}` }, [separator]));
497
514
  }
@@ -1,32 +1,26 @@
1
1
  // VirtualizedSectionList, the Vue wrapper that flattens sections into one virtualized stream
2
- // over VirtualizedList. Each section contributes a header row, its item rows, then a footer row
3
- // (RN counts 2 per section); the flattened tagged sequence is fed to VirtualizedList as one list,
4
- // so headers/items/footers are all windowed by the same machinery. The flattening, entry keying,
5
- // separator-item unwrap, and scrollToLocation mapping are shared from @symbiote-native/components; this
6
- // file wires Vue lifecycle (typed-prop inputs + handle re-expose + the per-entry render dispatch).
7
- // The Vue twin of the React adapter's VirtualizedSectionList.
2
+ // over VirtualizedList: each section contributes a header row, its item rows, then a footer row
3
+ // (RN counts 2 per section), and the flattened tagged sequence is windowed by the same
4
+ // machinery as one list. Flattening, entry keying, separator-item unwrap, and scrollToLocation
5
+ // mapping are shared from @symbiote-native/components; this file wires Vue lifecycle
6
+ // (typed-prop inputs + handle re-expose + per-entry render dispatch).
8
7
  //
9
- // Typed-emits generic component (mirrors FlatList / VirtualizedList): a GENERIC setup function
10
- // `<ItemT,>(props: IVirtualizedSectionListProps<ItemT>, ctx: ICtx<IVirtualizedSectionListEmits>)`
11
- // so the section inputs (sections/renderItem/renderSectionHeader/…) infer ItemT at the call site.
12
- // For that, those inputs are read from typed `props` (declared in the runtime `props` array); the
13
- // VirtualizedList passthrough tail (refreshing/style/raw scroll/…) rides through `$attrs` onto the
14
- // inner list. The three adapter-synthesized events (endReached/startReached/refresh — the exact set
15
- // React's section list exposes) stay GATED on listener presence: each emit bridge is wired ONLY
16
- // when the consumer listens (read off the instance vnode props), so the inner VirtualizedList keeps
17
- // building RefreshControl / computing edge-reached strictly on demand.
8
+ // Typed-emits generic component (mirrors FlatList/VirtualizedList): a GENERIC setup function so
9
+ // section inputs infer ItemT at the call site. Those inputs are read from typed `props`
10
+ // (declared in the runtime `props` array); the VirtualizedList passthrough tail rides through
11
+ // $attrs onto the inner list. The three adapter-synthesized events (endReached/startReached/
12
+ // refresh) stay GATED on listener presence so the inner VirtualizedList keeps building
13
+ // RefreshControl / computing edge-reached strictly on demand.
18
14
  import { defineComponent, getCurrentInstance, h, shallowRef, } from '@vue/runtime-core';
19
15
  import { flattenSections, resolveStickySectionHeaders, scrollLocationToFlatIndex, sectionEntryKey, unwrapEntryItem, } from '@symbiote-native/components';
20
16
  import { Platform, dlog, } from '@symbiote-native/engine';
21
17
  import { VirtualizedList } from '../virtualized-list/index.js';
22
18
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
23
- // VirtualizedList is a generic component (generic construct signature), which h()'s overloads can't
24
- // resolve. Drive it through a loose functional-component handle (generic-component h() limitation).
19
+ // VirtualizedList's generic construct signature can't be resolved by h()'s overloads, so drive
20
+ // it through a loose functional-component handle instead.
25
21
  const VirtualizedListHost = VirtualizedList;
26
- // The typed inputs VSL reads off `props`; everything else (the VirtualizedList passthrough tail and
27
- // raw scroll) falls through $attrs onto the inner list. Listed for the runtime `props` declaration
28
- // (keyof can't derive it: the index signature widens keyof to `string`). The three emit events are
29
- // deliberately absent (declared as emits below).
22
+ // Listed for the runtime `props` declaration (keyof can't derive it: the index signature widens
23
+ // keyof to `string`). The three emit events are deliberately absent (declared as emits below).
30
24
  const PROP_KEYS = ['sections', 'keyExtractor', 'stickySectionHeadersEnabled'];
31
25
  const EMIT_KEYS = ['endReached', 'startReached', 'refresh'];
32
26
  function isRecord(value) {
@@ -68,10 +62,8 @@ export const VirtualizedSectionList = defineComponent((props, { attrs, expose, e
68
62
  // headerIndices change each render (sections may change); the delegate reads them lazily.
69
63
  let headerIndices = [];
70
64
  expose(buildSectionDelegate(() => inner.value, () => headerIndices));
71
- // The three section-list events are emits, so Vue strips their onX listeners from $attrs. Detect
72
- // listener presence off the instance's own vnode props (what the parent passed), exactly like
73
- // FlatList/VirtualizedList, so an emit bridge is wired ONLY when the consumer actually listens
74
- // (preserving the inner list's RefreshControl / edge-reached gating).
65
+ // Emits are stripped from $attrs by Vue; detect listener presence off the instance's own
66
+ // vnode props instead, so an emit bridge is wired ONLY when the consumer actually listens.
75
67
  const instance = getCurrentInstance();
76
68
  const listens = (onName) => {
77
69
  const vnodeProps = instance?.vnode.props;
@@ -11,8 +11,6 @@
11
11
  import { h } from '@vue/runtime-core';
12
12
  import { normalizeVueAttrs } from './utils/normalize-attrs.js';
13
13
  function hostComponent(intrinsic, name) {
14
- // A functional component's ctx is Omit<SetupContext, 'expose'> (no instance to expose); let the
15
- // FunctionalComponent target infer the param types rather than annotate SetupContext.
16
14
  // normalizeVueAttrs folds kebab template props (:accessibility-label) to the RN camelCase contract.
17
15
  const component = (_props, { slots, attrs }) => h(intrinsic, normalizeVueAttrs(attrs), slots.default !== undefined ? slots.default() : undefined);
18
16
  component.displayName = name;
@@ -22,5 +20,5 @@ function hostComponent(intrinsic, name) {
22
20
  export const View = hostComponent('symbiote-view', 'View');
23
21
  export const Text = hostComponent('symbiote-text', 'Text');
24
22
  // Image is NOT a bare host primitive: it needs the shared fold (source/src/srcSet resolution,
25
- // width/height → style, alt → accessibility) + the Image statics, so it lives in ./image as a
23
+ // width/height -> style, alt -> accessibility) + the Image statics, so it lives in ./image as a
26
24
  // functional component over renderImage. View/Text stay bare; they forward attrs verbatim.
@@ -8,10 +8,8 @@
8
8
  // engine so React and Vue share one definition.
9
9
  //
10
10
  // The node -> tag resolution itself belongs to the engine (getNativeTag, keyed on the raw
11
- // node in the commit mirror); this only adapts the Vue-shaped inputs onto it. Accepts, in
12
- // RN's idempotent spirit: a bare number (passes through), an engine host node (a ref's
13
- // .value), a Vue Ref carrying either (unwrapped), or null / undefined (-> null). An
14
- // uncommitted or unknown input has no tag yet and surfaces as null.
11
+ // node in the commit mirror); this only unwraps the Vue-shaped inputs (a Ref or a bare node)
12
+ // onto it. An uncommitted or unknown input has no tag yet and surfaces as null.
15
13
  import { isRef, toRaw } from '@vue/runtime-core';
16
14
  import { getNativeTag, isSymbioteNode, dlog } from '@symbiote-native/engine';
17
15
  export function findNodeHandle(componentOrHandle) {
package/build/index.d.ts CHANGED
@@ -58,8 +58,9 @@ export type { IAlertType, IAlertButtonStyle, IAlertButton, IAlertButtons, IAlert
58
58
  export { Platform, StyleSheet, processColor, setColorProcessor, dlog, isDebug, } from '@symbiote-native/engine';
59
59
  export type { ISymbioteEvent, ISymbioteNode, IRootTag } from '@symbiote-native/engine';
60
60
  export type { IViewStyle, ITextStyle, IFlexAlign, IFlexJustify, IPlatformStatic, IPlatformOSType, IPlatformConstantsIOS, IPlatformConstantsAndroid, IPlatformSelectSpec, } from '@symbiote-native/engine';
61
- export { setNativeViewConfigSource } from '@symbiote-native/engine';
61
+ export { setNativeViewConfigSource, setDeviceEventSource } from '@symbiote-native/engine';
62
62
  export type { INativeViewConfig, INativeViewConfigSource } from '@symbiote-native/engine';
63
+ export type { IEnterKeyHint, IInputMode, ISubmitBehavior, ITextInputSelection, IImageStatics, } from '@symbiote-native/components';
63
64
  export { PixelRatio, PanResponder, PlatformColor, DynamicColorIOS, InteractionManager, } from '@symbiote-native/engine';
64
65
  export type { IPixelRatioStatic } from '@symbiote-native/engine';
65
66
  export type { IPanResponderGestureState, IPanResponderCallbacks, IGestureResponderHandlers, IPanResponderInstance, } from '@symbiote-native/engine';
package/build/index.js CHANGED
@@ -2,78 +2,43 @@
2
2
  // each RendererOptions call onto the engine's mutation API; all Fabric clone-on-write
3
3
  // lives in the engine, shared with every other adapter. App code names only @symbiote-native/vue.
4
4
  export { mount, unmount } from './render.js';
5
- // createTunnel: cross-surface content sharing (Teleport stays same-surface-only by design —
6
- // see create-tunnel.ts for why).
5
+ // Teleport stays same-surface-only by design - see create-tunnel.ts for why.
7
6
  export { createTunnel } from './create-tunnel/index.js';
8
7
  export { View, Text } from './components.js';
9
- // findNodeHandle: RN's ref -> native reactTag lookup, the Vue twin of the React adapter's
10
- // host-instance resolution. IHostInstance is the raw engine node (Vue host refs fall
11
- // through to it; imperative methods live on each component's expose() handle).
8
+ // IHostInstance is the raw engine node (Vue host refs fall through to it; imperative methods
9
+ // live on each component's expose() handle).
12
10
  export { findNodeHandle } from './host-instance/index.js';
13
- // Image. Full parity with React: source/src/srcSet resolution, the width/height → style fold,
14
- // alt → accessibility, and the Image statics (getSize / prefetch / queryCache / …) are shared
15
- // verbatim from @symbiote-native/components via renderImage; Vue supplies only the functional bridge.
16
11
  export { Image, setImageSourceResolver } from './components/image.js';
17
- // First component off the shared @symbiote-native/components layer: render-only, drives the
18
- // agnostic renderActivityIndicator through descriptorToVue. Proof the layer is reusable:
19
- // the render fn is shared verbatim with React; Vue supplies only the bridge.
20
12
  export { ActivityIndicator } from './components/activity-indicator';
21
- // First component to bring the state half into Vue (the lastNativeReport reducer + the
22
- // snap-back watch); render shared verbatim with React, Vue supplies the reactive lifecycle.
23
13
  export { Switch } from './components/switch';
24
- // ScrollView. Full parity: vertical + horizontal, every pass-through prop, the
25
- // synthesized onContentSizeChange, the imperative handle via expose() + shallowRef, RefreshControl
26
- // (iOS sibling / Android wrap), and sticky headers (the scroll AnimatedValue,
27
- // the headerLayoutYs cross-talk, and the per-header Animated.View wrap). The pure math (intrinsics,
28
- // decelerationRate, content-size dedupe, the handle, the sticky interpolation) is shared verbatim
29
- // with React from @symbiote-native/components.
30
14
  export { ScrollView } from './components/scroll-view';
31
- // Pressable family. Full parity with React (the 3-layer split): the press state machine + render
32
- // decisions are shared in @symbiote-native/components; Vue supplies the reactivity + descriptor bridge.
33
15
  export { Pressable } from './components/pressable.js';
34
16
  export { TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, } from './components/touchable.js';
35
17
  export { TouchableNativeFeedback } from './components/touchable-native-feedback.js';
36
18
  export { Button } from './components/button.js';
37
- // TextInput. Full parity with React (the 3-layer split): the value/selection folds + the
38
- // controlled-write predicate live in @symbiote-native/components; Vue supplies the reactivity
39
- // (shallowRef host node, post-flush watch) + the imperative handle via expose().
40
19
  export { TextInput } from './components/text-input/index.js';
41
- // VirtualizedList family. Full parity with React: the windowing math (visible range, cell
42
- // keys, viewability token diffing, edge-reached, the list plan) is shared in
43
- // @symbiote-native/components; Vue supplies the reactive lifecycle + the per-cell element.
44
20
  export { VirtualizedList } from './components/virtualized-list/index.js';
45
21
  export { FlatList } from './components/flat-list/index.js';
46
22
  export { VirtualizedSectionList } from './components/virtualized-section-list/index.js';
47
23
  export { SectionList } from './components/section-list/index.js';
48
- // SafeAreaView + RefreshControl, wired into ScrollView with the iOS-sibling /
49
- // Android-wrap platform split. RefreshControl hosts the wrapped scroll view via its default slot.
24
+ // RefreshControl hosts the wrapped scroll view via its default slot.
50
25
  export { SafeAreaView } from './components/safe-area-view.js';
51
26
  export { RefreshControl } from './components/refresh-control.js';
52
27
  export { descriptorToVue } from './descriptor-to-vue.js';
53
- // normalizeVueAttrs: the kebab→camel attr fold every component applies at entry. Exported so an
54
- // external wrapper package (e.g. @symbiote-native/slider/vue over a third-party native view) can fold
55
- // its incoming attrs through the SAME transform rather than reimplementing it.
28
+ // Exported so an external wrapper package (e.g. @symbiote-native/slider/vue over a third-party
29
+ // native view) can fold its incoming attrs/v-model through the SAME transform rather than
30
+ // reimplementing it.
56
31
  export { normalizeVueAttrs } from './utils/normalize-attrs.js';
57
- // resolveModelValue/emitModelUpdate: the v-model helper every
58
- // controlled-value component uses. Exported for the same external-package reason as
59
- // normalizeVueAttrs above (e.g. @symbiote-native/slider/vue).
60
32
  export { resolveModelValue, emitModelUpdate } from './utils/model-binding.js';
61
33
  export { createSymbioteRenderer } from './renderer/index.js';
62
- // Animated: Animated.View/Text/Image + the lazy Animated.ScrollView over the
63
- // Vue primitives, with the value graph / easing / drivers spread from @symbiote-native/engine. The wrap
64
- // mechanism (createAnimatedComponent) is the Vue twin of React's; the pure leaves live in the
65
- // engine.
66
34
  export { Animated, createAnimatedComponent } from './modules/animated/index.js';
67
- // Visual components off the shared @symbiote-native/components layer (wave: simple-visual).
68
35
  export { ImageBackground } from './components/image-background.js';
69
36
  export { InputAccessoryView } from './components/input-accessory-view.js';
70
37
  export { Modal } from './components/modal/index.js';
71
- // KeyboardAvoidingView: full parity via the shared inset math; Vue owns the Keyboard subscription.
72
38
  export { KeyboardAvoidingView } from './components/keyboard-avoiding-view.js';
73
- // StatusBar: declarative component + the shared imperative API.
74
39
  export { StatusBar } from './modules/status-bar.js';
75
- // AppRegistry: RN's app entry point over `mount`. setHostRegistrar wires RN's own
76
- // registrar so the native Fabric host finds our runnable by app key.
40
+ // RN's app entry point over `mount`. setHostRegistrar wires RN's own registrar so the native
41
+ // Fabric host finds our runnable by app key.
77
42
  export { AppRegistry, setHostRegistrar } from './modules/app-registry/index.js';
78
43
  // Vue composables over the core device-state modules.
79
44
  export { useColorScheme } from './composables/use-color-scheme.js';
@@ -84,7 +49,8 @@ export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Setting
84
49
  export { Platform, StyleSheet, processColor, setColorProcessor, dlog, isDebug, } from '@symbiote-native/engine';
85
50
  // Wired once by the app entry on a real host (like setColorProcessor): hands the engine
86
51
  // RN's ViewConfig registry so third-party Fabric views auto-derive their metadata.
87
- export { setNativeViewConfigSource } from '@symbiote-native/engine';
52
+ // setDeviceEventSource is the third seam of the same set and travels with them.
53
+ export { setNativeViewConfigSource, setDeviceEventSource } from '@symbiote-native/engine';
88
54
  // Pure utilities that moved to the engine (single source, both adapters re-export):
89
55
  // PixelRatio + PanResponder, plus the color builders and the interaction scheduler.
90
56
  export { PixelRatio, PanResponder, PlatformColor, DynamicColorIOS, InteractionManager, } from '@symbiote-native/engine';