From 0e2ec0822e97e491b6673acbf718ac76f54ee53c Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 29 Jul 2025 10:52:43 +0200 Subject: [PATCH] [DevTools] More robust resize handling --- .eslintrc.js | 1 + .../devtools/views/Components/Components.css | 12 +- .../devtools/views/Components/Components.js | 121 ++++++++---------- .../src/devtools/views/Components/Tree.css | 1 + 4 files changed, 67 insertions(+), 68 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4e023cd9d33..2c9ad7a4c92 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -608,6 +608,7 @@ module.exports = { symbol: 'readonly', SyntheticEvent: 'readonly', SyntheticMouseEvent: 'readonly', + SyntheticPointerEvent: 'readonly', Thenable: 'readonly', TimeoutID: 'readonly', WheelEventHandler: 'readonly', diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Components.css b/packages/react-devtools-shared/src/devtools/views/Components/Components.css index 5f259cd5255..8df59f72f16 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Components.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/Components.css @@ -16,7 +16,6 @@ .TreeWrapper { flex: 0 0 var(--horizontal-resize-percentage); - overflow: auto; } .InspectedElementWrapper { @@ -32,7 +31,14 @@ .ResizeBar { position: absolute; - left: -2px; + /* + * moving the bar out of its bounding box might cause its hitbox to overlap + * with another scrollbar creating disorienting UX where you both resize and scroll + * at the same time. + * If you adjust this value, double check that starting resize right on this edge + * doesn't also cause scroll + */ + left: 1px; width: 5px; height: 100%; cursor: ew-resize; @@ -52,7 +58,7 @@ } .ResizeBar { - top: -2px; + top: 1px; left: 0; width: 100%; height: 5px; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Components.js b/packages/react-devtools-shared/src/devtools/views/Components/Components.js index 1d99317ba09..b6278c2db99 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Components.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Components.js @@ -29,7 +29,6 @@ type Orientation = 'horizontal' | 'vertical'; type ResizeActionType = | 'ACTION_SET_DID_MOUNT' - | 'ACTION_SET_IS_RESIZING' | 'ACTION_SET_HORIZONTAL_PERCENTAGE' | 'ACTION_SET_VERTICAL_PERCENTAGE'; @@ -40,7 +39,6 @@ type ResizeAction = { type ResizeState = { horizontalPercentage: number, - isResizing: boolean, verticalPercentage: number, }; @@ -81,82 +79,81 @@ function Components(_: {}) { return () => clearTimeout(timeoutID); }, [horizontalPercentage, verticalPercentage]); - const {isResizing} = state; + const onResizeStart = (event: SyntheticPointerEvent) => { + const element = event.currentTarget; + element.setPointerCapture(event.pointerId); + }; + + const onResizeEnd = (event: SyntheticPointerEvent) => { + const element = event.currentTarget; + element.releasePointerCapture(event.pointerId); + }; + + const onResize = (event: SyntheticPointerEvent) => { + const element = event.currentTarget; + const isResizing = element.hasPointerCapture(event.pointerId); + if (!isResizing) { + return; + } - const onResizeStart = () => - dispatch({type: 'ACTION_SET_IS_RESIZING', payload: true}); + const resizeElement = resizeElementRef.current; + const wrapperElement = wrapperElementRef.current; - let onResize; - let onResizeEnd; - if (isResizing) { - onResizeEnd = () => - dispatch({type: 'ACTION_SET_IS_RESIZING', payload: false}); + if (wrapperElement === null || resizeElement === null) { + return; + } - // $FlowFixMe[missing-local-annot] - onResize = event => { - const resizeElement = resizeElementRef.current; - const wrapperElement = wrapperElementRef.current; + event.preventDefault(); - if (!isResizing || wrapperElement === null || resizeElement === null) { - return; - } + const orientation = getOrientation(wrapperElement); - event.preventDefault(); + const {height, width, left, top} = wrapperElement.getBoundingClientRect(); - const orientation = getOrientation(wrapperElement); + const currentMousePosition = + orientation === 'horizontal' ? event.clientX - left : event.clientY - top; - const {height, width, left, top} = wrapperElement.getBoundingClientRect(); + const boundaryMin = MINIMUM_SIZE; + const boundaryMax = + orientation === 'horizontal' + ? width - MINIMUM_SIZE + : height - MINIMUM_SIZE; - const currentMousePosition = - orientation === 'horizontal' - ? event.clientX - left - : event.clientY - top; + const isMousePositionInBounds = + currentMousePosition > boundaryMin && currentMousePosition < boundaryMax; - const boundaryMin = MINIMUM_SIZE; - const boundaryMax = + if (isMousePositionInBounds) { + const resizedElementDimension = + orientation === 'horizontal' ? width : height; + const actionType = orientation === 'horizontal' - ? width - MINIMUM_SIZE - : height - MINIMUM_SIZE; - - const isMousePositionInBounds = - currentMousePosition > boundaryMin && - currentMousePosition < boundaryMax; - - if (isMousePositionInBounds) { - const resizedElementDimension = - orientation === 'horizontal' ? width : height; - const actionType = - orientation === 'horizontal' - ? 'ACTION_SET_HORIZONTAL_PERCENTAGE' - : 'ACTION_SET_VERTICAL_PERCENTAGE'; - const percentage = - (currentMousePosition / resizedElementDimension) * 100; - - setResizeCSSVariable(resizeElement, orientation, percentage); - - dispatch({ - type: actionType, - payload: currentMousePosition / resizedElementDimension, - }); - } - }; - } + ? 'ACTION_SET_HORIZONTAL_PERCENTAGE' + : 'ACTION_SET_VERTICAL_PERCENTAGE'; + const percentage = (currentMousePosition / resizedElementDimension) * 100; + + setResizeCSSVariable(resizeElement, orientation, percentage); + + dispatch({ + type: actionType, + payload: currentMousePosition / resizedElementDimension, + }); + } + }; return ( -
+
-
+
@@ -193,18 +190,12 @@ function initResizeState(): ResizeState { return { horizontalPercentage, - isResizing: false, verticalPercentage, }; } function resizeReducer(state: ResizeState, action: ResizeAction): ResizeState { switch (action.type) { - case 'ACTION_SET_IS_RESIZING': - return { - ...state, - isResizing: action.payload, - }; case 'ACTION_SET_HORIZONTAL_PERCENTAGE': return { ...state, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Tree.css b/packages/react-devtools-shared/src/devtools/views/Components/Tree.css index a65cda45aac..cb2799d4a9c 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Tree.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/Tree.css @@ -38,6 +38,7 @@ font-family: var(--font-family-monospace); font-size: var(--font-size-monospace-normal); line-height: var(--line-height-data); + user-select: none; } .VRule {