Просмотр исходного кода

fix(custom-element): should properly patch as props for vue custom elements (#12409)

close #12408
lejunyang 1 месяц назад
Родитель
Сommit
740983e673

+ 57 - 0
packages/runtime-dom/__tests__/customElement.spec.ts

@@ -711,6 +711,27 @@ describe('defineCustomElement', () => {
       expect(e.shadowRoot!.innerHTML).toBe('<div></div>')
     })
 
+    // #12408
+    test('should set number tabindex as attribute', () => {
+      render(h('my-el-attrs', { tabindex: 1, 'data-test': true }), container)
+      const el = container.children[0] as HTMLElement
+      expect(el.getAttribute('tabindex')).toBe('1')
+      expect(el.getAttribute('data-test')).toBe('true')
+    })
+
+    test('should keep undeclared native attrs as attrs', () => {
+      const root = document.createElement('div')
+      document.body.appendChild(root)
+
+      render(h('my-el-attrs', { translate: 'no' }), root)
+      const el = root.children[0] as HTMLElement
+      expect(el.getAttribute('translate')).toBe('no')
+      expect(el.translate).toBe(false)
+
+      render(null, root)
+      root.remove()
+    })
+
     // https://github.com/vuejs/core/issues/12964
     // Disabled because of missing support for `delegatesFocus` in jsdom
     // https://github.com/jsdom/jsdom/issues/3418
@@ -1333,6 +1354,42 @@ describe('defineCustomElement', () => {
       expect(e2.shadowRoot!.innerHTML).toBe(`<div>hello</div>`)
     })
 
+    test('render object prop before resolve', async () => {
+      const AsyncComp = defineComponent({
+        props: { value: Object },
+        render(this: any) {
+          return h('div', this.value.x)
+        },
+      })
+      let resolve!: (comp: typeof AsyncComp) => void
+      const p = new Promise<typeof AsyncComp>(res => {
+        resolve = res
+      })
+      const E = defineCustomElement(defineAsyncComponent(() => p))
+      customElements.define('my-el-async-object-prop', E)
+
+      const root = document.createElement('div')
+      document.body.appendChild(root)
+      const value = { x: 1 }
+
+      render(h('my-el-async-object-prop', { value }), root)
+
+      const el = root.children[0] as VueElement & { value: typeof value }
+      expect(el.value).toBe(value)
+      expect(el.getAttribute('value')).toBe(null)
+
+      resolve(AsyncComp)
+
+      await new Promise(r => setTimeout(r))
+
+      expect(el.value).toBe(value)
+      expect(el.getAttribute('value')).toBe(null)
+      expect(el.shadowRoot!.innerHTML).toBe(`<div>1</div>`)
+
+      render(null, root)
+      root.remove()
+    })
+
     test('Number prop casting before resolve', async () => {
       const E = defineCustomElement(
         defineAsyncComponent(() => {

+ 18 - 1
packages/runtime-dom/src/patchProp.ts

@@ -60,7 +60,11 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
   } else if (
     // #11081 force set props for possible async custom element
     (el as VueElement)._isVueCE &&
-    (/[A-Z]/.test(key) || !isString(nextValue))
+    // #12408 check if it's declared prop or it's async custom element
+    (shouldSetAsPropForVueCE(el as VueElement, key) ||
+      // @ts-expect-error _def is private
+      ((el as VueElement)._def.__asyncLoader &&
+        (/[A-Z]/.test(key) || !isString(nextValue))))
   ) {
     patchDOMProp(el, camelize(key), nextValue, parentComponent, key)
   } else {
@@ -154,3 +158,16 @@ function shouldSetAsProp(
 
   return key in el
 }
+
+function shouldSetAsPropForVueCE(el: VueElement, key: string) {
+  const props = // @ts-expect-error _def is private
+    el._def.props as Record<string, unknown> | string[] | undefined
+  if (!props) {
+    return false
+  }
+
+  const camelKey = camelize(key)
+  return Array.isArray(props)
+    ? props.some(prop => camelize(prop) === camelKey)
+    : Object.keys(props).some(prop => camelize(prop) === camelKey)
+}