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 bc6d1f7caa..04105c4c75 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
@@ -56,6 +56,9 @@ private FormConstants() {
/** The resource type for date picker v1 */
public static final String RT_FD_FORM_DATE_PICKER_V1 = RT_FD_FORM_PREFIX + "datepicker/v1/datepicker";
+ /** The resource type for date picker v2 */
+ public static final String RT_FD_FORM_DATE_PICKER_V2 = RT_FD_FORM_PREFIX + "datepicker/v2/datepicker";
+
/** The resource type for number input v1 */
public static final String RT_FD_FORM_NUMBER_INPUT_V1 = RT_FD_FORM_PREFIX + "numberinput/v1/numberinput";
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java
index 205cc2fead..69ed63840d 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java
@@ -44,7 +44,10 @@
adaptables = { SlingHttpServletRequest.class, Resource.class },
adapters = { DatePicker.class,
ComponentExporter.class },
- resourceType = { FormConstants.RT_FD_FORM_DATE_PICKER_V1 })
+ resourceType = {
+ FormConstants.RT_FD_FORM_DATE_PICKER_V1,
+ FormConstants.RT_FD_FORM_DATE_PICKER_V2
+ })
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class DatePickerImpl extends AbstractFieldImpl implements DatePicker {
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml
index 1b446738e2..3859abc906 100644
--- a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml
@@ -5,4 +5,4 @@
cssProcessor="[default:none,min:none]"
jsProcessor="[default:none,min:none]"
categories="[core.forms.components.it.runtime.all]"
- embed="[core.forms.components.runtime.base,core.forms.components.it.container.v1.runtime,core.forms.components.datePicker.v1.runtime,core.forms.components.textinput.v1.runtime,core.forms.components.numberinput.v1.runtime,core.forms.components.panelcontainer.v1.runtime,core.forms.components.radiobutton.v1.runtime,core.forms.components.text.v1.runtime,core.forms.components.checkboxgroup.v1.runtime,core.forms.components.button.v1.runtime,core.forms.components.image.v1.runtime,core.forms.components.dropdown.v1.runtime,core.forms.components.fileinput.v2.runtime,core.forms.components.accordion.v1.runtime,core.forms.components.tabs.v1.runtime,core.forms.components.wizard.v1.runtime,core.forms.components.verticaltabs.v1.runtime,core.forms.components.recaptcha.v1.runtime,core.forms.components.checkbox.v1.runtime,core.forms.components.fragment.v1.runtime,core.forms.components.switch.v1.runtime,core.forms.components.termsandconditions.v1.runtime, core.forms.components.it.textinput.v1.runtime, core.forms.components.hcaptcha.v1.runtime, core.forms.components.turnstile.v1.runtime]"/>
+ embed="[core.forms.components.runtime.base,core.forms.components.it.container.v1.runtime,core.forms.components.datePicker.v1.runtime,core.forms.components.datePicker.v2.runtime,core.forms.components.textinput.v1.runtime,core.forms.components.numberinput.v1.runtime,core.forms.components.panelcontainer.v1.runtime,core.forms.components.radiobutton.v1.runtime,core.forms.components.text.v1.runtime,core.forms.components.checkboxgroup.v1.runtime,core.forms.components.button.v1.runtime,core.forms.components.image.v1.runtime,core.forms.components.dropdown.v1.runtime,core.forms.components.fileinput.v2.runtime,core.forms.components.accordion.v1.runtime,core.forms.components.tabs.v1.runtime,core.forms.components.wizard.v1.runtime,core.forms.components.verticaltabs.v1.runtime,core.forms.components.recaptcha.v1.runtime,core.forms.components.checkbox.v1.runtime,core.forms.components.fragment.v1.runtime,core.forms.components.switch.v1.runtime,core.forms.components.termsandconditions.v1.runtime, core.forms.components.it.textinput.v1.runtime, core.forms.components.hcaptcha.v1.runtime, core.forms.components.turnstile.v1.runtime]"/>
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 5587d80b82..d4312d0e34 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
@@ -4,6 +4,12 @@
"FileNameInvalid" : "Do not attach files where filename starts with (.), contains \\ / : * ? \" < > | ; % $, or is a reserved keyword like nul, prn, con, lpt, or com.",
"FileMimeTypeInvalid" : "File(s) ${0} are unsupported file types",
"InternalFormSubmissionError" : "Encountered an internal error while submitting the form.",
+ "previousMonth" : "Previous month",
+ "nextMonth" : "Next month",
+ "previousYear" : "Previous year",
+ "nextYear" : "Next year",
+ "previousSetOfYears" : "Previous set of years",
+ "nextSetOfYears" : "Next set of years",
"FileSizeZero" : "The uploaded file(s) ${0} are empty. Make sure you're uploading file(s) with content.",
"type" : "Please enter a valid value.",
"required" : "Please fill in this field.",
@@ -22,6 +28,7 @@
"accept" : "The specified file type not supported.",
"defaultError" : "There is an error in the field",
"clearText" : "Clear",
+ "openCalendarText" : "Open calendar",
"calendarSymbols" : {
"monthNames" : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
"abbrmonthNames" : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js
index 0300afe7dd..fcf743359f 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js
@@ -144,9 +144,9 @@ if (typeof window.DatePickerWidget === 'undefined') {
'
' +
'' +
'' +
- '' +
- '' +
- '';
+ '' +
+ '' +
+ '';
constructor(view, widget, model) {
this.#model = model;
@@ -232,6 +232,7 @@ if (typeof window.DatePickerWidget === 'undefined') {
}
});
this.caption = this.#dp.getElementsByClassName("dp-caption")[0];
+ this.caption.id = "dp-caption";
this.caption.addEventListener("click",
function (evnt) {
if (self.view && self.#curInstance) {
@@ -847,12 +848,14 @@ if (typeof window.DatePickerWidget === 'undefined') {
let rowEl = document.createElement("ul");
rowEl.removeAttribute("aria-label");
rowEl.classList.toggle("header", isHeader);
+ rowEl.setAttribute("role", "row");
$row = $view.appendChild(rowEl);
}
items = $row.getElementsByTagName("li").length;
while (items++ < rowArray.length) {
let listItemEl = document.createElement("li");
listItemEl.id = "li-" + elementAt(rowNum, items).gridId;
+ listItemEl.setAttribute("role", isHeader ? "columnheader" : "gridcell");
$tmp = $row.appendChild(listItemEl);
if (!isHeader) {
let cb = function (evnt) {
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/.content.xml
new file mode 100644
index 0000000000..f75e671c00
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/.content.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/.content.xml
new file mode 100644
index 0000000000..8d9a662fbb
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/.content.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/README.md
new file mode 100644
index 0000000000..22c6ded4be
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/README.md
@@ -0,0 +1,100 @@
+
+Adaptive Form Date Picker (v2)
+====
+Adaptive Form Date Picker field component written in HTL.
+
+## Features
+
+* Provides the following type of input:
+ * date
+* Allows replacing this component with other component (as mentioned below).
+
+### Use Object
+The Form Date Picker component uses the `com.adobe.cq.forms.core.components.models.form.DatePicker` Sling Model for its Use-object.
+
+### Edit Dialog Properties
+The following properties are written to JCR for this Form Date 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
+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 filed will be read only
+9. `./dataFormat` - defines the format in which the value exported/submitted
+10. `./editFormat` - defines the format in which the value will be edited by the user
+11. `./minimumDate` - define the minimum date input allowed for the field
+12. `./maximumDate` - define the maximum date input allowed for the field
+13. `./displayFormat` - define the template for display pattern (Reference can be found [here](https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns)).
+
+## Client Libraries
+The component provides a `core.forms.components.datePicker.v2.runtime` client library category that contains the Javascript runtime for the component.
+It should be added to a relevant site client library using the `embed` property.
+
+## BEM Description
+```
+BLOCK cmp-adaptiveform-datepicker
+ ELEMENT cmp-adaptiveform-datepicker__label
+ ELEMENT cmp-adaptiveform-datepicker__label-container
+ ELEMENT cmp-adaptiveform-datepicker__widget
+ ELEMENT cmp-adaptiveform-datepicker__questionmark
+ ELEMENT cmp-adaptiveform-datepicker__shortdescription
+ ELEMENT cmp-adaptiveform-datepicker__longdescription
+ ELEMENT cmp-adaptiveform-datepicker__errormessage
+```
+
+### Note
+By placing the class names `cmp-adaptiveform-datepicker__label` and `cmp-adaptiveform-datepicker__questionmark` within the `cmp-adaptiveform-datepicker__label-container` class, you create a logical grouping of the label and question mark elements. This approach simplifies the process of maintaining a consistent styling for both elements.
+
+## JavaScript Data Attribute Bindings
+
+The following attributes must be added for the initialization of the date-picker component in the form view:
+ 1. `data-cmp-is="adaptiveFormDatePicker"`
+ 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
+
+## Replace feature:
+We support replace feature that allows replacing Reset Button component to any of the below components:
+
+* Button
+* Email Input
+* Number Input
+* Reset Button
+* Submit Button
+* Telephone Input
+* Text Box
+* Text Input
+
+
+## Information
+* **Vendor**: Adobe
+* **Version**: v2
+* **Compatibility**: Cloud
+* **Status**: production-ready
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/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/datepicker/v2/datepicker/clientlibs/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/.content.xml
new file mode 100644
index 0000000000..5a6d805da1
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/.content.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css.txt
new file mode 100644
index 0000000000..130f10b2e3
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css.txt
@@ -0,0 +1,19 @@
+###############################################################################
+# Copyright 2025 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
+datepickerview.css
+datepickerwidget.css
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css/datepickerview.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css/datepickerview.css
new file mode 100644
index 0000000000..17de33f661
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css/datepickerview.css
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2025 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-datepicker {
+
+}
+
+.cmp-adaptiveform-datepicker__widget {
+
+}
+
+.cmp-adaptiveform-datepicker__label {
+
+}
+.cmp-adaptiveform-datepicker__label-container{
+
+}
+
+.cmp-adaptiveform-datepicker__longdescription {
+
+}
+
+.cmp-adaptiveform-datepicker__shortdescription {
+
+}
+
+.cmp-adaptiveform-datepicker__questionmark {
+
+}
+
+.cmp-adaptiveform-datepicker__questionmark {
+
+}
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css/datepickerwidget.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css/datepickerwidget.css
new file mode 100644
index 0000000000..2e14ed020a
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/css/datepickerwidget.css
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright 2025 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.
+ ******************************************************************************/
+
+.datetimepicker {
+ border: none;
+ background-color: #FFF;
+ display: none;
+ position: absolute;
+ cursor: default;
+ z-index: 100;
+ outline: solid #CCCCCC 2px;
+ flex-direction: column;
+ width: 433px;
+}
+
+.datetimepicker .dp-clear {
+ overflow: auto;
+ background-color: #F5F5F5;
+ text-align: center;
+}
+
+.datetimepicker .dp-clear a {
+ cursor: pointer;
+ height: 40px;
+ line-height: 40px;
+ padding: 0px 5px 0px 5px;
+ text-align: center;
+ display: inline-block;
+ font-size: 0.875rem;
+ color: #6d6d6d;
+}
+
+.datetimepicker-notouch .dp-close a:hover {
+ color: #c8bbff;
+}
+
+.datetimepicker .dp-header {
+ height: 40px;
+ line-height: 40px;
+ color: #555555;
+ margin-bottom: 5px;
+ background-color: #E6E6E6;
+ display: flex;
+ justify-content: space-between;
+}
+
+.datetimepicker .dp-header .dp-leftnav,
+.datetimepicker .dp-header .dp-rightnav,
+.datetimepicker .dp-header .dp-caption {
+ float: left;
+ text-align: center;
+ cursor: pointer;
+ height: 40px;
+}
+
+.datetimepicker-notouch .dp-header .dp-caption:not(.disabled):hover {
+ color: #969696;
+}
+
+.datetimepicker .dp-header .dp-rightnav {
+ float: right;
+ background: url("/etc.clientlibs/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/resources/rightnav.png") no-repeat center center;
+ width: 40px;
+}
+
+.datetimepicker .dp-header .dp-leftnav {
+ width: 40px;
+ background: url("/etc.clientlibs/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/resources/leftnav.png") no-repeat center center;
+}
+
+.datetimepicker .dp-header .dp-rightnav:hover {
+ background: url("/etc.clientlibs/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/resources/rightnav_hover.png") no-repeat center center;
+}
+
+.datetimepicker .dp-header .dp-leftnav:hover {
+ background: url("/etc.clientlibs/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/resources/leftnav_hover.png") no-repeat center center;
+}
+
+.datetimepicker .view {
+ display: none;
+}
+
+.datetimepicker .view ul {
+ display: flex;
+ justify-content: space-around;
+ list-style: none;
+ margin: 0;
+ padding: 5px;
+ overflow: hidden;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+}
+
+.datetimepicker .view ul li {
+ float: left;
+ padding: 5px;
+ text-align: center;
+ border: none;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ color: #666666;
+ min-width:40px;
+}
+
+.datetimepicker .view ul.header li {
+ color: #555555;
+}
+
+.datetimepicker .view ul:not(.header) li:not(.disabled) {
+ cursor: pointer;
+}
+
+.datetimepicker .view ul.header {
+ color: #000;
+ background-color: #FFF;
+ border-bottom: #E6E6E6 1px solid;
+ display: flex;
+ justify-content: space-around;
+}
+
+.datetimepicker-notouch .view ul:not(.header) li:not(.disabled):hover {
+ color: black;
+ background-color: #E6E6E6;
+ opacity: 0.5;
+}
+
+.datetimepicker .view ul li.disabled {
+ color: #CCCCCC;
+}
+
+.datetimepicker .view ul li.dp-selected {
+ outline: none;
+ background-color: #666666;
+ color: #FFFFFF;
+ opacity: 1.0;
+}
+
+.datetimepicker .view ul li.dp-focus {
+ border: 1px dashed black;
+}
+
+.cmp-adaptiveform-datepicker__calendar-icon {
+ position: absolute;
+ top: 41px;
+ right: 30px;
+ z-index: 10;
+ background: url("/etc.clientlibs/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/resources/calendar.png") no-repeat center center;
+ background-size: contain;
+ width: 24px;
+ height: 24px;
+}
+
+.datefieldwidget.widgetreadonly .cmp-adaptiveform-datepicker__calendar-icon {
+ display: none;
+}
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js.txt
new file mode 100644
index 0000000000..c307501a59
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js.txt
@@ -0,0 +1,20 @@
+###############################################################################
+# Copyright 2025 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
+cache.js
+datepickerview.js
+datepickerwidget.js
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/cache.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/cache.js
new file mode 100644
index 0000000000..97f9bd1e24
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/cache.js
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright 2025 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 Cache {
+
+ store = {};
+
+ put(el, key, value) {
+ this.store[el.id ] = {};
+ this.store[el.id][key] = value;
+ }
+
+ get(el, key) {
+ return this.store[el.id][key];
+ }
+ }
+ window.afCache = new Cache();
+})();
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/datepickerview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/datepickerview.js
new file mode 100644
index 0000000000..27d30b1ac9
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/datepickerview.js
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright 2025 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 DatePicker extends FormView.FormFieldBase {
+
+ static NS = FormView.Constants.NS;
+ static IS = "adaptiveFormDatePicker";
+ static bemBlock = 'cmp-adaptiveform-datepicker';
+ static selectors = {
+ self: "[data-" + this.NS + '-is="' + this.IS + '"]',
+ widget: `.${DatePicker.bemBlock}__widget`,
+ label: `.${DatePicker.bemBlock}__label`,
+ description: `.${DatePicker.bemBlock}__longdescription`,
+ qm: `.${DatePicker.bemBlock}__questionmark`,
+ errorDiv: `.${DatePicker.bemBlock}__errormessage`,
+ tooltipDiv: `.${DatePicker.bemBlock}__shortdescription`
+ };
+
+ constructor(params) {
+ super(params);
+ }
+
+ getWidget() {
+ return this.element.querySelector(DatePicker.selectors.widget);
+ }
+
+ getDescription() {
+ return this.element.querySelector(DatePicker.selectors.description);
+ }
+
+ getLabel() {
+ return this.element.querySelector(DatePicker.selectors.label);
+ }
+
+ getErrorDiv() {
+ return this.element.querySelector(DatePicker.selectors.errorDiv);
+ }
+
+ getQuestionMarkDiv() {
+ return this.element.querySelector(DatePicker.selectors.qm);
+ }
+
+ getTooltipDiv() {
+ return this.element.querySelector(DatePicker.selectors.tooltipDiv);
+ }
+
+ updateValue(value) {
+ if (this.widgetObject) {
+ if (this.isActive()) {
+ // Replacing undefined / null with empty string on reset
+ this.widgetObject.setValue(value || '');
+ } else {
+ // Replacing undefined / null with empty string on reset
+ this.widgetObject.setDisplayValue(value || '');
+ }
+ } else {
+ // Replacing undefined / null with empty string on reset
+ super.updateValue(value || '');
+ }
+ }
+
+ updateReadOnly(readOnly, state) {
+ super.updateReadOnly(readOnly, state);
+ if (this.widgetObject != null) {
+ this.widgetObject.markAsReadOnly(readOnly);
+ }
+ }
+
+
+ setModel(model) {
+ super.setModel(model);
+ if (!this.#noFormats()) {
+ if (this.widgetObject == null) {
+ this.widgetObject = new DatePickerWidget(this, this.getWidget(), model);
+ }
+ if (this.isActive()) {
+ this.widgetObject.setValue(model.value);
+ } else {
+ this.widgetObject.setDisplayValue(model.value);
+ }
+ this.widgetObject.addEventListener('blur', (e) => {
+ this.setModelValue(this.widgetObject.getValue())
+ //setDisplayValue is required for cases where value remains same while focussing in and out.
+ this.widgetObject.setDisplayValue(this._model.value);
+ this.widgetObject.setCalendarWidgetValue(this._model.value);
+ this.setInactive();
+ this.triggerExit();
+ }, this.getWidget());
+ this.widgetObject.addEventListener('focus', (e) => {
+ this.widgetObject.setValue(e.target.value);
+ this.setActive();
+ this.triggerEnter();
+ }, this.getWidget());
+ this.widgetObject.addEventListener('input', (e) => {
+ if( e.target.value === '') {
+ // clear the value if user manually empties the value in date input box
+ this.setModelValue("");
+ }
+ }, this.getWidget());
+ } else {
+ if (this.widget.value !== '') {
+ this.setModelValue(this.widget.value);
+ }
+ this.widget.addEventListener('blur', (e) => {
+ this.setModelValue(e.target.value);
+ this.setInactive();
+ this.triggerExit();
+ });
+ this.widget.addEventListener('focus', (e) => {
+ this.setActive();
+ this.triggerEnter();
+ });
+ }
+ }
+
+ #noFormats() {
+ return (this._model.editFormat == null || this._model.editFormat === 'date|short') &&
+ (this._model.displayFormat == null || this._model.displayFormat === 'date|short') && (this._model.displayValueExpression == null)
+ }
+ }
+
+ FormView.Utils.setupField(({element, formContainer}) => {
+ return new DatePicker({element, formContainer})
+ }, DatePicker.selectors.self);
+})();
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/datepickerwidget.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/datepickerwidget.js
new file mode 100644
index 0000000000..1a3633a3b6
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/clientlibs/site/js/datepickerwidget.js
@@ -0,0 +1,1345 @@
+/*******************************************************************************
+ * Copyright 2025 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.
+ ******************************************************************************/
+
+if (typeof window.DatePickerWidget === 'undefined') {
+
+ window.DatePickerWidget = class {
+
+ #widget = null;
+ #model = null // passed by reference
+ #options = null
+
+ #lang = "en";
+
+ #dp = null;
+ #curInstance = null;
+ #calendarIcon = null;
+ #documentTouchListener = null;
+ static #visible = false;
+ static #clickedWindow;
+
+ /** default configuration options
+ *
+ * yearsPerView: number of years to show in the yearset view
+ *
+ * width: with of the widget
+ *
+ * viewHeight: Height of the month,year and yearset view. This doesn't include
+ * the height of the header
+ *
+ * locale: locale information for the locale in which to show the datepicker which comprises of
+ * days: day names to display in the monthview
+ * months: month names to display in the yearview
+ * zero: string representation of zero in the locale. Numbers will be
+ * displayed in that locale only
+ * clearText: Text to display for the reset button
+ * name: name of the locale
+ *
+ * format: input format for the datepicker (not implemented)
+ *
+ * pickerType: type of the datetimepicker (date, datetime and time)
+ *
+ * positioning: element around which datepicker will be displayed. if null then it
+ * will be displayed around the input element
+ *
+ * showCalendarIcon: to show the Calendar on the right of the text field or not
+ */
+ #defaultOptions = {
+ yearsPerView: 16,
+ width: 340,
+ viewHeight: 248,
+ locale: {
+ days: ["S","M","T","W","T","F","S"],
+ months: ["January","February","March","April","May","June","July","August","September","October","November","December"],
+ zero: "0",
+ clearText: "Clear",
+ openCalendarText: "Open calendar",
+ name: "en_US"
+ },
+ format: "YYYY-MM-DD",
+ pickerType: "date",
+ positioning: null,
+ showCalendarIcon: true
+ }
+
+ #dates = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
+ /*
+ * Actions to perform when clicked on the datepicker buttons
+ * for different views
+ * caption: view to show when clicked on caption
+ * (Year/YearSet/Month/null) null means don't change the view
+ * li: view to show when clicked on date, month or year element
+ * upDown: add(up key) or subtract(down key) current date (for monthview),
+ * month(Year View) or year(YearSetView) with the number provided
+ * key: identifies the key that needs to be changed for that view
+ */
+ #viewAction = {
+ Month: {
+ caption: 'Year',
+ li: null,
+ key: "day",
+ upDown: 7
+ },
+ Year: {
+ caption: "Yearset",
+ li: "Month",
+ key: "month",
+ upDown: 3
+ },
+ Yearset: {
+ caption: null,
+ li: "Year",
+ key: "year",
+ upDown: 4
+ }
+ }
+
+ selectedDay = 0;
+ selectedMonth = 0;
+ selectedYear = 0;
+ currentDay = 0;
+ currentMonth = 0;
+ currentYear = 0;
+ //view="Month";
+ #touchSupported = !!("ontouchstart" in window || window.DocumentTouch
+ && document instanceof DocumentTouch);
+ #defaultView = "Month";
+ #keysEnabled = false;
+ #focusedOnLi = false;
+ #keyboardAccessibility = true;
+ #scriptFocus;
+
+ /* template for the clear Button */
+ #clearButtonTemplate = '';
+
+ /* template for the calendar
+ * header contains the navigation icons (left and right arrows)
+ * and the current caption (which can be date, year or month)
+ *
+ * monthview displays the grid for showing the dates for a particular
+ * month
+ *
+ * yearview displays all the months of that year
+ *
+ * yearsetview displays a grid of 16 years. This can be configured
+ * through the option: yearsPerView
+ *
+ */
+ #calendarTemplate = '' +
+ '' +
+ '' +
+ '';
+
+ constructor(view, widget, model) {
+ this.#model = model;
+ this.#lang = view.getModel().lang;
+ let editValFn = () => {
+ return model.editValue;
+ };
+ let displayValueFn = () => {
+ return model.displayValue;
+ };
+ this.#localizeDateElements(this.#defaultOptions, this.#lang);
+ this.#options = Object.assign(
+ {editValue: editValFn, displayValue: displayValueFn},
+ this.#defaultOptions, this.#model._jsonModel);
+
+ this.#initialiseCalenderElement();
+
+ //update widget markup to have input type=text element.
+ let newDatePickerElement = widget.cloneNode(true);
+ newDatePickerElement.setAttribute("type", "text");
+ newDatePickerElement.id = model.id + '-widget';
+
+ widget.parentNode.replaceChild(newDatePickerElement, widget);
+ this.#widget = view.getWidget();
+ this.#attachField(view.getWidget(), this.#options);
+ }
+
+ #initialiseCalenderElement() {
+ let self = this,
+ html = "";
+ if (this.#options.pickerType.match(/date/)) {
+ html += this.#calendarTemplate;
+ }
+ html += this.#clearButtonTemplate;
+
+ if (!this.#dp) {
+ this.#dp = document.createElement("div");
+ this.#dp.classList.add("datetimepicker", "datePickerTarget");
+ this.#dp.innerHTML = html;
+ //Always inserting it in body
+ document.body.appendChild(this.#dp);
+ }
+ // attach click event on entire datePicker popup
+ this.#dp.addEventListener("click",
+ function (evnt) {
+ //focus only if the device doesn't support touch
+ // input otherwise on screen keyboard will popup
+ if (!self.#touchSupported) {
+ if (self.#curInstance) {
+ self.#curInstance.$field.focus();
+ }
+ }
+ }, false);
+ "touchstart mousedown".split(" ").forEach(function (e) {
+ self.#dp.addEventListener(e, function (evt) {
+ self.#checkWindowClicked(evt)
+ }, false);
+ });
+
+ this.$month = this.#dp.getElementsByClassName("dp-monthview")[0];
+ this.$year = this.#dp.getElementsByClassName("dp-yearview")[0];
+ this.$yearset = this.#dp.getElementsByClassName("dp-yearsetview")[0];
+
+ this.clear = this.#dp.getElementsByClassName(
+ "dp-clear")[0].getElementsByTagName("a")[0];
+ this.clear.addEventListener("click", function (evnt) {
+ if (self.view && self.#curInstance) {
+ self.#clearDate(self.view);
+ }
+ });
+ this.prevNavWidthBtn = this.#dp.getElementsByClassName("dp-leftnav")[0];
+ this.prevNavWidthBtn.setAttribute("role", "button");
+ this.prevNavWidthBtn.addEventListener("click",
+ function (evnt) {
+ if (self.view && self.#curInstance) {
+ self.#adjustDate(-1, self.view, false)
+ }
+ });
+ this.nextNavWidthBtn = this.#dp.getElementsByClassName("dp-rightnav")[0];
+ this.nextNavWidthBtn.setAttribute("role", "button");
+ this.nextNavWidthBtn.addEventListener("click",
+ function (evnt) {
+ if (self.view && self.#curInstance) {
+ self.#adjustDate(1, self.view, false)
+ }
+ });
+ this.caption = this.#dp.getElementsByClassName("dp-caption")[0];
+ this.caption.setAttribute("role", "button");
+ this.caption.id = "dp-caption";
+ this.caption.addEventListener("click",
+ function (evnt) {
+ if (self.view && self.#curInstance) {
+ if (!self.caption.classList.contains("disabled")) {
+ self.#layout(self.#viewAction[self.view].caption);
+ }
+ }
+ });
+ if (this.#keyboardAccessibility) {
+ [this.prevNavWidthBtn, this.caption, this.nextNavWidthBtn,
+ this.clear].forEach(function (elem, i) {
+ elem.setAttribute("tabIndex", i + 1);
+ });
+ }
+
+ }
+
+ /*
+ * attaches the date picker to the field. This is a one time operation
+ * First creates a configuration object and stores it in the field data attributes
+ * then attaches event handlers on click, focus (to show the picker) and blur (to hide) events
+ */
+ #attachField(widget, options) {
+ let inst = this.#newInst(widget, options),
+ self = this,
+ activateField = function (evnt) {
+ if (!self.#curInstance) {
+ self.#activateField(evnt);
+ }
+
+ if (self.#options.showCalendarIcon) {
+ if (evnt.type === self.#getEvent()) {
+ if (self._iconClicked) {
+ self._iconClicked = false;
+ if (DatePickerWidget.#visible) {
+ self.#hide(); // hide the calendar popup if visible when calendar icon is clicked
+ self.#curInstance.$field.focus(); // bring back focus in field
+ } else {
+ self.#show(); //// show the calendar popup if not visible when calendar icon is clicked
+ }
+ }
+ }
+ } else {
+ /*show the popup only if
+ 1. click/touch event
+ 2. focus event in case of non-touch devices and focus is not done using script
+ */
+ if (evnt.type === self.#getEvent() || (evnt.type === "focus"
+ && !self.#touchSupported && !self.#scriptFocus)) {
+ self.#show(evnt);
+ }
+ }
+
+ DatePickerWidget.#clickedWindow = true;
+ self.#scriptFocus = false;
+ },
+ deactivateField = function (evnt) {
+ //deactivate only if clicked outside window
+ // on touch devices only keyboard or calendar should be active at once, touching keyboard should deactivate calendar
+ if ((DatePickerWidget.#clickedWindow && !self.#focusedOnLi)
+ && (self.#options.showCalendarIcon || !self.#touchSupported)) {
+ self.#hide();
+ self.#deactivateField();
+ DatePickerWidget.#clickedWindow = true;
+ }
+ };
+
+ window.afCache.put(widget, "datetimepicker", inst);
+
+ widget.addEventListener(this.#getEvent(), activateField);
+ widget.onfocus = activateField;
+ widget.onblur = deactivateField;
+
+ if (options.showCalendarIcon) {
+ let existingCalendarIcons = widget.parentNode.querySelectorAll('.cmp-adaptiveform-datepicker__calendar-icon');
+ existingCalendarIcons.forEach(icon => icon.remove());
+
+ let calendarIcon = document.createElement("div");
+ calendarIcon.classList.add("cmp-adaptiveform-datepicker__calendar-icon");
+
+ widget.parentNode.insertBefore(calendarIcon, widget.nextSibling);
+
+ if (this.#keyboardAccessibility) {
+ calendarIcon.setAttribute("tabindex", 0);
+ }
+ // Accessibility attributes for calendar icon button
+ calendarIcon.setAttribute("role", "button");
+ calendarIcon.setAttribute("aria-label", this.#options?.locale?.openCalendarText || "Open calendar");
+ calendarIcon.addEventListener(this.#getEvent(), function (evnt) {
+ self._iconClicked = true;
+ widget.click();
+ });
+ calendarIcon.addEventListener("keydown", function (event) {
+ if (event.keyCode === 32 || event.keyCode === 13) {
+ event.preventDefault();
+ event.stopPropagation();
+ self._iconClicked = true;
+ widget.click();
+ }
+ });
+ this.#calendarIcon = calendarIcon;
+ if (options.readOnly) {
+ this.markAsReadOnly(true)
+ }
+ }
+ }
+
+ markAsReadOnly(readonly) {
+ if (readonly) {
+ this.#calendarIcon.style.display = "none";
+ } else {
+ this.#calendarIcon.style.display = "";
+ }
+ }
+
+ #newInst(widget, options) {
+ return {
+ $field: widget,
+ locale: options.locale,
+ positioning: options.positioning || widget,
+ access: options.access,
+ selectedDate: options.value,
+ editValue: options.editValue,
+ displayValue: options.displayValue,
+ minValidDate: options.minimum,
+ maxValidDate: options.maximum,
+ exclMinDate: options.exclusiveMinimum,
+ exclMaxDate: options.exclusiveMaximum
+ }
+ }
+
+ /*
+ * To check where the click happened, if happened outside the datepicker
+ * then hide the picker. This is checked whether any ancestor of clicked target
+ * has a class datePickerTarget. This class is added to the attached element as well
+ */
+ #checkWindowClicked(evnt) {
+ let self = this;
+ if (self.#curInstance) {
+ // datepickerTarget class depicts that the component is a part of the Date Field
+ // and on click of that class, we should not hide the datepicker or fire exit events.
+ if (!evnt.target.closest(".datePickerTarget")) {
+ //non-touch devices do not deactivate on blur. Hence needs to be done here
+ if (self.#touchSupported) {
+ self.#hide();
+ //clicking outside a field doesn't blur the field in IPad. Doing it by script
+ self.#curInstance.$field.blur();
+ self.#deactivateField();
+ } else {
+ DatePickerWidget.#clickedWindow = true;
+ }
+ } else {
+ DatePickerWidget.#clickedWindow = false;
+ }
+ }
+ }
+
+ /*
+ * handling of key strokes. All the key strokes prevent the default browser action
+ * unless specified otherwise
+ * tab: set focus on calendar icon when dateinput field is active, navigates through date picker buttons when datepicker is open,
+ * otherwise perform default browser action
+ * escape: hides the datepicker
+ * down arrow key: navigate the picker downwards by the number specified in actionView.upDown of the current View
+ * up arrow key: navigate the picker upwards by the number specified in actionView.upDown of the current View
+ * left arrow key: navigate the picker one unit of that view backward
+ * right arrow key: navigate the picker one unit of that view forward
+ * shift + up: perform the action that happens on clicking the caption (as specified in actionView.caption)
+ * shift + left: perform the action that happens on clicking the left navigation button
+ * shift + right: perform the action that happens on clicking the right navigation button
+ * space/enter: triggers the click event for the current focused element from datepicker/ opens datepicker when calendar icon is focused.
+ */
+ #hotKeys(evnt) {
+ var handled = false, date;
+ switch (evnt.keyCode) {
+ case 9: //tab
+ // CQ-4239352 : Setting clickedWindow property to true on tabbing so that deactivateField logic gets executed
+ // When clicking on "x" on input field in in IE and when selecting the content and releasing the mouse select outside the field
+ // the click event is not trigerred on the field and hence activateField is not executed, so clickedWindow remains as false
+ DatePickerWidget.#clickedWindow = true;
+ handled = false;
+ break;
+ case 27://escape
+ if (DatePickerWidget.#visible) {
+ this.#hide();
+ this.#curInstance.$field.focus();
+ this.#deactivateField();
+ handled = true;
+ }
+ break;
+ case 32: //space
+ case 13: // enter
+ if (evnt.target.classList.contains("cmp-adaptiveform-datepicker__calendar-icon")) {
+ if (!DatePickerWidget.#visible) {
+ this.#show();
+ handled = true;
+ } else {
+ this.$focusedDate.classList.add("dp-focus");
+ handled = true;
+ }
+ }
+ break;
+ case 40: //down arrow key
+ if (!DatePickerWidget.#visible) {
+ this.#show();
+ return;
+ }
+ this.$focusedDate.classList.add("dp-focus");
+ break;
+ }
+
+ if (DatePickerWidget.#visible && this.#keysEnabled) {
+ var v = this.#viewAction[this.view].key,
+ updown = this.#viewAction[this.view].upDown;
+ switch (evnt.keyCode) {
+ case 9: // tab
+ if (evnt.shiftKey) {
+ if (evnt.target.classList.contains("dp-leftnav")
+ || evnt.target.classList.contains("dp-focus")) {
+ this.#hide();
+ this.#curInstance.$field.focus();
+ handled = true;
+ } else {
+ handled = false;
+ }
+ } else {
+ const buttonTabindex = evnt.target.getAttribute("tabindex");
+ if (buttonTabindex === '0') {
+ if (evnt.target.tagName.toLocaleLowerCase() === "input") {
+ this.#hide();
+ handled = false;
+ } else {
+ this.prevNavWidthBtn.focus();
+ handled = true;
+ }
+ } else if (buttonTabindex === '4') {
+ this.#hide();
+ this.#curInstance.$field.focus();
+ handled = true;
+ } else {
+ handled = false;
+ }
+ }
+ break;
+ case 32: //select on space
+ case 13: // select on enter
+ this.hotKeyPressed = true;
+ this.#focusedOnLi = false;
+ if (!this.#focusedOnLi) {
+ evnt.target.click();
+ } else {
+ if (this.$focusedDate) {
+ this.$focusedDate.click() //todo
+ }
+ }
+ this.hotKeyPressed = false;
+ handled = true;
+ break;
+ case 37: //left arrow key
+ if (evnt.shiftKey) {
+ this.prevNavWidthBtn.click();
+ } else {
+ this.#adjustDate(-1, v, true);
+ }
+ handled = true;
+ break;
+ case 38: //up arrow key
+ if (evnt.shiftKey) {
+ this.caption.click();
+ } else {
+ this.#adjustDate(-updown, v, true);
+ }
+ handled = true;
+ break;
+ case 39: //right arrow key
+ if (evnt.shiftKey) {
+ this.nextNavWidthBtn.click();
+ } else {
+ this.#adjustDate(+1, v, true);
+ }
+ handled = true;
+ break;
+ case 40: //down arrow key
+ this.#adjustDate(updown, v, true);
+ handled = true;
+ break;
+ default:
+ }
+ }
+ if (handled) {
+ evnt.preventDefault();
+ }
+ }
+
+ /*
+ * show the datepicker. //TODO: migrate this to vanilla js
+ */
+ #show() {
+ this.#options.locale = this.#curInstance.locale;
+ if (!DatePickerWidget.#visible) {
+ let date = new Date(),
+ validDate,
+ parsedDate;
+ if (this.#curInstance.selectedDate) {
+ validDate = this.#curInstance.selectedDate;
+ } else if (this.#model.valid) {
+ validDate = this.#model.value;
+ }
+ if (validDate) {
+ parsedDate = validDate ? new Date(validDate) : null;
+ date = (parsedDate == null || parsedDate == 'Invalid Date') ? date : parsedDate;
+ }
+ const timezoneOffset = date.getTimezoneOffset();
+ date.setMinutes(date.getMinutes() + timezoneOffset);
+ this.selectedDay = this.currentDay = date.getDate();
+ this.selectedMonth = this.currentMonth = date.getMonth();
+ this.selectedYear = this.currentYear = date.getFullYear();
+ this.maxValidDate = this.#options.maximum ? new Date(
+ this.#curInstance.maxValidDate) : null;
+ this.minValidDate = this.#options.minimum ? new Date(
+ this.#curInstance.minValidDate) : null;
+ this.exclMaxDate = this.#curInstance.exclMaxDate;
+ this.exclMinDate = this.#curInstance.exclMinDate;
+ this.#dp.getElementsByClassName("dp-clear")[0].getElementsByTagName(
+ "a")[0].innerText = this.#options.locale.clearText;
+ this.#layout(this.#defaultView);
+ this.#dp.style.display = "flex";
+ this.#focusedOnLi = false;
+ DatePickerWidget.#visible = true;
+ this.#position();
+ // Add document touch listener for mobile to close datepicker when tapping outside
+ if (this.#touchSupported && !this.#documentTouchListener) {
+ this.#documentTouchListener = (evt) => {
+ if (DatePickerWidget.#visible && this.#curInstance) {
+ this.#checkWindowClicked(evt);
+ }
+ };
+ document.addEventListener("touchstart", this.#documentTouchListener, false);
+ }
+ if (this.#options.showCalendarIcon) {
+ this.#curInstance.$field.setAttribute('readonly', true); // when the datepicker is active, deactivate the field
+ }
+ }
+
+ // Disabling the focus on ipad due to a bug where value of
+ // date picker is not being set
+ // Removing this code will only hamper one use case
+ // where on ipad if you click on the calander then
+ // the field becomes read only so
+ // there is no indication where the current focus is
+ // And if you remove this foucs code all together
+ // then what happens is that on desktop MF in iframe the exit event
+ // is not getting called hence calander getting remained open even
+ // when you click somewhere on window or focus into some other field
+ if (this.#options.showCalendarIcon && !this.#touchSupported) {
+ this.#curInstance.$field.focus(); // field loses focus after being marked readonly, causing blur event not to be fired later
+ }
+ }
+
+ /*
+ * position the datepicker around the positioning element //TODO: migrate this to vanilla js
+ * provided in the options
+ */
+ #position() {
+ let $elem = this.#curInstance.positioning,
+ windowScrollX = window.scrollX,
+ windowScrollY = window.scrollY,
+ windowInnerHeight = window.innerHeight,
+ windowInnerWidth = window.innerWidth,
+ inputRect = $elem.getBoundingClientRect(),
+ height = $elem.offsetHeight,
+ top = this.#getOffset($elem).top + height,
+ left = this.#getOffset($elem).left,
+ styles = {"top": (top + "px"), "left": (left + "px")},
+ diffBottom = top + this.#dp.offsetHeight - windowInnerHeight - windowScrollY, //this.#dp.offsetHeight is the widget's height
+ newLeft,
+ newTop;
+ if (diffBottom > 0) {
+ //can't appear at the bottom
+ //check top
+ newTop = top - height - this.#dp.offsetHeight - 20;
+ if (newTop < windowScrollY) {
+ //can't appear at the top as well ... the datePicker pop up overlaps the date field
+ newTop = top - diffBottom;
+ }
+ styles.top = newTop + "px";
+ }
+ if (left + this.#dp.offsetWidth > windowScrollX + windowInnerWidth) {
+ //align with the right edge
+ newLeft = windowScrollX + windowInnerWidth - this.#dp.offsetWidth - 20;
+ styles.left = newLeft + "px";
+ }
+ this.#dp.style.top = styles.top;
+ this.#dp.style.left = styles.left;
+ const localeObj = new Intl.Locale(this.#lang);
+ if(localeObj?.textInfo?.direction == "rtl") {
+ let right = windowInnerWidth - (left + inputRect.width); // Calculate right offset
+ if (right + this.#dp.offsetWidth > windowInnerWidth) { // this.#dp.offsetWidth is the widget's width
+ // Align with the right edge of the viewport
+ right = windowScrollX + windowInnerWidth - this.#dp.offsetWidth - 20; // Adjust with a 20px margin
+ }
+ this.#dp.style.right = right + "px";
+ this.#dp.style.left = "unset"
+ }
+ return this;
+ }
+
+ #getOffset(elem) {
+ let box = elem.getBoundingClientRect();
+ return {
+ top: box.top + window.scrollY,
+ left: box.left + window.scrollX
+ };
+ }
+
+ /*
+ * layout the nextView. if nextView is null return
+ *
+ */
+ #layout(nextView) {
+ if (nextView == null) {
+ this.#hide();
+ } else {
+ if (this.view) {
+ this['$' + this.view.toLowerCase()].style.display = "none";
+ }
+ this.view = nextView;
+ this.caption.classList.toggle("disabled",
+ !this.#viewAction[this.view].caption);
+ let ariaLabel = "";
+ if (this.view === "Month") {
+ ariaLabel = this.#options.locale.months[this.currentMonth] + " " + this.currentYear;
+ this.prevNavWidthBtn.setAttribute(
+ "aria-label",
+ this.#options.locale.previousMonth || "Previous month"
+ );
+ this.nextNavWidthBtn.setAttribute(
+ "aria-label",
+ this.#options.locale.nextMonth || "Next month"
+ );
+ } else if (this.view === "Year") {
+ ariaLabel = this.currentYear;
+ this.prevNavWidthBtn.setAttribute(
+ "aria-label",
+ this.#options.locale.previousYear || "Previous year"
+ );
+ this.nextNavWidthBtn.setAttribute(
+ "aria-label",
+ this.#options.locale.nextYear || "Next year"
+ );
+ } else if (this.view === "Yearset") {
+ const startYear = this.currentYear - this.#options.yearsPerView / 2;
+ const endYear = startYear + this.#options.yearsPerView - 1;
+ ariaLabel = startYear + "-" + endYear;
+ this.prevNavWidthBtn.setAttribute(
+ "aria-label",
+ this.#options.locale.previousSetOfYears || "Previous set of years"
+ );
+ this.nextNavWidthBtn.setAttribute(
+ "aria-label",
+ this.#options.locale.nextSetOfYears || "Next set of years"
+ );
+ }
+ this['$' + this.view.toLowerCase()].style.display = "block";
+ this["show" + this.view]();
+ }
+ return this;
+ }
+
+ /*
+ * show the month view
+ */
+ showMonth() {
+ var self = this,
+ curDate = new Date(this.currentYear, this.currentMonth),
+ maxDay = this.#maxDate(this.currentMonth),
+ prevMaxDay = this.#maxDate((this.currentMonth + 11) % 12),
+ day1 = new Date(this.currentYear, this.currentMonth, 1).getDay(),
+ data, display;
+ var localizedYear = this.#getLocalizedYear(curDate);
+
+ this.#tabulateView(
+ {
+ caption: this.#options.locale.months[this.currentMonth] + ", "
+ + this.#convertNumberToLocale(localizedYear),
+ header: this.#options.locale.days,
+ numRows: 7,
+ numColumns: 7,
+ elementAt: function (row, col) {
+ var day = (row - 1) * 7 + col - day1 + 1;
+ var monthVal = self.currentMonth + 1;
+ let gridId = "day-" + day;
+ display = self.#convertNumberToLocale(day);
+ data = day;
+ if (day < 1) {
+ display = self.#convertNumberToLocale(prevMaxDay + day);
+ data = -1;
+ monthVal = self.currentMonth;
+ } else if (day > maxDay) {
+ display = self.#convertNumberToLocale(day - maxDay);
+ data = -1;
+ monthVal = self.currentMonth + 2;
+ } else {
+ curDate.setDate(day);
+ // check if the currentdate is valid based on max and min valid date
+ if (self.#compareVal(curDate, self.maxValidDate,
+ self.exclMaxDate) || self.#compareVal(self.minValidDate,
+ curDate, self.exclMinDate)) {
+ data = -1;
+ }
+ }
+ return {
+ data: data,
+ gridId: gridId,
+ display: display,
+ ariaLabel: self.#options.editValue(
+ self.currentYear + "-" + self.#pad2(monthVal)
+ + "-" + self.#pad2(display))
+ };
+ }
+ });
+ }
+
+ /**
+ * returns boolean based on val1, val2, checkEqual
+ * For comparing date, use date object
+ */
+ #compareVal(val1, val2, checkEqual) {
+ if (!val1 || !val2) {
+ return false;
+ }
+
+ if (checkEqual) {
+ return val1 >= val2;
+ } else {
+ return val1 > val2;
+ }
+ }
+
+ /*
+ * show the year view
+ */
+ showYear() {
+ var self = this,
+ minDate = this.minValidDate ? new Date(
+ this.minValidDate.getFullYear(), this.minValidDate.getMonth())
+ : null,
+ maxDate = this.maxValidDate ? new Date(
+ this.maxValidDate.getFullYear(), this.maxValidDate.getMonth())
+ : null,
+ curDate = new Date(this.currentYear, 0), //can't omit month, if only one param present it is treated as millisecond
+ data,
+ month;
+ var localizedYear = this.#getLocalizedYear(curDate);
+ this.#tabulateView(
+ {
+ caption: this.#convertNumberToLocale(localizedYear),
+ numRows: 4,
+ numColumns: 3,
+ elementAt: function (row, col) {
+ data = month = row * 3 + col;
+ let gridId = "month-" + data;
+ curDate.setMonth(month);
+ if ((minDate && curDate < minDate) || (maxDate && curDate
+ > maxDate)) {
+ data = -1;
+ }
+ return {
+ data: data,
+ gridId: gridId,
+ display: self.#options.locale.months[month],
+ ariaLabel: self.#options.locale.months[month] + " "
+ + self.currentYear
+ };
+ }
+ });
+ }
+
+ #getLocalizedYear(date) {
+ /*
+ // Only Thai language return year according to Buddhist calendar, rest all languages follows gregorian calendar in practice.
+ // The Buddhist Year in 2024 = 543 + 2024 = B.E. 2567 (reference https://wesak.org.my/calculating-b-e/)
+ // Intl.DateTimeFormat#formatToParts returns number (2024) for all languages except for Thai (2567) & for Persian ('۱۴۰۳')
+ // For Persian ('۱۴۰۳'), returned year is not in numbers that is breaking next flow.
+ */
+ if(this.#lang === 'th') {
+ const dateFormat = new Intl.DateTimeFormat(this.#lang, {
+ year: 'numeric'
+ });
+ const dateParts = dateFormat.formatToParts(date);
+ const yearObject = dateParts.find(yearObject => yearObject.type === "year");
+ const localizedYear = yearObject.value;
+ return Number(localizedYear);
+ }
+ else {
+ return Number(date.getFullYear());
+ }
+ }
+
+ /*
+ * show the year set view
+ */
+ showYearset() {
+ var year,
+ minDate = this.minValidDate ? new Date(
+ this.minValidDate.getFullYear(), 0) : null,
+ maxDate = this.maxValidDate ? new Date(
+ this.maxValidDate.getFullYear() + 1, 0) : null,
+ curDate = new Date(),
+ data,
+ self = this;
+ var localizedYear = this.#getLocalizedYear(new Date(this.currentYear, 0)),
+ localizedYearSet = this.#getLocalizedYear(new Date(self.currentYear, 0));
+
+ this.#tabulateView(
+ {
+ caption: this.#convertNumberToLocale(
+ localizedYear - this.#options.yearsPerView / 2) + "-"
+ + this.#convertNumberToLocale(
+ localizedYear - this.#options.yearsPerView / 2
+ + this.#options.yearsPerView - 1),
+ numRows: 4,
+ numColumns: 4,
+ elementAt: function (row, col) {
+ data = year = localizedYearSet - 8 + (row * 4 + col);
+ let gridId = "year-" + data;
+ curDate.setFullYear(year);
+ if ((minDate && curDate < minDate) || (maxDate && curDate
+ > maxDate)) {
+ data = -1;
+ }
+ return {
+ "data": data,
+ "gridId": gridId,
+ "display": self.#convertNumberToLocale(year),
+ ariaLabel: year
+ };
+ }
+ });
+ }
+
+ insertRow(rowNum, rowArray, isHeader, elementAt) {
+ let $view = this["$" + this.view.toLowerCase()],
+ $row = $view.getElementsByTagName("ul")[rowNum],
+ items, $li, element, $tmp,
+ self = this;
+ if (!$row) {
+ let rowEl = document.createElement("ul");
+ rowEl.removeAttribute("aria-label");
+ rowEl.classList.toggle("header", isHeader);
+ rowEl.setAttribute("role", "row");
+ $row = $view.appendChild(rowEl);
+ }
+ items = $row.getElementsByTagName("li").length;
+ while (items++ < rowArray.length) {
+ let listItemEl = document.createElement("li");
+ listItemEl.id = "li-" + elementAt(rowNum, items).gridId;
+ listItemEl.setAttribute("role", isHeader ? "columnheader" : "gridcell");
+ $tmp = $row.appendChild(listItemEl);
+ if (!isHeader) {
+ let cb = function (evnt) {
+ self.#selectDate(evnt);
+ };
+ $tmp.addEventListener("click", cb);
+ }
+ }
+
+ rowArray.forEach(function (el, index) {
+ $li = $row.getElementsByTagName("li")[index];
+ if (isHeader) {
+ $li.innerText = rowArray[index];
+ } else {
+ element = rowArray[index];
+ window.afCache.put($li, "value", element.data);
+ if (self.#checkDateIsFocussed(element.data)) {
+ if (self.$focusedDate) {
+ self.$focusedDate.classList.remove("dp-focus");
+ self.$focusedDate.setAttribute("tabindex", "-1");
+ }
+ self.$focusedDate = $li;
+ if (self.#keysEnabled) {
+ self.$focusedDate.classList.add("dp-focus")
+ }
+ }
+ $li.setAttribute("title", element.ariaLabel);
+ $li.setAttribute("aria-label", element.ariaLabel);
+ $li.setAttribute("tabindex", -1);
+ $li.classList.toggle("dp-selected",
+ self.#checkDateIsSelected(element.data));
+ $li.classList.toggle("disabled", element.data === -1);
+ $li.innerText = element.display;
+ }
+ });
+ return $row;
+ }
+
+ /*
+ * creates a tabular view based on the options provided. The options that can be passed are
+ * numRows: number of rows that needs rendering
+ * numCols: number of columns that needs rendering
+ * caption: text for the datepickers caption element
+ * header: an array of elements that identifies the header row
+ * elementAt: a function(row, column) that returns an object (data: , display: ) where
+ * is the value to set for that view when the element at (row,column) is clicked and
+ * is the value that will be visible to the user
+ */
+ #tabulateView(options) {
+ var r = 0, rows = 0,
+ row = [],
+ c;
+ this.caption.innerHTML = options.caption;
+ if (options.header) {
+ this.insertRow(r++, options.header, true, options.elementAt);
+ }
+ while (r < options.numRows) {
+ c = 0;
+ while (c < options.numColumns) {
+ row[c] = options.elementAt(r, c);
+ c++;
+ }
+ this.insertRow(r++, row, false, options.elementAt);
+ }
+ }
+
+ #hotKeysCallBack = this.#hotKeys.bind(this);
+
+ #activateField(evnt) {
+ let self = this;
+ this.#curInstance = window.afCache.get(evnt.target, "datetimepicker");
+ // Issue LC-7049:
+ // datepickerTarget should be added when activate the field and should be removed
+ // after the fields gets deactivated.
+ if (this.#options.showCalendarIcon) {
+ this.#curInstance.$field.parentNode.classList.add("datePickerTarget");
+ }
+ //enable hot keys only for non touch devices
+ if (!this.#touchSupported && !this.#keysEnabled) {
+ document.addEventListener("keydown", self.#hotKeysCallBack);
+ this.#keysEnabled = true;
+ }
+ }
+
+ #deactivateField() {
+ let self = this;
+ if (self.#curInstance) {
+ if (this.#keysEnabled) {
+ document.removeEventListener("keydown", self.#hotKeysCallBack);
+ this.#keysEnabled = false;
+ }
+ // Remove document touch listener if it exists
+ if (this.#documentTouchListener) {
+ document.removeEventListener("touchstart", this.#documentTouchListener);
+ this.#documentTouchListener = null;
+ }
+ // Issue LC-7049:
+ // datepickerTarget should be added when activate the field and should be removed
+ // after the fields gets deactivated. Otherwise clicking on any other datefield
+ // will not hide the existing datepicker
+ if (this.#options.showCalendarIcon) {
+ self.#curInstance.$field.parentNode.classList.remove(
+ "datePickerTarget");
+ }
+ self.#curInstance = null;
+ this.#hideAllViews();
+ }
+ }
+
+ #hide() {
+ if (DatePickerWidget.#visible) {
+ this.#dp.style.display = "none";
+ this.#hideAllViews();
+ DatePickerWidget.#visible = false;
+ if (this.#options.showCalendarIcon) {
+ this.#curInstance.$field.removeAttribute('readonly'); // when the datepicker is deactivated, activate the field
+ }
+ }
+ }
+
+ #hideAllViews() {
+ this['$month'].replaceChildren();
+ this['$year'].replaceChildren();
+ this['$yearset'].replaceChildren();
+ this['$month'].style.display = "none";
+ this['$year'].style.display = "none";
+ this['$yearset'].style.display = "none";
+ }
+
+ #adjustDate(step, view, focus) {
+ let maxDate, prevMaxDate;
+ let _focus = focus || false;
+ switch (view.toLowerCase()) {
+ case "day":
+ this.currentDay += step;
+ maxDate = this.#maxDate(this.currentMonth)
+ if (this.currentDay < 1) {
+ prevMaxDate = this.#maxDate((this.currentMonth - 1 + 12) % 12);
+ this.currentDay = prevMaxDate + this.currentDay;
+ return this.#adjustDate(-1, "month", _focus);
+ }
+ if (this.currentDay > maxDate) {
+ this.currentDay -= maxDate;
+ return this.#adjustDate(+1, "month", _focus);
+ }
+ break;
+ case "month":
+ this.currentMonth += step;
+ if (this.currentMonth > 11) {
+ this.currentYear++;
+ this.currentMonth = 0;
+ }
+ if (this.currentMonth < 0) {
+ this.currentYear--;
+ this.currentMonth = 11;
+ }
+ break;
+ case "year":
+ this.currentYear += step;
+ break;
+ case "yearset":
+ this.currentYear += step * this.#options.yearsPerView;
+ break;
+ }
+ this.#layout(this.view);
+ if (_focus) {
+ this.#focusedOnLi = true;
+ this.$focusedDate.setAttribute("tabindex", 0);
+ this.$focusedDate.focus();
+ }
+ }
+
+ #checkDateIsSelected(data) {
+ switch (this.view.toLowerCase()) {
+ case "month":
+ return this.currentYear === this.selectedYear && this.currentMonth
+ === this.selectedMonth && data === this.selectedDay;
+ case "year":
+ return this.currentYear === this.selectedYear && this.selectedMonth
+ === data;
+ case "yearset":
+ return this.selectedYear === data;
+ }
+ }
+
+ #checkDateIsFocussed(data) {
+ switch (this.view.toLowerCase()) {
+ case "month":
+ return data === this.currentDay;
+ case "year":
+ return this.currentMonth === data;
+ case "yearset":
+ return this.currentYear === data;
+ }
+ }
+
+ #convertNumberToLocale(number) {
+ const zeroCode = this.#options.locale.zero.charCodeAt(0);
+ number += "";
+ let newNumber = [];
+ for (let i = 0; i < number.length; i++) {
+ newNumber.push(
+ String.fromCharCode(zeroCode + parseInt(number.charAt(i))));
+ }
+ return newNumber.join("");
+ }
+
+ #clearDate(view) {
+ this.#model.dispatch(new FormView.Actions.UIChange({'value': ''}));
+ let existingSelectedItem = this['$' + view.toLowerCase()].getElementsByClassName("dp-selected")[0];
+ if (existingSelectedItem) {
+ existingSelectedItem.classList.remove("dp-selected");
+ }
+ }
+
+ #getEvent() {
+ return "click";//this.#touchSupported ? "touchstart" : "click";
+ }
+
+ #pad2(m) {
+ return m = m < 10 ? "0" + m : m;
+ }
+
+ toString() {
+ if(this.selectedYear === -1 || this.selectedMonth === -1 || this.selectedDay === -1){
+ return "";
+ }
+ const formattedDate = `${this.selectedYear}-${this.#pad2(this.selectedMonth + 1)}-${this.#pad2(this.selectedDay)}`;
+ return formattedDate;
+ }
+
+ #selectDate(evnt) {
+ let val = window.afCache.get(evnt.target, "value"),
+ nextView = this.#viewAction[this.view].li;
+
+ //disabled dates have a value of -1. Do nothing in that case
+ if (val === -1) {
+ return;
+ }
+ switch (this.view.toLowerCase()) {
+ case "month":
+ this.selectedMonth = this.currentMonth;
+ this.selectedYear = this.currentYear;
+ this.selectedDay = val;
+ this.setValue(this.toString());
+ this.#model.dispatch(new FormView.Actions.UIChange({'value': this.toString()})); // updating model value to the latest when calender is changed
+ this.#curInstance.$field.focus();
+ let existingSelectedElement = this['$'
+ + this.view.toLowerCase()].getElementsByClassName("dp-selected")[0];
+ if (existingSelectedElement) {
+ existingSelectedElement.classList.remove("dp-selected");
+ }
+ evnt.target.classList.add("dp-selected");
+ break;
+ case "year":
+ this.currentMonth = val;
+ break;
+ case "yearset":
+ this.currentYear = val;
+ break;
+ }
+ this.#layout(nextView);
+ //manually focus on the field if clicked on the popup buttons for non-touch device
+ if (!this.#touchSupported) {
+ //No need to focus if selection is made by pressing space.
+ if (!this.hotKeyPressed) {
+ this.#scriptFocus = true;
+ }
+ } else if (nextView == null) {
+ //For touch devices, deactivate the field if a selection is made
+ this.#deactivateField()
+ }
+ }
+
+ #leapYear() {
+ return this.currentYear % 400 === 0 || (this.currentYear % 100 !== 0
+ && this.currentYear % 4 === 0);
+ }
+
+ #maxDate(m) {
+ if (this.#leapYear() && m === 1) {
+ return 29;
+ } else {
+ return this.#dates[m];
+ }
+ }
+
+ #localizeDateElements(defaultOptions, locale) {
+ var calendarSymbols = FormView.LanguageUtils.getTranslatedString(locale, "calendarSymbols");
+ if (calendarSymbols && calendarSymbols.abbrdayNames) {
+ defaultOptions.locale.days = calendarSymbols.abbrdayNames;
+ }
+ if (calendarSymbols && calendarSymbols.abbrdayNames) {
+ defaultOptions.locale.months = calendarSymbols.monthNames;
+ }
+ var clearText = FormView.LanguageUtils.getTranslatedString(locale, "clearText");
+ if (clearText) {
+ defaultOptions.locale.clearText = clearText;
+ }
+ var openCalendarText = FormView.LanguageUtils.getTranslatedString(locale, "openCalendarText");
+ if (openCalendarText) {
+ defaultOptions.locale.openCalendarText = openCalendarText;
+ }
+ var zero = FormView.LanguageUtils.getTranslatedString(locale, "0");
+ if (zero) {
+ defaultOptions.locale.zero = zero;
+ }
+ var previousMonth = FormView.LanguageUtils.getTranslatedString(locale, "previousMonth");
+ if (previousMonth) {
+ defaultOptions.locale.previousMonth = previousMonth;
+ }
+ var nextMonth = FormView.LanguageUtils.getTranslatedString(locale, "nextMonth");
+ if (nextMonth) {
+ defaultOptions.locale.nextMonth = nextMonth;
+ }
+ var previousYear = FormView.LanguageUtils.getTranslatedString(locale, "previousYear");
+ if (previousYear) {
+ defaultOptions.locale.previousYear = previousYear;
+ }
+ var nextYear = FormView.LanguageUtils.getTranslatedString(locale, "nextYear");
+ if (nextYear) {
+ defaultOptions.locale.nextYear = nextYear;
+ }
+ var previousSetOfYears = FormView.LanguageUtils.getTranslatedString(locale, "previousSetOfYears");
+ if (previousSetOfYears) {
+ defaultOptions.locale.previousSetOfYears = previousSetOfYears;
+ }
+ var nextSetOfYears = FormView.LanguageUtils.getTranslatedString(locale, "nextSetOfYears");
+ if (nextSetOfYears) {
+ defaultOptions.locale.nextSetOfYears = nextSetOfYears;
+ }
+ }
+
+ #isEditValueOrDisplayValue(value) {
+ return (this.#model.editValue === value || this.#model.displayValue
+ === value);
+ }
+
+ /**
+ * returns the original value of format YYYY-MM-DD
+ * @returns {*|string}
+ */
+ getValue() {
+ if (this.#isEditValueOrDisplayValue(this.#widget.value)) {
+ return this.#model.value; // if the widget has edit/display value thiss method should return model value
+ }
+ return this.#widget.value;
+ }
+
+ /**
+ * Sets the formatted display value on the widget.
+ * value should be in YYYY-MM-DD format
+ * @param value
+ */
+ setDisplayValue(value) {
+ if (this.#curInstance != null) {
+ this.#curInstance.$field.value = this.#curInstance.displayValue(
+ this.toString()) || value;
+ } else {
+ this.#widget.value = this.#model.displayValue || value;
+ }
+ }
+
+ setCalendarWidgetValue(value) {
+ if (this.#curInstance === null && this.#widget != null) {
+ this.#curInstance = window.afCache.get(this.#widget, "datetimepicker");
+ }
+ if (this.#curInstance != null) {
+ // also change the date of the calendar widget
+ this.#curInstance.selectedDate = value;
+ }
+ }
+
+ /**
+ * Sets the formatted edit value on the widget
+ * @param value
+ */
+
+ setValue(value) {
+ let currDate;
+ if (value instanceof Date && !isNaN(value)) {
+ // If value is already a Date object and it's valid, use it as is
+ currDate = value;
+ } else {
+ // Check if value is empty
+ if (!value || value.trim() === '') {
+ currDate = new Date();
+ } else {
+ let displayFormat = this.#model._jsonModel?.displayFormat;
+ // If displayFormat is null/undefined, use default parsing
+ if (!displayFormat) {
+ currDate = new Date(value);
+ } else {
+ // Use FormView.Formatters.parseDate for custom formats
+ currDate = FormView.Formatters.parseDate(value, this.#lang || 'en', displayFormat);
+ // If parseDate failed (returned null), fallback to default parsing
+ if (currDate === null) {
+ currDate = new Date(value);
+ }
+ const timezoneOffset = currDate.getTimezoneOffset();
+ currDate.setMinutes(currDate.getMinutes() + timezoneOffset);
+ }
+ }
+ }
+
+ if (!isNaN(currDate) && value != null) {
+ //in case the value is directly updated from the field without using calendar widget
+ this.selectedMonth = currDate.getMonth();
+ this.selectedYear = currDate.getFullYear();
+ this.selectedDay = currDate.getDate();
+ } else {
+ this.selectedYear
+ = this.selectedMonth
+ = this.selectedDay
+ = -1;
+ }
+ if (this.#curInstance != null) {
+ if (!this.#isEditValueOrDisplayValue(value)) {
+ this.#curInstance.selectedDate = value; // prevent edit/display value from getting set in calender
+ }
+ this.#curInstance.$field.value = this.#curInstance.editValue() || value;
+ } else {
+ this.#widget.value = value;
+ }
+ }
+
+ addEventListener(event, handler, widget) {
+ let inst = window.afCache.get(widget, "datetimepicker");
+ inst.$field.addEventListener(event, function (e) {
+ switch (e.type) {
+ case 'blur':
+ if (!DatePickerWidget.#visible) {
+ handler(e);
+ }
+ break;
+ case 'focus':
+ handler(e);
+ break;
+ case 'input':
+ handler(e);
+ break;
+
+ }
+ });
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/datepicker.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/datepicker.html
new file mode 100644
index 0000000000..6cff5a1b85
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/datepicker.html
@@ -0,0 +1,59 @@
+
+
+
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/datepicker.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/datepicker.js
new file mode 100644
index 0000000000..953cc77046
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v2/datepicker/datepicker.js
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright 2025 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
+ }
+});
\ No newline at end of file
diff --git a/ui.tests/test-module/specs/datepicker/datepickerv2.runtime.cy.js b/ui.tests/test-module/specs/datepicker/datepickerv2.runtime.cy.js
new file mode 100644
index 0000000000..454d29a8fd
--- /dev/null
+++ b/ui.tests/test-module/specs/datepicker/datepickerv2.runtime.cy.js
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright 2025 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 Date Picker v2", () => {
+
+ const pagePath = "content/forms/af/core-components-it/samples/datepicker/basic.html";
+ const bemBlock = "cmp-adaptiveform-datepicker";
+ let formContainer = null;
+
+ beforeEach(() => {
+ cy.previewForm(pagePath).then(p => {
+ formContainer = p;
+ });
+ });
+
+ const openFirstDatepickerCalendar = () => {
+ cy.get(`.${bemBlock}__calendar-icon`).first().as("calendarIcon");
+ cy.get("@calendarIcon").should("exist").click({ force: true });
+ cy.get(".datetimepicker").should("be.visible");
+ cy.get(".dp-monthview").should("exist");
+ };
+
+ it("should open calendar with keyboard on calendar icon (Space/Enter)", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get(`.${bemBlock}__calendar-icon`).first().as("calendarIcon");
+ cy.get("@calendarIcon").should("exist").focus();
+
+ cy.get("@calendarIcon").type(" ", { force: true });
+ cy.get(".datetimepicker").should("be.visible");
+ cy.get(".dp-monthview").should("exist");
+
+ cy.get("body").click(0, 0, { force: true });
+ cy.get(".datetimepicker").should("not.be.visible");
+
+ cy.get("@calendarIcon").focus().type("{enter}", { force: true });
+ cy.get(".datetimepicker").should("be.visible");
+ cy.get(".dp-monthview").should("exist");
+ });
+});
+
+