src/datepicker/reducer/bs-datepicker.reducer.ts
// tslint:disable:max-file-line-count
import { BsDatepickerState, initialDatepickerState } from './bs-datepicker.state';
import { Action } from '../../mini-ngrx/index';
import { BsDatepickerActions } from './bs-datepicker.actions';
import { calcDaysCalendar } from '../engine/calc-days-calendar';
import { formatDaysCalendar } from '../engine/format-days-calendar';
import { flagDaysCalendar } from '../engine/flag-days-calendar';
import { setFullDate, shiftDate } from '../../chronos/utils/date-setters';
import { canSwitchMode } from '../engine/view-mode';
import { formatMonthsCalendar } from '../engine/format-months-calendar';
import { flagMonthsCalendar } from '../engine/flag-months-calendar';
import { formatYearsCalendar, yearsPerCalendar } from '../engine/format-years-calendar';
import { flagYearsCalendar } from '../engine/flag-years-calendar';
import { BsViewNavigationEvent, DatepickerFormatOptions } from '../models/index';
import { isArray, isDateValid } from '../../chronos/utils/type-checks';
import { startOf } from '../../chronos/utils/start-end-of';
import { getLocale } from '../../chronos/locale/locales';
import { isAfter, isBefore } from '../../chronos/utils/date-compare';
export function bsDatepickerReducer(state = initialDatepickerState,
action: Action): BsDatepickerState {
switch (action.type) {
case BsDatepickerActions.CALCULATE: {
return calculateReducer(state);
}
case BsDatepickerActions.FORMAT: {
return formatReducer(state, action);
}
case BsDatepickerActions.FLAG: {
return flagReducer(state, action);
}
case BsDatepickerActions.NAVIGATE_OFFSET: {
const date = shiftDate(startOf(state.view.date, 'month'), action.payload);
const newState = {
view: {
mode: state.view.mode,
date
}
};
return Object.assign({}, state, newState);
}
case BsDatepickerActions.NAVIGATE_TO: {
const payload: BsViewNavigationEvent = action.payload;
const date = setFullDate(state.view.date, payload.unit);
const mode = payload.viewMode;
const newState = { view: { date, mode } };
return Object.assign({}, state, newState);
}
case BsDatepickerActions.CHANGE_VIEWMODE: {
if (!canSwitchMode(action.payload)) {
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: {
const newState = {
selectedDate: action.payload,
view: state.view
};
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.SET_OPTIONS: {
const newState = action.payload;
// preserve view mode
const mode = 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;
}
// if new value is a date -> datepicker
if (newState.value instanceof Date) {
newState.selectedDate = newState.value;
}
// provided value is not supported :)
// need to report it somehow
}
return Object.assign({}, state, newState);
}
// date range picker
case BsDatepickerActions.SELECT_RANGE: {
const newState = {
selectedRange: action.payload,
view: state.view
};
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
});
}
default:
return state;
}
}
function calculateReducer(state: BsDatepickerState): BsDatepickerState {
// how many calendars
const displayMonths = state.displayMonths;
// use selected date on initial rendering if set
let viewDate = state.view.date;
if (state.view.mode === 'day') {
state.monthViewOptions.firstDayOfWeek = getLocale(state.locale).firstDayOfWeek();
const 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 });
}
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)
);
viewDate = shiftDate(viewDate, { year: yearsPerCalendar });
}
return Object.assign({}, state, { yearsCalendarModel });
}
return state;
}
function formatReducer(state: BsDatepickerState,
action: Action): BsDatepickerState {
if (state.view.mode === 'day') {
const formattedMonths = state.monthsModel.map((month, monthIndex) =>
formatDaysCalendar(month, getFormatOptions(state), monthIndex)
);
return Object.assign({}, state, { formattedMonths });
}
// how many calendars
const displayMonths = state.displayMonths;
// 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: BsDatepickerState,
action: Action): BsDatepickerState {
if (state.view.mode === 'day') {
const flaggedMonths = state.formattedMonths.map(
(formattedMonth, monthIndex) =>
flagDaysCalendar(formattedMonth, {
isDisabled: state.isDisabled,
minDate: state.minDate,
maxDate: state.maxDate,
hoveredDate: state.hoveredDate,
selectedDate: state.selectedDate,
selectedRange: state.selectedRange,
displayMonths: state.displayMonths,
monthIndex
})
);
return Object.assign({}, state, { flaggedMonths });
}
if (state.view.mode === 'month') {
const flaggedMonthsCalendar = state.monthsCalendar.map(
(formattedMonth, monthIndex) =>
flagMonthsCalendar(formattedMonth, {
isDisabled: state.isDisabled,
minDate: state.minDate,
maxDate: state.maxDate,
hoveredMonth: state.hoveredMonth,
displayMonths: state.displayMonths,
monthIndex
})
);
return Object.assign({}, state, { flaggedMonthsCalendar });
}
if (state.view.mode === 'year') {
const yearsCalendarFlagged = state.yearsCalendarModel.map(
(formattedMonth, yearIndex) =>
flagYearsCalendar(formattedMonth, {
isDisabled: state.isDisabled,
minDate: state.minDate,
maxDate: state.maxDate,
hoveredYear: state.hoveredYear,
displayMonths: state.displayMonths,
yearIndex
})
);
return Object.assign({}, state, { yearsCalendarFlagged });
}
return state;
}
function getFormatOptions(state: BsDatepickerState): DatepickerFormatOptions {
return {
locale: state.locale,
monthTitle: state.monthTitle,
yearTitle: state.yearTitle,
dayLabel: state.dayLabel,
monthLabel: state.monthLabel,
yearLabel: state.yearLabel,
weekNumbers: state.weekNumbers
};
}
/**
* if view date is provided (bsValue|ngModel) it should be shown
* if view date is not provider:
* if minDate>currentDate (default view value), show minDate
* if maxDate<currentDate(default view value) show maxDate
*/
function getViewDate(viewDate: Date | Date[], minDate: Date, maxDate: Date) {
const _date = Array.isArray(viewDate) ? viewDate[0] : viewDate;
if (minDate && isAfter(minDate, _date, 'day')) {
return minDate;
}
if (maxDate && isBefore(maxDate, _date, 'day')) {
return maxDate;
}
return _date;
}