mirror of
https://github.com/vuejs/vue.git
synced 2024-11-21 20:28:54 +00:00
feat: support slot-props and its shorthand
See https://github.com/vuejs/vue/issues/9306 for more details.
This commit is contained in:
parent
4f61d5b73b
commit
584e89da4a
@ -45,6 +45,7 @@ let postTransforms
|
||||
let platformIsPreTag
|
||||
let platformMustUseProp
|
||||
let platformGetTagNamespace
|
||||
let maybeComponent
|
||||
|
||||
export function createASTElement (
|
||||
tag: string,
|
||||
@ -74,6 +75,8 @@ export function parse (
|
||||
platformIsPreTag = options.isPreTag || no
|
||||
platformMustUseProp = options.mustUseProp || no
|
||||
platformGetTagNamespace = options.getTagNamespace || no
|
||||
const isReservedTag = options.isReservedTag || no
|
||||
maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
|
||||
|
||||
transforms = pluckModuleFunction(options.modules, 'transformNode')
|
||||
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
|
||||
@ -390,7 +393,8 @@ export function processElement (
|
||||
)
|
||||
|
||||
processRef(element)
|
||||
processSlot(element)
|
||||
processSlotContent(element)
|
||||
processSlotOutlet(element)
|
||||
processComponent(element)
|
||||
for (let i = 0; i < transforms.length; i++) {
|
||||
element = transforms[i](element, options) || element
|
||||
@ -542,7 +546,79 @@ function processOnce (el) {
|
||||
}
|
||||
}
|
||||
|
||||
function processSlot (el) {
|
||||
// handle content being passed to a component as slot,
|
||||
// e.g. <template slot="xxx">, <div slot-scope="xxx">
|
||||
function processSlotContent (el) {
|
||||
let slotScope
|
||||
if (el.tag === 'template') {
|
||||
slotScope = getAndRemoveAttr(el, 'scope')
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && slotScope) {
|
||||
warn(
|
||||
`the "scope" attribute for scoped slots have been deprecated and ` +
|
||||
`replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
|
||||
`can also be used on plain elements in addition to <template> to ` +
|
||||
`denote scoped slots.`,
|
||||
el.rawAttrsMap['scope'],
|
||||
true
|
||||
)
|
||||
}
|
||||
el.slotScope = (
|
||||
slotScope ||
|
||||
getAndRemoveAttr(el, 'slot-scope') ||
|
||||
// new in 2.6: slot-props and its shorthand works the same as slot-scope
|
||||
// when used on <template> containers
|
||||
getAndRemoveAttr(el, 'slot-props') ||
|
||||
getAndRemoveAttr(el, '()')
|
||||
)
|
||||
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
|
||||
warn(
|
||||
`Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
|
||||
`(v-for takes higher priority). Use a wrapper <template> for the ` +
|
||||
`scoped slot to make it clearer.`,
|
||||
el.rawAttrsMap['slot-scope'],
|
||||
true
|
||||
)
|
||||
}
|
||||
el.slotScope = slotScope
|
||||
} else {
|
||||
// 2.6: slot-props on component, denotes default slot
|
||||
slotScope = getAndRemoveAttr(el, 'slot-props') || getAndRemoveAttr(el, '()')
|
||||
if (slotScope) {
|
||||
if (process.env.NODE_ENV !== 'production' && !maybeComponent(el)) {
|
||||
warn(
|
||||
`slot-props cannot be used on non-component elements.`,
|
||||
el.rawAttrsMap['slot-props'] || el.rawAttrsMap['()']
|
||||
)
|
||||
}
|
||||
// add the component's children to its default slot
|
||||
const slots = el.scopedSlots || (el.scopedSlots = {})
|
||||
const slotContainer = slots[`"default"`] = createASTElement('template', [], el)
|
||||
slotContainer.children = el.children
|
||||
slotContainer.slotScope = slotScope
|
||||
// remove children as they are returned from scopedSlots now
|
||||
el.children = []
|
||||
// mark el non-plain so data gets generated
|
||||
el.plain = false
|
||||
}
|
||||
}
|
||||
|
||||
// slot="xxx"
|
||||
const slotTarget = getBindingAttr(el, 'slot')
|
||||
if (slotTarget) {
|
||||
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
|
||||
// preserve slot as an attribute for native shadow DOM compat
|
||||
// only for non-scoped slots.
|
||||
if (el.tag !== 'template' && !el.slotScope) {
|
||||
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle <slot/> outlets
|
||||
function processSlotOutlet (el) {
|
||||
if (el.tag === 'slot') {
|
||||
el.slotName = getBindingAttr(el, 'name')
|
||||
if (process.env.NODE_ENV !== 'production' && el.key) {
|
||||
@ -553,44 +629,6 @@ function processSlot (el) {
|
||||
getRawBindingAttr(el, 'key')
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let slotScope
|
||||
if (el.tag === 'template') {
|
||||
slotScope = getAndRemoveAttr(el, 'scope')
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && slotScope) {
|
||||
warn(
|
||||
`the "scope" attribute for scoped slots have been deprecated and ` +
|
||||
`replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
|
||||
`can also be used on plain elements in addition to <template> to ` +
|
||||
`denote scoped slots.`,
|
||||
el.rawAttrsMap['scope'],
|
||||
true
|
||||
)
|
||||
}
|
||||
el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
|
||||
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
|
||||
warn(
|
||||
`Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
|
||||
`(v-for takes higher priority). Use a wrapper <template> for the ` +
|
||||
`scoped slot to make it clearer.`,
|
||||
el.rawAttrsMap['slot-scope'],
|
||||
true
|
||||
)
|
||||
}
|
||||
el.slotScope = slotScope
|
||||
}
|
||||
const slotTarget = getBindingAttr(el, 'slot')
|
||||
if (slotTarget) {
|
||||
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
|
||||
// preserve slot as an attribute for native shadow DOM compat
|
||||
// only for non-scoped slots.
|
||||
if (el.tag !== 'template' && !el.slotScope) {
|
||||
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -631,4 +631,105 @@ describe('Component scoped slot', () => {
|
||||
expect(vm.$el.innerHTML).toBe('<p>hello</p>')
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
// new in 2.6
|
||||
describe('slot-props syntax', () => {
|
||||
const Foo = {
|
||||
render(h) {
|
||||
return h('div', [
|
||||
this.$scopedSlots.default && this.$scopedSlots.default('from foo default'),
|
||||
this.$scopedSlots.one && this.$scopedSlots.one('from foo one'),
|
||||
this.$scopedSlots.two && this.$scopedSlots.two('from foo two')
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const Bar = {
|
||||
render(h) {
|
||||
return this.$scopedSlots.default && this.$scopedSlots.default('from bar')[0]
|
||||
}
|
||||
}
|
||||
|
||||
const Baz = {
|
||||
render(h) {
|
||||
return this.$scopedSlots.default && this.$scopedSlots.default('from baz')[0]
|
||||
}
|
||||
}
|
||||
|
||||
function runSuite(syntax) {
|
||||
it('default slot', () => {
|
||||
const vm = new Vue({
|
||||
template: `<foo ${syntax}="foo">{{ foo }}<div>{{ foo }}</div></foo>`,
|
||||
components: { Foo }
|
||||
}).$mount()
|
||||
expect(vm.$el.innerHTML).toBe(`from foo default<div>from foo default</div>`)
|
||||
})
|
||||
|
||||
it('nested default slots', () => {
|
||||
const vm = new Vue({
|
||||
template: `
|
||||
<foo ${syntax}="foo">
|
||||
<bar ${syntax}="bar">
|
||||
<baz ${syntax}="baz">
|
||||
{{ foo }} | {{ bar }} | {{ baz }}
|
||||
</baz>
|
||||
</bar>
|
||||
</foo>
|
||||
`,
|
||||
components: { Foo, Bar, Baz }
|
||||
}).$mount()
|
||||
expect(vm.$el.innerHTML.trim()).toBe(`from foo default | from bar | from baz`)
|
||||
})
|
||||
|
||||
it('default + named slots', () => {
|
||||
const vm = new Vue({
|
||||
template: `
|
||||
<foo ()="foo">
|
||||
{{ foo }}
|
||||
<template slot="one" ${syntax}="one">
|
||||
{{ one }}
|
||||
</template>
|
||||
<template slot="two" ${syntax}="two">
|
||||
{{ two }}
|
||||
</template>
|
||||
</foo>
|
||||
`,
|
||||
components: { Foo }
|
||||
}).$mount()
|
||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo default from foo one from foo two`)
|
||||
})
|
||||
|
||||
it('nested + named + default slots', () => {
|
||||
const vm = new Vue({
|
||||
template: `
|
||||
<foo>
|
||||
<template slot="one" ${syntax}="one">
|
||||
<bar ${syntax}="bar">
|
||||
{{ one }} {{ bar }}
|
||||
</bar>
|
||||
</template>
|
||||
<template slot="two" ${syntax}="two">
|
||||
<baz ${syntax}="baz">
|
||||
{{ two }} {{ baz }}
|
||||
</baz>
|
||||
</template>
|
||||
</foo>
|
||||
`,
|
||||
components: { Foo, Bar, Baz }
|
||||
}).$mount()
|
||||
expect(vm.$el.innerHTML.replace(/\s+/g, ' ')).toMatch(`from foo one from bar from foo two from baz`)
|
||||
})
|
||||
|
||||
it('should warn slot-props usage on non-component elements', () => {
|
||||
const vm = new Vue({
|
||||
template: `<div ${syntax}="foo"/>`
|
||||
}).$mount()
|
||||
expect(`slot-props cannot be used on non-component elements`).toHaveBeenWarned()
|
||||
})
|
||||
}
|
||||
|
||||
// run tests for both full syntax and shorthand
|
||||
runSuite('slot-props')
|
||||
runSuite('()')
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user