@poveste/plugin-vue 0.10.0 → 0.12.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/dist/bundled/client/app/Variant.js +29 -29
- package/dist/bundled/client/app/auto-props.js +43 -24
- package/dist/bundled/client/app/util.js +12 -6
- package/dist/client/app/auto-props.d.ts +3 -0
- package/dist/client/app/util.d.ts +2 -1
- package/package.json +11 -10
- package/src/client/app/Variant.ts +3 -3
- package/src/client/app/__tests__/auto-props.spec.ts +49 -0
- package/src/client/app/__tests__/state-sync.spec.ts +48 -2
- package/src/client/app/auto-props.ts +67 -18
- package/src/client/app/util.ts +17 -3
|
@@ -2,9 +2,9 @@ import { useRenderContext as e } from "./render-context.js";
|
|
|
2
2
|
import { syncVariantAutoProps as t } from "./auto-props.js";
|
|
3
3
|
import { syncStateBundledAndExternal as n, toRawDeep as r } from "./util.js";
|
|
4
4
|
import { computed as i, defineComponent as a, getCurrentInstance as o, inject as s, onBeforeUnmount as c, useAttrs as l } from "vue";
|
|
5
|
-
import { applyState as u } from "@poveste/shared";
|
|
5
|
+
import { applyState as u, autoPropsStateKeys as d } from "@poveste/shared";
|
|
6
6
|
//#region src/client/app/Variant.ts
|
|
7
|
-
var
|
|
7
|
+
var f = a({
|
|
8
8
|
name: "Variant",
|
|
9
9
|
__povesteType: "variant",
|
|
10
10
|
props: {
|
|
@@ -38,47 +38,47 @@ var d = a({
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
async setup(a) {
|
|
41
|
-
let
|
|
41
|
+
let f = l(), p = o(), m = s("story"), h = s("implicitState"), g = e(), _, v, y, b;
|
|
42
42
|
c(() => {
|
|
43
|
-
|
|
43
|
+
y?.stop(), b?.stop();
|
|
44
44
|
});
|
|
45
|
-
let
|
|
46
|
-
if (
|
|
45
|
+
let x = i(() => f.variant);
|
|
46
|
+
if (g?.mode !== "render" && typeof a.initState == "function" && x.value) {
|
|
47
47
|
let e = await a.initState();
|
|
48
|
-
u(
|
|
48
|
+
u(x.value.state, r(e));
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
function
|
|
50
|
+
g?.mode !== "render" && x.value && h && (b = n(x.value.state, h(), d));
|
|
51
|
+
function S(e) {
|
|
52
52
|
Object.assign(e, {
|
|
53
|
-
slots: () =>
|
|
53
|
+
slots: () => p.proxy.$slots,
|
|
54
54
|
source: a.source,
|
|
55
55
|
responsiveDisabled: a.responsiveDisabled,
|
|
56
56
|
autoPropsDisabled: a.autoPropsDisabled,
|
|
57
57
|
setupApp: a.setupApp,
|
|
58
58
|
meta: a.meta,
|
|
59
59
|
configReady: !0
|
|
60
|
-
}), !a.implicit && !
|
|
60
|
+
}), !a.implicit && !m.value?.meta?.hasVariantChildComponents && (m.value.meta || (m.value.meta = {}), Object.assign(m.value.meta, { hasVariantChildComponents: !0 }));
|
|
61
61
|
}
|
|
62
|
-
function
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
function C(e) {
|
|
66
|
-
if (h.slotName === "controls") return f.proxy.$slots.controls?.({ state: h.externalState }) ?? null;
|
|
67
|
-
if (h.slotName !== "default") return null;
|
|
68
|
-
let n = f.proxy.$slots.default?.({ state: h.externalState }) ?? null;
|
|
69
|
-
return n && !e.autoPropsDisabled && (g = t(e, n, h.externalState, g)), n;
|
|
62
|
+
function C() {
|
|
63
|
+
return f.variant ? f.variant : g?.mode === "render" ? (v || (v = m.value?.variants[g.nextVariantIndex.value], g.nextVariantIndex.value++), v) : null;
|
|
70
64
|
}
|
|
71
65
|
function w(e) {
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
if (g.slotName === "controls") return p.proxy.$slots.controls?.({ state: g.externalState }) ?? null;
|
|
67
|
+
if (g.slotName !== "default") return null;
|
|
68
|
+
let n = p.proxy.$slots.default?.({ state: g.externalState }) ?? null;
|
|
69
|
+
return n && !e.autoPropsDisabled && (_ = t(e, n, g.externalState, _)), n;
|
|
70
|
+
}
|
|
71
|
+
function T(e) {
|
|
72
|
+
if (!h) return;
|
|
73
|
+
let t = g?.mode === "render" && g.currentVariant?.id === e.id;
|
|
74
|
+
t && !y ? y = n(e.state, h(), d) : !t && y && (y.stop(), y = null);
|
|
75
75
|
}
|
|
76
|
-
return
|
|
77
|
-
renderContext:
|
|
78
|
-
renderVariantSlot:
|
|
79
|
-
resolveVariant:
|
|
80
|
-
syncRenderVariantState:
|
|
81
|
-
updateVariant:
|
|
76
|
+
return x.value && S(x.value), {
|
|
77
|
+
renderContext: g,
|
|
78
|
+
renderVariantSlot: w,
|
|
79
|
+
resolveVariant: C,
|
|
80
|
+
syncRenderVariantState: T,
|
|
81
|
+
updateVariant: S
|
|
82
82
|
};
|
|
83
83
|
},
|
|
84
84
|
render() {
|
|
@@ -87,4 +87,4 @@ var d = a({
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
//#endregion
|
|
90
|
-
export {
|
|
90
|
+
export { f as default };
|
|
@@ -9,42 +9,61 @@ function r(e, t) {
|
|
|
9
9
|
let n = [];
|
|
10
10
|
return i(e, t, { index: 0 }, n), n.filter((e) => e.props.length);
|
|
11
11
|
}
|
|
12
|
-
function i(t, n, r,
|
|
13
|
-
for (let
|
|
14
|
-
if (typeof
|
|
15
|
-
let t = r.index++, i = [];
|
|
16
|
-
for (let e
|
|
17
|
-
let r =
|
|
18
|
-
r && (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
case Object: return "object";
|
|
24
|
-
case Array: return "array";
|
|
25
|
-
default: return "unknown";
|
|
26
|
-
}
|
|
27
|
-
}), o = typeof r.default == "function" ? r.default.toString() : r.default), i.push({
|
|
12
|
+
function i(t, n, r, l) {
|
|
13
|
+
for (let u of c(t)) if (u) {
|
|
14
|
+
if (typeof u.type == "object") {
|
|
15
|
+
let t = r.index++, i = [], c = u.type.props, d = Array.isArray(c) ? c : Object.keys(c ?? {});
|
|
16
|
+
for (let e of d) {
|
|
17
|
+
let r = Array.isArray(c) ? void 0 : c[e], l, d;
|
|
18
|
+
if (r && (l = (Array.isArray(r.type) ? r.type : typeof r == "function" ? [r] : [r.type]).map(a), d = typeof r.default == "function" ? r.default.toString() : r.default), !l || l.every((e) => e === "unknown")) {
|
|
19
|
+
let t = o(s(u, e) ?? r?.default);
|
|
20
|
+
t !== "unknown" && (l = [t]);
|
|
21
|
+
}
|
|
22
|
+
i.push({
|
|
28
23
|
name: e,
|
|
29
|
-
types:
|
|
24
|
+
types: l,
|
|
30
25
|
required: r?.required,
|
|
31
|
-
default:
|
|
32
|
-
}), n?._hPropState?.[t]?.[e] != null && (
|
|
26
|
+
default: d
|
|
27
|
+
}), n?._hPropState?.[t]?.[e] != null && (u.props ||= {}, u.props[e] = n._hPropState[t][e], u.dynamicProps ||= [], u.dynamicProps.includes(e) || u.dynamicProps.push(e));
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
name: e(
|
|
29
|
+
l.push({
|
|
30
|
+
name: e(u),
|
|
36
31
|
index: t,
|
|
37
32
|
props: i
|
|
38
33
|
});
|
|
39
34
|
}
|
|
40
|
-
Array.isArray(
|
|
35
|
+
Array.isArray(u.children) && i(u.children, n, r, l);
|
|
41
36
|
}
|
|
42
37
|
}
|
|
43
38
|
function a(e) {
|
|
39
|
+
switch (e) {
|
|
40
|
+
case String: return "string";
|
|
41
|
+
case Number: return "number";
|
|
42
|
+
case Boolean: return "boolean";
|
|
43
|
+
case Object: return "object";
|
|
44
|
+
case Array: return "array";
|
|
45
|
+
default: return "unknown";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function o(e) {
|
|
49
|
+
if (Array.isArray(e)) return "array";
|
|
50
|
+
switch (typeof e) {
|
|
51
|
+
case "string": return "string";
|
|
52
|
+
case "number": return "number";
|
|
53
|
+
case "boolean": return "boolean";
|
|
54
|
+
case "object": return e === null ? "unknown" : "object";
|
|
55
|
+
default: return "unknown";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function s(e, t) {
|
|
59
|
+
let n = e.props;
|
|
60
|
+
if (n) return t in n ? n[t] : n[t.replace(/\B([A-Z])/g, "-$1").toLowerCase()];
|
|
61
|
+
}
|
|
62
|
+
function c(e) {
|
|
44
63
|
if (!Array.isArray(e)) return e == null ? [] : [e];
|
|
45
64
|
let t = [];
|
|
46
|
-
for (let n of e) Array.isArray(n) ? t.push(...
|
|
65
|
+
for (let n of e) Array.isArray(n) ? t.push(...c(n)) : n != null && t.push(n);
|
|
47
66
|
return t;
|
|
48
67
|
}
|
|
49
68
|
//#endregion
|
|
50
|
-
export { n as syncVariantAutoProps };
|
|
69
|
+
export { o as inferredType, s as passedValue, n as syncVariantAutoProps };
|
|
@@ -42,24 +42,30 @@ function f(e, t, n = /* @__PURE__ */ new WeakMap()) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
function p(e, t) {
|
|
45
|
-
|
|
45
|
+
if (!t.length) return e;
|
|
46
|
+
let n = { ...e };
|
|
47
|
+
for (let e of t) delete n[e];
|
|
48
|
+
return n;
|
|
49
|
+
}
|
|
50
|
+
function m(e, t, r = []) {
|
|
51
|
+
let i = s(), c = n(() => e, (e) => {
|
|
46
52
|
if (e == null) return;
|
|
47
|
-
let n =
|
|
53
|
+
let n = i.take(p(d(e), r));
|
|
48
54
|
n && o(t, n);
|
|
49
55
|
}, {
|
|
50
56
|
deep: !0,
|
|
51
57
|
immediate: !0
|
|
52
|
-
}),
|
|
58
|
+
}), u = a(() => t, (t) => {
|
|
53
59
|
if (t == null) return;
|
|
54
|
-
let n =
|
|
60
|
+
let n = i.take(p(l(t), r));
|
|
55
61
|
n && o(e, n);
|
|
56
62
|
}, {
|
|
57
63
|
deep: !0,
|
|
58
64
|
immediate: !0
|
|
59
65
|
});
|
|
60
66
|
return { stop() {
|
|
61
|
-
|
|
67
|
+
c(), u();
|
|
62
68
|
} };
|
|
63
69
|
}
|
|
64
70
|
//#endregion
|
|
65
|
-
export { d as _toRawDeep,
|
|
71
|
+
export { d as _toRawDeep, m as syncStateBundledAndExternal, l as toRawDeep };
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import type { Variant } from '@poveste/shared';
|
|
2
2
|
export declare function syncVariantAutoProps(variant: Variant, vnodes: any, externalState: Variant['state'], lastSnapshot: string): string;
|
|
3
|
+
/** What the value itself says, when the declaration no longer says anything. */
|
|
4
|
+
export declare function inferredType(value: unknown): string;
|
|
5
|
+
export declare function passedValue(vnode: any, key: string): unknown;
|
|
@@ -10,7 +10,8 @@ export declare function _toRawDeep(val: any, seen?: WeakMap<WeakKey, any>): any;
|
|
|
10
10
|
* Synchronize states between the bundled and external/user versions of Vue
|
|
11
11
|
* @param bundledState Reactive state created with the bundled Vue
|
|
12
12
|
* @param externalState Reactive state created with the external/user Vue
|
|
13
|
+
* @param omit Keys neither side may learn from the other
|
|
13
14
|
*/
|
|
14
|
-
export declare function syncStateBundledAndExternal(bundledState: any, externalState: any): {
|
|
15
|
+
export declare function syncStateBundledAndExternal(bundledState: any, externalState: any, omit?: string[]): {
|
|
15
16
|
stop(): void;
|
|
16
17
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poveste/plugin-vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"description": "Poveste plugin for Vue.js support",
|
|
6
6
|
"author": {
|
|
7
|
-
"name": "Sorin Gitlan"
|
|
7
|
+
"name": "Sorin Gitlan",
|
|
8
|
+
"url": "https://x.com/50rayn"
|
|
8
9
|
},
|
|
9
10
|
"license": "MIT",
|
|
10
11
|
"homepage": "https://poveste.dev",
|
|
@@ -69,26 +70,26 @@
|
|
|
69
70
|
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
70
71
|
},
|
|
71
72
|
"peerDependencies": {
|
|
72
|
-
"
|
|
73
|
-
"
|
|
73
|
+
"poveste": "^0.12.0",
|
|
74
|
+
"vue": "^3.5.26"
|
|
74
75
|
},
|
|
75
76
|
"dependencies": {
|
|
77
|
+
"@poveste/controls": "^0.12.0",
|
|
78
|
+
"@poveste/shared": "^0.12.0",
|
|
79
|
+
"@poveste/vendors": "^0.12.0",
|
|
76
80
|
"change-case": "^5.4.4",
|
|
77
81
|
"globby": "^14.0.2",
|
|
78
82
|
"launch-editor": "^2.9.1",
|
|
79
|
-
"pathe": "^1.1.2"
|
|
80
|
-
"@poveste/controls": "^0.10.0",
|
|
81
|
-
"@poveste/shared": "^0.10.0",
|
|
82
|
-
"@poveste/vendors": "^0.10.0"
|
|
83
|
+
"pathe": "^1.1.2"
|
|
83
84
|
},
|
|
84
85
|
"devDependencies": {
|
|
85
86
|
"@types/node": "^22.10.1",
|
|
86
87
|
"concurrently": "^9.1.0",
|
|
88
|
+
"poveste": "0.12.0",
|
|
87
89
|
"typescript": "5.6.3",
|
|
88
90
|
"vite": "^8.2.0",
|
|
89
91
|
"vitest": "^4.1.10",
|
|
90
|
-
"vue": "^3.5.26"
|
|
91
|
-
"poveste": "0.10.0"
|
|
92
|
+
"vue": "^3.5.26"
|
|
92
93
|
},
|
|
93
94
|
"scripts": {
|
|
94
95
|
"build": "rimraf dist && vite build && tsc -d -P tsconfig.build.json && pnpm run build:types",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Variant } from '@poveste/shared'
|
|
2
2
|
import type { ComputedRef, PropType } from 'vue'
|
|
3
|
-
import { applyState } from '@poveste/shared'
|
|
3
|
+
import { applyState, autoPropsStateKeys } from '@poveste/shared'
|
|
4
4
|
import { computed, defineComponent, getCurrentInstance, inject, onBeforeUnmount, useAttrs } from 'vue'
|
|
5
5
|
import { syncVariantAutoProps } from './auto-props.js'
|
|
6
6
|
import { useRenderContext } from './render-context.js'
|
|
@@ -84,7 +84,7 @@ export default defineComponent({
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
if (renderContext?.mode !== 'render' && mountVariant.value && implicitState) {
|
|
87
|
-
mountStateSync = syncStateBundledAndExternal(mountVariant.value.state, implicitState())
|
|
87
|
+
mountStateSync = syncStateBundledAndExternal(mountVariant.value.state, implicitState(), autoPropsStateKeys)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function updateVariant(variant: Variant) {
|
|
@@ -162,7 +162,7 @@ export default defineComponent({
|
|
|
162
162
|
&& renderContext.currentVariant?.id === variant.id
|
|
163
163
|
|
|
164
164
|
if (shouldSync && !renderStateSync) {
|
|
165
|
-
renderStateSync = syncStateBundledAndExternal(variant.state, implicitState())
|
|
165
|
+
renderStateSync = syncStateBundledAndExternal(variant.state, implicitState(), autoPropsStateKeys)
|
|
166
166
|
}
|
|
167
167
|
else if (!shouldSync && renderStateSync) {
|
|
168
168
|
renderStateSync.stop()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { inferredType, passedValue } from '../auto-props.js'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* A production build erases the runtime types Vue infers from a type-only
|
|
6
|
+
* `defineProps`, so the declaration says nothing and the value has to (#490).
|
|
7
|
+
*/
|
|
8
|
+
describe('the type a value implies', () => {
|
|
9
|
+
it.each([
|
|
10
|
+
['a string', 'hello', 'string'],
|
|
11
|
+
['a number', 42, 'number'],
|
|
12
|
+
['zero', 0, 'number'],
|
|
13
|
+
['a boolean', false, 'boolean'],
|
|
14
|
+
['an array', [1, 2], 'array'],
|
|
15
|
+
['an object', { a: 1 }, 'object'],
|
|
16
|
+
])('reads %s', (_, value, expected) => {
|
|
17
|
+
expect(inferredType(value)).toBe(expected)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Nothing to go on, and guessing would pick a control the prop cannot hold.
|
|
21
|
+
// A factory default arrives here as the function it is, which is why nothing
|
|
22
|
+
// upstream has to strip it first.
|
|
23
|
+
it.each([
|
|
24
|
+
['null', null],
|
|
25
|
+
['undefined', undefined],
|
|
26
|
+
['a factory default', () => []],
|
|
27
|
+
])('declines to guess from %s', (_, value) => {
|
|
28
|
+
expect(inferredType(value)).toBe('unknown')
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('the value a vnode passed', () => {
|
|
33
|
+
it('finds a prop under the name it was declared with', () => {
|
|
34
|
+
expect(passedValue({ props: { label: 'x' } }, 'label')).toBe('x')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// `vnode.type.props` is keyed as declared, `vnode.props` as authored.
|
|
38
|
+
it('finds a multi-word prop written in kebab case', () => {
|
|
39
|
+
expect(passedValue({ props: { 'my-label': 'x' } }, 'myLabel')).toBe('x')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('prefers the declared spelling when both are present', () => {
|
|
43
|
+
expect(passedValue({ props: { 'myLabel': 'camel', 'my-label': 'kebab' } }, 'myLabel')).toBe('camel')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('is undefined when the component was given no props at all', () => {
|
|
47
|
+
expect(passedValue({}, 'label')).toBeUndefined()
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { autoPropsStateKeys } from '@poveste/shared'
|
|
1
2
|
import { nextTick as bundledNextTick, reactive as bundledReactive } from '@poveste/vendors/vue'
|
|
2
3
|
import { describe, expect, it } from 'vitest'
|
|
3
4
|
import { nextTick, reactive, watch } from 'vue'
|
|
@@ -17,10 +18,10 @@ async function settle() {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function setup(initial: Record<string, any
|
|
21
|
+
function setup(initial: Record<string, any>, omit: string[] = []) {
|
|
21
22
|
const bundled = bundledReactive(structuredClone(initial))
|
|
22
23
|
const external = reactive(structuredClone(initial))
|
|
23
|
-
const sync = syncStateBundledAndExternal(bundled, external)
|
|
24
|
+
const sync = syncStateBundledAndExternal(bundled, external, omit)
|
|
24
25
|
return { bundled, external, sync }
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -339,3 +340,48 @@ describe('syncStateBundledAndExternal', () => {
|
|
|
339
340
|
sync.stop()
|
|
340
341
|
})
|
|
341
342
|
})
|
|
343
|
+
|
|
344
|
+
/*
|
|
345
|
+
* A variant's state is synced with the story's implicit state, which every
|
|
346
|
+
* variant shares — so anything that crosses reaches all of them. `_hPropState`
|
|
347
|
+
* is keyed by a component's index within one variant, so it must not (#473).
|
|
348
|
+
*/
|
|
349
|
+
describe('keys the sync is told to omit', () => {
|
|
350
|
+
it('does not carry an auto-props edit to the other side', async () => {
|
|
351
|
+
const { bundled, external, sync } = setup({ _hPropState: {}, label: 'a' }, autoPropsStateKeys)
|
|
352
|
+
await settle()
|
|
353
|
+
|
|
354
|
+
external._hPropState = { 0: { name: 'Bender' } }
|
|
355
|
+
await settle()
|
|
356
|
+
|
|
357
|
+
expect(bundled._hPropState).toEqual({})
|
|
358
|
+
|
|
359
|
+
sync.stop()
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('goes on carrying everything else', async () => {
|
|
363
|
+
const { bundled, external, sync } = setup({ _hPropState: {}, label: 'a' }, autoPropsStateKeys)
|
|
364
|
+
await settle()
|
|
365
|
+
|
|
366
|
+
external.label = 'edited'
|
|
367
|
+
await settle()
|
|
368
|
+
|
|
369
|
+
expect(bundled.label).toBe('edited')
|
|
370
|
+
|
|
371
|
+
sync.stop()
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
// The reason the sync exists: a variant with no `initState` picks up the
|
|
375
|
+
// story's own state, and omitting the bookkeeping must not cost that.
|
|
376
|
+
it('still carries a key the story owns', async () => {
|
|
377
|
+
const { bundled, external, sync } = setup({}, autoPropsStateKeys)
|
|
378
|
+
await settle()
|
|
379
|
+
|
|
380
|
+
bundled.fromStory = 'yes'
|
|
381
|
+
await settle()
|
|
382
|
+
|
|
383
|
+
expect(external.fromStory).toBe('yes')
|
|
384
|
+
|
|
385
|
+
sync.stop()
|
|
386
|
+
})
|
|
387
|
+
})
|
|
@@ -45,8 +45,13 @@ function visitVNodes(vnodes: any, externalState: Variant['state'], traversalStat
|
|
|
45
45
|
const index = traversalState.index++
|
|
46
46
|
const propDefs: PropDefinition[] = []
|
|
47
47
|
|
|
48
|
-
for
|
|
49
|
-
|
|
48
|
+
// `for…in` over the array form yields index strings, so every control was
|
|
49
|
+
// named `0` and the prop's own name never appeared (#498).
|
|
50
|
+
const declared = vnode.type.props
|
|
51
|
+
const names = Array.isArray(declared) ? declared : Object.keys(declared ?? {})
|
|
52
|
+
|
|
53
|
+
for (const key of names) {
|
|
54
|
+
const prop = Array.isArray(declared) ? undefined : declared[key]
|
|
50
55
|
let types
|
|
51
56
|
let defaultValue
|
|
52
57
|
|
|
@@ -57,28 +62,26 @@ function visitVNodes(vnodes: any, externalState: Variant['state'], traversalStat
|
|
|
57
62
|
? [prop]
|
|
58
63
|
: [prop.type]
|
|
59
64
|
|
|
60
|
-
types = rawTypes.map(
|
|
61
|
-
switch (type) {
|
|
62
|
-
case String:
|
|
63
|
-
return 'string'
|
|
64
|
-
case Number:
|
|
65
|
-
return 'number'
|
|
66
|
-
case Boolean:
|
|
67
|
-
return 'boolean'
|
|
68
|
-
case Object:
|
|
69
|
-
return 'object'
|
|
70
|
-
case Array:
|
|
71
|
-
return 'array'
|
|
72
|
-
default:
|
|
73
|
-
return 'unknown'
|
|
74
|
-
}
|
|
75
|
-
})
|
|
65
|
+
types = rawTypes.map(declaredType)
|
|
76
66
|
|
|
77
67
|
defaultValue = typeof prop.default === 'function'
|
|
78
68
|
? prop.default.toString()
|
|
79
69
|
: prop.default
|
|
80
70
|
}
|
|
81
71
|
|
|
72
|
+
// A production build erases the runtime types Vue infers from a
|
|
73
|
+
// type-only `defineProps`, leaving `props: { label: {} }` (#490), and
|
|
74
|
+
// the array form declares none to begin with (#498). Defaults survive
|
|
75
|
+
// erasure and the passed value is still there, and either is enough to
|
|
76
|
+
// pick a control. A factory default reaches `inferredType` as the
|
|
77
|
+
// function it is, and is declined there with everything else untyped.
|
|
78
|
+
if (!types || types.every(type => type === 'unknown')) {
|
|
79
|
+
const inferred = inferredType(passedValue(vnode, key) ?? prop?.default)
|
|
80
|
+
if (inferred !== 'unknown') {
|
|
81
|
+
types = [inferred]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
82
85
|
propDefs.push({
|
|
83
86
|
name: key,
|
|
84
87
|
types,
|
|
@@ -115,6 +118,52 @@ function visitVNodes(vnodes: any, externalState: Variant['state'], traversalStat
|
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
function declaredType(type: unknown): string {
|
|
122
|
+
switch (type) {
|
|
123
|
+
case String:
|
|
124
|
+
return 'string'
|
|
125
|
+
case Number:
|
|
126
|
+
return 'number'
|
|
127
|
+
case Boolean:
|
|
128
|
+
return 'boolean'
|
|
129
|
+
case Object:
|
|
130
|
+
return 'object'
|
|
131
|
+
case Array:
|
|
132
|
+
return 'array'
|
|
133
|
+
default:
|
|
134
|
+
return 'unknown'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** What the value itself says, when the declaration no longer says anything. */
|
|
139
|
+
export function inferredType(value: unknown): string {
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return 'array'
|
|
142
|
+
}
|
|
143
|
+
switch (typeof value) {
|
|
144
|
+
case 'string':
|
|
145
|
+
return 'string'
|
|
146
|
+
case 'number':
|
|
147
|
+
return 'number'
|
|
148
|
+
case 'boolean':
|
|
149
|
+
return 'boolean'
|
|
150
|
+
case 'object':
|
|
151
|
+
return value === null ? 'unknown' : 'object'
|
|
152
|
+
default:
|
|
153
|
+
return 'unknown'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// `vnode.type.props` is keyed as declared and `vnode.props` as authored, so a
|
|
158
|
+
// multi-word prop written `my-label` is not found under `myLabel`.
|
|
159
|
+
export function passedValue(vnode: any, key: string): unknown {
|
|
160
|
+
const props = vnode.props
|
|
161
|
+
if (!props) {
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
return key in props ? props[key] : props[key.replace(/\B([A-Z])/g, '-$1').toLowerCase()]
|
|
165
|
+
}
|
|
166
|
+
|
|
118
167
|
function normalizeVNodes(vnodes: any) {
|
|
119
168
|
if (!Array.isArray(vnodes)) {
|
|
120
169
|
return vnodes == null ? [] : [vnodes]
|
package/src/client/app/util.ts
CHANGED
|
@@ -84,12 +84,26 @@ function _toRawObject(obj: Record<any, any>, target: Record<any, any>, seen = ne
|
|
|
84
84
|
})
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Kept out of the baseline rather than out of the write, so the far side is
|
|
88
|
+
// never told these changed and an echo cannot carry them back.
|
|
89
|
+
function without(state: Record<string, any>, omit: string[]): Record<string, any> {
|
|
90
|
+
if (!omit.length) {
|
|
91
|
+
return state
|
|
92
|
+
}
|
|
93
|
+
const kept = { ...state }
|
|
94
|
+
for (const key of omit) {
|
|
95
|
+
delete kept[key]
|
|
96
|
+
}
|
|
97
|
+
return kept
|
|
98
|
+
}
|
|
99
|
+
|
|
87
100
|
/**
|
|
88
101
|
* Synchronize states between the bundled and external/user versions of Vue
|
|
89
102
|
* @param bundledState Reactive state created with the bundled Vue
|
|
90
103
|
* @param externalState Reactive state created with the external/user Vue
|
|
104
|
+
* @param omit Keys neither side may learn from the other
|
|
91
105
|
*/
|
|
92
|
-
export function syncStateBundledAndExternal(bundledState, externalState) {
|
|
106
|
+
export function syncStateBundledAndExternal(bundledState, externalState, omit: string[] = []) {
|
|
93
107
|
// Each side is asked only for what *it* changed, and that is all that crosses.
|
|
94
108
|
//
|
|
95
109
|
// Both watchers used to mirror the whole of their side. That works while the
|
|
@@ -109,7 +123,7 @@ export function syncStateBundledAndExternal(bundledState, externalState) {
|
|
|
109
123
|
|
|
110
124
|
const _stop = _watch(() => bundledState, (value) => {
|
|
111
125
|
if (value == null) return
|
|
112
|
-
const changes = baseline.take(_toRawDeep(value))
|
|
126
|
+
const changes = baseline.take(without(_toRawDeep(value), omit))
|
|
113
127
|
if (changes) applyState(externalState, changes)
|
|
114
128
|
}, {
|
|
115
129
|
deep: true,
|
|
@@ -118,7 +132,7 @@ export function syncStateBundledAndExternal(bundledState, externalState) {
|
|
|
118
132
|
|
|
119
133
|
const stop = watch(() => externalState, (value) => {
|
|
120
134
|
if (value == null) return
|
|
121
|
-
const changes = baseline.take(toRawDeep(value))
|
|
135
|
+
const changes = baseline.take(without(toRawDeep(value), omit))
|
|
122
136
|
if (changes) applyState(bundledState, changes)
|
|
123
137
|
}, {
|
|
124
138
|
deep: true,
|