mirror of https://github.com/vuejs/core.git
wip: dynamic slots
This commit is contained in:
parent
e6d4a24f1f
commit
aa96762ad4
|
@ -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 = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() : []
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue