@kupola/kupola 1.5.2 → 1.5.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/select.js CHANGED
@@ -20,6 +20,7 @@ class Select {
20
20
  this.maxSelection = options.maxSelection || parseInt(element.getAttribute('data-select-max')) || Infinity;
21
21
  this.remoteMethod = options.remoteMethod || null;
22
22
  this.onChange = options.onChange || null;
23
+ this.appendToBody = options.appendToBody !== false;
23
24
 
24
25
  // State
25
26
  this.isOpen = false;
@@ -30,6 +31,8 @@ class Select {
30
31
  this.searchInput = null;
31
32
  this.clearBtn = null;
32
33
  this.tagsWrap = null;
34
+ this._originalParent = null;
35
+ this._originalPosition = null;
33
36
 
34
37
  this._triggerClickHandler = null;
35
38
  this._documentClickHandler = null;
@@ -128,7 +131,7 @@ class Select {
128
131
 
129
132
  // Document click to close
130
133
  this._documentClickHandler = (e) => {
131
- if (!this.element.contains(e.target)) {
134
+ if (!this.element.contains(e.target) && !this.optionsEl.contains(e.target)) {
132
135
  this.hideOptions();
133
136
  }
134
137
  };
@@ -431,10 +434,17 @@ class Select {
431
434
  showOptions() {
432
435
  if (this.disabled || this.isOpen) return;
433
436
  this.isOpen = true;
434
- this.optionsEl.style.display = 'block';
435
- if (this.icon) this.icon.style.transform = 'rotate(180deg)';
436
437
  this.element.classList.add('is-open');
438
+ if (this.icon) this.icon.style.transform = 'rotate(180deg)';
437
439
  this.focusIndex = -1;
440
+
441
+ if (this.appendToBody) {
442
+ this._appendOptionsToBody();
443
+ this._addScrollListener();
444
+ }
445
+
446
+ this.optionsEl.style.display = 'block';
447
+ this._calculateOptionsPosition();
438
448
 
439
449
  if (this.searchInput) {
440
450
  setTimeout(() => this.searchInput.focus(), 50);
@@ -447,6 +457,11 @@ class Select {
447
457
  this.optionsEl.style.display = 'none';
448
458
  if (this.icon) this.icon.style.transform = 'rotate(0deg)';
449
459
  this.element.classList.remove('is-open');
460
+
461
+ if (this.appendToBody) {
462
+ this._restoreOptionsFromBody();
463
+ this._removeScrollListener();
464
+ }
450
465
 
451
466
  // Clear search
452
467
  if (this.searchInput) {
@@ -458,6 +473,72 @@ class Select {
458
473
  this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(o => o.classList.remove('is-focused'));
459
474
  }
460
475
 
476
+ _addScrollListener() {
477
+ this._scrollHandler = () => {
478
+ this.hideOptions();
479
+ };
480
+ window.addEventListener('scroll', this._scrollHandler, true);
481
+ }
482
+
483
+ _removeScrollListener() {
484
+ if (this._scrollHandler) {
485
+ window.removeEventListener('scroll', this._scrollHandler, true);
486
+ this._scrollHandler = null;
487
+ }
488
+ }
489
+
490
+ _appendOptionsToBody() {
491
+ if (!this.optionsEl) return;
492
+ this._originalParent = this.optionsEl.parentNode;
493
+ this._originalPosition = this.optionsEl.style.position;
494
+ this._originalTop = this.optionsEl.style.top;
495
+ this._originalLeft = this.optionsEl.style.left;
496
+ this._originalRight = this.optionsEl.style.right;
497
+ this._originalWidth = this.optionsEl.style.width;
498
+
499
+ this.optionsEl.style.position = 'fixed';
500
+ this.optionsEl.style.zIndex = '9999';
501
+ document.body.appendChild(this.optionsEl);
502
+ }
503
+
504
+ _restoreOptionsFromBody() {
505
+ if (!this.optionsEl || !this._originalParent) return;
506
+ this._originalParent.appendChild(this.optionsEl);
507
+ this.optionsEl.style.position = this._originalPosition || '';
508
+ this.optionsEl.style.top = this._originalTop || '';
509
+ this.optionsEl.style.left = this._originalLeft || '';
510
+ this.optionsEl.style.right = this._originalRight || '';
511
+ this.optionsEl.style.width = this._originalWidth || '';
512
+ this.optionsEl.style.zIndex = '';
513
+ this._originalParent = null;
514
+ }
515
+
516
+ _calculateOptionsPosition() {
517
+ if (!this.appendToBody || !this.optionsEl) return;
518
+
519
+ const triggerRect = this.element.getBoundingClientRect();
520
+ const optionsRect = this.optionsEl.getBoundingClientRect();
521
+ const viewportHeight = window.innerHeight;
522
+ const viewportWidth = window.innerWidth;
523
+
524
+ this.optionsEl.style.width = `${Math.max(triggerRect.width, optionsRect.width)}px`;
525
+
526
+ const spaceBelow = viewportHeight - triggerRect.bottom;
527
+ const spaceAbove = triggerRect.top;
528
+
529
+ if (spaceBelow < optionsRect.height && spaceAbove > spaceBelow) {
530
+ this.optionsEl.style.top = `${triggerRect.top - optionsRect.height - 4}px`;
531
+ } else {
532
+ this.optionsEl.style.top = `${triggerRect.bottom + 4}px`;
533
+ }
534
+
535
+ if (triggerRect.left + optionsRect.width > viewportWidth) {
536
+ this.optionsEl.style.left = `${triggerRect.right - Math.max(triggerRect.width, optionsRect.width)}px`;
537
+ } else {
538
+ this.optionsEl.style.left = `${triggerRect.left}px`;
539
+ }
540
+ }
541
+
461
542
  toggleOptions() {
462
543
  if (this.isOpen) {
463
544
  this.hideOptions();
@@ -548,6 +629,10 @@ class Select {
548
629
  if (this.clearBtn) this.clearBtn.remove();
549
630
  if (this.tagsWrap) this.tagsWrap.remove();
550
631
 
632
+ if (this.appendToBody && this._originalParent) {
633
+ this._restoreOptionsFromBody();
634
+ }
635
+
551
636
  this._documentClickHandler = null;
552
637
  this._documentClickListener = null;
553
638
  this._triggerClickHandler = null;