@symbiote-native/vue 3.4.0 → 3.6.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.
- package/build/composables/use-root-tag.d.ts +4 -0
- package/build/composables/use-root-tag.js +11 -0
- package/build/index.d.ts +2 -1
- package/build/index.js +2 -1
- package/build/render.js +5 -19
- package/metro-vue-transformer.cjs +90 -43
- package/package.json +4 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// The tag of the surface a component is rendered into, the Vue twin of RN's `RootTagContext`:
|
|
2
|
+
// `mount` provides it on the app, a composable reads it back in `setup()`
|
|
3
|
+
import { inject } from '@vue/runtime-core';
|
|
4
|
+
export const ROOT_TAG_KEY = Symbol('symbiote-root-tag');
|
|
5
|
+
export function useRootTag() {
|
|
6
|
+
const rootTag = inject(ROOT_TAG_KEY, undefined);
|
|
7
|
+
if (rootTag === undefined) {
|
|
8
|
+
throw new Error('useRootTag() needs a component mounted by mount()');
|
|
9
|
+
}
|
|
10
|
+
return rootTag;
|
|
11
|
+
}
|
package/build/index.d.ts
CHANGED
|
@@ -60,12 +60,13 @@ export type { IComponentProvider, IAppConfig, IAppParameters, IRunnable, IHostRe
|
|
|
60
60
|
export { useColorScheme } from './composables/use-color-scheme';
|
|
61
61
|
export { useAnimatedColor, useAnimatedValue, useAnimatedValueXY, } from './composables/use-animated-value';
|
|
62
62
|
export { useWindowDimensions } from './composables/use-window-dimensions';
|
|
63
|
+
export { useRootTag } from './composables/use-root-tag';
|
|
63
64
|
export { createPermissionHook } from './composables/create-permission-hook';
|
|
64
65
|
export { createResourceHook } from './composables/create-resource-hook';
|
|
65
66
|
export { defineNativeViewComponent } from './composables/define-native-view-component';
|
|
66
67
|
export { createEventValueHook } from './composables/create-event-value-hook';
|
|
67
68
|
export { useEvent, useEventListener } from './composables/use-event';
|
|
68
|
-
export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Settings, I18nManager, Dimensions, Appearance, AppState, Keyboard, KEYBOARD_EVENT, TextInputState, BackHandler, PermissionsAndroid, PERMISSIONS, RESULTS, AccessibilityInfo, LayoutAnimation, } from '@symbiote-native/engine';
|
|
69
|
+
export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Settings, I18nManager, Dimensions, Appearance, matchMedia, MediaQueryList, MediaQueryListEvent, setVariable, getComputedVariable, registerProperty, AppState, Keyboard, KEYBOARD_EVENT, TextInputState, BackHandler, PermissionsAndroid, PERMISSIONS, RESULTS, AccessibilityInfo, LayoutAnimation, } from '@symbiote-native/engine';
|
|
69
70
|
export type { IAlertType, IAlertButtonStyle, IAlertButton, IAlertButtons, IAlertOptions, IShareContent, IShareOptions, IShareAction, IActionSheetIOSOptions, IShareActionSheetIOSOptions, IShareActionSheetError, IUrlEvent, II18nManagerConstants, IDisplayMetrics, IDisplayMetricsAndroid, IDimensionsPayload, IDimensionsSet, IDimensionsKey, IDimensionsChangeListener, IDimensionsStatic, IColorSchemeName, IColorSchemePreference, IAppStateStatus, IAppStateEvent, IKeyboardEventName, IKeyboardEvent, IKeyboardMetrics, IBackPressEventName, IBackPressHandler, IPermission, IPermissionStatus, IRationale, IAccessibilityChangeEvent, IAccessibilityChangeEventName, IAccessibilityChangeEventHandler, IAccessibilityAnnouncementFinishedEvent, IAnnounceForAccessibilityOptions, IAccessibilityEventType, ILayoutAnimationType, ILayoutAnimationProperty, ILayoutAnimationConfig, ILayoutAnimationAnim, } from '@symbiote-native/engine';
|
|
70
71
|
export { Platform, StyleSheet, processColor, setColorProcessor, dlog, isDebug, } from '@symbiote-native/engine';
|
|
71
72
|
export type { ISymbioteEvent, ISymbioteNode, IRootTag, } from '@symbiote-native/engine';
|
package/build/index.js
CHANGED
|
@@ -54,13 +54,14 @@ export { AppRegistry, HeadlessJsTaskError, setHostRegistrar, } from './modules/a
|
|
|
54
54
|
export { useColorScheme } from './composables/use-color-scheme.js';
|
|
55
55
|
export { useAnimatedColor, useAnimatedValue, useAnimatedValueXY, } from './composables/use-animated-value.js';
|
|
56
56
|
export { useWindowDimensions } from './composables/use-window-dimensions.js';
|
|
57
|
+
export { useRootTag } from './composables/use-root-tag.js';
|
|
57
58
|
export { createPermissionHook } from './composables/create-permission-hook.js';
|
|
58
59
|
export { createResourceHook } from './composables/create-resource-hook.js';
|
|
59
60
|
export { defineNativeViewComponent } from './composables/define-native-view-component.js';
|
|
60
61
|
export { createEventValueHook } from './composables/create-event-value-hook.js';
|
|
61
62
|
export { useEvent, useEventListener } from './composables/use-event.js';
|
|
62
63
|
// Imperative runtime modules: the SAME module both adapters share, re-exported from @symbiote-native/engine.
|
|
63
|
-
export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Settings, I18nManager, Dimensions, Appearance, AppState, Keyboard, KEYBOARD_EVENT, TextInputState, BackHandler, PermissionsAndroid, PERMISSIONS, RESULTS, AccessibilityInfo, LayoutAnimation, } from '@symbiote-native/engine';
|
|
64
|
+
export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Settings, I18nManager, Dimensions, Appearance, matchMedia, MediaQueryList, MediaQueryListEvent, setVariable, getComputedVariable, registerProperty, AppState, Keyboard, KEYBOARD_EVENT, TextInputState, BackHandler, PermissionsAndroid, PERMISSIONS, RESULTS, AccessibilityInfo, LayoutAnimation, } from '@symbiote-native/engine';
|
|
64
65
|
// Re-export the framework-agnostic engine surface (pure utilities + diagnostics).
|
|
65
66
|
export { Platform, StyleSheet, processColor, setColorProcessor, dlog, isDebug, } from '@symbiote-native/engine';
|
|
66
67
|
// Wired once by the app entry on a real host (like setColorProcessor): hands the engine
|
package/build/render.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// nativeFabricUIManager; RN's own renderer never in the path.
|
|
4
4
|
import { createSurface, disposeRoot, dlog, reportUncaughtError, } from '@symbiote-native/engine';
|
|
5
5
|
import { createSymbioteRenderer } from './renderer/index.js';
|
|
6
|
+
import { ROOT_TAG_KEY } from './composables/use-root-tag.js';
|
|
6
7
|
// One Vue app per surface, so a surface can be torn down (unmount) or cleanly
|
|
7
8
|
// re-mounted on the same rootTag: the bridgeless host stops and restarts a surface on
|
|
8
9
|
// Fast Refresh and on lifecycle/focus changes, reusing the rootTag.
|
|
@@ -34,25 +35,9 @@ function componentStack(instance) {
|
|
|
34
35
|
}
|
|
35
36
|
return frames.length === 0 ? null : frames.join('');
|
|
36
37
|
}
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// the surface half-brought-up; in a release bundle it console.errors a bare error with no origin
|
|
41
|
-
// and no component context, never touching `global.ErrorUtils`, so nothing reaches the host.
|
|
42
|
-
// Setting a handler suppresses both branches and routes to the engine's shared channel instead —
|
|
43
|
-
// the native redbox on a device, console.error off one.
|
|
44
|
-
//
|
|
45
|
-
// CAUGHT vs UNCAUGHT — the asymmetry is Vue's own, and it costs us no code. `handleError` walks
|
|
46
|
-
// the `onErrorCaptured` chain BEFORE consulting `config.errorHandler`, and a hook returning
|
|
47
|
-
// `false` stops propagation and returns early (runtime-core 3.5 errorHandling.ts), so a boundary
|
|
48
|
-
// that claims the error never reaches this function at all. That is the behaviour we want and
|
|
49
|
-
// the same split the React adapter makes by hand: writing a boundary IS the developer saying
|
|
50
|
-
// "I am handling this", and answering it with a full-screen redbox over the fallback the app
|
|
51
|
-
// just rendered contradicts what the app asked for.
|
|
52
|
-
//
|
|
53
|
-
// So do NOT "fix" this to report from inside an `onErrorCaptured` of our own — that would
|
|
54
|
-
// re-report exactly the errors a boundary claimed. A hook that returns undefined instead of
|
|
55
|
-
// `false` still lands here, and correctly: Vue treats that error as still propagating.
|
|
38
|
+
// Without it Vue's `logError` re-throws from `app.mount()` in dev and hides the error in release
|
|
39
|
+
// A boundary that claims an error never gets here: `onErrorCaptured` runs before `errorHandler`
|
|
40
|
+
// Do not report from `onErrorCaptured`, it would re-report the errors a boundary claimed
|
|
56
41
|
function reportToHost(error, instance, info) {
|
|
57
42
|
// `info` is Vue's own phase label ('render function', 'setup function', 'native event
|
|
58
43
|
// handler'), which is the closest thing it has to React's "no error boundary" / "recovered"
|
|
@@ -82,6 +67,7 @@ export function mount(rootTag, rootComponent) {
|
|
|
82
67
|
const renderer = createSymbioteRenderer(surface);
|
|
83
68
|
const app = renderer.createApp(rootComponent);
|
|
84
69
|
app.config.errorHandler = reportToHost;
|
|
70
|
+
app.provide(ROOT_TAG_KEY, rootTag);
|
|
85
71
|
configurator?.(app);
|
|
86
72
|
apps.set(rootTag, app);
|
|
87
73
|
// The surface IS the Vue container: a top-level mutation routes to surface.appendChild
|
|
@@ -30,10 +30,13 @@ const {
|
|
|
30
30
|
compileCssModule,
|
|
31
31
|
compileCssToRules,
|
|
32
32
|
compileScopedCss,
|
|
33
|
+
conditionsArgument,
|
|
33
34
|
createCssMetroTransformer,
|
|
34
35
|
hashFilePath,
|
|
36
|
+
propertiesCall,
|
|
35
37
|
relabelForUpstream,
|
|
36
38
|
resolveUpstreamTransformer,
|
|
39
|
+
serializeRules,
|
|
37
40
|
} = require('@symbiote-native/css-parser');
|
|
38
41
|
|
|
39
42
|
const upstreamTransformer = resolveUpstreamTransformer();
|
|
@@ -134,7 +137,8 @@ function trimReflowedTextNodeTransform(node) {
|
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
// The scope of this package's own name, `@symbiote-native/`, so the prefix has one source
|
|
141
|
+
const SYMBIOTE_SOURCE_PREFIX = `${require('./package.json').name.split('/')[0]}/`;
|
|
138
142
|
|
|
139
143
|
// Local names the file imports from any `@symbiote-native/*` package (`FlatList`, `Animated`)
|
|
140
144
|
function symbioteImportsOf(descriptor) {
|
|
@@ -190,23 +194,27 @@ const SFC_COMPONENT_VAR = '__sfc__';
|
|
|
190
194
|
|
|
191
195
|
// Each block restarts its rules at order 0, so a later one would tie an earlier one
|
|
192
196
|
// Renumbering on append gives the file one monotonic sequence, as one stylesheet would
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
// Conditions join the file's table, so a block's condition indexes shift by the ones already there
|
|
198
|
+
function appendRules(sink, compiled) {
|
|
199
|
+
const offset = sink.conditions.length;
|
|
200
|
+
sink.conditions.push(...compiled.conditions);
|
|
201
|
+
for (const rule of compiled.rules) {
|
|
202
|
+
sink.rules.push({
|
|
203
|
+
...withOffsetCond(rule, offset),
|
|
204
|
+
order: sink.rules.length,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// A `:root` block cascades in append order, the way one stylesheet would
|
|
208
|
+
for (const variable of compiled.rootVariables) {
|
|
209
|
+
sink.rootVariables.push(withOffsetCond(variable, offset));
|
|
210
|
+
}
|
|
211
|
+
for (const property of compiled.properties) {
|
|
212
|
+
sink.properties.push(withOffsetCond(property, offset));
|
|
196
213
|
}
|
|
197
214
|
}
|
|
198
215
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
function serializeRules(rules) {
|
|
202
|
-
return JSON.stringify(
|
|
203
|
-
rules.map(({ tokens, specificity, order, style }) => ({
|
|
204
|
-
tokens,
|
|
205
|
-
specificity,
|
|
206
|
-
order,
|
|
207
|
-
style,
|
|
208
|
-
})),
|
|
209
|
-
);
|
|
216
|
+
function withOffsetCond(item, offset) {
|
|
217
|
+
return item.cond === undefined ? item : { ...item, cond: item.cond + offset };
|
|
210
218
|
}
|
|
211
219
|
|
|
212
220
|
// A style block's `lang` names a preprocessor (`lang="scss"`), where a file has an extension
|
|
@@ -251,12 +259,19 @@ function parseDescriptor(src, filename) {
|
|
|
251
259
|
`Vue SFC parse error in ${filename}:\n${errors.map(String).join('\n')}`,
|
|
252
260
|
);
|
|
253
261
|
}
|
|
254
|
-
if (descriptor.scriptSetup
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
if (descriptor.scriptSetup != null || descriptor.script != null) {
|
|
263
|
+
return descriptor;
|
|
264
|
+
}
|
|
265
|
+
if (descriptor.template == null) {
|
|
266
|
+
throw new Error(`Vue SFC ${filename} has no <template> / <script> block`);
|
|
258
267
|
}
|
|
259
|
-
|
|
268
|
+
// `compileScript` refuses a file without a script, yet a static template is a valid component
|
|
269
|
+
// A `<script setup>` appended after the source keeps every line number of the file
|
|
270
|
+
// It carries a comment since the parser drops a blank block, which would recurse forever
|
|
271
|
+
return parseDescriptor(
|
|
272
|
+
`${src}\n<script setup>/* static template */</script>\n`,
|
|
273
|
+
filename,
|
|
274
|
+
);
|
|
260
275
|
}
|
|
261
276
|
|
|
262
277
|
// `<style scoped>` and `<style module>` are one mechanism, lightningcss' CSS-Modules renaming
|
|
@@ -268,16 +283,15 @@ function compileSfcStyleBlock({ style, content, filename, scopeId }) {
|
|
|
268
283
|
if (style.module) {
|
|
269
284
|
const compiled = compileCssModule(content, filename);
|
|
270
285
|
const bindingName = typeof style.module === 'string' ? style.module : '$style';
|
|
271
|
-
return {
|
|
286
|
+
return { ...compiled, binding: [bindingName, compiled.classMap] };
|
|
272
287
|
}
|
|
273
288
|
if (style.scoped) {
|
|
274
|
-
|
|
289
|
+
return compileScopedCss(content, {
|
|
275
290
|
filename,
|
|
276
291
|
pattern: `[local]__${scopeId}`,
|
|
277
292
|
});
|
|
278
|
-
return { rules: compiled.rules, names: compiled.names };
|
|
279
293
|
}
|
|
280
|
-
return
|
|
294
|
+
return compileCssToRules(content, { filename });
|
|
281
295
|
}
|
|
282
296
|
|
|
283
297
|
// Blocks cascade last-wins like CSS, carried by the rules' `order` that `appendRules` renumbers
|
|
@@ -287,9 +301,14 @@ async function compileStyles(descriptor, filename, scopeId) {
|
|
|
287
301
|
const contents = await Promise.all(
|
|
288
302
|
descriptor.styles.map(style => compileStyleBlockContent(style, filename)),
|
|
289
303
|
);
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
304
|
+
const sink = {
|
|
305
|
+
rules: [],
|
|
306
|
+
conditions: [],
|
|
307
|
+
rootVariables: [],
|
|
308
|
+
properties: [],
|
|
309
|
+
scopedClassNames: new Map(),
|
|
310
|
+
cssModuleBindings: new Map(),
|
|
311
|
+
};
|
|
293
312
|
descriptor.styles.forEach((style, index) => {
|
|
294
313
|
const block = compileSfcStyleBlock({
|
|
295
314
|
style,
|
|
@@ -297,18 +316,18 @@ async function compileStyles(descriptor, filename, scopeId) {
|
|
|
297
316
|
filename,
|
|
298
317
|
scopeId,
|
|
299
318
|
});
|
|
300
|
-
appendRules(
|
|
319
|
+
appendRules(sink, block);
|
|
301
320
|
for (const [name, renamed] of block.names ?? [])
|
|
302
|
-
scopedClassNames.set(name, renamed);
|
|
321
|
+
sink.scopedClassNames.set(name, renamed);
|
|
303
322
|
if (block.binding) {
|
|
304
323
|
const [bindingName, classMap] = block.binding;
|
|
305
|
-
cssModuleBindings.set(bindingName, {
|
|
306
|
-
...cssModuleBindings.get(bindingName),
|
|
324
|
+
sink.cssModuleBindings.set(bindingName, {
|
|
325
|
+
...sink.cssModuleBindings.get(bindingName),
|
|
307
326
|
...classMap,
|
|
308
327
|
});
|
|
309
328
|
}
|
|
310
329
|
});
|
|
311
|
-
return
|
|
330
|
+
return sink;
|
|
312
331
|
}
|
|
313
332
|
|
|
314
333
|
// Scoped-class rewriting is skipped when nothing in the file is scoped, so it adds no runtime cost
|
|
@@ -342,20 +361,37 @@ function compileComponentScript(descriptor, scopeId, styles) {
|
|
|
342
361
|
);
|
|
343
362
|
}
|
|
344
363
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
364
|
+
// The module text ahead of the component: the registration, the scope map and the module maps
|
|
365
|
+
// Only a scoped file imports the rename helper, so other files leave it out
|
|
366
|
+
function buildPreamble(styles) {
|
|
367
|
+
const {
|
|
368
|
+
rules,
|
|
369
|
+
conditions,
|
|
370
|
+
rootVariables,
|
|
371
|
+
properties,
|
|
372
|
+
scopedClassNames,
|
|
373
|
+
cssModuleBindings,
|
|
374
|
+
} = styles;
|
|
350
375
|
const isScoped = scopedClassNames.size > 0;
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
376
|
+
const hasRoot = rootVariables.length > 0;
|
|
377
|
+
const hasProperties = properties.length > 0;
|
|
378
|
+
const engineImports = [
|
|
379
|
+
'registerRules',
|
|
380
|
+
...(hasRoot ? ['registerRootVariables'] : []),
|
|
381
|
+
...(hasProperties ? ['registerProperties'] : []),
|
|
382
|
+
...(isScoped ? ['renameClassTokens as __scopeClass'] : []),
|
|
383
|
+
].join(', ');
|
|
384
|
+
const table = conditionsArgument(conditions);
|
|
355
385
|
const preamble = [
|
|
356
386
|
`import { ${engineImports} } from '@symbiote-native/engine';`,
|
|
357
|
-
`registerRules(${serializeRules(rules)});`,
|
|
387
|
+
`registerRules(${serializeRules(rules)}${table});`,
|
|
358
388
|
];
|
|
389
|
+
if (hasRoot) {
|
|
390
|
+
preamble.push(
|
|
391
|
+
`registerRootVariables(${JSON.stringify(rootVariables)}${table});`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
if (hasProperties) preamble.push(propertiesCall(properties, conditions));
|
|
359
395
|
if (isScoped) {
|
|
360
396
|
preamble.push(
|
|
361
397
|
`const __scopedClassNames = ${JSON.stringify(Object.fromEntries(scopedClassNames))};`,
|
|
@@ -366,8 +402,19 @@ function withStyleRegistration(code, { rules, scopedClassNames, cssModuleBinding
|
|
|
366
402
|
for (const [bindingName, classMap] of cssModuleBindings) {
|
|
367
403
|
preamble.push(`const ${bindingName} = ${JSON.stringify(classMap)};`);
|
|
368
404
|
}
|
|
405
|
+
return preamble.join('\n');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function withStyleRegistration(code, styles) {
|
|
409
|
+
const { cssModuleBindings } = styles;
|
|
410
|
+
const hasCssModules = cssModuleBindings.size > 0;
|
|
411
|
+
const hasRegistrations =
|
|
412
|
+
styles.rules.length > 0 ||
|
|
413
|
+
styles.rootVariables.length > 0 ||
|
|
414
|
+
styles.properties.length > 0;
|
|
415
|
+
if (!hasRegistrations && !hasCssModules) return code;
|
|
369
416
|
|
|
370
|
-
const parts = [
|
|
417
|
+
const parts = [buildPreamble(styles), code];
|
|
371
418
|
if (hasCssModules) {
|
|
372
419
|
const bindings = [...cssModuleBindings.keys()]
|
|
373
420
|
.map(name => `${JSON.stringify(name)}: ${name}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/vue",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
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": {
|
|
@@ -76,12 +76,12 @@
|
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"vue": ">=3.5.0",
|
|
78
78
|
"react-native": ">=0.86",
|
|
79
|
-
"@symbiote-native/engine": "^1.
|
|
79
|
+
"@symbiote-native/engine": "^1.8.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@babel/core": "^7.25.2",
|
|
83
|
-
"@symbiote-native/engine": "1.
|
|
84
|
-
"@symbiote-native/test-utils": "0.5.
|
|
83
|
+
"@symbiote-native/engine": "1.8.0",
|
|
84
|
+
"@symbiote-native/test-utils": "0.5.1"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"typecheck": "tsc --build",
|