From 3fca376a80db133a3b43b643672506bcabc760ca Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 1 Nov 2026 16:10:49 +0100 Subject: [PATCH 1/3] fe: move the window from the band above an open modal, its backdrop blocked the only place to grab --- app/src/Assets/javascript/window-chrome.js | 52 ++++++++++++++++++++-- tests/e2e/bootstrap/DesktopContext.php | 15 +++++++ tests/e2e/features/shell/window.feature | 10 +++++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/app/src/Assets/javascript/window-chrome.js b/app/src/Assets/javascript/window-chrome.js index b8340c2..44d466e 100644 --- a/app/src/Assets/javascript/window-chrome.js +++ b/app/src/Assets/javascript/window-chrome.js @@ -22,6 +22,44 @@ const CONTROLS = "a, button, input, select, textarea, label, summary, [role=button], [tabindex], [contenteditable], dialog"; + // A press on a modal's backdrop lands on the dialog itself, whose content is all inside its box. + const onBackdrop = (event) => { + const target = /** @type {Element} */ (event.target); + if (!target.matches("dialog")) { + return false; + } + const box = target.getBoundingClientRect(); + return ( + event.clientX < box.left || + event.clientX > box.right || + event.clientY < box.top || + event.clientY > box.bottom + ); + }; + + // The backdrop still closes the dialog on a click, and a window drag takes the release, so the + // drag waits for the pointer to really move. preventDefault() leaves the click alone. + const dragOnceMoved = (press) => { + press.preventDefault(); + const stop = () => { + removeEventListener("mousemove", move); + removeEventListener("mouseup", stop); + }; + const move = (event) => { + if ( + Math.hypot( + event.clientX - press.clientX, + event.clientY - press.clientY, + ) >= 3 + ) { + stop(); + window.adminer.native?.dragWindow(); + } + }; + addEventListener("mousemove", move); + addEventListener("mouseup", stop); + }; + // The toolbar's hairline shows only once the page scrolls under it, or while the window is not // the active one, as Mail's does (window.css): two classes on to draw it by. if (window.adminer.native?.dragWindow) { @@ -43,11 +81,17 @@ if (window.adminer.native?.dragWindow) { document.addEventListener("mousedown", (event) => { const target = /** @type {Element} */ (event.target); + // Full screen has no title bar to stand in for. + if (event.button !== 0 || root.classList.contains("ad-fullscreen")) { + return; + } + if (document.querySelector("dialog[open]")) { + if (event.clientY < band() && onBackdrop(event)) { + dragOnceMoved(event); + } + return; + } if ( - event.button !== 0 || - // Full screen has no title bar to stand in for, and a dialog's backdrop closes it. - root.classList.contains("ad-fullscreen") || - document.querySelector("dialog[open]") || !(target === document.body || event.clientY < band()) || target.closest(CONTROLS) ) { diff --git a/tests/e2e/bootstrap/DesktopContext.php b/tests/e2e/bootstrap/DesktopContext.php index fcce0a1..30f279e 100644 --- a/tests/e2e/bootstrap/DesktopContext.php +++ b/tests/e2e/bootstrap/DesktopContext.php @@ -3444,6 +3444,21 @@ public function pressEmptyToolbar(): void $this->page->mouse()->click((float) $box['x'], (float) $box['y']); } + #[When('I drag the mouse along the backdrop in the toolbar\'s band')] + public function dragBackdropInBand(): void + { + $this->page->mouse()->move(100, 10); + $this->page->mouse()->down(); + $this->page->mouse()->move(140, 10); + $this->page->mouse()->up(); + } + + #[When('I click the backdrop in the toolbar\'s band')] + public function clickBackdropInBand(): void + { + $this->page->mouse()->click(100, 10); + } + #[When('I open the toolbar\'s sort menu')] public function openSortMenu(): void { diff --git a/tests/e2e/features/shell/window.feature b/tests/e2e/features/shell/window.feature index 24e1adc..57707f1 100644 --- a/tests/e2e/features/shell/window.feature +++ b/tests/e2e/features/shell/window.feature @@ -50,6 +50,16 @@ Feature: The window without a title bar When I press the mouse on one of the toolbar's buttons Then the window was asked to move 1 time + Scenario: With a modal open the band above it still moves the window, and a click there does not + When I open the "users" table + And I open the settings dialog + And I drag the mouse along the backdrop in the toolbar's band + Then the window was asked to move 1 time + And the settings dialog is open + When I click the backdrop in the toolbar's band + Then the window was asked to move 1 time + And the settings dialog is open + Scenario: The sidebar stays hidden across pages, its toggle beside the lights When I open the "users" table And I hide the sidebar From 1af9d0efa7c289f0d499a17cf90ad23060a32ca2 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 1 Nov 2026 17:12:25 +0100 Subject: [PATCH 2/3] fe: assert a click on the band closes a dialog, the settings one never closes on a click outside --- tests/e2e/bootstrap/DesktopContext.php | 9 +++++++++ tests/e2e/features/shell/window.feature | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/e2e/bootstrap/DesktopContext.php b/tests/e2e/bootstrap/DesktopContext.php index 30f279e..af891c6 100644 --- a/tests/e2e/bootstrap/DesktopContext.php +++ b/tests/e2e/bootstrap/DesktopContext.php @@ -3221,6 +3221,15 @@ public function clickToolbarSearch(): void )->getByRole('button', ['name' => 'Search'])->click(); } + #[Then('the search box is closed')] + public function searchBoxIsClosed(): void + { + $this->page->waitForFunction( + // language=JavaScript + "() => !document.querySelector('#desktop-search').open", + ); + } + #[Then('the search box is open')] public function searchBoxIsOpen(): void { diff --git a/tests/e2e/features/shell/window.feature b/tests/e2e/features/shell/window.feature index 57707f1..0d45c0c 100644 --- a/tests/e2e/features/shell/window.feature +++ b/tests/e2e/features/shell/window.feature @@ -60,6 +60,14 @@ Feature: The window without a title bar Then the window was asked to move 1 time And the settings dialog is open + Scenario: A click on the band above a dialog that closes on a click outside closes it, without moving the window + When I open the "users" table + And I click the toolbar's search + Then the search box is open + When I click the backdrop in the toolbar's band + Then the search box is closed + And the window was asked to move 0 times + Scenario: The sidebar stays hidden across pages, its toggle beside the lights When I open the "users" table And I hide the sidebar From 5ad5f758eb767cde622011492595da94a7d9d9ea Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 1 Nov 2026 17:12:25 +0100 Subject: [PATCH 3/3] fe: stop waiting to drag once no button is down, a lost release let the next drag move the window --- app/src/Assets/javascript/window-chrome.js | 5 +++++ tests/e2e/bootstrap/DesktopContext.php | 13 +++++++++++++ tests/e2e/features/shell/window.feature | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/app/src/Assets/javascript/window-chrome.js b/app/src/Assets/javascript/window-chrome.js index 44d466e..3f1b824 100644 --- a/app/src/Assets/javascript/window-chrome.js +++ b/app/src/Assets/javascript/window-chrome.js @@ -46,6 +46,11 @@ removeEventListener("mouseup", stop); }; const move = (event) => { + // A lost release would leave this listening, and the next drag anywhere would move the window. + if (!event.buttons) { + stop(); + return; + } if ( Math.hypot( event.clientX - press.clientX, diff --git a/tests/e2e/bootstrap/DesktopContext.php b/tests/e2e/bootstrap/DesktopContext.php index af891c6..d0e742f 100644 --- a/tests/e2e/bootstrap/DesktopContext.php +++ b/tests/e2e/bootstrap/DesktopContext.php @@ -3462,6 +3462,19 @@ public function dragBackdropInBand(): void $this->page->mouse()->up(); } + /** A real press, then the move a browser would report once the button is up: no release event. */ + #[When('I press the backdrop in the toolbar\'s band and the release gets lost')] + public function pressBackdropReleaseLost(): void + { + $this->page->mouse()->move(100, 10); + $this->page->mouse()->down(); + $this->page->evaluate( + // language=JavaScript + "() => dispatchEvent(new MouseEvent('mousemove', { clientX: 140, clientY: 10, buttons: 0 }))", + ); + $this->page->mouse()->up(); + } + #[When('I click the backdrop in the toolbar\'s band')] public function clickBackdropInBand(): void { diff --git a/tests/e2e/features/shell/window.feature b/tests/e2e/features/shell/window.feature index 0d45c0c..cbfb707 100644 --- a/tests/e2e/features/shell/window.feature +++ b/tests/e2e/features/shell/window.feature @@ -68,6 +68,12 @@ Feature: The window without a title bar Then the search box is closed And the window was asked to move 0 times + Scenario: A move with no button down does not move the window, if the release above a modal got lost + When I open the "users" table + And I open the settings dialog + And I press the backdrop in the toolbar's band and the release gets lost + Then the window was asked to move 0 times + Scenario: The sidebar stays hidden across pages, its toggle beside the lights When I open the "users" table And I hide the sidebar