vanilla-aria-modals 1.0.3 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ All notable changes to this project will be documented in this file. Dates use ISO 8601 formatting, and version numbers follow [Semantic Versioning](https://semver.org/).
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
+
7
+ ## [1.1.1] - 2026-02-15
8
+ ### Fixed
9
+ - Fix typos, update changelog, and add an example wrapper usage for the `closeHandler`.
10
+
11
+ ## [1.1.0] - 2026-02-15
12
+ ### Added
13
+ - Support automatic modal key generation
14
+ - Add a manual method to rebind the trap focus handler
15
+
16
+ ### Changed
17
+ - Improve automation and simplify logic flow
18
+ - Improve event-bubbling guard by binding the overlay method with `capture: true` and removing the dependency on asynchronous behavior and `e.stopPropagation`
19
+ - Extend `closeHandler` method with a new `modalKey` parameter for caller usage
20
+
21
+ ### Fixed
22
+ - Prevent overlayless modals from breaking the stacking order
4
23
 
5
24
  ## [1.0.3] - 2026-01-23
6
25
  ### Fixed
package/README.md CHANGED
@@ -1,7 +1,10 @@
1
1
  # ModalHandler
2
2
 
3
+ See the full release history and updates in the [Changelog](https://github.com/angelvalentino/vanilla-aria-modals/blob/main/CHANGELOG.md).
4
+
3
5
  ## Introduction
4
6
 
7
+
5
8
  `ModalHandler` is a framework-agnostic utility for managing accessibility (A11y) events in modals or modal-like UIs within your web application. It supports modal stacking and key accessibility features, including focus trapping, focus management, and closing modals with the Escape key or an outside click.
6
9
 
7
10
  Although designed primarily for modal interactions, it can be used in any UI logic that requires basic ARIA support and focus management.
@@ -27,9 +30,9 @@ TypeScript types are used in the **docs** and in the **.d.ts** file to indicate
27
30
 
28
31
  A fully detailed example including the necessary JavaScript, HTML, and CSS files, can be found [here](https://github.com/angelvalentino/vanilla-aria-modals/tree/main/example).
29
32
 
30
- **Note:** `lm` in the code stands for *HTMLElement*.
33
+ > **Note:** The **closeHandler** receives both the original **DOM event** and the associated **modalKey** automatically. The caller can use these to inspect the event or determine which modal triggered the close.
31
34
 
32
- **Note:** `e.stopPropagation()` prevents other click events (like overlay clicks) from triggering while opening a modal. This can happen because when adding the open modal event, the ARIA events are also added during propagation and can trigger the overlay click event. It is already managed via the class with a timeout, but it is better for robustness to stop propagation here as well if bubbling is not needed in that instance.
35
+ **Note:** `lm` in the code stands for *HTMLElement*.
33
36
 
34
37
  ```js
35
38
  // Basic example of showing a modal
@@ -42,40 +45,31 @@ hideModal() {
42
45
  modalContainerLm.style.display = 'none';
43
46
  }
44
47
 
45
- closeModal() {
46
- hideModal();
47
- // ...Your hide UI logic
48
+ openModal() {
49
+ // Generate a key or use your own
50
+ const modalKey = modalHandler.generateKey();
48
51
 
49
- // Restore focus
50
- modalHandler.restoreFocus({
51
- modalKey: 'myModal'
52
- });
52
+ const closeModal = () => {
53
+ hideModal();
54
+ // ...Your hide UI logic
53
55
 
54
- // Remove ARIA events added
55
- modalHandler.removeA11yEvents({
56
- modalKey: 'myModal',
57
- modalLm: modalContentLm,
58
- closeLms: [...modalCloseBtns]
59
- });
60
- }
61
-
62
-
63
- openModal(e) {
64
- // Stop event propagation to make sure no events are called on bubbling
65
- e.stopPropagation();
56
+ // Restore focus
57
+ modalHandler.restoreFocus({ modalKey: modalKey });
58
+ // Remove ARIA events added
59
+ modalHandler.removeA11yEvents({ modalKey: modalKey });
60
+ }
66
61
 
67
62
  showModal();
68
63
  // ...Your show UI logic
69
64
 
70
65
  // Add focus
71
66
  modalHandler.addFocus({
72
- modalKey: 'myModal',
67
+ modalKey: modalKey,
73
68
  firstFocusableLm: modalFirstFocusableLm
74
69
  });
75
-
76
70
  // Add ARIA events
77
71
  modalHandler.addA11yEvents({
78
- modalKey: 'myModal',
72
+ modalKey: modalKey,
79
73
  modalLm: modalContentLm,
80
74
  modalLmOuterLimits: modalContentLm,
81
75
  closeLms: [...modalCloseBtns],
@@ -98,13 +92,14 @@ In Single Page Applications (SPA) or frameworks like React, Vue, or vanilla JS w
98
92
  ```js
99
93
  // Suppose your SPA route or component changes
100
94
  function onRouteChange() {
101
- // Clear leftover document events, active modals, and focus tracking
95
+ // Clear leftover document events, active modals, focus tracking and modal ID key counter
102
96
  modalHandler.reset();
103
97
 
104
98
  // Or individually:
105
99
  // modalHandler.clearDocumentBodyEvents();
106
100
  // modalHandler.clearActiveModals();
107
101
  // modalHandler.clearFocusRegistry();
102
+ // modalHandler.resetKeys();
108
103
  }
109
104
  ```
110
105
  <br>
@@ -134,7 +129,22 @@ Registers ARIA events and modal stacking handling:
134
129
  - **`modalLmOuterLimits?: HTMLElement | null;`** *(optional)* The container that defines the modal boundary. Used to detect clicks outside the modal. **modalLm** is usually used here, but depending on the UI we may not want to trap focus into the same container we want to close, maybe just in a part of it.
135
130
  - **`closeLms?: HTMLElement[] | null;`** *(optional)* Array of elements that should trigger closing the modal (e.g., close buttons).
136
131
  - **`exemptLms?: HTMLElement[];`** *(optional)* Array of elements that should not trigger closing even if clicked outside.
137
- - **`closeHandler: () => void;`** Function to call when the modal should close. Usually should call **removeA11yEvents()**.
132
+ - **`closeHandler: (e: Event, modalKey: string) => void;`** Function to call when the modal should close. Usually should call **removeA11yEvents()**. Automatically receives the event **(e)** and **(modalKey)** from the wrapper; no need to pass as arguments. It’s up to the caller whether to use them.
133
+ If you need to pass additional arguments to the close handler, you can wrap it in a function that returns a handler accepting only the two parameters (`e` and `modalKey`):
134
+
135
+ ```js
136
+ const closeModalWrapper = (...args) => {
137
+ // Returns a handler that the utility calls internally with e and modalKey
138
+ return (e, modalKey) => {
139
+ // Your logic for the close handler here
140
+ // Access to parameters thanks to closures
141
+ }
142
+ }
143
+
144
+ modalHandler.addA11yEvents({
145
+ closeHandler: closeModalWrapper(...args)
146
+ });
147
+ ```
138
148
 
139
149
  Returns `void`
140
150
 
@@ -146,8 +156,7 @@ Removes all accessibility and interaction event listeners for the specific regis
146
156
  **Takes parameters as a single object, which are destructured inside the method.**
147
157
 
148
158
  - **`modalKey: string;`** Unique modal identifier. Must match the modalKey used in **addA11yEvents()**.
149
- - **`modalLm?: HTMLElement | null;`** *(optional)* The main modal element. Used to be able to properly remove the trap focus event.
150
- - **`closeLms?: HTMLElement[] | null;`** *(optional)* Array of close elements used in **addA11yEvents()**. Used to be able to properly remove the close event from the given elements.
159
+ - **`isToggle?: boolean;`** Optional flag to indicate that the modal (no overlay, usually popups with toggle logic) is being closed via a toggle action (outside of the usual close handler). Setting this flag to **true** ensures the modal is properly removed from the active stack, even when called outside of any closing handler.
151
160
 
152
161
  Returns `void`
153
162
 
@@ -195,4 +204,25 @@ Returns `void`
195
204
  ### reset()
196
205
  Combines **clearDocumentBodyEvents()**, **clearActiveModals()**, **clearFocusRegistry()** for a full cleanup.
197
206
 
198
- Returns `void`
207
+ Returns `void`
208
+
209
+ ### generateKey()
210
+ Generates a unique identifier for the modal.
211
+
212
+ - **`prefix?: string;`** Optional prefix to modify the generated modal key.
213
+
214
+ Returns `string`. The generated modal key to be used later in the code.
215
+
216
+ ### resetKeys()
217
+
218
+ Resets the internal modal key counter back to 0.
219
+
220
+ Returns `void`
221
+
222
+ ### rebindTrapFocus()
223
+
224
+ Re-attaches the focus-trapping event listener for a specific modal. The internal **trapFocus** method already queries the DOM on each keyboard event, so in most cases, manually rebinding is not necessary.
225
+
226
+ - **`modalKey: string;`** The unique key of the modal whose focus trap should be rebound.
227
+
228
+ returns `void`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-aria-modals",
3
- "version": "1.0.3",
3
+ "version": "1.1.1",
4
4
  "description": "Framework-agnostic utility for managing accessibility in modals or modal-like UIs, including modal stacking, focus management, and closing via Escape key or outside click.",
5
5
  "main": "src/ModalHandler.js",
6
6
  "scripts": {
@@ -13,15 +13,18 @@
13
13
  "keywords": [
14
14
  "modal",
15
15
  "accessibility",
16
- "aria",
16
+ "ARIA",
17
+ "WAI-ARIA",
18
+ "A11y",
17
19
  "trap-focus",
20
+ "focus-trap",
18
21
  "keyboard",
19
22
  "dialog",
20
- "a11y",
21
23
  "vanilla-js",
22
- "ui",
23
- "frontend",
24
24
  "javascript",
25
+ "js",
26
+ "UI",
27
+ "frontend",
25
28
  "keyboard-navigation",
26
29
  "modal-manager",
27
30
  "focus-management",
@@ -11,6 +11,12 @@ export default class ModalHandler {
11
11
 
12
12
  reset(): void;
13
13
 
14
+ generateKey(prefix?: string): string;
15
+
16
+ resetKeys(): void;
17
+
18
+ rebindTrapFocus(modalKey: string): void;
19
+
14
20
  addA11yEvents(options: {
15
21
  modalKey: string;
16
22
  modalLm?: HTMLElement | null;
@@ -22,8 +28,7 @@ export default class ModalHandler {
22
28
 
23
29
  removeA11yEvents(options: {
24
30
  modalKey: string;
25
- modalLm?: HTMLElement | null;
26
- closeLms?: HTMLElement[] | null;
31
+ isToggle?: boolean
27
32
  }): void;
28
33
 
29
34
  addFocus(options: {
@@ -1,16 +1,18 @@
1
1
  export default class ModalHandler {
2
+ #debug;
2
3
  #eventsHandler;
3
- #activeModals;
4
4
  #focusHandler;
5
- #debug;
5
+ #activeModals;
6
+ #modalIdCounter
6
7
 
7
8
  constructor() {
8
9
  if (ModalHandler.instance) return ModalHandler.instance;
9
10
  ModalHandler.instance = this;
11
+ this.#debug = false;
10
12
  this.#eventsHandler = {};
11
- this.#activeModals = [];
12
13
  this.#focusHandler = {};
13
- this.#debug = false;
14
+ this.#activeModals = [];
15
+ this.#modalIdCounter = 0;
14
16
  }
15
17
 
16
18
  setDebug(bool) {
@@ -39,7 +41,7 @@ export default class ModalHandler {
39
41
  return true;
40
42
  }
41
43
 
42
- #unregisterModal(modalKey) {
44
+ #unregisterModal(modalKey, isOverlayLess) {
43
45
  if (!this.#activeModals.includes(modalKey)) {
44
46
  console.warn(`[ModalHandler]: Modal with key "${modalKey}" was not registered. Nothing to remove.`);
45
47
  return false;
@@ -49,13 +51,27 @@ export default class ModalHandler {
49
51
  console.log('[ModalHandler][DEBUG]: Active modal stack before filtering => ', this.#activeModals);
50
52
  }
51
53
 
52
- this.#activeModals = this.#activeModals.filter(key => key !== modalKey);
54
+ if (isOverlayLess) {
55
+ if (this.#debug) {
56
+ console.log('[ModalHandler][DEBUG]: Overlayless modal closed ignoring stacking order.');
57
+ }
58
+
59
+ this.#activeModals = this.#activeModals.filter(key => key !== modalKey);
60
+ }
61
+ else {
62
+ this.#activeModals.pop();
63
+ }
53
64
 
54
65
  if (this.#debug) {
55
66
  console.log(`[ModalHandler][DEBUG]: Unregister modal with key => "${modalKey}"`);
56
67
  console.log('[ModalHandler][DEBUG]: Active modal stack after filtering => ', this.#activeModals);
57
68
  }
58
69
 
70
+ if (this.#activeModals.length === 0) {
71
+ if (this.#debug) console.log('[ModalHandler][DEBUG]: Active modal stack is now empty.');
72
+ this.resetKeys();
73
+ }
74
+
59
75
  return true;
60
76
  }
61
77
 
@@ -103,15 +119,15 @@ export default class ModalHandler {
103
119
  }
104
120
  }
105
121
 
106
- #handleEscapeKeyClose(closeHandler) {
122
+ #handleEscapeKeyClose(modalKey, closeHandler) {
107
123
  return e => {
108
124
  if (e.key === 'Escape') {
109
- closeHandler(e);
125
+ closeHandler(e, modalKey);
110
126
  }
111
127
  }
112
128
  }
113
129
 
114
- #handleOutsideClickClose(closeHandler, modalLmOuterLimits, exemptLms = []) {
130
+ #handleOutsideClickClose(modalKey, closeHandler, modalLmOuterLimits, exemptLms) {
115
131
  return e => {
116
132
  const clickedLm = e.target;
117
133
 
@@ -126,20 +142,27 @@ export default class ModalHandler {
126
142
  return;
127
143
  }
128
144
 
129
- closeHandler(e);
145
+ closeHandler(e, modalKey);
130
146
  }
131
147
  }
132
148
 
133
- #handleActiveModalClose(modalKey, closeHandler) {
149
+ #handleActiveModalClose(modalKey, closeHandler, modalLmOuterLimits) {
134
150
  return e => {
135
- e.stopPropagation();
136
- if (!this.#isActiveModal(modalKey)) return;
151
+ // Check if modal is overlayless and triggered by click so we can ignore stacking order later
152
+ const isOverlayLess = !modalLmOuterLimits && e?.type === 'click';
153
+
154
+ if (!isOverlayLess && !this.#isActiveModal(modalKey)) {
155
+ return;
156
+ }
157
+
158
+ const isRegistered = this.#unregisterModal(modalKey, isOverlayLess);
159
+ if (!isRegistered) return;
137
160
 
138
161
  if (this.#debug) {
139
162
  console.log(`[ModalHandler][DEBUG]: Close modal with key => "${modalKey}"`);
140
163
  }
141
164
 
142
- closeHandler(); // Only close if this is the topmost modal
165
+ closeHandler(e, modalKey); // Only close if this is the topmost modal
143
166
  }
144
167
  }
145
168
 
@@ -154,8 +177,13 @@ export default class ModalHandler {
154
177
  for (const key in documentBodyEvents) {
155
178
  const events = documentBodyEvents[key];
156
179
 
157
- events.forEach(eventHandler => {
158
- document.body.removeEventListener(eventHandler.eventName, eventHandler.callback);
180
+ events.forEach(({ eventName, callback, isOutsideClickHandler }) => {
181
+ if (isOutsideClickHandler) {
182
+ document.body.removeEventListener(eventName, callback, true);
183
+ }
184
+ else {
185
+ document.body.removeEventListener(eventName, callback);
186
+ }
159
187
  });
160
188
 
161
189
  events.length = 0;
@@ -202,6 +230,25 @@ export default class ModalHandler {
202
230
  this.clearDocumentBodyEvents();
203
231
  this.clearActiveModals();
204
232
  this.clearFocusRegistry();
233
+ this.resetKeys();
234
+ }
235
+
236
+ generateKey(prefix = 'modal') {
237
+ this.#modalIdCounter = (this.#modalIdCounter || 0) + 1;
238
+ return `${prefix}-${this.#modalIdCounter}`;
239
+ }
240
+
241
+ resetKeys() {
242
+ this.#modalIdCounter = 0;
243
+ }
244
+
245
+ rebindTrapFocus(modalKey) {
246
+ const eventsHandler = this.#eventsHandler[modalKey];
247
+ const trapFocusHandler = eventsHandler.find(hander => hander.isTrapFocusHandler === true);
248
+ const { lm, eventName, callback } = trapFocusHandler;
249
+
250
+ lm.removeEventListener(eventName, callback);
251
+ lm.addEventListener(eventName, callback);
205
252
  }
206
253
 
207
254
  addA11yEvents({
@@ -212,91 +259,92 @@ export default class ModalHandler {
212
259
  exemptLms = [],
213
260
  closeHandler
214
261
  }) {
215
- // Extra guard to protect against event bubbling after modal open
216
- setTimeout(() => {
217
- // Register modal key and skip if already registered
218
- const isNew = this.#registerModal(modalKey);
219
- if (!isNew) return;
220
-
221
- // Register event handlers reference
222
- const handleActiveModalClose = this.#handleActiveModalClose(modalKey, closeHandler);
223
- const escapeKeyHandler = this.#handleEscapeKeyClose(handleActiveModalClose);
224
- const outsideClickHandler = this.#handleOutsideClickClose(handleActiveModalClose, modalLmOuterLimits, exemptLms);
225
- const trapFocusHandler = this.#handleTrapFocus(modalLm);
226
-
227
- // Attach event listeners
228
- document.body.addEventListener('keydown', escapeKeyHandler);
229
-
230
- if (modalLmOuterLimits) {
231
- document.body.addEventListener('click', outsideClickHandler);
232
- }
233
- if (modalLm) {
234
- modalLm.addEventListener('keydown', trapFocusHandler);
235
- }
236
- if (closeLms && Array.isArray(closeLms)) {
237
- closeLms.forEach(closeLm => {
238
- closeLm.addEventListener('click', handleActiveModalClose);
239
- });
240
- }
241
-
242
- // Ensure eventsHandler object exists for this modal
243
- if (!this.#eventsHandler[modalKey]) {
244
- this.#eventsHandler[modalKey] = {};
245
- }
246
- const eventsHandler = this.#eventsHandler[modalKey];
247
-
248
- // Ensure the documentBody object and array exist for this modal key
249
- if (!this.#eventsHandler.documentBody) {
250
- this.#eventsHandler.documentBody = {};
251
- }
252
- if (!this.#eventsHandler.documentBody[modalKey]) {
253
- this.#eventsHandler.documentBody[modalKey] = [];
254
- }
255
- const documentEvents = this.#eventsHandler.documentBody[modalKey];
256
-
257
- // Store event handlers references so they can be removed later
258
- eventsHandler.escapeKeyHandler = escapeKeyHandler;
259
- modalLmOuterLimits && (eventsHandler.outsideClickHandler = outsideClickHandler);
260
- modalLm && (eventsHandler.trapFocusHandler = trapFocusHandler);
261
- closeLms && (eventsHandler.closeHandler = handleActiveModalClose);
262
-
263
- // Keep references to body events for SPA view changes,
264
- // so lingering events can be removed if the modal stays open across re-renders
265
- documentEvents.push({ eventName: 'keydown', callback: escapeKeyHandler });
266
- if (modalLmOuterLimits) {
267
- documentEvents.push({ eventName: 'click', callback: outsideClickHandler });
268
- }
269
- });
270
- }
271
-
272
- removeA11yEvents({
273
- modalKey,
274
- modalLm = null,
275
- closeLms = null
276
- }) {
277
- // Unregister modal key and skip if it wasn't registered
278
- const isRegistered = this.#unregisterModal(modalKey);
279
- if (!isRegistered) return;
280
-
262
+ // Register modal key and skip if already registered
263
+ const isNew = this.#registerModal(modalKey);
264
+ if (!isNew) return;
265
+
266
+ // Register event handlers reference
267
+ const handleActiveModalClose = this.#handleActiveModalClose(modalKey, closeHandler, modalLmOuterLimits);
268
+ const escapeKeyHandler = this.#handleEscapeKeyClose(modalKey, handleActiveModalClose);
269
+
270
+ // Ensure storage for this modal exists
271
+ if (!this.#eventsHandler[modalKey]) this.#eventsHandler[modalKey] = [];
272
+ if (!this.#eventsHandler.documentBody) this.#eventsHandler.documentBody = {};
273
+ if (!this.#eventsHandler.documentBody[modalKey]) this.#eventsHandler.documentBody[modalKey] = [];
274
+
281
275
  const eventsHandler = this.#eventsHandler[modalKey];
276
+ const documentEvents = this.#eventsHandler.documentBody[modalKey];
282
277
 
283
- // Remove event listeners from elements
284
- document.body.removeEventListener('keydown', eventsHandler.escapeKeyHandler);
278
+ // Attach event listeners
279
+ document.body.addEventListener('keydown', escapeKeyHandler);
280
+ eventsHandler.push({ eventName: 'keydown', callback: escapeKeyHandler });
281
+ // Keep references to body events for SPA view changes,
282
+ // so lingering events can be removed if the modal stays open across re-renders
283
+ documentEvents.push({ eventName: 'keydown', callback: escapeKeyHandler });
285
284
 
286
- if (eventsHandler.outsideClickHandler) {
287
- document.body.removeEventListener('click', eventsHandler.outsideClickHandler);
285
+ if (modalLmOuterLimits) {
286
+ const outsideClickHandler = this.#handleOutsideClickClose(modalKey, handleActiveModalClose, modalLmOuterLimits, exemptLms);
287
+
288
+ // Use capture phase to detect outside clicks before event bubbling,
289
+ // preventing auto-close on overlay click after modal opens
290
+ document.body.addEventListener('click', outsideClickHandler, true);
291
+ eventsHandler.push({ eventName: 'click', callback: outsideClickHandler, isOutsideClickHandler: true });
292
+ // Keep references to body events for SPA view changes
293
+ documentEvents.push({ eventName: 'click', callback: outsideClickHandler, isOutsideClickHandler: true });
288
294
  }
289
295
  if (modalLm) {
290
- modalLm.removeEventListener('keydown', eventsHandler.trapFocusHandler);
296
+ const trapFocusHandler = this.#handleTrapFocus(modalLm);
297
+
298
+ modalLm.addEventListener('keydown', trapFocusHandler);
299
+ eventsHandler.push({ lm: modalLm, eventName: 'keydown', callback: trapFocusHandler, isTrapFocusHandler: true });
291
300
  }
292
301
  if (closeLms && Array.isArray(closeLms)) {
293
302
  closeLms.forEach(closeLm => {
294
- closeLm.removeEventListener('click', eventsHandler.closeHandler);
303
+ closeLm.addEventListener('click', handleActiveModalClose);
295
304
  });
305
+ eventsHandler.push({ lm: closeLms, eventName: 'click', callback: handleActiveModalClose });
296
306
  }
307
+ }
308
+
309
+ removeA11yEvents({
310
+ modalKey,
311
+ isToggle,
312
+ }) {
313
+ if (isToggle) {
314
+ const removed = this.#unregisterModal(modalKey, isToggle);
315
+ if (this.#debug && removed) {
316
+ console.log('[ModalHandler][DEBUG]: Overlayless modal not triggered on close; removed via event cleanup.');
317
+ }
318
+ }
319
+
320
+ const eventsHandler = this.#eventsHandler[modalKey];
321
+
322
+ // Event cleanup
323
+ eventsHandler.forEach(({ lm, eventName, callback, isOutsideClickHandler }) => {
324
+ // if lm is undefined we just have to clear the body
325
+ if (lm === undefined) lm = document.body;
326
+
327
+ // Check for array of closing lms
328
+ if (Array.isArray(lm)) {
329
+ const lms = lm;
330
+
331
+ lms.forEach(lm => {
332
+ lm.removeEventListener(eventName, callback);
333
+ });
334
+ }
335
+ else {
336
+ if (isOutsideClickHandler) {
337
+ lm.removeEventListener(eventName, callback, true);
338
+ }
339
+ else {
340
+ lm.removeEventListener(eventName, callback);
341
+ }
342
+ }
343
+ });
297
344
 
298
345
  // Clean up stored handlers
299
- delete this.#eventsHandler[modalKey];
346
+ this.#eventsHandler[modalKey].length = 0; // empties the array to remove any lingering references
347
+ delete this.#eventsHandler[modalKey]; // removes the property entirely from the eventsHandler object
300
348
  const documentEvents = this.#eventsHandler.documentBody[modalKey];
301
349
  documentEvents.length = 0;
302
350
  }