fix: keep the widget action menu off the drag handle (#44)
CI / build-and-test (pull_request) Successful in 2m24s
CI / build-and-test (pull_request) Successful in 2m24s
The menu's button is one of the item's corner controls, but it was left at its own size: a menu-bar button measures 64px, twice the 32px slot the corner offsets in styles.css assume. Painted after the grip and overlapping it completely, it took every pointer event meant for the grip — widgets could not be dragged at all. Sized to 28px like the grip and the close button, so the slot math holds. The top offset goes back to 14px with it, the 11px having compensated for the taller button. Covered by a Playwright test that checks the grip is the element hit at its own centre and then drags the widget by it; the existing test only proved the opposite direction, that dragging the menu button moves nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
This commit is contained in:
@@ -307,13 +307,26 @@ apex-chart.dialect-sparkline .apexcharts-xaxis {
|
||||
its left one slot (32px) further in. */
|
||||
.dialect-action-menu {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
top: 14px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
/* Its button is a corner control like the grip and the close button, so it is
|
||||
sized like one. A menu-bar button at its own size is 64px wide, i.e. twice
|
||||
its slot: painted after the grip, it swallowed the grip's pointer events and
|
||||
left the widget undraggable. The button is a light-DOM child of the menu bar,
|
||||
so plain selectors reach it. */
|
||||
.dialect-action-menu vaadin-menu-bar-button {
|
||||
width: 28px;
|
||||
min-width: 0;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
color: var(--dialect-ink);
|
||||
}
|
||||
|
||||
.grid-stack-item:has(> .dialect-close-button) > .dialect-action-menu {
|
||||
right: 48px;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.example.e2e;
|
||||
|
||||
import com.microsoft.playwright.Download;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.Mouse;
|
||||
import com.microsoft.playwright.options.BoundingBox;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -12,6 +13,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
@@ -43,6 +45,45 @@ class WidgetActionMenuPlaywrightTest extends PlaywrightTestBase {
|
||||
assertEquals(before, slot(item), "the widget must not have moved");
|
||||
}
|
||||
|
||||
/** The other half of it: the grip has to stay reachable next to the menu.
|
||||
* Both sit in the item's top right corner and the menu is painted after
|
||||
* the grip, so a menu wider than its corner slot takes the grip's pointer
|
||||
* events and the widget cannot be dragged at all any more. */
|
||||
@Test
|
||||
void draggingTheGrip_movesTheWidget() {
|
||||
Locator item = widget();
|
||||
String before = slot(item);
|
||||
|
||||
BoundingBox grip = grip(item).boundingBox();
|
||||
double x = grip.x + grip.width / 2;
|
||||
double y = grip.y + grip.height / 2;
|
||||
assertEquals("dialect-drag-handle", topmostClassAt(x, y),
|
||||
"another corner control covers the grip");
|
||||
|
||||
page.mouse().move(x, y);
|
||||
page.mouse().down();
|
||||
// In steps, not one jump: gridstack starts the drag on the first move
|
||||
// past its threshold and places the item off the moves after that, so a
|
||||
// single move would only ever arm the drag.
|
||||
page.mouse().move(x + 240, y + 200, new Mouse.MoveOptions().setSteps(20));
|
||||
page.mouse().up();
|
||||
|
||||
page.waitForCondition(() -> !before.equals(slot(item)));
|
||||
assertNotEquals(before, slot(item), "the widget must have moved");
|
||||
}
|
||||
|
||||
/** The class of the corner control the browser actually hits at that point,
|
||||
* or the topmost element's own class list if it is no corner control. */
|
||||
private String topmostClassAt(double x, double y) {
|
||||
return (String) page.evaluate("""
|
||||
([x, y]) => {
|
||||
const el = document.elementFromPoint(x, y);
|
||||
const control = el?.closest(
|
||||
'.dialect-drag-handle, .dialect-close-button, .dialect-action-menu');
|
||||
return control ? control.className : String(el?.className ?? el);
|
||||
}""", java.util.List.of(x, y));
|
||||
}
|
||||
|
||||
@Test
|
||||
void menuButton_opensTheActions() {
|
||||
menuButton(widget()).click();
|
||||
@@ -111,6 +152,10 @@ class WidgetActionMenuPlaywrightTest extends PlaywrightTestBase {
|
||||
}""");
|
||||
}
|
||||
|
||||
private Locator grip(Locator item) {
|
||||
return item.locator("> .dialect-drag-handle");
|
||||
}
|
||||
|
||||
private Locator menuButton(Locator item) {
|
||||
return item.locator(".dialect-action-menu vaadin-menu-bar-button").first();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user