Skip to content

Commit a8772e8

Browse files
committed
fix(datetime): moving to overlay listeners for showing instead
1 parent 12ef934 commit a8772e8

File tree

1 file changed

+63
-53
lines changed

1 file changed

+63
-53
lines changed

core/src/components/datetime/datetime.tsx

Lines changed: 63 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export class Datetime implements ComponentInterface {
118118

119119
private destroyCalendarListener?: () => void;
120120
private destroyKeyboardMO?: () => void;
121+
private destroyOverlayListeners?: () => void;
121122

122123
// TODO(FW-2832): types (DatetimeParts causes some errors that need untangling)
123124
private minParts?: any;
@@ -1081,6 +1082,10 @@ export class Datetime implements ComponentInterface {
10811082
this.resizeObserver.disconnect();
10821083
this.resizeObserver = undefined;
10831084
}
1085+
if (this.destroyOverlayListeners) {
1086+
this.destroyOverlayListeners();
1087+
this.destroyOverlayListeners = undefined;
1088+
}
10841089
}
10851090

10861091
/**
@@ -1106,41 +1111,21 @@ export class Datetime implements ComponentInterface {
11061111
}
11071112

11081113
/**
1109-
* FW-6931: Fallback check for when ResizeObserver doesn't fire reliably
1110-
* (e.g., WebKit during modal re-presentation). Called after element is
1111-
* hidden to catch when it becomes visible again.
1114+
* Sets up visibility detection for the datetime component.
1115+
*
1116+
* Uses multiple strategies to reliably detect when the datetime becomes
1117+
* visible, which is necessary for proper initialization of scrollable areas:
1118+
* 1. ResizeObserver - detects dimension changes
1119+
* 2. Overlay event listeners - for datetime inside modals/popovers
1120+
* 3. Polling fallback - for browsers where observers are unreliable (WebKit)
11121121
*/
1113-
private checkVisibilityFallback = () => {
1114-
const { el } = this;
1115-
if (el.classList.contains('datetime-ready')) {
1116-
return;
1117-
}
1118-
1119-
const rect = el.getBoundingClientRect();
1120-
if (rect.width > 0 && rect.height > 0) {
1121-
this.initializeListeners();
1122-
writeTask(() => {
1123-
el.classList.add('datetime-ready');
1124-
});
1125-
}
1126-
};
1127-
1128-
componentDidLoad() {
1122+
private initializeVisibilityObserver() {
11291123
const { el } = this;
11301124

1131-
/**
1132-
* If a scrollable element is hidden using `display: none`,
1133-
* it will not have a scroll height meaning we cannot scroll elements
1134-
* into view. As a result, we will need to wait for the datetime to become
1135-
* visible if used inside of a modal or a popover otherwise the scrollable
1136-
* areas will not have the correct values snapped into place.
1137-
*
1138-
* We use ResizeObserver to detect when the element transitions between
1139-
* having dimensions (visible) and zero dimensions (hidden). This is more
1140-
* reliable than IntersectionObserver for detecting visibility changes,
1141-
* especially when the element is inside a modal or popover.
1142-
*/
11431125
const markReady = () => {
1126+
if (el.classList.contains('datetime-ready')) {
1127+
return;
1128+
}
11441129
this.initializeListeners();
11451130
writeTask(() => {
11461131
el.classList.add('datetime-ready');
@@ -1153,17 +1138,50 @@ export class Datetime implements ComponentInterface {
11531138
writeTask(() => {
11541139
el.classList.remove('datetime-ready');
11551140
});
1156-
/**
1157-
* Schedule fallback check for browsers where ResizeObserver
1158-
* doesn't fire reliably on re-presentation (e.g., WebKit).
1159-
*/
1160-
setTimeout(() => this.checkVisibilityFallback(), 100);
1141+
startVisibilityPolling();
1142+
};
1143+
1144+
/**
1145+
* FW-6931: Poll for visibility as a fallback for browsers where
1146+
* ResizeObserver doesn't fire reliably (e.g., WebKit).
1147+
*/
1148+
const startVisibilityPolling = () => {
1149+
let pollCount = 0;
1150+
const poll = () => {
1151+
if (el.classList.contains('datetime-ready') || pollCount++ >= 60) {
1152+
return;
1153+
}
1154+
const { width, height } = el.getBoundingClientRect();
1155+
if (width > 0 && height > 0) {
1156+
markReady();
1157+
} else {
1158+
raf(poll);
1159+
}
1160+
};
1161+
raf(poll);
11611162
};
11621163

1164+
/**
1165+
* FW-6931: Listen for overlay present/dismiss events when datetime
1166+
* is inside a modal or popover.
1167+
*/
1168+
const parentOverlay = el.closest('ion-modal, ion-popover') as HTMLIonModalElement | HTMLIonPopoverElement | null;
1169+
if (parentOverlay) {
1170+
const handlePresent = () => markReady();
1171+
const handleDismiss = () => markHidden();
1172+
1173+
parentOverlay.addEventListener('didPresent', handlePresent);
1174+
parentOverlay.addEventListener('didDismiss', handleDismiss);
1175+
1176+
this.destroyOverlayListeners = () => {
1177+
parentOverlay.removeEventListener('didPresent', handlePresent);
1178+
parentOverlay.removeEventListener('didDismiss', handleDismiss);
1179+
};
1180+
}
1181+
11631182
if (typeof ResizeObserver !== 'undefined') {
11641183
this.resizeObserver = new ResizeObserver((entries) => {
1165-
const entry = entries[0];
1166-
const { width, height } = entry.contentRect;
1184+
const { width, height } = entries[0].contentRect;
11671185
const isVisible = width > 0 && height > 0;
11681186
const isReady = el.classList.contains('datetime-ready');
11691187

@@ -1174,27 +1192,19 @@ export class Datetime implements ComponentInterface {
11741192
}
11751193
});
11761194

1177-
/**
1178-
* Use raf to avoid a race condition between the component loading and
1179-
* its display animation starting (such as when shown in a modal).
1180-
*/
1195+
// Use raf to avoid race condition with modal/popover animations
11811196
raf(() => this.resizeObserver?.observe(el));
1182-
1183-
/**
1184-
* Fallback for initial presentation in case ResizeObserver
1185-
* doesn't fire reliably (e.g., WebKit).
1186-
*/
1187-
setTimeout(() => this.checkVisibilityFallback(), 100);
1197+
startVisibilityPolling();
11881198
} else {
1189-
/**
1190-
* Fallback for test environments where ResizeObserver is not available.
1191-
* Just mark as ready without initializing scroll/keyboard listeners
1192-
* since those also require browser APIs not available in Jest.
1193-
*/
1199+
// Test environment fallback - mark ready immediately
11941200
writeTask(() => {
11951201
el.classList.add('datetime-ready');
11961202
});
11971203
}
1204+
}
1205+
1206+
componentDidLoad() {
1207+
this.initializeVisibilityObserver();
11981208

11991209
/**
12001210
* Datetime uses Ionic components that emit

0 commit comments

Comments
 (0)