diff --git a/components/_util/__mocks__/RenderSlot.tsx b/components/_util/__mocks__/RenderSlot.tsx new file mode 100644 index 0000000000..42e40ee0a9 --- /dev/null +++ b/components/_util/__mocks__/RenderSlot.tsx @@ -0,0 +1,11 @@ +import { defineComponent } from 'vue'; +import { customRenderSlot } from '../vnode'; + +export default defineComponent({ + name: 'RenderSlot', + setup(_props, { slots }) { + return () => { + return customRenderSlot(slots, 'default', {}, () => ['default value']); + }; + }, +}); diff --git a/components/_util/__tests__/vNode.test.js b/components/_util/__tests__/vNode.test.js new file mode 100644 index 0000000000..4bfc7e8fa9 --- /dev/null +++ b/components/_util/__tests__/vNode.test.js @@ -0,0 +1,26 @@ +import RenderSlot from '../__mocks__/RenderSlot'; +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; + +describe('render slot content', () => { + it('renders slot content', () => { + const wrapper = mount(RenderSlot, { + slots: { + default: () => 'This is slot content', + }, + }); + + expect(wrapper.html()).toContain('This is slot content'); + }); + + it('render default value when slot is fragment', async () => { + const wrapper = mount(RenderSlot, { + slots: { + default: () => <>, + }, + }); + + await nextTick(); + expect(wrapper.html()).toContain('default value'); + }); +}); diff --git a/components/_util/vnode.ts b/components/_util/vnode.ts index e5ab010a42..8565cbe5ba 100644 --- a/components/_util/vnode.ts +++ b/components/_util/vnode.ts @@ -1,6 +1,6 @@ import { filterEmpty } from './props-util'; -import type { VNode, VNodeProps } from 'vue'; -import { cloneVNode, isVNode, render as VueRender } from 'vue'; +import type { Slots, VNode, VNodeArrayChildren, VNodeProps } from 'vue'; +import { cloneVNode, isVNode, Comment, Fragment, render as VueRender } from 'vue'; import warning from './warning'; import type { RefObject } from './createRef'; type NodeProps = Record & @@ -55,3 +55,28 @@ export function deepCloneElement( export function triggerVNodeUpdate(vm: VNode, attrs: Record, dom: any) { VueRender(cloneVNode(vm, { ...attrs }), dom); } + +const ensureValidVNode = (slot: VNodeArrayChildren | null) => { + return (slot || []).some(child => { + if (!isVNode(child)) return true; + if (child.type === Comment) return false; + if (child.type === Fragment && !ensureValidVNode(child.children as VNodeArrayChildren)) + return false; + return true; + }) + ? slot + : null; +}; + +export function customRenderSlot( + slots: Slots, + name: string, + props: Record, + fallback?: () => VNodeArrayChildren, +) { + const slot = slots[name]?.(props); + if (ensureValidVNode(slot)) { + return slot; + } + return fallback?.(); +} diff --git a/components/card/Card.tsx b/components/card/Card.tsx index 18d330cf0f..a5f83c8496 100644 --- a/components/card/Card.tsx +++ b/components/card/Card.tsx @@ -1,5 +1,5 @@ import type { VNodeTypes, PropType, VNode, ExtractPropTypes, CSSProperties } from 'vue'; -import { isVNode, defineComponent, renderSlot } from 'vue'; +import { isVNode, defineComponent } from 'vue'; import Tabs from '../tabs'; import PropTypes from '../_util/vue-types'; import { flattenChildren, isEmptyElement, filterEmptyWithUndefined } from '../_util/props-util'; @@ -10,6 +10,8 @@ import devWarning from '../vc-util/devWarning'; import useStyle from './style'; import Skeleton from '../skeleton'; import type { CustomSlotsType } from '../_util/type'; +import { customRenderSlot } from '../_util/vnode'; + export interface CardTabListType { key: string; tab: any; @@ -152,7 +154,7 @@ const Card = defineComponent({ `tabList slots is deprecated, Please use \`customTab\` instead.`, ); let tab = temp !== undefined ? temp : slots[name] ? slots[name](item) : null; - tab = renderSlot(slots, 'customTab', item as any, () => [tab]); + tab = customRenderSlot(slots, 'customTab', item as any, () => [tab]); return ; })} diff --git a/components/vc-table/Cell/index.tsx b/components/vc-table/Cell/index.tsx index 37fd51fbc3..636f1fd652 100644 --- a/components/vc-table/Cell/index.tsx +++ b/components/vc-table/Cell/index.tsx @@ -1,7 +1,7 @@ import classNames from '../../_util/classNames'; import { filterEmpty, flattenChildren, isValidElement } from '../../_util/props-util'; import type { CSSProperties, VNodeArrayChildren } from 'vue'; -import { Text, computed, defineComponent, isVNode, renderSlot } from 'vue'; +import { Text, computed, defineComponent, isVNode } from 'vue'; import type { DataIndex, @@ -23,6 +23,7 @@ import { useInjectSticky } from '../context/StickyContext'; import { warning } from '../../vc-util/warning'; import type { MouseEventHandler } from '../../_util/EventInterface'; import eagerComputed from '../../_util/eagerComputed'; +import { customRenderSlot } from 'ant-design-vue/es/_util/vnode'; /** Check if cell is in hover range */ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: number, endRow: number) { @@ -223,7 +224,7 @@ export default defineComponent({ contextSlots.value.bodyCell && !column.slots?.customRender ) { - const child = renderSlot( + const child = customRenderSlot( contextSlots.value, 'bodyCell', { diff --git a/components/vc-table/hooks/useColumns.tsx b/components/vc-table/hooks/useColumns.tsx index d726de5f2a..811c1e24ef 100644 --- a/components/vc-table/hooks/useColumns.tsx +++ b/components/vc-table/hooks/useColumns.tsx @@ -1,6 +1,6 @@ import { warning } from '../../vc-util/warning'; import type { ComputedRef, Ref } from 'vue'; -import { renderSlot, computed, watchEffect } from 'vue'; +import { computed, watchEffect } from 'vue'; import type { ColumnsType, ColumnType, @@ -14,6 +14,7 @@ import type { import { INTERNAL_COL_DEFINE } from '../utils/legacyUtil'; import { EXPAND_COLUMN } from '../constant'; import { useInjectSlots } from '../../table/context'; +import { customRenderSlot } from '../../_util/vnode'; function flatColumns(columns: ColumnsType): ColumnType[] { return columns.reduce((list, column) => { @@ -179,7 +180,7 @@ function useColumns( class: `${prefixCls.value}-expand-icon-col`, columnType: 'EXPAND_COLUMN', }, - title: renderSlot(contextSlots.value, 'expandColumnTitle', {}, () => ['']), + title: customRenderSlot(contextSlots.value, 'expandColumnTitle', {}, () => ['']), fixed: fixedColumn, class: `${prefixCls.value}-row-expand-icon-cell`, width: expandColumnWidth.value,