Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions apps/ngx-bootstrap-docs-e2e/src/full/datepicker_page.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});

});
108 changes: 108 additions & 0 deletions apps/ngx-bootstrap-docs-e2e/src/support/datepicker.pw.po.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
6 changes: 6 additions & 0 deletions apps/ngx-bootstrap-docs/src/ng-api-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,12 @@ export const ngdoc: any = {
defaultValue: 'false',
type: 'boolean',
description: '<p>Shows timepicker under datepicker</p>\n'
},
{
name: 'unlinkedCalendars',
defaultValue: 'false',
type: 'boolean',
description: '<p>Allow date range picker to switch the calendars separately</p>\n'
}
]
},
Expand Down
14 changes: 14 additions & 0 deletions libs/doc-pages/datepicker/src/lib/datepicker-section.list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
{
Expand Down Expand Up @@ -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: `<p>If you use datepicker with rules (minDate, maxDate) you can set config property <code>keepDatesOutOfRules</code> to true to avoid overwriting invalid dates. Default value is false.</p>`,
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: `<p>If you use daterangepicker and don't want the calendars to move together you can set config property <code>unlinkedCalendars</code> to true. Default value is false.</p>`,
outlet: UnlinkedCalendarsComponent
}
]
},
Expand Down Expand Up @@ -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
},
]
}
];
4 changes: 3 additions & 1 deletion libs/doc-pages/datepicker/src/lib/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -90,5 +91,6 @@ export const DEMO_COMPONENTS = [
DemoDatepickerStartViewComponent,
DemoDatepickerWithTimepickerComponent,
DatepickerCloseBehaviorComponent,
KeepDatesOutOfRulesComponent
KeepDatesOutOfRulesComponent,
UnlinkedCalendarsComponent
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="row">
<div class="col-xs-12 col-12 col-md-4 form-group mb-3">
<input type="text"
placeholder="Daterangepicker"
class="form-control"
bsDaterangepicker
[(bsValue)]="dateRangePickerValue"
[bsConfig]="{unlinkedCalendars: true, displayMonths: 2}"
>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -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];
}
}
18 changes: 11 additions & 7 deletions src/datepicker/base/bs-datepicker-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
DayViewModel,
MonthsCalendarViewModel,
WeekViewModel,
YearsCalendarViewModel
YearsCalendarViewModel,
ComplexCalendarViewModel
} from '../models';

export abstract class BsDatepickerAbstractComponent {
Expand All @@ -36,6 +37,7 @@ export abstract class BsDatepickerAbstractComponent {

isRangePicker?: boolean;
withTimepicker?: boolean;
unlinkedCalendars?: boolean;

set minDate(value: Date|undefined) {
this._effects?.setMinDate(value);
Expand Down Expand Up @@ -75,6 +77,8 @@ export abstract class BsDatepickerAbstractComponent {
_daysCalendar$!: Observable<DaysCalendarViewModel[]|undefined>;
_daysCalendarSub = new Subscription();

complexCalendar?: ComplexCalendarViewModel[]|undefined;

set daysCalendar$(value: Observable<DaysCalendarViewModel[]|undefined>) {
this._daysCalendar$ = value;
this._daysCalendarSub.unsubscribe();
Expand All @@ -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 {}
Expand All @@ -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 {}
Expand Down
4 changes: 4 additions & 0 deletions src/datepicker/bs-datepicker.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 5 additions & 2 deletions src/datepicker/engine/flag-days-calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface FlagDaysCalendarOptions {
monthIndex: number;
dateCustomClasses: DatepickerDateCustomClasses[];
dateTooltipTexts: DatepickerDateTooltipText[];
unlinkedCalendars: boolean;
}

export function flagDaysCalendar(
Expand Down Expand Up @@ -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 }),
Expand Down
4 changes: 4 additions & 0 deletions src/datepicker/engine/flag-days.calendar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand All @@ -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'),
Expand All @@ -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: [
{
Expand All @@ -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'),
Expand Down
Loading