Skip to content

Commit 9a2e5e5

Browse files
committed
chore: support scroll range
1 parent fae1a2c commit 9a2e5e5

File tree

10 files changed

+163
-71
lines changed

10 files changed

+163
-71
lines changed

assets/index.less

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,13 @@
363363
position: absolute;
364364
top: 0;
365365
bottom: 0;
366-
left: 0;
367-
width: 10px;
368-
background: red;
366+
width: 6px;
369367
z-index: 10;
368+
background: rgba(0, 0, 0, 0.02);
369+
transition: background 0.3s;
370+
371+
&-ping {
372+
background: rgba(0, 0, 0, 0.2);
373+
}
370374
}
371375
}

docs/examples/stack-columns.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const columns: ColumnType<RecordType>[] = [
5050
title: 'title12',
5151
dataIndex: 'b',
5252
key: 'l',
53-
width: 100,
53+
width: 150,
5454
// Fixed
5555
fixed: 'end',
5656
},
@@ -66,18 +66,16 @@ const data: RecordType[] = [
6666
];
6767

6868
const Demo = () => {
69-
const [scrollY, setScrollY] = React.useState(true);
70-
7169
return (
7270
<div style={{ width: 800 }}>
73-
<label>
74-
<input type="checkbox" checked={scrollY} onChange={() => setScrollY(!scrollY)} />
75-
Scroll Y
76-
</label>
7771
<Table
7872
// direction="rtl"
7973
columns={columns}
80-
scroll={{ x: 1200 }}
74+
scroll={{
75+
x: 1200,
76+
// Scroll Y
77+
y: 100,
78+
}}
8179
data={data}
8280
/>
8381
</div>

src/FixedColumnShadows.tsx

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,101 @@
11
import * as React from 'react';
22
import { getTargetScrollBarSize } from '@rc-component/util/lib/getScrollBarSize';
3+
import type { ColumnType, StickyOffsets } from './interface';
4+
import { getCellFixedInfo } from './utils/fixUtil';
5+
import classNames from 'classnames';
36

47
export interface FixedColumnShadowsProps {
58
prefixCls: string;
9+
stickyOffsets: StickyOffsets;
10+
flattenColumns: readonly ColumnType<any>[];
11+
columnWidths: number[];
12+
fixHeader: boolean;
13+
scrollInfo: [scrollLeft: number, scrollRange: number];
614
}
715

8-
export default function FixedColumnShadows(props: FixedColumnShadowsProps) {
9-
const { prefixCls } = props;
16+
const FixedColumnShadows = React.memo((props: FixedColumnShadowsProps) => {
17+
const { prefixCls, stickyOffsets, flattenColumns, columnWidths, fixHeader, scrollInfo } = props;
1018
const holderRef = React.useRef<HTMLDivElement>(null);
1119

1220
const [scrollBarSize, setScrollBarSize] = React.useState<[height: number, width: number]>([0, 0]);
1321

22+
// console.log('>>>>', stickyOffsets, flattenColumns);
23+
24+
type FixedShadowInfo = [index: number, offset: number, showShadowScrollDeadline: number];
25+
26+
const [fixStartShadowList, fixEndShadowList] = React.useMemo<
27+
[FixedShadowInfo[], FixedShadowInfo[]]
28+
>(() => {
29+
const nextFixStartShadowList: FixedShadowInfo[] = [];
30+
const nextFixEndShadowList: FixedShadowInfo[] = [];
31+
32+
let startShowShadowScrollDeadline = 0;
33+
let endShowShadowScrollDeadline = 0;
34+
35+
for (let i = 0; i < flattenColumns.length; i += 1) {
36+
const revertI = flattenColumns.length - 1 - i;
37+
38+
const { fixStart } = getCellFixedInfo(i, i, flattenColumns, stickyOffsets);
39+
const { fixEnd } = getCellFixedInfo(revertI, revertI, flattenColumns, stickyOffsets);
40+
41+
if (typeof fixStart === 'number') {
42+
nextFixStartShadowList.push([i, fixStart, startShowShadowScrollDeadline]);
43+
} else {
44+
startShowShadowScrollDeadline += columnWidths[i];
45+
}
46+
47+
if (typeof fixEnd === 'number') {
48+
nextFixEndShadowList.push([revertI, fixEnd, endShowShadowScrollDeadline]);
49+
} else {
50+
endShowShadowScrollDeadline += columnWidths[revertI];
51+
}
52+
}
53+
54+
return [nextFixStartShadowList, nextFixEndShadowList];
55+
}, [stickyOffsets, flattenColumns, columnWidths]);
56+
57+
// console.log('=>', fixStartShadowList, fixEndShadowList);
58+
1459
React.useEffect(() => {
1560
if (holderRef.current) {
1661
const { height, width } = getTargetScrollBarSize(holderRef.current);
17-
setScrollBarSize([height, width]);
62+
setScrollBarSize([height, fixHeader ? width : 0]);
1863
}
19-
}, []);
64+
}, [columnWidths, fixHeader]);
2065

2166
const fixedColumnShadowCls = `${prefixCls}-fixed-column-shadow`;
2267

68+
// ===================== Render =====================
69+
const [scrollLeft, scrollRange] = scrollInfo;
70+
71+
// console.log('??????', scrollLeft, fixStartShadowList[1]);
72+
console.log('Scroll info:', scrollLeft, scrollRange);
73+
74+
const renderShadow = (position: 'Start' | 'End', shadowList: FixedShadowInfo[]) => {
75+
return shadowList.map(([index, offset, showShadowScrollDeadline]) => {
76+
const calcOffset = offset + columnWidths[index];
77+
78+
const showShadow =
79+
position === 'Start'
80+
? scrollLeft > showShadowScrollDeadline
81+
: scrollRange - scrollLeft > showShadowScrollDeadline;
82+
83+
return calcOffset ? (
84+
<div
85+
key={`${position}-${index}`}
86+
className={classNames(
87+
fixedColumnShadowCls,
88+
`${fixedColumnShadowCls}-${position.toLowerCase()}`,
89+
showShadow && `${fixedColumnShadowCls}-ping`,
90+
)}
91+
style={{
92+
[`insetInline${position}`]: calcOffset,
93+
}}
94+
/>
95+
) : null;
96+
});
97+
};
98+
2399
return (
24100
<div
25101
className={`${fixedColumnShadowCls}-holder`}
@@ -30,7 +106,14 @@ export default function FixedColumnShadows(props: FixedColumnShadowsProps) {
30106
insetInlineEnd: scrollBarSize[1],
31107
}}
32108
>
33-
<div className={fixedColumnShadowCls} />
109+
{renderShadow('Start', fixStartShadowList)}
110+
{renderShadow('End', fixEndShadowList)}
34111
</div>
35112
);
113+
});
114+
115+
if (process.env.NODE_ENV !== 'production') {
116+
FixedColumnShadows.displayName = 'FixedColumnShadows';
36117
}
118+
119+
export default FixedColumnShadows;

src/FixedHolder/index.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,22 @@ const FixedHolder = React.forwardRef<HTMLDivElement, FixedHeaderProps<any>>((pro
8585
function onWheel(e: WheelEvent) {
8686
const { currentTarget, deltaX } = e as unknown as React.WheelEvent<HTMLDivElement>;
8787
if (deltaX) {
88-
onScroll({ currentTarget, scrollLeft: currentTarget.scrollLeft + deltaX });
88+
const { scrollLeft, scrollWidth, clientWidth } = currentTarget;
89+
const maxScrollWidth = scrollWidth - clientWidth;
90+
let nextScroll = scrollLeft + deltaX;
91+
92+
if (direction === 'rtl') {
93+
nextScroll = Math.max(-maxScrollWidth, nextScroll);
94+
nextScroll = Math.min(0, nextScroll);
95+
} else {
96+
nextScroll = Math.min(maxScrollWidth, nextScroll);
97+
nextScroll = Math.max(0, nextScroll);
98+
}
99+
100+
onScroll({
101+
currentTarget,
102+
scrollLeft: nextScroll,
103+
});
89104
e.preventDefault();
90105
}
91106
}
@@ -124,13 +139,15 @@ const FixedHolder = React.forwardRef<HTMLDivElement, FixedHeaderProps<any>>((pro
124139

125140
// Calculate the sticky offsets
126141
const headerStickyOffsets = useMemo(() => {
127-
const { right, left } = stickyOffsets;
142+
const { start, end } = stickyOffsets;
128143
return {
129144
...stickyOffsets,
130-
left:
131-
direction === 'rtl' ? [...left.map(width => width + combinationScrollBarSize), 0] : left,
132-
right:
133-
direction === 'rtl' ? right : [...right.map(width => width + combinationScrollBarSize), 0],
145+
// left:
146+
// direction === 'rtl' ? [...left.map(width => width + combinationScrollBarSize), 0] : left,
147+
// right:
148+
// direction === 'rtl' ? right : [...right.map(width => width + combinationScrollBarSize), 0],
149+
start: start,
150+
end: [...end.map(width => width + combinationScrollBarSize), 0],
134151
isSticky,
135152
};
136153
}, [combinationScrollBarSize, stickyOffsets, isSticky]);

src/Footer/Cell.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function SummaryCell({
2323
rowSpan,
2424
align,
2525
}: SummaryCellProps) {
26-
const { prefixCls, direction } = useContext(TableContext, ['prefixCls', 'direction']);
26+
const { prefixCls } = useContext(TableContext, ['prefixCls']);
2727
const { scrollColumnIndex, stickyOffsets, flattenColumns } = React.useContext(SummaryContext);
2828
const lastIndex = index + colSpan - 1;
2929
const mergedColSpan = lastIndex + 1 === scrollColumnIndex ? colSpan + 1 : colSpan;
@@ -33,7 +33,6 @@ export default function SummaryCell({
3333
index + mergedColSpan - 1,
3434
flattenColumns,
3535
stickyOffsets,
36-
direction,
3736
);
3837

3938
return (

src/Header/HeaderRow.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const HeaderRow = <RecordType extends any>(props: RowProps<RecordType>) => {
3232
onHeaderRow,
3333
index,
3434
} = props;
35-
const { prefixCls, direction } = useContext(TableContext, ['prefixCls', 'direction']);
35+
const { prefixCls } = useContext(TableContext, ['prefixCls']);
3636
let rowProps: React.HTMLAttributes<HTMLElement>;
3737
if (onHeaderRow) {
3838
rowProps = onHeaderRow(
@@ -52,7 +52,6 @@ const HeaderRow = <RecordType extends any>(props: RowProps<RecordType>) => {
5252
cell.colEnd,
5353
flattenColumns,
5454
stickyOffsets,
55-
direction,
5655
);
5756

5857
let additionalProps: React.HTMLAttributes<HTMLElement>;

src/Table.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import ColumnGroup from './sugar/ColumnGroup';
7777
import { getColumnsKey, validateValue, validNumberValue } from './utils/valueUtil';
7878
import { getDOM } from '@rc-component/util/lib/Dom/findDOMNode';
7979
import FixedColumnShadows from './FixedColumnShadows';
80+
import isEqual from '@rc-component/util/lib/isEqual';
8081

8182
export const DEFAULT_PREFIX = 'rc-table';
8283

@@ -437,6 +438,9 @@ function Table<RecordType extends DefaultRecordType>(
437438
}
438439
}
439440

441+
type ScrollInfoType = [scrollLeft: number, scrollRange: number];
442+
const [scrollInfo, setScrollInfo] = React.useState<ScrollInfoType>([0, 0]);
443+
440444
const onInternalScroll = useEvent(
441445
({ currentTarget, scrollLeft }: { currentTarget: HTMLElement; scrollLeft?: number }) => {
442446
const isRTL = direction === 'rtl';
@@ -461,6 +465,12 @@ function Table<RecordType extends DefaultRecordType>(
461465
? mergedScrollX
462466
: measureTarget.scrollWidth;
463467
const clientWidth = measureTarget.clientWidth;
468+
469+
setScrollInfo(ori => {
470+
const nextScrollInfo: ScrollInfoType = [mergedScrollLeft, scrollWidth - clientWidth];
471+
return isEqual(ori, nextScrollInfo) ? ori : nextScrollInfo;
472+
});
473+
464474
// There is no space to scroll
465475
if (scrollWidth === clientWidth) {
466476
setPingedLeft(false);
@@ -788,7 +798,17 @@ function Table<RecordType extends DefaultRecordType>(
788798
{title && <Panel className={`${prefixCls}-title`}>{title(mergedData)}</Panel>}
789799
<div ref={scrollBodyContainerRef} className={`${prefixCls}-container`}>
790800
{groupTableNode}
791-
<FixedColumnShadows prefixCls={prefixCls} />
801+
802+
{horizonScroll && (
803+
<FixedColumnShadows
804+
prefixCls={prefixCls}
805+
stickyOffsets={stickyOffsets}
806+
flattenColumns={flattenColumns}
807+
columnWidths={colWidths}
808+
fixHeader={fixHeader}
809+
scrollInfo={scrollInfo}
810+
/>
811+
)}
792812
</div>
793813
{footer && <Panel className={`${prefixCls}-footer`}>{footer(mergedData)}</Panel>}
794814
</div>
@@ -798,7 +818,7 @@ function Table<RecordType extends DefaultRecordType>(
798818
fullTable = <ResizeObserver onResize={onFullTableResize}>{fullTable}</ResizeObserver>;
799819
}
800820

801-
const fixedInfoList = useFixedInfo(flattenColumns, stickyOffsets, direction);
821+
const fixedInfoList = useFixedInfo(flattenColumns, stickyOffsets);
802822

803823
const TableContextValue = React.useMemo(
804824
() => ({

src/hooks/useFixedInfo.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import useMemo from '@rc-component/util/lib/hooks/useMemo';
22
import isEqual from '@rc-component/util/lib/isEqual';
3-
import type { ColumnType, Direction, StickyOffsets } from '../interface';
3+
import type { ColumnType, StickyOffsets } from '../interface';
44
import { getCellFixedInfo } from '../utils/fixUtil';
55

66
export default function useFixedInfo<RecordType>(
77
flattenColumns: readonly ColumnType<RecordType>[],
88
stickyOffsets: StickyOffsets,
9-
direction: Direction,
109
) {
1110
const fixedInfoList = flattenColumns.map((_, colIndex) =>
12-
getCellFixedInfo(colIndex, colIndex, flattenColumns, stickyOffsets, direction),
11+
getCellFixedInfo(colIndex, colIndex, flattenColumns, stickyOffsets),
1312
);
1413

1514
return useMemo(

src/hooks/useStickyOffsets.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import { useMemo } from 'react';
2-
import type {
3-
ColumnType,
4-
// Direction,
5-
StickyOffsets,
6-
} from '../interface';
2+
import type { ColumnType, StickyOffsets } from '../interface';
73

84
/**
95
* Get sticky column offset width
106
*/
117
function useStickyOffsets<RecordType>(
128
colWidths: number[],
139
flattenColumns: readonly ColumnType<RecordType>[],
14-
// direction: Direction,
1510
) {
1611
const stickyOffsets: StickyOffsets = useMemo(() => {
1712
const columnCount = flattenColumns.length;
@@ -34,24 +29,11 @@ function useStickyOffsets<RecordType>(
3429
const startOffsets = getOffsets(0, columnCount, 1);
3530
const endOffsets = getOffsets(columnCount - 1, -1, -1).reverse();
3631

37-
// return direction === 'rtl'
38-
// ? {
39-
// left: endOffsets,
40-
// right: startOffsets,
41-
// }
42-
// : {
43-
// left: startOffsets,
44-
// right: endOffsets,
45-
// };
4632
return {
4733
start: startOffsets,
4834
end: endOffsets,
4935
};
50-
}, [
51-
colWidths,
52-
flattenColumns,
53-
// , direction
54-
]);
36+
}, [colWidths, flattenColumns]);
5537

5638
return stickyOffsets;
5739
}

0 commit comments

Comments
 (0)