mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
fix(datetime): scroll position no longer gets reset when using datetime in overlay (#23543)
This commit is contained in:
@ -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 {
|
||||
<slot name="buttons">
|
||||
<ion-buttons>
|
||||
<ion-button color={this.color} onClick={() => this.cancel(true)}>{this.cancelText}</ion-button>
|
||||
<ion-button color={this.color} onClick={() => this.confirm()}>{this.doneText}</ion-button>
|
||||
<ion-button color={this.color} onClick={() => this.confirm(true)}>{this.doneText}</ion-button>
|
||||
</ion-buttons>
|
||||
</slot>
|
||||
</div>
|
||||
|
69
core/src/components/datetime/test/cover/index.html
Normal file
69
core/src/components/datetime/test/cover/index.html
Normal file
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Datetime - Cover</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(250px, 1fr));
|
||||
grid-gap: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
|
||||
color: #6f7378;
|
||||
|
||||
margin-top: 10px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
ion-modal {
|
||||
--width: 350px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #777;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Datetime - Cover</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item button="true" id="datetime-trigger">
|
||||
<ion-label>Birthday</ion-label>
|
||||
<ion-text class="placeholder">Select a date</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<ion-modal trigger="datetime-trigger">
|
||||
<ion-datetime
|
||||
show-default-title="true"
|
||||
show-default-buttons="true"
|
||||
></ion-datetime>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
|
||||
<script>
|
||||
const datetime = document.querySelector('ion-datetime');
|
||||
const text = document.querySelector('ion-text');
|
||||
datetime.addEventListener('ionChange', (ev) => {
|
||||
const date = new Date(ev.detail.value);
|
||||
text.innerText = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format(date);
|
||||
text.classList.remove('placeholder');
|
||||
});
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user