From b94c3a4754df5d8d2f4a571c96d84d602844d012 Mon Sep 17 00:00:00 2001 From: mattheww-skyward Date: Mon, 28 Jul 2025 15:06:17 -0500 Subject: [PATCH 1/2] test(custom-element): Add custom property override test --- .../__tests__/.customElement.spec.ts.swp | Bin 0 -> 16384 bytes .../__tests__/customElement.spec.ts | 22 ++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 packages/runtime-dom/__tests__/.customElement.spec.ts.swp diff --git a/packages/runtime-dom/__tests__/.customElement.spec.ts.swp b/packages/runtime-dom/__tests__/.customElement.spec.ts.swp new file mode 100644 index 0000000000000000000000000000000000000000..073ed14222bbaae0f6369de9fa700d7874b10af7 GIT binary patch literal 16384 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 OhcG}^#7gOe&wl_C2l2lE literal 0 HcmV?d00001 diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 07ea091486e..0e86b33263d 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -474,6 +474,28 @@ describe('defineCustomElement', () => { '
1 is numbertrue is boolean
', ) }) + + test('prop overrides', async () => { + const EBase = defineCustomElement({ + props: { + value: Number, + }, + render() { + return h('span', this.value) + }, + }) + class E extends EBase { + set value(newValue: number) { + super.value = newValue + 1 + } + } + customElements.define('my-element-with-prop-overrides', E) + const el = document.createElement('my-element-with-prop-overrides') as any + container.appendChild(el) + el.value = 999 + await nextTick() + expect(el.shadowRoot.firstChild.innerHTML).toBe('1000') + }) }) describe('attrs', () => { From 8a0b7142d9efc10659448c0559f0929e1790bc8d Mon Sep 17 00:00:00 2001 From: mattheww-skyward Date: Mon, 28 Jul 2025 15:06:19 -0500 Subject: [PATCH 2/2] feat(custom-element): Allow overriding properties --- packages/runtime-dom/src/apiCustomElement.ts | 52 ++++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index edf7c431353..8528c91a19b 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -176,11 +176,13 @@ export function defineCustomElement( if (isPlainObject(Comp)) extend(Comp, extraOptions) class VueCustomElement extends VueElement { static def = Comp + static asyncDef: any | null = null constructor(initialProps?: Record) { - super(Comp, initialProps, _createApp) + super(VueCustomElement.def, initialProps, _createApp) } } + _defineProps(VueCustomElement.prototype, Comp) return VueCustomElement } @@ -406,7 +408,16 @@ export class VueElement if (asyncDef) { this._pendingResolve = asyncDef().then((def: InnerComponentDef) => { def.configureApp = this._def.configureApp - resolve((this._def = def), true) + this._def = def + const ctor = this.constructor as any + if (!ctor.asyncDef) { + const proto = Object.getPrototypeOf(this) + _defineProps(proto, def) + ctor.asyncDef = def + } else if (ctor.asyncDef !== def) { + warn('async loader returned inconsistent value') + } + resolve(def, true) }) } else { resolve(this._def) @@ -431,7 +442,7 @@ export class VueElement const exposed = this._instance && this._instance.exposed if (!exposed) return for (const key in exposed) { - if (!hasOwn(this, key)) { + if (!hasOwn(this, key) && !hasOwn(Object.getPrototypeOf(this), key)) { // exposed properties are readonly Object.defineProperty(this, key, { // unwrap ref to be consistent with public instance behavior @@ -451,20 +462,9 @@ export class VueElement for (const key of Object.keys(this)) { if (key[0] !== '_' && declaredPropKeys.includes(key)) { this._setProp(key, this[key as keyof this]) + delete this[key as keyof this] } } - - // defining getter/setters on prototype - for (const key of declaredPropKeys.map(camelize)) { - Object.defineProperty(this, key, { - get() { - return this._getProp(key) - }, - set(val) { - this._setProp(key, val, true, true) - }, - }) - } } protected _setAttr(key: string): void { @@ -688,6 +688,28 @@ export class VueElement } } +function _defineProps(proto: object, def: InnerComponentDef) { + const { props } = def + const declaredPropKeys = isArray(props) ? props : Object.keys(props || {}) + // defining getter/setters on prototype + for (const key of declaredPropKeys.map(camelize)) { + Object.defineProperty(proto, key, { + get() { + return this._getProp(key) + }, + set(val) { + if (this._resolved) { + this._setProp(key, val, true, true) + } else { + // when pre-upgrade or connect, set values directly on the instance + Reflect.set({}, key, val, this) + } + }, + enumerable: true, + }) + } +} + export function useHost(caller?: string): VueElement | null { const instance = getCurrentInstance() const el = instance && (instance.ce as VueElement)