@kupola/kupola 1.6.2 → 1.6.4

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/js/dropdown.js CHANGED
@@ -65,9 +65,9 @@ class Dropdown {
65
65
  this.onSelect({ item, value: item.getAttribute('data-value'), text: item.textContent.trim() });
66
66
  }
67
67
 
68
- if (this.closeOnClick) {
68
+ if (this.closeOnClick !== false) {
69
69
  this.hideMenu();
70
- this.trigger.focus();
70
+ if (this.trigger) {this.trigger.focus();}
71
71
  }
72
72
  };
73
73
 
@@ -170,7 +170,10 @@ class Dropdown {
170
170
 
171
171
  // Document click to close
172
172
  this._documentClickHandler = (e) => {
173
- if (!this.element.contains(e.target) && !this.menu.contains(e.target)) {
173
+ if (!this.isOpen) return;
174
+ const isInElement = this.element.contains(e.target);
175
+ const isInMenu = this.menu && this.menu.contains(e.target);
176
+ if (!isInElement && !isInMenu) {
174
177
  this.hideMenu();
175
178
  }
176
179
  };
@@ -183,8 +186,8 @@ class Dropdown {
183
186
 
184
187
  _bindMenuItems() {
185
188
  this.menu.querySelectorAll('.ds-dropdown__item').forEach(item => {
186
- item.addEventListener('click', (e) => this._itemClickHandler(e));
187
189
  item._dropdownItemClickHandler = (e) => this._itemClickHandler(e);
190
+ item.addEventListener('click', item._dropdownItemClickHandler);
188
191
  });
189
192
  }
190
193
 
@@ -205,7 +208,6 @@ class Dropdown {
205
208
  if (!this.autoPosition) {return;}
206
209
 
207
210
  const triggerRect = this.element.getBoundingClientRect();
208
- const menuRect = this.menu.getBoundingClientRect();
209
211
  const viewportHeight = window.innerHeight;
210
212
  const viewportWidth = window.innerWidth;
211
213
 
@@ -213,7 +215,8 @@ class Dropdown {
213
215
  this.menu.classList.remove('ds-dropdown--top', 'ds-dropdown--right', 'ds-dropdown--dropup');
214
216
 
215
217
  if (this.appendToBody) {
216
- this.menu.style.width = `${Math.max(triggerRect.width, menuRect.width)}px`;
218
+ this.menu.style.width = `${triggerRect.width}px`;
219
+ const menuRect = this.menu.getBoundingClientRect();
217
220
 
218
221
  const spaceBelow = viewportHeight - triggerRect.bottom;
219
222
  const spaceAbove = triggerRect.top;
@@ -227,7 +230,7 @@ class Dropdown {
227
230
  }
228
231
 
229
232
  if (triggerRect.left + menuRect.width > viewportWidth) {
230
- this.menu.style.left = `${triggerRect.right - Math.max(triggerRect.width, menuRect.width)}px`;
233
+ this.menu.style.left = `${triggerRect.right - menuRect.width}px`;
231
234
  this.menu.style.right = 'auto';
232
235
  } else {
233
236
  this.menu.style.left = `${triggerRect.left}px`;
@@ -304,9 +307,12 @@ class Dropdown {
304
307
  this._originalWidth = this.menu.style.width;
305
308
  this._originalTransform = this.menu.style.transform;
306
309
  this._originalZIndex = this.menu.style.zIndex;
310
+ this._originalDisplay = this.menu.style.display;
307
311
 
312
+ const triggerRect = this.element.getBoundingClientRect();
308
313
  const zIndex = getZIndexConfig().dropdown;
309
314
  this.menu.style.position = 'fixed';
315
+ this.menu.style.width = `${triggerRect.width}px`;
310
316
  this.menu.style.zIndex = zIndex;
311
317
  this.menu.style.transform = 'translateZ(0)';
312
318
  document.body.appendChild(this.menu);
@@ -324,7 +330,9 @@ class Dropdown {
324
330
  this.menu.style.width = this._originalWidth || '';
325
331
  this.menu.style.zIndex = this._originalZIndex || '';
326
332
  this.menu.style.transform = this._originalTransform || '';
333
+ this.menu.style.display = this._originalDisplay || '';
327
334
  this._originalParent = null;
335
+ console.log('[Dropdown] Menu restored from body');
328
336
  }
329
337
 
330
338
  _addScrollListener() {
@@ -362,6 +370,33 @@ class Dropdown {
362
370
 
363
371
  // Update menu items dynamically
364
372
  setItems(items) {
373
+ // Ensure _itemClickHandler is defined
374
+ if (!this._itemClickHandler) {
375
+ this._itemClickHandler = (e) => {
376
+ e.stopPropagation();
377
+ const item = e.currentTarget;
378
+ if (item.classList.contains('is-disabled') || item.classList.contains('ds-dropdown__divider')) {return;}
379
+
380
+ this.menu.querySelectorAll('.ds-dropdown__item').forEach(i => i.classList.remove('is-selected'));
381
+ item.classList.add('is-selected');
382
+
383
+ if (this.triggerText && !item.hasAttribute('data-no-update-trigger')) {
384
+ this.triggerText.textContent = item.textContent.trim();
385
+ }
386
+
387
+ this.element.setAttribute('data-value', item.getAttribute('data-value') || '');
388
+
389
+ if (this.onSelect) {
390
+ this.onSelect({ item, value: item.getAttribute('data-value'), text: item.textContent.trim() });
391
+ }
392
+
393
+ if (this.closeOnClick !== false) {
394
+ this.hideMenu();
395
+ if (this.trigger) {this.trigger.focus();}
396
+ }
397
+ };
398
+ }
399
+
365
400
  // Remove old item listeners
366
401
  this.menu.querySelectorAll('.ds-dropdown__item').forEach(item => {
367
402
  if (item._dropdownItemClickHandler) {
@@ -371,7 +406,7 @@ class Dropdown {
371
406
 
372
407
  // Rebuild menu content
373
408
  this.menu.innerHTML = '';
374
- items.forEach(item => {
409
+ items.forEach((item, index) => {
375
410
  if (item.type === 'divider') {
376
411
  const divider = document.createElement('div');
377
412
  divider.className = 'ds-dropdown__divider';
@@ -383,11 +418,13 @@ class Dropdown {
383
418
  if (item.value !== undefined) {el.setAttribute('data-value', item.value);}
384
419
  if (item.icon) {el.innerHTML = item.icon + el.innerHTML;}
385
420
  if (item.disabled) {el.classList.add('is-disabled');}
421
+
422
+ el._dropdownItemClickHandler = (e) => this._itemClickHandler(e);
423
+ el.addEventListener('click', el._dropdownItemClickHandler);
424
+
386
425
  this.menu.appendChild(el);
387
426
  }
388
427
  });
389
-
390
- this._bindMenuItems();
391
428
  }
392
429
 
393
430
  destroy() {
@@ -440,6 +477,9 @@ class Dropdown {
440
477
  }
441
478
 
442
479
  function initDropdown(element, options) {
480
+ if (element._kupolaDropdown) {
481
+ element._kupolaDropdown.destroy();
482
+ }
443
483
  const dropdown = new Dropdown(element, options);
444
484
  dropdown.init();
445
485
  element._kupolaDropdown = dropdown;
package/js/initializer.js CHANGED
@@ -86,7 +86,8 @@ class ComponentInitializerRegistry {
86
86
  const className = element.className;
87
87
  if (typeof className === 'string') {
88
88
  for (const cls of this._cssClasses) {
89
- if (className.includes(cls)) {
89
+ const regex = new RegExp(`(^|\\s)${cls}(\\s|$)`);
90
+ if (regex.test(className)) {
90
91
  const name = cls.replace('ds-', '');
91
92
  const initFn = this.initializers.get(name) || this.initializers.get(cls);
92
93
  if (initFn) {
@@ -127,7 +128,8 @@ class ComponentInitializerRegistry {
127
128
  const className = element.className;
128
129
  if (typeof className === 'string') {
129
130
  for (const cls of this._cssClasses) {
130
- if (className.includes(cls)) {
131
+ const regex = new RegExp(`(^|\\s)${cls}(\\s|$)`);
132
+ if (regex.test(className)) {
131
133
  const name = cls.replace('ds-', '');
132
134
  const cleanupFn = this.cleanupFunctions.get(name) || this.cleanupFunctions.get(cls);
133
135
  if (cleanupFn) {
package/js/select.js CHANGED
@@ -133,7 +133,10 @@ class Select {
133
133
 
134
134
  // Document click to close
135
135
  this._documentClickHandler = (e) => {
136
- if (!this.element.contains(e.target) && !this.optionsEl.contains(e.target)) {
136
+ if (!this.isOpen) return;
137
+ const isInElement = this.element.contains(e.target);
138
+ const isInOptions = this.optionsEl && this.optionsEl.contains(e.target);
139
+ if (!isInElement && !isInOptions) {
137
140
  this.hideOptions();
138
141
  }
139
142
  };
@@ -231,6 +234,23 @@ class Select {
231
234
  }
232
235
 
233
236
  _renderRemoteOptions(options) {
237
+ // Ensure _optionClickHandler is defined
238
+ if (!this._optionClickHandler) {
239
+ this._optionClickHandler = (e) => {
240
+ e.stopPropagation();
241
+ const option = e.currentTarget;
242
+ if (option.classList.contains('is-disabled')) return;
243
+
244
+ const value = option.getAttribute('data-value');
245
+
246
+ if (this.multiple) {
247
+ this._toggleMultiOption(value, option);
248
+ } else {
249
+ this._selectSingleOption(value, option);
250
+ }
251
+ };
252
+ }
253
+
234
254
  this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(opt => {
235
255
  if (opt._selectOptionClickHandler) {
236
256
  opt.removeEventListener('click', opt._selectOptionClickHandler);
@@ -246,8 +266,8 @@ class Select {
246
266
  if (opt.disabled) el.classList.add('is-disabled');
247
267
  if (this.selectedValues.has(opt.value)) el.classList.add('is-selected');
248
268
 
249
- el.addEventListener('click', (e) => this._optionClickHandler(e));
250
269
  el._selectOptionClickHandler = (e) => this._optionClickHandler(e);
270
+ el.addEventListener('click', el._selectOptionClickHandler);
251
271
 
252
272
  this.optionsEl.appendChild(el);
253
273
  });
@@ -408,8 +428,8 @@ class Select {
408
428
 
409
429
  _bindOptionClicks() {
410
430
  this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(option => {
411
- option.addEventListener('click', (e) => this._optionClickHandler(e));
412
431
  option._selectOptionClickHandler = (e) => this._optionClickHandler(e);
432
+ option.addEventListener('click', option._selectOptionClickHandler);
413
433
  });
414
434
  }
415
435
 
@@ -500,8 +520,10 @@ class Select {
500
520
  this._originalTransform = this.optionsEl.style.transform;
501
521
  this._originalZIndex = this.optionsEl.style.zIndex;
502
522
 
523
+ const triggerRect = this.element.getBoundingClientRect();
503
524
  const zIndex = getZIndexConfig().dropdown;
504
525
  this.optionsEl.style.position = 'fixed';
526
+ this.optionsEl.style.width = `${triggerRect.width}px`;
505
527
  this.optionsEl.style.zIndex = zIndex;
506
528
  this.optionsEl.style.transform = 'translateZ(0)';
507
529
  document.body.appendChild(this.optionsEl);
@@ -524,11 +546,11 @@ class Select {
524
546
  if (!this.appendToBody || !this.optionsEl) return;
525
547
 
526
548
  const triggerRect = this.element.getBoundingClientRect();
527
- const optionsRect = this.optionsEl.getBoundingClientRect();
528
549
  const viewportHeight = window.innerHeight;
529
550
  const viewportWidth = window.innerWidth;
530
551
 
531
- this.optionsEl.style.width = `${Math.max(triggerRect.width, optionsRect.width)}px`;
552
+ this.optionsEl.style.width = `${triggerRect.width}px`;
553
+ const optionsRect = this.optionsEl.getBoundingClientRect();
532
554
 
533
555
  const spaceBelow = viewportHeight - triggerRect.bottom;
534
556
  const spaceAbove = triggerRect.top;
@@ -540,7 +562,7 @@ class Select {
540
562
  }
541
563
 
542
564
  if (triggerRect.left + optionsRect.width > viewportWidth) {
543
- this.optionsEl.style.left = `${triggerRect.right - Math.max(triggerRect.width, optionsRect.width)}px`;
565
+ this.optionsEl.style.left = `${triggerRect.right - optionsRect.width}px`;
544
566
  } else {
545
567
  this.optionsEl.style.left = `${triggerRect.left}px`;
546
568
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kupola/kupola",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "A lightweight UI toolkit for any web project. No heavy frontend frameworks required.",
5
5
  "main": "dist/kupola.cjs.js",
6
6
  "module": "dist/kupola.esm.js",