From b805cefedcce1d7e64dd585bd15396d117328c68 Mon Sep 17 00:00:00 2001 From: lejunyang Date: Sat, 19 Oct 2024 01:29:41 +0800 Subject: [PATCH 1/2] fix(custom-element): fix that boolean prop with default true can't updated to falsy value (#12214) --- .../__tests__/customElement.spec.ts | 32 +++++++++++++++++++ packages/runtime-dom/src/apiCustomElement.ts | 8 ++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 6b9f7d1391e..df438d47eee 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -396,6 +396,38 @@ describe('defineCustomElement', () => { expect(e.value).toBe('hi') }) + // #12214 + test('Boolean prop with default true', async () => { + const E = defineCustomElement({ + props: { + foo: { + type: Boolean, + default: true, + }, + }, + render() { + return String(this.foo) + }, + }) + customElements.define('my-el-default-true', E) + container.innerHTML = `` + const e = container.childNodes[0] as HTMLElement & { foo: any }, + shadowRoot = e.shadowRoot as ShadowRoot + expect(shadowRoot.innerHTML).toBe('true') + e.foo = undefined + await nextTick() + expect(shadowRoot.innerHTML).toBe('true') + e.foo = false + await nextTick() + expect(shadowRoot.innerHTML).toBe('false') + e.foo = null + await nextTick() + expect(shadowRoot.innerHTML).toBe('null') + e.foo = '' + await nextTick() + expect(shadowRoot.innerHTML).toBe('true') + }) + test('support direct setup function syntax with extra options', () => { const E = defineCustomElement( props => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 6ddaf897130..a178459b543 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -242,6 +242,7 @@ export class VueElement private _childStyles?: Map private _ob?: MutationObserver | null = null private _slots?: Record + private _skipRemoveSet = new Set() constructor( /** @@ -466,8 +467,12 @@ export class VueElement protected _setAttr(key: string): void { if (key.startsWith('data-v-')) return const has = this.hasAttribute(key) - let value = has ? this.getAttribute(key) : REMOVAL const camelKey = camelize(key) + let value = has ? this.getAttribute(key) : REMOVAL + if (this._skipRemoveSet.has(camelKey)) { + if (value === REMOVAL) return + else this._skipRemoveSet.delete(camelKey) + } if (has && this._numberProps && this._numberProps[camelKey]) { value = toNumber(value) } @@ -510,6 +515,7 @@ export class VueElement } else if (typeof val === 'string' || typeof val === 'number') { this.setAttribute(hyphenate(key), val + '') } else if (!val) { + this._skipRemoveSet.add(key) this.removeAttribute(hyphenate(key)) } } From 9cd4dda5aa2dfb3bd9d6746af0059843ee14c8dd Mon Sep 17 00:00:00 2001 From: daiwei Date: Mon, 21 Oct 2024 08:55:46 +0800 Subject: [PATCH 2/2] chore: minor tweaks --- packages/runtime-dom/src/apiCustomElement.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index a178459b543..c90c5725e17 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -232,6 +232,8 @@ export class VueElement private _styleChildren = new WeakSet() private _pendingResolve: Promise | undefined private _parent: VueElement | undefined + private _removedAttributes = new Set() + /** * dev only */ @@ -242,7 +244,6 @@ export class VueElement private _childStyles?: Map private _ob?: MutationObserver | null = null private _slots?: Record - private _skipRemoveSet = new Set() constructor( /** @@ -468,11 +469,11 @@ export class VueElement if (key.startsWith('data-v-')) return const has = this.hasAttribute(key) const camelKey = camelize(key) - let value = has ? this.getAttribute(key) : REMOVAL - if (this._skipRemoveSet.has(camelKey)) { - if (value === REMOVAL) return - else this._skipRemoveSet.delete(camelKey) + if (this._removedAttributes.has(camelKey)) { + if (!has) return + else this._removedAttributes.delete(camelKey) } + let value = has ? this.getAttribute(key) : REMOVAL if (has && this._numberProps && this._numberProps[camelKey]) { value = toNumber(value) } @@ -515,8 +516,8 @@ export class VueElement } else if (typeof val === 'string' || typeof val === 'number') { this.setAttribute(hyphenate(key), val + '') } else if (!val) { - this._skipRemoveSet.add(key) this.removeAttribute(hyphenate(key)) + this._removedAttributes.add(key) } } }