diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java
index 1ed99e7d1b..a476eeaf4e 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java
@@ -44,6 +44,9 @@ private FormConstants() {
/** The resource type for email input v1 */
public static final String RT_FD_FORM_EMAIL_V1 = RT_FD_FORM_PREFIX + "emailinput/v1/emailinput";
+ /** The resource type for password input v1 */
+ public static final String RT_FD_FORM_PASSWORD_V1 = RT_FD_FORM_PREFIX + "passwordinput/v1/passwordinput";
+
/** The resource type for button v1 */
public static final String RT_FD_FORM_BUTTON_V1 = RT_FD_FORM_PREFIX + "button/v1/button";
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java
index c8b8563b79..6e985cf930 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java
@@ -152,6 +152,7 @@ private ReservedProperties() {
public static final String PN_SHOW_AS_POPUP = "showAsPopup";
public static final String PN_TEXT_IS_RICH = "textIsRich";
public static final String PN_MULTILINE = "multiLine";
+ public static final String PN_SHOW_HIDE_PASSWORD = "showHidePassword";
public static final String PN_DESIGN_DEFAULT_TYPE = Title.PN_DESIGN_DEFAULT_TYPE;
public static final String PN_TITLE_LINK_DISABLED = Title.PN_TITLE_LINK_DISABLED;
public static final String PN_DRAG_DROP_TEXT = "dragDropText";
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java
index 150f6bc9fc..fca69a22c2 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java
@@ -40,7 +40,8 @@
@Model(
adaptables = { SlingHttpServletRequest.class, Resource.class },
adapters = { TextInput.class, ComponentExporter.class },
- resourceType = { FormConstants.RT_FD_FORM_TEXT_V1, FormConstants.RT_FD_FORM_EMAIL_V1, FormConstants.RT_FD_FORM_TELEPHONE_V1 })
+ resourceType = { FormConstants.RT_FD_FORM_TEXT_V1, FormConstants.RT_FD_FORM_EMAIL_V1, FormConstants.RT_FD_FORM_TELEPHONE_V1,
+ FormConstants.RT_FD_FORM_PASSWORD_V1 })
@Exporter(
name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
extensions = ExporterConstants.SLING_MODEL_EXTENSION)
@@ -65,6 +66,10 @@ public class TextInputImpl extends AbstractFieldImpl implements TextInput {
@Nullable
protected String autocomplete;
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_SHOW_HIDE_PASSWORD)
+ @Default(booleanValues = true)
+ protected boolean showHidePassword;
+
/** Type number specific constraints **/
private Object exclusiveMinimumVaue;
private Object exclusiveMaximumValue;
@@ -83,6 +88,17 @@ public String getFieldType() {
return super.getFieldType(FieldType.TEXT_INPUT);
}
+ @Override
+ @Nullable
+ public Object[] getDefault() {
+ // password values must never be exposed in rendered markup or the exported JSON model,
+ // regardless of how the underlying property was set (authoring dialog, direct content write, etc.)
+ if (FieldType.PASSWORD.getValue().equals(getFieldType())) {
+ return null;
+ }
+ return super.getDefault();
+ }
+
@Override
@Nullable
public Integer getMinLength() {
@@ -106,6 +122,11 @@ public String getAutoComplete() {
return autocomplete;
}
+ @Override
+ public boolean isShowHidePasswordEnabled() {
+ return showHidePassword;
+ }
+
@Override
@Nullable
public Long getMinimum() {
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java
index 4415ceabde..0a5c0dd316 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java
@@ -43,6 +43,18 @@ default boolean isMultiLine() {
return false;
}
+ /**
+ * Returns {@code true} if the password show/hide visibility toggle should be rendered, otherwise {@code false}.
+ * Only relevant when the field's {@code fieldType} is {@code password}.
+ *
+ * @return {@code true} if the visibility toggle should be rendered, otherwise {@code false}
+ * @since com.adobe.cq.forms.core.components.models.form 2.0.0
+ */
+ @JsonIgnore
+ default boolean isShowHidePasswordEnabled() {
+ return true;
+ }
+
/**
* Returns {@code "off"} if autocomplete if disabled, otherwise {@code "on"} or values listed @see
* here
diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java
index 1542522422..21ca762ac9 100644
--- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java
+++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java
@@ -72,6 +72,8 @@ public class TextInputImplTest {
private static final String PATH_TEXTINPUT_EMPTYVALUE_EMPTY_STRING = CONTENT_ROOT + "/textinput-emptyvalue-empty-string";
private static final String PATH_TEXTINPUT_EMPTYVALUE_INVALID = CONTENT_ROOT + "/textinput-emptyvalue-invalid";
private static final String PATH_TEXTINPUT_EMPTYVALUE_NOT_SET = CONTENT_ROOT + "/textinput-emptyvalue-not-set";
+ private static final String PATH_PASSWORD_TEXTINPUT = CONTENT_ROOT + "/password-textinput";
+ private static final String PATH_PASSWORD_TEXTINPUT_TOGGLE_DISABLED = CONTENT_ROOT + "/password-textinput-toggle-disabled";
private final AemContext context = FormsCoreComponentTestContext.newAemContext();
@@ -594,6 +596,34 @@ void testEmptyValueEnumGetValue() {
assertEquals("", AbstractFieldImpl.EmptyValue.EMPTY_STRING.getValue());
}
+ @Test
+ void testFieldTypeForPassword() {
+ TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT, TextInput.class, context);
+ assertEquals(FieldType.PASSWORD.getValue(), textInput.getFieldType());
+ assertEquals(FormConstants.RT_FD_FORM_PASSWORD_V1, textInput.getExportedType());
+ }
+
+ @Test
+ void testShowHidePasswordEnabledByDefault() {
+ TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT, TextInput.class, context);
+ assertTrue(textInput.isShowHidePasswordEnabled());
+ TextInput textInputMock = Mockito.mock(TextInput.class);
+ Mockito.when(textInputMock.isShowHidePasswordEnabled()).thenCallRealMethod();
+ assertTrue(textInputMock.isShowHidePasswordEnabled());
+ }
+
+ @Test
+ void testShowHidePasswordCanBeDisabled() {
+ TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT_TOGGLE_DISABLED, TextInput.class, context);
+ assertFalse(textInput.isShowHidePasswordEnabled());
+ }
+
+ @Test
+ void testJSONExportForPassword() throws Exception {
+ TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT, TextInput.class, context);
+ Utils.testJSONExport(textInput, Utils.getTestExporterJSONPath(BASE, PATH_PASSWORD_TEXTINPUT));
+ }
+
@AfterEach
void tearDown() {
System.clearProperty(FeatureToggleConstants.FT_SKIP_DEFAULT_SET_PROPERTY_EVENT);
diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-password-textinput.json b/bundles/af-core/src/test/resources/form/textinput/exporter-password-textinput.json
new file mode 100644
index 0000000000..6763c23627
--- /dev/null
+++ b/bundles/af-core/src/test/resources/form/textinput/exporter-password-textinput.json
@@ -0,0 +1,20 @@
+{
+ "id": "passwordinput-5c6da9d601",
+ "fieldType": "password",
+ "name": "abc",
+ "type": "string",
+ "minLength": 8,
+ "maxLength": 20,
+ "label": {
+ "value": "def"
+ },
+ "properties": {
+ "fd:path": "/content/password-textinput"
+ },
+ "events": {
+ "custom:setProperty": [
+ "$event.payload"
+ ]
+ },
+ ":type": "core/fd/components/form/passwordinput/v1/passwordinput"
+}
diff --git a/bundles/af-core/src/test/resources/form/textinput/test-content.json b/bundles/af-core/src/test/resources/form/textinput/test-content.json
index 9fad69379c..54df55b2b2 100644
--- a/bundles/af-core/src/test/resources/form/textinput/test-content.json
+++ b/bundles/af-core/src/test/resources/form/textinput/test-content.json
@@ -234,5 +234,22 @@
"sling:resourceType" : "core/fd/components/form/textinput/v1/textinput",
"name" : "abc",
"jcr:title" : "def"
+ },
+ "password-textinput" : {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType" : "core/fd/components/form/passwordinput/v1/passwordinput",
+ "name" : "abc",
+ "jcr:title" : "def",
+ "fieldType": "password",
+ "minLength": 8,
+ "maxLength": 20
+ },
+ "password-textinput-toggle-disabled" : {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType" : "core/fd/components/form/passwordinput/v1/passwordinput",
+ "name" : "abc",
+ "jcr:title" : "def",
+ "fieldType": "password",
+ "showHidePassword": false
}
}
diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/.content.xml
new file mode 100644
index 0000000000..4596ce87d2
--- /dev/null
+++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/.content.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/_cq_template.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/_cq_template.xml
new file mode 100644
index 0000000000..a07d742033
--- /dev/null
+++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/_cq_template.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml
index c381b90879..73fb2442c9 100644
--- a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml
+++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml
@@ -28,6 +28,7 @@
+
diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/passwordinput/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/passwordinput/.content.xml
new file mode 100644
index 0000000000..042b98dde5
--- /dev/null
+++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/passwordinput/.content.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/.content.xml
new file mode 100644
index 0000000000..7256712059
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/.content.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/basic/.content.xml
new file mode 100644
index 0000000000..86a116e0b4
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/basic/.content.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/.content.xml
new file mode 100644
index 0000000000..bbf62ed22c
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/.content.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/basic/.content.xml
new file mode 100644
index 0000000000..c7decdac1d
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/basic/.content.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/de.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/de.json
index 267075fd71..e8c7790a79 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/de.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/de.json
@@ -93,5 +93,7 @@
"clearSign": "Unterschrift löschen",
"brushSize": "Pinselgröße: ",
"geoLocationError": "Fehler beim Aufrufen des geografischen Standorts",
- "geoLocationFetch": "Geografischer Standort wird abgerufen …"
+ "geoLocationFetch": "Geografischer Standort wird abgerufen …",
+ "showPassword": "Passwort anzeigen",
+ "hidePassword": "Passwort ausblenden"
}
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/en.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/en.json
index 5687eb74c5..d9ebd96aaa 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/en.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/en.json
@@ -52,5 +52,7 @@
"clearSign": "Clear Signature",
"brushSize": "Brush Size: ",
"geoLocationError": "Error fetching geolocation",
- "geoLocationFetch": "Fetching Geo Location..."
+ "geoLocationFetch": "Fetching Geo Location...",
+ "showPassword": "Show password",
+ "hidePassword": "Hide password"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/es.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/es.json
index 0b9c769159..bf085cd03f 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/es.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/es.json
@@ -93,5 +93,7 @@
"clearSign": "Borrar firma",
"brushSize": "Tamaño de pincel: ",
"geoLocationError": "Error al obtener la localización geográfica",
- "geoLocationFetch": "Obteniendo localización geográfica…"
+ "geoLocationFetch": "Obteniendo localización geográfica…",
+ "showPassword": "Mostrar contraseña",
+ "hidePassword": "Ocultar contraseña"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fr.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fr.json
index 4cf5c99bba..b0ffe22361 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fr.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fr.json
@@ -93,5 +93,7 @@
"clearSign": "Effacer la signature",
"brushSize": "Épaisseur du pinceau : ",
"geoLocationError": "Erreur lors de la récupération de la géolocalisation",
- "geoLocationFetch": "Récupération de la géolocalisation..."
+ "geoLocationFetch": "Récupération de la géolocalisation...",
+ "showPassword": "Afficher le mot de passe",
+ "hidePassword": "Masquer le mot de passe"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/it.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/it.json
index 60790cea14..43393a7e83 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/it.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/it.json
@@ -93,5 +93,7 @@
"clearSign": "Cancella firma",
"brushSize": "Dimensione pennello: ",
"geoLocationError": "Errore nel recupero della geolocalizzazione",
- "geoLocationFetch": "Recupero geolocalizzazione in corso..."
+ "geoLocationFetch": "Recupero geolocalizzazione in corso...",
+ "showPassword": "Mostra password",
+ "hidePassword": "Nascondi password"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ja.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ja.json
index 42e1bf2438..5d1e6a2305 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ja.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ja.json
@@ -93,5 +93,7 @@
"clearSign": "署名をクリア",
"brushSize": "ブラシサイズ :",
"geoLocationError": "位置情報の取得中にエラーが発生しました",
- "geoLocationFetch": "位置情報を取得しています..."
+ "geoLocationFetch": "位置情報を取得しています...",
+ "showPassword": "パスワードを表示",
+ "hidePassword": "パスワードを非表示"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ko-kr.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ko-kr.json
index c592049e2b..9727fa0e43 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ko-kr.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ko-kr.json
@@ -93,5 +93,7 @@
"clearSign": "서명 지우기",
"brushSize": "브러시 크기: ",
"geoLocationError": "지리적 위치 가져오기 중 오류 발생",
- "geoLocationFetch": "지리적 위치 가져오는 중..."
+ "geoLocationFetch": "지리적 위치 가져오는 중...",
+ "showPassword": "비밀번호 표시",
+ "hidePassword": "비밀번호 숨기기"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/pt-br.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/pt-br.json
index 13ade7043f..4929502ecf 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/pt-br.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/pt-br.json
@@ -93,5 +93,7 @@
"clearSign": "Limpar assinatura",
"brushSize": "Tamanho do pincel: ",
"geoLocationError": "Erro ao obter geolocalização",
- "geoLocationFetch": "Obtendo geolocalização..."
+ "geoLocationFetch": "Obtendo geolocalização...",
+ "showPassword": "Mostrar senha",
+ "hidePassword": "Ocultar senha"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-cn.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-cn.json
index 8729e1ce3e..3245ccf56d 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-cn.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-cn.json
@@ -93,5 +93,7 @@
"clearSign": "清晰的签名",
"brushSize": "画笔大小:",
"geoLocationError": "获取地理位置时出错",
- "geoLocationFetch": "正在获取地理位置..."
+ "geoLocationFetch": "正在获取地理位置...",
+ "showPassword": "显示密码",
+ "hidePassword": "隐藏密码"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-tw.json b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-tw.json
index 3a028252ca..a6ba68ffbe 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-tw.json
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/zh-tw.json
@@ -93,5 +93,7 @@
"clearSign": "清除簽名",
"brushSize": "筆刷大小:",
"geoLocationError": "擷取地理位置時出錯",
- "geoLocationFetch": "正在擷取地理位置…"
+ "geoLocationFetch": "正在擷取地理位置…",
+ "showPassword": "顯示密碼",
+ "hidePassword": "隱藏密碼"
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/.content.xml
new file mode 100644
index 0000000000..491392d539
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/.content.xml
new file mode 100644
index 0000000000..82f6a3bf66
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/.content.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/.content.xml
new file mode 100644
index 0000000000..40ce04ff41
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/.content.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/README.md
new file mode 100644
index 0000000000..2f1667a320
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/README.md
@@ -0,0 +1,105 @@
+
+Adaptive Form Password Input (v1)
+====
+Adaptive Form Password input field component written in HTL. It is a thin variant of the
+Text Input component: it reuses the `TextInput` Sling Model (`sling:resourceSuperType`
+points at `core/fd/components/form/textinput/v1/textinput`) and inherits that component's
+dialog, design dialog, and style config, overriding only what differs for a masked field.
+
+## Features
+
+* Renders an `` with an optional show/hide visibility toggle button.
+* Custom constraint messages (minLength/maxLength/pattern), same as Text Input.
+* Autofill attribute defaults to `new-password`, selectable between `off`, `new-password`,
+ and `current-password`.
+* Allows replacing this component with other components (as mentioned below).
+
+### Use Object
+The Form Password Input component uses the `com.adobe.cq.forms.core.components.models.form.TextInput` Sling Model for its Use-object.
+
+### Edit Dialog Properties
+The following properties are written to JCR for this component and are expected to be available as `Resource` properties:
+
+1. `./jcr:title` - defines the label to use for this field
+2. `./hideTitle` - if set to `true`, the label of this field will be hidden
+3. `./name` - defines the name of the field, which will be submitted with the form data
+4. `./default` - defines the default value of the field (masked in the dialog)
+5. `./description` - defines a help message that can be rendered in the field as a hint for the user
+6. `./required` - if set to `true`, this field will be marked as required, not allowing the form to be submitted until the field has a value
+7. `./requiredMessage` - defines the message displayed as tooltip when submitting the form if the value is left empty
+8. `./readOnly` - if set to `true`, the field will be read only
+9. `./maxLength` / `./minLength` - defines the maximum/minimum length of input allowed for the field
+10. `./maxLengthMessage` / `./minLengthMessage` - defines the maximum/minimum length error messages
+11. `./pattern` - a regular expression the value must satisfy (e.g. to require a digit and a symbol)
+12. `./autocomplete` - the autofill attribute (`off`, `new-password`, `current-password`)
+13. `./showHidePassword` - if set to `false`, the show/hide visibility toggle button is not rendered
+
+## Client Libraries
+The component reuses the `core.forms.components.textinput.v1.runtime` client library category
+for its JavaScript runtime (the same category `textinput`, `emailinput`, and `telephoneinput`
+share). It should be added to a relevant site client library using the `embed` property.
+
+It has no dedicated editor client library: the "Formats" tab and the pattern-dropdown-driven
+part of the "Validation" tab are hidden/simplified in this component's own dialog, so the
+`core.forms.components.textinput.v1.editor` interactive behavior isn't needed here.
+
+### Note on styling the show/hide toggle button
+This component only renders the `.cmp-adaptiveform-passwordinput__toggle-visibility` button
+element and its accessibility attributes (`aria-pressed`, `aria-label`). It intentionally
+ships with **no visual/icon CSS** for that button — consistent with how every other core
+form component ships unstyled BEM placeholders. The eye/eye-slash icon artwork is expected
+to be supplied by the consuming project's own theme/design system, not by this component.
+
+## BEM Description
+```
+BLOCK cmp-adaptiveform-passwordinput
+ ELEMENT cmp-adaptiveform-passwordinput__label
+ ELEMENT cmp-adaptiveform-passwordinput__label-container
+ ELEMENT cmp-adaptiveform-passwordinput__widget-wrapper
+ ELEMENT cmp-adaptiveform-passwordinput__widget
+ ELEMENT cmp-adaptiveform-passwordinput__toggle-visibility
+ ELEMENT cmp-adaptiveform-passwordinput__questionmark
+ ELEMENT cmp-adaptiveform-passwordinput__shortdescription
+ ELEMENT cmp-adaptiveform-passwordinput__longdescription
+ ELEMENT cmp-adaptiveform-passwordinput__errormessage
+```
+
+## Replace feature:
+We support a replace feature that allows replacing this component with any of the below components:
+
+* Button
+* Date Picker
+* Email Input
+* Number Input
+* Reset Button
+* Submit Button
+* Telephone Input
+* Text Box
+
+## JavaScript Data Attribute Bindings
+
+The following attributes must be added for the initialization of the password-input component in the form view:
+1. `data-cmp-is="adaptiveFormPasswordInput"`
+2. `data-cmp-adaptiveformcontainer-path="${formstructparser.formContainerPath}"`
+
+The following are optional attributes that can be added to the component in the form view:
+1. `data-cmp-valid` having a boolean value to indicate whether the field is currently valid or not
+2. `data-cmp-required` having a boolean value to indicate whether the field is currently required or not
+3. `data-cmp-readonly` having a boolean value to indicate whether the field is currently readonly or not
+4. `data-cmp-active` having a boolean value to indicate whether the field is currently active or not
+5. `data-cmp-visible` having a boolean value to indicate whether the field is currently visible or not
+6. `data-cmp-enabled` having a boolean value to indicate whether the field is currently enabled or not
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_design_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_design_dialog/.content.xml
new file mode 100644
index 0000000000..417d204bac
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_design_dialog/.content.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_dialog/.content.xml
new file mode 100644
index 0000000000..5858d5e18e
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_dialog/.content.xml
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_styleConfig/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_styleConfig/.content.xml
new file mode 100644
index 0000000000..2a8db3148f
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_styleConfig/.content.xml
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_template.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_template.xml
new file mode 100644
index 0000000000..90affe4531
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/_cq_template.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/.content.xml
new file mode 100644
index 0000000000..491392d539
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/.content.xml
new file mode 100644
index 0000000000..914ab5b3f7
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/.content.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/css.txt
new file mode 100644
index 0000000000..681be025ab
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/css.txt
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright 2026 Adobe
+#
+# 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.
+###############################################################################
+
+#base=css
+passwordinputview.css
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/css/passwordinputview.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/css/passwordinputview.css
new file mode 100644
index 0000000000..ca45275dfa
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/css/passwordinputview.css
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright 2026 Adobe
+ *
+ * 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.
+ ******************************************************************************/
+
+.cmp-adaptiveform-passwordinput {
+
+}
+
+.cmp-adaptiveform-passwordinput__widget-wrapper {
+
+}
+
+.cmp-adaptiveform-passwordinput__widget {
+
+}
+
+.cmp-adaptiveform-passwordinput__toggle-visibility {
+
+}
+
+.cmp-adaptiveform-passwordinput__label {
+
+}
+
+.cmp-adaptiveform-passwordinput__label-container {
+
+}
+
+.cmp-adaptiveform-passwordinput__longdescription {
+
+}
+
+.cmp-adaptiveform-passwordinput__shortdescription {
+
+}
+
+.cmp-adaptiveform-passwordinput__questionmark {
+
+}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/js.txt
new file mode 100644
index 0000000000..d7905815c0
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/js.txt
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright 2026 Adobe
+#
+# 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.
+###############################################################################
+
+#base=js
+passwordinputview.js
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/js/passwordinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/js/passwordinputview.js
new file mode 100644
index 0000000000..6603d1cfb9
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/clientlibs/site/js/passwordinputview.js
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright 2026 Adobe
+ *
+ * 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.
+ ******************************************************************************/
+(function() {
+
+ "use strict";
+ class PasswordInput extends FormView.FormFieldBase {
+
+ static NS = FormView.Constants.NS;
+ /**
+ * Each FormField has a data attribute class that is prefixed along with the global namespace to
+ * distinguish between them. If a component wants to put a data-attribute X, the attribute in HTML would be
+ * data-{NS}-{IS}-x=""
+ * @type {string}
+ */
+ static IS = "adaptiveFormPasswordInput";
+ static bemBlock = 'cmp-adaptiveform-passwordinput'
+ static selectors = {
+ self: "[data-" + this.NS + '-is="' + this.IS + '"]',
+ widget: `.${PasswordInput.bemBlock}__widget`,
+ toggle: `[data-cmp-hook-adaptiveformpasswordinput="toggleVisibility"]`,
+ label: `.${PasswordInput.bemBlock}__label`,
+ description: `.${PasswordInput.bemBlock}__longdescription`,
+ qm: `.${PasswordInput.bemBlock}__questionmark`,
+ errorDiv: `.${PasswordInput.bemBlock}__errormessage`,
+ tooltipDiv: `.${PasswordInput.bemBlock}__shortdescription`
+ };
+
+ constructor(params) {
+ super(params);
+ this.#setupVisibilityToggle();
+ }
+
+ getWidget() {
+ return this.element.querySelector(PasswordInput.selectors.widget);
+ }
+
+ getToggleButton() {
+ return this.element.querySelector(PasswordInput.selectors.toggle);
+ }
+
+ getDescription() {
+ return this.element.querySelector(PasswordInput.selectors.description);
+ }
+
+ getLabel() {
+ return this.element.querySelector(PasswordInput.selectors.label);
+ }
+
+ getErrorDiv() {
+ return this.element.querySelector(PasswordInput.selectors.errorDiv);
+ }
+
+ getTooltipDiv() {
+ return this.element.querySelector(PasswordInput.selectors.tooltipDiv);
+ }
+
+ getQuestionMarkDiv() {
+ return this.element.querySelector(PasswordInput.selectors.qm);
+ }
+
+ /**
+ * Wires the eye button so it toggles the widget between masked (type=password) and
+ * revealed (type=text). No-op when the button is absent, i.e. the toggle was disabled
+ * by the author (showHidePassword=false), so the field stays masked with no control.
+ */
+ #setupVisibilityToggle() {
+ const toggle = this.getToggleButton();
+ const widget = this.getWidget();
+ if (!toggle || !widget) {
+ return;
+ }
+ toggle.addEventListener('click', (event) => {
+ event.preventDefault();
+ const reveal = widget.getAttribute('type') === 'password';
+ widget.setAttribute('type', reveal ? 'text' : 'password');
+ toggle.setAttribute('aria-pressed', reveal ? 'true' : 'false');
+ const key = reveal ? 'hidePassword' : 'showPassword';
+ const fallback = reveal ? 'Hide password' : 'Show password';
+ const label = FormView.LanguageUtils.getTranslatedString(this.lang, key) || fallback;
+ toggle.setAttribute('aria-label', label);
+ toggle.setAttribute('title', label);
+ });
+ }
+
+ setModel(model) {
+ super.setModel(model);
+ this.lang = model.lang;
+ if (this.widget.value !== '') {
+ this.setModelValue(this.widget.value);
+ }
+ this.widget.addEventListener('blur', (e) => {
+ this.setModelValue(e.target.value);
+ this.setWidgetValueToDisplayValue();
+ this.setInactive();
+ this.triggerExit();
+ });
+ this.widget.addEventListener('focus', (e) => {
+ this.setActive();
+ this.setWidgetValueToModelValue();
+ this.triggerEnter();
+ });
+ }
+ }
+
+ FormView.Utils.setupField(({element, formContainer}) => {
+ return new PasswordInput({element, formContainer})
+ }, PasswordInput.selectors.self);
+
+})();
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/passwordinput.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/passwordinput.html
new file mode 100644
index 0000000000..f0786b3628
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/passwordinput.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/passwordinput.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/passwordinput.js
new file mode 100644
index 0000000000..57ef2e8b42
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/passwordinput/v1/passwordinput/passwordinput.js
@@ -0,0 +1,34 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Copyright 2026 Adobe
+ ~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+use(function () {
+
+ var clientlibsArr = ['core.forms.components.base.v1.editor'];
+ var labelPath = 'core/fd/components/af-commons/v1/fieldTemplates/label.html';
+ var shortDescriptionPath = "core/fd/components/af-commons/v1/fieldTemplates/shortDescription.html";
+ var longDescriptionPath = "core/fd/components/af-commons/v1/fieldTemplates/longDescription.html";
+ var questionMarkPath = "core/fd/components/af-commons/v1/fieldTemplates/questionMark.html"
+ var errorMessagePath = "core/fd/components/af-commons/v1/fieldTemplates/errorMessage.html";
+
+ return {
+ labelPath: labelPath,
+ shortDescriptionPath: shortDescriptionPath,
+ longDescriptionPath: longDescriptionPath,
+ questionMarkPath: questionMarkPath,
+ errorMessagePath: errorMessagePath,
+ clientlibs: clientlibsArr
+ }
+});
diff --git a/ui.tests/test-module/libs/commons/formsConstants.js b/ui.tests/test-module/libs/commons/formsConstants.js
index 72abaea1aa..db172f32c3 100644
--- a/ui.tests/test-module/libs/commons/formsConstants.js
+++ b/ui.tests/test-module/libs/commons/formsConstants.js
@@ -26,6 +26,7 @@ var formsConstants = {
"formtextinput": "/apps/forms-components-examples/components/form/textinput",
"formtelephoneinput": "/apps/forms-components-examples/components/form/telephoneinput",
"formemailinput": "/apps/forms-components-examples/components/form/emailinput",
+ "formpasswordinput": "/apps/forms-components-examples/components/form/passwordinput",
"formnumberinput": "/apps/forms-components-examples/components/form/numberinput",
"panelcontainer": "/apps/forms-components-examples/components/form/panelcontainer",
"pageheader": "/apps/forms-components-examples/components/form/pageheader",
diff --git a/ui.tests/test-module/specs/passwordinput/passwordinput.authoring.cy.js b/ui.tests/test-module/specs/passwordinput/passwordinput.authoring.cy.js
new file mode 100644
index 0000000000..c7be0e8161
--- /dev/null
+++ b/ui.tests/test-module/specs/passwordinput/passwordinput.authoring.cy.js
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2026 Adobe Systems Incorporated
+ *
+ * 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.
+ */
+
+
+const sitesSelectors = require('../../libs/commons/sitesSelectors'),
+ afConstants = require('../../libs/commons/formsConstants');
+
+/**
+ * Testing PasswordInput with Sites Editor
+ */
+describe('Page - Authoring', function () {
+ // we can use these values to log in
+
+ const dropPasswordInputInContainer = function() {
+ const dataPath = "/content/forms/af/core-components-it/blank/jcr:content/guideContainer/*",
+ responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']";
+ cy.selectLayer("Edit");
+ cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Password input", afConstants.components.forms.resourceType.formpasswordinput);
+ cy.get('body').click( 0,0);
+ }
+
+ const dropPasswordInputInSites = function() {
+ const dataPath = "/content/core-components-examples/library/adaptive-form/passwordinput/jcr:content/root/responsivegrid/demo/component/guideContainer/*",
+ responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']";
+ cy.selectLayer("Edit");
+ cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Password Input", afConstants.components.forms.resourceType.formpasswordinput);
+ cy.get('body').click( 0,0);
+ };
+
+ const testPasswordInputBehaviour = function(passwordInputEditPathSelector, passwordInputDrop, isSites) {
+ const bemEditDialog = '.cmp-adaptiveform-passwordinput__editdialog'
+ if (isSites) {
+ dropPasswordInputInSites();
+ } else {
+ dropPasswordInputInContainer();
+ }
+ cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + passwordInputEditPathSelector);
+ cy.invokeEditableAction("[data-action='CONFIGURE']"); // this line is causing frame busting which is causing cypress to fail
+ cy.get("[name='./autocomplete']")
+ .should("exist");
+ cy.get("[name='./showHidePassword']")
+ .should("exist");
+ cy.get(bemEditDialog).contains('Validation').click({force:true});
+ cy.clickDialogWithRetry();
+ cy.deleteComponentByPath(passwordInputDrop);
+ };
+
+ context('Open Forms Editor', function() {
+ const pagePath = "/content/forms/af/core-components-it/blank",
+ passwordInputEditPath = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/passwordinput",
+ passwordInputEditPathSelector = "[data-path='" + passwordInputEditPath + "']",
+ passwordInputDrop = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/" + afConstants.components.forms.resourceType.formpasswordinput.split("/").pop();
+ beforeEach(function () {
+ // this is done since cypress session results in 403 sometimes
+ cy.openAuthoring(pagePath);
+ });
+
+ it('insert PasswordInput in form container', { retries: 3 }, function () {
+ cy.cleanTest(passwordInputDrop).then(function() {
+ dropPasswordInputInContainer();
+ cy.deleteComponentByPath(passwordInputDrop);
+ });
+ });
+
+ it ('open edit dialog of PasswordInput', { retries: 3 }, function(){
+ cy.cleanTest(passwordInputDrop).then(function() {
+ testPasswordInputBehaviour(passwordInputEditPathSelector, passwordInputDrop);
+ });
+ });
+ });
+
+ context('Open Sites Editor', function () {
+ const pagePath = "/content/core-components-examples/library/adaptive-form/passwordinput",
+ passwordInputEditPath = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + "/guideContainer/passwordinput",
+ passwordInputEditPathSelector = "[data-path='" + passwordInputEditPath + "']",
+ passwordInputDrop = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + '/guideContainer/' + afConstants.components.forms.resourceType.formpasswordinput.split("/").pop();
+
+ beforeEach(function () {
+ // this is done since cypress session results in 403 sometimes
+ cy.openAuthoring(pagePath);
+ });
+
+ it('insert aem forms PasswordInput', function () {
+ dropPasswordInputInSites();
+ cy.deleteComponentByPath(passwordInputDrop);
+ });
+
+ it('open edit dialog of aem forms PasswordInput', function() {
+ testPasswordInputBehaviour(passwordInputEditPathSelector, passwordInputDrop, true);
+ });
+
+ });
+});
diff --git a/ui.tests/test-module/specs/passwordinput/passwordinput.runtime.cy.js b/ui.tests/test-module/specs/passwordinput/passwordinput.runtime.cy.js
new file mode 100644
index 0000000000..2c0a51db1d
--- /dev/null
+++ b/ui.tests/test-module/specs/passwordinput/passwordinput.runtime.cy.js
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright 2026 Adobe
+ *
+ * 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.
+ ******************************************************************************/
+describe("Form Runtime with Password Input", () => {
+
+ const pagePath = "content/forms/af/core-components-it/samples/passwordinput/basic.html"
+ const bemBlock = 'cmp-adaptiveform-passwordinput'
+ const IS = "adaptiveFormPasswordInput"
+ const selectors = {
+ passwordinput : `[data-cmp-is="${IS}"]`,
+ toggle: '[data-cmp-hook-adaptiveformpasswordinput="toggleVisibility"]'
+ }
+
+ let formContainer = null
+
+ beforeEach(() => {
+ cy.previewForm(pagePath).then(p => {
+ formContainer = p;
+ })
+ });
+
+ const checkHTML = (id, state) => {
+ const visible = state.visible;
+ const passVisibleCheck = `${visible === true ? "" : "not."}be.visible`;
+ const passDisabledAttributeCheck = `${state.enabled === false ? "" : "not."}have.attr`;
+ const value = state.value == null ? '' : state.value;
+ cy.get(`#${id}`)
+ .should(passVisibleCheck)
+ .invoke('attr', 'data-cmp-visible')
+ .should('eq', visible.toString());
+ cy.get(`#${id}`)
+ .invoke('attr', 'data-cmp-enabled')
+ .should('eq', state.enabled.toString());
+ return cy.get(`#${id}`).within((root) => {
+ cy.get('*').should(passVisibleCheck)
+ cy.get('input')
+ .should(passDisabledAttributeCheck, 'disabled');
+ cy.get('input').should('have.value', value)
+ })
+ }
+
+ it(" should get model and view initialized properly ", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ expect(formContainer._model.items.length, "model and view elements match").to.equal(Object.keys(formContainer._fields).length);
+ Object.entries(formContainer._fields).forEach(([id, field]) => {
+ expect(field.getId()).to.equal(id)
+ expect(formContainer._model.getElement(id), `model and view are in sync`).to.equal(field.getModel())
+ checkHTML(id, field.getModel().getState())
+ });
+ })
+
+ it(" should render masked by default ", () => {
+ const [id] = Object.entries(formContainer._fields)[0]
+ cy.get(`#${id} > .${bemBlock}__widget-wrapper > input`).should('have.attr', 'type', 'password');
+ });
+
+ it(" clicking the eye icon reveals plaintext and toggles aria-pressed/label ", () => {
+ const [id] = Object.entries(formContainer._fields)[0]
+ const value = "S3cret!23";
+ cy.get(`#${id}`).find("input").clear().type(value);
+ cy.get(`#${id} .${bemBlock}__toggle-visibility`)
+ .should('have.attr', 'aria-pressed', 'false')
+ .should('have.attr', 'aria-label', 'Show password')
+ .click();
+ cy.get(`#${id} > .${bemBlock}__widget-wrapper > input`)
+ .should('have.attr', 'type', 'text')
+ .should('have.value', value);
+ cy.get(`#${id} .${bemBlock}__toggle-visibility`)
+ .should('have.attr', 'aria-pressed', 'true')
+ .should('have.attr', 'aria-label', 'Hide password')
+ .click();
+ cy.get(`#${id} > .${bemBlock}__widget-wrapper > input`)
+ .should('have.attr', 'type', 'password')
+ .should('have.value', value);
+ cy.get(`#${id} .${bemBlock}__toggle-visibility`)
+ .should('have.attr', 'aria-pressed', 'false')
+ .should('have.attr', 'aria-label', 'Show password');
+ });
+
+ it(" toggle button is absent when showHidePassword is disabled ", () => {
+ const [id] = Object.entries(formContainer._fields)[1]
+ cy.get(`#${id}`).find(selectors.toggle).should('not.exist');
+ });
+
+ it(" value submits correctly regardless of toggle state ", () => {
+ const [id] = Object.entries(formContainer._fields)[0]
+ const model = formContainer._model.getElement(id)
+ const value = "AnotherSecret!1"
+ cy.get(`#${id}`).find("input").clear().type(value);
+ cy.get(`#${id} .${bemBlock}__toggle-visibility`).click().then(() => {
+ expect(model.getState().value).to.equal(value)
+ })
+ });
+
+ it(" minLength validation error message is displayed ", () => {
+ const [id] = Object.entries(formContainer._fields)[2]
+ cy.get(`#${id}`).find("input").clear().type("short").blur();
+ cy.window().then($window => {
+ if ($window.guideBridge && $window.guideBridge.isConnected()) {
+ $window.guideBridge.validate();
+ }
+ })
+ cy.get(`#${id} > div.${bemBlock}__errormessage`).should('have.text', 'Password must be at least 8 characters.');
+ });
+
+ it("mandatory message set by user is displayed", () => {
+ const [id] = Object.entries(formContainer._fields)[3]
+ cy.window().then($window => {
+ if ($window.guideBridge && $window.guideBridge.isConnected()) {
+ $window.guideBridge.validate();
+ }
+ })
+ cy.get(`#${id} > div.${bemBlock}__errormessage`).should('have.text', 'custom mandatory message!');
+ });
+
+ it("should toggle description and tooltip", () => {
+ cy.toggleDescriptionTooltip(bemBlock, Object.entries(formContainer._fields)[3][0]);
+ })
+
+ it("disabled field should not have aria-disabled attribute", () => {
+ const [id] = Object.entries(formContainer._fields)[4];
+ cy.get(`#${id} > .${bemBlock}__widget-wrapper > input`).should('not.have.attr', 'aria-disabled');
+ cy.get(`#${id} > .${bemBlock}__widget-wrapper > input`).should('have.attr', 'disabled');
+ });
+})