Repository navigation
fix(design-system): keep resizing when the cursor crosses an iframe - #531
Conversation
While dragging a resizer handle, mousemove/mouseup are dispatched to an iframe's document once the cursor crosses it, so the parent window stops receiving them: the resize freezes, and releasing over the iframe leaves the panel stuck in resize mode. Add a useIframePointerEventsShield hook that disables pointer events on every iframe in the document for the duration of the drag and restores the previous inline values on release (and on unmount mid-drag), and use it in both WithResizer and HeightResizer, which share the same window-listener drag pattern. Fixes aymericzip#524
The suite loads esbuild through the config bundler, whose startup invariant requires TextEncoder/Uint8Array from a single realm. Under jsdom those globals come from jsdom's own realm, so esbuild fails to load and the whole design-system test task exits 1 on PR CI runs. The test only asserts request batching and needs no DOM.
There was a problem hiding this comment.
I confirm that this PR fixes the initial issue without changing the existing behavior of WithResizer and HeightResizer.
Notes :
- Calling
disableIframePointerEventsmultiple times could prevent the original styles from being restored. - If two resizers use the hook at the same time, they could interfere with each other.
I think these cases are worth checking before merging. Other than that, the approach looks good to me. Good work @yangguan-xue
| // globals for its own realm's versions, which makes esbuild fail to load | ||
| // intermittently. The test asserts on request batching only and needs no DOM, | ||
| // so run it in node. | ||
|
|
There was a problem hiding this comment.
Hmm, why not..
We should check that this works in Github CI.
There was a problem hiding this comment.
RUN v5.0.3 /home/tornado-softwares/Bureau/projects/JS/intlayer
✓ packages/@intlayer/design-system/src/api/pushDictionariesInBatches.test.ts (6 tests) 20ms
✓ pushDictionariesInBatches (6)
✓ pushes every dictionary in batches and merges the results 4ms
✓ keeps at most PUSH_CONCURRENCY requests in flight 11ms
✓ reports the progress of the push 1ms
✓ keeps pushing when a batch throws, and reports its dictionaries 1ms
✓ throws when every batch fails 1ms
✓ reports a failed batch response 0ms
Test Files 1 passed (1)
Tests 6 passed (6)
Start at 21:05:15
Duration 729ms (transform 67%, import 30%, tests 3%, worker 1%)Okay !
| import { fireEvent, render } from '@testing-library/react'; | ||
| import { afterEach, describe, expect, it } from 'vitest'; | ||
| import { WithResizer } from './index'; | ||
|
|
There was a problem hiding this comment.
RUN v5.0.3 /home/tornado-softwares/Bureau/projects/JS/intlayer/packages/@intlayer/design-system
✓ src/components/WithResizer/withResizer.test.tsx (2 tests) 140ms
✓ WithResizer (2)
✓ disables pointer events on iframes while dragging and restores them on release 124ms
✓ restores iframe pointer events when unmounted mid-drag 13ms
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 21:10:13
Duration 1.14s (environment 72%, tests 13%, import 8%, transform 5%, setup 1%, worker 1%)Ok, Your tests pass and seem relevant to me.
| import { fireEvent, render, screen } from '@testing-library/react'; | ||
| import { afterEach, describe, expect, it } from 'vitest'; | ||
| import { HeightResizer } from './index'; | ||
|
|
There was a problem hiding this comment.
RUN v5.0.3 /home/tornado-softwares/Bureau/projects/JS/intlayer/packages/@intlayer/design-system
✓ src/components/HeightResizer/heightResizer.test.tsx (2 tests) 124ms
✓ HeightResizer (2)
✓ disables pointer events on iframes while dragging and restores them on release 108ms
✓ restores iframe pointer events when unmounted mid-drag 13ms
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 21:12:16
Duration 829ms (environment 65%, tests 17%, import 10%, transform 6%, setup 1%, worker 1%)Ok
| /** Disable pointer events on all iframes so the drag keeps tracking over them. */ | ||
| const disableIframePointerEvents = useCallback(() => { | ||
| const saved = new Map<HTMLIFrameElement, string>(); | ||
| for (const iframe of Array.from(document.querySelectorAll('iframe'))) { |
There was a problem hiding this comment.
document.querySelectorAll('iframe')
This can create side effects, but it shouldn't be an issue for us, provided the state is restored at the end of the drag operation.
| }, []); | ||
|
|
||
| /** Restore the pointer events saved when the interaction started. */ | ||
| const restoreIframePointerEvents = useCallback(() => { |
There was a problem hiding this comment.
It is called correctly in our case, but if someone else uses this function and calls it a second time, it appears to overwrite the previous state.
There was a problem hiding this comment.
Sorry I merged quickly and did not take consideration of that comment @tornado-softwares
Let's see how it can be fixed
| @@ -0,0 +1,59 @@ | |||
| import { useCallback, useRef } from 'react'; | |||
|
|
|||
There was a problem hiding this comment.
Well, it seems generally fine, except for the fact that if multiple resizers are used on a single page, I’m concerned that this hook might conflict with other instances of itself I’m not entirely sure, though; perhaps this behavior should be tested.
|
Thanks @yangguan-xue The code seems good to me too, let's merge and try it in live Thanks @yangguan-xue @tornado-softwares |
|
Good catch @tornado-softwares — both points check out. Right now every |
Fixes #524
Problem
On the dashboard editor page, dragging the right panel's resize handle breaks as soon as the cursor crosses the visual editor
<iframe>:mousemove/mouseupget dispatched to the iframe's document, so the parent window stops receiving them. The panel freezes mid-drag, and releasing the mouse over the iframe leaves it stuck in resize mode.Fix
Adds a
useIframePointerEventsShieldhook — the "iframe shield" the issue mentions. When a drag starts it saves everyiframe's inlinepointer-eventsand sets it tonone; on release, and on unmount mid-drag, the previous values are restored. The parent window keeps receiving events no matter what is under the cursor.The hook is wired into both
WithResizerandHeightResizer, since they share the same window-listener drag pattern. (InHeightResizerthe restore lives in an unmount-only effect: its listener effect re-runs when the drag starts, so restoring there would undo the shield instantly.)This also makes the extra
ms-3 md:mx-2spacing workaround on the editor<aside>unnecessary — left in place here, can be removed in a follow-up.Tests
pointer-events: noneon drag start and are restored on release / unmount, preserving pre-existing inline values.bun run build(turbo, 17 packages),tsc --noEmit(typecheck_),biome lintand the design-system vitest suite (341 tests) all pass.