quang 20.5.1 → 20.5.2-0

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.
@@ -0,0 +1,59 @@
1
+ import { NgClass, NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { input, computed, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
5
+ import { TranslocoPipe } from '@jsverse/transloco';
6
+ import { QuangBaseComponent } from 'quang/components/shared';
7
+
8
+ class QuangRadioGroupComponent extends QuangBaseComponent {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.radioOptions = input.required(...(ngDevMode ? [{ debugName: "radioOptions" }] : []));
12
+ this.name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
13
+ this.radioPosition = input('left', ...(ngDevMode ? [{ debugName: "radioPosition" }] : []));
14
+ this._radioName = computed(() => this.name() || this.componentId(), ...(ngDevMode ? [{ debugName: "_radioName" }] : []));
15
+ }
16
+ getOptionId(index) {
17
+ return `${this.componentId()}-option-${index}`;
18
+ }
19
+ isOptionDisabled(option) {
20
+ return this._isDisabled() || this.isReadonly() || !!option.disabled;
21
+ }
22
+ getOptionLabel(option) {
23
+ if (option.label !== undefined)
24
+ return option.label;
25
+ if (option.value === null)
26
+ return '';
27
+ return String(option.value);
28
+ }
29
+ onSelectOption(option) {
30
+ if (this.isOptionDisabled(option))
31
+ return;
32
+ this.onChangedHandler(option.value);
33
+ }
34
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: QuangRadioGroupComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
35
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: QuangRadioGroupComponent, isStandalone: true, selector: "quang-radio-group", inputs: { radioOptions: { classPropertyName: "radioOptions", publicName: "radioOptions", isSignal: true, isRequired: true, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, radioPosition: { classPropertyName: "radioPosition", publicName: "radioPosition", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
36
+ {
37
+ provide: NG_VALUE_ACCESSOR,
38
+ useExisting: forwardRef(() => QuangRadioGroupComponent),
39
+ multi: true,
40
+ },
41
+ ], usesInheritance: true, ngImport: i0, template: "<fieldset class=\"mb-3\">\n @if (componentLabel()) {\n <legend\n [id]=\"componentId() + '-label'\"\n class=\"form-label w-100\"\n >\n {{ componentLabel() | transloco }}\n <span [hidden]=\"!_isRequired()\">*</span>\n </legend>\n }\n\n <div\n [attr.aria-labelledby]=\"componentLabel() ? componentId() + '-label' : null\"\n [attr.role]=\"'radiogroup'\"\n >\n @for (option of radioOptions(); track option.value ?? $index; let index = $index) {\n <div\n [class.form-check-reverse]=\"radioPosition() === 'right'\"\n class=\"form-check text-start\"\n >\n <input\n [attr.name]=\"_radioName()\"\n [attr.required]=\"getIsRequiredControl()\"\n [checked]=\"_value() === option.value\"\n [class.is-invalid]=\"_showErrors()\"\n [class.is-valid]=\"_showSuccess()\"\n [disabled]=\"isOptionDisabled(option)\"\n [id]=\"getOptionId(index)\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n [value]=\"option.value ?? ''\"\n (blur)=\"onBlurHandler()\"\n (change)=\"onSelectOption(option)\"\n class=\"form-check-input\"\n type=\"radio\"\n />\n\n <label\n [htmlFor]=\"getOptionId(index)\"\n [ngClass]=\"{ 'cursor-pointer': !isOptionDisabled(option) }\"\n class=\"form-check-label\"\n >\n @if (!option.renderer) {\n {{ getOptionLabel(option) | transloco }}\n }\n <ng-container\n [ngTemplateOutlet]=\"option.renderer ?? null\"\n [ngTemplateOutletContext]=\"{ $implicit: option, selected: _value() === option.value, index: index }\"\n ></ng-container>\n </label>\n </div>\n }\n </div>\n\n <div\n [class.d-block]=\"_showSuccess()\"\n class=\"valid-feedback\"\n >\n {{ successMessage() | transloco }}\n </div>\n <div\n [class.d-block]=\"_showErrors()\"\n class=\"invalid-feedback\"\n >\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n\n @if (helpMessage()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</fieldset>\n", styles: ["legend.form-label{font-size:var(--bs-body-font-size)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
42
+ }
43
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: QuangRadioGroupComponent, decorators: [{
44
+ type: Component,
45
+ args: [{ selector: 'quang-radio-group', providers: [
46
+ {
47
+ provide: NG_VALUE_ACCESSOR,
48
+ useExisting: forwardRef(() => QuangRadioGroupComponent),
49
+ multi: true,
50
+ },
51
+ ], imports: [TranslocoPipe, NgClass, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"mb-3\">\n @if (componentLabel()) {\n <legend\n [id]=\"componentId() + '-label'\"\n class=\"form-label w-100\"\n >\n {{ componentLabel() | transloco }}\n <span [hidden]=\"!_isRequired()\">*</span>\n </legend>\n }\n\n <div\n [attr.aria-labelledby]=\"componentLabel() ? componentId() + '-label' : null\"\n [attr.role]=\"'radiogroup'\"\n >\n @for (option of radioOptions(); track option.value ?? $index; let index = $index) {\n <div\n [class.form-check-reverse]=\"radioPosition() === 'right'\"\n class=\"form-check text-start\"\n >\n <input\n [attr.name]=\"_radioName()\"\n [attr.required]=\"getIsRequiredControl()\"\n [checked]=\"_value() === option.value\"\n [class.is-invalid]=\"_showErrors()\"\n [class.is-valid]=\"_showSuccess()\"\n [disabled]=\"isOptionDisabled(option)\"\n [id]=\"getOptionId(index)\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n [value]=\"option.value ?? ''\"\n (blur)=\"onBlurHandler()\"\n (change)=\"onSelectOption(option)\"\n class=\"form-check-input\"\n type=\"radio\"\n />\n\n <label\n [htmlFor]=\"getOptionId(index)\"\n [ngClass]=\"{ 'cursor-pointer': !isOptionDisabled(option) }\"\n class=\"form-check-label\"\n >\n @if (!option.renderer) {\n {{ getOptionLabel(option) | transloco }}\n }\n <ng-container\n [ngTemplateOutlet]=\"option.renderer ?? null\"\n [ngTemplateOutletContext]=\"{ $implicit: option, selected: _value() === option.value, index: index }\"\n ></ng-container>\n </label>\n </div>\n }\n </div>\n\n <div\n [class.d-block]=\"_showSuccess()\"\n class=\"valid-feedback\"\n >\n {{ successMessage() | transloco }}\n </div>\n <div\n [class.d-block]=\"_showErrors()\"\n class=\"invalid-feedback\"\n >\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n\n @if (helpMessage()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</fieldset>\n", styles: ["legend.form-label{font-size:var(--bs-body-font-size)}\n"] }]
52
+ }], propDecorators: { radioOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "radioOptions", required: true }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], radioPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "radioPosition", required: false }] }] } });
53
+
54
+ /**
55
+ * Generated bundle index. Do not edit.
56
+ */
57
+
58
+ export { QuangRadioGroupComponent };
59
+ //# sourceMappingURL=quang-components-radio-group.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quang-components-radio-group.mjs","sources":["../../../projects/quang/components/radio-group/radio-group.component.ts","../../../projects/quang/components/radio-group/radio-group.component.html","../../../projects/quang/components/radio-group/quang-components-radio-group.ts"],"sourcesContent":["import { NgClass, NgTemplateOutlet } from '@angular/common'\nimport { ChangeDetectionStrategy, Component, TemplateRef, computed, forwardRef, input } from '@angular/core'\nimport { NG_VALUE_ACCESSOR } from '@angular/forms'\n\nimport { TranslocoPipe } from '@jsverse/transloco'\n\nimport { QuangBaseComponent } from 'quang/components/shared'\n\nexport type RadioPosition = 'left' | 'right'\n\nexport interface QuangRadioOptionTemplateContext<T extends string | number | null = string | number | null> {\n $implicit: RadioOption<T>\n selected: boolean\n index: number\n}\n\nexport interface RadioOption<T extends string | number | null = string | number | null> {\n value: T\n label?: string\n disabled?: boolean\n renderer?: TemplateRef<QuangRadioOptionTemplateContext<T>>\n}\n\n@Component({\n selector: 'quang-radio-group',\n templateUrl: './radio-group.component.html',\n styleUrl: './radio-group.component.scss',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => QuangRadioGroupComponent),\n multi: true,\n },\n ],\n imports: [TranslocoPipe, NgClass, NgTemplateOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class QuangRadioGroupComponent extends QuangBaseComponent<string | number | null> {\n radioOptions = input.required<RadioOption[]>()\n\n name = input<string>('')\n\n radioPosition = input<RadioPosition>('left')\n\n _radioName = computed(() => this.name() || this.componentId())\n\n getOptionId(index: number): string {\n return `${this.componentId()}-option-${index}`\n }\n\n isOptionDisabled(option: RadioOption): boolean {\n return this._isDisabled() || this.isReadonly() || !!option.disabled\n }\n\n getOptionLabel(option: RadioOption): string {\n if (option.label !== undefined) return option.label\n if (option.value === null) return ''\n return String(option.value)\n }\n\n onSelectOption(option: RadioOption): void {\n if (this.isOptionDisabled(option)) return\n this.onChangedHandler(option.value)\n }\n}\n","<fieldset class=\"mb-3\">\n @if (componentLabel()) {\n <legend\n [id]=\"componentId() + '-label'\"\n class=\"form-label w-100\"\n >\n {{ componentLabel() | transloco }}\n <span [hidden]=\"!_isRequired()\">*</span>\n </legend>\n }\n\n <div\n [attr.aria-labelledby]=\"componentLabel() ? componentId() + '-label' : null\"\n [attr.role]=\"'radiogroup'\"\n >\n @for (option of radioOptions(); track option.value ?? $index; let index = $index) {\n <div\n [class.form-check-reverse]=\"radioPosition() === 'right'\"\n class=\"form-check text-start\"\n >\n <input\n [attr.name]=\"_radioName()\"\n [attr.required]=\"getIsRequiredControl()\"\n [checked]=\"_value() === option.value\"\n [class.is-invalid]=\"_showErrors()\"\n [class.is-valid]=\"_showSuccess()\"\n [disabled]=\"isOptionDisabled(option)\"\n [id]=\"getOptionId(index)\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n [value]=\"option.value ?? ''\"\n (blur)=\"onBlurHandler()\"\n (change)=\"onSelectOption(option)\"\n class=\"form-check-input\"\n type=\"radio\"\n />\n\n <label\n [htmlFor]=\"getOptionId(index)\"\n [ngClass]=\"{ 'cursor-pointer': !isOptionDisabled(option) }\"\n class=\"form-check-label\"\n >\n @if (!option.renderer) {\n {{ getOptionLabel(option) | transloco }}\n }\n <ng-container\n [ngTemplateOutlet]=\"option.renderer ?? null\"\n [ngTemplateOutletContext]=\"{ $implicit: option, selected: _value() === option.value, index: index }\"\n ></ng-container>\n </label>\n </div>\n }\n </div>\n\n <div\n [class.d-block]=\"_showSuccess()\"\n class=\"valid-feedback\"\n >\n {{ successMessage() | transloco }}\n </div>\n <div\n [class.d-block]=\"_showErrors()\"\n class=\"invalid-feedback\"\n >\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n\n @if (helpMessage()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</fieldset>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;AAqCM,MAAO,wBAAyB,SAAQ,kBAA0C,CAAA;AAdxF,IAAA,WAAA,GAAA;;AAeE,QAAA,IAAA,CAAA,YAAY,GAAG,KAAK,CAAC,QAAQ,uDAAiB;AAE9C,QAAA,IAAA,CAAA,IAAI,GAAG,KAAK,CAAS,EAAE,gDAAC;AAExB,QAAA,IAAA,CAAA,aAAa,GAAG,KAAK,CAAgB,MAAM,yDAAC;AAE5C,QAAA,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,WAAW,EAAE,sDAAC;AAoB/D,IAAA;AAlBC,IAAA,WAAW,CAAC,KAAa,EAAA;QACvB,OAAO,CAAA,EAAG,IAAI,CAAC,WAAW,EAAE,CAAA,QAAA,EAAW,KAAK,EAAE;IAChD;AAEA,IAAA,gBAAgB,CAAC,MAAmB,EAAA;AAClC,QAAA,OAAO,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ;IACrE;AAEA,IAAA,cAAc,CAAC,MAAmB,EAAA;AAChC,QAAA,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,KAAK;AACnD,QAAA,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI;AAAE,YAAA,OAAO,EAAE;AACpC,QAAA,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;IAC7B;AAEA,IAAA,cAAc,CAAC,MAAmB,EAAA;AAChC,QAAA,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE;AACnC,QAAA,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC;IACrC;+GA1BW,wBAAwB,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAxB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,wBAAwB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,EAAA,YAAA,EAAA,EAAA,iBAAA,EAAA,cAAA,EAAA,UAAA,EAAA,cAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,aAAA,EAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,SAAA,EAVxB;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,iBAAiB;AAC1B,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,wBAAwB,CAAC;AACvD,gBAAA,KAAK,EAAE,IAAI;AACZ,aAAA;AACF,SAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECjCH,2zEA6EA,EAAA,MAAA,EAAA,CAAA,yDAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,ED3C2B,OAAO,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,gBAAgB,+IAAxC,aAAa,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAGZ,wBAAwB,EAAA,UAAA,EAAA,CAAA;kBAdpC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,mBAAmB,EAAA,SAAA,EAGlB;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,iBAAiB;AAC1B,4BAAA,WAAW,EAAE,UAAU,CAAC,8BAA8B,CAAC;AACvD,4BAAA,KAAK,EAAE,IAAI;AACZ,yBAAA;qBACF,EAAA,OAAA,EACQ,CAAC,aAAa,EAAE,OAAO,EAAE,gBAAgB,CAAC,EAAA,eAAA,EAClC,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,2zEAAA,EAAA,MAAA,EAAA,CAAA,yDAAA,CAAA,EAAA;;;AEnCjD;;AAEG;;;;"}
@@ -1,4 +1,4 @@
1
- import { NgClass } from '@angular/common';
1
+ import { NgClass, NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { input, viewChild, signal, computed, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
4
4
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -144,6 +144,9 @@ class QuangSelectComponent extends QuangBaseComponent {
144
144
  buttonEl.focus();
145
145
  }
146
146
  }
147
+ getOptionIndex(option) {
148
+ return this.selectOptions().findIndex((x) => x.value === option.value);
149
+ }
147
150
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: QuangSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
148
151
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: QuangSelectComponent, isStandalone: true, selector: "quang-select", inputs: { selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, optionListMaxHeight: { classPropertyName: "optionListMaxHeight", publicName: "optionListMaxHeight", isSignal: true, isRequired: false, transformFunction: null }, selectOptions: { classPropertyName: "selectOptions", publicName: "selectOptions", isSignal: true, isRequired: true, transformFunction: null }, scrollBehaviorOnOpen: { classPropertyName: "scrollBehaviorOnOpen", publicName: "scrollBehaviorOnOpen", isSignal: true, isRequired: false, transformFunction: null }, translateValue: { classPropertyName: "translateValue", publicName: "translateValue", isSignal: true, isRequired: false, transformFunction: null }, nullOption: { classPropertyName: "nullOption", publicName: "nullOption", isSignal: true, isRequired: false, transformFunction: null }, autoSelectSingleOption: { classPropertyName: "autoSelectSingleOption", publicName: "autoSelectSingleOption", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
149
152
  {
@@ -155,7 +158,7 @@ class QuangSelectComponent extends QuangBaseComponent {
155
158
  provide: QuangOptionListComponent,
156
159
  multi: false,
157
160
  },
158
- ], viewQueries: [{ propertyName: "selectButton", first: true, predicate: ["selectButton"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n (mouseleave)=\"onMouseLeaveCallback()\"\n class=\"mb-3\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label d-flex gap-2\"\n >\n <div>\n <span>{{ componentLabel() | transloco }}</span>\n <span [hidden]=\"!_isRequired()\">*</span>\n </div>\n @if (helpMessage() && helpMessageTooltip()) {\n <div [quangTooltip]=\"helpMessage() | transloco\">\n <ng-content select=\"[help-icon]\" />\n </div>\n }\n </label>\n }\n <div\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n class=\"option-list-container\"\n >\n <button\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n [disabled]=\"_isDisabled()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n (click)=\"changeOptionsVisibility()\"\n (keydown)=\"onButtonKeydown($event)\"\n #selectButton\n aria-haspopup=\"listbox\"\n class=\"form-select\"\n type=\"button\"\n >\n <div class=\"content\">\n @if (_selectedItems()?.length) {\n @for (val of _selectedItems(); track val.value; let last = $last) {\n {{ translateValue() ? (val.label | transloco) : val.label }}{{ !last ? ', ' : '' }}\n }\n } @else {\n <ng-container>{{ componentPlaceholder() | transloco }}</ng-container>\n }\n </div>\n </button>\n\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_value()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"nullOption()\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"selectButton\"\n [selectionMode]=\"selectionMode()\"\n [selectOptions]=\"selectOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurHandler()\"\n (changedHandler)=\"onChangedHandler($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n ></quang-option-list>\n }\n </div>\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n @if (helpMessage() && !helpMessageTooltip()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</div>\n", styles: [":host{display:block}:host .option-list-container{position:relative}.form-select{height:2.375rem}.form-select:disabled{background-image:unset}.content{text-align:left;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: QuangOptionListComponent, selector: "quang-option-list", inputs: ["selectionMode", "optionListMaxHeight", "selectOptions", "selectButtonRef", "_value", "_isDisabled", "componentClass", "componentLabel", "componentTabIndex", "translateValue", "nullOption", "scrollBehaviorOnOpen", "parentType", "parentID"], outputs: ["changedHandler", "blurHandler", "escapePressed", "tabPressed"] }, { kind: "directive", type: QuangTooltipDirective, selector: "[quangTooltip]", inputs: ["quangTooltip", "showMethod"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
161
+ ], viewQueries: [{ propertyName: "selectButton", first: true, predicate: ["selectButton"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n (mouseleave)=\"onMouseLeaveCallback()\"\n class=\"mb-3\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label d-flex gap-2\"\n >\n <div>\n <span>{{ componentLabel() | transloco }}</span>\n <span [hidden]=\"!_isRequired()\">*</span>\n </div>\n @if (helpMessage() && helpMessageTooltip()) {\n <div [quangTooltip]=\"helpMessage() | transloco\">\n <ng-content select=\"[help-icon]\" />\n </div>\n }\n </label>\n }\n <div\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n class=\"option-list-container\"\n >\n <button\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n [disabled]=\"_isDisabled()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n (click)=\"changeOptionsVisibility()\"\n (keydown)=\"onButtonKeydown($event)\"\n #selectButton\n aria-haspopup=\"listbox\"\n class=\"form-select\"\n type=\"button\"\n >\n <div\n [class.multiple]=\"selectionMode() === 'multiple'\"\n class=\"content\"\n >\n @if (_selectedItems()?.length) {\n @for (val of _selectedItems(); track val; let last = $last) {\n <span class=\"selected-item\">\n @if (val.renderer) {\n <ng-container\n [ngTemplateOutlet]=\"val.renderer\"\n [ngTemplateOutletContext]=\"{ $implicit: val, selected: true, index: getOptionIndex(val) }\"\n ></ng-container>\n } @else {\n {{ translateValue() ? (val.label | transloco) : val.label }}\n }\n </span>\n @if (!last) {\n <span class=\"separator\">, </span>\n }\n }\n } @else {\n <ng-container>{{ componentPlaceholder() | transloco }}</ng-container>\n }\n </div>\n </button>\n\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_value()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"nullOption()\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"selectButton\"\n [selectionMode]=\"selectionMode()\"\n [selectOptions]=\"selectOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurHandler()\"\n (changedHandler)=\"onChangedHandler($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n ></quang-option-list>\n }\n </div>\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n @if (helpMessage() && !helpMessageTooltip()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</div>\n", styles: [":host{display:block}:host .option-list-container{position:relative}.form-select{height:2.375rem;overflow:hidden;display:flex;align-items:center}.form-select:disabled{background-image:unset}.content{flex:1;min-width:0;text-align:left;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.content.multiple{display:block;text-overflow:unset;overflow:hidden;white-space:nowrap;max-width:100%}.selected-item{display:inline-flex;align-items:center;max-height:100%;white-space:nowrap}.separator{white-space:pre}:host ::ng-deep .content .d-flex{display:inline-flex!important}:host ::ng-deep .content *{max-height:100%}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QuangOptionListComponent, selector: "quang-option-list", inputs: ["selectionMode", "optionListMaxHeight", "selectOptions", "selectButtonRef", "_value", "_isDisabled", "componentClass", "componentLabel", "componentTabIndex", "translateValue", "nullOption", "scrollBehaviorOnOpen", "parentType", "parentID"], outputs: ["changedHandler", "blurHandler", "escapePressed", "tabPressed"] }, { kind: "directive", type: QuangTooltipDirective, selector: "[quangTooltip]", inputs: ["quangTooltip", "showMethod"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
159
162
  }
160
163
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: QuangSelectComponent, decorators: [{
161
164
  type: Component,
@@ -169,7 +172,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
169
172
  provide: QuangOptionListComponent,
170
173
  multi: false,
171
174
  },
172
- ], imports: [TranslocoPipe, NgClass, QuangOptionListComponent, QuangTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n (mouseleave)=\"onMouseLeaveCallback()\"\n class=\"mb-3\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label d-flex gap-2\"\n >\n <div>\n <span>{{ componentLabel() | transloco }}</span>\n <span [hidden]=\"!_isRequired()\">*</span>\n </div>\n @if (helpMessage() && helpMessageTooltip()) {\n <div [quangTooltip]=\"helpMessage() | transloco\">\n <ng-content select=\"[help-icon]\" />\n </div>\n }\n </label>\n }\n <div\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n class=\"option-list-container\"\n >\n <button\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n [disabled]=\"_isDisabled()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n (click)=\"changeOptionsVisibility()\"\n (keydown)=\"onButtonKeydown($event)\"\n #selectButton\n aria-haspopup=\"listbox\"\n class=\"form-select\"\n type=\"button\"\n >\n <div class=\"content\">\n @if (_selectedItems()?.length) {\n @for (val of _selectedItems(); track val.value; let last = $last) {\n {{ translateValue() ? (val.label | transloco) : val.label }}{{ !last ? ', ' : '' }}\n }\n } @else {\n <ng-container>{{ componentPlaceholder() | transloco }}</ng-container>\n }\n </div>\n </button>\n\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_value()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"nullOption()\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"selectButton\"\n [selectionMode]=\"selectionMode()\"\n [selectOptions]=\"selectOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurHandler()\"\n (changedHandler)=\"onChangedHandler($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n ></quang-option-list>\n }\n </div>\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n @if (helpMessage() && !helpMessageTooltip()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</div>\n", styles: [":host{display:block}:host .option-list-container{position:relative}.form-select{height:2.375rem}.form-select:disabled{background-image:unset}.content{text-align:left;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}\n"] }]
175
+ ], imports: [TranslocoPipe, NgClass, NgTemplateOutlet, QuangOptionListComponent, QuangTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n (mouseleave)=\"onMouseLeaveCallback()\"\n class=\"mb-3\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label d-flex gap-2\"\n >\n <div>\n <span>{{ componentLabel() | transloco }}</span>\n <span [hidden]=\"!_isRequired()\">*</span>\n </div>\n @if (helpMessage() && helpMessageTooltip()) {\n <div [quangTooltip]=\"helpMessage() | transloco\">\n <ng-content select=\"[help-icon]\" />\n </div>\n }\n </label>\n }\n <div\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n class=\"option-list-container\"\n >\n <button\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n [disabled]=\"_isDisabled()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n (click)=\"changeOptionsVisibility()\"\n (keydown)=\"onButtonKeydown($event)\"\n #selectButton\n aria-haspopup=\"listbox\"\n class=\"form-select\"\n type=\"button\"\n >\n <div\n [class.multiple]=\"selectionMode() === 'multiple'\"\n class=\"content\"\n >\n @if (_selectedItems()?.length) {\n @for (val of _selectedItems(); track val; let last = $last) {\n <span class=\"selected-item\">\n @if (val.renderer) {\n <ng-container\n [ngTemplateOutlet]=\"val.renderer\"\n [ngTemplateOutletContext]=\"{ $implicit: val, selected: true, index: getOptionIndex(val) }\"\n ></ng-container>\n } @else {\n {{ translateValue() ? (val.label | transloco) : val.label }}\n }\n </span>\n @if (!last) {\n <span class=\"separator\">, </span>\n }\n }\n } @else {\n <ng-container>{{ componentPlaceholder() | transloco }}</ng-container>\n }\n </div>\n </button>\n\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_value()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"nullOption()\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"selectButton\"\n [selectionMode]=\"selectionMode()\"\n [selectOptions]=\"selectOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurHandler()\"\n (changedHandler)=\"onChangedHandler($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n ></quang-option-list>\n }\n </div>\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n @if (helpMessage() && !helpMessageTooltip()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</div>\n", styles: [":host{display:block}:host .option-list-container{position:relative}.form-select{height:2.375rem;overflow:hidden;display:flex;align-items:center}.form-select:disabled{background-image:unset}.content{flex:1;min-width:0;text-align:left;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.content.multiple{display:block;text-overflow:unset;overflow:hidden;white-space:nowrap;max-width:100%}.selected-item{display:inline-flex;align-items:center;max-height:100%;white-space:nowrap}.separator{white-space:pre}:host ::ng-deep .content .d-flex{display:inline-flex!important}:host ::ng-deep .content *{max-height:100%}\n"] }]
173
176
  }], ctorParameters: () => [], propDecorators: { selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], optionListMaxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionListMaxHeight", required: false }] }], selectOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectOptions", required: true }] }], scrollBehaviorOnOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollBehaviorOnOpen", required: false }] }], selectButton: [{ type: i0.ViewChild, args: ['selectButton', { isSignal: true }] }], translateValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "translateValue", required: false }] }], nullOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "nullOption", required: false }] }], autoSelectSingleOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoSelectSingleOption", required: false }] }] } });
174
177
 
175
178
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"quang-components-select.mjs","sources":["../../../projects/quang/components/select/select.component.ts","../../../projects/quang/components/select/select.component.html","../../../projects/quang/components/select/quang-components-select.ts"],"sourcesContent":["import { NgClass } from '@angular/common'\nimport {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n forwardRef,\n input,\n signal,\n viewChild,\n} from '@angular/core'\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'\nimport { NG_VALUE_ACCESSOR } from '@angular/forms'\n\nimport { TranslocoPipe } from '@jsverse/transloco'\nimport { QuangTooltipDirective } from 'quang/overlay/tooltip'\nimport { combineLatest, filter } from 'rxjs'\n\nimport {\n OptionListParentType,\n QuangBaseComponent,\n QuangOptionListComponent,\n SelectOption,\n} from 'quang/components/shared'\n\n@Component({\n selector: 'quang-select',\n templateUrl: './select.component.html',\n styleUrl: './select.component.scss',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => QuangSelectComponent),\n multi: true,\n },\n {\n provide: QuangOptionListComponent,\n multi: false,\n },\n ],\n imports: [TranslocoPipe, NgClass, QuangOptionListComponent, QuangTooltipDirective],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Select component for choosing one or multiple options from a dropdown.\n *\n * @usageNotes\n * This component supports both single and multiple selection modes. It can be configured\n * to display a list of options and allows users to select one or more of them by setting the `selectionMode` property to either `single` or `multiple`.\n */\nexport class QuangSelectComponent\n extends QuangBaseComponent<string | number | string[] | number[] | null>\n implements AfterViewInit\n{\n selectionMode = input<'single' | 'multiple'>('single')\n\n /**\n * Set the max height of the selection list before scrolling.\n * Default: 18rem\n * @default 18rem\n */\n optionListMaxHeight = input<string>('18rem')\n\n selectOptions = input.required<SelectOption[]>()\n\n scrollBehaviorOnOpen = input<ScrollBehavior>('smooth')\n\n selectButton = viewChild<ElementRef<HTMLButtonElement>>('selectButton')\n\n /** Whether the option list is currently visible */\n _showOptions = signal<boolean>(false)\n\n _selectedItems = computed(() => {\n if (this._value() !== null) {\n const targetValue = this._value()\n return this.selectOptions().filter((x) => {\n if (Array.isArray(targetValue)) {\n return targetValue.some((k) => k === x.value)\n }\n return targetValue === x.value\n })\n }\n return null\n })\n\n translateValue = input<boolean>(true)\n\n nullOption = input<boolean>(true)\n\n autoSelectSingleOption = input<boolean>(false)\n\n readonly ParentType = OptionListParentType.SELECT\n\n constructor() {\n super()\n combineLatest([toObservable(this.autoSelectSingleOption), toObservable(this.selectOptions)])\n .pipe(\n takeUntilDestroyed(this.destroyRef),\n filter(([autoselect, options]) => autoselect === true && options.length === 1)\n )\n .subscribe(([_, options]) => {\n if (this._value() === null || this._value() === undefined || this._value() === '') {\n this.onChangedHandler(options[0].value)\n }\n })\n }\n\n changeOptionsVisibility(): void {\n if (this.isReadonly()) return\n if (this._showOptions()) {\n this.hideOptionVisibility()\n } else {\n this.showOptionVisibility()\n }\n }\n\n showOptionVisibility(): void {\n this._showOptions.set(true)\n }\n\n hideOptionVisibility(): void {\n this._showOptions.set(false)\n }\n\n override onBlurHandler() {\n if (this.selectionMode() === 'single') {\n this.hideOptionVisibility()\n super.onBlurHandler()\n }\n }\n\n override onChangedHandler(value: string | number | string[] | number[] | null): void {\n super.onChangedHandler(value)\n if (this.selectionMode() === 'single') {\n this.hideOptionVisibility()\n // Return focus to button after selection\n this.focusButton()\n }\n }\n\n onMouseLeaveCallback() {\n if (this.selectionMode() === 'multiple') {\n this.hideOptionVisibility()\n }\n }\n\n /**\n * Handles keydown events on the select button for accessibility.\n * @param event The keyboard event\n */\n onButtonKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowUp':\n // Open dropdown if closed\n if (!this._showOptions()) {\n event.preventDefault()\n this.showOptionVisibility()\n }\n break\n case ' ':\n case 'Enter':\n // Toggle dropdown with Space or Enter\n if (!this._showOptions()) {\n event.preventDefault()\n this.showOptionVisibility()\n }\n break\n case 'Escape':\n // Close dropdown and keep focus on button\n if (this._showOptions()) {\n event.preventDefault()\n this.onEscapePressed()\n }\n break\n }\n }\n\n /**\n * Handles Escape key press from option list.\n * Closes dropdown and returns focus to button.\n */\n onEscapePressed(): void {\n this.hideOptionVisibility()\n this.focusButton()\n }\n\n /**\n * Handles Tab key press from option list.\n * Closes dropdown and allows natural tab navigation.\n */\n onTabPressed(_event: { shiftKey: boolean }): void {\n this.hideOptionVisibility()\n }\n\n /**\n * Sets focus to the select button element.\n */\n focusButton(): void {\n const buttonEl = this.selectButton()?.nativeElement\n if (buttonEl) {\n buttonEl.focus()\n }\n }\n}\n","<div\n (mouseleave)=\"onMouseLeaveCallback()\"\n class=\"mb-3\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label d-flex gap-2\"\n >\n <div>\n <span>{{ componentLabel() | transloco }}</span>\n <span [hidden]=\"!_isRequired()\">*</span>\n </div>\n @if (helpMessage() && helpMessageTooltip()) {\n <div [quangTooltip]=\"helpMessage() | transloco\">\n <ng-content select=\"[help-icon]\" />\n </div>\n }\n </label>\n }\n <div\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n class=\"option-list-container\"\n >\n <button\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n [disabled]=\"_isDisabled()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n (click)=\"changeOptionsVisibility()\"\n (keydown)=\"onButtonKeydown($event)\"\n #selectButton\n aria-haspopup=\"listbox\"\n class=\"form-select\"\n type=\"button\"\n >\n <div class=\"content\">\n @if (_selectedItems()?.length) {\n @for (val of _selectedItems(); track val.value; let last = $last) {\n {{ translateValue() ? (val.label | transloco) : val.label }}{{ !last ? ', ' : '' }}\n }\n } @else {\n <ng-container>{{ componentPlaceholder() | transloco }}</ng-container>\n }\n </div>\n </button>\n\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_value()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"nullOption()\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"selectButton\"\n [selectionMode]=\"selectionMode()\"\n [selectOptions]=\"selectOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurHandler()\"\n (changedHandler)=\"onChangedHandler($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n ></quang-option-list>\n }\n </div>\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n @if (helpMessage() && !helpMessageTooltip()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;AA4CA;;;;;;AAMG;AACG,MAAO,oBACX,SAAQ,kBAAgE,CAAA;AA0CxE,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;AAxCT,QAAA,IAAA,CAAA,aAAa,GAAG,KAAK,CAAwB,QAAQ,yDAAC;AAEtD;;;;AAIG;AACH,QAAA,IAAA,CAAA,mBAAmB,GAAG,KAAK,CAAS,OAAO,+DAAC;AAE5C,QAAA,IAAA,CAAA,aAAa,GAAG,KAAK,CAAC,QAAQ,wDAAkB;AAEhD,QAAA,IAAA,CAAA,oBAAoB,GAAG,KAAK,CAAiB,QAAQ,gEAAC;AAEtD,QAAA,IAAA,CAAA,YAAY,GAAG,SAAS,CAAgC,cAAc,wDAAC;;AAGvE,QAAA,IAAA,CAAA,YAAY,GAAG,MAAM,CAAU,KAAK,wDAAC;AAErC,QAAA,IAAA,CAAA,cAAc,GAAG,QAAQ,CAAC,MAAK;AAC7B,YAAA,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;AAC1B,gBAAA,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE;gBACjC,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAI;AACvC,oBAAA,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE;AAC9B,wBAAA,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;oBAC/C;AACA,oBAAA,OAAO,WAAW,KAAK,CAAC,CAAC,KAAK;AAChC,gBAAA,CAAC,CAAC;YACJ;AACA,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,0DAAC;AAEF,QAAA,IAAA,CAAA,cAAc,GAAG,KAAK,CAAU,IAAI,0DAAC;AAErC,QAAA,IAAA,CAAA,UAAU,GAAG,KAAK,CAAU,IAAI,sDAAC;AAEjC,QAAA,IAAA,CAAA,sBAAsB,GAAG,KAAK,CAAU,KAAK,kEAAC;AAErC,QAAA,IAAA,CAAA,UAAU,GAAG,oBAAoB,CAAC,MAAM;AAI/C,QAAA,aAAa,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,sBAAsB,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AACxF,aAAA,IAAI,CACH,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,EACnC,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,UAAU,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;aAE/E,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAI;YAC1B,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;gBACjF,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YACzC;AACF,QAAA,CAAC,CAAC;IACN;IAEA,uBAAuB,GAAA;QACrB,IAAI,IAAI,CAAC,UAAU,EAAE;YAAE;AACvB,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE,EAAE;YACvB,IAAI,CAAC,oBAAoB,EAAE;QAC7B;aAAO;YACL,IAAI,CAAC,oBAAoB,EAAE;QAC7B;IACF;IAEA,oBAAoB,GAAA;AAClB,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;IAC7B;IAEA,oBAAoB,GAAA;AAClB,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;IAC9B;IAES,aAAa,GAAA;AACpB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE,KAAK,QAAQ,EAAE;YACrC,IAAI,CAAC,oBAAoB,EAAE;YAC3B,KAAK,CAAC,aAAa,EAAE;QACvB;IACF;AAES,IAAA,gBAAgB,CAAC,KAAmD,EAAA;AAC3E,QAAA,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC;AAC7B,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE,KAAK,QAAQ,EAAE;YACrC,IAAI,CAAC,oBAAoB,EAAE;;YAE3B,IAAI,CAAC,WAAW,EAAE;QACpB;IACF;IAEA,oBAAoB,GAAA;AAClB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE,KAAK,UAAU,EAAE;YACvC,IAAI,CAAC,oBAAoB,EAAE;QAC7B;IACF;AAEA;;;AAGG;AACH,IAAA,eAAe,CAAC,KAAoB,EAAA;AAClC,QAAA,QAAQ,KAAK,CAAC,GAAG;AACf,YAAA,KAAK,WAAW;AAChB,YAAA,KAAK,SAAS;;AAEZ,gBAAA,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE;oBACxB,KAAK,CAAC,cAAc,EAAE;oBACtB,IAAI,CAAC,oBAAoB,EAAE;gBAC7B;gBACA;AACF,YAAA,KAAK,GAAG;AACR,YAAA,KAAK,OAAO;;AAEV,gBAAA,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE;oBACxB,KAAK,CAAC,cAAc,EAAE;oBACtB,IAAI,CAAC,oBAAoB,EAAE;gBAC7B;gBACA;AACF,YAAA,KAAK,QAAQ;;AAEX,gBAAA,IAAI,IAAI,CAAC,YAAY,EAAE,EAAE;oBACvB,KAAK,CAAC,cAAc,EAAE;oBACtB,IAAI,CAAC,eAAe,EAAE;gBACxB;gBACA;;IAEN;AAEA;;;AAGG;IACH,eAAe,GAAA;QACb,IAAI,CAAC,oBAAoB,EAAE;QAC3B,IAAI,CAAC,WAAW,EAAE;IACpB;AAEA;;;AAGG;AACH,IAAA,YAAY,CAAC,MAA6B,EAAA;QACxC,IAAI,CAAC,oBAAoB,EAAE;IAC7B;AAEA;;AAEG;IACH,WAAW,GAAA;QACT,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa;QACnD,IAAI,QAAQ,EAAE;YACZ,QAAQ,CAAC,KAAK,EAAE;QAClB;IACF;+GAzJW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAApB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,oBAAoB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,EAAA,aAAA,EAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,mBAAA,EAAA,EAAA,iBAAA,EAAA,qBAAA,EAAA,UAAA,EAAA,qBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,aAAA,EAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,oBAAA,EAAA,EAAA,iBAAA,EAAA,sBAAA,EAAA,UAAA,EAAA,sBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,EAAA,iBAAA,EAAA,gBAAA,EAAA,UAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,sBAAA,EAAA,EAAA,iBAAA,EAAA,wBAAA,EAAA,UAAA,EAAA,wBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,SAAA,EArBpB;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,iBAAiB;AAC1B,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,oBAAoB,CAAC;AACnD,gBAAA,KAAK,EAAE,IAAI;AACZ,aAAA;AACD,YAAA;AACE,gBAAA,OAAO,EAAE,wBAAwB;AACjC,gBAAA,KAAK,EAAE,KAAK;AACb,aAAA;SACF,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,cAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,cAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECxCH,uyGA4FA,4RDnD2B,OAAO,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,wBAAwB,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,eAAA,EAAA,qBAAA,EAAA,eAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,aAAA,EAAA,gBAAA,EAAA,gBAAA,EAAA,mBAAA,EAAA,gBAAA,EAAA,YAAA,EAAA,sBAAA,EAAA,YAAA,EAAA,UAAA,CAAA,EAAA,OAAA,EAAA,CAAA,gBAAA,EAAA,aAAA,EAAA,eAAA,EAAA,YAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,qBAAqB,8FAAvE,aAAa,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAUZ,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBAzBhC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,cAAc,EAAA,SAAA,EAGb;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,iBAAiB;AAC1B,4BAAA,WAAW,EAAE,UAAU,CAAC,0BAA0B,CAAC;AACnD,4BAAA,KAAK,EAAE,IAAI;AACZ,yBAAA;AACD,wBAAA;AACE,4BAAA,OAAO,EAAE,wBAAwB;AACjC,4BAAA,KAAK,EAAE,KAAK;AACb,yBAAA;AACF,qBAAA,EAAA,OAAA,EACQ,CAAC,aAAa,EAAE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,CAAC,EAAA,eAAA,EACjE,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,uyGAAA,EAAA,MAAA,EAAA,CAAA,oOAAA,CAAA,EAAA;qiBA0BS,cAAc,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,cAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,UAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,YAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,sBAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,wBAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AEpExE;;AAEG;;;;"}
1
+ {"version":3,"file":"quang-components-select.mjs","sources":["../../../projects/quang/components/select/select.component.ts","../../../projects/quang/components/select/select.component.html","../../../projects/quang/components/select/quang-components-select.ts"],"sourcesContent":["import { NgClass, NgTemplateOutlet } from '@angular/common'\nimport {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n forwardRef,\n input,\n signal,\n viewChild,\n} from '@angular/core'\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'\nimport { NG_VALUE_ACCESSOR } from '@angular/forms'\n\nimport { TranslocoPipe } from '@jsverse/transloco'\nimport { QuangTooltipDirective } from 'quang/overlay/tooltip'\nimport { combineLatest, filter } from 'rxjs'\n\nimport {\n OptionListParentType,\n QuangBaseComponent,\n QuangOptionListComponent,\n SelectOption,\n} from 'quang/components/shared'\n\n@Component({\n selector: 'quang-select',\n templateUrl: './select.component.html',\n styleUrl: './select.component.scss',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => QuangSelectComponent),\n multi: true,\n },\n {\n provide: QuangOptionListComponent,\n multi: false,\n },\n ],\n imports: [TranslocoPipe, NgClass, NgTemplateOutlet, QuangOptionListComponent, QuangTooltipDirective],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Select component for choosing one or multiple options from a dropdown.\n *\n * @usageNotes\n * This component supports both single and multiple selection modes. It can be configured\n * to display a list of options and allows users to select one or more of them by setting the `selectionMode` property to either `single` or `multiple`.\n */\nexport class QuangSelectComponent\n extends QuangBaseComponent<string | number | string[] | number[] | null>\n implements AfterViewInit\n{\n selectionMode = input<'single' | 'multiple'>('single')\n\n /**\n * Set the max height of the selection list before scrolling.\n * Default: 18rem\n * @default 18rem\n */\n optionListMaxHeight = input<string>('18rem')\n\n selectOptions = input.required<SelectOption[]>()\n\n scrollBehaviorOnOpen = input<ScrollBehavior>('smooth')\n\n selectButton = viewChild<ElementRef<HTMLButtonElement>>('selectButton')\n\n /** Whether the option list is currently visible */\n _showOptions = signal<boolean>(false)\n\n _selectedItems = computed(() => {\n if (this._value() !== null) {\n const targetValue = this._value()\n return this.selectOptions().filter((x) => {\n if (Array.isArray(targetValue)) {\n return targetValue.some((k) => k === x.value)\n }\n return targetValue === x.value\n })\n }\n return null\n })\n\n translateValue = input<boolean>(true)\n\n nullOption = input<boolean>(true)\n\n autoSelectSingleOption = input<boolean>(false)\n\n readonly ParentType = OptionListParentType.SELECT\n\n constructor() {\n super()\n combineLatest([toObservable(this.autoSelectSingleOption), toObservable(this.selectOptions)])\n .pipe(\n takeUntilDestroyed(this.destroyRef),\n filter(([autoselect, options]) => autoselect === true && options.length === 1)\n )\n .subscribe(([_, options]) => {\n if (this._value() === null || this._value() === undefined || this._value() === '') {\n this.onChangedHandler(options[0].value)\n }\n })\n }\n\n changeOptionsVisibility(): void {\n if (this.isReadonly()) return\n if (this._showOptions()) {\n this.hideOptionVisibility()\n } else {\n this.showOptionVisibility()\n }\n }\n\n showOptionVisibility(): void {\n this._showOptions.set(true)\n }\n\n hideOptionVisibility(): void {\n this._showOptions.set(false)\n }\n\n override onBlurHandler() {\n if (this.selectionMode() === 'single') {\n this.hideOptionVisibility()\n super.onBlurHandler()\n }\n }\n\n override onChangedHandler(value: string | number | string[] | number[] | null): void {\n super.onChangedHandler(value)\n if (this.selectionMode() === 'single') {\n this.hideOptionVisibility()\n // Return focus to button after selection\n this.focusButton()\n }\n }\n\n onMouseLeaveCallback() {\n if (this.selectionMode() === 'multiple') {\n this.hideOptionVisibility()\n }\n }\n\n /**\n * Handles keydown events on the select button for accessibility.\n * @param event The keyboard event\n */\n onButtonKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowUp':\n // Open dropdown if closed\n if (!this._showOptions()) {\n event.preventDefault()\n this.showOptionVisibility()\n }\n break\n case ' ':\n case 'Enter':\n // Toggle dropdown with Space or Enter\n if (!this._showOptions()) {\n event.preventDefault()\n this.showOptionVisibility()\n }\n break\n case 'Escape':\n // Close dropdown and keep focus on button\n if (this._showOptions()) {\n event.preventDefault()\n this.onEscapePressed()\n }\n break\n }\n }\n\n /**\n * Handles Escape key press from option list.\n * Closes dropdown and returns focus to button.\n */\n onEscapePressed(): void {\n this.hideOptionVisibility()\n this.focusButton()\n }\n\n /**\n * Handles Tab key press from option list.\n * Closes dropdown and allows natural tab navigation.\n */\n onTabPressed(_event: { shiftKey: boolean }): void {\n this.hideOptionVisibility()\n }\n\n /**\n * Sets focus to the select button element.\n */\n focusButton(): void {\n const buttonEl = this.selectButton()?.nativeElement\n if (buttonEl) {\n buttonEl.focus()\n }\n }\n\n getOptionIndex(option: SelectOption): number {\n return this.selectOptions().findIndex((x) => x.value === option.value)\n }\n}\n","<div\n (mouseleave)=\"onMouseLeaveCallback()\"\n class=\"mb-3\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label d-flex gap-2\"\n >\n <div>\n <span>{{ componentLabel() | transloco }}</span>\n <span [hidden]=\"!_isRequired()\">*</span>\n </div>\n @if (helpMessage() && helpMessageTooltip()) {\n <div [quangTooltip]=\"helpMessage() | transloco\">\n <ng-content select=\"[help-icon]\" />\n </div>\n }\n </label>\n }\n <div\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n class=\"option-list-container\"\n >\n <button\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.is-invalid]=\"_ngControl()?.invalid && (_ngControl()?.dirty || _ngControl()?.touched) && errorMap().length\"\n [class.is-valid]=\"_ngControl()?.valid && (_ngControl()?.dirty || _ngControl()?.touched) && successMessage()\"\n [disabled]=\"_isDisabled()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [tabIndex]=\"componentTabIndex()\"\n (click)=\"changeOptionsVisibility()\"\n (keydown)=\"onButtonKeydown($event)\"\n #selectButton\n aria-haspopup=\"listbox\"\n class=\"form-select\"\n type=\"button\"\n >\n <div\n [class.multiple]=\"selectionMode() === 'multiple'\"\n class=\"content\"\n >\n @if (_selectedItems()?.length) {\n @for (val of _selectedItems(); track val; let last = $last) {\n <span class=\"selected-item\">\n @if (val.renderer) {\n <ng-container\n [ngTemplateOutlet]=\"val.renderer\"\n [ngTemplateOutletContext]=\"{ $implicit: val, selected: true, index: getOptionIndex(val) }\"\n ></ng-container>\n } @else {\n {{ translateValue() ? (val.label | transloco) : val.label }}\n }\n </span>\n @if (!last) {\n <span class=\"separator\">, </span>\n }\n }\n } @else {\n <ng-container>{{ componentPlaceholder() | transloco }}</ng-container>\n }\n </div>\n </button>\n\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_value()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"nullOption()\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"selectButton\"\n [selectionMode]=\"selectionMode()\"\n [selectOptions]=\"selectOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurHandler()\"\n (changedHandler)=\"onChangedHandler($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n ></quang-option-list>\n }\n </div>\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\n @if (helpMessage() && !helpMessageTooltip()) {\n <small\n [hidden]=\"_showSuccess() || _showErrors()\"\n aria-live=\"assertive\"\n class=\"form-text text-muted\"\n >\n {{ helpMessage() | transloco }}\n </small>\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { input, computed, output, signal, inject, Injector, DestroyRef, Directive, viewChild, effect, HostListener, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
- import { NgControl, Validators } from '@angular/forms';
5
- import { NgStyle, NgClass } from '@angular/common';
4
+ import { Validators, NgControl } from '@angular/forms';
5
+ import { NgStyle, NgClass, NgTemplateOutlet } from '@angular/common';
6
6
  import { TranslocoPipe } from '@jsverse/transloco';
7
7
  import { QUANG_LOGGING_BEHAVIOR } from 'quang';
8
8
  import { fromEvent } from 'rxjs';
@@ -52,7 +52,13 @@ class QuangBaseComponent {
52
52
  this._currentErrorMessageExtraData = signal({}, ...(ngDevMode ? [{ debugName: "_currentErrorMessageExtraData" }] : []));
53
53
  this._ngControl = signal(null, ...(ngDevMode ? [{ debugName: "_ngControl" }] : []));
54
54
  this._injector = signal(inject(Injector), ...(ngDevMode ? [{ debugName: "_injector" }] : []));
55
- this.getIsRequiredControl = computed(() => !!this._ngControl()?.control?._rawValidators?.find((x) => x.name === 'required'), ...(ngDevMode ? [{ debugName: "getIsRequiredControl" }] : []));
55
+ this.getIsRequiredControl = computed(() => {
56
+ const control = this._ngControl()?.control;
57
+ if (!control) {
58
+ return false;
59
+ }
60
+ return control.hasValidator(Validators.required) || control.hasValidator(Validators.requiredTrue);
61
+ }, ...(ngDevMode ? [{ debugName: "getIsRequiredControl" }] : []));
56
62
  this.destroyRef = inject(DestroyRef);
57
63
  this.onChangeIsReadonly = this.isReadonly$.pipe(takeUntilDestroyed()).subscribe((isReadonly) => {
58
64
  this._isDisabled.set(isReadonly || this._ngControl()?.disabled || false);
@@ -101,14 +107,29 @@ class QuangBaseComponent {
101
107
  this._statusChange$.unsubscribe();
102
108
  this._statusChange$ = undefined;
103
109
  }
110
+ if (this._valueChange$) {
111
+ this._valueChange$.unsubscribe();
112
+ this._valueChange$ = undefined;
113
+ }
114
+ if (this._eventsChange$) {
115
+ this._eventsChange$.unsubscribe();
116
+ this._eventsChange$ = undefined;
117
+ }
104
118
  this._ngControl.set(this._injector().get(NgControl));
105
- this._statusChange$ = this._ngControl()
106
- ?.control?.statusChanges.pipe(takeUntilDestroyed(this.destroyRef))
107
- .subscribe(() => {
119
+ const control = this._ngControl()?.control;
120
+ this._statusChange$ = control?.statusChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
121
+ this.checkFormErrors();
122
+ });
123
+ this._valueChange$ = control?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
124
+ this.checkFormErrors();
125
+ });
126
+ // `markAllAsTouched()` updates the control's `touched` state without emitting `statusChanges`.
127
+ // Angular exposes an `events` stream that includes touched/pristine changes.
128
+ this._eventsChange$ = control?.events?.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
108
129
  this.checkFormErrors();
109
130
  });
110
- this._isTouched.set(this._ngControl()?.touched ?? false);
111
- this._isDisabled.set(this.isReadonly() || this._ngControl()?.disabled || false);
131
+ this._isTouched.set(!!(control?.touched || control?.dirty));
132
+ this._isDisabled.set(this.isReadonly() || control?.disabled || false);
112
133
  this.checkFormErrors();
113
134
  }
114
135
  setDisabledState(isDisabled) {
@@ -117,9 +138,9 @@ class QuangBaseComponent {
117
138
  checkFormErrors() {
118
139
  const control = this._ngControl()?.control;
119
140
  this._isValid.set(control?.valid ?? false);
120
- this._isTouched.set(!control?.pristine);
141
+ this._isTouched.set(!!(control?.touched || control?.dirty));
142
+ this._isRequired.set(!!control && (control.hasValidator(Validators.required) || control.hasValidator(Validators.requiredTrue)));
121
143
  const validationErrors = control?.errors;
122
- this._isRequired.set(validationErrors?.[Validators.required.name]);
123
144
  let errorName = '';
124
145
  let errorMessage = '';
125
146
  let errorData = true;
@@ -428,11 +449,11 @@ class QuangOptionListComponent {
428
449
  });
429
450
  }
430
451
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: QuangOptionListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
431
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: QuangOptionListComponent, isStandalone: true, selector: "quang-option-list", inputs: { selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, optionListMaxHeight: { classPropertyName: "optionListMaxHeight", publicName: "optionListMaxHeight", isSignal: true, isRequired: false, transformFunction: null }, selectOptions: { classPropertyName: "selectOptions", publicName: "selectOptions", isSignal: true, isRequired: false, transformFunction: null }, selectButtonRef: { classPropertyName: "selectButtonRef", publicName: "selectButtonRef", isSignal: true, isRequired: true, transformFunction: null }, _value: { classPropertyName: "_value", publicName: "_value", isSignal: true, isRequired: false, transformFunction: null }, _isDisabled: { classPropertyName: "_isDisabled", publicName: "_isDisabled", isSignal: true, isRequired: false, transformFunction: null }, componentClass: { classPropertyName: "componentClass", publicName: "componentClass", isSignal: true, isRequired: false, transformFunction: null }, componentLabel: { classPropertyName: "componentLabel", publicName: "componentLabel", isSignal: true, isRequired: false, transformFunction: null }, componentTabIndex: { classPropertyName: "componentTabIndex", publicName: "componentTabIndex", isSignal: true, isRequired: false, transformFunction: null }, translateValue: { classPropertyName: "translateValue", publicName: "translateValue", isSignal: true, isRequired: false, transformFunction: null }, nullOption: { classPropertyName: "nullOption", publicName: "nullOption", isSignal: true, isRequired: false, transformFunction: null }, scrollBehaviorOnOpen: { classPropertyName: "scrollBehaviorOnOpen", publicName: "scrollBehaviorOnOpen", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: true, transformFunction: null }, parentID: { classPropertyName: "parentID", publicName: "parentID", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changedHandler: "changedHandler", blurHandler: "blurHandler", escapePressed: "escapePressed", tabPressed: "tabPressed" }, host: { listeners: { "window:scroll": "changePosition()" } }, viewQueries: [{ propertyName: "optionListContainer", first: true, predicate: ["optionListContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n [attr.aria-activedescendant]=\"getActiveDescendantId()\"\n [attr.aria-label]=\"componentLabel()\"\n [ngStyle]=\"{\n 'max-height': optionListMaxHeight(),\n '--option-list-width': elementWidth(),\n '--option-list-top': elementTop(),\n '--option-list-bottom': elementBottom(),\n }\"\n (blur)=\"onBlurHandler($event)\"\n #optionListContainer\n aria-orientation=\"vertical\"\n class=\"option-list\"\n id=\"optionList\"\n role=\"listbox\"\n tabindex=\"0\"\n>\n <ul role=\"presentation\">\n @for (\n item of selectOptionsList();\n track (item.value ?? '') + (item.label ?? '');\n let i = $index;\n let last = $last\n ) {\n <li\n [attr.aria-selected]=\"getSelected(item)\"\n [class.m-0]=\"last\"\n [class.selected]=\"selectionMode() === 'single' ? getSelected(item) : null\"\n [id]=\"'item-' + i\"\n (mousedown)=\"$event.stopImmediatePropagation(); onSelectItem(item)\"\n class=\"item\"\n role=\"option\"\n >\n <input\n [checked]=\"getSelected(item)\"\n [class.d-none]=\"selectionMode() === 'single' || !item?.value\"\n [disabled]=\"true\"\n [id]=\"i + '-' + item.value + '-checkbox'\"\n [name]=\"i + '-' + item.value + '-checkbox'\"\n [ngClass]=\"componentClass()\"\n [value]=\"getSelected(item)\"\n (blur)=\"onBlurHandler($event)\"\n #inputCheckbox\n aria-hidden=\"true\"\n class=\"form-check-input opacity-100\"\n tabindex=\"-1\"\n type=\"checkbox\"\n />\n <label\n [class.ms-3]=\"selectionMode() === 'multiple' && item?.value\"\n [for]=\"i + '-' + item.value + '-checkbox'\"\n class=\"form-check-label checkbox-label w-100 opacity-100\"\n >\n {{ translateValue() ? (item.label | transloco) : item.label }}\n </label>\n </li>\n }\n </ul>\n</div>\n", styles: [":host{display:block;z-index:999}.option-list{--option-list-width: 100%;--option-list-top: 10px;--option-list-bottom: unset;border:1px solid var(--bs-border-color);border-top:0;border-radius:var(--bs-border-radius);overflow:auto;position:fixed;width:var(--option-list-width);top:var(--option-list-top);bottom:var(--option-list-bottom);z-index:999}.option-list ul{margin:0;padding:0}.option-list .selected{background-color:var(--bs-body-bg);filter:brightness(95%)}.option-list.option-list-top{border-top:1px solid var(--bs-border-color);border-bottom:0}.option-list:focus-visible{outline:none}.item{display:flex;background-color:var(--bs-body-bg);z-index:1;padding:0 .5rem;min-height:2.5rem}.item label{padding:.5rem 0}.item:hover{cursor:pointer;background-color:var(--bs-body-bg);filter:brightness(95%)}.item *:hover{cursor:pointer}.item .form-check-input{align-self:center}.item .form-check-input:focus{border-color:var(--bs-border-color);box-shadow:unset}.item .form-check-input:checked{background-color:var(--bs-primary);border-color:var(--bs-border-color)}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
452
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: QuangOptionListComponent, isStandalone: true, selector: "quang-option-list", inputs: { selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, optionListMaxHeight: { classPropertyName: "optionListMaxHeight", publicName: "optionListMaxHeight", isSignal: true, isRequired: false, transformFunction: null }, selectOptions: { classPropertyName: "selectOptions", publicName: "selectOptions", isSignal: true, isRequired: false, transformFunction: null }, selectButtonRef: { classPropertyName: "selectButtonRef", publicName: "selectButtonRef", isSignal: true, isRequired: true, transformFunction: null }, _value: { classPropertyName: "_value", publicName: "_value", isSignal: true, isRequired: false, transformFunction: null }, _isDisabled: { classPropertyName: "_isDisabled", publicName: "_isDisabled", isSignal: true, isRequired: false, transformFunction: null }, componentClass: { classPropertyName: "componentClass", publicName: "componentClass", isSignal: true, isRequired: false, transformFunction: null }, componentLabel: { classPropertyName: "componentLabel", publicName: "componentLabel", isSignal: true, isRequired: false, transformFunction: null }, componentTabIndex: { classPropertyName: "componentTabIndex", publicName: "componentTabIndex", isSignal: true, isRequired: false, transformFunction: null }, translateValue: { classPropertyName: "translateValue", publicName: "translateValue", isSignal: true, isRequired: false, transformFunction: null }, nullOption: { classPropertyName: "nullOption", publicName: "nullOption", isSignal: true, isRequired: false, transformFunction: null }, scrollBehaviorOnOpen: { classPropertyName: "scrollBehaviorOnOpen", publicName: "scrollBehaviorOnOpen", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: true, transformFunction: null }, parentID: { classPropertyName: "parentID", publicName: "parentID", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changedHandler: "changedHandler", blurHandler: "blurHandler", escapePressed: "escapePressed", tabPressed: "tabPressed" }, host: { listeners: { "window:scroll": "changePosition()" } }, viewQueries: [{ propertyName: "optionListContainer", first: true, predicate: ["optionListContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n [attr.aria-activedescendant]=\"getActiveDescendantId()\"\n [attr.aria-label]=\"componentLabel()\"\n [ngStyle]=\"{\n 'max-height': optionListMaxHeight(),\n '--option-list-width': elementWidth(),\n '--option-list-top': elementTop(),\n '--option-list-bottom': elementBottom(),\n }\"\n (blur)=\"onBlurHandler($event)\"\n #optionListContainer\n aria-orientation=\"vertical\"\n class=\"option-list\"\n id=\"optionList\"\n role=\"listbox\"\n tabindex=\"0\"\n>\n <ul role=\"presentation\">\n @for (\n item of selectOptionsList();\n track (item.value ?? '') + (item.label ?? '');\n let i = $index;\n let last = $last\n ) {\n <li\n [attr.aria-selected]=\"getSelected(item)\"\n [class.m-0]=\"last\"\n [class.selected]=\"selectionMode() === 'single' ? getSelected(item) : null\"\n [id]=\"'item-' + i\"\n (mousedown)=\"$event.stopImmediatePropagation(); onSelectItem(item)\"\n class=\"item\"\n role=\"option\"\n >\n <input\n [checked]=\"getSelected(item)\"\n [class.d-none]=\"selectionMode() === 'single' || !item?.value\"\n [disabled]=\"true\"\n [id]=\"i + '-' + item.value + '-checkbox'\"\n [name]=\"i + '-' + item.value + '-checkbox'\"\n [ngClass]=\"componentClass()\"\n [value]=\"getSelected(item)\"\n (blur)=\"onBlurHandler($event)\"\n #inputCheckbox\n aria-hidden=\"true\"\n class=\"form-check-input opacity-100\"\n tabindex=\"-1\"\n type=\"checkbox\"\n />\n <label\n [class.ms-3]=\"selectionMode() === 'multiple' && item?.value\"\n [for]=\"i + '-' + item.value + '-checkbox'\"\n class=\"form-check-label checkbox-label w-100 opacity-100\"\n >\n @if (!item.renderer) {\n {{ translateValue() ? (item.label | transloco) : item.label }}\n }\n <ng-container\n [ngTemplateOutlet]=\"item.renderer ?? null\"\n [ngTemplateOutletContext]=\"{ $implicit: item, selected: getSelected(item), index: i }\"\n ></ng-container>\n </label>\n </li>\n }\n </ul>\n</div>\n", styles: [":host{display:block;z-index:999}.option-list{--option-list-width: 100%;--option-list-top: 10px;--option-list-bottom: unset;border:1px solid var(--bs-border-color);border-top:0;border-radius:var(--bs-border-radius);overflow-x:hidden;overflow-y:auto;scrollbar-color:auto;position:fixed;width:var(--option-list-width);top:var(--option-list-top);bottom:var(--option-list-bottom);z-index:999}.option-list ul{margin:0;padding:0}.option-list .selected{background-color:var(--bs-body-bg);filter:brightness(95%)}.option-list.option-list-top{border-top:1px solid var(--bs-border-color);border-bottom:0}.option-list:focus-visible{outline:none}.option-list::-webkit-scrollbar{width:8px}.option-list::-webkit-scrollbar-track{background:var(--bs-body-bg)}.option-list::-webkit-scrollbar-thumb{background-color:#00000038;border-radius:999px;border:2px solid transparent;background-clip:content-box}.option-list:hover::-webkit-scrollbar-thumb{background-color:#00000052}.item{display:flex;background-color:var(--bs-body-bg);z-index:1;padding:0 .5rem;min-height:2.5rem}.item label{padding:.5rem 0}.item:hover{cursor:pointer;background-color:var(--bs-body-bg);filter:brightness(95%)}.item *:hover{cursor:pointer}.item .form-check-input{align-self:center}.item .form-check-input:focus{border-color:var(--bs-border-color);box-shadow:unset}.item .form-check-input:checked{background-color:var(--bs-primary);border-color:var(--bs-border-color)}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
432
453
  }
433
454
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: QuangOptionListComponent, decorators: [{
434
455
  type: Component,
435
- args: [{ selector: 'quang-option-list', imports: [NgStyle, NgClass, TranslocoPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [attr.aria-activedescendant]=\"getActiveDescendantId()\"\n [attr.aria-label]=\"componentLabel()\"\n [ngStyle]=\"{\n 'max-height': optionListMaxHeight(),\n '--option-list-width': elementWidth(),\n '--option-list-top': elementTop(),\n '--option-list-bottom': elementBottom(),\n }\"\n (blur)=\"onBlurHandler($event)\"\n #optionListContainer\n aria-orientation=\"vertical\"\n class=\"option-list\"\n id=\"optionList\"\n role=\"listbox\"\n tabindex=\"0\"\n>\n <ul role=\"presentation\">\n @for (\n item of selectOptionsList();\n track (item.value ?? '') + (item.label ?? '');\n let i = $index;\n let last = $last\n ) {\n <li\n [attr.aria-selected]=\"getSelected(item)\"\n [class.m-0]=\"last\"\n [class.selected]=\"selectionMode() === 'single' ? getSelected(item) : null\"\n [id]=\"'item-' + i\"\n (mousedown)=\"$event.stopImmediatePropagation(); onSelectItem(item)\"\n class=\"item\"\n role=\"option\"\n >\n <input\n [checked]=\"getSelected(item)\"\n [class.d-none]=\"selectionMode() === 'single' || !item?.value\"\n [disabled]=\"true\"\n [id]=\"i + '-' + item.value + '-checkbox'\"\n [name]=\"i + '-' + item.value + '-checkbox'\"\n [ngClass]=\"componentClass()\"\n [value]=\"getSelected(item)\"\n (blur)=\"onBlurHandler($event)\"\n #inputCheckbox\n aria-hidden=\"true\"\n class=\"form-check-input opacity-100\"\n tabindex=\"-1\"\n type=\"checkbox\"\n />\n <label\n [class.ms-3]=\"selectionMode() === 'multiple' && item?.value\"\n [for]=\"i + '-' + item.value + '-checkbox'\"\n class=\"form-check-label checkbox-label w-100 opacity-100\"\n >\n {{ translateValue() ? (item.label | transloco) : item.label }}\n </label>\n </li>\n }\n </ul>\n</div>\n", styles: [":host{display:block;z-index:999}.option-list{--option-list-width: 100%;--option-list-top: 10px;--option-list-bottom: unset;border:1px solid var(--bs-border-color);border-top:0;border-radius:var(--bs-border-radius);overflow:auto;position:fixed;width:var(--option-list-width);top:var(--option-list-top);bottom:var(--option-list-bottom);z-index:999}.option-list ul{margin:0;padding:0}.option-list .selected{background-color:var(--bs-body-bg);filter:brightness(95%)}.option-list.option-list-top{border-top:1px solid var(--bs-border-color);border-bottom:0}.option-list:focus-visible{outline:none}.item{display:flex;background-color:var(--bs-body-bg);z-index:1;padding:0 .5rem;min-height:2.5rem}.item label{padding:.5rem 0}.item:hover{cursor:pointer;background-color:var(--bs-body-bg);filter:brightness(95%)}.item *:hover{cursor:pointer}.item .form-check-input{align-self:center}.item .form-check-input:focus{border-color:var(--bs-border-color);box-shadow:unset}.item .form-check-input:checked{background-color:var(--bs-primary);border-color:var(--bs-border-color)}\n"] }]
456
+ args: [{ selector: 'quang-option-list', imports: [NgStyle, NgClass, NgTemplateOutlet, TranslocoPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [attr.aria-activedescendant]=\"getActiveDescendantId()\"\n [attr.aria-label]=\"componentLabel()\"\n [ngStyle]=\"{\n 'max-height': optionListMaxHeight(),\n '--option-list-width': elementWidth(),\n '--option-list-top': elementTop(),\n '--option-list-bottom': elementBottom(),\n }\"\n (blur)=\"onBlurHandler($event)\"\n #optionListContainer\n aria-orientation=\"vertical\"\n class=\"option-list\"\n id=\"optionList\"\n role=\"listbox\"\n tabindex=\"0\"\n>\n <ul role=\"presentation\">\n @for (\n item of selectOptionsList();\n track (item.value ?? '') + (item.label ?? '');\n let i = $index;\n let last = $last\n ) {\n <li\n [attr.aria-selected]=\"getSelected(item)\"\n [class.m-0]=\"last\"\n [class.selected]=\"selectionMode() === 'single' ? getSelected(item) : null\"\n [id]=\"'item-' + i\"\n (mousedown)=\"$event.stopImmediatePropagation(); onSelectItem(item)\"\n class=\"item\"\n role=\"option\"\n >\n <input\n [checked]=\"getSelected(item)\"\n [class.d-none]=\"selectionMode() === 'single' || !item?.value\"\n [disabled]=\"true\"\n [id]=\"i + '-' + item.value + '-checkbox'\"\n [name]=\"i + '-' + item.value + '-checkbox'\"\n [ngClass]=\"componentClass()\"\n [value]=\"getSelected(item)\"\n (blur)=\"onBlurHandler($event)\"\n #inputCheckbox\n aria-hidden=\"true\"\n class=\"form-check-input opacity-100\"\n tabindex=\"-1\"\n type=\"checkbox\"\n />\n <label\n [class.ms-3]=\"selectionMode() === 'multiple' && item?.value\"\n [for]=\"i + '-' + item.value + '-checkbox'\"\n class=\"form-check-label checkbox-label w-100 opacity-100\"\n >\n @if (!item.renderer) {\n {{ translateValue() ? (item.label | transloco) : item.label }}\n }\n <ng-container\n [ngTemplateOutlet]=\"item.renderer ?? null\"\n [ngTemplateOutletContext]=\"{ $implicit: item, selected: getSelected(item), index: i }\"\n ></ng-container>\n </label>\n </li>\n }\n </ul>\n</div>\n", styles: [":host{display:block;z-index:999}.option-list{--option-list-width: 100%;--option-list-top: 10px;--option-list-bottom: unset;border:1px solid var(--bs-border-color);border-top:0;border-radius:var(--bs-border-radius);overflow-x:hidden;overflow-y:auto;scrollbar-color:auto;position:fixed;width:var(--option-list-width);top:var(--option-list-top);bottom:var(--option-list-bottom);z-index:999}.option-list ul{margin:0;padding:0}.option-list .selected{background-color:var(--bs-body-bg);filter:brightness(95%)}.option-list.option-list-top{border-top:1px solid var(--bs-border-color);border-bottom:0}.option-list:focus-visible{outline:none}.option-list::-webkit-scrollbar{width:8px}.option-list::-webkit-scrollbar-track{background:var(--bs-body-bg)}.option-list::-webkit-scrollbar-thumb{background-color:#00000038;border-radius:999px;border:2px solid transparent;background-clip:content-box}.option-list:hover::-webkit-scrollbar-thumb{background-color:#00000052}.item{display:flex;background-color:var(--bs-body-bg);z-index:1;padding:0 .5rem;min-height:2.5rem}.item label{padding:.5rem 0}.item:hover{cursor:pointer;background-color:var(--bs-body-bg);filter:brightness(95%)}.item *:hover{cursor:pointer}.item .form-check-input{align-self:center}.item .form-check-input:focus{border-color:var(--bs-border-color);box-shadow:unset}.item .form-check-input:checked{background-color:var(--bs-primary);border-color:var(--bs-border-color)}\n"] }]
436
457
  }], propDecorators: { selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], optionListMaxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionListMaxHeight", required: false }] }], selectOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectOptions", required: false }] }], selectButtonRef: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectButtonRef", required: true }] }], _value: [{ type: i0.Input, args: [{ isSignal: true, alias: "_value", required: false }] }], _isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "_isDisabled", required: false }] }], componentClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentClass", required: false }] }], componentLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentLabel", required: false }] }], componentTabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentTabIndex", required: false }] }], translateValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "translateValue", required: false }] }], nullOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "nullOption", required: false }] }], scrollBehaviorOnOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollBehaviorOnOpen", required: false }] }], changedHandler: [{ type: i0.Output, args: ["changedHandler"] }], blurHandler: [{ type: i0.Output, args: ["blurHandler"] }], escapePressed: [{ type: i0.Output, args: ["escapePressed"] }], tabPressed: [{ type: i0.Output, args: ["tabPressed"] }], optionListContainer: [{ type: i0.ViewChild, args: ['optionListContainer', { isSignal: true }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: true }] }], parentID: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentID", required: false }] }], changePosition: [{
437
458
  type: HostListener,
438
459
  args: ['window:scroll']