Skip to content

Commit

Permalink
feat: Table support scrollTo reference (ant-design#45245)
Browse files Browse the repository at this point in the history
* feat: add ref

* feat: table support reference

* chore: rename

* chore: proxy

* test: add test case

* chore: fix lint

* docs: update desc

* docs: update desc

* docs: update desc

* chore: clean up

* chore: fix lint
  • Loading branch information
zombieJ authored Oct 11, 2023
1 parent 6af1d95 commit bef9539
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 15 deletions.
47 changes: 47 additions & 0 deletions components/_util/hooks/useProxyImperativeHandle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Proxy the dom ref with `{ nativeElement, otherFn }` type
// ref: https://github.com/ant-design/ant-design/discussions/45242

import { useImperativeHandle, type Ref } from 'react';

function fillProxy(
element: HTMLElement & { _antProxy?: Record<string, any> },
handler: Record<string, any>,
) {
element._antProxy = element._antProxy || {};

Object.keys(handler).forEach((key) => {
if (!(key in element._antProxy!)) {
const ori = (element as any)[key];
element._antProxy![key] = ori;

(element as any)[key] = handler[key];
}
});

return element;
}

export default function useProxyImperativeHandle<
NativeELementType extends HTMLElement,
ReturnRefType extends { nativeElement: NativeELementType },
>(ref: Ref<any> | undefined, init: () => ReturnRefType) {
return useImperativeHandle(ref, () => {
const refObj = init();
const { nativeElement } = refObj;

if (typeof Proxy !== 'undefined') {
return new Proxy(nativeElement, {
get(obj: any, prop: any) {
if ((refObj as any)[prop]) {
return (refObj as any)[prop];
}

return Reflect.get(obj, prop);
},
});
}

// Fallback of IE
return fillProxy(nativeElement, refObj);
});
}
19 changes: 17 additions & 2 deletions components/table/InternalTable.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import * as React from 'react';
import classNames from 'classnames';
import { INTERNAL_HOOKS, type TableProps as RcTableProps } from 'rc-table';
import {
INTERNAL_HOOKS,
type Reference as RcReference,
type TableProps as RcTableProps,
} from 'rc-table';
import { convertChildrenToColumns } from 'rc-table/lib/hooks/useColumns';
import omit from 'rc-util/lib/omit';

import useProxyImperativeHandle from '../_util/hooks/useProxyImperativeHandle';
import type { Breakpoint } from '../_util/responsiveObserver';
import scrollTo from '../_util/scrollTo';
import type { AnyObject } from '../_util/type';
Expand Down Expand Up @@ -222,6 +227,15 @@ const InternalTable = <RecordType extends AnyObject = AnyObject>(
// ============================ Width =============================
const getContainerWidth = useContainerWidth(prefixCls);

// ============================= Refs =============================
const rootRef = React.useRef<HTMLDivElement>(null);
const tblRef = React.useRef<RcReference>(null);

useProxyImperativeHandle(ref, () => ({
...tblRef.current!,
nativeElement: rootRef.current!,
}));

// ============================ RowKey ============================
const getRowKey = React.useMemo<GetRowKey<RecordType>>(() => {
if (typeof rowKey === 'function') {
Expand Down Expand Up @@ -571,12 +585,13 @@ const InternalTable = <RecordType extends AnyObject = AnyObject>(
}

return wrapSSR(
<div ref={ref} className={wrapperClassNames} style={mergedStyle}>
<div ref={rootRef} className={wrapperClassNames} style={mergedStyle}>
<Spin spinning={false} {...spinProps}>
{topPaginationNode}
<TableComponent
{...virtualProps}
{...tableProps}
ref={tblRef}
columns={mergedColumns as RcTableProps<RecordType>['columns']}
direction={direction}
expandable={mergedExpandable}
Expand Down
9 changes: 5 additions & 4 deletions components/table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { EXPAND_COLUMN, Summary } from 'rc-table';
import * as React from 'react';
import { EXPAND_COLUMN, Summary, type Reference } from 'rc-table';

import type { AnyObject } from '../_util/type';
import Column from './Column';
import ColumnGroup from './ColumnGroup';
import type { TableProps } from './InternalTable';
import InternalTable from './InternalTable';
import {
SELECTION_ALL,
SELECTION_COLUMN,
SELECTION_INVERT,
SELECTION_NONE,
} from './hooks/useSelection';
import type { RefTable } from './interface';
import type { TableProps } from './InternalTable';
import InternalTable from './InternalTable';

const Table = <RecordType extends AnyObject = AnyObject>(
props: TableProps<RecordType>,
ref: React.Ref<HTMLDivElement>,
ref: React.Ref<Reference>,
) => {
const renderTimesRef = React.useRef<number>(0);
renderTimesRef.current += 1;
Expand Down
22 changes: 22 additions & 0 deletions components/table/__tests__/Table.IE.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';

import type { TableRef } from '..';
import Table from '..';
import { render } from '../../../tests/utils';

describe('Table.IE', () => {
beforeAll(() => {
window.Proxy = undefined as any;
global.Proxy = undefined as any;
});

it('support reference', () => {
const tblRef = React.createRef<TableRef>();
const { container } = render(<Table ref={tblRef} />);

const wrapDom = container.querySelector('.ant-table-wrapper')!;

expect(tblRef.current).toBe(wrapDom);
expect(tblRef.current?.nativeElement).toBe(wrapDom);
});
});
16 changes: 14 additions & 2 deletions components/table/__tests__/Table.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useRef } from 'react';
import { ConfigProvider } from 'antd';
import type { TableProps } from '..';

import type { TableProps, TableRef } from '..';
import Table from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
Expand Down Expand Up @@ -279,7 +280,7 @@ describe('Table', () => {
},
];
const Wrapper: React.FC = () => {
const ref = React.useRef<HTMLDivElement>(null);
const ref = React.useRef<any>(null);
return <Table ref={ref} columns={columns} />;
};
render(<Wrapper />);
Expand Down Expand Up @@ -401,4 +402,15 @@ describe('Table', () => {
await waitFakeTimer();
expect(container.querySelector('.ant-dropdown')).toBeTruthy();
});

it('support reference', () => {
const tblRef = React.createRef<TableRef>();
const { container } = render(<Table ref={tblRef} />);

const wrapDom = container.querySelector('.ant-table-wrapper')!;

expect(tblRef.current).toHaveClass('ant-table-wrapper');
expect(tblRef.current?.nativeElement).toBe(wrapDom);
expect(tblRef.current?.scrollTo instanceof Function).toBeTruthy();
});
});
14 changes: 13 additions & 1 deletion components/table/demo/virtual-list.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Segmented, Space, Switch, Table, Typography } from 'antd';
import { Button, Segmented, Space, Switch, Table, Typography } from 'antd';
import type { TableProps } from 'antd';

interface RecordType {
Expand Down Expand Up @@ -114,6 +114,7 @@ const App = () => {
const [empty, setEmpty] = React.useState(false);
const [count, setCount] = React.useState(10000);

const tblRef: Parameters<typeof Table>[0]['ref'] = React.useRef(null);
const data = React.useMemo(() => getData(count), [count]);

const mergedColumns = React.useMemo<typeof fixedColumns>(() => {
Expand Down Expand Up @@ -186,6 +187,16 @@ const App = () => {
},
]}
/>

{data.length >= 999 && (
<Button
onClick={() => {
tblRef.current?.scrollTo({ index: 999 });
}}
>
Scroll To index 999
</Button>
)}
</Space>

<Table
Expand All @@ -196,6 +207,7 @@ const App = () => {
rowKey="id"
dataSource={empty ? [] : data}
pagination={false}
ref={tblRef}
rowSelection={
expanded
? undefined
Expand Down
7 changes: 7 additions & 0 deletions components/table/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ Common props ref:[Common props](/docs/react/common-props)
| onRow | Set props on per row | function(record, index) | - | |
| virtual | Support virtual list | boolean | - | 5.9.0 |

### Table ref

| Property | Description | Type | Version |
| --- | --- | --- | --- |
| nativeElement | The wrap element | HTMLDivElement | 5.11.0 |
| scrollTo | Trigger to scroll to target position. `key` match with record `rowKey` | (config: { index?: number, key?: React.Key, top?: number }) => void | 5.11.0 |

#### onRow usage

Same as `onRow` `onHeaderRow` `onCell` `onHeaderCell`
Expand Down
1 change: 1 addition & 0 deletions components/table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Table from './Table';

export type { ColumnProps } from './Column';
export type { ColumnGroupType, ColumnType, ColumnsType } from './interface';
export type { Reference as TableRef } from 'rc-table';
export type { TablePaginationConfig, TableProps };

export default Table;
10 changes: 8 additions & 2 deletions components/table/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ const columns = [
| onRow | 设置行属性 | function(record, index) | - | |
| virtual | 支持虚拟列表 | boolean | - | 5.9.0 |

### Table ref

| 参数 | 说明 | 类型 | 版本 |
| --- | --- | --- | --- |
| nativeElement | 最外层 div 元素 | HTMLDivElement | 5.11.0 |
| scrollTo | 滚动到目标位置(设置 `key` 时为 Record 对应的 `rowKey`| (config: { index?: number, key?: React.Key, top?: number }) => void | 5.11.0 |

#### onRow 用法

适用于 `onRow` `onHeaderRow` `onCell` `onHeaderCell`
Expand Down Expand Up @@ -170,7 +177,6 @@ const columns = [

列描述数据对象,是 columns 中的一项,Column 使用相同的 API。

<!-- prettier-ignore -->
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| align | 设置列的对齐方式 | `left` \| `right` \| `center` | `left` | |
Expand Down Expand Up @@ -205,7 +211,7 @@ const columns = [
| width | 列宽度([指定了也不生效?](https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241)| string \| number | - | |
| onCell | 设置单元格属性 | function(record, rowIndex) | - | |
| onFilter | 本地模式下,确定筛选的运行函数 | function | - | |
| onFilterDropdownOpenChange | 自定义筛选菜单可见变化时调用 | function(visible) {} | - | | |
| onFilterDropdownOpenChange | 自定义筛选菜单可见变化时调用 | function(visible) {} | - | |
| onHeaderCell | 设置头部单元格属性 | function(column) | - | |

### ColumnGroup
Expand Down
5 changes: 3 additions & 2 deletions components/table/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type * as React from 'react';
import type { Reference } from 'rc-table';
import type {
FixedType,
GetComponentProps,
Expand All @@ -16,12 +17,12 @@ import type { INTERNAL_SELECTION_ITEM } from './hooks/useSelection';
import type { InternalTableProps, TableProps } from './InternalTable';

export type RefTable = <RecordType extends AnyObject = AnyObject>(
props: React.PropsWithChildren<TableProps<RecordType>> & { ref?: React.Ref<HTMLDivElement> },
props: React.PropsWithChildren<TableProps<RecordType>> & { ref?: React.Ref<Reference> },
) => React.ReactElement;

export type RefInternalTable = <RecordType extends AnyObject = AnyObject>(
props: React.PropsWithChildren<InternalTableProps<RecordType>> & {
ref?: React.Ref<HTMLDivElement>;
ref?: React.Ref<Reference>;
},
) => React.ReactElement;

Expand Down
2 changes: 1 addition & 1 deletion components/theme/util/statistic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default function statisticToken<T extends object>(token: T) {
let flush: (componentName: string, componentToken: Record<string, string | number>) => void =
noop;

if (enableStatistic) {
if (enableStatistic && typeof Proxy !== 'undefined') {
tokenKeys = new Set<string>();

proxy = new Proxy(token, {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
"rc-slider": "~10.3.0",
"rc-steps": "~6.0.1",
"rc-switch": "~4.1.0",
"rc-table": "~7.34.4",
"rc-table": "~7.35.1",
"rc-tabs": "~12.12.1",
"rc-textarea": "~1.5.1",
"rc-tooltip": "~6.1.0",
Expand Down

0 comments on commit bef9539

Please sign in to comment.