mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(datetime): moving to overlay listeners for showing instead
This commit is contained in:
@@ -118,6 +118,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
private destroyCalendarListener?: () => void;
|
private destroyCalendarListener?: () => void;
|
||||||
private destroyKeyboardMO?: () => void;
|
private destroyKeyboardMO?: () => void;
|
||||||
|
private destroyOverlayListeners?: () => void;
|
||||||
|
|
||||||
// TODO(FW-2832): types (DatetimeParts causes some errors that need untangling)
|
// TODO(FW-2832): types (DatetimeParts causes some errors that need untangling)
|
||||||
private minParts?: any;
|
private minParts?: any;
|
||||||
@@ -1081,6 +1082,10 @@ export class Datetime implements ComponentInterface {
|
|||||||
this.resizeObserver.disconnect();
|
this.resizeObserver.disconnect();
|
||||||
this.resizeObserver = undefined;
|
this.resizeObserver = undefined;
|
||||||
}
|
}
|
||||||
|
if (this.destroyOverlayListeners) {
|
||||||
|
this.destroyOverlayListeners();
|
||||||
|
this.destroyOverlayListeners = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1106,41 +1111,21 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FW-6931: Fallback check for when ResizeObserver doesn't fire reliably
|
* Sets up visibility detection for the datetime component.
|
||||||
* (e.g., WebKit during modal re-presentation). Called after element is
|
*
|
||||||
* hidden to catch when it becomes visible again.
|
* Uses multiple strategies to reliably detect when the datetime becomes
|
||||||
|
* visible, which is necessary for proper initialization of scrollable areas:
|
||||||
|
* 1. ResizeObserver - detects dimension changes
|
||||||
|
* 2. Overlay event listeners - for datetime inside modals/popovers
|
||||||
|
* 3. Polling fallback - for browsers where observers are unreliable (WebKit)
|
||||||
*/
|
*/
|
||||||
private checkVisibilityFallback = () => {
|
private initializeVisibilityObserver() {
|
||||||
const { el } = this;
|
|
||||||
if (el.classList.contains('datetime-ready')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
if (rect.width > 0 && rect.height > 0) {
|
|
||||||
this.initializeListeners();
|
|
||||||
writeTask(() => {
|
|
||||||
el.classList.add('datetime-ready');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidLoad() {
|
|
||||||
const { el } = this;
|
const { el } = this;
|
||||||
|
|
||||||
/**
|
|
||||||
* If a scrollable element is hidden using `display: none`,
|
|
||||||
* it will not have a scroll height meaning we cannot scroll elements
|
|
||||||
* into view. As a result, we will need to wait for the datetime to become
|
|
||||||
* visible if used inside of a modal or a popover otherwise the scrollable
|
|
||||||
* areas will not have the correct values snapped into place.
|
|
||||||
*
|
|
||||||
* We use ResizeObserver to detect when the element transitions between
|
|
||||||
* having dimensions (visible) and zero dimensions (hidden). This is more
|
|
||||||
* reliable than IntersectionObserver for detecting visibility changes,
|
|
||||||
* especially when the element is inside a modal or popover.
|
|
||||||
*/
|
|
||||||
const markReady = () => {
|
const markReady = () => {
|
||||||
|
if (el.classList.contains('datetime-ready')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initializeListeners();
|
this.initializeListeners();
|
||||||
writeTask(() => {
|
writeTask(() => {
|
||||||
el.classList.add('datetime-ready');
|
el.classList.add('datetime-ready');
|
||||||
@@ -1153,17 +1138,50 @@ export class Datetime implements ComponentInterface {
|
|||||||
writeTask(() => {
|
writeTask(() => {
|
||||||
el.classList.remove('datetime-ready');
|
el.classList.remove('datetime-ready');
|
||||||
});
|
});
|
||||||
/**
|
startVisibilityPolling();
|
||||||
* Schedule fallback check for browsers where ResizeObserver
|
|
||||||
* doesn't fire reliably on re-presentation (e.g., WebKit).
|
|
||||||
*/
|
|
||||||
setTimeout(() => this.checkVisibilityFallback(), 100);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FW-6931: Poll for visibility as a fallback for browsers where
|
||||||
|
* ResizeObserver doesn't fire reliably (e.g., WebKit).
|
||||||
|
*/
|
||||||
|
const startVisibilityPolling = () => {
|
||||||
|
let pollCount = 0;
|
||||||
|
const poll = () => {
|
||||||
|
if (el.classList.contains('datetime-ready') || pollCount++ >= 60) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { width, height } = el.getBoundingClientRect();
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
markReady();
|
||||||
|
} else {
|
||||||
|
raf(poll);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
raf(poll);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FW-6931: Listen for overlay present/dismiss events when datetime
|
||||||
|
* is inside a modal or popover.
|
||||||
|
*/
|
||||||
|
const parentOverlay = el.closest('ion-modal, ion-popover') as HTMLIonModalElement | HTMLIonPopoverElement | null;
|
||||||
|
if (parentOverlay) {
|
||||||
|
const handlePresent = () => markReady();
|
||||||
|
const handleDismiss = () => markHidden();
|
||||||
|
|
||||||
|
parentOverlay.addEventListener('didPresent', handlePresent);
|
||||||
|
parentOverlay.addEventListener('didDismiss', handleDismiss);
|
||||||
|
|
||||||
|
this.destroyOverlayListeners = () => {
|
||||||
|
parentOverlay.removeEventListener('didPresent', handlePresent);
|
||||||
|
parentOverlay.removeEventListener('didDismiss', handleDismiss);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof ResizeObserver !== 'undefined') {
|
if (typeof ResizeObserver !== 'undefined') {
|
||||||
this.resizeObserver = new ResizeObserver((entries) => {
|
this.resizeObserver = new ResizeObserver((entries) => {
|
||||||
const entry = entries[0];
|
const { width, height } = entries[0].contentRect;
|
||||||
const { width, height } = entry.contentRect;
|
|
||||||
const isVisible = width > 0 && height > 0;
|
const isVisible = width > 0 && height > 0;
|
||||||
const isReady = el.classList.contains('datetime-ready');
|
const isReady = el.classList.contains('datetime-ready');
|
||||||
|
|
||||||
@@ -1174,27 +1192,19 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
// Use raf to avoid race condition with modal/popover animations
|
||||||
* Use raf to avoid a race condition between the component loading and
|
|
||||||
* its display animation starting (such as when shown in a modal).
|
|
||||||
*/
|
|
||||||
raf(() => this.resizeObserver?.observe(el));
|
raf(() => this.resizeObserver?.observe(el));
|
||||||
|
startVisibilityPolling();
|
||||||
/**
|
|
||||||
* Fallback for initial presentation in case ResizeObserver
|
|
||||||
* doesn't fire reliably (e.g., WebKit).
|
|
||||||
*/
|
|
||||||
setTimeout(() => this.checkVisibilityFallback(), 100);
|
|
||||||
} else {
|
} else {
|
||||||
/**
|
// Test environment fallback - mark ready immediately
|
||||||
* Fallback for test environments where ResizeObserver is not available.
|
|
||||||
* Just mark as ready without initializing scroll/keyboard listeners
|
|
||||||
* since those also require browser APIs not available in Jest.
|
|
||||||
*/
|
|
||||||
writeTask(() => {
|
writeTask(() => {
|
||||||
el.classList.add('datetime-ready');
|
el.classList.add('datetime-ready');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidLoad() {
|
||||||
|
this.initializeVisibilityObserver();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datetime uses Ionic components that emit
|
* Datetime uses Ionic components that emit
|
||||||
|
|||||||
Reference in New Issue
Block a user