UNPKG

7.58 kBPlain TextView Raw
1/*
2 * Copyright 2016 Palantir Technologies, Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import { Boundary } from "@blueprintjs/core";
18
19import { DateRange } from "./common/dateRange";
20import { areSameDay } from "./common/dateUtils";
21
22export interface IDateRangeSelectionState {
23 /**
24 * The boundary that would be modified by clicking the provided `day`.
25 */
26 boundary?: Boundary;
27
28 /**
29 * The date range that would be selected after clicking the provided `day`.
30 */
31 dateRange: DateRange;
32}
33
34/* eslint-disable-next-line @typescript-eslint/no-extraneous-class */
35export class DateRangeSelectionStrategy {
36 /**
37 * Returns the new date-range and the boundary that would be affected if `day` were clicked. The
38 * affected boundary may be different from the provided `boundary` in some cases. For example,
39 * clicking a particular boundary's selected date will always deselect it regardless of which
40 * `boundary` you provide to this function (because it's simply a more intuitive interaction).
41 */
42 public static getNextState(
43 currentRange: DateRange,
44 day: Date,
45 allowSingleDayRange: boolean,
46 boundary?: Boundary,
47 ): IDateRangeSelectionState {
48 if (boundary != null) {
49 return this.getNextStateForBoundary(currentRange, day, allowSingleDayRange, boundary);
50 } else {
51 return this.getDefaultNextState(currentRange, day, allowSingleDayRange);
52 }
53 }
54
55 private static getNextStateForBoundary(
56 currentRange: DateRange,
57 day: Date,
58 allowSingleDayRange: boolean,
59 boundary: Boundary,
60 ): IDateRangeSelectionState {
61 const boundaryDate = this.getBoundaryDate(boundary, currentRange);
62 const otherBoundary = this.getOtherBoundary(boundary);
63 const otherBoundaryDate = this.getBoundaryDate(otherBoundary, currentRange);
64
65 let nextBoundary: Boundary;
66 let nextDateRange: DateRange;
67
68 if (boundaryDate == null && otherBoundaryDate == null) {
69 nextBoundary = boundary;
70 nextDateRange = this.createRangeForBoundary(boundary, day, null);
71 } else if (boundaryDate != null && otherBoundaryDate == null) {
72 const nextBoundaryDate = areSameDay(boundaryDate, day) ? null : day;
73 nextBoundary = boundary;
74 nextDateRange = this.createRangeForBoundary(boundary, nextBoundaryDate, null);
75 } else if (boundaryDate == null && otherBoundaryDate != null) {
76 if (areSameDay(day, otherBoundaryDate)) {
77 let nextDate: Date;
78 if (allowSingleDayRange) {
79 nextBoundary = boundary;
80 nextDate = otherBoundaryDate;
81 } else {
82 nextBoundary = otherBoundary;
83 nextDate = null;
84 }
85 nextDateRange = this.createRangeForBoundary(boundary, nextDate, nextDate);
86 } else if (this.isOverlappingOtherBoundary(boundary, day, otherBoundaryDate)) {
87 nextBoundary = otherBoundary;
88 nextDateRange = this.createRangeForBoundary(boundary, otherBoundaryDate, day);
89 } else {
90 nextBoundary = boundary;
91 nextDateRange = this.createRangeForBoundary(boundary, day, otherBoundaryDate);
92 }
93 } else {
94 // both boundaryDate and otherBoundaryDate are already defined
95 if (areSameDay(boundaryDate, day)) {
96 const isSingleDayRangeSelected = areSameDay(boundaryDate, otherBoundaryDate);
97 const nextOtherBoundaryDate = isSingleDayRangeSelected ? null : otherBoundaryDate;
98 nextBoundary = boundary;
99 nextDateRange = this.createRangeForBoundary(boundary, null, nextOtherBoundaryDate);
100 } else if (areSameDay(day, otherBoundaryDate)) {
101 const [nextBoundaryDate, nextOtherBoundaryDate] = allowSingleDayRange
102 ? [otherBoundaryDate, otherBoundaryDate]
103 : [boundaryDate, null];
104 nextBoundary = allowSingleDayRange ? boundary : otherBoundary;
105 nextDateRange = this.createRangeForBoundary(boundary, nextBoundaryDate, nextOtherBoundaryDate);
106 } else if (this.isOverlappingOtherBoundary(boundary, day, otherBoundaryDate)) {
107 nextBoundary = boundary;
108 nextDateRange = this.createRangeForBoundary(boundary, day, null);
109 } else {
110 // extend the date range with an earlier boundaryDate date
111 nextBoundary = boundary;
112 nextDateRange = this.createRangeForBoundary(boundary, day, otherBoundaryDate);
113 }
114 }
115
116 return { dateRange: nextDateRange, boundary: nextBoundary };
117 }
118
119 private static getDefaultNextState(
120 selectedRange: DateRange,
121 day: Date,
122 allowSingleDayRange: boolean,
123 ): IDateRangeSelectionState {
124 const [start, end] = selectedRange;
125
126 let nextDateRange: DateRange;
127
128 if (start == null && end == null) {
129 nextDateRange = [day, null];
130 } else if (start != null && end == null) {
131 nextDateRange = this.createRange(day, start, allowSingleDayRange);
132 } else if (start == null && end != null) {
133 nextDateRange = this.createRange(day, end, allowSingleDayRange);
134 } else {
135 const isStart = areSameDay(start, day);
136 const isEnd = areSameDay(end, day);
137 if (isStart && isEnd) {
138 nextDateRange = [null, null];
139 } else if (isStart) {
140 nextDateRange = [null, end];
141 } else if (isEnd) {
142 nextDateRange = [start, null];
143 } else {
144 nextDateRange = [day, null];
145 }
146 }
147
148 return { dateRange: nextDateRange };
149 }
150
151 private static getOtherBoundary(boundary: Boundary) {
152 return boundary === Boundary.START ? Boundary.END : Boundary.START;
153 }
154
155 private static getBoundaryDate(boundary: Boundary, dateRange: DateRange) {
156 return boundary === Boundary.START ? dateRange[0] : dateRange[1];
157 }
158
159 private static isOverlappingOtherBoundary(boundary: Boundary, boundaryDate: Date, otherBoundaryDate: Date) {
160 return boundary === Boundary.START ? boundaryDate > otherBoundaryDate : boundaryDate < otherBoundaryDate;
161 }
162
163 private static createRangeForBoundary(boundary: Boundary, boundaryDate: Date, otherBoundaryDate: Date) {
164 return boundary === Boundary.START
165 ? ([boundaryDate, otherBoundaryDate] as DateRange)
166 : ([otherBoundaryDate, boundaryDate] as DateRange);
167 }
168
169 private static createRange(a: Date, b: Date, allowSingleDayRange: boolean): DateRange {
170 // clicking the same date again will clear it
171 if (!allowSingleDayRange && areSameDay(a, b)) {
172 return [null, null];
173 }
174 return a < b ? [a, b] : [b, a];
175 }
176}