-
Notifications
You must be signed in to change notification settings - Fork 501
Forward scroll unless focused #6597
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
Changes from all commits
02424d9
3e13237
5c2030d
ae84b22
0b28200
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,5 +155,72 @@ describe('useCanvasInteractions', () => { | |
| expect(mockEvent.preventDefault).not.toHaveBeenCalled() | ||
| expect(mockEvent.stopPropagation).not.toHaveBeenCalled() | ||
| }) | ||
| it('should forward wheel events to canvas when capture element is NOT focused', () => { | ||
| const { get } = useSettingStore() | ||
| vi.mocked(get).mockReturnValue('legacy') | ||
|
|
||
| const captureElement = document.createElement('div') | ||
| captureElement.setAttribute('data-capture-wheel', 'true') | ||
| const textarea = document.createElement('textarea') | ||
| captureElement.appendChild(textarea) | ||
| document.body.appendChild(captureElement) | ||
|
|
||
| const { handleWheel } = useCanvasInteractions() | ||
| const mockEvent = createMockWheelEvent() | ||
| Object.defineProperty(mockEvent, 'target', { value: textarea }) | ||
|
|
||
| handleWheel(mockEvent) | ||
|
|
||
| expect(mockEvent.preventDefault).toHaveBeenCalled() | ||
| expect(mockEvent.stopPropagation).toHaveBeenCalled() | ||
|
|
||
| document.body.removeChild(captureElement) | ||
| }) | ||
|
|
||
| it('should NOT forward wheel events when capture element IS focused', () => { | ||
| const { get } = useSettingStore() | ||
| vi.mocked(get).mockReturnValue('legacy') | ||
|
|
||
| const captureElement = document.createElement('div') | ||
| captureElement.setAttribute('data-capture-wheel', 'true') | ||
| const textarea = document.createElement('textarea') | ||
| captureElement.appendChild(textarea) | ||
| document.body.appendChild(captureElement) | ||
| textarea.focus() | ||
|
|
||
| const { handleWheel } = useCanvasInteractions() | ||
| const mockEvent = createMockWheelEvent() | ||
| Object.defineProperty(mockEvent, 'target', { value: textarea }) | ||
|
|
||
| handleWheel(mockEvent) | ||
|
|
||
| expect(mockEvent.preventDefault).not.toHaveBeenCalled() | ||
| expect(mockEvent.stopPropagation).not.toHaveBeenCalled() | ||
|
|
||
| document.body.removeChild(captureElement) | ||
| }) | ||
|
|
||
| it('should forward ctrl+wheel to canvas when capture element IS focused in standard mode', () => { | ||
| const { get } = useSettingStore() | ||
| vi.mocked(get).mockReturnValue('standard') | ||
|
|
||
| const captureElement = document.createElement('div') | ||
| captureElement.setAttribute('data-capture-wheel', 'true') | ||
| const textarea = document.createElement('textarea') | ||
| captureElement.appendChild(textarea) | ||
| document.body.appendChild(captureElement) | ||
| textarea.focus() | ||
|
|
||
| const { handleWheel } = useCanvasInteractions() | ||
| const mockEvent = createMockWheelEvent(true) | ||
| Object.defineProperty(mockEvent, 'target', { value: textarea }) | ||
|
|
||
| handleWheel(mockEvent) | ||
|
|
||
| expect(mockEvent.preventDefault).toHaveBeenCalled() | ||
| expect(mockEvent.stopPropagation).toHaveBeenCalled() | ||
|
|
||
| document.body.removeChild(captureElement) | ||
| }) | ||
|
Comment on lines
+158
to
+224
|
||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,19 +26,31 @@ export function useCanvasInteractions() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => !(canvasStore.canvas?.read_only ?? false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Returns true if the wheel event target is inside an element that should | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * capture wheel events AND that element (or a descendant) currently has focus. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * This allows two-finger panning to continue over inputs until the user has | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * actively focused the widget, at which point the widget can consume scroll. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const wheelCapturedByFocusedElement = (event: WheelEvent): boolean => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const target = event.target as HTMLElement | null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const captureElement = target?.closest('[data-capture-wheel="true"]') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const active = document.activeElement as Element | null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return !!(captureElement && active && captureElement.contains(active)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const shouldForwardWheelEvent = (event: WheelEvent): boolean => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| !wheelCapturedByFocusedElement(event) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (isStandardNavMode.value && (event.ctrlKey || event.metaKey)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very true.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's actually incredible that ChatGPT was the one that came up with the best review comment here. This is actually something that's insane. I don't know how it managed to catch this one where like CodeRabbit and Copilot both missed it
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So basically the behavior I'm leaning towards right now is that the user would need to click on "Load3D Scene" for them to be able to pan it. I think that's perfectly reasonable because that's really the only solution that lets us let the user pan across the graph without having their pan be stopped in his tracks the second it manages to go over the "Load3D Scene" widget. I think that's reasonable and I made that change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, how does it feel to use? Maybe we can focus the 3D scene when clicking the node.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels fine? Then deselection would open another discussion. And I think that complicates things by introducing widget specific exceptions to a global rule.
Comment on lines
+29
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Prefer function declarations for the new pure helpers. Both helpers are pure and can be declared as functions to match the repo guideline. ♻️ Suggested refactor- const wheelCapturedByFocusedElement = (event: WheelEvent): boolean => {
+ function wheelCapturedByFocusedElement(event: WheelEvent): boolean {
const target = event.target as HTMLElement | null
const captureElement = target?.closest('[data-capture-wheel="true"]')
const active = document.activeElement as Element | null
return !!(captureElement && active && captureElement.contains(active))
}
- const shouldForwardWheelEvent = (event: WheelEvent): boolean =>
- !wheelCapturedByFocusedElement(event) ||
- (isStandardNavMode.value && (event.ctrlKey || event.metaKey))
+ function shouldForwardWheelEvent(event: WheelEvent): boolean {
+ return (
+ !wheelCapturedByFocusedElement(event) ||
+ (isStandardNavMode.value && (event.ctrlKey || event.metaKey))
+ )
+ }As per coding guidelines: Do not use function expressions if it is possible to use function declarations instead. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Handles wheel events from UI components that should be forwarded to canvas | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * when appropriate (e.g., Ctrl+wheel for zoom in standard mode) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleWheel = (event: WheelEvent) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if the wheel event is from an element that wants to capture wheel events | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const target = event.target as HTMLElement | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const captureElement = target?.closest('[data-capture-wheel="true"]') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (captureElement) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Element wants to capture wheel events, don't forward to canvas | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!shouldForwardWheelEvent(event)) return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // In standard mode, Ctrl+wheel should go to canvas for zoom | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isStandardNavMode.value && (event.ctrlKey || event.metaKey)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -87,14 +99,8 @@ export function useCanvasInteractions() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const forwardEventToCanvas = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event: WheelEvent | PointerEvent | MouseEvent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if the wheel event is from an element that wants to capture wheel events | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const target = event.target as HTMLElement | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const captureElement = target?.closest('[data-capture-wheel="true"]') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (captureElement) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Element wants to capture wheel events, don't forward to canvas | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Honor wheel capture only when the element is focused | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (event instanceof WheelEvent && !shouldForwardWheelEvent(event)) return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+102
to
+103
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const canvasEl = app.canvas?.canvas | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!canvasEl) return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test only checks ctrlKey but the implementation also supports metaKey (for macOS Command key). Consider adding a test case for metaKey to ensure both keyboard modifiers work correctly on different platforms.