fix: make dashboard widgets closable (#14)
CI / build-and-test (pull_request) Successful in 1m27s
CI / build-and-test (pull_request) Successful in 1m27s
Every GridStackItem now renders a close button next to its drag grip, mirroring the grip's hover affordance. Closing detaches the item server-side; grid-stack.ts already unregisters widgets via its MutationObserver, so the layout is persisted without an extra protocol. Closable is on by default and can be turned off per item with setClosable(false); GridStackItem.CloseEvent lets views react. The GridStackView "remove last" toolbar button is dropped — per-widget close replaces it, so the view no longer tracks a widget stack. Its counter now only grows, keeping a closed widget's id from being handed to a new widget (which would inherit the saved position). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ue9ZtWUBQF4SuSHpzZ3zwq
This commit is contained in:
@@ -5,13 +5,18 @@ import com.example.components.GridStackItem;
|
||||
import com.example.components.GridStackLayout;
|
||||
import com.vaadin.browserless.SpringBrowserlessTest;
|
||||
import com.vaadin.browserless.ViewPackages;
|
||||
import com.vaadin.browserless.internal.ElementUtilsKt;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.dom.DomEvent;
|
||||
import com.vaadin.flow.internal.JacksonUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -32,7 +37,7 @@ class GridStackViewTest extends SpringBrowserlessTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void addAndRemoveWidget_changesItemCount() {
|
||||
void addAndCloseWidget_changesItemCount() {
|
||||
navigate(GridStackView.class);
|
||||
|
||||
GridStackLayout grid = $view(GridStackLayout.class).first();
|
||||
@@ -41,18 +46,59 @@ class GridStackViewTest extends SpringBrowserlessTest {
|
||||
button("gridstack.addWidget").click();
|
||||
assertEquals(initial + 1, grid.getLayout().size());
|
||||
|
||||
button("gridstack.removeWidget").click();
|
||||
clickCloseButton($view(GridStackItem.class).last());
|
||||
assertEquals(initial, grid.getLayout().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeWidget_onDefaultLayout_keepsStaticWidgets() {
|
||||
void closeButton_removesTheClickedWidgetOnly() {
|
||||
navigate(GridStackView.class);
|
||||
|
||||
GridStackLayout grid = $view(GridStackLayout.class).first();
|
||||
button("gridstack.removeWidget").click();
|
||||
GridStackItem first = $view(GridStackItem.class).first();
|
||||
String closedId = first.getItemId();
|
||||
|
||||
assertEquals(4, grid.getLayout().size(), "static widgets must not be removable");
|
||||
clickCloseButton(first);
|
||||
|
||||
List<GridStackItem.Position> layout = grid.getLayout();
|
||||
assertEquals(3, layout.size());
|
||||
assertFalse(layout.stream().anyMatch(p -> closedId.equals(p.id())),
|
||||
"the closed widget must be gone from the layout");
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeListener_firesOnceItemIsDetached() {
|
||||
navigate(GridStackView.class);
|
||||
|
||||
GridStackLayout grid = $view(GridStackLayout.class).first();
|
||||
GridStackItem item = $view(GridStackItem.class).first();
|
||||
int[] fired = {0};
|
||||
item.addCloseListener(e -> fired[0]++);
|
||||
|
||||
item.close();
|
||||
|
||||
assertEquals(1, fired[0]);
|
||||
assertEquals(3, grid.getLayout().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonClosableItem_hasNoCloseButton() {
|
||||
navigate(GridStackView.class);
|
||||
|
||||
GridStackItem item = $view(GridStackItem.class).first();
|
||||
assertTrue(item.isClosable(), "items are closable by default");
|
||||
|
||||
item.setClosable(false);
|
||||
assertFalse(item.isClosable());
|
||||
assertFalse(item.getChildren().anyMatch(child -> child == item.getCloseButton()));
|
||||
}
|
||||
|
||||
/** Fires the DOM click the close button listens for, rather than calling
|
||||
* {@code close()} directly, so the button's own wiring is covered too. */
|
||||
private void clickCloseButton(GridStackItem item) {
|
||||
Div closeButton = item.getCloseButton();
|
||||
ElementUtilsKt._fireDomEvent(closeButton.getElement(),
|
||||
new DomEvent(closeButton.getElement(), "click", JacksonUtils.createObjectNode()));
|
||||
}
|
||||
|
||||
private Button button(String translationKey) {
|
||||
|
||||
Reference in New Issue
Block a user