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 {
}
}
+// TODO optimize
export function isValidBlock(block: Block): boolean {
return (
normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0
type RawSlots,
type StaticSlots,
dynamicSlotsProxyHandlers,
+ getSlot,
} from './componentSlots'
+import { insert } from './dom/element'
export { currentInstance } from '@vue/runtime-dom'
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
}
}
- const instance = new VaporComponentInstance(component, rawProps)
+ const instance = new VaporComponentInstance(component, rawProps, rawSlots)
const resetCurrentInstance = setCurrentInstance(instance)
pauseTracking()
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
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
}
// init slots
+ this.rawSlots = rawSlots || EMPTY_OBJ
this.slots = rawSlots
? rawSlots.$
? new Proxy(rawSlots, dynamicSlotsProxyHandlers)
*/
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
})
}
- // 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
}
export function hasFallthroughAttrs(
comp: VaporComponent,
- rawProps: RawProps | undefined,
+ rawProps: RawProps | null | undefined,
): boolean {
if (rawProps) {
// determine fallthrough
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)[]
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
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() : []
+ }
+}
} else {
// fragment
insert(block.nodes, parent, anchor)
+ if (block.anchor) parent.insertBefore(block.anchor, anchor)
}
}
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
}
export { renderEffect } from './renderEffect'
export { createVaporApp } from './apiCreateApp'
export { defineComponent } from './apiDefineComponent'
+export { createSlot } from './componentSlots'
// DOM
export { template, children, next } from './dom/template'