Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions packages/components/hooks/useResizeObserver.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,52 @@
import useIsomorphicLayoutEffect from './useLayoutEffect';
import { canUseDocument } from '../_util/dom';
import { off, on } from '../_util/listener';
import useDebounce from './useDebounce';
import useLatest from './useLatest';
import useIsomorphicLayoutEffect from './useLayoutEffect';

export default function useResizeObserver(
container: React.MutableRefObject<HTMLElement | null>,
callback: (data: ResizeObserverEntry[]) => void,
enabled = true,
options: {
enabled?: boolean;
debounce?: number;
} = {},
) {
const callbackRef = useLatest(callback);
const { enabled = true, debounce = 0 } = options;

const debounceRef = useDebounce(callback, debounce);
const callbackRef = useLatest(debounceRef);

const onResize = () => {
callbackRef.current();
};

useIsomorphicLayoutEffect(() => {
const isSupport = canUseDocument && window.ResizeObserver;
if (!enabled) return;

const isSupport = canUseDocument && typeof window.ResizeObserver !== 'undefined';
const element = container.current;
let observer: ResizeObserver = null;

if (!enabled) return;
let observer: ResizeObserver | null = null;

if (isSupport && element) {
const resizeCallback: ResizeObserverCallback = (entries) => {
observer = new ResizeObserver((entries) => {
callbackRef.current(entries);
};
observer = new ResizeObserver(resizeCallback);
});
observer.observe(element);
} else if (element) {
on(window, 'resize', onResize);
}

return () => {
if (observer && element) {
observer.unobserve(element);
observer.disconnect?.();
observer = null;
} else {
off(window, 'resize', onResize);
}
};
// eslint-disable-next-line
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [container, enabled]);
}
22 changes: 22 additions & 0 deletions packages/components/hooks/useVirtualScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
import { useEffect, useMemo, useRef, useState } from 'react';
import { isEqual } from 'lodash-es';
import useResizeObserver from './useResizeObserver';

import type { ScrollToElementParams, TScroll } from '../common';

export type UseVirtualScrollParams = {
Expand Down Expand Up @@ -185,6 +187,26 @@ const useVirtualScroll = (container: React.MutableRefObject<HTMLElement>, params
}
};

useResizeObserver(
container,
(entries) => {
for (const entry of entries) {
const newHeight = entry.contentRect.height;
const prevHeight = containerHeight.current;
if (newHeight !== prevHeight && newHeight > 0) {
containerHeight.current = newHeight;
// 容器高度变化时重新计算可见数据
const scrollTopHeightList = getTrScrollTopHeightList(trHeightList);
trScrollTopHeightList.current = scrollTopHeightList;
updateVisibleData(scrollTopHeightList, container.current.scrollTop);
}
}
},
{
enabled: isVirtualScroll,
},
);

// 固定高度场景,可直接通过数据长度计算出最大滚动高度
useEffect(
() => {
Expand Down
71 changes: 38 additions & 33 deletions packages/components/table/BaseTable.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React, { forwardRef, RefAttributes, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
import { pick } from 'lodash-es';

import log from '@tdesign/common-js/log/index';
import { getIEVersion } from '@tdesign/common-js/utils/helper';
import Affix, { type AffixRef } from '../affix';
import useDebounce from '../hooks/useDebounce';
import useDefaultProps from '../hooks/useDefaultProps';
import useElementLazyRender from '../hooks/useElementLazyRender';
import useResizeObserver from '../hooks/useResizeObserver';
import useVirtualScroll from '../hooks/useVirtualScroll';
import Loading from '../loading';
import TBody, { extendTableProps, type TableBodyProps } from './TBody';
Expand Down Expand Up @@ -49,9 +52,11 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
lazyLoad,
pagination,
} = props;

const tableRef = useRef<HTMLDivElement>(null);
const tableElmRef = useRef<HTMLTableElement>(null);
const bottomContentRef = useRef<HTMLDivElement>(null);
const scrolledRef = useRef(false);

const [tableFootHeight, setTableFootHeight] = useState(0);

const allTableClasses = useClassName();
Expand Down Expand Up @@ -89,6 +94,7 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
const {
scrollbarWidth,
tableWidth,
tableElmRef,
tableElmWidth,
tableContentRef,
isFixedHeader,
Expand All @@ -101,11 +107,9 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
refreshTable,
setTableElmWidth,
emitScrollEvent,
setUseFixedTableElmRef,
updateColumnFixedShadow,
getThWidthList,
updateThWidthList,
addTableResizeObserver,
updateTableAfterColumnResize,
} = useFixed(props, finalColumns, {
paginationAffixRef,
Expand All @@ -114,6 +118,9 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
footerBottomAffixRef,
});

const debouncedRefreshTable = useDebounce(refreshTable, 100);
useResizeObserver(tableRef, debouncedRefreshTable);

const { dataSource, innerPagination, isPaginateData, renderPagination } = usePagination(props, tableContentRef);

// 列宽拖拽逻辑
Expand Down Expand Up @@ -160,11 +167,6 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
setDividerBottom(bottom);
}, [bottomContentRef, paginationRef, bordered]);

useEffect(() => {
setUseFixedTableElmRef(tableElmRef.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tableElmRef]);

useEffect(() => {
setData(isPaginateData ? dataSource : props.data);
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -183,11 +185,8 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
}, [spansAndLeafNodes.leafColumns]);

const onFixedChange = () => {
const timer = setTimeout(() => {
onHorizontalScroll();
updateAffixHeaderOrFooter();
clearTimeout(timer);
}, 0);
onHorizontalScroll();
updateAffixHeaderOrFooter();
};

const virtualScrollParams = useMemo(() => {
Expand Down Expand Up @@ -230,10 +229,11 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
tableContentRef.current.style.display = '';
}, 0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tableContentRef, virtualConfig.isVirtualScroll]);
}, [virtualConfig.isVirtualScroll]);

let lastScrollY = -1;
const onInnerVirtualScroll = (e: React.WheelEvent<HTMLDivElement>) => {
scrolledRef.current = true;
const target = e.target as HTMLElement;
const top = target.scrollTop;
// 排除横向滚动触发的纵向虚拟滚动计算
Expand Down Expand Up @@ -280,26 +280,13 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)

// used for top margin
const getTFootHeight = () => {
requestAnimationFrame(() => {
if (!tableElmRef.current) return;
const height = tableElmRef.current.querySelector('tfoot')?.offsetHeight;
setTableFootHeight(height || 0);
});
if (!tableElmRef.current) return;
const height = tableElmRef.current.querySelector('tfoot')?.offsetHeight;
setTableFootHeight(height || 0);
};

useEffect(getTFootHeight, [tableElmRef, props.footData, props.footerSummary]);

useEffect(() => {
setTableContentRef(tableContentRef.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tableContentRef]);

useEffect(
() => addTableResizeObserver(tableRef.current),
// eslint-disable-next-line react-hooks/exhaustive-deps
[tableRef],
);

const newData = isPaginateData ? dataSource : data;

const renderColGroup = (isFixedHeader = true) => (
Expand All @@ -319,6 +306,9 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
})}
</colgroup>
);

const theadStyle = useMemo(() => (showAffixHeader ? { opacity: 0 } : undefined), [showAffixHeader]);

const headProps: TheadProps = {
isFixedHeader,
rowAndColFixedPosition,
Expand Down Expand Up @@ -351,6 +341,7 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
props.bordered,
props.resizable,
props.size,
theadStyle,
];

// 多级表头左边线缺失
Expand All @@ -373,10 +364,11 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
height: `${affixHeaderWrapHeight}px`,
opacity: headerOpacity,
};

const affixedHeader = Boolean((headerAffixedTop || virtualConfig.isVirtualScroll) && tableWidth.current) && (
<div
ref={affixHeaderRef}
style={{ width: `${tableWidth.current - affixedLeftBorder - barWidth}px`, opacity: headerOpacity }}
style={{ width: `${tableWidth.current - affixedLeftBorder}px`, opacity: headerOpacity }}
className={classNames([
'scrollbar',
{
Expand Down Expand Up @@ -506,7 +498,10 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
};
const tableContent = (
<div
ref={tableContentRef}
ref={(el) => {
tableContentRef.current = el;
setTableContentRef(el);
}}
className={tableBaseClass.content}
style={tableContentStyles}
onScroll={onInnerVirtualScroll}
Expand All @@ -527,7 +522,7 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
{renderColGroup(false)}
{useMemo(() => {
if (!showHeader) return null;
return <THead {...{ ...headProps, thWidthList: resizable ? thWidthList.current : {} }} />;
return <THead {...{ ...headProps, theadStyle, thWidthList: resizable ? thWidthList.current : {} }} />;
// eslint-disable-next-line
}, headUseMemoDependencies)}

Expand Down Expand Up @@ -753,6 +748,16 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
);
}

useEffect(() => {
const table = tableRef.current;
// 针对 Table 在 Dialog 等有动画效果组件的内部时,元素尺寸计算不稳定
// 初始化时依赖 virtualStyle.transform 判断是否稳定
// 滚动后不再触发
if (!table || !showElement || !virtualConfig.isVirtualScroll || scrolledRef.current) return;
debouncedRefreshTable();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showElement, virtualConfig.isVirtualScroll, virtualStyle.transform]);

if (!showElement) {
<div ref={tableRef}></div>;
}
Expand Down
21 changes: 12 additions & 9 deletions packages/components/table/THead.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React, { useRef, MutableRefObject, CSSProperties, useMemo } from 'react';
import { isFunction } from 'lodash-es';
import React, { CSSProperties, MutableRefObject, useMemo, useRef } from 'react';
import classNames from 'classnames';
import { getColumnFixedStyles } from './hooks/useFixed';
import { RowAndColFixedPosition } from './interface';
import { TableColumns, ThRowspanAndColspan } from './hooks/useMultiHeader';
import { isFunction } from 'lodash-es';

import TEllipsis from './Ellipsis';
import useClassName from './hooks/useClassName';
import { BaseTableCol, TableRowData, TdBaseTableProps } from './type';
import { getColumnFixedStyles } from './hooks/useFixed';
import { renderTitle } from './hooks/useTableHeader';
import TEllipsis from './Ellipsis';
import { formatClassNames } from './utils';
import { AttachNode } from '../common';

import type { AttachNode } from '../common';
import type { TableColumns, ThRowspanAndColspan } from './hooks/useMultiHeader';
import type { RowAndColFixedPosition } from './interface';
import type { BaseTableCol, TableRowData, TdBaseTableProps } from './type';

export interface TheadProps {
classPrefix: string;
Expand All @@ -33,6 +35,7 @@ export interface TheadProps {
resizable?: boolean;
attach?: AttachNode;
showColumnShadow?: { left: boolean; right: boolean };
theadStyle?: CSSProperties;
columnResizeParams?: {
resizeLineRef: MutableRefObject<HTMLDivElement>;
resizeLineStyle: CSSProperties;
Expand Down Expand Up @@ -195,7 +198,7 @@ export default function THead(props: TheadProps) {
};

return (
<thead ref={theadRef} className={classNames(theadClasses)}>
<thead ref={theadRef} className={classNames(theadClasses)} style={props.theadStyle}>
{renderThNodeList(props.rowAndColFixedPosition, props.thWidthList)}
</thead>
);
Expand Down
Loading
Loading