From 13d1f4c1700e0a0613ddc1ac9bd868aaa4b82f84 Mon Sep 17 00:00:00 2001 From: Pavel Ivannikov Date: Tue, 13 Aug 2024 13:46:38 +0400 Subject: [PATCH] feat(kit): prevent disabled days selection for calendar-range (#8328) --- .../input-date-range/input-date-range.spec.ts | 29 +++++++++ .../calendar-range.component.ts | 59 ++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/projects/demo-playwright/tests/kit/input-date-range/input-date-range.spec.ts b/projects/demo-playwright/tests/kit/input-date-range/input-date-range.spec.ts index da1585501bf2..32bf90329b25 100644 --- a/projects/demo-playwright/tests/kit/input-date-range/input-date-range.spec.ts +++ b/projects/demo-playwright/tests/kit/input-date-range/input-date-range.spec.ts @@ -160,6 +160,35 @@ test.describe('InputDateRange', () => { '07-item-and-calendar-interactions.png', ); }); + + test('Prevent selection of range with disabled days', async ({page}) => { + const calendar = new TuiCalendarPO( + inputDateRange.calendarRange.locator('tui-calendar'), + ); + + const getCellState = async (cell: Locator): Promise => + cell.getAttribute('data-state'); + + const getDaysState = async (): Promise> => + Promise.all((await calendar.getDays()).map(getCellState)); + + await tuiGoto(page, 'components/input-date-range/API?disabledItemHandler$=1'); + + await inputDateRange.textfield.click(); + + // check disabled items length before day selection + expect( + (await getDaysState()).filter(state => state === 'disabled'), + ).toHaveLength(20); + + await calendar.clickOnCalendarDay(7); + + // check range which includes disabled days + // range should have only 2 enabled items + expect( + (await getDaysState()).filter(state => state !== 'disabled'), + ).toHaveLength(2); + }); }); test.describe('Examples', () => { diff --git a/projects/kit/components/calendar-range/calendar-range.component.ts b/projects/kit/components/calendar-range/calendar-range.component.ts index 87ef136997fa..ae778069846c 100644 --- a/projects/kit/components/calendar-range/calendar-range.component.ts +++ b/projects/kit/components/calendar-range/calendar-range.component.ts @@ -78,6 +78,7 @@ export class TuiCalendarRangeComponent implements TuiWithOptionalMinMax @Output() readonly valueChange = new EventEmitter(); + availableRange: TuiDayRange | null = null; previousValue: TuiDayRange | null = null; selectedActivePeriod: TuiDayRangePeriod | null = null; @@ -175,6 +176,7 @@ export class TuiCalendarRangeComponent implements TuiWithOptionalMinMax if (value === null || !value.isSingleDay) { this.value = new TuiDayRange(day, day); + this.availableRange = this.findAvailableRange(); return; } @@ -225,7 +227,7 @@ export class TuiCalendarRangeComponent implements TuiWithOptionalMinMax ): TuiBooleanHandler { return item => { if (!value?.isSingleDay || !minLength) { - return disabledItemHandler(item); + return this.isDisabledItem(disabledItemHandler, value, item); } const negativeMinLength = tuiObjectFromEntries( @@ -236,7 +238,60 @@ export class TuiCalendarRangeComponent implements TuiWithOptionalMinMax const inDisabledRange = disabledBefore.dayBefore(item) && disabledAfter.dayAfter(item); - return inDisabledRange || disabledItemHandler(item); + return ( + inDisabledRange || this.isDisabledItem(disabledItemHandler, value, item) + ); }; } + + private isDisabledItem( + disabledItemHandler: TuiBooleanHandler, + value: TuiDayRange | null, + item: TuiDay, + ): boolean { + return ( + disabledItemHandler(item) || + (!!value?.isSingleDay && !this.availableRangeContainsItem(item)) + ); + } + + private availableRangeContainsItem(item: TuiDay): boolean { + if (this.availableRange === null) { + return true; + } + + const {from, to} = this.availableRange; + + return from.daySameOrBefore(item) && to.daySameOrAfter(item); + } + + private findAvailableRange(): TuiDayRange | null { + const {disabledItemHandler, value} = this; + + if (!value?.isSingleDay || disabledItemHandler === ALWAYS_FALSE_HANDLER) { + return null; + } + + let from = value.from; + let to = value.from; + + let leftShift = true; + let rightShift = true; + + while (leftShift || rightShift) { + leftShift = !disabledItemHandler(from.append({day: -1})); + + if (leftShift) { + from = from.append({day: -1}); + } + + rightShift = !disabledItemHandler(to.append({day: 1})); + + if (rightShift) { + to = to.append({day: 1}); + } + } + + return new TuiDayRange(from, to); + } }