Skip to content

Commit

Permalink
Flatten Fragments when rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
thecrypticace committed Jun 3, 2022
1 parent 4078b28 commit 9ea5ee2
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
40 changes: 40 additions & 0 deletions packages/@headlessui-vue/src/utils/render.test.ts
Expand Up @@ -79,4 +79,44 @@ describe('Validation', () => {

expect(document.getElementById('result')).toHaveClass('abc')
})

it('should allow use of <slot> as children', () => {
renderTemplate({
template: html`
<ExampleOuter>
<div id="result">Some Content</div>
</ExampleOuter>
`,

components: {
ExampleOuter: defineComponent({
template: html`
<ExampleInner>
<slot />
</ExampleInner>
`,

components: {
ExampleInner: defineComponent({
components: { Dummy },

template: html`
<Dummy as="template" class="foo" data-test="123">
<Dummy as="template" class="bar" data-test="345">
<slot />
</Dummy>
</Dummy>
`,
}),
},
}),
},
})

expect(document.getElementById('result')).toHaveClass('foo')
expect(document.getElementById('result')).toHaveClass('bar')

// TODO: Is this the expected behavior? Should it actually be `345`?
expect(document.getElementById('result')).toHaveAttribute('data-test', '123')
})
})
31 changes: 30 additions & 1 deletion packages/@headlessui-vue/src/utils/render.ts
@@ -1,4 +1,4 @@
import { h, cloneVNode, Slots } from 'vue'
import { h, cloneVNode, Slots, Fragment, VNode } from 'vue'
import { match } from './match'

export enum Features {
Expand Down Expand Up @@ -102,6 +102,8 @@ function _render({
}

if (as === 'template') {
children = flattenFragments(children as VNode[])

if (Object.keys(incomingProps).length > 0 || Object.keys(attrs).length > 0) {
let [firstChild, ...other] = children ?? []

Expand Down Expand Up @@ -144,6 +146,33 @@ function _render({
return h(as, Object.assign({}, incomingProps, dataAttributes), children)
}

/**
* When passed a structure like this:
* <Example><span>something</span></Example>
*
* And Example is defined as:
* <SomeComponent><slot /></SomeComponent>
*
* We need to turn the fragment that <slot> represents into the slot.
* Luckily by this point it's already rendered into an array of VNodes
* for us so we can just flatten it directly.
*
* We have to do this recursively because there could be multiple
* levels of Component nesting all with <slot> elements interspersed
*
* @param children
* @returns
*/
function flattenFragments(children: VNode[]): VNode[] {
return children.flatMap((child) => {
if (child.type === Fragment) {
return flattenFragments(child.children as VNode[])
}

return [child]
})
}

export function compact<T extends Record<any, any>>(object: T) {
let clone = Object.assign({}, object)
for (let key in clone) {
Expand Down

0 comments on commit 9ea5ee2

Please sign in to comment.