mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
fix(components): [descriptions] allow wrapping description-item inside components
This commit is contained in:
@@ -1,9 +1,21 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import {
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
inject,
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
} from 'vue'
|
||||
import { columnAlignment } from '@element-plus/constants'
|
||||
import { buildProps } from '@element-plus/utils'
|
||||
import { buildProps, throwError } from '@element-plus/utils'
|
||||
import { COMPONENT_NAME } from './constants'
|
||||
import { descriptionsKey } from './token'
|
||||
|
||||
import type { ExtractPropTypes, Slot, VNode } from 'vue'
|
||||
import type {
|
||||
ComponentInternalInstance,
|
||||
ExtractPropTypes,
|
||||
Slot,
|
||||
VNode,
|
||||
} from 'vue'
|
||||
|
||||
export const descriptionItemProps = buildProps({
|
||||
/**
|
||||
@@ -82,6 +94,30 @@ export const descriptionItemProps = buildProps({
|
||||
const DescriptionItem = defineComponent({
|
||||
name: COMPONENT_NAME,
|
||||
props: descriptionItemProps,
|
||||
setup(props) {
|
||||
const descriptions = inject(descriptionsKey)
|
||||
if (!descriptions)
|
||||
throwError(
|
||||
COMPONENT_NAME,
|
||||
'usage: <el-descriptions><el-descriptions-item /></el-descriptions/>'
|
||||
)
|
||||
|
||||
const instance = getCurrentInstance()! as DescriptionItemInternalInstance
|
||||
|
||||
onBeforeMount(() => {
|
||||
descriptions.addDescriptionItem({
|
||||
uid: instance.uid,
|
||||
props,
|
||||
get vnode() {
|
||||
return instance.vnode
|
||||
},
|
||||
})
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
descriptions.removeDescriptionItem(instance.uid)
|
||||
})
|
||||
},
|
||||
render: () => '',
|
||||
})
|
||||
|
||||
export default DescriptionItem
|
||||
@@ -91,3 +127,6 @@ export type DescriptionItemVNode = VNode & {
|
||||
children: { [name: string]: Slot } | null
|
||||
props: Partial<DescriptionItemProps> | null
|
||||
}
|
||||
type DescriptionItemInternalInstance = ComponentInternalInstance & {
|
||||
vnode: DescriptionItemVNode
|
||||
}
|
||||
|
||||
@@ -15,9 +15,20 @@
|
||||
<div :class="ns.e('body')">
|
||||
<table :class="[ns.e('table'), ns.is('bordered', border)]">
|
||||
<tbody>
|
||||
<template v-for="(row, _index) in getRows()" :key="_index">
|
||||
<el-descriptions-row :row="row" />
|
||||
</template>
|
||||
<slot />
|
||||
<rows-renderer
|
||||
:magic-prop="
|
||||
() => {
|
||||
// `getRows()` returns vnodes that are not reactive,
|
||||
// so the render functions cannot fully capture its own deps.
|
||||
// This workaround forces Vue to update `rows-renderer`.
|
||||
}
|
||||
"
|
||||
>
|
||||
<template v-for="(row, _index) in getRows()" :key="_index">
|
||||
<el-descriptions-row :row="row" />
|
||||
</template>
|
||||
</rows-renderer>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -25,17 +36,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, provide, useSlots } from 'vue'
|
||||
import { flattedChildren } from '@element-plus/utils'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { computed, getCurrentInstance, provide } from 'vue'
|
||||
import { useNamespace, useOrderedChildren } from '@element-plus/hooks'
|
||||
import { useFormSize } from '@element-plus/components/form'
|
||||
import ElDescriptionsRow from './descriptions-row.vue'
|
||||
import { descriptionsKey } from './token'
|
||||
import { descriptionProps } from './description'
|
||||
import { COMPONENT_NAME } from './constants'
|
||||
|
||||
import type { IDescriptionsInject } from './descriptions.type'
|
||||
import type { DescriptionItemContext } from './type'
|
||||
import type { DescriptionItemVNode } from './description-item'
|
||||
import type { Slots } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElDescriptions',
|
||||
@@ -47,9 +58,20 @@ const ns = useNamespace('descriptions')
|
||||
|
||||
const descriptionsSize = useFormSize()
|
||||
|
||||
const slots = useSlots()
|
||||
const {
|
||||
children: descriptionItems,
|
||||
addChild: addDescriptionItem,
|
||||
removeChild: removeDescriptionItem,
|
||||
} = useOrderedChildren<DescriptionItemContext>(
|
||||
getCurrentInstance()!,
|
||||
COMPONENT_NAME
|
||||
)
|
||||
|
||||
provide(descriptionsKey, props as IDescriptionsInject)
|
||||
provide(descriptionsKey, {
|
||||
props,
|
||||
addDescriptionItem,
|
||||
removeDescriptionItem,
|
||||
})
|
||||
|
||||
const descriptionKls = computed(() => [ns.b(), ns.m(descriptionsSize.value)])
|
||||
|
||||
@@ -73,21 +95,19 @@ const filledNode = (
|
||||
}
|
||||
|
||||
const getRows = () => {
|
||||
if (!slots.default) return []
|
||||
const children = descriptionItems.value
|
||||
if (children.length === 0) return []
|
||||
|
||||
const children = flattedChildren(slots.default()).filter(
|
||||
(node): node is DescriptionItemVNode =>
|
||||
(node as any)?.type?.name === COMPONENT_NAME
|
||||
)
|
||||
const rows: DescriptionItemVNode[][] = []
|
||||
let temp: DescriptionItemVNode[] = []
|
||||
let count = props.column
|
||||
let totalSpan = 0 // all spans number of item
|
||||
const rowspanTemp: number[] = [] // the number of row spans
|
||||
|
||||
children.forEach((node, index) => {
|
||||
const span = node.props?.span || 1
|
||||
const rowspan = node.props?.rowspan || 1
|
||||
children.forEach((child, index) => {
|
||||
const node = child.vnode
|
||||
const span = child.props?.span || 1
|
||||
const rowspan = child.props?.rowspan || 1
|
||||
const rowNo = rows.length
|
||||
rowspanTemp[rowNo] ||= 0
|
||||
|
||||
@@ -127,4 +147,8 @@ const getRows = () => {
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
const RowsRenderer = (_: unknown, { slots }: { slots: Slots }) => {
|
||||
return slots.default!()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,10 +5,7 @@ import { useNamespace } from '@element-plus/hooks'
|
||||
import { descriptionsKey } from './token'
|
||||
import type { DirectiveArguments, PropType, VNode } from 'vue'
|
||||
|
||||
import type {
|
||||
IDescriptionsInject,
|
||||
IDescriptionsItemInject,
|
||||
} from './descriptions.type'
|
||||
import type { IDescriptionsItemInject } from './type'
|
||||
import type { DescriptionItemVNode } from './description-item'
|
||||
|
||||
export default defineComponent({
|
||||
@@ -26,7 +23,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const descriptions = inject(descriptionsKey, {} as IDescriptionsInject)
|
||||
const descriptions = inject(descriptionsKey)!
|
||||
|
||||
return {
|
||||
descriptions,
|
||||
@@ -42,7 +39,7 @@ export default defineComponent({
|
||||
return [dir, value, arg, modifiers]
|
||||
}) as DirectiveArguments
|
||||
|
||||
const { border, direction } = this.descriptions
|
||||
const { border, direction } = this.descriptions.props
|
||||
const isVertical = direction === 'vertical'
|
||||
const renderLabel = () => this.cell?.children?.label?.() || item.label
|
||||
const renderContent = () => this.cell?.children?.default?.()
|
||||
@@ -54,7 +51,7 @@ export default defineComponent({
|
||||
const labelClassName = item.labelClassName
|
||||
const width =
|
||||
this.type === 'label'
|
||||
? item.labelWidth || this.descriptions.labelWidth || item.width
|
||||
? item.labelWidth || this.descriptions.props.labelWidth || item.width
|
||||
: item.width
|
||||
|
||||
const style = {
|
||||
@@ -109,7 +106,9 @@ export default defineComponent({
|
||||
default: {
|
||||
const label = renderLabel()
|
||||
const labelStyle: Record<string, any> = {}
|
||||
const width = addUnit(item.labelWidth || this.descriptions.labelWidth)
|
||||
const width = addUnit(
|
||||
item.labelWidth || this.descriptions.props.labelWidth
|
||||
)
|
||||
if (width) {
|
||||
labelStyle.width = width
|
||||
labelStyle.display = 'inline-block'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<template v-if="descriptions.direction === 'vertical'">
|
||||
<template v-if="descriptions.props.direction === 'vertical'">
|
||||
<tr>
|
||||
<template v-for="(cell, _index) in row" :key="`tr1-${_index}`">
|
||||
<el-descriptions-cell :cell="cell" tag="th" type="label" />
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
<tr v-else>
|
||||
<template v-for="(cell, _index) in row" :key="`tr3-${_index}`">
|
||||
<template v-if="descriptions.border">
|
||||
<template v-if="descriptions.props.border">
|
||||
<el-descriptions-cell :cell="cell" tag="td" type="label" />
|
||||
<el-descriptions-cell :cell="cell" tag="td" type="content" />
|
||||
</template>
|
||||
@@ -29,13 +29,11 @@ import ElDescriptionsCell from './descriptions-cell'
|
||||
import { descriptionsKey } from './token'
|
||||
import { descriptionsRowProps } from './descriptions-row'
|
||||
|
||||
import type { IDescriptionsInject } from './descriptions.type'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElDescriptionsRow',
|
||||
})
|
||||
|
||||
defineProps(descriptionsRowProps)
|
||||
|
||||
const descriptions = inject(descriptionsKey, {} as IDescriptionsInject)
|
||||
const descriptions = inject(descriptionsKey)!
|
||||
</script>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { ColumnAlignment, ComponentSize } from '@element-plus/constants'
|
||||
|
||||
export interface IDescriptionsInject {
|
||||
border: boolean
|
||||
column: number
|
||||
direction: 'horizontal' | 'vertical'
|
||||
size: ComponentSize
|
||||
title: string
|
||||
extra: string
|
||||
labelWidth: string | number
|
||||
}
|
||||
|
||||
export interface IDescriptionsItemInject {
|
||||
label: string
|
||||
span: number
|
||||
rowspan: number
|
||||
width: string | number
|
||||
minWidth: string | number
|
||||
labelWidth: string | number
|
||||
align: ColumnAlignment
|
||||
labelAlign?: ColumnAlignment
|
||||
className: string
|
||||
labelClassName: string
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { InjectionKey } from 'vue'
|
||||
import type { IDescriptionsInject } from './descriptions.type'
|
||||
import type { IDescriptionsInject } from './type'
|
||||
|
||||
export const descriptionsKey: InjectionKey<IDescriptionsInject> =
|
||||
Symbol('elDescriptions')
|
||||
|
||||
36
packages/components/descriptions/src/type.ts
Normal file
36
packages/components/descriptions/src/type.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { ColumnAlignment, ComponentSize } from '@element-plus/constants'
|
||||
import type {
|
||||
DescriptionItemProps,
|
||||
DescriptionItemVNode,
|
||||
} from './description-item'
|
||||
|
||||
export interface IDescriptionsInject {
|
||||
props: {
|
||||
border: boolean
|
||||
column: number
|
||||
direction: 'horizontal' | 'vertical'
|
||||
size: ComponentSize
|
||||
title: string
|
||||
extra: string
|
||||
labelWidth: string | number
|
||||
}
|
||||
addDescriptionItem: (item: DescriptionItemContext) => void
|
||||
removeDescriptionItem: (uid: number) => void
|
||||
}
|
||||
export interface IDescriptionsItemInject {
|
||||
label: string
|
||||
span: number
|
||||
rowspan: number
|
||||
width: string | number
|
||||
minWidth: string | number
|
||||
labelWidth: string | number
|
||||
align: ColumnAlignment
|
||||
labelAlign?: ColumnAlignment
|
||||
className: string
|
||||
labelClassName: string
|
||||
}
|
||||
export interface DescriptionItemContext {
|
||||
uid: number
|
||||
props: DescriptionItemProps
|
||||
vnode: DescriptionItemVNode
|
||||
}
|
||||
@@ -160,9 +160,6 @@ export const flattedChildren = (
|
||||
result.push(child, ...flattedChildren(child.component.subTree))
|
||||
} else if (isVNode(child) && isArray(child.children)) {
|
||||
result.push(...flattedChildren(child.children))
|
||||
} else if (isVNode(child) && child.shapeFlag === 2) {
|
||||
// @ts-ignore
|
||||
result.push(...flattedChildren(child.type()))
|
||||
} else {
|
||||
result.push(child)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user