From 1bcb6facb60934a9484f6ada273d66d50bcc9496 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Tue, 18 Nov 2025 15:45:00 +0200 Subject: [PATCH 01/17] feat --- packages/main/cypress/specs/Calendar.cy.tsx | 148 ++++++++++++++++++++ packages/main/src/Calendar.ts | 33 +++++ packages/main/src/CalendarTemplate.tsx | 1 + packages/main/src/DayPicker.ts | 72 +++++++++- packages/main/test/pages/Calendar.html | 23 ++- 5 files changed, 271 insertions(+), 6 deletions(-) diff --git a/packages/main/cypress/specs/Calendar.cy.tsx b/packages/main/cypress/specs/Calendar.cy.tsx index f8527984d2b7..3c78c6748f23 100644 --- a/packages/main/cypress/specs/Calendar.cy.tsx +++ b/packages/main/cypress/specs/Calendar.cy.tsx @@ -934,6 +934,154 @@ describe("Calendar general interaction", () => { .should("have.length", 1); }); + it("Disabled date range prevents selection of dates within the range", () => { + cy.mount( + + + + ); + + const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; + cy.get("#calendar1").invoke("prop", "timestamp", timestamp); + + // Check that disabled dates have the correct class and aria-disabled attribute + const disabledDate = new Date(Date.UTC(2024, 10, 12, 0, 0, 0)).valueOf() / 1000; + + cy.ui5CalendarGetDay("#calendar1", disabledDate.toString()) + .should("have.class", "ui5-dp-item--disabled") + .should("have.attr", "aria-disabled", "true"); + + // Try to click on a disabled date + cy.ui5CalendarGetDay("#calendar1", disabledDate.toString()) + .realClick(); + + // Verify the date was not selected + cy.get("#calendar1") + .invoke("prop", "selectedDates") + .should("have.length", 0); + }); + + it("Disabled date range with only start date disables dates from start onwards", () => { + cy.mount( + + + + ); + + const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; + cy.get("#calendar1").invoke("prop", "timestamp", timestamp); + + // Date before start should be enabled + const enabledDate = new Date(Date.UTC(2024, 10, 14, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", enabledDate.toString()) + .should("not.have.class", "ui5-dp-item--disabled"); + + // Date at start should be disabled + const startDate = new Date(Date.UTC(2024, 10, 15, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", startDate.toString()) + .should("have.class", "ui5-dp-item--disabled"); + + // Date after start should not be disabled + const afterStartDate = new Date(Date.UTC(2024, 10, 17, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", afterStartDate.toString()) + .should("not.have.class", "ui5-dp-item--disabled"); + }); + + it("Disabled date range with only end date disables dates up to end", () => { + cy.mount( + + + + ); + + const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; + cy.get("#calendar1").invoke("prop", "timestamp", timestamp); + + // Date after end should be enabled + const enabledDate = new Date(Date.UTC(2024, 10, 11, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", enabledDate.toString()) + .should("not.have.class", "ui5-dp-item--disabled"); + + // Date at end should not be disabled + const endDate = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", endDate.toString()) + .should("not.have.class", "ui5-dp-item--disabled"); + + // Date before end should be disabled + const beforeEndDate = new Date(Date.UTC(2024, 10, 8, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", beforeEndDate.toString()) + .should("have.class", "ui5-dp-item--disabled"); + }); + + it("Multiple disabled date ranges work correctly", () => { + cy.mount( + + + + + ); + + const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; + cy.get("#calendar1").invoke("prop", "timestamp", timestamp); + + // First range - should be disabled + const firstRangeDate = new Date(Date.UTC(2024, 10, 6, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", firstRangeDate.toString()) + .should("have.class", "ui5-dp-item--disabled"); + + // Between ranges - should be enabled + const betweenDate = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", betweenDate.toString()) + .should("not.have.class", "ui5-dp-item--disabled"); + + // Second range - should be disabled + const secondRangeDate = new Date(Date.UTC(2024, 10, 16, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", secondRangeDate.toString()) + .should("have.class", "ui5-dp-item--disabled"); + }); + + it("Disabled dates respect format pattern", () => { + cy.mount( + + + + ); + + const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; + cy.get("#calendar1").invoke("prop", "timestamp", timestamp); + + // Check disabled date + const disabledDate = new Date(Date.UTC(2024, 10, 12, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", disabledDate.toString()) + .should("have.class", "ui5-dp-item--disabled"); + }); + + it("Disabled dates work with range selection mode", () => { + cy.mount( + + + + ); + + const timestamp = new Date(Date.UTC(2024, 10, 5, 0, 0, 0)).valueOf() / 1000; + cy.get("#calendar1").invoke("prop", "timestamp", timestamp); + + // Try to select a range that includes disabled dates + const validStartDate = new Date(Date.UTC(2024, 10, 8, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", validStartDate.toString()) + .realClick(); + + // Try to select an end date in the disabled range + const disabledEndDate = new Date(Date.UTC(2024, 10, 12, 0, 0, 0)).valueOf() / 1000; + cy.ui5CalendarGetDay("#calendar1", disabledEndDate.toString()) + .realClick(); + + // The disabled date should not be selectable + cy.get("#calendar1") + .invoke("prop", "selectedDates") + .should("have.length", 1); // Only the first date should be selected + }); + it("Check calendar week numbers with specific CalendarWeekNumbering configuration", () => { cy.mount(getCalendarsWithWeekNumbers()); diff --git a/packages/main/src/Calendar.ts b/packages/main/src/Calendar.ts index ba14a1a2db4c..ce514dc43040 100644 --- a/packages/main/src/Calendar.ts +++ b/packages/main/src/Calendar.ts @@ -93,6 +93,11 @@ type CalendarYearRangeT = { endYear: number, } +type DisabledDateRangeT = { + startValue?: string, + endValue?: string +} + /** * @class * @@ -327,6 +332,16 @@ class Calendar extends CalendarPart { @slot({ type: HTMLElement, invalidateOnChildChange: true }) specialDates!: Array; + /** + * Defines the disabled date ranges that cannot be selected in the calendar. + * Use `ui5-date-range` elements to specify ranges of disabled dates. + * Each range can define a start date, an end date, or both. + * @public + * @since 2.16.0 + */ + @slot({ type: HTMLElement , invalidateOnChildChange: true }) + disabledDates!: Array; + /** * Defines the selected item type of the calendar legend item (if such exists). * @private @@ -431,6 +446,19 @@ class Calendar extends CalendarPart { return !!date; } + get _disabledDates() { + const validDisabledDateRanges = this._disabledDateRanges.filter(dateRange => { + const startValue = dateRange.startValue; + const endValue = dateRange.endValue; + return (startValue && this._isValidCalendarDate(startValue)) || (endValue && this._isValidCalendarDate(endValue)); + }); + + return validDisabledDateRanges.map(dateRange => ({ + startValue: dateRange.startValue, + endValue: dateRange.endValue, + })); + } + get _specialCalendarDates() { const hasSelectedType = this._specialDates.some(date => date.type === this._selectedItemType); const validSpecialDates = this._specialDates.filter(date => { @@ -778,6 +806,10 @@ class Calendar extends CalendarPart { return this.getSlottedNodes("specialDates"); } + get _disabledDateRanges() { + return this.getSlottedNodes("disabledDates"); + } + get classes() { return { prevButton: { @@ -963,4 +995,5 @@ export type { ICalendarSelectedDates, CalendarSelectionChangeEventDetail, SpecialCalendarDateT, + DisabledDateRangeT }; diff --git a/packages/main/src/CalendarTemplate.tsx b/packages/main/src/CalendarTemplate.tsx index dc96a6eccb24..edae4e593207 100644 --- a/packages/main/src/CalendarTemplate.tsx +++ b/packages/main/src/CalendarTemplate.tsx @@ -20,6 +20,7 @@ export default function CalendarTemplate(this: Calendar) { formatPattern={this._formatPattern} selectedDates={this._selectedDatesTimestamps} specialCalendarDates={this._specialCalendarDates} + disabledDates={this._disabledDates} _hidden={this._isDayPickerHidden} primaryCalendarType={this._primaryCalendarType} secondaryCalendarType={this._secondaryCalendarType} diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index d8adcc0e7d0c..426c5e2d3e28 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -39,6 +39,7 @@ import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js"; import CalendarSelectionMode from "./types/CalendarSelectionMode.js"; import CalendarPart from "./CalendarPart.js"; import type { + DisabledDateRangeT, ICalendarPicker, SpecialCalendarDateT, } from "./Calendar.js"; @@ -195,6 +196,14 @@ class DayPicker extends CalendarPart implements ICalendarPicker { @property({ type: Array }) specialCalendarDates: Array = []; + /** + * Array of disabled date ranges that cannot be selected. + * Each range can have a start and/or end date value. + * @private + */ + @property({ type: Array}) + disabledDates: Array = []; + @query("[data-sap-focus-ref]") _focusableDay!: HTMLElement; @@ -230,8 +239,6 @@ class DayPicker extends CalendarPart implements ICalendarPicker { const tempDate = this._getFirstDay(); // date that will be changed by 1 day 42 times const todayDate = CalendarDate.fromLocalJSDate(UI5Date.getInstance(), this._primaryCalendarType); // current day date - calculate once const calendarDate = this._calendarDate; // store the _calendarDate value as this getter is expensive and degrades IE11 perf - const minDate = this._minDate; // store the _minDate (expensive getter) - const maxDate = this._maxDate; // store the _maxDate (expensive getter) const tempSecondDate = this.hasSecondaryCalendarType ? this._getSecondaryDay(tempDate) : undefined; @@ -254,7 +261,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker { const isSelectedBetween = this._isDayInsideSelectionRange(timestamp); const isOtherMonth = tempDate.getMonth() !== calendarDate.getMonth(); const isWeekend = this._isWeekend(tempDate); - const isDisabled = tempDate.valueOf() < minDate.valueOf() || tempDate.valueOf() > maxDate.valueOf(); + const isDisabled = !this._isDisabledDate(tempDate); const isToday = tempDate.isSame(todayDate); const isFirstDayOfWeek = tempDate.getDay() === firstDayOfWeek; @@ -817,6 +824,65 @@ class DayPicker extends CalendarPart implements ICalendarPicker { || (iWeekendEnd < iWeekendStart && (iWeekDay >= iWeekendStart || iWeekDay <= iWeekendEnd)); } + /** + * Checks if a given date is disabled (not selectable). + * + * A date is considered disabled if: + * - It falls outside the min/max date range defined by the component + * - It matches a single disabled date + * - It falls within a disabled date range (inclusive of start and end dates) + * + * @param date - The date to check + * @returns `true` if the date is enabled (selectable), `false` if disabled + * @private + */ + _isDisabledDate(date: CalendarDate): boolean { + if ((this._minDate && date.valueOf() < this._minDate.valueOf()) + || (this._maxDate && date.valueOf() > this._maxDate.valueOf())) { + return false; + } + + const dateTimestamp = date.valueOf() / 1000; + + return !this.disabledDates.some(range => { + const startTimestamp = this._getTimestampFromDateValue(range.startValue); + const endTimestamp = this._getTimestampFromDateValue(range.endValue); + + if (endTimestamp) { + return dateTimestamp > startTimestamp && dateTimestamp < endTimestamp; + } + + if (startTimestamp && !endTimestamp) { + return dateTimestamp === startTimestamp; + } + + return false; + }); + } + + /** + * Converts a date value string to a timestamp. + * @param dateValue - Date string to convert + * @returns timestamp in seconds, or 0 if invalid + * @private + */ + _getTimestampFromDateValue(dateValue?: string): number { + if (!dateValue) { + return 0; + } + + try { + const jsDate = this.getValueFormat().parse(dateValue) as Date; + const calendarDate = CalendarDate.fromLocalJSDate( + jsDate, + this._primaryCalendarType + ); + return calendarDate.valueOf() / 1000; + } catch { + return 0; + } + } + _isDayPressed(target: HTMLElement): boolean { const targetParent = target.parentNode as HTMLElement; return (target.className.indexOf("ui5-dp-item") > -1) || (targetParent && targetParent.classList && targetParent.classList.contains("ui5-dp-item")); diff --git a/packages/main/test/pages/Calendar.html b/packages/main/test/pages/Calendar.html index a79dd84e08ec..a47cce746a70 100644 --- a/packages/main/test/pages/Calendar.html +++ b/packages/main/test/pages/Calendar.html @@ -78,7 +78,6 @@ -
Selection type for the first calendar: @@ -125,6 +124,8 @@
+ +
Calendar with no format pattern & ISO min-max dates @@ -170,9 +171,25 @@
+
+ Calendar with Disabled Dates + + + + +
+ +
+ Calendar with Disabled Dates with format pattern + + + + +
+ - --> From 11564acb368642ade071a82c3c7f6004631e8702 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 19 Nov 2025 10:51:16 +0200 Subject: [PATCH 02/17] feat: add sample in playground --- .../main/Calendar/Calendar.mdx | 7 +++++- .../CalendarWithDisabledDates.md | 0 .../CalendarWithDisabledDates/main.js | 2 ++ .../CalendarWithDisabledDates/sample.html | 24 +++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md create mode 100644 packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/main.js create mode 100644 packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html diff --git a/packages/website/docs/_components_pages/main/Calendar/Calendar.mdx b/packages/website/docs/_components_pages/main/Calendar/Calendar.mdx index a1b5f0de31e2..c91016c68395 100644 --- a/packages/website/docs/_components_pages/main/Calendar/Calendar.mdx +++ b/packages/website/docs/_components_pages/main/Calendar/Calendar.mdx @@ -9,6 +9,7 @@ import CalendarWithLegend from "../../../_samples/main/Calendar/CalendarWithLege import SelectionModes from "../../../_samples/main/Calendar/SelectionModes/SelectionModes.md"; import CalendarInDifferentTimezone from "../../../_samples/main/Calendar/CalendarInDifferentTimezone/CalendarInDifferentTimezone.md"; import CalendarWeekNumbering from "../../../_samples/main/Calendar/CalendarWeekNumbering/CalendarWeekNumbering.md"; +import CalendarWithDisabledDates from "../../../_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md"; import useBaseUrl from "@docusaurus/useBaseUrl"; import Link from '@docusaurus/Link'; @@ -50,4 +51,8 @@ You can set to the configuration the preferred time zone, such as: Asia/Tokyo, P ### CalendarWeekNumbering You can use the component, the preferred week numbering and first day of week. - \ No newline at end of file + + +### Calendar with Disabled Dates +Define disabled dates + \ No newline at end of file diff --git a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/main.js b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/main.js new file mode 100644 index 000000000000..bfb48840ad3f --- /dev/null +++ b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/main.js @@ -0,0 +1,2 @@ +import "@ui5/webcomponents/dist/Assets-fetch.js"; +import "@ui5/webcomponents/dist/Calendar.js"; \ No newline at end of file diff --git a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html new file mode 100644 index 000000000000..3e214d5779a2 --- /dev/null +++ b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html @@ -0,0 +1,24 @@ + + + + + + + + Sample + + + + + + + + + + + + + + + + From 8c5a832a3767a893a520b207fb0a38b267a7bca2 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 19 Nov 2025 11:20:52 +0200 Subject: [PATCH 03/17] feat: update md file --- packages/website/build-scripts/local-cdn.mjs | 4 +++- .../CalendarWithDisabledDates/CalendarWithDisabledDates.md | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/website/build-scripts/local-cdn.mjs b/packages/website/build-scripts/local-cdn.mjs index 406d6537da4b..58a178a3e18b 100644 --- a/packages/website/build-scripts/local-cdn.mjs +++ b/packages/website/build-scripts/local-cdn.mjs @@ -33,5 +33,7 @@ const filesToDelete = files.filter(f => { return f.isFile() && !f.name.endsWith(".js")&& !f.name.endsWith(".json") && !f.name.endsWith(".svg") && !f.name.endsWith(".d.ts") && !f.name.endsWith("package.json") }); -console.log(filesToDelete.filter(f => f.path.includes("localization/dist/generated/assets/cldr"))) +console.log(`Total files to delete in local-cdn: ${filesToDelete.length}`); + +// console.log(filesToDelete.filter(f => f.path.includes("localization/dist/generated/assets/cldr"))) filesToDelete.map(f => rm(path.join(f.path ?? f.parentPath, f.name))); \ No newline at end of file diff --git a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md index e69de29bb2d1..ffccbf6dd13e 100644 --- a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md +++ b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/CalendarWithDisabledDates.md @@ -0,0 +1,4 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; + + \ No newline at end of file From 33fef50f062c275d00eb07e48171c52d1164ded6 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 19 Nov 2025 11:30:38 +0200 Subject: [PATCH 04/17] fix: lint errors --- packages/main/src/Calendar.ts | 4 ++-- packages/main/src/DayPicker.ts | 10 ++++------ .../Calendar/CalendarWithDisabledDates/sample.html | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/main/src/Calendar.ts b/packages/main/src/Calendar.ts index ce514dc43040..afb421b0947d 100644 --- a/packages/main/src/Calendar.ts +++ b/packages/main/src/Calendar.ts @@ -339,7 +339,7 @@ class Calendar extends CalendarPart { * @public * @since 2.16.0 */ - @slot({ type: HTMLElement , invalidateOnChildChange: true }) + @slot({ type: HTMLElement, invalidateOnChildChange: true }) disabledDates!: Array; /** @@ -995,5 +995,5 @@ export type { ICalendarSelectedDates, CalendarSelectionChangeEventDetail, SpecialCalendarDateT, - DisabledDateRangeT + DisabledDateRangeT, }; diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index 426c5e2d3e28..23e90bc15ce8 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -201,7 +201,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker { * Each range can have a start and/or end date value. * @private */ - @property({ type: Array}) + @property({ type: Array }) disabledDates: Array = []; @query("[data-sap-focus-ref]") @@ -826,18 +826,16 @@ class DayPicker extends CalendarPart implements ICalendarPicker { /** * Checks if a given date is disabled (not selectable). - * * A date is considered disabled if: * - It falls outside the min/max date range defined by the component * - It matches a single disabled date * - It falls within a disabled date range (inclusive of start and end dates) - * * @param date - The date to check * @returns `true` if the date is enabled (selectable), `false` if disabled * @private */ _isDisabledDate(date: CalendarDate): boolean { - if ((this._minDate && date.valueOf() < this._minDate.valueOf()) + if ((this._minDate && date.valueOf() < this._minDate.valueOf()) || (this._maxDate && date.valueOf() > this._maxDate.valueOf())) { return false; } @@ -874,8 +872,8 @@ class DayPicker extends CalendarPart implements ICalendarPicker { try { const jsDate = this.getValueFormat().parse(dateValue) as Date; const calendarDate = CalendarDate.fromLocalJSDate( - jsDate, - this._primaryCalendarType + jsDate, + this._primaryCalendarType, ); return calendarDate.valueOf() / 1000; } catch { diff --git a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html index 3e214d5779a2..027a5a79d967 100644 --- a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html +++ b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html @@ -12,7 +12,7 @@ - + From 2e28efc274513a1cf580e563800ff4922bba5d9d Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 19 Nov 2025 11:32:08 +0200 Subject: [PATCH 05/17] refactor: revert change --- packages/website/build-scripts/local-cdn.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/website/build-scripts/local-cdn.mjs b/packages/website/build-scripts/local-cdn.mjs index 58a178a3e18b..406d6537da4b 100644 --- a/packages/website/build-scripts/local-cdn.mjs +++ b/packages/website/build-scripts/local-cdn.mjs @@ -33,7 +33,5 @@ const filesToDelete = files.filter(f => { return f.isFile() && !f.name.endsWith(".js")&& !f.name.endsWith(".json") && !f.name.endsWith(".svg") && !f.name.endsWith(".d.ts") && !f.name.endsWith("package.json") }); -console.log(`Total files to delete in local-cdn: ${filesToDelete.length}`); - -// console.log(filesToDelete.filter(f => f.path.includes("localization/dist/generated/assets/cldr"))) +console.log(filesToDelete.filter(f => f.path.includes("localization/dist/generated/assets/cldr"))) filesToDelete.map(f => rm(path.join(f.path ?? f.parentPath, f.name))); \ No newline at end of file From 68f947943f6ae15e9d17550427a0c7eeec000fc4 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 19 Nov 2025 16:54:40 +0200 Subject: [PATCH 06/17] refactor: update method name --- packages/main/src/DayPicker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index 23e90bc15ce8..89f2b126bec9 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -261,7 +261,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker { const isSelectedBetween = this._isDayInsideSelectionRange(timestamp); const isOtherMonth = tempDate.getMonth() !== calendarDate.getMonth(); const isWeekend = this._isWeekend(tempDate); - const isDisabled = !this._isDisabledDate(tempDate); + const isDisabled = !this._isDateEnabled(tempDate); const isToday = tempDate.isSame(todayDate); const isFirstDayOfWeek = tempDate.getDay() === firstDayOfWeek; @@ -834,7 +834,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker { * @returns `true` if the date is enabled (selectable), `false` if disabled * @private */ - _isDisabledDate(date: CalendarDate): boolean { + _isDateEnabled(date: CalendarDate): boolean { if ((this._minDate && date.valueOf() < this._minDate.valueOf()) || (this._maxDate && date.valueOf() > this._maxDate.valueOf())) { return false; From 71a2180186319627bd0023b726c7d28958502990 Mon Sep 17 00:00:00 2001 From: GDamyanov Date: Thu, 20 Nov 2025 09:47:08 +0200 Subject: [PATCH 07/17] Update packages/main/cypress/specs/Calendar.cy.tsx Co-authored-by: Dimitar Stoev --- packages/main/cypress/specs/Calendar.cy.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/cypress/specs/Calendar.cy.tsx b/packages/main/cypress/specs/Calendar.cy.tsx index 3c78c6748f23..e8d638743ccd 100644 --- a/packages/main/cypress/specs/Calendar.cy.tsx +++ b/packages/main/cypress/specs/Calendar.cy.tsx @@ -961,7 +961,7 @@ describe("Calendar general interaction", () => { .should("have.length", 0); }); - it("Disabled date range with only start date disables dates from start onwards", () => { + it("Disables a single date equal to start date when end date is not defined", () => { cy.mount( From 51bb17c676a300a34836ca4eed9b7b75d6d8e126 Mon Sep 17 00:00:00 2001 From: GDamyanov Date: Thu, 20 Nov 2025 09:47:15 +0200 Subject: [PATCH 08/17] Update packages/main/cypress/specs/Calendar.cy.tsx Co-authored-by: Dimitar Stoev --- packages/main/cypress/specs/Calendar.cy.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/cypress/specs/Calendar.cy.tsx b/packages/main/cypress/specs/Calendar.cy.tsx index e8d638743ccd..6a780f2f5caf 100644 --- a/packages/main/cypress/specs/Calendar.cy.tsx +++ b/packages/main/cypress/specs/Calendar.cy.tsx @@ -987,7 +987,7 @@ describe("Calendar general interaction", () => { .should("not.have.class", "ui5-dp-item--disabled"); }); - it("Disabled date range with only end date disables dates up to end", () => { + it("Disables all dates before end date when start date is not defined", () => { cy.mount( From 138dc54529c53e9abc098133f38661bb9ebe97f6 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 24 Nov 2025 21:38:34 +0200 Subject: [PATCH 09/17] refactor: refactor tests --- packages/main/cypress/specs/Calendar.cy.tsx | 80 +++++++++++++-------- packages/main/test/pages/Calendar.html | 4 +- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/packages/main/cypress/specs/Calendar.cy.tsx b/packages/main/cypress/specs/Calendar.cy.tsx index 101b4195e680..7b3395e227ee 100644 --- a/packages/main/cypress/specs/Calendar.cy.tsx +++ b/packages/main/cypress/specs/Calendar.cy.tsx @@ -37,6 +37,19 @@ const getCalendarsWithWeekNumbers = () => (<> ); +const getCalendarWithDisabledDates = (id, formatPattern, ranges, props = {}) => ( + + {ranges.map((range, idx) => ( + + ))} + +); + describe("Calendar general interaction", () => { it("Focus goes into the current day item of the day picker", () => { const date = new Date(Date.UTC(2000, 10, 22, 0, 0, 0)); @@ -937,11 +950,11 @@ describe("Calendar general interaction", () => { }); it("Disabled date range prevents selection of dates within the range", () => { - cy.mount( - - - - ); + cy.mount(getCalendarWithDisabledDates( + "calendar1", + "yyyy-MM-dd", + [{ startValue: "2024-11-10", endValue: "2024-11-15" }] + )); const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; cy.get("#calendar1").invoke("prop", "timestamp", timestamp); @@ -964,11 +977,12 @@ describe("Calendar general interaction", () => { }); it("Disables a single date equal to start date when end date is not defined", () => { - cy.mount( - - - - ); + cy.mount(getCalendarWithDisabledDates( + "calendar1", + "yyyy-MM-dd", + [{ startValue: "2024-11-15" }], + { maxDate: "2024-11-20" } + )); const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; cy.get("#calendar1").invoke("prop", "timestamp", timestamp); @@ -990,11 +1004,12 @@ describe("Calendar general interaction", () => { }); it("Disables all dates before end date when start date is not defined", () => { - cy.mount( - - - - ); + cy.mount(getCalendarWithDisabledDates( + "calendar1", + "yyyy-MM-dd", + [{ endValue: "2024-11-10" }], + { minDate: "2024-11-01" } + )); const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; cy.get("#calendar1").invoke("prop", "timestamp", timestamp); @@ -1016,12 +1031,14 @@ describe("Calendar general interaction", () => { }); it("Multiple disabled date ranges work correctly", () => { - cy.mount( - - - - - ); + cy.mount(getCalendarWithDisabledDates( + "calendar1", + "yyyy-MM-dd", + [ + { startValue: "2024-11-05", endValue: "2024-11-07" }, + { startValue: "2024-11-15", endValue: "2024-11-17" } + ] + )); const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; cy.get("#calendar1").invoke("prop", "timestamp", timestamp); @@ -1043,11 +1060,11 @@ describe("Calendar general interaction", () => { }); it("Disabled dates respect format pattern", () => { - cy.mount( - - - - ); + cy.mount(getCalendarWithDisabledDates( + "calendar1", + "dd/MM/yyyy", + [{ startValue: "10/11/2024", endValue: "15/11/2024" }] + )); const timestamp = new Date(Date.UTC(2024, 10, 10, 0, 0, 0)).valueOf() / 1000; cy.get("#calendar1").invoke("prop", "timestamp", timestamp); @@ -1059,11 +1076,12 @@ describe("Calendar general interaction", () => { }); it("Disabled dates work with range selection mode", () => { - cy.mount( - - - - ); + cy.mount(getCalendarWithDisabledDates( + "calendar1", + "yyyy-MM-dd", + [{ startValue: "2024-11-10", endValue: "2024-11-15" }], + { selectionMode: "Range" } + )); const timestamp = new Date(Date.UTC(2024, 10, 5, 0, 0, 0)).valueOf() / 1000; cy.get("#calendar1").invoke("prop", "timestamp", timestamp); diff --git a/packages/main/test/pages/Calendar.html b/packages/main/test/pages/Calendar.html index c1b8e0febda6..e3b21fc9e5c1 100644 --- a/packages/main/test/pages/Calendar.html +++ b/packages/main/test/pages/Calendar.html @@ -197,7 +197,7 @@ - + From 0c444cba22e7da3e9490e2d48d8ca156d2ecba7d Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Tue, 25 Nov 2025 10:51:17 +0200 Subject: [PATCH 10/17] refactor: use utils methods --- packages/main/src/DayPicker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index 7cc844451e14..18051ecef574 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -836,8 +836,8 @@ class DayPicker extends CalendarPart implements ICalendarPicker { * @private */ _isDateEnabled(date: CalendarDate): boolean { - if ((this._minDate && date.valueOf() < this._minDate.valueOf()) - || (this._maxDate && date.valueOf() > this._maxDate.valueOf())) { + if ((this._minDate && date.isAfter(this._minDate)) + || (this._maxDate && date.isBefore(this._maxDate))) { return false; } From fff4dfe21c2158b25345362d42741d38617d0f03 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Tue, 25 Nov 2025 10:55:41 +0200 Subject: [PATCH 11/17] feat: use utils --- packages/main/src/DayPicker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index 18051ecef574..68721bb0fb34 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -836,8 +836,8 @@ class DayPicker extends CalendarPart implements ICalendarPicker { * @private */ _isDateEnabled(date: CalendarDate): boolean { - if ((this._minDate && date.isAfter(this._minDate)) - || (this._maxDate && date.isBefore(this._maxDate))) { + if ((this._minDate && date.isBefore(this._minDate)) + || (this._maxDate && date.isAfter(this._maxDate))) { return false; } From f30b9e47dcb7d66d5cf374a039cbad89b142d37a Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Tue, 25 Nov 2025 16:42:19 +0200 Subject: [PATCH 12/17] fix: update samples --- packages/main/test/pages/Calendar.html | 4 ++-- .../main/Calendar/CalendarWithDisabledDates/sample.html | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/main/test/pages/Calendar.html b/packages/main/test/pages/Calendar.html index e3b21fc9e5c1..8f597809e13c 100644 --- a/packages/main/test/pages/Calendar.html +++ b/packages/main/test/pages/Calendar.html @@ -182,8 +182,8 @@
Calendar with Disabled Dates - - + +
diff --git a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html index 027a5a79d967..9712baa3b31a 100644 --- a/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html +++ b/packages/website/docs/_samples/main/Calendar/CalendarWithDisabledDates/sample.html @@ -11,9 +11,10 @@ - - - + + + + From e143612b9cc5bceff8f47c69901c1fdfdea2fb18 Mon Sep 17 00:00:00 2001 From: GDamyanov Date: Tue, 25 Nov 2025 16:44:00 +0200 Subject: [PATCH 13/17] Update packages/main/test/pages/Calendar.html Co-authored-by: Dimitar Stoev --- packages/main/test/pages/Calendar.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/test/pages/Calendar.html b/packages/main/test/pages/Calendar.html index 8f597809e13c..656df0e4a9d2 100644 --- a/packages/main/test/pages/Calendar.html +++ b/packages/main/test/pages/Calendar.html @@ -188,7 +188,7 @@
- Calendar with Disabled Dates with format pattern + Calendar with Disabled Dates using a format pattern From 555c611816a760544ea0658039f6aa817ee836f1 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Tue, 25 Nov 2025 16:48:29 +0200 Subject: [PATCH 14/17] refactor: update jsdoc --- packages/main/src/DayPicker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index 68721bb0fb34..28a72db8daf5 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -826,11 +826,11 @@ class DayPicker extends CalendarPart implements ICalendarPicker { } /** - * Checks if a given date is disabled (not selectable). + * Checks if a given date is enabled (selectable). * A date is considered disabled if: * - It falls outside the min/max date range defined by the component * - It matches a single disabled date - * - It falls within a disabled date range (inclusive of start and end dates) + * - It falls within a disabled date range (exclusive of start and end dates) * @param date - The date to check * @returns `true` if the date is enabled (selectable), `false` if disabled * @private From 5decd7995e6afa6b835d2921a12f6b174eebe69d Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 26 Nov 2025 17:59:38 +0200 Subject: [PATCH 15/17] test: refactor tests --- packages/main/cypress/specs/Calendar.cy.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/main/cypress/specs/Calendar.cy.tsx b/packages/main/cypress/specs/Calendar.cy.tsx index 7b3395e227ee..cc967a909e5d 100644 --- a/packages/main/cypress/specs/Calendar.cy.tsx +++ b/packages/main/cypress/specs/Calendar.cy.tsx @@ -971,9 +971,8 @@ describe("Calendar general interaction", () => { .realClick(); // Verify the date was not selected - cy.get("#calendar1") - .invoke("prop", "selectedDates") - .should("have.length", 0); + cy.ui5CalendarGetDay("#calendar1", disabledDate.toString()) + .should("not.have.class", "ui5-dp-item--selected"); }); it("Disables a single date equal to start date when end date is not defined", () => { @@ -1096,10 +1095,9 @@ describe("Calendar general interaction", () => { cy.ui5CalendarGetDay("#calendar1", disabledEndDate.toString()) .realClick(); - // The disabled date should not be selectable - cy.get("#calendar1") - .invoke("prop", "selectedDates") - .should("have.length", 1); // Only the first date should be selected + // Verify the date was not selected + cy.ui5CalendarGetDay("#calendar1", disabledEndDate.toString()) + .should("not.have.class", "ui5-dp-item--selected"); }); it("Check calendar week numbers with specific CalendarWeekNumbering configuration", () => { From 9d30dc515f7774672961b28281aaa2e39b11f6fc Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 26 Nov 2025 18:07:30 +0200 Subject: [PATCH 16/17] refactor: remove redundant getter --- packages/main/src/Calendar.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/main/src/Calendar.ts b/packages/main/src/Calendar.ts index afb421b0947d..dcf6eae80d14 100644 --- a/packages/main/src/Calendar.ts +++ b/packages/main/src/Calendar.ts @@ -447,7 +447,7 @@ class Calendar extends CalendarPart { } get _disabledDates() { - const validDisabledDateRanges = this._disabledDateRanges.filter(dateRange => { + const validDisabledDateRanges = this.disabledDates.filter(dateRange => { const startValue = dateRange.startValue; const endValue = dateRange.endValue; return (startValue && this._isValidCalendarDate(startValue)) || (endValue && this._isValidCalendarDate(endValue)); @@ -806,10 +806,6 @@ class Calendar extends CalendarPart { return this.getSlottedNodes("specialDates"); } - get _disabledDateRanges() { - return this.getSlottedNodes("disabledDates"); - } - get classes() { return { prevButton: { From e601fb83ed6c9389cdf37a19d59eb0c91b1f2d9f Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 26 Nov 2025 18:10:45 +0200 Subject: [PATCH 17/17] refactor: optimise code --- packages/main/src/DayPicker.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index 28a72db8daf5..6b39305e959a 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -851,11 +851,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker { return dateTimestamp > startTimestamp && dateTimestamp < endTimestamp; } - if (startTimestamp && !endTimestamp) { - return dateTimestamp === startTimestamp; - } - - return false; + return startTimestamp && dateTimestamp === startTimestamp; }); }