@@ -56,7 +56,10 @@ import {
5656} from './utils/parse' ;
5757import {
5858 getCalendarDayState ,
59- isDayDisabled
59+ isDayDisabled ,
60+ isMonthDisabled ,
61+ isNextMonthDisabled ,
62+ isPrevMonthDisabled
6063} from './utils/state' ;
6164
6265/**
@@ -714,6 +717,15 @@ export class Datetime implements ComponentInterface {
714717 return ;
715718 }
716719
720+ const { month, year, day } = refMonthFn ( this . workingParts ) ;
721+
722+ if ( isMonthDisabled ( { month, year, day : null } , {
723+ minParts : this . minParts ,
724+ maxParts : this . maxParts
725+ } ) ) {
726+ return ;
727+ }
728+
717729 /**
718730 * On iOS, we need to set pointer-events: none
719731 * when the user is almost done with the gesture
@@ -724,7 +736,8 @@ export class Datetime implements ComponentInterface {
724736 */
725737 if ( mode === 'ios' ) {
726738 const ratio = ev . intersectionRatio ;
727- const shouldDisable = Math . abs ( ratio - 0.7 ) <= 0.1 ;
739+ // `maxTouchPoints` will be 1 in device preview, but > 1 on device
740+ const shouldDisable = Math . abs ( ratio - 0.7 ) <= 0.1 && navigator . maxTouchPoints > 1 ;
728741
729742 if ( shouldDisable ) {
730743 calendarBodyRef . style . setProperty ( 'pointer-events' , 'none' ) ;
@@ -757,7 +770,6 @@ export class Datetime implements ComponentInterface {
757770 * if we did not do this.
758771 */
759772 writeTask ( ( ) => {
760- const { month, year, day } = refMonthFn ( this . workingParts ) ;
761773
762774 this . setWorkingParts ( {
763775 ...this . workingParts ,
@@ -766,9 +778,11 @@ export class Datetime implements ComponentInterface {
766778 year
767779 } ) ;
768780
769- calendarBodyRef . scrollLeft = workingMonth . clientWidth * ( isRTL ( this . el ) ? - 1 : 1 ) ;
770- calendarBodyRef . style . removeProperty ( 'overflow' ) ;
771- calendarBodyRef . style . removeProperty ( 'pointer-events' ) ;
781+ raf ( ( ) => {
782+ calendarBodyRef . scrollLeft = workingMonth . clientWidth * ( isRTL ( this . el ) ? - 1 : 1 ) ;
783+ calendarBodyRef . style . removeProperty ( 'overflow' ) ;
784+ calendarBodyRef . style . removeProperty ( 'pointer-events' ) ;
785+ } ) ;
772786
773787 /**
774788 * Now that state has been updated
@@ -781,6 +795,12 @@ export class Datetime implements ComponentInterface {
781795 } ) ;
782796 }
783797
798+ const threshold = mode === 'ios' &&
799+ // tslint:disable-next-line
800+ typeof navigator !== 'undefined' &&
801+ navigator . maxTouchPoints > 1 ?
802+ [ 0.7 , 1 ] : 1 ;
803+
784804 /**
785805 * Listen on the first month to
786806 * prepend a new month and on the last
@@ -800,13 +820,13 @@ export class Datetime implements ComponentInterface {
800820 * something WebKit does.
801821 */
802822 endIO = new IntersectionObserver ( ev => ioCallback ( 'end' , ev ) , {
803- threshold : mode === 'ios' ? [ 0.7 , 1 ] : 1 ,
823+ threshold,
804824 root : calendarBodyRef
805825 } ) ;
806826 endIO . observe ( endMonth ) ;
807827
808828 startIO = new IntersectionObserver ( ev => ioCallback ( 'start' , ev ) , {
809- threshold : mode === 'ios' ? [ 0.7 , 1 ] : 1 ,
829+ threshold,
810830 root : calendarBodyRef
811831 } ) ;
812832 startIO . observe ( startMonth ) ;
@@ -963,9 +983,9 @@ export class Datetime implements ComponentInterface {
963983 }
964984
965985 componentWillLoad ( ) {
966- this . processValue ( this . value ) ;
967986 this . processMinParts ( ) ;
968987 this . processMaxParts ( ) ;
988+ this . processValue ( this . value ) ;
969989 this . parsedHourValues = convertToArrayOfNumbers ( this . hourValues ) ;
970990 this . parsedMinuteValues = convertToArrayOfNumbers ( this . minuteValues ) ;
971991 this . parsedMonthValues = convertToArrayOfNumbers ( this . monthValues ) ;
@@ -1091,6 +1111,13 @@ export class Datetime implements ComponentInterface {
10911111 items = { months }
10921112 value = { workingParts . month }
10931113 onIonChange = { ( ev : CustomEvent ) => {
1114+ // Due to a Safari 14 issue we need to destroy
1115+ // the intersection observer before we update state
1116+ // and trigger a re-render.
1117+ if ( this . destroyCalendarIO ) {
1118+ this . destroyCalendarIO ( ) ;
1119+ }
1120+
10941121 this . setWorkingParts ( {
10951122 ...this . workingParts ,
10961123 month : ev . detail . value
@@ -1103,6 +1130,10 @@ export class Datetime implements ComponentInterface {
11031130 } ) ;
11041131 }
11051132
1133+ // We can re-attach the intersection observer after
1134+ // the working parts have been updated.
1135+ this . initializeCalendarIOListeners ( ) ;
1136+
11061137 ev . stopPropagation ( ) ;
11071138 } }
11081139 > </ ion-picker-column-internal >
@@ -1114,6 +1145,13 @@ export class Datetime implements ComponentInterface {
11141145 items = { years }
11151146 value = { workingParts . year }
11161147 onIonChange = { ( ev : CustomEvent ) => {
1148+ // Due to a Safari 14 issue we need to destroy
1149+ // the intersection observer before we update state
1150+ // and trigger a re-render.
1151+ if ( this . destroyCalendarIO ) {
1152+ this . destroyCalendarIO ( ) ;
1153+ }
1154+
11171155 this . setWorkingParts ( {
11181156 ...this . workingParts ,
11191157 year : ev . detail . value
@@ -1126,6 +1164,10 @@ export class Datetime implements ComponentInterface {
11261164 } ) ;
11271165 }
11281166
1167+ // We can re-attach the intersection observer after
1168+ // the working parts have been updated.
1169+ this . initializeCalendarIOListeners ( ) ;
1170+
11291171 ev . stopPropagation ( ) ;
11301172 } }
11311173 > </ ion-picker-column-internal >
@@ -1139,6 +1181,10 @@ export class Datetime implements ComponentInterface {
11391181 private renderCalendarHeader ( mode : Mode ) {
11401182 const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp ;
11411183 const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp ;
1184+
1185+ const prevMonthDisabled = isPrevMonthDisabled ( this . workingParts , this . minParts , this . maxParts ) ;
1186+ const nextMonthDisabled = isNextMonthDisabled ( this . workingParts , this . maxParts ) ;
1187+
11421188 return (
11431189 < div class = "calendar-header" >
11441190 < div class = "calendar-action-buttons" >
@@ -1152,10 +1198,14 @@ export class Datetime implements ComponentInterface {
11521198
11531199 < div class = "calendar-next-prev" >
11541200 < ion-buttons >
1155- < ion-button onClick = { ( ) => this . prevMonth ( ) } >
1201+ < ion-button
1202+ disabled = { prevMonthDisabled }
1203+ onClick = { ( ) => this . prevMonth ( ) } >
11561204 < ion-icon slot = "icon-only" icon = { chevronBack } lazy = { false } flipRtl > </ ion-icon >
11571205 </ ion-button >
1158- < ion-button onClick = { ( ) => this . nextMonth ( ) } >
1206+ < ion-button
1207+ disabled = { nextMonthDisabled }
1208+ onClick = { ( ) => this . nextMonth ( ) } >
11591209 < ion-icon slot = "icon-only" icon = { chevronForward } lazy = { false } flipRtl > </ ion-icon >
11601210 </ ion-button >
11611211 </ ion-buttons >
@@ -1173,9 +1223,26 @@ export class Datetime implements ComponentInterface {
11731223 private renderMonth ( month : number , year : number ) {
11741224 const yearAllowed = this . parsedYearValues === undefined || this . parsedYearValues . includes ( year ) ;
11751225 const monthAllowed = this . parsedMonthValues === undefined || this . parsedMonthValues . includes ( month ) ;
1176- const isMonthDisabled = ! yearAllowed || ! monthAllowed ;
1226+ const isCalMonthDisabled = ! yearAllowed || ! monthAllowed ;
1227+ const swipeDisabled = isMonthDisabled ( {
1228+ month,
1229+ year,
1230+ day : null
1231+ } , {
1232+ minParts : this . minParts ,
1233+ maxParts : this . maxParts
1234+ } ) ;
1235+ // The working month should never have swipe disabled.
1236+ // Otherwise the CSS scroll snap will not work and the user
1237+ // can free-scroll the calendar.
1238+ const isWorkingMonth = this . workingParts . month === month && this . workingParts . year === year ;
1239+
11771240 return (
1178- < div class = "calendar-month" >
1241+ < div class = { {
1242+ 'calendar-month' : true ,
1243+ // Prevents scroll snap swipe gestures for months outside of the min/max bounds
1244+ 'calendar-month-disabled' : ! isWorkingMonth && swipeDisabled
1245+ } } >
11791246 < div class = "calendar-month-grid" >
11801247 { getDaysOfMonth ( month , year , this . firstDayOfWeek % 7 ) . map ( ( dateObject , index ) => {
11811248 const { day, dayOfWeek } = dateObject ;
@@ -1190,7 +1257,7 @@ export class Datetime implements ComponentInterface {
11901257 data-year = { year }
11911258 data-index = { index }
11921259 data-day-of-week = { dayOfWeek }
1193- disabled = { isMonthDisabled || disabled }
1260+ disabled = { isCalMonthDisabled || disabled }
11941261 class = { {
11951262 'calendar-day-padding' : day === null ,
11961263 'calendar-day' : true ,
0 commit comments