Merge branches 'next' and 'next' of https://github.com/vueComponent/ant-design-vue into next

This commit is contained in:
tanjinzhou 2020-11-02 17:19:21 +08:00
commit 948727a2ef
12 changed files with 557 additions and 381 deletions

View File

@ -0,0 +1,55 @@
import { VNodeTypes, HTMLAttributes, FunctionalComponent } from 'vue';
function notEmpty(val: any) {
return val !== undefined && val !== null;
}
interface CellProps extends HTMLAttributes {
itemPrefixCls: string;
span: number;
component: string;
bordered?: boolean;
label?: VNodeTypes;
content?: VNodeTypes;
colon?: boolean;
}
const Cell: FunctionalComponent<CellProps> = props => {
const { itemPrefixCls, component, span, bordered, label, content, colon } = props;
const Component = component as any;
if (bordered) {
return (
<Component
class={[
{
[`${itemPrefixCls}-item-label`]: notEmpty(label),
[`${itemPrefixCls}-item-content`]: notEmpty(content),
},
]}
colSpan={span}
>
{notEmpty(label) ? label : content}
</Component>
);
}
return (
<Component class={[`${itemPrefixCls}-item`]} colSpan={span}>
{label && (
<span
class={[
`${itemPrefixCls}-item-label`,
{
[`${itemPrefixCls}-item-no-colon`]: !colon,
},
]}
>
{label}
</span>
)}
{content && <span class={`${itemPrefixCls}-item-content`}>{content}</span>}
</Component>
);
};
export default Cell;

View File

@ -1,82 +0,0 @@
import { SetupContext, VNode } from 'vue';
import { getOptionProps } from '../_util/props-util';
interface ColProps {
child: VNode;
bordered: boolean;
colon: boolean;
type?: 'label' | 'content';
layout?: 'horizontal' | 'vertical';
colKey?: string;
}
const Col = (_props: ColProps, { attrs }: SetupContext) => {
const {
child = {} as VNode,
bordered,
colon,
type,
layout,
colKey: key,
} = (attrs as unknown) as ColProps;
const { prefixCls, span = 1 } = getOptionProps(child);
const { children = {} as any, props = {} } = child;
const label = props.label || (children.label && children.label());
const defaultSlot = children.default && children.default();
const labelProps: any = {
class: [
`${prefixCls}-item-label`,
{
[`${prefixCls}-item-colon`]: colon,
[`${prefixCls}-item-no-label`]: !label,
},
],
key: `${key}-label`,
};
if (layout === 'vertical') {
labelProps.colspan = span * 2 - 1;
}
if (bordered) {
if (type === 'label') {
return <th {...labelProps}>{label}</th>;
}
return (
<td class={`${prefixCls}-item-content`} key={`${key}-content`} colspan={span * 2 - 1}>
{defaultSlot}
</td>
);
}
if (layout === 'vertical') {
if (type === 'content') {
return (
<td colspan={span} class={`${prefixCls}-item`}>
<span class={`${prefixCls}-item-content`} key={`${key}-content`}>
{defaultSlot}
</span>
</td>
);
}
return (
<td colspan={span} class={`${prefixCls}-item`}>
<span
class={[`${prefixCls}-item-label`, { [`${prefixCls}-item-colon`]: colon }]}
key={`${key}-label`}
>
{label}
</span>
</td>
);
}
return (
<td colspan={span} class={`${prefixCls}-item`}>
<span {...labelProps}>{label}</span>
<span class={`${prefixCls}-item-content`} key={`${key}-content`}>
{defaultSlot}
</span>
</td>
);
};
export default Col;

View File

@ -0,0 +1,109 @@
import Cell from './Cell';
import { getOptionProps, getSlot, getClass, getStyle, getComponent } from '../_util/props-util';
import { FunctionalComponent } from 'vue';
interface CellConfig {
component: string | [string, string];
type: string;
showLabel?: boolean;
showContent?: boolean;
}
export interface RowProps {
prefixCls: string;
vertical: boolean;
row: any[];
bordered: boolean;
colon: boolean;
index: number;
}
const Row: FunctionalComponent<RowProps> = props => {
const renderCells = (
items,
{ colon, prefixCls, bordered },
{ component, type, showLabel, showContent }: CellConfig,
) => {
return items.map((item, index) => {
const { prefixCls: itemPrefixCls = prefixCls, span = 1 } = getOptionProps(item);
const label = getComponent(item, 'label');
const children = getSlot(item);
const className = getClass(item);
const style = getStyle(item);
const { key } = item;
if (typeof component === 'string') {
return (
<Cell
key={`${type}-${key || index}`}
class={className}
style={style}
span={span}
colon={colon}
component={component}
itemPrefixCls={itemPrefixCls}
bordered={bordered}
label={showLabel ? label : null}
content={showContent ? children : null}
/>
);
}
return [
<Cell
key={`label-${key || index}`}
class={className}
style={style}
span={1}
colon={colon}
component={component[0]}
itemPrefixCls={itemPrefixCls}
bordered={bordered}
label={label}
/>,
<Cell
key={`content-${key || index}`}
class={className}
style={style}
span={span * 2 - 1}
component={component[1]}
itemPrefixCls={itemPrefixCls}
bordered={bordered}
content={children}
/>,
];
});
};
const { prefixCls, vertical, row, index, bordered } = props;
if (vertical) {
return (
<>
<tr key={`label-${index}`} class={`${prefixCls}-row`}>
{renderCells(row, props, { component: 'th', type: 'label', showLabel: true })}
</tr>
<tr key={`content-${index}`} class={`${prefixCls}-row`}>
{renderCells(row, props, {
component: 'td',
type: 'content',
showContent: true,
})}
</tr>
</>
);
}
return (
<tr key={index} class={`${prefixCls}-row`}>
{renderCells(row, props, {
component: bordered ? ['th', 'td'] : 'td',
type: 'item',
showLabel: true,
showContent: true,
})}
</tr>
);
};
export default Row;

View File

@ -7,7 +7,7 @@ exports[`Descriptions Descriptions support colon 1`] = `
<table> <table>
<tbody> <tbody>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="3" class="ant-descriptions-item"><span class="ant-descriptions-item-label">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td> <td class="ant-descriptions-item" colspan="3"><span class="ant-descriptions-item-label ant-descriptions-item-no-colon">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -22,7 +22,9 @@ exports[`Descriptions Descriptions support style 1`] = `
<table> <table>
<tbody> <tbody>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="3" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon ant-descriptions-item-no-label"><!----></span><span class="ant-descriptions-item-content">Cloud Database</span></td> <td class="ant-descriptions-item" colspan="3">
<!----><span class="ant-descriptions-item-content">Cloud Database</span>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -37,7 +39,7 @@ exports[`Descriptions Descriptions.Item support className 1`] = `
<table> <table>
<tbody> <tbody>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="3" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td> <td class="ant-descriptions-item my-class" colspan="3"><span class="ant-descriptions-item-label">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -52,12 +54,12 @@ exports[`Descriptions column is number 1`] = `
<table> <table>
<tbody> <tbody>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td> <td class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td>
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Billing</span><span class="ant-descriptions-item-content">Prepaid</span></td> <td class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">Billing</span><span class="ant-descriptions-item-content">Prepaid</span></td>
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">time</span><span class="ant-descriptions-item-content">18:00:00</span></td> <td class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">time</span><span class="ant-descriptions-item-content">18:00:00</span></td>
</tr> </tr>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="3" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Amount</span><span class="ant-descriptions-item-content">$80.00</span></td> <td class="ant-descriptions-item" colspan="3"><span class="ant-descriptions-item-label">Amount</span><span class="ant-descriptions-item-content">$80.00</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -72,20 +74,36 @@ exports[`Descriptions vertical layout 1`] = `
<table> <table>
<tbody> <tbody>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Product</span></td> <th class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">Product</span>
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Billing</span></td> <!---->
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">time</span></td> </th>
<th class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">Billing</span>
<!---->
</th>
<th class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">time</span>
<!---->
</th>
</tr> </tr>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-content">Cloud Database</span></td> <td class="ant-descriptions-item" colspan="1">
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-content">Prepaid</span></td> <!----><span class="ant-descriptions-item-content">Cloud Database</span>
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-content">18:00:00</span></td> </td>
<td class="ant-descriptions-item" colspan="1">
<!----><span class="ant-descriptions-item-content">Prepaid</span>
</td>
<td class="ant-descriptions-item" colspan="1">
<!----><span class="ant-descriptions-item-content">18:00:00</span>
</td>
</tr> </tr>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="3" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Amount</span></td> <th class="ant-descriptions-item" colspan="3"><span class="ant-descriptions-item-label">Amount</span>
<!---->
</th>
</tr> </tr>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="3" class="ant-descriptions-item"><span class="ant-descriptions-item-content">$80.00</span></td> <td class="ant-descriptions-item" colspan="3">
<!----><span class="ant-descriptions-item-content">$80.00</span>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -100,12 +118,12 @@ exports[`Descriptions when item is rendered conditionally 1`] = `
<table> <table>
<tbody> <tbody>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td> <td class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">Product</span><span class="ant-descriptions-item-content">Cloud Database</span></td>
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Billing</span><span class="ant-descriptions-item-content">Prepaid</span></td> <td class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">Billing</span><span class="ant-descriptions-item-content">Prepaid</span></td>
<td colspan="1" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">time</span><span class="ant-descriptions-item-content">18:00:00</span></td> <td class="ant-descriptions-item" colspan="1"><span class="ant-descriptions-item-label">time</span><span class="ant-descriptions-item-content">18:00:00</span></td>
</tr> </tr>
<tr class="ant-descriptions-row"> <tr class="ant-descriptions-row">
<td colspan="3" class="ant-descriptions-item"><span class="ant-descriptions-item-label ant-descriptions-item-colon">Amount</span><span class="ant-descriptions-item-content">$80.00</span></td> <td class="ant-descriptions-item" colspan="3"><span class="ant-descriptions-item-label">Amount</span><span class="ant-descriptions-item-content">$80.00</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -33,7 +33,9 @@ describe('Descriptions', () => {
{ sync: false, attachTo: 'body' }, { sync: false, attachTo: 'body' },
); );
await asyncExpect(() => { await asyncExpect(() => {
expect(wrapper.vm.$refs.descriptions.getColumn()).toBe(8); expect(
wrapper.findAll('td').reduce((total, td) => total + parseInt(td.attributes().colspan), 0),
).toBe(8);
}, 100); }, 100);
wrapper.unmount(); wrapper.unmount();
}); });
@ -74,7 +76,7 @@ describe('Descriptions', () => {
}, },
}); });
expect(errorSpy).toHaveBeenCalledWith( expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antdv: Descriptions] Sum of column `span` in a line exceeds `column` of Descriptions.', 'Warning: [antdv: Descriptions] Sum of column `span` in a line not match `column` of Descriptions.',
); );
}); });
@ -120,7 +122,7 @@ describe('Descriptions', () => {
render() { render() {
return ( return (
<Descriptions> <Descriptions>
<Descriptions.Item label="Product" className="my-class"> <Descriptions.Item label="Product" class="my-class">
Cloud Database Cloud Database
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
@ -176,7 +178,7 @@ describe('Descriptions', () => {
); );
await asyncExpect(() => { await asyncExpect(() => {
expect(wrapper.findAll('tr')).toHaveLength(5); expect(wrapper.findAll('tr')).toHaveLength(5);
expect(wrapper.findAll('.ant-descriptions-item-no-label')).toHaveLength(1); expect(wrapper.findAll('.ant-descriptions-item-label')).toHaveLength(4);
}); });
wrapper.unmount(); wrapper.unmount();
@ -198,8 +200,86 @@ describe('Descriptions', () => {
}, },
{ sync: false, attachTo: 'body' }, { sync: false, attachTo: 'body' },
); );
await asyncExpect(() => {}); await asyncExpect(() => {
expect(wrapper.findAll('tr')).toHaveLength(2); expect(wrapper.findAll('tr')).toHaveLength(2);
});
wrapper.unmount(); wrapper.unmount();
}); });
it('columns 5 with customize', () => {
const wrapper = mount({
render() {
return (
<Descriptions layout="vertical" column={4}>
{/* 1 1 1 1 */}
<Descriptions.Item label="bamboo">bamboo</Descriptions.Item>
<Descriptions.Item label="bamboo">bamboo</Descriptions.Item>
<Descriptions.Item label="bamboo">bamboo</Descriptions.Item>
<Descriptions.Item label="bamboo">bamboo</Descriptions.Item>
{/* 2 2 */}
<Descriptions.Item label="bamboo" span={2}>
bamboo
</Descriptions.Item>
<Descriptions.Item label="bamboo" span={2}>
bamboo
</Descriptions.Item>
{/* 3 1 */}
<Descriptions.Item label="bamboo" span={3}>
bamboo
</Descriptions.Item>
<Descriptions.Item label="bamboo">bamboo</Descriptions.Item>
</Descriptions>
);
},
});
function matchSpan(rowIndex, spans) {
const tr = wrapper.findAll('tr')[rowIndex];
const tds = tr.findAll('th');
expect(tds.length).toEqual(spans.length);
tds.forEach((td, index) => {
expect(parseInt(td.attributes().colspan)).toEqual(spans[index]);
});
}
matchSpan(0, [1, 1, 1, 1]);
matchSpan(2, [2, 2]);
matchSpan(4, [3, 1]);
});
it('number value should render correct', () => {
const wrapper = mount({
render() {
return (
<Descriptions bordered>
<Descriptions.Item label={0}>{0}</Descriptions.Item>
</Descriptions>
);
},
});
expect(wrapper.find('th').classes()).toContain('ant-descriptions-item-label');
expect(wrapper.find('td').classes()).toContain('ant-descriptions-item-content');
});
it('Descriptions support extra', async () => {
const wrapper = mount({
render() {
return (
<Descriptions extra="Edit">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
</Descriptions>
);
},
});
await asyncExpect(() => {
expect(wrapper.find('.ant-descriptions-extra').exists()).toBe(true);
wrapper.setProps({ extra: undefined });
});
await asyncExpect(() => {
expect(wrapper.find('.ant-descriptions-extra').exists()).toBe(false);
});
});
}); });

View File

@ -1,12 +1,27 @@
import { inject, cloneVNode, App, defineComponent, PropType, VNode, Plugin } from 'vue'; import {
inject,
ref,
App,
defineComponent,
PropType,
VNode,
HTMLAttributes,
ExtractPropTypes,
onMounted,
onBeforeUnmount,
} from 'vue';
import warning from '../_util/warning'; import warning from '../_util/warning';
import ResponsiveObserve, { Breakpoint, responsiveArray } from '../_util/responsiveObserve'; import ResponsiveObserve, {
Breakpoint,
responsiveArray,
ScreenMap,
} from '../_util/responsiveObserve';
import { defaultConfigProvider } from '../config-provider'; import { defaultConfigProvider } from '../config-provider';
import Col from './Col'; import Row from './Row';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { getOptionProps, getComponent, isValidElement, getSlot } from '../_util/props-util'; import { tuple } from '../_util/type';
import BaseMixin from '../_util/BaseMixin'; import { cloneElement } from '../_util/vnode';
import { tuple, VueNode } from '../_util/type'; import { filterEmpty } from '../_util/props-util';
export const DescriptionsItemProps = { export const DescriptionsItemProps = {
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
@ -14,16 +29,6 @@ export const DescriptionsItemProps = {
span: PropTypes.number, span: PropTypes.number,
}; };
function toArray(value: any) {
let ret = value;
if (value === undefined) {
ret = [];
} else if (!Array.isArray(value)) {
ret = [value];
}
return ret;
}
export const DescriptionsItem = { export const DescriptionsItem = {
name: 'ADescriptionsItem', name: 'ADescriptionsItem',
props: { props: {
@ -36,7 +41,7 @@ export const DescriptionsItem = {
}, },
}; };
const defaultColumnMap = { const DEFAULT_COLUMN_MAP: Record<Breakpoint, number> = {
xxl: 3, xxl: 3,
xl: 3, xl: 3,
lg: 3, lg: 3,
@ -45,229 +50,172 @@ const defaultColumnMap = {
xs: 1, xs: 1,
}; };
export const DescriptionsProps = { function getColumn(column: DescriptionsProps['column'], screens: ScreenMap): number {
if (typeof column === 'number') {
return column;
}
if (typeof column === 'object') {
for (let i = 0; i < responsiveArray.length; i++) {
const breakpoint: Breakpoint = responsiveArray[i];
if (screens[breakpoint] && column[breakpoint] !== undefined) {
return column[breakpoint] || DEFAULT_COLUMN_MAP[breakpoint];
}
}
}
return 3;
}
function getFilledItem(node: VNode, span: number | undefined, rowRestCol: number): VNode {
let clone = node;
if (span === undefined || span > rowRestCol) {
clone = cloneElement(node, {
span: rowRestCol,
});
warning(
span === undefined,
'Descriptions',
'Sum of column `span` in a line not match `column` of Descriptions.',
);
}
return clone;
}
function getRows(children: VNode[], column: number) {
const childNodes = filterEmpty(children);
const rows: VNode[][] = [];
let tmpRow: VNode[] = [];
let rowRestCol = column;
childNodes.forEach((node, index) => {
const span: number | undefined = node.props?.span;
const mergedSpan = span || 1;
// Additional handle last one
if (index === childNodes.length - 1) {
tmpRow.push(getFilledItem(node, span, rowRestCol));
rows.push(tmpRow);
return;
}
if (mergedSpan < rowRestCol) {
rowRestCol -= mergedSpan;
tmpRow.push(node);
} else {
tmpRow.push(getFilledItem(node, mergedSpan, rowRestCol));
rows.push(tmpRow);
rowRestCol = column;
tmpRow = [];
}
});
return rows;
}
const descriptionsProps = {
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
bordered: PropTypes.looseBool, bordered: PropTypes.looseBool,
size: PropTypes.oneOf(tuple('default', 'middle', 'small')).def('default'), size: PropTypes.oneOf(tuple('default', 'middle', 'small')).def('default'),
title: PropTypes.VNodeChild, title: PropTypes.VNodeChild,
extra: PropTypes.VNodeChild,
column: { column: {
type: [Number, Object] as PropType<number | Partial<Record<Breakpoint, number>>>, type: [Number, Object] as PropType<number | Partial<Record<Breakpoint, number>>>,
default: () => defaultColumnMap, default: () => DEFAULT_COLUMN_MAP,
}, },
layout: PropTypes.oneOf(tuple('horizontal', 'vertical')), layout: PropTypes.oneOf(tuple('horizontal', 'vertical')),
colon: PropTypes.looseBool, colon: PropTypes.looseBool,
}; };
/** export type DescriptionsProps = HTMLAttributes &
* Convert children into `column` groups. Partial<ExtractPropTypes<typeof descriptionsProps>>;
* @param children: DescriptionsItem
* @param column: number
*/
const generateChildrenRows = (children: VueNode, column: number) => {
const rows = [];
let columns = null;
let leftSpans: number;
const itemNodes = toArray(children); const Descriptions = defineComponent<DescriptionsProps>({
itemNodes.forEach((node: VNode, index: number) => {
const itemProps = getOptionProps(node);
let itemNode = node;
if (!columns) {
leftSpans = column;
columns = [];
rows.push(columns);
}
// Always set last span to align the end of Descriptions
const lastItem = index === itemNodes.length - 1;
let lastSpanSame = true;
if (lastItem) {
lastSpanSame = !itemProps.span || itemProps.span === leftSpans;
itemNode = cloneVNode(itemNode, {
span: leftSpans,
});
}
// Calculate left fill span
const { span = 1 } = itemProps;
columns.push(itemNode);
leftSpans -= span;
if (leftSpans <= 0) {
columns = null;
warning(
leftSpans === 0 && lastSpanSame,
'Descriptions',
'Sum of column `span` in a line exceeds `column` of Descriptions.',
);
}
});
return rows;
};
const Descriptions = defineComponent({
name: 'ADescriptions', name: 'ADescriptions',
Item: DescriptionsItem, Item: DescriptionsItem,
mixins: [BaseMixin], setup(props, { slots }) {
props: DescriptionsProps, const { getPrefixCls } = inject('configProvider', defaultConfigProvider);
setup() {
return { let token: number;
configProvider: inject('configProvider', defaultConfigProvider),
}; const screens = ref<ScreenMap>({});
},
data() { onMounted(() => {
return { token = ResponsiveObserve.subscribe(screen => {
screens: {}, if (typeof props.column !== 'object') {
token: undefined, return;
};
},
methods: {
getColumn() {
const { column } = this.$props;
if (typeof column === 'object') {
for (let i = 0; i < responsiveArray.length; i++) {
const breakpoint = responsiveArray[i];
if (this.screens[breakpoint] && column[breakpoint] !== undefined) {
return column[breakpoint] || defaultColumnMap[breakpoint];
}
} }
}
// If the configuration is not an object, it is a number, return number
if (typeof column === 'number') {
return column;
}
// If it is an object, but no response is found, this happens only in the test.
// Maybe there are some strange environments
return 3;
},
renderRow(
children: VNode[],
index: number,
{ prefixCls }: { prefixCls: string },
bordered: boolean,
layout: 'horizontal' | 'vertical',
colon: boolean,
) {
const renderCol = (colItem: VNode, type: 'label' | 'content', idx: number) => {
return (
<Col
child={colItem}
bordered={bordered}
colon={colon}
type={type}
key={`${type}-${colItem.key || idx}`}
colKey={`${type}-${colItem.key || idx}`}
layout={layout}
/>
);
};
const cloneChildren = []; screens.value = screen;
const cloneContentChildren = [];
toArray(children).forEach((childrenItem: VNode, idx: number) => {
cloneChildren.push(renderCol(childrenItem, 'label', idx));
if (layout === 'vertical') {
cloneContentChildren.push(renderCol(childrenItem, 'content', idx));
} else if (bordered) {
cloneChildren.push(renderCol(childrenItem, 'content', idx));
}
});
if (layout === 'vertical') {
return [
<tr class={`${prefixCls}-row`} key={`label-${index}`}>
{cloneChildren}
</tr>,
<tr class={`${prefixCls}-row`} key={`content-${index}`}>
{cloneContentChildren}
</tr>,
];
}
return (
<tr class={`${prefixCls}-row`} key={index}>
{cloneChildren}
</tr>
);
},
},
mounted() {
const { column } = this.$props;
this.token = ResponsiveObserve.subscribe(screens => {
if (typeof column !== 'object') {
return;
}
this.setState({
screens,
}); });
}); });
},
beforeUnmount() {
ResponsiveObserve.unsubscribe(this.token);
},
render() {
const {
prefixCls: customizePrefixCls,
size,
bordered = false,
layout = 'horizontal',
colon = true,
} = this.$props;
const title = getComponent(this, 'title');
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('descriptions', customizePrefixCls);
const column = this.getColumn(); onBeforeUnmount(() => {
const children = getSlot(this); ResponsiveObserve.unsubscribe(token);
const cloneChildren = toArray(children) });
.map((child: VNode) => {
if (isValidElement(child)) { return () => {
return cloneVNode(child, { const {
prefixCls: customizePrefixCls,
column,
size,
bordered = false,
layout = 'horizontal',
colon = true,
title = slots.title?.(),
extra = slots.extra?.(),
} = props;
const prefixCls = getPrefixCls('descriptions', customizePrefixCls);
const mergeColumn = getColumn(column, screens.value);
const children = slots.default?.();
const rows = getRows(children, mergeColumn);
return (
<div
class={[
prefixCls, prefixCls,
}); {
} [`${prefixCls}-${size}`]: size !== 'default',
return null; [`${prefixCls}-bordered`]: !!bordered,
}) },
.filter(node => node); ]}
>
const childrenArray = generateChildrenRows(cloneChildren, column); {(title || extra) && (
return ( <div class={`${prefixCls}-header`}>
<div <div class={`${prefixCls}-title`}>{title}</div>
class={[ <div class={`${prefixCls}-extra`}>{extra}</div>
prefixCls, </div>
{ )}
[`${prefixCls}-${size}`]: size !== 'default', <div class={`${prefixCls}-view`}>
[`${prefixCls}-bordered`]: !!bordered, <table>
}, <tbody>
]} {rows.map((row, index) => (
> <Row
{title && <div class={`${prefixCls}-title`}>{title}</div>} key={index}
<div class={`${prefixCls}-view`}> index={index}
<table> colon={colon}
<tbody> prefixCls={prefixCls}
{childrenArray.map((child, index) => vertical={layout === 'vertical'}
this.renderRow( bordered={bordered}
child, row={row}
index, />
{ ))}
prefixCls, </tbody>
}, </table>
bordered, </div>
layout,
colon,
),
)}
</tbody>
</table>
</div> </div>
</div> );
); };
}, },
}); });
Descriptions.props = descriptionsProps;
Descriptions.install = function(app: App) { Descriptions.install = function(app: App) {
app.component(Descriptions.name, Descriptions); app.component(Descriptions.name, Descriptions);
app.component(Descriptions.Item.name, Descriptions.Item); app.component(Descriptions.Item.name, Descriptions.Item);

View File

@ -3,17 +3,28 @@
@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions'; @descriptions-prefix-cls: ~'@{ant-prefix}-descriptions';
@descriptions-default-padding: 16px 24px;
@descriptions-middle-padding: 12px 24px;
@descriptions-small-padding: 8px 16px;
.@{descriptions-prefix-cls} { .@{descriptions-prefix-cls} {
&-header {
display: flex;
align-items: center;
margin-bottom: @descriptions-title-margin-bottom;
}
&-title { &-title {
margin-bottom: 20px; flex: auto;
overflow: hidden;
color: @heading-color; color: @heading-color;
font-weight: bold; font-weight: bold;
font-size: @font-size-lg; font-size: @font-size-lg;
line-height: @line-height-base; line-height: @line-height-base;
white-space: nowrap;
text-overflow: ellipsis;
}
&-extra {
margin-left: auto;
color: @descriptions-extra-color;
font-size: @font-size-base;
} }
&-view { &-view {
@ -29,7 +40,7 @@
&-row { &-row {
> th, > th,
> td { > td {
padding-bottom: 16px; padding-bottom: @descriptions-item-padding-bottom;
} }
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
@ -41,18 +52,24 @@
font-weight: normal; font-weight: normal;
font-size: @font-size-base; font-size: @font-size-base;
line-height: @line-height-base; line-height: @line-height-base;
text-align: start;
&::after { &::after {
& when (@descriptions-item-trailing-colon=true) {
content: ':';
}
& when not (@descriptions-item-trailing-colon=true) {
content: ' ';
}
position: relative; position: relative;
top: -0.5px; top: -0.5px;
margin: 0 8px 0 2px; margin: 0 @descriptions-item-label-colon-margin-right 0
content: ' '; @descriptions-item-label-colon-margin-left;
} }
}
&-item-colon { &.@{descriptions-prefix-cls}-item-no-colon::after {
&::after { content: ' ';
content: ':';
} }
} }
@ -65,15 +82,23 @@
&-item-content { &-item-content {
display: table-cell; display: table-cell;
flex: 1;
color: @text-color; color: @text-color;
font-size: @font-size-base; font-size: @font-size-base;
line-height: @line-height-base; line-height: @line-height-base;
overflow-wrap: break-word;
} }
&-item { &-item {
padding-bottom: 0; padding-bottom: 0;
vertical-align: top;
> span { > span {
display: inline-block; display: inline-flex;
align-items: baseline;
}
&-container {
display: flex;
} }
} }
@ -90,7 +115,7 @@
.@{descriptions-prefix-cls}-row { .@{descriptions-prefix-cls}-row {
> th, > th,
> td { > td {
padding-bottom: 8px; padding-bottom: @padding-xs;
} }
} }
} }

View File

@ -192,6 +192,15 @@
// Descriptions // Descriptions
@descriptions-bg: #fafafa; @descriptions-bg: #fafafa;
@descriptions-title-margin-bottom: 20px;
@descriptions-default-padding: @padding-md @padding-lg;
@descriptions-middle-padding: @padding-sm @padding-lg;
@descriptions-small-padding: @padding-xs @padding-md;
@descriptions-item-padding-bottom: @padding-md;
@descriptions-item-trailing-colon: true;
@descriptions-item-label-colon-margin-right: 8px;
@descriptions-item-label-colon-margin-left: 2px;
@descriptions-extra-color: @text-color;
// Dropdown // Dropdown
@dropdown-selected-color: @primary-color; @dropdown-selected-color: @primary-color;

View File

@ -1,9 +1,9 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { ColumnProps } from './interface'; import { columnProps } from './interface';
export default defineComponent({ export default defineComponent({
name: 'ATableColumn', name: 'ATableColumn',
props: ColumnProps, props: columnProps,
render() { render() {
return null; return null;
}, },

View File

@ -17,11 +17,11 @@ import initDefaultProps from '../_util/props-util/initDefaultProps';
import BaseMixin from '../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import { defaultConfigProvider } from '../config-provider'; import { defaultConfigProvider } from '../config-provider';
import { import {
TableProps, tableProps,
TableComponents, TableComponents,
TableState, TableState,
ITableProps, TableProps,
IColumnProps, ColumnProps,
TableStateFilters, TableStateFilters,
} from './interface'; } from './interface';
import Pagination from '../pagination'; import Pagination from '../pagination';
@ -38,15 +38,15 @@ function stopPropagation(e) {
e.stopPropagation(); e.stopPropagation();
} }
function getRowSelection(props: ITableProps) { function getRowSelection(props: TableProps) {
return props.rowSelection || {}; return props.rowSelection || {};
} }
function getColumnKey(column: IColumnProps, index?: number) { function getColumnKey(column: ColumnProps, index?: number) {
return column.key || column.dataIndex || index; return column.key || column.dataIndex || index;
} }
function isSameColumn(a: IColumnProps, b: IColumnProps): boolean { function isSameColumn(a: ColumnProps, b: ColumnProps): boolean {
if (a && b && a.key && a.key === b.key) { if (a && b && a.key && a.key === b.key) {
return true; return true;
} }
@ -94,16 +94,16 @@ function isTheSameComponents(components1: TableComponents = {}, components2: Tab
); );
} }
function getFilteredValueColumns(state: TableState, columns?: IColumnProps) { function getFilteredValueColumns(state: TableState, columns?: ColumnProps) {
return flatFilter( return flatFilter(
columns || (state || {}).columns || [], columns || (state || {}).columns || [],
(column: IColumnProps) => typeof column.filteredValue !== 'undefined', (column: ColumnProps) => typeof column.filteredValue !== 'undefined',
); );
} }
function getFiltersFromColumns(state: TableState, columns: IColumnProps) { function getFiltersFromColumns(state: TableState, columns: ColumnProps) {
const filters = {}; const filters = {};
getFilteredValueColumns(state, columns).forEach((col: IColumnProps) => { getFilteredValueColumns(state, columns).forEach((col: ColumnProps) => {
const colKey = getColumnKey(col); const colKey = getColumnKey(col);
filters[colKey] = col.filteredValue; filters[colKey] = col.filteredValue;
}); });
@ -117,26 +117,28 @@ function isFiltersChanged(state: TableState, filters: TableStateFilters[]) {
return Object.keys(filters).some(columnKey => filters[columnKey] !== state.filters[columnKey]); return Object.keys(filters).some(columnKey => filters[columnKey] !== state.filters[columnKey]);
} }
export const defaultTableProps = initDefaultProps(tableProps, {
dataSource: [],
useFixedHeader: false,
// rowSelection: null,
size: 'default',
loading: false,
bordered: false,
indentSize: 20,
locale: {},
rowKey: 'key',
showHeader: true,
sortDirections: ['ascend', 'descend'],
childrenColumnName: 'children',
});
export default defineComponent({ export default defineComponent({
name: 'Table', name: 'Table',
mixins: [BaseMixin], mixins: [BaseMixin],
inheritAttrs: false, inheritAttrs: false,
Column, Column,
ColumnGroup, ColumnGroup,
props: initDefaultProps(TableProps, { props: defaultTableProps,
dataSource: [],
useFixedHeader: false,
// rowSelection: null,
size: 'default',
loading: false,
bordered: false,
indentSize: 20,
locale: {},
rowKey: 'key',
showHeader: true,
sortDirections: ['ascend', 'descend'],
childrenColumnName: 'children',
}),
setup() { setup() {
return { return {

View File

@ -1,5 +1,5 @@
import { App, defineComponent } from 'vue'; import { App, defineComponent } from 'vue';
import T from './Table'; import T, { defaultTableProps } from './Table';
import Column from './Column'; import Column from './Column';
import ColumnGroup from './ColumnGroup'; import ColumnGroup from './ColumnGroup';
import {} from './interface'; import {} from './interface';
@ -9,7 +9,7 @@ const Table = defineComponent({
name: 'ATable', name: 'ATable',
Column: T.Column, Column: T.Column,
ColumnGroup: T.ColumnGroup, ColumnGroup: T.ColumnGroup,
props: T.props, props: defaultTableProps,
inheritAttrs: false, inheritAttrs: false,
methods: { methods: {
normalize(elements = []) { normalize(elements = []) {

View File

@ -1,21 +1,21 @@
import { ExtractPropTypes, PropType } from 'vue';
import PropTypes, { withUndefined } from '../_util/vue-types'; import PropTypes, { withUndefined } from '../_util/vue-types';
import { PaginationProps as getPaginationProps } from '../pagination'; import { PaginationProps as getPaginationProps } from '../pagination';
import { SpinProps as getSpinProps } from '../spin'; import { SpinProps as getSpinProps } from '../spin';
import { Store } from './createStore'; import { Store } from './createStore';
import { tuple } from '../_util/type'; import { tuple } from '../_util/type';
import { ExtractPropTypes } from 'vue';
const PaginationProps = getPaginationProps(); const PaginationProps = getPaginationProps();
const SpinProps = getSpinProps(); const SpinProps = getSpinProps();
// export type CompareFn<T> = ((a: T, b: T) => number); export type CompareFn<T> = (a: T, b: T, sortOrder?: SortOrder) => number;
export const ColumnFilterItem = PropTypes.shape({ export const ColumnFilterItem = PropTypes.shape({
text: PropTypes.string, text: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
children: PropTypes.array, children: PropTypes.array,
}).loose; }).loose;
export const ColumnProps = { export const columnProps = {
title: PropTypes.VNodeChild, title: PropTypes.VNodeChild,
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
dataIndex: PropTypes.string, dataIndex: PropTypes.string,
@ -52,7 +52,7 @@ export const ColumnProps = {
// onHeaderCell?: (props: ColumnProps<T>) => any; // onHeaderCell?: (props: ColumnProps<T>) => any;
}; };
export type IColumnProps = Partial<ExtractPropTypes<typeof ColumnProps>>; export type ColumnProps = Partial<ExtractPropTypes<typeof columnProps>>;
export interface TableComponents { export interface TableComponents {
table?: any; table?: any;
@ -80,10 +80,10 @@ export const TableLocale = PropTypes.shape({
collapse: PropTypes.string, collapse: PropTypes.string,
}).loose; }).loose;
export const RowSelectionType = PropTypes.oneOf(['checkbox', 'radio']); export const RowSelectionType = PropTypes.oneOf(tuple('checkbox', 'radio'));
// export type SelectionSelectFn<T> = (record: T, selected: boolean, selectedRows: Object[]) => any; // export type SelectionSelectFn<T> = (record: T, selected: boolean, selectedRows: Object[]) => any;
export const TableRowSelection = { export const tableRowSelection = {
type: RowSelectionType, type: RowSelectionType,
selectedRowKeys: PropTypes.array, selectedRowKeys: PropTypes.array,
// onChange?: (selectedRowKeys: string[] | number[], selectedRows: Object[]) => any; // onChange?: (selectedRowKeys: string[] | number[], selectedRows: Object[]) => any;
@ -101,10 +101,12 @@ export const TableRowSelection = {
columnTitle: PropTypes.any, columnTitle: PropTypes.any,
}; };
export const TableProps = { export type SortOrder = 'descend' | 'ascend';
export const tableProps = {
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
dropdownPrefixCls: PropTypes.string, dropdownPrefixCls: PropTypes.string,
rowSelection: PropTypes.oneOfType([PropTypes.shape(TableRowSelection).loose, Object]), rowSelection: PropTypes.oneOfType([PropTypes.shape(tableRowSelection).loose, Object]),
pagination: withUndefined( pagination: withUndefined(
PropTypes.oneOfType([ PropTypes.oneOfType([
PropTypes.shape({ PropTypes.shape({
@ -117,7 +119,9 @@ export const TableProps = {
size: PropTypes.oneOf(tuple('default', 'middle', 'small', 'large')), size: PropTypes.oneOf(tuple('default', 'middle', 'small', 'large')),
dataSource: PropTypes.array, dataSource: PropTypes.array,
components: PropTypes.object, components: PropTypes.object,
columns: PropTypes.array, columns: {
type: Array as PropType<ColumnProps>,
},
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
rowClassName: PropTypes.func, rowClassName: PropTypes.func,
expandedRowRender: PropTypes.any, expandedRowRender: PropTypes.any,
@ -137,10 +141,18 @@ export const TableProps = {
showHeader: PropTypes.looseBool, showHeader: PropTypes.looseBool,
footer: PropTypes.func, footer: PropTypes.func,
title: PropTypes.func, title: PropTypes.func,
scroll: PropTypes.object, scroll: {
type: Object as PropType<{
x?: boolean | number | string;
y?: boolean | number | string;
scrollToFirstRowOnChange?: boolean;
}>,
},
childrenColumnName: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), childrenColumnName: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
bodyStyle: PropTypes.any, bodyStyle: PropTypes.style,
sortDirections: PropTypes.array, sortDirections: {
type: Array as PropType<SortOrder[]>,
},
tableLayout: PropTypes.string, tableLayout: PropTypes.string,
getPopupContainer: PropTypes.func, getPopupContainer: PropTypes.func,
expandIcon: PropTypes.func, expandIcon: PropTypes.func,
@ -153,9 +165,9 @@ export const TableProps = {
// children?: React.ReactNode; // children?: React.ReactNode;
}; };
export type ITableRowSelection = Partial<ExtractPropTypes<typeof TableRowSelection>>; export type TableRowSelection = Partial<ExtractPropTypes<typeof tableRowSelection>>;
export type ITableProps = Partial<ExtractPropTypes<typeof TableProps>>; export type TableProps = Partial<ExtractPropTypes<typeof tableProps>>;
export interface TableStateFilters { export interface TableStateFilters {
[key: string]: string[]; [key: string]: string[];
@ -164,9 +176,9 @@ export interface TableStateFilters {
export interface TableState { export interface TableState {
pagination?: Partial<ExtractPropTypes<typeof PaginationProps>>; pagination?: Partial<ExtractPropTypes<typeof PaginationProps>>;
filters?: TableStateFilters; filters?: TableStateFilters;
sortColumn?: Partial<ExtractPropTypes<typeof ColumnProps>> | null; sortColumn?: ColumnProps | null;
sortOrder?: string; sortOrder?: string;
columns?: IColumnProps[]; columns?: ColumnProps[];
} }
// export type SelectionItemSelectFn = (key: string[]) => any; // export type SelectionItemSelectFn = (key: string[]) => any;