]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: slots
authorEvan You <evan@vuejs.org>
Sat, 7 Dec 2024 07:12:32 +0000 (15:12 +0800)
committerEvan You <evan@vuejs.org>
Sat, 7 Dec 2024 07:12:32 +0000 (15:12 +0800)
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/componentProps.ts
packages/runtime-vapor/src/componentSlots.ts
packages/runtime-vapor/src/dom/element.ts
packages/runtime-vapor/src/index.ts

index 44fee9a55632e0be55dd53326fb376f43a92a000..e9e2204e9818c501eb9c311d61298fe40e9f3240 100644 (file)
@@ -1,17 +1,25 @@
 import { isArray } from '@vue/shared'
 import { type VaporComponentInstance, isVaporComponent } from './component'
-
-export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
+import { createComment } from './dom/element'
 
 export type Block = Node | Fragment | VaporComponentInstance | Block[]
-export type Fragment = {
+
+export class Fragment {
   nodes: Block
   anchor?: Node
-  [fragmentKey]: true
+  constructor(nodes: Block, anchorLabel?: string) {
+    this.nodes = nodes
+    if (anchorLabel) {
+      this.anchor = __DEV__
+        ? createComment(anchorLabel)
+        : // eslint-disable-next-line no-restricted-globals
+          document.createTextNode('')
+    }
+  }
 }
 
 export function isFragment(val: NonNullable<unknown>): val is Fragment {
-  return fragmentKey in val
+  return val instanceof Fragment
 }
 
 export function isBlock(val: NonNullable<unknown>): val is Block {
@@ -59,6 +67,7 @@ export function getFirstNode(block: Block | null): Node | undefined {
   }
 }
 
+// TODO optimize
 export function isValidBlock(block: Block): boolean {
   return (
     normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0
index bb2e21db0d56cc2e2521e8667549a703158a6167..8e2220cb747973165f8dcbc8cb9de95e3710d498 100644 (file)
@@ -36,7 +36,9 @@ import {
   type RawSlots,
   type StaticSlots,
   dynamicSlotsProxyHandlers,
+  getSlot,
 } from './componentSlots'
+import { insert } from './dom/element'
 
 export { currentInstance } from '@vue/runtime-dom'
 
@@ -84,7 +86,8 @@ interface SharedInternalOptions {
 
 export function createComponent(
   component: VaporComponent,
-  rawProps?: RawProps,
+  rawProps?: RawProps | null,
+  rawSlots?: RawSlots | null,
   isSingleRoot?: boolean,
 ): VaporComponentInstance {
   // check if we are the single root of the parent
@@ -102,7 +105,7 @@ export function createComponent(
     }
   }
 
-  const instance = new VaporComponentInstance(component, rawProps)
+  const instance = new VaporComponentInstance(component, rawProps, rawSlots)
   const resetCurrentInstance = setCurrentInstance(instance)
 
   pauseTracking()
@@ -175,12 +178,14 @@ export class VaporComponentInstance implements GenericComponentInstance {
 
   block: Block
   scope: EffectScope
-  rawProps: RawProps
   props: Record<string, any>
   attrs: Record<string, any>
   slots: StaticSlots
   exposed: Record<string, any> | null
 
+  rawProps: RawProps
+  rawSlots: RawSlots
+
   emitted: Record<string, boolean> | null
   propsDefaults: Record<string, any> | null
 
@@ -221,7 +226,11 @@ export class VaporComponentInstance implements GenericComponentInstance {
   propsOptions?: NormalizedPropsOptions
   emitsOptions?: ObjectEmitsOptions | null
 
-  constructor(comp: VaporComponent, rawProps?: RawProps, rawSlots?: RawSlots) {
+  constructor(
+    comp: VaporComponent,
+    rawProps?: RawProps | null,
+    rawSlots?: RawSlots | null,
+  ) {
     this.vapor = true
     this.uid = nextUid()
     this.type = comp
@@ -257,6 +266,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
     }
 
     // init slots
+    this.rawSlots = rawSlots || EMPTY_OBJ
     this.slots = rawSlots
       ? rawSlots.$
         ? new Proxy(rawSlots, dynamicSlotsProxyHandlers)
@@ -304,12 +314,12 @@ export class SetupContext<E = EmitsOptions> {
  */
 export function createComponentWithFallback(
   comp: VaporComponent | string,
-  rawProps: RawProps | undefined,
-  // TODO slots: RawSlots | null
+  rawProps: RawProps | null | undefined,
+  rawSlots: RawSlots | null | undefined,
   isSingleRoot?: boolean,
 ): HTMLElement | VaporComponentInstance {
   if (!isString(comp)) {
-    return createComponent(comp, rawProps, isSingleRoot)
+    return createComponent(comp, rawProps, rawSlots, isSingleRoot)
   }
 
   // eslint-disable-next-line no-restricted-globals
@@ -331,17 +341,11 @@ export function createComponentWithFallback(
     })
   }
 
-  // TODO
-  // if (slots) {
-  //   if (!Array.isArray(slots)) slots = [slots]
-  //   for (let i = 0; i < slots.length; i++) {
-  //     const slot = slots[i]
-  //     if (!isDynamicSlotFn(slot) && slot.default) {
-  //       const block = slot.default && slot.default()
-  //       if (block) el.append(...normalizeBlock(block))
-  //     }
-  //   }
-  // }
+  const defaultSlot = rawSlots && getSlot(rawSlots, 'default')
+  if (defaultSlot) {
+    const res = defaultSlot()
+    insert(res, el)
+  }
 
   return el
 }
index 120c9d1398e73f90ddcfb7e78a0c7d0225a5542e..eb83463c746e4a81c00281f767d98a0c9a943dd2 100644 (file)
@@ -213,7 +213,7 @@ function resolveDefault(
 
 export function hasFallthroughAttrs(
   comp: VaporComponent,
-  rawProps: RawProps | undefined,
+  rawProps: RawProps | null | undefined,
 ): boolean {
   if (rawProps) {
     // determine fallthrough
index 3a37abee2495a3ee2b5aa2de072daaa8275922d3..dd78e7bd71b762c7cc526bac2a7854d0d98b1dab 100644 (file)
@@ -1,5 +1,9 @@
 import { NO, hasOwn, isArray, isFunction } from '@vue/shared'
-import type { Block } from './block'
+import { type Block, Fragment, isValidBlock } from './block'
+import { type RawProps, resolveDynamicProps } 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)[]
@@ -47,7 +51,8 @@ export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
   deleteProperty: NO,
 }
 
-function getSlot(target: RawSlots, key: string) {
+export function getSlot(target: RawSlots, key: string): Slot | undefined {
+  if (key === '$') return
   const dynamicSources = target.$
   if (dynamicSources) {
     let i = dynamicSources.length
@@ -72,3 +77,31 @@ function getSlot(target: RawSlots, key: string) {
     return target[key]
   }
 }
+
+export function createSlot(
+  name: string | (() => string),
+  props?: RawProps,
+  fallback?: Slot,
+): Block {
+  const slots = (currentInstance as VaporComponentInstance)!.rawSlots
+  if (isFunction(name) || slots.$) {
+    // dynamic slot name, or dynamic slot sources
+    // TODO togglable fragment class
+    const fragment = new Fragment([], 'slot')
+    return fragment
+  } else {
+    // static
+    return renderSlot(name)
+  }
+
+  function renderSlot(name: string) {
+    const slot = getSlot(slots, name)
+    if (slot) {
+      const block = slot(props ? resolveDynamicProps(props) : {})
+      if (isValidBlock(block)) {
+        return block
+      }
+    }
+    return fallback ? fallback() : []
+  }
+}
index 250a99e66aa9d28f325ce0bf546a5740f80de71e..5a0c530ba34c1cd9fe29699855a6a3e16192ca41 100644 (file)
@@ -27,6 +27,7 @@ export function insert(
   } else {
     // fragment
     insert(block.nodes, parent, anchor)
+    if (block.anchor) parent.insertBefore(block.anchor, anchor)
   }
 }
 
@@ -47,12 +48,13 @@ export function remove(block: Block, parent: ParentNode): void {
 export function createTextNode(values?: any[] | (() => any[])): Text {
   // eslint-disable-next-line no-restricted-globals
   const node = document.createTextNode('')
-  if (values)
+  if (values) {
     if (isArray(values)) {
       setText(node, ...values)
     } else {
       renderEffect(() => setText(node, ...values()))
     }
+  }
   return node
 }
 
index eeced2e2a0cfca124a401c873e34cf5bbb1dc349..944b772bbc582adf8845651a7f66796ba1aa5285 100644 (file)
@@ -2,6 +2,7 @@ export { createComponent, createComponentWithFallback } from './component'
 export { renderEffect } from './renderEffect'
 export { createVaporApp } from './apiCreateApp'
 export { defineComponent } from './apiDefineComponent'
+export { createSlot } from './componentSlots'
 
 // DOM
 export { template, children, next } from './dom/template'