1- import { Input , Output , EventEmitter , Directive , Inject , LOCALE_ID } from '@angular/core' ;
1+ import { Input , Output , EventEmitter , Directive , Inject , LOCALE_ID , HostListener } from '@angular/core' ;
22import { WEEKDAYS , Calendar , isDateInRanges , IFormattingOptions , IFormattingViews } from './calendar' ;
33import { ControlValueAccessor } from '@angular/forms' ;
44import { DateRangeDescriptor } from '../core/dates' ;
@@ -115,6 +115,16 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
115115 */
116116 public selectedDates ;
117117
118+ /**
119+ * @hidden
120+ */
121+ public shiftKey : boolean = false ;
122+
123+ /**
124+ * @hidden
125+ */
126+ public lastSelectedDate : Date ;
127+
118128 /**
119129 * @hidden
120130 */
@@ -154,6 +164,11 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
154164 */
155165 protected _onChangeCallback : ( _ : Date ) => void = noop ;
156166
167+ /**
168+ * @hidden
169+ */
170+ protected _deselectDate : boolean ;
171+
157172 /**
158173 * @hidden
159174 */
@@ -174,6 +189,16 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
174189 */
175190 private _viewDate : Date ;
176191
192+ /**
193+ * @hidden
194+ */
195+ private _startDate : Date ;
196+
197+ /**
198+ * @hidden
199+ */
200+ private _endDate : Date ;
201+
177202 /**
178203 * @hidden
179204 */
@@ -462,16 +487,45 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
462487 }
463488
464489 /**
465- * Performs deselection of a single value, when selection is multi
490+ * Multi/Range selection with shift key
491+ *
492+ * @hidden
493+ * @internal
494+ */
495+ @HostListener ( 'pointerdown' , [ '$event' ] )
496+ public onPointerdown ( event : MouseEvent ) {
497+ this . shiftKey = event . button === 0 && event . shiftKey ;
498+ }
499+
500+ /**
501+ * Performs deselection of date/dates, when selection is multi
466502 * Usually performed by the selectMultiple method, but leads to bug when multiple months are in view
467503 *
468504 * @hidden
469505 */
470506 public deselectMultipleInMonth ( value : Date ) {
471- const valueDateOnly = this . getDateOnly ( value ) ;
472- this . selectedDates = this . selectedDates . filter (
473- ( date : Date ) => date . getTime ( ) !== valueDateOnly . getTime ( )
474- ) ;
507+ // deselect multiple dates from last clicked to shift clicked date (excluding)
508+ if ( this . shiftKey ) {
509+ let start : Date ;
510+ let end : Date ;
511+
512+ [ start , end ] = this . lastSelectedDate . getTime ( ) < value . getTime ( )
513+ ? [ this . lastSelectedDate , value ]
514+ : [ value , this . lastSelectedDate ] ;
515+
516+ this . selectedDates = this . selectedDates . filter (
517+ ( date : Date ) => date . getTime ( ) < start . getTime ( ) || date . getTime ( ) > end . getTime ( )
518+ ) ;
519+
520+ this . selectedDates . push ( value ) ;
521+
522+ } else {
523+ // deselect a single date
524+ const valueDateOnly = this . getDateOnly ( value ) ;
525+ this . selectedDates = this . selectedDates . filter (
526+ ( date : Date ) => date . getTime ( ) !== valueDateOnly . getTime ( )
527+ ) ;
528+ }
475529 }
476530
477531 /**
@@ -641,20 +695,55 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
641695
642696 this . selectedDates = Array . from ( new Set ( [ ...newDates , ...selDates ] ) ) . map ( v => new Date ( v ) ) ;
643697 } else {
644- const valueDateOnly = this . getDateOnly ( value ) ;
645- const newSelection = [ ] ;
646- if ( this . selectedDates . every ( ( date : Date ) => date . getTime ( ) !== valueDateOnly . getTime ( ) ) ) {
647- newSelection . push ( valueDateOnly ) ;
698+ let newSelection = [ ] ;
699+
700+ if ( this . shiftKey && this . lastSelectedDate ) {
701+
702+ [ this . _startDate , this . _endDate ] = this . lastSelectedDate . getTime ( ) < value . getTime ( )
703+ ? [ this . lastSelectedDate , value ]
704+ : [ value , this . lastSelectedDate ] ;
705+
706+ const unselectedDates = [ this . _startDate , ...this . generateDateRange ( this . _startDate , this . _endDate ) ]
707+ . filter ( date => ! this . isDateDisabled ( date )
708+ && this . selectedDates . every ( ( d : Date ) => d . getTime ( ) !== date . getTime ( ) )
709+ ) ;
710+
711+ // select all dates from last selected to shift clicked date
712+ if ( this . selectedDates . some ( ( date : Date ) => date . getTime ( ) === this . lastSelectedDate . getTime ( ) )
713+ && unselectedDates . length ) {
714+
715+ newSelection = unselectedDates ;
716+ } else {
717+ // delesect all dates from last clicked to shift clicked date (excluding)
718+ this . selectedDates = this . selectedDates . filter ( ( date : Date ) =>
719+ date . getTime ( ) < this . _startDate . getTime ( ) || date . getTime ( ) > this . _endDate . getTime ( )
720+ ) ;
721+
722+ this . selectedDates . push ( value ) ;
723+ this . _deselectDate = true ;
724+ }
725+
726+ this . _startDate = this . _endDate = undefined ;
727+
728+ } else if ( this . selectedDates . every ( ( date : Date ) => date . getTime ( ) !== value . getTime ( ) ) ) {
729+ newSelection . push ( value ) ;
730+
648731 } else {
649732 this . selectedDates = this . selectedDates . filter (
650- ( date : Date ) => date . getTime ( ) !== valueDateOnly . getTime ( )
733+ ( date : Date ) => date . getTime ( ) !== value . getTime ( )
651734 ) ;
735+
736+ this . _deselectDate = true ;
652737 }
653738
654739 if ( newSelection . length > 0 ) {
655740 this . selectedDates = this . selectedDates . concat ( newSelection ) ;
741+ this . _deselectDate = false ;
656742 }
743+
744+ this . lastSelectedDate = value ;
657745 }
746+
658747 this . selectedDates = this . selectedDates . filter ( d => ! this . isDateDisabled ( d ) ) ;
659748 this . selectedDates . sort ( ( a : Date , b : Date ) => a . valueOf ( ) - b . valueOf ( ) ) ;
660749 this . _onChangeCallback ( this . selectedDates ) ;
@@ -664,19 +753,46 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
664753 * @hidden
665754 */
666755 private selectRange ( value : Date | Date [ ] , excludeDisabledDates : boolean = false ) {
667- let start : Date ;
668- let end : Date ;
669-
670756 if ( Array . isArray ( value ) ) {
671- // this.rangeStarted = false;
672757 value . sort ( ( a : Date , b : Date ) => a . valueOf ( ) - b . valueOf ( ) ) ;
673- start = this . getDateOnly ( value [ 0 ] ) ;
674- end = this . getDateOnly ( value [ value . length - 1 ] ) ;
675- this . selectedDates = [ start , ...this . generateDateRange ( start , end ) ] ;
758+ this . _startDate = this . getDateOnly ( value [ 0 ] ) ;
759+ this . _endDate = this . getDateOnly ( value [ value . length - 1 ] ) ;
676760 } else {
677- if ( ! this . rangeStarted ) {
761+
762+ if ( this . shiftKey && this . lastSelectedDate ) {
763+
764+ if ( this . lastSelectedDate . getTime ( ) === value . getTime ( ) ) {
765+ this . selectedDates = this . selectedDates . length === 1 ? [ ] : [ value ] ;
766+ this . rangeStarted = ! ! this . selectedDates . length ;
767+ this . _onChangeCallback ( this . selectedDates ) ;
768+ return ;
769+ }
770+
771+ // shortens the range when selecting a date inside of it
772+ if ( this . selectedDates . some ( ( date : Date ) => date . getTime ( ) === value . getTime ( ) ) ) {
773+
774+ this . lastSelectedDate . getTime ( ) < value . getTime ( )
775+ ? this . _startDate = value
776+ : this . _endDate = value ;
777+
778+ } else {
779+ // extends the range when selecting a date outside of it
780+ // allows selection from last deselected to current selected date
781+ if ( this . lastSelectedDate . getTime ( ) < value . getTime ( ) ) {
782+ this . _startDate = this . _startDate ?? this . lastSelectedDate ;
783+ this . _endDate = value ;
784+ } else {
785+ this . _startDate = value ;
786+ this . _endDate = this . _endDate ?? this . lastSelectedDate ;
787+ }
788+ }
789+
790+ this . rangeStarted = false ;
791+
792+ } else if ( ! this . rangeStarted ) {
678793 this . rangeStarted = true ;
679794 this . selectedDates = [ value ] ;
795+ this . _startDate = this . _endDate = undefined ;
680796 } else {
681797 this . rangeStarted = false ;
682798
@@ -686,13 +802,16 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
686802 return ;
687803 }
688804
689- this . selectedDates . push ( value ) ;
690- this . selectedDates . sort ( ( a : Date , b : Date ) => a . valueOf ( ) - b . valueOf ( ) ) ;
691-
692- start = this . selectedDates . shift ( ) ;
693- end = this . selectedDates . pop ( ) ;
694- this . selectedDates = [ start , ...this . generateDateRange ( start , end ) ] ;
805+ [ this . _startDate , this . _endDate ] = this . lastSelectedDate . getTime ( ) < value . getTime ( )
806+ ? [ this . lastSelectedDate , value ]
807+ : [ value , this . lastSelectedDate ] ;
695808 }
809+
810+ this . lastSelectedDate = value ;
811+ }
812+
813+ if ( this . _startDate && this . _endDate ) {
814+ this . selectedDates = [ this . _startDate , ...this . generateDateRange ( this . _startDate , this . _endDate ) ] ;
696815 }
697816
698817 if ( excludeDisabledDates ) {
0 commit comments