wip: slots

This commit is contained in:
Evan You 2024-12-07 15:12:32 +08:00
parent 8331aa43c4
commit 4b6100623f
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
6 changed files with 76 additions and 27 deletions

View 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

View 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
}

View File

@ -213,7 +213,7 @@ function resolveDefault(
export function hasFallthroughAttrs(
comp: VaporComponent,
rawProps: RawProps | undefined,
rawProps: RawProps | null | undefined,
): boolean {
if (rawProps) {
// determine fallthrough

View 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() : []
}
}

View 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
}

View 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'