diff --git a/apps/ngx-bootstrap-docs-e2e/src/full/datepicker_page.spec.ts b/apps/ngx-bootstrap-docs-e2e/src/full/datepicker_page.spec.ts
new file mode 100644
index 0000000000..c8475bd163
--- /dev/null
+++ b/apps/ngx-bootstrap-docs-e2e/src/full/datepicker_page.spec.ts
@@ -0,0 +1,65 @@
+import { test as base } from '@playwright/test';
+import { DatepickerPwPo } from '../support/datepicker.pw.po';
+
+const test = base.extend<{ datepickerPo: DatepickerPwPo }>({
+ datepickerPo: async ({ page }, use) => {
+ const datepickerPo = new DatepickerPwPo(page);
+ await use(datepickerPo);
+ },
+});
+test.describe('Datepicker page testing suite', () => {
+ let tabSelector: string;
+
+ test.beforeEach(async ({ datepickerPo }) => {
+ tabSelector = datepickerPo.getTabSelector('Overview');
+ await datepickerPo.navigateTo();
+ });
+
+ test.describe('Unlinked calendar', () => {
+ let unlinkedCalendars: string;
+
+ test.beforeEach(async ({ datepickerPo }) => {
+ unlinkedCalendars = tabSelector + datepickerPo.exampleDemosArr.unlinkedCalendars;
+ await datepickerPo.scrollToMenu('Unlinked calendars');
+ });
+
+ test('click should open two calendars', async ({ datepickerPo }) => {
+ await datepickerPo.clickOnDaterangepickerInput(unlinkedCalendars, 0);
+ await datepickerPo.waitForElementToBeVisible('bs-daterangepicker-container');
+ await datepickerPo.expectItemVisible('.bs-datepicker-body', 0);
+ await datepickerPo.expectItemVisible('.bs-datepicker-body', 1);
+ });
+
+ test('when user goes to previous in the left calendar, right one stays the same', async ({ datepickerPo }) => {
+ await datepickerPo.clickOnDaterangepickerInput(unlinkedCalendars, 0);
+ await datepickerPo.waitForElementToBeVisible('bs-daterangepicker-container');
+ await datepickerPo.expectTextInViewInTheHeader(0, 0, 'October');
+ await datepickerPo.expectTextInViewInTheHeader(0, 1, '1979');
+ await datepickerPo.expectTextInViewInTheHeader(1, 0, 'April');
+ await datepickerPo.expectTextInViewInTheHeader(1, 1, '1985');
+ await datepickerPo.clickOnNavigation(0, '<');
+ await datepickerPo.expectTextInViewInTheHeader(0, 0, 'September');
+ await datepickerPo.expectTextInViewInTheHeader(0, 1, '1979');
+ await datepickerPo.expectTextInViewInTheHeader(1, 0, 'April');
+ await datepickerPo.expectTextInViewInTheHeader(1, 1, '1985');
+ });
+
+ test('when user changes mode to month on the calendar, right one stays the same', async ({ datepickerPo }) => {
+ await datepickerPo.clickOnDaterangepickerInput(unlinkedCalendars, 0);
+ await datepickerPo.waitForElementToBeVisible('bs-daterangepicker-container');
+ await datepickerPo.expectTextInViewInTheHeader(0, 0, 'October');
+ await datepickerPo.expectTextInViewInTheHeader(0, 1, '1979');
+ await datepickerPo.expectTextInViewInTheHeader(1, 0, 'April');
+ await datepickerPo.expectTextInViewInTheHeader(1, 1, '1985');
+ await datepickerPo.clickOnNavigation(0, 'month');
+ await datepickerPo.expectTextInViewInTheHeader(0, 0, '1979');
+ await datepickerPo.expectTextInViewInTheBody(0, 'October', true);
+ await datepickerPo.expectTextInViewInTheBody(0, 'September', true);
+ await datepickerPo.expectTextInViewInTheHeader(1, 0, 'April');
+ await datepickerPo.expectTextInViewInTheHeader(1, 1, '1985');
+ await datepickerPo.expectTextInViewInTheBody(1, 'October', false);
+ await datepickerPo.expectTextInViewInTheBody(1, 'September', false);
+ });
+ });
+
+});
diff --git a/apps/ngx-bootstrap-docs-e2e/src/support/datepicker.pw.po.ts b/apps/ngx-bootstrap-docs-e2e/src/support/datepicker.pw.po.ts
new file mode 100644
index 0000000000..5880264010
--- /dev/null
+++ b/apps/ngx-bootstrap-docs-e2e/src/support/datepicker.pw.po.ts
@@ -0,0 +1,108 @@
+// Todo: remove eslint-disable
+/* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/no-explicit-any */
+import { BasePo } from './base.po';
+import { expect } from '@playwright/test';
+
+export class DatepickerPwPo extends BasePo {
+ override pageUrl = '/ngx-bootstrap/components/datepicker';
+ pageTitle = 'Datepicker';
+ ghLinkToComponent = 'https://github.com/valor-software/ngx-bootstrap/tree/development/src/datepicker';
+
+ datepickerInput = 'input[bsdatepicker]';
+ daterangepickerInput = 'input[bsdaterangepicker]';
+ datepickerNavView = 'bs-datepicker-navigation-view';
+ datepickerContainer = 'bs-datepicker-container';
+ datepickerInlineContainer = 'bs-datepicker-inline-container';
+ daterangepickerContainer = 'bs-daterangepicker-container';
+ datepickerBodyDaysView = 'bs-days-calendar-view';
+ datepickerBodyMonthView = 'bs-month-calendar-view';
+ datepickerBodyYearsView = 'bs-years-calendar-view';
+ daterangepickerQuickSelectContainer = 'bs-custom-date-view';
+ monthNames = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ 'January'
+ ];
+ locales = [];
+
+ exampleDemosArr = {
+ unlinkedCalendars: ' datepicker-unlinked-calendar-views'
+ };
+
+ async clickOnDatepickerInput(baseSelector: string, datepickerIndex = 0) {
+ const datepicker = this.page.locator(`${baseSelector} ${this.datepickerInput}`).nth(datepickerIndex);
+ await datepicker.waitFor({state: 'visible', timeout: 10000});
+ await datepicker.click();
+ }
+
+ async clickOnDaterangepickerInput(baseSelector: string, dateRangeIndex = 0) {
+ const datepicker = this.page.locator(`${baseSelector} ${this.daterangepickerInput}`).nth(dateRangeIndex);
+ await datepicker.waitFor({state: 'visible', timeout: 10000});
+ await datepicker.click();
+ }
+ async waitForElementToBeVisible(selector: string) {
+ await this.page.waitForSelector(selector, { state: 'visible', timeout: 10000 });
+ }
+ async expectItemVisible(selector: string, index: number) {
+ await expect(await this.page.locator(selector).nth(index)).toBeVisible({visible: true, timeout: 10000});
+ }
+
+ async expectTextInViewInTheHeader(calendarIndex: number, currentIndex: number, text: string) {
+ // this is needed so it won't return up with the inline range pickers that are also on the page
+ const mainPopup = await this.page.locator('bs-daterangepicker-container');
+ const header = await mainPopup.locator('.bs-datepicker-head').nth(calendarIndex);
+ await expect(header).toBeVisible({visible: true, timeout: 10000});
+ const current = await header.locator('.current').nth(currentIndex);
+ await expect(current).toBeVisible({visible: true, timeout: 10000});
+ const button = await current.locator('span');
+ await expect(button).toBeVisible({visible: true, timeout: 10000});
+ await expect(button).toHaveText(text);
+ }
+
+ async clickOnNavigation(dateRangeIndex = 0, navigationItem: '<' | '>' | 'month' | 'year' ) {
+ const mainPopup = await this.page.locator('bs-daterangepicker-container');
+ const datepicker = await mainPopup.locator(`.bs-datepicker-head`).nth(dateRangeIndex);
+ switch (navigationItem) {
+ case '<':
+ await datepicker.locator('.previous').click();
+ break;
+
+ case '>':
+ await datepicker.locator('.next').click();
+ break;
+
+ case 'month':
+ await datepicker.locator('.current').nth(0).click();
+ break;
+
+ case 'year':
+ await datepicker.locator('.current').nth(1).click();
+ break;
+
+ default:
+ throw new Error('Unknown navigation item, correct: <, >, month, year');
+ }
+ await this.page.waitForTimeout(200); // waiting for the navigation to happen
+ }
+ async expectTextInViewInTheBody(calendarIndex: number, text: string, visible: boolean) {
+ // this is needed so it won't return up with the inline range pickers that are also on the page
+ const mainPopup = await this.page.locator('bs-daterangepicker-container');
+ const body = await mainPopup.locator('.bs-datepicker-body').nth(calendarIndex);
+ if (visible) {
+ await expect(body.getByText(text).first()).toBeVisible();
+ } else {
+ await expect(body.getByText(text)).toHaveCount(0);
+ }
+ }
+
+}
diff --git a/apps/ngx-bootstrap-docs/src/ng-api-doc.ts b/apps/ngx-bootstrap-docs/src/ng-api-doc.ts
index 507066b4ad..b5b65d2ca7 100644
--- a/apps/ngx-bootstrap-docs/src/ng-api-doc.ts
+++ b/apps/ngx-bootstrap-docs/src/ng-api-doc.ts
@@ -742,6 +742,12 @@ export const ngdoc: any = {
defaultValue: 'false',
type: 'boolean',
description: '
Shows timepicker under datepicker
\n'
+ },
+ {
+ name: 'unlinkedCalendars',
+ defaultValue: 'false',
+ type: 'boolean',
+ description: 'Allow date range picker to switch the calendars separately
\n'
}
]
},
diff --git a/libs/doc-pages/datepicker/src/lib/datepicker-section.list.ts b/libs/doc-pages/datepicker/src/lib/datepicker-section.list.ts
index 8544ebeb82..791b3e0950 100644
--- a/libs/doc-pages/datepicker/src/lib/datepicker-section.list.ts
+++ b/libs/doc-pages/datepicker/src/lib/datepicker-section.list.ts
@@ -53,6 +53,7 @@ import { DemoDatepickerPreventChangeToNextMonthComponent } from './demos/prevent
import { DemoDatepickerWithTimepickerComponent } from './demos/with-timepicker/with-timepicker';
import { DatepickerCloseBehaviorComponent } from './demos/closeBehaviour/datepicker-close-behavior';
import { KeepDatesOutOfRulesComponent } from './demos/keep-dates-out-of-rules/keep-dates-out-of-rules.component';
+import { UnlinkedCalendarsComponent } from './demos/unlinked-calendar-views/unlinked-calendar-views.component';
export const demoComponentContent: ContentSection[] = [
{
@@ -478,6 +479,14 @@ export const demoComponentContent: ContentSection[] = [
html: require('!!raw-loader!./demos/keep-dates-out-of-rules/keep-dates-out-of-rules.component.html'),
description: `If you use datepicker with rules (minDate, maxDate) you can set config property keepDatesOutOfRules to true to avoid overwriting invalid dates. Default value is false.
`,
outlet: KeepDatesOutOfRulesComponent
+ },
+ {
+ title: "Unlinked calendars",
+ anchor: 'unlinked-calendar-views',
+ component: require('!!raw-loader!./demos/unlinked-calendar-views/unlinked-calendar-views.component'),
+ html: require('!!raw-loader!./demos/unlinked-calendar-views/unlinked-calendar-views.component.html'),
+ description: `If you use daterangepicker and don't want the calendars to move together you can set config property unlinkedCalendars to true. Default value is false.
`,
+ outlet: UnlinkedCalendarsComponent
}
]
},
@@ -737,6 +746,11 @@ export const demoComponentContent: ContentSection[] = [
anchor: 'keep-dates-out-of-rules',
outlet: KeepDatesOutOfRulesComponent
},
+ {
+ title: "Unlinked calendars",
+ anchor: 'unlinked-calendar-views',
+ outlet: UnlinkedCalendarsComponent
+ },
]
}
];
diff --git a/libs/doc-pages/datepicker/src/lib/demos/index.ts b/libs/doc-pages/datepicker/src/lib/demos/index.ts
index f79d2fbabb..71b2759b0c 100644
--- a/libs/doc-pages/datepicker/src/lib/demos/index.ts
+++ b/libs/doc-pages/datepicker/src/lib/demos/index.ts
@@ -45,6 +45,7 @@ import { DemoDatepickerPreventChangeToNextMonthComponent } from './prevent-chang
import { DemoDatepickerWithTimepickerComponent } from './with-timepicker/with-timepicker';
import { DatepickerCloseBehaviorComponent } from './closeBehaviour/datepicker-close-behavior';
import { KeepDatesOutOfRulesComponent } from './keep-dates-out-of-rules/keep-dates-out-of-rules.component';
+import { UnlinkedCalendarsComponent } from './unlinked-calendar-views/unlinked-calendar-views.component';
export const DEMO_COMPONENTS = [
DemoDatePickerAdaptivePositionComponent,
@@ -90,5 +91,6 @@ export const DEMO_COMPONENTS = [
DemoDatepickerStartViewComponent,
DemoDatepickerWithTimepickerComponent,
DatepickerCloseBehaviorComponent,
- KeepDatesOutOfRulesComponent
+ KeepDatesOutOfRulesComponent,
+ UnlinkedCalendarsComponent
];
diff --git a/libs/doc-pages/datepicker/src/lib/demos/unlinked-calendar-views/unlinked-calendar-views.component.html b/libs/doc-pages/datepicker/src/lib/demos/unlinked-calendar-views/unlinked-calendar-views.component.html
new file mode 100644
index 0000000000..23898a0584
--- /dev/null
+++ b/libs/doc-pages/datepicker/src/lib/demos/unlinked-calendar-views/unlinked-calendar-views.component.html
@@ -0,0 +1,11 @@
+
diff --git a/libs/doc-pages/datepicker/src/lib/demos/unlinked-calendar-views/unlinked-calendar-views.component.ts b/libs/doc-pages/datepicker/src/lib/demos/unlinked-calendar-views/unlinked-calendar-views.component.ts
new file mode 100644
index 0000000000..68645d6223
--- /dev/null
+++ b/libs/doc-pages/datepicker/src/lib/demos/unlinked-calendar-views/unlinked-calendar-views.component.ts
@@ -0,0 +1,16 @@
+import { Component } from '@angular/core';
+
+@Component({
+ // eslint-disable-next-line @angular-eslint/component-selector
+ selector: 'datepicker-unlinked-calendar-views',
+ templateUrl: './unlinked-calendar-views.component.html',
+ standalone: false
+})
+export class UnlinkedCalendarsComponent {
+ dateRangePickerValue?: (Date | undefined)[];
+ range1: Date = new Date(1979, 9, 27);
+ range2: Date = new Date(1985, 3, 2);
+ ngOnInit(): void {
+ this.dateRangePickerValue = [this.range1, this.range2];
+ }
+}
diff --git a/src/datepicker/base/bs-datepicker-container.ts b/src/datepicker/base/bs-datepicker-container.ts
index 0d8ce5073e..36d38f6c9b 100644
--- a/src/datepicker/base/bs-datepicker-container.ts
+++ b/src/datepicker/base/bs-datepicker-container.ts
@@ -14,7 +14,8 @@ import {
DayViewModel,
MonthsCalendarViewModel,
WeekViewModel,
- YearsCalendarViewModel
+ YearsCalendarViewModel,
+ ComplexCalendarViewModel
} from '../models';
export abstract class BsDatepickerAbstractComponent {
@@ -36,6 +37,7 @@ export abstract class BsDatepickerAbstractComponent {
isRangePicker?: boolean;
withTimepicker?: boolean;
+ unlinkedCalendars?: boolean;
set minDate(value: Date|undefined) {
this._effects?.setMinDate(value);
@@ -75,6 +77,8 @@ export abstract class BsDatepickerAbstractComponent {
_daysCalendar$!: Observable;
_daysCalendarSub = new Subscription();
+ complexCalendar?: ComplexCalendarViewModel[]|undefined;
+
set daysCalendar$(value: Observable) {
this._daysCalendar$ = value;
this._daysCalendarSub.unsubscribe();
@@ -92,10 +96,10 @@ export abstract class BsDatepickerAbstractComponent {
// todo: valorkin fix
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
- setViewMode(event: BsDatepickerViewMode): void {}
+ setViewMode(event: BsDatepickerViewMode, source?: number): void {}
// eslint-disable-next-line
- navigateTo(event: BsNavigationEvent): void {}
+ navigateTo(event: BsNavigationEvent, source?: number): void {}
// eslint-disable-next-line
dayHoverHandler(event: CellHoverEvent): void {}
@@ -110,16 +114,16 @@ export abstract class BsDatepickerAbstractComponent {
yearHoverHandler(event: CellHoverEvent): void {}
// eslint-disable-next-line
- timeSelectHandler(date: Date, index: number): void {}
+ timeSelectHandler(date: Date, index: number, source?: number): void {}
// eslint-disable-next-line
- daySelectHandler(day: DayViewModel): void {}
+ daySelectHandler(day: DayViewModel, source?: number): void {}
// eslint-disable-next-line
- monthSelectHandler(event: CalendarCellViewModel): void {}
+ monthSelectHandler(event: CalendarCellViewModel, source?: number): void {}
// eslint-disable-next-line
- yearSelectHandler(event: CalendarCellViewModel): void {}
+ yearSelectHandler(event: CalendarCellViewModel, source?: number): void {}
// eslint-disable-next-line
setRangeOnCalendar(dates: BsCustomDates): void {}
diff --git a/src/datepicker/bs-datepicker.config.ts b/src/datepicker/bs-datepicker.config.ts
index ac735988ac..2501db7a72 100644
--- a/src/datepicker/bs-datepicker.config.ts
+++ b/src/datepicker/bs-datepicker.config.ts
@@ -195,4 +195,8 @@ export class BsDatepickerConfig implements DatepickerRenderOptions {
* Allows keep invalid dates in range. Can be used with minDate, maxDate
* */
keepDatesOutOfRules = false;
+ /**
+ * If true calendar views can be changed separately (dateRangePicker only)
+ */
+ unlinkedCalendars = false;
}
diff --git a/src/datepicker/engine/flag-days-calendar.ts b/src/datepicker/engine/flag-days-calendar.ts
index 061f41329f..f64c263117 100644
--- a/src/datepicker/engine/flag-days-calendar.ts
+++ b/src/datepicker/engine/flag-days-calendar.ts
@@ -31,6 +31,7 @@ export interface FlagDaysCalendarOptions {
monthIndex: number;
dateCustomClasses: DatepickerDateCustomClasses[];
dateTooltipTexts: DatepickerDateTooltipText[];
+ unlinkedCalendars: boolean;
}
export function flagDaysCalendar(
@@ -122,12 +123,14 @@ export function flagDaysCalendar(
// todo: add check for linked calendars
formattedMonth.hideLeftArrow =
+ !options.unlinkedCalendars && (
options.isDisabled ||
- (!!options.monthIndex && options.monthIndex > 0 && options.monthIndex !== options.displayMonths);
+ (!!options.monthIndex && options.monthIndex > 0 && options.monthIndex !== options.displayMonths));
formattedMonth.hideRightArrow =
+ !options.unlinkedCalendars && (
options.isDisabled ||
((!!options.monthIndex || options.monthIndex === 0) && !!options.displayMonths && options.monthIndex < options.displayMonths &&
- options.monthIndex + 1 !== options.displayMonths);
+ options.monthIndex + 1 !== options.displayMonths));
formattedMonth.disableLeftArrow = isMonthDisabled(
shiftDate(formattedMonth.month, { month: -1 }),
diff --git a/src/datepicker/engine/flag-days.calendar.spec.ts b/src/datepicker/engine/flag-days.calendar.spec.ts
index 637d7ceb06..97145594a3 100644
--- a/src/datepicker/engine/flag-days.calendar.spec.ts
+++ b/src/datepicker/engine/flag-days.calendar.spec.ts
@@ -5,6 +5,7 @@ describe('flag-days-calendar:', () => {
it('should flag days as disabled when they are part of the datesDisabled', () => {
const weekViewModel = {
+ unlinkedCalendars: false,
month: new Date('2019-02-01'),
weeks: [
{
@@ -29,6 +30,7 @@ describe('flag-days-calendar:', () => {
new Date('2019-02-09')
];
const result = flagDaysCalendar(weekViewModel, {
+ unlinkedCalendars: false,
datesDisabled,
isDisabled: false,
minDate: new Date('2019-01-01'),
@@ -51,6 +53,7 @@ describe('flag-days-calendar:', () => {
it('should flag days as disabled when they are not part of the datesEnabled', () => {
const weekViewModel = {
+ unlinkedCalendars: false,
month: new Date('2020-02-01'),
weeks: [
{
@@ -75,6 +78,7 @@ describe('flag-days-calendar:', () => {
new Date('2020-02-09')
];
const result = flagDaysCalendar(weekViewModel, {
+ unlinkedCalendars: false,
datesEnabled,
isDisabled: false,
minDate: new Date('2020-01-01'),
diff --git a/src/datepicker/engine/flag-months-calendar.ts b/src/datepicker/engine/flag-months-calendar.ts
index 45f78ca97a..63c31fca73 100644
--- a/src/datepicker/engine/flag-months-calendar.ts
+++ b/src/datepicker/engine/flag-months-calendar.ts
@@ -16,6 +16,7 @@ export interface FlagMonthCalendarOptions {
datesEnabled: Date[];
displayMonths: number;
monthIndex: number;
+ unlinkedCalendars: boolean;
}
export function flagMonthsCalendar(
@@ -59,10 +60,12 @@ export function flagMonthsCalendar(
// todo: add check for linked calendars
monthCalendar.hideLeftArrow =
+ !options.unlinkedCalendars &&
!!options.monthIndex && options.monthIndex > 0 && options.monthIndex !== options.displayMonths;
monthCalendar.hideRightArrow =
- (!!options.monthIndex || options.monthIndex === 0 )
+ !options.unlinkedCalendars
+ && (!!options.monthIndex || options.monthIndex === 0 )
&& (!!options.displayMonths || options.displayMonths === 0)
&& options.monthIndex < options.displayMonths
&& options.monthIndex + 1 !== options.displayMonths;
diff --git a/src/datepicker/engine/flag-years-calendar.ts b/src/datepicker/engine/flag-years-calendar.ts
index 6dd357fd12..99cdca51d1 100644
--- a/src/datepicker/engine/flag-years-calendar.ts
+++ b/src/datepicker/engine/flag-years-calendar.ts
@@ -13,6 +13,7 @@ export interface FlagYearsCalendarOptions {
datesEnabled: Date[];
displayMonths: number;
yearIndex: number;
+ unlinkedCalendars: boolean;
}
export function flagYearsCalendar(
@@ -53,8 +54,10 @@ export function flagYearsCalendar(
// todo: add check for linked calendars
yearsCalendar.hideLeftArrow =
+ !options.unlinkedCalendars &&
!!options.yearIndex && options.yearIndex > 0 && options.yearIndex !== options.displayMonths;
yearsCalendar.hideRightArrow =
+ !options.unlinkedCalendars &&
!!options.yearIndex && !!options.displayMonths &&
options.yearIndex < options.displayMonths &&
options.yearIndex + 1 !== options.displayMonths;
diff --git a/src/datepicker/engine/format-days-calendar.ts b/src/datepicker/engine/format-days-calendar.ts
index 43832b11e3..b611582531 100644
--- a/src/datepicker/engine/format-days-calendar.ts
+++ b/src/datepicker/engine/format-days-calendar.ts
@@ -38,7 +38,8 @@ export function formatDaysCalendar(daysCalendar: DaysCalendarModel,
hideLeftArrow: false,
hideRightArrow: false,
disableLeftArrow: false,
- disableRightArrow: false
+ disableRightArrow: false,
+ unlinkedCalendars: false,
};
}
diff --git a/src/datepicker/engine/format-months-calendar.ts b/src/datepicker/engine/format-months-calendar.ts
index 0a260c9b6a..9a81d0bca9 100644
--- a/src/datepicker/engine/format-months-calendar.ts
+++ b/src/datepicker/engine/format-months-calendar.ts
@@ -34,6 +34,7 @@ export function formatMonthsCalendar(
hideRightArrow: false,
hideLeftArrow: false,
disableRightArrow: false,
- disableLeftArrow: false
+ disableLeftArrow: false,
+ unlinkedCalendars: false,
};
}
diff --git a/src/datepicker/engine/format-years-calendar.ts b/src/datepicker/engine/format-years-calendar.ts
index 6a4b643dcb..c2b4d36613 100644
--- a/src/datepicker/engine/format-years-calendar.ts
+++ b/src/datepicker/engine/format-years-calendar.ts
@@ -34,7 +34,8 @@ export function formatYearsCalendar(
hideLeftArrow: false,
hideRightArrow: false,
disableLeftArrow: false,
- disableRightArrow: false
+ disableRightArrow: false,
+ unlinkedCalendars: false,
};
}
diff --git a/src/datepicker/models/index.ts b/src/datepicker/models/index.ts
index f93b50091a..bbcce4cb6f 100644
--- a/src/datepicker/models/index.ts
+++ b/src/datepicker/models/index.ts
@@ -11,6 +11,7 @@ export interface NavigationViewModel {
hideRightArrow: boolean;
disableLeftArrow: boolean;
disableRightArrow: boolean;
+ unlinkedCalendars: boolean;
}
export interface CalendarCellViewModel {
@@ -52,6 +53,8 @@ export interface DaysCalendarViewModel extends NavigationViewModel {
weekdays: string[];
}
+export type ComplexCalendarViewModel = { mode: 'day', calendar: DaysCalendarViewModel } | { mode: 'month', calendar: MonthsCalendarViewModel} | { mode: 'year', calendar: YearsCalendarViewModel};
+
/** *************** */
// months calendar
export interface MonthsCalendarViewModel extends NavigationViewModel {
diff --git a/src/datepicker/reducer/bs-datepicker.actions.ts b/src/datepicker/reducer/bs-datepicker.actions.ts
index ee41b783da..1bf9ff09b8 100644
--- a/src/datepicker/reducer/bs-datepicker.actions.ts
+++ b/src/datepicker/reducer/bs-datepicker.actions.ts
@@ -7,7 +7,7 @@ import {
CellHoverEvent,
DatepickerRenderOptions,
DatepickerDateCustomClasses,
- DatepickerDateTooltipText
+ DatepickerDateTooltipText,
} from '../models';
@Injectable({providedIn: 'platform'})
@@ -47,38 +47,38 @@ export class BsDatepickerActions {
return { type: BsDatepickerActions.FLAG };
}
- select(date?: Date): Action {
+ select(date?: Date, source?: number): Action {
return {
type: BsDatepickerActions.SELECT,
- payload: date
+ payload: { date, source }
};
}
- selectTime(date: Date, index: number): Action {
+ selectTime(date: Date, index: number, source?: number): Action {
return {
type: BsDatepickerActions.SELECT_TIME,
- payload: { date, index },
+ payload: { date, index, source },
};
}
- changeViewMode(event: BsDatepickerViewMode): Action {
+ changeViewMode(event: BsDatepickerViewMode, source?: number): Action {
return {
type: BsDatepickerActions.CHANGE_VIEWMODE,
- payload: event
+ payload: { event, source }
};
}
- navigateTo(event: BsViewNavigationEvent): Action {
+ navigateTo(event: BsViewNavigationEvent, source?: number): Action {
return {
type: BsDatepickerActions.NAVIGATE_TO,
- payload: event
+ payload: { event, source }
};
}
- navigateStep(step?: TimeUnit): Action {
+ navigateStep(step?: TimeUnit, source?: number): Action {
return {
type: BsDatepickerActions.NAVIGATE_OFFSET,
- payload: step
+ payload: { step, source }
};
}
@@ -90,10 +90,10 @@ export class BsDatepickerActions {
}
// date range picker
- selectRange(value?: (Date|undefined)[] | undefined): Action {
+ selectRange(value?: (Date|undefined)[] | undefined, source?: number): Action {
return {
type: BsDatepickerActions.SELECT_RANGE,
- payload: value
+ payload: { value, source }
};
}
diff --git a/src/datepicker/reducer/bs-datepicker.effects.ts b/src/datepicker/reducer/bs-datepicker.effects.ts
index b7c4ef61c2..23ac900b39 100644
--- a/src/datepicker/reducer/bs-datepicker.effects.ts
+++ b/src/datepicker/reducer/bs-datepicker.effects.ts
@@ -11,6 +11,7 @@ import {
BsDatepickerViewMode,
BsNavigationEvent,
CellHoverEvent,
+ ComplexCalendarViewModel,
DatepickerDateCustomClasses,
DatepickerDateTooltipText,
DatepickerRenderOptions,
@@ -46,12 +47,12 @@ export class BsDatepickerEffects {
/** setters */
- setValue(value?: Date): void {
- this._store?.dispatch(this._actions.select(value));
+ setValue(value?: Date, source?: number): void {
+ this._store?.dispatch(this._actions.select(value, source));
}
- setRangeValue(value?: (Date|undefined)[] | undefined): void {
- this._store?.dispatch(this._actions.selectRange(value));
+ setRangeValue(value?: (Date|undefined)[] | undefined, source?: number): void {
+ this._store?.dispatch(this._actions.selectRange(value, source));
}
setMinDate(value?: Date): BsDatepickerEffects {
@@ -116,6 +117,7 @@ export class BsDatepickerEffects {
return this;
}
+
container.selectedTime = this._store.select(state => state.selectedTime)
.pipe(filter(times => !!times));
@@ -142,17 +144,69 @@ export class BsDatepickerEffects {
})
));
+ this._subs.push(
+ combineLatest([
+ this._store.select(state => state.viewStates?.map(state => state.mode)),
+ this._store.select(state => state.flaggedMonths),
+ this._store.select(state => state.flaggedMonthsCalendar),
+ this._store.select(state => state.yearsCalendarFlagged),
+ ]).pipe(map(([modes, days, months, years]) => {
+ if (modes == null) {
+ return undefined;
+ }
+ const calendars: ComplexCalendarViewModel[] = [];
+ for(let idx = 0; idx < modes.length; idx++) {
+ const flaggedMonth = days != null && days.length > idx ? days[idx] : undefined;
+ const flaggedMonthsCalendar = months != null && months.length > idx ? months[idx] : undefined;
+ const yearsCalendarFlagged = years != null && years.length > idx ? years[idx] : undefined;
+ let complex: ComplexCalendarViewModel | undefined;
+ switch (modes[idx]) {
+ case 'day':
+ complex = flaggedMonth != null ? { mode: 'day', calendar: flaggedMonth } : undefined;
+ break;
+ case 'month':
+ complex = flaggedMonthsCalendar != null ? { mode: 'month', calendar: flaggedMonthsCalendar } : undefined;
+ break;
+ case 'year':
+ complex = yearsCalendarFlagged != null ? { mode: 'year', calendar: yearsCalendarFlagged } : undefined;
+ break;
+ }
+ if (complex) {
+ calendars[idx] = complex;
+ }
+ }
+ return calendars;
+ })
+ ).subscribe(complex => {
+ if(complex == null){
+ return;
+ }
+ if ((container.complexCalendar?? []).length != complex.length) {
+ container.complexCalendar = new Array(complex.length);
+ }
+ complex.forEach((vm, idx) => {
+ if (container.complexCalendar![idx] == null) {
+ container.complexCalendar![idx] = vm;
+ } else {
+ const act = container.complexCalendar![idx];
+ act.mode = vm.mode;
+ act.calendar = vm.calendar;
+ }
+ })
+ })
+ );
return this;
}
+
/** event handlers */
setEventHandlers(container: BsDatepickerAbstractComponent): BsDatepickerEffects {
- container.setViewMode = (event: BsDatepickerViewMode): void => {
- this._store?.dispatch(this._actions.changeViewMode(event));
+ container.setViewMode = (event: BsDatepickerViewMode, source?: number): void => {
+ this._store?.dispatch(this._actions.changeViewMode(event, source));
};
- container.navigateTo = (event: BsNavigationEvent): void => {
- this._store?.dispatch(this._actions.navigateStep(event.step));
+ container.navigateTo = (event: BsNavigationEvent, source: number): void => {
+ this._store?.dispatch(this._actions.navigateStep(event.step, source));
};
container.dayHoverHandler = (event: CellHoverEvent): void => {
diff --git a/src/datepicker/reducer/bs-datepicker.reducer.ts b/src/datepicker/reducer/bs-datepicker.reducer.ts
index 95f14ff29b..5e3ea7bde9 100644
--- a/src/datepicker/reducer/bs-datepicker.reducer.ts
+++ b/src/datepicker/reducer/bs-datepicker.reducer.ts
@@ -20,7 +20,7 @@ import { formatMonthsCalendar } from '../engine/format-months-calendar';
import { flagMonthsCalendar } from '../engine/flag-months-calendar';
import { formatYearsCalendar, initialYearShift, yearsPerCalendar } from '../engine/format-years-calendar';
import { flagYearsCalendar } from '../engine/flag-years-calendar';
-import { BsViewNavigationEvent, DatepickerFormatOptions, BsDatepickerViewMode } from '../models';
+import { BsViewNavigationEvent, DatepickerFormatOptions, BsDatepickerViewMode, DaysCalendarViewModel, MonthsCalendarViewModel, YearsCalendarViewModel } from '../models';
import { getYearsCalendarInitialDate } from '../utils/bs-calendar-utils';
import { copyTime } from '../utils/copy-time-utils';
@@ -45,7 +45,7 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
}
case BsDatepickerActions.NAVIGATE_TO: {
- const payload: BsViewNavigationEvent = action.payload;
+ const payload: BsViewNavigationEvent = action.payload.event;
if (!state.view || !payload.unit) {
return state;
}
@@ -55,24 +55,22 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
let mode: BsDatepickerViewMode;
if (canSwitchMode(payload.viewMode, state.minMode)) {
mode = payload.viewMode;
- newState = { view: { date, mode } };
+ newState = { view: { date, mode, source: action.payload.source } };
} else {
mode = state.view.mode;
- newState = { selectedDate: date, view: { date, mode } };
+ newState = { selectedDate: date, view: { date, mode, source: action.payload.source } };
}
-
return Object.assign({}, state, newState);
}
case BsDatepickerActions.CHANGE_VIEWMODE: {
- if (!canSwitchMode(action.payload, state.minMode) || !state.view) {
+ if (!canSwitchMode(action.payload.event, state.minMode) || !state.view) {
return state;
}
const date = state.view.date;
- const mode = action.payload;
- const newState = { view: { date, mode } };
-
+ const mode = action.payload.event;
+ const newState = { view: { date, mode, source: action.payload.source } };
return Object.assign({}, state, newState);
}
@@ -84,10 +82,11 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
if (!state.view) {
return state;
}
-
+ const source = action.payload.source;
const newState = {
- selectedDate: action.payload,
- view: state.view
+ selectedDate: action.payload.date,
+ view: state.view,
+ source: source,
};
if (Array.isArray(state.selectedTime)) {
@@ -97,8 +96,8 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
}
}
- const mode = state.view.mode;
- const _date = action.payload || state.view.date;
+ const mode = state.viewStates != null && source != null ? state.viewStates[source].mode : state.view.mode;
+ const _date = action.payload.date || state.view.date;
const date = getViewDate(_date, state.minDate, state.maxDate);
newState.view = { mode, date };
@@ -106,10 +105,10 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
}
case BsDatepickerActions.SELECT_TIME: {
- const {date, index} = action.payload;
+ const {date, index, source} = action.payload;
const selectedTime = state.selectedTime ? [...state.selectedTime] : [];
selectedTime[index] = date;
- return Object.assign({}, state, { selectedTime });
+ return Object.assign({}, state, { selectedTime, source });
}
case BsDatepickerActions.SET_OPTIONS: {
@@ -119,7 +118,7 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
const newState = action.payload;
// preserve view mode
- const mode = newState.minMode ? newState.minMode : state.view.mode;
+ const mode = newState.minMode ? newState.minMode : newState.startView;
const _viewDate = isDateValid(newState.value) && newState.value
|| isArray(newState.value) && isDateValid(newState.value[0]) && newState.value[0]
|| state.view.date;
@@ -153,8 +152,8 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
}
const newState = {
- selectedRange: action.payload,
- view: state.view
+ selectedRange: action.payload.value,
+ view: state.view,
};
newState.selectedRange?.forEach((dte: Date, index: number) => {
if (Array.isArray(state.selectedTime)) {
@@ -164,11 +163,11 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
}
}
});
-
- const mode = state.view.mode;
- const _date = action.payload && action.payload[0] || state.view.date;
+ const source = action.payload.source;
+ const mode = state.viewStates != null && source != null ? state.viewStates[source].mode : state.view.mode;
+ const _date = action.payload.value && action.payload.value[0] || state.view.date;
const date = getViewDate(_date, state.minDate, state.maxDate);
- newState.view = { mode, date };
+ newState.view = { mode, date, source };
return Object.assign({}, state, newState);
}
@@ -208,7 +207,7 @@ function calculateReducer(state: BsDatepickerState): BsDatepickerState {
if (!state.view) {
return state;
}
-
+ const source = state.view.source;
// how many calendars
let displayMonths: number | undefined;
if (state.displayOneMonthRange &&
@@ -221,110 +220,140 @@ function calculateReducer(state: BsDatepickerState): BsDatepickerState {
// use selected date on initial rendering if set
let viewDate = state.view.date;
- if (state.view.mode === 'day' && state.monthViewOptions) {
- if (state.showPreviousMonth && state.selectedRange && state.selectedRange.length === 0) {
- viewDate = shiftDate(viewDate, { month: -1 });
- }
-
- state.monthViewOptions.firstDayOfWeek = getLocale(state.locale).firstDayOfWeek();
- let monthsModel = new Array(displayMonths);
- for (let monthIndex = 0; monthIndex < displayMonths; monthIndex++) {
- // todo: for unlinked calendars it will be harder
- monthsModel[monthIndex] = calcDaysCalendar(
- viewDate,
- state.monthViewOptions
- );
- viewDate = shiftDate(viewDate, { month: 1 });
+ if (state.viewStates == null) {
+ state.viewStates = new Array(displayMonths);
+ }
+ let monthsModel = new Array(displayMonths);
+ const monthsCalendar = new Array(displayMonths);
+ const yearsCalendarModel = new Array(displayMonths);
+ for (let calendarIndex = 0; calendarIndex < displayMonths; calendarIndex++) {
+ if (source != null && state.viewStates?.length > calendarIndex && state.viewStates[calendarIndex] != null) {
+ if(state.unlinkedCalendars) {
+ if (source == calendarIndex) {
+ state.viewStates[calendarIndex].mode = state.view.mode;
+ }
+ } else {
+ state.viewStates[calendarIndex].mode = state.view.mode;
+ }
}
- // Check if parameter enabled and check if it's not months navigation event
- if (state.preventChangeToNextMonth && state.flaggedMonths && state.hoveredDate) {
- const viewMonth = calcDaysCalendar(state.view.date, state.monthViewOptions);
- // Check if viewed right month same as in flaggedMonths state, then override months model with flaggedMonths
- if (state.flaggedMonths.length && state.flaggedMonths[1].month.getMonth() === viewMonth.month.getMonth()) {
- monthsModel = state.flaggedMonths
- .map(item => {
- if (state.monthViewOptions) {
- return calcDaysCalendar(
- item.month,
- state.monthViewOptions
- );
- }
- return null;
- })
- .filter(item => item !== null);
+ const checkedMode = state.viewStates?.length > calendarIndex ? state.viewStates[calendarIndex]?.mode ?? state.view.mode : state.view.mode;
+ if (checkedMode === 'day' && state.monthViewOptions != null) {
+ if (calendarIndex == 0) {
+ if (state.showPreviousMonth && state.selectedRange && state.selectedRange.length === 0) {
+ viewDate = shiftDate(viewDate, { month: -1 });
+ }
+ state.monthViewOptions.firstDayOfWeek = getLocale(state.locale).firstDayOfWeek();
+ }
+ if (source != null && state.unlinkedCalendars) {
+ viewDate = state.viewStates[calendarIndex].date;
+ if (calendarIndex == source) {
+ viewDate = shiftDate(viewDate, { month: state.view.direction });
+ state.viewStates[calendarIndex] = { date: viewDate, mode: 'day' };
+ }
+ monthsModel[calendarIndex] = calcDaysCalendar(
+ viewDate,
+ state.monthViewOptions
+ );
+ } else {
+ if(calendarIndex == displayMonths -1 && state.unlinkedCalendars && (state.selectedRange ?? []).length == 2) {
+ viewDate = state.selectedRange![1];
+ }
+ monthsModel[calendarIndex] = calcDaysCalendar(
+ viewDate,
+ state.monthViewOptions
+ );
+ state.viewStates[calendarIndex] = { date: viewDate, mode: 'day' };
+ viewDate = shiftDate(viewDate, { month: 1 });
+ }
+ // Check if parameter enabled and check if it's not months navigation event
+ if ((calendarIndex == displayMonths -1) && !state.unlinkedCalendars && state.preventChangeToNextMonth && state.flaggedMonths && state.hoveredDate) {
+ const viewMonth = calcDaysCalendar(state.view.date, state.monthViewOptions);
+ // Check if viewed right month same as in flaggedMonths state, then override months model with flaggedMonths
+ if (state.flaggedMonths.length && state.flaggedMonths[1].month.getMonth() === viewMonth.month.getMonth()) {
+ monthsModel = state.flaggedMonths
+ .map(item => {
+ if (state.monthViewOptions) {
+ return calcDaysCalendar(
+ item.month,
+ state.monthViewOptions
+ );
+ }
+ return null;
+ })
+ .filter(item => item !== null);
+ }
}
}
-
- return Object.assign({}, state, { monthsModel });
- }
-
- if (state.view.mode === 'month') {
- const monthsCalendar = new Array(displayMonths);
- for (
- let calendarIndex = 0;
- calendarIndex < displayMonths;
- calendarIndex++
- ) {
- // todo: for unlinked calendars it will be harder
- monthsCalendar[calendarIndex] = formatMonthsCalendar(
- viewDate,
- getFormatOptions(state)
- );
- viewDate = shiftDate(viewDate, { year: 1 });
+ if (checkedMode === 'month') {
+ if (source != null && state.unlinkedCalendars) {
+ viewDate = state.viewStates[calendarIndex].date;
+ if (calendarIndex == source) {
+ viewDate = shiftDate(viewDate, { year: state.view.direction });
+ state.viewStates[calendarIndex] = { date: viewDate, mode: 'month' };
+ }
+ monthsCalendar[calendarIndex] = formatMonthsCalendar(
+ viewDate,
+ getFormatOptions(state)
+ );
+ } else {
+ monthsCalendar[calendarIndex] = formatMonthsCalendar(
+ viewDate,
+ getFormatOptions(state)
+ );
+ state.viewStates[calendarIndex] = { date: viewDate, mode: 'month' };
+ viewDate = shiftDate(viewDate, { year: 1 });
+ }
}
-
- return Object.assign({}, state, { monthsCalendar });
- }
-
- if (state.view.mode === 'year') {
- const yearsCalendarModel = new Array(displayMonths);
-
- for (
- let calendarIndex = 0;
- calendarIndex < displayMonths;
- calendarIndex++
- ) {
- // todo: for unlinked calendars it will be harder
- yearsCalendarModel[calendarIndex] = formatYearsCalendar(
- viewDate,
- getFormatOptions(state),
- state.minMode === 'year' ? getYearsCalendarInitialDate(state, calendarIndex) : undefined
- );
- viewDate = shiftDate(viewDate, { year: yearsPerCalendar });
+ if (checkedMode === 'year') {
+ if (source != null && state.unlinkedCalendars) {
+ viewDate = state.viewStates[calendarIndex].date;
+ if (calendarIndex == source) {
+ viewDate = shiftDate(viewDate, { year: state.view.direction });
+ state.viewStates[calendarIndex] = { date: viewDate, mode: 'year' };
+ }
+ yearsCalendarModel[calendarIndex] = formatYearsCalendar(
+ viewDate,
+ getFormatOptions(state),
+ state.minMode === 'year' ? getYearsCalendarInitialDate(state, calendarIndex) : undefined
+ );
+ } else {
+ yearsCalendarModel[calendarIndex] = formatYearsCalendar(
+ viewDate,
+ getFormatOptions(state),
+ state.minMode === 'year' ? getYearsCalendarInitialDate(state, calendarIndex) : undefined
+ );
+ state.viewStates[calendarIndex] = { date: viewDate, mode: 'year' };
+ viewDate = shiftDate(viewDate, { year: yearsPerCalendar });
+ }
}
-
- return Object.assign({}, state, { yearsCalendarModel });
}
-
- return state;
+ return Object.assign({}, state, { monthsModel, monthsCalendar, yearsCalendarModel });
}
function formatReducer(state: BsDatepickerState): BsDatepickerState {
if (!state.view) {
return state;
}
-
- if (state.view.mode === 'day' && state.monthsModel) {
- const formattedMonths = state.monthsModel.map((month, monthIndex) =>
- formatDaysCalendar(month, getFormatOptions(state), monthIndex)
- );
-
- return Object.assign({}, state, { formattedMonths });
- }
-
- // how many calendars
const displayMonths = state.displayMonths || 1;
- // check initial rendering
- // use selected date on initial rendering if set
- let viewDate = state.view.date;
- if (state.view.mode === 'month') {
- const monthsCalendar = new Array(displayMonths);
- for (
- let calendarIndex = 0;
- calendarIndex < displayMonths;
- calendarIndex++
- ) {
+ const formattedMonths: DaysCalendarViewModel[] = new Array(displayMonths);
+ const monthsCalendar: MonthsCalendarViewModel[] = new Array(displayMonths);
+ const yearsCalendarModel: YearsCalendarViewModel[] = new Array(displayMonths);
+ for (
+ let calendarIndex = 0;
+ calendarIndex < displayMonths;
+ calendarIndex++
+ ) {
+ const viewState = (state.viewStates != null && state.viewStates?.length > calendarIndex) ? state.viewStates[calendarIndex] : state.view;
+ const checkedMode = viewState.mode;
+ if (checkedMode === 'day' && state.monthsModel) {
+ formattedMonths[calendarIndex] = formatDaysCalendar(state.monthsModel[calendarIndex], getFormatOptions(state), calendarIndex)
+ }
+ // how many calendars
+ // check initial rendering
+ // use selected date on initial rendering if set
+ let viewDate = viewState.date;
+ if (checkedMode === 'month') {
// todo: for unlinked calendars it will be harder
monthsCalendar[calendarIndex] = formatMonthsCalendar(
viewDate,
@@ -333,39 +362,38 @@ function formatReducer(state: BsDatepickerState): BsDatepickerState {
viewDate = shiftDate(viewDate, { year: 1 });
}
- return Object.assign({}, state, { monthsCalendar });
- }
-
- if (state.view.mode === 'year') {
- const yearsCalendarModel = new Array(displayMonths);
- for (
- let calendarIndex = 0;
- calendarIndex < displayMonths;
- calendarIndex++
- ) {
- // todo: for unlinked calendars it will be harder
+ if (checkedMode === 'year') {
yearsCalendarModel[calendarIndex] = formatYearsCalendar(
viewDate,
getFormatOptions(state)
);
viewDate = shiftDate(viewDate, { year: 16 });
}
-
- return Object.assign({}, state, { yearsCalendarModel });
}
- return state;
+ const res = Object.assign({}, state, {
+ formattedMonths: formattedMonths,
+ monthsCalendar: monthsCalendar,
+ yearsCalendarModel: yearsCalendarModel
+ });
+ return res;
}
function flagReducer(state: BsDatepickerState): BsDatepickerState {
if (!state.view) {
return state;
}
-
const displayMonths = isDisplayOneMonth(state.view.date, state.minDate, state.maxDate) ? 1 : state.displayMonths;
- if (state.formattedMonths && state.view.mode === 'day') {
- const flaggedMonths = state.formattedMonths.map(
- (formattedMonth, monthIndex) =>
+ const flaggedMonths: DaysCalendarViewModel[] = new Array(displayMonths);
+ const flaggedMonthsCalendar: MonthsCalendarViewModel[] = new Array(displayMonths);
+ const yearsCalendarFlagged: YearsCalendarViewModel[] = new Array(displayMonths);
+
+ for(let idx = 0; idx < displayMonths; idx++) {
+ const viewState = (state.viewStates != null && state.viewStates?.length > idx) ? state.viewStates[idx] : state.view;
+ const checkedState = viewState.mode;
+ if (state.formattedMonths && checkedState === 'day') {
+ const formattedMonth = state.formattedMonths[idx];
+ flaggedMonths[idx] =
flagDaysCalendar(formattedMonth, {
isDisabled: state.isDisabled,
minDate: state.minDate,
@@ -379,17 +407,13 @@ function flagReducer(state: BsDatepickerState): BsDatepickerState {
displayMonths,
dateCustomClasses: state.dateCustomClasses,
dateTooltipTexts: state.dateTooltipTexts,
- monthIndex
- })
- );
-
- return Object.assign({}, state, { flaggedMonths });
- }
-
- if (state.view.mode === 'month' && state.monthsCalendar) {
- const flaggedMonthsCalendar = state.monthsCalendar.map(
- (formattedMonth, monthIndex) =>
- flagMonthsCalendar(formattedMonth, {
+ monthIndex: idx,
+ unlinkedCalendars: state.unlinkedCalendars,
+ });
+ }
+ if (checkedState === 'month' && state.monthsCalendar) {
+ const formattedMonth = state.monthsCalendar[idx];
+ flaggedMonthsCalendar[idx] = flagMonthsCalendar(formattedMonth, {
isDisabled: state.isDisabled,
minDate: state.minDate,
maxDate: state.maxDate,
@@ -399,51 +423,49 @@ function flagReducer(state: BsDatepickerState): BsDatepickerState {
datesEnabled: state.datesEnabled,
selectedRange: state.selectedRange,
displayMonths,
- monthIndex
- })
- );
-
- return Object.assign({}, state, { flaggedMonthsCalendar });
- }
-
- if (state.view.mode === 'year' && state.yearsCalendarModel) {
- const yearsCalendarFlagged = state.yearsCalendarModel.map(
- (formattedMonth, yearIndex) =>
- flagYearsCalendar(formattedMonth, {
- isDisabled: state.isDisabled,
- minDate: state.minDate,
- maxDate: state.maxDate,
- hoveredYear: state.hoveredYear,
- selectedDate: state.selectedDate,
- datesDisabled: state.datesDisabled,
- datesEnabled: state.datesEnabled,
- selectedRange: state.selectedRange,
- displayMonths,
- yearIndex
- })
- );
-
- return Object.assign({}, state, { yearsCalendarFlagged });
+ monthIndex: idx,
+ unlinkedCalendars: state.unlinkedCalendars,
+ });
+ }
+ if (checkedState === 'year' && state.yearsCalendarModel) {
+ const formattedMonth = state.yearsCalendarModel[idx];
+ yearsCalendarFlagged[idx] = flagYearsCalendar(formattedMonth, {
+ isDisabled: state.isDisabled,
+ minDate: state.minDate,
+ maxDate: state.maxDate,
+ hoveredYear: state.hoveredYear,
+ selectedDate: state.selectedDate,
+ datesDisabled: state.datesDisabled,
+ datesEnabled: state.datesEnabled,
+ selectedRange: state.selectedRange,
+ displayMonths,
+ yearIndex: idx,
+ unlinkedCalendars: state.unlinkedCalendars,
+ });
+ }
}
- return state;
+ return Object.assign({}, state, { flaggedMonths, flaggedMonthsCalendar, yearsCalendarFlagged });
}
function navigateOffsetReducer(state: BsDatepickerState, action: Action): BsDatepickerState {
+
if (!state.view) {
return state;
}
-
- const date = shiftViewDate(state, action);
+ const date = shiftViewDate(state, { ...action, payload: action.payload.step });
if (!date) {
return state;
}
-
+ const source = action.payload.source;
const newState: {view: BsDatepickerViewState} = {
view: {
- mode: state.view.mode,
- date
- }
+ mode: source != null && state.viewStates != null ? state.viewStates[source].mode : state.view.mode,
+ date,
+ source,
+ direction: action.payload.step['month'] ?? action.payload.step['year'],
+ },
+
};
return Object.assign({}, state, newState);
diff --git a/src/datepicker/reducer/bs-datepicker.state.ts b/src/datepicker/reducer/bs-datepicker.state.ts
index ee4c6490c1..85d90f7f30 100644
--- a/src/datepicker/reducer/bs-datepicker.state.ts
+++ b/src/datepicker/reducer/bs-datepicker.state.ts
@@ -8,7 +8,7 @@ import {
DaysCalendarViewModel,
MonthsCalendarViewModel,
MonthViewOptions,
- YearsCalendarViewModel
+ YearsCalendarViewModel,
} from '../models';
import { defaultMonthOptions } from './_defaults';
import { BsDatepickerConfig } from '../bs-datepicker.config';
@@ -16,6 +16,8 @@ import { BsDatepickerConfig } from '../bs-datepicker.config';
export interface BsDatepickerViewState {
date: Date;
mode: BsDatepickerViewMode;
+ source?: number;
+ direction?: number;
}
export class BsDatepickerState
@@ -29,6 +31,7 @@ export class BsDatepickerState
// initial date of calendar, today by default
view?: BsDatepickerViewState;
+ viewStates?: BsDatepickerViewState[];
isDisabled?: boolean;
// bounds
@@ -80,6 +83,8 @@ export class BsDatepickerState
yearLabel?: string;
weekNumbers?: string;
+ unlinkedCalendars?: boolean;
+
}
const _initialView: BsDatepickerViewState = { date: new Date(), mode: 'day' };
diff --git a/src/datepicker/testing/bs-datepicker.reducer.spec.ts b/src/datepicker/testing/bs-datepicker.reducer.spec.ts
index 1ba66f5c28..933dccc9b8 100644
--- a/src/datepicker/testing/bs-datepicker.reducer.spec.ts
+++ b/src/datepicker/testing/bs-datepicker.reducer.spec.ts
@@ -9,7 +9,7 @@ describe('BsDatepickerReducer.', () => {
const state = initialDatepickerState;
const action: Action = {
type: BsDatepickerActions.NAVIGATE_TO,
- payload: { unit: { year: 2017, month: 11 }, viewMode: 'month'}
+ payload: { event: { unit: { year: 2017, month: 11 }, viewMode: 'month'}}
};
const reducer = bsDatepickerReducer(state, action);
expect(reducer.view.mode).toEqual('month');
@@ -24,7 +24,7 @@ describe('BsDatepickerReducer.', () => {
const action: Action = {
type: BsDatepickerActions.NAVIGATE_TO,
- payload: { unit: { year: 2017, month: 11, day: 1}, viewMode: 'day' }
+ payload: { event: { unit: { year: 2017, month: 11, day: 1}, viewMode: 'day' }}
};
const reducer = bsDatepickerReducer(state, action);
diff --git a/src/datepicker/testing/flaggedMonthsMock.ts b/src/datepicker/testing/flaggedMonthsMock.ts
index 62a022ba9e..07878375a3 100644
--- a/src/datepicker/testing/flaggedMonthsMock.ts
+++ b/src/datepicker/testing/flaggedMonthsMock.ts
@@ -6,6 +6,7 @@ export const mockFlaggedMonths: DaysCalendarViewModel[] = [{
yearTitle: '2021',
weekNumbers: ['10', '11', '12', '13', '14', '15'],
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ unlinkedCalendars: false,
weeks: [{
days: [{
date: new Date('2021-02-28T13:36:16'),
@@ -701,6 +702,7 @@ export const mockFlaggedMonths: DaysCalendarViewModel[] = [{
yearTitle: '2021',
weekNumbers: ['14', '15', '16', '17', '18', '19'],
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ unlinkedCalendars: false,
weeks: [{
days: [{
date: new Date('2021-03-28T12:36:16'),
diff --git a/src/datepicker/themes/bs/bs-datepicker-container.component.ts b/src/datepicker/themes/bs/bs-datepicker-container.component.ts
index 4d2d4b990a..2ab842929d 100644
--- a/src/datepicker/themes/bs/bs-datepicker-container.component.ts
+++ b/src/datepicker/themes/bs/bs-datepicker-container.component.ts
@@ -29,7 +29,7 @@ import { BsYearsCalendarViewComponent } from './bs-years-calendar-view.component
import { BsMonthCalendarViewComponent } from './bs-months-calendar-view.component';
import { TimepickerModule } from 'ngx-bootstrap/timepicker';
import { BsDaysCalendarViewComponent } from './bs-days-calendar-view.component';
-import { NgIf, NgClass, NgSwitch, NgSwitchCase, NgFor, AsyncPipe } from '@angular/common';
+import { NgIf, NgClass, NgFor, AsyncPipe } from '@angular/common';
@Component({
selector: 'bs-datepicker-container',
@@ -43,7 +43,7 @@ import { NgIf, NgClass, NgSwitch, NgSwitchCase, NgFor, AsyncPipe } from '@angula
},
animations: [datepickerAnimation],
standalone: true,
- imports: [NgIf, NgClass, NgSwitch, NgSwitchCase, NgFor, BsDaysCalendarViewComponent, TimepickerModule,
+ imports: [NgIf, NgClass, NgFor, BsDaysCalendarViewComponent, TimepickerModule,
BsMonthCalendarViewComponent, BsYearsCalendarViewComponent, BsCustomDatesViewComponent, AsyncPipe
]
})
@@ -182,11 +182,11 @@ export class BsDatepickerContainerComponent
this._positionService.enable();
}
- override timeSelectHandler(date: Date, index: number) {
- this._store.dispatch(this._actions.selectTime(date, index));
+ override timeSelectHandler(date: Date, index: number, source?: number) {
+ this._store.dispatch(this._actions.selectTime(date, index, source));
}
- override daySelectHandler(day: DayViewModel): void {
+ override daySelectHandler(day: DayViewModel, source?: number): void {
if (!day) {
return;
}
@@ -197,10 +197,10 @@ export class BsDatepickerContainerComponent
return;
}
- this._store.dispatch(this._actions.select(day.date));
+ this._store.dispatch(this._actions.select(day.date, source));
}
- override monthSelectHandler(day: CalendarCellViewModel): void {
+ override monthSelectHandler(day: CalendarCellViewModel, source?: number): void {
if (!day || day.isDisabled) {
return;
}
@@ -212,11 +212,11 @@ export class BsDatepickerContainerComponent
year: getFullYear(day.date)
},
viewMode: 'day'
- })
+ }, source)
);
}
- override yearSelectHandler(day: CalendarCellViewModel): void {
+ override yearSelectHandler(day: CalendarCellViewModel, source?: number): void {
if (!day || day.isDisabled) {
return;
}
@@ -227,7 +227,7 @@ export class BsDatepickerContainerComponent
year: getFullYear(day.date)
},
viewMode: 'month'
- })
+ }, source)
);
}
diff --git a/src/datepicker/themes/bs/bs-datepicker-inline-container.component.ts b/src/datepicker/themes/bs/bs-datepicker-inline-container.component.ts
index 6bf6c6d6c0..811e53faf9 100644
--- a/src/datepicker/themes/bs/bs-datepicker-inline-container.component.ts
+++ b/src/datepicker/themes/bs/bs-datepicker-inline-container.component.ts
@@ -13,7 +13,7 @@ import { BsYearsCalendarViewComponent } from './bs-years-calendar-view.component
import { BsMonthCalendarViewComponent } from './bs-months-calendar-view.component';
import { TimepickerModule } from 'ngx-bootstrap/timepicker';
import { BsDaysCalendarViewComponent } from './bs-days-calendar-view.component';
-import { NgIf, NgClass, NgSwitch, NgSwitchCase, NgFor, AsyncPipe } from '@angular/common';
+import { NgIf, NgClass, NgFor, AsyncPipe } from '@angular/common';
@Component({
selector: 'bs-datepicker-inline-container',
@@ -24,7 +24,7 @@ import { NgIf, NgClass, NgSwitch, NgSwitchCase, NgFor, AsyncPipe } from '@angula
},
animations: [datepickerAnimation],
standalone: true,
- imports: [NgIf, NgClass, NgSwitch, NgSwitchCase, NgFor, BsDaysCalendarViewComponent, TimepickerModule, BsMonthCalendarViewComponent, BsYearsCalendarViewComponent, BsCustomDatesViewComponent, AsyncPipe]
+ imports: [NgIf, NgClass, NgFor, BsDaysCalendarViewComponent, TimepickerModule, BsMonthCalendarViewComponent, BsYearsCalendarViewComponent, BsCustomDatesViewComponent, AsyncPipe]
})
export class BsDatepickerInlineContainerComponent extends BsDatepickerContainerComponent
implements OnInit, OnDestroy {
diff --git a/src/datepicker/themes/bs/bs-datepicker-view.html b/src/datepicker/themes/bs/bs-datepicker-view.html
index 4fbfea672a..0419909e2a 100644
--- a/src/datepicker/themes/bs/bs-datepicker-view.html
+++ b/src/datepicker/themes/bs/bs-datepicker-view.html
@@ -4,56 +4,51 @@
[@datepickerAnimation]="animationState"
(@datepickerAnimation.done)="positionServiceEnable()">
-