Skip to content
This repository was archived by the owner on May 19, 2025. It is now read-only.

Commit 17a7900

Browse files
authored
Merge pull request #492 from strada/master
Update range selection behavior
2 parents 66b385a + 67aee9f commit 17a7900

File tree

4 files changed

+108
-9
lines changed

4 files changed

+108
-9
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ scroll | Object | { enabled: false }| infinite
113113
showMonthArrow | Boolean | true | show/hide month arrow button
114114
navigatorRenderer | Func | | renderer for focused date navigation area. fn(currentFocusedDate: Date, changeShownDate: func, props: object)
115115
ranges | *Object[] | [] | Defines ranges. array of range object
116-
moveRangeOnFirstSelection(DateRange) | Boolean | false | move range on startDate selection. Otherwise endDate will replace with startDate.
116+
moveRangeOnFirstSelection(DateRange) | Boolean | false | move range on startDate selection. Otherwise endDate will replace with startDate unless `retainEndDateOnFirstSelection` is set to true.
117+
retainEndDateOnFirstSelection(DateRange) | Boolean | false | Retain end date when the start date is changed, unless start date is later than end date. Ignored if `moveRangeOnFirstSelection` is set to true.
117118
onChange(Calendar) | Func | | callback function for date changes. fn(date: Date)
118119
onChange(DateRange) | Func | | callback function for range changes. fn(changes). changes contains changed ranges with new `startDate`/`endDate` properties.
119120
color(Calendar) | String | `#3d91ff` | defines color for selected date in Calendar

src/components/DateRange/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ This component extends all the props of **[Calendar](#calendar)** component. In
33
| Prop Name | Type |
44
|---|---|
55
| **moveRangeOnFirstSelection** | boolean |
6-
| **onRangeFocusChange** | function |
7-
| **rangeColors** | array |
8-
| **ranges** | array |
6+
| **retainEndDateOnFirstSelection** | boolean |
7+
| **onRangeFocusChange** | function |
8+
| **rangeColors** | array |
9+
| **ranges** | array |
910

1011

1112
#### Example: Editable Date Inputs

src/components/DateRange/index.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,40 @@ class DateRange extends Component {
1818
}
1919
calcNewSelection = (value, isSingleValue = true) => {
2020
const focusedRange = this.props.focusedRange || this.state.focusedRange;
21-
const { ranges, onChange, maxDate, moveRangeOnFirstSelection, disabledDates } = this.props;
21+
const {
22+
ranges,
23+
onChange,
24+
maxDate,
25+
moveRangeOnFirstSelection,
26+
retainEndDateOnFirstSelection,
27+
disabledDates,
28+
} = this.props;
2229
const focusedRangeIndex = focusedRange[0];
2330
const selectedRange = ranges[focusedRangeIndex];
2431
if (!selectedRange || !onChange) return {};
25-
2632
let { startDate, endDate } = selectedRange;
27-
if (!endDate) endDate = new Date(startDate);
33+
const now = new Date();
2834
let nextFocusRange;
2935
if (!isSingleValue) {
3036
startDate = value.startDate;
3137
endDate = value.endDate;
3238
} else if (focusedRange[1] === 0) {
3339
// startDate selection
34-
const dayOffset = differenceInCalendarDays(endDate, startDate);
40+
const dayOffset = differenceInCalendarDays(endDate || now, startDate);
41+
const calculateEndDate = () => {
42+
if (moveRangeOnFirstSelection) {
43+
return addDays(value, dayOffset);
44+
}
45+
if (retainEndDateOnFirstSelection) {
46+
if (!endDate || isBefore(value, endDate)) {
47+
return endDate;
48+
}
49+
return value;
50+
}
51+
return value || now;
52+
};
3553
startDate = value;
36-
endDate = moveRangeOnFirstSelection ? addDays(value, dayOffset) : value;
54+
endDate = calculateEndDate();
3755
if (maxDate) endDate = min([endDate, maxDate]);
3856
nextFocusRange = [focusedRange[0], 1];
3957
} else {
@@ -131,6 +149,7 @@ DateRange.defaultProps = {
131149
classNames: {},
132150
ranges: [],
133151
moveRangeOnFirstSelection: false,
152+
retainEndDateOnFirstSelection: false,
134153
rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'],
135154
disabledDates: [],
136155
};
@@ -142,6 +161,7 @@ DateRange.propTypes = {
142161
className: PropTypes.string,
143162
ranges: PropTypes.arrayOf(rangeShape),
144163
moveRangeOnFirstSelection: PropTypes.bool,
164+
retainEndDateOnFirstSelection: PropTypes.bool,
145165
};
146166

147167
export default DateRange;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,84 @@
1+
import React from 'react';
2+
import { subDays, addDays, isSameDay } from 'date-fns';
13
import DateRange from '../DateRange';
4+
import renderer from 'react-test-renderer';
5+
6+
let testRenderer = null;
7+
let instance = null;
8+
const endDate = new Date();
9+
const startDate = subDays(endDate, 7);
10+
11+
const commonProps = {
12+
ranges: [{ startDate, endDate, key: 'selection' }],
13+
onChange: () => {},
14+
moveRangeOnFirstSelection: false,
15+
};
16+
17+
const compareRanges = (newRange, assertionRange) => {
18+
['startDate', 'endDate'].forEach(key => {
19+
if (!newRange[key] || !assertionRange[key]) {
20+
return expect(newRange[key]).toEqual(assertionRange[key]);
21+
}
22+
return expect(isSameDay(newRange[key], assertionRange[key])).toEqual(true);
23+
});
24+
};
25+
26+
beforeEach(() => {
27+
testRenderer = renderer.create(<DateRange {...commonProps} />);
28+
instance = testRenderer.getInstance();
29+
});
230

331
describe('DateRange', () => {
432
test('Should resolve', () => {
533
expect(DateRange).toEqual(expect.anything());
634
});
35+
36+
test('calculate new selection by resetting end date', () => {
37+
const methodResult = instance.calcNewSelection(subDays(endDate, 10), true);
38+
compareRanges(methodResult.range, {
39+
startDate: subDays(endDate, 10),
40+
endDate: subDays(endDate, 10),
41+
});
42+
});
43+
44+
test('calculate new selection by resetting end date if start date is not before', () => {
45+
const methodResult = instance.calcNewSelection(addDays(endDate, 2), true);
46+
compareRanges(methodResult.range, {
47+
startDate: addDays(endDate, 2),
48+
endDate: addDays(endDate, 2),
49+
});
50+
});
51+
52+
test('calculate new selection based on moveRangeOnFirstSelection prop', () => {
53+
testRenderer.update(<DateRange {...commonProps} moveRangeOnFirstSelection />);
54+
const methodResult = instance.calcNewSelection(subDays(endDate, 10), true);
55+
compareRanges(methodResult.range, {
56+
startDate: subDays(endDate, 10),
57+
endDate: subDays(endDate, 3),
58+
});
59+
});
60+
61+
test('calculate new selection by retaining end date, based on retainEndDateOnFirstSelection prop', () => {
62+
testRenderer.update(<DateRange {...commonProps} retainEndDateOnFirstSelection />);
63+
const methodResult = instance.calcNewSelection(subDays(endDate, 10), true);
64+
compareRanges(methodResult.range, {
65+
startDate: subDays(endDate, 10),
66+
endDate,
67+
});
68+
});
69+
70+
test('calculate new selection by retaining the unset end date, based on retainEndDateOnFirstSelection prop', () => {
71+
testRenderer.update(
72+
<DateRange
73+
{...commonProps}
74+
ranges={[{ ...commonProps.ranges[0], endDate: null }]}
75+
retainEndDateOnFirstSelection
76+
/>
77+
);
78+
const methodResult = instance.calcNewSelection(subDays(endDate, 10), true);
79+
compareRanges(methodResult.range, {
80+
startDate: subDays(endDate, 10),
81+
endDate: null,
82+
});
83+
});
784
});

0 commit comments

Comments
 (0)