src/lib/calendar/Cell.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import '../style/DateTimeRange.css';
import moment from 'moment';
import PropTypes from 'prop-types';
import momentPropTypes from 'react-moment-proptypes';
import {
startDateStyle,
endDateStyle,
inBetweenStyle,
normalCellStyle,
hoverCellStyle,
greyCellStyle,
invalidStyle,
isInbetweenDates,
} from '../utils/TimeFunctionUtils';
import { addFocusStyle } from '../utils/StyleUtils';
import { pastMaxDate } from '../utils/DateSelectedUtils';
import { getCalendarGridCellClassName } from '../utils/CssClassNameHelper';
import { ModeEnum } from '../DateTimeRangePicker';
class Cell extends React.Component {
constructor(props) {
super(props);
this.state = { style: {} };
this.mouseEnter = this.mouseEnter.bind(this);
this.mouseLeave = this.mouseLeave.bind(this);
this.onClick = this.onClick.bind(this);
this.keyDown = this.keyDown.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
componentDidUpdate(oldProps) {
let isDifferentMomentObject = !oldProps.date.isSame(this.props.date) || !oldProps.otherDate.isSame(this.props.otherDate);
let isDifferentTime = this.props.date.format('DD-MM-YYYY HH:mm') !== oldProps.date.format('DD-MM-YYYY HH:mm') || this.props.otherDate.format('DD-MM-YYYY HH:mm') !== oldProps.otherDate.format('DD-MM-YYYY HH:mm')
if (isDifferentMomentObject || isDifferentTime) {
this.styleCellNonMouseEnter();
}
isDifferentMomentObject = !oldProps.cellDay.isSame(this.props.cellDay);
isDifferentTime = this.props.cellDay.format('DD-MM-YYYY HH:mm') !== oldProps.cellDay.format('DD-MM-YYYY HH:mm');
if (isDifferentMomentObject || isDifferentTime) {
this.styleCellNonMouseEnter();
}
// If a Cell is Selected
// If the focusDate is this cell
// and its not a gray cell
// Then Focus on this cell
let cellFocused = false;
let focusDateIsCellDate =
typeof this.props.focusDate === 'object' && this.props.focusDate.isSame(this.props.cellDay, 'day');
let activeElement = document.activeElement.id;
if (activeElement && activeElement.indexOf('_cell_') !== -1) {
cellFocused = true;
}
if (cellFocused && focusDateIsCellDate && !this.isCellMonthSameAsPropMonth(this.props.cellDay)) {
this.cell.focus();
this.props.focusOnCallback(false);
}
}
pastMaxDatePropsChecker(isCellDateProp, days) {
if (isCellDateProp) {
if (pastMaxDate(moment(this.props.date).add(days, 'days'), this.props.maxDate, true)) {
return true;
}
} else {
if (pastMaxDate(moment(this.props.otherDate).add(days, 'days'), this.props.maxDate, true)) {
return true;
}
}
return false;
}
keyDown(e) {
let componentFocused = document.activeElement === ReactDOM.findDOMNode(this.cell);
if (componentFocused && e.keyCode >= 37 && e.keyCode <= 40) {
e.preventDefault();
let newDate = moment(this.props.cellDay);
// Check to see if this cell is the date prop
let isCellDateProp = this.props.cellDay.isSame(this.props.date, 'day');
if (e.keyCode === 38) {
// Up Key
newDate.subtract(7, 'days');
} else if (e.keyCode === 40) {
// Down Key
if (this.pastMaxDatePropsChecker(isCellDateProp, 7)) {
return;
}
newDate.add(7, 'days');
} else if (e.keyCode === 37) {
// Left Key
newDate.subtract(1, 'days');
} else if (e.keyCode === 39) {
// Right Key
if (this.pastMaxDatePropsChecker(isCellDateProp, 1)) {
return;
}
newDate.add(1, 'days');
}
let isSuccessfulCallback = this.props.keyboardCellCallback(this.props.cellDay, newDate);
if (isSuccessfulCallback) {
this.props.focusOnCallback(newDate);
}
}
}
onClick() {
if (pastMaxDate(this.props.cellDay, this.props.maxDate, false)) {
return;
}
this.props.dateSelectedNoTimeCallback(this.props.cellDay, this.props.mode);
}
mouseEnter() {
// If Past Max Date Style Cell Out of Use
if (this.checkAndSetMaxDateStyle(this.props.cellDay)) {
return;
}
// If smart mode disabled check cell dates to ensure not past end in start mode and not before start in end mode
if (!this.props.smartMode && this.nonSmartModePastStartAndEndChecks(this.props.cellDay)) {
return;
}
// Custom hover cell styling
if (this.props.style && this.props.style.hoverCell) {
let style = Object.assign(hoverCellStyle(false, this.props.darkMode), this.props.style.hoverCell);
return this.setState({ style: style });
}
// Hover Style Cell, Different if inbetween start and end date
let isDateStart = this.props.date.isSameOrBefore(this.props.otherDate, 'second');
if (isInbetweenDates(isDateStart, this.props.cellDay, this.props.date, this.props.otherDate)) {
this.setState({ style: hoverCellStyle(true, this.props.darkMode) });
} else {
this.setState({ style: hoverCellStyle(false, this.props.darkMode) });
}
}
mouseLeave() {
this.styleCellNonMouseEnter();
}
onFocus() {
this.props.cellFocusedCallback(this.props.cellDay);
this.setState({ focus: true });
}
onBlur() {
this.setState({ focus: false });
}
isCellMonthSameAsPropMonth(cellDay) {
let month = this.props.month;
let cellDayMonth = cellDay.month();
if (month !== cellDayMonth) {
return true;
}
}
shouldStyleCellStartEnd(cellDay, date, otherDate, startCheck, endCheck) {
let isCellDateProp = cellDay.isSame(date, 'day');
let isCellOtherDateProp = cellDay.isSame(otherDate, 'day');
let isDateStart = date.isSameOrBefore(otherDate, 'second');
let isOtherDateStart = otherDate.isSameOrBefore(date, 'second');
if (startCheck) {
return (isCellDateProp && isDateStart) || (isCellOtherDateProp && isOtherDateStart);
} else if (endCheck) {
return (isCellDateProp && !isDateStart) || (isCellOtherDateProp && !isOtherDateStart);
}
}
checkAndSetMaxDateStyle(cellDate) {
// If Past Max Date Style Cell Out of Use
if (pastMaxDate(cellDate, this.props.maxDate, false)) {
this.setState({ style: invalidStyle(this.props.darkMode) });
return true;
}
return false;
}
nonSmartModePastStartAndEndChecks(cellDate) {
// If in start mode and cellDate past end date style as unavailable. If in end mode and cellDate before start date style as unavailable
if (this.props.mode === ModeEnum.start) {
// We know now the date prop is the start date and the otherDate is the end date in non smart mode
// If this cell is after end date then invalid cell as this is the start mode
if (cellDate.isAfter(this.props.otherDate, 'day')) {
this.setState({ style: invalidStyle(this.props.darkMode) });
return true;
}
} else if (this.props.mode === ModeEnum.end) {
// We know now the date prop is the end date and the otherDate is the start date in non smart mode
// If this cell is before start date then invalid cell as this is the end mode
if (cellDate.isBefore(this.props.otherDate, 'day')) {
this.setState({ style: invalidStyle(this.props.darkMode) });
return true;
}
}
return false;
}
styleCellNonMouseEnter() {
let cellDay = this.props.cellDay;
let date = this.props.date;
let otherDate = this.props.otherDate;
// If Past Max Date Style Cell Out of Use
if (this.checkAndSetMaxDateStyle(cellDay)) {
return;
}
// If smart mode disabled check cell dates to ensure not past end in start mode and not before start in end mode
if (!this.props.smartMode && this.nonSmartModePastStartAndEndChecks(cellDay)) {
return;
}
// Anything cellDay month that is before or after the cell prop month style grey
if (this.isCellMonthSameAsPropMonth(cellDay)) {
this.setState({ style: greyCellStyle(this.props.darkMode) });
return;
}
let isDateStart = date.isSameOrBefore(otherDate, 'second');
let inbetweenDates = isInbetweenDates(isDateStart, cellDay, date, otherDate);
let isStart = this.shouldStyleCellStartEnd(cellDay, date, otherDate, true, false);
let isEnd = this.shouldStyleCellStartEnd(cellDay, date, otherDate, false, true);
// If start, end or inbetween date then style according to user input or use default
if (isStart || isEnd || inbetweenDates) {
let style;
if (isStart && this.props.style && this.props.style.fromDate) {
style = Object.assign(startDateStyle(), this.props.style.fromDate);
} else if (isStart) {
style = startDateStyle();
} else if (isEnd && this.props.style && this.props.style.toDate) {
style = Object.assign(endDateStyle(), this.props.style.toDate);
} else if (isEnd) {
style = endDateStyle();
} else if (inbetweenDates && this.props.style && this.props.style.betweenDates) {
style = Object.assign(inBetweenStyle(), this.props.style.betweenDates);
} else {
style = inBetweenStyle();
}
this.setState({ style: style });
} else if (inbetweenDates) {
this.setState({ style: inBetweenStyle() });
} else {
this.setState({ style: normalCellStyle(this.props.darkMode) });
}
}
isStartOrEndDate() {
let cellDay = this.props.cellDay;
let date = this.props.date;
let otherDate = this.props.otherDate;
if (
this.shouldStyleCellStartEnd(cellDay, date, otherDate, true, false) ||
this.shouldStyleCellStartEnd(cellDay, date, otherDate, false, true)
) {
return true;
}
return false;
}
render() {
let className = getCalendarGridCellClassName();
let dateFormatted = this.props.cellDay.format('D');
let tabIndex = -1;
if (this.isStartOrEndDate() && !this.isCellMonthSameAsPropMonth(this.props.cellDay)) {
document.addEventListener('keydown', this.keyDown, false);
tabIndex = 0;
} else {
document.removeEventListener('keydown', this.keyDown, false);
}
let style = addFocusStyle(this.state.focus, this.state.style);
return (
<div
ref={cell => {
this.cell = cell;
}}
className={className}
tabIndex={tabIndex}
style={style}
onMouseEnter={this.mouseEnter}
onMouseLeave={this.mouseLeave}
onClick={this.onClick}
onFocus={this.onFocus}
onBlur={this.onBlur}
id={`row_${this.props.row}_cell_${this.props.id}_${this.props.mode}`}
>
{dateFormatted}
</div>
);
}
}
Cell.propTypes = {
id: PropTypes.number.isRequired,
cellDay: momentPropTypes.momentObj.isRequired,
date: momentPropTypes.momentObj.isRequired,
otherDate: momentPropTypes.momentObj,
maxDate: momentPropTypes.momentObj,
dateSelectedNoTimeCallback: PropTypes.func.isRequired,
keyboardCellCallback: PropTypes.func.isRequired,
focusOnCallback: PropTypes.func.isRequired,
focusDate: PropTypes.any.isRequired,
month: PropTypes.number.isRequired,
cellFocusedCallback: PropTypes.func.isRequired,
mode: PropTypes.string.isRequired,
smartMode: PropTypes.bool,
style: PropTypes.object,
darkMode: PropTypes.bool,
};
export default Cell;