diff --git a/demo/src/app/components/+datepicker/datepicker-section.list.ts b/demo/src/app/components/+datepicker/datepicker-section.list.ts index ed8771a8f1..417b68bdb5 100644 --- a/demo/src/app/components/+datepicker/datepicker-section.list.ts +++ b/demo/src/app/components/+datepicker/datepicker-section.list.ts @@ -10,6 +10,7 @@ import { DemoDatePickerConfigObjectComponent } from './demos/config-object/confi import { DemoDatePickerCustomFormatComponent } from './demos/custom-format/custom-format'; import { DemoDatepickerDateInitialStateComponent } from './demos/date-initial-state/date-initial-state'; import { DemoDatepickerDatesDisabledComponent } from './demos/disable-dates/disable-dates'; +import { DemoDatepickerDatesEnabledComponent } from './demos/enable-dates/enable-dates'; import { DemoDatepickerDaysDisabledComponent } from './demos/disable-days/disable-days'; import { DemoDatepickerDisabledComponent } from './demos/disabled/disabled.component'; import { DemoDatepickerFormsComponent } from './demos/forms/forms.component'; @@ -205,9 +206,21 @@ export const demoComponentContent: ContentSection[] = [ html: require('!!raw-loader!./demos/disable-dates/disable-dates.html'), description: `

You can set which dates should be disabled with datesDisabled

-

In the following example datesDisabled is set with an array to disable 2019-02-05 and 2019-02-09.

`, +

In the following example datesDisabled is set with an array to disable 2019-02-05 and 2019-02-09.

+

NOTE: DO NOT USE this functionality with datesEnabled at the same time

`, outlet: DemoDatepickerDatesDisabledComponent }, + { + title: 'Dates enabled', + anchor: 'dates-enabled', + component: require('!!raw-loader!./demos/enable-dates/enable-dates.ts'), + html: require('!!raw-loader!./demos/enable-dates/enable-dates.html'), + description: ` +

You can set which dates should be enable with datesEnabled

+

In the following example datesEnabled is set with an array to enable 2020-02-06, 2020-02-08 and 2020-02-11. All other dates are disabled

+

NOTE: DO NOT USE this functionality with datesDisabled at the same time

`, + outlet: DemoDatepickerDatesEnabledComponent + }, { title: 'Min-mode', anchor: 'min-mode', diff --git a/demo/src/app/components/+datepicker/demos/enable-dates/enable-dates.html b/demo/src/app/components/+datepicker/demos/enable-dates/enable-dates.html new file mode 100644 index 0000000000..5332c660ad --- /dev/null +++ b/demo/src/app/components/+datepicker/demos/enable-dates/enable-dates.html @@ -0,0 +1,16 @@ +
+
+ +
+
+ +
+
diff --git a/demo/src/app/components/+datepicker/demos/enable-dates/enable-dates.ts b/demo/src/app/components/+datepicker/demos/enable-dates/enable-dates.ts new file mode 100644 index 0000000000..0b2581b8f6 --- /dev/null +++ b/demo/src/app/components/+datepicker/demos/enable-dates/enable-dates.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'demo-datepicker-datesenabled', + templateUrl: './enable-dates.html' +}) +export class DemoDatepickerDatesEnabledComponent { + enabledDates = [ + new Date('2020-02-06'), + new Date('2020-02-08'), + new Date('2020-02-11'), + ]; +} diff --git a/demo/src/app/components/+datepicker/demos/index.ts b/demo/src/app/components/+datepicker/demos/index.ts index 409f2a0658..3b401d09ed 100644 --- a/demo/src/app/components/+datepicker/demos/index.ts +++ b/demo/src/app/components/+datepicker/demos/index.ts @@ -10,6 +10,7 @@ import { DemoDatepickerCustomTodayClassComponent } from './custom-today-class/cu import { DemoDatepickerDateInitialStateComponent } from './date-initial-state/date-initial-state'; import { DemoDatepickerDaysDisabledComponent } from './disable-days/disable-days'; import { DemoDatepickerDatesDisabledComponent } from './disable-dates/disable-dates'; +import { DemoDatepickerDatesEnabledComponent } from './enable-dates/enable-dates'; import { DemoDatepickerDisabledComponent } from './disabled/disabled.component'; import { DemoDatepickerFormsComponent } from './forms/forms.component'; import { DemoDatepickerHideOnScrollComponent } from './hide-on-scroll/hide-on-scroll'; @@ -52,6 +53,7 @@ export const DEMO_COMPONENTS = [ DemoDatepickerDateCustomClassesComponent, DemoDatepickerDateInitialStateComponent, DemoDatepickerDatesDisabledComponent, + DemoDatepickerDatesEnabledComponent, DemoDatepickerDaysDisabledComponent, DemoDatepickerDisabledComponent, DemoDatepickerFormsComponent, diff --git a/src/datepicker/base/bs-datepicker-container.ts b/src/datepicker/base/bs-datepicker-container.ts index e0df4710ca..11667cad8c 100644 --- a/src/datepicker/base/bs-datepicker-container.ts +++ b/src/datepicker/base/bs-datepicker-container.ts @@ -38,6 +38,10 @@ export abstract class BsDatepickerAbstractComponent { this._effects.setDatesDisabled(value); } + set datesEnabled(value: Date[]) { + this._effects.setDatesEnabled(value); + } + set isDisabled(value: boolean) { this._effects.setDisabled(value); } diff --git a/src/datepicker/bs-datepicker-inline.component.ts b/src/datepicker/bs-datepicker-inline.component.ts index f0262f17f8..52f253ac54 100644 --- a/src/datepicker/bs-datepicker-inline.component.ts +++ b/src/datepicker/bs-datepicker-inline.component.ts @@ -53,6 +53,10 @@ export class BsDatepickerInlineDirective implements OnInit, OnDestroy, OnChanges /** * Disable specific dates */ + @Input() datesEnabled: Date[]; + /** + * Enable specific dates + */ @Input() datesDisabled: Date[]; /** * Emits when datepicker value has been changed @@ -124,6 +128,11 @@ export class BsDatepickerInlineDirective implements OnInit, OnDestroy, OnChanges this._datepickerRef.instance.value = this._bsValue; } + if (changes.datesEnabled) { + this._datepickerRef.instance.datesEnabled = this.datesEnabled; + this._datepickerRef.instance.value = this._bsValue; + } + if (changes.isDisabled) { this._datepickerRef.instance.isDisabled = this.isDisabled; } @@ -143,7 +152,8 @@ export class BsDatepickerInlineDirective implements OnInit, OnDestroy, OnChanges minDate: this.minDate || this.bsConfig && this.bsConfig.minDate, maxDate: this.maxDate || this.bsConfig && this.bsConfig.maxDate, dateCustomClasses: this.dateCustomClasses || this.bsConfig && this.bsConfig.dateCustomClasses, - datesDisabled: this.datesDisabled || this.bsConfig && this.bsConfig.datesDisabled + datesDisabled: this.datesDisabled || this.bsConfig && this.bsConfig.datesDisabled, + datesEnabled: this.datesEnabled || this.bsConfig && this.bsConfig.datesEnabled }); if (this._datepickerRef !== undefined) { diff --git a/src/datepicker/bs-datepicker.component.ts b/src/datepicker/bs-datepicker.component.ts index 6dd2eb84eb..2ab7bade16 100644 --- a/src/datepicker/bs-datepicker.component.ts +++ b/src/datepicker/bs-datepicker.component.ts @@ -104,6 +104,10 @@ export class BsDatepickerDirective implements OnInit, OnDestroy, OnChanges { * Disable specific dates */ @Input() datesDisabled: Date[]; + /** + * Enable specific dates + */ + @Input() datesEnabled: Date[]; /** * Date custom classes */ @@ -165,6 +169,10 @@ export class BsDatepickerDirective implements OnInit, OnDestroy, OnChanges { this._datepickerRef.instance.datesDisabled = this.datesDisabled; } + if (changes.datesEnabled) { + this._datepickerRef.instance.datesEnabled = this.datesEnabled; + } + if (changes.isDisabled) { this._datepickerRef.instance.isDisabled = this.isDisabled; } @@ -249,6 +257,7 @@ export class BsDatepickerDirective implements OnInit, OnDestroy, OnChanges { daysDisabled: this.daysDisabled || this.bsConfig && this.bsConfig.daysDisabled, dateCustomClasses: this.dateCustomClasses || this.bsConfig && this.bsConfig.dateCustomClasses, datesDisabled: this.datesDisabled || this.bsConfig && this.bsConfig.datesDisabled, + datesEnabled: this.datesEnabled || this.bsConfig && this.bsConfig.datesEnabled, minMode: this.minMode || this.bsConfig && this.bsConfig.minMode }); } diff --git a/src/datepicker/bs-datepicker.config.ts b/src/datepicker/bs-datepicker.config.ts index 7e93d6f148..ef80030546 100644 --- a/src/datepicker/bs-datepicker.config.ts +++ b/src/datepicker/bs-datepicker.config.ts @@ -41,6 +41,10 @@ export class BsDatepickerConfig implements DatepickerRenderOptions { * Disable specific dates */ datesDisabled?: Date[]; + /** + * Enable specific dates + */ + datesEnabled?: Date[]; /** * Makes dates from other months active */ diff --git a/src/datepicker/bs-daterangepicker-inline.component.ts b/src/datepicker/bs-daterangepicker-inline.component.ts index 7b6ab443b7..54b99d8f7b 100644 --- a/src/datepicker/bs-daterangepicker-inline.component.ts +++ b/src/datepicker/bs-daterangepicker-inline.component.ts @@ -59,6 +59,10 @@ export class BsDaterangepickerInlineDirective implements OnInit, OnDestroy, OnCh * Disable specific dates */ @Input() datesDisabled: Date[]; + /** + * Disable specific dates + */ + @Input() datesEnabled: Date[]; /** * Emits when daterangepicker value has been changed */ @@ -126,6 +130,10 @@ export class BsDaterangepickerInlineDirective implements OnInit, OnDestroy, OnCh this._datepickerRef.instance.maxDate = this.maxDate; } + if (changes.datesEnabled) { + this._datepickerRef.instance.datesEnabled = this.datesEnabled; + } + if (changes.datesDisabled) { this._datepickerRef.instance.datesDisabled = this.datesDisabled; } @@ -155,6 +163,7 @@ export class BsDaterangepickerInlineDirective implements OnInit, OnDestroy, OnCh daysDisabled: this.daysDisabled || this.bsConfig && this.bsConfig.daysDisabled, dateCustomClasses: this.dateCustomClasses || this.bsConfig && this.bsConfig.dateCustomClasses, datesDisabled: this.datesDisabled || this.bsConfig && this.bsConfig.datesDisabled, + datesEnabled: this.datesEnabled || this.bsConfig && this.bsConfig.datesEnabled, ranges: this.bsConfig && this.bsConfig.ranges }); } diff --git a/src/datepicker/bs-daterangepicker.component.ts b/src/datepicker/bs-daterangepicker.component.ts index c8dc45c850..1ea0fae434 100644 --- a/src/datepicker/bs-daterangepicker.component.ts +++ b/src/datepicker/bs-daterangepicker.component.ts @@ -115,6 +115,10 @@ export class BsDaterangepickerDirective */ @Input() datesDisabled: Date[]; + /** + * Enable specific dates + */ + @Input() datesEnabled: Date[]; /** * Emits when daterangepicker value has been changed */ @@ -167,6 +171,10 @@ export class BsDaterangepickerDirective this._datepickerRef.instance.datesDisabled = this.datesDisabled; } + if (changes.datesEnabled) { + this._datepickerRef.instance.datesEnabled = this.datesEnabled; + } + if (changes.daysDisabled) { this._datepickerRef.instance.daysDisabled = this.daysDisabled; } @@ -234,6 +242,7 @@ export class BsDaterangepickerDirective daysDisabled: this.daysDisabled || this.bsConfig && this.bsConfig.daysDisabled, dateCustomClasses: this.dateCustomClasses || this.bsConfig && this.bsConfig.dateCustomClasses, datesDisabled: this.datesDisabled || this.bsConfig && this.bsConfig.datesDisabled, + datesEnabled: this.datesEnabled || this.bsConfig && this.bsConfig.datesEnabled, ranges: this.bsConfig && this.bsConfig.ranges } ); diff --git a/src/datepicker/engine/flag-days-calendar.ts b/src/datepicker/engine/flag-days-calendar.ts index 0c2d4865ae..7adbe888fb 100644 --- a/src/datepicker/engine/flag-days-calendar.ts +++ b/src/datepicker/engine/flag-days-calendar.ts @@ -14,7 +14,7 @@ import { shiftDate } from 'ngx-bootstrap/chronos'; -import { isMonthDisabled, isDisabledDate } from '../utils/bs-calendar-utils'; +import { isMonthDisabled, isDisabledDate, isEnabledDate } from '../utils/bs-calendar-utils'; export interface FlagDaysCalendarOptions { isDisabled: boolean; @@ -22,6 +22,7 @@ export interface FlagDaysCalendarOptions { maxDate: Date; daysDisabled: number[]; datesDisabled: Date[]; + datesEnabled: Date[]; hoveredDate: Date; selectedDate: Date; selectedRange: Date[]; @@ -67,7 +68,8 @@ export function flagDaysCalendar( isBefore(day.date, options.minDate, 'day') || isAfter(day.date, options.maxDate, 'day') || isDisabledDay(day.date, options.daysDisabled) || - isDisabledDate(day.date, options.datesDisabled); + isDisabledDate(day.date, options.datesDisabled) || + isEnabledDate(day.date, options.datesEnabled); const currentDate = new Date(); const isToday = !isOtherMonth && isSameDay(day.date, currentDate); diff --git a/src/datepicker/engine/flag-days.calendar.spec.ts b/src/datepicker/engine/flag-days.calendar.spec.ts index 48be80497f..43ff6f944c 100644 --- a/src/datepicker/engine/flag-days.calendar.spec.ts +++ b/src/datepicker/engine/flag-days.calendar.spec.ts @@ -2,42 +2,85 @@ import { flagDaysCalendar } from './flag-days-calendar'; describe('flag-days-calendar:', () => { - it('should flag days as disabled when they are part of the datesDisabled', () => { - const weekViewModel = { - month: new Date('2019-02-01'), - weeks: [ - { - days: [ - { date: new Date('2019-02-07'), label: '2019-02-07' }, - { date: new Date('2019-02-08'), label: '2019-02-08' }, - { date: new Date('2019-02-09'), label: '2019-02-09' } - ]} - ], - weekNumbers: [], - weekdays: [], - monthTitle: '', - yearTitle: '' - }; - const datesDisabled = [ - new Date('2019-02-07'), - new Date('2019-02-09') - ]; - const result = flagDaysCalendar(weekViewModel, { - datesDisabled, - isDisabled: false, - minDate: new Date('2019-01-01'), - maxDate: new Date('2019-12-31'), - daysDisabled: [], - hoveredDate: new Date('2019-02-06'), - selectedDate: new Date('2019-02-05'), - selectedRange: [], - displayMonths: 1, - monthIndex: 1, - dateCustomClasses: [] - }); + it('should flag days as disabled when they are part of the datesDisabled', () => { + const weekViewModel = { + month: new Date('2019-02-01'), + weeks: [ + { + days: [ + { date: new Date('2019-02-07'), label: '2019-02-07' }, + { date: new Date('2019-02-08'), label: '2019-02-08' }, + { date: new Date('2019-02-09'), label: '2019-02-09' } + ] + } + ], + weekNumbers: [], + weekdays: [], + monthTitle: '', + yearTitle: '' + }; + const datesDisabled = [ + new Date('2019-02-07'), + new Date('2019-02-09') + ]; + const result = flagDaysCalendar(weekViewModel, { + datesDisabled, + isDisabled: false, + minDate: new Date('2019-01-01'), + maxDate: new Date('2019-12-31'), + daysDisabled: [], + datesEnabled: [], + hoveredDate: new Date('2019-02-06'), + selectedDate: new Date('2019-02-05'), + selectedRange: [], + displayMonths: 1, + monthIndex: 1, + dateCustomClasses: [] + }); + + expect(result.weeks[0].days.find(day => day.label === '2019-02-07').isDisabled).toBe(true); + expect(result.weeks[0].days.find(day => day.label === '2019-02-08').isDisabled).toBe(false); + expect(result.weeks[0].days.find(day => day.label === '2019-02-09').isDisabled).toBe(true); + }); - expect(result.weeks[0].days.find(day => day.label === '2019-02-07').isDisabled).toBe(true); - expect(result.weeks[0].days.find(day => day.label === '2019-02-08').isDisabled).toBe(false); - expect(result.weeks[0].days.find(day => day.label === '2019-02-09').isDisabled).toBe(true); + it('should flag days as disabled when they are not part of the datesEnabled', () => { + const weekViewModel = { + month: new Date('2020-02-01'), + weeks: [ + { + days: [ + { date: new Date('2020-02-07'), label: '2020-02-07' }, + { date: new Date('2020-02-08'), label: '2020-02-08' }, + { date: new Date('2020-02-09'), label: '2020-02-09' } + ] + } + ], + weekNumbers: [], + weekdays: [], + monthTitle: '', + yearTitle: '' + }; + const datesEnabled = [ + new Date('2020-02-07'), + new Date('2020-02-09') + ]; + const result = flagDaysCalendar(weekViewModel, { + datesEnabled, + isDisabled: false, + minDate: new Date('2020-01-01'), + maxDate: new Date('2020-12-31'), + daysDisabled: [], + datesDisabled: [], + hoveredDate: new Date('2020-02-06'), + selectedDate: new Date('2020-02-05'), + selectedRange: [], + displayMonths: 1, + monthIndex: 1, + dateCustomClasses: [] }); - }); + + expect(result.weeks[0].days.find(day => day.label === '2020-02-07').isDisabled).toBe(false); + expect(result.weeks[0].days.find(day => day.label === '2020-02-08').isDisabled).toBe(true); + expect(result.weeks[0].days.find(day => day.label === '2020-02-09').isDisabled).toBe(false); + }); +}); diff --git a/src/datepicker/reducer/bs-datepicker.actions.ts b/src/datepicker/reducer/bs-datepicker.actions.ts index 9c98bb5ea8..4ded45b8f1 100644 --- a/src/datepicker/reducer/bs-datepicker.actions.ts +++ b/src/datepicker/reducer/bs-datepicker.actions.ts @@ -25,6 +25,7 @@ export class BsDatepickerActions { static readonly SET_MAX_DATE = '[datepicker] set max date'; static readonly SET_DAYSDISABLED = '[datepicker] set days disabled'; static readonly SET_DATESDISABLED = '[datepicker] set dates disabled'; + static readonly SET_DATESENABLED = '[datepicker] set dates enabled'; static readonly SET_IS_DISABLED = '[datepicker] set is disabled'; static readonly SET_DATE_CUSTOM_CLASSES = '[datepicker] set date custom classes'; @@ -122,6 +123,13 @@ export class BsDatepickerActions { }; } + datesEnabled(dates: Date[]): Action { + return { + type: BsDatepickerActions.SET_DATESENABLED, + payload: dates + }; + } + isDisabled(value: boolean): Action { return { type: BsDatepickerActions.SET_IS_DISABLED, diff --git a/src/datepicker/reducer/bs-datepicker.effects.ts b/src/datepicker/reducer/bs-datepicker.effects.ts index 8796ba10ef..b93ef7dc34 100644 --- a/src/datepicker/reducer/bs-datepicker.effects.ts +++ b/src/datepicker/reducer/bs-datepicker.effects.ts @@ -67,18 +67,24 @@ export class BsDatepickerEffects { return this; } - setDaysDisabled(value: number[]) { + setDaysDisabled(value: number[]): BsDatepickerEffects { this._store.dispatch(this._actions.daysDisabled(value)); return this; } - setDatesDisabled(value: Date[]) { + setDatesDisabled(value: Date[]): BsDatepickerEffects { this._store.dispatch(this._actions.datesDisabled(value)); return this; } + setDatesEnabled(value: Date[]): BsDatepickerEffects { + this._store.dispatch(this._actions.datesEnabled(value)); + + return this; + } + setDisabled(value: boolean): BsDatepickerEffects { this._store.dispatch(this._actions.isDisabled(value)); diff --git a/src/datepicker/reducer/bs-datepicker.reducer.ts b/src/datepicker/reducer/bs-datepicker.reducer.ts index e1570143b9..31e0e497c1 100644 --- a/src/datepicker/reducer/bs-datepicker.reducer.ts +++ b/src/datepicker/reducer/bs-datepicker.reducer.ts @@ -291,6 +291,7 @@ function flagReducer(state: BsDatepickerState, maxDate: state.maxDate, daysDisabled: state.daysDisabled, datesDisabled: state.datesDisabled, + datesEnabled: state.datesEnabled, hoveredDate: state.hoveredDate, selectedDate: state.selectedDate, selectedRange: state.selectedRange, diff --git a/src/datepicker/reducer/bs-datepicker.state.ts b/src/datepicker/reducer/bs-datepicker.state.ts index f8ce2f3982..0dcc129ff5 100644 --- a/src/datepicker/reducer/bs-datepicker.state.ts +++ b/src/datepicker/reducer/bs-datepicker.state.ts @@ -33,6 +33,7 @@ export class BsDatepickerState maxDate?: Date; daysDisabled?: number[]; datesDisabled?: Date[]; + datesEnabled?: Date[]; minMode?: BsDatepickerViewMode; dateCustomClasses?: DatepickerDateCustomClasses[]; diff --git a/src/datepicker/utils/bs-calendar-utils.ts b/src/datepicker/utils/bs-calendar-utils.ts index 94cee4779f..25e3edad82 100644 --- a/src/datepicker/utils/bs-calendar-utils.ts +++ b/src/datepicker/utils/bs-calendar-utils.ts @@ -6,6 +6,7 @@ import { shiftDate, endOf, startOf, + isArray, isSame } from 'ngx-bootstrap/chronos'; import { BsDatepickerState } from '../reducer/bs-datepicker.state'; @@ -47,13 +48,21 @@ export function isYearDisabled(date: Date, min: Date, max: Date): boolean { } export function isDisabledDate(date: Date, datesDisabled: Date[]): boolean { - if (datesDisabled === undefined || !datesDisabled || !datesDisabled.length) { + if (!datesDisabled || !isArray(datesDisabled) || !datesDisabled.length) { return false; } return datesDisabled.some((dateDisabled: Date) => isSame(date, dateDisabled, 'date')); } +export function isEnabledDate(date: Date, datesEnabled: Date[]): boolean { + if (!datesEnabled || !isArray(datesEnabled) || !datesEnabled.length) { + return false; + } + + return !datesEnabled.some((enabledDate: Date) => isSame(date, enabledDate, 'date')); +} + export function getYearsCalendarInitialDate(state: BsDatepickerState, calendarIndex = 0): Date { const model = state && state.yearsCalendarModel && state.yearsCalendarModel[calendarIndex];