@nysds/nys-modal 1.11.4 → 1.13.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 CHANGED
@@ -1,28 +1,22 @@
1
- import { LitElement as g, unsafeCSS as b, html as p } from "lit";
2
- import { property as r, state as _ } from "lit/decorators.js";
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, E = Object.getOwnPropertyDescriptor, a = (v, t, o, s) => {
5
- for (var e = s > 1 ? void 0 : s ? E(t, o) : t, d = v.length - 1, h; d >= 0; d--)
6
- (h = v[d]) && (e = (s ? h(t, o, e) : h(e)) || e);
7
- return s && e && B(t, o, e), e;
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 x = 0;
10
- var i;
11
- const n = (i = class extends g {
12
- /**************** Lifecycle Methods ****************/
9
+ let E = 0;
10
+ const p = class p extends g {
11
+ /**
12
+ * Lifecycle Methods
13
+ * --------------------------------------------------------------------------
14
+ */
13
15
  constructor() {
14
- 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;
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";
16
+ 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
17
  }
24
18
  connectedCallback() {
25
- super.connectedCallback(), this.id || (this.id = `nys-{{componentName}}-${Date.now()}-${x++}`), window.addEventListener("resize", () => this._updateSlottedButtonWidth()), window.addEventListener("keydown", (t) => this._handleKeydown(t));
19
+ super.connectedCallback(), this.id || (this.id = `nys-{{componentName}}-${Date.now()}-${E++}`), window.addEventListener("resize", () => this._updateSlottedButtonWidth()), window.addEventListener("keydown", (t) => this._handleKeydown(t));
26
20
  }
27
21
  disconnectedCallback() {
28
22
  super.disconnectedCallback(), this._restoreBodyScroll(), window.removeEventListener("keydown", (t) => this._handleKeydown(t));
@@ -30,7 +24,10 @@ const n = (i = class extends g {
30
24
  async updated(t) {
31
25
  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
26
  }
33
- /******************** Functions ********************/
27
+ /**
28
+ * Functions
29
+ * --------------------------------------------------------------------------
30
+ */
34
31
  _hideBodyScroll() {
35
32
  this._originalBodyOverflow === null && (this._originalBodyOverflow = document.body.style.overflow), document.body.style.overflow = "hidden";
36
33
  }
@@ -46,9 +43,9 @@ const n = (i = class extends g {
46
43
  async _restorePrevFocused() {
47
44
  const t = this._prevFocusedElement;
48
45
  if (t && t.tagName.toLowerCase() === "nys-button") {
49
- const s = await t.getButtonElement();
50
- if (s) {
51
- s.focus();
46
+ const n = await t.getButtonElement();
47
+ if (n) {
48
+ n.focus();
52
49
  return;
53
50
  }
54
51
  } else
@@ -77,8 +74,8 @@ const n = (i = class extends g {
77
74
  if (!this._actionButtonSlot) return;
78
75
  const t = window.innerWidth <= 480;
79
76
  this._actionButtonSlot.assignedElements().forEach((o) => {
80
- o.querySelectorAll("nys-button").forEach((s) => {
81
- t ? s?.setAttribute("fullWidth", "") : s?.removeAttribute("fullWidth");
77
+ o.querySelectorAll("nys-button").forEach((n) => {
78
+ t ? n?.setAttribute("fullWidth", "") : n?.removeAttribute("fullWidth");
82
79
  });
83
80
  });
84
81
  }
@@ -114,34 +111,37 @@ const n = (i = class extends g {
114
111
  t.setAttribute("ariaLabel", "Close this window");
115
112
  }, 100));
116
113
  }
117
- /****************** Event Handlers ******************/
114
+ /**
115
+ * Event Handlers
116
+ * --------------------------------------------------------------------------
117
+ */
118
118
  async _handleKeydown(t) {
119
119
  if (this.open && (t.key === "Escape" && !this.mandatory && (t.preventDefault(), this._closeModal()), t.key === "Tab")) {
120
120
  const o = this.shadowRoot?.querySelector(".nys-modal");
121
121
  if (!o) return;
122
- const s = '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 = [], d = o.querySelector("nys-button");
123
- d && e.push(d);
124
- const h = Array.from(o.querySelectorAll("slot"));
125
- for (const c of h) {
126
- const u = c.assignedElements({ flatten: !0 });
127
- for (const l of u)
128
- l instanceof HTMLElement && l.matches(s) && e.push(l), l.querySelectorAll("nys-button").forEach(
129
- (m) => {
130
- e.push(m);
122
+ 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");
123
+ l && e.push(l);
124
+ const c = Array.from(o.querySelectorAll("slot"));
125
+ for (const r of c) {
126
+ const y = r.assignedElements({ flatten: !0 });
127
+ for (const a of y)
128
+ a instanceof HTMLElement && a.matches(n) && e.push(a), a.querySelectorAll("nys-button").forEach(
129
+ (u) => {
130
+ e.push(u);
131
131
  }
132
132
  );
133
133
  }
134
134
  if (e.length > 0) {
135
- const c = e[0], u = e[e.length - 1];
136
- let l = document.activeElement, m = e.indexOf(l);
135
+ const r = e[0], y = e[e.length - 1];
136
+ let a = document.activeElement, u = e.indexOf(a);
137
137
  if (t.shiftKey) {
138
138
  t.preventDefault();
139
- let y = m - 1;
140
- y < 0 && (y = e.length - 1);
141
- const f = e[y];
142
- e[y].tagName.toLowerCase() === "nys-button" ? (await f.getButtonElement())?.focus() : f.focus();
139
+ let h = u - 1;
140
+ h < 0 && (h = e.length - 1);
141
+ const m = e[h];
142
+ e[h].tagName.toLowerCase() === "nys-button" ? (await m.getButtonElement())?.focus() : m.focus();
143
143
  } else
144
- l === u && (t.preventDefault(), c.tagName.toLowerCase() === "nys-button" ? (await c.getButtonElement())?.focus() : c.focus());
144
+ a === y && (t.preventDefault(), r.tagName.toLowerCase() === "nys-button" ? (await r.getButtonElement())?.focus() : r.focus());
145
145
  }
146
146
  }
147
147
  }
@@ -149,7 +149,7 @@ const n = (i = class extends g {
149
149
  this.open = !1, this._dispatchCloseEvent();
150
150
  }
151
151
  render() {
152
- return this.open ? p`<div
152
+ return this.open ? f`<div
153
153
  class="nys-modal-overlay"
154
154
  role="dialog"
155
155
  aria-modal="true"
@@ -160,7 +160,7 @@ const n = (i = class extends g {
160
160
  <div class="nys-modal_header">
161
161
  <div class="nys-modal_header-inner">
162
162
  <h2 id="${this.id}-heading">${this.heading}</h2>
163
- ${this.mandatory ? "" : p`<nys-button
163
+ ${this.mandatory ? "" : f`<nys-button
164
164
  id="dismiss-modal"
165
165
  circle
166
166
  icon="close"
@@ -168,7 +168,7 @@ const n = (i = class extends g {
168
168
  @nys-click=${this._closeModal}
169
169
  ></nys-button>`}
170
170
  </div>
171
- ${this.subheading ? p`<p id="${this.id}-subheading">${this.subheading}</p>` : ""}
171
+ ${this.subheading ? f`<p id="${this.id}-subheading">${this.subheading}</p>` : ""}
172
172
  </div>
173
173
 
174
174
  <div
@@ -191,34 +191,35 @@ const n = (i = class extends g {
191
191
  </div>
192
192
  </div>` : "";
193
193
  }
194
- }, i.styles = b(w), i.VALID_WIDTHS = ["sm", "md", "lg"], i);
195
- a([
196
- r({ type: String, reflect: !0 })
197
- ], n.prototype, "id", 2);
198
- a([
199
- r({ type: String })
200
- ], n.prototype, "heading", 2);
201
- a([
202
- r({ type: String })
203
- ], n.prototype, "subheading", 2);
204
- a([
205
- r({ type: Boolean, reflect: !0 })
206
- ], n.prototype, "open", 2);
207
- a([
208
- r({ type: Boolean, reflect: !0 })
209
- ], n.prototype, "mandatory", 2);
210
- a([
211
- r({ reflect: !0 })
212
- ], n.prototype, "width", 1);
213
- a([
194
+ };
195
+ p.styles = b(w);
196
+ let s = p;
197
+ i([
198
+ d({ type: String, reflect: !0 })
199
+ ], s.prototype, "id");
200
+ i([
201
+ d({ type: String })
202
+ ], s.prototype, "heading");
203
+ i([
204
+ d({ type: String })
205
+ ], s.prototype, "subheading");
206
+ i([
207
+ d({ type: Boolean, reflect: !0 })
208
+ ], s.prototype, "open");
209
+ i([
210
+ d({ type: Boolean, reflect: !0 })
211
+ ], s.prototype, "mandatory");
212
+ i([
213
+ d({ type: String, reflect: !0 })
214
+ ], s.prototype, "width");
215
+ i([
214
216
  _()
215
- ], n.prototype, "hasBodySlots", 2);
216
- a([
217
+ ], s.prototype, "hasBodySlots");
218
+ i([
217
219
  _()
218
- ], n.prototype, "hasActionSlots", 2);
219
- let S = n;
220
- customElements.get("nys-modal") || customElements.define("nys-modal", S);
220
+ ], s.prototype, "hasActionSlots");
221
+ customElements.get("nys-modal") || customElements.define("nys-modal", s);
221
222
  export {
222
- S as NysModal
223
+ s as NysModal
223
224
  };
224
225
  //# sourceMappingURL=nys-modal.js.map
@@ -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;\n\n/**\n * `<nys-modal>` renders an accessible modal dialog.\n *\n * Supports headings, optional subheading, body content, and action buttons.\n * Manages focus trapping, escape key handling, and body scroll locking.\n *\n * @slot - Modal body content\n * @slot actions - Action buttons shown in the footer\n *\n * @fires nys-open - Emitted when the modal opens\n * @fires nys-close - Emitted when the modal closes\n */\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 /**\n * Lifecycle Methods\n * --------------------------------------------------------------------------\n */\n\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 /**\n * Functions\n * --------------------------------------------------------------------------\n */\n\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 /**\n * Event Handlers\n * --------------------------------------------------------------------------\n */\n\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.11.4",
3
+ "version": "1.13.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.11.4"
25
+ "@nysds/nys-button": "^1.13.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "lit": "^3.3.1",