Skip to content

Commit 448b72d

Browse files
authored
Merge pull request #13 from web-illinois/issue-12
Issue 12 fix for page and modal scrolling
2 parents e754fb1 + 04e65e5 commit 448b72d

7 files changed

Lines changed: 168 additions & 5 deletions

File tree

builder/ilw-modal.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"element-name": "ilw-modal",
77
"description": "The modal component provides a modal that opens on top of a web page.",
88
"purpose": "To provide a modal window to open content on top of a web page.",
9-
"toolkit-version": "1.1",
10-
"production-version": "1.1",
9+
"toolkit-version": "1.2",
10+
"production-version": "1.2",
1111
"development-version": "1.0-alpha"
1212
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"title": "Modal",
3+
"tag": "ilw-modal",
4+
"github": "https://github.com/web-illinois/ilw-modal/",
5+
"type": "web component",
6+
"element-name": "ilw-modal",
7+
"description": "The modal component provides a modal that opens on top of a web page.",
8+
"builder-version": "1.2",
9+
"version": "1.2",
10+
"date": "02/27/2026",
11+
"css": "https://cdn.toolkit.illinois.edu/ilw-modal/1.2/ilw-modal.css",
12+
"js": "https://cdn.toolkit.illinois.edu/ilw-modal/1.2/ilw-modal.js",
13+
"production": true,
14+
"notes": "",
15+
"parent-style": "",
16+
"addon-html": "<button class='ilw-button' data-modal-target='modal'>Open Modal Button</button> -- <a href='javascript:void(0)' class='ilw-button' data-modal-target='modal' role='button'>Open Modal Link</a>",
17+
"samples": [
18+
{
19+
"name": "default",
20+
"description": "Default information",
21+
"text": "<h2 slot=\"title\">Default Modal</h2>\n <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed lectus velit, feugiat in lacinia eget, tristique ut orci. Phasellus ut egestas tellus. In hac habitasse platea dictumst.</p>\n <form style=\"display: flex; flex-direction: column; gap: 1rem;\">\n <label style=\"font-size: 1rem; font-weight: bold;\" for=\"email\">Enter your email</label>\n <div style=\"display: flex; gap: 1rem; width: 100%; align-items: center;\">\n <input style=\"flex: 1; padding: 0.75rem; font-size: 1rem; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;\" type=\"email\" id=\"email\" name=\"email\" required>\n <button class=\"ilw-button\" type=\"submit\">Submit</button>\n </div>\n </form>"
22+
},
23+
{
24+
"name": "image",
25+
"description": "Modal with an image",
26+
"text": "<img src=\"https://picsum.photos/700/400\" alt=\"\" slot=\"image\">\n <h2 slot=\"title\">Medium Modal</h2>\n <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed lectus velit, feugiat in lacinia eget, tristique ut orci. Phasellus ut egestas tellus. In hac habitasse platea dictumst.</p>\n <form style=\"display: flex; flex-direction: column; gap: 1rem;\">\n <label style=\"font-size: 1rem; font-weight: bold;\" for=\"email\">Enter your email</label>\n <div style=\"display: flex; gap: 1rem; width: 100%; align-items: center;\">\n <input style=\"flex: 1; padding: 0.75rem; font-size: 1rem; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;\" type=\"email\" id=\"email\" name=\"email\" required>\n <button class=\"ilw-button\" type=\"submit\">Submit</button>\n </div>\n </form>"
27+
}
28+
],
29+
"attributes-fixed": [
30+
{
31+
"name": "id",
32+
"description": "id of the modal",
33+
"depreciated": false,
34+
"value": "modal"
35+
}
36+
],
37+
"attributes-text": [
38+
{
39+
"depreciated": false,
40+
"name": "id",
41+
"description" : "Provide your own id for the modal, this will replace the default id.",
42+
"type": "string",
43+
"value": ""
44+
}
45+
],
46+
"attributes": [
47+
{
48+
"name": "theme",
49+
"description": "Color theme used",
50+
"depreciated": false,
51+
"values": [
52+
"",
53+
"gray"
54+
]
55+
},
56+
{
57+
"name": "size",
58+
"description": "Size of modal window",
59+
"depreciated": false,
60+
"values": [
61+
"",
62+
"small",
63+
"medium",
64+
"large"
65+
]
66+
},
67+
{
68+
"name": "align",
69+
"description": "Alignment of content in modal window",
70+
"depreciated": false,
71+
"values": [
72+
"",
73+
"center"
74+
]
75+
}
76+
],
77+
"classes-fixed": [],
78+
"classes": [],
79+
"css-variables": [
80+
]
81+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"repository": "github:web-illinois/ilw-modal",
55
"private": false,
66
"license": "MIT",
7-
"version": "1.1.1",
7+
"version": "1.2.0",
88
"type": "module",
99
"files": [
1010
"src/**",

samples/index.html

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ <h1>Modal</h1>
4848

4949
<button class="ilw-button" data-modal-target="modal-id2">Open Modal Button</button>
5050

51+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
52+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
53+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
54+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
55+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
56+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
57+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
58+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
59+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
60+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
61+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
62+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
63+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
64+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
65+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
66+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
67+
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
5168

5269

5370

@@ -64,8 +81,10 @@ <h2 slot="title">Apply to the College</h2>
6481

6582
<ilw-modal id="modal-id1" size="medium" theme="gray">
6683
<img src="https://picsum.photos/700/400" alt="" slot="image">
67-
<h2 slot="title">Medium Modal</h2>
84+
<h2 slot="title">Medium Modal with a Long Title Will it Wrap</h2>
6885
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed lectus velit, feugiat in lacinia eget, tristique ut orci. Phasellus ut egestas tellus. In hac habitasse platea dictumst.</p>
86+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed lectus velit, feugiat in lacinia eget, tristique ut orci. Phasellus ut egestas tellus. In hac habitasse platea dictumst.</p>
87+
6988
<form>
7089
<label for="email">Enter your email</label>
7190
<div class="form-row">

src/ilw-modal.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ ilw-modal {
55
h2 {
66
font-family: var(--ilw-modal--heading--font);
77
font-size: var(--ilw-modal--heading--font-size);
8+
line-height: var(--ilw-modal--heading--line-height);
9+
margin: var(--ilw-modal--heading--margin);
810
color: var(--ilw-modal--heading-color);
911
}
1012
}

src/ilw-modal.styles.css

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
--ilw-modal--heading-color: var(---ilw-color--heading);
1010
--ilw-modal--heading--font: var(--il-font-montserrat);
1111
--ilw-modal--heading--font-size: 2.25rem;
12+
--ilw-modal--heading--line-height: 1.2;
13+
--ilw-modal--heading--margin: 2rem 0 1.3125rem 0;
1214
--ilw-modal--border-top: 8px solid var(--ilw-color--control-accent);
1315
--ilw-modal--background: var(--ilw-color--background);
1416
--ilw-modal--close-btn--color: var(--ilw-color--control-text);
@@ -29,13 +31,16 @@
2931
background: rgba(0, 0, 0, 0.5);
3032
display: flex;
3133
align-items: flex-start;
32-
padding-top: 20vh;
34+
padding-top: 5vh;
3335
justify-content: center;
3436
visibility: hidden;
3537
opacity: 0;
3638
transition: opacity 0.3s ease-in-out;
39+
overflow-y: auto;
40+
-webkit-overflow-scrolling: touch;
3741
}
3842

43+
3944
:host([open]) .backdrop {
4045
visibility: visible;
4146
opacity: 1;
@@ -51,6 +56,9 @@
5156
border-top: var(--ilw-modal--border-top);
5257
display: flex;
5358
flex-direction: column;
59+
max-height: 80vh;
60+
overflow-y: auto;
61+
-webkit-overflow-scrolling: touch;
5462
}
5563

5664
.modal.small {
@@ -73,6 +81,9 @@
7381
}
7482

7583
@media (max-width: 600px) {
84+
.backdrop {
85+
padding-top: 0;
86+
}
7687
.modal.small,
7788
.modal.medium,
7889
.modal.large {

src/ilw-modal.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,51 @@ export default class Modal extends LitElement {
2929
private _lastFocusable: Element | null = null;
3030
private _activeTrigger: Element | null = null;
3131

32+
private static _scrollLockCount = 0;
33+
34+
private _prevHtmlOverflow: string | null = null;
35+
private _prevBodyOverflow: string | null = null;
36+
private _prevBodyPaddingRight: string | null = null;
37+
38+
private _lockPageScroll() {
39+
if (Modal._scrollLockCount === 0) {
40+
const html = document.documentElement;
41+
const body = document.body;
42+
43+
this._prevHtmlOverflow = html.style.overflow;
44+
this._prevBodyOverflow = body.style.overflow;
45+
this._prevBodyPaddingRight = body.style.paddingRight;
46+
47+
// Prevent layout shift when scrollbar disappears
48+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
49+
if (scrollbarWidth > 0) {
50+
body.style.paddingRight = `${scrollbarWidth}px`;
51+
}
52+
53+
html.style.overflow = 'hidden';
54+
body.style.overflow = 'hidden';
55+
}
56+
57+
Modal._scrollLockCount++;
58+
}
59+
60+
private _unlockPageScroll() {
61+
Modal._scrollLockCount = Math.max(0, Modal._scrollLockCount - 1);
62+
63+
if (Modal._scrollLockCount === 0) {
64+
const html = document.documentElement;
65+
const body = document.body;
66+
67+
html.style.overflow = this._prevHtmlOverflow ?? '';
68+
body.style.overflow = this._prevBodyOverflow ?? '';
69+
body.style.paddingRight = this._prevBodyPaddingRight ?? '';
70+
71+
this._prevHtmlOverflow = null;
72+
this._prevBodyOverflow = null;
73+
this._prevBodyPaddingRight = null;
74+
}
75+
}
76+
3277
static override get styles(): CSSResult {
3378
return unsafeCSS(styles);
3479
}
@@ -44,12 +89,16 @@ export default class Modal extends LitElement {
4489

4590
protected override updated(changedProps: PropertyValues): void {
4691
super.updated(changedProps);
92+
4793
if (changedProps.has('open')) {
4894
if (this.open) {
95+
this._lockPageScroll();
4996
this._setInitialFocus();
5097
document.addEventListener('keydown', this._handleKeydown);
5198
} else {
99+
this._unlockPageScroll();
52100
document.removeEventListener('keydown', this._handleKeydown);
101+
53102
if (this._activeTrigger && this._activeTrigger instanceof HTMLElement) {
54103
this._activeTrigger.focus();
55104
this._activeTrigger = null;
@@ -65,6 +114,7 @@ export default class Modal extends LitElement {
65114

66115
override disconnectedCallback(): void {
67116
super.disconnectedCallback();
117+
if (this.open) this._unlockPageScroll(); // Ensure scroll is unlocked if component is removed while open
68118
document.removeEventListener('click', this.handleExternalTrigger);
69119
document.removeEventListener('keydown', this._handleKeydown);
70120
}

0 commit comments

Comments
 (0)