diff --git a/packages/main/cypress/specs/DateRangePicker.cy.tsx b/packages/main/cypress/specs/DateRangePicker.cy.tsx index 4dbd613c0a64..59985f6b73aa 100644 --- a/packages/main/cypress/specs/DateRangePicker.cy.tsx +++ b/packages/main/cypress/specs/DateRangePicker.cy.tsx @@ -970,6 +970,14 @@ describe("Validation inside a form", () => { }); }); +describe("DateRangePicker rejects relative dates", () => { + const relativeKeywords = ["today", "tomorrow", "yesterday"]; + + relativeKeywords.forEach(keyword => { + it(`typing '${keyword}' sets error state`, () => { + cy.mount(); + + cy.get("[ui5-daterange-picker]") describe("DateRangePicker - Two Calendars Feature", () => { describe("Basic Two Calendars Display", () => { it("should display two calendars when showTwoMonths is true", () => { @@ -1003,6 +1011,32 @@ describe("DateRangePicker - Two Calendars Feature", () => { .realClick() .should("be.focused"); + cy.realType(keyword); + cy.realPress("Enter"); + + cy.get("@dateRangePicker") + .should("have.value", keyword) + .should("have.attr", "value-state", "Negative"); + }); + }); + + it("valid concrete date range does not set error state", () => { + cy.mount(); + + cy.get("[ui5-daterange-picker]") + .as("dateRangePicker") + .shadow() + .find("[ui5-datetime-input]") + .realClick() + .should("be.focused"); + + cy.realType("09/09/2020 - 10/10/2020"); + cy.realPress("Enter"); + + cy.get("@dateRangePicker") + .should("have.attr", "value-state", "None"); + }); +}); cy.realPress("F4"); cy.get("@dateRangePicker") diff --git a/packages/main/src/DateRangePicker.ts b/packages/main/src/DateRangePicker.ts index 397a032e2c05..6e7d2fa97b75 100644 --- a/packages/main/src/DateRangePicker.ts +++ b/packages/main/src/DateRangePicker.ts @@ -6,6 +6,7 @@ import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDat import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js"; import modifyDateBy from "@ui5/webcomponents-localization/dist/dates/modifyDateBy.js"; import getTodayUTCTimestamp from "@ui5/webcomponents-localization/dist/dates/getTodayUTCTimestamp.js"; +import type DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js"; import { DATERANGE_DESCRIPTION, DATERANGEPICKER_POPOVER_ACCESSIBLE_NAME, @@ -42,7 +43,10 @@ const DEFAULT_DELIMITER = "-"; * ### Usage * The user can enter a date by: * Using the calendar that opens in a popup or typing it in directly in the input field (not available for mobile devices). - * For the `ui5-daterange-picker` + * For the `ui5-daterange-picker`: + * + * **Note:** Relative date values such as "today", "yesterday", or "tomorrow" are not supported. + * Entering a relative date sets the component to an error state. * ### ES6 Module Import * * `import "@ui5/webcomponents/dist/DateRangePicker.js";` @@ -155,6 +159,27 @@ class DateRangePicker extends DatePicker implements IFormInputElement { this._prevDelimiter = null; } + /** + * Checks if a date string is a relative date (e.g. "today", "tomorrow") + * that would be resolved by DateFormat.parseRelative(). + * Relative dates are not supported in DateRangePicker. + * @private + */ + _isRelativeValue(dateString: string, format: DateFormat): boolean { + const trimmed = dateString.trim(); + if (!trimmed) { + return false; + } + + const parsed = format.parse(trimmed); + if (!parsed) { + return false; + } + + const formatted = format.format(parsed); + return formatted !== trimmed; + } + /** * **Note:** The getter method is inherited and not supported. If called it will return an empty value. * @public @@ -329,6 +354,10 @@ class DateRangePicker extends DatePicker implements IFormInputElement { isValid(value: string): boolean { const parts = this._splitValueByDelimiter(value).filter(str => str.trim() !== ""); + if (parts.some(dateString => this._isRelativeValue(dateString, this.getFormat()))) { + return false; + } + return parts.length <= 2 && parts.every(dateString => super.isValid(dateString)); // must be at most 2 dates and each must be valid } @@ -340,6 +369,10 @@ class DateRangePicker extends DatePicker implements IFormInputElement { isValidValue(value: string): boolean { const parts = this._splitValueByDelimiter(value).filter(str => str.trim() !== ""); + if (parts.some(dateString => this._isRelativeValue(dateString, this.getValueFormat()))) { + return false; + } + return parts.length <= 2 && parts.every(dateString => super.isValidValue(dateString)); // must be at most 2 dates and each must be valid } @@ -351,6 +384,10 @@ class DateRangePicker extends DatePicker implements IFormInputElement { isValidDisplayValue(value: string): boolean { const parts = this._splitValueByDelimiter(value).filter(str => str.trim() !== ""); + if (parts.some(dateString => this._isRelativeValue(dateString, this.getDisplayFormat()))) { + return false; + } + return parts.length <= 2 && parts.every(dateString => super.isValidDisplayValue(dateString)); // must be at most 2 dates and each must be valid }