quang 19.3.15-3 → 19.3.15-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,4 +1,4 @@
1
- import { NgClass, NgStyle } from '@angular/common';
1
+ import { NgClass, NgTemplateOutlet, NgStyle } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { input, output, viewChild, signal, computed, effect, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
4
4
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -106,6 +106,13 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
106
106
  * @default 'vertical'
107
107
  */
108
108
  this.multiSelectDisplayMode = input('vertical');
109
+ /**
110
+ * Position of chips relative to the input in multiple selection mode.
111
+ * - 'top': Chips are displayed above the input (default)
112
+ * - 'bottom': Chips are displayed below the input
113
+ * @default 'top'
114
+ */
115
+ this.chipsPosition = input('top');
109
116
  /**
110
117
  * Debounce time in milliseconds for search text changes.
111
118
  * @default 300
@@ -241,12 +248,12 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
241
248
  /** Effect to handle input element setup and keyboard events */
242
249
  this.onChangeSelectInputEffect = effect(() => {
243
250
  const selectInput = this.selectInput();
244
- if (selectInput) {
245
- this.inputHeight.set(selectInput.nativeElement.getBoundingClientRect().height);
246
- selectInput.nativeElement.addEventListener('keydown', (e) => {
247
- this.handleInputKeydown(e, selectInput.nativeElement);
248
- });
249
- }
251
+ if (!selectInput)
252
+ return;
253
+ this.inputHeight.set(selectInput.nativeElement.getBoundingClientRect().height);
254
+ selectInput.nativeElement.addEventListener('keydown', (e) => {
255
+ this.handleInputKeydown(e, selectInput.nativeElement);
256
+ });
250
257
  });
251
258
  /** Subscription to options changes */
252
259
  this.selectOptionsChangeSubscription = toObservable(this.selectOptions)
@@ -262,20 +269,20 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
262
269
  // for immediate processing. This subscription is kept for backwards compatibility
263
270
  // but the _isSearching check prevents double-processing since onBlurHandler
264
271
  // already sets _isSearching to false before this subscription fires.
265
- if (!data && data !== null && this._isSearching()) {
266
- // Only process if still in search mode (which means onBlurHandler didn't run)
267
- this.processTextToFormValue(this._userSearchText(), {
268
- exitSearchMode: true,
269
- updateOnMatch: true,
270
- clearSearchText: true,
271
- });
272
- }
272
+ if (!(!data && data !== null && this._isSearching()))
273
+ return;
274
+ // Only process if still in search mode (which means onBlurHandler didn't run)
275
+ this.processTextToFormValue(this._userSearchText(), {
276
+ exitSearchMode: true,
277
+ updateOnMatch: true,
278
+ clearSearchText: true,
279
+ });
273
280
  });
274
281
  this.destroyRef.onDestroy(() => {
275
282
  this._isDestroyed = true;
276
- if (this._searchDebounceTimer) {
277
- clearTimeout(this._searchDebounceTimer);
278
- }
283
+ if (!this._searchDebounceTimer)
284
+ return;
285
+ clearTimeout(this._searchDebounceTimer);
279
286
  });
280
287
  }
281
288
  // ============================================
@@ -288,13 +295,13 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
288
295
  this.formValueChangeSubscription.unsubscribe();
289
296
  this.formValueChangeSubscription = undefined;
290
297
  }
291
- if (formControl) {
292
- this.formValueChangeSubscription = formControl.valueChanges
293
- .pipe(takeUntilDestroyed(this.destroyRef))
294
- .subscribe((value) => {
295
- this.handleFormValueChange(value);
296
- });
297
- }
298
+ if (!formControl)
299
+ return;
300
+ this.formValueChangeSubscription = formControl.valueChanges
301
+ .pipe(takeUntilDestroyed(this.destroyRef))
302
+ .subscribe((value) => {
303
+ this.handleFormValueChange(value);
304
+ });
298
305
  }
299
306
  writeValue(val) {
300
307
  // Simply update the value - _inputValue is computed and will automatically
@@ -310,7 +317,7 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
310
317
  onChangedHandler(value) {
311
318
  super.onChangedHandler(value);
312
319
  // Exit search mode - _inputValue will now derive from _value
313
- // Note: Don't clear _userSearchText here - it's needed for checkInputValue matching
320
+ // Note: Don't clear _userSearchText here - it's needed for processTextToFormValue matching
314
321
  this._isSearching.set(false);
315
322
  }
316
323
  onBlurHandler() {
@@ -336,11 +343,11 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
336
343
  // Initialize _userSearchText with current input value when showing options
337
344
  // This ensures that if user focuses and blurs without typing, the value is preserved
338
345
  // Also enter search mode to enable filtering
339
- if (!this._isSearching()) {
340
- const currentInputValue = this._inputValue();
341
- this._userSearchText.set(currentInputValue || '');
342
- this._isSearching.set(true);
343
- }
346
+ if (this._isSearching())
347
+ return;
348
+ const currentInputValue = this._inputValue();
349
+ this._userSearchText.set(currentInputValue || '');
350
+ this._isSearching.set(true);
344
351
  }
345
352
  /**
346
353
  * Hides the option list dropdown.
@@ -365,6 +372,20 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
365
372
  * @param hideOptions Whether to hide the dropdown after selection
366
373
  */
367
374
  onValueChange(value, hideOptions = true) {
375
+ // When allowFreeText is true and a null/undefined value is received (e.g., from selecting
376
+ // a non-existent option in the dropdown), use the typed text as the value instead of clearing
377
+ if ((value === null || value === undefined) && this._allowFreeTextInternal()) {
378
+ const typedText = this._userSearchText()?.trim();
379
+ if (typedText) {
380
+ this.onChangedHandler(typedText);
381
+ if (hideOptions) {
382
+ this.hideOptionVisibility();
383
+ this.focusInput();
384
+ }
385
+ this.selectedOption.emit(typedText);
386
+ return;
387
+ }
388
+ }
368
389
  if (this.multiple()) {
369
390
  this.handleSelectValue(value);
370
391
  this.onChangedHandler(this._chipList());
@@ -372,21 +393,100 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
372
393
  this._userSearchText.set('');
373
394
  this._isSearching.set(false);
374
395
  }
396
+ return;
375
397
  }
376
- else {
377
- // Update _userSearchText to the selected option's label
378
- // This enables checkInputValue to match correctly on blur
379
- const selectedOption = this.selectOptions().find((x) => x.value === value);
380
- if (selectedOption) {
381
- this._userSearchText.set(selectedOption.label);
382
- }
383
- this.onChangedHandler(value);
384
- if (hideOptions) {
385
- this.hideOptionVisibility();
386
- }
387
- this.selectedOption.emit(value);
398
+ // Update _userSearchText to the selected option's label
399
+ // This enables processTextToFormValue to match correctly on blur
400
+ const selectedOption = this.selectOptions().find((x) => x.value === value);
401
+ if (selectedOption) {
402
+ this._userSearchText.set(selectedOption.label);
403
+ }
404
+ this.onChangedHandler(value);
405
+ if (hideOptions) {
406
+ this.hideOptionVisibility();
407
+ // Return focus to input after selection
408
+ this.focusInput();
409
+ }
410
+ this.selectedOption.emit(value);
411
+ }
412
+ /**
413
+ * Handles keydown events on the input element for accessibility.
414
+ * @param event The keyboard event
415
+ */
416
+ onInputKeydown(event) {
417
+ switch (event.key) {
418
+ case 'ArrowDown':
419
+ // Open dropdown if closed, or let option-list handle navigation
420
+ if (!this._showOptions()) {
421
+ event.preventDefault();
422
+ this.showOptionVisibility();
423
+ }
424
+ break;
425
+ case 'ArrowUp':
426
+ // Open dropdown if closed
427
+ if (!this._showOptions()) {
428
+ event.preventDefault();
429
+ this.showOptionVisibility();
430
+ }
431
+ break;
432
+ case 'Escape':
433
+ // Close dropdown and keep focus on input
434
+ if (this._showOptions()) {
435
+ event.preventDefault();
436
+ this.onEscapePressed();
437
+ }
438
+ break;
439
+ case 'Enter':
440
+ // When allowFreeText is true and dropdown is open, handle Enter specially
441
+ if (this._showOptions() && this._allowFreeTextInternal()) {
442
+ // Check if there are any filtered options
443
+ const filteredOptions = this._filteredOptions();
444
+ if (filteredOptions.length === 0) {
445
+ // No options to select - use the typed text as the value
446
+ event.preventDefault();
447
+ this.processTextToFormValue(this._userSearchText(), {
448
+ exitSearchMode: true,
449
+ updateOnMatch: true,
450
+ clearSearchText: false,
451
+ });
452
+ this.hideOptionVisibility();
453
+ }
454
+ // If there are filtered options, let option-list handle the selection
455
+ }
456
+ break;
388
457
  }
389
458
  }
459
+ /**
460
+ * Handles Escape key press from option list.
461
+ * Closes dropdown and returns focus to input.
462
+ */
463
+ onEscapePressed() {
464
+ this.hideOptionVisibility();
465
+ this.focusInput();
466
+ }
467
+ /**
468
+ * Handles Tab key press from option list.
469
+ * Closes dropdown and allows natural tab navigation.
470
+ */
471
+ onTabPressed(_event) {
472
+ // Close the dropdown, tab will naturally move focus
473
+ this.hideOptionVisibility();
474
+ // Process any pending input value
475
+ this.processTextToFormValue(this._userSearchText(), {
476
+ exitSearchMode: true,
477
+ updateOnMatch: true,
478
+ clearSearchText: true,
479
+ });
480
+ }
481
+ /**
482
+ * Sets focus to the input element.
483
+ */
484
+ focusInput() {
485
+ const inputEl = this.selectInput()?.nativeElement;
486
+ if (!inputEl)
487
+ return;
488
+ inputEl.focus();
489
+ }
390
490
  /**
391
491
  * Handles input blur event.
392
492
  * @param event The focus event
@@ -394,18 +494,18 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
394
494
  onBlurInput(event) {
395
495
  const relatedTarget = event.relatedTarget;
396
496
  const optionListId = this.optionList()?.optionListContainer()?.nativeElement?.id;
397
- if (relatedTarget?.id !== optionListId) {
398
- this.onBlurHandler();
399
- }
497
+ if (relatedTarget?.id === optionListId)
498
+ return;
499
+ this.onBlurHandler();
400
500
  }
401
501
  /**
402
502
  * Handles blur event on the option list.
403
503
  * @param event The blur event (truthy if should hide)
404
504
  */
405
505
  onBlurOptionList(event) {
406
- if (event) {
407
- this.hideOptionVisibility();
408
- }
506
+ if (!event)
507
+ return;
508
+ this.hideOptionVisibility();
409
509
  }
410
510
  /**
411
511
  * Gets the display description for a chip value.
@@ -423,10 +523,10 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
423
523
  deleteChip(chipValue) {
424
524
  const stringChipValue = chipValue?.toString();
425
525
  const index = this._chipList().findIndex((x) => x.toString() === stringChipValue);
426
- if (index >= 0) {
427
- this._chipList.update((list) => list.filter((_, i) => i !== index));
428
- this.onChangedHandler(this._chipList());
429
- }
526
+ if (index < 0)
527
+ return;
528
+ this._chipList.update((list) => list.filter((_, i) => i !== index));
529
+ this.onChangedHandler(this._chipList());
430
530
  }
431
531
  // ============================================
432
532
  // PROTECTED METHODS - Internal logic, accessible to subclasses
@@ -438,10 +538,10 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
438
538
  */
439
539
  filterOptions(value) {
440
540
  const options = this.selectOptions();
441
- if (this.internalFilterOptions()) {
442
- return options.filter((x) => x.label.toLowerCase().includes(value.toLowerCase()));
443
- }
444
- return options;
541
+ const trimmedValue = value?.trim();
542
+ return this.internalFilterOptions() && trimmedValue
543
+ ? options.filter((x) => x.label.toLowerCase().includes(trimmedValue.toLowerCase()))
544
+ : options;
445
545
  }
446
546
  // ============================================
447
547
  // PRIVATE METHODS - Internal implementation
@@ -479,9 +579,11 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
479
579
  const shouldAutoSelect = matchingOption && this.autoSelectOnExactMatch() && options.updateOnMatch;
480
580
  const shouldUseFreeText = this._allowFreeTextInternal() && searchText && options.updateOnMatch;
481
581
  // Clear logic differs between typing and blur:
482
- // - On blur (exitSearchMode=true): clear when no valid selection and free text not allowed
582
+ // - On blur (exitSearchMode=true): clear when input is empty (regardless of allowFreeText setting)
583
+ // - On blur: also clear when no valid selection and free text not allowed
483
584
  // - During typing (exitSearchMode=false): only clear when updateOnMatch is true and text doesn't match
484
- const shouldClearOnBlur = options.exitSearchMode && !this._allowFreeTextInternal() && (!matchingOption || !this.autoSelectOnExactMatch());
585
+ const shouldClearOnBlurEmpty = options.exitSearchMode && !searchText;
586
+ const shouldClearOnBlurNoMatch = options.exitSearchMode && !this._allowFreeTextInternal() && (!matchingOption || !this.autoSelectOnExactMatch());
485
587
  const shouldClearWhileTyping = !options.exitSearchMode && options.updateOnMatch && !matchingOption && !this._allowFreeTextInternal();
486
588
  if (shouldAutoSelect) {
487
589
  // Auto-select the matching option
@@ -501,9 +603,9 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
501
603
  this.onValueChange(searchText, false);
502
604
  }
503
605
  }
504
- else if (shouldClearOnBlur) {
505
- // On blur, no valid selection possible: clear the value
506
- this.onChangedHandler('');
606
+ else if (shouldClearOnBlurEmpty || shouldClearOnBlurNoMatch) {
607
+ // On blur with empty input or no valid selection: clear the value to null
608
+ this.onChangedHandler(null);
507
609
  }
508
610
  else if (shouldClearWhileTyping) {
509
611
  // While typing, text doesn't match any option: clear the value but stay in search mode
@@ -518,25 +620,25 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
518
620
  * Handles keyboard events on the input element.
519
621
  */
520
622
  handleInputKeydown(e, inputElement) {
521
- if (this.multiple() && this._chipList().length > 0 && !this._inputValue()?.length && e.key === 'Backspace') {
522
- e.preventDefault();
523
- const chipContainerEl = this.chipContainer()?.nativeElement;
524
- if (chipContainerEl) {
525
- const chips = chipContainerEl.querySelectorAll('.chip button.btn-chip');
526
- if (chips.length > 0) {
527
- const lastChip = chips[chips.length - 1];
528
- lastChip.focus();
529
- lastChip.addEventListener('keydown', (event) => {
530
- if (event.key === 'Backspace') {
531
- event.preventDefault();
532
- this.deleteChip(this._chipList()[this._chipList().length - 1]);
533
- inputElement.focus();
534
- }
535
- else {
536
- event.preventDefault();
537
- }
538
- });
539
- }
623
+ if (!(this.multiple() && this._chipList().length > 0 && !this._inputValue()?.length && e.key === 'Backspace')) {
624
+ return;
625
+ }
626
+ e.preventDefault();
627
+ const chipContainerEl = this.chipContainer()?.nativeElement;
628
+ if (chipContainerEl) {
629
+ const chips = chipContainerEl.querySelectorAll('.chip button.btn-chip');
630
+ if (chips.length > 0) {
631
+ const lastChip = chips[chips.length - 1];
632
+ lastChip.focus();
633
+ lastChip.addEventListener('keydown', (event) => {
634
+ if (event.key === 'Backspace') {
635
+ event.preventDefault();
636
+ this.deleteChip(this._chipList()[this._chipList().length - 1]);
637
+ inputElement.focus();
638
+ return;
639
+ }
640
+ event.preventDefault();
641
+ });
540
642
  }
541
643
  }
542
644
  }
@@ -556,14 +658,15 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
556
658
  * Handles form value changes from external sources.
557
659
  */
558
660
  handleFormValueChange(value) {
559
- if (this.multiple() && Array.isArray(value)) {
560
- this._chipList.set([]);
561
- this._selectedOptions.set([]);
562
- value.forEach((x) => {
563
- this.handleSelectValue(x);
564
- });
661
+ if (!(this.multiple() && Array.isArray(value))) {
662
+ return;
565
663
  }
566
- // Note: Don't clear _userSearchText here - it's managed by checkInputValue
664
+ this._chipList.set([]);
665
+ this._selectedOptions.set([]);
666
+ value.forEach((x) => {
667
+ this.handleSelectValue(x);
668
+ });
669
+ // Note: Don't clear _userSearchText here - it's managed by processTextToFormValue
567
670
  // which runs when options are hidden and needs _userSearchText for matching.
568
671
  }
569
672
  /**
@@ -579,7 +682,7 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
579
682
  /**
580
683
  * Emits search text change after debounce.
581
684
  * When `updateValueOnType` is true, also updates the form value using the same
582
- * matching logic as checkInputValue (auto-select matching options, or use free text).
685
+ * matching logic as processTextToFormValue (auto-select matching options, or use free text).
583
686
  */
584
687
  emitDebouncedSearchText(value) {
585
688
  if (this._searchDebounceTimer) {
@@ -589,18 +692,19 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
589
692
  if (this._isDestroyed) {
590
693
  return;
591
694
  }
592
- if (value !== this._lastEmittedSearchText) {
593
- this._lastEmittedSearchText = value;
594
- this.searchTextChange.emit(value || '');
595
- // Update form value based on what the user typed
596
- // - When updateValueOnType is true: update on both match and no-match
597
- // - When updateValueOnType is false: only clear the value when text doesn't match
598
- this.processTextToFormValue(value, {
599
- exitSearchMode: false,
600
- updateOnMatch: this.updateValueOnType(),
601
- clearSearchText: false,
602
- });
695
+ if (value === this._lastEmittedSearchText) {
696
+ return;
603
697
  }
698
+ this._lastEmittedSearchText = value;
699
+ this.searchTextChange.emit(value || '');
700
+ // Update form value based on what the user typed
701
+ // - When updateValueOnType is true: update on both match and no-match
702
+ // - When updateValueOnType is false: only clear the value when text doesn't match
703
+ this.processTextToFormValue(value, {
704
+ exitSearchMode: false,
705
+ updateOnMatch: this.updateValueOnType(),
706
+ clearSearchText: false,
707
+ });
604
708
  }, this.searchTextDebounce());
605
709
  }
606
710
  /**
@@ -615,7 +719,7 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
615
719
  }
616
720
  }
617
721
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: QuangAutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
618
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: QuangAutocompleteComponent, isStandalone: true, selector: "quang-autocomplete", inputs: { selectOptions: { classPropertyName: "selectOptions", publicName: "selectOptions", isSignal: true, isRequired: true, transformFunction: null }, allowFreeText: { classPropertyName: "allowFreeText", publicName: "allowFreeText", isSignal: true, isRequired: false, transformFunction: null }, autoSelectOnExactMatch: { classPropertyName: "autoSelectOnExactMatch", publicName: "autoSelectOnExactMatch", isSignal: true, isRequired: false, transformFunction: null }, updateValueOnType: { classPropertyName: "updateValueOnType", publicName: "updateValueOnType", isSignal: true, isRequired: false, transformFunction: null }, syncFormWithText: { classPropertyName: "syncFormWithText", publicName: "syncFormWithText", isSignal: true, isRequired: false, transformFunction: null }, optionListMaxHeight: { classPropertyName: "optionListMaxHeight", publicName: "optionListMaxHeight", isSignal: true, isRequired: false, transformFunction: null }, translateValue: { classPropertyName: "translateValue", publicName: "translateValue", isSignal: true, isRequired: false, transformFunction: null }, scrollBehaviorOnOpen: { classPropertyName: "scrollBehaviorOnOpen", publicName: "scrollBehaviorOnOpen", isSignal: true, isRequired: false, transformFunction: null }, emitOnly: { classPropertyName: "emitOnly", publicName: "emitOnly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, chipMaxLength: { classPropertyName: "chipMaxLength", publicName: "chipMaxLength", isSignal: true, isRequired: false, transformFunction: null }, multiSelectDisplayMode: { classPropertyName: "multiSelectDisplayMode", publicName: "multiSelectDisplayMode", isSignal: true, isRequired: false, transformFunction: null }, searchTextDebounce: { classPropertyName: "searchTextDebounce", publicName: "searchTextDebounce", isSignal: true, isRequired: false, transformFunction: null }, internalFilterOptions: { classPropertyName: "internalFilterOptions", publicName: "internalFilterOptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedOption: "selectedOption", searchTextChange: "searchTextChange" }, providers: [
722
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: QuangAutocompleteComponent, isStandalone: true, selector: "quang-autocomplete", inputs: { selectOptions: { classPropertyName: "selectOptions", publicName: "selectOptions", isSignal: true, isRequired: true, transformFunction: null }, allowFreeText: { classPropertyName: "allowFreeText", publicName: "allowFreeText", isSignal: true, isRequired: false, transformFunction: null }, autoSelectOnExactMatch: { classPropertyName: "autoSelectOnExactMatch", publicName: "autoSelectOnExactMatch", isSignal: true, isRequired: false, transformFunction: null }, updateValueOnType: { classPropertyName: "updateValueOnType", publicName: "updateValueOnType", isSignal: true, isRequired: false, transformFunction: null }, syncFormWithText: { classPropertyName: "syncFormWithText", publicName: "syncFormWithText", isSignal: true, isRequired: false, transformFunction: null }, optionListMaxHeight: { classPropertyName: "optionListMaxHeight", publicName: "optionListMaxHeight", isSignal: true, isRequired: false, transformFunction: null }, translateValue: { classPropertyName: "translateValue", publicName: "translateValue", isSignal: true, isRequired: false, transformFunction: null }, scrollBehaviorOnOpen: { classPropertyName: "scrollBehaviorOnOpen", publicName: "scrollBehaviorOnOpen", isSignal: true, isRequired: false, transformFunction: null }, emitOnly: { classPropertyName: "emitOnly", publicName: "emitOnly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, chipMaxLength: { classPropertyName: "chipMaxLength", publicName: "chipMaxLength", isSignal: true, isRequired: false, transformFunction: null }, multiSelectDisplayMode: { classPropertyName: "multiSelectDisplayMode", publicName: "multiSelectDisplayMode", isSignal: true, isRequired: false, transformFunction: null }, chipsPosition: { classPropertyName: "chipsPosition", publicName: "chipsPosition", isSignal: true, isRequired: false, transformFunction: null }, searchTextDebounce: { classPropertyName: "searchTextDebounce", publicName: "searchTextDebounce", isSignal: true, isRequired: false, transformFunction: null }, internalFilterOptions: { classPropertyName: "internalFilterOptions", publicName: "internalFilterOptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedOption: "selectedOption", searchTextChange: "searchTextChange" }, providers: [
619
723
  {
620
724
  provide: NG_VALUE_ACCESSOR,
621
725
  useExisting: forwardRef(() => QuangAutocompleteComponent),
@@ -625,11 +729,11 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
625
729
  provide: QuangOptionListComponent,
626
730
  multi: false,
627
731
  },
628
- ], viewQueries: [{ propertyName: "optionList", first: true, predicate: ["optionList"], descendants: true, isSignal: true }, { propertyName: "selectInput", first: true, predicate: ["selectInput"], descendants: true, isSignal: true }, { propertyName: "chipContainer", first: true, predicate: ["chipContainer"], descendants: true, isSignal: true }, { propertyName: "autocompleteContainer", first: true, predicate: ["autocompleteContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n [ngStyle]=\"{ '--chip-max-length': chipMaxLength() ? chipMaxLength() + 'ch' : 'none' }\"\n #autocompleteContainer\n class=\"autocomplete-container\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label\"\n >\n {{ componentLabel() | transloco }}\n <span [hidden]=\"!_isRequired()\">*</span>\n </label>\n }\n <div\n [ngClass]=\"multiSelectDisplayMode() === 'horizontal' ? 'horizontal form-control' : ''\"\n #chipContainer\n class=\"container-wrap\"\n >\n @if (multiple() && _chipList().length > 0) {\n @for (chip of _chipList(); track chip) {\n @if (getDescription(chip)) {\n <div\n [quangTooltip]=\"chipMaxLength() ? getDescription(chip) : ''\"\n class=\"chip chip-hover\"\n >\n <p [ngClass]=\"{ 'm-0': isReadonly() || _isDisabled() }\">\n {{ getDescription(chip) }}\n </p>\n @if (!isReadonly() && !_isDisabled()) {\n <button\n [tabIndex]=\"$index + 1\"\n (click)=\"deleteChip(chip)\"\n class=\"btn btn-chip\"\n type=\"button\"\n >\n <svg\n class=\"ionicon\"\n fill=\"currentColor\"\n height=\"24\"\n viewBox=\"0 0 512 512\"\n width=\"24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M368 368L144 144M368 144L144 368\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"32\"\n />\n </svg>\n </button>\n }\n </div>\n }\n }\n }\n\n <input\n [attr.required]=\"getIsRequiredControl()\"\n [class.form-control]=\"multiSelectDisplayMode() !== 'horizontal'\"\n [class.is-invalid]=\"_showErrors()\"\n [class.is-valid]=\"_showSuccess()\"\n [disabled]=\"_isDisabled() || isReadonly()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [placeholder]=\"componentPlaceholder() | transloco\"\n [tabIndex]=\"componentTabIndex()\"\n [value]=\"_inputValue()\"\n (blur)=\"onBlurInput($event)\"\n (input)=\"onChangeInput($event)\"\n (mousedown)=\"showOptionVisibility()\"\n #selectInput\n autocomplete=\"off\"\n type=\"text\"\n />\n </div>\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_highlightedValue()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"false\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentID]=\"componentId()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"autocompleteContainer\"\n [selectOptions]=\"_filteredOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurOptionList($event)\"\n (changedHandler)=\"onValueChange($event)\"\n #optionList\n selectionMode=\"single\"\n />\n }\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\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</div>\n", styles: [":host{display:block;--chip-max-length: none}.autocomplete-container{margin-bottom:1rem;position:relative}.chip:has(.btn-chip:disabled):hover{filter:unset;cursor:unset}.container-wrap{display:flex;flex-wrap:wrap;gap:.5rem}.container-wrap.horizontal{display:flex}.container-wrap.horizontal .chip-container{max-width:70%;margin-bottom:0;margin-left:.5rem;flex-wrap:nowrap;white-space:nowrap;overflow-x:auto;position:absolute;align-items:center}.container-wrap.horizontal .chip-container .chip{white-space:nowrap}.container-wrap.horizontal input{min-width:30%;flex:1 1 0;width:auto;border:none}.container-wrap.horizontal input:focus-visible{outline:none}.chip{display:flex;justify-content:space-between;align-items:center;padding:.25rem .5rem;border-radius:16px;color:var(--bs-btn-color);background-color:rgba(var(--bs-primary-rgb),.1);border-width:1px;border-style:solid;border-color:var(--bs-primary-border-subtle);height:2rem}.chip p{margin:0;max-width:var(--chip-max-length);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.chip .btn-chip{text-align:end;padding:0;min-width:unset}.chip .btn-chip:hover{opacity:80%}.chip .btn-chip:active{border-color:transparent}.chip .btn-chip svg{color:var(--bs-primary);vertical-align:sub}.chip:has(.btn-chip:focus-visible){border-width:2px;filter:brightness(80%)}\n"], dependencies: [{ kind: "pipe", type: TranslocoPipe, name: "transloco" }, { 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"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: QuangTooltipDirective, selector: "[quangTooltip]", inputs: ["quangTooltip", "showMethod"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
732
+ ], viewQueries: [{ propertyName: "optionList", first: true, predicate: ["optionList"], descendants: true, isSignal: true }, { propertyName: "selectInput", first: true, predicate: ["selectInput"], descendants: true, isSignal: true }, { propertyName: "chipContainer", first: true, predicate: ["chipContainer"], descendants: true, isSignal: true }, { propertyName: "autocompleteContainer", first: true, predicate: ["autocompleteContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n [ngStyle]=\"{ '--chip-max-length': chipMaxLength() ? chipMaxLength() + 'ch' : 'none' }\"\n #autocompleteContainer\n class=\"autocomplete-container\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label\"\n >\n {{ componentLabel() | transloco }}\n <span [hidden]=\"!_isRequired()\">*</span>\n </label>\n }\n <div\n [ngClass]=\"{\n horizontal: multiSelectDisplayMode() === 'horizontal',\n 'form-control': multiSelectDisplayMode() === 'horizontal',\n 'chips-bottom': chipsPosition() === 'bottom',\n }\"\n #chipContainer\n class=\"container-wrap\"\n >\n @if (multiple() && _chipList().length > 0 && chipsPosition() === 'top') {\n <ng-container *ngTemplateOutlet=\"chipsTemplate\" />\n }\n\n <input\n [attr.aria-activedescendant]=\"_showOptions() ? optionList()?.getActiveDescendantId() : null\"\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.form-control]=\"multiSelectDisplayMode() !== 'horizontal'\"\n [class.is-invalid]=\"_showErrors()\"\n [class.is-valid]=\"_showSuccess()\"\n [disabled]=\"_isDisabled() || isReadonly()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [placeholder]=\"componentPlaceholder() | transloco\"\n [tabIndex]=\"componentTabIndex()\"\n [value]=\"_inputValue()\"\n (blur)=\"onBlurInput($event)\"\n (input)=\"onChangeInput($event)\"\n (keydown)=\"onInputKeydown($event)\"\n (mousedown)=\"showOptionVisibility()\"\n #selectInput\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n autocomplete=\"off\"\n role=\"combobox\"\n type=\"text\"\n />\n\n @if (multiple() && _chipList().length > 0 && chipsPosition() === 'bottom') {\n <ng-container *ngTemplateOutlet=\"chipsTemplate\" />\n }\n </div>\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_highlightedValue()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"false\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentID]=\"componentId()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"autocompleteContainer\"\n [selectOptions]=\"_filteredOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurOptionList($event)\"\n (changedHandler)=\"onValueChange($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n selectionMode=\"single\"\n />\n }\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\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</div>\n\n<!-- Chips template for reuse in top/bottom positions -->\n<ng-template #chipsTemplate>\n <div class=\"chips-container\">\n @for (chip of _chipList(); track chip) {\n @if (getDescription(chip)) {\n <div\n [quangTooltip]=\"chipMaxLength() ? getDescription(chip) : ''\"\n class=\"chip chip-hover\"\n >\n <p [ngClass]=\"{ 'm-0': isReadonly() || _isDisabled() }\">\n {{ getDescription(chip) }}\n </p>\n @if (!isReadonly() && !_isDisabled()) {\n <button\n [tabIndex]=\"$index + 1\"\n (click)=\"deleteChip(chip)\"\n class=\"btn btn-chip\"\n type=\"button\"\n >\n <svg\n class=\"ionicon\"\n fill=\"currentColor\"\n height=\"24\"\n viewBox=\"0 0 512 512\"\n width=\"24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M368 368L144 144M368 144L144 368\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"32\"\n />\n </svg>\n </button>\n }\n </div>\n }\n }\n </div>\n</ng-template>\n", styles: [":host{display:block;--chip-max-length: none}.autocomplete-container{margin-bottom:1rem;position:relative}.chip:has(.btn-chip:disabled):hover{filter:unset;cursor:unset}.container-wrap,.container-wrap .chips-container{display:flex;flex-wrap:wrap;gap:.5rem}.container-wrap.chips-bottom{flex-direction:column}.container-wrap.chips-bottom input{order:-1}.container-wrap.horizontal{display:flex}.container-wrap.horizontal .chip-container{max-width:70%;margin-bottom:0;margin-left:.5rem;flex-wrap:nowrap;white-space:nowrap;overflow-x:auto;position:absolute;align-items:center}.container-wrap.horizontal .chip-container .chip{white-space:nowrap}.container-wrap.horizontal input{min-width:30%;flex:1 1 0;width:auto;border:none}.container-wrap.horizontal input:focus-visible{outline:none}.chip{display:flex;justify-content:space-between;align-items:center;padding:.25rem .5rem;border-radius:16px;color:var(--bs-btn-color);background-color:rgba(var(--bs-primary-rgb),.1);border-width:1px;border-style:solid;border-color:var(--bs-primary-border-subtle);height:2rem}.chip p{margin:0;max-width:var(--chip-max-length);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.chip .btn-chip{text-align:end;padding:0;min-width:unset}.chip .btn-chip:hover{opacity:80%}.chip .btn-chip:active{border-color:transparent}.chip .btn-chip svg{color:var(--bs-primary);vertical-align:sub}.chip:has(.btn-chip:focus-visible){border-width:2px;filter:brightness(80%)}\n"], dependencies: [{ kind: "pipe", type: TranslocoPipe, name: "transloco" }, { 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: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: QuangTooltipDirective, selector: "[quangTooltip]", inputs: ["quangTooltip", "showMethod"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
629
733
  }
630
734
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: QuangAutocompleteComponent, decorators: [{
631
735
  type: Component,
632
- args: [{ selector: 'quang-autocomplete', imports: [TranslocoPipe, NgClass, QuangOptionListComponent, NgStyle, QuangTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
736
+ args: [{ selector: 'quang-autocomplete', imports: [TranslocoPipe, NgClass, NgTemplateOutlet, QuangOptionListComponent, NgStyle, QuangTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
633
737
  {
634
738
  provide: NG_VALUE_ACCESSOR,
635
739
  useExisting: forwardRef(() => QuangAutocompleteComponent),
@@ -639,7 +743,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
639
743
  provide: QuangOptionListComponent,
640
744
  multi: false,
641
745
  },
642
- ], template: "<div\n [ngStyle]=\"{ '--chip-max-length': chipMaxLength() ? chipMaxLength() + 'ch' : 'none' }\"\n #autocompleteContainer\n class=\"autocomplete-container\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label\"\n >\n {{ componentLabel() | transloco }}\n <span [hidden]=\"!_isRequired()\">*</span>\n </label>\n }\n <div\n [ngClass]=\"multiSelectDisplayMode() === 'horizontal' ? 'horizontal form-control' : ''\"\n #chipContainer\n class=\"container-wrap\"\n >\n @if (multiple() && _chipList().length > 0) {\n @for (chip of _chipList(); track chip) {\n @if (getDescription(chip)) {\n <div\n [quangTooltip]=\"chipMaxLength() ? getDescription(chip) : ''\"\n class=\"chip chip-hover\"\n >\n <p [ngClass]=\"{ 'm-0': isReadonly() || _isDisabled() }\">\n {{ getDescription(chip) }}\n </p>\n @if (!isReadonly() && !_isDisabled()) {\n <button\n [tabIndex]=\"$index + 1\"\n (click)=\"deleteChip(chip)\"\n class=\"btn btn-chip\"\n type=\"button\"\n >\n <svg\n class=\"ionicon\"\n fill=\"currentColor\"\n height=\"24\"\n viewBox=\"0 0 512 512\"\n width=\"24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M368 368L144 144M368 144L144 368\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"32\"\n />\n </svg>\n </button>\n }\n </div>\n }\n }\n }\n\n <input\n [attr.required]=\"getIsRequiredControl()\"\n [class.form-control]=\"multiSelectDisplayMode() !== 'horizontal'\"\n [class.is-invalid]=\"_showErrors()\"\n [class.is-valid]=\"_showSuccess()\"\n [disabled]=\"_isDisabled() || isReadonly()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [placeholder]=\"componentPlaceholder() | transloco\"\n [tabIndex]=\"componentTabIndex()\"\n [value]=\"_inputValue()\"\n (blur)=\"onBlurInput($event)\"\n (input)=\"onChangeInput($event)\"\n (mousedown)=\"showOptionVisibility()\"\n #selectInput\n autocomplete=\"off\"\n type=\"text\"\n />\n </div>\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_highlightedValue()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"false\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentID]=\"componentId()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"autocompleteContainer\"\n [selectOptions]=\"_filteredOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurOptionList($event)\"\n (changedHandler)=\"onValueChange($event)\"\n #optionList\n selectionMode=\"single\"\n />\n }\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\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</div>\n", styles: [":host{display:block;--chip-max-length: none}.autocomplete-container{margin-bottom:1rem;position:relative}.chip:has(.btn-chip:disabled):hover{filter:unset;cursor:unset}.container-wrap{display:flex;flex-wrap:wrap;gap:.5rem}.container-wrap.horizontal{display:flex}.container-wrap.horizontal .chip-container{max-width:70%;margin-bottom:0;margin-left:.5rem;flex-wrap:nowrap;white-space:nowrap;overflow-x:auto;position:absolute;align-items:center}.container-wrap.horizontal .chip-container .chip{white-space:nowrap}.container-wrap.horizontal input{min-width:30%;flex:1 1 0;width:auto;border:none}.container-wrap.horizontal input:focus-visible{outline:none}.chip{display:flex;justify-content:space-between;align-items:center;padding:.25rem .5rem;border-radius:16px;color:var(--bs-btn-color);background-color:rgba(var(--bs-primary-rgb),.1);border-width:1px;border-style:solid;border-color:var(--bs-primary-border-subtle);height:2rem}.chip p{margin:0;max-width:var(--chip-max-length);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.chip .btn-chip{text-align:end;padding:0;min-width:unset}.chip .btn-chip:hover{opacity:80%}.chip .btn-chip:active{border-color:transparent}.chip .btn-chip svg{color:var(--bs-primary);vertical-align:sub}.chip:has(.btn-chip:focus-visible){border-width:2px;filter:brightness(80%)}\n"] }]
746
+ ], template: "<div\n [ngStyle]=\"{ '--chip-max-length': chipMaxLength() ? chipMaxLength() + 'ch' : 'none' }\"\n #autocompleteContainer\n class=\"autocomplete-container\"\n>\n @if (componentLabel()) {\n <label\n [htmlFor]=\"componentId()\"\n class=\"form-label\"\n >\n {{ componentLabel() | transloco }}\n <span [hidden]=\"!_isRequired()\">*</span>\n </label>\n }\n <div\n [ngClass]=\"{\n horizontal: multiSelectDisplayMode() === 'horizontal',\n 'form-control': multiSelectDisplayMode() === 'horizontal',\n 'chips-bottom': chipsPosition() === 'bottom',\n }\"\n #chipContainer\n class=\"container-wrap\"\n >\n @if (multiple() && _chipList().length > 0 && chipsPosition() === 'top') {\n <ng-container *ngTemplateOutlet=\"chipsTemplate\" />\n }\n\n <input\n [attr.aria-activedescendant]=\"_showOptions() ? optionList()?.getActiveDescendantId() : null\"\n [attr.aria-controls]=\"_showOptions() ? 'optionList' : null\"\n [attr.aria-expanded]=\"_showOptions()\"\n [attr.required]=\"getIsRequiredControl()\"\n [class.form-control]=\"multiSelectDisplayMode() !== 'horizontal'\"\n [class.is-invalid]=\"_showErrors()\"\n [class.is-valid]=\"_showSuccess()\"\n [disabled]=\"_isDisabled() || isReadonly()\"\n [id]=\"componentId()\"\n [ngClass]=\"componentClass()\"\n [placeholder]=\"componentPlaceholder() | transloco\"\n [tabIndex]=\"componentTabIndex()\"\n [value]=\"_inputValue()\"\n (blur)=\"onBlurInput($event)\"\n (input)=\"onChangeInput($event)\"\n (keydown)=\"onInputKeydown($event)\"\n (mousedown)=\"showOptionVisibility()\"\n #selectInput\n aria-autocomplete=\"list\"\n aria-haspopup=\"listbox\"\n autocomplete=\"off\"\n role=\"combobox\"\n type=\"text\"\n />\n\n @if (multiple() && _chipList().length > 0 && chipsPosition() === 'bottom') {\n <ng-container *ngTemplateOutlet=\"chipsTemplate\" />\n }\n </div>\n @if (_showOptions()) {\n <quang-option-list\n [_isDisabled]=\"_isDisabled()\"\n [_value]=\"_highlightedValue()\"\n [componentClass]=\"componentClass()\"\n [componentLabel]=\"componentLabel()\"\n [componentTabIndex]=\"componentTabIndex()\"\n [nullOption]=\"false\"\n [optionListMaxHeight]=\"optionListMaxHeight()\"\n [parentID]=\"componentId()\"\n [parentType]=\"ParentType\"\n [scrollBehaviorOnOpen]=\"scrollBehaviorOnOpen()\"\n [selectButtonRef]=\"autocompleteContainer\"\n [selectOptions]=\"_filteredOptions()\"\n [translateValue]=\"translateValue()\"\n (blurHandler)=\"onBlurOptionList($event)\"\n (changedHandler)=\"onValueChange($event)\"\n (escapePressed)=\"onEscapePressed()\"\n (tabPressed)=\"onTabPressed($event)\"\n #optionList\n selectionMode=\"single\"\n />\n }\n <div class=\"valid-feedback\">\n {{ successMessage() | transloco }}\n </div>\n <div class=\"invalid-feedback\">\n {{ _currentErrorMessage() | transloco: _currentErrorMessageExtraData() }}\n </div>\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</div>\n\n<!-- Chips template for reuse in top/bottom positions -->\n<ng-template #chipsTemplate>\n <div class=\"chips-container\">\n @for (chip of _chipList(); track chip) {\n @if (getDescription(chip)) {\n <div\n [quangTooltip]=\"chipMaxLength() ? getDescription(chip) : ''\"\n class=\"chip chip-hover\"\n >\n <p [ngClass]=\"{ 'm-0': isReadonly() || _isDisabled() }\">\n {{ getDescription(chip) }}\n </p>\n @if (!isReadonly() && !_isDisabled()) {\n <button\n [tabIndex]=\"$index + 1\"\n (click)=\"deleteChip(chip)\"\n class=\"btn btn-chip\"\n type=\"button\"\n >\n <svg\n class=\"ionicon\"\n fill=\"currentColor\"\n height=\"24\"\n viewBox=\"0 0 512 512\"\n width=\"24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M368 368L144 144M368 144L144 368\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"32\"\n />\n </svg>\n </button>\n }\n </div>\n }\n }\n </div>\n</ng-template>\n", styles: [":host{display:block;--chip-max-length: none}.autocomplete-container{margin-bottom:1rem;position:relative}.chip:has(.btn-chip:disabled):hover{filter:unset;cursor:unset}.container-wrap,.container-wrap .chips-container{display:flex;flex-wrap:wrap;gap:.5rem}.container-wrap.chips-bottom{flex-direction:column}.container-wrap.chips-bottom input{order:-1}.container-wrap.horizontal{display:flex}.container-wrap.horizontal .chip-container{max-width:70%;margin-bottom:0;margin-left:.5rem;flex-wrap:nowrap;white-space:nowrap;overflow-x:auto;position:absolute;align-items:center}.container-wrap.horizontal .chip-container .chip{white-space:nowrap}.container-wrap.horizontal input{min-width:30%;flex:1 1 0;width:auto;border:none}.container-wrap.horizontal input:focus-visible{outline:none}.chip{display:flex;justify-content:space-between;align-items:center;padding:.25rem .5rem;border-radius:16px;color:var(--bs-btn-color);background-color:rgba(var(--bs-primary-rgb),.1);border-width:1px;border-style:solid;border-color:var(--bs-primary-border-subtle);height:2rem}.chip p{margin:0;max-width:var(--chip-max-length);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.chip .btn-chip{text-align:end;padding:0;min-width:unset}.chip .btn-chip:hover{opacity:80%}.chip .btn-chip:active{border-color:transparent}.chip .btn-chip svg{color:var(--bs-primary);vertical-align:sub}.chip:has(.btn-chip:focus-visible){border-width:2px;filter:brightness(80%)}\n"] }]
643
747
  }], ctorParameters: () => [] });
644
748
 
645
749
  /**