diff --git a/dist/DateRangePicker.js b/dist/DateRangePicker.js index 23ac2a5f..409ab7d6 100644 --- a/dist/DateRangePicker.js +++ b/dist/DateRangePicker.js @@ -10,14 +10,18 @@ var _reactAddons = require('react/addons'); var _reactAddons2 = _interopRequireDefault(_reactAddons); -var _moment = require('moment'); +var _momentRange = require('moment-range'); -var _moment2 = _interopRequireDefault(_moment); +var _momentRange2 = _interopRequireDefault(_momentRange); var _immutable = require('immutable'); var _immutable2 = _interopRequireDefault(_immutable); +var _calendar = require('calendar'); + +var _calendar2 = _interopRequireDefault(_calendar); + var _utilsBemMixin = require('./utils/BemMixin'); var _utilsBemMixin2 = _interopRequireDefault(_utilsBemMixin); @@ -38,11 +42,15 @@ var _PaginationArrow = require('./PaginationArrow'); var _PaginationArrow2 = _interopRequireDefault(_PaginationArrow); +var _utilsIsMomentRange = require('./utils/isMomentRange'); + +var _utilsIsMomentRange2 = _interopRequireDefault(_utilsIsMomentRange); + 'use strict'; var PureRenderMixin = _reactAddons2['default'].addons.PureRenderMixin; -var absoluteMinimum = _moment2['default'](new Date(-8640000000000000 / 2)).startOf('day'); -var absoluteMaximum = _moment2['default'](new Date(8640000000000000 / 2)).startOf('day'); +var absoluteMinimum = _momentRange2['default'](new Date(-8640000000000000 / 2)).startOf('day'); +var absoluteMaximum = _momentRange2['default'](new Date(8640000000000000 / 2)).startOf('day'); _reactAddons2['default'].initializeTouchEvents(true); @@ -82,9 +90,9 @@ var DateRangePicker = _reactAddons2['default'].createClass({ if (!val) { return null; - } else if (_moment2['default'].isMoment(val)) { + } else if (_momentRange2['default'].isMoment(val)) { return null; - } else if (val.start && val.end && _moment2['default'].isMoment(val.start) && _moment2['default'].isMoment(val.end)) { + } else if (val.start && val.end && _momentRange2['default'].isMoment(val.start) && _momentRange2['default'].isMoment(val.end)) { return null; } return new Error('Value must be a moment or a moment range'); @@ -123,9 +131,12 @@ var DateRangePicker = _reactAddons2['default'].createClass({ }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { + var nextDateStates = this.getDateStates(nextProps); + var nextEnabledRange = this.getEnabledRange(nextProps); + this.setState({ - dateStates: this.getDateStates(nextProps), - enabledRange: this.getEnabledRange(nextProps) + dateStates: this.state.dateStates && _immutable2['default'].is(this.state.dateStates, nextDateStates) ? this.state.dateStates : nextDateStates, + enabledRange: this.state.enabledRange && _immutable2['default'].is(this.state.enabledRange, nextEnabledRange) ? this.state.enabledRange : nextEnabledRange }); }, @@ -170,10 +181,10 @@ var DateRangePicker = _reactAddons2['default'].createClass({ }, getEnabledRange: function getEnabledRange(props) { - var min = props.minimumDate ? _moment2['default'](props.minimumDate).startOf('day') : absoluteMinimum; - var max = props.maximumDate ? _moment2['default'](props.maximumDate).startOf('day') : absoluteMaximum; + var min = props.minimumDate ? _momentRange2['default'](props.minimumDate).startOf('day') : absoluteMinimum; + var max = props.maximumDate ? _momentRange2['default'](props.maximumDate).startOf('day') : absoluteMaximum; - return _moment2['default']().range(min, max); + return _momentRange2['default']().range(min, max); }, getDateStates: function getDateStates(props) { @@ -184,7 +195,7 @@ var DateRangePicker = _reactAddons2['default'].createClass({ var actualStates = []; var minDate = absoluteMinimum; var maxDate = absoluteMaximum; - var dateCursor = _moment2['default'](minDate).startOf('day'); + var dateCursor = _momentRange2['default'](minDate).startOf('day'); var defs = _immutable2['default'].fromJS(stateDefinitions); @@ -196,7 +207,7 @@ var DateRangePicker = _reactAddons2['default'].createClass({ if (!dateCursor.isSame(start)) { actualStates.push({ state: defaultState, - range: _moment2['default']().range(dateCursor, start) + range: _momentRange2['default']().range(dateCursor, start) }); } actualStates.push(s); @@ -205,7 +216,7 @@ var DateRangePicker = _reactAddons2['default'].createClass({ actualStates.push({ state: defaultState, - range: _moment2['default']().range(dateCursor, maxDate) + range: _momentRange2['default']().range(dateCursor, maxDate) }); // sanitize date states @@ -220,8 +231,124 @@ var DateRangePicker = _reactAddons2['default'].createClass({ }); }, - onSelect: function onSelect(date) { - this.props.onSelect(_moment2['default'](date)); + isDateDisabled: function isDateDisabled(date) { + return !this.state.enabledRange.contains(date); + }, + + isDateSelectable: function isDateSelectable(date) { + return this.dateRangesForDate(date).some(function (r) { + return r.get('selectable'); + }); + }, + + nonSelectableStateRanges: function nonSelectableStateRanges() { + return this.state.dateStates.filter(function (d) { + return !d.get('selectable'); + }); + }, + + dateRangesForDate: function dateRangesForDate(date) { + return this.state.dateStates.filter(function (d) { + return d.get('range').contains(date); + }); + }, + + sanitizeRange: function sanitizeRange(range, forwards) { + /* Truncates the provided range at the first intersection + * with a non-selectable state. Using forwards to determine + * which direction to work + */ + var blockedRanges = this.nonSelectableStateRanges().map(function (r) { + return r.get('range'); + }); + var intersect = undefined; + + if (forwards) { + intersect = blockedRanges.find(function (r) { + return range.intersect(r); + }); + if (intersect) { + return _momentRange2['default']().range(range.start, intersect.start); + } + } else { + intersect = blockedRanges.findLast(function (r) { + return range.intersect(r); + }); + + if (intersect) { + return _momentRange2['default']().range(intersect.end, range.end); + } + } + + if (range.start.isBefore(this.state.enabledRange.start)) { + return _momentRange2['default']().range(this.state.enabledRange.start, range.end); + } + + if (range.end.isAfter(this.state.enabledRange.end)) { + return _momentRange2['default']().range(range.start, this.state.enabledRange.end); + } + + return range; + }, + + highlightRange: function highlightRange(range) { + this.setState({ + highlightedRange: range, + highlightedDate: null + }); + if (typeof this.props.onHighlightRange === 'function') { + this.props.onHighlightRange(range, this.statesForRange(range)); + } + }, + + onUnHighlightDate: function onUnHighlightDate(date) { + this.setState({ + highlightedDate: null + }); + }, + + onSelectDate: function onSelectDate(date) { + var selectionType = this.props.selectionType; + var selectedStartDate = this.state.selectedStartDate; + + if (selectionType === 'range') { + if (selectedStartDate) { + this.completeRangeSelection(); + } else if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.startRangeSelection(date); + } + } else { + if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.completeSelection(); + } + } + }, + + onHighlightDate: function onHighlightDate(date) { + var selectionType = this.props.selectionType; + var selectedStartDate = this.state.selectedStartDate; + + var datePair = undefined; + var range = undefined; + var forwards = undefined; + + if (selectionType === 'range') { + if (selectedStartDate) { + datePair = _immutable2['default'].List.of(selectedStartDate, date).sortBy(function (d) { + return d.unix(); + }); + range = _momentRange2['default']().range(datePair.get(0), datePair.get(1)); + forwards = range.start.unix() === selectedStartDate.unix(); + range = this.sanitizeRange(range, forwards); + this.highlightRange(range); + } else if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.highlightDate(date); + } + } else { + if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.highlightDate(date); + } + } }, startRangeSelection: function startRangeSelection(date) { @@ -230,7 +357,7 @@ var DateRangePicker = _reactAddons2['default'].createClass({ selectedStartDate: date }); if (typeof this.props.onSelectStart === 'function') { - this.props.onSelectStart(_moment2['default'](date)); + this.props.onSelectStart(_momentRange2['default'](date)); } }, @@ -277,7 +404,7 @@ var DateRangePicker = _reactAddons2['default'].createClass({ } }, - onHighlightDate: function onHighlightDate(date) { + highlightDate: function highlightDate(date) { this.setState({ highlightedDate: date }); @@ -286,24 +413,8 @@ var DateRangePicker = _reactAddons2['default'].createClass({ } }, - onHighlightRange: function onHighlightRange(range) { - this.setState({ - highlightedRange: range, - highlightedDate: null - }); - if (typeof this.props.onHighlightRange === 'function') { - this.props.onHighlightRange(range, this.statesForRange(range)); - } - }, - - onUnHighlightDate: function onUnHighlightDate() { - this.setState({ - highlightedDate: null - }); - }, - getMonthDate: function getMonthDate() { - return _moment2['default'](new Date(this.state.year, this.state.month, 1)); + return _momentRange2['default'](new Date(this.state.year, this.state.month, 1)); }, canMoveBack: function canMoveBack() { @@ -363,11 +474,11 @@ var DateRangePicker = _reactAddons2['default'].createClass({ var enabledRange = _state.enabledRange; var month = _state.month; - if (_moment2['default']({ years: year, months: month, date: 1 }).unix() < enabledRange.start.unix()) { + if (_momentRange2['default']({ years: year, months: month, date: 1 }).unix() < enabledRange.start.unix()) { month = enabledRange.start.month(); } - if (_moment2['default']({ years: year, months: month + 1, date: 1 }).unix() > enabledRange.end.unix()) { + if (_momentRange2['default']({ years: year, months: month + 1, date: 1 }).unix() > enabledRange.end.unix()) { month = enabledRange.end.month(); } @@ -398,7 +509,6 @@ var DateRangePicker = _reactAddons2['default'].createClass({ var highlightedDate = _state2.highlightedDate; var highlightedRange = _state2.highlightedRange; var highlightStartDate = _state2.highlightStartDate; - var selectedStartDate = _state2.selectedStartDate; var monthDate = this.getMonthDate(); var year = monthDate.year(); @@ -408,6 +518,30 @@ var DateRangePicker = _reactAddons2['default'].createClass({ monthDate.add(index, 'months'); + var cal = new _calendar2['default'].Calendar(firstOfWeek); + var monthDates = _immutable2['default'].fromJS(cal.monthDates(monthDate.year(), monthDate.month())); + var monthStart = monthDates.first().first(); + var monthEnd = monthDates.last().last(); + var monthRange = _momentRange2['default']().range(monthStart, monthEnd); + + if (_momentRange2['default'].isMoment(value)) { + if (!monthRange.contains(value)) { + value = null; + } + } else if (_utilsIsMomentRange2['default'](value)) { + if (!monthRange.overlaps(value)) { + value = null; + } + } + + if (!_momentRange2['default'].isMoment(highlightedDate) || !monthRange.contains(highlightedDate)) { + highlightedDate = null; + } + + if (!_utilsIsMomentRange2['default'](highlightedRange) || !monthRange.overlaps(highlightedRange)) { + highlightedRange = null; + } + props = { bemBlock: bemBlock, bemNamespace: bemNamespace, @@ -417,23 +551,18 @@ var DateRangePicker = _reactAddons2['default'].createClass({ hideSelection: hideSelection, highlightedDate: highlightedDate, highlightedRange: highlightedRange, - highlightStartDate: highlightStartDate, index: index, key: key, - selectedStartDate: selectedStartDate, selectionType: selectionType, value: value, maxIndex: numberOfCalendars - 1, firstOfMonth: monthDate, onMonthChange: this.changeMonth, onYearChange: this.changeYear, - onHighlightRange: this.onHighlightRange, + onSelectDate: this.onSelectDate, onHighlightDate: this.onHighlightDate, onUnHighlightDate: this.onUnHighlightDate, - onSelect: this.onSelect, - startRangeSelection: this.startRangeSelection, - completeSelection: this.completeSelection, - completeRangeSelection: this.completeRangeSelection, + dateRangesForDate: this.dateRangesForDate, dateComponent: _calendarCalendarDate2['default'] }; diff --git a/dist/calendar/CalendarDate.js b/dist/calendar/CalendarDate.js index 42861a49..4a99cffd 100644 --- a/dist/calendar/CalendarDate.js +++ b/dist/calendar/CalendarDate.js @@ -22,6 +22,10 @@ var _utilsBemMixin = require('../utils/BemMixin'); var _utilsBemMixin2 = _interopRequireDefault(_utilsBemMixin); +var _utilsPureRenderMixin = require('../utils/PureRenderMixin'); + +var _utilsPureRenderMixin2 = _interopRequireDefault(_utilsPureRenderMixin); + var _utilsLightenDarkenColor = require('../utils/lightenDarkenColor'); var _utilsLightenDarkenColor2 = _interopRequireDefault(_utilsLightenDarkenColor); @@ -40,38 +44,34 @@ var _CalendarSelection2 = _interopRequireDefault(_CalendarSelection); 'use strict'; -var PureRenderMixin = _reactAddons2['default'].addons.PureRenderMixin; - var CalendarDate = _reactAddons2['default'].createClass({ displayName: 'CalendarDate', - mixins: [_utilsBemMixin2['default'], PureRenderMixin], + mixins: [_utilsBemMixin2['default'], _utilsPureRenderMixin2['default']], propTypes: { date: _reactAddons2['default'].PropTypes.object.isRequired, firstOfMonth: _reactAddons2['default'].PropTypes.object.isRequired, - index: _reactAddons2['default'].PropTypes.number.isRequired, - maxIndex: _reactAddons2['default'].PropTypes.number.isRequired, - selectionType: _reactAddons2['default'].PropTypes.string.isRequired, - value: _reactAddons2['default'].PropTypes.object, - hideSelection: _reactAddons2['default'].PropTypes.bool, - highlightedRange: _reactAddons2['default'].PropTypes.object, + isSelectedDate: _reactAddons2['default'].PropTypes.bool, + isSelectedRangeStart: _reactAddons2['default'].PropTypes.bool, + isSelectedRangeEnd: _reactAddons2['default'].PropTypes.bool, + isInSelectedRange: _reactAddons2['default'].PropTypes.bool, + + isHighlightedDate: _reactAddons2['default'].PropTypes.bool, + isHighlightedRangeStart: _reactAddons2['default'].PropTypes.bool, + isHighlightedRangeEnd: _reactAddons2['default'].PropTypes.bool, + isInHighlightedRange: _reactAddons2['default'].PropTypes.bool, + highlightedDate: _reactAddons2['default'].PropTypes.object, - selectedStartDate: _reactAddons2['default'].PropTypes.object, dateStates: _reactAddons2['default'].PropTypes.instanceOf(_immutable2['default'].List), + isDisabled: _reactAddons2['default'].PropTypes.bool, + dateRangesForDate: _reactAddons2['default'].PropTypes.func, onHighlightDate: _reactAddons2['default'].PropTypes.func, onUnHighlightDate: _reactAddons2['default'].PropTypes.func, - onStartSelection: _reactAddons2['default'].PropTypes.func, - onCompleteSelection: _reactAddons2['default'].PropTypes.func - }, - - getDefaultProps: function getDefaultProps() { - return { - hideSelection: false - }; + onSelectDate: _reactAddons2['default'].PropTypes.func }, getInitialState: function getInitialState() { @@ -80,68 +80,8 @@ var CalendarDate = _reactAddons2['default'].createClass({ }; }, - isDisabled: function isDisabled(date) { - return !this.props.enabledRange.contains(date); - }, - - isDateSelectable: function isDateSelectable(date) { - return this.dateRangesForDate(date).some(function (r) { - return r.get('selectable'); - }); - }, - - nonSelectableStateRanges: function nonSelectableStateRanges() { - return this.props.dateStates.filter(function (d) { - return !d.get('selectable'); - }); - }, - - dateRangesForDate: function dateRangesForDate(date) { - return this.props.dateStates.filter(function (d) { - return d.get('range').contains(date); - }); - }, - - sanitizeRange: function sanitizeRange(range, forwards) { - /* Truncates the provided range at the first intersection - * with a non-selectable state. Using forwards to determine - * which direction to work - */ - var blockedRanges = this.nonSelectableStateRanges().map(function (r) { - return r.get('range'); - }); - var intersect = undefined; - - if (forwards) { - intersect = blockedRanges.find(function (r) { - return range.intersect(r); - }); - if (intersect) { - return _momentRange2['default']().range(range.start, intersect.start); - } - } else { - intersect = blockedRanges.findLast(function (r) { - return range.intersect(r); - }); - - if (intersect) { - return _momentRange2['default']().range(intersect.end, range.end); - } - } - - if (range.start.isBefore(this.props.enabledRange.start)) { - return _momentRange2['default']().range(this.props.enabledRange.start, range.end); - } - - if (range.end.isAfter(this.props.enabledRange.end)) { - return _momentRange2['default']().range(range.start, this.props.enabledRange.end); - } - - return range; - }, - mouseUp: function mouseUp() { - this.selectDate(); + this.props.onSelectDate(this.props.date); if (this.state.mouseDown) { this.setState({ @@ -159,8 +99,8 @@ var CalendarDate = _reactAddons2['default'].createClass({ }, touchEnd: function touchEnd() { - this.highlightDate(); - this.selectDate(); + this.props.onHighlightDate(this.props.date); + this.props.onSelectDate(this.props.date); if (this.state.mouseDown) { this.setState({ @@ -179,81 +119,24 @@ var CalendarDate = _reactAddons2['default'].createClass({ }, mouseEnter: function mouseEnter() { - this.highlightDate(); + this.props.onHighlightDate(this.props.date); }, mouseLeave: function mouseLeave() { if (this.state.mouseDown) { - this.selectDate(); + this.props.onSelectDate(this.props.date); this.setState({ mouseDown: false }); } - this.unHighlightDate(); - }, - - highlightDate: function highlightDate() { - var _props = this.props; - var date = _props.date; - var selectionType = _props.selectionType; - var selectedStartDate = _props.selectedStartDate; - var onHighlightRange = _props.onHighlightRange; - var onHighlightDate = _props.onHighlightDate; - - var datePair = undefined; - var range = undefined; - var forwards = undefined; - - if (selectionType === 'range') { - if (selectedStartDate) { - datePair = _immutable2['default'].List.of(selectedStartDate, date).sortBy(function (d) { - return d.unix(); - }); - range = _momentRange2['default']().range(datePair.get(0), datePair.get(1)); - forwards = range.start.unix() === selectedStartDate.unix(); - range = this.sanitizeRange(range, forwards); - onHighlightRange(range); - } else if (!this.isDisabled(date) && this.isDateSelectable(date)) { - onHighlightDate(date); - } - } else { - if (!this.isDisabled(date) && this.isDateSelectable(date)) { - onHighlightDate(date); - } - } - }, - - unHighlightDate: function unHighlightDate() { this.props.onUnHighlightDate(this.props.date); }, - selectDate: function selectDate() { - var _props2 = this.props; - var date = _props2.date; - var selectionType = _props2.selectionType; - var selectedStartDate = _props2.selectedStartDate; - var completeRangeSelection = _props2.completeRangeSelection; - var completeSelection = _props2.completeSelection; - var startRangeSelection = _props2.startRangeSelection; - - if (selectionType === 'range') { - if (selectedStartDate) { - completeRangeSelection(); - } else if (!this.isDisabled(date) && this.isDateSelectable(date)) { - startRangeSelection(date); - } - } else { - if (!this.isDisabled(date) && this.isDateSelectable(date)) { - completeSelection(); - } - } - }, - getBemModifiers: function getBemModifiers() { - var _props3 = this.props; - var date = _props3.date; - var firstOfMonth = _props3.firstOfMonth; + var _props = this.props; + var date = _props.date; + var firstOfMonth = _props.firstOfMonth; var otherMonth = false; var weekend = false; @@ -270,88 +153,57 @@ var CalendarDate = _reactAddons2['default'].createClass({ }, getBemStates: function getBemStates() { - var _props4 = this.props; - var date = _props4.date; - var value = _props4.value; - var highlightedRange = _props4.highlightedRange; - var highlightedDate = _props4.highlightedDate; - var hideSelection = _props4.hideSelection; - - var disabled = this.isDisabled(date); - var highlighted = false; - var selected = false; - - if (value && !hideSelection) { - if (!value.start && date.isSame(value)) { - selected = true; - } else if (value.start && value.start.isSame(value.end) && date.isSame(value.start)) { - selected = true; - } else if (value.start && value.contains(date)) { - selected = true; - } - } + var _props2 = this.props; + var isSelectedDate = _props2.isSelectedDate; + var isInSelectedRange = _props2.isInSelectedRange; + var isInHighlightedRange = _props2.isInHighlightedRange; + var highlighted = _props2.isHighlightedDate; + var disabled = _props2.isDisabled; - if (highlightedRange && highlightedRange.contains(date)) { - selected = true; - } else if (highlightedDate && date.isSame(highlightedDate)) { - highlighted = true; - } + var selected = isSelectedDate || isInSelectedRange || isInHighlightedRange; return { disabled: disabled, highlighted: highlighted, selected: selected }; }, render: function render() { - var _props5 = this.props; - var value = _props5.value; - var date = _props5.date; - var highlightedRange = _props5.highlightedRange; - var highlightedDate = _props5.highlightedDate; - var hideSelection = _props5.hideSelection; + var _props3 = this.props; + var date = _props3.date; + var dateRangesForDate = _props3.dateRangesForDate; + var isSelectedDate = _props3.isSelectedDate; + var isSelectedRangeStart = _props3.isSelectedRangeStart; + var isSelectedRangeEnd = _props3.isSelectedRangeEnd; + var isInSelectedRange = _props3.isInSelectedRange; + var isHighlightedDate = _props3.isHighlightedDate; + var isHighlightedRangeStart = _props3.isHighlightedRangeStart; + var isHighlightedRangeEnd = _props3.isHighlightedRangeEnd; + var isInHighlightedRange = _props3.isInHighlightedRange; var bemModifiers = this.getBemModifiers(); var bemStates = this.getBemStates(); - var pending = false; + var pending = isInHighlightedRange; var color = undefined; var amColor = undefined; var pmColor = undefined; - var states = this.dateRangesForDate(date); + var states = dateRangesForDate(date); var numStates = states.count(); var cellStyle = {}; var style = {}; - var highlightModifier = null; - var selectionModifier = null; - - if (value && !hideSelection && value.start) { - if (value.start.isSame(date) && value.start.isSame(value.end)) { - selectionModifier = 'single'; - } else if (value.contains(date)) { - if (date.isSame(value.start)) { - selectionModifier = 'start'; - } else if (date.isSame(value.end)) { - selectionModifier = 'end'; - } else { - selectionModifier = 'segment'; - } - } - } else if (value && !hideSelection && date.isSame(value)) { - selectionModifier = 'single'; - } - - if (highlightedRange && highlightedRange.contains(date)) { - pending = true; + var highlightModifier = undefined; + var selectionModifier = undefined; - if (date.isSame(highlightedRange.start)) { - selectionModifier = 'start'; - } else if (date.isSame(highlightedRange.end)) { - selectionModifier = 'end'; - } else { - selectionModifier = 'segment'; - } + if (isSelectedDate || isSelectedRangeStart && isSelectedRangeEnd) { + selectionModifier = 'single'; + } else if (isSelectedRangeStart || isHighlightedRangeStart) { + selectionModifier = 'start'; + } else if (isSelectedRangeEnd || isHighlightedRangeEnd) { + selectionModifier = 'end'; + } else if (isInSelectedRange || isInHighlightedRange) { + selectionModifier = 'segment'; } - if (highlightedDate && date.isSame(highlightedDate)) { + if (isHighlightedDate) { highlightModifier = 'single'; } @@ -387,7 +239,6 @@ var CalendarDate = _reactAddons2['default'].createClass({ { className: this.cx({ element: 'Date', modifiers: bemModifiers, states: bemStates }), style: cellStyle, onTouchStart: this.touchStart, - onMouseOver: this.mouseOver, onMouseEnter: this.mouseEnter, onMouseLeave: this.mouseLeave, onMouseDown: this.mouseDown }, @@ -403,8 +254,8 @@ var CalendarDate = _reactAddons2['default'].createClass({ { className: this.cx({ element: 'DateLabel' }) }, date.format('D') ), - selectionModifier && _reactAddons2['default'].createElement(_CalendarSelection2['default'], { modifier: selectionModifier, pending: pending }), - highlightModifier && _reactAddons2['default'].createElement(_CalendarHighlight2['default'], { modifier: highlightModifier }) + selectionModifier ? _reactAddons2['default'].createElement(_CalendarSelection2['default'], { modifier: selectionModifier, pending: pending }) : null, + highlightModifier ? _reactAddons2['default'].createElement(_CalendarHighlight2['default'], { modifier: highlightModifier }) : null ); } diff --git a/dist/calendar/CalendarDatePeriod.js b/dist/calendar/CalendarDatePeriod.js index 9c48d219..7f876a06 100644 --- a/dist/calendar/CalendarDatePeriod.js +++ b/dist/calendar/CalendarDatePeriod.js @@ -16,14 +16,16 @@ var _utilsBemMixin = require('../utils/BemMixin'); var _utilsBemMixin2 = _interopRequireDefault(_utilsBemMixin); -'use strict'; +var _utilsPureRenderMixin = require('../utils/PureRenderMixin'); + +var _utilsPureRenderMixin2 = _interopRequireDefault(_utilsPureRenderMixin); -var PureRenderMixin = _reactAddons2['default'].addons.PureRenderMixin; +'use strict'; var CalendarDatePeriod = _reactAddons2['default'].createClass({ displayName: 'CalendarDatePeriod', - mixins: [_utilsBemMixin2['default'], PureRenderMixin], + mixins: [_utilsBemMixin2['default'], _utilsPureRenderMixin2['default']], render: function render() { var _props = this.props; diff --git a/dist/calendar/CalendarHighlight.js b/dist/calendar/CalendarHighlight.js index aa8764e1..60280f22 100644 --- a/dist/calendar/CalendarHighlight.js +++ b/dist/calendar/CalendarHighlight.js @@ -16,24 +16,22 @@ var _utilsBemMixin = require('../utils/BemMixin'); var _utilsBemMixin2 = _interopRequireDefault(_utilsBemMixin); -'use strict'; +var _utilsPureRenderMixin = require('../utils/PureRenderMixin'); + +var _utilsPureRenderMixin2 = _interopRequireDefault(_utilsPureRenderMixin); -var PureRenderMixin = _reactAddons2['default'].addons.PureRenderMixin; +'use strict'; var CalendarHighlight = _reactAddons2['default'].createClass({ displayName: 'CalendarHighlight', - mixins: [_utilsBemMixin2['default'], PureRenderMixin], + mixins: [_utilsBemMixin2['default'], _utilsPureRenderMixin2['default']], render: function render() { - var _props = this.props; - var modifier = _props.modifier; - var inOtherMonth = _props.inOtherMonth; + var modifier = this.props.modifier; var modifiers = _defineProperty({}, modifier, true); - var states = { - inOtherMonth: inOtherMonth - }; + var states = {}; return _reactAddons2['default'].createElement('div', { className: this.cx({ states: states, modifiers: modifiers }) }); } diff --git a/dist/calendar/CalendarMonth.js b/dist/calendar/CalendarMonth.js index af1bf180..0621e1d9 100644 --- a/dist/calendar/CalendarMonth.js +++ b/dist/calendar/CalendarMonth.js @@ -30,9 +30,15 @@ var _utilsBemMixin = require('../utils/BemMixin'); var _utilsBemMixin2 = _interopRequireDefault(_utilsBemMixin); -'use strict'; +var _utilsIsMomentRange = require('../utils/isMomentRange'); + +var _utilsIsMomentRange2 = _interopRequireDefault(_utilsIsMomentRange); -var PureRenderMixin = _reactAddons2['default'].addons.PureRenderMixin; +var _utilsPureRenderMixin = require('../utils/PureRenderMixin'); + +var _utilsPureRenderMixin2 = _interopRequireDefault(_utilsPureRenderMixin); + +'use strict'; var lang = _moment2['default']().localeData(); @@ -42,15 +48,51 @@ var MONTHS = _immutable2['default'].List(lang._months); var CalendarMonth = _reactAddons2['default'].createClass({ displayName: 'CalendarMonth', - mixins: [_utilsBemMixin2['default'], PureRenderMixin], + mixins: [_utilsBemMixin2['default'], _utilsPureRenderMixin2['default']], renderDay: function renderDay(date, i) { var _props = this.props; var CalendarDate = _props.dateComponent; + var value = _props.value; + var highlightedDate = _props.highlightedDate; + var highlightedRange = _props.highlightedRange; + var hideSelection = _props.hideSelection; + var enabledRange = _props.enabledRange; + + var props = _objectWithoutProperties(_props, ['dateComponent', 'value', 'highlightedDate', 'highlightedRange', 'hideSelection', 'enabledRange']); + + var d = _moment2['default'](date); + + var isInSelectedRange = undefined; + var isSelectedDate = undefined; + var isSelectedRangeStart = undefined; + var isSelectedRangeEnd = undefined; + + if (!hideSelection && value && _moment2['default'].isMoment(value) && value.isSame(d)) { + isSelectedDate = true; + } else if (!hideSelection && value && _utilsIsMomentRange2['default'](value) && value.contains(d)) { + isInSelectedRange = true; + + if (value.start.isSame(d)) { + isSelectedRangeStart = true; + } else if (value.end.isSame(d)) { + isSelectedRangeEnd = true; + } + } - var props = _objectWithoutProperties(_props, ['dateComponent']); - - return _reactAddons2['default'].createElement(CalendarDate, _extends({}, props, { date: _moment2['default'](date), key: i })); + return _reactAddons2['default'].createElement(CalendarDate, _extends({ + key: i, + isDisabled: !enabledRange.contains(d), + isHighlightedDate: !!(highlightedDate && highlightedDate.isSame(d)), + isHighlightedRangeStart: !!(highlightedRange && highlightedRange.start.isSame(d)), + isHighlightedRangeEnd: !!(highlightedRange && highlightedRange.end.isSame(d)), + isInHighlightedRange: !!(highlightedRange && highlightedRange.contains(d)), + isSelectedDate: isSelectedDate, + isSelectedRangeStart: isSelectedRangeStart, + isSelectedRangeEnd: isSelectedRangeEnd, + isInSelectedRange: isInSelectedRange, + date: d + }, props)); }, renderWeek: function renderWeek(dates, i) { diff --git a/dist/calendar/CalendarSelection.js b/dist/calendar/CalendarSelection.js index fc93e14e..d87d9df1 100644 --- a/dist/calendar/CalendarSelection.js +++ b/dist/calendar/CalendarSelection.js @@ -16,27 +16,25 @@ var _utilsBemMixin = require('../utils/BemMixin'); var _utilsBemMixin2 = _interopRequireDefault(_utilsBemMixin); -'use strict'; +var _utilsPureRenderMixin = require('../utils/PureRenderMixin'); + +var _utilsPureRenderMixin2 = _interopRequireDefault(_utilsPureRenderMixin); -var PureRenderMixin = _reactAddons2['default'].addons.PureRenderMixin; +'use strict'; var CalendarSelection = _reactAddons2['default'].createClass({ displayName: 'CalendarSelection', - mixins: [_utilsBemMixin2['default'], PureRenderMixin], + mixins: [_utilsBemMixin2['default'], _utilsPureRenderMixin2['default']], render: function render() { var _props = this.props; var modifier = _props.modifier; - var inOtherMonth = _props.inOtherMonth; - var newSelectionStarted = _props.newSelectionStarted; var pending = _props.pending; var modifiers = _defineProperty({}, modifier, true); var states = { - pending: pending, - newSelectionStarted: newSelectionStarted, - inOtherMonth: inOtherMonth + pending: pending }; return _reactAddons2['default'].createElement('div', { className: this.cx({ states: states, modifiers: modifiers }) }); diff --git a/dist/utils/PureRenderMixin.js b/dist/utils/PureRenderMixin.js new file mode 100644 index 00000000..05ff0d39 --- /dev/null +++ b/dist/utils/PureRenderMixin.js @@ -0,0 +1,20 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _utilsShallowEqual = require('../utils/shallowEqual'); + +var _utilsShallowEqual2 = _interopRequireDefault(_utilsShallowEqual); + +var PureRenderMixin = { + shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) { + return !_utilsShallowEqual2['default'](this.props, nextProps) || !_utilsShallowEqual2['default'](this.state, nextState); + } +}; + +exports['default'] = PureRenderMixin; +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/utils/areMomentRangesEqual.js b/dist/utils/areMomentRangesEqual.js new file mode 100644 index 00000000..cdbe72eb --- /dev/null +++ b/dist/utils/areMomentRangesEqual.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = areMomentRangesEqual; + +function areMomentRangesEqual(r1, r2) { + return r1.start.isSame(r2.start) && r1.end.isSame(r2.end); +} + +module.exports = exports["default"]; \ No newline at end of file diff --git a/dist/utils/isMomentRange.js b/dist/utils/isMomentRange.js new file mode 100644 index 00000000..7f216a3f --- /dev/null +++ b/dist/utils/isMomentRange.js @@ -0,0 +1,18 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +exports['default'] = isMomentRange; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _momentRange = require('moment-range'); + +var _momentRange2 = _interopRequireDefault(_momentRange); + +function isMomentRange(val) { + return val && val.start && val.end && _momentRange2['default'].isMoment(val.start) && _momentRange2['default'].isMoment(val.end); +} + +module.exports = exports['default']; \ No newline at end of file diff --git a/dist/utils/shallowEqual.js b/dist/utils/shallowEqual.js new file mode 100644 index 00000000..4db861a7 --- /dev/null +++ b/dist/utils/shallowEqual.js @@ -0,0 +1,47 @@ +'use strict'; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _momentRange = require('moment-range'); + +var _momentRange2 = _interopRequireDefault(_momentRange); + +var _areMomentRangesEqual = require('./areMomentRangesEqual'); + +var _areMomentRangesEqual2 = _interopRequireDefault(_areMomentRangesEqual); + +var _isMomentRange = require('./isMomentRange'); + +var _isMomentRange2 = _interopRequireDefault(_isMomentRange); + +function shallowEqual(objA, objB) { + if (objA === objB) { + return true; + } + var key; + // Test for A's keys different from B. + for (key in objA) { + if (objA.hasOwnProperty(key)) { + if (!objB.hasOwnProperty(key)) { + return false; + } else if (_momentRange2['default'].isMoment(objA[key]) && _momentRange2['default'].isMoment(objB[key])) { + if (!objA[key].isSame(objB[key])) { + return false; + } + } else if (_isMomentRange2['default'](objA[key]) && _isMomentRange2['default'](objB[key]) && !_areMomentRangesEqual2['default'](objA[key], objB[key])) { + return false; + } else if (objA[key] !== objB[key]) { + return false; + } + } + } + // Test for B's keys missing from A. + for (key in objB) { + if (objB.hasOwnProperty(key) && !objA.hasOwnProperty(key)) { + return false; + } + } + return true; +} + +module.exports = shallowEqual; \ No newline at end of file diff --git a/example/components/code-snippet.jsx b/example/components/code-snippet.jsx index 05955ae6..41276bc4 100644 --- a/example/components/code-snippet.jsx +++ b/example/components/code-snippet.jsx @@ -1,10 +1,10 @@ /* global hljs */ -"use strict"; +'use strict'; -var React = require('react/addons'); -var cx = React.addons.classSet; +import React from 'react/addons'; +import cx from 'classnames'; -var CodeSnippet = React.createClass({ +const CodeSnippet = React.createClass({ propTypes: { language: React.PropTypes.string.isRequired, toggle: React.PropTypes.bool, @@ -72,4 +72,4 @@ var CodeSnippet = React.createClass({ } }); -module.exports = CodeSnippet; +export default CodeSnippet; diff --git a/package.json b/package.json index 07cb939e..9ff6013b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ }, "dependencies": { "calendar": "^0.1.0", - "immutable": "^3.6.2", + "classnames": "^2.1.1", + "immutable": "^3.7.2", "moment": "^2.8.3", "moment-range": "^1.0.5" }, diff --git a/src/DateRangePicker.jsx b/src/DateRangePicker.jsx index 5dbbb3cb..45f77899 100644 --- a/src/DateRangePicker.jsx +++ b/src/DateRangePicker.jsx @@ -1,7 +1,8 @@ 'use strict'; import React from 'react/addons'; -import moment from 'moment'; +import moment from 'moment-range'; import Immutable from 'immutable'; +import calendar from 'calendar'; import BemMixin from './utils/BemMixin'; import Legend from './Legend'; @@ -11,6 +12,8 @@ import CalendarDate from './calendar/CalendarDate'; import PaginationArrow from './PaginationArrow'; +import isMomentRange from './utils/isMomentRange'; + const PureRenderMixin = React.addons.PureRenderMixin; const absoluteMinimum = moment(new Date(-8640000000000000 / 2)).startOf('day'); const absoluteMaximum = moment(new Date(8640000000000000 / 2)).startOf('day'); @@ -93,9 +96,12 @@ const DateRangePicker = React.createClass({ }, componentWillReceiveProps(nextProps) { + var nextDateStates = this.getDateStates(nextProps); + var nextEnabledRange = this.getEnabledRange(nextProps); + this.setState({ - dateStates: this.getDateStates(nextProps), - enabledRange: this.getEnabledRange(nextProps) + dateStates: this.state.dateStates && Immutable.is(this.state.dateStates, nextDateStates) ? this.state.dateStates : nextDateStates, + enabledRange: this.state.enabledRange && Immutable.is(this.state.enabledRange, nextEnabledRange) ? this.state.enabledRange : nextEnabledRange }); }, @@ -187,10 +193,113 @@ const DateRangePicker = React.createClass({ }); }, - onSelect(date) { - this.props.onSelect(moment(date)); + isDateDisabled(date) { + return !this.state.enabledRange.contains(date); + }, + + isDateSelectable(date) { + return this.dateRangesForDate(date).some(r => r.get('selectable')); + }, + + nonSelectableStateRanges() { + return this.state.dateStates.filter(d => !d.get('selectable')); + }, + + dateRangesForDate(date) { + return this.state.dateStates.filter(d => d.get('range').contains(date)); + }, + + sanitizeRange(range, forwards) { + /* Truncates the provided range at the first intersection + * with a non-selectable state. Using forwards to determine + * which direction to work + */ + let blockedRanges = this.nonSelectableStateRanges().map(r => r.get('range')); + let intersect; + + if (forwards) { + intersect = blockedRanges.find(r => range.intersect(r)); + if (intersect) { + return moment().range(range.start, intersect.start); + } + + } else { + intersect = blockedRanges.findLast(r => range.intersect(r)); + + if (intersect) { + return moment().range(intersect.end, range.end); + } + } + + if (range.start.isBefore(this.state.enabledRange.start)) { + return moment().range(this.state.enabledRange.start, range.end); + } + + if (range.end.isAfter(this.state.enabledRange.end)) { + return moment().range(range.start, this.state.enabledRange.end); + } + + return range; + }, + + highlightRange(range) { + this.setState({ + highlightedRange: range, + highlightedDate: null + }); + if (typeof this.props.onHighlightRange === 'function') { + this.props.onHighlightRange(range, this.statesForRange(range)); + } }, + onUnHighlightDate(date) { + this.setState({ + highlightedDate: null + }); + }, + + onSelectDate(date) { + let {selectionType} = this.props; + let {selectedStartDate} = this.state; + + if (selectionType === 'range') { + if (selectedStartDate) { + this.completeRangeSelection(); + } else if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.startRangeSelection(date); + } + } else { + if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.completeSelection(); + } + } + }, + + onHighlightDate(date) { + let {selectionType} = this.props; + let {selectedStartDate} = this.state; + + let datePair; + let range; + let forwards; + + if (selectionType === 'range') { + if (selectedStartDate) { + datePair = Immutable.List.of(selectedStartDate, date).sortBy(d => d.unix()); + range = moment().range(datePair.get(0), datePair.get(1)); + forwards = (range.start.unix() === selectedStartDate.unix()); + range = this.sanitizeRange(range, forwards); + this.highlightRange(range); + } else if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.highlightDate(date); + } + } else { + if (!this.isDateDisabled(date) && this.isDateSelectable(date)) { + this.highlightDate(date); + } + } + }, + startRangeSelection(date) { this.setState({ hideSelection: true, @@ -212,7 +321,6 @@ const DateRangePicker = React.createClass({ return this.state.dateStates.filter(d => d.get('range').intersect(range)).map(d => d.get('state')); }, - completeSelection() { let highlightedDate = this.state.highlightedDate; if (highlightedDate) { @@ -237,7 +345,7 @@ const DateRangePicker = React.createClass({ } }, - onHighlightDate(date) { + highlightDate(date) { this.setState({ highlightedDate: date }); @@ -246,22 +354,6 @@ const DateRangePicker = React.createClass({ } }, - onHighlightRange(range) { - this.setState({ - highlightedRange: range, - highlightedDate: null - }); - if (typeof this.props.onHighlightRange === 'function') { - this.props.onHighlightRange(range, this.statesForRange(range)); - } - }, - - onUnHighlightDate() { - this.setState({ - highlightedDate: null - }); - }, - getMonthDate() { return moment(new Date(this.state.year, this.state.month, 1)); }, @@ -357,8 +449,7 @@ const DateRangePicker = React.createClass({ hideSelection, highlightedDate, highlightedRange, - highlightStartDate, - selectedStartDate + highlightStartDate } = this.state; let monthDate = this.getMonthDate(); @@ -369,6 +460,30 @@ const DateRangePicker = React.createClass({ monthDate.add(index, 'months'); + let cal = new calendar.Calendar(firstOfWeek); + let monthDates = Immutable.fromJS(cal.monthDates(monthDate.year(), monthDate.month())); + let monthStart = monthDates.first().first(); + let monthEnd = monthDates.last().last(); + let monthRange = moment().range(monthStart, monthEnd); + + if (moment.isMoment(value)) { + if (!monthRange.contains(value)) { + value = null; + } + } else if (isMomentRange(value)) { + if (!monthRange.overlaps(value)) { + value = null; + } + } + + if (!moment.isMoment(highlightedDate) || !monthRange.contains(highlightedDate)) { + highlightedDate = null; + } + + if (!isMomentRange(highlightedRange) || !monthRange.overlaps(highlightedRange)) { + highlightedRange = null; + } + props = { bemBlock, bemNamespace, @@ -378,23 +493,18 @@ const DateRangePicker = React.createClass({ hideSelection, highlightedDate, highlightedRange, - highlightStartDate, index, key, - selectedStartDate, selectionType, value, maxIndex: numberOfCalendars - 1, firstOfMonth: monthDate, onMonthChange: this.changeMonth, onYearChange: this.changeYear, - onHighlightRange: this.onHighlightRange, + onSelectDate: this.onSelectDate, onHighlightDate: this.onHighlightDate, onUnHighlightDate: this.onUnHighlightDate, - onSelect: this.onSelect, - startRangeSelection: this.startRangeSelection, - completeSelection: this.completeSelection, - completeRangeSelection: this.completeRangeSelection, + dateRangesForDate: this.dateRangesForDate, dateComponent: CalendarDate }; diff --git a/src/calendar/CalendarDate.jsx b/src/calendar/CalendarDate.jsx index a267deba..13709eaa 100644 --- a/src/calendar/CalendarDate.jsx +++ b/src/calendar/CalendarDate.jsx @@ -1,18 +1,18 @@ 'use strict'; import React from 'react/addons'; + import moment from 'moment-range'; import Immutable from 'immutable'; import BemMixin from '../utils/BemMixin'; +import PureRenderMixin from '../utils/PureRenderMixin'; import lightenDarkenColor from '../utils/lightenDarkenColor'; import CalendarDatePeriod from './CalendarDatePeriod'; import CalendarHighlight from './CalendarHighlight'; import CalendarSelection from './CalendarSelection'; -const PureRenderMixin = React.addons.PureRenderMixin; - const CalendarDate = React.createClass({ mixins: [BemMixin, PureRenderMixin], @@ -21,27 +21,25 @@ const CalendarDate = React.createClass({ date: React.PropTypes.object.isRequired, firstOfMonth: React.PropTypes.object.isRequired, - index: React.PropTypes.number.isRequired, - maxIndex: React.PropTypes.number.isRequired, - selectionType: React.PropTypes.string.isRequired, - value: React.PropTypes.object, - hideSelection: React.PropTypes.bool, - highlightedRange: React.PropTypes.object, + isSelectedDate: React.PropTypes.bool, + isSelectedRangeStart: React.PropTypes.bool, + isSelectedRangeEnd: React.PropTypes.bool, + isInSelectedRange: React.PropTypes.bool, + + isHighlightedDate: React.PropTypes.bool, + isHighlightedRangeStart: React.PropTypes.bool, + isHighlightedRangeEnd: React.PropTypes.bool, + isInHighlightedRange: React.PropTypes.bool, + highlightedDate: React.PropTypes.object, - selectedStartDate: React.PropTypes.object, dateStates: React.PropTypes.instanceOf(Immutable.List), + isDisabled: React.PropTypes.bool, + dateRangesForDate: React.PropTypes.func, onHighlightDate: React.PropTypes.func, onUnHighlightDate: React.PropTypes.func, - onStartSelection: React.PropTypes.func, - onCompleteSelection: React.PropTypes.func - }, - - getDefaultProps() { - return { - hideSelection: false - }; + onSelectDate: React.PropTypes.func }, getInitialState() { @@ -50,57 +48,8 @@ const CalendarDate = React.createClass({ }; }, - isDisabled(date) { - return !this.props.enabledRange.contains(date); - }, - - isDateSelectable(date) { - return this.dateRangesForDate(date).some(r => r.get('selectable')); - }, - - nonSelectableStateRanges() { - return this.props.dateStates.filter(d => !d.get('selectable')); - }, - - dateRangesForDate(date) { - return this.props.dateStates.filter(d => d.get('range').contains(date)); - }, - - sanitizeRange(range, forwards) { - /* Truncates the provided range at the first intersection - * with a non-selectable state. Using forwards to determine - * which direction to work - */ - let blockedRanges = this.nonSelectableStateRanges().map(r => r.get('range')); - let intersect; - - if (forwards) { - intersect = blockedRanges.find(r => range.intersect(r)); - if (intersect) { - return moment().range(range.start, intersect.start); - } - - } else { - intersect = blockedRanges.findLast(r => range.intersect(r)); - - if (intersect) { - return moment().range(intersect.end, range.end); - } - } - - if (range.start.isBefore(this.props.enabledRange.start)) { - return moment().range(this.props.enabledRange.start, range.end); - } - - if (range.end.isAfter(this.props.enabledRange.end)) { - return moment().range(range.start, this.props.enabledRange.end); - } - - return range; - }, - mouseUp() { - this.selectDate(); + this.props.onSelectDate(this.props.date); if (this.state.mouseDown) { this.setState({ @@ -118,8 +67,8 @@ const CalendarDate = React.createClass({ }, touchEnd() { - this.highlightDate(); - this.selectDate(); + this.props.onHighlightDate(this.props.date); + this.props.onSelectDate(this.props.date); if (this.state.mouseDown) { this.setState({ @@ -138,63 +87,20 @@ const CalendarDate = React.createClass({ }, mouseEnter() { - this.highlightDate(); + this.props.onHighlightDate(this.props.date); }, mouseLeave() { if (this.state.mouseDown) { - this.selectDate(); + this.props.onSelectDate(this.props.date); this.setState({ mouseDown: false }); } - this.unHighlightDate(); - }, - - highlightDate() { - let {date, selectionType, selectedStartDate, onHighlightRange, onHighlightDate} = this.props; - let datePair; - let range; - let forwards; - - if (selectionType === 'range') { - if (selectedStartDate) { - datePair = Immutable.List.of(selectedStartDate, date).sortBy(d => d.unix()); - range = moment().range(datePair.get(0), datePair.get(1)); - forwards = (range.start.unix() === selectedStartDate.unix()); - range = this.sanitizeRange(range, forwards); - onHighlightRange(range); - } else if (!this.isDisabled(date) && this.isDateSelectable(date)) { - onHighlightDate(date); - } - } else { - if (!this.isDisabled(date) && this.isDateSelectable(date)) { - onHighlightDate(date); - } - } - }, - - unHighlightDate() { this.props.onUnHighlightDate(this.props.date); }, - selectDate() { - let {date, selectionType, selectedStartDate, completeRangeSelection, completeSelection, startRangeSelection} = this.props; - - if (selectionType === 'range') { - if (selectedStartDate) { - completeRangeSelection(); - } else if (!this.isDisabled(date) && this.isDateSelectable(date)) { - startRangeSelection(date); - } - } else { - if (!this.isDisabled(date) && this.isDateSelectable(date)) { - completeSelection(); - } - } - }, - getBemModifiers() { let {date, firstOfMonth} = this.props; @@ -213,77 +119,59 @@ const CalendarDate = React.createClass({ }, getBemStates() { - let {date, value, highlightedRange, highlightedDate, hideSelection} = this.props; - let disabled = this.isDisabled(date); - let highlighted = false; - let selected = false; - - if (value && !hideSelection) { - if (!value.start && date.isSame(value)) { - selected = true; - } else if (value.start && value.start.isSame(value.end) && date.isSame(value.start)) { - selected = true; - } else if (value.start && value.contains(date)) { - selected = true; - } - } + let { + isSelectedDate, + isInSelectedRange, + isInHighlightedRange, + isHighlightedDate: highlighted, + isDisabled: disabled + } = this.props; - if (highlightedRange && highlightedRange.contains(date)) { - selected = true; - } else if (highlightedDate && date.isSame(highlightedDate)) { - highlighted = true; - } + let selected = isSelectedDate || isInSelectedRange || isInHighlightedRange; return {disabled, highlighted, selected}; }, render() { - let {value, date, highlightedRange, highlightedDate, hideSelection} = this.props; + let { + date, + dateRangesForDate, + isSelectedDate, + isSelectedRangeStart, + isSelectedRangeEnd, + isInSelectedRange, + isHighlightedDate, + isHighlightedRangeStart, + isHighlightedRangeEnd, + isInHighlightedRange + } = this.props; let bemModifiers = this.getBemModifiers(); let bemStates = this.getBemStates(); - let pending = false; + let pending = isInHighlightedRange; let color; let amColor; let pmColor; - let states = this.dateRangesForDate(date); + let states = dateRangesForDate(date); let numStates = states.count(); let cellStyle = {}; let style = {}; - let highlightModifier = null; - let selectionModifier = null; - - if (value && !hideSelection && value.start) { - if (value.start.isSame(date) && value.start.isSame(value.end)) { - selectionModifier = 'single'; - } else if (value.contains(date)) { - if (date.isSame(value.start)) { - selectionModifier = 'start'; - } else if (date.isSame(value.end)) { - selectionModifier = 'end'; - } else { - selectionModifier = 'segment'; - } - } - } else if (value && !hideSelection && date.isSame(value)) { - selectionModifier = 'single'; - } + let highlightModifier; + let selectionModifier; - if (highlightedRange && highlightedRange.contains(date)) { - pending = true; - - if (date.isSame(highlightedRange.start)) { - selectionModifier = 'start'; - } else if (date.isSame(highlightedRange.end)) { - selectionModifier = 'end'; - } else { - selectionModifier = 'segment'; - } + if (isSelectedDate || (isSelectedRangeStart && isSelectedRangeEnd)) { + selectionModifier = 'single'; + } else if (isSelectedRangeStart || isHighlightedRangeStart) { + selectionModifier = 'start'; + } else if (isSelectedRangeEnd || isHighlightedRangeEnd) { + selectionModifier = 'end'; + } else if (isInSelectedRange || isInHighlightedRange) { + selectionModifier = 'segment'; } - if (highlightedDate && date.isSame(highlightedDate)) { + if (isHighlightedDate) { highlightModifier = 'single'; } @@ -318,7 +206,6 @@ const CalendarDate = React.createClass({ @@ -330,8 +217,8 @@ const CalendarDate = React.createClass({ {numStates === 1 &&
} {date.format('D')} - {selectionModifier && } - {highlightModifier && } + {selectionModifier ? : null} + {highlightModifier ? : null} ); } diff --git a/src/calendar/CalendarDatePeriod.jsx b/src/calendar/CalendarDatePeriod.jsx index 69b061b4..5d7ca630 100644 --- a/src/calendar/CalendarDatePeriod.jsx +++ b/src/calendar/CalendarDatePeriod.jsx @@ -1,8 +1,9 @@ 'use strict'; import React from 'react/addons'; + import BemMixin from '../utils/BemMixin'; +import PureRenderMixin from '../utils/PureRenderMixin'; -const PureRenderMixin = React.addons.PureRenderMixin; const CalendarDatePeriod = React.createClass({ mixins: [BemMixin, PureRenderMixin], diff --git a/src/calendar/CalendarHighlight.jsx b/src/calendar/CalendarHighlight.jsx index a7e5dd9f..f824d548 100644 --- a/src/calendar/CalendarHighlight.jsx +++ b/src/calendar/CalendarHighlight.jsx @@ -2,19 +2,16 @@ import React from 'react/addons'; import BemMixin from '../utils/BemMixin'; - -const PureRenderMixin = React.addons.PureRenderMixin; +import PureRenderMixin from '../utils/PureRenderMixin'; const CalendarHighlight = React.createClass({ mixins: [BemMixin, PureRenderMixin], render() { - let {modifier, inOtherMonth} = this.props; + let {modifier} = this.props; let modifiers = {[modifier]: true}; - let states = { - inOtherMonth - }; + let states = {}; return (
@@ -22,4 +19,4 @@ const CalendarHighlight = React.createClass({ } }); -export default CalendarHighlight; \ No newline at end of file +export default CalendarHighlight; diff --git a/src/calendar/CalendarMonth.jsx b/src/calendar/CalendarMonth.jsx index 078b3a9a..b8093f2d 100644 --- a/src/calendar/CalendarMonth.jsx +++ b/src/calendar/CalendarMonth.jsx @@ -6,8 +6,8 @@ import calendar from 'calendar'; import Immutable from 'immutable'; import BemMixin from '../utils/BemMixin'; - -const PureRenderMixin = React.addons.PureRenderMixin; +import isMomentRange from '../utils/isMomentRange'; +import PureRenderMixin from '../utils/PureRenderMixin'; const lang = moment().localeData(); @@ -19,9 +19,41 @@ const CalendarMonth = React.createClass({ mixins: [BemMixin, PureRenderMixin], renderDay(date, i) { - let {dateComponent: CalendarDate, ...props} = this.props; + let {dateComponent: CalendarDate, value, highlightedDate, highlightedRange, hideSelection, enabledRange, ...props} = this.props; + let d = moment(date); + + let isInSelectedRange; + let isSelectedDate; + let isSelectedRangeStart; + let isSelectedRangeEnd; + + if (!hideSelection && value && moment.isMoment(value) && value.isSame(d)) { + isSelectedDate = true; + } else if (!hideSelection && value && isMomentRange(value) && value.contains(d)) { + isInSelectedRange = true; + + if (value.start.isSame(d)) { + isSelectedRangeStart = true; + } else if (value.end.isSame(d)) { + isSelectedRangeEnd = true; + } + } - return ; + return ( + + ); }, renderWeek(dates, i) { diff --git a/src/calendar/CalendarSelection.jsx b/src/calendar/CalendarSelection.jsx index 7db0bdfc..41bad696 100644 --- a/src/calendar/CalendarSelection.jsx +++ b/src/calendar/CalendarSelection.jsx @@ -2,20 +2,17 @@ import React from 'react/addons'; import BemMixin from '../utils/BemMixin'; - -const PureRenderMixin = React.addons.PureRenderMixin; +import PureRenderMixin from '../utils/PureRenderMixin'; const CalendarSelection = React.createClass({ mixins: [BemMixin, PureRenderMixin], render() { - let {modifier, inOtherMonth, newSelectionStarted, pending} = this.props; + let {modifier, pending} = this.props; let modifiers = {[modifier]: true}; let states = { - pending, - newSelectionStarted, - inOtherMonth + pending }; return ( diff --git a/src/utils/PureRenderMixin.js b/src/utils/PureRenderMixin.js new file mode 100644 index 00000000..8ed08a0b --- /dev/null +++ b/src/utils/PureRenderMixin.js @@ -0,0 +1,13 @@ +import shallowEqual from '../utils/shallowEqual'; + + +const PureRenderMixin = { + shouldComponentUpdate(nextProps, nextState) { + return ( + !shallowEqual(this.props, nextProps) || + !shallowEqual(this.state, nextState) + ); + } +}; + +export default PureRenderMixin; \ No newline at end of file diff --git a/src/utils/areMomentRangesEqual.js b/src/utils/areMomentRangesEqual.js new file mode 100644 index 00000000..89ac353c --- /dev/null +++ b/src/utils/areMomentRangesEqual.js @@ -0,0 +1,3 @@ +export default function areMomentRangesEqual(r1, r2) { + return r1.start.isSame(r2.start) && r1.end.isSame(r2.end); +} diff --git a/src/utils/isMomentRange.js b/src/utils/isMomentRange.js new file mode 100644 index 00000000..1fcf5dfb --- /dev/null +++ b/src/utils/isMomentRange.js @@ -0,0 +1,5 @@ +import moment from 'moment-range'; + +export default function isMomentRange(val) { + return val && val.start && val.end && moment.isMoment(val.start) && moment.isMoment(val.end); +} diff --git a/src/utils/shallowEqual.js b/src/utils/shallowEqual.js new file mode 100644 index 00000000..dac93c5d --- /dev/null +++ b/src/utils/shallowEqual.js @@ -0,0 +1,44 @@ +import moment from 'moment-range'; + +import areMomentRangesEqual from './areMomentRangesEqual'; +import isMomentRange from './isMomentRange'; + + +function shallowEqual(objA, objB) { + if (objA === objB) { + return true; + } + var key; + // Test for A's keys different from B. + for (key in objA) { + if (objA.hasOwnProperty(key)) { + if (!objB.hasOwnProperty(key)) { + return false; + } else if ( + moment.isMoment(objA[key]) && + moment.isMoment(objB[key]) + ) { + if (!objA[key].isSame(objB[key])) { + return false; + } + } else if ( + isMomentRange(objA[key]) && + isMomentRange(objB[key]) && + !areMomentRangesEqual(objA[key], objB[key]) + ) { + return false; + } else if (objA[key] !== objB[key]) { + return false; + } + } + } + // Test for B's keys missing from A. + for (key in objB) { + if (objB.hasOwnProperty(key) && !objA.hasOwnProperty(key)) { + return false; + } + } + return true; +} + +module.exports = shallowEqual;