@m1z23r/ngx-ui 1.0.3 → 1.0.5

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, PLATFORM_ID, signal, Injectable, computed, InjectionToken, ApplicationRef, EnvironmentInjector, createComponent, Injector, input, output, ChangeDetectionStrategy, Component, ElementRef, HostListener, effect, Directive, model, contentChildren, TemplateRef, contentChild, viewChild } from '@angular/core';
2
+ import { inject, PLATFORM_ID, signal, Injectable, computed, InjectionToken, ApplicationRef, EnvironmentInjector, createComponent, Injector, input, output, ChangeDetectionStrategy, Component, ElementRef, HostListener, effect, Directive, model, contentChildren, ViewChild, TemplateRef, contentChild, viewChild } from '@angular/core';
3
3
  import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { FormsModule } from '@angular/forms';
@@ -935,11 +935,15 @@ class SelectComponent {
935
935
  closed = output();
936
936
  // Content children
937
937
  options = contentChildren(OptionComponent, ...(ngDevMode ? [{ debugName: "options" }] : []));
938
+ // View children for dropdown portal
939
+ triggerRef;
940
+ dropdownRef;
938
941
  // Internal state
939
942
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
940
943
  searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
941
944
  focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
942
945
  elementRef = inject(ElementRef);
946
+ positionCleanup = null;
943
947
  constructor() {
944
948
  // Sync selected state to options
945
949
  effect(() => {
@@ -984,6 +988,13 @@ class SelectComponent {
984
988
  });
985
989
  });
986
990
  }
991
+ ngOnDestroy() {
992
+ const dropdown = this.dropdownRef?.nativeElement;
993
+ if (dropdown?.parentElement === document.body) {
994
+ document.body.removeChild(dropdown);
995
+ }
996
+ this.removePositionListeners();
997
+ }
987
998
  triggerClasses = computed(() => {
988
999
  return `ui-select__trigger--${this.variant()} ui-select__trigger--${this.size()}`;
989
1000
  }, ...(ngDevMode ? [{ debugName: "triggerClasses" }] : []));
@@ -1022,7 +1033,9 @@ class SelectComponent {
1022
1033
  });
1023
1034
  }, ...(ngDevMode ? [{ debugName: "visibleOptions" }] : []));
1024
1035
  onDocumentClick(event) {
1025
- if (!this.elementRef.nativeElement.contains(event.target)) {
1036
+ const target = event.target;
1037
+ if (!this.elementRef.nativeElement.contains(target) &&
1038
+ !this.dropdownRef?.nativeElement?.contains(target)) {
1026
1039
  this.close();
1027
1040
  }
1028
1041
  }
@@ -1043,10 +1056,12 @@ class SelectComponent {
1043
1056
  this.searchQuery.set('');
1044
1057
  this.focusedIndex.set(-1);
1045
1058
  this.opened.emit();
1059
+ this.portalDropdown();
1046
1060
  }
1047
1061
  close() {
1048
1062
  if (!this.isOpen())
1049
1063
  return;
1064
+ this.unportalDropdown();
1050
1065
  this.isOpen.set(false);
1051
1066
  this.searchQuery.set('');
1052
1067
  this.focusedIndex.set(-1);
@@ -1177,13 +1192,88 @@ class SelectComponent {
1177
1192
  focused.elementRef.nativeElement.scrollIntoView({ block: 'nearest' });
1178
1193
  }
1179
1194
  }
1195
+ portalDropdown() {
1196
+ const dropdown = this.dropdownRef?.nativeElement;
1197
+ if (!dropdown)
1198
+ return;
1199
+ document.body.appendChild(dropdown);
1200
+ this.updateDropdownPosition();
1201
+ this.addPositionListeners();
1202
+ }
1203
+ unportalDropdown() {
1204
+ const dropdown = this.dropdownRef?.nativeElement;
1205
+ if (!dropdown)
1206
+ return;
1207
+ if (dropdown.parentElement === document.body) {
1208
+ const wrapper = this.elementRef.nativeElement.querySelector('.ui-select-wrapper');
1209
+ if (wrapper) {
1210
+ wrapper.appendChild(dropdown);
1211
+ }
1212
+ }
1213
+ dropdown.style.position = '';
1214
+ dropdown.style.top = '';
1215
+ dropdown.style.left = '';
1216
+ dropdown.style.bottom = '';
1217
+ dropdown.style.width = '';
1218
+ dropdown.style.zIndex = '';
1219
+ dropdown.style.margin = '';
1220
+ this.removePositionListeners();
1221
+ }
1222
+ updateDropdownPosition() {
1223
+ const trigger = this.triggerRef?.nativeElement;
1224
+ const dropdown = this.dropdownRef?.nativeElement;
1225
+ if (!trigger || !dropdown)
1226
+ return;
1227
+ const triggerRect = trigger.getBoundingClientRect();
1228
+ const dropdownHeight = dropdown.scrollHeight;
1229
+ const gap = 4;
1230
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
1231
+ const spaceAbove = triggerRect.top;
1232
+ const openAbove = spaceBelow < dropdownHeight + gap && spaceAbove > spaceBelow;
1233
+ dropdown.style.position = 'fixed';
1234
+ dropdown.style.width = `${triggerRect.width}px`;
1235
+ dropdown.style.left = `${triggerRect.left}px`;
1236
+ dropdown.style.zIndex = '99999';
1237
+ dropdown.style.margin = '0';
1238
+ if (openAbove) {
1239
+ dropdown.style.top = 'auto';
1240
+ dropdown.style.bottom = `${window.innerHeight - triggerRect.top + gap}px`;
1241
+ }
1242
+ else {
1243
+ dropdown.style.top = `${triggerRect.bottom + gap}px`;
1244
+ dropdown.style.bottom = 'auto';
1245
+ }
1246
+ }
1247
+ addPositionListeners() {
1248
+ const update = () => {
1249
+ if (this.isOpen()) {
1250
+ this.updateDropdownPosition();
1251
+ }
1252
+ };
1253
+ window.addEventListener('scroll', update, true);
1254
+ window.addEventListener('resize', update);
1255
+ this.positionCleanup = () => {
1256
+ window.removeEventListener('scroll', update, true);
1257
+ window.removeEventListener('resize', update);
1258
+ };
1259
+ }
1260
+ removePositionListeners() {
1261
+ this.positionCleanup?.();
1262
+ this.positionCleanup = null;
1263
+ }
1180
1264
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1181
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: SelectComponent, isStandalone: true, selector: "ui-select", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", opened: "opened", closed: "closed" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, queries: [{ propertyName: "options", predicate: OptionComponent, isSignal: true }], ngImport: i0, template: "<div\n class=\"ui-select-wrapper\"\n [class.ui-select-wrapper--error]=\"error()\"\n [class.ui-select-wrapper--disabled]=\"disabled()\"\n [class.ui-select-wrapper--open]=\"isOpen()\"\n>\n @if (label()) {\n <label class=\"ui-select__label\">\n {{ label() }}\n </label>\n }\n\n <div\n class=\"ui-select__trigger\"\n [class]=\"triggerClasses()\"\n [attr.role]=\"'combobox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <span class=\"ui-select__value\">\n @if (displayValue()) {\n {{ displayValue() }}\n } @else {\n <span class=\"ui-select__placeholder\">{{ placeholder() }}</span>\n }\n </span>\n\n <div class=\"ui-select__icons\">\n @if (clearable() && hasValue() && !disabled()) {\n <button\n type=\"button\"\n class=\"ui-select__clear\"\n (click)=\"clear($event)\"\n aria-label=\"Clear selection\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n }\n <svg\n class=\"ui-select__arrow\"\n [class.ui-select__arrow--open]=\"isOpen()\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n </div>\n </div>\n\n <div\n class=\"ui-select__dropdown\"\n [class.ui-select__dropdown--open]=\"isOpen()\"\n [attr.role]=\"'listbox'\"\n [attr.aria-multiselectable]=\"multiple()\"\n >\n @if (searchable()) {\n <div class=\"ui-select__search\">\n <input\n type=\"text\"\n class=\"ui-select__search-input\"\n placeholder=\"Search...\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"searchQuery.set($event)\"\n (keydown)=\"handleSearchKeydown($event)\"\n #searchInput\n />\n </div>\n }\n <div class=\"ui-select__options\">\n <ng-content />\n </div>\n </div>\n\n @if (error()) {\n <span class=\"ui-select__error\">{{ error() }}</span>\n }\n @if (hint() && !error()) {\n <span class=\"ui-select__hint\">{{ hint() }}</span>\n }\n</div>\n", styles: [":host{display:block;position:relative}.ui-select-wrapper{display:flex;flex-direction:column;gap:var(--ui-spacing-xs)}.ui-select__label{font-size:var(--ui-font-sm);font-weight:500;color:var(--ui-text)}.ui-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-sm);width:100%;background-color:var(--ui-bg);border:1px solid var(--ui-border);border-radius:var(--ui-radius-md);cursor:pointer;transition:border-color var(--ui-transition-fast),box-shadow var(--ui-transition-fast)}.ui-select__trigger:hover:not([aria-disabled=true]){border-color:var(--ui-border-hover)}.ui-select__trigger:focus{outline:none;border-color:var(--ui-border-focus);box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent)}.ui-select__trigger[aria-disabled=true]{background-color:var(--ui-bg-secondary);color:var(--ui-text-disabled);cursor:not-allowed}.ui-select__trigger--sm{padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-size:var(--ui-font-sm)}.ui-select__trigger--md{padding:var(--ui-spacing-sm) var(--ui-spacing-md);font-size:var(--ui-font-md)}.ui-select__trigger--lg{padding:var(--ui-spacing-md) var(--ui-spacing-lg);font-size:var(--ui-font-lg)}.ui-select__trigger--outlined{background-color:transparent}.ui-select__trigger--filled{background-color:var(--ui-bg-secondary);border-color:transparent}.ui-select__trigger--filled:hover:not([aria-disabled=true]){background-color:var(--ui-bg-tertiary)}.ui-select__trigger--filled:focus{border-color:var(--ui-border-focus)}.ui-select__value{flex:1;min-width:0;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-select__placeholder{color:var(--ui-text-muted)}.ui-select__icons{display:flex;align-items:center;gap:var(--ui-spacing-xs);flex-shrink:0}.ui-select__clear{display:flex;align-items:center;justify-content:center;padding:2px;background:none;border:none;cursor:pointer;color:var(--ui-text-muted);border-radius:var(--ui-radius-sm);transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-select__clear:hover{color:var(--ui-text);background-color:var(--ui-bg-hover)}.ui-select__arrow{color:var(--ui-text-muted);transition:transform var(--ui-transition-fast)}.ui-select__arrow--open{transform:rotate(180deg)}.ui-select__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:var(--ui-spacing-xs);background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg));overflow:hidden;visibility:hidden;opacity:0;transform:translateY(-8px);transition:opacity var(--ui-transition-fast),transform var(--ui-transition-fast),visibility var(--ui-transition-fast)}.ui-select__dropdown--open{visibility:visible;opacity:1;transform:translateY(0)}.ui-select__search{padding:var(--ui-spacing-sm);border-bottom:1px solid var(--ui-border)}.ui-select__search-input{width:100%;padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-family:inherit;font-size:var(--ui-font-sm);color:var(--ui-text);background-color:var(--ui-bg-secondary);border:1px solid var(--ui-border);border-radius:var(--ui-radius-sm);outline:none}.ui-select__search-input:focus{border-color:var(--ui-border-focus)}.ui-select__options{max-height:var(--ui-dropdown-max-height, 300px);overflow-y:auto}.ui-select__empty{padding:var(--ui-spacing-md);text-align:center;color:var(--ui-text-muted);font-size:var(--ui-font-sm)}.ui-select-wrapper--error .ui-select__trigger{border-color:var(--ui-danger)}.ui-select-wrapper--error .ui-select__trigger:focus{box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-danger) 20%,transparent)}.ui-select__error{font-size:var(--ui-font-sm);color:var(--ui-danger)}.ui-select__hint{font-size:var(--ui-font-sm);color:var(--ui-text-muted)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1265
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: SelectComponent, isStandalone: true, selector: "ui-select", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", opened: "opened", closed: "closed" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, queries: [{ propertyName: "options", predicate: OptionComponent, isSignal: true }], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerRef"], descendants: true, static: true }, { propertyName: "dropdownRef", first: true, predicate: ["dropdownRef"], descendants: true, static: true }], ngImport: i0, template: "<div\n class=\"ui-select-wrapper\"\n [class.ui-select-wrapper--error]=\"error()\"\n [class.ui-select-wrapper--disabled]=\"disabled()\"\n [class.ui-select-wrapper--open]=\"isOpen()\"\n>\n @if (label()) {\n <label class=\"ui-select__label\">\n {{ label() }}\n </label>\n }\n\n <div\n #triggerRef\n class=\"ui-select__trigger\"\n [class]=\"triggerClasses()\"\n [attr.role]=\"'combobox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <span class=\"ui-select__value\">\n @if (displayValue()) {\n {{ displayValue() }}\n } @else {\n <span class=\"ui-select__placeholder\">{{ placeholder() }}</span>\n }\n </span>\n\n <div class=\"ui-select__icons\">\n @if (clearable() && hasValue() && !disabled()) {\n <button\n type=\"button\"\n class=\"ui-select__clear\"\n (click)=\"clear($event)\"\n aria-label=\"Clear selection\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n }\n <svg\n class=\"ui-select__arrow\"\n [class.ui-select__arrow--open]=\"isOpen()\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n </div>\n </div>\n\n <div\n #dropdownRef\n class=\"ui-select__dropdown\"\n [class.ui-select__dropdown--open]=\"isOpen()\"\n [attr.role]=\"'listbox'\"\n [attr.aria-multiselectable]=\"multiple()\"\n >\n @if (searchable()) {\n <div class=\"ui-select__search\">\n <input\n type=\"text\"\n class=\"ui-select__search-input\"\n placeholder=\"Search...\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"searchQuery.set($event)\"\n (keydown)=\"handleSearchKeydown($event)\"\n #searchInput\n />\n </div>\n }\n <div class=\"ui-select__options\">\n <ng-content />\n </div>\n </div>\n\n @if (error()) {\n <span class=\"ui-select__error\">{{ error() }}</span>\n }\n @if (hint() && !error()) {\n <span class=\"ui-select__hint\">{{ hint() }}</span>\n }\n</div>\n", styles: [":host{display:block;position:relative}.ui-select-wrapper{display:flex;flex-direction:column;gap:var(--ui-spacing-xs)}.ui-select__label{font-size:var(--ui-font-sm);font-weight:500;color:var(--ui-text)}.ui-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-sm);width:100%;background-color:var(--ui-bg);border:1px solid var(--ui-border);border-radius:var(--ui-radius-md);cursor:pointer;transition:border-color var(--ui-transition-fast),box-shadow var(--ui-transition-fast)}.ui-select__trigger:hover:not([aria-disabled=true]){border-color:var(--ui-border-hover)}.ui-select__trigger:focus{outline:none;border-color:var(--ui-border-focus);box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent)}.ui-select__trigger[aria-disabled=true]{background-color:var(--ui-bg-secondary);color:var(--ui-text-disabled);cursor:not-allowed}.ui-select__trigger--sm{padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-size:var(--ui-font-sm)}.ui-select__trigger--md{padding:var(--ui-spacing-sm) var(--ui-spacing-md);font-size:var(--ui-font-md)}.ui-select__trigger--lg{padding:var(--ui-spacing-md) var(--ui-spacing-lg);font-size:var(--ui-font-lg)}.ui-select__trigger--outlined{background-color:transparent}.ui-select__trigger--filled{background-color:var(--ui-bg-secondary);border-color:transparent}.ui-select__trigger--filled:hover:not([aria-disabled=true]){background-color:var(--ui-bg-tertiary)}.ui-select__trigger--filled:focus{border-color:var(--ui-border-focus)}.ui-select__value{flex:1;min-width:0;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-select__placeholder{color:var(--ui-text-muted)}.ui-select__icons{display:flex;align-items:center;gap:var(--ui-spacing-xs);flex-shrink:0}.ui-select__clear{display:flex;align-items:center;justify-content:center;padding:2px;background:none;border:none;cursor:pointer;color:var(--ui-text-muted);border-radius:var(--ui-radius-sm);transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-select__clear:hover{color:var(--ui-text);background-color:var(--ui-bg-hover)}.ui-select__arrow{color:var(--ui-text-muted);transition:transform var(--ui-transition-fast)}.ui-select__arrow--open{transform:rotate(180deg)}.ui-select__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:var(--ui-spacing-xs);background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg));overflow:hidden;visibility:hidden;opacity:0;transform:translateY(-8px);transition:opacity var(--ui-transition-fast),transform var(--ui-transition-fast),visibility var(--ui-transition-fast)}.ui-select__dropdown--open{visibility:visible;opacity:1;transform:translateY(0)}.ui-select__search{padding:var(--ui-spacing-sm);border-bottom:1px solid var(--ui-border)}.ui-select__search-input{width:100%;padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-family:inherit;font-size:var(--ui-font-sm);color:var(--ui-text);background-color:var(--ui-bg-secondary);border:1px solid var(--ui-border);border-radius:var(--ui-radius-sm);outline:none}.ui-select__search-input:focus{border-color:var(--ui-border-focus)}.ui-select__options{max-height:var(--ui-dropdown-max-height, 300px);overflow-y:auto}.ui-select__empty{padding:var(--ui-spacing-md);text-align:center;color:var(--ui-text-muted);font-size:var(--ui-font-sm)}.ui-select-wrapper--error .ui-select__trigger{border-color:var(--ui-danger)}.ui-select-wrapper--error .ui-select__trigger:focus{box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-danger) 20%,transparent)}.ui-select__error{font-size:var(--ui-font-sm);color:var(--ui-danger)}.ui-select__hint{font-size:var(--ui-font-sm);color:var(--ui-text-muted)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1182
1266
  }
1183
1267
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: SelectComponent, decorators: [{
1184
1268
  type: Component,
1185
- args: [{ selector: 'ui-select', standalone: true, imports: [FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"ui-select-wrapper\"\n [class.ui-select-wrapper--error]=\"error()\"\n [class.ui-select-wrapper--disabled]=\"disabled()\"\n [class.ui-select-wrapper--open]=\"isOpen()\"\n>\n @if (label()) {\n <label class=\"ui-select__label\">\n {{ label() }}\n </label>\n }\n\n <div\n class=\"ui-select__trigger\"\n [class]=\"triggerClasses()\"\n [attr.role]=\"'combobox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <span class=\"ui-select__value\">\n @if (displayValue()) {\n {{ displayValue() }}\n } @else {\n <span class=\"ui-select__placeholder\">{{ placeholder() }}</span>\n }\n </span>\n\n <div class=\"ui-select__icons\">\n @if (clearable() && hasValue() && !disabled()) {\n <button\n type=\"button\"\n class=\"ui-select__clear\"\n (click)=\"clear($event)\"\n aria-label=\"Clear selection\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n }\n <svg\n class=\"ui-select__arrow\"\n [class.ui-select__arrow--open]=\"isOpen()\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n </div>\n </div>\n\n <div\n class=\"ui-select__dropdown\"\n [class.ui-select__dropdown--open]=\"isOpen()\"\n [attr.role]=\"'listbox'\"\n [attr.aria-multiselectable]=\"multiple()\"\n >\n @if (searchable()) {\n <div class=\"ui-select__search\">\n <input\n type=\"text\"\n class=\"ui-select__search-input\"\n placeholder=\"Search...\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"searchQuery.set($event)\"\n (keydown)=\"handleSearchKeydown($event)\"\n #searchInput\n />\n </div>\n }\n <div class=\"ui-select__options\">\n <ng-content />\n </div>\n </div>\n\n @if (error()) {\n <span class=\"ui-select__error\">{{ error() }}</span>\n }\n @if (hint() && !error()) {\n <span class=\"ui-select__hint\">{{ hint() }}</span>\n }\n</div>\n", styles: [":host{display:block;position:relative}.ui-select-wrapper{display:flex;flex-direction:column;gap:var(--ui-spacing-xs)}.ui-select__label{font-size:var(--ui-font-sm);font-weight:500;color:var(--ui-text)}.ui-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-sm);width:100%;background-color:var(--ui-bg);border:1px solid var(--ui-border);border-radius:var(--ui-radius-md);cursor:pointer;transition:border-color var(--ui-transition-fast),box-shadow var(--ui-transition-fast)}.ui-select__trigger:hover:not([aria-disabled=true]){border-color:var(--ui-border-hover)}.ui-select__trigger:focus{outline:none;border-color:var(--ui-border-focus);box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent)}.ui-select__trigger[aria-disabled=true]{background-color:var(--ui-bg-secondary);color:var(--ui-text-disabled);cursor:not-allowed}.ui-select__trigger--sm{padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-size:var(--ui-font-sm)}.ui-select__trigger--md{padding:var(--ui-spacing-sm) var(--ui-spacing-md);font-size:var(--ui-font-md)}.ui-select__trigger--lg{padding:var(--ui-spacing-md) var(--ui-spacing-lg);font-size:var(--ui-font-lg)}.ui-select__trigger--outlined{background-color:transparent}.ui-select__trigger--filled{background-color:var(--ui-bg-secondary);border-color:transparent}.ui-select__trigger--filled:hover:not([aria-disabled=true]){background-color:var(--ui-bg-tertiary)}.ui-select__trigger--filled:focus{border-color:var(--ui-border-focus)}.ui-select__value{flex:1;min-width:0;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-select__placeholder{color:var(--ui-text-muted)}.ui-select__icons{display:flex;align-items:center;gap:var(--ui-spacing-xs);flex-shrink:0}.ui-select__clear{display:flex;align-items:center;justify-content:center;padding:2px;background:none;border:none;cursor:pointer;color:var(--ui-text-muted);border-radius:var(--ui-radius-sm);transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-select__clear:hover{color:var(--ui-text);background-color:var(--ui-bg-hover)}.ui-select__arrow{color:var(--ui-text-muted);transition:transform var(--ui-transition-fast)}.ui-select__arrow--open{transform:rotate(180deg)}.ui-select__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:var(--ui-spacing-xs);background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg));overflow:hidden;visibility:hidden;opacity:0;transform:translateY(-8px);transition:opacity var(--ui-transition-fast),transform var(--ui-transition-fast),visibility var(--ui-transition-fast)}.ui-select__dropdown--open{visibility:visible;opacity:1;transform:translateY(0)}.ui-select__search{padding:var(--ui-spacing-sm);border-bottom:1px solid var(--ui-border)}.ui-select__search-input{width:100%;padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-family:inherit;font-size:var(--ui-font-sm);color:var(--ui-text);background-color:var(--ui-bg-secondary);border:1px solid var(--ui-border);border-radius:var(--ui-radius-sm);outline:none}.ui-select__search-input:focus{border-color:var(--ui-border-focus)}.ui-select__options{max-height:var(--ui-dropdown-max-height, 300px);overflow-y:auto}.ui-select__empty{padding:var(--ui-spacing-md);text-align:center;color:var(--ui-text-muted);font-size:var(--ui-font-sm)}.ui-select-wrapper--error .ui-select__trigger{border-color:var(--ui-danger)}.ui-select-wrapper--error .ui-select__trigger:focus{box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-danger) 20%,transparent)}.ui-select__error{font-size:var(--ui-font-sm);color:var(--ui-danger)}.ui-select__hint{font-size:var(--ui-font-sm);color:var(--ui-text-muted)}\n"] }]
1186
- }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => OptionComponent), { isSignal: true }] }], onDocumentClick: [{
1269
+ args: [{ selector: 'ui-select', standalone: true, imports: [FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"ui-select-wrapper\"\n [class.ui-select-wrapper--error]=\"error()\"\n [class.ui-select-wrapper--disabled]=\"disabled()\"\n [class.ui-select-wrapper--open]=\"isOpen()\"\n>\n @if (label()) {\n <label class=\"ui-select__label\">\n {{ label() }}\n </label>\n }\n\n <div\n #triggerRef\n class=\"ui-select__trigger\"\n [class]=\"triggerClasses()\"\n [attr.role]=\"'combobox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggle()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n >\n <span class=\"ui-select__value\">\n @if (displayValue()) {\n {{ displayValue() }}\n } @else {\n <span class=\"ui-select__placeholder\">{{ placeholder() }}</span>\n }\n </span>\n\n <div class=\"ui-select__icons\">\n @if (clearable() && hasValue() && !disabled()) {\n <button\n type=\"button\"\n class=\"ui-select__clear\"\n (click)=\"clear($event)\"\n aria-label=\"Clear selection\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n }\n <svg\n class=\"ui-select__arrow\"\n [class.ui-select__arrow--open]=\"isOpen()\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <polyline points=\"6 9 12 15 18 9\"></polyline>\n </svg>\n </div>\n </div>\n\n <div\n #dropdownRef\n class=\"ui-select__dropdown\"\n [class.ui-select__dropdown--open]=\"isOpen()\"\n [attr.role]=\"'listbox'\"\n [attr.aria-multiselectable]=\"multiple()\"\n >\n @if (searchable()) {\n <div class=\"ui-select__search\">\n <input\n type=\"text\"\n class=\"ui-select__search-input\"\n placeholder=\"Search...\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"searchQuery.set($event)\"\n (keydown)=\"handleSearchKeydown($event)\"\n #searchInput\n />\n </div>\n }\n <div class=\"ui-select__options\">\n <ng-content />\n </div>\n </div>\n\n @if (error()) {\n <span class=\"ui-select__error\">{{ error() }}</span>\n }\n @if (hint() && !error()) {\n <span class=\"ui-select__hint\">{{ hint() }}</span>\n }\n</div>\n", styles: [":host{display:block;position:relative}.ui-select-wrapper{display:flex;flex-direction:column;gap:var(--ui-spacing-xs)}.ui-select__label{font-size:var(--ui-font-sm);font-weight:500;color:var(--ui-text)}.ui-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-sm);width:100%;background-color:var(--ui-bg);border:1px solid var(--ui-border);border-radius:var(--ui-radius-md);cursor:pointer;transition:border-color var(--ui-transition-fast),box-shadow var(--ui-transition-fast)}.ui-select__trigger:hover:not([aria-disabled=true]){border-color:var(--ui-border-hover)}.ui-select__trigger:focus{outline:none;border-color:var(--ui-border-focus);box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent)}.ui-select__trigger[aria-disabled=true]{background-color:var(--ui-bg-secondary);color:var(--ui-text-disabled);cursor:not-allowed}.ui-select__trigger--sm{padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-size:var(--ui-font-sm)}.ui-select__trigger--md{padding:var(--ui-spacing-sm) var(--ui-spacing-md);font-size:var(--ui-font-md)}.ui-select__trigger--lg{padding:var(--ui-spacing-md) var(--ui-spacing-lg);font-size:var(--ui-font-lg)}.ui-select__trigger--outlined{background-color:transparent}.ui-select__trigger--filled{background-color:var(--ui-bg-secondary);border-color:transparent}.ui-select__trigger--filled:hover:not([aria-disabled=true]){background-color:var(--ui-bg-tertiary)}.ui-select__trigger--filled:focus{border-color:var(--ui-border-focus)}.ui-select__value{flex:1;min-width:0;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-select__placeholder{color:var(--ui-text-muted)}.ui-select__icons{display:flex;align-items:center;gap:var(--ui-spacing-xs);flex-shrink:0}.ui-select__clear{display:flex;align-items:center;justify-content:center;padding:2px;background:none;border:none;cursor:pointer;color:var(--ui-text-muted);border-radius:var(--ui-radius-sm);transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-select__clear:hover{color:var(--ui-text);background-color:var(--ui-bg-hover)}.ui-select__arrow{color:var(--ui-text-muted);transition:transform var(--ui-transition-fast)}.ui-select__arrow--open{transform:rotate(180deg)}.ui-select__dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:var(--ui-spacing-xs);background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg));overflow:hidden;visibility:hidden;opacity:0;transform:translateY(-8px);transition:opacity var(--ui-transition-fast),transform var(--ui-transition-fast),visibility var(--ui-transition-fast)}.ui-select__dropdown--open{visibility:visible;opacity:1;transform:translateY(0)}.ui-select__search{padding:var(--ui-spacing-sm);border-bottom:1px solid var(--ui-border)}.ui-select__search-input{width:100%;padding:var(--ui-spacing-xs) var(--ui-spacing-sm);font-family:inherit;font-size:var(--ui-font-sm);color:var(--ui-text);background-color:var(--ui-bg-secondary);border:1px solid var(--ui-border);border-radius:var(--ui-radius-sm);outline:none}.ui-select__search-input:focus{border-color:var(--ui-border-focus)}.ui-select__options{max-height:var(--ui-dropdown-max-height, 300px);overflow-y:auto}.ui-select__empty{padding:var(--ui-spacing-md);text-align:center;color:var(--ui-text-muted);font-size:var(--ui-font-sm)}.ui-select-wrapper--error .ui-select__trigger{border-color:var(--ui-danger)}.ui-select-wrapper--error .ui-select__trigger:focus{box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-danger) 20%,transparent)}.ui-select__error{font-size:var(--ui-font-sm);color:var(--ui-danger)}.ui-select__hint{font-size:var(--ui-font-sm);color:var(--ui-text-muted)}\n"] }]
1270
+ }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => OptionComponent), { isSignal: true }] }], triggerRef: [{
1271
+ type: ViewChild,
1272
+ args: ['triggerRef', { static: true }]
1273
+ }], dropdownRef: [{
1274
+ type: ViewChild,
1275
+ args: ['dropdownRef', { static: true }]
1276
+ }], onDocumentClick: [{
1187
1277
  type: HostListener,
1188
1278
  args: ['document:click', ['$event']]
1189
1279
  }] } });
@@ -1259,7 +1349,10 @@ class DropdownComponent {
1259
1349
  closeOnSelect = input(true, ...(ngDevMode ? [{ debugName: "closeOnSelect" }] : []));
1260
1350
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1261
1351
  focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
1352
+ triggerRef;
1353
+ menuRef;
1262
1354
  elementRef = inject(ElementRef);
1355
+ positionCleanup = null;
1263
1356
  trigger = contentChild(DropdownTriggerDirective, ...(ngDevMode ? [{ debugName: "trigger" }] : []));
1264
1357
  items = contentChildren(DropdownItemComponent, ...(ngDevMode ? [{ debugName: "items" }] : []));
1265
1358
  constructor() {
@@ -1291,11 +1384,17 @@ class DropdownComponent {
1291
1384
  });
1292
1385
  });
1293
1386
  }
1294
- menuPositionClass() {
1295
- return `ui-dropdown__menu--${this.position()}`;
1387
+ ngOnDestroy() {
1388
+ const menu = this.menuRef?.nativeElement;
1389
+ if (menu?.parentElement === document.body) {
1390
+ document.body.removeChild(menu);
1391
+ }
1392
+ this.removePositionListeners();
1296
1393
  }
1297
1394
  onDocumentClick(event) {
1298
- if (!this.elementRef.nativeElement.contains(event.target)) {
1395
+ const target = event.target;
1396
+ if (!this.elementRef.nativeElement.contains(target) &&
1397
+ !this.menuRef?.nativeElement?.contains(target)) {
1299
1398
  this.close();
1300
1399
  }
1301
1400
  }
@@ -1341,10 +1440,12 @@ class DropdownComponent {
1341
1440
  return;
1342
1441
  this.isOpen.set(true);
1343
1442
  this.focusedIndex.set(-1);
1443
+ this.portalMenu();
1344
1444
  }
1345
1445
  close() {
1346
1446
  if (!this.isOpen())
1347
1447
  return;
1448
+ this.unportalMenu();
1348
1449
  this.isOpen.set(false);
1349
1450
  this.focusedIndex.set(-1);
1350
1451
  }
@@ -1370,13 +1471,99 @@ class DropdownComponent {
1370
1471
  this.focusedIndex.set(prev);
1371
1472
  }
1372
1473
  }
1474
+ portalMenu() {
1475
+ const menu = this.menuRef?.nativeElement;
1476
+ if (!menu)
1477
+ return;
1478
+ document.body.appendChild(menu);
1479
+ this.updateMenuPosition();
1480
+ this.addPositionListeners();
1481
+ }
1482
+ unportalMenu() {
1483
+ const menu = this.menuRef?.nativeElement;
1484
+ if (!menu)
1485
+ return;
1486
+ if (menu.parentElement === document.body) {
1487
+ const wrapper = this.elementRef.nativeElement.querySelector('.ui-dropdown');
1488
+ if (wrapper) {
1489
+ wrapper.appendChild(menu);
1490
+ }
1491
+ }
1492
+ menu.style.position = '';
1493
+ menu.style.top = '';
1494
+ menu.style.left = '';
1495
+ menu.style.right = '';
1496
+ menu.style.bottom = '';
1497
+ menu.style.width = '';
1498
+ menu.style.zIndex = '';
1499
+ menu.style.margin = '';
1500
+ this.removePositionListeners();
1501
+ }
1502
+ updateMenuPosition() {
1503
+ const trigger = this.triggerRef?.nativeElement;
1504
+ const menu = this.menuRef?.nativeElement;
1505
+ if (!trigger || !menu)
1506
+ return;
1507
+ const triggerRect = trigger.getBoundingClientRect();
1508
+ const menuHeight = menu.scrollHeight;
1509
+ const gap = 4;
1510
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
1511
+ const spaceAbove = triggerRect.top;
1512
+ const preferTop = this.position().startsWith('top');
1513
+ const openAbove = preferTop
1514
+ ? spaceAbove >= menuHeight + gap || spaceAbove > spaceBelow
1515
+ : spaceBelow < menuHeight + gap && spaceAbove > spaceBelow;
1516
+ const alignEnd = this.position().endsWith('end');
1517
+ menu.style.position = 'fixed';
1518
+ menu.style.zIndex = '99999';
1519
+ menu.style.margin = '0';
1520
+ if (openAbove) {
1521
+ menu.style.top = 'auto';
1522
+ menu.style.bottom = `${window.innerHeight - triggerRect.top + gap}px`;
1523
+ }
1524
+ else {
1525
+ menu.style.top = `${triggerRect.bottom + gap}px`;
1526
+ menu.style.bottom = 'auto';
1527
+ }
1528
+ if (alignEnd) {
1529
+ menu.style.left = 'auto';
1530
+ menu.style.right = `${window.innerWidth - triggerRect.right}px`;
1531
+ }
1532
+ else {
1533
+ menu.style.left = `${triggerRect.left}px`;
1534
+ menu.style.right = 'auto';
1535
+ }
1536
+ }
1537
+ addPositionListeners() {
1538
+ const update = () => {
1539
+ if (this.isOpen()) {
1540
+ this.updateMenuPosition();
1541
+ }
1542
+ };
1543
+ window.addEventListener('scroll', update, true);
1544
+ window.addEventListener('resize', update);
1545
+ this.positionCleanup = () => {
1546
+ window.removeEventListener('scroll', update, true);
1547
+ window.removeEventListener('resize', update);
1548
+ };
1549
+ }
1550
+ removePositionListeners() {
1551
+ this.positionCleanup?.();
1552
+ this.positionCleanup = null;
1553
+ }
1373
1554
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: DropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1374
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: DropdownComponent, isStandalone: true, selector: "ui-dropdown", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown": "onKeydown($event)" } }, queries: [{ propertyName: "trigger", first: true, predicate: DropdownTriggerDirective, descendants: true, isSignal: true }, { propertyName: "items", predicate: DropdownItemComponent, isSignal: true }], ngImport: i0, template: "<div class=\"ui-dropdown\">\n <div class=\"ui-dropdown__trigger\">\n <ng-content select=\"[uiDropdownTrigger]\" />\n </div>\n\n @if (isOpen()) {\n <div\n class=\"ui-dropdown__menu\"\n [class]=\"menuPositionClass()\"\n [attr.role]=\"'menu'\"\n >\n <ng-content />\n </div>\n }\n</div>\n", styles: [":host{display:inline-block;position:relative}.ui-dropdown{position:relative}.ui-dropdown__trigger{display:inline-block}.ui-dropdown__menu{position:absolute;z-index:1000;min-width:160px;padding:var(--ui-spacing-xs) 0;background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg))}.ui-dropdown__menu--bottom-start{top:100%;left:0;margin-top:var(--ui-spacing-xs)}.ui-dropdown__menu--bottom-end{top:100%;right:0;margin-top:var(--ui-spacing-xs)}.ui-dropdown__menu--top-start{bottom:100%;left:0;margin-bottom:var(--ui-spacing-xs)}.ui-dropdown__menu--top-end{bottom:100%;right:0;margin-bottom:var(--ui-spacing-xs)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1555
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.1", type: DropdownComponent, isStandalone: true, selector: "ui-dropdown", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown": "onKeydown($event)" } }, queries: [{ propertyName: "trigger", first: true, predicate: DropdownTriggerDirective, descendants: true, isSignal: true }, { propertyName: "items", predicate: DropdownItemComponent, isSignal: true }], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerRef"], descendants: true, static: true }, { propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"ui-dropdown\">\n <div #triggerRef class=\"ui-dropdown__trigger\">\n <ng-content select=\"[uiDropdownTrigger]\" />\n </div>\n\n <div\n #menuRef\n class=\"ui-dropdown__menu\"\n [class.ui-dropdown__menu--open]=\"isOpen()\"\n [attr.role]=\"'menu'\"\n >\n <ng-content />\n </div>\n</div>\n", styles: [":host{display:inline-block;position:relative}.ui-dropdown{position:relative}.ui-dropdown__trigger{display:inline-block}.ui-dropdown__menu{position:absolute;top:100%;left:0;z-index:1000;min-width:160px;margin-top:var(--ui-spacing-xs);padding:var(--ui-spacing-xs) 0;background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg));overflow:hidden;visibility:hidden;opacity:0;transform:translateY(-8px);transition:opacity var(--ui-transition-fast),transform var(--ui-transition-fast),visibility var(--ui-transition-fast)}.ui-dropdown__menu--open{visibility:visible;opacity:1;transform:translateY(0)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1375
1556
  }
1376
1557
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: DropdownComponent, decorators: [{
1377
1558
  type: Component,
1378
- args: [{ selector: 'ui-dropdown', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ui-dropdown\">\n <div class=\"ui-dropdown__trigger\">\n <ng-content select=\"[uiDropdownTrigger]\" />\n </div>\n\n @if (isOpen()) {\n <div\n class=\"ui-dropdown__menu\"\n [class]=\"menuPositionClass()\"\n [attr.role]=\"'menu'\"\n >\n <ng-content />\n </div>\n }\n</div>\n", styles: [":host{display:inline-block;position:relative}.ui-dropdown{position:relative}.ui-dropdown__trigger{display:inline-block}.ui-dropdown__menu{position:absolute;z-index:1000;min-width:160px;padding:var(--ui-spacing-xs) 0;background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg))}.ui-dropdown__menu--bottom-start{top:100%;left:0;margin-top:var(--ui-spacing-xs)}.ui-dropdown__menu--bottom-end{top:100%;right:0;margin-top:var(--ui-spacing-xs)}.ui-dropdown__menu--top-start{bottom:100%;left:0;margin-bottom:var(--ui-spacing-xs)}.ui-dropdown__menu--top-end{bottom:100%;right:0;margin-bottom:var(--ui-spacing-xs)}\n"] }]
1379
- }], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DropdownTriggerDirective), { isSignal: true }] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DropdownItemComponent), { isSignal: true }] }], onDocumentClick: [{
1559
+ args: [{ selector: 'ui-dropdown', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ui-dropdown\">\n <div #triggerRef class=\"ui-dropdown__trigger\">\n <ng-content select=\"[uiDropdownTrigger]\" />\n </div>\n\n <div\n #menuRef\n class=\"ui-dropdown__menu\"\n [class.ui-dropdown__menu--open]=\"isOpen()\"\n [attr.role]=\"'menu'\"\n >\n <ng-content />\n </div>\n</div>\n", styles: [":host{display:inline-block;position:relative}.ui-dropdown{position:relative}.ui-dropdown__trigger{display:inline-block}.ui-dropdown__menu{position:absolute;top:100%;left:0;z-index:1000;min-width:160px;margin-top:var(--ui-spacing-xs);padding:var(--ui-spacing-xs) 0;background-color:var(--ui-dropdown-bg, var(--ui-bg));border:1px solid var(--ui-dropdown-border, var(--ui-border));border-radius:var(--ui-dropdown-radius, var(--ui-radius-md));box-shadow:var(--ui-dropdown-shadow, var(--ui-shadow-lg));overflow:hidden;visibility:hidden;opacity:0;transform:translateY(-8px);transition:opacity var(--ui-transition-fast),transform var(--ui-transition-fast),visibility var(--ui-transition-fast)}.ui-dropdown__menu--open{visibility:visible;opacity:1;transform:translateY(0)}\n"] }]
1560
+ }], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], triggerRef: [{
1561
+ type: ViewChild,
1562
+ args: ['triggerRef', { static: true }]
1563
+ }], menuRef: [{
1564
+ type: ViewChild,
1565
+ args: ['menuRef', { static: true }]
1566
+ }], trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DropdownTriggerDirective), { isSignal: true }] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DropdownItemComponent), { isSignal: true }] }], onDocumentClick: [{
1380
1567
  type: HostListener,
1381
1568
  args: ['document:click', ['$event']]
1382
1569
  }], onKeydown: [{