UNPKG

ngx-bootstrap

Version:
1,257 lines (1,237 loc) 213 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, Component, HostBinding, ViewChild, Directive, forwardRef, HostListener, Host, NgModule } from '@angular/core'; import { filter, map, take, takeUntil, distinctUntilChanged } from 'rxjs/operators'; import { isFirstDayOfWeek, getDay, shiftDate, isBefore, endOf, isAfter, startOf, isArray, isSame, getFirstDayOfMonth, formatDate, getLocale, isSameMonth, isSameDay, isDisabledDay, isSameYear, isDateValid, setFullDate, getFullYear, getMonth, isDate, parseDate, utcAsLocal } from 'ngx-bootstrap/chronos'; import * as i5 from 'ngx-bootstrap/positioning'; import { PositioningService } from 'ngx-bootstrap/positioning'; import * as i6 from 'ngx-bootstrap/timepicker'; import { TimepickerModule } from 'ngx-bootstrap/timepicker'; import { trigger, state, transition, style, animate } from '@angular/animations'; import { Subscription, BehaviorSubject, combineLatest, Subject } from 'rxjs'; import { MiniStore, MiniState } from 'ngx-bootstrap/mini-ngrx'; import { NgFor, NgIf, NgClass, NgSwitch, NgSwitchCase, AsyncPipe, CommonModule } from '@angular/common'; import * as i2 from 'ngx-bootstrap/tooltip'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import * as i2$1 from 'ngx-bootstrap/component-loader'; import { ComponentLoaderFactory } from 'ngx-bootstrap/component-loader'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; /** * For date range picker there are `BsDaterangepickerConfig` which inherits all properties, * except `displayMonths`, for range picker it default to `2` */ class BsDatepickerConfig { constructor() { /** sets use adaptive position */ this.adaptivePosition = false; /** sets use UTC date time format */ this.useUtc = false; /** turn on/off animation */ this.isAnimated = false; /** * The view that the datepicker should start in */ this.startView = 'day'; /** * If true, returns focus to the datepicker / daterangepicker input after date selection */ this.returnFocusToInput = false; /** CSS class which will be applied to datepicker container, * usually used to set color theme */ this.containerClass = 'theme-green'; // DatepickerRenderOptions this.displayMonths = 1; /** * Allows to hide week numbers in datepicker */ this.showWeekNumbers = true; this.dateInputFormat = 'L'; // range picker this.rangeSeparator = ' - '; /** * Date format for date range input field */ this.rangeInputFormat = 'L'; // DatepickerFormatOptions this.monthTitle = 'MMMM'; this.yearTitle = 'YYYY'; this.dayLabel = 'D'; this.monthLabel = 'MMMM'; this.yearLabel = 'YYYY'; this.weekNumbers = 'w'; /** * Shows 'today' button */ this.showTodayButton = false; /** * Shows clear button */ this.showClearButton = false; /** * Positioning of 'today' button */ this.todayPosition = 'center'; /** * Positioning of 'clear' button */ this.clearPosition = 'right'; /** * Label for 'today' button */ this.todayButtonLabel = 'Today'; /** * Label for 'clear' button */ this.clearButtonLabel = 'Clear'; /** * Label for 'custom range' button */ this.customRangeButtonLabel = 'Custom Range'; /** * Shows timepicker under datepicker */ this.withTimepicker = false; /** * Set allowed positions of container. */ this.allowedPositions = ['top', 'bottom']; /** * Set rule for datepicker closing. If value is true datepicker closes only if date is changed, if user changes only time datepicker doesn't close. It is available only if property withTimepicker is set true * */ this.keepDatepickerOpened = false; /** * Allows keep invalid dates in range. Can be used with minDate, maxDate * */ this.keepDatesOutOfRules = false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerConfig, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerConfig, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); const DATEPICKER_ANIMATION_TIMING = '220ms cubic-bezier(0, 0, 0.2, 1)'; const datepickerAnimation = trigger('datepickerAnimation', [ state('animated-down', style({ height: '*', overflow: 'hidden' })), transition('* => animated-down', [ style({ height: 0, overflow: 'hidden' }), animate(DATEPICKER_ANIMATION_TIMING) ]), state('animated-up', style({ height: '*', overflow: 'hidden' })), transition('* => animated-up', [ style({ height: '*', overflow: 'hidden' }), animate(DATEPICKER_ANIMATION_TIMING) ]), transition('* => unanimated', animate('0s')) ]); class BsDatepickerAbstractComponent { constructor() { this.containerClass = ''; this.customRanges = []; this.chosenRange = []; this._daysCalendarSub = new Subscription(); this.selectedTimeSub = new Subscription(); } set minDate(value) { this._effects?.setMinDate(value); } set maxDate(value) { this._effects?.setMaxDate(value); } set daysDisabled(value) { this._effects?.setDaysDisabled(value); } set datesDisabled(value) { this._effects?.setDatesDisabled(value); } set datesEnabled(value) { this._effects?.setDatesEnabled(value); } set isDisabled(value) { this._effects?.setDisabled(value); } set dateCustomClasses(value) { this._effects?.setDateCustomClasses(value); } set dateTooltipTexts(value) { this._effects?.setDateTooltipTexts(value); } set daysCalendar$(value) { this._daysCalendar$ = value; this._daysCalendarSub.unsubscribe(); this._daysCalendarSub.add(this._daysCalendar$.subscribe(value => { this.multipleCalendars = !!value && value.length > 1; })); } get daysCalendar$() { return this._daysCalendar$; } // todo: valorkin fix // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function setViewMode(event) { } // eslint-disable-next-line navigateTo(event) { } // eslint-disable-next-line dayHoverHandler(event) { } // eslint-disable-next-line weekHoverHandler(event) { } // eslint-disable-next-line monthHoverHandler(event) { } // eslint-disable-next-line yearHoverHandler(event) { } // eslint-disable-next-line timeSelectHandler(date, index) { } // eslint-disable-next-line daySelectHandler(day) { } // eslint-disable-next-line monthSelectHandler(event) { } // eslint-disable-next-line yearSelectHandler(event) { } // eslint-disable-next-line setRangeOnCalendar(dates) { } // eslint-disable-next-line setToday() { } // eslint-disable-next-line clearDate() { } // eslint-disable-next-line @typescript-eslint/no-explicit-any _stopPropagation(event) { event.stopPropagation(); } } class BsDatepickerActions { static { this.CALCULATE = '[datepicker] calculate dates matrix'; } static { this.FORMAT = '[datepicker] format datepicker values'; } static { this.FLAG = '[datepicker] set flags'; } static { this.SELECT = '[datepicker] select date'; } static { this.NAVIGATE_OFFSET = '[datepicker] shift view date'; } static { this.NAVIGATE_TO = '[datepicker] change view date'; } static { this.SET_OPTIONS = '[datepicker] update render options'; } static { this.HOVER = '[datepicker] hover date'; } static { this.CHANGE_VIEWMODE = '[datepicker] switch view mode'; } static { this.SET_MIN_DATE = '[datepicker] set min date'; } static { this.SET_MAX_DATE = '[datepicker] set max date'; } static { this.SET_DAYSDISABLED = '[datepicker] set days disabled'; } static { this.SET_DATESDISABLED = '[datepicker] set dates disabled'; } static { this.SET_DATESENABLED = '[datepicker] set dates enabled'; } static { this.SET_IS_DISABLED = '[datepicker] set is disabled'; } static { this.SET_DATE_CUSTOM_CLASSES = '[datepicker] set date custom classes'; } static { this.SET_DATE_TOOLTIP_TEXTS = '[datepicker] set date tooltip texts'; } static { this.SET_LOCALE = '[datepicker] set datepicker locale'; } static { this.SELECT_TIME = '[datepicker] select time'; } static { this.SELECT_RANGE = '[daterangepicker] select dates range'; } calculate() { return { type: BsDatepickerActions.CALCULATE }; } format() { return { type: BsDatepickerActions.FORMAT }; } flag() { return { type: BsDatepickerActions.FLAG }; } select(date) { return { type: BsDatepickerActions.SELECT, payload: date }; } selectTime(date, index) { return { type: BsDatepickerActions.SELECT_TIME, payload: { date, index }, }; } changeViewMode(event) { return { type: BsDatepickerActions.CHANGE_VIEWMODE, payload: event }; } navigateTo(event) { return { type: BsDatepickerActions.NAVIGATE_TO, payload: event }; } navigateStep(step) { return { type: BsDatepickerActions.NAVIGATE_OFFSET, payload: step }; } setOptions(options) { return { type: BsDatepickerActions.SET_OPTIONS, payload: options }; } // date range picker selectRange(value) { return { type: BsDatepickerActions.SELECT_RANGE, payload: value }; } hoverDay(event) { return { type: BsDatepickerActions.HOVER, payload: event.isHovered ? event.cell.date : null }; } minDate(date) { return { type: BsDatepickerActions.SET_MIN_DATE, payload: date }; } maxDate(date) { return { type: BsDatepickerActions.SET_MAX_DATE, payload: date }; } daysDisabled(days) { return { type: BsDatepickerActions.SET_DAYSDISABLED, payload: days }; } datesDisabled(dates) { return { type: BsDatepickerActions.SET_DATESDISABLED, payload: dates }; } datesEnabled(dates) { return { type: BsDatepickerActions.SET_DATESENABLED, payload: dates }; } isDisabled(value) { return { type: BsDatepickerActions.SET_IS_DISABLED, payload: value }; } setDateCustomClasses(value) { return { type: BsDatepickerActions.SET_DATE_CUSTOM_CLASSES, payload: value }; } setDateTooltipTexts(value) { return { type: BsDatepickerActions.SET_DATE_TOOLTIP_TEXTS, payload: value }; } setLocale(locale) { return { type: BsDatepickerActions.SET_LOCALE, payload: locale }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerActions, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerActions, providedIn: 'platform' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerActions, decorators: [{ type: Injectable, args: [{ providedIn: 'platform' }] }] }); class BsLocaleService { constructor() { this._defaultLocale = 'en'; this._locale = new BehaviorSubject(this._defaultLocale); this._localeChange = this._locale.asObservable(); } get locale() { return this._locale; } get localeChange() { return this._localeChange; } get currentLocale() { return this._locale.getValue(); } use(locale) { if (locale === this.currentLocale) { return; } this._locale.next(locale); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsLocaleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsLocaleService, providedIn: 'platform' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsLocaleService, decorators: [{ type: Injectable, args: [{ providedIn: 'platform' }] }] }); class BsDatepickerEffects { constructor(_actions, _localeService) { this._actions = _actions; this._localeService = _localeService; this._subs = []; } init(_bsDatepickerStore) { this._store = _bsDatepickerStore; return this; } /** setters */ setValue(value) { this._store?.dispatch(this._actions.select(value)); } setRangeValue(value) { this._store?.dispatch(this._actions.selectRange(value)); } setMinDate(value) { this._store?.dispatch(this._actions.minDate(value)); return this; } setMaxDate(value) { this._store?.dispatch(this._actions.maxDate(value)); return this; } setDaysDisabled(value) { this._store?.dispatch(this._actions.daysDisabled(value)); return this; } setDatesDisabled(value) { this._store?.dispatch(this._actions.datesDisabled(value)); return this; } setDatesEnabled(value) { this._store?.dispatch(this._actions.datesEnabled(value)); return this; } setDisabled(value) { this._store?.dispatch(this._actions.isDisabled(value)); return this; } setDateCustomClasses(value) { this._store?.dispatch(this._actions.setDateCustomClasses(value)); return this; } setDateTooltipTexts(value) { this._store?.dispatch(this._actions.setDateTooltipTexts(value)); return this; } /* Set rendering options */ setOptions(_config) { const _options = Object.assign({ locale: this._localeService.currentLocale }, _config); this._store?.dispatch(this._actions.setOptions(_options)); return this; } /** view to mode bindings */ setBindings(container) { if (!this._store) { return this; } container.selectedTime = this._store.select(state => state.selectedTime) .pipe(filter(times => !!times)); container.daysCalendar$ = this._store.select(state => state.flaggedMonths) .pipe(filter(months => !!months)); // month calendar container.monthsCalendar = this._store.select(state => state.flaggedMonthsCalendar) .pipe(filter(months => !!months)); // year calendar container.yearsCalendar = this._store.select(state => state.yearsCalendarFlagged) .pipe(filter(years => !!years)); container.viewMode = this._store.select(state => state.view?.mode); container.options$ = combineLatest([ this._store.select(state => state.showWeekNumbers), this._store.select(state => state.displayMonths) ]) .pipe(map((latest) => ({ showWeekNumbers: latest[0], displayMonths: latest[1] }))); return this; } /** event handlers */ setEventHandlers(container) { container.setViewMode = (event) => { this._store?.dispatch(this._actions.changeViewMode(event)); }; container.navigateTo = (event) => { this._store?.dispatch(this._actions.navigateStep(event.step)); }; container.dayHoverHandler = (event) => { const _cell = event.cell; if (_cell.isOtherMonth || _cell.isDisabled) { return; } this._store?.dispatch(this._actions.hoverDay(event)); _cell.isHovered = event.isHovered; }; container.monthHoverHandler = (event) => { event.cell.isHovered = event.isHovered; }; container.yearHoverHandler = (event) => { event.cell.isHovered = event.isHovered; }; return this; } registerDatepickerSideEffects() { if (!this._store) { return this; } this._subs.push(this._store.select(state => state.view).subscribe(() => { this._store?.dispatch(this._actions.calculate()); })); // format calendar values on month model change this._subs.push(this._store .select(state => state.monthsModel) .pipe(filter(monthModel => !!monthModel)) .subscribe(() => this._store?.dispatch(this._actions.format()))); // flag day values this._subs.push(this._store .select(state => state.formattedMonths) .pipe(filter(month => !!month)) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // flag day values this._subs.push(this._store .select(state => state.selectedDate) .pipe(filter(selectedDate => !!selectedDate)) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // flag for date range picker this._subs.push(this._store .select(state => state.selectedRange) .pipe(filter(selectedRange => !!selectedRange)) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // monthsCalendar this._subs.push(this._store .select(state => state.monthsCalendar) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // years calendar this._subs.push(this._store .select(state => state.yearsCalendarModel) .pipe(filter(state => !!state)) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // on hover this._subs.push(this._store .select(state => state.hoveredDate) .pipe(filter(hoveredDate => !!hoveredDate)) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // date custom classes this._subs.push(this._store .select(state => state.dateCustomClasses) .pipe(filter(dateCustomClasses => !!dateCustomClasses)) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // date tooltip texts this._subs.push(this._store .select(state => state.dateTooltipTexts) .pipe(filter(dateTooltipTexts => !!dateTooltipTexts)) .subscribe(() => this._store?.dispatch(this._actions.flag()))); // on locale change this._subs.push(this._localeService.localeChange .subscribe(locale => this._store?.dispatch(this._actions.setLocale(locale)))); return this; } destroy() { for (const sub of this._subs) { sub.unsubscribe(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerEffects, deps: [{ token: BsDatepickerActions }, { token: BsLocaleService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerEffects, providedIn: 'platform' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: BsDatepickerEffects, decorators: [{ type: Injectable, args: [{ providedIn: 'platform' }] }], ctorParameters: () => [{ type: BsDatepickerActions }, { type: BsLocaleService }] }); const defaultMonthOptions = { width: 7, height: 6 }; const dayInMilliseconds = 24 * 60 * 60 * 1000; class BsDatepickerState { constructor() { // DatepickerRenderOptions this.showWeekNumbers = true; this.displayMonths = 1; } } const _initialView = { date: new Date(), mode: 'day' }; const initialDatepickerState = Object.assign(new BsDatepickerConfig(), { locale: 'en', view: _initialView, selectedRange: [], selectedTime: [], monthViewOptions: defaultMonthOptions }); function getStartingDayOfCalendar(date, options) { if (isFirstDayOfWeek(date, options.firstDayOfWeek)) { return date; } const weekDay = getDay(date); const offset = calculateDateOffset(weekDay, options.firstDayOfWeek); return shiftDate(date, { day: -offset }); } function calculateDateOffset(weekday, startingDayOffset) { const _startingDayOffset = Number(startingDayOffset); if (isNaN(_startingDayOffset)) { return 0; } if (_startingDayOffset === 0) { return weekday; } const offset = weekday - _startingDayOffset % 7; return offset < 0 ? offset + 7 : offset; } function isMonthDisabled(date, min, max) { const minBound = min && isBefore(endOf(date, 'month'), min, 'day'); const maxBound = max && isAfter(startOf(date, 'month'), max, 'day'); return minBound || maxBound || false; } function isYearDisabled(date, min, max) { const minBound = min && isBefore(endOf(date, 'year'), min, 'day'); const maxBound = max && isAfter(startOf(date, 'year'), max, 'day'); return minBound || maxBound || false; } function isDisabledDate(date, datesDisabled, unit) { if (!datesDisabled || !isArray(datesDisabled) || !datesDisabled.length) { return false; } if (unit && unit === 'year' && !datesDisabled[0].getDate()) { return datesDisabled.some((dateDisabled) => isSame(date, dateDisabled, 'year')); } return datesDisabled.some((dateDisabled) => isSame(date, dateDisabled, 'date')); } function isEnabledDate(date, datesEnabled, unit) { if (!datesEnabled || !isArray(datesEnabled) || !datesEnabled.length) { return false; } return !datesEnabled.some((enabledDate) => isSame(date, enabledDate, unit || 'date')); } function getYearsCalendarInitialDate(state, calendarIndex = 0) { const model = state && state.yearsCalendarModel && state.yearsCalendarModel[calendarIndex]; return model?.years[0] && model.years[0][0] && model.years[0][0].date; } function checkRangesWithMaxDate(ranges, maxDate) { if (!ranges) return ranges; if (!maxDate) return ranges; if (!ranges.length && !ranges[0].value) return ranges; ranges.forEach((item) => { if (!item || !item.value) return ranges; if (item.value instanceof Date) return ranges; if (!(item.value instanceof Array && item.value.length)) return ranges; item.value = compareDateWithMaxDateHelper(item.value, maxDate); return ranges; }); return ranges; } function checkBsValue(date, maxDate) { if (!date) return date; if (!maxDate) return date; if (date instanceof Array && !date.length) return date; if (date instanceof Date) return date; return compareDateWithMaxDateHelper(date, maxDate); } function compareDateWithMaxDateHelper(date, maxDate) { if (date instanceof Array) { const editedValues = date.map(item => { if (!item) return item; if (isAfter(item, maxDate, 'date')) item = maxDate; return item; }); return editedValues; } return date; } function setCurrentTimeOnDateSelect(value) { if (!value) return value; return setCurrentTimeHelper(value); } function setDateRangesCurrentTimeOnDateSelect(value) { if (!value?.length) return value; value.map((date) => { if (!date) { return date; } return setCurrentTimeHelper(date); }); return value; } function setCurrentTimeHelper(date) { const now = new Date(); date.setMilliseconds(now.getMilliseconds()); date.setSeconds(now.getSeconds()); date.setMinutes(now.getMinutes()); date.setHours(now.getHours()); return date; } function createMatrix(options, fn) { let prevValue = options.initialDate; const matrix = new Array(options.height); for (let i = 0; i < options.height; i++) { matrix[i] = new Array(options.width); for (let j = 0; j < options.width; j++) { matrix[i][j] = fn(prevValue); prevValue = shiftDate(prevValue, options.shift); } } return matrix; } // user and model input should handle parsing and validating input values function calcDaysCalendar(startingDate, options) { const firstDay = getFirstDayOfMonth(startingDate); const initialDate = getStartingDayOfCalendar(firstDay, options); // todo test const matrixOptions = { width: options.width || 0, height: options.height || 0, initialDate, shift: { day: 1 } }; const daysMatrix = createMatrix(matrixOptions, date => date); return { daysMatrix, month: firstDay }; } function formatDaysCalendar(daysCalendar, formatOptions, monthIndex) { return { month: daysCalendar.month, monthTitle: formatDate(daysCalendar.month, formatOptions.monthTitle, formatOptions.locale), yearTitle: formatDate(daysCalendar.month, formatOptions.yearTitle, formatOptions.locale), weekNumbers: getWeekNumbers(daysCalendar.daysMatrix, formatOptions.weekNumbers, formatOptions.locale), weekdays: getShiftedWeekdays(formatOptions.locale), weeks: daysCalendar.daysMatrix.map((week, weekIndex) => ({ days: week.map((date, dayIndex) => ({ date, label: formatDate(date, formatOptions.dayLabel, formatOptions.locale), monthIndex, weekIndex, dayIndex })) })), hideLeftArrow: false, hideRightArrow: false, disableLeftArrow: false, disableRightArrow: false }; } function getWeekNumbers(daysMatrix, format, locale) { return daysMatrix.map((days) => (days[0] ? formatDate(days[0], format, locale) : '')); } function getShiftedWeekdays(locale) { const _locale = getLocale(locale); const weekdays = _locale.weekdaysShort(); const firstDayOfWeek = _locale.firstDayOfWeek(); return [...weekdays.slice(firstDayOfWeek), ...weekdays.slice(0, firstDayOfWeek)]; } function flagDaysCalendar(formattedMonth, options) { formattedMonth.weeks.forEach((week) => { week.days.forEach((day, dayIndex) => { // datepicker const isOtherMonth = !isSameMonth(day.date, formattedMonth.month); const isHovered = !isOtherMonth && isSameDay(day.date, options.hoveredDate); // date range picker const isSelectionStart = !isOtherMonth && options.selectedRange && isSameDay(day.date, options.selectedRange[0]); const isSelectionEnd = !isOtherMonth && options.selectedRange && isSameDay(day.date, options.selectedRange[1]); const isSelected = (!isOtherMonth && isSameDay(day.date, options.selectedDate)) || isSelectionStart || isSelectionEnd; const isInRange = !isOtherMonth && options.selectedRange && isDateInRange(day.date, options.selectedRange, options.hoveredDate); const isDisabled = options.isDisabled || isBefore(day.date, options.minDate, 'day') || isAfter(day.date, options.maxDate, 'day') || isDisabledDay(day.date, options.daysDisabled) || isDisabledDate(day.date, options.datesDisabled) || isEnabledDate(day.date, options.datesEnabled); const currentDate = new Date(); const isToday = !isOtherMonth && isSameDay(day.date, currentDate); const customClasses = options.dateCustomClasses && options.dateCustomClasses .map(dcc => isSameDay(day.date, dcc.date) ? dcc.classes : []) .reduce((previousValue, currentValue) => previousValue.concat(currentValue), []) .join(' ') || ''; const tooltipText = options.dateTooltipTexts && options.dateTooltipTexts .map(tt => isSameDay(day.date, tt.date) ? tt.tooltipText : '') .reduce((previousValue, currentValue) => { previousValue.push(currentValue); return previousValue; }, []) .join(' ') || ''; // decide update or not const newDay = Object.assign({}, day, { isOtherMonth, isHovered, isSelected, isSelectionStart, isSelectionEnd, isInRange, isDisabled, isToday, customClasses, tooltipText }); if (day.isOtherMonth !== newDay.isOtherMonth || day.isHovered !== newDay.isHovered || day.isSelected !== newDay.isSelected || day.isSelectionStart !== newDay.isSelectionStart || day.isSelectionEnd !== newDay.isSelectionEnd || day.isDisabled !== newDay.isDisabled || day.isInRange !== newDay.isInRange || day.customClasses !== newDay.customClasses || day.tooltipText !== newDay.tooltipText) { week.days[dayIndex] = newDay; } }); }); // todo: add check for linked calendars formattedMonth.hideLeftArrow = options.isDisabled || (!!options.monthIndex && options.monthIndex > 0 && options.monthIndex !== options.displayMonths); formattedMonth.hideRightArrow = options.isDisabled || ((!!options.monthIndex || options.monthIndex === 0) && !!options.displayMonths && options.monthIndex < options.displayMonths && options.monthIndex + 1 !== options.displayMonths); formattedMonth.disableLeftArrow = isMonthDisabled(shiftDate(formattedMonth.month, { month: -1 }), options.minDate, options.maxDate); formattedMonth.disableRightArrow = isMonthDisabled(shiftDate(formattedMonth.month, { month: 1 }), options.minDate, options.maxDate); return formattedMonth; } function isDateInRange(date, selectedRange, hoveredDate) { if (!date || !selectedRange || !selectedRange[0]) { return false; } if (selectedRange[1]) { return date > selectedRange[0] && date <= selectedRange[1]; } if (hoveredDate) { return date > selectedRange[0] && date <= hoveredDate; } return false; } function canSwitchMode(mode, minMode) { return minMode ? mode >= minMode : true; } const height$1 = 4; const width$1 = 3; const shift$1 = { month: 1 }; function formatMonthsCalendar(viewDate, formatOptions) { const initialDate = startOf(viewDate, 'year'); const matrixOptions = { width: width$1, height: height$1, initialDate, shift: shift$1 }; const monthMatrix = createMatrix(matrixOptions, date => ({ date, label: formatDate(date, formatOptions.monthLabel, formatOptions.locale) })); return { months: monthMatrix, monthTitle: '', yearTitle: formatDate(viewDate, formatOptions.yearTitle, formatOptions.locale), hideRightArrow: false, hideLeftArrow: false, disableRightArrow: false, disableLeftArrow: false }; } function flagMonthsCalendar(monthCalendar, options) { monthCalendar.months.forEach((months, rowIndex) => { months.forEach((month, monthIndex) => { let isSelected; const isHovered = isSameMonth(month.date, options.hoveredMonth); const isDisabled = options.isDisabled || isDisabledDate(month.date, options.datesDisabled) || isEnabledDate(month.date, options.datesEnabled, 'month') || isMonthDisabled(month.date, options.minDate, options.maxDate); if (!options.selectedDate && options.selectedRange) { isSelected = isSameMonth(month.date, options.selectedRange[0]); if (!isSelected) { isSelected = isSameMonth(month.date, options.selectedRange[1]); } } else { isSelected = isSameMonth(month.date, options.selectedDate); } const newMonth = Object.assign(/*{},*/ month, { isHovered, isDisabled, isSelected }); if (month.isHovered !== newMonth.isHovered || month.isDisabled !== newMonth.isDisabled || month.isSelected !== newMonth.isSelected) { monthCalendar.months[rowIndex][monthIndex] = newMonth; } }); }); // todo: add check for linked calendars monthCalendar.hideLeftArrow = !!options.monthIndex && options.monthIndex > 0 && options.monthIndex !== options.displayMonths; monthCalendar.hideRightArrow = (!!options.monthIndex || options.monthIndex === 0) && (!!options.displayMonths || options.displayMonths === 0) && options.monthIndex < options.displayMonths && options.monthIndex + 1 !== options.displayMonths; monthCalendar.disableLeftArrow = isYearDisabled(shiftDate(monthCalendar.months[0][0].date, { year: -1 }), options.minDate, options.maxDate); monthCalendar.disableRightArrow = isYearDisabled(shiftDate(monthCalendar.months[0][0].date, { year: 1 }), options.minDate, options.maxDate); return monthCalendar; } const height = 4; const width = 4; const yearsPerCalendar = height * width; const initialYearShift = (Math.floor(yearsPerCalendar / 2) - 1) * -1; const shift = { year: 1 }; function formatYearsCalendar(viewDate, formatOptions, previousInitialDate) { const initialDate = calculateInitialDate(viewDate, previousInitialDate); const matrixOptions = { width, height, initialDate, shift }; const yearsMatrix = createMatrix(matrixOptions, date => ({ date, label: formatDate(date, formatOptions.yearLabel, formatOptions.locale) })); const yearTitle = formatYearRangeTitle(yearsMatrix, formatOptions); return { years: yearsMatrix, monthTitle: '', yearTitle, hideLeftArrow: false, hideRightArrow: false, disableLeftArrow: false, disableRightArrow: false }; } function calculateInitialDate(viewDate, previousInitialDate) { if (previousInitialDate && viewDate.getFullYear() >= previousInitialDate.getFullYear() && viewDate.getFullYear() < previousInitialDate.getFullYear() + yearsPerCalendar) { return previousInitialDate; } return shiftDate(viewDate, { year: initialYearShift }); } function formatYearRangeTitle(yearsMatrix, formatOptions) { const from = formatDate(yearsMatrix[0][0].date, formatOptions.yearTitle, formatOptions.locale); const to = formatDate(yearsMatrix[height - 1][width - 1].date, formatOptions.yearTitle, formatOptions.locale); return `${from} - ${to}`; } function flagYearsCalendar(yearsCalendar, options) { yearsCalendar.years.forEach((years, rowIndex) => { years.forEach((year, yearIndex) => { let isSelected; const isHovered = isSameYear(year.date, options.hoveredYear); const isDisabled = options.isDisabled || isDisabledDate(year.date, options.datesDisabled, 'year') || isEnabledDate(year.date, options.datesEnabled, 'year') || isYearDisabled(year.date, options.minDate, options.maxDate); if (!options.selectedDate && options.selectedRange) { isSelected = isSameYear(year.date, options.selectedRange[0]); if (!isSelected) { isSelected = isSameYear(year.date, options.selectedRange[1]); } } else { isSelected = isSameYear(year.date, options.selectedDate); } const newMonth = Object.assign(/*{},*/ year, { isHovered, isDisabled, isSelected }); if (year.isHovered !== newMonth.isHovered || year.isDisabled !== newMonth.isDisabled || year.isSelected !== newMonth.isSelected) { yearsCalendar.years[rowIndex][yearIndex] = newMonth; } }); }); // todo: add check for linked calendars yearsCalendar.hideLeftArrow = !!options.yearIndex && options.yearIndex > 0 && options.yearIndex !== options.displayMonths; yearsCalendar.hideRightArrow = !!options.yearIndex && !!options.displayMonths && options.yearIndex < options.displayMonths && options.yearIndex + 1 !== options.displayMonths; yearsCalendar.disableLeftArrow = isYearDisabled(shiftDate(yearsCalendar.years[0][0].date, { year: -1 }), options.minDate, options.maxDate); const i = yearsCalendar.years.length - 1; const j = yearsCalendar.years[i].length - 1; yearsCalendar.disableRightArrow = isYearDisabled(shiftDate(yearsCalendar.years[i][j].date, { year: 1 }), options.minDate, options.maxDate); return yearsCalendar; } function copyTime(sourceDate, time) { if (!sourceDate || !isNaN(sourceDate.getTime())) { return; } sourceDate.setHours(time.getHours()); sourceDate.setMinutes(time.getMinutes()); sourceDate.setSeconds(time.getSeconds()); sourceDate.setMilliseconds(time.getMilliseconds()); } function bsDatepickerReducer(state = initialDatepickerState, action) { switch (action.type) { case BsDatepickerActions.CALCULATE: { return calculateReducer(state); } case BsDatepickerActions.FORMAT: { return formatReducer(state); } case BsDatepickerActions.FLAG: { return flagReducer(state); } case BsDatepickerActions.NAVIGATE_OFFSET: { return navigateOffsetReducer(state, action); } case BsDatepickerActions.NAVIGATE_TO: { const payload = action.payload; if (!state.view || !payload.unit) { return state; } const date = setFullDate(state.view.date, payload.unit); let newState; let mode; if (canSwitchMode(payload.viewMode, state.minMode)) { mode = payload.viewMode; newState = { view: { date, mode } }; } else { mode = state.view.mode; newState = { selectedDate: date, view: { date, mode } }; } return Object.assign({}, state, newState); } case BsDatepickerActions.CHANGE_VIEWMODE: { if (!canSwitchMode(action.payload, state.minMode) || !state.view) { return state; } const date = state.view.date; const mode = action.payload; const newState = { view: { date, mode } }; return Object.assign({}, state, newState); } case BsDatepickerActions.HOVER: { return Object.assign({}, state, { hoveredDate: action.payload }); } case BsDatepickerActions.SELECT: { if (!state.view) { return state; } const newState = { selectedDate: action.payload, view: state.view }; if (Array.isArray(state.selectedTime)) { const _time = state.selectedTime[0]; if (newState.selectedDate && _time) { copyTime(newState.selectedDate, _time); } } const mode = state.view.mode; const _date = action.payload || state.view.date; const date = getViewDate(_date, state.minDate, state.maxDate); newState.view = { mode, date }; return Object.assign({}, state, newState); } case BsDatepickerActions.SELECT_TIME: { const { date, index } = action.payload; const selectedTime = state.selectedTime ? [...state.selectedTime] : []; selectedTime[index] = date; return Object.assign({}, state, { selectedTime }); } case BsDatepickerActions.SET_OPTIONS: { if (!state.view) { return state; } const newState = action.payload; // preserve view mode const mode = newState.minMode ? newState.minMode : state.view.mode; const _viewDate = isDateValid(newState.value) && newState.value || isArray(newState.value) && isDateValid(newState.value[0]) && newState.value[0] || state.view.date; const date = getViewDate(_viewDate, newState.minDate, newState.maxDate); newState.view = { mode, date }; // update selected value if (newState.value) { // if new value is array we work with date range if (isArray(newState.value)) { newState.selectedRange = newState.value; newState.selectedTime = newState.value.map((i) => i); } // if new value is a date -> datepicker if (newState.value instanceof Date) { newState.selectedDate = newState.value; newState.selectedTime = [newState.value]; } // provided value is not supported :) // need to report it somehow } return Object.assign({}, state, newState); } // date range picker case BsDatepickerActions.SELECT_RANGE: { if (!state.view) { return state; } const newState = { selectedRange: action.payload, view: state.view }; newState.selectedRange?.forEach((dte, index) => { if (Array.isArray(state.selectedTime)) { const _time = state.selectedTime[index]; if (_time) { copyTime(dte, _time); } } }); const mode = state.view.mode; const _date = action.payload && action.payload[0] || state.view.date; const date = getViewDate(_date, state.minDate, state.maxDate); newState.view = { mode, date }; return Object.assign({}, state, newState); } case BsDatepickerActions.SET_MIN_DATE: { return Object.assign({}, state, { minDate: action.payload }); } case BsDatepickerActions.SET_MAX_DATE: { return Object.assign({}, state, { maxDate: action.payload }); } case BsDatepickerActions.SET_IS_DISABLED: { return Object.assign({}, state, { isDisabled: action.payload }); } case BsDatepickerActions.SET_DATE_CUSTOM_CLASSES: { return Object.assign({}, state, { dateCustomClasses: action.payload }); } case BsDatepickerActions.SET_DATE_TOOLTIP_TEXTS: { return Object.assign({}, state, { dateTooltipTexts: action.payload }); } default: return state; } } function calculateReducer(state) { if (!state.view) { return state; } // how many calendars let displayMonths; if (state.displayOneMonthRange && isDisplayOneMonth(state.view.date, state.minDate, state.maxDate)) { displayMonths = 1; } else { displayMonths = state.displayMonths || 1; } // 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 }); } // 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); } } 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 }); } 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 }); } return Object.assign({}, state, { yearsCalendarModel }); } return state; } function formatReducer(state) { 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++) { // todo: for unlinked calendars it will be harder monthsCalendar[calendarIndex] = formatMonthsCalendar(viewDate, getFormatOptions(state)); 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)); viewDate = shiftDate(viewDate, { year: 16 }); } return Object.assign({}, state, { yearsCalendarModel }); } return state; } function flagReducer(state) { 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) => flagDaysCalendar(formattedMonth, { isDisabled: state.isDisabled, minDate: state.minDate, maxDate: state.maxDate, daysDisabled: state.daysDisabled, datesDisabled: state.