Skip to content

Commit 68127b3

Browse files
authored
fix: add data-slot-ignore attribute to auto-added components (#8250)
1 parent f55eb29 commit 68127b3

4 files changed

Lines changed: 175 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.component.confirmdialog.tests;
17+
18+
import com.vaadin.flow.component.button.Button;
19+
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
20+
import com.vaadin.flow.component.html.Div;
21+
import com.vaadin.flow.router.Route;
22+
23+
@Route(value = "vaadin-confirm-dialog/child-overlay")
24+
public class ChildOverlayPage extends Div {
25+
public ChildOverlayPage() {
26+
ConfirmDialog dialog = new ConfirmDialog();
27+
dialog.setId("parent-dialog");
28+
dialog.setText("This is the parent dialog");
29+
30+
ConfirmDialog dialog2 = new ConfirmDialog();
31+
dialog2.setText("This is a child dialog");
32+
// Just so the issue is more visible
33+
dialog2.getElement().executeJs("this.$.overlay.style.top='300px'");
34+
35+
Button openDialog = new Button("Open dialogs", e -> {
36+
dialog.open();
37+
dialog2.open();
38+
});
39+
openDialog.setId("open-dialogs");
40+
41+
add(new Div(openDialog));
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.component.confirmdialog.tests;
17+
18+
import org.junit.Assert;
19+
import org.junit.Before;
20+
import org.junit.Test;
21+
22+
import com.vaadin.flow.component.button.testbench.ButtonElement;
23+
import com.vaadin.flow.component.confirmdialog.testbench.ConfirmDialogElement;
24+
import com.vaadin.flow.testutil.TestPath;
25+
import com.vaadin.testbench.TestBenchElement;
26+
import com.vaadin.tests.AbstractComponentIT;
27+
28+
@TestPath("vaadin-confirm-dialog/child-overlay")
29+
public class ChildOverlayIT extends AbstractComponentIT {
30+
31+
private ButtonElement openDialogs;
32+
33+
@Before
34+
public void init() {
35+
open();
36+
37+
openDialogs = $(ButtonElement.class).id("open-dialogs");
38+
}
39+
40+
@Test
41+
public void openDialogs_parentDialogMessageIsVisible() {
42+
openDialogs.click();
43+
var parentDialogElement = $(ConfirmDialogElement.class)
44+
.id("parent-dialog");
45+
var messageElement = (TestBenchElement) executeScript(
46+
"return arguments[0].querySelector(':scope > div')",
47+
parentDialogElement);
48+
49+
Assert.assertEquals("This is the parent dialog",
50+
messageElement.getText());
51+
Assert.assertTrue(messageElement.isDisplayed());
52+
}
53+
54+
}

vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddController.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ public OverlayAutoAddController(C component,
8484
public void add() {
8585
if (!isAttached()) {
8686
UI ui = getUI();
87+
// Mark component as slot-ignored if being added inside another
88+
// modal. This prevents web component SlotController from treating
89+
// auto-added overlays as custom content that should hide default
90+
// slot content
91+
if (ui.hasModalComponent()) {
92+
component.getElement().setAttribute("data-slot-ignore", "");
93+
}
8794
ui.addToModalComponent(component);
8895
ui.setChildComponentModal(component, modalityModeSupplier.get());
8996
autoAdded = true;
@@ -137,6 +144,7 @@ private void handleOpen() {
137144
private void handleClose() {
138145
if (autoAdded) {
139146
autoAdded = false;
147+
component.getElement().removeAttribute("data-slot-ignore");
140148
component.getElement().removeFromParent();
141149
}
142150
}

vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/test/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddControllerTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,76 @@ public void notAutoAdded_remove_doesNothing() {
345345
component.getElement().getParent());
346346
}
347347

348+
@Test
349+
public void open_insideModalComponent_dataSlotIgnoreAttributeSet() {
350+
// Open a modal component first
351+
TestComponent modal = new TestComponent(() -> ModalityMode.STRICT);
352+
modal.setOpened(true);
353+
fakeClientResponse();
354+
355+
// Open another component inside the modal
356+
TestComponent innerComponent = new TestComponent();
357+
innerComponent.setOpened(true);
358+
fakeClientResponse();
359+
360+
// Verify the inner component has data-slot-ignore attribute
361+
Assert.assertTrue(
362+
innerComponent.getElement().hasAttribute("data-slot-ignore"));
363+
}
364+
365+
@Test
366+
public void open_notInsideModalComponent_dataSlotIgnoreAttributeNotSet() {
367+
// Open a component without a modal parent
368+
TestComponent component = new TestComponent();
369+
component.setOpened(true);
370+
fakeClientResponse();
371+
372+
// Verify the component does not have data-slot-ignore attribute
373+
Assert.assertFalse(
374+
component.getElement().hasAttribute("data-slot-ignore"));
375+
}
376+
377+
@Test
378+
public void open_insideModalComponent_close_dataSlotIgnoreAttributeRemoved() {
379+
// Open a modal component first
380+
TestComponent modal = new TestComponent(() -> ModalityMode.STRICT);
381+
modal.setOpened(true);
382+
fakeClientResponse();
383+
384+
// Open another component inside the modal
385+
TestComponent innerComponent = new TestComponent();
386+
innerComponent.setOpened(true);
387+
fakeClientResponse();
388+
389+
// Verify the attribute is set
390+
Assert.assertTrue(
391+
innerComponent.getElement().hasAttribute("data-slot-ignore"));
392+
393+
// Close the component
394+
innerComponent.setOpened(false);
395+
fireClosedEvent(innerComponent);
396+
397+
// Verify the attribute is removed
398+
Assert.assertFalse(
399+
innerComponent.getElement().hasAttribute("data-slot-ignore"));
400+
}
401+
402+
@Test
403+
public void add_insideModalComponent_dataSlotIgnoreAttributeSet() {
404+
// Open a modal component first
405+
TestComponent modal = new TestComponent(() -> ModalityMode.STRICT);
406+
modal.setOpened(true);
407+
fakeClientResponse();
408+
409+
// Add another component using controller.add()
410+
TestComponent innerComponent = new TestComponent();
411+
innerComponent.controller.add();
412+
413+
// Verify the inner component has data-slot-ignore attribute
414+
Assert.assertTrue(
415+
innerComponent.getElement().hasAttribute("data-slot-ignore"));
416+
}
417+
348418
private void fakeClientResponse() {
349419
ui.getInternals().getStateTree().runExecutionsBeforeClientResponse();
350420
ui.getInternals().getStateTree().collectChanges(ignore -> {

0 commit comments

Comments
 (0)