} from '@vue/observer'
import { isFunction, EMPTY_OBJ } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy'
-import { ComponentPropsOptions, PropValidator } from './componentProps'
+import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
+import { Slots } from './componentSlots'
export type Data = { [key: string]: any }
-type ExtractPropTypes<PropOptions> = {
- readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
- infer V
- >
- ? V
- : PropOptions[key] extends null | undefined ? any : PropOptions[key]
-}
-
export type ComponentPublicProperties<P = Data, S = Data> = {
$state: S
$props: P
ec: LifecycleHook // errorCaptured
}
-export type Slot = (...args: any[]) => VNode[]
-
-export type Slots = Readonly<{
- [name: string]: Slot
-}>
-
export type ComponentInstance<P = Data, S = Data> = {
type: FunctionalComponent | ComponentOptions
vnode: VNode
[K in keyof P]: PropValidator<P[K]>
}
-export type Prop<T> = { (): T } | { new (...args: any[]): T & object }
+type Prop<T> = { (): T } | { new (...args: any[]): T & object }
-export type PropType<T> = Prop<T> | Prop<T>[]
+type PropType<T> = Prop<T> | Prop<T>[]
-export type PropValidator<T> = PropOptions<T> | PropType<T>
+type PropValidator<T> = PropOptions<T> | PropType<T>
-export interface PropOptions<T = any> {
+interface PropOptions<T = any> {
type?: PropType<T> | true | null
required?: boolean
default?: T | null | undefined | (() => T | null | undefined)
- validator?(value: T): boolean
+ validator?(value: any): boolean
+}
+
+export type ExtractPropTypes<PropOptions> = {
+ readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
+ infer V
+ >
+ ? V
+ : PropOptions[key] extends null | true ? any : PropOptions[key]
}
const enum BooleanFlags {
--- /dev/null
+import { ComponentInstance } from './component'
+import { VNode, NormalizedChildren, normalizeVNode, VNodeChild } from './vnode'
+import { isArray, isObject, isFunction } from '@vue/shared'
+
+export type Slot = (...args: any[]) => VNode[]
+export type Slots = Readonly<{
+ [name: string]: Slot
+}>
+export type RawSlots = {
+ [name: string]: unknown
+}
+
+const normalizeSlotValue = (value: unknown): VNode[] =>
+ isArray(value)
+ ? value.map(normalizeVNode)
+ : [normalizeVNode(value as VNodeChild)]
+
+const normalizeSlot = (rawSlot: Function): Slot => (props: any) =>
+ normalizeSlotValue(rawSlot(props))
+
+export function resolveSlots(
+ instance: ComponentInstance,
+ children: NormalizedChildren
+) {
+ let slots: Slots | void
+ if (isObject(children) && !isArray(children)) {
+ // pre-normalized slots object generated by compiler
+ if ((children as any)._normalized) {
+ slots = children as Slots
+ } else {
+ slots = {}
+ for (const key in children) {
+ let value = children[key]
+ if (isFunction(value)) {
+ ;(slots as any)[key] = normalizeSlot(value)
+ } else {
+ if (__DEV__) {
+ // TODO show tip on using functions
+ console.log('use function slots!')
+ }
+ value = normalizeSlotValue(value)
+ ;(slots as any)[key] = () => value
+ }
+ }
+ }
+ } else if (children != null) {
+ // Array, string or null.
+ // non object children passed to a component
+ if (__DEV__) {
+ // TODO show tip on using functions
+ console.log('use function slots!')
+ }
+ const normalized = normalizeSlotValue(children)
+ slots = { default: () => normalized }
+ }
+ if (slots !== void 0) {
+ instance.slots = slots
+ }
+}
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
import { resolveProps } from './componentProps'
+import { resolveSlots } from './componentSlots'
const prodEffectOptions = {
scheduler: queueJob
}
if (isString(vnode.children)) {
hostSetElementText(el, vnode.children)
- } else if (vnode.children != null) {
+ } else if (isArray(vnode.children)) {
mountChildren(vnode.children, el)
}
hostInsert(el, container, anchor)
if (target != null) {
if (isString(children)) {
hostSetElementText(target, children)
- } else if (children != null) {
+ } else if (isArray(children)) {
mountChildren(children, target)
}
} else {
if (isString(children)) {
hostSetElementText(target, '')
hostSetElementText(nextTarget, children)
- } else if (children != null) {
+ } else if (isArray(children)) {
for (let i = 0; i < children.length; i++) {
move(children[i] as VNode, nextTarget, null)
}
// initial mount
instance.vnode = vnode
resolveProps(instance, vnode.props, Component.props)
+ resolveSlots(instance, vnode.children)
// setup stateful
if (typeof Component === 'object') {
setupStatefulComponent(instance)
instance.vnode = next
instance.next = null
resolveProps(instance, next.props, Component.props)
- // TODO slots
+ resolveSlots(instance, next.children)
}
const prevTree = instance.subTree
const nextTree = (instance.subTree = renderComponentRoot(instance))
} else {
if (isString(c1)) {
hostSetElementText(container, '')
- if (c2 != null) {
+ if (isArray(c2)) {
mountChildren(c2, container, anchor)
}
} else if (isArray(c1)) {
export {
ComponentOptions,
FunctionalComponent,
- Slots,
- Slot,
createComponent
} from './component'
-export * from './componentLifecycle'
+export { Slot, Slots } from './componentSlots'
+
+export { ComponentPropsOptions } from './componentProps'
+export * from './reactivity'
+export * from './componentLifecycle'
export { createRenderer, RendererOptions } from './createRenderer'
+
export { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
-export * from './reactivity'
-import { isArray, EMPTY_ARR } from '@vue/shared'
+import { isArray, isFunction, isString, EMPTY_ARR } from '@vue/shared'
import { ComponentInstance } from './component'
import { HostNode } from './createRenderer'
+import { RawSlots } from './componentSlots'
export const Fragment = Symbol('Fragment')
export const Text = Symbol('Text')
export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
export type VNodeChild = VNodeChildAtom | VNodeChildren
+export type NormalizedChildren = string | VNodeChildren | RawSlots | null
+
export interface VNode {
type: VNodeTypes
props: { [key: string]: any } | null
key: string | number | null
- children: string | VNodeChildren | null
+ children: NormalizedChildren
component: ComponentInstance | null
// DOM
type,
props,
key: props && props.key,
- children: typeof children === 'number' ? children + '' : children,
+ children: normalizeChildren(children),
component: null,
el: null,
anchor: null,
// fragment
return createVNode(Fragment, null, child)
} else if (typeof child === 'object') {
- // already vnode
+ // already vnode, this should be the most common since compiled templates
+ // always produce all-vnode children arrays
return child as VNode
} else {
// primitive types
return createVNode(Text, null, child + '')
}
}
+
+export function normalizeChildren(children: unknown): NormalizedChildren {
+ if (children == null) {
+ return null
+ } else if (isArray(children)) {
+ return children
+ } else if (typeof children === 'object') {
+ return children as RawSlots
+ } else if (isFunction(children)) {
+ return { default: children }
+ } else {
+ return isString(children) ? children : children + ''
+ }
+}