Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@
<artifactId>vaadin-testbench</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.flowingcode.vaadin.test</groupId>
<artifactId>testbench-rpc</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.flowingcode.vaadin.addons.demo</groupId>
<artifactId>commons-demo</artifactId>
Expand Down Expand Up @@ -445,7 +451,8 @@
<exclude>**/test/*</exclude>
<exclude>**/it/*</exclude>
<exclude>**/DemoView.class</exclude>
<exclude>**/DemoLayout.class</exclude>
<exclude>**/DemoLayout.class</exclude>
<exclude>**/AppShellConfiguratorImpl.class</exclude>
</excludes>
</configuration>
</execution>
Expand All @@ -459,7 +466,7 @@
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<vaadin.version>25.0.0</vaadin.version>
<vaadin.version>25.0.6</vaadin.version>
<jetty.version>11.0.26</jetty.version>
</properties>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,17 @@ public void removeRouterLayoutContent(HasElement oldContent) {
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
setOpened(true);
this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').getElementsByTagName('vaadin-login-form-wrapper')[0].replaceChildren())");
this.getElement().executeJs(
LoginOverlayUtils.getLoginFormWrapperScript(
"""
formWrapper.querySelectorAll('[slot="form"], [slot="submit"], [slot="forgot-password"]').forEach(c => c.remove());
"""));
this.getElement().appendChild(content.getElement());
content.getElement().setAttribute("slot", "form");
this.content
.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').getElementsByTagName('vaadin-login-form-wrapper')[0].appendChild(this))");
this.content.getElement().executeJs(
LoginOverlayUtils.getLoginFormWrapperScript(
"""
formWrapper.appendChild(this);
"""));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*-
* #%L
* Extended Login Add-on
* %%
* Copyright (C) 2023 - 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.
* 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.
* #L%
*/
package com.flowingcode.vaadin.addons.extendedlogin;

/**
* Utility class for LoginOverlay.
*/
class LoginOverlayUtils {

Check warning on line 25 in src/main/java/com/flowingcode/vaadin/addons/extendedlogin/LoginOverlayUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a private constructor to hide the implicit public one.

See more on https://sonarcloud.io/project/issues?id=FlowingCode_ExtendedLoginAddon&issues=AZzErk2BTBjk2HgQEnEL&open=AZzErk2BTBjk2HgQEnEL&pullRequest=27

/**
* Generates a JavaScript snippet that finds the overlay wrapper element.
* Handles both Vaadin 24 and Vaadin 25+ compatibility.
*
* @param action the JavaScript code to execute on the overlay wrapper
* @return the complete JavaScript string with the overlay wrapper lookup and
* the provided action
*/
static String getOverlayWrapperScript(String action) {
return """
setTimeout(() => {
var overlayWrapper = document.getElementById('vaadinLoginOverlayWrapper');
if (!overlayWrapper) {
var loginOverlay = document.querySelector('vaadin-login-overlay');
if (loginOverlay && loginOverlay.shadowRoot) {
overlayWrapper = loginOverlay.shadowRoot
.querySelector('vaadin-login-overlay-wrapper');
}
}
if (!overlayWrapper) return;
%s
});
""".formatted(action);
}

/**
* Generates a JavaScript snippet that finds the form element. Handles both
* Vaadin 24 and Vaadin
* 25+ compatibility.
*
* @param action the JavaScript code to execute on the form
* @return the complete JavaScript string with the form lookup and the provided
* action
*/
static String getFormWrapperScript(String action) {
return """
setTimeout(() => {
var overlayFormWrapper = document.getElementById('vaadinLoginOverlayWrapper');
if (!overlayFormWrapper) {
overlayFormWrapper = document.querySelector('vaadin-login-overlay');
}
if (!overlayFormWrapper) return;
var form = overlayFormWrapper.querySelector('form');
if (form) {
%s
}
});
""".formatted(action);
}
Comment on lines +61 to +75
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Vaadin 25 vaadin-login-overlay shadow DOM structure form element location

💡 Result:

In Vaadin 25, the <vaadin-login-overlay> contains its UI inside its shadow root, and it exposes the key nodes via shadow parts, including:

  • form-wrapper (wrapper around the login form)
  • form (the login form element/area) (cdn.vaadin.com)

However, the actual <vaadin-login-form> component is designed to have no shadowRoot so the underlying native <form> + inputs stay accessible at the document layer (important for password managers). Using <vaadin-login-overlay> is specifically meant to ensure the login form is attached to document body via the overlay mechanism. (cdn.vaadin.com)

Practical DOM “where is the form?” pointers

  • Styling hook (recommended):

    • vaadin-login-overlay::part(form) targets the form area exposed by the overlay. (cdn.vaadin.com)
  • JS query (shadow DOM path to the exposed form area):

    • document.querySelector('vaadin-login-overlay')?.shadowRoot?.querySelector('[part="form"]') (cdn.vaadin.com)
  • If you specifically need the native <form> tag (e.g., for testing):

    • Look under the rendered <vaadin-login-form> instance (which itself has no shadowRoot), after the overlay is opened/attached to document.body. (cdn.vaadin.com)

Citations:


🏁 Script executed:

cat -n src/main/java/com/flowingcode/vaadin/addons/extendedlogin/LoginOverlayUtils.java

Repository: FlowingCode/ExtendedLoginAddon

Length of output: 4432


Inconsistent shadow DOM traversal in getFormWrapperScript for Vaadin 25 compatibility.

This method does not check the shadowRoot of <vaadin-login-overlay> when querying for the form element, unlike getOverlayWrapperScript (lines 41–43) and getLoginFormWrapperScript (lines 92–94). In Vaadin 25, the form resides within the overlay's shadow DOM, so calling overlayFormWrapper.querySelector('form') at line 69 will return null and the action will not execute.

To match the pattern used in the other methods, check overlayFormWrapper.shadowRoot before querying for the form element.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/flowingcode/vaadin/addons/extendedlogin/LoginOverlayUtils.java`
around lines 61 - 75, The getFormWrapperScript method fails to traverse the
overlay's shadow DOM when locating the <form>, so update the lookup for the form
on the overlayFormWrapper to check overlayFormWrapper.shadowRoot first (like
getOverlayWrapperScript/getLoginFormWrapperScript): if
overlayFormWrapper.shadowRoot exists, query the form from
overlayFormWrapper.shadowRoot.querySelector('form'), otherwise fall back to
overlayFormWrapper.querySelector('form'), then run the formatted action when
form is found (symbols: getFormWrapperScript, overlayFormWrapper, shadowRoot,
querySelector).


/**
* Generates a JavaScript snippet that finds the form wrapper containing form
* elements. Handles
* both Vaadin 24 and Vaadin 25+ compatibility.
*
* @param action the JavaScript code to execute on the form wrapper
* @return the complete JavaScript string with the form wrapper lookup and the
* provided action
*/
static String getLoginFormWrapperScript(String action) {
return """
setTimeout(() => {
var overlayWrapper = document.getElementById('vaadinLoginOverlayWrapper');
if (!overlayWrapper) {
var loginOverlay = document.querySelector('vaadin-login-overlay');
if (loginOverlay && loginOverlay.shadowRoot) {
overlayWrapper = loginOverlay.shadowRoot
.querySelector('vaadin-login-overlay-wrapper');
}
}
if (!overlayWrapper) return;
var formWrapper = overlayWrapper.querySelector('vaadin-login-form-wrapper');
if (!formWrapper) return;
%s
});
""".formatted(action);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,49 +28,104 @@
*/
public interface ReplaceableLoginOverlay extends HasElement {

/**
* Replaces the contents of the login form with the provided elements. Clears
* existing form
* contents and appends the provided elements.
*
* @param withElement the elements to add to the form
*/
default void replaceFormComponents(HasElement... withElement) {
this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').getElementsByTagName('vaadin-login-form-wrapper')[0].getElementsByTagName('form')[0].replaceChildren())");
this.getElement().executeJs(LoginOverlayUtils.getFormWrapperScript("form.replaceChildren();"));

for (HasElement we : withElement) {
getElement().appendChild(we.getElement());
this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').getElementsByTagName('vaadin-login-form-wrapper')[0].getElementsByTagName('form')[0].appendChild($0))",
we.getElement());
.executeJs(LoginOverlayUtils.getFormWrapperScript("form.appendChild($0);"), we.getElement());
}
}

/**
* Replaces the header/brand component of the login overlay. Clears the brand
* section and appends
* the provided element.
*
* @param withElement the element to set as the new brand/header
*/
default void replaceHeaderComponent(HasElement withElement) {
getElement().appendChild(withElement.getElement());

this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').shadowRoot.querySelector('[part=\"brand\"]').replaceChildren())");
LoginOverlayUtils.getOverlayWrapperScript(
"""
var brand = overlayWrapper.shadowRoot ? overlayWrapper.shadowRoot.querySelector('[part="brand"]') : overlayWrapper.querySelector('[part="brand"]');
if (brand) {
brand.replaceChildren();
}
"""));

this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').shadowRoot.querySelector('[part=\"brand\"]').appendChild($0))",
LoginOverlayUtils.getOverlayWrapperScript(
"""
var brand = overlayWrapper.shadowRoot ? overlayWrapper.shadowRoot.querySelector('[part="brand"]') : overlayWrapper.querySelector('[part="brand"]');
if (brand) {
brand.appendChild($0);
}
"""),
withElement);
}

/**
* Removes the forgot password link from the login form.
*/
default void removeForgotPassword() {
this.getElement()
.executeJs(
LoginOverlayUtils.getLoginFormWrapperScript(
"""
var forgotPassword = formWrapper.querySelector('[slot="forgot-password"]');
if (forgotPassword) {
forgotPassword.remove();
}
"""));
}

/**
* Replaces the forgot password component in the login overlay. Clears the
* forgot password section
* and appends the provided element.
*
* @param withElement the element to set as the new forgot password component
*
*/
default void replaceForgotPassword(HasElement withElement) {
withElement.getElement().setAttribute("slot", "forgot-password");
getElement().appendChild(withElement.getElement());
this.removeForgotPassword();
this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').getElementsByTagName('vaadin-login-form-wrapper')[0].querySelector('[slot=\\\"forgot-password\\\"]').remove())");
this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').getElementsByTagName('vaadin-login-form-wrapper')[0].appendChild($0))",
LoginOverlayUtils.getLoginFormWrapperScript(
"""
formWrapper.appendChild($0);
"""),
withElement);
}

/**
* Removes the default submit button.
* Removes the default submit button.
*/
default void removeSubmitButton() {
this.getElement()
.executeJs(
"setTimeout(()=>document.getElementById('vaadinLoginOverlayWrapper').getElementsByTagName('vaadin-login-form-wrapper')[0].querySelector('[slot=\"submit\"]').remove())");
this.getElement()
.executeJs(
LoginOverlayUtils.getLoginFormWrapperScript(
"""
var submitButton = formWrapper.querySelector('[slot="submit"]');
if (submitButton) {
submitButton.remove();
}
"""));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*-
* #%L
* Extended Login Add-on
* %%
* Copyright (C) 2023 - 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.
* 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.
* #L%
*/
package com.flowingcode.vaadin.addons;

import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;

@Theme
public class AppShellConfiguratorImpl implements AppShellConfigurator {

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@

@DemoSource(
"/src/test/java/com/flowingcode/vaadin/addons/extendedlogin/TestExtendedLoginOverlayView.java")
//#if vaadin eq 0
@DemoSource(value = "/src/test/resources/META-INF/frontend/styles/extended-login-styles.css",
caption = "extended-login-styles.css", condition = "vaadin eq 24")
@DemoSource(value = "/src/test/resources/META-INF/frontend/styles/extended-login-styles-v25.css",
caption = "extended-login-styles-v25.css", condition = "vaadin ge 25")
//#endif
@PageTitle("Extended Login Overlay Demo")
@SuppressWarnings("serial")
@Route(value = "extended-login/login-overlay-demo", layout = ExtendedLoginDemoView.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@

@DemoSource("/src/test/java/com/flowingcode/vaadin/addons/extendedlogin/TestLoginLayoutView.java")
@DemoSource("/src/test/java/com/flowingcode/vaadin/addons/extendedlogin/TestLoginLayout.java")
//#if vaadin eq 0
@DemoSource(value = "/src/test/resources/META-INF/frontend/styles/extended-login-styles.css",
caption = "extended-login-styles.css", condition = "vaadin eq 24")
@DemoSource(value = "/src/test/resources/META-INF/frontend/styles/extended-login-styles-v25.css",
caption = "extended-login-styles-v25.css", condition = "vaadin ge 25")
//#endif
@PageTitle("Login Layout Demo")
@SuppressWarnings("serial")
@Route(value = "extended-login/login-layout-demo", layout = ExtendedLoginDemoView.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*/
package com.flowingcode.vaadin.addons.extendedlogin;

import com.flowingcode.vaadin.addons.extendedlogin.it.ServerVersionCallables;
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.dependency.CssImport;
Expand All @@ -39,9 +41,14 @@
*/
@SuppressWarnings("serial")
@Route(value = "extended-login/login-overlay")
@CssImport("./styles/extended-login-styles.css") // hide-source
//#if vaadin eq 24
@CssImport("./styles/extended-login-styles.css")
//#else
@CssImport("./styles/extended-login-styles-v25.css")
//#endif
@Ignore
public class TestExtendedLoginOverlayView extends Div {
// show-source public class TestExtendedLoginOverlayView extends Div {

Check warning on line 50 in src/test/java/com/flowingcode/vaadin/addons/extendedlogin/TestExtendedLoginOverlayView.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=FlowingCode_ExtendedLoginAddon&issues=AZzErky_TBjk2HgQEnEF&open=AZzErky_TBjk2HgQEnEF&pullRequest=27
public class TestExtendedLoginOverlayView extends Div implements ServerVersionCallables { //hide-source

public TestExtendedLoginOverlayView() {
ExtendedLoginOverlay elo = new ExtendedLoginOverlay();
Expand All @@ -60,4 +67,14 @@
elo.setOpened(true);
add(elo);
}

//#if vaadin eq 0
@Override
@ClientCallable
public int getMajorVersion() {
// System.out.println("getMajorVersion called on server" + com.vaadin.flow.server.Version.getMajorVersion());

Check warning on line 75 in src/test/java/com/flowingcode/vaadin/addons/extendedlogin/TestExtendedLoginOverlayView.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=FlowingCode_ExtendedLoginAddon&issues=AZzErky_TBjk2HgQEnEG&open=AZzErky_TBjk2HgQEnEG&pullRequest=27
return com.vaadin.flow.server.Version.getMajorVersion();
}
//#endif

}
Loading