]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: attr fallthrough
authorEvan You <evan@vuejs.org>
Tue, 3 Dec 2024 05:21:10 +0000 (13:21 +0800)
committerEvan You <evan@vuejs.org>
Tue, 3 Dec 2024 05:21:10 +0000 (13:21 +0800)
packages/runtime-vapor/src/apiCreateComponentSimple.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/dom/element.ts

index 562fdd640b2d2f349240c55627b03f56462a6021..98af50c19a95dae7e83e5c6890684dcf9de90a51 100644 (file)
@@ -4,17 +4,16 @@ import {
   pauseTracking,
   resetTracking,
 } from '@vue/reactivity'
-import {
-  type Component,
-  type ComponentInternalInstance,
-  SetupContext,
-} from './component'
+import type { Component } from './component'
 import { NO, camelize, hasOwn, isFunction } from '@vue/shared'
 import { type SchedulerJob, queueJob } from '../../runtime-core/src/scheduler'
 import { insert } from './dom/element'
 import { normalizeContainer } from './apiRender'
 import { normalizePropsOptions, resolvePropValue } from './componentProps'
 import type { Block } from './block'
+import { EmitFn, type EmitsOptions } from './componentEmits'
+import { StaticSlots } from './componentSlots'
+import { setDynamicProp } from './dom/prop'
 
 interface RawProps {
   [key: string]: PropSource
@@ -28,11 +27,19 @@ type DynamicPropsSource = PropSource<Record<string, any>>
 export function createComponentSimple(
   component: Component,
   rawProps?: RawProps,
-): Block {
-  const instance = new ComponentInstance(
-    component,
-    rawProps,
-  ) as any as ComponentInternalInstance
+  isSingleRoot?: boolean,
+): ComponentInstance {
+  // check if we are the single root of the parent
+  // if yes, inject parent attrs as dynamic props source
+  if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
+    if (rawProps) {
+      ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
+    } else {
+      rawProps = { $: [currentInstance.attrs] }
+    }
+  }
+
+  const instance = new ComponentInstance(component, rawProps)
 
   pauseTracking()
   let prevInstance = currentInstance
@@ -41,30 +48,46 @@ export function createComponentSimple(
 
   const setupFn = isFunction(component) ? component : component.setup
   const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
-  const node = setupFn!(
+  instance.block = setupFn!(
     instance.props,
     // @ts-expect-error
     setupContext,
-  ) as Block
+  ) as Block // TODO handle return object
 
   // single root, inherit attrs
   if (
-    rawProps &&
+    instance.hasFallthrough &&
     component.inheritAttrs !== false &&
-    node instanceof Element &&
+    instance.block instanceof Element &&
     Object.keys(instance.attrs).length
   ) {
     renderEffectSimple(() => {
-      // TODO
+      for (const key in instance.attrs) {
+        setDynamicProp(instance.block as Element, key, instance.attrs[key])
+      }
     })
   }
 
   instance.scope.off()
   currentInstance = prevInstance
   resetTracking()
-  // @ts-expect-error
-  node.__vue__ = instance
-  return node
+  return instance
+}
+
+class SetupContext<E = EmitsOptions> {
+  attrs: Record<string, any>
+  // emit: EmitFn<E>
+  // slots: Readonly<StaticSlots>
+  expose: (exposed?: Record<string, any>) => void
+
+  constructor(instance: ComponentInstance) {
+    this.attrs = instance.attrs
+    // this.emit = instance.emit as EmitFn<E>
+    // this.slots = instance.slots
+    this.expose = (exposed = {}) => {
+      instance.exposed = exposed
+    }
+  }
 }
 
 let uid = 0
@@ -76,19 +99,24 @@ export class ComponentInstance {
   scope: EffectScope = new EffectScope(true)
   props: Record<string, any>
   attrs: Record<string, any>
+  block: Block
+  exposed?: Record<string, any>
+  hasFallthrough: boolean
+
   constructor(comp: Component, rawProps?: RawProps) {
     this.type = comp
+    this.block = null! // to be set
 
     // init props
-    let mayHaveFallthroughAttrs = false
+    this.hasFallthrough = false
     if (comp.props && rawProps && rawProps.$) {
       // has dynamic props, use proxy
       const handlers = getDynamicPropsHandlers(comp, this)
       this.props = new Proxy(rawProps, handlers[0])
       this.attrs = new Proxy(rawProps, handlers[1])
-      mayHaveFallthroughAttrs = true
+      this.hasFallthrough = true
     } else {
-      mayHaveFallthroughAttrs = initStaticProps(
+      this.hasFallthrough = initStaticProps(
         comp,
         rawProps,
         (this.props = {}),
@@ -97,14 +125,14 @@ export class ComponentInstance {
     }
 
     // TODO validate props
-
-    if (mayHaveFallthroughAttrs) {
-      // TODO apply fallthrough attrs
-    }
     // TODO init slots
   }
 }
 
+export function isVaporComponent(value: unknown): value is ComponentInstance {
+  return value instanceof ComponentInstance
+}
+
 function initStaticProps(
   comp: Component,
   rawProps: RawProps | undefined,
@@ -185,11 +213,12 @@ function getDynamicPropsHandlers(
         return castProp(resolveSource(target[key as string]))
       }
       if (target.$) {
-        let source, resolved
-        for (source of target.$) {
-          resolved = resolveSource(source)
-          if (hasOwn(resolved, key)) {
-            return castProp(resolved[key])
+        let i = target.$.length
+        let source
+        while (i--) {
+          source = resolveSource(target.$[i])
+          if (hasOwn(source, key)) {
+            return castProp(source[key])
           }
         }
       }
@@ -219,10 +248,9 @@ function getDynamicPropsHandlers(
     if (key === '$' || isProp(key)) return false
     if (hasOwn(target, key)) return true
     if (target.$) {
-      let source, resolved
-      for (source of target.$) {
-        resolved = resolveSource(source)
-        if (hasOwn(resolved, key)) {
+      let i = target.$.length
+      while (i--) {
+        if (hasOwn(resolveSource(target.$[i]), key)) {
           return true
         }
       }
@@ -247,8 +275,9 @@ function getDynamicPropsHandlers(
         key => key !== '$' && !isProp(key),
       )
       if (target.$) {
-        for (const source of target.$) {
-          staticKeys.push(...Object.keys(resolveSource(source)))
+        let i = target.$.length
+        while (i--) {
+          staticKeys.push(...Object.keys(resolveSource(target.$[i])))
         }
       }
       return staticKeys
index b14032c9251cfdd6c80159271b8252888bbfe981..de5a301d1903f9787be7eb31703ca796106de0b8 100644 (file)
@@ -1,9 +1,12 @@
 import { isArray } from '@vue/shared'
-import { type ComponentInternalInstance, isVaporComponent } from './component'
+import {
+  type ComponentInstance,
+  isVaporComponent,
+} from './apiCreateComponentSimple'
 
 export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
 
-export type Block = Node | Fragment | ComponentInternalInstance | Block[]
+export type Block = Node | Fragment | ComponentInstance | Block[]
 export type Fragment = {
   nodes: Block
   anchor?: Node
@@ -27,7 +30,7 @@ export function normalizeBlock(block: Block): Node[] {
 }
 
 export function findFirstRootElement(
-  instance: ComponentInternalInstance,
+  instance: ComponentInstance,
 ): Element | undefined {
   const element = getFirstNode(instance.block)
   return element instanceof Element ? element : undefined
index b66fb49302ae869051159ea0aa90565bdfb560ce..859b5ed2419306dbc74111813dbed13b9416b107 100644 (file)
@@ -25,6 +25,7 @@ import {
   createAppContext,
 } from './apiCreateVaporApp'
 import type { Data } from '@vue/runtime-shared'
+import type { ComponentInstance } from './apiCreateComponentSimple'
 
 export type Component = FunctionalComponent | ObjectComponent
 
@@ -49,7 +50,7 @@ export class SetupContext<E = EmitsOptions> {
   slots: Readonly<StaticSlots>
   expose: (exposed?: Record<string, any>) => void
 
-  constructor(instance: ComponentInternalInstance) {
+  constructor(instance: ComponentInstance) {
     this.attrs = instance.attrs
     this.emit = instance.emit as EmitFn<E>
     this.slots = instance.slots
index 4aaf12dfe58b5dfa8cebf2765f73d557fc3a16c8..6f0fc27f89f56426753950b281e830db6c3f2dc7 100644 (file)
@@ -2,6 +2,7 @@ import { isArray } from '@vue/shared'
 import { renderEffect } from '../renderEffect'
 import { setText } from './prop'
 import { type Block, normalizeBlock } from '../block'
+import { isVaporComponent } from '../apiCreateComponentSimple'
 
 // export function insert(
 //   block: Block,
@@ -21,6 +22,8 @@ export function insert(
 ): void {
   if (block instanceof Node) {
     parent.insertBefore(block, anchor)
+  } else if (isVaporComponent(block)) {
+    insert(block.block, parent, anchor)
   } else if (isArray(block)) {
     for (let i = 0; i < block.length; i++) {
       insert(block[i], parent, anchor)