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 2, 2022
1 parent 4078b28 commit 36918a9
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
44 changes: 44 additions & 0 deletions packages/@headlessui-vue/src/components/popover/popover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,50 @@ describe('Rendering', () => {
})
)

it(
'should be possible to render a PopoverButton with <slot> content',
suppressConsoleLogs(async () => {
renderTemplate({
template: html`<ExampleOuter><span>Some Content</span></ExampleOuter>`,
components: {
ExampleOuter: defineComponent({
template: html`<ExampleInner><slot /></ExampleInner>`,
components: {
ExampleInner: defineComponent({
components: getDefaultComponents(),

template: html`
<Popover>
<PopoverButton as="template">
<slot />
</PopoverButton>
<PopoverPanel />
</Popover>
`,
}),
},
}),
},
})

assertPopoverButton({
state: PopoverState.InvisibleUnmounted,
attributes: { id: 'headlessui-popover-button-1' },
textContent: 'Some Content',
})
assertPopoverPanel({ state: PopoverState.InvisibleUnmounted })

await click(getPopoverButton())

assertPopoverButton({
state: PopoverState.Visible,
attributes: { id: 'headlessui-popover-button-1' },
textContent: 'Some Content',
})
assertPopoverPanel({ state: PopoverState.Visible })
})
)

describe('`type` attribute', () => {
it('should set the `type` to "button" by default', async () => {
renderTemplate(
Expand Down
31 changes: 30 additions & 1 deletion packages/@headlessui-vue/src/utils/render.ts
Original file line number Diff line number Diff line change
@@ -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 36918a9

Please sign in to comment.