diff --git a/projects/addon-mobile/components/mobile-calendar-dropdown/mobile-calendar-dropdown.component.ts b/projects/addon-mobile/components/mobile-calendar-dropdown/mobile-calendar-dropdown.component.ts
index 387dceab43df..75e9328b3855 100644
--- a/projects/addon-mobile/components/mobile-calendar-dropdown/mobile-calendar-dropdown.component.ts
+++ b/projects/addon-mobile/components/mobile-calendar-dropdown/mobile-calendar-dropdown.component.ts
@@ -66,7 +66,8 @@ export class TuiMobileCalendarDropdown {
protected readonly range = this.is('tui-input-date-range');
protected readonly multi = this.data.multi || this.is('tui-input-date[multiple]');
protected readonly single =
- this.data.single || this.is('tui-input-date:not([multiple])');
+ this.data.single || // TODO(v5): use `rangeMode` from DI token `TUI_CALENDAR_SHEET_DEFAULT_OPTIONS`
+ this.is('tui-input-date:not([multiple])');
constructor() {
this.keyboard.hide();
diff --git a/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts b/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts
index 492376605c4c..1704c84d794e 100644
--- a/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts
+++ b/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts
@@ -37,6 +37,7 @@ import {TuiMapperPipe} from '@taiga-ui/cdk/pipes/mapper';
import {TUI_IS_E2E, TUI_IS_IOS} from '@taiga-ui/cdk/tokens';
import type {TuiBooleanHandler, TuiMapper} from '@taiga-ui/cdk/types';
import {TuiButton} from '@taiga-ui/core/components/button';
+import {TUI_CALENDAR_SHEET_OPTIONS} from '@taiga-ui/core/components/calendar';
import {TuiLink} from '@taiga-ui/core/components/link';
import {TuiMonthPipe} from '@taiga-ui/core/pipes/month';
import {TuiOrderWeekDaysPipe} from '@taiga-ui/core/pipes/order-week-days';
@@ -153,8 +154,15 @@ export class TuiMobileCalendar implements AfterViewInit {
),
);
+ /**
+ * @deprecated use static DI options instead
+ * ```
+ * tuiCalendarSheetOptionsProvider({rangeMode: boolean})
+ * ```
+ * TODO(v5): delete it
+ */
@Input()
- public single = true;
+ public single = !inject(TUI_CALENDAR_SHEET_OPTIONS).rangeMode;
@Input()
public multi = false;
@@ -180,7 +188,7 @@ export class TuiMobileCalendar implements AfterViewInit {
public readonly valueChange = this.value$.pipe(
skip(1),
distinctUntilChanged((a, b) => a?.toString() === b?.toString()),
- takeUntilDestroyed(),
+ map((x) => (!this.single && x instanceof TuiDay ? new TuiDayRange(x, x) : x)),
);
constructor() {
@@ -245,10 +253,8 @@ export class TuiMobileCalendar implements AfterViewInit {
this.value = tuiToggleDay(this.value, day);
} else if (this.value instanceof TuiDay) {
this.value = TuiDayRange.sort(this.value, day);
- } else if (this.value instanceof TuiDayRange && !this.value.isSingleDay) {
- this.value = day;
} else if (this.value instanceof TuiDayRange) {
- this.value = TuiDayRange.sort(this.value.from, day);
+ this.value = day;
} else if (!this.value) {
this.value = day;
}
diff --git a/projects/addon-mobile/components/mobile-calendar/mobile-calendar.template.html b/projects/addon-mobile/components/mobile-calendar/mobile-calendar.template.html
index 0b724559abcf..88f4c72b438c 100644
--- a/projects/addon-mobile/components/mobile-calendar/mobile-calendar.template.html
+++ b/projects/addon-mobile/components/mobile-calendar/mobile-calendar.template.html
@@ -84,6 +84,7 @@
{{ month | tuiMonth | async }}
class="t-calendar"
[disabledItemHandler]="disabledItemHandler | tuiMapper: disabledItemHandlerMapper : min : max"
[month]="month"
+ [single]="single"
[value]="value"
(dayClick)="onDayClick($event)"
/>
diff --git a/projects/cdk/date-time/day-range.ts b/projects/cdk/date-time/day-range.ts
index 86a1443c2948..c91689484247 100644
--- a/projects/cdk/date-time/day-range.ts
+++ b/projects/cdk/date-time/day-range.ts
@@ -102,4 +102,19 @@ export class TuiDayRange extends TuiMonthRange {
): string {
return this.getFormattedDayRange(dateFormat, dateSeparator);
}
+
+ public toArray(): readonly TuiDay[] {
+ const {from, to} = this;
+ const arr = [];
+
+ for (
+ const day = from.toUtcNativeDate();
+ day <= to.toUtcNativeDate();
+ day.setDate(day.getDate() + 1)
+ ) {
+ arr.push(TuiDay.fromLocalNativeDate(day));
+ }
+
+ return arr;
+ }
}
diff --git a/projects/cdk/date-time/test/day-range.spec.ts b/projects/cdk/date-time/test/day-range.spec.ts
index 39734109f7c6..abd44b6afa50 100644
--- a/projects/cdk/date-time/test/day-range.spec.ts
+++ b/projects/cdk/date-time/test/day-range.spec.ts
@@ -183,6 +183,24 @@ describe('TuiDayRange', () => {
});
});
+ describe('toArray', () => {
+ it('returns array with all dates between `from` and `to` (including `from` and `to` day)', () => {
+ const range = new TuiDayRange(
+ new TuiDay(2024, 11, 29),
+ new TuiDay(2025, 0, 3),
+ );
+
+ expect(range.toArray()).toEqual([
+ new TuiDay(2024, 11, 29),
+ new TuiDay(2024, 11, 30),
+ new TuiDay(2024, 11, 31),
+ new TuiDay(2025, 0, 1),
+ new TuiDay(2025, 0, 2),
+ new TuiDay(2025, 0, 3),
+ ]);
+ });
+ });
+
describe('dayLimit', () => {
it('limits from one side if the other is null', () => {
const y2000m0d1 = new TuiDay(2000, 0, 1);
diff --git a/projects/core/components/calendar/calendar-sheet.component.ts b/projects/core/components/calendar/calendar-sheet.component.ts
index bb4cdb129b4c..7f1c13605eb1 100644
--- a/projects/core/components/calendar/calendar-sheet.component.ts
+++ b/projects/core/components/calendar/calendar-sheet.component.ts
@@ -18,6 +18,8 @@ import {tuiNullableSame, tuiPure} from '@taiga-ui/cdk/utils/miscellaneous';
import {TuiCalendarSheetPipe, TuiOrderWeekDaysPipe} from '@taiga-ui/core/pipes';
import {TUI_DAY_TYPE_HANDLER, TUI_SHORT_WEEK_DAYS} from '@taiga-ui/core/tokens';
+import {TUI_CALENDAR_SHEET_OPTIONS} from './calendar-sheet.options';
+
export type TuiMarkerHandler = TuiHandler;
@Component({
@@ -36,10 +38,11 @@ export type TuiMarkerHandler = TuiHandler();
@Output()
public readonly dayClick = new EventEmitter();
+ /**
+ * @deprecated TODO(v5): delete it. It is used nowhere except unit tests
+ */
public itemIsInterval(day: TuiDay): boolean {
const {value, hoveredItem} = this;
@@ -96,17 +112,25 @@ export class TuiCalendarSheet {
public getItemRange(item: TuiDay): 'active' | 'end' | 'middle' | 'start' | null {
const {value, hoveredItem} = this;
- if (value instanceof TuiDay) {
+ if (!value) {
+ return null;
+ }
+
+ if (value instanceof TuiDay && !this.computedRangeMode) {
return value.daySame(item) ? 'active' : null;
}
- if (!value || !(value instanceof TuiDayRange)) {
- return value?.find((day) => day.daySame(item)) ? 'active' : null;
+ if (value instanceof TuiDayRange && value.isSingleDay) {
+ return value.from.daySame(item) ? 'active' : null;
+ }
+
+ if (!(value instanceof TuiDay) && !(value instanceof TuiDayRange)) {
+ return value.find((day) => day.daySame(item)) ? 'active' : null;
}
const range = this.getRange(value, hoveredItem);
- if (value.isSingleDay && range.isSingleDay && value.from.daySame(item)) {
+ if (range.isSingleDay && range.from.daySame(item)) {
return 'active';
}
@@ -121,8 +145,18 @@ export class TuiCalendarSheet {
return range.from.dayBefore(item) && range.to.dayAfter(item) ? 'middle' : null;
}
- protected get isSingleDayRange(): boolean {
- return this.value instanceof TuiDayRange && this.value.isSingleDay;
+ protected get computedRangeMode(): boolean {
+ return !this.single || this.options.rangeMode;
+ }
+
+ protected get isRangePicking(): boolean {
+ return this.computedRangeMode
+ ? this.value instanceof TuiDay
+ : /**
+ * Only for backward compatibility!
+ * TODO(v5): replace with `this.options.rangeMode && this.value instanceof TuiDay`
+ */
+ this.value instanceof TuiDayRange && this.value.isSingleDay;
}
protected readonly toMarkers = (
@@ -157,7 +191,14 @@ export class TuiCalendarSheet {
}
@tuiPure
- private getRange(value: TuiDayRange, hoveredItem: TuiDay | null): TuiDayRange {
+ private getRange(
+ value: TuiDay | TuiDayRange,
+ hoveredItem: TuiDay | null,
+ ): TuiDayRange {
+ if (value instanceof TuiDay) {
+ return TuiDayRange.sort(value, hoveredItem ?? value);
+ }
+
return value.isSingleDay
? TuiDayRange.sort(value.from, hoveredItem ?? value.to)
: value;
@@ -173,20 +214,10 @@ export class TuiCalendarSheet {
}
private rangeHasDisabledDay(item: TuiDay): boolean {
- if (this.value instanceof TuiDayRange) {
- const range = this.getRange(this.value, item);
-
- for (
- const day = range.from.toUtcNativeDate();
- day <= range.to.toUtcNativeDate();
- day.setDate(day.getDate() + 1)
- ) {
- const tuiDay = TuiDay.fromLocalNativeDate(day);
-
- if (this.disabledItemHandler(tuiDay)) {
- return true;
- }
- }
+ if (this.isRangePicking && this.value instanceof TuiDay) {
+ return this.getRange(this.value, item)
+ .toArray()
+ .some((x) => this.disabledItemHandler(x));
}
return false;
diff --git a/projects/core/components/calendar/calendar-sheet.options.ts b/projects/core/components/calendar/calendar-sheet.options.ts
new file mode 100644
index 000000000000..179c9a3a76ff
--- /dev/null
+++ b/projects/core/components/calendar/calendar-sheet.options.ts
@@ -0,0 +1,24 @@
+import type {Provider} from '@angular/core';
+import {tuiCreateToken, tuiProvideOptions} from '@taiga-ui/cdk/utils/miscellaneous';
+
+export interface TuiCalendarSheetOptions {
+ readonly rangeMode: boolean;
+}
+
+export const TUI_CALENDAR_SHEET_DEFAULT_OPTIONS: TuiCalendarSheetOptions = {
+ rangeMode: false,
+};
+
+export const TUI_CALENDAR_SHEET_OPTIONS = tuiCreateToken(
+ TUI_CALENDAR_SHEET_DEFAULT_OPTIONS,
+);
+
+export function tuiCalendarSheetOptionsProvider(
+ options: Partial,
+): Provider {
+ return tuiProvideOptions(
+ TUI_CALENDAR_SHEET_OPTIONS,
+ options,
+ TUI_CALENDAR_SHEET_DEFAULT_OPTIONS,
+ );
+}
diff --git a/projects/core/components/calendar/index.ts b/projects/core/components/calendar/index.ts
index b6f2d29e18e6..ed9fbde1dadd 100644
--- a/projects/core/components/calendar/index.ts
+++ b/projects/core/components/calendar/index.ts
@@ -1,4 +1,5 @@
export * from './calendar.component';
export * from './calendar-sheet.component';
+export * from './calendar-sheet.options';
export * from './calendar-spin.component';
export * from './calendar-year.component';
diff --git a/projects/demo-playwright/tests/kit/calendar-range/calendar-range.pw.spec.ts b/projects/demo-playwright/tests/kit/calendar-range/calendar-range.pw.spec.ts
index 96d539ab38bc..c820a4553053 100644
--- a/projects/demo-playwright/tests/kit/calendar-range/calendar-range.pw.spec.ts
+++ b/projects/demo-playwright/tests/kit/calendar-range/calendar-range.pw.spec.ts
@@ -7,7 +7,10 @@ import {
import type {Locator} from '@playwright/test';
import {expect, test} from '@playwright/test';
-test.describe('CalendarRange', () => {
+const {describe, beforeEach} = test;
+
+describe('CalendarRange', () => {
+ const today = new Date(2020, 8, 25);
let example!: Locator;
let calendarRange!: TuiCalendarRangePO;
let documentationPage!: TuiDocumentationPagePO;
@@ -16,24 +19,22 @@ test.describe('CalendarRange', () => {
viewport: {width: 650, height: 650},
});
- test.beforeEach(async ({page}) => {
- await tuiGoto(page, DemoRoute.CalendarRange, {
- date: new Date(2020, 8, 25),
- });
-
+ beforeEach(({page}) => {
documentationPage = new TuiDocumentationPagePO(page);
- example = documentationPage.apiPageExample;
-
- calendarRange = new TuiCalendarRangePO(example.locator('tui-calendar-range'));
});
- test.describe('Examples', () => {
+ describe('Examples', () => {
+ beforeEach(async ({page}) => {
+ await tuiGoto(page, DemoRoute.CalendarRange, {
+ date: today,
+ });
+ });
+
test('With another range switcher', async () => {
example = documentationPage.getExample('#with-another-range-switcher');
calendarRange = new TuiCalendarRangePO(example.locator('tui-calendar-range'));
- const getRangeSwitcher = (): Locator =>
- example.locator('p button[data-appearance="action"]');
+ const resetButton = example.locator('p button[data-appearance="action"]');
await expect(example).toHaveScreenshot(
'05-calendar-range-correct-display-defaults-items-and-values-1.png',
@@ -46,36 +47,33 @@ test.describe('CalendarRange', () => {
'05-calendar-range-correct-display-range-switcher-after-select-week.png',
);
- await getRangeSwitcher().click();
+ await resetButton.click();
await expect(example).toHaveScreenshot(
'05-calendar-range-correct-display-items-and-values-after-click-on-month-range-switcher.png',
);
- await getRangeSwitcher().click();
+ await resetButton.click();
await expect(example).toHaveScreenshot(
'05-calendar-range-correct-display-items-and-values-after-click-on-quarter-range-switcher.png',
);
- await getRangeSwitcher().click();
+ await resetButton.click();
await expect(example).toHaveScreenshot(
'05-calendar-range-correct-display-defaults-items-and-values-2.png',
);
});
- test.describe('With value', () => {
+ describe('With value', () => {
test('Month switching via chevron', async ({page}) => {
example = documentationPage.getExample('#with-value');
calendarRange = new TuiCalendarRangePO(
example.locator('tui-calendar-range'),
);
- const getPreviousMonthChevron = (): Locator =>
- example.locator('tui-spin-button > button').first();
-
- await getPreviousMonthChevron().click();
+ await calendarRange.previousMonth.click();
await page.mouse.click(100, 100); // clear focus
@@ -86,7 +84,18 @@ test.describe('CalendarRange', () => {
});
});
- test.describe('API', () => {
+ describe('API', () => {
+ beforeEach(async ({page}) => {
+ await tuiGoto(page, DemoRoute.CalendarRange, {
+ date: today,
+ });
+
+ documentationPage = new TuiDocumentationPagePO(page);
+ example = documentationPage.apiPageExample;
+
+ calendarRange = new TuiCalendarRangePO(example.locator('tui-calendar-range'));
+ });
+
test('Maximum month when items not empty', async ({page}) => {
await tuiGoto(page, `${DemoRoute.CalendarRange}/API?items$=1&max$=4`);
@@ -119,5 +128,89 @@ test.describe('CalendarRange', () => {
await expect(example).toHaveScreenshot('08-disabled-dates-5-click-to.png');
});
+
+ describe('Selecting range consisting of the same start/end date', () => {
+ let alert!: Locator;
+
+ beforeEach(({page}) => {
+ alert = page.locator('tui-alerts tui-notification');
+ });
+
+ test('double click on the same day - selects single-day range', async ({
+ page,
+ }) => {
+ await tuiGoto(page, `${DemoRoute.CalendarRange}/API`);
+
+ const [calendarSheet] = await calendarRange
+ .getCalendars()
+ .then(async ([x]) => x.getCalendarSheets());
+
+ await calendarSheet.clickOnDay(15);
+
+ await expect(alert).not.toBeAttached();
+
+ await calendarSheet.clickOnDay(15);
+
+ await expect(alert).toContainText(
+ '{from: {year: 2020, month: 8, day: 15}, to: {year: 2020, month: 8, day: 15}}',
+ );
+ await expect(example).toHaveScreenshot(
+ '07-double-click-on-the-same-day.png',
+ );
+ });
+
+ test('allows to select new range start after double click on the same day', async ({
+ page,
+ }) => {
+ await tuiGoto(page, `${DemoRoute.CalendarRange}/API`);
+
+ const [calendarSheet] = await calendarRange
+ .getCalendars()
+ .then(async ([x]) => x.getCalendarSheets());
+
+ await calendarSheet.clickOnDay(15);
+ await calendarSheet.clickOnDay(15);
+
+ await calendarSheet.clickOnDay(22);
+
+ await expect(alert).not.toBeAttached();
+
+ await calendarSheet.clickOnDay(25);
+
+ await expect(alert).toContainText(
+ '{from: {year: 2020, month: 8, day: 22}, to: {year: 2020, month: 8, day: 25}}',
+ );
+ await expect(example).toHaveScreenshot(
+ '08-new-range-after-double-click-on-the-same-day.png',
+ );
+ });
+
+ test('no highlighting hover effect after double click on the same day', async ({
+ page,
+ }) => {
+ await tuiGoto(page, `${DemoRoute.CalendarRange}/API`);
+
+ const [calendarSheet] = await calendarRange
+ .getCalendars()
+ .then(async ([x]) => x.getCalendarSheets());
+
+ await calendarSheet.clickOnDay(15);
+ await calendarSheet.getCalendarDay(20).then(async (x) => x!.hover());
+
+ await expect(example).toHaveScreenshot('09-1-has-hover-effect.png');
+
+ await calendarSheet.clickOnDay(15);
+
+ await calendarSheet.getCalendarDay(22).then(async (x) => x!.hover());
+
+ await expect(example).toHaveScreenshot('09-2-no-hover-effect.png');
+
+ await calendarSheet.clickOnDay(22);
+
+ await calendarSheet.getCalendarDay(25).then(async (x) => x!.hover());
+
+ await expect(example).toHaveScreenshot('09-3-has-hover-effect.png');
+ });
+ });
});
});
diff --git a/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts b/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts
index 360ce4e37e04..5575e6671958 100644
--- a/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts
+++ b/projects/demo-playwright/tests/legacy/input-date-range/input-date-range.pw.spec.ts
@@ -13,7 +13,9 @@ import {
TuiMobileCalendarPO,
} from '../../../utils';
-test.describe('InputDateRange', () => {
+const {describe, beforeEach} = test;
+
+describe('InputDateRange', () => {
let inputDateRange!: TuiInputDateRangePO;
let documentationPage!: TuiDocumentationPagePO;
@@ -21,16 +23,16 @@ test.describe('InputDateRange', () => {
viewport: {width: 650, height: 650},
});
- test.beforeEach(({page}) => {
+ beforeEach(({page}) => {
documentationPage = new TuiDocumentationPagePO(page);
});
- test.describe('API', () => {
+ describe('API', () => {
let example!: Locator;
let calendar!: TuiCalendarPO;
- test.beforeEach(() => {
+ beforeEach(() => {
example = documentationPage.apiPageExample;
inputDateRange = new TuiInputDateRangePO(
@@ -120,7 +122,7 @@ test.describe('InputDateRange', () => {
);
});
- test.describe('prevents changes if you enter an invalid date', () => {
+ describe('prevents changes if you enter an invalid date', () => {
test('day > 31', async ({page}) => {
await tuiGoto(page, `${DemoRoute.InputDateRange}/API`);
@@ -183,6 +185,7 @@ test.describe('InputDateRange', () => {
await inputDateRange.textfield.click();
await calendarSheet.clickOnDay(21);
+ await calendarSheet.clickOnDay(25);
await expect(inputDateRange.textfield).toHaveValue(
`21.09.2020${CHAR_NO_BREAK_SPACE}–${CHAR_NO_BREAK_SPACE}25.09.2020`,
@@ -230,12 +233,12 @@ test.describe('InputDateRange', () => {
);
});
- test.describe('Mobile emulation', () => {
+ describe('Mobile emulation', () => {
test.use(TUI_PLAYWRIGHT_MOBILE);
let mobileCalendar!: TuiMobileCalendarPO;
- test.beforeEach(() => {
+ beforeEach(() => {
mobileCalendar = new TuiMobileCalendarPO(inputDateRange.calendar);
});
@@ -255,10 +258,108 @@ test.describe('InputDateRange', () => {
);
});
});
+
+ describe('Selecting range consisting of the same start/end date', () => {
+ test('double click on the same day - selects single-day range', async ({
+ page,
+ }) => {
+ await tuiGoto(page, `${DemoRoute.InputDateRange}/API`);
+
+ await inputDateRange.textfield.click();
+
+ const calendarSheet = new TuiCalendarSheetPO(
+ inputDateRange.calendar.locator('tui-calendar-sheet'),
+ );
+
+ await calendarSheet.clickOnDay(15);
+
+ await expect(inputDateRange.textfield).toHaveValue('');
+
+ await calendarSheet.clickOnDay(15);
+
+ await expect(inputDateRange.textfield).toHaveValue(
+ '15.09.2020 – 15.09.2020',
+ );
+ });
+
+ test('allows to select new range start after double click on the same day', async ({
+ page,
+ }) => {
+ await tuiGoto(page, `${DemoRoute.InputDateRange}/API`);
+
+ await inputDateRange.textfield.click();
+
+ const calendarSheet = new TuiCalendarSheetPO(
+ inputDateRange.calendar.locator('tui-calendar-sheet'),
+ );
+
+ await calendarSheet.clickOnDay(15);
+ await calendarSheet.clickOnDay(15);
+
+ await expect(inputDateRange.calendar).not.toBeAttached();
+
+ await inputDateRange.textfield.click();
+
+ await expect(inputDateRange.calendar).toBeAttached();
+
+ await calendarSheet.clickOnDay(22);
+
+ await expect(inputDateRange.textfield).toHaveValue(
+ '15.09.2020 – 15.09.2020',
+ );
+
+ await calendarSheet.clickOnDay(25);
+
+ await expect(inputDateRange.textfield).toHaveValue(
+ '22.09.2020 – 25.09.2020',
+ );
+ });
+
+ test('no highlighting hover effect after double click on the same day', async ({
+ page,
+ }) => {
+ await tuiGoto(page, `${DemoRoute.InputDateRange}/API`);
+
+ await inputDateRange.textfield.click();
+
+ const calendarSheet = new TuiCalendarSheetPO(
+ inputDateRange.calendar.locator('tui-calendar-sheet'),
+ );
+
+ await calendarSheet.clickOnDay(15);
+ await calendarSheet.getCalendarDay(20).then(async (x) => x!.hover());
+
+ await expect(inputDateRange.calendar).toHaveScreenshot(
+ '12-1-has-hover-effect.png',
+ );
+
+ await calendarSheet.clickOnDay(15);
+
+ await expect(inputDateRange.textfield).toHaveValue(
+ '15.09.2020 – 15.09.2020',
+ );
+ await expect(inputDateRange.calendar).not.toBeAttached();
+
+ await inputDateRange.textfield.click();
+ await calendarSheet.getCalendarDay(22).then(async (x) => x!.hover());
+
+ await expect(inputDateRange.calendar).toHaveScreenshot(
+ '12-2-no-hover-effect.png',
+ );
+
+ await calendarSheet.clickOnDay(22);
+
+ await calendarSheet.getCalendarDay(25).then(async (x) => x!.hover());
+
+ await expect(inputDateRange.calendar).toHaveScreenshot(
+ '12-3-has-hover-effect.png',
+ );
+ });
+ });
});
- test.describe('Examples', () => {
- test.beforeEach(async ({page}) => {
+ describe('Examples', () => {
+ beforeEach(async ({page}) => {
await tuiGoto(page, DemoRoute.InputDateRange);
});
diff --git a/projects/demo-playwright/utils/page-objects/calendar-range.po.ts b/projects/demo-playwright/utils/page-objects/calendar-range.po.ts
index e3be75cf15af..dc33a9883fc6 100644
--- a/projects/demo-playwright/utils/page-objects/calendar-range.po.ts
+++ b/projects/demo-playwright/utils/page-objects/calendar-range.po.ts
@@ -1,9 +1,29 @@
import type {Locator} from '@playwright/test';
import {expect} from '@playwright/test';
+import {TuiCalendarPO} from './calendar.po';
+
export class TuiCalendarRangePO {
+ public previousMonth = this.host
+ .locator('tui-calendar-spin tui-spin-button > button')
+ .first();
+
+ public nextMonth = this.host
+ .locator('tui-calendar-spin tui-spin-button > button')
+ .last();
+
constructor(private readonly host: Locator) {}
+ public async getCalendars(): Promise<
+ [TuiCalendarPO, TuiCalendarPO] | [TuiCalendarPO]
+ > {
+ const calendars = await this.host.locator('tui-calendar').all();
+
+ return calendars.map((x) => new TuiCalendarPO(x)) as
+ | [TuiCalendarPO, TuiCalendarPO]
+ | [TuiCalendarPO];
+ }
+
public async getItems(): Promise {
const dataList = this.host.locator('[automation-id="tui-calendar-range__menu"]');
diff --git a/projects/demo-playwright/utils/page-objects/calendar-sheet.po.ts b/projects/demo-playwright/utils/page-objects/calendar-sheet.po.ts
index 7e222fe0b480..a6d552e463cf 100644
--- a/projects/demo-playwright/utils/page-objects/calendar-sheet.po.ts
+++ b/projects/demo-playwright/utils/page-objects/calendar-sheet.po.ts
@@ -11,15 +11,21 @@ export class TuiCalendarSheetPO {
.all();
}
- public async clickOnDay(day: number): Promise {
+ public async getCalendarDay(day: number): Promise {
const cells = await this.getDays();
for (const cell of cells) {
if ((await cell.textContent())?.trim() === day.toString()) {
- await cell.click();
-
- break;
+ return cell;
}
}
+
+ return null;
+ }
+
+ public async clickOnDay(day: number): Promise {
+ const element = await this.getCalendarDay(day);
+
+ return element!.click();
}
}
diff --git a/projects/demo-playwright/utils/page-objects/calendar.po.ts b/projects/demo-playwright/utils/page-objects/calendar.po.ts
index af10c5f2e800..99aa79154544 100644
--- a/projects/demo-playwright/utils/page-objects/calendar.po.ts
+++ b/projects/demo-playwright/utils/page-objects/calendar.po.ts
@@ -9,12 +9,17 @@ export class TuiCalendarPO {
constructor(private readonly host: Locator) {}
- public async getCalendarSheets(): Promise {
+ public async getCalendarSheets(): Promise<
+ [TuiCalendarSheetPO, ...TuiCalendarSheetPO[]]
+ > {
const locators = await this.host
.page()
.locator('tui-calendar-sheet, tui-mobile-calendar-sheet')
.all();
- return locators.map((x) => new TuiCalendarSheetPO(x));
+ return locators.map((x) => new TuiCalendarSheetPO(x)) as [
+ TuiCalendarSheetPO,
+ ...TuiCalendarSheetPO[],
+ ];
}
}
diff --git a/projects/demo/src/modules/components/mobile-calendar/examples/2/index.html b/projects/demo/src/modules/components/mobile-calendar/examples/2/index.html
index cc76cca398e5..8633812836bd 100644
--- a/projects/demo/src/modules/components/mobile-calendar/examples/2/index.html
+++ b/projects/demo/src/modules/components/mobile-calendar/examples/2/index.html
@@ -2,6 +2,5 @@
diff --git a/projects/demo/src/modules/components/mobile-calendar/examples/2/index.ts b/projects/demo/src/modules/components/mobile-calendar/examples/2/index.ts
index deb96ff91b21..9a1f4a11aaad 100644
--- a/projects/demo/src/modules/components/mobile-calendar/examples/2/index.ts
+++ b/projects/demo/src/modules/components/mobile-calendar/examples/2/index.ts
@@ -3,6 +3,7 @@ import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiMobileCalendar} from '@taiga-ui/addon-mobile';
import {TuiDay} from '@taiga-ui/cdk';
+import {tuiCalendarSheetOptionsProvider} from '@taiga-ui/core';
@Component({
standalone: true,
@@ -11,6 +12,7 @@ import {TuiDay} from '@taiga-ui/cdk';
styleUrls: ['./index.less'],
encapsulation,
changeDetection,
+ providers: [tuiCalendarSheetOptionsProvider({rangeMode: true})],
})
export default class Example {
protected min = new TuiDay(new Date().getFullYear(), new Date().getMonth(), 1);
diff --git a/projects/demo/src/modules/components/mobile-calendar/examples/import/template.md b/projects/demo/src/modules/components/mobile-calendar/examples/import/template.md
index 61769426df3f..dab883b09668 100644
--- a/projects/demo/src/modules/components/mobile-calendar/examples/import/template.md
+++ b/projects/demo/src/modules/components/mobile-calendar/examples/import/template.md
@@ -2,7 +2,6 @@
```
diff --git a/projects/demo/src/modules/components/mobile-calendar/index.html b/projects/demo/src/modules/components/mobile-calendar/index.html
index 39d736dd4d9b..57fc7bf40d83 100644
--- a/projects/demo/src/modules/components/mobile-calendar/index.html
+++ b/projects/demo/src/modules/components/mobile-calendar/index.html
@@ -121,14 +121,6 @@
>
Min date
-
- Single date or a range
-
"Confirm" button clicked
+
+ Single date or a range
+
+
+ Use
+ tuiCalendarSheetOptionsProvider({rangeMode: boolean"})
+ instead
+
+
diff --git a/projects/demo/src/modules/components/mobile-calendar/index.less b/projects/demo/src/modules/components/mobile-calendar/index.less
index 1c577cfeef28..b69c1585b79b 100644
--- a/projects/demo/src/modules/components/mobile-calendar/index.less
+++ b/projects/demo/src/modules/components/mobile-calendar/index.less
@@ -1,3 +1,3 @@
.calendar {
- block-size: 28.75rem;
+ block-size: min(28.75rem, 60vh);
}
diff --git a/projects/kit/components/calendar-range/calculate-disabled-item-handler.ts b/projects/kit/components/calendar-range/calculate-disabled-item-handler.ts
index 970ad1f909a1..a96449f92141 100644
--- a/projects/kit/components/calendar-range/calculate-disabled-item-handler.ts
+++ b/projects/kit/components/calendar-range/calculate-disabled-item-handler.ts
@@ -1,20 +1,21 @@
-import type {TuiDay, TuiDayLike, TuiDayRange} from '@taiga-ui/cdk/date-time';
+import type {TuiDay, TuiDayLike} from '@taiga-ui/cdk/date-time';
+import {TuiDayRange} from '@taiga-ui/cdk/date-time';
import type {TuiBooleanHandler} from '@taiga-ui/cdk/types';
export const calculateDisabledItemHandler: (
disabledItemHandler: TuiBooleanHandler,
- value: TuiDayRange | null,
+ value: TuiDay | TuiDayRange | null,
minLength: TuiDayLike | null,
) => TuiBooleanHandler = (disabledItemHandler, value, minLength) => (item) => {
- if (!value?.isSingleDay || !minLength) {
+ if (!value || value instanceof TuiDayRange || !minLength) {
return disabledItemHandler(item);
}
const negativeMinLength = Object.fromEntries(
Object.entries(minLength).map(([key, value]) => [key, -value]),
);
- const disabledBefore = value.from.append(negativeMinLength).append({day: 1});
- const disabledAfter = value.from.append(minLength).append({day: -1});
+ const disabledBefore = value.append(negativeMinLength).append({day: 1});
+ const disabledAfter = value.append(minLength).append({day: -1});
const inDisabledRange =
disabledBefore.dayBefore(item) && disabledAfter.dayAfter(item);
diff --git a/projects/kit/components/calendar-range/calendar-range.component.ts b/projects/kit/components/calendar-range/calendar-range.component.ts
index 37942240e629..a809874e3100 100644
--- a/projects/kit/components/calendar-range/calendar-range.component.ts
+++ b/projects/kit/components/calendar-range/calendar-range.component.ts
@@ -10,10 +10,11 @@ import {
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {TUI_FALSE_HANDLER} from '@taiga-ui/cdk/constants';
-import type {TuiDay, TuiDayLike} from '@taiga-ui/cdk/date-time';
+import type {TuiDayLike} from '@taiga-ui/cdk/date-time';
import {
TUI_FIRST_DAY,
TUI_LAST_DAY,
+ TuiDay,
TuiDayRange,
TuiMonth,
} from '@taiga-ui/cdk/date-time';
@@ -22,7 +23,10 @@ import {TuiMapperPipe} from '@taiga-ui/cdk/pipes/mapper';
import type {TuiBooleanHandler, TuiMapper} from '@taiga-ui/cdk/types';
import {tuiIsString, tuiNullableSame, tuiPure} from '@taiga-ui/cdk/utils/miscellaneous';
import type {TuiMarkerHandler} from '@taiga-ui/core/components/calendar';
-import {TuiCalendar} from '@taiga-ui/core/components/calendar';
+import {
+ TuiCalendar,
+ tuiCalendarSheetOptionsProvider,
+} from '@taiga-ui/core/components/calendar';
import {TuiDataList} from '@taiga-ui/core/components/data-list';
import {TuiIcon} from '@taiga-ui/core/components/icon';
import {TUI_COMMON_ICONS} from '@taiga-ui/core/tokens';
@@ -40,6 +44,7 @@ import type {TuiDayRangePeriod} from './day-range-period';
templateUrl: './calendar-range.template.html',
styleUrls: ['./calendar-range.style.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [tuiCalendarSheetOptionsProvider({rangeMode: true})],
host: {
'(document:keydown.capture)': 'onEsc($event)',
},
@@ -50,7 +55,8 @@ export class TuiCalendarRange implements OnInit, OnChanges {
*/
private selectedPeriod: TuiDayRangePeriod | null = null;
- protected previousValue: TuiDayRange | null = null;
+ protected value: TuiDay | TuiDayRange | null = null;
+ protected previousValue: TuiDay | TuiDayRange | null = null;
protected hoveredItem: TuiDay | null = null;
protected month: TuiMonth = TuiMonth.currentLocal();
@@ -79,9 +85,6 @@ export class TuiCalendarRange implements OnInit, OnChanges {
@Input()
public maxLength: TuiDayLike | null = null;
- @Input()
- public value: TuiDayRange | null = null;
-
@Input()
public item: TuiDayRangePeriod | null = null;
@@ -100,6 +103,11 @@ export class TuiCalendarRange implements OnInit, OnChanges {
});
}
+ @Input('value')
+ public set valueSetter(value: TuiDayRange | null) {
+ this.value = value;
+ }
+
@Input()
public set defaultViewedMonth(month: TuiMonth) {
if (!this.value) {
@@ -144,7 +152,7 @@ export class TuiCalendarRange implements OnInit, OnChanges {
}
protected onEsc(event: KeyboardEvent): void {
- if (event.key !== 'Escape' || !this.value?.isSingleDay) {
+ if (event.key !== 'Escape' || !(this.value instanceof TuiDay)) {
return;
}
@@ -212,14 +220,14 @@ export class TuiCalendarRange implements OnInit, OnChanges {
this.previousValue = this.value;
this.selectedActivePeriod = null;
- if (!this.value?.isSingleDay) {
- this.value = new TuiDayRange(day, day);
- this.itemChange.emit(this.findItemByDayRange(this.value));
- } else {
- const sortedDayRange = TuiDayRange.sort(this.value.from, day);
+ if (this.value instanceof TuiDay) {
+ const range = TuiDayRange.sort(this.value, day);
- this.updateValue(sortedDayRange);
- this.itemChange.emit(this.findItemByDayRange(sortedDayRange));
+ this.value = range;
+ this.updateValue(range);
+ this.itemChange.emit(this.findItemByDayRange(range));
+ } else {
+ this.value = day;
}
}
@@ -234,7 +242,9 @@ export class TuiCalendarRange implements OnInit, OnChanges {
this.selectedActivePeriod ??
(this.items.find((item) =>
tuiNullableSame(
- this.value,
+ this.value instanceof TuiDay
+ ? new TuiDayRange(this.value, this.value)
+ : this.value,
item.range,
(a, b) =>
a.from.daySame(b.from.dayLimit(this.min, this.max)) &&
@@ -248,14 +258,16 @@ export class TuiCalendarRange implements OnInit, OnChanges {
@tuiPure
private calculateDisabledItemHandler(
disabledItemHandler: TuiBooleanHandler,
- value: TuiDayRange | null,
+ value: TuiDay | TuiDayRange | null,
minLength: TuiDayLike | null,
): TuiBooleanHandler {
return calculateDisabledItemHandler(disabledItemHandler, value, minLength);
}
private initDefaultViewedMonth(): void {
- if (this.value) {
+ if (this.value instanceof TuiDay) {
+ this.month = this.value;
+ } else if (this.value) {
this.month = this.items.length ? this.value.to : this.value.from;
} else if (this.max && this.month.monthSameOrAfter(this.max)) {
this.month = this.items.length ? this.max : this.max.append({month: -1});
diff --git a/projects/kit/components/calendar-range/day-caps-mapper.ts b/projects/kit/components/calendar-range/day-caps-mapper.ts
index b68eff5d9651..62dde2a22575 100644
--- a/projects/kit/components/calendar-range/day-caps-mapper.ts
+++ b/projects/kit/components/calendar-range/day-caps-mapper.ts
@@ -1,12 +1,12 @@
-import type {TuiDay, TuiDayLike, TuiDayRange} from '@taiga-ui/cdk/date-time';
-import {TUI_FIRST_DAY, TUI_LAST_DAY} from '@taiga-ui/cdk/date-time';
+import type {TuiDayLike, TuiDayRange} from '@taiga-ui/cdk/date-time';
+import {TUI_FIRST_DAY, TUI_LAST_DAY, TuiDay} from '@taiga-ui/cdk/date-time';
import type {TuiMapper} from '@taiga-ui/cdk/types';
export const TUI_DAY_CAPS_MAPPER: TuiMapper<
- [TuiDay | null, TuiDayRange | null, TuiDayLike | null, boolean],
+ [TuiDay | null, TuiDay | TuiDayRange | null, TuiDayLike | null, boolean],
TuiDay
> = (current, value, maxLength, backwards) => {
- if (!value?.isSingleDay || !maxLength) {
+ if (value instanceof TuiDay || !value?.isSingleDay || !maxLength) {
return backwards ? current || TUI_FIRST_DAY : current || TUI_LAST_DAY;
}