diff --git a/.circleci/ci/accessibilityConfig.json b/.circleci/ci/accessibilityConfig.json
index f89990d697..3aa96f3c30 100644
--- a/.circleci/ci/accessibilityConfig.json
+++ b/.circleci/ci/accessibilityConfig.json
@@ -1,3 +1,3 @@
{
- "accessibilityExceptionList": ["landmark-one-main", "label-title-only", "region", "focus-order-semantics", "target-size"]
+ "accessibilityExceptionList": ["landmark-one-main", "label-title-only", "region", "focus-order-semantics", "target-size", "page-has-heading-one"]
}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractBaseImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractBaseImpl.java
index 9c7ab9f2ea..79f8f78ba2 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractBaseImpl.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractBaseImpl.java
@@ -182,12 +182,12 @@ public String getScreenReaderText() {
if (AssistPriority.LABEL.equals(assistPriority)) {
Label label = getLabel();
if (label != null) {
- screenReaderText = "$label.$value";
+ screenReaderText = label.getValue();
}
} else if (AssistPriority.NAME.equals(assistPriority)) {
- screenReaderText = "$name";
+ screenReaderText = getName();
} else if (AssistPriority.DESCRIPTION.equals(assistPriority)) {
- screenReaderText = "$description";
+ screenReaderText = getDescription();
} else if (AssistPriority.CUSTOM.equals(assistPriority)) {
screenReaderText = "'" + customAssistPriorityMsg + "'"; // json formula string literal
}
diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java
index 0e62af6be2..7506d2deea 100644
--- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java
+++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java
@@ -45,6 +45,9 @@ public class RadioButtonImplTest {
private static final String BASE = "/form/radiobutton";
private static final String CONTENT_ROOT = "/content";
private static final String PATH_RADIOBUTTON_CUSTOMIZED = CONTENT_ROOT + "/radiobutton-customized";
+ private static final String PATH_RADIOBUTTON_CUSTOMIZED_WITH_LABEL = CONTENT_ROOT + "/radiobutton-customized-withLabel";
+ private static final String PATH_RADIOBUTTON_CUSTOMIZED_WITH_NAME = CONTENT_ROOT + "/radiobutton-customized-withName";
+ private static final String PATH_RADIOBUTTON_CUSTOMIZED_WITH_DESC = CONTENT_ROOT + "/radiobutton-customized-withDescription";
private static final String PATH_RADIOBUTTON = CONTENT_ROOT + "/radiobutton";
private static final String PATH_RADIOBUTTON_DATALAYER = CONTENT_ROOT + "/radiobutton-datalayer";
@@ -390,4 +393,31 @@ void testInsertionOrderForEnumNames() {
"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen", "Twenty"));
assertArrayEquals(set.toArray(new String[0]), radioButton.getEnumNames());
}
+
+ @Test
+ void testGetScreenReaderTextWithLabel() {
+ RadioButton radioButton = getRadioButtonUnderTest(PATH_RADIOBUTTON_CUSTOMIZED_WITH_LABEL);
+ assertEquals("Radio Button", radioButton.getScreenReaderText());
+ RadioButton radioButtonMock = Mockito.mock(RadioButton.class);
+ Mockito.when(radioButtonMock.getScreenReaderText()).thenCallRealMethod();
+ assertEquals(null, radioButtonMock.getScreenReaderText());
+ }
+
+ @Test
+ void testGetScreenReaderTextWithName() {
+ RadioButton radioButton = getRadioButtonUnderTest(PATH_RADIOBUTTON_CUSTOMIZED_WITH_NAME);
+ assertEquals("radiobutton_12345", radioButton.getScreenReaderText());
+ RadioButton radioButtonMock = Mockito.mock(RadioButton.class);
+ Mockito.when(radioButtonMock.getScreenReaderText()).thenCallRealMethod();
+ assertEquals(null, radioButtonMock.getScreenReaderText());
+ }
+
+ @Test
+ void testGetScreenReaderTextWithDescription() {
+ RadioButton radioButton = getRadioButtonUnderTest(PATH_RADIOBUTTON_CUSTOMIZED_WITH_DESC);
+ assertEquals("long description", radioButton.getScreenReaderText());
+ RadioButton radioButtonMock = Mockito.mock(RadioButton.class);
+ Mockito.when(radioButtonMock.getScreenReaderText()).thenCallRealMethod();
+ assertEquals(null, radioButtonMock.getScreenReaderText());
+ }
}
diff --git a/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton-customized-withDescription.json b/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton-customized-withDescription.json
new file mode 100644
index 0000000000..f1a904eaf5
--- /dev/null
+++ b/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton-customized-withDescription.json
@@ -0,0 +1,48 @@
+{
+ "id": "radiobutton-3b6e1ca0f6",
+ "dataRef": "a.b",
+ "fieldType": "radio-group",
+ "name": "radiobutton_12345",
+ "visible": false,
+ "description": "long description",
+ "tooltip": "
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js.txt
new file mode 100644
index 0000000000..7cb945cd83
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js.txt
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright 2023 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
+tabs.js
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js/tabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js/tabs.js
new file mode 100644
index 0000000000..be37e3d278
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js/tabs.js
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright 2024 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 () {
+
+ const TabsMixin = window.Forms.CoreComponentsCommons.TabsMixin;
+
+ class Tabs extends TabsMixin(class {}) {
+ static NS = "cmp";
+ static IS = "adaptiveFormTabs";
+ static bemBlock = "cmp-tabs";
+ static selectors = {
+ self: "[data-" + this.NS + '-is="' + this.IS + '"]',
+ active: {
+ tab: "cmp-tabs__tab--active",
+ tabpanel: "cmp-tabs__tabpanel--active"
+ },
+ };
+
+ constructor(params) {
+ super(params, Tabs.NS, Tabs.IS, Tabs.selectors)
+ params.element.removeAttribute("data-" + Tabs.NS + "-is");
+
+ if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
+ /*
+ * Editor message handling:
+ * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
+ * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component
+ * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data
+ */
+ CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window);
+ var _self = this;
+ CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) {
+ if (message.data && message.data.type === "cmp-tabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) {
+ if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] !== undefined) {
+ _self.navigate(_self._elements["tab"][message.data.index].id);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary.
+ *
+ * @private
+ */
+ function onDocumentReady() {
+
+ var elements = document.querySelectorAll(Tabs.selectors.self);
+ for (var i = 0; i < elements.length; i++) {
+ new Tabs({ element: elements[i] });
+ }
+
+ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
+ var body = document.querySelector("body");
+ var observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ // needed for IE
+ var nodesArray = [].slice.call(mutation.addedNodes);
+ if (nodesArray.length > 0) {
+ nodesArray.forEach(function(addedNode) {
+ if (addedNode.querySelectorAll) {
+ var elementsArray = [].slice.call(addedNode.querySelectorAll(Tabs.selectors.self));
+ elementsArray.forEach(function(element) {
+ new Tabs({ element: element });
+ });
+ }
+ });
+ }
+ });
+ });
+
+ observer.observe(body, {
+ subtree: true,
+ childList: true,
+ characterData: true
+ });
+ }
+
+ if (document.readyState !== "loading") {
+ onDocumentReady();
+ } else {
+ document.addEventListener("DOMContentLoaded", onDocumentReady);
+ }
+}());
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js
index 13c323cb4f..4dd738e70b 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js
@@ -37,23 +37,6 @@
constructor(params) {
super(params, Tabs.NS, Tabs.IS, Tabs.selectors);
- if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
- /*
- * Editor message handling:
- * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
- * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component
- * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data
- */
- CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window);
- var _self = this;
- CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) {
- if (message.data && message.data.type === "cmp-tabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) {
- if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] != undefined) {
- _self.navigate(_self._elements["tab"][message.data.index].id);
- }
- }
- });
- }
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html
index ab946ee8f5..c86f0a19c5 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html
@@ -63,6 +63,6 @@
data-sly-test="${(wcmmode.edit || wcmmode.preview)}">
-
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/.content.xml
new file mode 100644
index 0000000000..edb9403aa3
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/.content.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js.txt
new file mode 100644
index 0000000000..2702421cd2
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js.txt
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright 2024 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
+verticaltabs.js
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js/verticaltabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js/verticaltabs.js
new file mode 100644
index 0000000000..4db1b36749
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js/verticaltabs.js
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright 2024 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 () {
+
+ const TabsMixin = window.Forms.CoreComponentsCommons.TabsMixin;
+
+ class VerticalTabs extends TabsMixin(class {}) {
+ static NS = "cmp";
+ static IS = "adaptiveFormVerticalTabs";
+ static bemBlock = "cmp-verticaltabs";
+ static selectors = {
+ self: "[data-" + this.NS + '-is="' + this.IS + '"]',
+ active: {
+ tab: "cmp-verticaltabs__tab--active",
+ tabpanel: "cmp-verticaltabs__tabpanel--active"
+ },
+ };
+
+ constructor(params) {
+ super(params, VerticalTabs.NS, VerticalTabs.IS, VerticalTabs.selectors)
+ params.element.removeAttribute("data-" + VerticalTabs.NS + "-is");
+
+ if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
+ /*
+ * Editor message handling:
+ * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
+ * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component
+ * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data
+ */
+ CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window);
+ var _self = this;
+ CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) {
+ if (message.data && message.data.type === "cmp-verticaltabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) {
+ if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] !== undefined) {
+ _self.navigate(_self._elements["tab"][message.data.index].id);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary.
+ *
+ * @private
+ */
+ function onDocumentReady() {
+
+ var elements = document.querySelectorAll(VerticalTabs.selectors.self);
+ for (var i = 0; i < elements.length; i++) {
+ new VerticalTabs({ element: elements[i] });
+ }
+
+ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
+ var body = document.querySelector("body");
+ var observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ // needed for IE
+ var nodesArray = [].slice.call(mutation.addedNodes);
+ if (nodesArray.length > 0) {
+ nodesArray.forEach(function(addedNode) {
+ if (addedNode.querySelectorAll) {
+ var elementsArray = [].slice.call(addedNode.querySelectorAll(VerticalTabs.selectors.self));
+ elementsArray.forEach(function(element) {
+ new VerticalTabs({ element: element });
+ });
+ }
+ });
+ }
+ });
+ });
+
+ observer.observe(body, {
+ subtree: true,
+ childList: true,
+ characterData: true
+ });
+ }
+
+ if (document.readyState !== "loading") {
+ onDocumentReady();
+ } else {
+ document.addEventListener("DOMContentLoaded", onDocumentReady);
+ }
+}());
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js
index 3b272b089d..69667ae682 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js
@@ -37,23 +37,6 @@
constructor(params) {
super(params, VerticalTabs.NS, VerticalTabs.IS, VerticalTabs.selectors);
- if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
- /*
- * Editor message handling:
- * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
- * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component
- * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data
- */
- CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window);
- var _self = this;
- CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) {
- if (message.data && message.data.type === "cmp-verticaltabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) {
- if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] != undefined) {
- _self.navigate(_self._elements["tab"][message.data.index].id);
- }
- }
- });
- }
}
getClass() {
@@ -63,7 +46,9 @@
setFocus(id) {
super.setFocus(id);
this.setActive();
- this.navigateAndFocusTab(id + '__tab');
+ if(id) {
+ this.navigateAndFocusTab(id + '__tab');
+ }
}
getWidget() {
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html
index e6c810e3df..ea042f2561 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html
@@ -64,6 +64,6 @@
data-sly-test="${(wcmmode.edit || wcmmode.preview)}">
-
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/.content.xml
new file mode 100644
index 0000000000..118c0b592c
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/.content.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js.txt
new file mode 100644
index 0000000000..90e023d251
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js.txt
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright 2024 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
+common.js
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js
new file mode 100644
index 0000000000..061e27e306
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright 2024 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 () {
+
+ function WizardMixin(Base) {
+ return class extends Base {
+ static NS = "cmp";
+ static IS = "adaptiveFormWizard";
+ static bemBlock = "cmp-adaptiveform-wizard";
+ static DATA_ATTRIBUTE_VISIBLE = 'data-cmp-visible';
+
+ static selectors = {
+ self: "[data-" + this.NS + '-is="' + this.IS + '"]',
+ active: {
+ tab: "cmp-adaptiveform-wizard__tab--active",
+ wizardpanel: "cmp-adaptiveform-wizard__wizardpanel--active"
+ }
+ };
+
+ _active;
+
+ constructor(params) {
+ super(params);
+ }
+
+ /**
+ * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API
+ *
+ * @private
+ * @param {HTMLElement} wrapper The Tabs wrapper element
+ */
+ cacheElements(wrapper) {
+ this._elements = {};
+ this._elements.self = wrapper;
+ const hooks = this._elements.self.querySelectorAll("[data-" + this.constructor.NS + "-hook-" + this.constructor.IS + "]");
+
+ for (let i = 0; i < hooks.length; i++) {
+ let hook = hooks[i];
+ if (hook.closest("[data-cmp-is=" + this.constructor.IS + "]") === this._elements.self) { // only process own tab elements
+ let key = hook.dataset[this.constructor.NS + "Hook" + "Adaptiveformwizard"];
+ if (this._elements[key]) {
+ if (!Array.isArray(this._elements[key])) {
+ let tmp = this._elements[key];
+ this._elements[key] = [tmp];
+ }
+ this._elements[key].push(hook);
+ } else {
+ this._elements[key] = [hook];
+ }
+ }
+ }
+ }
+
+ setActive(tabs) {
+ if (tabs) {
+ tabs[0].classList.add(this.constructor.selectors.active.tab);
+ }
+ }
+
+ /**
+ * Returns the index of the active tab, if no tab is active returns 0
+ *
+ * @param {Array} tabs Tab elements
+ * @returns {Number} Index of the active tab, 0 if none is active
+ */
+ getActiveIndex(tabs) {
+ if (tabs) {
+ for (let i = 0; i < tabs.length; i++) {
+ if (tabs[i].classList.contains(this.constructor.selectors.active.tab)) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+
+ getCachedTabs() {
+ return this._elements["tab"];
+ }
+
+ getCachedWizardPanels() {
+ return this._elements["wizardpanel"]
+ }
+
+ /**
+ * Navigates to the tab at the provided index
+ *
+ * @private
+ * @param {Number} index The index of the tab to navigate to
+ */
+ navigate(index) {
+ this._active = index;
+ this.refreshActive();
+ }
+
+ /**
+ * Refreshes the tab markup based on the current {@code Tabs_active} index
+ *
+ * @private
+ */
+ refreshActive() {
+ const wizardPanels = this.getCachedWizardPanels();
+ const tabs = this.getCachedTabs();
+ if (wizardPanels) {
+ for (let i = 0; i < wizardPanels.length; i++) {
+ if( wizardPanels[i] && tabs[i]) {
+ if (i === parseInt(this._active)) {
+ wizardPanels[i].classList.add(this.constructor.selectors.active.wizardpanel);
+ wizardPanels[i].removeAttribute("aria-hidden");
+ tabs[i].classList.add(this.constructor.selectors.active.tab);
+ tabs[i].setAttribute("aria-selected", true);
+ tabs[i].setAttribute("tabindex", "0");
+ } else {
+ wizardPanels[i].classList.remove(this.constructor.selectors.active.wizardpanel);
+ wizardPanels[i].setAttribute("aria-hidden", true);
+ tabs[i].classList.remove(this.constructor.selectors.active.tab);
+ tabs[i].setAttribute("aria-selected", false);
+ tabs[i].setAttribute("tabindex", "-1");
+ }
+ }
+ }
+ }
+ if (this.hideUnhideNavButtons) {
+ this.hideUnhideNavButtons(this._active);
+ }
+ }
+ }
+ }
+
+ window.Forms = window.Forms || {};
+ window.Forms.CoreComponentsCommons = window.Forms.CoreComponentsCommons || {};
+ window.Forms.CoreComponentsCommons.WizardMixin = WizardMixin;
+
+}());
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/.content.xml
new file mode 100644
index 0000000000..abbbd1c97a
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/.content.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js.txt
new file mode 100644
index 0000000000..fd5429b6f7
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js.txt
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright 2024 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
+wizard.js
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js/wizard.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js/wizard.js
new file mode 100644
index 0000000000..bb8a2932ef
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js/wizard.js
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright 2024 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 () {
+
+ const WizardMixin = window.Forms.CoreComponentsCommons.WizardMixin;
+
+ class Wizard extends WizardMixin(class {}) {
+
+ constructor(params) {
+ super(params);
+ const {element} = params;
+ this.cacheElements(element);
+ this.setActive(this.getCachedTabs())
+ this._active = this.getActiveIndex(this.getCachedTabs());
+ this.refreshActive();
+
+ element.removeAttribute("data-" + Wizard.NS + "-is");
+
+ if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
+ /*
+ * Editor message handling:
+ * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
+ * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component
+ * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data
+ */
+ CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window);
+ const _self = this;
+ CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) {
+ if (message.data && message.data.type === "cmp-adaptiveform-wizard" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) {
+ if (message.data.operation === "navigate") {
+ _self.navigate(message.data.index);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary.
+ *
+ * @private
+ */
+ function onDocumentReady() {
+
+ var elements = document.querySelectorAll(Wizard.selectors.self);
+ for (var i = 0; i < elements.length; i++) {
+ new Wizard({ element: elements[i] });
+ }
+
+ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
+ var body = document.querySelector("body");
+ var observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ // needed for IE
+ var nodesArray = [].slice.call(mutation.addedNodes);
+ if (nodesArray.length > 0) {
+ nodesArray.forEach(function(addedNode) {
+ if (addedNode.querySelectorAll) {
+ var elementsArray = [].slice.call(addedNode.querySelectorAll(Wizard.selectors.self));
+ elementsArray.forEach(function(element) {
+ new Wizard({ element: element });
+ });
+ }
+ });
+ }
+ });
+ });
+
+ observer.observe(body, {
+ subtree: true,
+ childList: true,
+ characterData: true
+ });
+ }
+
+ if (document.readyState !== "loading") {
+ onDocumentReady();
+ } else {
+ document.addEventListener("DOMContentLoaded", onDocumentReady);
+ }
+}());
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml
index ca7663dbfc..f783007439 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml
@@ -19,5 +19,5 @@
allowProxy="{Boolean}true"
categories="[core.forms.components.wizard.v1.runtime]"
jsProcessor="[default:none,min:none]"
- dependencies="[core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.wcm.components.commons.site.container]"
+ dependencies="[core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.wcm.components.commons.site.container,core.forms.components.wizard.v1.commons]"
/>
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
index 815bd476fa..903a16b793 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
@@ -25,20 +25,15 @@
ARROW_DOWN: 40
};
+ const WizardMixin = window.Forms.CoreComponentsCommons.WizardMixin;
- class Wizard extends FormView.FormPanel {
+ class Wizard extends WizardMixin(FormView.FormPanel) {
_templateHTML = {};
- #_active;
static NS = FormView.Constants.NS;
- static IS = "adaptiveFormWizard";
- static bemBlock = "cmp-adaptiveform-wizard";
static #tabIdSuffix = "_wizard-item-nav";
static #wizardPanelIdSuffix = "__wizardpanel";
- maxEnabledTab = 0;
- minEnabledTab = 0;
- static DATA_ATTRIBUTE_VISIBLE = 'data-cmp-visible';
static selectors = {
self: "[data-" + Wizard.NS + '-is="' + Wizard.IS + '"]',
@@ -63,59 +58,14 @@
constructor(params) {
super(params);
const {element} = params;
- this.#cacheElements(element);
- this.#setActive(this.#getCachedTabs())
- this.#_active = this.#getActiveIndex(this.#getCachedTabs());
+ this.cacheElements(element);
+ this.setActive(this.getCachedTabs())
+ this._active = this.getActiveIndex(this.getCachedTabs());
this.#setNavigationRange();
- this.#hideUnhideNavButtons(this.#_active);
- this.#refreshActive();
+ this.#hideUnhideNavButtons(this._active);
+ this.refreshActive();
this.#bindEvents();
- if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
- /*
- * Editor message handling:
- * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
- * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component
- * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data
- */
- CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window);
- const _self = this;
- CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) {
- if (message.data && message.data.type === "cmp-adaptiveform-wizard" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) {
- if (message.data.operation === "navigate") {
- _self.#navigate(message.data.index);
- }
- }
- });
- }
- }
-
- /**
- * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API
- *
- * @private
- * @param {HTMLElement} wrapper The Tabs wrapper element
- */
- #cacheElements(wrapper) {
- this._elements = {};
- this._elements.self = wrapper;
- const hooks = this._elements.self.querySelectorAll("[data-" + Wizard.NS + "-hook-" + Wizard.IS + "]");
-
- for (let i = 0; i < hooks.length; i++) {
- let hook = hooks[i];
- if (hook.closest("[data-cmp-is=" + Wizard.IS + "]") === this._elements.self) { // only process own tab elements
- let key = hook.dataset[Wizard.NS + "Hook" + "Adaptiveformwizard"];
- if (this._elements[key]) {
- if (!Array.isArray(this._elements[key])) {
- let tmp = this._elements[key];
- this._elements[key] = [tmp];
- }
- this._elements[key].push(hook);
- } else {
- this._elements[key] = [hook];
- }
- }
- }
}
getClass() {
@@ -126,7 +76,7 @@
super.setFocus(id);
this.setActive();
const index = this.#getTabIndexById(id + '_wizard-item-nav');
- this.#navigate(index);
+ this.navigate(index);
}
getWidget() {
@@ -183,30 +133,6 @@
}
- /**
- * Returns the index of the active tab, if no tab is active returns 0
- *
- * @param {Array} tabs Tab elements
- * @returns {Number} Index of the active tab, 0 if none is active
- */
- #getActiveIndex(tabs) {
- if (tabs) {
- for (let i = 0; i < tabs.length; i++) {
- if (tabs[i].classList.contains(Wizard.selectors.active.tab)) {
- return i;
- }
- }
- }
- return 0;
- }
-
-
- #setActive(tabs) {
- if (tabs) {
- tabs[0].classList.add(Wizard.selectors.active.tab);
- }
- }
-
/**
* Handles tab keydown events
*
@@ -214,9 +140,9 @@
* @param {Object} event The keydown event
*/
#onKeyDown(event) {
- const index = this.#_active;
+ const index = this._active;
- const lastIndex = this.#getCachedTabs().length - 1;
+ const lastIndex = this.getCachedTabs().length - 1;
switch (event.keyCode) {
case keyCodes.ARROW_LEFT:
@@ -247,33 +173,13 @@
}
/**
- * Refreshes the tab markup based on the current {@code Tabs#_active} index
+ * Refreshes the tab markup based on the current {@code Tabs_active} index
*
* @private
*/
- #refreshActive() {
- const wizardPanels = this.#getCachedWizardPanels();
- const tabs = this.#getCachedTabs();
- if (wizardPanels) {
- for (let i = 0; i < wizardPanels.length; i++) {
- if(tabs[i]) {
- if (i === parseInt(this.#_active)) {
- wizardPanels[i].classList.add(Wizard.selectors.active.wizardpanel);
- wizardPanels[i].removeAttribute(FormView.Constants.ARIA_HIDDEN);
- tabs[i].classList.add(Wizard.selectors.active.tab);
- tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, true);
- tabs[i].setAttribute(FormView.Constants.TABINDEX, "0");
- } else {
- wizardPanels[i].classList.remove(Wizard.selectors.active.wizardpanel);
- wizardPanels[i].setAttribute(FormView.Constants.ARIA_HIDDEN, true);
- tabs[i].classList.remove(Wizard.selectors.active.tab);
- tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, false);
- tabs[i].setAttribute(FormView.Constants.TABINDEX, "-1");
- }
- }
- }
- }
- this.#hideUnhideNavButtons(this.#_active);
+ refreshActive() {
+ super.refreshActive();
+ this.#hideUnhideNavButtons(this._active);
}
/**
@@ -290,8 +196,8 @@
#navigateToNextTab() {
- const activeIndex = this.#_active;
- const activeTabElement = this.#getCachedTabs()[activeIndex];
+ const activeIndex = this._active;
+ const activeTabElement = this.getCachedTabs()[activeIndex];
const activeChildId = activeTabElement.id.substring(0, activeTabElement.id.lastIndexOf(Wizard.#tabIdSuffix));
const activeChildView = this.getChild(activeChildId);
let activeChildModel;
@@ -306,13 +212,13 @@
validationErrorList = activeChildModel.validate();
}
if (validationErrorList === undefined || validationErrorList.length == 0) {
- let tabs = this.#getCachedTabs();
+ let tabs = this.getCachedTabs();
let nextVisibleIndex = this.#findNextVisibleChildIndex(activeIndex);
if (tabs && nextVisibleIndex >= 0) {
this.#navigateAndFocusTab(nextVisibleIndex);
}
}
- this.#hideUnhideNavButtons(this.#_active);
+ this.#hideUnhideNavButtons(this._active);
}
#isAuthoring() {
@@ -320,13 +226,13 @@
}
#navigateToPreviousTab() {
- const activeIndex = this.#_active;
- const tabs = this.#getCachedTabs();
+ const activeIndex = this._active;
+ const tabs = this.getCachedTabs();
const lastVisibleIndex = this.#findLastVisibleChildIndex(activeIndex);
if (tabs && lastVisibleIndex >= 0) {
this.#navigateAndFocusTab(lastVisibleIndex);
}
- this.#hideUnhideNavButtons(this.#_active);
+ this.#hideUnhideNavButtons(this._active);
}
/**
@@ -337,7 +243,7 @@
* @param {Number} total number of tabs
*/
#hideUnhideNavButtons(activeTabIndex) {
- const tabsLength = this.#getCachedTabs() ? this.#getCachedTabs().length : 0;
+ const tabsLength = this.getCachedTabs() ? this.getCachedTabs().length : 0;
const nextVisible = this.#findNextVisibleChildIndex(activeTabIndex);
const previousVisible = this.#findLastVisibleChildIndex(activeTabIndex);
@@ -364,26 +270,26 @@
}
#setNavigationRange() {
- const wizardPanels = this.#getCachedWizardPanels();
+ const wizardPanels = this.getCachedWizardPanels();
if(wizardPanels) {
this.maxEnabledTab = wizardPanels.length-1;
this.minEnabledTab = 0;
for (let i = 0; i < wizardPanels.length; i++) {
- if(!this.#childComponentVisible(this.#getCachedWizardPanels()[i])) {
+ if(!this.#childComponentVisible(this.getCachedWizardPanels()[i])) {
this.minEnabledTab = i+1;
} else {
break;
}
}
for (let i = wizardPanels.length - 1; i >= 0; i--) {
- if(!this.#childComponentVisible(this.#getCachedWizardPanels()[i])) {
+ if(!this.#childComponentVisible(this.getCachedWizardPanels()[i])) {
this.maxEnabledTab = i;
} else {
break;
}
}
this.minEnabledTab = Math.max(0, this.minEnabledTab);
- this.maxEnabledTab = Math.min(this.#getCachedTabs().length-1, this.maxEnabledTab);
+ this.maxEnabledTab = Math.min(this.getCachedTabs().length-1, this.maxEnabledTab);
}
}
@@ -392,7 +298,7 @@
}
#findNextVisibleChildIndex(currentIndex) {
- const tabs = this.#getCachedTabs();
+ const tabs = this.getCachedTabs();
const tabsLength = tabs? tabs.length : 0;
for (let i = currentIndex + 1; i < tabsLength; i++) {
let isVisible = tabs[i].getAttribute(Wizard.DATA_ATTRIBUTE_VISIBLE);
@@ -404,7 +310,7 @@
}
#findLastVisibleChildIndex(currentIndex) {
- const tabs = this.#getCachedTabs();
+ const tabs = this.getCachedTabs();
if(tabs) {
for (let i = currentIndex - 1; i >= 0; i--) {
let isVisible = tabs[i].getAttribute(Wizard.DATA_ATTRIBUTE_VISIBLE);
@@ -416,18 +322,6 @@
return -1;
}
-
- /**
- * Navigates to the tab at the provided index
- *
- * @private
- * @param {Number} index The index of the tab to navigate to
- */
- #navigate(index) {
- this.#_active = index;
- this.#refreshActive();
- }
-
/**
* Navigates to the item at the provided index and ensures the active tab gains focus
*
@@ -435,13 +329,13 @@
* @param {Number} index The index of the item to navigate to
*/
#navigateAndFocusTab(index) {
- this.#navigate(index);
- this.focusWithoutScroll(this.#getCachedTabs()[index]);
+ this.navigate(index);
+ this.focusWithoutScroll(this.getCachedTabs()[index]);
}
#syncWizardNavLabels() {
- const tabs = this.#getCachedTabs();
- const wizardPanels = this.#getCachedWizardPanels();
+ const tabs = this.getCachedTabs();
+ const wizardPanels = this.getCachedWizardPanels();
if (tabs) {
for (let i = 0; i < tabs.length; i++) {
let id = wizardPanels[i].querySelectorAll("[data-cmp-is]")[0].id;
@@ -452,7 +346,7 @@
}
#syncWizardPanels() {
- const wizardPanels = this.#getCachedWizardPanels();
+ const wizardPanels = this.getCachedWizardPanels();
if (wizardPanels) {
for (let i = 0; i < wizardPanels.length; i++) {
let id = wizardPanels[i].querySelectorAll("[data-cmp-is]")[0].id;
@@ -484,7 +378,7 @@
let tabListParentElement = this.#getTabListElement();
tabListParentElement.insertBefore(navigationTabToBeRepeated, tabListParentElement.firstChild);
} else {
- let beforeElement = this.#getCachedTabs()[indexToInsert - 1];
+ let beforeElement = this.getCachedTabs()[indexToInsert - 1];
beforeElement.after(navigationTabToBeRepeated);
}
} else {
@@ -492,10 +386,10 @@
let beforeElement = this.#getTabNavElementById(beforeTabNavElementId);
beforeElement.after(navigationTabToBeRepeated);
}
- this.#cacheElements(this._elements.self);
+ this.cacheElements(this._elements.self);
let repeatedWizardPanel = this.#getWizardPanelElementById(childView.id + Wizard.#wizardPanelIdSuffix);
repeatedWizardPanel.setAttribute("aria-labelledby", childView.id + Wizard.#tabIdSuffix);
- this.#refreshActive();
+ this.refreshActive();
this.#getTabIndexById();
if (childView.getInstanceManager().getModel().minOccur != undefined && childView.getInstanceManager().children.length > childView.getInstanceManager().getModel().minOccur) {
this.#navigateAndFocusTab(this.#getTabIndexById(navigationTabToBeRepeated.id));
@@ -511,9 +405,9 @@
tabNavElement.remove();
wizardPanelElement.remove();
this.children.splice(this.children.indexOf(removedInstanceView), 1);
- this.#cacheElements(this._elements.self);
- this.#_active = this.#getActiveIndex(this.#getCachedTabs());
- this.#refreshActive();
+ this.cacheElements(this._elements.self);
+ this._active = this.getActiveIndex(this.getCachedTabs());
+ this.refreshActive();
}
addChild(childView) {
@@ -525,11 +419,11 @@
this.handleHiddenChildrenVisibility();
}
this.#setNavigationRange();
- this.#hideUnhideNavButtons(this.#_active);
+ this.#hideUnhideNavButtons(this._active);
}
getChildViewByIndex(index) {
- let wizardPanels = this.#getCachedWizardPanels();
+ let wizardPanels = this.getCachedWizardPanels();
let fieldId = wizardPanels[index].id.substring(0, wizardPanels[index].id.lastIndexOf("__"));
return this.getChild(fieldId);
}
@@ -563,16 +457,8 @@
}
}
- #getCachedTabs() {
- return this._elements["tab"];
- }
-
- #getCachedWizardPanels() {
- return this._elements["wizardpanel"]
- }
-
#getTabNavElementById(tabId) {
- let tabs = this.#getCachedTabs();
+ let tabs = this.getCachedTabs();
if (tabs) {
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].id === tabId) {
@@ -583,7 +469,7 @@
}
#getWizardPanelElementById(wizardPanelId) {
- let wizardPanels = this.#getCachedWizardPanels();
+ let wizardPanels = this.getCachedWizardPanels();
if (wizardPanels) {
for (let i = 0; i < wizardPanels.length; i++) {
if (wizardPanels[i].id === wizardPanelId) {
@@ -594,7 +480,7 @@
}
#getTabIndexById(tabId) {
- let tabs = this.#getCachedTabs();
+ let tabs = this.getCachedTabs();
if (tabs) {
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].id === tabId) {
@@ -612,7 +498,7 @@
let closestNonRepeatableFieldId = this._templateHTML[instanceManagerId]['closestNonRepeatableFieldId'];
let closestRepeatableFieldInstanceManagerIds = this._templateHTML[instanceManagerId]['closestRepeatableFieldInstanceManagerIds'];
let indexToInsert = this.getIndexToInsert(closestNonRepeatableFieldId, closestRepeatableFieldInstanceManagerIds);
- let wizardPanels = this.#getCachedWizardPanels();
+ let wizardPanels = this.getCachedWizardPanels();
if (indexToInsert > 0) {
result.beforeViewElement = this.#getWizardPanelElementById(wizardPanels[indexToInsert - 1].id);
} else {
@@ -632,11 +518,11 @@
updateChildVisibility(visible, state) {
this.updateVisibilityOfNavigationElement(this.#getTabNavElementById(state.id + Wizard.#tabIdSuffix), visible);
- let activeTabNavElement = this.#getCachedTabs()[this.#_active];
+ let activeTabNavElement = this.getCachedTabs()[this._active];
this.#setNavigationRange();
- this.#hideUnhideNavButtons(this.#_active);
+ this.#hideUnhideNavButtons(this._active);
if (!visible && activeTabNavElement.id === state.id + Wizard.#tabIdSuffix) {
- let child = this.findFirstVisibleChild(this.#getCachedTabs());
+ let child = this.findFirstVisibleChild(this.getCachedTabs());
if (child) {
this.#navigateAndFocusTab(this.#getTabIndexById(child.id));
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html
index 2e0967c8ec..565ee7130d 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html
@@ -77,6 +77,6 @@
-
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html
index a052d40b54..b377027624 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html
@@ -78,6 +78,6 @@
-
+
diff --git a/ui.frontend/package-lock.json b/ui.frontend/package-lock.json
index 05996ad7fe..d8e70d1762 100644
--- a/ui.frontend/package-lock.json
+++ b/ui.frontend/package-lock.json
@@ -9,9 +9,9 @@
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
- "@aemforms/af-core": "^0.22.98",
- "@aemforms/af-custom-functions": "1.0.9",
- "@aemforms/af-formatters": "^0.22.98"
+ "@aemforms/af-core": "^0.22.100",
+ "@aemforms/af-custom-functions": "1.0.10",
+ "@aemforms/af-formatters": "^0.22.100"
},
"devDependencies": {
"@babel/preset-env": "^7.18.2",
@@ -61,23 +61,23 @@
}
},
"node_modules/@aemforms/af-core": {
- "version": "0.22.98",
- "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.98.tgz",
- "integrity": "sha512-Ij4ENmVCuuqa881IEewPo9fMOo9/0uhc8V2C41yVECDRyfLqFonE9ni5ibq677z79qlysr3l40qKM/6duq/JdQ==",
+ "version": "0.22.100",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.100.tgz",
+ "integrity": "sha512-k/w5NeupZ5sl8hCWwB3e1RJ3CxLGf301ABqmxlNURh6+0HzHACTByje+NjpYrgWBYS5WgYAZPNylWFekZuPDEA==",
"dependencies": {
"@adobe/json-formula": "0.1.50",
- "@aemforms/af-formatters": "^0.22.98"
+ "@aemforms/af-formatters": "^0.22.100"
}
},
"node_modules/@aemforms/af-custom-functions": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.9.tgz",
- "integrity": "sha512-ZDWTUNAbzNsfK7kTVSRyMQiFh0ypz0cBY10cr6N1py6CJFa8VFIpxznPRL0FzdOp6wtIZtMusw0Uloh98G2+RA=="
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.10.tgz",
+ "integrity": "sha512-n3w9tHkJOI5ISVYAK2cCi5k/oTu3rGgByDmMIgOH1+Ry4mL9nM3cxBTKEkPF8Y8JiKF1aUHIKM+MeP6u5PiiUA=="
},
"node_modules/@aemforms/af-formatters": {
- "version": "0.22.98",
- "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.98.tgz",
- "integrity": "sha512-ZDgaK0mYlndf3Q7CxmF2euEwkfW7TqTNS5QZ4z/DOWl/eqEzM/v4KIzK+9J7obVC9qWYxjc0lRZAC6YC5ynwqg=="
+ "version": "0.22.100",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.100.tgz",
+ "integrity": "sha512-Rp6WrqUuRyzlaljID60OW7onxcxsUou7LlCGoneZDgR/bQw8DTiI1r1ppnKJOwK3pvy6f1ZEqbCw66kUWqt4hg=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
@@ -11076,23 +11076,23 @@
"integrity": "sha512-dmlLYfbty8NPVIdxvI9cJ+ZdXsrRCFrCdmL1+aR2auEzXJ86rD0bm1qu+S4NOpFiZLKIyx0zvUTykms40vNjsA=="
},
"@aemforms/af-core": {
- "version": "0.22.98",
- "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.98.tgz",
- "integrity": "sha512-Ij4ENmVCuuqa881IEewPo9fMOo9/0uhc8V2C41yVECDRyfLqFonE9ni5ibq677z79qlysr3l40qKM/6duq/JdQ==",
+ "version": "0.22.100",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.100.tgz",
+ "integrity": "sha512-k/w5NeupZ5sl8hCWwB3e1RJ3CxLGf301ABqmxlNURh6+0HzHACTByje+NjpYrgWBYS5WgYAZPNylWFekZuPDEA==",
"requires": {
"@adobe/json-formula": "0.1.50",
- "@aemforms/af-formatters": "^0.22.98"
+ "@aemforms/af-formatters": "^0.22.100"
}
},
"@aemforms/af-custom-functions": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.9.tgz",
- "integrity": "sha512-ZDWTUNAbzNsfK7kTVSRyMQiFh0ypz0cBY10cr6N1py6CJFa8VFIpxznPRL0FzdOp6wtIZtMusw0Uloh98G2+RA=="
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.10.tgz",
+ "integrity": "sha512-n3w9tHkJOI5ISVYAK2cCi5k/oTu3rGgByDmMIgOH1+Ry4mL9nM3cxBTKEkPF8Y8JiKF1aUHIKM+MeP6u5PiiUA=="
},
"@aemforms/af-formatters": {
- "version": "0.22.98",
- "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.98.tgz",
- "integrity": "sha512-ZDgaK0mYlndf3Q7CxmF2euEwkfW7TqTNS5QZ4z/DOWl/eqEzM/v4KIzK+9J7obVC9qWYxjc0lRZAC6YC5ynwqg=="
+ "version": "0.22.100",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.100.tgz",
+ "integrity": "sha512-Rp6WrqUuRyzlaljID60OW7onxcxsUou7LlCGoneZDgR/bQw8DTiI1r1ppnKJOwK3pvy6f1ZEqbCw66kUWqt4hg=="
},
"@ampproject/remapping": {
"version": "2.2.1",
@@ -11368,8 +11368,7 @@
},
"@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
@@ -13307,8 +13306,7 @@
},
"@webpack-cli/configtest": {
"version": "1.2.0",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@webpack-cli/info": {
"version": "1.5.0",
@@ -13319,8 +13317,7 @@
},
"@webpack-cli/serve": {
"version": "1.7.0",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@xtuc/ieee754": {
"version": "1.2.0",
@@ -13354,8 +13351,7 @@
},
"acorn-import-assertions": {
"version": "1.9.0",
- "dev": true,
- "requires": {}
+ "dev": true
},
"acorn-walk": {
"version": "7.2.0",
@@ -13391,8 +13387,7 @@
},
"ajv-keywords": {
"version": "3.5.2",
- "dev": true,
- "requires": {}
+ "dev": true
},
"ansi-escapes": {
"version": "4.3.2",
@@ -15758,8 +15753,7 @@
},
"jest-pnp-resolver": {
"version": "1.2.3",
- "dev": true,
- "requires": {}
+ "dev": true
},
"jest-regex-util": {
"version": "26.0.0",
@@ -18241,8 +18235,7 @@
},
"ws": {
"version": "8.16.0",
- "dev": true,
- "requires": {}
+ "dev": true
},
"xml-name-validator": {
"version": "4.0.0",
diff --git a/ui.frontend/package.json b/ui.frontend/package.json
index 0f7ab36b8c..8242b416c5 100644
--- a/ui.frontend/package.json
+++ b/ui.frontend/package.json
@@ -23,8 +23,8 @@
"webpack-merge": "^5.8.0"
},
"dependencies": {
- "@aemforms/af-core": "^0.22.98",
- "@aemforms/af-formatters": "^0.22.98",
- "@aemforms/af-custom-functions": "1.0.9"
+ "@aemforms/af-core": "^0.22.100",
+ "@aemforms/af-formatters": "^0.22.100",
+ "@aemforms/af-custom-functions": "1.0.10"
}
}
diff --git a/ui.frontend/src/view/FormContainer.js b/ui.frontend/src/view/FormContainer.js
index 540f7901dc..bfa2a5e0ee 100644
--- a/ui.frontend/src/view/FormContainer.js
+++ b/ui.frontend/src/view/FormContainer.js
@@ -35,6 +35,23 @@ class FormContainer {
this._fields = {};
this._deferredParents = {};
this._element = params._element;
+
+ // Prevent default behaviour on form container.
+ this.#preventDefaultSubmit();
+ }
+
+ /**
+ * Prevents the default behavior of the Enter key on components within the formContainer
+ * from triggering a form submission and redirecting to the Thank-You Page.
+ */
+ #preventDefaultSubmit(){
+ if(this._element) {
+ this._element.addEventListener('keydown', (event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ }
+ });
+ }
}
/**
diff --git a/ui.frontend/src/view/FormFieldBase.js b/ui.frontend/src/view/FormFieldBase.js
index 9a2e33b8f6..72da2c75d6 100644
--- a/ui.frontend/src/view/FormFieldBase.js
+++ b/ui.frontend/src/view/FormFieldBase.js
@@ -208,6 +208,7 @@ class FormFieldBase extends FormField {
}
if (widgetElement) {
+
if (this.getDescription()) {
const descriptionDiv = this.getDescription();
if (!(descriptionDiv.innerHTML.trim() === '' || descriptionDiv.children.length === 0)) {
@@ -222,10 +223,27 @@ class FormFieldBase extends FormField {
if (this.getErrorDiv() && this.getErrorDiv().innerHTML) {
appendDescription('errormessage', this.getId());
}
+
widgetElement.setAttribute('aria-describedby', ariaDescribedby);
}
}
+ #syncAriaLabel() {
+ let widgetElement = typeof this.getWidget === 'function' ? this.getWidget() : null;
+ let widgetElements = typeof this.getWidgets === 'function' ? this.getWidgets() : null;
+ widgetElement = widgetElements || widgetElement;
+ const model = this.getModel?.();
+
+ if (widgetElement && model?.screenReaderText) {
+ // Use DOMPurify to sanitize and strip HTML tags
+ const screenReaderText = window.DOMPurify ? window.DOMPurify.sanitize(model.screenReaderText, { ALLOWED_TAGS: [] }) : model.screenReaderText;
+ widgetElement.setAttribute('aria-label', screenReaderText);
+ }
+ }
+
+
+
+
/**
* Synchronizes the markup with the model.
* @method
@@ -237,6 +255,7 @@ class FormFieldBase extends FormField {
this. #syncLongDesc()
this.#syncAriaDescribedBy()
this.#syncError()
+ this.#syncAriaLabel()
}
/**
@@ -606,19 +625,34 @@ class FormFieldBase extends FormField {
* Updates the HTML state based on the description state of the field.
* @param {string} descriptionText - The description.
*/
+
updateDescription(descriptionText) {
if (typeof descriptionText !== 'undefined') {
- const sanitizedDescriptionText = window.DOMPurify ? window.DOMPurify.sanitize(descriptionText) : descriptionText;
+ const sanitizedDescriptionText = window.DOMPurify ? window.DOMPurify.sanitize(descriptionText, { ALLOWED_TAGS: [] }).trim() : descriptionText;
let descriptionElement = this.getDescription();
+
if (descriptionElement) {
- let pElement = descriptionElement.querySelector("p");
- if (!pElement) {
- // If the description is updated via rule then it might not have tags
+ // Check if the content inside the descriptionElement needs updating
+ let currentTextContent = descriptionElement.innerText.trim();
+
+ if (currentTextContent === sanitizedDescriptionText) {
+ // No update needed if the text content already matches
+ return;
+ }
+
+ // Find the existing
element
+ let pElement = descriptionElement.querySelector('p');
+
+ if (!pElement) {
+ // If no
tag exists, create one and set it as the content
pElement = document.createElement('p');
+ descriptionElement.innerHTML = ''; // Clear existing content
descriptionElement.appendChild(pElement);
}
- pElement.textContent = sanitizedDescriptionText;
- } else {
+
+ // Update the
element's content with sanitized content
+ pElement.innerHTML = sanitizedDescriptionText;
+ } else {
// If no description was set during authoring
this.#addDescriptionInRuntime(sanitizedDescriptionText);
}
diff --git a/ui.tests/test-module/libs/commons/localeDataSets.js b/ui.tests/test-module/libs/commons/localeDataSets.js
index 317249caf7..076e36233e 100644
--- a/ui.tests/test-module/libs/commons/localeDataSets.js
+++ b/ui.tests/test-module/libs/commons/localeDataSets.js
@@ -80,7 +80,7 @@ const languages = [
I18N_STRINGS: {
"FileCloseAccessText" : "Drücken Sie die Eingabetaste, um die Datei zu löschen ",
"FileSizeGreater" : "Dateien ${0} übersteigen die erwartete Größe: ${1}MB.",
- "FileNameInvalid" : "Datei(en) ${0} hat/haben ungültige Zeichen in ihrem Namen. Es werden nur alphanumerische Zeichen unterstützt",
+ "FileNameInvalid" : 'Hängen Sie keine Dateien an, deren Dateiname mit (.) beginnt, \\ / : * ? " < > | ; % oder $ enthält oder ein reserviertes Schlüsselwort wie nul, prn, con, lpt oder com ist.',
"FileMimeTypeInvalid" : "Datei(en) ${0} ist/sind nicht unterstützte(r) Dateityp(en)",
"InternalFormSubmissionError" : "Beim Übermitteln des Formulars ist ein interner Fehler aufgetreten."
}
@@ -110,7 +110,7 @@ const languages = [
I18N_STRINGS: {
"FileCloseAccessText" : "ファイルを削除するには Enter を押します ",
"FileSizeGreater" : "ファイル「${0}」は予期されたサイズを超えています :${1} MB。",
- "FileNameInvalid" : "${0} ファイルの名前に無効な文字が含まれています。サポートされるのは英数字のみになります",
+ "FileNameInvalid" : 'ファイル名が (.) で始まる、\\ / : * ? " < > | ; % $ を含む、または nul、prn、con、lpt、com などの予約キーワードを含むファイルは添付しないでください。',
"FileMimeTypeInvalid" : "${0} ファイルの形式はサポートされていません",
"InternalFormSubmissionError" : "フォームを送信中に内部エラーが発生しました。"
}
@@ -140,7 +140,7 @@ const languages = [
I18N_STRINGS: {
"FileCloseAccessText" : "Premete Invio per eliminare il file ",
"FileSizeGreater" : "I file ${0} superano le dimensioni previste: ${1} MB.",
- "FileNameInvalid" : "I file ${0} contengono caratteri non validi nel nome. Sono supportati solo caratteri alfanumerici",
+ "FileNameInvalid" : 'Non allegare file il cui nome inizia con (.), contiene \\ / : * ? " < > | ; % $ oppure è una parola chiave riservata come nul, prn, con, lpt o com.',
"FileMimeTypeInvalid" : "I file ${0} non sono tipi di file supportati",
"InternalFormSubmissionError" : "Errore interno durante l'invio del modulo."
}
@@ -169,8 +169,8 @@ const languages = [
},
I18N_STRINGS: {
"FileCloseAccessText" : "Appuyer sur Entrée pour supprimer le fichier ",
- "FileSizeGreater" : "FLes fichiers ${0} font plus que la taille attendue : ${1} Mo.",
- "FileNameInvalid" : "Le nom du ou des fichiers ${0} comportent des caractères non valides. Seuls les caractères alphanumériques sont pris en charge",
+ "FileSizeGreater" : '${0} fichier(s) dépasse(nt) la taille attendue : ${1} Mo.',
+ "FileNameInvalid" : "Ne joignez pas de fichiers dont le nom commence par (.), contient \\ / : * ? \" < > | ; % $, ou est un mot-clé réservé comme nul, prn, con, lpt ou com.",
"FileMimeTypeInvalid" : "Le ou les fichiers ${0} sont des types de fichiers non pris en charge",
"InternalFormSubmissionError" : "Une erreur interne s'est produite lors de l'envoi du formulaire."
}
@@ -199,8 +199,8 @@ const languages = [
},
I18N_STRINGS: {
"FileCloseAccessText" : "Presione Intro para eliminar el archivo ",
- "FileSizeGreater" : "FLos archivos ${0} tienen un tamaño superior al esperado: ${1}MB.",
- "FileNameInvalid" : "El nombre de los archivos ${0} contiene caracteres no válidos. Solo se admiten caracteres alfanuméricos",
+ "FileSizeGreater" : "Los archivos ${0} son mayores que el tamaño esperado: ${1} MB.",
+ "FileNameInvalid" : 'No adjunte archivos cuyo nombre comience con (.), contenga \\ / : * ? " < > | ; % $, o sea una palabra clave reservada como nul, prn, con, lpt o com.',
"FileMimeTypeInvalid" : "Los tipos de archivo ${0} no son compatibles",
"InternalFormSubmissionError" : "Error interno al enviar el formulario."
}
@@ -230,7 +230,7 @@ const languages = [
I18N_STRINGS: {
"FileCloseAccessText" : "Enter 키를 눌러 파일 삭제",
"FileSizeGreater" : "파일 ${0}이(가) 예상 크기 ${1}MB를 초과합니다.",
- "FileNameInvalid" : "파일 ${0}의 이름에 잘못된 문자가 포함되어 있습니다. 영숫자만 지원됩니다.",
+ "FileNameInvalid" : '파일 이름이 (.)으로 시작하거나, \\ / : * ? " < > | ; % $를 포함하거나, nul, prn, con, lpt 또는 com과 같이 예약된 키워드인 파일은 첨부하지 마십시오.',
"FileMimeTypeInvalid" : "파일 ${0}은(는) 지원되지 않는 파일 유형입니다.",
"InternalFormSubmissionError" : "양식을 제출하는 중 내부 오류가 발생했습니다."
}
@@ -260,7 +260,7 @@ const languages = [
I18N_STRINGS: {
"FileCloseAccessText" : "按 Enter 以刪除檔案",
"FileSizeGreater" : "檔案 ${0} 的大小比預期大: ${1}MB。",
- "FileNameInvalid" : "${0} 檔案名稱中包含無效字元。僅支援字母數字字元",
+ "FileNameInvalid" : '不要附加以下檔案:檔案名稱以 (.) 開頭且包含 \\ / : * ? " < > | ; % $,或檔案名稱為保留關鍵字,例如 nul、prn、con、lpt 或 com。',
"FileMimeTypeInvalid" : "${0} 檔案的檔案類型不受支援",
"InternalFormSubmissionError" : "提交表單時發生內部錯誤。"
}
@@ -291,7 +291,7 @@ const languages = [
I18N_STRINGS: {
"FileCloseAccessText" : "按 Enter 可删除文件",
"FileSizeGreater" : "文件 ${0} 大于预期大小: ${1}MB。",
- "FileNameInvalid" : "文件 ${0} 的名称中包含无效字符。仅支持字母数字字符",
+ "FileNameInvalid" : '请勿附加文件名以 (.) 开头、包含 \\ / : * ? " < > | ; % $ 或为保留关键字(如 nul、prn、con、lpt 或 com)的文件。',
"FileMimeTypeInvalid" : "文件 ${0} 的类型不受支持",
"InternalFormSubmissionError" : "提交表单时遇到内部错误。"
}
@@ -321,7 +321,7 @@ const languages = [
I18N_STRINGS: {
"FileCloseAccessText" : "Pressione Enter para excluir o arquivo ",
"FileSizeGreater" : "Fos arquivos ${0} são maiores do que o tamanho esperado: ${1}MB.",
- "FileNameInvalid" : "O(s) arquivo(s) ${0} tem caracteres inválidos em seu nome. Somente caracteres alfanuméricos são suportados",
+ "FileNameInvalid" : 'Não anexe arquivos cujos nomes comecem com (.), contenham \\ / : * ? " < > | ; % $, ou sejam palavras-chave reservadas, como nul, prn, con, lpt ou com.',
"FileMimeTypeInvalid" : "O(s) arquivo(s) ${0} não é(são) suportado(s)",
"InternalFormSubmissionError" : "Encontrou um erro interno ao enviar o formulário."
}
diff --git a/ui.tests/test-module/package-lock.json b/ui.tests/test-module/package-lock.json
index 11fbffd3e0..5c54673beb 100644
--- a/ui.tests/test-module/package-lock.json
+++ b/ui.tests/test-module/package-lock.json
@@ -14913,8 +14913,7 @@
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz",
"integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"backo2": {
"version": "1.0.2",
@@ -15927,8 +15926,7 @@
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz",
"integrity": "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"cypress-iframe": {
"version": "1.0.1",
@@ -20788,8 +20786,7 @@
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"xhr": {
"version": "2.6.0",
diff --git a/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js b/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js
index 468cfa396c..d5831802a1 100644
--- a/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js
+++ b/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js
@@ -84,6 +84,22 @@ describe('Page - Authoring', function () {
cy.deleteComponentByPath(accordionEditPath);
});
+ it('runtime time library should not be loaded', function() {
+ cy.intercept('GET', /jcr:content\/guideContainer\/accordion\.html/).as('accordionRequest');
+ dropAccordionInContainer()
+ cy.wait('@accordionRequest').then((interception) => {
+ const htmlContent = interception.response.body;
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlContent, 'text/html');
+ const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/;
+ const scriptTags = Array.from(doc.querySelectorAll('script[src]'));
+ console.log("tags ", scriptTags);
+ const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src));
+ expect(isClientLibraryLoaded).to.be.false;
+ })
+ cy.deleteComponentByPath(accordionEditPath);
+ })
+
it('open edit dialog of Accordion', {retries: 3}, function () {
cy.cleanTest(accordionEditPath).then(function() {
testAccordionBehaviour(accordionPathSelector, accordionEditPath);
diff --git a/ui.tests/test-module/specs/datepicker/datepicker.runtime.localisation.spec.js b/ui.tests/test-module/specs/datepicker/datepicker.runtime.localisation.spec.js
index c62f3e2b45..544032d85d 100644
--- a/ui.tests/test-module/specs/datepicker/datepicker.runtime.localisation.spec.js
+++ b/ui.tests/test-module/specs/datepicker/datepicker.runtime.localisation.spec.js
@@ -45,7 +45,7 @@ describe("Form Runtime with Date Picker", () => {
cy.get(`#${datePicker5}`).find("input").should('have.attr',"type", "text");
cy.get(`#${datePicker5}`).find("input").clear().type(incorrectInput).blur().then(x => {
- cy.get(`#${datePicker5}`).find(".cmp-adaptiveform-datepicker__errormessage").should('have.text',"Specify the value in allowed format : date.")
+ cy.get(`#${datePicker5}`).find(".cmp-adaptiveform-datepicker__errormessage").should('contain.text', 'la valeur au format')
})
cy.get(`#${datePicker5}`).find("input").clear().type(correctInput).blur().then(x => {
diff --git a/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js b/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js
index e7d2a41103..6b9e2133b2 100644
--- a/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js
+++ b/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js
@@ -158,13 +158,13 @@ describe("Form Runtime with Date Picker", () => {
cy.get(`#${datePicker4}`).find("input").clear().type(incorrectInput).blur().then(x => {
cy.get(`#${datePicker4}`).find(".cmp-adaptiveform-datepicker__errormessage").should('have.text',"Please enter a valid value.")
cy.get(`#${datePicker4} > div.${bemBlock}__errormessage`).should('have.attr', 'id', `${datePicker4}__errormessage`)
- cy.get(`#${datePicker4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${datePicker4}__longdescription ${datePicker4}__shortdescription ${datePicker4}__errormessage`)
+ cy.get(`#${datePicker4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${datePicker4}__shortdescription ${datePicker4}__errormessage`)
cy.get(`#${datePicker4} > .${bemBlock}__widget`).should('have.attr', 'aria-invalid', 'true')
})
cy.get(`#${datePicker4}`).find("input").clear().type(correctInput).blur().then(x => {
cy.get(`#${datePicker4}`).find(".cmp-adaptiveform-datepicker__errormessage").should('have.text',"")
- cy.get(`#${datePicker4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${datePicker4}__longdescription ${datePicker4}__shortdescription`)
+ cy.get(`#${datePicker4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${datePicker4}__shortdescription`)
cy.get(`#${datePicker4} > .${bemBlock}__widget`).should('have.attr', 'aria-invalid', 'false')
})
})
diff --git a/ui.tests/test-module/specs/formcontainer.spec.js b/ui.tests/test-module/specs/formcontainer.spec.js
index 803df179b2..584425d33c 100644
--- a/ui.tests/test-module/specs/formcontainer.spec.js
+++ b/ui.tests/test-module/specs/formcontainer.spec.js
@@ -287,4 +287,36 @@ describe('Page/Form Authoring', function () {
});
});
+
+ context("Check default behaviour in Form Editor", function () {
+ const pagePath = "/content/forms/af/core-components-it/samples/numberinput/validation.html";
+
+ beforeEach(function () {
+ cy.previewForm(pagePath);
+ });
+
+ it('check the preventDefaultSubmit method by simulating keydown event on the form', function () {
+ cy.get('.cmp-adaptiveform-container').then((formContainer) => {
+ cy.stub(formContainer[0], 'onsubmit').as('submit');
+ cy.get('form').trigger('keydown', {key: 'Enter'});
+ cy.get('@submit').should('not.be.called');
+ });
+ });
+
+ it('should prevent form submission by default', function () {
+ cy.get('.cmp-adaptiveform-container').then((formContainer) => {
+ // Trigger enter on button
+ cy.get('.cmp-adaptiveform-container button').eq(0).type('{enter}');
+ cy.get('.cmp-adaptiveform-container button').eq(0).should('be.visible');
+
+ // Trigger enter on first input where display:none is not present
+ cy.get('.cmp-adaptiveform-container input').eq(3).type('{enter}');
+ cy.get('.cmp-adaptiveform-container input').eq(3).should('be.visible');
+
+ // Trigger enter on numberinput widget
+ cy.get('.cmp-adaptiveform-numberinput__widget').eq(0).type('{enter}');
+ cy.get('.cmp-adaptiveform-numberinput__widget').eq(0).should('be.visible');
+ });
+ });
+ });
});
diff --git a/ui.tests/test-module/specs/fragment/fragment.runtime.spec.js b/ui.tests/test-module/specs/fragment/fragment.runtime.spec.js
index 549d92a703..a523a11e6b 100644
--- a/ui.tests/test-module/specs/fragment/fragment.runtime.spec.js
+++ b/ui.tests/test-module/specs/fragment/fragment.runtime.spec.js
@@ -46,7 +46,10 @@ describe("Form Runtime with Fragment", () => {
cy.get('*').should(passVisibleCheck)
cy.get('input')
.should(passDisabledAttributeCheck, 'disabled');
- cy.get('input').should('have.value', value)
+ // now panel's also have value because of exportData support in container
+ if (value && (typeof value !== 'object' || Array.isArray(value))) {
+ cy.get('input').should('have.value', value)
+ }
})
}
@@ -89,7 +92,7 @@ describe("Form Runtime with Fragment", () => {
return innerPromise;
};
- it(" should get model and view initialized properly ", () => {
+ it.skip(" should get model and view initialized properly ", () => {
expect(formContainer, "formcontainer is initialized").to.not.be.null;
const fields = formContainer.getAllFields();
@@ -110,7 +113,7 @@ describe("Form Runtime with Fragment", () => {
});
})
- it(" model's changes are reflected in the html ", () => {
+ it.skip(" model's changes are reflected in the html ", () => {
const fragmentId = formContainer._model.items[0].items[0].id;
const model = formContainer._model.getElement(fragmentId);
checkHTML(model.id, model.getState()).then(() => {
@@ -122,7 +125,7 @@ describe("Form Runtime with Fragment", () => {
});
});
- it("responsive component in fragment", () => {
+ it.skip("responsive component in fragment", () => {
const responsiveTextInputId = formContainer._model.items[0].items[0].items[1].id;
cy.get(`#${responsiveTextInputId}`).should('be.visible');
cy.get(`#${responsiveTextInputId}`).parent()
diff --git a/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js b/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js
index bfdecd20c9..7097ab14c2 100644
--- a/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js
+++ b/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js
@@ -166,13 +166,13 @@ describe("Form with Number Input", () => {
cy.get(`#${numberInput4}`).find("input").clear().type(incorrectInput).blur().then(x => {
cy.get(`#${numberInput4}`).find(".cmp-adaptiveform-numberinput__errormessage").should('have.text',"Please enter a valid value.")
cy.get(`#${numberInput4} > div.${bemBlock}__errormessage`).should('have.attr', 'id', `${numberInput4}__errormessage`)
- cy.get(`#${numberInput4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${numberInput4}__longdescription ${numberInput4}__shortdescription ${numberInput4}__errormessage`)
+ cy.get(`#${numberInput4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${numberInput4}__shortdescription ${numberInput4}__errormessage`)
cy.get(`#${numberInput4} > .${bemBlock}__widget`).should('have.attr', 'aria-invalid', 'true')
})
cy.get(`#${numberInput4}`).find("input").clear().type(correctInput).blur().then(x => {
cy.get(`#${numberInput4}`).find(".cmp-adaptiveform-numberinput__errormessage").should('have.text',"")
- cy.get(`#${numberInput4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${numberInput4}__longdescription ${numberInput4}__shortdescription`)
+ cy.get(`#${numberInput4} > .${bemBlock}__widget`).should('have.attr', 'aria-describedby', `${numberInput4}__shortdescription`)
cy.get(`#${numberInput4} > .${bemBlock}__widget`).should('have.attr', 'aria-invalid', 'false')
})
})
diff --git a/ui.tests/test-module/specs/screenreadertext.runtime.spec.js b/ui.tests/test-module/specs/screenreadertext.runtime.spec.js
new file mode 100644
index 0000000000..997cd0ad9e
--- /dev/null
+++ b/ui.tests/test-module/specs/screenreadertext.runtime.spec.js
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright 2024 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 screen reader text", () => {
+
+ const pagePath = "content/forms/af/core-components-it/samples/accessibility/screenreadertext.html";
+ let formContainer = null;
+
+ /**
+ * initialization of form container before every test
+ * */
+ beforeEach(() => {
+ cy.previewForm(pagePath).then(p => {
+ formContainer = p;
+ });
+ });
+
+ it("Aria label should be present for screen reader text in date picker component", () => {
+ const [datePicker1, datePicker1FieldView] = Object.entries(formContainer._fields)[0];
+ const model = formContainer._model.getElement(datePicker1);
+ cy.get(`#${datePicker1}`).find("input").focus().blur().then(x => {
+ cy.get(`#${datePicker1}`).find(".cmp-adaptiveform-datepicker__widget").should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+ });
+
+ it("Aria label should be present for screen reader text in text input component", () => {
+ const [textbox1, textbox1FieldView] = Object.entries(formContainer._fields)[2];
+ cy.get(`#${textbox1}`).find("input").focus().blur().then(x => {
+ cy.get(`#${textbox1}`).find(".cmp-adaptiveform-textinput__widget").should('have.attr','aria-label', 'Long Text Box\n');
+ });
+ });
+
+ it("Aria label should be present for screen reader text in email input component", () => {
+ const [id, fieldView] = Object.entries(formContainer._fields)[3];
+ const model = formContainer._model.getElement(id);
+ cy.get(`#${id}`).find("input").focus().blur().then(x => {
+ cy.get(`#${id}`).find(".cmp-adaptiveform-emailinput__widget").should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+ });
+
+ it("Aria label should be present for screen reader text in radio-button component", () => {
+ const [id, fieldView] = Object.entries(formContainer._fields)[5];
+ const model = formContainer._model.getElement(id);
+ cy.get(`#${id}`).find(".cmp-adaptiveform-radiobutton__widget").should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+
+ it("Aria label should be present for screen reader text in checkbox group component", () => {
+ const [id, fieldView] = Object.entries(formContainer._fields)[6];
+ const model = formContainer._model.getElement(id);
+ cy.get(`#${id}`).find(".cmp-adaptiveform-checkboxgroup__widget").should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+
+ it("Aria label should be present for screen reader text in dropdown component", () => {
+ const [id, fieldView] = Object.entries(formContainer._fields)[7];
+ const model = formContainer._model.getElement(id);
+ cy.get(`#${id}`).find(".cmp-adaptiveform-dropdown__widget").should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+
+ it("Aria label should be present for screen reader text in tabs on top component", () => {
+ const [id, fieldView] = Object.entries(formContainer._fields)[8];
+ const model = formContainer._model.getElement(id);
+ cy.get(`#${id} > .cmp-tabs__tablist`).should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+
+ it("Aria label should be present for screen reader text in wizard component", () => {
+ const [id, fieldView] = Object.entries(formContainer._fields)[9];
+ const model = formContainer._model.getElement(id);
+ cy.get(`#${id} > .cmp-adaptiveform-wizard__widget`).should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+
+ it("Aria label should be present for screen reader text in checkbox component", () => {
+ const [id, fieldView] = Object.entries(formContainer._fields)[10];
+ const model = formContainer._model.getElement(id);
+ cy.get(`#${id}`).find(".cmp-adaptiveform-checkbox__widget").should('have.attr','aria-label', model.getState().screenReaderText);
+ });
+
+});
\ No newline at end of file
diff --git a/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js b/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js
index f7dade60a6..9f3fd84df3 100644
--- a/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js
+++ b/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js
@@ -98,6 +98,21 @@ describe.only('Page - Authoring', function () {
cy.deleteComponentByPath(tabsPath);
});
+ it('runtime library should not be loaded', function() {
+ cy.intercept('GET', /jcr:content\/guideContainer\/tabsontop\.html/).as('tabsRequest');
+ dropTabsInContainer()
+ cy.wait('@tabsRequest').then((interception) => {
+ const htmlContent = interception.response.body;
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlContent, 'text/html');
+ const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/;
+ const scriptTags = Array.from(doc.querySelectorAll('script[src]'));
+ const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src));
+ expect(isClientLibraryLoaded).to.be.false;
+ })
+ cy.deleteComponentByPath(tabsPath);
+ })
+
it('drop element in tabs on top', {retries: 3}, function () {
cy.cleanTest(tabsPath).then(function () {
diff --git a/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js b/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js
index 9670a02ebd..411753b934 100644
--- a/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js
+++ b/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js
@@ -97,6 +97,21 @@ describe.only('Page - Authoring', function () {
cy.deleteComponentByPath(tabsPath);
});
+ it('runtime library should not be loaded', function() {
+ cy.intercept('GET', /jcr:content\/guideContainer\/verticaltabs\.html/).as('verticaltabsRequest');
+ dropTabsInContainer()
+ cy.wait('@verticaltabsRequest').then((interception) => {
+ const htmlContent = interception.response.body;
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlContent, 'text/html');
+ const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/;
+ const scriptTags = Array.from(doc.querySelectorAll('script[src]'));
+ const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src));
+ expect(isClientLibraryLoaded).to.be.false;
+ })
+ cy.deleteComponentByPath(tabsPath);
+ })
+
it ('open edit dialog of Vertical Tabs',{ retries: 3 }, function(){
cy.cleanTest(tabsPath).then(function() {
testPanelBehaviour(tabsContainerPathSelector, tabsPath);
diff --git a/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js b/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js
index 2d4ad015df..29d4290f94 100644
--- a/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js
+++ b/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js
@@ -66,6 +66,21 @@ describe('Page - Authoring', function () {
cy.openAuthoring(pagePath);
});
+ it('runtime library should not be loaded', function() {
+ cy.intercept('GET', /jcr:content\/guideContainer\/wizard\.html/).as('wizardRequest');
+ dropWizardInContainer();
+ cy.wait('@wizardRequest').then((interception) => {
+ const htmlContent = interception.response.body;
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlContent, 'text/html');
+ const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/;
+ const scriptTags = Array.from(doc.querySelectorAll('script[src]'));
+ const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src));
+ expect(isClientLibraryLoaded).to.be.false;
+ })
+ cy.deleteComponentByPath(wizardLayoutDrop);
+ })
+
it('verify Basic tab in edit dialog of Wizard', function () {
dropWizardInContainer();
cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + wizardEditPathSelector).then(() => {