]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: dynamic slots
authorEvan You <evan@vuejs.org>
Sat, 7 Dec 2024 13:43:08 +0000 (21:43 +0800)
committerEvan You <evan@vuejs.org>
Sat, 7 Dec 2024 13:56:54 +0000 (21:56 +0800)
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/componentSlots.ts

index e9e2204e9818c501eb9c311d61298fe40e9f3240..ea4cc881c9d5506ba54e0d976485dd8e0ba0a5bd 100644 (file)
@@ -1,19 +1,54 @@
 import { isArray } from '@vue/shared'
 import { type VaporComponentInstance, isVaporComponent } from './component'
-import { createComment } from './dom/element'
+import { createComment, insert, remove } from './dom/element'
+import { EffectScope } from '@vue/reactivity'
 
 export type Block = Node | Fragment | VaporComponentInstance | Block[]
 
+export type BlockRenderFn = (...args: any[]) => Block
+
 export class Fragment {
   nodes: Block
   anchor?: Node
-  constructor(nodes: Block, anchorLabel?: string) {
+
+  constructor(nodes: Block) {
     this.nodes = nodes
-    if (anchorLabel) {
-      this.anchor = __DEV__
+  }
+}
+
+export class DynamicFragment extends Fragment {
+  anchor: Node
+  scope: EffectScope | undefined
+  key: any
+
+  constructor(anchorLabel?: string) {
+    super([])
+    this.anchor =
+      __DEV__ && anchorLabel
         ? createComment(anchorLabel)
         : // eslint-disable-next-line no-restricted-globals
           document.createTextNode('')
+  }
+
+  update(render?: BlockRenderFn, key: any = render): void {
+    if (key === this.key) return
+    this.key = key
+
+    const parent = this.anchor.parentNode
+
+    // teardown previous branch
+    if (this.scope) {
+      this.scope.off()
+      parent && remove(this.nodes, parent)
+    }
+
+    if (render) {
+      this.scope = new EffectScope()
+      this.nodes = this.scope.run(render) || []
+      if (parent) insert(this.nodes, parent)
+    } else {
+      this.scope = undefined
+      this.nodes = []
     }
   }
 }
index 8e2220cb747973165f8dcbc8cb9de95e3710d498..1c98f42f01844f025bae24c96624043017041416 100644 (file)
@@ -114,13 +114,15 @@ export function createComponent(
   }
 
   const setupFn = isFunction(component) ? component : component.setup
-  const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
-  const setupResult =
-    setupFn!(
-      instance.props,
-      // @ts-expect-error
-      setupContext,
-    ) || EMPTY_OBJ
+  const setupContext =
+    setupFn && setupFn.length > 1 ? new SetupContext(instance) : null
+  const setupResult = setupFn
+    ? setupFn(
+        instance.props,
+        // @ts-expect-error
+        setupContext,
+      ) || EMPTY_OBJ
+    : EMPTY_OBJ
 
   if (__DEV__ && !isBlock(setupResult)) {
     if (isFunction(component)) {
@@ -341,10 +343,12 @@ export function createComponentWithFallback(
     })
   }
 
-  const defaultSlot = rawSlots && getSlot(rawSlots, 'default')
-  if (defaultSlot) {
-    const res = defaultSlot()
-    insert(res, el)
+  if (rawSlots) {
+    if (rawSlots.$) {
+      // TODO dynamic slot fragment
+    } else {
+      insert(getSlot(rawSlots, 'default')!(), el)
+    }
   }
 
   return el
index 7557f79e0771f370b0101f7fb1bbde31c459b2c8..2e8c898ef39c3aa41c3ce8aab5d42f40026c23a8 100644 (file)
@@ -1,8 +1,9 @@
-import { NO, hasOwn, isArray, isFunction } from '@vue/shared'
-import { type Block, Fragment, isValidBlock } from './block'
-import { type RawProps, resolveDynamicProps } from './componentProps'
+import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
+import { type Block, type BlockRenderFn, DynamicFragment } from './block'
+import type { RawProps } from './componentProps'
 import { currentInstance } from '@vue/runtime-core'
 import type { VaporComponentInstance } from './component'
+import { renderEffect } from './renderEffect'
 
 export type RawSlots = Record<string, Slot> & {
   $?: (StaticSlots | DynamicSlotFn)[]
@@ -10,7 +11,7 @@ export type RawSlots = Record<string, Slot> & {
 
 export type StaticSlots = Record<string, Slot>
 
-export type Slot = (...args: any[]) => Block
+export type Slot = BlockRenderFn
 export type DynamicSlot = { name: string; fn: Slot }
 export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
 
@@ -77,29 +78,56 @@ export function getSlot(target: RawSlots, key: string): Slot | undefined {
   }
 }
 
+// TODO
+const dynamicSlotsPropsProxyHandlers: ProxyHandler<RawProps> = {
+  get(target, key: string) {
+    return target[key]
+  },
+  has(target, key) {
+    return key in target
+  },
+}
+
+// TODO how to handle empty slot return blocks?
+// e.g. a slot renders a v-if node that may toggle inside.
+// we may need special handling by passing the fallback into the slot
+// and make the v-if use it as fallback
 export function createSlot(
   name: string | (() => string),
-  props?: RawProps,
+  rawProps?: RawProps,
   fallback?: Slot,
 ): Block {
-  const slots = (currentInstance as VaporComponentInstance)!.rawSlots
-  if (isFunction(name) || slots.$) {
+  const rawSlots = (currentInstance as VaporComponentInstance)!.rawSlots
+  const resolveSlot = () => getSlot(rawSlots, isFunction(name) ? name() : name)
+  const slotProps = rawProps
+    ? rawProps.$
+      ? new Proxy(rawProps, dynamicSlotsPropsProxyHandlers)
+      : rawProps
+    : EMPTY_OBJ
+
+  if (isFunction(name) || rawSlots.$) {
     // dynamic slot name, or dynamic slot sources
-    // TODO togglable fragment class
-    const fragment = new Fragment([], 'slot')
+    const fragment = new DynamicFragment('slot')
+    renderEffect(() => {
+      const slot = resolveSlot()
+      if (slot) {
+        fragment.update(
+          () => slot(slotProps) || (fallback && fallback()),
+          // pass the stable slot fn as key to avoid toggling when resolving
+          // to the same slot
+          slot,
+        )
+      } else {
+        fragment.update(fallback)
+      }
+    })
     return fragment
   } else {
     // static
-    return renderSlot(name)
-  }
-
-  function renderSlot(name: string) {
-    const slot = getSlot(slots, name)
+    const slot = resolveSlot()
     if (slot) {
-      const block = slot(props ? resolveDynamicProps(props) : {})
-      if (isValidBlock(block)) {
-        return block
-      }
+      const block = slot(slotProps)
+      if (block) return block
     }
     return fallback ? fallback() : []
   }