Skip to content

Commit 6f8750a

Browse files
DiegoCardosoclaude
andauthored
fix: style spreadsheet overlays when nested in a shadow root (#9713)
Since #9303, spreadsheet overlays (context menu, popup-button filters, cell comments, tooltips) live in a `#spreadsheet-overlays` container in the component's light DOM, styled by a global `<style>` in `document.head`. When `<vaadin-spreadsheet>` is nested in another element's shadow root — for example embedded as a web component, as in the docs filtering example — the container ends up inside that shadow tree, which the document-level stylesheet cannot cross, so the overlays render unstyled. To reproduce: open the [docs "spreadsheet filtering" example](https://vaadin.com/docs/latest/example?embed=spreadsheet-filtering-wc.js&import=component/spreadsheet/spreadsheet-imports.ts) (the spreadsheet is embedded as a web component) and open a column filter — the popup shows with no rounded corners, background, or shadow. On attach, also adopt the overlay stylesheet onto the container's own root (`this._overlays.getRootNode()`) when that root is a `ShadowRoot`, so the scoped `#spreadsheet-overlays …` rules apply inside the shadow tree. Top-level usage is unchanged: the root is the document, already covered by the existing `document.head` stylesheet. Follow-up to #9303 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QjNdPXWEUciMEXQe9hMgMt --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 305bb4b commit 6f8750a

3 files changed

Lines changed: 123 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* This program is available under Vaadin Commercial License and Service Terms.
5+
*
6+
* See {@literal <https://vaadin.com/commercial-license-and-service-terms>} for the full
7+
* license.
8+
*/
9+
package com.vaadin.flow.component.spreadsheet.tests;
10+
11+
import org.apache.poi.ss.util.CellReference;
12+
13+
import com.vaadin.flow.component.html.Div;
14+
import com.vaadin.flow.component.html.Span;
15+
import com.vaadin.flow.component.spreadsheet.PopupButton;
16+
import com.vaadin.flow.component.spreadsheet.Spreadsheet;
17+
import com.vaadin.flow.dom.ShadowRoot;
18+
import com.vaadin.flow.router.PageTitle;
19+
import com.vaadin.flow.router.Route;
20+
21+
/**
22+
* A {@code <vaadin-spreadsheet>} nested inside another element's shadow root.
23+
* The overlay container ({@code #spreadsheet-overlays}) then lives inside that
24+
* shadow tree, which a {@code document.head} stylesheet cannot reach. Selecting
25+
* a cell opens a popup-button overlay, letting the IT verify the overlay styles
26+
* still reach it. Reproduces the vaadin.com docs filtering example, which
27+
* embeds the spreadsheet as a web component (shadow DOM).
28+
*/
29+
@Route("spreadsheet-in-shadow-root")
30+
@PageTitle("Spreadsheet in shadow root")
31+
public class SpreadsheetInShadowRootPage extends Div {
32+
33+
public SpreadsheetInShadowRootPage() {
34+
Div host = new Div();
35+
host.setId("shadow-host");
36+
host.setWidth("400px");
37+
host.setHeight("300px");
38+
ShadowRoot shadow = host.getElement().attachShadow();
39+
40+
Spreadsheet spreadsheet = new Spreadsheet();
41+
spreadsheet.setHeight("250px");
42+
spreadsheet.addSelectionChangeListener(event -> {
43+
if (event.getAllSelectedCells().size() != 1) {
44+
return;
45+
}
46+
CellReference ref = event.getSelectedCellReference();
47+
CellReference newRef = new CellReference(
48+
spreadsheet.getActiveSheet().getSheetName(), ref.getRow(),
49+
ref.getCol(), false, false);
50+
PopupButton popupButton = new PopupButton();
51+
popupButton.setContent(new Span("Popup content"));
52+
spreadsheet.setPopup(newRef, popupButton);
53+
popupButton.openPopup();
54+
});
55+
56+
shadow.appendChild(spreadsheet.getElement());
57+
add(host);
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* This program is available under Vaadin Commercial License and Service Terms.
5+
*
6+
* See {@literal <https://vaadin.com/commercial-license-and-service-terms>} for the full
7+
* license.
8+
*/
9+
package com.vaadin.flow.component.spreadsheet.test;
10+
11+
import org.junit.Assert;
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
15+
import com.vaadin.flow.component.html.testbench.DivElement;
16+
import com.vaadin.flow.component.spreadsheet.testbench.SpreadsheetElement;
17+
import com.vaadin.flow.testutil.TestPath;
18+
import com.vaadin.tests.AbstractComponentIT;
19+
20+
@TestPath("spreadsheet-in-shadow-root")
21+
public class SpreadsheetInShadowRootIT extends AbstractComponentIT {
22+
23+
@Before
24+
public void init() {
25+
open();
26+
}
27+
28+
@Test
29+
public void spreadsheetNestedInShadowRoot_overlayIsStyled() {
30+
SpreadsheetElement spreadsheet = $(DivElement.class).id("shadow-host")
31+
.$(SpreadsheetElement.class).first();
32+
// Selecting a cell opens a popup-button overlay (see the page).
33+
spreadsheet.getCellAt(2, 2).click();
34+
35+
// The overlay lives inside the host's shadow root, so it can't be
36+
// located at document level; drill through the shadow root instead.
37+
waitUntil(driver -> (Boolean) executeScript(
38+
"return !!document.getElementById('shadow-host').shadowRoot"
39+
+ ".querySelector('.v-spreadsheet-popupbutton-overlay');"));
40+
41+
String borderRadius = (String) executeScript(
42+
"return getComputedStyle(document.getElementById('shadow-host')"
43+
+ ".shadowRoot.querySelector('.v-spreadsheet-popupbutton-overlay'))"
44+
+ ".borderRadius;");
45+
46+
// Unstyled (bug): the UA popover default border-radius is 0px. Styled
47+
// (fixed): the overlay stylesheet, adopted onto the shadow root, sets a
48+
// non-zero border-radius.
49+
Assert.assertNotEquals(
50+
"Overlay styles should reach the overlay even when the "
51+
+ "spreadsheet is nested in a shadow root",
52+
"0px", borderRadius);
53+
}
54+
}

vaadin-spreadsheet-flow-parent/vaadin-spreadsheet-flow/src/main/resources/META-INF/frontend/vaadin-spreadsheet/vaadin-spreadsheet.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ export class VaadinSpreadsheet extends LitElement {
179179

180180
this._firstUpdate = true;
181181
}
182+
183+
// The overlay container lives in light DOM, so the overlay styles are
184+
// injected into `document.head` (see constructor). That does not reach the
185+
// container when `<vaadin-spreadsheet>` is nested inside another element's
186+
// shadow root. In that case, also adopt the overlay styles onto the
187+
// container's root so the scoped rules apply there too.
188+
const root = this._overlays.getRootNode();
189+
if (root instanceof ShadowRoot && !root.adoptedStyleSheets.includes(spreadsheetOverlayStyles.styleSheet)) {
190+
root.adoptedStyleSheets.push(spreadsheetOverlayStyles.styleSheet);
191+
}
182192
}
183193

184194
disconnectedCallback() {

0 commit comments

Comments
 (0)