Skip to content

Commit 01ae74b

Browse files
driskullbenelan
authored andcommitted
fix(dialog, modal, sheet): fix page scrolling after closing (#12006)
**Related Issue:** #11729 #11408 #12018 ## Summary - Add a controller to handle modifying the overflow styling on the document across components - This is so multiple components are no longer conflicting with each other while modifying the overflow styling on the document - Modifies `modal` to use embedded prop similar to dialog and sheet - Component tests already exist
1 parent 942bef6 commit 01ae74b

File tree

7 files changed

+261
-71
lines changed

7 files changed

+261
-71
lines changed

packages/calcite-components/src/components/dialog/dialog.tsx

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { OverlayPositioning } from "../../utils/floating-ui";
1616
import { useT9n } from "../../controllers/useT9n";
1717
import type { Panel } from "../panel/panel";
1818
import { FocusTrapOptions, useFocusTrap } from "../../controllers/useFocusTrap";
19+
import { usePreventDocumentScroll } from "../../controllers/usePreventDocumentScroll";
1920
import { resizeShiftStep } from "../../utils/resources";
2021
import T9nStrings from "./assets/t9n/messages.en.json";
2122
import { CSS, initialDragPosition, initialResizePosition, SLOTS } from "./resources";
@@ -28,9 +29,6 @@ declare global {
2829
}
2930
}
3031

31-
let totalOpenDialogs: number = 0;
32-
let initialDocumentOverflowStyle: string = "";
33-
3432
/**
3533
* @slot - A slot for adding content.
3634
* @slot content - [Deprecated] Use `custom-content` slot instead.
@@ -75,6 +73,8 @@ export class Dialog extends LitElement implements OpenCloseComponent {
7573
},
7674
})(this);
7775

76+
usePreventDocumentScroll = usePreventDocumentScroll()(this);
77+
7878
private ignoreOpenChange = false;
7979

8080
private interaction: Interactable;
@@ -117,6 +117,10 @@ export class Dialog extends LitElement implements OpenCloseComponent {
117117

118118
@state() opened = false;
119119

120+
@state() get preventDocumentScroll(): boolean {
121+
return !this.embedded && this.modal;
122+
}
123+
120124
// #endregion
121125

122126
// #region Public Properties
@@ -322,9 +326,6 @@ export class Dialog extends LitElement implements OpenCloseComponent {
322326
To account for this semantics change, the checks for (this.hasUpdated || value != defaultValue) was added in this method
323327
Please refactor your code to reduce the need for this check.
324328
Docs: https://qawebgis.esri.com/arcgis-components/?path=/docs/lumina-transition-from-stencil--docs#watching-for-property-changes */
325-
if (changes.has("modal") && (this.hasUpdated || this.modal !== false)) {
326-
this.updateOverflowHiddenClass();
327-
}
328329

329330
if (
330331
(changes.has("open") && (this.hasUpdated || this.open !== false)) ||
@@ -349,7 +350,6 @@ export class Dialog extends LitElement implements OpenCloseComponent {
349350
}
350351

351352
override disconnectedCallback(): void {
352-
this.removeOverflowHiddenClass();
353353
this.mutationObserver?.disconnect();
354354
this.embedded = false;
355355
this.cleanupInteractions();
@@ -713,7 +713,6 @@ export class Dialog extends LitElement implements OpenCloseComponent {
713713
this.openEnd,
714714
) /* TODO: [MIGRATION] If possible, refactor to use on* JSX prop or this.listen()/this.listenOn() utils - they clean up event listeners automatically, thus prevent memory leaks */;
715715
this.opened = true;
716-
this.updateOverflowHiddenClass();
717716
}
718717

719718
private handleOutsideClose(): void {
@@ -739,31 +738,7 @@ export class Dialog extends LitElement implements OpenCloseComponent {
739738
}
740739
}
741740

742-
totalOpenDialogs--;
743741
this.opened = false;
744-
this.updateOverflowHiddenClass();
745-
}
746-
747-
private updateOverflowHiddenClass(): void {
748-
if (this.opened && !this.embedded && this.modal) {
749-
this.addOverflowHiddenClass();
750-
} else {
751-
this.removeOverflowHiddenClass();
752-
}
753-
}
754-
755-
private addOverflowHiddenClass(): void {
756-
if (totalOpenDialogs === 0) {
757-
initialDocumentOverflowStyle = document.documentElement.style.overflow;
758-
}
759-
760-
totalOpenDialogs++;
761-
// use an inline style instead of a utility class to avoid global class declarations.
762-
document.documentElement.style.setProperty("overflow", "hidden");
763-
}
764-
765-
private removeOverflowHiddenClass(): void {
766-
document.documentElement.style.setProperty("overflow", initialDocumentOverflowStyle);
767742
}
768743

769744
private handleMutationObserver(): void {

packages/calcite-components/src/components/modal/modal.e2e.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ describe("calcite-modal", () => {
3535
propertyName: "widthScale",
3636
defaultValue: "m",
3737
},
38+
{
39+
propertyName: "embedded",
40+
defaultValue: false,
41+
},
3842
]);
3943
});
4044

packages/calcite-components/src/components/modal/modal.tsx

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
setAttribute,
1212
state,
1313
} from "@arcgis/lumina";
14-
import { getNearestOverflowAncestor } from "@floating-ui/utils/dom";
1514
import {
1615
ensureId,
1716
focusFirstTabbable,
@@ -25,6 +24,7 @@ import { Kind, Scale } from "../interfaces";
2524
import { getIconScale } from "../../utils/component";
2625
import { logger } from "../../utils/logger";
2726
import { useT9n } from "../../controllers/useT9n";
27+
import { usePreventDocumentScroll } from "../../controllers/usePreventDocumentScroll";
2828
import { FocusTrapOptions, useFocusTrap } from "../../controllers/useFocusTrap";
2929
import T9nStrings from "./assets/t9n/messages.en.json";
3030
import { CSS, ICONS, SLOTS } from "./resources";
@@ -36,9 +36,6 @@ declare global {
3636
}
3737
}
3838

39-
let totalOpenModals: number = 0;
40-
let initialDocumentOverflowStyle: string = "";
41-
4239
/**
4340
* @deprecated Use the `calcite-dialog` component instead.
4441
* @slot header - A slot for adding header text.
@@ -82,6 +79,8 @@ export class Modal extends LitElement implements OpenCloseComponent {
8279
},
8380
})(this);
8481

82+
usePreventDocumentScroll = usePreventDocumentScroll()(this);
83+
8584
private ignoreOpenChange = false;
8685

8786
private modalContent = createRef<HTMLDivElement>();
@@ -132,6 +131,10 @@ export class Modal extends LitElement implements OpenCloseComponent {
132131

133132
@state() titleEl: HTMLElement;
134133

134+
@state() get preventDocumentScroll(): boolean {
135+
return !this.embedded;
136+
}
137+
135138
// #endregion
136139

137140
// #region Public Properties
@@ -145,6 +148,14 @@ export class Modal extends LitElement implements OpenCloseComponent {
145148
/** When `true`, prevents the component from expanding to the entire screen on mobile devices. */
146149
@property({ reflect: true }) docked: boolean;
147150

151+
/**
152+
* This internal property, managed by a containing calcite-shell, is used
153+
* to inform the component if special configuration or styles are needed
154+
*
155+
* @private
156+
*/
157+
@property() embedded = false;
158+
148159
/** When `true`, disables the default close on escape behavior. */
149160
@property({ reflect: true }) escapeDisabled = false;
150161

@@ -314,9 +325,9 @@ export class Modal extends LitElement implements OpenCloseComponent {
314325
}
315326

316327
override disconnectedCallback(): void {
317-
this.removeOverflowHiddenClass();
318328
this.mutationObserver?.disconnect();
319329
this.cssVarObserver?.disconnect();
330+
this.embedded = false;
320331
}
321332

322333
// #endregion
@@ -426,16 +437,6 @@ export class Modal extends LitElement implements OpenCloseComponent {
426437

427438
this.titleId = ensureId(this.titleEl);
428439
this.contentId = ensureId(this.contentEl);
429-
430-
if (getNearestOverflowAncestor(this.el) === document.body) {
431-
if (totalOpenModals === 0) {
432-
initialDocumentOverflowStyle = document.documentElement.style.overflow;
433-
}
434-
435-
totalOpenModals++;
436-
// use an inline style instead of a utility class to avoid global class declarations.
437-
document.documentElement.style.setProperty("overflow", "hidden");
438-
}
439440
}
440441

441442
private handleOutsideClose(): void {
@@ -461,20 +462,9 @@ export class Modal extends LitElement implements OpenCloseComponent {
461462
}
462463
}
463464

464-
if (getNearestOverflowAncestor(this.el) === document.body) {
465-
totalOpenModals--;
466-
if (totalOpenModals === 0) {
467-
this.removeOverflowHiddenClass();
468-
}
469-
}
470-
471465
this.opened = false;
472466
}
473467

474-
private removeOverflowHiddenClass(): void {
475-
document.documentElement.style.setProperty("overflow", initialDocumentOverflowStyle);
476-
}
477-
478468
private updateSizeCssVars(): void {
479469
this.cssWidth = getComputedStyle(this.el).getPropertyValue("--calcite-modal-width");
480470
this.cssHeight = getComputedStyle(this.el).getPropertyValue("--calcite-modal-height");

packages/calcite-components/src/components/sheet/sheet.tsx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Height, LogicalFlowPosition, Scale, Width } from "../interfaces";
2121
import { CSS_UTILITY } from "../../utils/resources";
2222
import { clamp } from "../../utils/math";
2323
import { useT9n } from "../../controllers/useT9n";
24+
import { usePreventDocumentScroll } from "../../controllers/usePreventDocumentScroll";
2425
import { FocusTrapOptions, useFocusTrap } from "../../controllers/useFocusTrap";
2526
import { resizeStep, resizeShiftStep } from "../../utils/resources";
2627
import { CSS } from "./resources";
@@ -64,9 +65,9 @@ export class Sheet extends LitElement implements OpenCloseComponent {
6465
},
6566
})(this);
6667

67-
private ignoreOpenChange = false;
68+
usePreventDocumentScroll = usePreventDocumentScroll()(this);
6869

69-
private initialOverflowCSS: string;
70+
private ignoreOpenChange = false;
7071

7172
private interaction: Interactable;
7273

@@ -107,6 +108,10 @@ export class Sheet extends LitElement implements OpenCloseComponent {
107108
maxBlockSize: null,
108109
};
109110

111+
@state() get preventDocumentScroll(): boolean {
112+
return !this.embedded;
113+
}
114+
110115
// #endregion
111116

112117
// #region Public Properties
@@ -290,7 +295,6 @@ export class Sheet extends LitElement implements OpenCloseComponent {
290295
}
291296

292297
override disconnectedCallback(): void {
293-
this.removeOverflowHiddenClass();
294298
this.mutationObserver?.disconnect();
295299
this.embedded = false;
296300
this.cleanupInteractions();
@@ -551,11 +555,6 @@ export class Sheet extends LitElement implements OpenCloseComponent {
551555
this.openEnd,
552556
) /* TODO: [MIGRATION] If possible, refactor to use on* JSX prop or this.listen()/this.listenOn() utils - they clean up event listeners automatically, thus prevent memory leaks */;
553557
this.opened = true;
554-
if (!this.embedded) {
555-
this.initialOverflowCSS = document.documentElement.style.overflow;
556-
// use an inline style instead of a utility class to avoid global class declarations.
557-
document.documentElement.style.setProperty("overflow", "hidden");
558-
}
559558
}
560559

561560
private handleOutsideClose(): void {
@@ -582,11 +581,6 @@ export class Sheet extends LitElement implements OpenCloseComponent {
582581
}
583582

584583
this.opened = false;
585-
this.removeOverflowHiddenClass();
586-
}
587-
588-
private removeOverflowHiddenClass(): void {
589-
document.documentElement.style.setProperty("overflow", this.initialOverflowCSS);
590584
}
591585

592586
private handleMutationObserver(): void {

packages/calcite-components/src/components/shell/shell.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { slotChangeGetAssignedElements, slotChangeHasAssignedElement } from "../
55
import type { Dialog } from "../dialog/dialog";
66
import type { Sheet } from "../sheet/sheet";
77
import type { Alert } from "../alert/alert";
8+
import { Modal } from "../modal/modal";
89
import { styles } from "./shell.scss";
910
import { CSS, SLOTS } from "./resources";
1011

@@ -135,6 +136,11 @@ export class Shell extends LitElement {
135136

136137
private handleModalsSlotChange(event: Event): void {
137138
this.hasModals = !!slotChangeHasAssignedElement(event);
139+
slotChangeGetAssignedElements(event)?.map((el) => {
140+
if (el.tagName === "CALCITE-MODAL") {
141+
(el as Modal["el"]).embedded = true;
142+
}
143+
});
138144
}
139145

140146
private handlePanelTopChange(event: Event): void {

0 commit comments

Comments
 (0)