diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 777f9677d04..376527fe108 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -210,6 +210,45 @@ describe('defineCustomElement', () => { customElements.define('my-el-upgrade', E) expect(el.shadowRoot.innerHTML).toBe(`foo: hello`) }) + + // #5687 + test('using .xx-xx underlined naming conventions when used by WebComponent is reactive', () => { + const E = defineCustomElement({ + props: { + maxAge: Number + }, + render() { + return `max age: ${this.maxAge}` + } + }) + customElements.define('my-element-hyphenate', E) + const el = document.createElement('my-element-hyphenate') as any + el.setAttribute('max-age', 0) + container.appendChild(el) + expect(el.maxAge).toBe(0) + el.maxAge = 50 + expect(el.maxAge).toBe(50) + el['max-age'] = 100 + expect(el.maxAge).toBe(100) + }) + + // # 5793 + test('set number value in dom property', () => { + const E = defineCustomElement({ + props: { + maxAge: Number + }, + render() { + return `max age: ${this.maxAge}/type: ${typeof this.maxAge}` + } + }) + customElements.define('my-element-number-property', E) + const el = document.createElement('my-element-number-property') as any + container.appendChild(el) + el.maxAge = 50 + expect(el.maxAge).toBe(50) + expect(el.shadowRoot.innerHTML).toBe('max age: 50/type: number') + }) }) describe('emits', () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 059fcac16f1..36eef69dfe2 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -222,10 +222,12 @@ export class VueElement extends BaseClass { // cast Number-type props set before resolve let numberProps if (hasOptions) { - for (const key in this._props) { + for (const key in props) { const opt = props[key] if (opt === Number || (opt && opt.type === Number)) { - this._props[key] = toNumber(this._props[key]) + if (key in this._props) { + this._props[key] = toNumber(this._props[key]) + } ;(numberProps || (numberProps = Object.create(null)))[key] = true } } @@ -241,14 +243,10 @@ export class VueElement extends BaseClass { // defining getter/setters on prototype for (const key of rawKeys.map(camelize)) { - Object.defineProperty(this, key, { - get() { - return this._getProp(key) - }, - set(val) { - this._setProp(key, val) - } - }) + this._defineElementProperty(key) + if (hyphenate(key) !== key) { + this._defineElementProperty(hyphenate(key), key) + } } // apply CSS @@ -266,9 +264,23 @@ export class VueElement extends BaseClass { } } + private _defineElementProperty(key: string, originalKey = key) { + Object.defineProperty(this, key, { + get() { + return this._getProp(originalKey) + }, + set(val) { + this._setProp(originalKey, val) + } + }) + } + protected _setAttr(key: string) { let value = this.getAttribute(key) - if (this._numberProps && this._numberProps[key]) { + if ( + this._numberProps && + (this._numberProps[key] || this._numberProps[camelize(key)]) + ) { value = toNumber(value) } this._setProp(camelize(key), value, false)