diff --git a/core/src/components/datetime/datetime.scss b/core/src/components/datetime/datetime.scss index c9af0f8024..ce8438b6db 100644 --- a/core/src/components/datetime/datetime.scss +++ b/core/src/components/datetime/datetime.scss @@ -241,6 +241,13 @@ width: 100%; } +:host .calendar-body .calendar-month-disabled { + /** + * Disables swipe gesture snapping for scroll-snap containers + */ + scroll-snap-align: none; +} + /** * Hide scrollbars on Chrome and Safari */ diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 8d63783d16..0ddfbe3eb4 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -56,7 +56,10 @@ import { } from './utils/parse'; import { getCalendarDayState, - isDayDisabled + isDayDisabled, + isMonthDisabled, + isNextMonthDisabled, + isPrevMonthDisabled } from './utils/state'; /** @@ -714,6 +717,15 @@ export class Datetime implements ComponentInterface { return; } + const { month, year, day } = refMonthFn(this.workingParts); + + if (isMonthDisabled({ month, year, day: null }, { + minParts: this.minParts, + maxParts: this.maxParts + })) { + return; + } + /** * On iOS, we need to set pointer-events: none * when the user is almost done with the gesture @@ -724,7 +736,8 @@ export class Datetime implements ComponentInterface { */ if (mode === 'ios') { const ratio = ev.intersectionRatio; - const shouldDisable = Math.abs(ratio - 0.7) <= 0.1; + // `maxTouchPoints` will be 1 in device preview, but > 1 on device + const shouldDisable = Math.abs(ratio - 0.7) <= 0.1 && navigator.maxTouchPoints > 1; if (shouldDisable) { calendarBodyRef.style.setProperty('pointer-events', 'none'); @@ -757,7 +770,6 @@ export class Datetime implements ComponentInterface { * if we did not do this. */ writeTask(() => { - const { month, year, day } = refMonthFn(this.workingParts); this.setWorkingParts({ ...this.workingParts, @@ -766,9 +778,11 @@ export class Datetime implements ComponentInterface { year }); - calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1); - calendarBodyRef.style.removeProperty('overflow'); - calendarBodyRef.style.removeProperty('pointer-events'); + raf(() => { + calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1); + calendarBodyRef.style.removeProperty('overflow'); + calendarBodyRef.style.removeProperty('pointer-events'); + }); /** * Now that state has been updated @@ -781,6 +795,12 @@ export class Datetime implements ComponentInterface { }); } + const threshold = mode === 'ios' && + // tslint:disable-next-line + typeof navigator !== 'undefined' && + navigator.maxTouchPoints > 1 ? + [0.7, 1] : 1; + /** * Listen on the first month to * prepend a new month and on the last @@ -800,13 +820,13 @@ export class Datetime implements ComponentInterface { * something WebKit does. */ endIO = new IntersectionObserver(ev => ioCallback('end', ev), { - threshold: mode === 'ios' ? [0.7, 1] : 1, + threshold, root: calendarBodyRef }); endIO.observe(endMonth); startIO = new IntersectionObserver(ev => ioCallback('start', ev), { - threshold: mode === 'ios' ? [0.7, 1] : 1, + threshold, root: calendarBodyRef }); startIO.observe(startMonth); @@ -963,9 +983,9 @@ export class Datetime implements ComponentInterface { } componentWillLoad() { - this.processValue(this.value); this.processMinParts(); this.processMaxParts(); + this.processValue(this.value); this.parsedHourValues = convertToArrayOfNumbers(this.hourValues); this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues); this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues); @@ -1091,6 +1111,13 @@ export class Datetime implements ComponentInterface { items={months} value={workingParts.month} onIonChange={(ev: CustomEvent) => { + // Due to a Safari 14 issue we need to destroy + // the intersection observer before we update state + // and trigger a re-render. + if (this.destroyCalendarIO) { + this.destroyCalendarIO(); + } + this.setWorkingParts({ ...this.workingParts, month: ev.detail.value @@ -1103,6 +1130,10 @@ export class Datetime implements ComponentInterface { }); } + // We can re-attach the intersection observer after + // the working parts have been updated. + this.initializeCalendarIOListeners(); + ev.stopPropagation(); }} > @@ -1114,6 +1145,13 @@ export class Datetime implements ComponentInterface { items={years} value={workingParts.year} onIonChange={(ev: CustomEvent) => { + // Due to a Safari 14 issue we need to destroy + // the intersection observer before we update state + // and trigger a re-render. + if (this.destroyCalendarIO) { + this.destroyCalendarIO(); + } + this.setWorkingParts({ ...this.workingParts, year: ev.detail.value @@ -1126,6 +1164,10 @@ export class Datetime implements ComponentInterface { }); } + // We can re-attach the intersection observer after + // the working parts have been updated. + this.initializeCalendarIOListeners(); + ev.stopPropagation(); }} > @@ -1139,6 +1181,10 @@ export class Datetime implements ComponentInterface { private renderCalendarHeader(mode: Mode) { const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp; const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp; + + const prevMonthDisabled = isPrevMonthDisabled(this.workingParts, this.minParts, this.maxParts); + const nextMonthDisabled = isNextMonthDisabled(this.workingParts, this.maxParts); + return (