refactor: table
parent
c9db47533f
commit
60ea53ce91
|
|
@ -1,6 +1,12 @@
|
||||||
let cached;
|
/* eslint-disable no-param-reassign */
|
||||||
|
|
||||||
|
let cached: number;
|
||||||
|
|
||||||
|
export default function getScrollBarSize(fresh?: boolean) {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
export default function getScrollBarSize(fresh) {
|
|
||||||
if (fresh || cached === undefined) {
|
if (fresh || cached === undefined) {
|
||||||
const inner = document.createElement('div');
|
const inner = document.createElement('div');
|
||||||
inner.style.width = '100%';
|
inner.style.width = '100%';
|
||||||
|
|
@ -10,8 +16,8 @@ export default function getScrollBarSize(fresh) {
|
||||||
const outerStyle = outer.style;
|
const outerStyle = outer.style;
|
||||||
|
|
||||||
outerStyle.position = 'absolute';
|
outerStyle.position = 'absolute';
|
||||||
outerStyle.top = 0;
|
outerStyle.top = '0';
|
||||||
outerStyle.left = 0;
|
outerStyle.left = '0';
|
||||||
outerStyle.pointerEvents = 'none';
|
outerStyle.pointerEvents = 'none';
|
||||||
outerStyle.visibility = 'hidden';
|
outerStyle.visibility = 'hidden';
|
||||||
outerStyle.width = '200px';
|
outerStyle.width = '200px';
|
||||||
|
|
@ -36,3 +42,21 @@ export default function getScrollBarSize(fresh) {
|
||||||
}
|
}
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureSize(str: string) {
|
||||||
|
const match = str.match(/^(.*)px$/);
|
||||||
|
const value = Number(match?.[1]);
|
||||||
|
return Number.isNaN(value) ? getScrollBarSize() : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTargetScrollBarSize(target: HTMLElement) {
|
||||||
|
if (typeof document === 'undefined' || !target || !(target instanceof Element)) {
|
||||||
|
return { width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { width, height } = getComputedStyle(target, '::-webkit-scrollbar');
|
||||||
|
return {
|
||||||
|
width: ensureSize(width),
|
||||||
|
height: ensureSize(height),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ const parseStyleText = (cssText = '', camel) => {
|
||||||
const res = {};
|
const res = {};
|
||||||
const listDelimiter = /;(?![^(]*\))/g;
|
const listDelimiter = /;(?![^(]*\))/g;
|
||||||
const propertyDelimiter = /:(.+)/;
|
const propertyDelimiter = /:(.+)/;
|
||||||
|
if (typeof cssText === 'object') return cssText;
|
||||||
cssText.split(listDelimiter).forEach(function (item) {
|
cssText.split(listDelimiter).forEach(function (item) {
|
||||||
if (item) {
|
if (item) {
|
||||||
const tmp = item.split(propertyDelimiter);
|
const tmp = item.split(propertyDelimiter);
|
||||||
|
|
|
||||||
|
|
@ -62,4 +62,9 @@ export function getDataAndAriaProps(props) {
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toPx(val) {
|
||||||
|
if (typeof val === 'number') return `${val}px`;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
export { isOn, cacheStringFunction, camelize, hyphenate, capitalize, resolvePropValue };
|
export { isOn, cacheStringFunction, camelize, hyphenate, capitalize, resolvePropValue };
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,227 @@
|
||||||
|
import Cell from '../Cell';
|
||||||
|
import { getColumnsKey } from '../utils/valueUtil';
|
||||||
|
import type { CustomizeComponent, GetComponentProps, Key, GetRowKey } from '../interface';
|
||||||
|
import ExpandedRow from './ExpandedRow';
|
||||||
|
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
||||||
|
import { useInjectTable } from '../context/TableContext';
|
||||||
|
import { useInjectBody } from '../context/BodyContext';
|
||||||
|
import classNames from 'ant-design-vue/es/_util/classNames';
|
||||||
|
import { parseStyleText } from 'ant-design-vue/es/_util/props-util';
|
||||||
|
|
||||||
|
export interface BodyRowProps<RecordType> {
|
||||||
|
record: RecordType;
|
||||||
|
index: number;
|
||||||
|
recordKey: Key;
|
||||||
|
expandedKeys: Set<Key>;
|
||||||
|
rowComponent: CustomizeComponent;
|
||||||
|
cellComponent: CustomizeComponent;
|
||||||
|
customRow: GetComponentProps<RecordType>;
|
||||||
|
rowExpandable: (record: RecordType) => boolean;
|
||||||
|
indent?: number;
|
||||||
|
rowKey: Key;
|
||||||
|
getRowKey: GetRowKey<RecordType>;
|
||||||
|
childrenColumnName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent<BodyRowProps<unknown>>({
|
||||||
|
props: [
|
||||||
|
'record',
|
||||||
|
'index',
|
||||||
|
'recordKey',
|
||||||
|
'expandedKeys',
|
||||||
|
'rowComponent',
|
||||||
|
'cellComponent',
|
||||||
|
'customRow',
|
||||||
|
'rowExpandable',
|
||||||
|
'indent',
|
||||||
|
'rowKey',
|
||||||
|
'getRowKey',
|
||||||
|
'childrenColumnName',
|
||||||
|
] as any,
|
||||||
|
name: 'BodyRow',
|
||||||
|
inheritAttrs: false,
|
||||||
|
setup(props, { attrs }) {
|
||||||
|
const tableContext = useInjectTable();
|
||||||
|
const bodyContext = useInjectBody();
|
||||||
|
const expandRended = ref(false);
|
||||||
|
|
||||||
|
const expanded = computed(() => props.expandedKeys && props.expandedKeys.has(props.recordKey));
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (expanded.value) {
|
||||||
|
expandRended.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rowSupportExpand = computed(
|
||||||
|
() =>
|
||||||
|
bodyContext.expandableType === 'row' &&
|
||||||
|
(!props.rowExpandable || props.rowExpandable(props.record)),
|
||||||
|
);
|
||||||
|
// Only when row is not expandable and `children` exist in record
|
||||||
|
const nestExpandable = computed(() => bodyContext.expandableType === 'nest');
|
||||||
|
const hasNestChildren = computed(
|
||||||
|
() => props.childrenColumnName && props.record && props.record[props.childrenColumnName],
|
||||||
|
);
|
||||||
|
const mergedExpandable = computed(() => rowSupportExpand.value || nestExpandable.value);
|
||||||
|
|
||||||
|
const onInternalTriggerExpand = (record, event) => {
|
||||||
|
bodyContext.onTriggerExpand(record, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================== onRow ===========================
|
||||||
|
let additionalProps = computed<Record<string, any>>(
|
||||||
|
() => props.customRow?.(props.record, props.index) || {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClick = (event, ...args) => {
|
||||||
|
if (bodyContext.expandRowByClick && mergedExpandable.value) {
|
||||||
|
onInternalTriggerExpand(props.record, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalProps.value?.onClick) {
|
||||||
|
additionalProps.value.onClick(event, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let computeRowClassName = computed(() => {
|
||||||
|
const { record, index, indent } = props;
|
||||||
|
const { rowClassName } = bodyContext;
|
||||||
|
if (typeof rowClassName === 'string') {
|
||||||
|
return rowClassName;
|
||||||
|
} else if (typeof rowClassName === 'function') {
|
||||||
|
return rowClassName(record, index, indent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnsKey = computed(() => getColumnsKey(bodyContext.flattenColumns));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const { class: className, style } = attrs as any;
|
||||||
|
const {
|
||||||
|
record,
|
||||||
|
index,
|
||||||
|
rowKey,
|
||||||
|
indent = 0,
|
||||||
|
rowComponent: RowComponent,
|
||||||
|
cellComponent,
|
||||||
|
} = props;
|
||||||
|
const { prefixCls, fixedInfoList } = tableContext;
|
||||||
|
const {
|
||||||
|
fixHeader,
|
||||||
|
fixColumn,
|
||||||
|
horizonScroll,
|
||||||
|
componentWidth,
|
||||||
|
flattenColumns,
|
||||||
|
expandedRowClassName,
|
||||||
|
indentSize,
|
||||||
|
expandIcon,
|
||||||
|
expandedRowRender,
|
||||||
|
expandIconColumnIndex,
|
||||||
|
} = bodyContext;
|
||||||
|
const baseRowNode = (
|
||||||
|
<RowComponent
|
||||||
|
{...additionalProps.value}
|
||||||
|
data-row-key={rowKey}
|
||||||
|
class={classNames(
|
||||||
|
className,
|
||||||
|
`${prefixCls}-row`,
|
||||||
|
`${prefixCls}-row-level-${indent}`,
|
||||||
|
computeRowClassName.value,
|
||||||
|
additionalProps.value.class,
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
...parseStyleText(additionalProps.value.style),
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{flattenColumns.map((column, colIndex) => {
|
||||||
|
const { customRender, dataIndex, className: columnClassName } = column;
|
||||||
|
|
||||||
|
const key = columnsKey[colIndex];
|
||||||
|
const fixedInfo = fixedInfoList[colIndex];
|
||||||
|
|
||||||
|
// ============= Used for nest expandable =============
|
||||||
|
let appendCellNode;
|
||||||
|
if (colIndex === (expandIconColumnIndex || 0) && nestExpandable.value) {
|
||||||
|
appendCellNode = (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
style={{ paddingLeft: `${indentSize * indent}px` }}
|
||||||
|
class={`${prefixCls}-row-indent indent-level-${indent}`}
|
||||||
|
/>
|
||||||
|
{expandIcon({
|
||||||
|
prefixCls,
|
||||||
|
expanded: expanded.value,
|
||||||
|
expandable: hasNestChildren.value,
|
||||||
|
record,
|
||||||
|
onExpand: onInternalTriggerExpand,
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let additionalCellProps;
|
||||||
|
if (column.customCell) {
|
||||||
|
additionalCellProps = column.customCell(record, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Cell
|
||||||
|
class={columnClassName}
|
||||||
|
ellipsis={column.ellipsis}
|
||||||
|
align={column.align}
|
||||||
|
component={cellComponent}
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
key={key}
|
||||||
|
record={record}
|
||||||
|
index={index}
|
||||||
|
dataIndex={dataIndex}
|
||||||
|
customRender={customRender}
|
||||||
|
{...fixedInfo}
|
||||||
|
appendNode={appendCellNode}
|
||||||
|
additionalProps={additionalCellProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</RowComponent>
|
||||||
|
);
|
||||||
|
|
||||||
|
// ======================== Expand Row =========================
|
||||||
|
let expandRowNode;
|
||||||
|
if (rowSupportExpand.value && (expandRended.value || expanded.value)) {
|
||||||
|
const expandContent = expandedRowRender(record, index, indent + 1, expanded.value);
|
||||||
|
const computedExpandedRowClassName =
|
||||||
|
expandedRowClassName && expandedRowClassName(record, index, indent);
|
||||||
|
expandRowNode = (
|
||||||
|
<ExpandedRow
|
||||||
|
expanded={expanded.value}
|
||||||
|
class={classNames(
|
||||||
|
`${prefixCls}-expanded-row`,
|
||||||
|
`${prefixCls}-expanded-row-level-${indent + 1}`,
|
||||||
|
computedExpandedRowClassName,
|
||||||
|
)}
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
fixHeader={fixHeader}
|
||||||
|
fixColumn={fixColumn}
|
||||||
|
horizonScroll={horizonScroll}
|
||||||
|
component={RowComponent}
|
||||||
|
componentWidth={componentWidth}
|
||||||
|
cellComponent={cellComponent}
|
||||||
|
colSpan={flattenColumns.length}
|
||||||
|
>
|
||||||
|
{expandContent}
|
||||||
|
</ExpandedRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{baseRowNode}
|
||||||
|
{expandRowNode}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { CustomizeComponent } from '../interface';
|
||||||
|
import Cell from '../Cell';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useInjectTable } from '../context/TableContext';
|
||||||
|
|
||||||
|
export interface ExpandedRowProps {
|
||||||
|
prefixCls: string;
|
||||||
|
component: CustomizeComponent;
|
||||||
|
cellComponent: CustomizeComponent;
|
||||||
|
fixHeader: boolean;
|
||||||
|
fixColumn: boolean;
|
||||||
|
horizonScroll: boolean;
|
||||||
|
componentWidth: number;
|
||||||
|
expanded: boolean;
|
||||||
|
colSpan: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent<ExpandedRowProps>({
|
||||||
|
name: 'ExpandedRow',
|
||||||
|
props: [
|
||||||
|
'prefixCls',
|
||||||
|
'component',
|
||||||
|
'cellComponent',
|
||||||
|
'fixHeader',
|
||||||
|
'fixColumn',
|
||||||
|
'horizonScroll',
|
||||||
|
'componentWidth',
|
||||||
|
'expanded',
|
||||||
|
'colSpan',
|
||||||
|
] as any,
|
||||||
|
inheritAttrs: false,
|
||||||
|
setup(props, { slots, attrs }) {
|
||||||
|
const tableContext = useInjectTable();
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
prefixCls,
|
||||||
|
component: Component,
|
||||||
|
cellComponent,
|
||||||
|
fixHeader,
|
||||||
|
fixColumn,
|
||||||
|
expanded,
|
||||||
|
componentWidth,
|
||||||
|
colSpan,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let contentNode: any = slots.default?.();
|
||||||
|
|
||||||
|
if (fixColumn) {
|
||||||
|
contentNode = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: componentWidth - (fixHeader ? tableContext.scrollbarSize : 0),
|
||||||
|
position: 'sticky',
|
||||||
|
left: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
class={`${prefixCls}-expanded-row-fixed`}
|
||||||
|
>
|
||||||
|
{contentNode}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
class={attrs.class}
|
||||||
|
style={{
|
||||||
|
display: expanded ? null : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Cell component={cellComponent} prefixCls={prefixCls} colSpan={colSpan}>
|
||||||
|
{contentNode}
|
||||||
|
</Cell>
|
||||||
|
</Component>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import VCResizeObserver from 'ant-design-vue/es/vc-resize-observer';
|
||||||
|
import { Key } from '../interface';
|
||||||
|
|
||||||
|
export interface MeasureCellProps {
|
||||||
|
columnKey: Key;
|
||||||
|
onColumnResize: (key: Key, width: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MeasureCell({ columnKey }: MeasureCellProps, { emit }) {
|
||||||
|
return (
|
||||||
|
<VCResizeObserver
|
||||||
|
onResize={({ offsetWidth }) => {
|
||||||
|
emit('columnResize', columnKey, offsetWidth);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td style={{ padding: 0, border: 0, height: 0 }}>
|
||||||
|
<div style={{ height: 0, overflow: 'hidden' }}> </div>
|
||||||
|
</td>
|
||||||
|
</VCResizeObserver>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
import type { GetRowKey, Key, GetComponentProps } from '../interface';
|
||||||
|
import ExpandedRow from './ExpandedRow';
|
||||||
|
import { getColumnsKey } from '../utils/valueUtil';
|
||||||
|
import MeasureCell from './MeasureCell';
|
||||||
|
import BodyRow from './BodyRow';
|
||||||
|
import useFlattenRecords from '../hooks/useFlattenRecords';
|
||||||
|
import { defineComponent, toRef } from 'vue';
|
||||||
|
import { useInjectResize } from '../context/ResizeContext';
|
||||||
|
import { useInjectTable } from '../context/TableContext';
|
||||||
|
import { useInjectBody } from '../context/BodyContext';
|
||||||
|
|
||||||
|
export interface BodyProps<RecordType> {
|
||||||
|
data: RecordType[];
|
||||||
|
getRowKey: GetRowKey<RecordType>;
|
||||||
|
measureColumnWidth: boolean;
|
||||||
|
expandedKeys: Set<Key>;
|
||||||
|
customRow: GetComponentProps<RecordType>;
|
||||||
|
rowExpandable: (record: RecordType) => boolean;
|
||||||
|
// emptyNode: React.ReactNode;
|
||||||
|
childrenColumnName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent<BodyProps<any>>({
|
||||||
|
name: 'Body',
|
||||||
|
props: [
|
||||||
|
'data',
|
||||||
|
'getRowKey',
|
||||||
|
'measureColumnWidth',
|
||||||
|
'expandedKeys',
|
||||||
|
'customRow',
|
||||||
|
'rowExpandable',
|
||||||
|
'childrenColumnName',
|
||||||
|
] as any,
|
||||||
|
slots: ['emptyNode'],
|
||||||
|
setup(props, { slots }) {
|
||||||
|
const resizeContext = useInjectResize();
|
||||||
|
const tableContext = useInjectTable();
|
||||||
|
const bodyContext = useInjectBody();
|
||||||
|
|
||||||
|
const flattenData = useFlattenRecords(
|
||||||
|
toRef(props, 'data'),
|
||||||
|
toRef(props, 'childrenColumnName'),
|
||||||
|
toRef(props, 'expandedKeys'),
|
||||||
|
toRef(props, 'getRowKey'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
getRowKey,
|
||||||
|
measureColumnWidth,
|
||||||
|
expandedKeys,
|
||||||
|
customRow,
|
||||||
|
rowExpandable,
|
||||||
|
childrenColumnName,
|
||||||
|
} = props;
|
||||||
|
const { onColumnResize } = resizeContext;
|
||||||
|
const { prefixCls, getComponent } = tableContext;
|
||||||
|
const { fixHeader, horizonScroll, flattenColumns, componentWidth } = bodyContext;
|
||||||
|
const WrapperComponent = getComponent(['body', 'wrapper'], 'tbody');
|
||||||
|
const trComponent = getComponent(['body', 'row'], 'tr');
|
||||||
|
const tdComponent = getComponent(['body', 'cell'], 'td');
|
||||||
|
|
||||||
|
let rows;
|
||||||
|
if (data.length) {
|
||||||
|
rows = flattenData.value.map((item, index) => {
|
||||||
|
const { record, indent } = item;
|
||||||
|
|
||||||
|
const key = getRowKey(record, index);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BodyRow
|
||||||
|
key={key}
|
||||||
|
rowKey={key}
|
||||||
|
record={record}
|
||||||
|
recordKey={key}
|
||||||
|
index={index}
|
||||||
|
rowComponent={trComponent}
|
||||||
|
cellComponent={tdComponent}
|
||||||
|
expandedKeys={expandedKeys}
|
||||||
|
customRow={customRow}
|
||||||
|
getRowKey={getRowKey}
|
||||||
|
rowExpandable={rowExpandable}
|
||||||
|
childrenColumnName={childrenColumnName}
|
||||||
|
indent={indent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rows = (
|
||||||
|
<ExpandedRow
|
||||||
|
expanded
|
||||||
|
class={`${prefixCls}-placeholder`}
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
fixHeader={fixHeader}
|
||||||
|
fixColumn={horizonScroll}
|
||||||
|
horizonScroll={horizonScroll}
|
||||||
|
component={trComponent}
|
||||||
|
componentWidth={componentWidth}
|
||||||
|
cellComponent={tdComponent}
|
||||||
|
colSpan={flattenColumns.length}
|
||||||
|
>
|
||||||
|
{slots.emptyNode?.()}
|
||||||
|
</ExpandedRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnsKey = getColumnsKey(flattenColumns);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WrapperComponent className={`${prefixCls}-tbody`}>
|
||||||
|
{/* Measure body column width with additional hidden col */}
|
||||||
|
{measureColumnWidth && (
|
||||||
|
<tr
|
||||||
|
aria-hidden="true"
|
||||||
|
class={`${prefixCls}-measure-row`}
|
||||||
|
style={{ height: 0, fontSize: 0 }}
|
||||||
|
>
|
||||||
|
{columnsKey.map(columnKey => (
|
||||||
|
<MeasureCell
|
||||||
|
key={columnKey}
|
||||||
|
columnKey={columnKey}
|
||||||
|
onColumnResize={onColumnResize}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rows}
|
||||||
|
</WrapperComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import classNames from 'ant-design-vue/es/_util/classNames';
|
import classNames from 'ant-design-vue/es/_util/classNames';
|
||||||
import { isValidElement } from 'ant-design-vue/es/_util/props-util';
|
import { isValidElement, parseStyleText } from 'ant-design-vue/es/_util/props-util';
|
||||||
import { CSSProperties, defineComponent, HTMLAttributes } from 'vue';
|
import { CSSProperties, defineComponent, HTMLAttributes } from 'vue';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -46,7 +46,7 @@ export interface CellProps<RecordType = DefaultRecordType> {
|
||||||
// Additional
|
// Additional
|
||||||
/** @private Used for `expandable` with nest tree */
|
/** @private Used for `expandable` with nest tree */
|
||||||
appendNode?: any;
|
appendNode?: any;
|
||||||
additionalProps?: Omit<HTMLAttributes, 'style'> & { style?: CSSProperties };
|
additionalProps?: HTMLAttributes;
|
||||||
|
|
||||||
rowType?: 'header' | 'body' | 'footer';
|
rowType?: 'header' | 'body' | 'footer';
|
||||||
|
|
||||||
|
|
@ -196,7 +196,7 @@ export default defineComponent<CellProps>({
|
||||||
cellClass,
|
cellClass,
|
||||||
),
|
),
|
||||||
style: {
|
style: {
|
||||||
...additionalProps.style,
|
...parseStyleText(additionalProps.style as any),
|
||||||
...alignStyle,
|
...alignStyle,
|
||||||
...fixedStyle,
|
...fixedStyle,
|
||||||
...cellStyle,
|
...cellStyle,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
import type { HeaderProps } from '../Header/Header';
|
||||||
|
import ColGroup from '../ColGroup';
|
||||||
|
import type { ColumnsType, ColumnType, DefaultRecordType } from '../interface';
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
ref,
|
||||||
|
Ref,
|
||||||
|
toRef,
|
||||||
|
watchEffect,
|
||||||
|
} from 'vue';
|
||||||
|
import { useInjectTable } from '../context/TableContext';
|
||||||
|
import classNames from 'ant-design-vue/es/_util/classNames';
|
||||||
|
|
||||||
|
function useColumnWidth(colWidthsRef: Ref<readonly number[]>, columCountRef: Ref<number>) {
|
||||||
|
return computed(() => {
|
||||||
|
const cloneColumns: number[] = [];
|
||||||
|
const colWidths = colWidthsRef.value;
|
||||||
|
const columCount = columCountRef.value;
|
||||||
|
for (let i = 0; i < columCount; i += 1) {
|
||||||
|
const val = colWidths[i];
|
||||||
|
if (val !== undefined) {
|
||||||
|
cloneColumns[i] = val;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cloneColumns;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FixedHeaderProps<RecordType> extends HeaderProps<RecordType> {
|
||||||
|
noData: boolean;
|
||||||
|
maxContentScroll: boolean;
|
||||||
|
colWidths: readonly number[];
|
||||||
|
columCount: number;
|
||||||
|
direction: 'ltr' | 'rtl';
|
||||||
|
fixHeader: boolean;
|
||||||
|
stickyTopOffset?: number;
|
||||||
|
stickyBottomOffset?: number;
|
||||||
|
stickyClassName?: string;
|
||||||
|
onScroll: (info: { currentTarget: HTMLDivElement; scrollLeft?: number }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent<FixedHeaderProps<DefaultRecordType>>({
|
||||||
|
name: 'FixedHolder',
|
||||||
|
props: [
|
||||||
|
'columns',
|
||||||
|
'flattenColumns',
|
||||||
|
'stickyOffsets',
|
||||||
|
'customHeaderRow',
|
||||||
|
'noData',
|
||||||
|
'maxContentScroll',
|
||||||
|
'colWidths',
|
||||||
|
'columCount',
|
||||||
|
'direction',
|
||||||
|
'fixHeader',
|
||||||
|
'stickyTopOffset',
|
||||||
|
'stickyBottomOffset',
|
||||||
|
'stickyClassName',
|
||||||
|
] as any,
|
||||||
|
emits: ['scroll'],
|
||||||
|
inheritAttrs: false,
|
||||||
|
setup(props, { attrs, slots, emit }) {
|
||||||
|
const tableContext = useInjectTable();
|
||||||
|
const combinationScrollBarSize = computed(() =>
|
||||||
|
tableContext.isSticky && !props.fixHeader ? 0 : tableContext.scrollbarSize,
|
||||||
|
);
|
||||||
|
const scrollRef = ref();
|
||||||
|
function onWheel(e: WheelEvent) {
|
||||||
|
const { currentTarget, deltaX } = e;
|
||||||
|
if (deltaX) {
|
||||||
|
emit('scroll', { currentTarget, scrollLeft: (currentTarget as any).scrollLeft + deltaX });
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
scrollRef.value?.addEventListener('wheel', onWheel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
scrollRef.value?.removeEventListener('wheel', onWheel);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if all flattenColumns has width
|
||||||
|
const allFlattenColumnsWithWidth = computed(() =>
|
||||||
|
props.flattenColumns.every(
|
||||||
|
column => column.width && column.width !== 0 && column.width !== '0px',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const columnsWithScrollbar = ref<ColumnsType<unknown>>([]);
|
||||||
|
const flattenColumnsWithScrollbar = ref<ColumnsType<unknown>>([]);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
// Add scrollbar column
|
||||||
|
const lastColumn = props.flattenColumns[props.flattenColumns.length - 1];
|
||||||
|
const ScrollBarColumn: ColumnType<unknown> & { scrollbar: true } = {
|
||||||
|
fixed: lastColumn ? lastColumn.fixed : null,
|
||||||
|
scrollbar: true,
|
||||||
|
customHeaderCell: () => ({
|
||||||
|
class: `${tableContext.prefixCls}-cell-scrollbar`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
columnsWithScrollbar.value = combinationScrollBarSize
|
||||||
|
? [...props.columns, ScrollBarColumn]
|
||||||
|
: props.columns;
|
||||||
|
|
||||||
|
flattenColumnsWithScrollbar.value = combinationScrollBarSize
|
||||||
|
? [...props.flattenColumns, ScrollBarColumn]
|
||||||
|
: props.flattenColumns;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate the sticky offsets
|
||||||
|
const headerStickyOffsets = computed(() => {
|
||||||
|
const { stickyOffsets, direction } = props;
|
||||||
|
const { right, left } = stickyOffsets;
|
||||||
|
return {
|
||||||
|
...stickyOffsets,
|
||||||
|
left:
|
||||||
|
direction === 'rtl'
|
||||||
|
? [...left.map(width => width + combinationScrollBarSize.value), 0]
|
||||||
|
: left,
|
||||||
|
right:
|
||||||
|
direction === 'rtl'
|
||||||
|
? right
|
||||||
|
: [...right.map(width => width + combinationScrollBarSize.value), 0],
|
||||||
|
isSticky: tableContext.isSticky,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergedColumnWidth = useColumnWidth(toRef(props, 'colWidths'), toRef(props, 'columCount'));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
noData,
|
||||||
|
columCount,
|
||||||
|
stickyTopOffset,
|
||||||
|
stickyBottomOffset,
|
||||||
|
stickyClassName,
|
||||||
|
maxContentScroll,
|
||||||
|
} = props;
|
||||||
|
const { isSticky } = tableContext;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
...(isSticky ? { top: `${stickyTopOffset}px`, bottom: `${stickyBottomOffset}px` } : {}),
|
||||||
|
}}
|
||||||
|
ref={scrollRef}
|
||||||
|
class={classNames(attrs.class, {
|
||||||
|
[stickyClassName]: !!stickyClassName,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
visibility: noData || mergedColumnWidth.value ? null : 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(!noData || !maxContentScroll || allFlattenColumnsWithWidth.value) && (
|
||||||
|
<ColGroup
|
||||||
|
colWidths={
|
||||||
|
mergedColumnWidth.value
|
||||||
|
? [...mergedColumnWidth.value, combinationScrollBarSize.value]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
columCount={columCount + 1}
|
||||||
|
columns={flattenColumnsWithScrollbar.value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{slots.default?.({
|
||||||
|
...props,
|
||||||
|
stickyOffsets: headerStickyOffsets.value,
|
||||||
|
columns: columnsWithScrollbar.value,
|
||||||
|
flattenColumns: flattenColumnsWithScrollbar.value,
|
||||||
|
})}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import Cell from '../Cell';
|
||||||
|
import { useInjectSummary } from '../context/SummaryContext';
|
||||||
|
import { useInjectTable } from '../context/TableContext';
|
||||||
|
import type { AlignType } from '../interface';
|
||||||
|
import { getCellFixedInfo } from '../utils/fixUtil';
|
||||||
|
|
||||||
|
export interface SummaryCellProps {
|
||||||
|
className?: string;
|
||||||
|
children?: any;
|
||||||
|
index: number;
|
||||||
|
colSpan?: number;
|
||||||
|
rowSpan?: number;
|
||||||
|
align?: AlignType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent<SummaryCellProps>({
|
||||||
|
name: 'SummaryCell',
|
||||||
|
props: ['index', 'colSpan', 'rowSpan', 'align'] as any,
|
||||||
|
inheritAttrs: false,
|
||||||
|
setup(props, { attrs, slots }) {
|
||||||
|
const tableContext = useInjectTable();
|
||||||
|
const summaryContext = useInjectSummary();
|
||||||
|
return () => {
|
||||||
|
const { index, colSpan, rowSpan, align } = props;
|
||||||
|
const { prefixCls, direction } = tableContext;
|
||||||
|
const { scrollColumnIndex, stickyOffsets, flattenColumns } = summaryContext;
|
||||||
|
const lastIndex = index + colSpan - 1;
|
||||||
|
const mergedColSpan = lastIndex + 1 === scrollColumnIndex ? colSpan + 1 : colSpan;
|
||||||
|
|
||||||
|
const fixedInfo = getCellFixedInfo(
|
||||||
|
index,
|
||||||
|
index + mergedColSpan - 1,
|
||||||
|
flattenColumns,
|
||||||
|
stickyOffsets,
|
||||||
|
direction,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Cell
|
||||||
|
className={attrs.class as string}
|
||||||
|
index={index}
|
||||||
|
component="td"
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
record={null}
|
||||||
|
dataIndex={null}
|
||||||
|
align={align}
|
||||||
|
customRender={() => ({
|
||||||
|
children: slots.defalut?.(),
|
||||||
|
props: {
|
||||||
|
colSpan: mergedColSpan,
|
||||||
|
rowSpan,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
{...fixedInfo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default function FooterRow(props, { slots }) {
|
||||||
|
return <tr {...props}>{slots.default?.()}</tr>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { FunctionalComponent } from 'vue';
|
||||||
|
import Cell from './Cell';
|
||||||
|
import Row from './Row';
|
||||||
|
|
||||||
|
export interface SummaryProps {
|
||||||
|
fixed?: boolean | 'top' | 'bottom';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SummaryFC extends FunctionalComponent<SummaryProps> {
|
||||||
|
Row: typeof Row;
|
||||||
|
Cell: typeof Cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syntactic sugar. Do not support HOC.
|
||||||
|
*/
|
||||||
|
const Summary: SummaryFC = (_props, { slots }) => {
|
||||||
|
return slots.default?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
Summary.Row = Row;
|
||||||
|
Summary.Cell = Cell;
|
||||||
|
|
||||||
|
Summary.displayName = 'Summary';
|
||||||
|
|
||||||
|
export default Summary;
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import Summary from './Summary';
|
||||||
|
import type { DefaultRecordType, StickyOffsets } from '../interface';
|
||||||
|
import { computed, defineComponent, reactive, toRef } from 'vue';
|
||||||
|
import { FlattenColumns, useProvideSummary } from '../context/SummaryContext';
|
||||||
|
import { useInjectTable } from '../context/TableContext';
|
||||||
|
|
||||||
|
export interface FooterProps<RecordType = DefaultRecordType> {
|
||||||
|
stickyOffsets: StickyOffsets;
|
||||||
|
flattenColumns: FlattenColumns<RecordType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: ['stickyOffsets', 'flattenColumns'],
|
||||||
|
name: 'Footer',
|
||||||
|
setup(props, { slots }) {
|
||||||
|
const tableContext = useInjectTable();
|
||||||
|
useProvideSummary(
|
||||||
|
reactive({
|
||||||
|
stickyOffsets: toRef(props, 'stickyOffsets'),
|
||||||
|
flattenColumns: toRef(props, 'flattenColumns'),
|
||||||
|
scrollColumnIndex: computed(() => {
|
||||||
|
const lastColumnIndex = props.flattenColumns.length - 1;
|
||||||
|
const scrollColumn = props.flattenColumns[lastColumnIndex];
|
||||||
|
return scrollColumn?.scrollbar ? lastColumnIndex : null;
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
const { prefixCls } = tableContext;
|
||||||
|
return <tfoot class={`${prefixCls}-summary`}>{slots.default?.()}</tfoot>;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const FooterComponents = Summary;
|
||||||
|
|
@ -87,18 +87,18 @@ export interface HeaderProps<RecordType = DefaultRecordType> {
|
||||||
columns: ColumnsType<RecordType>;
|
columns: ColumnsType<RecordType>;
|
||||||
flattenColumns: readonly ColumnType<RecordType>[];
|
flattenColumns: readonly ColumnType<RecordType>[];
|
||||||
stickyOffsets: StickyOffsets;
|
stickyOffsets: StickyOffsets;
|
||||||
onHeaderRow: GetComponentProps<readonly ColumnType<RecordType>[]>;
|
customHeaderRow: GetComponentProps<readonly ColumnType<RecordType>[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent<HeaderProps>({
|
export default defineComponent<HeaderProps>({
|
||||||
name: 'Header',
|
name: 'Header',
|
||||||
props: ['columns', 'flattenColumns', 'stickyOffsets', 'onHeaderRow'] as any,
|
props: ['columns', 'flattenColumns', 'stickyOffsets', 'customHeaderRow'] as any,
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const tableContext = useInjectTable();
|
const tableContext = useInjectTable();
|
||||||
const rows = computed(() => parseHeaderRows(props.columns));
|
const rows = computed(() => parseHeaderRows(props.columns));
|
||||||
return () => {
|
return () => {
|
||||||
const { prefixCls, getComponent } = tableContext;
|
const { prefixCls, getComponent } = tableContext;
|
||||||
const { stickyOffsets, flattenColumns, onHeaderRow } = props;
|
const { stickyOffsets, flattenColumns, customHeaderRow } = props;
|
||||||
const WrapperComponent = getComponent(['header', 'wrapper'], 'thead');
|
const WrapperComponent = getComponent(['header', 'wrapper'], 'thead');
|
||||||
const trComponent = getComponent(['header', 'row'], 'tr');
|
const trComponent = getComponent(['header', 'row'], 'tr');
|
||||||
const thComponent = getComponent(['header', 'cell'], 'th');
|
const thComponent = getComponent(['header', 'cell'], 'th');
|
||||||
|
|
@ -113,7 +113,7 @@ export default defineComponent<HeaderProps>({
|
||||||
stickyOffsets={stickyOffsets}
|
stickyOffsets={stickyOffsets}
|
||||||
rowComponent={trComponent}
|
rowComponent={trComponent}
|
||||||
cellComponent={thComponent}
|
cellComponent={thComponent}
|
||||||
onHeaderRow={onHeaderRow}
|
customHeaderRow={customHeaderRow}
|
||||||
index={rowIndex}
|
index={rowIndex}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export interface RowProps<RecordType = DefaultRecordType> {
|
||||||
flattenColumns: readonly ColumnType<RecordType>[];
|
flattenColumns: readonly ColumnType<RecordType>[];
|
||||||
rowComponent: CustomizeComponent;
|
rowComponent: CustomizeComponent;
|
||||||
cellComponent: CustomizeComponent;
|
cellComponent: CustomizeComponent;
|
||||||
onHeaderRow: GetComponentProps<readonly ColumnType<RecordType>[]>;
|
customHeaderRow: GetComponentProps<readonly ColumnType<RecordType>[]>;
|
||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default defineComponent<RowProps>({
|
||||||
'rowComponent',
|
'rowComponent',
|
||||||
'cellComponent',
|
'cellComponent',
|
||||||
'index',
|
'index',
|
||||||
'onHeaderRow',
|
'customHeaderRow',
|
||||||
] as any,
|
] as any,
|
||||||
setup(props: RowProps) {
|
setup(props: RowProps) {
|
||||||
const tableContext = useInjectTable();
|
const tableContext = useInjectTable();
|
||||||
|
|
@ -43,13 +43,13 @@ export default defineComponent<RowProps>({
|
||||||
flattenColumns,
|
flattenColumns,
|
||||||
rowComponent: RowComponent,
|
rowComponent: RowComponent,
|
||||||
cellComponent: CellComponent,
|
cellComponent: CellComponent,
|
||||||
onHeaderRow,
|
customHeaderRow,
|
||||||
index,
|
index,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
let rowProps;
|
let rowProps;
|
||||||
if (onHeaderRow) {
|
if (customHeaderRow) {
|
||||||
rowProps = onHeaderRow(
|
rowProps = customHeaderRow(
|
||||||
cells.map(cell => cell.column),
|
cells.map(cell => cell.column),
|
||||||
index,
|
index,
|
||||||
);
|
);
|
||||||
|
|
@ -70,8 +70,8 @@ export default defineComponent<RowProps>({
|
||||||
);
|
);
|
||||||
|
|
||||||
let additionalProps;
|
let additionalProps;
|
||||||
if (column && column.onHeaderCell) {
|
if (column && column.customHeaderCell) {
|
||||||
additionalProps = cell.column.onHeaderCell(column);
|
additionalProps = cell.column.customHeaderCell(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,654 @@
|
||||||
|
import ColumnGroup from './sugar/ColumnGroup';
|
||||||
|
import Column from './sugar/Column';
|
||||||
|
import Header from './Header/Header';
|
||||||
|
import type {
|
||||||
|
GetRowKey,
|
||||||
|
ColumnsType,
|
||||||
|
TableComponents,
|
||||||
|
Key,
|
||||||
|
DefaultRecordType,
|
||||||
|
TriggerEventHandler,
|
||||||
|
GetComponentProps,
|
||||||
|
ExpandableConfig,
|
||||||
|
LegacyExpandableProps,
|
||||||
|
GetComponent,
|
||||||
|
PanelRender,
|
||||||
|
TableLayout,
|
||||||
|
ExpandableType,
|
||||||
|
RowClassName,
|
||||||
|
CustomizeComponent,
|
||||||
|
ColumnType,
|
||||||
|
CustomizeScrollBody,
|
||||||
|
TableSticky,
|
||||||
|
FixedType,
|
||||||
|
} from './interface';
|
||||||
|
import Body from './Body';
|
||||||
|
import useColumns from './hooks/useColumns';
|
||||||
|
import { useLayoutState, useTimeoutLock } from './hooks/useFrame';
|
||||||
|
import { getPathValue, mergeObject, validateValue, getColumnsKey } from './utils/valueUtil';
|
||||||
|
import useStickyOffsets from './hooks/useStickyOffsets';
|
||||||
|
import ColGroup from './ColGroup';
|
||||||
|
import { getExpandableProps, getDataAndAriaProps } from './utils/legacyUtil';
|
||||||
|
import Panel from './Panel';
|
||||||
|
import Footer, { FooterComponents } from './Footer';
|
||||||
|
import { findAllChildrenKeys, renderExpandIcon } from './utils/expandUtil';
|
||||||
|
import { getCellFixedInfo } from './utils/fixUtil';
|
||||||
|
import StickyScrollBar from './stickyScrollBar';
|
||||||
|
import useSticky from './hooks/useSticky';
|
||||||
|
import FixedHolder from './FixedHolder';
|
||||||
|
import type { SummaryProps } from './Footer/Summary';
|
||||||
|
import Summary from './Footer/Summary';
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
CSSProperties,
|
||||||
|
defineComponent,
|
||||||
|
nextTick,
|
||||||
|
onMounted,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
toRef,
|
||||||
|
toRefs,
|
||||||
|
watch,
|
||||||
|
watchEffect,
|
||||||
|
} from 'vue';
|
||||||
|
import { warning } from '../vc-util/warning';
|
||||||
|
import { reactivePick } from '../_util/reactivePick';
|
||||||
|
import useState from '../_util/hooks/useState';
|
||||||
|
import { toPx } from '../_util/util';
|
||||||
|
import isVisible from '../vc-util/Dom/isVisible';
|
||||||
|
import { getTargetScrollBarSize } from '../_util/getScrollBarSize';
|
||||||
|
import classNames from '../_util/classNames';
|
||||||
|
import { EventHandler } from '../_util/EventInterface';
|
||||||
|
|
||||||
|
// Used for conditions cache
|
||||||
|
const EMPTY_DATA = [];
|
||||||
|
|
||||||
|
// Used for customize scroll
|
||||||
|
const EMPTY_SCROLL_TARGET = {};
|
||||||
|
|
||||||
|
export const INTERNAL_HOOKS = 'rc-table-internal-hook';
|
||||||
|
|
||||||
|
export interface TableProps<RecordType = unknown> extends LegacyExpandableProps<RecordType> {
|
||||||
|
prefixCls?: string;
|
||||||
|
data?: RecordType[];
|
||||||
|
columns?: ColumnsType<RecordType>;
|
||||||
|
rowKey?: string | GetRowKey<RecordType>;
|
||||||
|
tableLayout?: TableLayout;
|
||||||
|
|
||||||
|
// Fixed Columns
|
||||||
|
scroll?: { x?: number | true | string; y?: number | string };
|
||||||
|
|
||||||
|
// Expandable
|
||||||
|
/** Config expand rows */
|
||||||
|
expandable?: ExpandableConfig<RecordType>;
|
||||||
|
indentSize?: number;
|
||||||
|
rowClassName?: string | RowClassName<RecordType>;
|
||||||
|
|
||||||
|
// Additional Part
|
||||||
|
// title?: PanelRender<RecordType>;
|
||||||
|
// footer?: PanelRender<RecordType>;
|
||||||
|
// summary?: (data: readonly RecordType[]) => any;
|
||||||
|
|
||||||
|
// Customize
|
||||||
|
id?: string;
|
||||||
|
showHeader?: boolean;
|
||||||
|
components?: TableComponents<RecordType>;
|
||||||
|
customRow?: GetComponentProps<RecordType>;
|
||||||
|
customHeaderRow?: GetComponentProps<ColumnType<RecordType>[]>;
|
||||||
|
// emptyText?: any;
|
||||||
|
|
||||||
|
direction?: 'ltr' | 'rtl';
|
||||||
|
|
||||||
|
expandFixed?: boolean;
|
||||||
|
expandColumnWidth?: number;
|
||||||
|
expandIconColumnIndex?: number;
|
||||||
|
|
||||||
|
// // =================================== Internal ===================================
|
||||||
|
// /**
|
||||||
|
// * @private Internal usage, may remove by refactor. Should always use `columns` instead.
|
||||||
|
// *
|
||||||
|
// * !!! DO NOT USE IN PRODUCTION ENVIRONMENT !!!
|
||||||
|
// */
|
||||||
|
// internalHooks?: string;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @private Internal usage, may remove by refactor. Should always use `columns` instead.
|
||||||
|
// *
|
||||||
|
// * !!! DO NOT USE IN PRODUCTION ENVIRONMENT !!!
|
||||||
|
// */
|
||||||
|
// // Used for antd table transform column with additional column
|
||||||
|
// transformColumns?: (columns: ColumnsType<RecordType>) => ColumnsType<RecordType>;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @private Internal usage, may remove by refactor.
|
||||||
|
// *
|
||||||
|
// * !!! DO NOT USE IN PRODUCTION ENVIRONMENT !!!
|
||||||
|
// */
|
||||||
|
// internalRefs?: {
|
||||||
|
// body: React.MutableRefObject<HTMLDivElement>;
|
||||||
|
// };
|
||||||
|
|
||||||
|
sticky?: boolean | TableSticky;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent<TableProps>({
|
||||||
|
name: 'Table',
|
||||||
|
slots: ['title', 'footer', 'summary', 'emptyText'],
|
||||||
|
inheritAttrs: false,
|
||||||
|
emits: ['expand', 'expandedRowsChange'],
|
||||||
|
setup(props, { slots, attrs, emit }) {
|
||||||
|
const mergedData = computed(() => props.data || EMPTY_DATA);
|
||||||
|
const hasData = computed(() => !!mergedData.value.length);
|
||||||
|
|
||||||
|
// ==================== Customize =====================
|
||||||
|
const mergedComponents = computed(() =>
|
||||||
|
mergeObject<TableComponents<any>>(props.components, {}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const getComponent = (path, defaultComponent?: string) =>
|
||||||
|
getPathValue<CustomizeComponent, TableComponents<any>>(mergedComponents.value, path) ||
|
||||||
|
defaultComponent;
|
||||||
|
|
||||||
|
const getRowKey = computed(() => {
|
||||||
|
const rowKey = props.rowKey;
|
||||||
|
if (typeof rowKey === 'function') {
|
||||||
|
return rowKey;
|
||||||
|
}
|
||||||
|
return record => {
|
||||||
|
const key = record && record[rowKey];
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
warning(
|
||||||
|
key !== undefined,
|
||||||
|
'Each record in table should have a unique `key` prop, or set `rowKey` to an unique primary key.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// ====================== Expand ======================
|
||||||
|
|
||||||
|
const mergedExpandIcon = computed(() => props.expandIcon || renderExpandIcon);
|
||||||
|
|
||||||
|
const mergedChildrenColumnName = computed(() => props.childrenColumnName || 'children');
|
||||||
|
|
||||||
|
const expandableType = computed(() => {
|
||||||
|
if (props.expandedRowRender) {
|
||||||
|
return 'row';
|
||||||
|
}
|
||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
|
/**
|
||||||
|
* Fix https://github.com/ant-design/ant-design/issues/21154
|
||||||
|
* This is a workaround to not to break current behavior.
|
||||||
|
* We can remove follow code after final release.
|
||||||
|
*
|
||||||
|
* To other developer:
|
||||||
|
* Do not use `__PARENT_RENDER_ICON__` in prod since we will remove this when refactor
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
mergedData.value.some(
|
||||||
|
record => record && typeof record === 'object' && record[mergedChildrenColumnName.value],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'nest';
|
||||||
|
}
|
||||||
|
/* eslint-enable */
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const innerExpandedKeys = ref([]);
|
||||||
|
|
||||||
|
// defalutXxxx 仅仅第一次生效 不用考虑响应式问题
|
||||||
|
if (props.defaultExpandedRowKeys) {
|
||||||
|
innerExpandedKeys.value = props.defaultExpandedRowKeys;
|
||||||
|
}
|
||||||
|
if (props.defaultExpandAllRows) {
|
||||||
|
innerExpandedKeys.value = findAllChildrenKeys(
|
||||||
|
mergedData.value,
|
||||||
|
getRowKey.value,
|
||||||
|
mergedChildrenColumnName.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const mergedExpandedKeys = computed(
|
||||||
|
() => new Set(props.expandedRowKeys || innerExpandedKeys.value || []),
|
||||||
|
);
|
||||||
|
|
||||||
|
const onTriggerExpand: TriggerEventHandler<any> = record => {
|
||||||
|
const key = getRowKey.value(record, mergedData.value.indexOf(record));
|
||||||
|
|
||||||
|
let newExpandedKeys: Key[];
|
||||||
|
const hasKey = mergedExpandedKeys.value.has(key);
|
||||||
|
if (hasKey) {
|
||||||
|
mergedExpandedKeys.value.delete(key);
|
||||||
|
newExpandedKeys = [...mergedExpandedKeys.value];
|
||||||
|
} else {
|
||||||
|
newExpandedKeys = [...mergedExpandedKeys.value, key];
|
||||||
|
}
|
||||||
|
innerExpandedKeys.value = newExpandedKeys;
|
||||||
|
|
||||||
|
emit('expand', !hasKey, record);
|
||||||
|
emit('expandedRowsChange', newExpandedKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentWidth = ref(0);
|
||||||
|
|
||||||
|
const [columns, flattenColumns] = useColumns({
|
||||||
|
...toRefs(props),
|
||||||
|
|
||||||
|
// children,
|
||||||
|
expandable: computed(() => !!props.expandedRowRender),
|
||||||
|
expandedKeys: mergedExpandedKeys,
|
||||||
|
getRowKey,
|
||||||
|
onTriggerExpand,
|
||||||
|
expandIcon: mergedExpandIcon,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnContext = computed(() => ({
|
||||||
|
columns: columns.value,
|
||||||
|
flattenColumns: flattenColumns.value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ====================== Scroll ======================
|
||||||
|
const fullTableRef = ref<HTMLDivElement>();
|
||||||
|
const scrollHeaderRef = ref<HTMLDivElement>();
|
||||||
|
const scrollBodyRef = ref<HTMLDivElement>();
|
||||||
|
const scrollSummaryRef = ref<HTMLDivElement>();
|
||||||
|
const [pingedLeft, setPingedLeft] = useState(false);
|
||||||
|
const [pingedRight, setPingedRight] = useState(false);
|
||||||
|
const [colsWidths, updateColsWidths] = useLayoutState(new Map<Key, number>());
|
||||||
|
|
||||||
|
// Convert map to number width
|
||||||
|
const colsKeys = computed(() => getColumnsKey(flattenColumns.value));
|
||||||
|
const colWidths = computed(() =>
|
||||||
|
colsKeys.value.map(columnKey => colsWidths.value.get(columnKey)),
|
||||||
|
);
|
||||||
|
const columnCount = computed(() => flattenColumns.value.length);
|
||||||
|
const stickyOffsets = useStickyOffsets(colWidths, columnCount, toRef(props, 'direction'));
|
||||||
|
const fixHeader = computed(() => props.scroll && validateValue(props.scroll.y));
|
||||||
|
const horizonScroll = computed(
|
||||||
|
() => (props.scroll && validateValue(props.scroll.x)) || Boolean(props.expandFixed),
|
||||||
|
);
|
||||||
|
const fixColumn = computed(
|
||||||
|
() => horizonScroll.value && flattenColumns.value.some(({ fixed }) => fixed),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sticky
|
||||||
|
const stickyRef = ref<{ setScrollLeft: (left: number) => void }>();
|
||||||
|
const stickyState = useSticky(toRef(props, 'sticky'), toRef(props, 'prefixCls'));
|
||||||
|
|
||||||
|
const summaryFixedInfos = reactive<Record<string, boolean | string>>({});
|
||||||
|
const fixFooter = computed(() => {
|
||||||
|
const info = Object.values(summaryFixedInfos)[0];
|
||||||
|
return fixHeader.value || (stickyState.value.isSticky && info);
|
||||||
|
});
|
||||||
|
|
||||||
|
const summaryCollect = (uniKey: string, fixed: boolean | string) => {
|
||||||
|
if (fixed) {
|
||||||
|
summaryFixedInfos[uniKey] = fixed;
|
||||||
|
} else {
|
||||||
|
delete summaryFixedInfos[uniKey];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scroll
|
||||||
|
let scrollXStyle = ref<CSSProperties>({});
|
||||||
|
let scrollYStyle = ref<CSSProperties>({});
|
||||||
|
let scrollTableStyle = ref<CSSProperties>({});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (fixHeader.value) {
|
||||||
|
scrollYStyle.value = {
|
||||||
|
overflowY: 'scroll',
|
||||||
|
maxHeight: toPx(props.scroll.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (horizonScroll.value) {
|
||||||
|
scrollXStyle.value = { overflowX: 'auto' };
|
||||||
|
// When no vertical scrollbar, should hide it
|
||||||
|
// https://github.com/ant-design/ant-design/pull/20705
|
||||||
|
// https://github.com/ant-design/ant-design/issues/21879
|
||||||
|
if (!fixHeader.value) {
|
||||||
|
scrollYStyle.value = { overflowY: 'hidden' };
|
||||||
|
}
|
||||||
|
scrollTableStyle.value = {
|
||||||
|
width: props.scroll.x === true ? 'auto' : toPx(props.scroll.x),
|
||||||
|
minWidth: '100%',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onColumnResize = (columnKey: Key, width: number) => {
|
||||||
|
if (isVisible(fullTableRef.value)) {
|
||||||
|
updateColsWidths(widths => {
|
||||||
|
if (widths.get(columnKey) !== width) {
|
||||||
|
const newWidths = new Map(widths);
|
||||||
|
newWidths.set(columnKey, width);
|
||||||
|
return newWidths;
|
||||||
|
}
|
||||||
|
return widths;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [setScrollTarget, getScrollTarget] = useTimeoutLock(null);
|
||||||
|
|
||||||
|
function forceScroll(scrollLeft: number, target: HTMLDivElement | ((left: number) => void)) {
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof target === 'function') {
|
||||||
|
target(scrollLeft);
|
||||||
|
} else if (target.scrollLeft !== scrollLeft) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
target.scrollLeft = scrollLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScroll: EventHandler = ({
|
||||||
|
currentTarget,
|
||||||
|
scrollLeft,
|
||||||
|
}: {
|
||||||
|
currentTarget: HTMLElement;
|
||||||
|
scrollLeft?: number;
|
||||||
|
}) => {
|
||||||
|
const isRTL = props.direction === 'rtl';
|
||||||
|
const mergedScrollLeft =
|
||||||
|
typeof scrollLeft === 'number' ? scrollLeft : currentTarget.scrollLeft;
|
||||||
|
|
||||||
|
const compareTarget = currentTarget || EMPTY_SCROLL_TARGET;
|
||||||
|
if (!getScrollTarget() || getScrollTarget() === compareTarget) {
|
||||||
|
setScrollTarget(compareTarget);
|
||||||
|
|
||||||
|
forceScroll(mergedScrollLeft, scrollHeaderRef.value);
|
||||||
|
forceScroll(mergedScrollLeft, scrollBodyRef.value);
|
||||||
|
forceScroll(mergedScrollLeft, scrollSummaryRef.value);
|
||||||
|
forceScroll(mergedScrollLeft, stickyRef.value?.setScrollLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTarget) {
|
||||||
|
const { scrollWidth, clientWidth } = currentTarget;
|
||||||
|
if (isRTL) {
|
||||||
|
setPingedLeft(-mergedScrollLeft < scrollWidth - clientWidth);
|
||||||
|
setPingedRight(-mergedScrollLeft > 0);
|
||||||
|
} else {
|
||||||
|
setPingedLeft(mergedScrollLeft > 0);
|
||||||
|
setPingedRight(mergedScrollLeft < scrollWidth - clientWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerOnScroll = () => {
|
||||||
|
if (scrollBodyRef.value) {
|
||||||
|
onScroll({ currentTarget: scrollBodyRef.value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFullTableResize = ({ width }) => {
|
||||||
|
if (width !== componentWidth) {
|
||||||
|
triggerOnScroll();
|
||||||
|
componentWidth.value = fullTableRef.value ? fullTableRef.value.offsetWidth : width;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch([horizonScroll, () => props.data, () => props.columns], () => {
|
||||||
|
if (horizonScroll.value) {
|
||||||
|
triggerOnScroll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [scrollbarSize, setScrollbarSize] = useState(0);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
triggerOnScroll();
|
||||||
|
setScrollbarSize(getTargetScrollBarSize(scrollBodyRef.value).width);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Table layout
|
||||||
|
const mergedTableLayout = computed(() => {
|
||||||
|
if (props.tableLayout) {
|
||||||
|
return props.tableLayout;
|
||||||
|
}
|
||||||
|
// https://github.com/ant-design/ant-design/issues/25227
|
||||||
|
// When scroll.x is max-content, no need to fix table layout
|
||||||
|
// it's width should stretch out to fit content
|
||||||
|
if (fixColumn.value) {
|
||||||
|
return props.scroll.x === 'max-content' ? 'auto' : 'fixed';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
fixHeader.value ||
|
||||||
|
stickyState.value.isSticky ||
|
||||||
|
flattenColumns.value.some(({ ellipsis }) => ellipsis)
|
||||||
|
) {
|
||||||
|
return 'fixed';
|
||||||
|
}
|
||||||
|
return 'auto';
|
||||||
|
});
|
||||||
|
|
||||||
|
const emptyNode = () => {
|
||||||
|
return hasData.value ? null : slots.emptyText?.() || 'No Data';
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
prefixCls,
|
||||||
|
rowClassName,
|
||||||
|
data,
|
||||||
|
rowKey,
|
||||||
|
scroll,
|
||||||
|
tableLayout,
|
||||||
|
direction,
|
||||||
|
|
||||||
|
// Additional Part
|
||||||
|
title = slots.title,
|
||||||
|
footer = slots.footer,
|
||||||
|
|
||||||
|
// Customize
|
||||||
|
id,
|
||||||
|
showHeader,
|
||||||
|
components,
|
||||||
|
customHeaderRow,
|
||||||
|
rowExpandable,
|
||||||
|
sticky,
|
||||||
|
|
||||||
|
customRow,
|
||||||
|
} = props;
|
||||||
|
const { isSticky, offsetHeader, offsetSummary, offsetScroll, stickyClassName, container } =
|
||||||
|
stickyState.value;
|
||||||
|
const TableComponent = getComponent(['table'], 'table');
|
||||||
|
|
||||||
|
const summaryNode = slots.summary?.();
|
||||||
|
|
||||||
|
let groupTableNode;
|
||||||
|
|
||||||
|
// Header props
|
||||||
|
const headerProps = {
|
||||||
|
colWidths: colWidths.value,
|
||||||
|
columCount: flattenColumns.value.length,
|
||||||
|
stickyOffsets: stickyOffsets.value,
|
||||||
|
customHeaderRow,
|
||||||
|
fixHeader: fixHeader.value,
|
||||||
|
scroll,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Body
|
||||||
|
const bodyTable = (
|
||||||
|
<Body
|
||||||
|
data={mergedData.value}
|
||||||
|
measureColumnWidth={fixHeader.value || horizonScroll.value || isSticky}
|
||||||
|
expandedKeys={mergedExpandedKeys.value}
|
||||||
|
rowExpandable={rowExpandable}
|
||||||
|
getRowKey={getRowKey.value}
|
||||||
|
customRow={customRow}
|
||||||
|
childrenColumnName={mergedChildrenColumnName.value}
|
||||||
|
v-slots={{ emptyText: emptyNode }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const bodyColGroup = (
|
||||||
|
<ColGroup
|
||||||
|
colWidths={flattenColumns.value.map(({ width }) => width)}
|
||||||
|
columns={flattenColumns.value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const customizeScrollBody = getComponent(['body']) as unknown as CustomizeScrollBody<any>;
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production' &&
|
||||||
|
typeof customizeScrollBody === 'function' &&
|
||||||
|
hasData.value &&
|
||||||
|
!fixHeader.value
|
||||||
|
) {
|
||||||
|
warning(false, '`components.body` with render props is only work on `scroll.y`.');
|
||||||
|
}
|
||||||
|
if (fixHeader.value || isSticky) {
|
||||||
|
// >>>>>> Fixed Header
|
||||||
|
let bodyContent;
|
||||||
|
|
||||||
|
if (typeof customizeScrollBody === 'function') {
|
||||||
|
bodyContent = customizeScrollBody(mergedData.value, {
|
||||||
|
scrollbarSize: scrollbarSize.value,
|
||||||
|
ref: scrollBodyRef,
|
||||||
|
onScroll,
|
||||||
|
});
|
||||||
|
|
||||||
|
headerProps.colWidths = flattenColumns.value.map(({ width }, index) => {
|
||||||
|
const colWidth =
|
||||||
|
index === columns.value.length - 1 ? (width as number) - scrollbarSize.value : width;
|
||||||
|
if (typeof colWidth === 'number' && !Number.isNaN(colWidth)) {
|
||||||
|
return colWidth;
|
||||||
|
}
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'When use `components.body` with render props. Each column should have a fixed `width` value.',
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}) as number[];
|
||||||
|
} else {
|
||||||
|
bodyContent = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...scrollXStyle.value,
|
||||||
|
...scrollYStyle.value,
|
||||||
|
}}
|
||||||
|
onScroll={onScroll}
|
||||||
|
ref={scrollBodyRef}
|
||||||
|
class={classNames(`${prefixCls}-body`)}
|
||||||
|
>
|
||||||
|
<TableComponent
|
||||||
|
style={{
|
||||||
|
...scrollTableStyle.value,
|
||||||
|
tableLayout: mergedTableLayout.value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{bodyColGroup}
|
||||||
|
{bodyTable}
|
||||||
|
{!fixFooter && summaryNode && (
|
||||||
|
<Footer stickyOffsets={stickyOffsets} flattenColumns={flattenColumns.value}>
|
||||||
|
{summaryNode}
|
||||||
|
</Footer>
|
||||||
|
)}
|
||||||
|
</TableComponent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed holder share the props
|
||||||
|
const fixedHolderProps = {
|
||||||
|
noData: !mergedData.value.length,
|
||||||
|
maxContentScroll: horizonScroll.value && scroll.x === 'max-content',
|
||||||
|
...headerProps,
|
||||||
|
...columnContext.value,
|
||||||
|
direction,
|
||||||
|
stickyClassName,
|
||||||
|
onScroll,
|
||||||
|
};
|
||||||
|
|
||||||
|
groupTableNode = (
|
||||||
|
<>
|
||||||
|
{/* Header Table */}
|
||||||
|
{showHeader !== false && (
|
||||||
|
<FixedHolder
|
||||||
|
{...fixedHolderProps}
|
||||||
|
stickyTopOffset={offsetHeader}
|
||||||
|
class={`${prefixCls}-header`}
|
||||||
|
ref={scrollHeaderRef}
|
||||||
|
v-slots={{
|
||||||
|
default: fixedHolderPassProps => (
|
||||||
|
<>
|
||||||
|
<Header {...fixedHolderPassProps} />
|
||||||
|
{fixFooter.value === 'top' && (
|
||||||
|
<Footer {...fixedHolderPassProps}>{summaryNode}</Footer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
></FixedHolder>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Body Table */}
|
||||||
|
{bodyContent}
|
||||||
|
|
||||||
|
{/* Summary Table */}
|
||||||
|
{fixFooter.value !== 'top' && (
|
||||||
|
<FixedHolder
|
||||||
|
{...fixedHolderProps}
|
||||||
|
stickyBottomOffset={offsetSummary}
|
||||||
|
class={`${prefixCls}-summary`}
|
||||||
|
ref={scrollSummaryRef}
|
||||||
|
v-slots={{
|
||||||
|
default: fixedHolderPassProps => (
|
||||||
|
<Footer {...fixedHolderPassProps}>{summaryNode}</Footer>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
></FixedHolder>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isSticky && (
|
||||||
|
<StickyScrollBar
|
||||||
|
ref={stickyRef}
|
||||||
|
offsetScroll={offsetScroll}
|
||||||
|
scrollBodyRef={scrollBodyRef}
|
||||||
|
onScroll={onScroll}
|
||||||
|
container={container}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// >>>>>> Unique table
|
||||||
|
groupTableNode = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...scrollXStyle.value,
|
||||||
|
...scrollYStyle.value,
|
||||||
|
}}
|
||||||
|
class={classNames(`${prefixCls}-content`)}
|
||||||
|
onScroll={onScroll}
|
||||||
|
ref={scrollBodyRef}
|
||||||
|
>
|
||||||
|
<TableComponent
|
||||||
|
style={{ ...scrollTableStyle.value, tableLayout: mergedTableLayout.value }}
|
||||||
|
>
|
||||||
|
{bodyColGroup}
|
||||||
|
{showHeader !== false && <Header {...headerProps} {...columnContext.value} />}
|
||||||
|
{bodyTable}
|
||||||
|
{summaryNode && (
|
||||||
|
<Footer stickyOffsets={stickyOffsets.value} flattenColumns={flattenColumns.value}>
|
||||||
|
{summaryNode}
|
||||||
|
</Footer>
|
||||||
|
)}
|
||||||
|
</TableComponent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { inject, InjectionKey, provide } from 'vue';
|
||||||
|
import { ColumnType, StickyOffsets } from '../interface';
|
||||||
|
|
||||||
|
export type FlattenColumns<RecordType> = readonly (ColumnType<RecordType> & {
|
||||||
|
scrollbar?: boolean;
|
||||||
|
})[];
|
||||||
|
type SummaryContextProps = {
|
||||||
|
stickyOffsets?: StickyOffsets;
|
||||||
|
scrollColumnIndex?: number;
|
||||||
|
flattenColumns?: FlattenColumns<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SummaryContextKey: InjectionKey<SummaryContextProps> = Symbol('SummaryContextProps');
|
||||||
|
|
||||||
|
export const useProvideSummary = (props: SummaryContextProps) => {
|
||||||
|
provide(SummaryContextKey, props);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useInjectSummary = () => {
|
||||||
|
return inject(SummaryContextKey, {} as SummaryContextProps);
|
||||||
|
};
|
||||||
|
|
@ -102,8 +102,7 @@ function revertForRtl<RecordType>(columns: ColumnsType<RecordType>): ColumnsType
|
||||||
/**
|
/**
|
||||||
* Parse `columns` & `children` into `columns`.
|
* Parse `columns` & `children` into `columns`.
|
||||||
*/
|
*/
|
||||||
function useColumns<RecordType>(
|
function useColumns<RecordType>({
|
||||||
{
|
|
||||||
prefixCls,
|
prefixCls,
|
||||||
columns: baseColumns,
|
columns: baseColumns,
|
||||||
// children,
|
// children,
|
||||||
|
|
@ -116,26 +115,25 @@ function useColumns<RecordType>(
|
||||||
expandIconColumnIndex,
|
expandIconColumnIndex,
|
||||||
direction,
|
direction,
|
||||||
expandRowByClick,
|
expandRowByClick,
|
||||||
columnWidth,
|
expandColumnWidth,
|
||||||
fixed,
|
expandFixed,
|
||||||
}: {
|
}: {
|
||||||
prefixCls?: Ref<string>;
|
prefixCls?: Ref<string>;
|
||||||
columns?: Ref<ColumnsType<RecordType>>;
|
columns?: Ref<ColumnsType<RecordType>>;
|
||||||
// children?: React.ReactNode;
|
// children?: React.ReactNode;
|
||||||
expandable: Ref<boolean>;
|
expandable: Ref<boolean>;
|
||||||
expandedKeys: Ref<Set<Key>>;
|
expandedKeys: Ref<Set<Key>>;
|
||||||
getRowKey: GetRowKey<RecordType>;
|
getRowKey: Ref<GetRowKey<RecordType>>;
|
||||||
onTriggerExpand: TriggerEventHandler<RecordType>;
|
onTriggerExpand: TriggerEventHandler<RecordType>;
|
||||||
expandIcon?: Ref<RenderExpandIcon<RecordType>>;
|
expandIcon?: Ref<RenderExpandIcon<RecordType>>;
|
||||||
rowExpandable?: Ref<(record: RecordType) => boolean>;
|
rowExpandable?: Ref<(record: RecordType) => boolean>;
|
||||||
expandIconColumnIndex?: Ref<number>;
|
expandIconColumnIndex?: Ref<number>;
|
||||||
direction?: Ref<'ltr' | 'rtl'>;
|
direction?: Ref<'ltr' | 'rtl'>;
|
||||||
expandRowByClick?: Ref<boolean>;
|
expandRowByClick?: Ref<boolean>;
|
||||||
columnWidth?: Ref<number | string>;
|
expandColumnWidth?: Ref<number | string>;
|
||||||
fixed?: Ref<FixedType>;
|
expandFixed?: Ref<FixedType>;
|
||||||
},
|
}): // transformColumns: (columns: ColumnsType<RecordType>) => ColumnsType<RecordType>,
|
||||||
transformColumns: (columns: ColumnsType<RecordType>) => ColumnsType<RecordType>,
|
[ComputedRef<ColumnsType<RecordType>>, ComputedRef<readonly ColumnType<RecordType>[]>] {
|
||||||
): [ComputedRef<ColumnsType<RecordType>>, ComputedRef<readonly ColumnType<RecordType>[]>] {
|
|
||||||
// const baseColumns = React.useMemo<ColumnsType<RecordType>>(
|
// const baseColumns = React.useMemo<ColumnsType<RecordType>>(
|
||||||
// () => columns || convertChildrenToColumns(children),
|
// () => columns || convertChildrenToColumns(children),
|
||||||
// [columns, children],
|
// [columns, children],
|
||||||
|
|
@ -148,10 +146,10 @@ function useColumns<RecordType>(
|
||||||
const prevColumn = baseColumns[expandColIndex];
|
const prevColumn = baseColumns[expandColIndex];
|
||||||
|
|
||||||
let fixedColumn: FixedType | null;
|
let fixedColumn: FixedType | null;
|
||||||
if ((fixed.value === 'left' || fixed.value) && !expandIconColumnIndex.value) {
|
if ((expandFixed.value === 'left' || expandFixed.value) && !expandIconColumnIndex.value) {
|
||||||
fixedColumn = 'left';
|
fixedColumn = 'left';
|
||||||
} else if (
|
} else if (
|
||||||
(fixed.value === 'right' || fixed.value) &&
|
(expandFixed.value === 'right' || expandFixed.value) &&
|
||||||
expandIconColumnIndex.value === baseColumns.value.length
|
expandIconColumnIndex.value === baseColumns.value.length
|
||||||
) {
|
) {
|
||||||
fixedColumn = 'right';
|
fixedColumn = 'right';
|
||||||
|
|
@ -170,9 +168,9 @@ function useColumns<RecordType>(
|
||||||
title: '',
|
title: '',
|
||||||
fixed: fixedColumn,
|
fixed: fixedColumn,
|
||||||
class: `${prefixCls.value}-row-expand-icon-cell`,
|
class: `${prefixCls.value}-row-expand-icon-cell`,
|
||||||
width: columnWidth.value,
|
width: expandColumnWidth.value,
|
||||||
render: (_, record, index) => {
|
customRender: ({ record, index }) => {
|
||||||
const rowKey = getRowKey(record, index);
|
const rowKey = getRowKey.value(record, index);
|
||||||
const expanded = expandedKeysValue.has(rowKey);
|
const expanded = expandedKeysValue.has(rowKey);
|
||||||
const recordExpandable = rowExpandableValue ? rowExpandableValue(record) : true;
|
const recordExpandable = rowExpandableValue ? rowExpandableValue(record) : true;
|
||||||
|
|
||||||
|
|
@ -203,9 +201,9 @@ function useColumns<RecordType>(
|
||||||
|
|
||||||
const mergedColumns = computed(() => {
|
const mergedColumns = computed(() => {
|
||||||
let finalColumns = withExpandColumns.value;
|
let finalColumns = withExpandColumns.value;
|
||||||
if (transformColumns) {
|
// if (transformColumns) {
|
||||||
finalColumns = transformColumns(finalColumns);
|
// finalColumns = transformColumns(finalColumns);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Always provides at least one column for table display
|
// Always provides at least one column for table display
|
||||||
if (!finalColumns.length) {
|
if (!finalColumns.length) {
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,11 @@ function flatRecord<T>(
|
||||||
* @param {GetRowKey<T>} getRowKey : 获取当前rowKey的方法
|
* @param {GetRowKey<T>} getRowKey : 获取当前rowKey的方法
|
||||||
* @returns flattened data
|
* @returns flattened data
|
||||||
*/
|
*/
|
||||||
export default function useFlattenRecords<T>(
|
export default function useFlattenRecords<T = unknown>(
|
||||||
dataRef: Ref<[]>,
|
dataRef: Ref<T[]>,
|
||||||
childrenColumnNameRef: Ref<string>,
|
childrenColumnNameRef: Ref<string>,
|
||||||
expandedKeysRef: Ref<Set<Key>>,
|
expandedKeysRef: Ref<Set<Key>>,
|
||||||
getRowKey: GetRowKey<T>,
|
getRowKey: Ref<GetRowKey<T>>,
|
||||||
) {
|
) {
|
||||||
const arr: Ref<{ record: T; indent: number }[]> = computed(() => {
|
const arr: Ref<{ record: T; indent: number }[]> = computed(() => {
|
||||||
const childrenColumnName = childrenColumnNameRef.value;
|
const childrenColumnName = childrenColumnNameRef.value;
|
||||||
|
|
@ -67,7 +67,7 @@ export default function useFlattenRecords<T>(
|
||||||
for (let i = 0; i < data?.length; i += 1) {
|
for (let i = 0; i < data?.length; i += 1) {
|
||||||
const record = data[i];
|
const record = data[i];
|
||||||
|
|
||||||
temp.push(...flatRecord<T>(record, 0, childrenColumnName, expandedKeys, getRowKey));
|
temp.push(...flatRecord<T>(record, 0, childrenColumnName, expandedKeys, getRowKey.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return temp;
|
return temp;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ interface ColumnSharedType<RecordType> {
|
||||||
class?: string;
|
class?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
fixed?: FixedType;
|
fixed?: FixedType;
|
||||||
onHeaderCell?: GetComponentProps<ColumnsType<RecordType>[number]>;
|
customHeaderCell?: GetComponentProps<ColumnsType<RecordType>[number]>;
|
||||||
ellipsis?: CellEllipsisType;
|
ellipsis?: CellEllipsisType;
|
||||||
align?: AlignType;
|
align?: AlignType;
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +87,7 @@ export interface ColumnType<RecordType> extends ColumnSharedType<RecordType> {
|
||||||
}) => any | RenderedCell<RecordType>;
|
}) => any | RenderedCell<RecordType>;
|
||||||
rowSpan?: number;
|
rowSpan?: number;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
onCell?: GetComponentProps<RecordType>;
|
customCell?: GetComponentProps<RecordType>;
|
||||||
/** @deprecated Please use `onCell` instead */
|
/** @deprecated Please use `onCell` instead */
|
||||||
onCellClick?: (record: RecordType, e: MouseEvent) => void;
|
onCellClick?: (record: RecordType, e: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
@ -150,30 +150,31 @@ export type GetComponent = (
|
||||||
export type ExpandableType = false | 'row' | 'nest';
|
export type ExpandableType = false | 'row' | 'nest';
|
||||||
|
|
||||||
export interface LegacyExpandableProps<RecordType> {
|
export interface LegacyExpandableProps<RecordType> {
|
||||||
/** @deprecated Use `expandable.expandedRowKeys` instead */
|
|
||||||
expandedRowKeys?: Key[];
|
expandedRowKeys?: Key[];
|
||||||
/** @deprecated Use `expandable.defaultExpandedRowKeys` instead */
|
|
||||||
defaultExpandedRowKeys?: Key[];
|
defaultExpandedRowKeys?: Key[];
|
||||||
/** @deprecated Use `expandable.expandedRowRender` instead */
|
|
||||||
expandedRowRender?: ExpandedRowRender<RecordType>;
|
expandedRowRender?: ExpandedRowRender<RecordType>;
|
||||||
/** @deprecated Use `expandable.expandRowByClick` instead */
|
|
||||||
expandRowByClick?: boolean;
|
expandRowByClick?: boolean;
|
||||||
/** @deprecated Use `expandable.expandIcon` instead */
|
|
||||||
expandIcon?: RenderExpandIcon<RecordType>;
|
expandIcon?: RenderExpandIcon<RecordType>;
|
||||||
/** @deprecated Use `expandable.onExpand` instead */
|
|
||||||
onExpand?: (expanded: boolean, record: RecordType) => void;
|
onExpand?: (expanded: boolean, record: RecordType) => void;
|
||||||
/** @deprecated Use `expandable.onExpandedRowsChange` instead */
|
|
||||||
onExpandedRowsChange?: (expandedKeys: Key[]) => void;
|
onExpandedRowsChange?: (expandedKeys: Key[]) => void;
|
||||||
/** @deprecated Use `expandable.defaultExpandAllRows` instead */
|
|
||||||
defaultExpandAllRows?: boolean;
|
defaultExpandAllRows?: boolean;
|
||||||
/** @deprecated Use `expandable.indentSize` instead */
|
|
||||||
indentSize?: number;
|
indentSize?: number;
|
||||||
/** @deprecated Use `expandable.expandIconColumnIndex` instead */
|
|
||||||
expandIconColumnIndex?: number;
|
expandIconColumnIndex?: number;
|
||||||
/** @deprecated Use `expandable.expandedRowClassName` instead */
|
|
||||||
expandedRowClassName?: RowClassName<RecordType>;
|
expandedRowClassName?: RowClassName<RecordType>;
|
||||||
/** @deprecated Use `expandable.childrenColumnName` instead */
|
|
||||||
childrenColumnName?: string;
|
childrenColumnName?: string;
|
||||||
|
|
||||||
|
rowExpandable?: (record: RecordType) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExpandedRowRender<ValueType> = (
|
export type ExpandedRowRender<ValueType> = (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, Ref, watch } from 'vue';
|
||||||
|
import addEventListenerWrap from '../vc-util/Dom/addEventListener';
|
||||||
|
import { getOffset } from '../vc-util/Dom/css';
|
||||||
|
import classNames from '../_util/classNames';
|
||||||
|
import { MouseEventHandler } from '../_util/EventInterface';
|
||||||
|
import getScrollBarSize from '../_util/getScrollBarSize';
|
||||||
|
import { useInjectTable } from './context/TableContext';
|
||||||
|
import { useLayoutState } from './hooks/useFrame';
|
||||||
|
|
||||||
|
interface StickyScrollBarProps {
|
||||||
|
scrollBodyRef: Ref<HTMLElement>;
|
||||||
|
onScroll: (params: { scrollLeft?: number }) => void;
|
||||||
|
offsetScroll: number;
|
||||||
|
container: HTMLElement | Window;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent<StickyScrollBarProps>({
|
||||||
|
name: 'StickyScrollBar',
|
||||||
|
props: ['offsetScroll', 'container', 'scrollBodyRef'] as any,
|
||||||
|
emits: ['scroll'],
|
||||||
|
inheritAttrs: false,
|
||||||
|
setup(props, { emit, expose }) {
|
||||||
|
const tableContext = useInjectTable();
|
||||||
|
const bodyScrollWidth = computed(() => props.scrollBodyRef.value.scrollWidth || 0);
|
||||||
|
const bodyWidth = computed(() => props.scrollBodyRef.value.clientWidth || 0);
|
||||||
|
const scrollBarWidth = computed(
|
||||||
|
() => bodyScrollWidth.value && bodyWidth.value * (bodyWidth.value / bodyScrollWidth.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
const scrollBarRef = ref();
|
||||||
|
|
||||||
|
const [scrollState, setScrollState] = useLayoutState({
|
||||||
|
scrollLeft: 0,
|
||||||
|
isHiddenScrollBar: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const refState = ref({
|
||||||
|
delta: 0,
|
||||||
|
x: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isActive = ref(false);
|
||||||
|
|
||||||
|
const onMouseUp: MouseEventHandler = () => {
|
||||||
|
isActive.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseDown: MouseEventHandler = event => {
|
||||||
|
refState.value = { delta: event.pageX - scrollState.value.scrollLeft, x: 0 };
|
||||||
|
isActive.value = true;
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove: MouseEventHandler = event => {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
||||||
|
const { buttons } = event || (window?.event as any);
|
||||||
|
if (!isActive.value || buttons === 0) {
|
||||||
|
// If out body mouse up, we can set isActive false when mouse move
|
||||||
|
if (isActive.value) {
|
||||||
|
isActive.value = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let left: number = refState.value.x + event.pageX - refState.value.x - refState.value.delta;
|
||||||
|
|
||||||
|
if (left <= 0) {
|
||||||
|
left = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left + scrollBarWidth.value >= bodyWidth.value) {
|
||||||
|
left = bodyWidth.value - scrollBarWidth.value;
|
||||||
|
}
|
||||||
|
emit('scroll', {
|
||||||
|
scrollLeft: (left / bodyWidth.value) * (bodyScrollWidth.value + 2),
|
||||||
|
});
|
||||||
|
|
||||||
|
refState.value.x = event.pageX;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContainerScroll = () => {
|
||||||
|
const tableOffsetTop = getOffset(props.scrollBodyRef.value).top;
|
||||||
|
const tableBottomOffset = tableOffsetTop + props.scrollBodyRef.value.offsetHeight;
|
||||||
|
const currentClientOffset =
|
||||||
|
props.container === window
|
||||||
|
? document.documentElement.scrollTop + window.innerHeight
|
||||||
|
: getOffset(props.container).top + (props.container as HTMLElement).clientHeight;
|
||||||
|
|
||||||
|
if (
|
||||||
|
tableBottomOffset - getScrollBarSize() <= currentClientOffset ||
|
||||||
|
tableOffsetTop >= currentClientOffset - props.offsetScroll
|
||||||
|
) {
|
||||||
|
setScrollState(state => ({
|
||||||
|
...state,
|
||||||
|
isHiddenScrollBar: true,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setScrollState(state => ({
|
||||||
|
...state,
|
||||||
|
isHiddenScrollBar: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setScrollLeft = (left: number) => {
|
||||||
|
setScrollState(state => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
scrollLeft: (left / bodyScrollWidth.value) * bodyWidth.value || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
expose({
|
||||||
|
setScrollLeft,
|
||||||
|
});
|
||||||
|
let onMouseUpListener = null;
|
||||||
|
let onMouseMoveListener = null;
|
||||||
|
let onResizeListener = null;
|
||||||
|
let onScrollListener = null;
|
||||||
|
onMounted(() => {
|
||||||
|
onMouseUpListener = addEventListenerWrap(document.body, 'mouseup', onMouseUp, false);
|
||||||
|
onMouseMoveListener = addEventListenerWrap(document.body, 'mousemove', onMouseMove, false);
|
||||||
|
onResizeListener = addEventListenerWrap(window, 'resize', onContainerScroll, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[scrollBarWidth, isActive],
|
||||||
|
() => {
|
||||||
|
onContainerScroll();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.container,
|
||||||
|
() => {
|
||||||
|
onScrollListener?.remove();
|
||||||
|
onScrollListener = addEventListenerWrap(
|
||||||
|
props.container,
|
||||||
|
'scroll',
|
||||||
|
onContainerScroll,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ immediate: true, flush: 'post' },
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
onMouseUpListener?.remove();
|
||||||
|
onMouseMoveListener?.remove();
|
||||||
|
onScrollListener?.remove();
|
||||||
|
onResizeListener?.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => ({ ...scrollState.value }),
|
||||||
|
(newState, preState) => {
|
||||||
|
if (
|
||||||
|
newState.isHiddenScrollBar !== preState?.isHiddenScrollBar &&
|
||||||
|
!newState.isHiddenScrollBar
|
||||||
|
) {
|
||||||
|
setScrollState(state => {
|
||||||
|
const bodyNode = props.scrollBodyRef.value;
|
||||||
|
if (!bodyNode) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
scrollLeft: (bodyNode.scrollLeft / bodyNode.scrollWidth) * bodyNode.clientWidth,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
const scrollbarSize = getScrollBarSize();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (
|
||||||
|
bodyScrollWidth.value <= bodyWidth.value ||
|
||||||
|
!scrollBarWidth.value ||
|
||||||
|
scrollState.value.isHiddenScrollBar
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { prefixCls } = tableContext;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: `${scrollbarSize}px`,
|
||||||
|
width: `${bodyWidth}px`,
|
||||||
|
bottom: `${props.offsetScroll}px`,
|
||||||
|
}}
|
||||||
|
class={`${prefixCls}-sticky-scroll`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onMousedown={onMouseDown}
|
||||||
|
ref={scrollBarRef}
|
||||||
|
class={classNames(`${prefixCls}-sticky-scroll-bar`, {
|
||||||
|
[`${prefixCls}-sticky-scroll-bar-active`]: isActive,
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
width: `${scrollBarWidth.value}px`,
|
||||||
|
transform: `translate3d(${scrollState.value.scrollLeft}px, 0, 0)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -10,7 +10,7 @@ export function getExpandableProps<RecordType>(
|
||||||
): ExpandableConfig<RecordType> {
|
): ExpandableConfig<RecordType> {
|
||||||
const { expandable, ...legacyExpandableConfig } = props;
|
const { expandable, ...legacyExpandableConfig } = props;
|
||||||
|
|
||||||
if ('expandable' in props) {
|
if (props.expandable !== undefined) {
|
||||||
return {
|
return {
|
||||||
...legacyExpandableConfig,
|
...legacyExpandableConfig,
|
||||||
...expandable,
|
...expandable,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue