Skip to content

Commit a3f72ab

Browse files
nstepienadityatoshniwal
authored andcommitted
Handle column sorting events directly on the header cell (adazzle#3316)
* Handle column sorting events directly on the header cell * fix main styles * add resize handler element * rm newline
1 parent adebe80 commit a3f72ab

File tree

6 files changed

+60
-85
lines changed

6 files changed

+60
-85
lines changed

src/HeaderCell.tsx

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
import { css } from '@linaria/core';
22

33
import { useRovingTabIndex } from './hooks';
4-
import { clampColumnWidth, getCellClassname, getCellStyle } from './utils';
4+
import { clampColumnWidth, getCellClassname, getCellStyle, stopPropagation } from './utils';
55
import type { CalculatedColumn, SortColumn } from './types';
66
import type { HeaderRowProps } from './HeaderRow';
77
import defaultRenderHeaderCell from './renderHeaderCell';
88

9+
const cellSortableClassname = css`
10+
@layer rdg.HeaderCell {
11+
cursor: pointer;
12+
}
13+
`;
14+
915
const cellResizable = css`
1016
@layer rdg.HeaderCell {
1117
touch-action: none;
12-
13-
&::after {
14-
content: '';
15-
cursor: col-resize;
16-
position: absolute;
17-
inset-block-start: 0;
18-
inset-inline-end: 0;
19-
inset-block-end: 0;
20-
inline-size: 10px;
21-
}
2218
}
2319
`;
2420

2521
const cellResizableClassname = `rdg-cell-resizable ${cellResizable}`;
2622

23+
export const resizeHandleClassname = css`
24+
@layer rdg.HeaderCell {
25+
cursor: col-resize;
26+
position: absolute;
27+
inset-block-start: 0;
28+
inset-inline-end: 0;
29+
inset-block-end: 0;
30+
inline-size: 10px;
31+
}
32+
`;
33+
2734
type SharedHeaderRowProps<R, SR> = Pick<
2835
HeaderRowProps<R, SR, React.Key>,
2936
| 'sortColumns'
@@ -62,6 +69,7 @@ export default function HeaderCell<R, SR>({
6269
sortDirection && !priority ? (sortDirection === 'ASC' ? 'ascending' : 'descending') : undefined;
6370

6471
const className = getCellClassname(column, column.headerCellClass, {
72+
[cellSortableClassname]: column.sortable,
6573
[cellResizableClassname]: column.resizable
6674
});
6775

@@ -73,18 +81,14 @@ export default function HeaderCell<R, SR>({
7381
}
7482

7583
const { currentTarget, pointerId } = event;
76-
const { right, left } = currentTarget.getBoundingClientRect();
84+
const headerCell = currentTarget.parentElement!;
85+
const { right, left } = headerCell.getBoundingClientRect();
7786
const offset = isRtl ? event.clientX - left : right - event.clientX;
7887

79-
if (offset > 11) {
80-
// +1px to account for the border size
81-
return;
82-
}
83-
8488
function onPointerMove(event: PointerEvent) {
8589
// prevents text selection in Chrome, which fixes scrolling the grid while dragging, and fixes re-size on an autosized column
8690
event.preventDefault();
87-
const { right, left } = currentTarget.getBoundingClientRect();
91+
const { right, left } = headerCell.getBoundingClientRect();
8892
const width = isRtl ? right + offset - event.clientX : event.clientX + offset - left;
8993
if (width > 0) {
9094
onColumnResize(column, clampColumnWidth(width, column));
@@ -138,19 +142,15 @@ export default function HeaderCell<R, SR>({
138142
}
139143
}
140144

141-
function onClick() {
145+
function onClick(event: React.MouseEvent<HTMLSpanElement>) {
142146
selectCell(column.idx);
143-
}
144-
145-
function onDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
146-
const { right, left } = event.currentTarget.getBoundingClientRect();
147-
const offset = isRtl ? event.clientX - left : right - event.clientX;
148147

149-
if (offset > 11) {
150-
// +1px to account for the border size
151-
return;
148+
if (column.sortable) {
149+
onSort(event.ctrlKey || event.metaKey);
152150
}
151+
}
153152

153+
function onDoubleClick() {
154154
onColumnResize(column, 'max-content');
155155
}
156156

@@ -162,6 +162,14 @@ export default function HeaderCell<R, SR>({
162162
}
163163
}
164164

165+
function onKeyDown(event: React.KeyboardEvent<HTMLSpanElement>) {
166+
if (event.key === ' ' || event.key === 'Enter') {
167+
// prevent scrolling
168+
event.preventDefault();
169+
onSort(event.ctrlKey || event.metaKey);
170+
}
171+
}
172+
165173
return (
166174
<div
167175
role="columnheader"
@@ -175,16 +183,23 @@ export default function HeaderCell<R, SR>({
175183
style={getCellStyle(column, colSpan)}
176184
onFocus={handleFocus}
177185
onClick={onClick}
178-
onDoubleClick={column.resizable ? onDoubleClick : undefined}
179-
onPointerDown={column.resizable ? onPointerDown : undefined}
186+
onKeyDown={column.sortable ? onKeyDown : undefined}
180187
>
181188
{renderHeaderCell({
182189
column,
183190
sortDirection,
184191
priority,
185-
onSort,
186192
tabIndex: childTabIndex
187193
})}
194+
195+
{column.resizable && (
196+
<div
197+
className={resizeHandleClassname}
198+
onClick={stopPropagation}
199+
onDoubleClick={onDoubleClick}
200+
onPointerDown={onPointerDown}
201+
/>
202+
)}
188203
</div>
189204
);
190205
}

src/renderHeaderCell.tsx

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,15 @@ import { css } from '@linaria/core';
33
import type { RenderHeaderCellProps } from './types';
44
import { useDefaultRenderers } from './DataGridDefaultRenderersProvider';
55

6-
const headerSortCell = css`
6+
const headerSortCellClassname = css`
77
@layer rdg.SortableHeaderCell {
8-
cursor: pointer;
98
display: flex;
10-
11-
&:focus {
12-
outline: none;
13-
}
149
}
1510
`;
1611

17-
const headerSortCellClassname = `rdg-header-sort-cell ${headerSortCell}`;
18-
1912
const headerSortName = css`
2013
@layer rdg.SortableHeaderCellName {
2114
flex-grow: 1;
22-
overflow: hidden;
2315
overflow: clip;
2416
text-overflow: ellipsis;
2517
}
@@ -30,61 +22,35 @@ const headerSortNameClassname = `rdg-header-sort-name ${headerSortName}`;
3022
export default function renderHeaderCell<R, SR>({
3123
column,
3224
sortDirection,
33-
priority,
34-
onSort,
35-
tabIndex
25+
priority
3626
}: RenderHeaderCellProps<R, SR>) {
3727
if (!column.sortable) return column.name;
3828

3929
return (
40-
<SortableHeaderCell
41-
onSort={onSort}
42-
sortDirection={sortDirection}
43-
priority={priority}
44-
tabIndex={tabIndex}
45-
>
30+
<SortableHeaderCell sortDirection={sortDirection} priority={priority}>
4631
{column.name}
4732
</SortableHeaderCell>
4833
);
4934
}
5035

5136
type SharedHeaderCellProps<R, SR> = Pick<
5237
RenderHeaderCellProps<R, SR>,
53-
'sortDirection' | 'onSort' | 'priority' | 'tabIndex'
38+
'sortDirection' | 'priority'
5439
>;
5540

5641
interface SortableHeaderCellProps<R, SR> extends SharedHeaderCellProps<R, SR> {
5742
children: React.ReactNode;
5843
}
5944

6045
function SortableHeaderCell<R, SR>({
61-
onSort,
6246
sortDirection,
6347
priority,
64-
children,
65-
tabIndex
48+
children
6649
}: SortableHeaderCellProps<R, SR>) {
6750
const renderSortStatus = useDefaultRenderers<R, SR>()!.renderSortStatus!;
6851

69-
function handleKeyDown(event: React.KeyboardEvent<HTMLSpanElement>) {
70-
if (event.key === ' ' || event.key === 'Enter') {
71-
// stop propagation to prevent scrolling
72-
event.preventDefault();
73-
onSort(event.ctrlKey || event.metaKey);
74-
}
75-
}
76-
77-
function handleClick(event: React.MouseEvent<HTMLSpanElement>) {
78-
onSort(event.ctrlKey || event.metaKey);
79-
}
80-
8152
return (
82-
<span
83-
tabIndex={tabIndex}
84-
className={headerSortCellClassname}
85-
onClick={handleClick}
86-
onKeyDown={handleKeyDown}
87-
>
53+
<span className={headerSortCellClassname}>
8854
<span className={headerSortNameClassname}>{children}</span>
8955
<span>{renderSortStatus({ sortDirection, priority })}</span>
9056
</span>

src/style/cell.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export const cell = css`
1717
background-color: inherit;
1818
1919
white-space: nowrap;
20-
overflow: hidden;
2120
overflow: clip;
2221
text-overflow: ellipsis;
2322
outline: none;

src/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ export interface RenderHeaderCellProps<TRow, TSummaryRow = unknown> {
111111
sortDirection: SortDirection | undefined;
112112
priority: number | undefined;
113113
tabIndex: number;
114-
onSort: (ctrlClick: boolean) => void;
115114
}
116115

117116
export interface CellRendererProps<TRow, TSummaryRow>

test/column/resizable.test.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { fireEvent } from '@testing-library/react';
22

33
import type { Column } from '../../src';
4+
import { resizeHandleClassname } from '../../src/HeaderCell';
45
import { getGrid, getHeaderCells, setup } from '../utils';
56

67
const pointerId = 1;
@@ -38,19 +39,22 @@ function resize<K extends keyof DOMRect>({
3839
clientXEnd,
3940
rect
4041
}: ResizeEvent<K>) {
42+
const resizeHandle = column.querySelector(`.${resizeHandleClassname}`);
43+
if (resizeHandle === null) return;
44+
4145
const original = column.getBoundingClientRect.bind(column);
4246
column.getBoundingClientRect = () => ({
4347
...original(),
4448
...rect
4549
});
4650
// eslint-disable-next-line testing-library/prefer-user-event
4751
fireEvent.pointerDown(
48-
column,
52+
resizeHandle,
4953
new PointerEvent('pointerdown', { pointerId, clientX: clientXStart })
5054
);
5155
// eslint-disable-next-line testing-library/prefer-user-event
52-
fireEvent.pointerMove(column, new PointerEvent('pointermove', { clientX: clientXEnd }));
53-
fireEvent.lostPointerCapture(column, new PointerEvent('lostpointercapture', {}));
56+
fireEvent.pointerMove(resizeHandle, new PointerEvent('pointermove', { clientX: clientXEnd }));
57+
fireEvent.lostPointerCapture(resizeHandle, new PointerEvent('lostpointercapture', {}));
5458
}
5559

5660
const columns: readonly Column<Row>[] = [
@@ -77,15 +81,7 @@ test('should not resize column if resizable is not specified', () => {
7781
expect(getGrid()).toHaveStyle({ gridTemplateColumns: '100px 200px' });
7882
});
7983

80-
test('should not resize column if cursor offset is not within the allowed range', () => {
81-
setup({ columns, rows: [] });
82-
const [, col2] = getHeaderCells();
83-
expect(getGrid()).toHaveStyle({ gridTemplateColumns: '100px 200px' });
84-
resize({ column: col2, clientXStart: 288, clientXEnd: 250, rect: { right: 300, left: 100 } });
85-
expect(getGrid()).toHaveStyle({ gridTemplateColumns: '100px 200px' });
86-
});
87-
88-
test('should resize column if cursor offset is within the allowed range', () => {
84+
test('should resize column when dragging the handle', () => {
8985
setup({ columns, rows: [] });
9086
const [, col2] = getHeaderCells();
9187
expect(getGrid()).toHaveStyle({ gridTemplateColumns: '100px 200px' });

website/root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const mainClassname = css`
3333
box-sizing: border-box;
3434
block-size: 100vh;
3535
padding: 8px;
36-
overflow: hidden;
36+
contain: inline-size;
3737
`;
3838

3939
function Root() {

0 commit comments

Comments
 (0)