From b49a156a456dbbd28414ce5f2e6b69cf972b454d Mon Sep 17 00:00:00 2001 From: Pavel Ivannikov Date: Sat, 20 Jul 2024 14:19:42 +0400 Subject: [PATCH 1/2] feat(kit): prevent disabled date selection for calendar-range in 3x version --- .../input-date-range/input-date-range.spec.ts | 29 ++++++++++++++ .../calendar-range.component.ts | 38 ++++++++++++++++++- 2 files changed, 65 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 44ed3699af81..63920cfc7769 100644 --- a/projects/kit/components/calendar-range/calendar-range.component.ts +++ b/projects/kit/components/calendar-range/calendar-range.component.ts @@ -221,7 +221,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( @@ -232,7 +232,41 @@ 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.isExternalDaysDisabled(disabledItemHandler, value.from, item)) + ); + } + + private isExternalDaysDisabled( + disabledItemHandler: TuiBooleanHandler, + startDay: TuiDay, + item: TuiDay, + ): boolean { + let temp = item; + + while (temp.dayBefore(startDay) || temp.dayAfter(startDay)) { + if (disabledItemHandler(temp)) { + return true; + } + + const day = temp.dayBefore(startDay) ? 1 : -1; + + temp = temp.append({day}); + } + + return false; + } } From 0f606c26e9bc5af81edabfd7fd68b4a984db8e9d Mon Sep 17 00:00:00 2001 From: Pavel Ivannikov Date: Tue, 13 Aug 2024 09:10:01 +0400 Subject: [PATCH 2/2] feat(kit): updated search of available range --- .../calendar-range.component.ts | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/projects/kit/components/calendar-range/calendar-range.component.ts b/projects/kit/components/calendar-range/calendar-range.component.ts index 63920cfc7769..d34bece62ddd 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; @@ -171,6 +172,7 @@ export class TuiCalendarRangeComponent implements TuiWithOptionalMinMax if (value === null || !value.isSingleDay) { this.value = new TuiDayRange(day, day); + this.availableRange = this.findAvailableRange(); return; } @@ -245,28 +247,47 @@ export class TuiCalendarRangeComponent implements TuiWithOptionalMinMax ): boolean { return ( disabledItemHandler(item) || - (!!value?.isSingleDay && - this.isExternalDaysDisabled(disabledItemHandler, value.from, item)) + (!!value?.isSingleDay && !this.availableRangeContainsItem(item)) ); } - private isExternalDaysDisabled( - disabledItemHandler: TuiBooleanHandler, - startDay: TuiDay, - item: TuiDay, - ): boolean { - let temp = item; + private availableRangeContainsItem(item: TuiDay): boolean { + if (this.availableRange === null) { + return true; + } + + const {from, to} = this.availableRange; + + return from.daySameOrBefore(item) && to.daySameOrAfter(item); + } - while (temp.dayBefore(startDay) || temp.dayAfter(startDay)) { - if (disabledItemHandler(temp)) { - return true; + 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}); } - const day = temp.dayBefore(startDay) ? 1 : -1; + rightShift = !disabledItemHandler(to.append({day: 1})); - temp = temp.append({day}); + if (rightShift) { + to = to.append({day: 1}); + } } - return false; + return new TuiDayRange(from, to); } }