From 58fefe9647455cbe1976d9198bf5864f43257fb3 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:59:06 -0300 Subject: [PATCH 01/14] build: set version to 2.2.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3d44729..601340d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.vaadin.addons.flowingcode grid-helpers - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT Grid Helpers Add-on Grid Helpers Add-on for Vaadin Flow https://www.flowingcode.com/en/open-source/ From 9d9195fc9509a42954ded5531c5701e5b9ca81c8 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:52:33 -0300 Subject: [PATCH 02/14] feat: move column toggle into a slot Close #177 --- .../gridhelpers/ColumnToggleHelper.java | 29 ++++++++++--------- .../vaadin/addons/gridhelpers/GridHelper.java | 19 ++++++++++-- .../frontend/fcGridHelper/connector.js | 7 ++++- .../{vaadin-menu-bar.css => styles.css} | 21 ++++++++------ .../frontend/fcGridHelper/vaadin-grid.css | 9 ------ .../addons/gridhelpers/it/ColumnToggleIT.java | 15 ++++++++-- .../gridhelpers/it/GridHelperElement.java | 20 +++++++++++++ .../gridhelpers/test/GridHelperTest.java | 6 ++-- 8 files changed, 84 insertions(+), 42 deletions(-) rename src/main/resources/META-INF/frontend/fcGridHelper/{vaadin-menu-bar.css => styles.css} (60%) diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java index fc08616..28a99f6 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ class ColumnToggleHelper implements Serializable { private final GridHelper helper; - private Column menuToggleColumn; + private MenuBar menuToggle; public void setColumnToggleVisible(boolean visible) { // https://cookbook.vaadin.com/grid-column-toggle @@ -66,7 +66,7 @@ public void setColumnToggleVisible(boolean visible) { } public boolean isColumnToggleVisible() { - return menuToggleColumn != null && menuToggleColumn.isVisible(); + return menuToggle != null; } private void showColumnToggle() { @@ -74,18 +74,20 @@ private void showColumnToggle() { .ifPresent( toggle -> { Grid grid = helper.getGrid(); - if (menuToggleColumn == null) { - menuToggleColumn = grid.addColumn(t -> "").setWidth("auto").setFlexGrow(0); - } else { - menuToggleColumn.setVisible(true); - } - grid.getHeaderRows().get(0).getCell(menuToggleColumn).setComponent(toggle); + removeMenuToggle(); + menuToggle = toggle; + grid.getElement().appendChild(toggle.getElement()); }); } private void hideColumnToggle() { - if (menuToggleColumn != null) { - menuToggleColumn.setVisible(false); + removeMenuToggle(); + } + + private void removeMenuToggle() { + if (menuToggle != null) { + menuToggle.getElement().removeFromParent(); + menuToggle = null; } } @@ -93,6 +95,7 @@ private Optional createMenuToggle() { Grid grid = helper.getGrid(); MenuBar menuBar = new MenuBar(); + menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY.getVariantName()); menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY_INLINE.getVariantName()); MenuItem menuItem = menuBar.addItem(VaadinIcon.ELLIPSIS_DOTS_V.create()); SubMenu subMenu = menuItem.getSubMenu(); @@ -110,6 +113,7 @@ private Optional createMenuToggle() { } menuBar.getThemeNames().add(GRID_HELPER_TOGGLE_THEME); + menuBar.getElement().setAttribute("slot", "fc-column-toggle"); return Optional.of(menuBar).filter(_menuBar -> !_menuBar.getItems().isEmpty()); } @@ -170,7 +174,4 @@ public void setHidingToggleCaption(Column column, String caption) { } } - Column getMenuToggleColumn() { - return menuToggleColumn; - } } diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java index 8ab6408..89adad3 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2025 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ import com.vaadin.flow.shared.Registration; import java.io.Serializable; import java.util.Collection; +import java.util.Objects; import lombok.AccessLevel; import lombok.Getter; import org.slf4j.Logger; @@ -50,7 +51,6 @@ @SuppressWarnings("serial") @JsModule("./fcGridHelper/connector.js") -@CssImport(value = "./fcGridHelper/vaadin-menu-bar.css", themeFor = "vaadin-menu-bar") @CssImport(value = GridHelper.GRID_STYLES, themeFor = "vaadin-grid") @CssImport( value = "./fcGridHelper/vaadin-context-menu-item.css", @@ -67,6 +67,7 @@ @CssImport( value = "./fcGridHelper/vaadin-checkbox.css", themeFor = "vaadin-checkbox") +@CssImport(value = "./fcGridHelper/styles.css") public final class GridHelper implements Serializable { private static final Logger logger = LoggerFactory.getLogger(GridHelper.class); @@ -362,8 +363,20 @@ public static String getHidingToggleCaption(Column column) { return getHelper(column.getGrid()).columnToggleHelper.getHidingToggleCaption(column); } + /** + * Returns whether the given column renders the column toggle. + * + * @param column the column to test, not {@code null} + * @return always {@code false} + * @throws NullPointerException if {@code column} is {@code null} + * @deprecated Since 2.2.0, the column toggle is rendered in the {@code fc-column-toggle} slot + * instead of in a dedicated column, so no column is a menu toggle column and this + * method always returns {@code false}. + */ + @Deprecated(forRemoval = true, since = "2.2.0") public static boolean isMenuToggleColumn(Column column) { - return column == getHelper(column).columnToggleHelper.getMenuToggleColumn(); + Objects.requireNonNull(column); + return false; } // Empty Label diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/connector.js b/src/main/resources/META-INF/frontend/fcGridHelper/connector.js index 9ee5833..fdee270 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/connector.js +++ b/src/main/resources/META-INF/frontend/fcGridHelper/connector.js @@ -29,7 +29,12 @@ import { Grid } from '@vaadin/grid/src/vaadin-grid.js'; (function () { window.Vaadin.Flow.fcGridHelperConnector = { initLazy: grid => { - + if (!grid.shadowRoot.querySelector('slot[name="fc-column-toggle"]')) { + const slot = document.createElement('slot'); + slot.setAttribute('name','fc-column-toggle'); + grid.shadowRoot.appendChild(slot); + } + //https://cookbook.vaadin.com/grid-arrow-selection grid.addEventListener('keyup', function(e) { if (e.keyCode == 32) return; diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-menu-bar.css b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css similarity index 60% rename from src/main/resources/META-INF/frontend/fcGridHelper/vaadin-menu-bar.css rename to src/main/resources/META-INF/frontend/fcGridHelper/styles.css index 2a3c774..47188c9 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-menu-bar.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,18 @@ * limitations under the License. * #L% */ - -:host([theme~="gridHelperToggle"]) { +vaadin-menu-bar[theme~="gridHelperToggle"][slot] { position: absolute; - top: 0; - right: var(--fcgh-toggle-right); - height: 100%; + top: -4px; + right: calc( 6px - var(--vaadin-aura-theme,0)*1px); +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] vaadin-menu-bar-button::part(suffix) { + display:none; } -:host([theme~="gridHelperToggle"]) [part="container"] { - vertical-align: middle; - height: 100%; +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button { + padding: calc(var(--vaadin-aura-theme, 0) * 2px); + --lumo-size-m: 0; } + diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css b/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css index 675f114..91b5817 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css @@ -42,15 +42,6 @@ min-height: calc(var(--lumo-size-xxs) - var(--_lumo-grid-border-width)); } -:host { - --fcgh-toggle-right: 0; -} - -:host([overflow~="right"]), :host([overflow~="left"]), -:host([overflow~="start"]), :host([overflow~="end"]) { - --fcgh-toggle-right: 24px; -} - table[aria-multiselectable="true"] [part~="fcGh-noselect"][first-column] ::slotted(*) { opacity: var(--fcgh-noselect-opacity, 0.5); pointer-events: none; diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java index 4366562..275d86d 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java @@ -52,13 +52,24 @@ public void testColumnToggleVisible() { $server.setColumnToggleVisible(true); assertNotNull("ColumnToggle should be present", grid.getColumnToggleButton()); - assertThat(grid.getVisibleColumns(), hasSize(nColumns + 1)); + assertThat(grid.getVisibleColumns(), hasSize(nColumns)); $server.setColumnToggleVisible(false); assertNull("ColumnToggle should be absent", grid.getColumnToggleButton()); assertThat(grid.getVisibleColumns(), hasSize(nColumns)); } + @Test + public void testColumnToggleSlot() { + $server.setColumnToggleVisible(true); + assertNotNull("ColumnToggle should be present", grid.getColumnToggle()); + assertEquals("fc-column-toggle", grid.getColumnToggleSlotName()); + + $server.setColumnToggleVisible(false); + assertNull("ColumnToggle should be absent", grid.getColumnToggle()); + assertNull(grid.getColumnToggleSlotName()); + } + @Test public void testColumnToggleClick() { int nColumns = grid.getVisibleColumns().size(); @@ -66,9 +77,7 @@ public void testColumnToggleClick() { $server.setColumnToggleVisible(true); grid.getColumnToggleButton().click(); - // the toggle is rendered in its own column assertThat(grid.getColumnToggleElements(), hasSize(nColumns)); - assertThat(grid.getVisibleColumns(), hasSize(++nColumns)); grid.getColumnToggleElements().get(0).setChecked(false); assertThat(grid.getVisibleColumns(), hasSize(nColumns - 1)); diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java index ad44b82..0c45255 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java @@ -76,6 +76,26 @@ public TestBenchElement getColumnToggleButton() { } } + /** + * Returns the column toggle menu bar, which is slotted into the grid, or {@code null} if the + * column toggle is not visible. + */ + public TestBenchElement getColumnToggle() { + List elements = + findElements(By.cssSelector("vaadin-menu-bar[theme~='gridHelperToggle']")); + return (TestBenchElement) elements.stream().findFirst().orElse(null); + } + + /** + * Returns the name of the slot that the column toggle is assigned to, or {@code null} if the + * column toggle is not visible or is not assigned to any slot. + */ + public String getColumnToggleSlotName() { + return (String) executeScript( + "const toggle = this.querySelector('vaadin-menu-bar[theme~=\"gridHelperToggle\"]');" + + "return toggle && toggle.assignedSlot && toggle.assignedSlot.name;"); + } + public List getColumnToggleElements() { try { ElementQuery query; diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java index 9dc31cc..3ac7e5b 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java @@ -184,11 +184,11 @@ public void testSelectOnClick() { } @Test - public void testMenuToggleColumn() { + public void testMenuToggleSlot() { grid.setColumnToggleVisible(true); - Column toggleColumn=grid.getColumns().get(grid.getColumns().size()-1); - assertTrue(GridHelper.isMenuToggleColumn(toggleColumn)); + assertTrue(grid.getElement().getChildren() + .anyMatch(e -> "fc-column-toggle".equals(e.getAttribute("slot")))); } @Test From 88da91cb6a9da7447e3569fa3161cb9190bf0717 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 20 Aug 2026 08:49:16 -0300 Subject: [PATCH 03/14] feat: add API for replacing column toggle icon See #177 --- .../gridhelpers/ColumnToggleHelper.java | 20 +++++++++++++++- .../vaadin/addons/gridhelpers/GridHelper.java | 23 +++++++++++++++++++ .../gridhelpers/ColumnToggleMenuDemo.java | 2 ++ .../vaadin/addons/gridhelpers/LombokDemo.java | 3 ++- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java index 28a99f6..ec68ebf 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java @@ -26,6 +26,7 @@ package com.flowingcode.vaadin.addons.gridhelpers; +import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.checkbox.Checkbox; @@ -38,6 +39,7 @@ import com.vaadin.flow.component.menubar.MenuBarVariant; import com.vaadin.flow.shared.Registration; import java.io.Serializable; +import java.util.Objects; import java.util.Optional; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -56,6 +58,8 @@ class ColumnToggleHelper implements Serializable { private MenuBar menuToggle; + private Component icon; + public void setColumnToggleVisible(boolean visible) { // https://cookbook.vaadin.com/grid-column-toggle if (visible) { @@ -69,6 +73,20 @@ public boolean isColumnToggleVisible() { return menuToggle != null; } + private Component getColumnToggleIcon() { + if (icon == null) { + icon = VaadinIcon.ELLIPSIS_DOTS_V.create(); + } + return icon; + } + + public void setColumnToggleIcon(Component icon) { + this.icon = Objects.requireNonNull(icon); + if (isColumnToggleVisible()) { + showColumnToggle(); + } + } + private void showColumnToggle() { createMenuToggle() .ifPresent( @@ -97,7 +115,7 @@ private Optional createMenuToggle() { MenuBar menuBar = new MenuBar(); menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY.getVariantName()); menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY_INLINE.getVariantName()); - MenuItem menuItem = menuBar.addItem(VaadinIcon.ELLIPSIS_DOTS_V.create()); + MenuItem menuItem = menuBar.addItem(getColumnToggleIcon()); SubMenu subMenu = menuItem.getSubMenu(); for (Column column : grid.getColumns()) { diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java index 89adad3..88d57a5 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java @@ -38,6 +38,7 @@ import com.vaadin.flow.component.grid.HeaderRow; import com.vaadin.flow.component.grid.HeaderRow.HeaderCell; import com.vaadin.flow.component.grid.ItemClickEvent; +import com.vaadin.flow.component.icon.IconFactory; import com.vaadin.flow.function.SerializableFunction; import com.vaadin.flow.function.SerializablePredicate; import com.vaadin.flow.shared.Registration; @@ -310,6 +311,28 @@ public static boolean isColumnToggleVisible(Grid grid) { return getHelper(grid).columnToggleHelper.isColumnToggleVisible(); } + /** + * Sets the icon of the menu that toggles the visibility of grid columns. + * + * @param grid the grid to be configured + * @param icon the component that is rendered in the column toggle + * @throws NullPointerException if {@code icon} is {@code null} + */ + public static void setColumnToggleIcon(Grid grid, Component icon) { + getHelper(grid).columnToggleHelper.setColumnToggleIcon(icon); + } + + /** + * Sets the icon of the menu that toggles the visibility of grid columns. + * + * @param grid the grid to be configured + * @param icon the factory of the icon that is rendered in the column toggle + * @throws NullPointerException if {@code icon} is {@code null} + */ + public static void setColumnToggleIcon(Grid grid, IconFactory icon) { + setColumnToggleIcon(grid, icon.create()); + } + /** * Returns whether this column can be hidden by the user. Default is {@code false}. * diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java index ef0e2f0..7fc543b 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java @@ -24,6 +24,7 @@ import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.Column; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; @@ -43,6 +44,7 @@ public ColumnToggleMenuDemo() { Column lastNameColumn = grid.addColumn(Person::getLastName).setHeader("Last name"); Column countryColumn = grid.addColumn(Person::getCountry).setHeader("Country"); + GridHelper.setColumnToggleIcon(grid, VaadinIcon.CARET_DOWN); GridHelper.setHidingToggleCaption(firstNameColumn, "First name"); GridHelper.setHidingToggleCaption(lastNameColumn, "Last name"); GridHelper.setHidingToggleCaption(countryColumn, "Country"); diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java index 7ef7911..38005af 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java @@ -24,9 +24,9 @@ import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.SelectionMode; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; - import lombok.experimental.ExtensionMethod; @PageTitle("Using Lombok") @@ -55,6 +55,7 @@ public LombokDemo() { grid.getColumns().forEach(c -> c.setAutoWidth(true)); grid.setColumnToggleVisible(true); + grid.setColumnToggleIcon(VaadinIcon.CARET_DOWN); grid.setSelectionColumnFrozen(true); grid.setSelectOnClick(true); grid.setSelectionFilter(Person::isActive); From f6747c612a93dc11813a1c165c769006ba481b76 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:18:54 -0300 Subject: [PATCH 04/14] WIP: refactor: remove the column-based getHelper overload The overload became unused when isMenuToggleColumn stopped resolving the helper of the grid. See #177 --- .../flowingcode/vaadin/addons/gridhelpers/GridHelper.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java index 88d57a5..b821f71 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java @@ -144,11 +144,6 @@ private GridHelper(Grid grid) { } } - @SuppressWarnings("unchecked") - private static GridHelper getHelper(Column column) { - return getHelper((Grid) column.getGrid()); - } - private static GridHelper getHelper(Grid grid) { @SuppressWarnings("unchecked") GridHelper helper = ComponentUtil.getData(grid, GridHelper.class); From e203a7d1333c276fab8518f6931277c4a9bb730e Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:24:03 -0300 Subject: [PATCH 05/14] WIP: feat: fit the column toggle into the first header row Track the height of the first visible header row in the --_fc-grid-helper--header-row-height custom property, and size the toggle to that height, so that the toggle no longer overlaps the second header row. The button is stripped of its padding, border and state overlay in order to occupy exactly the size of the icon, which is configurable through --fc-grid-helper-toggle--button-size. See #177 --- .../frontend/fcGridHelper/connector.js | 57 ++++++++++++++++++- .../META-INF/frontend/fcGridHelper/styles.css | 28 ++++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/connector.js b/src/main/resources/META-INF/frontend/fcGridHelper/connector.js index fdee270..2eb609f 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/connector.js +++ b/src/main/resources/META-INF/frontend/fcGridHelper/connector.js @@ -29,6 +29,13 @@ import { Grid } from '@vaadin/grid/src/vaadin-grid.js'; (function () { window.Vaadin.Flow.fcGridHelperConnector = { initLazy: grid => { + // initLazy runs on every attach, and grid.fcGridHelper is replaced below, + // so the observers of the previous initialization have to be released here + if (grid.fcGridHelper) { + grid.fcGridHelper._toggleHeightResizeObserver.disconnect(); + grid.fcGridHelper._toggleHeightMutationObserver?.disconnect(); + } + if (!grid.shadowRoot.querySelector('slot[name="fc-column-toggle"]')) { const slot = document.createElement('slot'); slot.setAttribute('name','fc-column-toggle'); @@ -143,9 +150,57 @@ import { Grid } from '@vaadin/grid/src/vaadin-grid.js'; observer.width=undefined; grid.dispatchEvent(new CustomEvent("fcgh-responsive-step", { detail: {step: -1} })); } + }, + + _toggleHeightResizeObserver : new ResizeObserver(() => { + grid.fcGridHelper._updateToggleHeight(); + }), + + // keep --_fc-grid-helper--header-row-height in sync with the first visible header row, + // so that the column toggle defaults to the height of that row + _observeToggleHeight : function() { + const thead = grid.shadowRoot.querySelector("thead#header"); + if (!thead) return; + + if (!grid.fcGridHelper._toggleHeightMutationObserver) { + grid.fcGridHelper._toggleHeightMutationObserver = new MutationObserver(() => { + grid.fcGridHelper._updateToggleHeight(); + }); + grid.fcGridHelper._toggleHeightMutationObserver.observe(thead, + {childList: true, subtree: true, attributes: true, attributeFilter: ['hidden']}); + } + + grid.fcGridHelper._updateToggleHeight(); + }, + + _updateToggleHeight : function() { + const row = grid.shadowRoot.querySelector("thead#header tr:not([hidden])"); + const observer = grid.fcGridHelper._toggleHeightResizeObserver; + + if (row !== grid.fcGridHelper._toggleHeightRow) { + if (grid.fcGridHelper._toggleHeightRow) { + observer.unobserve(grid.fcGridHelper._toggleHeightRow); + } + grid.fcGridHelper._toggleHeightRow = row; + if (row) { + observer.observe(row); + } + } + + const height = row ? row.getBoundingClientRect().height : 0; + if (height !== grid.fcGridHelper._toggleHeight) { + grid.fcGridHelper._toggleHeight = height; + if (height > 0) { + grid.style.setProperty('--_fc-grid-helper--header-row-height', height+'px'); + } else { + grid.style.removeProperty('--_fc-grid-helper--header-row-height'); + } + } } - + }; + + grid.fcGridHelper._observeToggleHeight(); } } diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css index 47188c9..90b4a11 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css @@ -19,8 +19,14 @@ */ vaadin-menu-bar[theme~="gridHelperToggle"][slot] { position: absolute; - top: -4px; - right: calc( 6px - var(--vaadin-aura-theme,0)*1px); + top: 0; + right: 12px; + display: flex; + height: var(--_fc-grid-helper--header-row-height, unset); +} + +[theme~="fcGh-dense"] > vaadin-menu-bar[theme~="gridHelperToggle"][slot] { + top: calc(var(--vaadin-aura-theme, 0) * 2px - 1px); } vaadin-menu-bar[theme~="gridHelperToggle"][slot] vaadin-menu-bar-button::part(suffix) { @@ -28,7 +34,23 @@ vaadin-menu-bar[theme~="gridHelperToggle"][slot] vaadin-menu-bar-button::part(su } vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button { - padding: calc(var(--vaadin-aura-theme, 0) * 2px); + padding: 0; + margin: 1px 0; + border: 0; + border-radius: 0; --lumo-size-m: 0; + --_fc-grid-helper-toggle--button-size: var(--fc-grid-helper-toggle--button-size, 16px); + height: var(--_fc-grid-helper-toggle--button-size); +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button vaadin-icon { + --vaadin-icon-visual-size: var(--_fc-grid-helper-toggle--button-size); + --lumo-icon-size-m: var(--_fc-grid-helper-toggle--button-size); + --vaadin-icon-size: var(--_fc-grid-helper-toggle--button-size); + padding: 0; +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button::before { + display: none; } From a4c50563b5e54de1ed95bb8b90d9055bb070beab Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:24:04 -0300 Subject: [PATCH 06/14] WIP: fix: prevent the column toggle from covering cell content The toggle floats over the last column, so reserve the width of the toggle in the cells of that column, and paint the toggle over an opaque background, which is configurable through --fc-grid-helper-toggle--background. See #177 --- .../resources/META-INF/frontend/fcGridHelper/styles.css | 7 +++++++ .../META-INF/frontend/fcGridHelper/vaadin-grid.css | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css index 90b4a11..28dc604 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css @@ -23,6 +23,8 @@ vaadin-menu-bar[theme~="gridHelperToggle"][slot] { right: 12px; display: flex; height: var(--_fc-grid-helper--header-row-height, unset); + --_fc-grid-helper-toggle--background: var(--fc-grid-helper-toggle--background, + var(--lumo-base-color, var(--vaadin-background-color, unset))); } [theme~="fcGh-dense"] > vaadin-menu-bar[theme~="gridHelperToggle"][slot] { @@ -48,6 +50,11 @@ vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button vaadin --lumo-icon-size-m: var(--_fc-grid-helper-toggle--button-size); --vaadin-icon-size: var(--_fc-grid-helper-toggle--button-size); padding: 0; + background: var(--_fc-grid-helper-toggle--background); +} + +[theme~="fcGh-dense"] > vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button { + background: var(--_fc-grid-helper-toggle--background); } vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button::before { diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css b/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css index 91b5817..7785872 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css @@ -63,3 +63,7 @@ table[aria-multiselectable="true"] [part~="fcGh-noselect"][first-column] ::slott height : var(--height-by-rows); max-height : var(--height-by-rows); } + +:host [part~='last-column-cell'] ::slotted(vaadin-grid-cell-content) { + padding-right: calc(var(--fc-grid-helper-toggle--button-size, 16px) - 2px); +} From 90ca36c5ae01e32d081da5c3ec007ce8fd32c294 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:04:29 -0300 Subject: [PATCH 07/14] refactor: remove the unused logger --- .../com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java index b821f71..7010a90 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java @@ -47,8 +47,6 @@ import java.util.Objects; import lombok.AccessLevel; import lombok.Getter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @SuppressWarnings("serial") @JsModule("./fcGridHelper/connector.js") @@ -71,8 +69,6 @@ @CssImport(value = "./fcGridHelper/styles.css") public final class GridHelper implements Serializable { - private static final Logger logger = LoggerFactory.getLogger(GridHelper.class); - private static final String ARROW_SELECTION_PROPERTY = "_fcghArrowSelection"; private static final String ENHANCED_SELECTION_PROPERTY = "_fcghEnhancedSelection"; From e4942f03906bf611f7463b949678e9934f5b02e1 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:04:29 -0300 Subject: [PATCH 08/14] feat: change the default icon of the column toggle See #177 --- .../vaadin/addons/gridhelpers/ColumnToggleHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java index ec68ebf..3873dce 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java @@ -75,7 +75,7 @@ public boolean isColumnToggleVisible() { private Component getColumnToggleIcon() { if (icon == null) { - icon = VaadinIcon.ELLIPSIS_DOTS_V.create(); + icon = VaadinIcon.GRID_H.create(); } return icon; } From f07aaedb7bb1d7e4c4bc7e9efe2253d2afd68759 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:04:29 -0300 Subject: [PATCH 09/14] feat: add API for aligning the column toggle See #177 --- .../addons/gridhelpers/ColumnToggleHelper.java | 17 +++++++++++++++++ .../vaadin/addons/gridhelpers/GridHelper.java | 12 ++++++++++++ .../META-INF/frontend/fcGridHelper/styles.css | 2 ++ 3 files changed, 31 insertions(+) diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java index 3873dce..b365c26 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java @@ -37,8 +37,11 @@ import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.menubar.MenuBar; import com.vaadin.flow.component.menubar.MenuBarVariant; +import com.vaadin.flow.dom.Style; +import com.vaadin.flow.dom.Style.AlignItems; import com.vaadin.flow.shared.Registration; import java.io.Serializable; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import lombok.NonNull; @@ -50,6 +53,8 @@ class ColumnToggleHelper implements Serializable { private static final String GRID_HELPER_TOGGLE_THEME = "gridHelperToggle"; + private static final String TOGGLE_ALIGN_ITEMS_PROPERTY = "--fc-grid-helper-toggle--align-items"; + private static final String TOGGLE_CAPTION_DATA = GridHelper.class.getName() + "#TOGGLE_CAPTION"; private static final String HIDABLE_DATA = GridHelper.class.getName() + "#HIDABLE"; @@ -87,6 +92,18 @@ public void setColumnToggleIcon(Component icon) { } } + public void setColumnToggleAlignment(AlignItems alignment) { + // the custom property is set on the grid and inherited by the slotted menu bar, + // so that it survives the re-creation of the toggle + Style style = helper.getGrid().getStyle(); + if (alignment == null) { + style.remove(TOGGLE_ALIGN_ITEMS_PROPERTY); + } else { + style.set( + TOGGLE_ALIGN_ITEMS_PROPERTY, alignment.name().replace('_', '-').toLowerCase(Locale.ROOT)); + } + } + private void showColumnToggle() { createMenuToggle() .ifPresent( diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java index 7010a90..48d9f14 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java @@ -39,6 +39,7 @@ import com.vaadin.flow.component.grid.HeaderRow.HeaderCell; import com.vaadin.flow.component.grid.ItemClickEvent; import com.vaadin.flow.component.icon.IconFactory; +import com.vaadin.flow.dom.Style; import com.vaadin.flow.function.SerializableFunction; import com.vaadin.flow.function.SerializablePredicate; import com.vaadin.flow.shared.Registration; @@ -324,6 +325,17 @@ public static void setColumnToggleIcon(Grid grid, IconFactory icon) { setColumnToggleIcon(grid, icon.create()); } + /** + * Sets the vertical alignment of the menu that toggles the visibility of grid columns, relative to + * the first header row. + * + * @param grid the grid to be configured + * @param alignment the alignment of the column toggle, or {@code null} to restore the default + */ + public static void setColumnToggleAlignment(Grid grid, Style.AlignItems alignment) { + getHelper(grid).columnToggleHelper.setColumnToggleAlignment(alignment); + } + /** * Returns whether this column can be hidden by the user. Default is {@code false}. * diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css index 28dc604..4e22478 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css @@ -23,11 +23,13 @@ vaadin-menu-bar[theme~="gridHelperToggle"][slot] { right: 12px; display: flex; height: var(--_fc-grid-helper--header-row-height, unset); + align-items: var(--fc-grid-helper-toggle--align-items, start); --_fc-grid-helper-toggle--background: var(--fc-grid-helper-toggle--background, var(--lumo-base-color, var(--vaadin-background-color, unset))); } [theme~="fcGh-dense"] > vaadin-menu-bar[theme~="gridHelperToggle"][slot] { + align-items: var(--fc-grid-helper-toggle--align-items, center); top: calc(var(--vaadin-aura-theme, 0) * 2px - 1px); } From d30c90f66cec4821669410cd13f4f083f7540044 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:15:34 -0300 Subject: [PATCH 10/14] feat: add API for setting the label of the column toggle See #177 --- .../addons/gridhelpers/ColumnToggleHelper.java | 13 +++++++++++++ .../vaadin/addons/gridhelpers/GridHelper.java | 11 +++++++++++ .../META-INF/frontend/fcGridHelper/styles.css | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java index b365c26..4987dfc 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java @@ -34,6 +34,7 @@ import com.vaadin.flow.component.contextmenu.SubMenu; import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.menubar.MenuBar; import com.vaadin.flow.component.menubar.MenuBarVariant; @@ -65,6 +66,8 @@ class ColumnToggleHelper implements Serializable { private Component icon; + private String toggleLabel; + public void setColumnToggleVisible(boolean visible) { // https://cookbook.vaadin.com/grid-column-toggle if (visible) { @@ -92,6 +95,13 @@ public void setColumnToggleIcon(Component icon) { } } + public void setColumnToggleLabel(String label) { + this.toggleLabel = label; + if (isColumnToggleVisible()) { + showColumnToggle(); + } + } + public void setColumnToggleAlignment(AlignItems alignment) { // the custom property is set on the grid and inherited by the slotted menu bar, // so that it survives the re-creation of the toggle @@ -133,6 +143,9 @@ private Optional createMenuToggle() { menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY.getVariantName()); menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY_INLINE.getVariantName()); MenuItem menuItem = menuBar.addItem(getColumnToggleIcon()); + if (toggleLabel != null) { + menuItem.add(new Span(toggleLabel)); + } SubMenu subMenu = menuItem.getSubMenu(); for (Column column : grid.getColumns()) { diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java index 48d9f14..a3674c7 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java @@ -325,6 +325,17 @@ public static void setColumnToggleIcon(Grid grid, IconFactory icon) { setColumnToggleIcon(grid, icon.create()); } + /** + * Sets the text that is rendered next to the icon of the menu that toggles the visibility of grid + * columns. + * + * @param grid the grid to be configured + * @param label the text to show before the icon, or {@code null} to show the icon alone + */ + public static void setColumnToggleLabel(Grid grid, String label) { + getHelper(grid).columnToggleHelper.setColumnToggleLabel(label); + } + /** * Sets the vertical alignment of the menu that toggles the visibility of grid columns, relative to * the first header row. diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css index 4e22478..05cf1e2 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css @@ -47,6 +47,12 @@ vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button { height: var(--_fc-grid-helper-toggle--button-size); } +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button > vaadin-menu-bar-item::part(content) { + font-size: var(--fc-grid-helper-toggle--font-size, 80%); + gap: var(--fc-grid-helper-toggle--gap, 4px); + flex-direction: var(--fc-grid-helper-toggle--flex-direction, row-reverse); +} + vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button vaadin-icon { --vaadin-icon-visual-size: var(--_fc-grid-helper-toggle--button-size); --lumo-icon-size-m: var(--_fc-grid-helper-toggle--button-size); From ca2e1b93a2b4b98f2967c6f93e816c7b89771851 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:04:30 -0300 Subject: [PATCH 11/14] feat(demo): add a selector for the column toggle icon See #177 --- .../addons/gridhelpers/AllFeaturesDemo.java | 59 +++++++++++++++++++ .../META-INF/resources/gridhelpers/styles.css | 4 ++ 2 files changed, 63 insertions(+) diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java index fad5d65..78f0096 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java @@ -35,8 +35,11 @@ import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.SelectionMode; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment; import com.vaadin.flow.component.orderedlayout.FlexComponent.JustifyContentMode; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; @@ -44,6 +47,8 @@ import com.vaadin.flow.component.splitlayout.SplitLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.data.renderer.ComponentRenderer; +import com.vaadin.flow.dom.Style.AlignItems; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import java.util.Arrays; @@ -51,6 +56,7 @@ import java.util.List; import java.util.Map; import java.util.stream.IntStream; +import lombok.AllArgsConstructor; import lombok.experimental.ExtensionMethod; @SuppressWarnings("serial") @@ -100,6 +106,7 @@ public AllFeaturesDemo() { grid.setItems(TestData.initializeData()); grid.setSelectionMode(SelectionMode.MULTI); + ToggleIcon.DEFAULT.apply(grid); grid.getElement().getStyle().set("flex-grow", "1"); GridHelper.getHeaderStyles(grid, grid.getHeaderRows().get(0).getCells().get(1)) @@ -188,6 +195,20 @@ public AllFeaturesDemo() { updateHeightByRowsField(grid, heightByRowsField); heightByRowsField.setStepButtonsVisible(true); + Select toggleIconSelect = new Select<>(); + toggleIconSelect.setLabel("Column toggle icon"); + toggleIconSelect.setItems(ToggleIcon.values()); + toggleIconSelect.setRenderer(new ComponentRenderer<>(item -> { + Icon itemIcon = item.icon.create(); + itemIcon.setSize("var(--lumo-icon-size-s)"); + HorizontalLayout itemLayout = new HorizontalLayout(itemIcon, new Span(item.caption)); + itemLayout.setAlignItems(Alignment.CENTER); + itemLayout.setSpacing(false); + itemLayout.getThemeList().add("spacing-s"); + return itemLayout; + })); + binder.forField(toggleIconSelect).bind(this::getColumnToggleIcon, this::setColumnToggleIcon); + binder.getFields().map(Component.class::cast).forEach(features::add); CompatibilityLabel label = new CompatibilityLabel("Features"); @@ -210,8 +231,37 @@ public AllFeaturesDemo() { grid.addToolbarFooter(hl); } + @AllArgsConstructor + private enum ToggleIcon { + DEFAULT("Default icon", VaadinIcon.GRID_H, null, null), + ELLIPSIS_DOTS("Ellipsis icon, centered", VaadinIcon.ELLIPSIS_DOTS_V, AlignItems.CENTER, null), + CARET_DOWN("Caret down, with label", VaadinIcon.CARET_DOWN, null, "Columns"); + + private final String caption; + + private final VaadinIcon icon; + + private final AlignItems alignment; + + private final String label; + + @Override + public String toString() { + return caption; + } + + void apply(Grid grid) { + grid.setColumnToggleIcon(icon); + grid.setColumnToggleAlignment(alignment); + grid.setColumnToggleLabel(label); + } + + } + private final Map> checkboxes = new HashMap<>(); + private ToggleIcon toggleIcon = ToggleIcon.DEFAULT; + private Checkbox newCheckbox(String labelText, SelectionMode... modes) { Checkbox checkbox = new Checkbox(labelText); if (modes.length > 0) { @@ -262,6 +312,15 @@ private boolean hasSelectionFilter(Grid grid) { return grid.getSelectionFilter() != null; } + private ToggleIcon getColumnToggleIcon(Grid grid) { + return toggleIcon; + } + + private void setColumnToggleIcon(Grid grid, ToggleIcon value) { + toggleIcon = value; + value.apply(grid); + } + private void setDenseTheme(Grid grid, boolean value) { GridHelper.setDenseTheme(grid, value); } diff --git a/src/test/resources/META-INF/resources/gridhelpers/styles.css b/src/test/resources/META-INF/resources/gridhelpers/styles.css index 1430b4a..a5c7421 100644 --- a/src/test/resources/META-INF/resources/gridhelpers/styles.css +++ b/src/test/resources/META-INF/resources/gridhelpers/styles.css @@ -18,6 +18,10 @@ * #L% */ +.grid-helpers-all-features vaadin-grid:not([theme~="fcGh-dense"]) vaadin-menu-bar-button:has([icon="vaadin:caret-down"]) { + margin-top: -1px; +} + label.label { color: var(--lumo-body-text-color); font-size: var(--lumo-font-size-s); From 1a7df155c66bd5b1abe2697947a2a941a0d699c5 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:04:30 -0300 Subject: [PATCH 12/14] test: retry the slotted cell lookup while the grid re-renders --- .../gridhelpers/it/GridHelperElement.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java index 0c45255..f17ab24 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java @@ -26,6 +26,7 @@ import com.vaadin.testbench.ElementQuery; import com.vaadin.testbench.ElementQuery.AttributeMatch.Comparison; import com.vaadin.testbench.TestBenchElement; +import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,8 +34,10 @@ import java.util.Optional; import lombok.NonNull; import org.openqa.selenium.By; +import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.FluentWait; public class GridHelperElement extends MyGridElement { @@ -122,18 +125,36 @@ public TestBenchElement getToolbarFooter() { public TestBenchElement getSelectionCheckbox(int rowIndex) { // assumes that Grid is in multi-selection mode - TestBenchElement cell = getSlottedCell(getRow(rowIndex)); - List elements = cell.findElements(By.tagName("vaadin-checkbox")); - return (TestBenchElement) elements.stream().findFirst().orElse(null); + return getSlottedCellChild(rowIndex, "vaadin-checkbox"); } public TestBenchElement getSelectionRadioButton(int rowIndex) { // assumes that Grid is in single-selection mode - TestBenchElement cell = getSlottedCell(getRow(rowIndex)); - List elements = cell.findElements(By.tagName("vaadin-radio-button")); - return (TestBenchElement) elements.stream().findFirst().orElse(null); + return getSlottedCellChild(rowIndex, "vaadin-radio-button"); } + /** + * Returns the first child with the given tag name in the first slotted cell of a row, or + * {@code null} if the cell has no such child. + * + *

+ * Showing or hiding the selection column makes the grid re-render the column tree, which + * invalidates the row and its slots. The lookup is retried while that happens, so that a stale + * element or a cell whose slot is not rendered yet is not reported as an absent child. + */ + private TestBenchElement getSlottedCellChild(int rowIndex, String tagName) { + return new FluentWait<>(getDriver()) + .withTimeout(Duration.ofSeconds(2)) + .pollingEvery(Duration.ofMillis(50)) + .ignoring(org.openqa.selenium.NoSuchElementException.class, + StaleElementReferenceException.class) + .until(driver -> { + TestBenchElement cell = getSlottedCell(getRow(rowIndex)); + return cell.findElements(By.tagName(tagName)).stream().findFirst() + .map(TestBenchElement.class::cast); + }) + .orElse(null); + } private TestBenchElement getSlottedCell(WebElement e) { String slot = e.findElement(By.tagName("slot")).getAttribute("name"); From b3ec001e03e62e90ce9762d2989757dc1c63ab23 Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:14:07 -0300 Subject: [PATCH 13/14] WIP: update copyright headers squash-into the first in-branch commit that modifies each file --- .../flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java | 2 +- .../vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java | 2 +- .../com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java | 2 +- .../vaadin/addons/gridhelpers/test/GridHelperTest.java | 2 +- src/test/resources/META-INF/resources/gridhelpers/styles.css | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java index 78f0096..04f11a6 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2025 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java index 7fc543b..4c4feed 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java index 38005af..589c245 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java index 3ac7e5b..5bf4819 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/resources/META-INF/resources/gridhelpers/styles.css b/src/test/resources/META-INF/resources/gridhelpers/styles.css index a5c7421..70860fd 100644 --- a/src/test/resources/META-INF/resources/gridhelpers/styles.css +++ b/src/test/resources/META-INF/resources/gridhelpers/styles.css @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 860f90e09119128afb93ccaf05347fa9e7c85b1d Mon Sep 17 00:00:00 2001 From: Javier Godoy <11554739+javier-godoy@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:15:41 -0300 Subject: [PATCH 14/14] WIP: return the slotted cell child from the wait predicate An empty Optional is a non-null value, so FluentWait treated the absent child as success on the first poll and never retried. Return the element or null from the predicate and map the timeout to null. squash-into 1a7df15 Co-Authored-By: Claude Opus 5 (1M context) --- .../gridhelpers/it/GridHelperElement.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java index f17ab24..6a6b53c 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java @@ -143,17 +143,20 @@ public TestBenchElement getSelectionRadioButton(int rowIndex) { * element or a cell whose slot is not rendered yet is not reported as an absent child. */ private TestBenchElement getSlottedCellChild(int rowIndex, String tagName) { - return new FluentWait<>(getDriver()) - .withTimeout(Duration.ofSeconds(2)) - .pollingEvery(Duration.ofMillis(50)) - .ignoring(org.openqa.selenium.NoSuchElementException.class, - StaleElementReferenceException.class) - .until(driver -> { - TestBenchElement cell = getSlottedCell(getRow(rowIndex)); - return cell.findElements(By.tagName(tagName)).stream().findFirst() - .map(TestBenchElement.class::cast); - }) - .orElse(null); + try { + return new FluentWait<>(getDriver()) + .withTimeout(Duration.ofSeconds(2)) + .pollingEvery(Duration.ofMillis(50)) + .ignoring(org.openqa.selenium.NoSuchElementException.class, + StaleElementReferenceException.class) + .until(driver -> { + TestBenchElement cell = getSlottedCell(getRow(rowIndex)); + return cell.findElements(By.tagName(tagName)).stream().findFirst() + .map(TestBenchElement.class::cast).orElse(null); + }); + } catch (TimeoutException e) { + return null; + } } private TestBenchElement getSlottedCell(WebElement e) {