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

fix(custom-element): should properly patch as props for vapor custom elements (#14583)

sync from #12409
edison 3 месяцев назад
Родитель
Сommit
c4b51d8188

+ 1 - 1
packages/runtime-dom/src/index.ts

@@ -336,7 +336,7 @@ export { patchStyle } from './modules/style'
 /**
  * @internal
  */
-export { shouldSetAsProp } from './patchProp'
+export { shouldSetAsProp, shouldSetAsPropForVueCE } from './patchProp'
 /**
  * @internal
  */

+ 2 - 3
packages/runtime-dom/src/patchProp.ts

@@ -107,9 +107,8 @@ export 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
+export function shouldSetAsPropForVueCE(el: any, key: string): boolean {
+  const props = el._def.props as Record<string, unknown> | string[] | undefined
   if (!props) {
     return false
   }

+ 69 - 0
packages/runtime-vapor/__tests__/customElement.spec.ts

@@ -604,6 +604,26 @@ describe('defineVaporCustomElement', () => {
       expect(e.shadowRoot!.innerHTML).toBe('<div></div>')
     })
 
+    // #12408
+    test('should set number tabindex as attribute', () => {
+      const { container: root } = render('my-el-attrs', {
+        tabindex: () => 1,
+        'data-test': () => true,
+      })
+      const el = root.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 { container: root } = render('my-el-attrs', {
+        translate: () => 'no',
+      })
+      const el = root.children[0] as HTMLElement
+      expect(el.getAttribute('translate')).toBe('no')
+      expect(el.translate).toBe(false)
+    })
+
     // https://github.com/vuejs/core/issues/12964
     // Disabled because of missing support for `delegatesFocus` in jsdom
     // https://github.com/jsdom/jsdom/issues/3418
@@ -1419,6 +1439,55 @@ describe('defineVaporCustomElement', () => {
       expect(e2.shadowRoot!.innerHTML).toBe(`<div>hello</div>`)
     })
 
+    test('render object prop before resolve', async () => {
+      const AsyncComp = defineVaporComponent({
+        props: { value: Object },
+        setup(props: any) {
+          const n0 = template('<div> </div>', true)() as any
+          const x0 = txt(n0) as any
+          renderEffect(() => setText(x0, props.value.x))
+          return n0
+        },
+      })
+      let resolve!: (comp: typeof AsyncComp) => void
+      const p = new Promise<typeof AsyncComp>(res => {
+        resolve = res
+      })
+      const E = defineVaporCustomElement(defineVaporAsyncComponent(() => p))
+      customElements.define('my-el-async-object-prop', E)
+
+      const root = document.createElement('div')
+      document.body.appendChild(root)
+      const value = { x: 1 }
+
+      const app = createVaporApp({
+        setup() {
+          return createPlainElement(
+            'my-el-async-object-prop',
+            { value: () => value },
+            null,
+            true,
+          )
+        },
+      })
+      app.mount(root)
+
+      const el = root.children[0] as VaporElement & { 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>`)
+
+      app.unmount()
+      root.remove()
+    })
+
     test('Number prop casting before resolve', async () => {
       const E = defineVaporCustomElement(
         defineVaporAsyncComponent(() => {

+ 7 - 2
packages/runtime-vapor/src/dom/prop.ts

@@ -28,6 +28,7 @@ import {
   patchStyle,
   queuePostFlushCb,
   shouldSetAsProp,
+  shouldSetAsPropForVueCE,
   toClassSet,
   toStyleMap,
   unsafeToTrustedHTML,
@@ -519,9 +520,13 @@ export function setDynamicProp(
       setDOMProp(el, key, value, forceHydrate)
     }
   } else if (
-    // custom elements
+    // #11081 force set props for possible async custom element
     (el as VaporElement)._isVueCE &&
-    (/[A-Z]/.test(key) || !isString(value))
+    // #12408 check if it's declared prop or it's async custom element
+    (shouldSetAsPropForVueCE(el as VaporElement, key) ||
+      // @ts-expect-error _def is private
+      ((el as VaporElement)._def.__asyncLoader &&
+        (/[A-Z]/.test(key) || !isString(value))))
   ) {
     setDOMProp(el, camelize(key), value, forceHydrate, key)
   } else {