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(); }); }