Skip to content

Commit f759776

Browse files
perf(datetime): calendar body shows immediately in modal on ios (#29163)
Issue number: resolves #24542 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> WebKit has a quirk where IntersectionObserver callbacks are delayed until after an accelerated animation finishes if the "root" specified in the config is the browser viewport (the default behavior if "root" is not specified) This means that when presenting a datetime in a modal on iOS the calendar body appears blank until the modal animation finishes. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - We can work around this issue by observing an element inside of the datetime component and using the datetime component itself as the root. To do this, I added an `.intersection-tracker` element inside of datetime. This element has a dimension of 0x0 so it should not affect component layout or functionality. I opted to add this element instead of re-using an existing element because the existing elements are not guaranteed to always be in the DOM due to different datetime presentation styles. | `main` | branch | | - | - | | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/e84d111d-b156-4f45-887a-d68a1097e5dd"></video> | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/3dccf1e5-cf79-46ab-b542-0537fd46fa76"></video> | ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev build: `7.8.1-dev.11710449785.14ebd5a0` --------- Co-authored-by: Amanda Johnston <[email protected]>
1 parent fdfecd3 commit f759776

File tree

1 file changed

+21
-4
lines changed

1 file changed

+21
-4
lines changed

core/src/components/datetime/datetime.tsx

+21-4
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export class Datetime implements ComponentInterface {
108108
private calendarBodyRef?: HTMLElement;
109109
private monthYearToggleItemRef?: HTMLIonItemElement;
110110
private popoverRef?: HTMLIonPopoverElement;
111+
private intersectionTrackerRef?: HTMLElement;
111112
private clearFocusVisible?: () => void;
112113
private parsedMinuteValues?: number[];
113114
private parsedHourValues?: number[];
@@ -1079,6 +1080,8 @@ export class Datetime implements ComponentInterface {
10791080
}
10801081

10811082
componentDidLoad() {
1083+
const { el, intersectionTrackerRef } = this;
1084+
10821085
/**
10831086
* If a scrollable element is hidden using `display: none`,
10841087
* it will not have a scroll height meaning we cannot scroll elements
@@ -1106,15 +1109,15 @@ export class Datetime implements ComponentInterface {
11061109
this.el.classList.add('datetime-ready');
11071110
});
11081111
};
1109-
const visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01 });
1112+
const visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01, root: el });
11101113

11111114
/**
11121115
* Use raf to avoid a race condition between the component loading and
11131116
* its display animation starting (such as when shown in a modal). This
11141117
* could cause the datetime to start at a visibility of 0, erroneously
11151118
* triggering the `hiddenIO` observer below.
11161119
*/
1117-
raf(() => visibleIO?.observe(this.el));
1120+
raf(() => visibleIO?.observe(intersectionTrackerRef!));
11181121

11191122
/**
11201123
* We need to clean up listeners when the datetime is hidden
@@ -1144,8 +1147,8 @@ export class Datetime implements ComponentInterface {
11441147
this.el.classList.remove('datetime-ready');
11451148
});
11461149
};
1147-
const hiddenIO = new IntersectionObserver(hiddenCallback, { threshold: 0 });
1148-
raf(() => hiddenIO?.observe(this.el));
1150+
const hiddenIO = new IntersectionObserver(hiddenCallback, { threshold: 0, root: el });
1151+
raf(() => hiddenIO?.observe(intersectionTrackerRef!));
11491152

11501153
/**
11511154
* Datetime uses Ionic components that emit
@@ -2613,6 +2616,20 @@ export class Datetime implements ComponentInterface {
26132616
}),
26142617
}}
26152618
>
2619+
{/*
2620+
WebKit has a quirk where IntersectionObserver callbacks are delayed until after
2621+
an accelerated animation finishes if the "root" specified in the config is the
2622+
browser viewport (the default behavior if "root" is not specified). This means
2623+
that when presenting a datetime in a modal on iOS the calendar body appears
2624+
blank until the modal animation finishes.
2625+
2626+
We can work around this by observing .intersection-tracker and using the host
2627+
(ion-datetime) as the "root". This allows the IO callback to fire the moment
2628+
the datetime is visible. The .intersection-tracker element should not have
2629+
dimensions or additional styles, and it should not be positioned absolutely
2630+
otherwise the IO callback may fire at unexpected times.
2631+
*/}
2632+
<div class="intersection-tracker" ref={(el) => (this.intersectionTrackerRef = el)}></div>
26162633
{this.renderDatetime(mode)}
26172634
</Host>
26182635
);

0 commit comments

Comments
 (0)