diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts
index 03a5361d690..09ee971d225 100644
--- a/packages/runtime-dom/__tests__/customElement.spec.ts
+++ b/packages/runtime-dom/__tests__/customElement.spec.ts
@@ -230,6 +230,33 @@ describe('defineCustomElement', () => {
})
})
+ describe('attrs', () => {
+ const E = defineCustomElement({
+ render() {
+ return [h('div', null, this.$attrs.foo as string)]
+ }
+ })
+ customElements.define('my-el-attrs', E)
+
+ test('attrs via attribute', async () => {
+ container.innerHTML = ``
+ const e = container.childNodes[0] as VueElement
+ expect(e.shadowRoot!.innerHTML).toBe('
hello
')
+
+ e.setAttribute('foo', 'changed')
+ await nextTick()
+ expect(e.shadowRoot!.innerHTML).toBe('changed
')
+ })
+
+ test('non-declared properties should not show up in $attrs', () => {
+ const e = new E()
+ // @ts-ignore
+ e.foo = '123'
+ container.appendChild(e)
+ expect(e.shadowRoot!.innerHTML).toBe('')
+ })
+ })
+
describe('emits', () => {
const CompDef = defineComponent({
setup(_, { emit }) {
diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts
index 55530b98c32..ebb1eccc0bd 100644
--- a/packages/runtime-dom/src/apiCustomElement.ts
+++ b/packages/runtime-dom/src/apiCustomElement.ts
@@ -228,13 +228,12 @@ export class VueElement extends BaseClass {
}).observe(this, { attributes: true })
const resolve = (def: InnerComponentDef) => {
- const { props = {}, styles } = def
- const hasOptions = !isArray(props)
- const rawKeys = props ? (hasOptions ? Object.keys(props) : props) : []
+ const { props, styles } = def
+ const declaredPropKeys = isArray(props) ? props : Object.keys(props || {})
// cast Number-type props set before resolve
let numberProps
- if (hasOptions) {
+ if (props && !isArray(props)) {
for (const key in this._props) {
const opt = props[key]
if (opt === Number || (opt && opt.type === Number)) {
@@ -247,18 +246,13 @@ export class VueElement extends BaseClass {
// check if there are props set pre-upgrade or connect
for (const key of Object.keys(this)) {
- if (key[0] !== '_') {
- this._setProp(
- key,
- this[key as keyof this],
- rawKeys.includes(key),
- false
- )
+ if (key[0] !== '_' && declaredPropKeys.includes(key)) {
+ this._setProp(key, this[key as keyof this], true, false)
}
}
// defining getter/setters on prototype
- for (const key of rawKeys.map(camelize)) {
+ for (const key of declaredPropKeys.map(camelize)) {
Object.defineProperty(this, key, {
get() {
return this._getProp(key)