@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 CHANGED
@@ -1,28 +1,19 @@
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
+ // Lifecycle Methods
13
12
  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";
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()}-${x++}`), window.addEventListener("resize", () => this._updateSlottedButtonWidth()), window.addEventListener("keydown", (t) => this._handleKeydown(t));
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
- /******************** Functions ********************/
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 s = await t.getButtonElement();
50
- if (s) {
51
- s.focus();
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((s) => {
81
- t ? s?.setAttribute("fullWidth", "") : s?.removeAttribute("fullWidth");
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
- /****************** Event Handlers ******************/
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 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);
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 c = e[0], u = e[e.length - 1];
136
- let l = document.activeElement, m = e.indexOf(l);
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 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();
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
- l === u && (t.preventDefault(), c.tagName.toLowerCase() === "nys-button" ? (await c.getButtonElement())?.focus() : c.focus());
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 ? p`<div
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 ? "" : p`<nys-button
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 ? p`<p id="${this.id}-subheading">${this.subheading}</p>` : ""}
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
- }, 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([
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
- ], n.prototype, "hasBodySlots", 2);
216
- a([
208
+ ], s.prototype, "hasBodySlots");
209
+ i([
217
210
  _()
218
- ], n.prototype, "hasActionSlots", 2);
219
- let S = n;
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
- S as NysModal
214
+ s as NysModal
223
215
  };
224
216
  //# 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; // 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.11.4",
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.11.4"
25
+ "@nysds/nys-button": "^1.12.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "lit": "^3.3.1",