Skip to content

Commit 3a53387

Browse files
authored
Use react events for header resizing (#3753)
Add state to handle onPointerMove event Add ResizeHandle component
1 parent 3887ea8 commit 3a53387

File tree

2 files changed

+66
-53
lines changed

2 files changed

+66
-53
lines changed

src/HeaderCell.tsx

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useRef, useState } from 'react';
22
import { css } from '@linaria/core';
33

44
import { useRovingTabIndex } from './hooks';
@@ -87,7 +87,6 @@ export default function HeaderCell<R, SR>({
8787
}: HeaderCellProps<R, SR>) {
8888
const [isDragging, setIsDragging] = useState(false);
8989
const [isOver, setIsOver] = useState(false);
90-
const isRtl = direction === 'rtl';
9190
const rowSpan = getHeaderCellRowSpan(column, rowIdx);
9291
const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);
9392
const sortIndex = sortColumns?.findIndex((sort) => sort.columnKey === column.key);
@@ -107,43 +106,6 @@ export default function HeaderCell<R, SR>({
107106
[cellOverClassname]: isOver
108107
});
109108

110-
function onPointerDown(event: React.PointerEvent<HTMLDivElement>) {
111-
if (event.pointerType === 'mouse' && event.buttons !== 1) {
112-
return;
113-
}
114-
115-
// Fix column resizing on a draggable column in FF
116-
event.preventDefault();
117-
118-
const { currentTarget, pointerId } = event;
119-
const headerCell = currentTarget.parentElement!;
120-
const { right, left } = headerCell.getBoundingClientRect();
121-
const offset = isRtl ? event.clientX - left : right - event.clientX;
122-
function onPointerMove(event: PointerEvent) {
123-
const { width, right, left } = headerCell.getBoundingClientRect();
124-
let newWidth = isRtl ? right + offset - event.clientX : event.clientX + offset - left;
125-
newWidth = clampColumnWidth(newWidth, column);
126-
if (width > 0 && newWidth !== width) {
127-
onColumnResize(column, newWidth);
128-
}
129-
}
130-
131-
function onLostPointerCapture() {
132-
currentTarget.removeEventListener('pointermove', onPointerMove);
133-
currentTarget.removeEventListener('lostpointercapture', onLostPointerCapture);
134-
}
135-
136-
currentTarget.setPointerCapture(pointerId);
137-
currentTarget.addEventListener('pointermove', onPointerMove);
138-
// we are not using pointerup because it does not fire in some cases
139-
// pointer down -> alt+tab -> pointer up over another window -> pointerup event not fired
140-
currentTarget.addEventListener('lostpointercapture', onLostPointerCapture);
141-
}
142-
143-
function onDoubleClick() {
144-
onColumnResize(column, 'max-content');
145-
}
146-
147109
function onSort(ctrlClick: boolean) {
148110
if (onSortColumnsChange == null) return;
149111
const { sortDescendingFirst } = column;
@@ -291,17 +253,69 @@ export default function HeaderCell<R, SR>({
291253
})}
292254

293255
{resizable && (
294-
<div
295-
className={resizeHandleClassname}
296-
onClick={stopPropagation}
297-
onPointerDown={onPointerDown}
298-
onDoubleClick={onDoubleClick}
299-
/>
256+
<ResizeHandle column={column} onColumnResize={onColumnResize} direction={direction} />
300257
)}
301258
</div>
302259
);
303260
}
304261

262+
type ResizeHandleProps<R, SR> = Pick<
263+
HeaderCellProps<R, SR>,
264+
'column' | 'onColumnResize' | 'direction'
265+
>;
266+
267+
function ResizeHandle<R, SR>({ column, onColumnResize, direction }: ResizeHandleProps<R, SR>) {
268+
const resizingOffsetRef = useRef<number>(undefined);
269+
const isRtl = direction === 'rtl';
270+
271+
function onPointerDown(event: React.PointerEvent<HTMLDivElement>) {
272+
if (event.pointerType === 'mouse' && event.buttons !== 1) {
273+
return;
274+
}
275+
276+
// Fix column resizing on a draggable column in FF
277+
event.preventDefault();
278+
279+
const { currentTarget, pointerId } = event;
280+
currentTarget.setPointerCapture(pointerId);
281+
const headerCell = currentTarget.parentElement!;
282+
const { right, left } = headerCell.getBoundingClientRect();
283+
resizingOffsetRef.current = isRtl ? event.clientX - left : right - event.clientX;
284+
}
285+
286+
function onPointerMove(event: React.PointerEvent<HTMLDivElement>) {
287+
const offset = resizingOffsetRef.current;
288+
if (offset === undefined) return;
289+
const { width, right, left } = event.currentTarget.parentElement!.getBoundingClientRect();
290+
let newWidth = isRtl ? right + offset - event.clientX : event.clientX + offset - left;
291+
newWidth = clampColumnWidth(newWidth, column);
292+
if (width > 0 && newWidth !== width) {
293+
onColumnResize(column, newWidth);
294+
}
295+
}
296+
297+
function onLostPointerCapture() {
298+
resizingOffsetRef.current = undefined;
299+
}
300+
301+
function onDoubleClick() {
302+
onColumnResize(column, 'max-content');
303+
}
304+
305+
return (
306+
<div
307+
className={resizeHandleClassname}
308+
onClick={stopPropagation}
309+
onPointerDown={onPointerDown}
310+
onPointerMove={onPointerMove}
311+
// we are not using pointerup because it does not fire in some cases
312+
// pointer down -> alt+tab -> pointer up over another window -> pointerup event not fired
313+
onLostPointerCapture={onLostPointerCapture}
314+
onDoubleClick={onDoubleClick}
315+
/>
316+
);
317+
}
318+
305319
// only accept pertinent drag events:
306320
// - ignore drag events going from the container to an element inside the container
307321
// - ignore drag events going from an element inside the container to the container

src/hooks/useCalculatedColumns.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { CalculatedColumn, CalculatedColumnParent, ColumnOrColumnGroup, Omi
55
import { renderValue } from '../cellRenderers';
66
import { SELECT_COLUMN_KEY } from '../Columns';
77
import type { DataGridProps } from '../DataGrid';
8-
import defaultRenderHeaderCell from '../renderHeaderCell';
8+
import renderHeaderCell from '../renderHeaderCell';
99

1010
type Mutable<T> = {
1111
-readonly [P in keyof T]: T[P] extends ReadonlyArray<infer V> ? Mutable<V>[] : T[P];
@@ -48,9 +48,8 @@ export function useCalculatedColumns<R, SR>({
4848
const defaultWidth = defaultColumnOptions?.width ?? DEFAULT_COLUMN_WIDTH;
4949
const defaultMinWidth = defaultColumnOptions?.minWidth ?? DEFAULT_COLUMN_MIN_WIDTH;
5050
const defaultMaxWidth = defaultColumnOptions?.maxWidth ?? undefined;
51-
const defaultCellRenderer = defaultColumnOptions?.renderCell ?? renderValue;
52-
const defaultHeaderCellRenderer =
53-
defaultColumnOptions?.renderHeaderCell ?? defaultRenderHeaderCell;
51+
const defaultRenderCell = defaultColumnOptions?.renderCell ?? renderValue;
52+
const defaultRenderHeaderCell = defaultColumnOptions?.renderHeaderCell ?? renderHeaderCell;
5453
const defaultSortable = defaultColumnOptions?.sortable ?? false;
5554
const defaultResizable = defaultColumnOptions?.resizable ?? false;
5655
const defaultDraggable = defaultColumnOptions?.draggable ?? false;
@@ -101,8 +100,8 @@ export function useCalculatedColumns<R, SR>({
101100
sortable: rawColumn.sortable ?? defaultSortable,
102101
resizable: rawColumn.resizable ?? defaultResizable,
103102
draggable: rawColumn.draggable ?? defaultDraggable,
104-
renderCell: rawColumn.renderCell ?? defaultCellRenderer,
105-
renderHeaderCell: rawColumn.renderHeaderCell ?? defaultHeaderCellRenderer
103+
renderCell: rawColumn.renderCell ?? defaultRenderCell,
104+
renderHeaderCell: rawColumn.renderHeaderCell ?? defaultRenderHeaderCell
106105
};
107106

108107
columns.push(column);
@@ -156,8 +155,8 @@ export function useCalculatedColumns<R, SR>({
156155
defaultWidth,
157156
defaultMinWidth,
158157
defaultMaxWidth,
159-
defaultCellRenderer,
160-
defaultHeaderCellRenderer,
158+
defaultRenderCell,
159+
defaultRenderHeaderCell,
161160
defaultResizable,
162161
defaultSortable,
163162
defaultDraggable

0 commit comments

Comments
 (0)