Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix vertical splitter in variables pane #5391

Merged
merged 4 commits into from
Nov 22, 2024

Conversation

dhruvisompura
Copy link
Contributor

@dhruvisompura dhruvisompura commented Nov 16, 2024

Description

Addresses #4720

Fixes a bug where users were unable to smoothly resize the columns in the variables pane. This PR changes how the vertical splitter handles the events used to resize columns. The vertical splitter component would re-render itself upon a resize which would cause relevant state information to be reset. We now directly interact with the DOM element to avoid this issue.

QA Notes

  • Verify the vertical splitter respects changes made to the sash size from Positron settings
  • Verify the vertical splitter in the data explorer panel behavior has not changed and matches behavior in production version of Positron
  • Verify the vertical splitter in the variables panel can be dragged around (and still respects the min and max widths)

Screenshots

Screen.Recording.2024-11-15.at.7.18.31.PM.mov

The vertical splitter component would re-render itself while being resized. The renders would cause the React component to be re-created and the move event listeners to be detached from the original element the resize was happening on.

Event handling for the resizing operation is now done directly on the DOM elements and not as part of React state to preserve a link to the element that is not possible due to the nature of React component lifecycle.
* VerticalSplitterResizeParams interface. This defines the parameters of a resize operation. When
* invert is true, the mouse delta is subtracted from the starting width instead of being added to
* it, which inverts the resize operation.
* VerticalSplitterResizeParams interface. This defines the parameters of a resize operation.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the comment here since it looked to be incorrect.

/**
* ResizeParams interface. Represents a resize that's in progress.
*/
interface ResizeParams {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer require this interface because the react state variable for the resize params is no longer needed for the new approach to handle resizing.

Comment on lines 399 to 385
onPointerDown={sashPointerDownHandler}
onPointerMove={sashPointerEventHandler}
onLostPointerCapture={sashPointerEventHandler}
onPointerDown={pointerDownHandler}
Copy link
Contributor Author

@dhruvisompura dhruvisompura Nov 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the same approach for resizing that we have in <HorizontalSplitter>. The pointermove and lostpointercapture events are no longer being managed via React. The event listeners for these are added directly to the element in the DOM.

* Sash onPointerDown handler.
* @param e A PointerEvent that describes a user interaction with the pointer.
*/
const sashPointerDownHandler = (e: PointerEvent<HTMLDivElement>) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has been replaced by pointerDownHandler - most of the logic is the same and just been moved over. I may end up renaming pointerDownHandler to sashPointerDownHandler if that's the preference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename pointerDownHandler to sashPointerDownHandler for consistency.

Comment on lines +360 to +362
target.setPointerCapture(e.pointerId);
target.addEventListener('pointermove', pointerMoveHandler);
target.addEventListener('lostpointercapture', lostPointerCaptureHandler);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the secret sauce that fixed the bug. This event listener has been moved to the window body. When the event listener was on the component, a re-render of the component meant that we stopped listening to the original event that was happening.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding correctly, this relies on the lostpointercapture event to fire. I guess there may be some convoluted situation where that doesn't happen like using a keyboard shortcut to toggle the sidebar while actively dragging the sash. Testing this though the callback for lost pointer will still eventually fire and clean stuff up. So I think it's fine!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's a great edge case that you caught! I hadn't tested that so its good to know it works 😅!

@dhruvisompura dhruvisompura marked this pull request as ready for review November 18, 2024 18:23
softwarenerd
softwarenerd previously approved these changes Nov 19, 2024
Copy link
Contributor

@softwarenerd softwarenerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. I think you're right and we should rename pointerDownHandler to sashPointerDownHandler, as I commented below.

* Sash onPointerDown handler.
* @param e A PointerEvent that describes a user interaction with the pointer.
*/
const sashPointerDownHandler = (e: PointerEvent<HTMLDivElement>) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename pointerDownHandler to sashPointerDownHandler for consistency.

nstrayer
nstrayer previously approved these changes Nov 20, 2024
Copy link
Contributor

@nstrayer nstrayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great!
I poked at it and it works as advertised. The only large-scale stylistic thing I might suggest is moving the hook-related logic to a custom hook ala useVerticalSplitter() or something. But there's enough variables that would need to be returned it's not that much cleaner so take-it-or-leave-it.
Great work!

// Setup the resize state.
const resizeParams = onBeginResize();
const startingWidth = collapsed ? sashWidth : resizeParams.columnsWidth;
const target = DOM.getWindow(e.currentTarget).document.body;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first inclination is that the whole window body is too broad, but you're adding the events in a way that they don't clobber anything else listening... so this seems good to me!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, great point! 😄 I had the same exact inclination initially as well and I chatted about this exact thing with @softwarenerd. My understanding is that we need to know when a user does a drag and a release that ends outside of the application window. I'm a little new to handling drag events and followed the existing pattern we have in <HorizontalSplitter>.

Comment on lines +360 to +362
target.setPointerCapture(e.pointerId);
target.addEventListener('pointermove', pointerMoveHandler);
target.addEventListener('lostpointercapture', lostPointerCaptureHandler);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding correctly, this relies on the lostpointercapture event to fire. I guess there may be some convoluted situation where that doesn't happen like using a keyboard shortcut to toggle the sidebar while actively dragging the sash. Testing this though the callback for lost pointer will still eventually fire and clean stuff up. So I think it's fine!

Copy link
Contributor

@nstrayer nstrayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me!

@dhruvisompura dhruvisompura merged commit 3d64ee8 into main Nov 22, 2024
3 checks passed
@dhruvisompura dhruvisompura deleted the fix/variables-pane-vertical-splitter branch November 22, 2024 17:58
@github-actions github-actions bot locked and limited conversation to collaborators Nov 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants