@symbiote-native/vue 3.0.0 → 3.0.2

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.
@@ -10,8 +10,8 @@
10
10
  // $attrs onto the inner VirtualizedList. The inner list still GATES RefreshControl + viewability
11
11
  // on callback presence, so each emit bridge is wired ONLY when the consumer actually listens.
12
12
  import { defineComponent, getCurrentInstance, h, shallowRef, } from '@vue/runtime-core';
13
- import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, rowKeyExtractor, } from '@symbiote-native/components';
14
- import { dlog, resolveClassName, } from '@symbiote-native/engine';
13
+ import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, removeClippedSubviewsOrDefault, rowKeyExtractor, } from '@symbiote-native/components';
14
+ import { Platform, dlog, resolveClassName, } from '@symbiote-native/engine';
15
15
  import { VirtualizedList } from '../virtualized-list/index.js';
16
16
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
17
17
  // VirtualizedList's generic construct signature can't be resolved by h()'s overloads, so drive
@@ -81,7 +81,12 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots, })
81
81
  footer: slots.footer,
82
82
  empty: slots.empty,
83
83
  };
84
- const forwarded = normalizeVueAttrs(attrs);
84
+ const normalizedAttrs = normalizeVueAttrs(attrs);
85
+ // FlatList.js always sends it, defaulted per platform.
86
+ const forwarded = {
87
+ ...normalizedAttrs,
88
+ removeClippedSubviews: removeClippedSubviewsOrDefault(normalizedAttrs.removeClippedSubviews, Platform.OS),
89
+ };
85
90
  dlog(`Vue FlatList over ${data.length} items, ${numColumns} column(s)`);
86
91
  const endReached = listens('onEndReached')
87
92
  ? (info) => emit('endReached', info)
@@ -11,8 +11,8 @@
11
11
  // events (onShow/onDismiss/onRequestClose/onOrientationChange) are real ViewConfig DirectEvents,
12
12
  // so, unlike Switch's pure-JS onValueChange, they forward to the host raw via passthrough.
13
13
  import { defineComponent, h, ref, watch } from '@vue/runtime-core';
14
- import { createInitialModalState, modalReducer, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
15
- import { dlog, } from '@symbiote-native/engine';
14
+ import { createInitialModalState, isModalVisible, modalReducer, modalVisibilityAction, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
15
+ import { dlog, Platform, } from '@symbiote-native/engine';
16
16
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
17
17
  function asBoolean(value) {
18
18
  return typeof value === 'boolean' ? value : undefined;
@@ -79,16 +79,23 @@ function forwardAttrs(attrs) {
79
79
  return result;
80
80
  }
81
81
  export const Modal = defineComponent((_props, { attrs: rawAttrs, slots, emit }) => {
82
- const state = ref(createInitialModalState(rawAttrs.visible === true));
83
- // POST-flush so the transition fires AFTER the render that used the OLD state: on visible→hidden
84
- // the node renders once more (state.isRendered still true → the keep-alive frame), then this
85
- // drops it and the next render unmounts. flush:'pre' would unmount immediately, killing the
86
- // keep-alive. The reducer is identity-stable, so a no-op transition triggers no extra render.
87
- watch(() => rawAttrs.visible === true, isVisible => {
88
- state.value = modalReducer(state.value, isVisible ? { type: 'show' } : { type: 'hide' });
82
+ const state = ref(createInitialModalState(isModalVisible(rawAttrs.visible)));
83
+ // Arms the iOS keep-alive on show; a hide is left to the native dismiss (state/modal.ts). The
84
+ // reducer is identity-stable, so a no-op transition triggers no extra render.
85
+ watch(() => isModalVisible(rawAttrs.visible), isVisible => {
86
+ const action = modalVisibilityAction(isVisible);
87
+ if (action !== undefined)
88
+ state.value = modalReducer(state.value, action);
89
89
  }, { flush: 'post' });
90
+ // Modal.js: onDismiss is iOS-only — it drops the keep-alive, then tells the app.
91
+ const handleDismiss = () => {
92
+ if (Platform.OS !== 'ios')
93
+ return;
94
+ state.value = modalReducer(state.value, { type: 'hide' });
95
+ emit('dismiss');
96
+ };
90
97
  return () => {
91
- const isVisible = rawAttrs.visible === true;
98
+ const isVisible = isModalVisible(rawAttrs.visible);
92
99
  if (!shouldRenderModal(isVisible, state.value)) {
93
100
  dlog('Modal hidden -> no node committed');
94
101
  return null;
@@ -118,7 +125,7 @@ export const Modal = defineComponent((_props, { attrs: rawAttrs, slots, emit })
118
125
  ...root.props,
119
126
  key: root.key,
120
127
  onShow: () => emit('show'),
121
- onDismiss: () => emit('dismiss'),
128
+ onDismiss: handleDismiss,
122
129
  onRequestClose: () => emit('requestClose'),
123
130
  onOrientationChange: (event) => emit('orientationChange', event),
124
131
  }, [
@@ -39,6 +39,8 @@ export interface IVirtualizedListProps<ItemT> {
39
39
  scrollEventThrottle?: number;
40
40
  keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
41
41
  keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
42
+ removeClippedSubviews?: boolean;
43
+ nestedScrollEnabled?: boolean;
42
44
  style?: IStyleProp<IViewStyle>;
43
45
  contentContainerStyle?: IStyleProp<IViewStyle>;
44
46
  [key: string]: unknown;
@@ -562,8 +562,9 @@ export const VirtualizedList = defineComponent((props, { attrs, expose, emit, sl
562
562
  const resolvedContentContainerStyle = p.horizontal
563
563
  ? [p.contentContainerStyle, { width: m.total }]
564
564
  : p.contentContainerStyle;
565
+ // VirtualizedList.js: `[inversionStyle, style]` — the app's style can override the flip.
565
566
  const resolvedStyle = p.inverted
566
- ? [p.style, p.horizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE]
567
+ ? [p.horizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE, p.style]
567
568
  : p.style;
568
569
  // `horizontal` is NOT in the bag: the axis is the TAG (`horizontal-scroll-view`), so passing
569
570
  // it would be redundant at best and a contradiction the behavior has to warn about at worst.
@@ -578,6 +579,9 @@ export const VirtualizedList = defineComponent((props, { attrs, expose, emit, sl
578
579
  // The raw scroll-lifecycle callbacks (onScrollBeginDrag/…) and scrollEventThrottle are NOT in
579
580
  // PROP_KEYS, so they already ride through via ...p.forwarded. The keyboard props come from
580
581
  // typed props (PROP_KEYS), so re-set them explicitly here (only when provided).
582
+ // VirtualizedList.js:1111 — Android moves the scrollbar back after the `scale: -1` flip.
583
+ if (p.inverted)
584
+ scrollProps.isInvertedVirtualizedList = true;
581
585
  if (p.keyboardShouldPersistTaps !== undefined) {
582
586
  scrollProps.keyboardShouldPersistTaps = p.keyboardShouldPersistTaps;
583
587
  }
@@ -35,6 +35,8 @@ export interface IVirtualizedSectionListProps<ItemT> {
35
35
  scrollEventThrottle?: number;
36
36
  keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
37
37
  keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
38
+ removeClippedSubviews?: boolean;
39
+ nestedScrollEnabled?: boolean;
38
40
  style?: IStyleProp<IViewStyle>;
39
41
  contentContainerStyle?: IStyleProp<IViewStyle>;
40
42
  [key: string]: unknown;
@@ -16,8 +16,12 @@ const {
16
16
  parseCache,
17
17
  compileScript,
18
18
  registerTS,
19
+ babelParse,
19
20
  } = require('@vue/compiler-sfc');
20
- const { createCompoundExpression } = require('@vue/compiler-core');
21
+ const {
22
+ createCompoundExpression,
23
+ createSimpleExpression,
24
+ } = require('@vue/compiler-core');
21
25
 
22
26
  // A bare-specifier type import (`import type { X } from '@symbiote-native/navigation/vue'`) needs
23
27
  // real node_modules resolution to turn the specifier into a file path - compileScript's own `fs`
@@ -146,6 +150,51 @@ function trimReflowedTextNodeTransform(node) {
146
150
  // Shared with ./babel-jsx.cjs: Vue's two compilers must give one answer to element-vs-component.
147
151
  const INTRINSIC_TAGS = require('./intrinsic-tags.cjs');
148
152
 
153
+ const SYMBIOTE_SOURCE_PREFIX = '@symbiote-native/';
154
+
155
+ // Local names the file imports from any `@symbiote-native/*` package (`FlatList`, `Animated`).
156
+ function symbioteImportsOf(descriptor) {
157
+ const locals = new Set();
158
+ for (const block of [descriptor.script, descriptor.scriptSetup]) {
159
+ if (block == null) continue;
160
+ const plugins = block.lang === 'ts' || block.lang === 'tsx' ? ['typescript'] : [];
161
+ const program = babelParse(block.content, { sourceType: 'module', plugins }).program;
162
+ for (const statement of program.body) {
163
+ if (
164
+ statement.type !== 'ImportDeclaration' ||
165
+ !statement.source.value.startsWith(SYMBIOTE_SOURCE_PREFIX)
166
+ )
167
+ continue;
168
+ for (const specifier of statement.specifiers) locals.add(specifier.local.name);
169
+ }
170
+ }
171
+ return locals;
172
+ }
173
+
174
+ // Vue casts a bare attribute to `true` only for a prop declared Boolean. Our tags and components
175
+ // declare none, so `nested-scroll-enabled` reached Android as "" and the ViewManager threw
176
+ // `String cannot be cast to Boolean`. The app's own components keep Vue's semantics.
177
+ function createBareBooleanNodeTransform(symbioteLocals) {
178
+ const isOurs = tag =>
179
+ INTRINSIC_TAGS.has(tag) || symbioteLocals.has(tag.split('.')[0]);
180
+ return function bareBooleanNodeTransform(node) {
181
+ if (node.type !== 1 /* NodeTypes.ELEMENT */ || !isOurs(node.tag)) return;
182
+ node.props = node.props.map(prop =>
183
+ prop.type === 6 /* NodeTypes.ATTRIBUTE */ && prop.value === undefined
184
+ ? {
185
+ type: 7 /* NodeTypes.DIRECTIVE */,
186
+ name: 'bind',
187
+ rawName: `:${prop.name}`,
188
+ arg: createSimpleExpression(prop.name, true, prop.loc),
189
+ exp: createSimpleExpression('true', false, prop.loc, 3 /* CAN_STRINGIFY */),
190
+ modifiers: [],
191
+ loc: prop.loc,
192
+ }
193
+ : prop,
194
+ );
195
+ };
196
+ }
197
+
149
198
 
150
199
  // A short, stable id per file, used as the SFC scope id regardless of whether the file has
151
200
  // scoped styles - built on css-parser's shared hashFilePath so the algorithm isn't duplicated
@@ -307,7 +356,10 @@ async function compileSfc(src, filename) {
307
356
 
308
357
  // Scoped-class rewriting is skipped entirely (not even passed to the compiler) when nothing in
309
358
  // this file is scoped, so a .vue with only unscoped/no styles adds no runtime cost.
310
- const nodeTransforms = [trimReflowedTextNodeTransform];
359
+ const nodeTransforms = [
360
+ trimReflowedTextNodeTransform,
361
+ createBareBooleanNodeTransform(symbioteImportsOf(descriptor)),
362
+ ];
311
363
  if (scopedClassNames.size > 0)
312
364
  nodeTransforms.push(createScopeClassNodeTransform(scopedClassNames));
313
365
  // UNCONDITIONAL, and covering EVERY intrinsic rather than the ones this file happens to import.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/vue",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "SymbioteNative's Vue adapter — a @vue/runtime-core createRenderer driving real native iOS/Android views through the same engine as the React adapter.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,18 +70,18 @@
70
70
  "@vue/compiler-sfc": "^3.5.13",
71
71
  "@vue/runtime-core": "^3.5.13",
72
72
  "typescript": "~6.0.0",
73
- "@symbiote-native/components": "3.0.0",
73
+ "@symbiote-native/components": "3.1.0",
74
74
  "@symbiote-native/css-parser": "0.5.0"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "vue": ">=3.5.0",
78
78
  "react-native": ">=0.86",
79
- "@symbiote-native/engine": "^1.0.0"
79
+ "@symbiote-native/engine": "^1.3.0"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@babel/core": "^7.25.2",
83
- "@symbiote-native/engine": "1.0.0",
84
- "@symbiote-native/test-utils": "0.3.1"
83
+ "@symbiote-native/engine": "1.3.0",
84
+ "@symbiote-native/test-utils": "0.4.2"
85
85
  },
86
86
  "scripts": {
87
87
  "typecheck": "tsc --build",