wip: slots
This commit is contained in:
parent
8331aa43c4
commit
4b6100623f
|
@ -1,17 +1,25 @@
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import { type VaporComponentInstance, isVaporComponent } from './component'
|
import { type VaporComponentInstance, isVaporComponent } from './component'
|
||||||
|
import { createComment } from './dom/element'
|
||||||
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
|
|
||||||
|
|
||||||
export type Block = Node | Fragment | VaporComponentInstance | Block[]
|
export type Block = Node | Fragment | VaporComponentInstance | Block[]
|
||||||
export type Fragment = {
|
|
||||||
|
export class Fragment {
|
||||||
nodes: Block
|
nodes: Block
|
||||||
anchor?: Node
|
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 {
|
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 {
|
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 {
|
export function isValidBlock(block: Block): boolean {
|
||||||
return (
|
return (
|
||||||
normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0
|
normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0
|
||||||
|
|
|
@ -36,7 +36,9 @@ import {
|
||||||
type RawSlots,
|
type RawSlots,
|
||||||
type StaticSlots,
|
type StaticSlots,
|
||||||
dynamicSlotsProxyHandlers,
|
dynamicSlotsProxyHandlers,
|
||||||
|
getSlot,
|
||||||
} from './componentSlots'
|
} from './componentSlots'
|
||||||
|
import { insert } from './dom/element'
|
||||||
|
|
||||||
export { currentInstance } from '@vue/runtime-dom'
|
export { currentInstance } from '@vue/runtime-dom'
|
||||||
|
|
||||||
|
@ -84,7 +86,8 @@ interface SharedInternalOptions {
|
||||||
|
|
||||||
export function createComponent(
|
export function createComponent(
|
||||||
component: VaporComponent,
|
component: VaporComponent,
|
||||||
rawProps?: RawProps,
|
rawProps?: RawProps | null,
|
||||||
|
rawSlots?: RawSlots | null,
|
||||||
isSingleRoot?: boolean,
|
isSingleRoot?: boolean,
|
||||||
): VaporComponentInstance {
|
): VaporComponentInstance {
|
||||||
// check if we are the single root of the parent
|
// 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)
|
const resetCurrentInstance = setCurrentInstance(instance)
|
||||||
|
|
||||||
pauseTracking()
|
pauseTracking()
|
||||||
|
@ -175,12 +178,14 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
||||||
|
|
||||||
block: Block
|
block: Block
|
||||||
scope: EffectScope
|
scope: EffectScope
|
||||||
rawProps: RawProps
|
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
attrs: Record<string, any>
|
attrs: Record<string, any>
|
||||||
slots: StaticSlots
|
slots: StaticSlots
|
||||||
exposed: Record<string, any> | null
|
exposed: Record<string, any> | null
|
||||||
|
|
||||||
|
rawProps: RawProps
|
||||||
|
rawSlots: RawSlots
|
||||||
|
|
||||||
emitted: Record<string, boolean> | null
|
emitted: Record<string, boolean> | null
|
||||||
propsDefaults: Record<string, any> | null
|
propsDefaults: Record<string, any> | null
|
||||||
|
|
||||||
|
@ -221,7 +226,11 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
||||||
propsOptions?: NormalizedPropsOptions
|
propsOptions?: NormalizedPropsOptions
|
||||||
emitsOptions?: ObjectEmitsOptions | null
|
emitsOptions?: ObjectEmitsOptions | null
|
||||||
|
|
||||||
constructor(comp: VaporComponent, rawProps?: RawProps, rawSlots?: RawSlots) {
|
constructor(
|
||||||
|
comp: VaporComponent,
|
||||||
|
rawProps?: RawProps | null,
|
||||||
|
rawSlots?: RawSlots | null,
|
||||||
|
) {
|
||||||
this.vapor = true
|
this.vapor = true
|
||||||
this.uid = nextUid()
|
this.uid = nextUid()
|
||||||
this.type = comp
|
this.type = comp
|
||||||
|
@ -257,6 +266,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
// init slots
|
// init slots
|
||||||
|
this.rawSlots = rawSlots || EMPTY_OBJ
|
||||||
this.slots = rawSlots
|
this.slots = rawSlots
|
||||||
? rawSlots.$
|
? rawSlots.$
|
||||||
? new Proxy(rawSlots, dynamicSlotsProxyHandlers)
|
? new Proxy(rawSlots, dynamicSlotsProxyHandlers)
|
||||||
|
@ -304,12 +314,12 @@ export class SetupContext<E = EmitsOptions> {
|
||||||
*/
|
*/
|
||||||
export function createComponentWithFallback(
|
export function createComponentWithFallback(
|
||||||
comp: VaporComponent | string,
|
comp: VaporComponent | string,
|
||||||
rawProps: RawProps | undefined,
|
rawProps: RawProps | null | undefined,
|
||||||
// TODO slots: RawSlots | null
|
rawSlots: RawSlots | null | undefined,
|
||||||
isSingleRoot?: boolean,
|
isSingleRoot?: boolean,
|
||||||
): HTMLElement | VaporComponentInstance {
|
): HTMLElement | VaporComponentInstance {
|
||||||
if (!isString(comp)) {
|
if (!isString(comp)) {
|
||||||
return createComponent(comp, rawProps, isSingleRoot)
|
return createComponent(comp, rawProps, rawSlots, isSingleRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
|
@ -331,17 +341,11 @@ export function createComponentWithFallback(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
const defaultSlot = rawSlots && getSlot(rawSlots, 'default')
|
||||||
// if (slots) {
|
if (defaultSlot) {
|
||||||
// if (!Array.isArray(slots)) slots = [slots]
|
const res = defaultSlot()
|
||||||
// for (let i = 0; i < slots.length; i++) {
|
insert(res, el)
|
||||||
// const slot = slots[i]
|
}
|
||||||
// if (!isDynamicSlotFn(slot) && slot.default) {
|
|
||||||
// const block = slot.default && slot.default()
|
|
||||||
// if (block) el.append(...normalizeBlock(block))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ function resolveDefault(
|
||||||
|
|
||||||
export function hasFallthroughAttrs(
|
export function hasFallthroughAttrs(
|
||||||
comp: VaporComponent,
|
comp: VaporComponent,
|
||||||
rawProps: RawProps | undefined,
|
rawProps: RawProps | null | undefined,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (rawProps) {
|
if (rawProps) {
|
||||||
// determine fallthrough
|
// determine fallthrough
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { NO, hasOwn, isArray, isFunction } from '@vue/shared'
|
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> & {
|
export type RawSlots = Record<string, Slot> & {
|
||||||
$?: (StaticSlots | DynamicSlotFn)[]
|
$?: (StaticSlots | DynamicSlotFn)[]
|
||||||
|
@ -47,7 +51,8 @@ export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
|
||||||
deleteProperty: NO,
|
deleteProperty: NO,
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSlot(target: RawSlots, key: string) {
|
export function getSlot(target: RawSlots, key: string): Slot | undefined {
|
||||||
|
if (key === '$') return
|
||||||
const dynamicSources = target.$
|
const dynamicSources = target.$
|
||||||
if (dynamicSources) {
|
if (dynamicSources) {
|
||||||
let i = dynamicSources.length
|
let i = dynamicSources.length
|
||||||
|
@ -72,3 +77,31 @@ function getSlot(target: RawSlots, key: string) {
|
||||||
return target[key]
|
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() : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ export function insert(
|
||||||
} else {
|
} else {
|
||||||
// fragment
|
// fragment
|
||||||
insert(block.nodes, parent, anchor)
|
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 {
|
export function createTextNode(values?: any[] | (() => any[])): Text {
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
const node = document.createTextNode('')
|
const node = document.createTextNode('')
|
||||||
if (values)
|
if (values) {
|
||||||
if (isArray(values)) {
|
if (isArray(values)) {
|
||||||
setText(node, ...values)
|
setText(node, ...values)
|
||||||
} else {
|
} else {
|
||||||
renderEffect(() => setText(node, ...values()))
|
renderEffect(() => setText(node, ...values()))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ export { createComponent, createComponentWithFallback } from './component'
|
||||||
export { renderEffect } from './renderEffect'
|
export { renderEffect } from './renderEffect'
|
||||||
export { createVaporApp } from './apiCreateApp'
|
export { createVaporApp } from './apiCreateApp'
|
||||||
export { defineComponent } from './apiDefineComponent'
|
export { defineComponent } from './apiDefineComponent'
|
||||||
|
export { createSlot } from './componentSlots'
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
export { template, children, next } from './dom/template'
|
export { template, children, next } from './dom/template'
|
||||||
|
|
Loading…
Reference in New Issue