diff --git a/vaadin-confirm-dialog-flow-parent/vaadin-confirm-dialog-flow/src/main/java/com/vaadin/flow/component/confirmdialog/ConfirmDialog.java b/vaadin-confirm-dialog-flow-parent/vaadin-confirm-dialog-flow/src/main/java/com/vaadin/flow/component/confirmdialog/ConfirmDialog.java
index f00f3d169b6..24a76fd7b00 100644
--- a/vaadin-confirm-dialog-flow-parent/vaadin-confirm-dialog-flow/src/main/java/com/vaadin/flow/component/confirmdialog/ConfirmDialog.java
+++ b/vaadin-confirm-dialog-flow-parent/vaadin-confirm-dialog-flow/src/main/java/com/vaadin/flow/component/confirmdialog/ConfirmDialog.java
@@ -28,18 +28,15 @@
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Shortcuts;
-import com.vaadin.flow.component.Synchronize;
import com.vaadin.flow.component.Tag;
-import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.shared.SlotUtils;
+import com.vaadin.flow.component.shared.internal.OverlayAutoAddController;
import com.vaadin.flow.component.shared.internal.OverlayClassListProxy;
import com.vaadin.flow.dom.ClassList;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.Style;
-import com.vaadin.flow.internal.StateTree;
-import com.vaadin.flow.router.NavigationTrigger;
import com.vaadin.flow.shared.Registration;
/**
@@ -105,8 +102,6 @@ public CancelEvent(ConfirmDialog source, boolean fromClient) {
private String height;
private String width;
- private Registration afterProgrammaticNavigationListenerRegistration;
-
/**
* Sets the width of the component content area.
*
@@ -217,21 +212,16 @@ public Optional getAriaDescribedBy() {
getElement().getProperty("accessibleDescriptionRef"));
}
- private boolean autoAddedToTheUi;
-
/**
* Creates an empty dialog with a Confirm button
*/
public ConfirmDialog() {
- getElement().addEventListener("opened-changed", event -> {
- if (!isOpened()) {
- setModality(false);
- }
- if (autoAddedToTheUi && !isOpened()) {
- getElement().removeFromParent();
- autoAddedToTheUi = false;
- }
- });
+ // Initialize auto-add behavior
+ new OverlayAutoAddController<>(this, () -> true);
+
+ addConfirmListener(event -> close());
+ addRejectListener(event -> close());
+ addCancelListener(event -> close());
}
/**
@@ -648,7 +638,6 @@ public void close() {
setOpened(false);
}
- @Synchronize(property = "opened", value = "opened-changed")
public boolean isOpened() {
return getElement().getProperty("opened", false);
}
@@ -664,9 +653,6 @@ public boolean isOpened() {
* close it
*/
public void setOpened(boolean opened) {
- if (opened) {
- ensureAttached();
- }
setModality(opened);
getElement().setProperty("opened", opened);
}
@@ -873,6 +859,9 @@ public Component getComponentAt(int index) {
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
+ updateWidth();
+ updateHeight();
+
// Same as https://github.com/vaadin/flow-components/pull/725
Shortcuts.setShortcutListenOnElement("this._overlayElement", this);
}
@@ -882,45 +871,4 @@ private void setModality(boolean modal) {
getUI().ifPresent(ui -> ui.setChildComponentModal(this, modal));
}
}
-
- private UI getCurrentUI() {
- UI ui = UI.getCurrent();
- if (ui == null) {
- throw new IllegalStateException("UI instance is not available. "
- + "It means that you are calling this method "
- + "out of a normal workflow where it's always implicitly set. "
- + "That may happen if you call the method from the custom thread without "
- + "'UI::access' or from tests without proper initialization.");
- }
- return ui;
- }
-
- private void ensureAttached() {
- UI ui = getCurrentUI();
- StateTree.ExecutionRegistration addToUiRegistration = ui
- .beforeClientResponse(ui, context -> {
- if (getElement().getNode().getParent() == null) {
- ui.addToModalComponent(this);
- autoAddedToTheUi = true;
- updateWidth();
- updateHeight();
- ui.setChildComponentModal(this, true);
- }
- if (afterProgrammaticNavigationListenerRegistration != null) {
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- if (ui.getSession() != null) {
- afterProgrammaticNavigationListenerRegistration = ui
- .addAfterNavigationListener(event -> {
- if (event.getLocationChangeEvent()
- .getTrigger() == NavigationTrigger.PROGRAMMATIC) {
- addToUiRegistration.remove();
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- }
- }
}
diff --git a/vaadin-dialog-flow-parent/vaadin-dialog-flow/src/main/java/com/vaadin/flow/component/dialog/Dialog.java b/vaadin-dialog-flow-parent/vaadin-dialog-flow/src/main/java/com/vaadin/flow/component/dialog/Dialog.java
index 7f9784f2196..92ad8459e73 100644
--- a/vaadin-dialog-flow-parent/vaadin-dialog-flow/src/main/java/com/vaadin/flow/component/dialog/Dialog.java
+++ b/vaadin-dialog-flow-parent/vaadin-dialog-flow/src/main/java/com/vaadin/flow/component/dialog/Dialog.java
@@ -39,6 +39,7 @@
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.shared.HasThemeVariant;
+import com.vaadin.flow.component.shared.internal.OverlayAutoAddController;
import com.vaadin.flow.component.shared.internal.OverlayClassListProxy;
import com.vaadin.flow.dom.ClassList;
import com.vaadin.flow.dom.Element;
@@ -46,8 +47,6 @@
import com.vaadin.flow.dom.ElementDetachEvent;
import com.vaadin.flow.dom.ElementDetachListener;
import com.vaadin.flow.dom.Style;
-import com.vaadin.flow.internal.StateTree;
-import com.vaadin.flow.router.NavigationTrigger;
import com.vaadin.flow.shared.Registration;
/**
@@ -87,7 +86,6 @@ public class Dialog extends Component implements HasComponents, HasSize,
private static final String OVERLAY_LOCATOR_JS = "this.$.overlay";
- private boolean autoAddedToTheUi;
private int configuredCloseActionListeners;
private String minWidth;
private String maxWidth;
@@ -96,8 +94,6 @@ public class Dialog extends Component implements HasComponents, HasSize,
private DialogHeader dialogHeader;
private DialogFooter dialogFooter;
- private Registration afterProgrammaticNavigationListenerRegistration;
-
/**
* Creates an empty dialog.
*/
@@ -132,6 +128,9 @@ public Dialog() {
});
setOverlayRole("dialog");
+
+ // Initialize auto-add behavior
+ new OverlayAutoAddController<>(this, this::isModal);
}
/**
@@ -884,46 +883,6 @@ public void setVisible(boolean visible) {
ui -> ui.setChildComponentModal(this, visible && isModal()));
}
- private UI getCurrentUI() {
- UI ui = UI.getCurrent();
- if (ui == null) {
- throw new IllegalStateException("UI instance is not available. "
- + "It means that you are calling this method "
- + "out of a normal workflow where it's always implicitly set. "
- + "That may happen if you call the method from the custom thread without "
- + "'UI::access' or from tests without proper initialization.");
- }
- return ui;
- }
-
- private void ensureAttached() {
- UI ui = getCurrentUI();
- StateTree.ExecutionRegistration addToUiRegistration = ui
- .beforeClientResponse(ui, context -> {
- if (getElement().getNode().getParent() == null
- && isOpened()) {
- ui.addToModalComponent(this);
- ui.setChildComponentModal(this, isModal());
- autoAddedToTheUi = true;
- }
- if (afterProgrammaticNavigationListenerRegistration != null) {
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- if (ui.getSession() != null) {
- afterProgrammaticNavigationListenerRegistration = ui
- .addAfterNavigationListener(event -> {
- if (event.getLocationChangeEvent()
- .getTrigger() == NavigationTrigger.PROGRAMMATIC) {
- addToUiRegistration.remove();
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- }
- }
-
/**
* Registers event listeners on the dialog's overlay that prevent it from
* closing itself on outside click and escape press. Instead, the event
@@ -982,12 +941,6 @@ public void setOpened(boolean opened) {
}
private void doSetOpened(boolean opened, boolean fromClient) {
- if (opened) {
- ensureAttached();
- } else if (autoAddedToTheUi) {
- getElement().removeFromParent();
- autoAddedToTheUi = false;
- }
setModality(opened && isModal());
getElement().setProperty("opened", opened);
fireEvent(new OpenedChangeEvent(this, fromClient));
diff --git a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddController.java b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddController.java
new file mode 100644
index 00000000000..9d10e90d14a
--- /dev/null
+++ b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/main/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.shared.internal;
+
+import java.io.Serializable;
+
+import com.vaadin.flow.component.Component;
+import com.vaadin.flow.component.UI;
+import com.vaadin.flow.function.SerializableSupplier;
+import com.vaadin.flow.internal.StateTree;
+import com.vaadin.flow.router.NavigationTrigger;
+import com.vaadin.flow.shared.Registration;
+
+/**
+ * An internal controller for automatically adding a component to the UI when
+ * it's opened. Not intended to be used publicly.
+ *
+ * @param
+ * Type of the component that uses this controller.
+ */
+public class OverlayAutoAddController
+ implements Serializable {
+ private final C component;
+ private final SerializableSupplier isModalSupplier;
+
+ private boolean autoAdded;
+ private Registration afterProgrammaticNavigationListenerRegistration;
+
+ public OverlayAutoAddController(C component) {
+ this(component, () -> false);
+ }
+
+ public OverlayAutoAddController(C component,
+ SerializableSupplier isModalSupplier) {
+ this.component = component;
+ this.isModalSupplier = isModalSupplier;
+
+ component.getElement().addPropertyChangeListener("opened", event -> {
+ if (isOpened()) {
+ handleOpen();
+ } else {
+ handleClose();
+ }
+ });
+ }
+
+ private void handleOpen() {
+ UI ui = getUI();
+ StateTree.ExecutionRegistration addToUiRegistration = ui
+ .beforeClientResponse(ui, context -> {
+ if (isOpened() && !isAttached()) {
+ ui.addToModalComponent(component);
+ ui.setChildComponentModal(component,
+ isModalSupplier.get());
+ autoAdded = true;
+ }
+ if (afterProgrammaticNavigationListenerRegistration != null) {
+ afterProgrammaticNavigationListenerRegistration
+ .remove();
+ }
+ });
+ if (ui.getSession() != null) {
+ afterProgrammaticNavigationListenerRegistration = ui
+ .addAfterNavigationListener(event -> {
+ if (event.getLocationChangeEvent()
+ .getTrigger() == NavigationTrigger.PROGRAMMATIC) {
+ addToUiRegistration.remove();
+ afterProgrammaticNavigationListenerRegistration
+ .remove();
+ }
+ });
+ }
+ }
+
+ private void handleClose() {
+ if (!isOpened() && autoAdded) {
+ autoAdded = false;
+ component.getElement().removeFromParent();
+ }
+ }
+
+ private UI getUI() {
+ UI ui = UI.getCurrent();
+ if (ui == null) {
+ throw new IllegalStateException("UI instance is not available. "
+ + "It means that you are calling this method "
+ + "out of a normal workflow where it's always implicitly set. "
+ + "That may happen if you call the method from the custom thread without "
+ + "'UI::access' or from tests without proper initialization.");
+ }
+ return ui;
+ }
+
+ private boolean isOpened() {
+ return component.getElement().getProperty("opened", false);
+ }
+
+ private boolean isAttached() {
+ return component.getElement().getNode().getParent() != null;
+ }
+}
diff --git a/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/test/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddControllerTest.java b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/test/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddControllerTest.java
new file mode 100644
index 00000000000..6bd38585933
--- /dev/null
+++ b/vaadin-flow-components-shared-parent/vaadin-flow-components-base/src/test/java/com/vaadin/flow/component/shared/internal/OverlayAutoAddControllerTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.shared.internal;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import com.vaadin.flow.component.Component;
+import com.vaadin.flow.component.HasComponents;
+import com.vaadin.flow.component.Tag;
+import com.vaadin.flow.component.UI;
+import com.vaadin.flow.function.SerializableSupplier;
+import com.vaadin.flow.router.AfterNavigationEvent;
+import com.vaadin.flow.router.AfterNavigationListener;
+import com.vaadin.flow.router.LocationChangeEvent;
+import com.vaadin.flow.router.NavigationTrigger;
+import com.vaadin.flow.server.VaadinSession;
+
+@NotThreadSafe
+public class OverlayAutoAddControllerTest {
+ private UI ui;
+
+ @Before
+ public void setUp() {
+ ui = Mockito.spy(new TestUI());
+ UI.setCurrent(ui);
+
+ VaadinSession session = Mockito.mock(VaadinSession.class);
+ Mockito.when(session.hasLock()).thenReturn(true);
+ ui.getInternals().setSession(session);
+ }
+
+ @Test
+ public void open_withoutUI_throws() {
+ UI.setCurrent(null);
+
+ TestComponent component = new TestComponent();
+
+ Assert.assertThrows(IllegalStateException.class,
+ () -> component.setOpened(true));
+ }
+
+ @Test
+ public void open_withoutParent_autoAdded() {
+ TestComponent component = new TestComponent();
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ Assert.assertEquals(ui.getElement(),
+ component.getElement().getParent());
+ }
+
+ @Test
+ public void open_withParent_notAutoAdded() {
+ ParentComponent parent = new ParentComponent();
+ TestComponent component = new TestComponent();
+ parent.add(component);
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ Assert.assertEquals(parent.getElement(),
+ component.getElement().getParent());
+ }
+
+ @Test
+ public void open_addParentBeforeClientResponse_notAutoAdded() {
+ ParentComponent parent = new ParentComponent();
+ TestComponent component = new TestComponent();
+
+ component.setOpened(true);
+ parent.add(component);
+ fakeClientResponse();
+
+ Assert.assertEquals(parent.getElement(),
+ component.getElement().getParent());
+ }
+
+ @Test
+ public void open_closeBeforeClientResponse_notAutoAdded() {
+ TestComponent component = new TestComponent();
+
+ component.setOpened(true);
+ component.setOpened(false);
+ fakeClientResponse();
+
+ Assert.assertNull(component.getElement().getParent());
+ }
+
+ @Test
+ public void open_programmaticNavigationBeforeClientResponse_notAutoAdded() {
+ TestComponent component = new TestComponent();
+
+ component.setOpened(true);
+
+ ArgumentCaptor captor = ArgumentCaptor
+ .forClass(AfterNavigationListener.class);
+ Mockito.verify(ui).addAfterNavigationListener(captor.capture());
+
+ LocationChangeEvent locationChangeEvent = Mockito
+ .mock(LocationChangeEvent.class);
+ Mockito.when(locationChangeEvent.getTrigger())
+ .thenReturn(NavigationTrigger.PROGRAMMATIC);
+
+ AfterNavigationEvent afterNavigationEvent = Mockito
+ .mock(AfterNavigationEvent.class);
+ Mockito.when(afterNavigationEvent.getLocationChangeEvent())
+ .thenReturn(locationChangeEvent);
+
+ captor.getValue().afterNavigation(afterNavigationEvent);
+ fakeClientResponse();
+
+ Assert.assertNull(component.getElement().getParent());
+ }
+
+ @Test
+ public void close_withoutParent_autoRemoved() {
+ TestComponent component = new TestComponent();
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ component.setOpened(false);
+
+ Assert.assertNull(component.getElement().getParent());
+ }
+
+ @Test
+ public void close_withParent_notAutoRemoved() {
+ ParentComponent parent = new ParentComponent();
+ TestComponent component = new TestComponent();
+ parent.add(component);
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ component.setOpened(false);
+
+ Assert.assertEquals(parent.getElement(),
+ component.getElement().getParent());
+ }
+
+ @Test
+ public void close_reopenBeforeClientResponse_notAutoRemoved() {
+ TestComponent component = new TestComponent();
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ component.setOpened(false);
+ component.setOpened(true);
+ fakeClientResponse();
+
+ Assert.assertEquals(ui.getElement(),
+ component.getElement().getParent());
+ }
+
+ @Test
+ public void open_withoutModalSupplier_notModal() {
+ TestComponent component = new TestComponent();
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ Mockito.verify(ui, Mockito.times(1)).setChildComponentModal(component,
+ false);
+ }
+
+ @Test
+ public void open_withModalSupplierReturningTrue_isModal() {
+ TestComponent component = new TestComponent(() -> true);
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ Mockito.verify(ui, Mockito.times(1)).setChildComponentModal(component,
+ true);
+ }
+
+ @Test
+ public void open_withModalSupplierReturningFalse_notModal() {
+ TestComponent component = new TestComponent(() -> false);
+
+ component.setOpened(true);
+ fakeClientResponse();
+
+ Mockito.verify(ui, Mockito.times(1)).setChildComponentModal(component,
+ false);
+ }
+
+ private void fakeClientResponse() {
+ ui.getInternals().getStateTree().runExecutionsBeforeClientResponse();
+ ui.getInternals().getStateTree().collectChanges(ignore -> {
+ });
+ }
+
+ @Tag("test")
+ private static class TestComponent extends Component {
+ public TestComponent() {
+ new OverlayAutoAddController<>(this);
+ }
+
+ public TestComponent(SerializableSupplier isModalSupplier) {
+ new OverlayAutoAddController<>(this, isModalSupplier);
+ }
+
+ public void setOpened(boolean opened) {
+ getElement().setProperty("opened", opened);
+ }
+ }
+
+ @Tag("parent")
+ private static class ParentComponent extends Component
+ implements HasComponents {
+ }
+
+ private static class TestUI extends UI {
+ @Override
+ public boolean equals(Object obj) {
+ // Needed for check in UI.setChildComponentModal to pass
+ return true;
+ }
+ }
+}
diff --git a/vaadin-login-flow-parent/vaadin-login-flow/src/main/java/com/vaadin/flow/component/login/LoginOverlay.java b/vaadin-login-flow-parent/vaadin-login-flow/src/main/java/com/vaadin/flow/component/login/LoginOverlay.java
index c84506e2a54..f617680b157 100644
--- a/vaadin-login-flow-parent/vaadin-login-flow/src/main/java/com/vaadin/flow/component/login/LoginOverlay.java
+++ b/vaadin-login-flow-parent/vaadin-login-flow/src/main/java/com/vaadin/flow/component/login/LoginOverlay.java
@@ -22,17 +22,14 @@
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Synchronize;
import com.vaadin.flow.component.Tag;
-import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.shared.SlotUtils;
+import com.vaadin.flow.component.shared.internal.OverlayAutoAddController;
import com.vaadin.flow.component.shared.internal.OverlayClassListProxy;
import com.vaadin.flow.dom.ClassList;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.Style;
-import com.vaadin.flow.internal.StateTree;
-import com.vaadin.flow.router.NavigationTrigger;
-import com.vaadin.flow.shared.Registration;
/**
* Server-side component for the {@code } component.
@@ -57,26 +54,18 @@ public class LoginOverlay extends AbstractLogin implements HasStyle {
private LoginOverlayFooter footer;
private LoginOverlayCustomFormArea customFormArea;
- private boolean autoAddedToTheUi;
-
- private Registration afterProgrammaticNavigationListenerRegistration;
-
public LoginOverlay() {
- initEnsureDetachListener();
+ init();
}
public LoginOverlay(LoginI18n i18n) {
super(i18n);
- initEnsureDetachListener();
+ init();
}
- private void initEnsureDetachListener() {
- getElement().addPropertyChangeListener("opened", event -> {
- if (autoAddedToTheUi && !isOpened()) {
- getElement().removeFromParent();
- autoAddedToTheUi = false;
- }
- });
+ private void init() {
+ // Initialize auto-add behavior
+ new OverlayAutoAddController<>(this);
}
/**
@@ -107,50 +96,11 @@ public boolean isOpened() {
*/
public void setOpened(boolean opened) {
if (opened) {
- ensureAttached();
setEnabled(true);
}
getElement().setProperty("opened", opened);
}
- private UI getCurrentUI() {
- UI ui = UI.getCurrent();
- if (ui == null) {
- throw new IllegalStateException("UI instance is not available. "
- + "It means that you are calling this method "
- + "out of a normal workflow where it's always implicitly set. "
- + "That may happen if you call the method from the custom thread without "
- + "'UI::access' or from tests without proper initialization.");
- }
- return ui;
- }
-
- private void ensureAttached() {
- if (getElement().getNode().getParent() == null) {
- UI ui = getCurrentUI();
- StateTree.ExecutionRegistration addToUiRegistration = ui
- .beforeClientResponse(ui, context -> {
- ui.addToModalComponent(this);
- autoAddedToTheUi = true;
- if (afterProgrammaticNavigationListenerRegistration != null) {
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- if (ui.getSession() != null) {
- afterProgrammaticNavigationListenerRegistration = ui
- .addAfterNavigationListener(event -> {
- if (event.getLocationChangeEvent()
- .getTrigger() == NavigationTrigger.PROGRAMMATIC) {
- addToUiRegistration.remove();
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- }
- }
- }
-
/**
* Sets the application title. Detaches the component title if it was set
* earlier. Note: the method calls {@link #setTitle(Component)}, which will
diff --git a/vaadin-notification-flow-parent/vaadin-notification-flow/src/main/java/com/vaadin/flow/component/notification/Notification.java b/vaadin-notification-flow-parent/vaadin-notification-flow/src/main/java/com/vaadin/flow/component/notification/Notification.java
index 2b45da81457..9f078a5b051 100644
--- a/vaadin-notification-flow-parent/vaadin-notification-flow/src/main/java/com/vaadin/flow/component/notification/Notification.java
+++ b/vaadin-notification-flow-parent/vaadin-notification-flow/src/main/java/com/vaadin/flow/component/notification/Notification.java
@@ -37,14 +37,13 @@
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.shared.HasThemeVariant;
+import com.vaadin.flow.component.shared.internal.OverlayAutoAddController;
import com.vaadin.flow.component.shared.internal.OverlayClassListProxy;
import com.vaadin.flow.dom.ClassList;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementDetachEvent;
import com.vaadin.flow.dom.ElementDetachListener;
import com.vaadin.flow.dom.Style;
-import com.vaadin.flow.internal.StateTree;
-import com.vaadin.flow.router.NavigationTrigger;
import com.vaadin.flow.shared.Registration;
/**
@@ -65,11 +64,6 @@ public class Notification extends Component implements HasComponents, HasStyle,
private static final int DEFAULT_DURATION = 5000;
private static final Position DEFAULT_POSITION = Position.BOTTOM_START;
private static final String OPENED_PROPERTY = "opened";
- private static final String OPENED_CHANGED_EVENT = "opened-changed";
-
- private boolean autoAddedToTheUi = false;
-
- private Registration afterProgrammaticNavigationListenerRegistration;
/**
* Enumeration of all available positions for notification component
@@ -251,18 +245,8 @@ private void initBaseElementsAndListeners() {
event -> fireEvent(
new OpenedChangeEvent(this, event.isUserOriginated())));
- getElement().addEventListener(OPENED_CHANGED_EVENT,
- event -> removeAutoAdded());
- }
-
- /**
- * Removes the notification from its parent if it was added automatically.
- */
- private void removeAutoAdded() {
- if (autoAddedToTheUi && !isOpened()) {
- autoAddedToTheUi = false;
- getElement().removeFromParent();
- }
+ // Initialize auto add behavior
+ new OverlayAutoAddController<>(this);
}
/**
@@ -454,37 +438,6 @@ public void addComponentAtIndex(int index, Component component) {
* it
*/
public void setOpened(boolean opened) {
- UI ui = UI.getCurrent();
- if (ui == null) {
- throw new IllegalStateException("UI instance is not available. "
- + "It means that you are calling this method "
- + "out of a normal workflow where it's always implicitly set. "
- + "That may happen if you call the method from the custom thread without "
- + "'UI::access' or from tests without proper initialization.");
- }
- StateTree.ExecutionRegistration addToUiRegistration = ui
- .beforeClientResponse(ui, context -> {
- if (isOpened()
- && getElement().getNode().getParent() == null) {
- ui.addToModalComponent(this);
- autoAddedToTheUi = true;
- }
- if (afterProgrammaticNavigationListenerRegistration != null) {
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- if (ui.getSession() != null) {
- afterProgrammaticNavigationListenerRegistration = ui
- .addAfterNavigationListener(event -> {
- if (event.getLocationChangeEvent()
- .getTrigger() == NavigationTrigger.PROGRAMMATIC) {
- addToUiRegistration.remove();
- afterProgrammaticNavigationListenerRegistration
- .remove();
- }
- });
- }
getElement().setProperty(OPENED_PROPERTY, opened);
}
@@ -682,7 +635,6 @@ protected void onDetach(DetachEvent detachEvent) {
// itself when its parent, for example a dialog, gets attached
// again.
setOpened(false);
- removeAutoAdded();
});
}