Skip to content

Commit 5db3b86

Browse files
authored
feat: Cross fixed column support (#1073)
* chore: init * chore: refactor of hooks * chore: basic fixed offsets * chore: support multiple fixed * test: update test * chore: style * test: update test * chore: ci * chore: ci * update deps
1 parent 4b67382 commit 5db3b86

File tree

12 files changed

+3283
-126
lines changed

12 files changed

+3283
-126
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
key: lock-${{ github.sha }}
2525

2626
- name: create package-lock.json
27-
run: npm i --package-lock-only --ignore-scripts
27+
run: npm i --package-lock-only --ignore-scripts --legacy-peer-deps
2828

2929
- name: hack for singe file
3030
run: |
@@ -41,7 +41,7 @@ jobs:
4141

4242
- name: install
4343
if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
44-
run: npm ci
44+
run: npm ci --legacy-peer-deps
4545

4646
lint:
4747
runs-on: ubuntu-latest

assets/index.less

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@
5656
}
5757

5858
// ================== Cell ==================
59+
&-fixed-column-gapped {
60+
.@{tablePrefixCls}-cell-fix-left-last::after,
61+
.@{tablePrefixCls}-cell-fix-right-first::after {
62+
display: none !important;
63+
}
64+
}
65+
5966
&-cell {
6067
background: #f4f4f4;
6168

docs/examples/fixedColumns.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const columns: ColumnType<RecordType>[] = [
3434
{ title: 'title8', dataIndex: 'b', key: 'h' },
3535
{ title: 'title9', dataIndex: 'b', key: 'i' },
3636
{ title: 'title10', dataIndex: 'b', key: 'j' },
37-
{ title: 'title11', dataIndex: 'b', key: 'k', width: 50, fixed: 'right' },
37+
{ title: 'title11', dataIndex: 'b', key: 'k', width: 50 },
3838
{ title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' },
3939
];
4040

@@ -65,6 +65,7 @@ const Demo = () => {
6565
Scroll Y
6666
</label>
6767
<Table
68+
// direction="rtl"
6869
columns={columns}
6970
expandedRowRender={({ b, c }) => b || c}
7071
scroll={{ x: 1200, y: scrollY ? 200 : null }}

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,16 @@
6262
},
6363
"devDependencies": {
6464
"@rc-component/father-plugin": "^1.0.2",
65-
"@testing-library/jest-dom": "^5.16.5",
65+
"@testing-library/jest-dom": "^6.4.0",
6666
"@testing-library/react": "^12.1.5",
6767
"@types/enzyme": "^3.10.5",
68-
"@types/react": "^18.0.28",
6968
"@types/jest": "^29.5.0",
69+
"@types/react": "^18.0.28",
7070
"@types/react-dom": "^18.0.5",
7171
"@types/responselike": "^1.0.0",
7272
"@types/styled-components": "^5.1.32",
73-
"@types/testing-library__jest-dom": "^6.0.0",
7473
"@umijs/fabric": "^4.0.1",
75-
"@vitest/coverage-c8": "^0.31.0",
74+
"@vitest/coverage-v8": "^1.2.2",
7675
"cross-env": "^7.0.0",
7776
"dumi": "^2.1.3",
7877
"enzyme": "^3.1.0",
@@ -105,7 +104,7 @@
105104
"regenerator-runtime": "^0.14.0",
106105
"styled-components": "^6.1.1",
107106
"typescript": "~5.3.0",
108-
"vitest": "^0.31.0"
107+
"vitest": "^1.2.2"
109108
},
110109
"lint-staged": {
111110
"**/*.{js,jsx,tsx,ts,md,json}": [

src/Table.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ function Table<RecordType extends DefaultRecordType>(
280280
const scrollX = scroll?.x;
281281
const [componentWidth, setComponentWidth] = React.useState(0);
282282

283-
const [columns, flattenColumns, flattenScrollX] = useColumns(
283+
const [columns, flattenColumns, flattenScrollX, hasGapFixed] = useColumns(
284284
{
285285
...props,
286286
...expandableConfig,
@@ -348,7 +348,7 @@ function Table<RecordType extends DefaultRecordType>(
348348
const colsKeys = getColumnsKey(flattenColumns);
349349
const pureColWidths = colsKeys.map(columnKey => colsWidths.get(columnKey));
350350
const colWidths = React.useMemo(() => pureColWidths, [pureColWidths.join('_')]);
351-
const stickyOffsets = useStickyOffsets(colWidths, flattenColumns.length, direction);
351+
const stickyOffsets = useStickyOffsets(colWidths, flattenColumns, direction);
352352
const fixHeader = scroll && validateValue(scroll.y);
353353
const horizonScroll = (scroll && validateValue(mergedScrollX)) || Boolean(expandableConfig.fixed);
354354
const fixColumn = horizonScroll && flattenColumns.some(({ fixed }) => fixed);
@@ -750,6 +750,7 @@ function Table<RecordType extends DefaultRecordType>(
750750
[`${prefixCls}-fixed-header`]: fixHeader,
751751
/** No used but for compatible */
752752
[`${prefixCls}-fixed-column`]: fixColumn,
753+
[`${prefixCls}-fixed-column-gapped`]: fixColumn && hasGapFixed,
753754
[`${prefixCls}-scroll-horizontal`]: horizonScroll,
754755
[`${prefixCls}-has-fix-left`]: flattenColumns[0] && flattenColumns[0].fixed,
755756
[`${prefixCls}-has-fix-right`]:

src/hooks/useColumns/index.tsx

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -88,30 +88,6 @@ function flatColumns<RecordType>(
8888
}, []);
8989
}
9090

91-
function warningFixed(flattenColumns: readonly { fixed?: FixedType }[]) {
92-
let allFixLeft = true;
93-
for (let i = 0; i < flattenColumns.length; i += 1) {
94-
const col = flattenColumns[i];
95-
if (allFixLeft && col.fixed !== 'left') {
96-
allFixLeft = false;
97-
} else if (!allFixLeft && col.fixed === 'left') {
98-
warning(false, `Index ${i - 1} of \`columns\` missing \`fixed='left'\` prop.`);
99-
break;
100-
}
101-
}
102-
103-
let allFixRight = true;
104-
for (let i = flattenColumns.length - 1; i >= 0; i -= 1) {
105-
const col = flattenColumns[i];
106-
if (allFixRight && col.fixed !== 'right') {
107-
allFixRight = false;
108-
} else if (!allFixRight && col.fixed === 'right') {
109-
warning(false, `Index ${i + 1} of \`columns\` missing \`fixed='right'\` prop.`);
110-
break;
111-
}
112-
}
113-
}
114-
11591
function revertForRtl<RecordType>(columns: ColumnsType<RecordType>): ColumnsType<RecordType> {
11692
return columns.map(column => {
11793
const { fixed, ...restProps } = column;
@@ -176,6 +152,7 @@ function useColumns<RecordType>(
176152
columns: ColumnsType<RecordType>,
177153
flattenColumns: readonly ColumnType<RecordType>[],
178154
realScrollWidth: undefined | number,
155+
hasGapFixed: boolean,
179156
] {
180157
const baseColumns = React.useMemo<ColumnsType<RecordType>>(() => {
181158
const newColumns = columns || convertChildrenToColumns(children) || [];
@@ -294,10 +271,40 @@ function useColumns<RecordType>(
294271
return flatColumns(mergedColumns);
295272
}, [mergedColumns, direction, scrollWidth]);
296273

297-
// Only check out of production since it's waste for each render
298-
if (process.env.NODE_ENV !== 'production') {
299-
warningFixed(direction === 'rtl' ? flattenColumns.slice().reverse() : flattenColumns);
300-
}
274+
// ========================= Gap Fixed ========================
275+
const hasGapFixed = React.useMemo(() => {
276+
// Fixed: left, since old browser not support `findLastIndex`, we should use reverse loop
277+
let lastLeftIndex = -1;
278+
for (let i = flattenColumns.length - 1; i >= 0; i -= 1) {
279+
const colFixed = flattenColumns[i].fixed;
280+
if (colFixed === 'left' || colFixed === true) {
281+
lastLeftIndex = i;
282+
break;
283+
}
284+
}
285+
286+
if (lastLeftIndex >= 0) {
287+
for (let i = 0; i <= lastLeftIndex; i += 1) {
288+
const colFixed = flattenColumns[i].fixed;
289+
if (colFixed !== 'left' && colFixed !== true) {
290+
return true;
291+
}
292+
}
293+
}
294+
295+
// Fixed: right
296+
const firstRightIndex = flattenColumns.findIndex(({ fixed: colFixed }) => colFixed === 'right');
297+
if (firstRightIndex >= 0) {
298+
for (let i = firstRightIndex; i < flattenColumns.length; i += 1) {
299+
const colFixed = flattenColumns[i].fixed;
300+
if (colFixed !== 'right') {
301+
return true;
302+
}
303+
}
304+
}
305+
306+
return false;
307+
}, [flattenColumns]);
301308

302309
// ========================= FillWidth ========================
303310
const [filledColumns, realScrollWidth] = useWidthColumns(
@@ -306,7 +313,7 @@ function useColumns<RecordType>(
306313
clientWidth,
307314
);
308315

309-
return [mergedColumns, filledColumns, realScrollWidth];
316+
return [mergedColumns, filledColumns, realScrollWidth, hasGapFixed];
310317
}
311318

312319
export default useColumns;

src/hooks/useStickyOffsets.ts

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
11
import { useMemo } from 'react';
2-
import type { Direction, StickyOffsets } from '../interface';
2+
import type { ColumnType, Direction, StickyOffsets } from '../interface';
33

44
/**
55
* Get sticky column offset width
66
*/
7-
function useStickyOffsets(colWidths: number[], columnCount: number, direction: Direction) {
7+
function useStickyOffsets<RecordType>(
8+
colWidths: number[],
9+
flattenColumns: readonly ColumnType<RecordType>[],
10+
direction: Direction,
11+
) {
812
const stickyOffsets: StickyOffsets = useMemo(() => {
9-
const leftOffsets: number[] = [];
10-
const rightOffsets: number[] = [];
11-
let left = 0;
12-
let right = 0;
13-
14-
for (let start = 0; start < columnCount; start += 1) {
15-
if (direction === 'rtl') {
16-
// Left offset
17-
rightOffsets[start] = right;
18-
right += colWidths[start] || 0;
19-
20-
// Right offset
21-
const end = columnCount - start - 1;
22-
leftOffsets[end] = left;
23-
left += colWidths[end] || 0;
24-
} else {
25-
// Left offset
26-
leftOffsets[start] = left;
27-
left += colWidths[start] || 0;
28-
29-
// Right offset
30-
const end = columnCount - start - 1;
31-
rightOffsets[end] = right;
32-
right += colWidths[end] || 0;
13+
const columnCount = flattenColumns.length;
14+
15+
const getOffsets = (startIndex: number, endIndex: number, offset: number) => {
16+
const offsets: number[] = [];
17+
let total = 0;
18+
19+
for (let i = startIndex; i !== endIndex; i += offset) {
20+
offsets.push(total);
21+
22+
if (flattenColumns[i].fixed) {
23+
total += colWidths[i] || 0;
24+
}
3325
}
34-
}
3526

36-
return {
37-
left: leftOffsets,
38-
right: rightOffsets,
27+
return offsets;
3928
};
40-
}, [colWidths, columnCount, direction]);
29+
30+
const startOffsets = getOffsets(0, columnCount, 1);
31+
const endOffsets = getOffsets(columnCount - 1, -1, -1).reverse();
32+
33+
return direction === 'rtl'
34+
? {
35+
left: endOffsets,
36+
right: startOffsets,
37+
}
38+
: {
39+
left: startOffsets,
40+
right: endOffsets,
41+
};
42+
}, [colWidths, flattenColumns, direction]);
4143

4244
return stickyOffsets;
4345
}

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EXPAND_COLUMN, INTERNAL_HOOKS } from './constant';
22
import { FooterComponents as Summary } from './Footer';
3-
import type { ColumnType, Reference } from './interface';
3+
import type { ColumnType, ColumnsType, Reference } from './interface';
44
import Column from './sugar/Column';
55
import ColumnGroup from './sugar/ColumnGroup';
66
import type { TableProps } from './Table';
@@ -23,6 +23,7 @@ export {
2323
type VirtualTableProps,
2424
type Reference,
2525
type ColumnType,
26+
type ColumnsType,
2627
};
2728

2829
export default Table;

0 commit comments

Comments
 (0)