Skip to content
This repository was archived by the owner on Sep 13, 2021. It is now read-only.

Commit a8dc913

Browse files
committed
Added 'since' and 'until' selection modes
1 parent a44856c commit a8dc913

22 files changed

+13311
-439
lines changed

package-lock.json

+12,566
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"publish": "(cd dist; npm publish)",
1313
"test": "ng test",
1414
"lint": "ng lint",
15+
"lintFix": "prettier --write 'src/**/*.ts' && tslint --fix 'src/**/*.ts'",
1516
"e2e": "ng e2e"
1617
},
1718
"dependencies": {
@@ -25,6 +26,7 @@
2526
"@angular/platform-browser": "7.1.0",
2627
"@angular/platform-browser-dynamic": "7.1.0",
2728
"core-js": "^2.6.5",
29+
"prettier": "^1.17.1",
2830
"rxjs": "^6.4.0",
2931
"zone.js": "0.8.26"
3032
},

saturn-datepicker/src/datepicker/calendar-body.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@ export class SatCalendarBody implements OnChanges {
8888
/** Whether to mark all dates as semi-selected. */
8989
@Input() rangeFull: boolean;
9090

91-
/** Whether to use date range selection behaviour.*/
92-
@Input() rangeMode = false;
91+
/** Selection mode */
92+
@Input() selectionMode = '';
93+
94+
/** Possible selection modes, in the order that they should appear */
95+
@Input() selectionModes = [];
9396

9497
/** The minimum number of free cells needed to fit the label in the first row. */
9598
@Input() labelMinRequiredCells: number;
@@ -167,7 +170,11 @@ export class SatCalendarBody implements OnChanges {
167170

168171
/** Whenever to mark cell as semi-selected (inside dates interval). */
169172
_isSemiSelected(date: number) {
170-
if (!this.rangeMode) {
173+
if (
174+
!['range', 'since', 'until'].includes(this.selectionMode) &&
175+
this.begin === null &&
176+
this.end === null
177+
) {
171178
return false;
172179
}
173180
if (this.rangeFull) {
@@ -188,7 +195,7 @@ export class SatCalendarBody implements OnChanges {
188195

189196
/** Whenever to mark cell as semi-selected before the second date is selected (between the begin cell and the hovered cell). */
190197
_isBetweenOverAndBegin(date: number): boolean {
191-
if (!this._cellOver || !this.rangeMode || !this.beginSelected) {
198+
if (!this._cellOver || !['range', 'since', 'until'].includes(this.selectionMode) || !this.beginSelected) {
192199
return false;
193200
}
194201
if (this.isBeforeSelected && !this.begin) {
@@ -205,7 +212,7 @@ export class SatCalendarBody implements OnChanges {
205212

206213
/** Whenever to mark cell as begin of the range. */
207214
_isBegin(date: number): boolean {
208-
if (this.rangeMode && this.beginSelected && this._cellOver) {
215+
if (['range', 'since', 'until'].includes(this.selectionMode) && this.beginSelected && this._cellOver) {
209216
if (this.isBeforeSelected && !this.begin) {
210217
return this._cellOver === date;
211218
} else {
@@ -218,7 +225,7 @@ export class SatCalendarBody implements OnChanges {
218225

219226
/** Whenever to mark cell as end of the range. */
220227
_isEnd(date: number): boolean {
221-
if (this.rangeMode && this.beginSelected && this._cellOver) {
228+
if (['range', 'since', 'until'].includes(this.selectionMode) && this.beginSelected && this._cellOver) {
222229
if (this.isBeforeSelected && !this.begin) {
223230
return false;
224231
} else {
@@ -234,7 +241,7 @@ export class SatCalendarBody implements OnChanges {
234241
this._ngZone.runOutsideAngular(() => {
235242
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
236243
const activeCell: HTMLElement | null =
237-
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active');
244+
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active');
238245

239246
if (activeCell) {
240247
activeCell.focus();
@@ -245,6 +252,6 @@ export class SatCalendarBody implements OnChanges {
245252

246253
/** Whenever to highlight the target cell when selecting the second date in range mode */
247254
_previewCellOver(date: number): boolean {
248-
return this._cellOver === date && this.rangeMode && this.beginSelected;
255+
return this._cellOver === date && ['range', 'since', 'until'].includes(this.selectionMode) && this.beginSelected;
249256
}
250257
}

saturn-datepicker/src/datepicker/calendar.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

saturn-datepicker/src/datepicker/calendar.html

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
<div class="selection-wrapper" *ngIf="selectionModes && selectionModes.length > 1">
2+
<mat-button-toggle-group
3+
name="fontStyle"
4+
aria-label="Selection type"
5+
[(value)]="selectionMode"
6+
>
7+
<mat-button-toggle *ngFor="let sm of selectionModes;" [value]="sm">
8+
<span class="selection-mode-title">{{ sm }}</span>
9+
</mat-button-toggle>
10+
</mat-button-toggle-group>
11+
</div>
112

213
<ng-template [cdkPortalOutlet]="_calendarHeaderPortal"></ng-template>
314

@@ -8,7 +19,9 @@
819
[selected]="selected"
920
[beginDate]="beginDate"
1021
[endDate]="endDate"
11-
[rangeMode]="rangeMode"
22+
[selectionMode]="selectionMode"
23+
[selectionModes]="selectionModes"
24+
[initialSelectionMode]="initialSelectionMode"
1225
[closeAfterSelection]="closeAfterSelection"
1326
[dateFilter]="dateFilter"
1427
[maxDate]="maxDate"

saturn-datepicker/src/datepicker/calendar.ts

+116-49
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ import {MAT_DATE_FORMATS, MatDateFormats} from '../datetime/date-formats';
4141
* Possible views for the calendar.
4242
* @docs-private
4343
*/
44+
export enum SelectionModeType {
45+
Date = 'date',
46+
Range = 'range',
47+
Since = 'since',
48+
Until = 'until',
49+
50+
Default = Date
51+
};
4452
export type SatCalendarView = 'month' | 'year' | 'multi-year';
4553

4654
/** Default header for SatCalendar */
@@ -204,37 +212,38 @@ export class SatCalendarFooter<D> {
204212
})
205213
export class SatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDestroy, OnChanges {
206214

207-
/** Beginning of date range. */
208-
@Input()
209-
get beginDate(): D | null { return this._beginDate; }
210-
set beginDate(value: D | null) {
211-
this._beginDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
212-
}
213-
private _beginDate: D | null;
214-
215-
/** Date range end. */
216-
@Input()
217-
get endDate(): D | null { return this._endDate; }
218-
set endDate(value: D | null) {
219-
this._endDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
220-
}
221-
private _endDate: D | null;
215+
/** Beginning of date range. */
216+
@Input()
217+
get beginDate() {
218+
return this._beginDate;
219+
}
220+
set beginDate(value) {
221+
this._beginDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
222+
}
223+
private _beginDate;
222224

223-
/** Whenever datepicker is for selecting range of dates. */
224-
@Input() rangeMode = false;
225+
/** Date range end. */
226+
@Input()
227+
get endDate() {
228+
return this._endDate;
229+
}
230+
set endDate(value) {
231+
this._endDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
232+
}
233+
private _endDate;
225234

226-
/** Enables datepicker closing after selection */
227-
@Input() closeAfterSelection = true;
235+
/** Enables datepicker closing after selection */
236+
@Input() closeAfterSelection = true;
228237

229-
/** Emits when new pair of dates selected. */
230-
@Output() dateRangesChange = new EventEmitter<SatDatepickerRangeValue<D>>();
238+
/** Emits when new pair of dates selected. */
239+
@Output() dateRangesChange = new EventEmitter<SatDatepickerRangeValue<D>>();
231240

232241

233-
/** Whenever user already selected start of dates interval. */
234-
beginDateSelected: D | boolean = false;
242+
/** Whenever user already selected start of dates interval. */
243+
beginDateSelected: D | boolean = false;
235244

236-
/** Emits when a new start date has been selected in range mode. */
237-
@Output() beginDateSelectedChange = new EventEmitter<D>();
245+
/** Emits when a new start date has been selected in range mode. */
246+
@Output() beginDateSelectedChange = new EventEmitter<D>();
238247

239248
/** An input indicating the type of the header component, if set. */
240249
@Input() headerComponent: ComponentType<any>;
@@ -259,11 +268,11 @@ export class SatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
259268

260269
/** A date representing the period (month or year) to start the calendar in. */
261270
@Input()
262-
get startAt(): D | null { return this._startAt; }
263-
set startAt(value: D | null) {
271+
get startAt(): D | number | null { return this._startAt; }
272+
set startAt(value: D | number | null) {
264273
this._startAt = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
265274
}
266-
private _startAt: D | null;
275+
private _startAt: D | number | null;
267276

268277
/** Whether the calendar should be started in month or year view. */
269278
@Input() startView: SatCalendarView = 'month';
@@ -301,6 +310,33 @@ export class SatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
301310
/** Order the views when clicking on period label button */
302311
@Input() orderPeriodLabel: 'multi-year' | 'month' = 'multi-year';
303312

313+
/** Initial selectiom mode */
314+
@Input()
315+
get initialSelectionMode(): SelectionModeType { return this._initialSelectionMode; }
316+
set initialSelectionMode(mode: SelectionModeType) {
317+
if (mode) {
318+
this._initialSelectionMode = mode;
319+
}
320+
}
321+
_initialSelectionMode = SelectionModeType.Default;
322+
323+
/** Selection mode */
324+
@Input()
325+
set selectionMode(mode) {
326+
this._selectionMode = mode;
327+
}
328+
get selectionMode() {
329+
if (!this._selectionMode) {
330+
this._selectionMode = this.initialSelectionMode;
331+
}
332+
333+
return this._selectionMode;
334+
}
335+
_selectionMode: SelectionModeType = this.initialSelectionMode;
336+
337+
/** Possible selection modes, in the order that they should appear */
338+
@Input() selectionModes = [];
339+
304340
/** Emits when the currently selected date changes. */
305341
@Output() readonly selectedChange: EventEmitter<D> = new EventEmitter<D>();
306342

@@ -332,13 +368,13 @@ export class SatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
332368
* The current active date. This determines which time period is shown and which date is
333369
* highlighted when using keyboard navigation.
334370
*/
335-
get activeDate(): D { return this._clampedActiveDate; }
336-
set activeDate(value: D) {
371+
get activeDate() { return this._clampedActiveDate; }
372+
set activeDate(value) {
337373
this._clampedActiveDate = this._dateAdapter.clampDate(value, this.minDate, this.maxDate);
338374
this.stateChanges.next();
339375
this._changeDetectorRef.markForCheck();
340376
}
341-
private _clampedActiveDate: D;
377+
private _clampedActiveDate;
342378

343379
/** Whether the calendar is in month view. */
344380
get currentView(): SatCalendarView { return this._currentView; }
@@ -419,29 +455,60 @@ export class SatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
419455
/** Updates today's date after an update of the active date */
420456
updateTodaysDate() {
421457
let view = this.currentView == 'month' ? this.monthView :
422-
(this.currentView == 'year' ? this.yearView : this.multiYearView);
458+
(this.currentView == 'year' ? this.yearView : this.multiYearView);
423459

424460
view.ngAfterContentInit();
425461
}
426462

427463
/** Handles date selection in the month view. */
428-
_dateSelected(date: D): void {
429-
if (this.rangeMode) {
430-
if (!this.beginDateSelected) {
431-
this.beginDateSelected = date;
432-
this.beginDate = date;
433-
this.endDate = date;
434-
this.beginDateSelectedChange.emit(date);
435-
} else {
436-
this.beginDateSelected = false;
437-
if (this._dateAdapter.compareDate(<D>this.beginDate, date) <= 0) {
438-
this.endDate = date;
439-
} else {
440-
this.endDate = this.beginDate;
441-
this.beginDate = date;
442-
}
443-
this.dateRangesChange.emit({begin: <D>this.beginDate, end: this.endDate});
464+
_dateSelected(date): void {
465+
if (this.selectionMode == 'range') {
466+
467+
if (!this.beginDateSelected) {
468+
this.beginDateSelected = date;
469+
this.beginDate = date;
470+
this.endDate = date;
471+
this.beginDateSelectedChange.emit(date);
472+
} else {
473+
/*
474+
Problem: Assign each date from the range accordingly with the user
475+
selection, knowing that each "Date" assignment has to be cloned before,
476+
preventing assigning a variable to a variable's reference.
477+
478+
Compare the two dates selected and if they are different,
479+
clone them into new consts isolated in this iteration.
480+
481+
If they are equal just set both dates to
482+
the "date" variable, as it only exists in this current iteration
483+
484+
Emit the change in the end
485+
*/
486+
487+
this.beginDateSelected = false;
488+
const sumDates = this._dateAdapter.compareDate(this.beginDate, date);
489+
490+
if (sumDates < 0) { // Seconds date is greater than first date
491+
const start = new Date(this.beginDate.getTime());
492+
const end = new Date(date.getTime());
493+
494+
this.beginDate = start;
495+
this.endDate = end;
496+
} else if (sumDates > 0) { // First date is greater than second date
497+
const start = new Date(date.getTime());
498+
const end = new Date(this.beginDate.getTime());
499+
500+
this.beginDate = start;
501+
this.endDate = end;
502+
} else { // Dates are equal
503+
this.beginDate = this.endDate = date;
444504
}
505+
506+
this.dateRangesChange.emit({begin: this.beginDate, end: this.endDate});
507+
}
508+
} else if (this.selectionMode === 'since') {
509+
this.dateRangesChange.emit({begin: date, end: Infinity});
510+
} else if (this.selectionMode === 'until') {
511+
this.dateRangesChange.emit({begin: Infinity, end: date});
445512
} else if (!this._dateAdapter.sameDate(date, this.selected)) {
446513
this.selectedChange.emit(date);
447514
}
@@ -472,7 +539,7 @@ export class SatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
472539
* @returns The given object if it is both a date instance and valid, otherwise null.
473540
*/
474541
private _getValidDateOrNull(obj: any): D | null {
475-
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
542+
return (obj === Infinity || (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj))) ? obj : null;
476543
}
477544

478545
/** Returns the component instance that corresponds to the current calendar view. */

saturn-datepicker/src/datepicker/datepicker-content.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

saturn-datepicker/src/datepicker/datepicker-content.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
(monthSelected)="datepicker._selectMonth($event)"
1717
[beginDate]="datepicker._beginDate"
1818
[endDate]="datepicker._endDate"
19-
[rangeMode]="datepicker.rangeMode"
19+
[selectionMode]="datepicker.selectionMode"
20+
[selectionModes]="datepicker.selectionModes"
21+
[initialSelectionMode]="datepicker.initialSelectionMode"
2022
[closeAfterSelection]="datepicker.closeAfterSelection"
2123
[orderPeriodLabel]="datepicker.orderPeriodLabel"
2224
(dateRangesChange)="datepicker._selectRange($event)"

0 commit comments

Comments
 (0)