@vanduo-oss/framework 1.3.0 → 1.3.2

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.
@@ -282,19 +282,20 @@
282
282
  const codeElement = activePane.querySelector('code') || activePane;
283
283
  const code = codeElement.textContent;
284
284
 
285
+ let copySuccess;
285
286
  try {
286
287
  await navigator.clipboard.writeText(code);
287
- this.showCopyFeedback(copyBtn, true);
288
+ copySuccess = true;
288
289
  } catch (_err) {
289
290
  // Fallback for older browsers
290
- const success = this.fallbackCopy(code);
291
- this.showCopyFeedback(copyBtn, success);
291
+ copySuccess = this.fallbackCopy(code);
292
292
  }
293
+ this.showCopyFeedback(copyBtn, copySuccess);
293
294
 
294
295
  // Dispatch event
295
296
  const event = new CustomEvent('codesnippet:copy', {
296
297
  bubbles: true,
297
- detail: { snippet, code, success: true }
298
+ detail: { snippet, code, success: copySuccess }
298
299
  });
299
300
  snippet.dispatchEvent(event);
300
301
  },
@@ -12,9 +12,6 @@
12
12
  const Dropdown = {
13
13
  // Store initialized dropdowns and their cleanup functions
14
14
  instances: new Map(),
15
- // Typeahead state
16
- _typeaheadBuffer: '',
17
- _typeaheadTimer: null,
18
15
 
19
16
  /**
20
17
  * Initialize dropdown components
@@ -95,7 +92,7 @@
95
92
  cleanupFunctions.push(() => item.removeEventListener('keydown', itemKeydownHandler));
96
93
  });
97
94
 
98
- this.instances.set(dropdown, { toggle, menu, cleanup: cleanupFunctions });
95
+ this.instances.set(dropdown, { toggle, menu, cleanup: cleanupFunctions, typeaheadBuffer: '', typeaheadTimer: null });
99
96
  },
100
97
 
101
98
  /**
@@ -249,18 +246,21 @@
249
246
  default:
250
247
  // Typeahead: jump to matching item when typing printable characters
251
248
  if (isOpen && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
252
- clearTimeout(this._typeaheadTimer);
253
- this._typeaheadBuffer += e.key.toLowerCase();
249
+ // Per-instance typeahead state to avoid cross-instance corruption
250
+ const instance = this.instances.get(dropdown);
251
+ if (!instance) break;
252
+ clearTimeout(instance.typeaheadTimer);
253
+ instance.typeaheadBuffer += e.key.toLowerCase();
254
254
 
255
255
  const match = items.find(item =>
256
- item.textContent.trim().toLowerCase().startsWith(this._typeaheadBuffer)
256
+ item.textContent.trim().toLowerCase().startsWith(instance.typeaheadBuffer)
257
257
  );
258
258
  if (match) {
259
259
  match.focus();
260
260
  }
261
261
 
262
- this._typeaheadTimer = setTimeout(() => {
263
- this._typeaheadBuffer = '';
262
+ instance.typeaheadTimer = setTimeout(() => {
263
+ instance.typeaheadBuffer = '';
264
264
  }, 500);
265
265
  }
266
266
  break;
@@ -274,9 +274,10 @@
274
274
  // Handle image load
275
275
  if (!this.img.complete) {
276
276
  this.img.style.opacity = '0';
277
- this.img.onload = () => {
277
+ this._imgLoadHandler = () => {
278
278
  this.img.style.opacity = '';
279
279
  };
280
+ this.img.addEventListener('load', this._imgLoadHandler, { once: true });
280
281
  }
281
282
  },
282
283
 
@@ -305,6 +306,11 @@
305
306
  // Clear image after transition
306
307
  setTimeout(() => {
307
308
  if (!this.isOpen) {
309
+ // Clean up load handler if still pending
310
+ if (this._imgLoadHandler) {
311
+ this.img.removeEventListener('load', this._imgLoadHandler);
312
+ this._imgLoadHandler = null;
313
+ }
308
314
  this.img.src = '';
309
315
  this.img.alt = '';
310
316
  }
@@ -16,6 +16,8 @@
16
16
 
17
17
  // Store trigger cleanup functions
18
18
  _triggerCleanups: [],
19
+ // Shared ESC key handler (installed once)
20
+ _sharedEscHandler: null,
19
21
 
20
22
  /**
21
23
  * Initialize modals
@@ -99,17 +101,18 @@
99
101
  backdrop.addEventListener('click', backdropClickHandler);
100
102
  cleanupFunctions.push(() => backdrop.removeEventListener('click', backdropClickHandler));
101
103
 
102
- // ESC key handler
103
- const escKeyHandler = (e) => {
104
- if (e.key === 'Escape' && this.openModals.length > 0) {
105
- const topModal = this.openModals[this.openModals.length - 1];
106
- if (topModal === modal && topModal.dataset.keyboard !== 'false') {
107
- this.close(topModal);
104
+ // ESC key handler — use a single shared handler instead of one-per-modal
105
+ if (!this._sharedEscHandler) {
106
+ this._sharedEscHandler = (e) => {
107
+ if (e.key === 'Escape' && this.openModals.length > 0) {
108
+ const topModal = this.openModals[this.openModals.length - 1];
109
+ if (topModal.dataset.keyboard !== 'false') {
110
+ this.close(topModal);
111
+ }
108
112
  }
109
- }
110
- };
111
- document.addEventListener('keydown', escKeyHandler);
112
- cleanupFunctions.push(() => document.removeEventListener('keydown', escKeyHandler));
113
+ };
114
+ document.addEventListener('keydown', this._sharedEscHandler);
115
+ }
113
116
 
114
117
  this.modals.set(modal, { backdrop, dialog, trapHandler: null, cleanup: cleanupFunctions });
115
118
  },
@@ -352,6 +355,11 @@
352
355
  // Clean up trigger listeners
353
356
  this._triggerCleanups.forEach(fn => fn());
354
357
  this._triggerCleanups = [];
358
+ // Remove shared ESC handler
359
+ if (this._sharedEscHandler) {
360
+ document.removeEventListener('keydown', this._sharedEscHandler);
361
+ this._sharedEscHandler = null;
362
+ }
355
363
  }
356
364
  };
357
365