diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts
index da0d614f6..ac66230e3 100644
--- a/packages/runtime-dom/__tests__/customElement.spec.ts
+++ b/packages/runtime-dom/__tests__/customElement.spec.ts
@@ -1241,4 +1241,25 @@ describe('defineCustomElement', () => {
expect(e.shadowRoot!.innerHTML).toBe(`
fooValue
`)
app.unmount()
})
+
+ // #11276
+ test('delete prop on attr removal', async () => {
+ const E = defineCustomElement({
+ props: {
+ boo: {
+ type: Boolean,
+ },
+ },
+ render() {
+ return this.boo + ',' + typeof this.boo
+ },
+ })
+ customElements.define('el-attr-removal', E)
+ container.innerHTML = ''
+ const e = container.childNodes[0] as VueElement
+ expect(e.shadowRoot!.innerHTML).toBe(`true,boolean`)
+ e.removeAttribute('boo')
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe(`false,boolean`)
+ })
})
diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts
index aa191ba6c..fe0e4d90b 100644
--- a/packages/runtime-dom/src/apiCustomElement.ts
+++ b/packages/runtime-dom/src/apiCustomElement.ts
@@ -42,6 +42,9 @@ import {
} from '@vue/shared'
import { createApp, createSSRApp, render } from '.'
+// marker for attr removal
+const REMOVAL = {}
+
export type VueElementConstructor = {
new (initialProps?: Record): VueElement & P
}
@@ -455,9 +458,10 @@ export class VueElement
protected _setAttr(key: string) {
if (key.startsWith('data-v-')) return
- let value = this.hasAttribute(key) ? this.getAttribute(key) : undefined
+ const has = this.hasAttribute(key)
+ let value = has ? this.getAttribute(key) : REMOVAL
const camelKey = camelize(key)
- if (this._numberProps && this._numberProps[camelKey]) {
+ if (has && this._numberProps && this._numberProps[camelKey]) {
value = toNumber(value)
}
this._setProp(camelKey, value, false, true)
@@ -475,7 +479,11 @@ export class VueElement
*/
_setProp(key: string, val: any, shouldReflect = true, shouldUpdate = false) {
if (val !== this._props[key]) {
- this._props[key] = val
+ if (val === REMOVAL) {
+ delete this._props[key]
+ } else {
+ this._props[key] = val
+ }
if (shouldUpdate && this._instance) {
this._update()
}