quang 19.3.15-3 → 19.3.15-4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import * as i0 from "@angular/core";
9
9
  *
10
10
  * `searchTextDebounce` is by default set to 300ms.
11
11
  */
12
- export declare class QuangAutocompleteComponent extends QuangBaseComponent<string | number | string[] | number[]> {
12
+ export declare class QuangAutocompleteComponent extends QuangBaseComponent<string | number | string[] | number[] | null> {
13
13
  /**
14
14
  * The list of options to display in the autocomplete dropdown.
15
15
  */
@@ -166,8 +166,8 @@ export declare class QuangAutocompleteComponent extends QuangBaseComponent<strin
166
166
  private readonly showOptionsChangeSubscription;
167
167
  constructor();
168
168
  setupFormControl(): void;
169
- writeValue(val: string | number | string[] | number[]): void;
170
- onChangedHandler(value: string | number | string[] | number[]): void;
169
+ writeValue(val: string | number | string[] | number[] | null): void;
170
+ onChangedHandler(value: string | number | string[] | number[] | null): void;
171
171
  onBlurHandler(): void;
172
172
  /**
173
173
  * Shows the option list dropdown.
@@ -187,7 +187,28 @@ export declare class QuangAutocompleteComponent extends QuangBaseComponent<strin
187
187
  * @param value The selected option's value
188
188
  * @param hideOptions Whether to hide the dropdown after selection
189
189
  */
190
- onValueChange(value: string | number, hideOptions?: boolean): void;
190
+ onValueChange(value: string | number | null, hideOptions?: boolean): void;
191
+ /**
192
+ * Handles keydown events on the input element for accessibility.
193
+ * @param event The keyboard event
194
+ */
195
+ onInputKeydown(event: KeyboardEvent): void;
196
+ /**
197
+ * Handles Escape key press from option list.
198
+ * Closes dropdown and returns focus to input.
199
+ */
200
+ onEscapePressed(): void;
201
+ /**
202
+ * Handles Tab key press from option list.
203
+ * Closes dropdown and allows natural tab navigation.
204
+ */
205
+ onTabPressed(_event: {
206
+ shiftKey: boolean;
207
+ }): void;
208
+ /**
209
+ * Sets focus to the input element.
210
+ */
211
+ focusInput(): void;
191
212
  /**
192
213
  * Handles input blur event.
193
214
  * @param event The focus event
@@ -249,7 +270,7 @@ export declare class QuangAutocompleteComponent extends QuangBaseComponent<strin
249
270
  /**
250
271
  * Emits search text change after debounce.
251
272
  * When `updateValueOnType` is true, also updates the form value using the same
252
- * matching logic as checkInputValue (auto-select matching options, or use free text).
273
+ * matching logic as processTextToFormValue (auto-select matching options, or use free text).
253
274
  */
254
275
  private emitDebouncedSearchText;
255
276
  /**
@@ -26,6 +26,27 @@ export declare class QuangSelectComponent extends QuangBaseComponent<string | nu
26
26
  onBlurHandler(): void;
27
27
  onChangedHandler(value: string | number | string[] | number[] | null): void;
28
28
  onMouseLeaveCallback(): void;
29
+ /**
30
+ * Handles keydown events on the select button for accessibility.
31
+ * @param event The keyboard event
32
+ */
33
+ onButtonKeydown(event: KeyboardEvent): void;
34
+ /**
35
+ * Handles Escape key press from option list.
36
+ * Closes dropdown and returns focus to button.
37
+ */
38
+ onEscapePressed(): void;
39
+ /**
40
+ * Handles Tab key press from option list.
41
+ * Closes dropdown and allows natural tab navigation.
42
+ */
43
+ onTabPressed(_event: {
44
+ shiftKey: boolean;
45
+ }): void;
46
+ /**
47
+ * Sets focus to the select button element.
48
+ */
49
+ focusButton(): void;
29
50
  static ɵfac: i0.ɵɵFactoryDeclaration<QuangSelectComponent, never>;
30
51
  static ɵcmp: i0.ɵɵComponentDeclaration<QuangSelectComponent, "quang-select", never, { "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "optionListMaxHeight": { "alias": "optionListMaxHeight"; "required": false; "isSignal": true; }; "selectOptions": { "alias": "selectOptions"; "required": true; "isSignal": true; }; "scrollBehaviorOnOpen": { "alias": "scrollBehaviorOnOpen"; "required": false; "isSignal": true; }; "translateValue": { "alias": "translateValue"; "required": false; "isSignal": true; }; "nullOption": { "alias": "nullOption"; "required": false; "isSignal": true; }; "autoSelectSingleOption": { "alias": "autoSelectSingleOption"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
31
52
  }
@@ -14,7 +14,7 @@ export declare class QuangOptionListComponent {
14
14
  selectionMode: import("@angular/core").InputSignal<"single" | "multiple">;
15
15
  optionListMaxHeight: import("@angular/core").InputSignal<string>;
16
16
  selectOptions: import("@angular/core").InputSignal<SelectOption[]>;
17
- selectButtonRef: import("@angular/core").InputSignal<HTMLInputElement | HTMLButtonElement | HTMLDivElement>;
17
+ selectButtonRef: import("@angular/core").InputSignal<HTMLButtonElement | HTMLInputElement | HTMLDivElement>;
18
18
  _value: import("@angular/core").InputSignal<any>;
19
19
  _isDisabled: import("@angular/core").InputSignal<boolean | undefined>;
20
20
  componentClass: import("@angular/core").InputSignal<string | string[]>;
@@ -28,6 +28,12 @@ export declare class QuangOptionListComponent {
28
28
  scrollBehaviorOnOpen: import("@angular/core").InputSignal<ScrollBehavior>;
29
29
  changedHandler: import("@angular/core").OutputEmitterRef<any>;
30
30
  blurHandler: import("@angular/core").OutputEmitterRef<any>;
31
+ /** Emitted when user presses Escape - parent should close dropdown and return focus to trigger */
32
+ escapePressed: import("@angular/core").OutputEmitterRef<void>;
33
+ /** Emitted when user presses Tab - parent should handle focus transition */
34
+ tabPressed: import("@angular/core").OutputEmitterRef<{
35
+ shiftKey: boolean;
36
+ }>;
31
37
  optionListContainer: import("@angular/core").Signal<ElementRef<HTMLDivElement> | undefined>;
32
38
  destroyRef: DestroyRef;
33
39
  parentType: import("@angular/core").InputSignal<OptionListParentType>;
@@ -38,6 +44,12 @@ export declare class QuangOptionListComponent {
38
44
  selectOptionsList: import("@angular/core").Signal<SelectOption[]>;
39
45
  onKeyDown: Subscription | null;
40
46
  selectedElementIndex: import("@angular/core").Signal<number>;
47
+ /** Signal to track currently focused item index for aria-activedescendant */
48
+ focusedItemIndex: import("@angular/core").WritableSignal<number>;
49
+ /**
50
+ * Returns the ID of the currently focused item for aria-activedescendant
51
+ */
52
+ getActiveDescendantId(): string | null;
41
53
  optionList$: import("@angular/core").EffectRef;
42
54
  handleSearch(key: string, listItems: HTMLLIElement[], currentIndex: number): number;
43
55
  changePosition(): void;
@@ -49,5 +61,5 @@ export declare class QuangOptionListComponent {
49
61
  getOptionListWidth(): void;
50
62
  getOptionListTop(): void;
51
63
  static ɵfac: i0.ɵɵFactoryDeclaration<QuangOptionListComponent, never>;
52
- static ɵcmp: i0.ɵɵComponentDeclaration<QuangOptionListComponent, "quang-option-list", never, { "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "optionListMaxHeight": { "alias": "optionListMaxHeight"; "required": false; "isSignal": true; }; "selectOptions": { "alias": "selectOptions"; "required": false; "isSignal": true; }; "selectButtonRef": { "alias": "selectButtonRef"; "required": true; "isSignal": true; }; "_value": { "alias": "_value"; "required": false; "isSignal": true; }; "_isDisabled": { "alias": "_isDisabled"; "required": false; "isSignal": true; }; "componentClass": { "alias": "componentClass"; "required": false; "isSignal": true; }; "componentLabel": { "alias": "componentLabel"; "required": false; "isSignal": true; }; "componentTabIndex": { "alias": "componentTabIndex"; "required": false; "isSignal": true; }; "translateValue": { "alias": "translateValue"; "required": false; "isSignal": true; }; "nullOption": { "alias": "nullOption"; "required": false; "isSignal": true; }; "scrollBehaviorOnOpen": { "alias": "scrollBehaviorOnOpen"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": true; "isSignal": true; }; "parentID": { "alias": "parentID"; "required": false; "isSignal": true; }; }, { "changedHandler": "changedHandler"; "blurHandler": "blurHandler"; }, never, never, true, never>;
64
+ static ɵcmp: i0.ɵɵComponentDeclaration<QuangOptionListComponent, "quang-option-list", never, { "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "optionListMaxHeight": { "alias": "optionListMaxHeight"; "required": false; "isSignal": true; }; "selectOptions": { "alias": "selectOptions"; "required": false; "isSignal": true; }; "selectButtonRef": { "alias": "selectButtonRef"; "required": true; "isSignal": true; }; "_value": { "alias": "_value"; "required": false; "isSignal": true; }; "_isDisabled": { "alias": "_isDisabled"; "required": false; "isSignal": true; }; "componentClass": { "alias": "componentClass"; "required": false; "isSignal": true; }; "componentLabel": { "alias": "componentLabel"; "required": false; "isSignal": true; }; "componentTabIndex": { "alias": "componentTabIndex"; "required": false; "isSignal": true; }; "translateValue": { "alias": "translateValue"; "required": false; "isSignal": true; }; "nullOption": { "alias": "nullOption"; "required": false; "isSignal": true; }; "scrollBehaviorOnOpen": { "alias": "scrollBehaviorOnOpen"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": true; "isSignal": true; }; "parentID": { "alias": "parentID"; "required": false; "isSignal": true; }; }, { "changedHandler": "changedHandler"; "blurHandler": "blurHandler"; "escapePressed": "escapePressed"; "tabPressed": "tabPressed"; }, never, never, true, never>;
53
65
  }
@@ -241,12 +241,12 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
241
241
  /** Effect to handle input element setup and keyboard events */
242
242
  this.onChangeSelectInputEffect = effect(() => {
243
243
  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
- }
244
+ if (!selectInput)
245
+ return;
246
+ this.inputHeight.set(selectInput.nativeElement.getBoundingClientRect().height);
247
+ selectInput.nativeElement.addEventListener('keydown', (e) => {
248
+ this.handleInputKeydown(e, selectInput.nativeElement);
249
+ });
250
250
  });
251
251
  /** Subscription to options changes */
252
252
  this.selectOptionsChangeSubscription = toObservable(this.selectOptions)
@@ -262,20 +262,20 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
262
262
  // for immediate processing. This subscription is kept for backwards compatibility
263
263
  // but the _isSearching check prevents double-processing since onBlurHandler
264
264
  // 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
- }
265
+ if (!(!data && data !== null && this._isSearching()))
266
+ return;
267
+ // Only process if still in search mode (which means onBlurHandler didn't run)
268
+ this.processTextToFormValue(this._userSearchText(), {
269
+ exitSearchMode: true,
270
+ updateOnMatch: true,
271
+ clearSearchText: true,
272
+ });
273
273
  });
274
274
  this.destroyRef.onDestroy(() => {
275
275
  this._isDestroyed = true;
276
- if (this._searchDebounceTimer) {
277
- clearTimeout(this._searchDebounceTimer);
278
- }
276
+ if (!this._searchDebounceTimer)
277
+ return;
278
+ clearTimeout(this._searchDebounceTimer);
279
279
  });
280
280
  }
281
281
  // ============================================
@@ -288,13 +288,13 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
288
288
  this.formValueChangeSubscription.unsubscribe();
289
289
  this.formValueChangeSubscription = undefined;
290
290
  }
291
- if (formControl) {
292
- this.formValueChangeSubscription = formControl.valueChanges
293
- .pipe(takeUntilDestroyed(this.destroyRef))
294
- .subscribe((value) => {
295
- this.handleFormValueChange(value);
296
- });
297
- }
291
+ if (!formControl)
292
+ return;
293
+ this.formValueChangeSubscription = formControl.valueChanges
294
+ .pipe(takeUntilDestroyed(this.destroyRef))
295
+ .subscribe((value) => {
296
+ this.handleFormValueChange(value);
297
+ });
298
298
  }
299
299
  writeValue(val) {
300
300
  // Simply update the value - _inputValue is computed and will automatically
@@ -310,7 +310,7 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
310
310
  onChangedHandler(value) {
311
311
  super.onChangedHandler(value);
312
312
  // Exit search mode - _inputValue will now derive from _value
313
- // Note: Don't clear _userSearchText here - it's needed for checkInputValue matching
313
+ // Note: Don't clear _userSearchText here - it's needed for processTextToFormValue matching
314
314
  this._isSearching.set(false);
315
315
  }
316
316
  onBlurHandler() {
@@ -336,11 +336,11 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
336
336
  // Initialize _userSearchText with current input value when showing options
337
337
  // This ensures that if user focuses and blurs without typing, the value is preserved
338
338
  // 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
- }
339
+ if (this._isSearching())
340
+ return;
341
+ const currentInputValue = this._inputValue();
342
+ this._userSearchText.set(currentInputValue || '');
343
+ this._isSearching.set(true);
344
344
  }
345
345
  /**
346
346
  * Hides the option list dropdown.
@@ -365,6 +365,20 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
365
365
  * @param hideOptions Whether to hide the dropdown after selection
366
366
  */
367
367
  onValueChange(value, hideOptions = true) {
368
+ // When allowFreeText is true and a null/undefined value is received (e.g., from selecting
369
+ // a non-existent option in the dropdown), use the typed text as the value instead of clearing
370
+ if ((value === null || value === undefined) && this._allowFreeTextInternal()) {
371
+ const typedText = this._userSearchText()?.trim();
372
+ if (typedText) {
373
+ this.onChangedHandler(typedText);
374
+ if (hideOptions) {
375
+ this.hideOptionVisibility();
376
+ this.focusInput();
377
+ }
378
+ this.selectedOption.emit(typedText);
379
+ return;
380
+ }
381
+ }
368
382
  if (this.multiple()) {
369
383
  this.handleSelectValue(value);
370
384
  this.onChangedHandler(this._chipList());
@@ -372,21 +386,100 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
372
386
  this._userSearchText.set('');
373
387
  this._isSearching.set(false);
374
388
  }
389
+ return;
375
390
  }
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);
391
+ // Update _userSearchText to the selected option's label
392
+ // This enables processTextToFormValue to match correctly on blur
393
+ const selectedOption = this.selectOptions().find((x) => x.value === value);
394
+ if (selectedOption) {
395
+ this._userSearchText.set(selectedOption.label);
396
+ }
397
+ this.onChangedHandler(value);
398
+ if (hideOptions) {
399
+ this.hideOptionVisibility();
400
+ // Return focus to input after selection
401
+ this.focusInput();
402
+ }
403
+ this.selectedOption.emit(value);
404
+ }
405
+ /**
406
+ * Handles keydown events on the input element for accessibility.
407
+ * @param event The keyboard event
408
+ */
409
+ onInputKeydown(event) {
410
+ switch (event.key) {
411
+ case 'ArrowDown':
412
+ // Open dropdown if closed, or let option-list handle navigation
413
+ if (!this._showOptions()) {
414
+ event.preventDefault();
415
+ this.showOptionVisibility();
416
+ }
417
+ break;
418
+ case 'ArrowUp':
419
+ // Open dropdown if closed
420
+ if (!this._showOptions()) {
421
+ event.preventDefault();
422
+ this.showOptionVisibility();
423
+ }
424
+ break;
425
+ case 'Escape':
426
+ // Close dropdown and keep focus on input
427
+ if (this._showOptions()) {
428
+ event.preventDefault();
429
+ this.onEscapePressed();
430
+ }
431
+ break;
432
+ case 'Enter':
433
+ // When allowFreeText is true and dropdown is open, handle Enter specially
434
+ if (this._showOptions() && this._allowFreeTextInternal()) {
435
+ // Check if there are any filtered options
436
+ const filteredOptions = this._filteredOptions();
437
+ if (filteredOptions.length === 0) {
438
+ // No options to select - use the typed text as the value
439
+ event.preventDefault();
440
+ this.processTextToFormValue(this._userSearchText(), {
441
+ exitSearchMode: true,
442
+ updateOnMatch: true,
443
+ clearSearchText: false,
444
+ });
445
+ this.hideOptionVisibility();
446
+ }
447
+ // If there are filtered options, let option-list handle the selection
448
+ }
449
+ break;
388
450
  }
389
451
  }
452
+ /**
453
+ * Handles Escape key press from option list.
454
+ * Closes dropdown and returns focus to input.
455
+ */
456
+ onEscapePressed() {
457
+ this.hideOptionVisibility();
458
+ this.focusInput();
459
+ }
460
+ /**
461
+ * Handles Tab key press from option list.
462
+ * Closes dropdown and allows natural tab navigation.
463
+ */
464
+ onTabPressed(_event) {
465
+ // Close the dropdown, tab will naturally move focus
466
+ this.hideOptionVisibility();
467
+ // Process any pending input value
468
+ this.processTextToFormValue(this._userSearchText(), {
469
+ exitSearchMode: true,
470
+ updateOnMatch: true,
471
+ clearSearchText: true,
472
+ });
473
+ }
474
+ /**
475
+ * Sets focus to the input element.
476
+ */
477
+ focusInput() {
478
+ const inputEl = this.selectInput()?.nativeElement;
479
+ if (!inputEl)
480
+ return;
481
+ inputEl.focus();
482
+ }
390
483
  /**
391
484
  * Handles input blur event.
392
485
  * @param event The focus event
@@ -394,18 +487,18 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
394
487
  onBlurInput(event) {
395
488
  const relatedTarget = event.relatedTarget;
396
489
  const optionListId = this.optionList()?.optionListContainer()?.nativeElement?.id;
397
- if (relatedTarget?.id !== optionListId) {
398
- this.onBlurHandler();
399
- }
490
+ if (relatedTarget?.id === optionListId)
491
+ return;
492
+ this.onBlurHandler();
400
493
  }
401
494
  /**
402
495
  * Handles blur event on the option list.
403
496
  * @param event The blur event (truthy if should hide)
404
497
  */
405
498
  onBlurOptionList(event) {
406
- if (event) {
407
- this.hideOptionVisibility();
408
- }
499
+ if (!event)
500
+ return;
501
+ this.hideOptionVisibility();
409
502
  }
410
503
  /**
411
504
  * Gets the display description for a chip value.
@@ -423,10 +516,10 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
423
516
  deleteChip(chipValue) {
424
517
  const stringChipValue = chipValue?.toString();
425
518
  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
- }
519
+ if (index < 0)
520
+ return;
521
+ this._chipList.update((list) => list.filter((_, i) => i !== index));
522
+ this.onChangedHandler(this._chipList());
430
523
  }
431
524
  // ============================================
432
525
  // PROTECTED METHODS - Internal logic, accessible to subclasses
@@ -438,10 +531,10 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
438
531
  */
439
532
  filterOptions(value) {
440
533
  const options = this.selectOptions();
441
- if (this.internalFilterOptions()) {
442
- return options.filter((x) => x.label.toLowerCase().includes(value.toLowerCase()));
443
- }
444
- return options;
534
+ const trimmedValue = value?.trim();
535
+ return this.internalFilterOptions() && trimmedValue
536
+ ? options.filter((x) => x.label.toLowerCase().includes(trimmedValue.toLowerCase()))
537
+ : options;
445
538
  }
446
539
  // ============================================
447
540
  // PRIVATE METHODS - Internal implementation
@@ -479,9 +572,11 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
479
572
  const shouldAutoSelect = matchingOption && this.autoSelectOnExactMatch() && options.updateOnMatch;
480
573
  const shouldUseFreeText = this._allowFreeTextInternal() && searchText && options.updateOnMatch;
481
574
  // Clear logic differs between typing and blur:
482
- // - On blur (exitSearchMode=true): clear when no valid selection and free text not allowed
575
+ // - On blur (exitSearchMode=true): clear when input is empty (regardless of allowFreeText setting)
576
+ // - On blur: also clear when no valid selection and free text not allowed
483
577
  // - 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());
578
+ const shouldClearOnBlurEmpty = options.exitSearchMode && !searchText;
579
+ const shouldClearOnBlurNoMatch = options.exitSearchMode && !this._allowFreeTextInternal() && (!matchingOption || !this.autoSelectOnExactMatch());
485
580
  const shouldClearWhileTyping = !options.exitSearchMode && options.updateOnMatch && !matchingOption && !this._allowFreeTextInternal();
486
581
  if (shouldAutoSelect) {
487
582
  // Auto-select the matching option
@@ -501,9 +596,9 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
501
596
  this.onValueChange(searchText, false);
502
597
  }
503
598
  }
504
- else if (shouldClearOnBlur) {
505
- // On blur, no valid selection possible: clear the value
506
- this.onChangedHandler('');
599
+ else if (shouldClearOnBlurEmpty || shouldClearOnBlurNoMatch) {
600
+ // On blur with empty input or no valid selection: clear the value to null
601
+ this.onChangedHandler(null);
507
602
  }
508
603
  else if (shouldClearWhileTyping) {
509
604
  // While typing, text doesn't match any option: clear the value but stay in search mode
@@ -518,25 +613,25 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
518
613
  * Handles keyboard events on the input element.
519
614
  */
520
615
  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
- }
616
+ if (!(this.multiple() && this._chipList().length > 0 && !this._inputValue()?.length && e.key === 'Backspace')) {
617
+ return;
618
+ }
619
+ e.preventDefault();
620
+ const chipContainerEl = this.chipContainer()?.nativeElement;
621
+ if (chipContainerEl) {
622
+ const chips = chipContainerEl.querySelectorAll('.chip button.btn-chip');
623
+ if (chips.length > 0) {
624
+ const lastChip = chips[chips.length - 1];
625
+ lastChip.focus();
626
+ lastChip.addEventListener('keydown', (event) => {
627
+ if (event.key === 'Backspace') {
628
+ event.preventDefault();
629
+ this.deleteChip(this._chipList()[this._chipList().length - 1]);
630
+ inputElement.focus();
631
+ return;
632
+ }
633
+ event.preventDefault();
634
+ });
540
635
  }
541
636
  }
542
637
  }
@@ -556,14 +651,15 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
556
651
  * Handles form value changes from external sources.
557
652
  */
558
653
  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
- });
654
+ if (!(this.multiple() && Array.isArray(value))) {
655
+ return;
565
656
  }
566
- // Note: Don't clear _userSearchText here - it's managed by checkInputValue
657
+ this._chipList.set([]);
658
+ this._selectedOptions.set([]);
659
+ value.forEach((x) => {
660
+ this.handleSelectValue(x);
661
+ });
662
+ // Note: Don't clear _userSearchText here - it's managed by processTextToFormValue
567
663
  // which runs when options are hidden and needs _userSearchText for matching.
568
664
  }
569
665
  /**
@@ -579,7 +675,7 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
579
675
  /**
580
676
  * Emits search text change after debounce.
581
677
  * 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).
678
+ * matching logic as processTextToFormValue (auto-select matching options, or use free text).
583
679
  */
584
680
  emitDebouncedSearchText(value) {
585
681
  if (this._searchDebounceTimer) {
@@ -589,18 +685,19 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
589
685
  if (this._isDestroyed) {
590
686
  return;
591
687
  }
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
- });
688
+ if (value === this._lastEmittedSearchText) {
689
+ return;
603
690
  }
691
+ this._lastEmittedSearchText = value;
692
+ this.searchTextChange.emit(value || '');
693
+ // Update form value based on what the user typed
694
+ // - When updateValueOnType is true: update on both match and no-match
695
+ // - When updateValueOnType is false: only clear the value when text doesn't match
696
+ this.processTextToFormValue(value, {
697
+ exitSearchMode: false,
698
+ updateOnMatch: this.updateValueOnType(),
699
+ clearSearchText: false,
700
+ });
604
701
  }, this.searchTextDebounce());
605
702
  }
606
703
  /**
@@ -625,7 +722,7 @@ class QuangAutocompleteComponent extends QuangBaseComponent {
625
722
  provide: QuangOptionListComponent,
626
723
  multi: false,
627
724
  },
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 }); }
725
+ ], 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.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 </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", 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", "escapePressed", "tabPressed"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: QuangTooltipDirective, selector: "[quangTooltip]", inputs: ["quangTooltip", "showMethod"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
629
726
  }
630
727
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: QuangAutocompleteComponent, decorators: [{
631
728
  type: Component,
@@ -639,7 +736,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
639
736
  provide: QuangOptionListComponent,
640
737
  multi: false,
641
738
  },
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"] }]
739
+ ], 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.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 </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", 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"] }]
643
740
  }], ctorParameters: () => [] });
644
741
 
645
742
  /**