From b735b587cda777ac481bb580c883d9734145f31e Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 1 Jul 2021 09:22:59 -0400 Subject: [PATCH] fix(datetime): scroll position no longer gets reset when using datetime in overlay (#23543) --- core/src/components/datetime/datetime.tsx | 90 ++++++++++++++++--- .../components/datetime/test/cover/index.html | 69 ++++++++++++++ 2 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 core/src/components/datetime/test/cover/index.html diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 727eead221..2e28491f3a 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -87,6 +87,11 @@ export class Datetime implements ComponentInterface { private parsedYearValues?: number[]; private parsedDayValues?: number[]; + private destroyCalendarIO?: () => void; + private destroyKeyboardMO?: () => void; + private destroyTimeScroll?: () => void; + private destroyMonthAndYearScroll?: () => void; + private minParts?: any; private maxParts?: any; @@ -441,6 +446,10 @@ export class Datetime implements ComponentInterface { const mo = new MutationObserver(checkCalendarBodyFocus); mo.observe(calendarBodyRef, { attributeFilter: ['class'], attributeOldValue: true }); + this.destroyKeyboardMO = () => { + mo?.disconnect(); + } + /** * We must use keydown not keyup as we want * to prevent scrolling when using the arrow keys. @@ -729,6 +738,11 @@ export class Datetime implements ComponentInterface { root: calendarBodyRef }); startIO.observe(startMonth); + + this.destroyCalendarIO = () => { + endIO?.disconnect(); + startIO?.disconnect(); + } }); } @@ -743,6 +757,31 @@ export class Datetime implements ComponentInterface { } } + /** + * Clean up all listeners except for the overlay + * listener. This is so that we can re-create the listeners + * if the datetime has been hidden/presented by a modal or popover. + */ + private destroyListeners = () => { + const { destroyCalendarIO, destroyKeyboardMO, destroyTimeScroll, destroyMonthAndYearScroll } = this; + + if (destroyCalendarIO !== undefined) { + destroyCalendarIO(); + } + + if (destroyKeyboardMO !== undefined) { + destroyKeyboardMO(); + } + + if (destroyTimeScroll !== undefined) { + destroyTimeScroll(); + } + + if (destroyMonthAndYearScroll !== undefined) { + destroyMonthAndYearScroll(); + } + } + componentDidLoad() { const mode = getIonMode(this); @@ -758,11 +797,6 @@ export class Datetime implements ComponentInterface { const ev = entries[0]; if (!ev.isIntersecting) { return; } - /** - * This needs to run at most once for initial setup. - */ - visibleIO!.disconnect() - this.initializeCalendarIOListeners(); this.initializeKeyboardListeners(); this.initializeTimeScrollListener(); @@ -786,6 +820,27 @@ export class Datetime implements ComponentInterface { } visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01 }); visibleIO.observe(this.el); + + /** + * We need to clean up listeners when the datetime is hidden + * in a popover/modal so that we can properly scroll containers + * back into view if they are re-presented. When the datetime is hidden + * the scroll areas have scroll widths/heights of 0px, so any snapping + * we did originally has been lost. + */ + let hiddenIO: IntersectionObserver | undefined; + const hiddenCallback = (entries: IntersectionObserverEntry[]) => { + const ev = entries[0]; + if (ev.isIntersecting) { return; } + + this.destroyListeners(); + + writeTask(() => { + this.el.classList.remove('datetime-ready'); + }); + } + hiddenIO = new IntersectionObserver(hiddenCallback, { threshold: 0 }); + hiddenIO.observe(this.el); } /** @@ -897,8 +952,15 @@ export class Datetime implements ComponentInterface { * does not fire when we do our initial scrollIntoView above. */ raf(() => { - monthRef.addEventListener('scroll', () => scrollCallback('month')); - yearRef.addEventListener('scroll', () => scrollCallback('year')); + const monthScroll = () => scrollCallback('month'); + const yearScroll = () => scrollCallback('year'); + monthRef.addEventListener('scroll', monthScroll); + yearRef.addEventListener('scroll', yearScroll); + + this.destroyMonthAndYearScroll = () => { + monthRef.removeEventListener('scroll', monthScroll); + yearRef.removeEventListener('scroll', yearScroll); + } }); } @@ -979,8 +1041,16 @@ export class Datetime implements ComponentInterface { * does not fire when we do our initial scrollIntoView above. */ raf(() => { - timeHourRef.addEventListener('scroll', () => scrollCallback('hour')); - timeMinuteRef.addEventListener('scroll', () => scrollCallback('minute')); + const hourScroll = () => scrollCallback('hour'); + const minuteScroll = () => scrollCallback('minute'); + + timeHourRef.addEventListener('scroll', hourScroll); + timeMinuteRef.addEventListener('scroll', minuteScroll); + + this.destroyTimeScroll = () => { + timeHourRef.removeEventListener('scroll', hourScroll); + timeMinuteRef.removeEventListener('scroll', minuteScroll); + } }); }); } @@ -1076,7 +1146,7 @@ export class Datetime implements ComponentInterface { this.cancel(true)}>{this.cancelText} - this.confirm()}>{this.doneText} + this.confirm(true)}>{this.doneText} diff --git a/core/src/components/datetime/test/cover/index.html b/core/src/components/datetime/test/cover/index.html new file mode 100644 index 0000000000..f0a00dbbc4 --- /dev/null +++ b/core/src/components/datetime/test/cover/index.html @@ -0,0 +1,69 @@ + + + + + Datetime - Cover + + + + + + + + + + + + Datetime - Cover + + + + + Birthday + Select a date + + + + + + + + + + +