/* @flow */
|
|
import { def } from 'core/util/lang'
|
import { normalizeChildren } from 'core/vdom/helpers/normalize-children'
|
import { emptyObject } from 'shared/util'
|
import { isAsyncPlaceholder } from './is-async-placeholder'
|
|
export function normalizeScopedSlots (
|
slots: { [key: string]: Function } | void,
|
normalSlots: { [key: string]: Array<VNode> },
|
prevSlots?: { [key: string]: Function } | void
|
): any {
|
let res
|
const hasNormalSlots = Object.keys(normalSlots).length > 0
|
const isStable = slots ? !!slots.$stable : !hasNormalSlots
|
const key = slots && slots.$key
|
if (!slots) {
|
res = {}
|
} else if (slots._normalized) {
|
// fast path 1: child component re-render only, parent did not change
|
return slots._normalized
|
} else if (
|
isStable &&
|
prevSlots &&
|
prevSlots !== emptyObject &&
|
key === prevSlots.$key &&
|
!hasNormalSlots &&
|
!prevSlots.$hasNormal
|
) {
|
// fast path 2: stable scoped slots w/ no normal slots to proxy,
|
// only need to normalize once
|
return prevSlots
|
} else {
|
res = {}
|
for (const key in slots) {
|
if (slots[key] && key[0] !== '$') {
|
res[key] = normalizeScopedSlot(normalSlots, key, slots[key])
|
}
|
}
|
}
|
// expose normal slots on scopedSlots
|
for (const key in normalSlots) {
|
if (!(key in res)) {
|
res[key] = proxyNormalSlot(normalSlots, key)
|
}
|
}
|
// avoriaz seems to mock a non-extensible $scopedSlots object
|
// and when that is passed down this would cause an error
|
if (slots && Object.isExtensible(slots)) {
|
(slots: any)._normalized = res
|
}
|
def(res, '$stable', isStable)
|
def(res, '$key', key)
|
def(res, '$hasNormal', hasNormalSlots)
|
return res
|
}
|
|
function normalizeScopedSlot(normalSlots, key, fn) {
|
const normalized = function () {
|
let res = arguments.length ? fn.apply(null, arguments) : fn({})
|
res = res && typeof res === 'object' && !Array.isArray(res)
|
? [res] // single vnode
|
: normalizeChildren(res)
|
let vnode: ?VNode = res && res[0]
|
return res && (
|
!vnode ||
|
(res.length === 1 && vnode.isComment && !isAsyncPlaceholder(vnode)) // #9658, #10391
|
) ? undefined
|
: res
|
}
|
// this is a slot using the new v-slot syntax without scope. although it is
|
// compiled as a scoped slot, render fn users would expect it to be present
|
// on this.$slots because the usage is semantically a normal slot.
|
if (fn.proxy) {
|
Object.defineProperty(normalSlots, key, {
|
get: normalized,
|
enumerable: true,
|
configurable: true
|
})
|
}
|
return normalized
|
}
|
|
function proxyNormalSlot(slots, key) {
|
return () => slots[key]
|
}
|