From 21cc37bb997356e1dd700904f6cb8c7fc9605299 Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 4 Nov 2020 14:51:31 +0100 Subject: [PATCH 01/13] Add support for touch column resize --- .../styled-wrappers/GridRootStyles.ts | 1 + .../grid/hooks/features/useColumnResize.tsx | 133 +++++++++++++++++- .../features/virtualization/useVirtualRows.ts | 2 +- .../_modules_/grid/hooks/utils/useScrollFn.ts | 1 - .../src/stories/grid-resize.stories.tsx | 2 +- 5 files changed, 134 insertions(+), 5 deletions(-) diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index fbe307c282bb..b8bfc03cfae1 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -10,6 +10,7 @@ export const useStyles = makeStyles( const gridStyle: { root: any } = { root: { flex: 1, + touchAction: 'none', boxSizing: 'border-box', position: 'relative', border: `1px solid ${borderColor}`, diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index 8e71971e713a..a3dc5865b2b5 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -18,6 +18,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api const colCellElementsRef = React.useRef>(); const initialOffset = React.useRef(); const stopResizeEventTimeout = React.useRef(); + const touchId = React.useRef(); const updateWidth = (newWidth: number) => { logger.debug(`Updating width to ${newWidth} for col ${colDefRef.current!.field}`); @@ -110,12 +111,134 @@ export const useColumnResize = (columnsRef: React.RefObject, api doc.addEventListener('mouseup', handleResizeMouseUp); }); + // TODO: remove support for Safari < 13. + // https://caniuse.com/#search=touch-action + // + // Safari, on iOS, supports touch action since v13. + // Over 80% of the iOS phones are compatible + // in August 2020. + let cachedSupportsTouchActionNone; + function doesSupportTouchActionNone() { + if (cachedSupportsTouchActionNone === undefined) { + const element = document.createElement('div'); + element.style.touchAction = 'none'; + document.body.appendChild(element); + cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none'; + element.parentElement!.removeChild(element); + } + return cachedSupportsTouchActionNone; + } + + function trackFinger(event, currentTouchId) { + if (currentTouchId !== undefined && event.changedTouches) { + for (let i = 0; i < event.changedTouches.length; i += 1) { + const touch = event.changedTouches[i]; + if (touch.identifier === currentTouchId) { + return { + x: touch.clientX, + y: touch.clientY, + }; + } + } + + return false; + } + + return { + x: event.clientX, + y: event.clientY, + }; + } + + const handleTouchEnd = useEventCallback((nativeEvent) => { + const finger = trackFinger(nativeEvent, touchId.current); + + if (!finger) { + return; + } + + // 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); + }); + + logger.debug( + `Updating col ${colDefRef.current!.field} with new width: ${colDefRef.current!.width}`, + ); + }); + + const handleTouchMove = useEventCallback((nativeEvent) => { + const finger = trackFinger(nativeEvent, touchId.current); + if (!finger) { + return; + } + + // Cancel move in case some other element consumed a touchmove event and it was not fired. + if (nativeEvent.type === 'mousemove' && nativeEvent.buttons === 0) { + handleTouchEnd(nativeEvent); + return; + } + + let newWidth = + initialOffset.current + finger.x - colElementRef.current!.getBoundingClientRect().left; + newWidth = Math.max(MIN_COL_WIDTH, newWidth); + + updateWidth(newWidth); + }); + + const handleTouchStart = useEventCallback((event: React.TouchEvent) => { + // If touch-action: none; is not supported we need to prevent the scroll manually. + if (!doesSupportTouchActionNone()) { + event.preventDefault(); + } + + const touch = event.changedTouches[0]; + if (touch != null) { + // A number that uniquely identifies the current finger in the touch session. + touchId.current = touch.identifier; + } + // const finger = trackFinger(event, touchId.current); + + 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); + + logger.debug(`Start Resize on col ${colDef.field}`); + apiRef.current.publishEvent(COL_RESIZE_START, { field }); + + colDefRef.current = colDef; + colElementRef.current = columnsRef.current!.querySelector( + `[data-field="${colDef.field}"]`, + ) as HTMLDivElement; + + colCellElementsRef.current = findCellElementsFromCol(colElementRef.current) as NodeListOf< + Element + >; + + initialOffset.current = + (colDefRef.current.width as number) - + (touch.clientX - colElementRef.current!.getBoundingClientRect().left); + + const doc = ownerDocument(event.currentTarget as HTMLElement); + doc.addEventListener('touchmove', handleTouchMove); + doc.addEventListener('touchend', handleTouchEnd); + }); + 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]); + doc.removeEventListener('touchmove', handleTouchMove); + doc.removeEventListener('touchend', handleTouchEnd); + }, [apiRef, handleResizeMouseMove, handleResizeMouseUp, handleTouchMove, handleTouchEnd]); React.useEffect(() => { return () => { @@ -124,5 +247,11 @@ export const useColumnResize = (columnsRef: React.RefObject, api }; }, [stopListening]); - return React.useMemo(() => ({ onMouseDown: handleMouseDown }), [handleMouseDown]); + return React.useMemo( + () => ({ + onMouseDown: handleMouseDown, + onTouchStart: handleTouchStart, + }), + [handleMouseDown, handleTouchStart], + ); }; diff --git a/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts b/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts index 8cbbf04a653d..f60573b2c355 100644 --- a/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts @@ -39,7 +39,7 @@ export const useVirtualRows = ( const paginationState = useGridSelector(apiRef, paginationSelector); const totalRowCount = useGridSelector(apiRef, rowCountSelector); - const [scrollTo] = useScrollFn(apiRef, renderingZoneRef, colRef); + const [scrollTo] = useScrollFn(renderingZoneRef, colRef); const [renderedColRef, updateRenderedCols] = useVirtualColumns(options, apiRef); const setRenderingState = React.useCallback( diff --git a/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts b/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts index 523da0f3441b..0aecac4b182d 100644 --- a/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts +++ b/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts @@ -4,7 +4,6 @@ import { ScrollFn, ScrollParams } from '../../models/params/scrollParams'; import { useLogger } from './useLogger'; export function useScrollFn( - apiRef: any, renderingZoneElementRef: React.RefObject, columnHeadersElementRef: React.RefObject, ): [ScrollFn] { diff --git a/packages/storybook/src/stories/grid-resize.stories.tsx b/packages/storybook/src/stories/grid-resize.stories.tsx index e26a6e61ed6a..ae3a8f73e874 100644 --- a/packages/storybook/src/stories/grid-resize.stories.tsx +++ b/packages/storybook/src/stories/grid-resize.stories.tsx @@ -30,7 +30,7 @@ export const ResizeSmallDataset = () => { Switch sizes -
+
From 7416e1647f8e91d8fbdd14e4530202a5562137e5 Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 5 Nov 2020 17:08:28 +0100 Subject: [PATCH 02/13] Change the touch start event listner from the separator to the document --- .../grid/hooks/features/useColumnResize.tsx | 108 ++++++++++-------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index a3dc5865b2b5..e5d683de5219 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -7,8 +7,48 @@ import { COL_RESIZE_START, COL_RESIZE_STOP } from '../../constants/eventsConstan import { HEADER_CELL_CSS_CLASS } from '../../constants/cssClassesConstants'; import { findCellElementsFromCol } from '../../utils'; import { ApiRef } from '../../models'; +import { CursorCoordinates } from '../../models/api/columnReorderApi'; const MIN_COL_WIDTH = 50; +let cachedSupportsTouchActionNone = false; + +// TODO: remove support for Safari < 13. +// https://caniuse.com/#search=touch-action +// +// Safari, on iOS, supports touch action since v13. +// Over 80% of the iOS phones are compatible +// in August 2020. +function doesSupportTouchActionNone(): boolean { + if (!cachedSupportsTouchActionNone) { + const element = document.createElement('div'); + element.style.touchAction = 'none'; + document.body.appendChild(element); + cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none'; + element.parentElement!.removeChild(element); + } + return cachedSupportsTouchActionNone; +} + +function trackFinger(event, currentTouchId): CursorCoordinates | boolean { + if (currentTouchId !== undefined && event.changedTouches) { + for (let i = 0; i < event.changedTouches.length; i += 1) { + const touch = event.changedTouches[i]; + if (touch.identifier === currentTouchId) { + return { + x: touch.clientX, + y: touch.clientY, + }; + } + } + + return false; + } + + return { + x: event.clientX, + y: event.clientY, + }; +} // TODO improve experience for last column export const useColumnResize = (columnsRef: React.RefObject, apiRef: ApiRef) => { @@ -111,45 +151,6 @@ export const useColumnResize = (columnsRef: React.RefObject, api doc.addEventListener('mouseup', handleResizeMouseUp); }); - // TODO: remove support for Safari < 13. - // https://caniuse.com/#search=touch-action - // - // Safari, on iOS, supports touch action since v13. - // Over 80% of the iOS phones are compatible - // in August 2020. - let cachedSupportsTouchActionNone; - function doesSupportTouchActionNone() { - if (cachedSupportsTouchActionNone === undefined) { - const element = document.createElement('div'); - element.style.touchAction = 'none'; - document.body.appendChild(element); - cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none'; - element.parentElement!.removeChild(element); - } - return cachedSupportsTouchActionNone; - } - - function trackFinger(event, currentTouchId) { - if (currentTouchId !== undefined && event.changedTouches) { - for (let i = 0; i < event.changedTouches.length; i += 1) { - const touch = event.changedTouches[i]; - if (touch.identifier === currentTouchId) { - return { - x: touch.clientX, - y: touch.clientY, - }; - } - } - - return false; - } - - return { - x: event.clientX, - y: event.clientY, - }; - } - const handleTouchEnd = useEventCallback((nativeEvent) => { const finger = trackFinger(nativeEvent, touchId.current); @@ -185,14 +186,21 @@ export const useColumnResize = (columnsRef: React.RefObject, api } let newWidth = - initialOffset.current + finger.x - colElementRef.current!.getBoundingClientRect().left; + initialOffset.current! + + (finger as CursorCoordinates).x - + colElementRef.current!.getBoundingClientRect().left; newWidth = Math.max(MIN_COL_WIDTH, newWidth); updateWidth(newWidth); }); - const handleTouchStart = useEventCallback((event: React.TouchEvent) => { + const handleTouchStart = useEventCallback((event) => { // If touch-action: none; is not supported we need to prevent the scroll manually. + colElementRef.current = event.target.closest(`.${HEADER_CELL_CSS_CLASS}`) as HTMLDivElement; + + // Let the event bubble if the target is not a col separator + if (!colElementRef.current) return; + if (!doesSupportTouchActionNone()) { event.preventDefault(); } @@ -202,12 +210,8 @@ export const useColumnResize = (columnsRef: React.RefObject, api // A number that uniquely identifies the current finger in the touch session. touchId.current = touch.identifier; } - // const finger = trackFinger(event, touchId.current); - colElementRef.current = event.currentTarget.closest( - `.${HEADER_CELL_CSS_CLASS}`, - ) as HTMLDivElement; - const field = colElementRef.current.getAttribute('data-field') as string; + const field = colElementRef.current!.getAttribute('data-field') as string; const colDef = apiRef.current.getColumnFromField(field); logger.debug(`Start Resize on col ${colDef.field}`); @@ -241,17 +245,23 @@ export const useColumnResize = (columnsRef: React.RefObject, api }, [apiRef, handleResizeMouseMove, handleResizeMouseUp, handleTouchMove, handleTouchEnd]); React.useEffect(() => { + const doc = ownerDocument(apiRef.current.rootElementRef!.current as HTMLElement); + doc.addEventListener('touchstart', handleTouchStart, { + passive: doesSupportTouchActionNone(), + }); + return () => { + doc.removeEventListener('touchstart', handleTouchStart); + clearTimeout(stopResizeEventTimeout.current); stopListening(); }; - }, [stopListening]); + }, [stopListening, apiRef, handleTouchStart]); return React.useMemo( () => ({ onMouseDown: handleMouseDown, - onTouchStart: handleTouchStart, }), - [handleMouseDown, handleTouchStart], + [handleMouseDown], ); }; From e4317d1716ad6ca067c34791d7a8295cfadefbe4 Mon Sep 17 00:00:00 2001 From: Danail H Date: Fri, 6 Nov 2020 13:29:56 +0100 Subject: [PATCH 03/13] Allow resizing on mobile only when column separator is touched --- .../components/styled-wrappers/GridRootStyles.ts | 2 +- .../grid/constants/cssClassesConstants.ts | 1 + .../grid/hooks/features/useColumnResize.tsx | 16 ++++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index b8bfc03cfae1..9212eb9f886a 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -10,7 +10,6 @@ export const useStyles = makeStyles( const gridStyle: { root: any } = { root: { flex: 1, - touchAction: 'none', boxSizing: 'border-box', position: 'relative', border: `1px solid ${borderColor}`, @@ -125,6 +124,7 @@ export const useStyles = makeStyles( }, '& .MuiDataGrid-columnSeparatorResizable': { cursor: 'col-resize', + touchAction: 'none', '&:hover, &.Mui-resizing': { color: theme.palette.text.primary, }, diff --git a/packages/grid/_modules_/grid/constants/cssClassesConstants.ts b/packages/grid/_modules_/grid/constants/cssClassesConstants.ts index dd62b6933a9a..92a4b926a9d9 100644 --- a/packages/grid/_modules_/grid/constants/cssClassesConstants.ts +++ b/packages/grid/_modules_/grid/constants/cssClassesConstants.ts @@ -2,6 +2,7 @@ export const CELL_CSS_CLASS = 'MuiDataGrid-cell'; export const ROW_CSS_CLASS = 'MuiDataGrid-row'; export const HEADER_CELL_CSS_CLASS = 'MuiDataGrid-colCell'; +export const HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS = 'MuiDataGrid-columnSeparatorResizable'; export const DATA_CONTAINER_CSS_CLASS = 'data-container'; export const HEADER_CELL_DROP_ZONE_CSS_CLASS = 'MuiDataGrid-colCell-dropZone'; export const HEADER_CELL_DRAGGING_CSS_CLASS = 'MuiDataGrid-colCell-dragging'; diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index e5d683de5219..bd5404d98ef4 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -4,7 +4,10 @@ import { ColDef } from '../../models/colDef'; 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 { + HEADER_CELL_CSS_CLASS, + HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS, +} from '../../constants/cssClassesConstants'; import { findCellElementsFromCol } from '../../utils'; import { ApiRef } from '../../models'; import { CursorCoordinates } from '../../models/api/columnReorderApi'; @@ -195,12 +198,12 @@ export const useColumnResize = (columnsRef: React.RefObject, api }); const handleTouchStart = useEventCallback((event) => { - // If touch-action: none; is not supported we need to prevent the scroll manually. - colElementRef.current = event.target.closest(`.${HEADER_CELL_CSS_CLASS}`) as HTMLDivElement; - + const cellSeparator = event.target.closest( + `.${HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS}`, + ) as HTMLDivElement; // Let the event bubble if the target is not a col separator - if (!colElementRef.current) return; - + if (!cellSeparator) return; + // If touch-action: none; is not supported we need to prevent the scroll manually. if (!doesSupportTouchActionNone()) { event.preventDefault(); } @@ -211,6 +214,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api touchId.current = touch.identifier; } + colElementRef.current = event.target.closest(`.${HEADER_CELL_CSS_CLASS}`) as HTMLDivElement; const field = colElementRef.current!.getAttribute('data-field') as string; const colDef = apiRef.current.getColumnFromField(field); From d8df748558d873c1383f00666866b810d7af1a75 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 6 Nov 2020 14:08:41 +0100 Subject: [PATCH 04/13] fix hover --- .../grid/components/styled-wrappers/GridRootStyles.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index 9212eb9f886a..ee08abf2fcb9 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -125,7 +125,14 @@ export const useStyles = makeStyles( '& .MuiDataGrid-columnSeparatorResizable': { cursor: 'col-resize', touchAction: 'none', - '&:hover, &.Mui-resizing': { + '&:hover': { + color: theme.palette.text.primary, + // Reset on touch devices, it doesn't add specificity + '@media (hover: none)': { + color: borderColor, + }, + }, + '&.Mui-resizing': { color: theme.palette.text.primary, }, }, From a5d5a9cb98159aa72dd2a189e96782876bd36bb3 Mon Sep 17 00:00:00 2001 From: Danail H Date: Fri, 6 Nov 2020 16:41:33 +0100 Subject: [PATCH 05/13] Attach touchStart event to the columns header element, not the document --- .../grid/hooks/features/useColumnResize.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index bd5404d98ef4..edeb103b0dda 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -62,6 +62,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api const initialOffset = React.useRef(); const stopResizeEventTimeout = React.useRef(); const touchId = React.useRef(); + const columnsHeaderElement = columnsRef.current; const updateWidth = (newWidth: number) => { logger.debug(`Updating width to ${newWidth} for col ${colDefRef.current!.field}`); @@ -135,7 +136,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api apiRef.current.publishEvent(COL_RESIZE_START, { field }); colDefRef.current = colDef; - colElementRef.current = columnsRef.current!.querySelector( + colElementRef.current = columnsHeaderElement!.querySelector( `[data-field="${colDef.field}"]`, ) as HTMLDivElement; @@ -222,7 +223,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api apiRef.current.publishEvent(COL_RESIZE_START, { field }); colDefRef.current = colDef; - colElementRef.current = columnsRef.current!.querySelector( + colElementRef.current = columnsHeaderElement!.querySelector( `[data-field="${colDef.field}"]`, ) as HTMLDivElement; @@ -249,18 +250,17 @@ export const useColumnResize = (columnsRef: React.RefObject, api }, [apiRef, handleResizeMouseMove, handleResizeMouseUp, handleTouchMove, handleTouchEnd]); React.useEffect(() => { - const doc = ownerDocument(apiRef.current.rootElementRef!.current as HTMLElement); - doc.addEventListener('touchstart', handleTouchStart, { + columnsHeaderElement?.addEventListener('touchstart', handleTouchStart, { passive: doesSupportTouchActionNone(), }); return () => { - doc.removeEventListener('touchstart', handleTouchStart); + columnsHeaderElement?.removeEventListener('touchstart', handleTouchStart); clearTimeout(stopResizeEventTimeout.current); stopListening(); }; - }, [stopListening, apiRef, handleTouchStart]); + }, [columnsHeaderElement, handleTouchStart, stopListening]); return React.useMemo( () => ({ From bf5ea87a18ae642c71daec2c603279a47dfb4258 Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 4 Nov 2020 14:51:31 +0100 Subject: [PATCH 06/13] Add support for touch column resize --- .../styled-wrappers/GridRootStyles.ts | 1 + .../grid/hooks/features/useColumnResize.tsx | 133 +++++++++++++++++- .../features/virtualization/useVirtualRows.ts | 2 +- .../_modules_/grid/hooks/utils/useScrollFn.ts | 1 - .../src/stories/grid-resize.stories.tsx | 2 +- 5 files changed, 134 insertions(+), 5 deletions(-) diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index da560bd9f257..4c271d9bda63 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -10,6 +10,7 @@ export const useStyles = makeStyles( const gridStyle: { root: any } = { root: { flex: 1, + touchAction: 'none', boxSizing: 'border-box', position: 'relative', border: `1px solid ${borderColor}`, diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index 8e71971e713a..a3dc5865b2b5 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -18,6 +18,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api const colCellElementsRef = React.useRef>(); const initialOffset = React.useRef(); const stopResizeEventTimeout = React.useRef(); + const touchId = React.useRef(); const updateWidth = (newWidth: number) => { logger.debug(`Updating width to ${newWidth} for col ${colDefRef.current!.field}`); @@ -110,12 +111,134 @@ export const useColumnResize = (columnsRef: React.RefObject, api doc.addEventListener('mouseup', handleResizeMouseUp); }); + // TODO: remove support for Safari < 13. + // https://caniuse.com/#search=touch-action + // + // Safari, on iOS, supports touch action since v13. + // Over 80% of the iOS phones are compatible + // in August 2020. + let cachedSupportsTouchActionNone; + function doesSupportTouchActionNone() { + if (cachedSupportsTouchActionNone === undefined) { + const element = document.createElement('div'); + element.style.touchAction = 'none'; + document.body.appendChild(element); + cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none'; + element.parentElement!.removeChild(element); + } + return cachedSupportsTouchActionNone; + } + + function trackFinger(event, currentTouchId) { + if (currentTouchId !== undefined && event.changedTouches) { + for (let i = 0; i < event.changedTouches.length; i += 1) { + const touch = event.changedTouches[i]; + if (touch.identifier === currentTouchId) { + return { + x: touch.clientX, + y: touch.clientY, + }; + } + } + + return false; + } + + return { + x: event.clientX, + y: event.clientY, + }; + } + + const handleTouchEnd = useEventCallback((nativeEvent) => { + const finger = trackFinger(nativeEvent, touchId.current); + + if (!finger) { + return; + } + + // 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); + }); + + logger.debug( + `Updating col ${colDefRef.current!.field} with new width: ${colDefRef.current!.width}`, + ); + }); + + const handleTouchMove = useEventCallback((nativeEvent) => { + const finger = trackFinger(nativeEvent, touchId.current); + if (!finger) { + return; + } + + // Cancel move in case some other element consumed a touchmove event and it was not fired. + if (nativeEvent.type === 'mousemove' && nativeEvent.buttons === 0) { + handleTouchEnd(nativeEvent); + return; + } + + let newWidth = + initialOffset.current + finger.x - colElementRef.current!.getBoundingClientRect().left; + newWidth = Math.max(MIN_COL_WIDTH, newWidth); + + updateWidth(newWidth); + }); + + const handleTouchStart = useEventCallback((event: React.TouchEvent) => { + // If touch-action: none; is not supported we need to prevent the scroll manually. + if (!doesSupportTouchActionNone()) { + event.preventDefault(); + } + + const touch = event.changedTouches[0]; + if (touch != null) { + // A number that uniquely identifies the current finger in the touch session. + touchId.current = touch.identifier; + } + // const finger = trackFinger(event, touchId.current); + + 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); + + logger.debug(`Start Resize on col ${colDef.field}`); + apiRef.current.publishEvent(COL_RESIZE_START, { field }); + + colDefRef.current = colDef; + colElementRef.current = columnsRef.current!.querySelector( + `[data-field="${colDef.field}"]`, + ) as HTMLDivElement; + + colCellElementsRef.current = findCellElementsFromCol(colElementRef.current) as NodeListOf< + Element + >; + + initialOffset.current = + (colDefRef.current.width as number) - + (touch.clientX - colElementRef.current!.getBoundingClientRect().left); + + const doc = ownerDocument(event.currentTarget as HTMLElement); + doc.addEventListener('touchmove', handleTouchMove); + doc.addEventListener('touchend', handleTouchEnd); + }); + 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]); + doc.removeEventListener('touchmove', handleTouchMove); + doc.removeEventListener('touchend', handleTouchEnd); + }, [apiRef, handleResizeMouseMove, handleResizeMouseUp, handleTouchMove, handleTouchEnd]); React.useEffect(() => { return () => { @@ -124,5 +247,11 @@ export const useColumnResize = (columnsRef: React.RefObject, api }; }, [stopListening]); - return React.useMemo(() => ({ onMouseDown: handleMouseDown }), [handleMouseDown]); + return React.useMemo( + () => ({ + onMouseDown: handleMouseDown, + onTouchStart: handleTouchStart, + }), + [handleMouseDown, handleTouchStart], + ); }; diff --git a/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts b/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts index 9094afcd988d..0f49c2c9106f 100644 --- a/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/virtualization/useVirtualRows.ts @@ -37,7 +37,7 @@ export const useVirtualRows = ( const paginationState = useGridSelector(apiRef, paginationSelector); const totalRowCount = useGridSelector(apiRef, rowCountSelector); - const [scrollTo] = useScrollFn(apiRef, renderingZoneRef, colRef); + const [scrollTo] = useScrollFn(renderingZoneRef, colRef); const [renderedColRef, updateRenderedCols] = useVirtualColumns(options, apiRef); const setRenderingState = React.useCallback( diff --git a/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts b/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts index a9a5d331c9fb..ff546cd1ba35 100644 --- a/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts +++ b/packages/grid/_modules_/grid/hooks/utils/useScrollFn.ts @@ -4,7 +4,6 @@ import { ScrollFn, ScrollParams } from '../../models/params/scrollParams'; import { useLogger } from './useLogger'; export function useScrollFn( - apiRef: any, renderingZoneElementRef: React.RefObject, columnHeadersElementRef: React.RefObject, ): [ScrollFn] { diff --git a/packages/storybook/src/stories/grid-resize.stories.tsx b/packages/storybook/src/stories/grid-resize.stories.tsx index e26a6e61ed6a..ae3a8f73e874 100644 --- a/packages/storybook/src/stories/grid-resize.stories.tsx +++ b/packages/storybook/src/stories/grid-resize.stories.tsx @@ -30,7 +30,7 @@ export const ResizeSmallDataset = () => { Switch sizes
-
+
From b4e055b4b311a114065451e5dac99659f5d8624d Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 5 Nov 2020 17:08:28 +0100 Subject: [PATCH 07/13] Change the touch start event listner from the separator to the document --- .../grid/hooks/features/useColumnResize.tsx | 108 ++++++++++-------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index a3dc5865b2b5..e5d683de5219 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -7,8 +7,48 @@ import { COL_RESIZE_START, COL_RESIZE_STOP } from '../../constants/eventsConstan import { HEADER_CELL_CSS_CLASS } from '../../constants/cssClassesConstants'; import { findCellElementsFromCol } from '../../utils'; import { ApiRef } from '../../models'; +import { CursorCoordinates } from '../../models/api/columnReorderApi'; const MIN_COL_WIDTH = 50; +let cachedSupportsTouchActionNone = false; + +// TODO: remove support for Safari < 13. +// https://caniuse.com/#search=touch-action +// +// Safari, on iOS, supports touch action since v13. +// Over 80% of the iOS phones are compatible +// in August 2020. +function doesSupportTouchActionNone(): boolean { + if (!cachedSupportsTouchActionNone) { + const element = document.createElement('div'); + element.style.touchAction = 'none'; + document.body.appendChild(element); + cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none'; + element.parentElement!.removeChild(element); + } + return cachedSupportsTouchActionNone; +} + +function trackFinger(event, currentTouchId): CursorCoordinates | boolean { + if (currentTouchId !== undefined && event.changedTouches) { + for (let i = 0; i < event.changedTouches.length; i += 1) { + const touch = event.changedTouches[i]; + if (touch.identifier === currentTouchId) { + return { + x: touch.clientX, + y: touch.clientY, + }; + } + } + + return false; + } + + return { + x: event.clientX, + y: event.clientY, + }; +} // TODO improve experience for last column export const useColumnResize = (columnsRef: React.RefObject, apiRef: ApiRef) => { @@ -111,45 +151,6 @@ export const useColumnResize = (columnsRef: React.RefObject, api doc.addEventListener('mouseup', handleResizeMouseUp); }); - // TODO: remove support for Safari < 13. - // https://caniuse.com/#search=touch-action - // - // Safari, on iOS, supports touch action since v13. - // Over 80% of the iOS phones are compatible - // in August 2020. - let cachedSupportsTouchActionNone; - function doesSupportTouchActionNone() { - if (cachedSupportsTouchActionNone === undefined) { - const element = document.createElement('div'); - element.style.touchAction = 'none'; - document.body.appendChild(element); - cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none'; - element.parentElement!.removeChild(element); - } - return cachedSupportsTouchActionNone; - } - - function trackFinger(event, currentTouchId) { - if (currentTouchId !== undefined && event.changedTouches) { - for (let i = 0; i < event.changedTouches.length; i += 1) { - const touch = event.changedTouches[i]; - if (touch.identifier === currentTouchId) { - return { - x: touch.clientX, - y: touch.clientY, - }; - } - } - - return false; - } - - return { - x: event.clientX, - y: event.clientY, - }; - } - const handleTouchEnd = useEventCallback((nativeEvent) => { const finger = trackFinger(nativeEvent, touchId.current); @@ -185,14 +186,21 @@ export const useColumnResize = (columnsRef: React.RefObject, api } let newWidth = - initialOffset.current + finger.x - colElementRef.current!.getBoundingClientRect().left; + initialOffset.current! + + (finger as CursorCoordinates).x - + colElementRef.current!.getBoundingClientRect().left; newWidth = Math.max(MIN_COL_WIDTH, newWidth); updateWidth(newWidth); }); - const handleTouchStart = useEventCallback((event: React.TouchEvent) => { + const handleTouchStart = useEventCallback((event) => { // If touch-action: none; is not supported we need to prevent the scroll manually. + colElementRef.current = event.target.closest(`.${HEADER_CELL_CSS_CLASS}`) as HTMLDivElement; + + // Let the event bubble if the target is not a col separator + if (!colElementRef.current) return; + if (!doesSupportTouchActionNone()) { event.preventDefault(); } @@ -202,12 +210,8 @@ export const useColumnResize = (columnsRef: React.RefObject, api // A number that uniquely identifies the current finger in the touch session. touchId.current = touch.identifier; } - // const finger = trackFinger(event, touchId.current); - colElementRef.current = event.currentTarget.closest( - `.${HEADER_CELL_CSS_CLASS}`, - ) as HTMLDivElement; - const field = colElementRef.current.getAttribute('data-field') as string; + const field = colElementRef.current!.getAttribute('data-field') as string; const colDef = apiRef.current.getColumnFromField(field); logger.debug(`Start Resize on col ${colDef.field}`); @@ -241,17 +245,23 @@ export const useColumnResize = (columnsRef: React.RefObject, api }, [apiRef, handleResizeMouseMove, handleResizeMouseUp, handleTouchMove, handleTouchEnd]); React.useEffect(() => { + const doc = ownerDocument(apiRef.current.rootElementRef!.current as HTMLElement); + doc.addEventListener('touchstart', handleTouchStart, { + passive: doesSupportTouchActionNone(), + }); + return () => { + doc.removeEventListener('touchstart', handleTouchStart); + clearTimeout(stopResizeEventTimeout.current); stopListening(); }; - }, [stopListening]); + }, [stopListening, apiRef, handleTouchStart]); return React.useMemo( () => ({ onMouseDown: handleMouseDown, - onTouchStart: handleTouchStart, }), - [handleMouseDown, handleTouchStart], + [handleMouseDown], ); }; From 4b4e4a358d022eec001143f3d5d666a0b10de925 Mon Sep 17 00:00:00 2001 From: Danail H Date: Fri, 6 Nov 2020 13:29:56 +0100 Subject: [PATCH 08/13] Allow resizing on mobile only when column separator is touched --- .../components/styled-wrappers/GridRootStyles.ts | 2 +- .../grid/constants/cssClassesConstants.ts | 1 + .../grid/hooks/features/useColumnResize.tsx | 16 ++++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index 4c271d9bda63..0c6cfc810745 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -10,7 +10,6 @@ export const useStyles = makeStyles( const gridStyle: { root: any } = { root: { flex: 1, - touchAction: 'none', boxSizing: 'border-box', position: 'relative', border: `1px solid ${borderColor}`, @@ -125,6 +124,7 @@ export const useStyles = makeStyles( }, '& .MuiDataGrid-columnSeparatorResizable': { cursor: 'col-resize', + touchAction: 'none', '&:hover, &.Mui-resizing': { color: theme.palette.text.primary, }, diff --git a/packages/grid/_modules_/grid/constants/cssClassesConstants.ts b/packages/grid/_modules_/grid/constants/cssClassesConstants.ts index dd62b6933a9a..92a4b926a9d9 100644 --- a/packages/grid/_modules_/grid/constants/cssClassesConstants.ts +++ b/packages/grid/_modules_/grid/constants/cssClassesConstants.ts @@ -2,6 +2,7 @@ export const CELL_CSS_CLASS = 'MuiDataGrid-cell'; export const ROW_CSS_CLASS = 'MuiDataGrid-row'; export const HEADER_CELL_CSS_CLASS = 'MuiDataGrid-colCell'; +export const HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS = 'MuiDataGrid-columnSeparatorResizable'; export const DATA_CONTAINER_CSS_CLASS = 'data-container'; export const HEADER_CELL_DROP_ZONE_CSS_CLASS = 'MuiDataGrid-colCell-dropZone'; export const HEADER_CELL_DRAGGING_CSS_CLASS = 'MuiDataGrid-colCell-dragging'; diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index e5d683de5219..bd5404d98ef4 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -4,7 +4,10 @@ import { ColDef } from '../../models/colDef'; 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 { + HEADER_CELL_CSS_CLASS, + HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS, +} from '../../constants/cssClassesConstants'; import { findCellElementsFromCol } from '../../utils'; import { ApiRef } from '../../models'; import { CursorCoordinates } from '../../models/api/columnReorderApi'; @@ -195,12 +198,12 @@ export const useColumnResize = (columnsRef: React.RefObject, api }); const handleTouchStart = useEventCallback((event) => { - // If touch-action: none; is not supported we need to prevent the scroll manually. - colElementRef.current = event.target.closest(`.${HEADER_CELL_CSS_CLASS}`) as HTMLDivElement; - + const cellSeparator = event.target.closest( + `.${HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS}`, + ) as HTMLDivElement; // Let the event bubble if the target is not a col separator - if (!colElementRef.current) return; - + if (!cellSeparator) return; + // If touch-action: none; is not supported we need to prevent the scroll manually. if (!doesSupportTouchActionNone()) { event.preventDefault(); } @@ -211,6 +214,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api touchId.current = touch.identifier; } + colElementRef.current = event.target.closest(`.${HEADER_CELL_CSS_CLASS}`) as HTMLDivElement; const field = colElementRef.current!.getAttribute('data-field') as string; const colDef = apiRef.current.getColumnFromField(field); From 8e17584d066d82a5ba62b983e6b4445db7e57a02 Mon Sep 17 00:00:00 2001 From: Danail H Date: Fri, 6 Nov 2020 16:41:33 +0100 Subject: [PATCH 09/13] Attach touchStart event to the columns header element, not the document --- .../grid/hooks/features/useColumnResize.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index bd5404d98ef4..edeb103b0dda 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -62,6 +62,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api const initialOffset = React.useRef(); const stopResizeEventTimeout = React.useRef(); const touchId = React.useRef(); + const columnsHeaderElement = columnsRef.current; const updateWidth = (newWidth: number) => { logger.debug(`Updating width to ${newWidth} for col ${colDefRef.current!.field}`); @@ -135,7 +136,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api apiRef.current.publishEvent(COL_RESIZE_START, { field }); colDefRef.current = colDef; - colElementRef.current = columnsRef.current!.querySelector( + colElementRef.current = columnsHeaderElement!.querySelector( `[data-field="${colDef.field}"]`, ) as HTMLDivElement; @@ -222,7 +223,7 @@ export const useColumnResize = (columnsRef: React.RefObject, api apiRef.current.publishEvent(COL_RESIZE_START, { field }); colDefRef.current = colDef; - colElementRef.current = columnsRef.current!.querySelector( + colElementRef.current = columnsHeaderElement!.querySelector( `[data-field="${colDef.field}"]`, ) as HTMLDivElement; @@ -249,18 +250,17 @@ export const useColumnResize = (columnsRef: React.RefObject, api }, [apiRef, handleResizeMouseMove, handleResizeMouseUp, handleTouchMove, handleTouchEnd]); React.useEffect(() => { - const doc = ownerDocument(apiRef.current.rootElementRef!.current as HTMLElement); - doc.addEventListener('touchstart', handleTouchStart, { + columnsHeaderElement?.addEventListener('touchstart', handleTouchStart, { passive: doesSupportTouchActionNone(), }); return () => { - doc.removeEventListener('touchstart', handleTouchStart); + columnsHeaderElement?.removeEventListener('touchstart', handleTouchStart); clearTimeout(stopResizeEventTimeout.current); stopListening(); }; - }, [stopListening, apiRef, handleTouchStart]); + }, [columnsHeaderElement, handleTouchStart, stopListening]); return React.useMemo( () => ({ From 110f0d6ec024ace93a5db2e4e12752635841e9ee Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 6 Nov 2020 14:08:41 +0100 Subject: [PATCH 10/13] fix hover --- .../grid/components/styled-wrappers/GridRootStyles.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index 0c6cfc810745..6178bf298131 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -125,7 +125,14 @@ export const useStyles = makeStyles( '& .MuiDataGrid-columnSeparatorResizable': { cursor: 'col-resize', touchAction: 'none', - '&:hover, &.Mui-resizing': { + '&:hover': { + color: theme.palette.text.primary, + // Reset on touch devices, it doesn't add specificity + '@media (hover: none)': { + color: borderColor, + }, + }, + '&.Mui-resizing': { color: theme.palette.text.primary, }, }, From 155107289cf8db47369e790298b260cc65a4cf5a Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 6 Nov 2020 20:40:16 +0100 Subject: [PATCH 11/13] minimize git diff --- .../grid/_modules_/grid/hooks/features/useColumnResize.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index edeb103b0dda..cbbab6e0b842 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -262,10 +262,5 @@ export const useColumnResize = (columnsRef: React.RefObject, api }; }, [columnsHeaderElement, handleTouchStart, stopListening]); - return React.useMemo( - () => ({ - onMouseDown: handleMouseDown, - }), - [handleMouseDown], - ); + return React.useMemo(() => ({ onMouseDown: handleMouseDown }), [handleMouseDown]); }; From 3f1a19adffb33fa99953c96aea19b084f3d5831f Mon Sep 17 00:00:00 2001 From: Danail H Date: Mon, 9 Nov 2020 15:03:57 +0100 Subject: [PATCH 12/13] Use findParentElementFromClassName dom util --- .../grid/hooks/features/useColumnResize.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index edeb103b0dda..97d420c0b025 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -8,7 +8,7 @@ import { HEADER_CELL_CSS_CLASS, HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS, } from '../../constants/cssClassesConstants'; -import { findCellElementsFromCol } from '../../utils'; +import { findCellElementsFromCol, findParentElementFromClassName } from '../../utils/domUtils'; import { ApiRef } from '../../models'; import { CursorCoordinates } from '../../models/api/columnReorderApi'; @@ -126,8 +126,9 @@ export const useColumnResize = (columnsRef: React.RefObject, api // Avoid text selection event.preventDefault(); - colElementRef.current = event.currentTarget.closest( - `.${HEADER_CELL_CSS_CLASS}`, + colElementRef.current = findParentElementFromClassName( + event.currentTarget, + HEADER_CELL_CSS_CLASS, ) as HTMLDivElement; const field = colElementRef.current.getAttribute('data-field') as string; const colDef = apiRef.current.getColumnFromField(field); @@ -199,9 +200,10 @@ export const useColumnResize = (columnsRef: React.RefObject, api }); const handleTouchStart = useEventCallback((event) => { - const cellSeparator = event.target.closest( - `.${HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS}`, - ) as HTMLDivElement; + const cellSeparator = findParentElementFromClassName( + event.target, + HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS, + ); // Let the event bubble if the target is not a col separator if (!cellSeparator) return; // If touch-action: none; is not supported we need to prevent the scroll manually. @@ -215,7 +217,10 @@ export const useColumnResize = (columnsRef: React.RefObject, api touchId.current = touch.identifier; } - colElementRef.current = event.target.closest(`.${HEADER_CELL_CSS_CLASS}`) as HTMLDivElement; + colElementRef.current = findParentElementFromClassName( + event.target, + HEADER_CELL_CSS_CLASS, + ) as HTMLDivElement; const field = colElementRef.current!.getAttribute('data-field') as string; const colDef = apiRef.current.getColumnFromField(field); From f63b0e2ed03f7158f48d915b6f2de961dd77b205 Mon Sep 17 00:00:00 2001 From: Danail H Date: Mon, 9 Nov 2020 15:32:06 +0100 Subject: [PATCH 13/13] Add new findHeaderElementFromField dom util --- .../grid/hooks/features/useColumnResize.tsx | 15 ++++++++++----- packages/grid/_modules_/grid/utils/domUtils.ts | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx index 97d420c0b025..614c13cc5573 100644 --- a/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx +++ b/packages/grid/_modules_/grid/hooks/features/useColumnResize.tsx @@ -8,7 +8,12 @@ import { HEADER_CELL_CSS_CLASS, HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS, } from '../../constants/cssClassesConstants'; -import { findCellElementsFromCol, findParentElementFromClassName } from '../../utils/domUtils'; +import { + findCellElementsFromCol, + findParentElementFromClassName, + getFieldFromHeaderElem, + findHeaderElementFromField, +} from '../../utils/domUtils'; import { ApiRef } from '../../models'; import { CursorCoordinates } from '../../models/api/columnReorderApi'; @@ -221,17 +226,17 @@ export const useColumnResize = (columnsRef: React.RefObject, api event.target, HEADER_CELL_CSS_CLASS, ) as HTMLDivElement; - const field = colElementRef.current!.getAttribute('data-field') as string; + const field = getFieldFromHeaderElem(colElementRef.current!); const colDef = apiRef.current.getColumnFromField(field); logger.debug(`Start Resize on col ${colDef.field}`); apiRef.current.publishEvent(COL_RESIZE_START, { field }); colDefRef.current = colDef; - colElementRef.current = columnsHeaderElement!.querySelector( - `[data-field="${colDef.field}"]`, + colElementRef.current = findHeaderElementFromField( + columnsHeaderElement!, + colDef.field, ) as HTMLDivElement; - colCellElementsRef.current = findCellElementsFromCol(colElementRef.current) as NodeListOf< Element >; diff --git a/packages/grid/_modules_/grid/utils/domUtils.ts b/packages/grid/_modules_/grid/utils/domUtils.ts index 825020c494d7..b414cd88427e 100644 --- a/packages/grid/_modules_/grid/utils/domUtils.ts +++ b/packages/grid/_modules_/grid/utils/domUtils.ts @@ -28,6 +28,11 @@ export function getIdFromRowElem(rowEl: Element): string { export function getFieldFromHeaderElem(colCellEl: Element): string { return colCellEl.getAttribute('data-field')!; } + +export function findHeaderElementFromField(elem: Element, field: string): Element | null { + return elem.querySelector(`[data-field="${field}"]`); +} + export function findCellElementsFromCol(col: HTMLElement): NodeListOf | null { const field = col.getAttribute('data-field'); const root = findParentElementFromClassName(col, 'MuiDataGrid-root');