From 690fa4ec7af61ca704105f78d38973a06768a0c7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 10 Oct 2024 20:15:19 +0800 Subject: [PATCH 1/3] fix(customElement): properly remove hyphenated attribute with null value --- packages/runtime-dom/src/modules/props.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/runtime-dom/src/modules/props.ts b/packages/runtime-dom/src/modules/props.ts index 93d45c9e160..8577520dc4a 100644 --- a/packages/runtime-dom/src/modules/props.ts +++ b/packages/runtime-dom/src/modules/props.ts @@ -1,6 +1,7 @@ import { DeprecationTypes, compatUtils, warn } from '@vue/runtime-core' -import { includeBooleanAttr } from '@vue/shared' +import { hyphenate, includeBooleanAttr } from '@vue/shared' import { unsafeToTrustedHTML } from '../nodeOps' +import type { VueElement } from '@vue/runtime-dom' // functions. The user is responsible for using them with only trusted content. export function patchDOMProp( @@ -106,5 +107,6 @@ export function patchDOMProp( ) } } - needRemove && el.removeAttribute(key) + needRemove && + el.removeAttribute((el as VueElement)._isVueCE ? hyphenate(key) : key) } From bf15662977ed9a61df90221659d79b3fe81491e7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 10 Oct 2024 21:44:13 +0800 Subject: [PATCH 2/3] chore: update --- .../__tests__/customElement.spec.ts | 35 +++++++++++++++++++ packages/runtime-dom/src/modules/props.ts | 7 ++-- packages/runtime-dom/src/patchProp.ts | 2 +- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index ef5051f42f7..6b9f7d1391e 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1386,4 +1386,39 @@ describe('defineCustomElement', () => { await nextTick() expect(e.shadowRoot!.innerHTML).toBe(`false,boolean`) }) + + test('hyphenated attr removal', async () => { + const E = defineCustomElement({ + props: { + fooBar: { + type: Boolean, + }, + }, + render() { + return this.fooBar + }, + }) + customElements.define('el-hyphenated-attr-removal', E) + const toggle = ref(true) + const Comp = { + render() { + return h('el-hyphenated-attr-removal', { + 'foo-bar': toggle.value ? '' : null, + }) + }, + } + render(h(Comp), container) + const el = container.children[0] + expect(el.hasAttribute('foo-bar')).toBe(true) + expect((el as any).outerHTML).toBe( + ``, + ) + + toggle.value = false + await nextTick() + expect(el.hasAttribute('foo-bar')).toBe(false) + expect((el as any).outerHTML).toBe( + ``, + ) + }) }) diff --git a/packages/runtime-dom/src/modules/props.ts b/packages/runtime-dom/src/modules/props.ts index 8577520dc4a..ace696bf7e1 100644 --- a/packages/runtime-dom/src/modules/props.ts +++ b/packages/runtime-dom/src/modules/props.ts @@ -1,7 +1,6 @@ import { DeprecationTypes, compatUtils, warn } from '@vue/runtime-core' -import { hyphenate, includeBooleanAttr } from '@vue/shared' +import { includeBooleanAttr } from '@vue/shared' import { unsafeToTrustedHTML } from '../nodeOps' -import type { VueElement } from '@vue/runtime-dom' // functions. The user is responsible for using them with only trusted content. export function patchDOMProp( @@ -9,6 +8,7 @@ export function patchDOMProp( key: string, value: any, parentComponent: any, + attrName: string = '', ): void { // __UNSAFE__ // Reason: potentially setting innerHTML. @@ -107,6 +107,5 @@ export function patchDOMProp( ) } } - needRemove && - el.removeAttribute((el as VueElement)._isVueCE ? hyphenate(key) : key) + needRemove && el.removeAttribute(attrName || key) } diff --git a/packages/runtime-dom/src/patchProp.ts b/packages/runtime-dom/src/patchProp.ts index 5814e77c4f8..b6af8997112 100644 --- a/packages/runtime-dom/src/patchProp.ts +++ b/packages/runtime-dom/src/patchProp.ts @@ -62,7 +62,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = ( (el as VueElement)._isVueCE && (/[A-Z]/.test(key) || !isString(nextValue)) ) { - patchDOMProp(el, camelize(key), nextValue, parentComponent) + patchDOMProp(el, camelize(key), nextValue, parentComponent, key) } else { // special case for with // :true-value & :false-value From 107ec22e0c73bb2e706a1551151dd071f78d606f Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 10 Oct 2024 21:48:00 +0800 Subject: [PATCH 3/3] chore: update --- packages/runtime-dom/src/modules/props.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-dom/src/modules/props.ts b/packages/runtime-dom/src/modules/props.ts index ace696bf7e1..98608831a9a 100644 --- a/packages/runtime-dom/src/modules/props.ts +++ b/packages/runtime-dom/src/modules/props.ts @@ -8,7 +8,7 @@ export function patchDOMProp( key: string, value: any, parentComponent: any, - attrName: string = '', + attrName?: string, ): void { // __UNSAFE__ // Reason: potentially setting innerHTML.