From 58021a7a93b86013690fa83424b570319d159a3d Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 19 Oct 2020 20:17:35 +0200 Subject: [PATCH] [XGrid] Second iteration on resizing logic (#436) --- .../grid/_modules_/grid/GridComponent.tsx | 4 +- .../_modules_/grid/components/AutoSizer.tsx | 4 +- .../grid/components/column-header-item.tsx | 203 +++++++------ .../components/column-header-separator.tsx | 49 ++-- .../grid/components/column-headers.tsx | 178 ++++++------ .../styled-wrappers/GridRootStyles.ts | 9 +- .../grid/hooks/features/useColumnResize.tsx | 273 +++++++----------- .../_modules_/grid/utils/material-ui-utils.ts | 6 + 8 files changed, 336 insertions(+), 390 deletions(-) create mode 100644 packages/grid/_modules_/grid/utils/material-ui-utils.ts diff --git a/packages/grid/_modules_/grid/GridComponent.tsx b/packages/grid/_modules_/grid/GridComponent.tsx index ed6b473268c2..a6447f8b6f85 100644 --- a/packages/grid/_modules_/grid/GridComponent.tsx +++ b/packages/grid/_modules_/grid/GridComponent.tsx @@ -94,7 +94,7 @@ export const GridComponent = React.forwardRef { @@ -222,7 +222,7 @@ export const GridComponent = React.forwardRef(functi const detectElementResize = createDetectElementResize(nonce, win); detectElementResize.addResizeListener(parentElement.current, handleResize); - // @ts-expect-error fixed in v5 handleResize(); return () => { diff --git a/packages/grid/_modules_/grid/components/column-header-item.tsx b/packages/grid/_modules_/grid/components/column-header-item.tsx index ee78ea41e08c..288777f0c5e2 100644 --- a/packages/grid/_modules_/grid/components/column-header-item.tsx +++ b/packages/grid/_modules_/grid/components/column-header-item.tsx @@ -1,8 +1,11 @@ import * as React from 'react'; import { ColDef } from '../models/colDef'; +import { ApiRef } from '../models/api'; import { ApiContext } from './api-context'; import { HEADER_CELL_CSS_CLASS } from '../constants/cssClassesConstants'; +import { COL_RESIZE_START, COL_RESIZE_STOP } from '../constants/eventsConstants'; import { classnames } from '../utils'; +import { useApiEventHandler } from '../hooks/root/useApiEventHandler'; import { ColumnHeaderSortIcon } from './column-header-sort-icon'; import { ColumnHeaderTitle } from './column-header-title'; import { ColumnHeaderSeparator } from './column-header-separator'; @@ -10,111 +13,125 @@ import { OptionsContext } from './options-context'; import { CursorCoordinates } from '../hooks/features/useColumnReorder'; interface ColumnHeaderItemProps { - column: ColDef; colIndex: number; - onResizeColumn?: (c: any) => void; - onColumnDragStart?: (col: ColDef, currentTarget: HTMLElement) => void; + column: ColDef; onColumnDragEnter?: (event: Event) => void; onColumnDragOver?: (col: ColDef, coordinates: CursorCoordinates) => void; + onColumnDragStart?: (col: ColDef, currentTarget: HTMLElement) => void; + separatorProps: React.HTMLAttributes; } -export const ColumnHeaderItem = React.memo( - ({ - column, +export const ColumnHeaderItem = React.memo((props: ColumnHeaderItemProps) => { + const { colIndex, - onResizeColumn, - onColumnDragStart, + column, onColumnDragEnter, onColumnDragOver, - }: ColumnHeaderItemProps) => { - const api = React.useContext(ApiContext); - const { showColumnRightBorder, disableColumnResize, disableColumnReorder } = React.useContext( - OptionsContext, - ); + onColumnDragStart, + separatorProps, + } = props; + const apiRef = React.useContext(ApiContext) as ApiRef; + const { showColumnRightBorder, disableColumnResize, disableColumnReorder } = React.useContext( + OptionsContext, + ); + + const [resizing, setResizing] = React.useState(false); + const handleResizeStart = React.useCallback( + (params) => { + if (column.field === params.field) { + setResizing(true); + } + }, + [column.field], + ); + const handleResizeStop = React.useCallback(() => { + setResizing(false); + }, []); + useApiEventHandler(apiRef, COL_RESIZE_START, handleResizeStart); + useApiEventHandler(apiRef, COL_RESIZE_STOP, handleResizeStop); - let headerComponent: React.ReactElement | null = null; - if (column.renderHeader) { - headerComponent = column.renderHeader({ - api: api!.current!, - colDef: column, - colIndex, - field: column.field, - }); - } + let headerComponent: React.ReactElement | null = null; + if (column.renderHeader) { + headerComponent = column.renderHeader({ + api: apiRef.current!, + colDef: column, + colIndex, + field: column.field, + }); + } - const handleResize = onResizeColumn && (() => onResizeColumn(column)); - const dragConfig = { - draggable: - !disableColumnReorder && !!onColumnDragStart && !!onColumnDragEnter && !!onColumnDragOver, - onDragStart: onColumnDragStart && ((event) => onColumnDragStart(column, event.currentTarget)), - onDragEnter: onColumnDragEnter && ((event) => onColumnDragEnter(event)), - onDragOver: - onColumnDragOver && - ((event) => { - onColumnDragOver(column, { - x: event.clientX, - y: event.clientY, - }); - }), - }; - const width = column.width!; + const dragConfig = { + draggable: + !disableColumnReorder && !!onColumnDragStart && !!onColumnDragEnter && !!onColumnDragOver, + onDragStart: onColumnDragStart && ((event) => onColumnDragStart(column, event.currentTarget)), + onDragEnter: onColumnDragEnter && ((event) => onColumnDragEnter(event)), + onDragOver: + onColumnDragOver && + ((event) => { + onColumnDragOver(column, { + x: event.clientX, + y: event.clientY, + }); + }), + }; + const width = column.width!; - let ariaSort: any; - if (column.sortDirection != null) { - ariaSort = { 'aria-sort': column.sortDirection === 'asc' ? 'ascending' : 'descending' }; - } + let ariaSort: any; + if (column.sortDirection != null) { + ariaSort = { 'aria-sort': column.sortDirection === 'asc' ? 'ascending' : 'descending' }; + } - return ( -
+
+ {column.type === 'number' && ( + + )} + {headerComponent || ( + + )} + {column.type !== 'number' && ( + )} - key={column.field} - data-field={column.field} - style={{ - width, - minWidth: width, - maxWidth: width, - }} - role="columnheader" - tabIndex={-1} - aria-colindex={colIndex + 1} - {...ariaSort} - > -
- {column.type === 'number' && ( - - )} - {headerComponent || ( - - )} - {column.type !== 'number' && ( - - )} -
-
- ); - }, -); + +
+ ); +}); ColumnHeaderItem.displayName = 'ColumnHeaderItem'; diff --git a/packages/grid/_modules_/grid/components/column-header-separator.tsx b/packages/grid/_modules_/grid/components/column-header-separator.tsx index 779270f6593e..431fba089234 100644 --- a/packages/grid/_modules_/grid/components/column-header-separator.tsx +++ b/packages/grid/_modules_/grid/components/column-header-separator.tsx @@ -1,32 +1,31 @@ import * as React from 'react'; import { useIcons } from '../hooks/utils/useIcons'; +import { classnames } from '../utils'; import { OptionsContext } from './options-context'; -export interface ColumnHeaderSeparatorProps { - resizable: boolean | undefined; - onResize?: () => void; +export interface ColumnHeaderSeparatorProps extends React.HTMLAttributes { + resizable: boolean; + resizing: boolean; } -export const ColumnHeaderSeparator: React.FC = React.memo( - ({ onResize, resizable }) => { - const icons = useIcons(); - const { showColumnRightBorder, headerHeight } = React.useContext(OptionsContext); +export const ColumnHeaderSeparator = React.memo(function ColumnHeaderSeparator( + props: ColumnHeaderSeparatorProps, +) { + const { resizable, resizing, ...other } = props; + const icons = useIcons(); + const { showColumnRightBorder, headerHeight } = React.useContext(OptionsContext); + const Icon = icons!.columnResize!; - const resizeIconProps = { - className: `MuiDataGrid-iconSeparator ${resizable ? 'MuiDataGrid-resizable' : ''}`, - ...(resizable && onResize ? { onMouseDown: onResize } : {}), - }; - - const icon = React.createElement(icons!.columnResize!, resizeIconProps); - - return ( -
- {icon} -
- ); - }, -); -ColumnHeaderSeparator.displayName = 'ColumnHeaderSeparator'; + return ( +
+ +
+ ); +}); diff --git a/packages/grid/_modules_/grid/components/column-headers.tsx b/packages/grid/_modules_/grid/components/column-headers.tsx index 79a74d54332b..493303c99f51 100644 --- a/packages/grid/_modules_/grid/components/column-headers.tsx +++ b/packages/grid/_modules_/grid/components/column-headers.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { ColDef, Columns, RenderContextProps } from '../models'; +import { Columns, RenderContextProps } from '../models'; +import { ColDef } from '../models/colDef'; import { ColumnHeaderItem } from './column-header-item'; import { ApiContext } from './api-context'; import { LeftEmptyCell, RightEmptyCell } from './cell'; @@ -9,34 +10,35 @@ import { CursorCoordinates } from '../hooks'; export interface ColumnHeadersItemCollectionProps { columns: Columns; - onResizeColumn?: (col: ColDef) => void; - onColumnDragStart?: (col: ColDef, htmlEL: HTMLElement) => void; onColumnDragEnter?: (event: Event) => void; onColumnDragOver?: (col: ColDef, pos: CursorCoordinates) => void; + onColumnDragStart?: (col: ColDef, htmlEL: HTMLElement) => void; + separatorProps: React.HTMLAttributes; } -export const ColumnHeaderItemCollection: React.FC = React.memo( - ({ onResizeColumn, onColumnDragStart, onColumnDragEnter, onColumnDragOver, columns }) => { - const items = columns.map((col, idx) => ( - - )); - return {items}; - }, -); -ColumnHeaderItemCollection.displayName = 'ColumnHeaderItemCollection'; +export const ColumnHeaderItemCollection = React.memo(function ColumnHeaderItemCollection( + props: ColumnHeadersItemCollectionProps, +) { + const { separatorProps, onColumnDragStart, onColumnDragEnter, onColumnDragOver, columns } = props; + const items = columns.map((col, idx) => ( + + )); + + return {items}; +}); export interface ColumnsHeaderProps { columns: Columns; hasScrollX: boolean; - onResizeColumn?: (col: ColDef) => void; + separatorProps: React.HTMLAttributes; onColumnHeaderDragOver?: (event: Event) => void; onColumnDragOver?: (col: ColDef, pos: CursorCoordinates) => void; onColumnDragStart?: (col: ColDef, htmlEl: HTMLElement) => void; @@ -45,80 +47,76 @@ export interface ColumnsHeaderProps { } export const ColumnsHeader = React.memo( - React.forwardRef( - ( - { - columns, - hasScrollX, - onResizeColumn, - onColumnHeaderDragOver, - onColumnDragOver, - onColumnDragStart, - onColumnDragEnter, - renderCtx, - }, - ref, - ) => { - const wrapperCssClasses = `MuiDataGrid-colCellWrapper ${hasScrollX ? 'scroll' : ''}`; - const api = React.useContext(ApiContext); - const { disableColumnReorder } = React.useContext(OptionsContext); + React.forwardRef((props, ref) => { + const { + columns, + hasScrollX, + onColumnDragEnter, + onColumnDragOver, + onColumnDragStart, + onColumnHeaderDragOver, + renderCtx, + separatorProps, + } = props; + const wrapperCssClasses = `MuiDataGrid-colCellWrapper ${hasScrollX ? 'scroll' : ''}`; + const api = React.useContext(ApiContext); + const { disableColumnReorder } = React.useContext(OptionsContext); - if (!api) { - throw new Error('Material-UI: ApiRef was not found in context.'); - } - const lastRenderedColIndexes = React.useRef({ - first: renderCtx?.firstColIdx, - last: renderCtx?.lastColIdx, - }); - const [renderedCols, setRenderedCols] = React.useState(columns); + if (!api) { + throw new Error('Material-UI: ApiRef was not found in context.'); + } + const lastRenderedColIndexes = React.useRef({ + first: renderCtx?.firstColIdx, + last: renderCtx?.lastColIdx, + }); + const [renderedCols, setRenderedCols] = React.useState(columns); - React.useEffect(() => { - if (renderCtx && renderCtx.firstColIdx != null && renderCtx.lastColIdx != null) { - setRenderedCols(columns.slice(renderCtx.firstColIdx, renderCtx.lastColIdx + 1)); + React.useEffect(() => { + if (renderCtx && renderCtx.firstColIdx != null && renderCtx.lastColIdx != null) { + setRenderedCols(columns.slice(renderCtx.firstColIdx, renderCtx.lastColIdx + 1)); - if ( - lastRenderedColIndexes.current.first !== renderCtx.firstColIdx || - lastRenderedColIndexes.current.last !== renderCtx.lastColIdx - ) { - lastRenderedColIndexes.current = { - first: renderCtx.firstColIdx, - last: renderCtx.lastColIdx, - }; - } + if ( + lastRenderedColIndexes.current.first !== renderCtx.firstColIdx || + lastRenderedColIndexes.current.last !== renderCtx.lastColIdx + ) { + lastRenderedColIndexes.current = { + first: renderCtx.firstColIdx, + last: renderCtx.lastColIdx, + }; } - }, [renderCtx, columns]); + } + }, [renderCtx, columns]); - const handleDragOver = - onColumnHeaderDragOver && !disableColumnReorder - ? (event) => onColumnHeaderDragOver(event) - : undefined; + const handleDragOver = + onColumnHeaderDragOver && !disableColumnReorder + ? (event) => onColumnHeaderDragOver(event) + : undefined; - return ( - - -
- - - -
- -
- ); - }, - ), + return ( + + +
+ + + +
+ +
+ ); + }), ); ColumnsHeader.displayName = 'GridColumnsHeader'; diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index 576a30c63b8f..fbe307c282bb 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -120,12 +120,15 @@ export const useStyles = makeStyles( display: 'flex', flexDirection: 'column', justifyContent: 'center', - }, - '& .MuiDataGrid-iconSeparator': { color: borderColor, }, - '& .MuiDataGrid-columnSeparator:hover .MuiDataGrid-resizable': { + '& .MuiDataGrid-columnSeparatorResizable': { cursor: 'col-resize', + '&:hover, &.Mui-resizing': { + color: theme.palette.text.primary, + }, + }, + '& .MuiDataGrid-iconSeparator': { color: 'inherit', }, '& .MuiDataGrid-colCellWrapper.scroll .MuiDataGrid-colCell:last-child': { diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index 80b926bccc09..9d721d2138fa 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -1,204 +1,127 @@ import * as React from 'react'; +import { ownerDocument } from '@material-ui/core/utils'; import { ColDef } from '../../models/colDef'; -import { ScrollParams, useLogger } from '../utils'; -import { COL_RESIZE_START, COL_RESIZE_STOP, SCROLLING } from '../../constants/eventsConstants'; -import { findCellElementsFromCol, findDataContainerFromCurrent } from '../../utils'; -import { useStateRef } from '../utils/useStateRef'; +import { useLogger } from '../utils'; +import { useEventCallback } from '../../utils/material-ui-utils'; +import { COL_RESIZE_START, COL_RESIZE_STOP } from '../../constants/eventsConstants'; +import { HEADER_CELL_CSS_CLASS } from '../../constants/cssClassesConstants'; +import { findCellElementsFromCol } from '../../utils'; import { ApiRef } from '../../models'; const MIN_COL_WIDTH = 50; -const MOUSE_LEFT_TIMEOUT = 1000; - -// TODO improve experience for last column -export const useColumnResize = ( - columnsRef: React.RefObject, - apiRef: ApiRef, - headerHeight: number, -) => { + +export const useColumnResize = (columnsRef: React.RefObject, apiRef: ApiRef) => { const logger = useLogger('useColumnResize'); + const colDefRef = React.useRef(); + const colElementRef = React.useRef(); + const colCellElementsRef = React.useRef>(); + const initialOffset = React.useRef(); + const stopResizeEventTimeout = React.useRef(); - const isResizing = React.useRef(false); - const isLastColumn = React.useRef(false); - const mouseLeftTimeout = React.useRef(); - const stopResizeEventTimeout = React.useRef(); + const updateWidth = (newWidth: number) => { + logger.debug(`Updating width to ${newWidth} for col ${colDefRef.current!.field}`); - const currentColDefRef = React.useRef(); - const currentColElem = React.useRef(); - const currentColPosition = React.useRef(); - const currentColPreviousWidth = React.useRef(); - const currentColCellsElems = React.useRef>(); + colDefRef.current!.width = newWidth; - const dataContainerElemRef = React.useRef(); - const dataContainerPreviousWidth = React.useRef(); - const scrollOffset = React.useRef(0); - const resizingMouseMove = React.useRef<{ x: number; y: number }>(); + colElementRef.current!.style.width = `${newWidth}px`; + colElementRef.current!.style.minWidth = `${newWidth}px`; + colElementRef.current!.style.maxWidth = `${newWidth}px`; - const onScrollHandler = React.useCallback((params: ScrollParams) => { - scrollOffset.current = params.left; - }, []); + colCellElementsRef.current!.forEach((element) => { + const div = element as HTMLDivElement; + div.style.width = `${newWidth}px`; + div.style.minWidth = `${newWidth}px`; + div.style.maxWidth = `${newWidth}px`; + }); + }; - React.useEffect(() => { - return apiRef.current.subscribeEvent(SCROLLING, onScrollHandler); - }, [apiRef, onScrollHandler]); - - const handleMouseDown = React.useCallback( - (col: ColDef): void => { - logger.debug(`Start Resize on col ${col.field}`); - apiRef.current.publishEvent(COL_RESIZE_START); - isResizing.current = true; - currentColDefRef.current = col; - currentColPreviousWidth.current = col.width; - currentColElem.current = columnsRef?.current?.querySelector( - `[data-field="${col.field}"]`, - ) as HTMLDivElement; - currentColCellsElems.current = findCellElementsFromCol(currentColElem.current) || undefined; - dataContainerElemRef.current = - findDataContainerFromCurrent(currentColElem.current) || undefined; - dataContainerPreviousWidth.current = Number( - dataContainerElemRef.current!.style.minWidth.replace('px', ''), - ); - currentColPosition.current = apiRef.current.getColumnPosition(col.field); - isLastColumn.current = - apiRef.current.getColumnIndex(col.field) === apiRef.current.getVisibleColumns().length - 1; - }, - [apiRef, columnsRef, logger], - ); - - const stopResize = React.useCallback((): void => { - isResizing.current = false; - currentColPosition.current = undefined; - currentColElem.current = undefined; - currentColCellsElems.current = undefined; - resizingMouseMove.current = undefined; - isLastColumn.current = false; - if (currentColDefRef.current) { - logger.debug( - `Updating col ${currentColDefRef.current.field} with new width: ${currentColDefRef.current.width}`, - ); - apiRef?.current?.updateColumn(currentColDefRef.current); - currentColDefRef.current = undefined; - } + const handleResizeMouseUp = useEventCallback(() => { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + stopListening(); + apiRef.current!.updateColumn(colDefRef.current as ColDef); + + clearTimeout(stopResizeEventTimeout.current); stopResizeEventTimeout.current = setTimeout(() => { apiRef.current.publishEvent(COL_RESIZE_STOP); - }, 200); - }, [apiRef, logger]); - - const updateWidth = React.useCallback( - (newWidth: number) => { - logger.debug(`Updating width to ${newWidth} for col ${currentColDefRef.current!.field}`); - if (currentColDefRef.current) { - currentColDefRef.current.width = newWidth; - } - if (currentColElem.current) { - currentColElem.current.style.width = `${newWidth}px`; - currentColElem.current.style.minWidth = `${newWidth}px`; - currentColElem.current.style.maxWidth = `${newWidth}px`; - } - if (dataContainerElemRef.current) { - const diffWithPrev = newWidth - currentColPreviousWidth.current!; - dataContainerElemRef.current.style.minWidth = `${ - dataContainerPreviousWidth.current! + diffWithPrev - }px`; - - if (isLastColumn.current) { - apiRef.current.scroll({ left: dataContainerPreviousWidth.current! + diffWithPrev }); - } - } - if (currentColCellsElems.current) { - currentColCellsElems.current.forEach((el) => { - const div = el as HTMLDivElement; - div.style.width = `${newWidth}px`; - div.style.minWidth = `${newWidth}px`; - div.style.maxWidth = `${newWidth}px`; - }); - } - }, - [apiRef, logger], - ); - - const handleMouseEnter = React.useCallback((): void => { - if (mouseLeftTimeout.current != null) { - clearTimeout(mouseLeftTimeout.current); + }); + + logger.debug( + `Updating col ${colDefRef.current!.field} with new width: ${colDefRef.current!.width}`, + ); + }); + + const handleResizeMouseMove = useEventCallback((nativeEvent) => { + // Cancel move in case some other element consumed a mouseup event and it was not fired. + if (nativeEvent.buttons === 0) { + handleResizeMouseUp(); + return; + } + + let newWidth = + initialOffset.current + + nativeEvent.clientX - + colElementRef.current!.getBoundingClientRect().left; + newWidth = Math.max(MIN_COL_WIDTH, newWidth); + + updateWidth(newWidth); + }); + const handleMouseDown = useEventCallback((event: React.MouseEvent) => { + // Only handle left clicks + if (event.button !== 0) { + return; } - }, []); - const handleMouseLeave = React.useCallback((): void => { - if ( - isLastColumn.current && - resizingMouseMove.current && - resizingMouseMove.current.y >= 0 && - resizingMouseMove.current.y <= headerHeight && - currentColDefRef.current - ) { - logger.debug(`Mouse left and same row, so extending last column width of 100`); - - // we are resizing the last column outside the window - updateWidth(currentColDefRef.current.width! + 10); - mouseLeftTimeout.current = setTimeout(() => { - stopResize(); - }, MOUSE_LEFT_TIMEOUT); - } else if (isResizing) { - mouseLeftTimeout.current = setTimeout(() => { - stopResize(); - }, MOUSE_LEFT_TIMEOUT); + + // Skip if the column isn't resizable + if (!event.currentTarget.classList.contains('MuiDataGrid-columnSeparatorResizable')) { + return; } - }, [headerHeight, logger, stopResize, updateWidth]); - const handleMouseMove = React.useCallback( - (ev: MouseEvent): void => { - if (isResizing.current) { - const target = ev.currentTarget! as HTMLDivElement; - const rect = target.getBoundingClientRect(); + // Avoid text selection + event.preventDefault(); - resizingMouseMove.current = { x: ev.clientX - rect.left, y: ev.clientY - rect.top }; + colElementRef.current = event.currentTarget.closest( + `.${HEADER_CELL_CSS_CLASS}`, + ) as HTMLDivElement; + const field = colElementRef.current.getAttribute('data-field') as string; + const colDef = apiRef.current.getColumnFromField(field); - const offsetLeft = !isLastColumn.current ? rect.left : scrollOffset.current * -1; + logger.debug(`Start Resize on col ${colDef.field}`); + apiRef.current.publishEvent(COL_RESIZE_START, { field }); - let newWidth = ev.clientX - offsetLeft - currentColPosition.current!; - newWidth = newWidth > MIN_COL_WIDTH ? newWidth : MIN_COL_WIDTH; - updateWidth(newWidth); - } - }, - [updateWidth], - ); + colDefRef.current = colDef; + colElementRef.current = columnsRef.current!.querySelector( + `[data-field="${colDef.field}"]`, + ) as HTMLDivElement; - // This a hack due to the limitation of react as I cannot put columnsRef in the dependency array of the effect adding the Event listener - const columnsRefState = useStateRef(columnsRef); + colCellElementsRef.current = findCellElementsFromCol(colElementRef.current) as NodeListOf< + Element + >; - React.useEffect(() => { - if (columnsRef && columnsRef.current) { - logger.info('Adding resizing event listener'); - const columnsRefEvents = columnsRef.current; - columnsRef.current.addEventListener('mouseup', stopResize); - columnsRef.current.addEventListener('mouseleave', handleMouseLeave); - columnsRef.current.addEventListener('mouseenter', handleMouseEnter); - columnsRef.current.addEventListener('mousemove', handleMouseMove); - - return () => { - columnsRefEvents.removeEventListener('mouseup', stopResize); - columnsRefEvents.removeEventListener('mouseleave', handleMouseLeave); - columnsRefEvents.removeEventListener('mouseenter', handleMouseEnter); - columnsRefEvents.removeEventListener('mousemove', handleMouseMove); - }; - } - return undefined; - }, [ - columnsRefState, - columnsRef, - handleMouseLeave, - handleMouseDown, - handleMouseMove, - handleMouseEnter, - logger, - stopResize, - ]); + const doc = ownerDocument(apiRef.current.rootElementRef!.current as HTMLElement); + doc.body.style.cursor = 'col-resize'; + + initialOffset.current = + (colDefRef.current.width as number) - + (event.clientX - colElementRef.current!.getBoundingClientRect().left); + + doc.addEventListener('mousemove', handleResizeMouseMove); + doc.addEventListener('mouseup', handleResizeMouseUp); + }); + + const stopListening = React.useCallback(() => { + const doc = ownerDocument(apiRef.current.rootElementRef!.current as HTMLElement); + doc.body.style.removeProperty('cursor'); + doc.removeEventListener('mousemove', handleResizeMouseMove); + doc.removeEventListener('mouseup', handleResizeMouseUp); + }, [apiRef, handleResizeMouseMove, handleResizeMouseUp]); React.useEffect(() => { return () => { - clearTimeout(mouseLeftTimeout.current); clearTimeout(stopResizeEventTimeout.current); + stopListening(); }; - }, []); + }, [stopListening]); - return handleMouseDown; + return React.useMemo(() => ({ onMouseDown: handleMouseDown }), [handleMouseDown]); }; diff --git a/packages/grid/_modules_/grid/utils/material-ui-utils.ts b/packages/grid/_modules_/grid/utils/material-ui-utils.ts new file mode 100644 index 000000000000..a6f961545d5d --- /dev/null +++ b/packages/grid/_modules_/grid/utils/material-ui-utils.ts @@ -0,0 +1,6 @@ +import { useEventCallback as muiUseEventCallback } from '@material-ui/core/utils'; + +export function useEventCallback any>(func: T): T { + // @ts-expect-error TODO remove wrapper once upgraded to v5 + return muiUseEventCallback(func); +}