feat(modal): enhance modal behavior and viewport handling for iOS devices
This commit is contained in:
@@ -51,6 +51,10 @@ describe('ModalHostComponent', () => {
|
||||
afterEach(() => {
|
||||
doc.body.style.overflow = '';
|
||||
doc.body.style.paddingRight = '';
|
||||
doc.body.style.position = '';
|
||||
doc.body.style.top = '';
|
||||
doc.body.style.left = '';
|
||||
doc.body.style.width = '';
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
@@ -99,6 +103,10 @@ describe('ModalHostComponent', () => {
|
||||
expect(service.activeModal()).toBeNull();
|
||||
expect(doc.body.style.overflow).toBe('');
|
||||
expect(doc.body.style.paddingRight).toBe('');
|
||||
expect(doc.body.style.position).toBe('');
|
||||
expect(doc.body.style.top).toBe('');
|
||||
expect(doc.body.style.left).toBe('');
|
||||
expect(doc.body.style.width).toBe('');
|
||||
});
|
||||
|
||||
it('closes on backdrop click when enabled', () => {
|
||||
|
||||
@@ -55,11 +55,23 @@ export class ModalHostComponent {
|
||||
const body = this.document.body;
|
||||
const previousOverflow = body.style.overflow;
|
||||
const previousPaddingRight = body.style.paddingRight;
|
||||
const previousPosition = body.style.position;
|
||||
const previousTop = body.style.top;
|
||||
const previousLeft = body.style.left;
|
||||
const previousWidth = body.style.width;
|
||||
const view = this.document.defaultView;
|
||||
const scrollX = view?.scrollX ?? 0;
|
||||
const scrollY = view?.scrollY ?? 0;
|
||||
const scrollbarWidth = view
|
||||
? Math.max(0, view.innerWidth - this.document.documentElement.clientWidth)
|
||||
: 0;
|
||||
|
||||
const activeElement = this.document.activeElement;
|
||||
|
||||
if (activeElement instanceof HTMLElement && activeElement !== body) {
|
||||
activeElement.blur();
|
||||
}
|
||||
|
||||
if (scrollbarWidth > 0 && view) {
|
||||
const currentPaddingRight =
|
||||
Number.parseFloat(view.getComputedStyle(body).paddingRight) || 0;
|
||||
@@ -68,6 +80,47 @@ export class ModalHostComponent {
|
||||
|
||||
body.style.overflow = 'hidden';
|
||||
|
||||
const isIos = view
|
||||
? /iPad|iPhone|iPod/.test(view.navigator.userAgent) ||
|
||||
(view.navigator.platform === 'MacIntel' && view.navigator.maxTouchPoints > 1)
|
||||
: false;
|
||||
|
||||
if (isIos) {
|
||||
body.style.position = 'fixed';
|
||||
body.style.top = `${-scrollY}px`;
|
||||
body.style.left = `${-scrollX}px`;
|
||||
body.style.width = '100%';
|
||||
}
|
||||
|
||||
let viewportFrame: number | undefined;
|
||||
const viewportSyncTimers: number[] = [];
|
||||
const syncVisualViewport = () => {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewportFrame !== undefined) {
|
||||
view.cancelAnimationFrame(viewportFrame);
|
||||
}
|
||||
|
||||
viewportFrame = view.requestAnimationFrame(() => {
|
||||
viewportFrame = undefined;
|
||||
this.modalShell()?.setVisualViewport(view.visualViewport, view.scrollX, view.scrollY);
|
||||
});
|
||||
};
|
||||
|
||||
view?.visualViewport?.addEventListener('resize', syncVisualViewport);
|
||||
view?.visualViewport?.addEventListener('scroll', syncVisualViewport);
|
||||
view?.addEventListener('orientationchange', syncVisualViewport);
|
||||
syncVisualViewport();
|
||||
|
||||
if (view) {
|
||||
viewportSyncTimers.push(
|
||||
view.setTimeout(syncVisualViewport, 100),
|
||||
view.setTimeout(syncVisualViewport, 300),
|
||||
);
|
||||
}
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== 'Escape' || !this.activeModal()?.config.closeOnEscape) {
|
||||
return;
|
||||
@@ -82,8 +135,26 @@ export class ModalHostComponent {
|
||||
|
||||
onCleanup(() => {
|
||||
this.document.removeEventListener('keydown', onKeyDown);
|
||||
view?.visualViewport?.removeEventListener('resize', syncVisualViewport);
|
||||
view?.visualViewport?.removeEventListener('scroll', syncVisualViewport);
|
||||
view?.removeEventListener('orientationchange', syncVisualViewport);
|
||||
|
||||
if (viewportFrame !== undefined) {
|
||||
view?.cancelAnimationFrame(viewportFrame);
|
||||
}
|
||||
|
||||
viewportSyncTimers.forEach((timer) => view?.clearTimeout(timer));
|
||||
|
||||
body.style.overflow = previousOverflow;
|
||||
body.style.paddingRight = previousPaddingRight;
|
||||
body.style.position = previousPosition;
|
||||
body.style.top = previousTop;
|
||||
body.style.left = previousLeft;
|
||||
body.style.width = previousWidth;
|
||||
|
||||
if (isIos) {
|
||||
view?.scrollTo(scrollX, scrollY);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user