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.
- package/components/autocomplete/autocomplete.component.d.ts +34 -6
- package/components/select/select.component.d.ts +24 -3
- package/components/shared/option-list/option-list.component.d.ts +14 -2
- package/fesm2022/quang-components-autocomplete.mjs +208 -104
- package/fesm2022/quang-components-autocomplete.mjs.map +1 -1
- package/fesm2022/quang-components-select.mjs +66 -20
- package/fesm2022/quang-components-select.mjs.map +1 -1
- package/fesm2022/quang-components-shared.mjs +56 -43
- package/fesm2022/quang-components-shared.mjs.map +1 -1
- package/package.json +30 -30
|
@@ -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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
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 (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
398
|
-
|
|
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
|
-
|
|
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
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
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
|
|
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 (
|
|
505
|
-
// On blur
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
593
|
-
|
|
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'
|
|
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'
|
|
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
|
/**
|