From 36918a907813f2ec7483981c444d914005598ab5 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 2 Jun 2022 17:02:39 -0400 Subject: [PATCH] Flatten Fragments when rendering --- .../src/components/popover/popover.test.ts | 44 +++++++++++++++++++ packages/@headlessui-vue/src/utils/render.ts | 31 ++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/@headlessui-vue/src/components/popover/popover.test.ts b/packages/@headlessui-vue/src/components/popover/popover.test.ts index 54a730fe89..ac8a2c4a12 100644 --- a/packages/@headlessui-vue/src/components/popover/popover.test.ts +++ b/packages/@headlessui-vue/src/components/popover/popover.test.ts @@ -318,6 +318,50 @@ describe('Rendering', () => { }) ) + it( + 'should be possible to render a PopoverButton with content', + suppressConsoleLogs(async () => { + renderTemplate({ + template: html`Some Content`, + components: { + ExampleOuter: defineComponent({ + template: html``, + components: { + ExampleInner: defineComponent({ + components: getDefaultComponents(), + + template: html` + + + + + + + `, + }), + }, + }), + }, + }) + + 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( diff --git a/packages/@headlessui-vue/src/utils/render.ts b/packages/@headlessui-vue/src/utils/render.ts index 10c742655b..172bd0d8ba 100644 --- a/packages/@headlessui-vue/src/utils/render.ts +++ b/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 { @@ -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 ?? [] @@ -144,6 +146,33 @@ function _render({ return h(as, Object.assign({}, incomingProps, dataAttributes), children) } +/** + * When passed a structure like this: + * something + * + * And Example is defined as: + * + * + * We need to turn the fragment that 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 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>(object: T) { let clone = Object.assign({}, object) for (let key in clone) {