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 parsedYearValues?: number[];
|
||||||
private parsedDayValues?: number[];
|
private parsedDayValues?: number[];
|
||||||
|
|
||||||
|
private destroyCalendarIO?: () => void;
|
||||||
|
private destroyKeyboardMO?: () => void;
|
||||||
|
private destroyTimeScroll?: () => void;
|
||||||
|
private destroyMonthAndYearScroll?: () => void;
|
||||||
|
|
||||||
private minParts?: any;
|
private minParts?: any;
|
||||||
private maxParts?: any;
|
private maxParts?: any;
|
||||||
|
|
||||||
@ -441,6 +446,10 @@ export class Datetime implements ComponentInterface {
|
|||||||
const mo = new MutationObserver(checkCalendarBodyFocus);
|
const mo = new MutationObserver(checkCalendarBodyFocus);
|
||||||
mo.observe(calendarBodyRef, { attributeFilter: ['class'], attributeOldValue: true });
|
mo.observe(calendarBodyRef, { attributeFilter: ['class'], attributeOldValue: true });
|
||||||
|
|
||||||
|
this.destroyKeyboardMO = () => {
|
||||||
|
mo?.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We must use keydown not keyup as we want
|
* We must use keydown not keyup as we want
|
||||||
* to prevent scrolling when using the arrow keys.
|
* to prevent scrolling when using the arrow keys.
|
||||||
@ -729,6 +738,11 @@ export class Datetime implements ComponentInterface {
|
|||||||
root: calendarBodyRef
|
root: calendarBodyRef
|
||||||
});
|
});
|
||||||
startIO.observe(startMonth);
|
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() {
|
componentDidLoad() {
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
|
|
||||||
@ -758,11 +797,6 @@ export class Datetime implements ComponentInterface {
|
|||||||
const ev = entries[0];
|
const ev = entries[0];
|
||||||
if (!ev.isIntersecting) { return; }
|
if (!ev.isIntersecting) { return; }
|
||||||
|
|
||||||
/**
|
|
||||||
* This needs to run at most once for initial setup.
|
|
||||||
*/
|
|
||||||
visibleIO!.disconnect()
|
|
||||||
|
|
||||||
this.initializeCalendarIOListeners();
|
this.initializeCalendarIOListeners();
|
||||||
this.initializeKeyboardListeners();
|
this.initializeKeyboardListeners();
|
||||||
this.initializeTimeScrollListener();
|
this.initializeTimeScrollListener();
|
||||||
@ -786,6 +820,27 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01 });
|
visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01 });
|
||||||
visibleIO.observe(this.el);
|
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.
|
* does not fire when we do our initial scrollIntoView above.
|
||||||
*/
|
*/
|
||||||
raf(() => {
|
raf(() => {
|
||||||
monthRef.addEventListener('scroll', () => scrollCallback('month'));
|
const monthScroll = () => scrollCallback('month');
|
||||||
yearRef.addEventListener('scroll', () => scrollCallback('year'));
|
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.
|
* does not fire when we do our initial scrollIntoView above.
|
||||||
*/
|
*/
|
||||||
raf(() => {
|
raf(() => {
|
||||||
timeHourRef.addEventListener('scroll', () => scrollCallback('hour'));
|
const hourScroll = () => scrollCallback('hour');
|
||||||
timeMinuteRef.addEventListener('scroll', () => scrollCallback('minute'));
|
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">
|
<slot name="buttons">
|
||||||
<ion-buttons>
|
<ion-buttons>
|
||||||
<ion-button color={this.color} onClick={() => this.cancel(true)}>{this.cancelText}</ion-button>
|
<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>
|
</ion-buttons>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</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