From 4b6100623f8d6e45410d7c91679f84a63d8e2e82 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 7 Dec 2024 15:12:32 +0800 Subject: [PATCH] wip: slots --- packages/runtime-vapor/src/block.ts | 19 +++++++--- packages/runtime-vapor/src/component.ts | 40 +++++++++++--------- packages/runtime-vapor/src/componentProps.ts | 2 +- packages/runtime-vapor/src/componentSlots.ts | 37 +++++++++++++++++- packages/runtime-vapor/src/dom/element.ts | 4 +- packages/runtime-vapor/src/index.ts | 1 + 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 44fee9a55..e9e2204e9 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -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): val is Fragment { - return fragmentKey in val + return val instanceof Fragment } export function isBlock(val: NonNullable): 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 diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index bb2e21db0..8e2220cb7 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -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 attrs: Record slots: StaticSlots exposed: Record | null + rawProps: RawProps + rawSlots: RawSlots + emitted: Record | null propsDefaults: Record | 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 { */ 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 } diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index 120c9d139..eb83463c7 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -213,7 +213,7 @@ function resolveDefault( export function hasFallthroughAttrs( comp: VaporComponent, - rawProps: RawProps | undefined, + rawProps: RawProps | null | undefined, ): boolean { if (rawProps) { // determine fallthrough diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 3a37abee2..dd78e7bd7 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -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 & { $?: (StaticSlots | DynamicSlotFn)[] @@ -47,7 +51,8 @@ export const dynamicSlotsProxyHandlers: ProxyHandler = { 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() : [] + } +} diff --git a/packages/runtime-vapor/src/dom/element.ts b/packages/runtime-vapor/src/dom/element.ts index 250a99e66..5a0c530ba 100644 --- a/packages/runtime-vapor/src/dom/element.ts +++ b/packages/runtime-vapor/src/dom/element.ts @@ -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 } diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index eeced2e2a..944b772bb 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -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'