fix: add a per-widget action menu (#27)
CI / build-and-test (pull_request) Successful in 2m25s

A GridStackItem now carries an overflow menu next to the grip and the
close button: refresh, maximize, duplicate, remove. Maximize and remove
are handled by the item itself; refresh and duplicate are only reported,
since what they mean depends on the widget.

Maximizing only sets a class — the item's gs-* attributes and its
gridstack node are untouched, so restoring is by definition the position
it had.

The menu sits outside the drag-handle selector, like the close button, so
opening it never starts a drag; a Playwright test drags it to prove it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
This commit is contained in:
Pit Friedrich
2026-07-28 21:26:05 +02:00
parent cff232070d
commit 8545b06f41
11 changed files with 537 additions and 8 deletions
@@ -1,10 +1,12 @@
package com.example.widgets;
import com.example.data.InMemoryChartDataService;
import com.vaadin.flow.component.html.Div;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -29,6 +31,14 @@ class WidgetRegistryTest {
"every definition carries a usable default size");
}
/** A widget with no refresher must not blow up when one is asked for: the
* dashboard offers the action per widget, the registry only carries it
* out. */
@Test
void refresh_ignoresAWidgetThatCarriesNoRefresher() {
assertDoesNotThrow(() -> WidgetRegistry.refresh(new Div()));
}
@Test
void require_rejectsAnUnknownType() {
assertThrows(IllegalArgumentException.class, () -> registry.require("nope"));