diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts
index 042ac68a7af..7e69ec28ef2 100644
--- a/packages/runtime-dom/__tests__/customElement.spec.ts
+++ b/packages/runtime-dom/__tests__/customElement.spec.ts
@@ -136,26 +136,40 @@ describe('defineCustomElement', () => {
const E = defineCustomElement({
props: {
foo: Number,
- bar: Boolean
+ bar: Boolean,
+ baz: String
},
render() {
- return [this.foo, typeof this.foo, this.bar, typeof this.bar].join(
- ' '
- )
+ return [
+ this.foo,
+ typeof this.foo,
+ this.bar,
+ typeof this.bar,
+ this.baz,
+ typeof this.baz
+ ].join(' ')
}
})
customElements.define('my-el-props-cast', E)
- container.innerHTML = ``
+ container.innerHTML = ``
const e = container.childNodes[0] as VueElement
- expect(e.shadowRoot!.innerHTML).toBe(`1 number false boolean`)
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `1 number false boolean 12345 string`
+ )
e.setAttribute('bar', '')
await nextTick()
- expect(e.shadowRoot!.innerHTML).toBe(`1 number true boolean`)
+ expect(e.shadowRoot!.innerHTML).toBe(`1 number true boolean 12345 string`)
e.setAttribute('foo', '2e1')
await nextTick()
- expect(e.shadowRoot!.innerHTML).toBe(`20 number true boolean`)
+ expect(e.shadowRoot!.innerHTML).toBe(
+ `20 number true boolean 12345 string`
+ )
+
+ e.setAttribute('baz', '2e1')
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(`20 number true boolean 2e1 string`)
})
test('handling properties set before upgrading', () => {
@@ -392,5 +406,25 @@ describe('defineCustomElement', () => {
e2.msg = 'hello'
expect(e2.shadowRoot!.innerHTML).toBe(`
hello
`)
})
+
+ test('Number prop casting before resolve', async () => {
+ const E = defineCustomElement(
+ defineAsyncComponent(() => {
+ return Promise.resolve({
+ props: { n: Number },
+ render(this: any) {
+ return h('div', this.n + ',' + typeof this.n)
+ }
+ })
+ })
+ )
+ customElements.define('my-el-async-3', E)
+ container.innerHTML = ``
+
+ await new Promise(r => setTimeout(r))
+
+ const e = container.childNodes[0] as VueElement
+ expect(e.shadowRoot!.innerHTML).toBe(`20,number
`)
+ })
})
})
diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts
index ca29a436c72..f72b8765f6d 100644
--- a/packages/runtime-dom/src/apiCustomElement.ts
+++ b/packages/runtime-dom/src/apiCustomElement.ts
@@ -154,6 +154,7 @@ export class VueElement extends BaseClass {
private _connected = false
private _resolved = false
+ private _numberProps: Record | null = null
private _styles?: HTMLStyleElement[]
constructor(
@@ -179,19 +180,18 @@ export class VueElement extends BaseClass {
this._setAttr(this.attributes[i].name)
}
// watch future attr changes
- const observer = new MutationObserver(mutations => {
+ new MutationObserver(mutations => {
for (const m of mutations) {
this._setAttr(m.attributeName!)
}
- })
- observer.observe(this, { attributes: true })
+ }).observe(this, { attributes: true })
}
connectedCallback() {
this._connected = true
if (!this._instance) {
this._resolveDef()
- render(this._createVNode(), this.shadowRoot!)
+ this._update()
}
}
@@ -215,15 +215,33 @@ export class VueElement extends BaseClass {
const resolve = (def: InnerComponentDef) => {
this._resolved = true
+ const { props, styles } = def
+ const hasOptions = !isArray(props)
+ const rawKeys = props ? (hasOptions ? Object.keys(props) : props) : []
+
+ // cast Number-type props set before resolve
+ let numberProps
+ if (hasOptions) {
+ for (const key in this._props) {
+ const opt = props[key]
+ if (opt === Number || (opt && opt.type === Number)) {
+ this._props[key] = toNumber(this._props[key])
+ ;(numberProps || (numberProps = Object.create(null)))[key] = true
+ }
+ }
+ }
+ if (numberProps) {
+ this._numberProps = numberProps
+ this._update()
+ }
+
// check if there are props set pre-upgrade or connect
for (const key of Object.keys(this)) {
if (key[0] !== '_') {
this._setProp(key, this[key as keyof this])
}
}
- const { props, styles } = def
// defining getter/setters on prototype
- const rawKeys = props ? (isArray(props) ? props : Object.keys(props)) : []
for (const key of rawKeys.map(camelize)) {
Object.defineProperty(this, key, {
get() {
@@ -246,7 +264,11 @@ export class VueElement extends BaseClass {
}
protected _setAttr(key: string) {
- this._setProp(camelize(key), toNumber(this.getAttribute(key)), false)
+ let value = this.getAttribute(key)
+ if (this._numberProps && this._numberProps[key]) {
+ value = toNumber(value)
+ }
+ this._setProp(camelize(key), value, false)
}
/**
@@ -263,7 +285,7 @@ export class VueElement extends BaseClass {
if (val !== this._props[key]) {
this._props[key] = val
if (this._instance) {
- render(this._createVNode(), this.shadowRoot!)
+ this._update()
}
// reflect
if (shouldReflect) {
@@ -278,6 +300,10 @@ export class VueElement extends BaseClass {
}
}
+ private _update() {
+ render(this._createVNode(), this.shadowRoot!)
+ }
+
private _createVNode(): VNode {
const vnode = createVNode(this._def, extend({}, this._props))
if (!this._instance) {
@@ -298,7 +324,7 @@ export class VueElement extends BaseClass {
if (!(this._def as ComponentOptions).__asyncLoader) {
// reload
this._instance = null
- render(this._createVNode(), this.shadowRoot!)
+ this._update()
}
}
}