diff --git a/.travis.yml b/.travis.yml
index b55536c174..f8c4163fc0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,7 +18,7 @@ script:
- 'if [ -n "${KARMA-}" ]; then npm run tests-karma ; fi'
- 'if [ -n "${COVERAGE-}" ] && [ "${TRAVIS_BRANCH-}" = "master" ]; then npm run cover; cat ./coverage/lcov.info | ./node_modules/.bin/coveralls ; fi'
install:
- - 'if [ -n "${LINT-}" ]; then npm install --legacy-bundling ; else npm install ; fi'
+ - 'if [ -n "${LINT-}" ]; then NPM_CONFIG_LEGACY_PEER_DEPS=true npm install --legacy-bundling ; else npm install ; fi'
env:
global:
- TEST=true
diff --git a/examples/DateRangePickerWrapper.jsx b/examples/DateRangePickerWrapper.jsx
index 620a286cdd..96154f4d06 100644
--- a/examples/DateRangePickerWrapper.jsx
+++ b/examples/DateRangePickerWrapper.jsx
@@ -14,6 +14,7 @@ import {
HORIZONTAL_ORIENTATION,
ANCHOR_LEFT,
NAV_POSITION_TOP,
+ OPEN_DOWN,
} from '../src/constants';
import isInclusivelyAfterDay from '../src/utils/isInclusivelyAfterDay';
@@ -70,6 +71,7 @@ const defaultProps = {
keepOpenOnDateSelect: false,
reopenPickerOnClearDates: false,
isRTL: false,
+ openDirection: OPEN_DOWN,
// navigation related props
navPosition: NAV_POSITION_TOP,
diff --git a/examples/DayPickerSingleDateControllerWrapper.jsx b/examples/DayPickerSingleDateControllerWrapper.jsx
index 02d94e931b..44a0a5e1fb 100644
--- a/examples/DayPickerSingleDateControllerWrapper.jsx
+++ b/examples/DayPickerSingleDateControllerWrapper.jsx
@@ -19,6 +19,7 @@ const propTypes = forbidExtraProps({
initialDate: momentPropTypes.momentObj,
showInput: PropTypes.bool,
+ allowUnselect: PropTypes.bool,
keepOpenOnDateSelect: PropTypes.bool,
isOutsideRange: PropTypes.func,
isDayBlocked: PropTypes.func,
@@ -56,6 +57,7 @@ const defaultProps = {
showInput: false,
// day presentation and interaction related props
+ allowUnselect: false,
renderCalendarDay: undefined,
renderDayContents: null,
isDayBlocked: () => false,
diff --git a/examples/SingleDatePickerWrapper.jsx b/examples/SingleDatePickerWrapper.jsx
index 92a309f7a8..0dcf491462 100644
--- a/examples/SingleDatePickerWrapper.jsx
+++ b/examples/SingleDatePickerWrapper.jsx
@@ -8,7 +8,7 @@ import SingleDatePicker from '../src/components/SingleDatePicker';
import { SingleDatePickerPhrases } from '../src/defaultPhrases';
import SingleDatePickerShape from '../src/shapes/SingleDatePickerShape';
-import { HORIZONTAL_ORIENTATION, ANCHOR_LEFT } from '../src/constants';
+import { HORIZONTAL_ORIENTATION, ANCHOR_LEFT, OPEN_DOWN } from '../src/constants';
import isInclusivelyAfterDay from '../src/utils/isInclusivelyAfterDay';
const propTypes = {
@@ -56,6 +56,7 @@ const defaultProps = {
keepOpenOnDateSelect: false,
reopenPickerOnClearDate: false,
isRTL: false,
+ openDirection: OPEN_DOWN,
// navigation related props
navPrev: null,
diff --git a/package.json b/package.json
index d5f62e238c..7798b21855 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"lint": "eslint --ext .js,.jsx src test",
"mocha": "mocha ./test/_helpers",
"storybook:uninstall": "npm uninstall --no-save @storybook/react && rimraf node_modules/@storybook node_modules/react-modal node_modules/react-dom-factories",
- "react": "enzyme-adapter-react-install 16",
+ "react": "NPM_CONFIG_LEGACY_PEER_DEPS=true enzyme-adapter-react-install 16",
"pretest": "npm run --silent lint",
"pretests-only": "npm run react",
"tests-only": "npm run mocha --silent",
diff --git a/src/components/DayPicker.jsx b/src/components/DayPicker.jsx
index f39aa8ec01..34345f7259 100644
--- a/src/components/DayPicker.jsx
+++ b/src/components/DayPicker.jsx
@@ -391,10 +391,19 @@ class DayPicker extends React.PureComponent {
monthTitleHeight,
} = this.state;
+ let shouldAdjustHeight = false;
+ if (numberOfMonths !== prevProps.numberOfMonths) {
+ this.setCalendarMonthWeeks(currentMonth);
+ shouldAdjustHeight = true;
+ }
if (
this.isHorizontal()
&& (orientation !== prevProps.orientation || daySize !== prevProps.daySize)
) {
+ shouldAdjustHeight = true;
+ }
+
+ if (shouldAdjustHeight) {
const visibleCalendarWeeks = this.calendarMonthWeeks.slice(1, numberOfMonths + 1);
const calendarMonthWeeksHeight = Math.max(0, ...visibleCalendarWeeks) * (daySize - 1);
const newMonthHeight = monthTitleHeight + calendarMonthWeeksHeight + 1;
diff --git a/src/components/DayPickerSingleDateController.jsx b/src/components/DayPickerSingleDateController.jsx
index 89be9f6ed8..de492c3a24 100644
--- a/src/components/DayPickerSingleDateController.jsx
+++ b/src/components/DayPickerSingleDateController.jsx
@@ -34,12 +34,18 @@ import {
import DayPicker from './DayPicker';
import getPooledMoment from '../utils/getPooledMoment';
+// Default value of the date property. Represents the state
+// when there is no date selected.
+// TODO: use null
+const DATE_UNSET_VALUE = undefined;
+
const propTypes = forbidExtraProps({
date: momentPropTypes.momentObj,
minDate: momentPropTypes.momentObj,
maxDate: momentPropTypes.momentObj,
onDateChange: PropTypes.func,
+ allowUnselect: PropTypes.bool,
focused: PropTypes.bool,
onFocusChange: PropTypes.func,
onClose: PropTypes.func,
@@ -102,11 +108,12 @@ const propTypes = forbidExtraProps({
});
const defaultProps = {
- date: undefined, // TODO: use null
+ date: DATE_UNSET_VALUE,
minDate: null,
maxDate: null,
onDateChange() {},
+ allowUnselect: false,
focused: false,
onFocusChange() {},
onClose() {},
@@ -352,16 +359,19 @@ export default class DayPickerSingleDateController extends React.PureComponent {
if (e) e.preventDefault();
if (this.isBlocked(day)) return;
const {
+ allowUnselect,
onDateChange,
keepOpenOnDateSelect,
onFocusChange,
onClose,
} = this.props;
- onDateChange(day);
+ const clickedDay = allowUnselect && this.isSelected(day) ? DATE_UNSET_VALUE : day;
+
+ onDateChange(clickedDay);
if (!keepOpenOnDateSelect) {
onFocusChange({ focused: false });
- onClose({ date: day });
+ onClose({ date: clickedDay });
}
}
diff --git a/stories/DayPickerSingleDateController.js b/stories/DayPickerSingleDateController.js
index e7fd81a5f0..ef05c08305 100644
--- a/stories/DayPickerSingleDateController.js
+++ b/stories/DayPickerSingleDateController.js
@@ -145,6 +145,14 @@ storiesOf('DayPickerSingleDateController', module)
onNextMonthClick={action('DayPickerSingleDateController::onNextMonthClick')}
/>
)))
+ .add('with day unselection', withInfo()(() => (
+
+ )))
.add('with custom input', withInfo()(() => (
{
expect(onDateChangeStub.callCount).to.equal(1);
});
+ it('props.onDateChange receives undefined when day selected', () => {
+ const date = moment();
+ const onDateChangeStub = sinon.stub();
+ const wrapper = shallow((
+ {}}
+ date={date}
+ allowUnselect
+ />
+ ));
+ // Click same day as the provided date.
+ wrapper.instance().onDayClick(date);
+ expect(onDateChangeStub.callCount).to.equal(1);
+ expect(onDateChangeStub.getCall(0).args[0]).to.equal(undefined);
+ });
+
+ it('props.onDateChange receives day when allowUnselect is disabled', () => {
+ const date = moment();
+ const onDateChangeStub = sinon.stub();
+ const wrapper = shallow((
+ {}}
+ date={date}
+ allowUnselect={false}
+ />
+ ));
+ // Click same day as the provided date.
+ wrapper.instance().onDayClick(date);
+ expect(onDateChangeStub.callCount).to.equal(1);
+ expect(onDateChangeStub.getCall(0).args[0]).to.equal(date);
+ });
+
describe('props.keepOpenOnDateSelect is false', () => {
it('props.onFocusChange is called', () => {
const onFocusChangeStub = sinon.stub();
diff --git a/test/components/DayPicker_spec.jsx b/test/components/DayPicker_spec.jsx
index 5f98b61e9b..46d0697f90 100644
--- a/test/components/DayPicker_spec.jsx
+++ b/test/components/DayPicker_spec.jsx
@@ -12,6 +12,7 @@ import CalendarMonthGrid from '../../src/components/CalendarMonthGrid';
import DayPickerNavigation from '../../src/components/DayPickerNavigation';
import DayPickerKeyboardShortcuts from '../../src/components/DayPickerKeyboardShortcuts';
import {
+ DAY_SIZE,
HORIZONTAL_ORIENTATION,
VERTICAL_ORIENTATION,
VERTICAL_SCROLLABLE,
@@ -22,8 +23,9 @@ const today = moment().locale('en');
const event = { preventDefault() {}, stopPropagation() {} };
describe('DayPicker', () => {
+ let adjustDayPickerHeightSpy;
beforeEach(() => {
- sinon.stub(PureDayPicker.prototype, 'adjustDayPickerHeight');
+ adjustDayPickerHeightSpy = sinon.stub(PureDayPicker.prototype, 'adjustDayPickerHeight');
});
afterEach(() => {
@@ -907,13 +909,8 @@ describe('DayPicker', () => {
});
});
- describe.skip('life cycle methods', () => {
- let adjustDayPickerHeightSpy;
- beforeEach(() => {
- adjustDayPickerHeightSpy = sinon.stub(PureDayPicker.prototype, 'adjustDayPickerHeight');
- });
-
- describe('#componentDidMount', () => {
+ describe('life cycle methods', () => {
+ describe.skip('#componentDidMount', () => {
describe('props.orientation === HORIZONTAL_ORIENTATION', () => {
it('calls adjustDayPickerHeight', () => {
mount();
@@ -927,7 +924,7 @@ describe('DayPicker', () => {
});
});
- describe('props.orientation === VERTICAL_ORIENTATION', () => {
+ describe.skip('props.orientation === VERTICAL_ORIENTATION', () => {
it('does not call adjustDayPickerHeight', () => {
mount();
expect(adjustDayPickerHeightSpy.called).to.equal(false);
@@ -949,7 +946,7 @@ describe('DayPicker', () => {
});
});
- describe('#componentWillReceiveProps', () => {
+ describe.skip('#componentWillReceiveProps', () => {
describe('props.orientation === VERTICAL_SCROLLABLE', () => {
it('updates state.currentMonthScrollTop', () => {
sinon.spy(DayPicker.prototype, 'setTransitionContainerRef');
@@ -966,15 +963,16 @@ describe('DayPicker', () => {
describe('#componentDidUpdate', () => {
let updateStateAfterMonthTransitionSpy;
+
beforeEach(() => {
updateStateAfterMonthTransitionSpy = sinon.stub(
- DayPicker.prototype,
+ PureDayPicker.prototype,
'updateStateAfterMonthTransition',
);
});
describe('props.orientation === HORIZONTAL_ORIENTATION', () => {
- it('calls adjustDayPickerHeight if state.monthTransition is truthy', () => {
+ it.skip('calls adjustDayPickerHeight if state.monthTransition is truthy', () => {
const wrapper = mount();
wrapper.setState({
monthTransition: 'foo',
@@ -982,7 +980,7 @@ describe('DayPicker', () => {
expect(adjustDayPickerHeightSpy).to.have.property('callCount', 2);
});
- it('does not call adjustDayPickerHeight if state.monthTransition is falsy', () => {
+ it.skip('does not call adjustDayPickerHeight if state.monthTransition is falsy', () => {
const wrapper = mount();
wrapper.setState({
monthTransition: null,
@@ -990,7 +988,7 @@ describe('DayPicker', () => {
expect(adjustDayPickerHeightSpy.calledTwice).to.equal(false);
});
- it('calls adjustDayPickerHeight if orientation has changed from HORIZONTAL_ORIENTATION to VERTICAL_ORIENTATION', () => {
+ it.skip('calls adjustDayPickerHeight if orientation has changed from HORIZONTAL_ORIENTATION to VERTICAL_ORIENTATION', () => {
const wrapper = mount();
wrapper.setState({
orientation: VERTICAL_ORIENTATION,
@@ -998,7 +996,7 @@ describe('DayPicker', () => {
expect(adjustDayPickerHeightSpy).to.have.property('callCount', 2);
});
- it('calls adjustDayPickerHeight if daySize has changed', () => {
+ it.skip('calls adjustDayPickerHeight if daySize has changed', () => {
const wrapper = mount();
wrapper.setState({
daySize: 40,
@@ -1007,7 +1005,7 @@ describe('DayPicker', () => {
expect(adjustDayPickerHeightSpy).to.have.property('callCount', 2);
});
- it('calls updateStateAfterMonthTransition if state.monthTransition is truthy', () => {
+ it.skip('calls updateStateAfterMonthTransition if state.monthTransition is truthy', () => {
const wrapper = mount();
wrapper.setState({
monthTransition: 'foo',
@@ -1015,16 +1013,36 @@ describe('DayPicker', () => {
expect(updateStateAfterMonthTransitionSpy).to.have.property('callCount', 1);
});
- it('does not call updateStateAfterMonthTransition if state.monthTransition is falsy', () => {
+ it.skip('does not call updateStateAfterMonthTransition if state.monthTransition is falsy', () => {
const wrapper = mount();
wrapper.setState({
monthTransition: null,
});
expect(updateStateAfterMonthTransitionSpy.calledOnce).to.equal(false);
});
+
+ it('calls adjustDayPickerHeightSpy if props.numberOfMonths changes', () => {
+ const wrapper = shallow().dive();
+ wrapper.instance().componentDidUpdate({
+ daySize: DAY_SIZE,
+ numberOfMonths: 3,
+ orientation: HORIZONTAL_ORIENTATION,
+ });
+ expect(adjustDayPickerHeightSpy.callCount).to.equal(1);
+ });
+
+ it('does not call adjustDayPickerHeightSpy if props.numberOfMonths does not change', () => {
+ const wrapper = shallow().dive();
+ wrapper.instance().componentDidUpdate({
+ daySize: DAY_SIZE,
+ numberOfMonths: 2,
+ orientation: HORIZONTAL_ORIENTATION,
+ });
+ expect(adjustDayPickerHeightSpy.called).to.equal(false);
+ });
});
- describe('props.orientation === VERTICAL_ORIENTATION', () => {
+ describe.skip('props.orientation === VERTICAL_ORIENTATION', () => {
it('does not call adjustDayPickerHeight if state.monthTransition is truthy', () => {
const wrapper = mount();
wrapper.setState({
@@ -1075,7 +1093,7 @@ describe('DayPicker', () => {
});
});
- describe('props.orientation === VERTICAL_SCROLLABLE', () => {
+ describe.skip('props.orientation === VERTICAL_SCROLLABLE', () => {
it('does not update transitionContainer ref`s scrollTop currentMonth stays the same', () => {
sinon.spy(DayPicker.prototype, 'setTransitionContainerRef');
const wrapper = mount();
@@ -1097,7 +1115,7 @@ describe('DayPicker', () => {
});
});
- describe('when isFocused is updated to true', () => {
+ describe.skip('when isFocused is updated to true', () => {
const prevProps = { isFocused: false };
const newProps = { isFocused: true };