@nysds/nys-modal 1.11.4 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nys-modal.js +64 -72
- package/dist/nys-modal.js.map +1 -1
- package/package.json +2 -2
package/dist/nys-modal.js
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
import { LitElement as g, unsafeCSS as b, html as
|
|
2
|
-
import { property as
|
|
1
|
+
import { LitElement as g, unsafeCSS as b, html as f } from "lit";
|
|
2
|
+
import { property as d, state as _ } from "lit/decorators.js";
|
|
3
3
|
const w = ':host{--_nys-modal-width: 480px;--_nys-modal-min-width: 320px;--_nys-modal-border-radius: var(--nys-radius-lg, 8px);--_nys-modal-border-color: var(--nys-color-neutral-200, #bec0c1);--_nys-modal-border-width: 1px;--_nys-modal-background-color: var(--nys-color-surface, #fff);--_nys-modal-margin: var(--nys-space-250, 20px);--_nys-modal-padding: var(--nys-space-300, 24px);--_nys-modal-gap: var(--nys-space-200, 16px);--_nys-modal-background-color--overlay: var( --nys-color-black-transparent-700, rgba(27, 27, 27, .7) );--_nys-modal-gap--header: var(--nys-space-100, 8px);--_nys-modal-gap--footer: var(--nys-space-250, 20px);--_nys-modal-font-size: var(--nys-font-size-ui-md, 16px);--_nys-modal-font-weight--header: var(--nys-font-weight-bold, 700);--_nys-modal-font-weight--subheader: var(--nys-font-weight-regular, 400);--_nys-modal-line-height: var(--nys-font-lineheight-ui-md, 24px);--_nys-modal-font-family: var( --nys-font-family-ui, var( --nys-font-family-sans, "Proxima Nova", "Helvetica Neue", "Helvetica", "Arial", sans-serif ) )}*{box-sizing:border-box}::slotted(p){margin:0!important}h2,p{flex:1;margin:0}.nys-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;z-index:1000;background:var(--_nys-modal-background-color--overlay)}.nys-modal{display:flex;flex-direction:column;margin:var(--_nys-modal-margin);padding:var(--_nys-modal-padding);gap:var(--_nys-modal-gap);width:var(--_nys-modal-width);min-width:var(--_nys-modal-min-width);border-radius:var(--_nys-modal-border-radius);border:var(--_nys-modal-border-width) solid var(--_nys-modal-border-color);font-family:var(--_nys-modal-font-family);font-size:var(--_nys-modal-font-size);line-height:var(--_nys-modal-line-height);background:var(--_nys-modal-background-color);position:relative;z-index:10000}.nys-modal_header{display:flex;flex-direction:column;align-items:flex-start;gap:var(--_nys-modal-gap--header)}.nys-modal_header p{font-weight:var(--_nys-modal-font-weight--subheader)}.nys-modal_header-inner{display:flex;align-items:center;width:100%;font-weight:var(--_nys-modal-font-weight--header)}.nys-modal_body{display:flex;flex-direction:column;align-items:flex-start}.nys-modal_body-inner{overflow:auto;width:100%;max-height:45vh}.nys-modal_body.hidden{display:none}.nys-modal_footer ::slotted(*){display:flex;flex-direction:column-reverse;justify-content:center;gap:var(--_nys-modal-gap--footer);align-self:stretch}.nys-modal_footer.hidden ::slotted(*){display:none}@media(min-width:480px){.nys-modal_body-inner{max-height:25vh}.nys-modal_footer ::slotted(*){flex-direction:row;justify-content:flex-end;align-items:center}}';
|
|
4
|
-
var B = Object.defineProperty,
|
|
5
|
-
for (var e =
|
|
6
|
-
(
|
|
7
|
-
return
|
|
4
|
+
var B = Object.defineProperty, i = (v, t, o, n) => {
|
|
5
|
+
for (var e = void 0, l = v.length - 1, c; l >= 0; l--)
|
|
6
|
+
(c = v[l]) && (e = c(t, o, e) || e);
|
|
7
|
+
return e && B(t, o, e), e;
|
|
8
8
|
};
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**************** Lifecycle Methods ****************/
|
|
9
|
+
let E = 0;
|
|
10
|
+
const p = class p extends g {
|
|
11
|
+
// Lifecycle Methods
|
|
13
12
|
constructor() {
|
|
14
|
-
super(), this.id = "", this.heading = "", this.subheading = "", this.open = !1, this.mandatory = !1, this.
|
|
15
|
-
}
|
|
16
|
-
get width() {
|
|
17
|
-
return this._width;
|
|
18
|
-
}
|
|
19
|
-
set width(t) {
|
|
20
|
-
this._width = i.VALID_WIDTHS.includes(
|
|
21
|
-
t
|
|
22
|
-
) ? t : "md";
|
|
13
|
+
super(), this.id = "", this.heading = "", this.subheading = "", this.open = !1, this.mandatory = !1, this.width = "md", this._actionButtonSlot = null, this._prevFocusedElement = null, this._originalBodyOverflow = null, this.hasBodySlots = !1, this.hasActionSlots = !1;
|
|
23
14
|
}
|
|
24
15
|
connectedCallback() {
|
|
25
|
-
super.connectedCallback(), this.id || (this.id = `nys-{{componentName}}-${Date.now()}-${
|
|
16
|
+
super.connectedCallback(), this.id || (this.id = `nys-{{componentName}}-${Date.now()}-${E++}`), window.addEventListener("resize", () => this._updateSlottedButtonWidth()), window.addEventListener("keydown", (t) => this._handleKeydown(t));
|
|
26
17
|
}
|
|
27
18
|
disconnectedCallback() {
|
|
28
19
|
super.disconnectedCallback(), this._restoreBodyScroll(), window.removeEventListener("keydown", (t) => this._handleKeydown(t));
|
|
@@ -30,7 +21,7 @@ const n = (i = class extends g {
|
|
|
30
21
|
async updated(t) {
|
|
31
22
|
t.has("open") && (this.open ? (this._hideBodyScroll(), this._dispatchOpenEvent(), await this.updateComplete, this._savePrevFocused(), this._focusOnModal(), this._updateDismissAria()) : (this._restorePrevFocused(), this._restoreBodyScroll(), this._dispatchCloseEvent(), this._updateDismissAria()));
|
|
32
23
|
}
|
|
33
|
-
|
|
24
|
+
// Functions
|
|
34
25
|
_hideBodyScroll() {
|
|
35
26
|
this._originalBodyOverflow === null && (this._originalBodyOverflow = document.body.style.overflow), document.body.style.overflow = "hidden";
|
|
36
27
|
}
|
|
@@ -46,9 +37,9 @@ const n = (i = class extends g {
|
|
|
46
37
|
async _restorePrevFocused() {
|
|
47
38
|
const t = this._prevFocusedElement;
|
|
48
39
|
if (t && t.tagName.toLowerCase() === "nys-button") {
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
|
|
40
|
+
const n = await t.getButtonElement();
|
|
41
|
+
if (n) {
|
|
42
|
+
n.focus();
|
|
52
43
|
return;
|
|
53
44
|
}
|
|
54
45
|
} else
|
|
@@ -77,8 +68,8 @@ const n = (i = class extends g {
|
|
|
77
68
|
if (!this._actionButtonSlot) return;
|
|
78
69
|
const t = window.innerWidth <= 480;
|
|
79
70
|
this._actionButtonSlot.assignedElements().forEach((o) => {
|
|
80
|
-
o.querySelectorAll("nys-button").forEach((
|
|
81
|
-
t ?
|
|
71
|
+
o.querySelectorAll("nys-button").forEach((n) => {
|
|
72
|
+
t ? n?.setAttribute("fullWidth", "") : n?.removeAttribute("fullWidth");
|
|
82
73
|
});
|
|
83
74
|
});
|
|
84
75
|
}
|
|
@@ -114,34 +105,34 @@ const n = (i = class extends g {
|
|
|
114
105
|
t.setAttribute("ariaLabel", "Close this window");
|
|
115
106
|
}, 100));
|
|
116
107
|
}
|
|
117
|
-
|
|
108
|
+
// Event Handlers
|
|
118
109
|
async _handleKeydown(t) {
|
|
119
110
|
if (this.open && (t.key === "Escape" && !this.mandatory && (t.preventDefault(), this._closeModal()), t.key === "Tab")) {
|
|
120
111
|
const o = this.shadowRoot?.querySelector(".nys-modal");
|
|
121
112
|
if (!o) return;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
for (const
|
|
126
|
-
const
|
|
127
|
-
for (const
|
|
128
|
-
|
|
129
|
-
(
|
|
130
|
-
e.push(
|
|
113
|
+
const n = 'a[href], area[href], button:not([disabled]), details, iframe, object, input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contentEditable="true"], [tabindex]:not([tabindex^="-"])', e = [], l = o.querySelector("nys-button");
|
|
114
|
+
l && e.push(l);
|
|
115
|
+
const c = Array.from(o.querySelectorAll("slot"));
|
|
116
|
+
for (const r of c) {
|
|
117
|
+
const y = r.assignedElements({ flatten: !0 });
|
|
118
|
+
for (const a of y)
|
|
119
|
+
a instanceof HTMLElement && a.matches(n) && e.push(a), a.querySelectorAll("nys-button").forEach(
|
|
120
|
+
(u) => {
|
|
121
|
+
e.push(u);
|
|
131
122
|
}
|
|
132
123
|
);
|
|
133
124
|
}
|
|
134
125
|
if (e.length > 0) {
|
|
135
|
-
const
|
|
136
|
-
let
|
|
126
|
+
const r = e[0], y = e[e.length - 1];
|
|
127
|
+
let a = document.activeElement, u = e.indexOf(a);
|
|
137
128
|
if (t.shiftKey) {
|
|
138
129
|
t.preventDefault();
|
|
139
|
-
let
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
e[
|
|
130
|
+
let h = u - 1;
|
|
131
|
+
h < 0 && (h = e.length - 1);
|
|
132
|
+
const m = e[h];
|
|
133
|
+
e[h].tagName.toLowerCase() === "nys-button" ? (await m.getButtonElement())?.focus() : m.focus();
|
|
143
134
|
} else
|
|
144
|
-
|
|
135
|
+
a === y && (t.preventDefault(), r.tagName.toLowerCase() === "nys-button" ? (await r.getButtonElement())?.focus() : r.focus());
|
|
145
136
|
}
|
|
146
137
|
}
|
|
147
138
|
}
|
|
@@ -149,7 +140,7 @@ const n = (i = class extends g {
|
|
|
149
140
|
this.open = !1, this._dispatchCloseEvent();
|
|
150
141
|
}
|
|
151
142
|
render() {
|
|
152
|
-
return this.open ?
|
|
143
|
+
return this.open ? f`<div
|
|
153
144
|
class="nys-modal-overlay"
|
|
154
145
|
role="dialog"
|
|
155
146
|
aria-modal="true"
|
|
@@ -160,7 +151,7 @@ const n = (i = class extends g {
|
|
|
160
151
|
<div class="nys-modal_header">
|
|
161
152
|
<div class="nys-modal_header-inner">
|
|
162
153
|
<h2 id="${this.id}-heading">${this.heading}</h2>
|
|
163
|
-
${this.mandatory ? "" :
|
|
154
|
+
${this.mandatory ? "" : f`<nys-button
|
|
164
155
|
id="dismiss-modal"
|
|
165
156
|
circle
|
|
166
157
|
icon="close"
|
|
@@ -168,7 +159,7 @@ const n = (i = class extends g {
|
|
|
168
159
|
@nys-click=${this._closeModal}
|
|
169
160
|
></nys-button>`}
|
|
170
161
|
</div>
|
|
171
|
-
${this.subheading ?
|
|
162
|
+
${this.subheading ? f`<p id="${this.id}-subheading">${this.subheading}</p>` : ""}
|
|
172
163
|
</div>
|
|
173
164
|
|
|
174
165
|
<div
|
|
@@ -191,34 +182,35 @@ const n = (i = class extends g {
|
|
|
191
182
|
</div>
|
|
192
183
|
</div>` : "";
|
|
193
184
|
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
185
|
+
};
|
|
186
|
+
p.styles = b(w);
|
|
187
|
+
let s = p;
|
|
188
|
+
i([
|
|
189
|
+
d({ type: String, reflect: !0 })
|
|
190
|
+
], s.prototype, "id");
|
|
191
|
+
i([
|
|
192
|
+
d({ type: String })
|
|
193
|
+
], s.prototype, "heading");
|
|
194
|
+
i([
|
|
195
|
+
d({ type: String })
|
|
196
|
+
], s.prototype, "subheading");
|
|
197
|
+
i([
|
|
198
|
+
d({ type: Boolean, reflect: !0 })
|
|
199
|
+
], s.prototype, "open");
|
|
200
|
+
i([
|
|
201
|
+
d({ type: Boolean, reflect: !0 })
|
|
202
|
+
], s.prototype, "mandatory");
|
|
203
|
+
i([
|
|
204
|
+
d({ type: String, reflect: !0 })
|
|
205
|
+
], s.prototype, "width");
|
|
206
|
+
i([
|
|
214
207
|
_()
|
|
215
|
-
],
|
|
216
|
-
|
|
208
|
+
], s.prototype, "hasBodySlots");
|
|
209
|
+
i([
|
|
217
210
|
_()
|
|
218
|
-
],
|
|
219
|
-
|
|
220
|
-
customElements.get("nys-modal") || customElements.define("nys-modal", S);
|
|
211
|
+
], s.prototype, "hasActionSlots");
|
|
212
|
+
customElements.get("nys-modal") || customElements.define("nys-modal", s);
|
|
221
213
|
export {
|
|
222
|
-
|
|
214
|
+
s as NysModal
|
|
223
215
|
};
|
|
224
216
|
//# sourceMappingURL=nys-modal.js.map
|
package/dist/nys-modal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nys-modal.js","sources":["../src/nys-modal.ts"],"sourcesContent":["import { LitElement, html, unsafeCSS } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\n// @ts-ignore: SCSS module imported via bundler as inline\nimport styles from \"./nys-modal.scss?inline\";\n\nlet componentIdCounter = 0; // Counter for generating unique IDs\n\nexport class NysModal extends LitElement {\n static styles = unsafeCSS(styles);\n\n @property({ type: String, reflect: true }) id = \"\";\n @property({ type: String }) heading = \"\";\n @property({ type: String }) subheading = \"\";\n @property({ type: Boolean, reflect: true }) open = false;\n @property({ type: Boolean, reflect: true }) mandatory = false;\n\n private static readonly VALID_WIDTHS = [\"sm\", \"md\", \"lg\"] as const;\n private _width: (typeof NysModal.VALID_WIDTHS)[number] = \"md\";\n @property({ reflect: true })\n get width(): (typeof NysModal.VALID_WIDTHS)[number] {\n return this._width;\n }\n set width(value: string) {\n this._width = NysModal.VALID_WIDTHS.includes(\n value as (typeof NysModal.VALID_WIDTHS)[number],\n )\n ? (value as (typeof NysModal.VALID_WIDTHS)[number])\n : \"md\";\n }\n\n private _actionButtonSlot: HTMLSlotElement | null = null; // cache action button slots (if given) so we can manipulate their widths for mobile vs desktop\n private _prevFocusedElement: HTMLElement | null = null;\n private _originalBodyOverflow: string | null = null;\n\n // Track slot contents to control what HTML is rendered\n @state() private hasBodySlots = false;\n @state() private hasActionSlots = false;\n\n /**************** Lifecycle Methods ****************/\n constructor() {\n super();\n }\n\n connectedCallback() {\n super.connectedCallback();\n if (!this.id) {\n this.id = `nys-{{componentName}}-${Date.now()}-${componentIdCounter++}`;\n }\n window.addEventListener(\"resize\", () => this._updateSlottedButtonWidth());\n window.addEventListener(\"keydown\", (e) => this._handleKeydown(e));\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this._restoreBodyScroll(); // make sure scroll is restored when modal is removed\n window.removeEventListener(\"keydown\", (e) => this._handleKeydown(e));\n }\n\n async updated(changeProps: Map<string, any>) {\n // Hide main body's scroll bar if modal is open/active\n if (changeProps.has(\"open\")) {\n if (this.open) {\n this._hideBodyScroll();\n this._dispatchOpenEvent();\n await this.updateComplete;\n this._savePrevFocused();\n this._focusOnModal();\n this._updateDismissAria();\n } else {\n this._restorePrevFocused();\n this._restoreBodyScroll();\n this._dispatchCloseEvent();\n this._updateDismissAria();\n }\n }\n }\n\n /******************** Functions ********************/\n private _hideBodyScroll() {\n if (this._originalBodyOverflow === null) {\n this._originalBodyOverflow = document.body.style.overflow;\n }\n document.body.style.overflow = \"hidden\";\n }\n\n private _restoreBodyScroll() {\n if (this._originalBodyOverflow !== null) {\n document.body.style.overflow = this._originalBodyOverflow;\n this._originalBodyOverflow = null;\n }\n }\n\n private _savePrevFocused() {\n this._prevFocusedElement = document.activeElement as HTMLElement;\n }\n\n private _focusOnModal() {\n const modal = this.shadowRoot?.querySelector<HTMLElement>(\".nys-modal\");\n modal?.focus();\n }\n\n private async _restorePrevFocused() {\n const prev = this._prevFocusedElement;\n\n if (prev && prev.tagName.toLowerCase() === \"nys-button\") {\n const nysButton = prev as HTMLElement & {\n getButtonElement: () => Promise<HTMLButtonElement>;\n };\n const innerBtn = await nysButton.getButtonElement();\n if (innerBtn) {\n innerBtn.focus();\n return;\n }\n } else {\n this._prevFocusedElement?.focus();\n }\n this._prevFocusedElement = null;\n }\n\n // Check if the slot contains stuff (aka user add texts & action buttons), and render visibility accordingly\n private async _handleBodySlotChange() {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(\"slot\");\n if (!slot) return;\n this.hasBodySlots = slot\n .assignedNodes({ flatten: true })\n .some(\n (node) =>\n node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim(),\n );\n }\n\n // Determines whether we hide the action buttons slot container based on if user put in action buttons\n private async _handleActionSlotChange() {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(\n 'slot[name=\"actions\"]',\n );\n if (!slot) return;\n this.hasActionSlots = slot\n .assignedNodes({ flatten: true })\n .some(\n (node) =>\n node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim(),\n );\n\n // Cached the action button slot container so we can use it continuously for _updateSlottedButtonWidth() during screen resize\n this._actionButtonSlot = slot;\n // Update button widths immediately\n this._updateSlottedButtonWidth();\n }\n\n // Design has it that the slotted action buttons should be fullWidth and display:column direction for mobile view.\n // Therefore, we need to account for mobile size and screen resizes\n private _updateSlottedButtonWidth() {\n if (!this._actionButtonSlot) return; // use the cached variable\n const isMobile = window.innerWidth <= 480;\n\n this._actionButtonSlot.assignedElements().forEach((el) => {\n el.querySelectorAll(\"nys-button\").forEach((btn) => {\n if (isMobile) {\n btn?.setAttribute(\"fullWidth\", \"\");\n } else {\n btn?.removeAttribute(\"fullWidth\");\n }\n });\n });\n }\n\n private _dispatchOpenEvent() {\n this.dispatchEvent(\n new CustomEvent(\"nys-open\", {\n detail: { id: this.id },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private _dispatchCloseEvent() {\n this.dispatchEvent(\n new CustomEvent(\"nys-close\", {\n detail: { id: this.id },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private _getAriaDescribedBy() {\n // Handling what aria-describedby needs to announce in VO based on if the subheading or slot contents exists\n const ariaDescriptions: string[] = [];\n if (this.subheading) {\n ariaDescriptions.push(`${this.id}-subheading`);\n }\n if (this.hasBodySlots) {\n ariaDescriptions.push(`${this.id}-desc`);\n }\n return ariaDescriptions.join(\" \");\n }\n\n /**\n * This exist to prevent the VO for dismiss button from announcing itself between the heading & subheading/slot content.\n * We add the \"Close this window\" ariaLabel after the initial VO is done\n */\n private _updateDismissAria() {\n const dismissBtn = this.shadowRoot?.querySelector(\"nys-button\");\n if (!dismissBtn) return;\n\n // Hide from VO initially\n dismissBtn.setAttribute(\"ariaLabel\", \" \");\n\n if (this.open) {\n // After focus is moved into modal, update label\n setTimeout(() => {\n dismissBtn.setAttribute(\"ariaLabel\", \"Close this window\");\n }, 100);\n }\n }\n\n /****************** Event Handlers ******************/\n private async _handleKeydown(e: KeyboardEvent) {\n if (!this.open) return;\n\n /** Exit the modal for \"escape\" key **/\n if (e.key === \"Escape\" && !this.mandatory) {\n e.preventDefault();\n this._closeModal();\n }\n\n /** Trap focus to be within the modal only **/\n if (e.key === \"Tab\") {\n const modal = this.shadowRoot?.querySelector(\".nys-modal\");\n if (!modal) return;\n\n // Gather all elements from slots + dismissible btn\n const knownFocusableElements =\n 'a[href], area[href], button:not([disabled]), details, iframe, object, input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contentEditable=\"true\"], [tabindex]:not([tabindex^=\"-\"])';\n const focusableElements: HTMLElement[] = [];\n const dismissBtn = modal.querySelector(\"nys-button\") as HTMLElement;\n\n if (dismissBtn) {\n focusableElements.push(dismissBtn);\n }\n\n // Gather from slot elements to store the focusable elements in focusableElements for focus trapping\n const slotElements = Array.from(modal.querySelectorAll(\"slot\"));\n for (const slot of slotElements) {\n const assigned = slot.assignedElements({ flatten: true });\n for (const el of assigned) {\n if (el instanceof HTMLElement && el.matches(knownFocusableElements)) {\n focusableElements.push(el);\n }\n // also account for the action slot container that has nys-buttons\n el.querySelectorAll<HTMLElement>(\"nys-button\").forEach(\n (actionBtn) => {\n focusableElements.push(actionBtn);\n },\n );\n }\n }\n\n if (focusableElements.length > 0) {\n // Laying out the starting (i.e. dismiss btn) and ending elements for looping focus elements\n const firstFocusableEl = focusableElements[0];\n const lastFocusableEl = focusableElements[focusableElements.length - 1];\n let active = document.activeElement as HTMLElement | null;\n let activeIndex = focusableElements.indexOf(active as HTMLElement);\n\n /**\n * Move focus backward when Shift+Tab is pressed.\n * Focus goes to the previous element in focusableElements.\n * If currently at the first element, wrap around to the last element.\n * For <nys-button>, focus the internal button. For other elements, focus directly.\n */\n if (e.shiftKey) {\n e.preventDefault();\n\n let prevIndex = activeIndex - 1;\n if (prevIndex < 0) {\n prevIndex = focusableElements.length - 1; // wrap back to lastFocusableEl\n }\n\n const prevElement = focusableElements[prevIndex];\n const isNysButton =\n focusableElements[prevIndex].tagName.toLowerCase() === \"nys-button\";\n\n // When users slot in \"nys-button\", we have to call focus for the native button within its shadowDOM.\n // Hence the condition statement below\n if (isNysButton) {\n const nysButton = prevElement as HTMLElement & {\n getButtonElement: () => Promise<HTMLButtonElement>;\n };\n const innerBtn = await nysButton.getButtonElement();\n innerBtn?.focus();\n } else {\n prevElement.focus();\n }\n } else {\n // Tab (go back to first focusable element if we're at last)\n if (active === lastFocusableEl) {\n e.preventDefault();\n if (firstFocusableEl.tagName.toLowerCase() === \"nys-button\") {\n const nysButton = firstFocusableEl as HTMLElement & {\n getButtonElement: () => Promise<HTMLButtonElement>;\n };\n const innerBtn = await nysButton.getButtonElement();\n innerBtn?.focus();\n } else {\n firstFocusableEl.focus();\n }\n }\n }\n }\n }\n }\n\n private _closeModal() {\n this.open = false;\n this._dispatchCloseEvent();\n }\n\n render() {\n return this.open\n ? html`<div\n class=\"nys-modal-overlay\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"${this.id}-heading\"\n aria-describedby=\"${this._getAriaDescribedBy()}\"\n >\n <div class=\"nys-modal\" tabindex=\"-1\">\n <div class=\"nys-modal_header\">\n <div class=\"nys-modal_header-inner\">\n <h2 id=\"${this.id}-heading\">${this.heading}</h2>\n ${!this.mandatory\n ? html`<nys-button\n id=\"dismiss-modal\"\n circle\n icon=\"close\"\n variant=\"ghost\"\n @nys-click=${this._closeModal}\n ></nys-button>`\n : \"\"}\n </div>\n ${this.subheading\n ? html`<p id=\"${this.id}-subheading\">${this.subheading}</p>`\n : \"\"}\n </div>\n\n <div\n id=\"${this.id}-desc\"\n class=\"nys-modal_body ${!this.hasBodySlots ? \"hidden\" : \"\"}\"\n >\n <div class=\"nys-modal_body-inner\">\n <slot @slotchange=${this._handleBodySlotChange}></slot>\n </div>\n </div>\n\n <div\n class=\"nys-modal_footer ${!this.hasActionSlots ? \"hidden\" : \"\"}\"\n >\n <slot\n name=\"actions\"\n @slotchange=${this._handleActionSlotChange}\n ></slot>\n </div>\n </div>\n </div>`\n : \"\";\n }\n}\n\nif (!customElements.get(\"nys-modal\")) {\n customElements.define(\"nys-modal\", NysModal);\n}\n"],"names":["componentIdCounter","_NysModal","_a","LitElement","value","e","changeProps","prev","innerBtn","slot","node","isMobile","el","btn","ariaDescriptions","dismissBtn","modal","knownFocusableElements","focusableElements","slotElements","assigned","actionBtn","firstFocusableEl","lastFocusableEl","active","activeIndex","prevIndex","prevElement","html","unsafeCSS","styles","__decorateClass","property","state","NysModal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nys-modal.js","sources":["../src/nys-modal.ts"],"sourcesContent":["import { LitElement, html, unsafeCSS } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\n// @ts-ignore: SCSS module imported via bundler as inline\nimport styles from \"./nys-modal.scss?inline\";\n\nlet componentIdCounter = 0; // Counter for generating unique IDs\n\nexport class NysModal extends LitElement {\n static styles = unsafeCSS(styles);\n\n @property({ type: String, reflect: true }) id = \"\";\n @property({ type: String }) heading = \"\";\n @property({ type: String }) subheading = \"\";\n @property({ type: Boolean, reflect: true }) open = false;\n @property({ type: Boolean, reflect: true }) mandatory = false;\n @property({ type: String, reflect: true }) width: \"sm\" | \"md\" | \"lg\" = \"md\";\n\n private _actionButtonSlot: HTMLSlotElement | null = null; // cache action button slots (if given) so we can manipulate their widths for mobile vs desktop\n private _prevFocusedElement: HTMLElement | null = null;\n private _originalBodyOverflow: string | null = null;\n\n // Track slot contents to control what HTML is rendered\n @state() private hasBodySlots = false;\n @state() private hasActionSlots = false;\n\n // Lifecycle Methods\n constructor() {\n super();\n }\n\n connectedCallback() {\n super.connectedCallback();\n if (!this.id) {\n this.id = `nys-{{componentName}}-${Date.now()}-${componentIdCounter++}`;\n }\n window.addEventListener(\"resize\", () => this._updateSlottedButtonWidth());\n window.addEventListener(\"keydown\", (e) => this._handleKeydown(e));\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this._restoreBodyScroll(); // make sure scroll is restored when modal is removed\n window.removeEventListener(\"keydown\", (e) => this._handleKeydown(e));\n }\n\n async updated(changeProps: Map<string, any>) {\n // Hide main body's scroll bar if modal is open/active\n if (changeProps.has(\"open\")) {\n if (this.open) {\n this._hideBodyScroll();\n this._dispatchOpenEvent();\n await this.updateComplete;\n this._savePrevFocused();\n this._focusOnModal();\n this._updateDismissAria();\n } else {\n this._restorePrevFocused();\n this._restoreBodyScroll();\n this._dispatchCloseEvent();\n this._updateDismissAria();\n }\n }\n }\n\n // Functions\n private _hideBodyScroll() {\n if (this._originalBodyOverflow === null) {\n this._originalBodyOverflow = document.body.style.overflow;\n }\n document.body.style.overflow = \"hidden\";\n }\n\n private _restoreBodyScroll() {\n if (this._originalBodyOverflow !== null) {\n document.body.style.overflow = this._originalBodyOverflow;\n this._originalBodyOverflow = null;\n }\n }\n\n private _savePrevFocused() {\n this._prevFocusedElement = document.activeElement as HTMLElement;\n }\n\n private _focusOnModal() {\n const modal = this.shadowRoot?.querySelector<HTMLElement>(\".nys-modal\");\n modal?.focus();\n }\n\n private async _restorePrevFocused() {\n const prev = this._prevFocusedElement;\n\n if (prev && prev.tagName.toLowerCase() === \"nys-button\") {\n const nysButton = prev as HTMLElement & {\n getButtonElement: () => Promise<HTMLButtonElement>;\n };\n const innerBtn = await nysButton.getButtonElement();\n if (innerBtn) {\n innerBtn.focus();\n return;\n }\n } else {\n this._prevFocusedElement?.focus();\n }\n this._prevFocusedElement = null;\n }\n\n // Check if the slot contains stuff (aka user add texts & action buttons), and render visibility accordingly\n private async _handleBodySlotChange() {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(\"slot\");\n if (!slot) return;\n this.hasBodySlots = slot\n .assignedNodes({ flatten: true })\n .some(\n (node) =>\n node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim(),\n );\n }\n\n // Determines whether we hide the action buttons slot container based on if user put in action buttons\n private async _handleActionSlotChange() {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(\n 'slot[name=\"actions\"]',\n );\n if (!slot) return;\n this.hasActionSlots = slot\n .assignedNodes({ flatten: true })\n .some(\n (node) =>\n node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim(),\n );\n\n // Cached the action button slot container so we can use it continuously for _updateSlottedButtonWidth() during screen resize\n this._actionButtonSlot = slot;\n // Update button widths immediately\n this._updateSlottedButtonWidth();\n }\n\n // Design has it that the slotted action buttons should be fullWidth and display:column direction for mobile view.\n // Therefore, we need to account for mobile size and screen resizes\n private _updateSlottedButtonWidth() {\n if (!this._actionButtonSlot) return; // use the cached variable\n const isMobile = window.innerWidth <= 480;\n\n this._actionButtonSlot.assignedElements().forEach((el) => {\n el.querySelectorAll(\"nys-button\").forEach((btn) => {\n if (isMobile) {\n btn?.setAttribute(\"fullWidth\", \"\");\n } else {\n btn?.removeAttribute(\"fullWidth\");\n }\n });\n });\n }\n\n private _dispatchOpenEvent() {\n this.dispatchEvent(\n new CustomEvent(\"nys-open\", {\n detail: { id: this.id },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private _dispatchCloseEvent() {\n this.dispatchEvent(\n new CustomEvent(\"nys-close\", {\n detail: { id: this.id },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private _getAriaDescribedBy() {\n // Handling what aria-describedby needs to announce in VO based on if the subheading or slot contents exists\n const ariaDescriptions: string[] = [];\n if (this.subheading) {\n ariaDescriptions.push(`${this.id}-subheading`);\n }\n if (this.hasBodySlots) {\n ariaDescriptions.push(`${this.id}-desc`);\n }\n return ariaDescriptions.join(\" \");\n }\n\n /**\n * This exist to prevent the VO for dismiss button from announcing itself between the heading & subheading/slot content.\n * We add the \"Close this window\" ariaLabel after the initial VO is done\n */\n private _updateDismissAria() {\n const dismissBtn = this.shadowRoot?.querySelector(\"nys-button\");\n if (!dismissBtn) return;\n\n // Hide from VO initially\n dismissBtn.setAttribute(\"ariaLabel\", \" \");\n\n if (this.open) {\n // After focus is moved into modal, update label\n setTimeout(() => {\n dismissBtn.setAttribute(\"ariaLabel\", \"Close this window\");\n }, 100);\n }\n }\n\n // Event Handlers\n private async _handleKeydown(e: KeyboardEvent) {\n if (!this.open) return;\n\n // Exit the modal for \"escape\" key\n if (e.key === \"Escape\" && !this.mandatory) {\n e.preventDefault();\n this._closeModal();\n }\n\n // Trap focus to be within the modal only\n if (e.key === \"Tab\") {\n const modal = this.shadowRoot?.querySelector(\".nys-modal\");\n if (!modal) return;\n\n // Gather all elements from slots + dismissible btn\n const knownFocusableElements =\n 'a[href], area[href], button:not([disabled]), details, iframe, object, input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contentEditable=\"true\"], [tabindex]:not([tabindex^=\"-\"])';\n const focusableElements: HTMLElement[] = [];\n const dismissBtn = modal.querySelector(\"nys-button\") as HTMLElement;\n\n if (dismissBtn) {\n focusableElements.push(dismissBtn);\n }\n\n // Gather from slot elements to store the focusable elements in focusableElements for focus trapping\n const slotElements = Array.from(modal.querySelectorAll(\"slot\"));\n for (const slot of slotElements) {\n const assigned = slot.assignedElements({ flatten: true });\n for (const el of assigned) {\n if (el instanceof HTMLElement && el.matches(knownFocusableElements)) {\n focusableElements.push(el);\n }\n // also account for the action slot container that has nys-buttons\n el.querySelectorAll<HTMLElement>(\"nys-button\").forEach(\n (actionBtn) => {\n focusableElements.push(actionBtn);\n },\n );\n }\n }\n\n if (focusableElements.length > 0) {\n // Laying out the starting (i.e. dismiss btn) and ending elements for looping focus elements\n const firstFocusableEl = focusableElements[0];\n const lastFocusableEl = focusableElements[focusableElements.length - 1];\n let active = document.activeElement as HTMLElement | null;\n let activeIndex = focusableElements.indexOf(active as HTMLElement);\n\n /**\n * Move focus backward when Shift+Tab is pressed.\n * Focus goes to the previous element in focusableElements.\n * If currently at the first element, wrap around to the last element.\n * For <nys-button>, focus the internal button. For other elements, focus directly.\n */\n if (e.shiftKey) {\n e.preventDefault();\n\n let prevIndex = activeIndex - 1;\n if (prevIndex < 0) {\n prevIndex = focusableElements.length - 1; // wrap back to lastFocusableEl\n }\n\n const prevElement = focusableElements[prevIndex];\n const isNysButton =\n focusableElements[prevIndex].tagName.toLowerCase() === \"nys-button\";\n\n // When users slot in \"nys-button\", we have to call focus for the native button within its shadowDOM.\n // Hence the condition statement below\n if (isNysButton) {\n const nysButton = prevElement as HTMLElement & {\n getButtonElement: () => Promise<HTMLButtonElement>;\n };\n const innerBtn = await nysButton.getButtonElement();\n innerBtn?.focus();\n } else {\n prevElement.focus();\n }\n } else {\n // Tab (go back to first focusable element if we're at last)\n if (active === lastFocusableEl) {\n e.preventDefault();\n if (firstFocusableEl.tagName.toLowerCase() === \"nys-button\") {\n const nysButton = firstFocusableEl as HTMLElement & {\n getButtonElement: () => Promise<HTMLButtonElement>;\n };\n const innerBtn = await nysButton.getButtonElement();\n innerBtn?.focus();\n } else {\n firstFocusableEl.focus();\n }\n }\n }\n }\n }\n }\n\n private _closeModal() {\n this.open = false;\n this._dispatchCloseEvent();\n }\n\n render() {\n return this.open\n ? html`<div\n class=\"nys-modal-overlay\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"${this.id}-heading\"\n aria-describedby=\"${this._getAriaDescribedBy()}\"\n >\n <div class=\"nys-modal\" tabindex=\"-1\">\n <div class=\"nys-modal_header\">\n <div class=\"nys-modal_header-inner\">\n <h2 id=\"${this.id}-heading\">${this.heading}</h2>\n ${!this.mandatory\n ? html`<nys-button\n id=\"dismiss-modal\"\n circle\n icon=\"close\"\n variant=\"ghost\"\n @nys-click=${this._closeModal}\n ></nys-button>`\n : \"\"}\n </div>\n ${this.subheading\n ? html`<p id=\"${this.id}-subheading\">${this.subheading}</p>`\n : \"\"}\n </div>\n\n <div\n id=\"${this.id}-desc\"\n class=\"nys-modal_body ${!this.hasBodySlots ? \"hidden\" : \"\"}\"\n >\n <div class=\"nys-modal_body-inner\">\n <slot @slotchange=${this._handleBodySlotChange}></slot>\n </div>\n </div>\n\n <div\n class=\"nys-modal_footer ${!this.hasActionSlots ? \"hidden\" : \"\"}\"\n >\n <slot\n name=\"actions\"\n @slotchange=${this._handleActionSlotChange}\n ></slot>\n </div>\n </div>\n </div>`\n : \"\";\n }\n}\n\nif (!customElements.get(\"nys-modal\")) {\n customElements.define(\"nys-modal\", NysModal);\n}\n"],"names":["componentIdCounter","_NysModal","LitElement","e","changeProps","prev","innerBtn","slot","node","isMobile","el","btn","ariaDescriptions","dismissBtn","modal","knownFocusableElements","focusableElements","slotElements","assigned","actionBtn","firstFocusableEl","lastFocusableEl","active","activeIndex","prevIndex","prevElement","html","unsafeCSS","styles","NysModal","__decorateClass","property","state"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nysds/nys-modal",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "The Modal component from the NYS Design System.",
|
|
5
5
|
"module": "dist/nys-modal.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vite build && wtr --watch"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@nysds/nys-button": "^1.
|
|
25
|
+
"@nysds/nys-button": "^1.12.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"lit": "^3.3.1",
|