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' ;
2
2
import { WEEKDAYS , Calendar , isDateInRanges , IFormattingOptions , IFormattingViews } from './calendar' ;
3
3
import { ControlValueAccessor } from '@angular/forms' ;
4
4
import { DateRangeDescriptor } from '../core/dates' ;
@@ -115,6 +115,16 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
115
115
*/
116
116
public selectedDates ;
117
117
118
+ /**
119
+ * @hidden
120
+ */
121
+ public shiftKey : boolean = false ;
122
+
123
+ /**
124
+ * @hidden
125
+ */
126
+ public lastSelectedDate : Date ;
127
+
118
128
/**
119
129
* @hidden
120
130
*/
@@ -154,6 +164,11 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
154
164
*/
155
165
protected _onChangeCallback : ( _ : Date ) => void = noop ;
156
166
167
+ /**
168
+ * @hidden
169
+ */
170
+ protected _deselectDate : boolean ;
171
+
157
172
/**
158
173
* @hidden
159
174
*/
@@ -174,6 +189,16 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
174
189
*/
175
190
private _viewDate : Date ;
176
191
192
+ /**
193
+ * @hidden
194
+ */
195
+ private _startDate : Date ;
196
+
197
+ /**
198
+ * @hidden
199
+ */
200
+ private _endDate : Date ;
201
+
177
202
/**
178
203
* @hidden
179
204
*/
@@ -462,16 +487,45 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
462
487
}
463
488
464
489
/**
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
466
502
* Usually performed by the selectMultiple method, but leads to bug when multiple months are in view
467
503
*
468
504
* @hidden
469
505
*/
470
506
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
+ }
475
529
}
476
530
477
531
/**
@@ -641,20 +695,55 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
641
695
642
696
this . selectedDates = Array . from ( new Set ( [ ...newDates , ...selDates ] ) ) . map ( v => new Date ( v ) ) ;
643
697
} 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
+
648
731
} else {
649
732
this . selectedDates = this . selectedDates . filter (
650
- ( date : Date ) => date . getTime ( ) !== valueDateOnly . getTime ( )
733
+ ( date : Date ) => date . getTime ( ) !== value . getTime ( )
651
734
) ;
735
+
736
+ this . _deselectDate = true ;
652
737
}
653
738
654
739
if ( newSelection . length > 0 ) {
655
740
this . selectedDates = this . selectedDates . concat ( newSelection ) ;
741
+ this . _deselectDate = false ;
656
742
}
743
+
744
+ this . lastSelectedDate = value ;
657
745
}
746
+
658
747
this . selectedDates = this . selectedDates . filter ( d => ! this . isDateDisabled ( d ) ) ;
659
748
this . selectedDates . sort ( ( a : Date , b : Date ) => a . valueOf ( ) - b . valueOf ( ) ) ;
660
749
this . _onChangeCallback ( this . selectedDates ) ;
@@ -664,19 +753,46 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
664
753
* @hidden
665
754
*/
666
755
private selectRange ( value : Date | Date [ ] , excludeDisabledDates : boolean = false ) {
667
- let start : Date ;
668
- let end : Date ;
669
-
670
756
if ( Array . isArray ( value ) ) {
671
- // this.rangeStarted = false;
672
757
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 ] ) ;
676
760
} 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 ) {
678
793
this . rangeStarted = true ;
679
794
this . selectedDates = [ value ] ;
795
+ this . _startDate = this . _endDate = undefined ;
680
796
} else {
681
797
this . rangeStarted = false ;
682
798
@@ -686,13 +802,16 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
686
802
return ;
687
803
}
688
804
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 ] ;
695
808
}
809
+
810
+ this . lastSelectedDate = value ;
811
+ }
812
+
813
+ if ( this . _startDate && this . _endDate ) {
814
+ this . selectedDates = [ this . _startDate , ...this . generateDateRange ( this . _startDate , this . _endDate ) ] ;
696
815
}
697
816
698
817
if ( excludeDisabledDates ) {
0 commit comments