diff --git a/packages/main/cypress/specs/DynamicDateRange.cy.tsx b/packages/main/cypress/specs/DynamicDateRange.cy.tsx
index db96ee3700a1..b72075cb11f6 100644
--- a/packages/main/cypress/specs/DynamicDateRange.cy.tsx
+++ b/packages/main/cypress/specs/DynamicDateRange.cy.tsx
@@ -4,13 +4,13 @@ import DateRange from '../../src/dynamic-date-range-options/DateRange.js';
import Today from '../../src/dynamic-date-range-options/Today.js';
import LastOptions from '../../src/dynamic-date-range-options/LastOptions.js';
import NextOptions from '../../src/dynamic-date-range-options/NextOptions.js';
+import DateTimeRange from '../../src/dynamic-date-range-options/DateTimeRange.js';
import FromDateTime from '../../src/dynamic-date-range-options/FromDateTime.js';
import ToDateTime from '../../src/dynamic-date-range-options/ToDateTime.js';
describe('DynamicDateRange Component', () => {
beforeEach(() => {
- cy.mount(
-
+ cy.mount(
);
});
@@ -38,41 +38,16 @@ describe('DynamicDateRange Component', () => {
const mockOptions: Array = [
new Today(),
new SingleDate(),
- new DateRange()
+ new DateRange(),
+ new DateTimeRange()
];
cy.get('[ui5-dynamic-date-range]')
.as("ddr");
cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .should('exist');
-
- cy.get("@input")
- .find('[ui5-icon]')
- .as("icon");
-
- cy.get("@icon")
- .realClick(); // Open the picker
-
- cy.get("@ddr")
- .shadow()
- .find("[ui5-responsive-popover]")
- .as("popover");
-
- cy.get("@popover")
- .should('exist');
-
- cy.get("@popover")
- .find("[ui5-list]")
- .as("list");
-
- cy.get('@list')
- .find("[ui5-li]")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeGetOptionsList()
.as("listItems");
cy.get("@listItems")
@@ -90,34 +65,8 @@ describe('DynamicDateRange Component', () => {
.as("ddr");
cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .should('exist');
-
- cy.get("@input")
- .find('[ui5-icon]')
- .as("icon");
-
- cy.get("@icon")
- .realClick(); // Open the picker
-
- cy.get("@ddr")
- .shadow()
- .find("[ui5-responsive-popover]")
- .as("popover");
-
- cy.get("@popover")
- .should('exist');
-
- cy.get("@popover")
- .find("[ui5-list]")
- .as("list");
-
- cy.get('@list')
- .find("[ui5-li]")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeGetOptionsList()
.as("listItems");
cy.get("@listItems")
@@ -261,13 +210,8 @@ describe('DynamicDateRange Component', () => {
.eq(22) // May 21, 2035 is the 21st day
.realClick();
- cy.get("@popover")
- .find("[ui5-button][design='Emphasized']")
- .as("submitButton");
-
- cy.get("@submitButton")
- .should('exist')
- .realClick();
+ cy.get("@ddr")
+ .ui5DynamicDateRangeSubmit();
cy.get("@input")
.shadow()
@@ -336,8 +280,7 @@ describe('DynamicDateRange Component', () => {
describe('DynamicDateRange Last/Next Options', () => {
beforeEach(() => {
- cy.mount(
-
+ cy.mount(
);
});
@@ -349,36 +292,8 @@ describe('DynamicDateRange Last/Next Options', () => {
.as("ddr");
cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .shadow()
- .find("input")
- .as("innerInput");
-
- cy.get("@input")
- .find('[ui5-icon]')
- .as("icon");
-
- cy.get("@icon")
- .realClick();
-
- cy.get("@ddr")
- .shadow()
- .find("[ui5-responsive-popover]")
- .as("popover");
-
- cy.get("@popover")
- .should('exist');
-
- cy.get("@popover")
- .find("[ui5-list]")
- .as("list");
-
- cy.get("@list")
- .find("[ui5-li]")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeGetOptionsList()
.as("listItems");
cy.get("@listItems")
@@ -411,12 +326,8 @@ describe('DynamicDateRange Last/Next Options', () => {
.clear()
.realType('7');
- cy.get("@popover")
- .find("[ui5-button][design='Emphasized']")
- .as("submitButton");
-
- cy.get("@submitButton")
- .realClick();
+ cy.get("@ddr")
+ .ui5DynamicDateRangeSubmit();
cy.get("@innerInput")
.should('have.value', 'Last 7 Days');
@@ -434,33 +345,8 @@ describe('DynamicDateRange Last/Next Options', () => {
.as("ddr");
cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .shadow()
- .find("input")
- .as("innerInput");
-
- cy.get("@input")
- .find('[ui5-icon]')
- .as("icon");
-
- cy.get("@icon")
- .realClick();
-
- cy.get("@ddr")
- .shadow()
- .find("[ui5-responsive-popover]")
- .as("popover");
-
- cy.get("@popover")
- .find("[ui5-list]")
- .as("list");
-
- cy.get("@list")
- .find("[ui5-li]")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeGetOptionsList()
.as("listItems");
cy.get("@listItems")
@@ -493,12 +379,8 @@ describe('DynamicDateRange Last/Next Options', () => {
.find(".ui5-ddr-current-value")
.should('contain.text', 'Selected:');
- cy.get("@popover")
- .find("[ui5-button][design='Emphasized']")
- .as("submitButton");
-
- cy.get("@submitButton")
- .realClick();
+ cy.get("@ddr")
+ .ui5DynamicDateRangeSubmit();
cy.get("@innerInput")
.should('have.value', 'Next 3 Weeks');
@@ -561,38 +443,16 @@ describe('FromDateTime Option', () => {
const mockOptions: Array = [
new FromDateTime(),
];
- cy.get('[ui5-dynamic-date-range]')
- .as("ddr");
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption();
- cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .should('exist');
-
- cy.get("@input")
- .find('[ui5-icon]')
- .click();
-
cy.get("@ddr")
.shadow()
.find("[ui5-responsive-popover]")
.as("popover");
- cy.get("@popover")
- .should('exist');
-
- cy.get("@popover")
- .find("[ui5-list]")
- .as("list");
-
- cy.get("@list")
- .find("[ui5-li]")
- .contains('From')
- .click();
-
cy.get("@popover")
.find(".ui5-dynamic-date-range-option-datetime-container")
.should('exist');
@@ -610,36 +470,23 @@ describe('FromDateTime Option', () => {
const mockOptions: Array = [
new FromDateTime(),
];
- cy.get('[ui5-dynamic-date-range]')
- .as("ddr");
-
- cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .find('[ui5-icon]')
- .click();
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption();
cy.get("@ddr")
.shadow()
.find("[ui5-responsive-popover]")
.as("popover");
- cy.get("@popover")
- .find("[ui5-list]")
- .find("[ui5-li]")
- .contains('From')
- .click();
-
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Date']")
.should('have.attr', 'selected');
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Time']")
- .click();
+ .realClick();
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Time']")
@@ -651,7 +498,7 @@ describe('FromDateTime Option', () => {
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Date']")
- .click();
+ .realClick();
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Date']")
@@ -662,28 +509,15 @@ describe('FromDateTime Option', () => {
const mockOptions: Array = [
new FromDateTime(),
];
- cy.get('[ui5-dynamic-date-range]')
- .as("ddr");
-
- cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .find('[ui5-icon]')
- .click();
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption();
cy.get("@ddr")
.shadow()
.find("[ui5-responsive-popover]")
.as("popover");
-
- cy.get("@popover")
- .find("[ui5-list]")
- .find("[ui5-li]")
- .contains('From')
- .click();
cy.get("@popover")
.find("[ui5-calendar]")
@@ -695,11 +529,10 @@ describe('FromDateTime Option', () => {
.shadow()
.find(".ui5-dp-daytext")
.eq(15)
- .click(); // Select day 13
+ .realClick(); // Select day 13
- cy.get("@popover")
- .find("[ui5-button][design='Emphasized']")
- .click();
+ cy.get("@ddr")
+ .ui5DynamicDateRangeSubmit();
cy.get("@input")
.shadow()
@@ -723,38 +556,16 @@ describe('ToDateTime Option', () => {
const mockOptions: Array = [
new ToDateTime(),
];
- cy.get('[ui5-dynamic-date-range]')
- .as("ddr");
-
- cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .should('exist');
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption();
- cy.get("@input")
- .find('[ui5-icon]')
- .click();
-
cy.get("@ddr")
.shadow()
.find("[ui5-responsive-popover]")
.as("popover");
- cy.get("@popover")
- .should('exist');
-
- cy.get("@popover")
- .find("[ui5-list]")
- .as("list");
-
- cy.get("@list")
- .find("[ui5-li]")
- .contains('To')
- .click();
-
cy.get("@popover")
.find(".ui5-dynamic-date-range-option-datetime-container")
.should('exist');
@@ -772,36 +583,23 @@ describe('ToDateTime Option', () => {
const mockOptions: Array = [
new ToDateTime(),
];
- cy.get('[ui5-dynamic-date-range]')
- .as("ddr");
-
- cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .find('[ui5-icon]')
- .click();
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption();
cy.get("@ddr")
.shadow()
.find("[ui5-responsive-popover]")
.as("popover");
- cy.get("@popover")
- .find("[ui5-list]")
- .find("[ui5-li]")
- .contains('To')
- .click();
-
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Date']")
.should('have.attr', 'selected');
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Time']")
- .click();
+ .realClick();
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Time']")
@@ -813,7 +611,7 @@ describe('ToDateTime Option', () => {
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Date']")
- .click();
+ .realClick();
cy.get("@popover")
.find("[ui5-segmented-button-item][data-ui5-key='Date']")
@@ -824,28 +622,15 @@ describe('ToDateTime Option', () => {
const mockOptions: Array = [
new ToDateTime(),
];
- cy.get('[ui5-dynamic-date-range]')
- .as("ddr");
-
- cy.get("@ddr")
- .shadow()
- .find('[ui5-input]')
- .as("input");
-
- cy.get("@input")
- .find('[ui5-icon]')
- .click();
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption();
cy.get("@ddr")
.shadow()
.find("[ui5-responsive-popover]")
.as("popover");
-
- cy.get("@popover")
- .find("[ui5-list]")
- .find("[ui5-li]")
- .contains('To')
- .click();
cy.get("@popover")
.find("[ui5-calendar]")
@@ -857,11 +642,10 @@ describe('ToDateTime Option', () => {
.shadow()
.find(".ui5-dp-daytext")
.eq(15)
- .click(); // Select day 13
+ .realClick(); // Select day 13
- cy.get("@popover")
- .find("[ui5-button][design='Emphasized']")
- .click();
+ cy.get("@ddr")
+ .ui5DynamicDateRangeSubmit();
cy.get("@input")
.shadow()
@@ -873,4 +657,101 @@ describe('ToDateTime Option', () => {
.find("input")
.should('contain.value', 'To Oct 13, 2025');
});
+});
+
+describe('DynamicDateRange DateTimeRange Option', () => {
+ beforeEach(() => {
+ cy.mount();
+ });
+
+ it("should select DateTimeRange option and pick 2 dates from the DateTimePickers", () => {
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption()
+ .ui5DynamicDateRangeSetDateTime("from-picker", "Dec 25, 2023, 2:30:00 PM")
+ .ui5DynamicDateRangeSetDateTime("to-picker", "Dec 26, 2023, 4:45:00 AM")
+ .ui5DynamicDateRangeSubmit();
+
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-input]")
+ .as("input");
+
+ cy.get("@input")
+ .should("have.value", "Dec 25, 2023, 2:30:00 PM - Dec 26, 2023, 4:45:00 AM");
+ });
+
+ it("should auto-correct date order when second date is earlier than first", () => {
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption()
+ .ui5DynamicDateRangeSetDateTime("to-picker", "Dec 25, 2023, 2:30:00 PM")
+ .ui5DynamicDateRangeSetDateTime("from-picker", "Dec 26, 2023, 4:45:00 AM")
+ .ui5DynamicDateRangeSubmit();
+
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-input]")
+ .as("input");
+
+ cy.get("@input")
+ .should("have.value", "Dec 25, 2023, 2:30:00 PM - Dec 26, 2023, 4:45:00 AM");
+ });
+
+ it("should parse input value correctly when option is reopened", () => {
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr");
+
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-input]")
+ .as("input");
+
+ cy.get("@input")
+ .shadow()
+ .find("input")
+ .as("innerInput");
+
+ cy.get("@innerInput")
+ .clear()
+ .realType("Jan 4, 2025, 12:00:00 AM - Feb 26, 2025, 11:59:00 PM")
+ .realPress("Enter");
+
+ cy.get("[ui5-dynamic-date-range]")
+ .as("ddr")
+ .ui5DynamicDateRangeOpen()
+ .ui5DynamicDateRangeSelectOption();
+
+ // Verify the parsed dates are displayed in the pickers
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-responsive-popover]")
+ .as("popover");
+
+ cy.get("@popover")
+ .find("[ui5-datetime-picker]#from-picker")
+ .as("fromPicker");
+
+ cy.get("@fromPicker")
+ .shadow()
+ .find("[ui5-datetime-input]")
+ .as("fromInput");
+
+ cy.get("@fromInput")
+ .should("have.value", "Jan 4, 2025, 12:00:00 AM");
+
+ cy.get("@popover")
+ .find("[ui5-datetime-picker]#to-picker")
+ .as("toPicker");
+
+ cy.get("@toPicker")
+ .shadow()
+ .find("[ui5-datetime-input]")
+ .as("toInput");
+
+ cy.get("@toInput")
+ .should("have.value", "Feb 26, 2025, 11:59:00 PM");
+ });
});
\ No newline at end of file
diff --git a/packages/main/cypress/support/commands.ts b/packages/main/cypress/support/commands.ts
index 71ef25b616a4..54ff9f71f1a6 100644
--- a/packages/main/cypress/support/commands.ts
+++ b/packages/main/cypress/support/commands.ts
@@ -47,6 +47,7 @@ import "./commands/ColorPalettePopover.commands.js";
import "./commands/ColorPicker.commands.js";
import "./commands/DateTimePicker.commands.js";
import "./commands/DateRangePicker.commands.js";
+import "./commands/DynamicDateRange.commands.js";
import "./commands/Dialog.commands.ts";
import "./commands/Popover.commands.ts";
import "./commands/ResponsivePopover.commands.js";
diff --git a/packages/main/cypress/support/commands/DynamicDateRange.commands.ts b/packages/main/cypress/support/commands/DynamicDateRange.commands.ts
new file mode 100644
index 000000000000..3bbab0dc9b09
--- /dev/null
+++ b/packages/main/cypress/support/commands/DynamicDateRange.commands.ts
@@ -0,0 +1,135 @@
+Cypress.Commands.add("ui5DynamicDateRangeOpen", { prevSubject: true }, (prevSubject) => {
+ cy.wrap(prevSubject)
+ .as("ddr")
+ .shadow()
+ .find("[ui5-input]")
+ .as("input");
+
+ cy.get("@input")
+ .shadow()
+ .find("input")
+ .as("innerInput");
+
+ cy.get("@input")
+ .find('[ui5-icon]')
+ .as("icon");
+
+ cy.get("@icon")
+ .realClick();
+
+ cy.get("@ddr")
+ .ui5DynamicDateRangeOpened();
+
+ return cy.wrap(prevSubject);
+});
+
+Cypress.Commands.add("ui5DynamicDateRangeOpened", { prevSubject: true }, (subject) => {
+ cy.wrap(subject)
+ .as("ddr");
+
+ cy.get("@ddr")
+ .should("have.attr", "open");
+
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-responsive-popover]")
+ .as("popover")
+ .should("have.attr", "open");
+
+ cy.get("@popover")
+ .find("[ui5-list]")
+ .as("list")
+ .should("be.visible");
+});
+
+Cypress.Commands.add("ui5DynamicDateRangeGetOptionsList", { prevSubject: true }, (subject) => {
+ cy.wrap(subject)
+ .as("ddr");
+
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-responsive-popover]")
+ .as("popover");
+
+ cy.get("@popover")
+ .should('exist');
+
+ cy.get("@popover")
+ .find("[ui5-list]")
+ .as("list");
+
+ return cy.get('@list')
+ .find("[ui5-li]");
+});
+
+Cypress.Commands.add("ui5DynamicDateRangeSelectOption", { prevSubject: true }, (prevSubject, index?: number) => {
+ const optionIndex = index ?? 0;
+
+ cy.wrap(prevSubject)
+ .as("ddr");
+
+ cy.get("@ddr")
+ .ui5DynamicDateRangeGetOptionsList()
+ .eq(optionIndex)
+ .realClick();
+
+ return cy.wrap(prevSubject);
+});
+
+Cypress.Commands.add("ui5DynamicDateRangeSetDateTime", { prevSubject: true }, (prevSubject, pickerId: string, dateTimeValue: string) => {
+ cy.wrap(prevSubject)
+ .as("ddr");
+
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-responsive-popover]")
+ .find(`[ui5-datetime-picker]#${pickerId}`)
+ .as("picker");
+
+ cy.get("@picker")
+ .shadow()
+ .find("[ui5-datetime-input]")
+ .as("input");
+
+ cy.get("@input")
+ .shadow()
+ .find("input")
+ .as("innerInput");
+
+ cy.get("@innerInput")
+ .clear()
+ .realType(dateTimeValue)
+ .realPress("Enter");
+
+ return cy.wrap(prevSubject);
+});
+
+Cypress.Commands.add("ui5DynamicDateRangeSubmit", { prevSubject: true }, (prevSubject) => {
+ cy.wrap(prevSubject)
+ .as("ddr");
+
+ cy.get("@ddr")
+ .shadow()
+ .find("[ui5-responsive-popover]")
+ .as("popover");
+
+ cy.get("@popover")
+ .find("[ui5-button][design='Emphasized']")
+ .as("submitButton");
+
+ cy.get("@submitButton")
+ .realClick();
+});
+
+declare global {
+ namespace Cypress {
+ interface Chainable {
+ ui5DynamicDateRangeOpen(): Chainable>
+ ui5DynamicDateRangeOpened(): Chainable
+ ui5DynamicDateRangeGetOptionsList(): Chainable>
+ ui5DynamicDateRangeSelectOption(index?: number): Chainable>
+ ui5DynamicDateRangeSetDateTime(pickerId: string, dateTimeValue: string): Chainable>
+ ui5DynamicDateRangeSubmit(): Chainable
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/main/src/DynamicDateRange.ts b/packages/main/src/DynamicDateRange.ts
index d3dab554806b..52bcec26dbaa 100644
--- a/packages/main/src/DynamicDateRange.ts
+++ b/packages/main/src/DynamicDateRange.ts
@@ -106,6 +106,7 @@ interface IDynamicDateRangeOption {
* - "TOMORROW" - Represents the next date. An example value is `{ operator: "TOMORROW"}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/Tomorrow.js";`
* - "DATE" - Represents a single date. An example value is `{ operator: "DATE", values: [new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/SingleDate.js";`
* - "DATERANGE" - Represents a range of dates. An example value is `{ operator: "DATERANGE", values: [new Date(), new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/DateRange.js";`
+ * - "DATETIMERANGE" - Represents a range of dates with times. An example value is `{ operator: "DATETIMERANGE", values: [new Date(), new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/DateTimeRange.js";`
* - "FROMDATETIME" - Represents a range from date and time. An example value is `{ operator: "FROMDATETIME", values: [new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/FromDateTime.js";`
* - "TODATETIME" - Represents a range to date and time. An example value is `{ operator: "TODATETIME", values: [new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/ToDateTime.js";`
* - "LASTDAYS" - Represents Last X Days from today. An example value is `{ operator: "LASTDAYS", values: [2]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/LastOptions.js";`
diff --git a/packages/main/src/bundle.esm.ts b/packages/main/src/bundle.esm.ts
index 376dad86a029..77c873528352 100644
--- a/packages/main/src/bundle.esm.ts
+++ b/packages/main/src/bundle.esm.ts
@@ -56,6 +56,7 @@ import Yesterday from "./dynamic-date-range-options/Yesterday.js";
import Tomorrow from "./dynamic-date-range-options/Tomorrow.js";
import SingleDate from "./dynamic-date-range-options/SingleDate.js";
import DateRange from "./dynamic-date-range-options/DateRange.js";
+import DateTimeRange from "./dynamic-date-range-options/DateTimeRange.js";
import FromDateTime from "./dynamic-date-range-options/FromDateTime.js";
import ToDateTime from "./dynamic-date-range-options/ToDateTime.js";
import ExpandableText from "./ExpandableText.js";
diff --git a/packages/main/src/dynamic-date-range-options/DateTimeRange.ts b/packages/main/src/dynamic-date-range-options/DateTimeRange.ts
new file mode 100644
index 000000000000..d358ea54a6b6
--- /dev/null
+++ b/packages/main/src/dynamic-date-range-options/DateTimeRange.ts
@@ -0,0 +1,112 @@
+import DateTimeRangeTemplate from "./DateTimeRangeTemplate.js";
+import type { DynamicDateRangeValue, IDynamicDateRangeOption } from "../DynamicDateRange.js";
+import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js";
+import type { JsxTemplate } from "@ui5/webcomponents-base/dist/index.js";
+import {
+ DYNAMIC_DATE_TIME_RANGE_TEXT,
+} from "../generated/i18n/i18n-defaults.js";
+import { dateTimeRangeOptionToDates } from "./toDates.js";
+import DynamicDateRange from "../DynamicDateRange.js";
+import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/getCachedLocaleDataInstance.js";
+import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js";
+
+const DEFAULT_DELIMITER = "-";
+
+/**
+ * @class
+ * @constructor
+ * @public
+ * @since 2.16.0
+ */
+
+class DateTimeRange implements IDynamicDateRangeOption {
+ template: JsxTemplate;
+
+ constructor() {
+ this.template = DateTimeRangeTemplate;
+ }
+
+ parse(value: string): DynamicDateRangeValue {
+ const returnValue = { operator: this.operator, values: [] } as DynamicDateRangeValue;
+
+ if (!value) {
+ return returnValue;
+ }
+ const splitValue = value.split(DEFAULT_DELIMITER);
+ const startDate = this._parseDate(splitValue[0].trim()) as Date;
+ const endDate = this._parseDate(splitValue[1].trim()) as Date;
+
+ returnValue.values = [startDate, endDate];
+
+ if (returnValue.values[0].getTime() > returnValue.values[1].getTime()) {
+ returnValue.values.reverse();
+ }
+
+ return returnValue;
+ }
+
+ _parseDate(value: string): Date | undefined {
+ return this.getFormat().parse(value) as Date;
+ }
+
+ format(value: DynamicDateRangeValue) {
+ const valuesArray = value?.values as Array;
+
+ if (!valuesArray || valuesArray.length !== 2 || !valuesArray[0] || !valuesArray[1]) {
+ return "";
+ }
+
+ const startDate = this._formatDate(valuesArray[0]);
+ const endDate = this._formatDate(valuesArray[1]);
+
+ return `${startDate} ${DEFAULT_DELIMITER} ${endDate}`;
+ }
+
+ _formatDate(date: Date) {
+ return this.getFormat().format(date);
+ }
+
+ toDates(value: DynamicDateRangeValue): Array {
+ return dateTimeRangeOptionToDates(value);
+ }
+
+ get text(): string {
+ return DynamicDateRange.i18nBundle.getText(DYNAMIC_DATE_TIME_RANGE_TEXT);
+ }
+
+ get operator() {
+ return "DATETIMERANGE";
+ }
+
+ get icon() {
+ return "appointment-2";
+ }
+
+ isValidString(value: string): boolean {
+ const splitValue = value.split(DEFAULT_DELIMITER);
+ const startDate = this._parseDate(splitValue[0].trim()) as Date;
+ const endDate = this._parseDate(splitValue[1].trim()) as Date;
+
+ if (!startDate || !endDate || Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ getFormatPattern() {
+ const localeData = getCachedLocaleDataInstance(getLocale());
+ return localeData.getCombinedDateTimePattern("medium", "medium");
+ }
+
+ getFormat(): DateFormat {
+ return DateFormat.getDateInstance({
+ strictParsing: true,
+ pattern: this.getFormatPattern(),
+ });
+ }
+}
+
+DynamicDateRange.register("DATETIMERANGE", DateTimeRange);
+
+export default DateTimeRange;
diff --git a/packages/main/src/dynamic-date-range-options/DateTimeRangeTemplate.tsx b/packages/main/src/dynamic-date-range-options/DateTimeRangeTemplate.tsx
new file mode 100644
index 000000000000..9b2c92b7157e
--- /dev/null
+++ b/packages/main/src/dynamic-date-range-options/DateTimeRangeTemplate.tsx
@@ -0,0 +1,62 @@
+import DynamicDateRange from "../DynamicDateRange.js";
+import DateTimePicker from "../DateTimePicker.js";
+import Label from "../Label.js";
+import {
+ DYNAMIC_DATE_TIME_RANGE_TEXT_TO_LABEL,
+ DYNAMIC_DATE_TIME_RANGE_TEXT_FROM_LABEL,
+} from "../generated/i18n/i18n-defaults.js";
+
+export default function DateTimeRangeTemplate(this: DynamicDateRange) {
+ const currentOperator = this.currentValue?.operator || "DATETIMERANGE";
+
+ const getDateFromValue = (index = 0) => {
+ if (this.value?.operator === currentOperator && this.value.values && this.value.values.length === 2) {
+ return this.getOption(this.value.operator)?.format(this.value)?.split("-")[index].trim();
+ }
+ return undefined;
+ };
+
+ const handleSelectionChange = () => {
+ const fromPicker = this.shadowRoot?.querySelector("[ui5-datetime-picker]#from-picker") as DateTimePicker;
+ const toPicker = this.shadowRoot?.querySelector("[ui5-datetime-picker]#to-picker") as DateTimePicker;
+
+ const fromDateValue = fromPicker.dateValue;
+ const toDateValue = toPicker.dateValue;
+
+ // If there are no dates selected, clear the value
+ if (!(fromDateValue && toDateValue)) {
+ this.currentValue = {
+ operator: currentOperator,
+ values: []
+ };
+ return;
+ }
+
+ const newValue = [fromDateValue, toDateValue];
+
+ if (newValue[0] && newValue[1] && newValue[0].getTime() > newValue[1].getTime()) {
+ newValue.reverse();
+ }
+
+ this.currentValue = {
+ operator: currentOperator,
+ values: newValue,
+ };
+ };
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/main/src/dynamic-date-range-options/toDates.ts b/packages/main/src/dynamic-date-range-options/toDates.ts
index 577cf1626369..cfda66bf9e3c 100644
--- a/packages/main/src/dynamic-date-range-options/toDates.ts
+++ b/packages/main/src/dynamic-date-range-options/toDates.ts
@@ -2,6 +2,10 @@ import type { DynamicDateRangeValue, IDynamicDateRangeOption } from "../DynamicD
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
const dateOptionToDates = (value: DynamicDateRangeValue): Array => {
+ if (!value || !value.values || value.values.length !== 1) {
+ return [];
+ }
+
const startDate = value.values ? value.values[0] as Date : UI5Date.getInstance();
const endDate = UI5Date.getInstance(startDate.getTime());
@@ -12,6 +16,10 @@ const dateOptionToDates = (value: DynamicDateRangeValue): Array => {
};
const dateRangeOptionToDates = (value: DynamicDateRangeValue): Array => {
+ if (!value || !value.values || value.values.length !== 2) {
+ return [];
+ }
+
const startDate = value.values ? value.values[0] as Date : UI5Date.getInstance();
const endDate = value.values ? value.values[1] as Date : UI5Date.getInstance();
@@ -21,6 +29,17 @@ const dateRangeOptionToDates = (value: DynamicDateRangeValue): Array => {
return [startDate, endDate];
};
+const dateTimeRangeOptionToDates = (value: DynamicDateRangeValue): Array => {
+ if (!value || !value.values || value.values.length !== 2) {
+ return [];
+ }
+
+ const startDate = value.values ? value.values[0] as Date : UI5Date.getInstance();
+ const endDate = value.values ? value.values[1] as Date : UI5Date.getInstance();
+
+ return [startDate, endDate];
+};
+
const todayToDates = (): Array => {
const startDate = UI5Date.getInstance();
const endDate = UI5Date.getInstance();
@@ -194,6 +213,7 @@ const dateTimeOptionToDates = (value: DynamicDateRangeValue): Array => {
export {
dateOptionToDates,
dateRangeOptionToDates,
+ dateTimeRangeOptionToDates,
todayToDates,
tomorrowToDates,
yesterdayToDates,
diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties
index 3523174068e1..c1a7eaf42035 100644
--- a/packages/main/src/i18n/messagebundle.properties
+++ b/packages/main/src/i18n/messagebundle.properties
@@ -817,6 +817,12 @@ DYNAMIC_DATE_RANGE_TO_TEXT=To (Date Time)
#XFLD: Text for the selected date in the DynamicDateRange component.
DYNAMIC_DATE_RANGE_SELECTED_TEXT=Selected
+DYNAMIC_DATE_TIME_RANGE_TEXT=From / To (Date and Time)
+
+DYNAMIC_DATE_TIME_RANGE_TEXT_TO_LABEL=To
+
+DYNAMIC_DATE_TIME_RANGE_TEXT_FROM_LABEL=From
+
#FLD: Text for the selected date section when there's no value in the DynamicDateRange component.
DYNAMIC_DATE_RANGE_EMPTY_SELECTED_TEXT=Choose Dates
diff --git a/packages/main/test/pages/DynamicDateRange.html b/packages/main/test/pages/DynamicDateRange.html
index a027aa1dca29..6a69decd28d3 100644
--- a/packages/main/test/pages/DynamicDateRange.html
+++ b/packages/main/test/pages/DynamicDateRange.html
@@ -26,7 +26,7 @@ Basic Options
diff --git a/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js b/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js
index 9dcab9c9bf1d..0d5c26f19089 100644
--- a/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js
+++ b/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js
@@ -4,4 +4,5 @@ import "@ui5/webcomponents/dist/dynamic-date-range-options/Yesterday.js";
import "@ui5/webcomponents/dist/dynamic-date-range-options/Tomorrow.js";
import "@ui5/webcomponents/dist/dynamic-date-range-options/SingleDate.js";
import "@ui5/webcomponents/dist/dynamic-date-range-options/DateRange.js";
+import "@ui5/webcomponents/dist/dynamic-date-range-options/DateTimeRange.js";
import "@ui5/webcomponents/dist/Text.js";
\ No newline at end of file
diff --git a/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html b/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html
index aaa733a73c14..26589b4f7fe6 100644
--- a/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html
+++ b/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html
@@ -12,7 +12,7 @@
All options
-
+
Only two options