Skip to content

Commit

Permalink
feat: support rendering scoped slots (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
ktsn committed Jul 18, 2018
1 parent 1f7b4f5 commit 9cbbae5
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 30 deletions.
5 changes: 5 additions & 0 deletions src/view/components/Child.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const Child = Vue.extend({
slots: {
type: Object as { (): Record<string, VNode[]> },
required: true
},
scopedSlots: {
type: Object,
required: true
}
},
Expand Down
5 changes: 5 additions & 0 deletions src/view/components/ContainerNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export default Vue.extend({
slots: {
type: Object as { (): Record<string, VNode[]> },
required: true
},
scopedSlots: {
type: Object,
required: true
}
},
Expand Down
3 changes: 2 additions & 1 deletion src/view/components/ContainerVueComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export default Vue.extend({
childComponents: this.document.childComponents,
styles: this.document.styleCode,
propsData: this.propsData
}
},
scopedSlots: this.$scopedSlots
},
flattenSlots
)
Expand Down
65 changes: 57 additions & 8 deletions src/view/components/Node.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { DefaultValue, ChildComponent } from '@/parser/script/types'
import {
convertToVNodeData,
resolveControlDirectives,
ResolvedChild
ResolvedChild,
resolveScopedSlots
} from '../rendering'
import { DraggingPlace } from '../store/modules/project/types'
import { mapValues } from '@/utils'
export default Vue.extend({
name: 'Node',
Expand All @@ -35,6 +37,10 @@ export default Vue.extend({
type: Object as { (): Record<string, VNode[]> },
required: true
},
scopedSlots: {
type: Object,
required: true
},
selectable: Boolean,
selected: Boolean
},
Expand Down Expand Up @@ -74,6 +80,39 @@ export default Vue.extend({
}
}
const scopedSlots = resolveScopedSlots(node)
if (scopedSlots) {
const h = this.$createElement
data.scopedSlots = mapValues(scopedSlots, ({ scopeName, contents }) => {
return (props: any) => {
const newScope = {
...scope,
[scopeName]: props
}
const resolved = contents.reduce<ResolvedChild[]>((acc, child) => {
return resolveControlDirectives(acc, {
el: child,
scope: newScope
})
}, [])
return resolved.map(c => {
return h(Child, {
props: {
uri: this.uri,
data: c.el,
scope: c.scope,
childComponents: this.childComponents,
slots: this.slots,
scopedSlots: this.scopedSlots
},
on: this.$listeners
})
})
}
})
}
return data
},
Expand All @@ -93,12 +132,21 @@ export default Vue.extend({
* Returns children which is resolved v-for, v-if and its family.
*/
resolvedChildren(): ResolvedChild[] {
return this.data.children.reduce<ResolvedChild[]>((acc, child) => {
return resolveControlDirectives(acc, {
el: child,
scope: this.scope
return this.data.children
.filter(child => {
return (
child.type !== 'Element' ||
!child.startTag.attributes.some(
attr => !attr.directive && attr.name === 'slot-scope'
)
)
})
}, [])
.reduce<ResolvedChild[]>((acc, child) => {
return resolveControlDirectives(acc, {
el: child,
scope: this.scope
})
}, [])
}
},
Expand Down Expand Up @@ -156,7 +204,7 @@ export default Vue.extend({
},
render(h): VNode {
const { uri, childComponents, slots } = this
const { uri, childComponents, slots, scopedSlots } = this
return h(
this.vnodeTag,
Expand All @@ -169,7 +217,8 @@ export default Vue.extend({
data: c.el,
scope: c.scope,
childComponents,
slots
slots,
scopedSlots
},
on: this.$listeners
})
Expand Down
56 changes: 37 additions & 19 deletions src/view/components/NodeSlot.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts">
import Vue, { VNode } from 'vue'
import Vue, { VNode, VNodeChildrenArrayContents } from 'vue'
import Child from './Child.vue'
import { Element } from '@/parser/template/types'
import { DefaultValue, ChildComponent } from '@/parser/script/types'
import { convertToSlotScope } from '@/view/rendering'
export default Vue.extend({
name: 'NodeSlot',
Expand All @@ -28,30 +29,47 @@ export default Vue.extend({
slots: {
type: Object as { (): Record<string, VNode[]> },
required: true
},
scopedSlots: {
type: Object as {
(): Record<string, (props: any) => string | VNodeChildrenArrayContents>
},
required: true
}
},
render(h, { props }): any /* VNode[] */ {
const { data, slots } = props
const slotAttr = data.startTag.attributes.find(attr => attr.name === 'name')
const slot = slots[slotAttr ? slotAttr.value : 'default']
if (!slot) {
// placeholder content
return props.data.children.map(child => {
return h(Child, {
props: {
uri: props.uri,
data: child,
scope: props.scope,
childComponents: props.childComponents,
slots
}
})
})
const { data, scope, slots, scopedSlots } = props
const slotScope = convertToSlotScope(data.startTag.attributes, scope)
const slotName = String(slotScope.name || 'default')
delete slotScope.name
const slot = slots[slotName]
const scopedSlot = scopedSlots[slotName]
if (scopedSlot) {
return scopedSlot(slotScope)
}
return slot
if (slot) {
return slot
}
// placeholder content
return props.data.children.map(child => {
return h(Child, {
props: {
uri: props.uri,
data: child,
scope: props.scope,
childComponents: props.childComponents,
slots,
scopedSlots
}
})
})
}
})
</script>
3 changes: 2 additions & 1 deletion src/view/components/VueComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export default Vue.extend({
data: child.el,
scope: child.scope,
childComponents: this.childComponents,
slots: this.$slots
slots: this.$slots,
scopedSlots: this.$scopedSlots
},
on: {
select: (path: number[]) => {
Expand Down
56 changes: 56 additions & 0 deletions src/view/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export interface ResolvedChild {
scope: Record<string, DefaultValue>
}

export interface ResolvedScopedSlot {
scopeName: string
contents: ElementChild[]
}

function resolveVFor(
acc: ResolvedChild[],
vFor: VForDirective,
Expand Down Expand Up @@ -160,6 +165,40 @@ function resolveVElse(
}
}

export function resolveScopedSlots(
el: Element
): Record<string, ResolvedScopedSlot> | undefined {
let scopedSlots: Record<string, ResolvedScopedSlot> | undefined

el.children.forEach(child => {
if (child.type !== 'Element') return

const slotScope = child.startTag.attributes.find(
attr => !attr.directive && attr.name === 'slot-scope'
)
const slotScopeName = slotScope && slotScope.value
if (!slotScopeName) return

const slot = child.startTag.attributes.find(
attr => !attr.directive && attr.name === 'slot'
)
const slotName = (slot && slot.value) || 'default'

const contents = child.name === 'template' ? child.children : [child]

if (!scopedSlots) {
scopedSlots = {}
}

scopedSlots[slotName] = {
scopeName: slotScopeName,
contents
}
})

return scopedSlots
}

/**
* Resolve v-if/-else/-else-if and v-for,
* so that add or remove AST node from final output.
Expand Down Expand Up @@ -261,6 +300,21 @@ function parseStyleText(cssText: string): Record<string, string> {
return res
}

export function convertToSlotScope(
attrs: (Attribute | Directive)[],
scope: Record<string, DefaultValue>
): Record<string, DefaultValue> {
const slotScope: Record<string, any> = {}
attrs.forEach(attr => {
if (!attr.directive) {
slotScope[attr.name] = attr.value === null ? true : attr.value
} else if (attr.name === 'bind' && attr.argument) {
slotScope[attr.argument] = directiveValue(attr, scope)
}
})
return slotScope
}

export function convertToVNodeData(
tag: string,
attrs: (Attribute | Directive)[],
Expand All @@ -280,6 +334,8 @@ export function convertToVNodeData(
acc.staticClass = attr.value || undefined
} else if (attr.name === 'style') {
acc.staticStyle = parseStyleText(attr.value || '')
} else if (attr.name === 'slot') {
acc.slot = attr.value || undefined
} else if (isValidAttributeName(attr.name)) {
acc.attrs![attr.name] = attr.value || ''
}
Expand Down
7 changes: 7 additions & 0 deletions test/view/VueComponent/__snapshots__/scoped-slot.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VueComponent scoped slot renders named scoped slot content 1`] = `"<div><style></style><div class=\\"\\"><style></style><div data-scope-scope-id=\\"\\" class=\\"\\"><p data-scope-scope-id=\\"\\" class=\\"\\">foo content</p><p slot-scope=\\"props\\" class=\\"\\"><span class=\\"\\">test1</span></p><p slot-scope=\\"props\\" class=\\"\\"><span class=\\"\\">test2</span></p></div></div></div>"`;
exports[`VueComponent scoped slot renders scoped slot content 1`] = `"<div><style></style><div class=\\"\\"><style></style><div data-scope-scope-id=\\"\\" class=\\"\\"><p data-scope-scope-id=\\"\\" class=\\"\\">foo content</p><p slot-scope=\\"props\\" class=\\"\\"><span class=\\"\\">test</span></p></div></div></div>"`;
exports[`VueComponent scoped slot resolves template element children as a slot 1`] = `"<div><style></style><div class=\\"\\"><style></style><div data-scope-scope-id=\\"\\" class=\\"\\"><strong class=\\"\\">named</strong><span class=\\"\\"><span class=\\"\\">test</span></span></div></div></div>"`;
2 changes: 1 addition & 1 deletion test/view/VueComponent/__snapshots__/slot.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VueComponent slot renders named slot content 1`] = `"<div><style></style><div class=\\"\\"><style></style><div data-scope-scope-id=\\"\\" class=\\"\\"><p data-scope-scope-id=\\"\\" class=\\"\\">foo content</p><p slot=\\"test\\" class=\\"\\">named</p></div></div></div>"`;
exports[`VueComponent slot renders named slot content 1`] = `"<div><style></style><div class=\\"\\"><style></style><div data-scope-scope-id=\\"\\" class=\\"\\"><p data-scope-scope-id=\\"\\" class=\\"\\">foo content</p><p class=\\"\\">named</p></div></div></div>"`;
exports[`VueComponent slot renders placeholder slot content 1`] = `"<div><style></style><div class=\\"\\"><p class=\\"\\">placeholder content</p></div></div>"`;
Expand Down

0 comments on commit 9cbbae5

Please sign in to comment.