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.
- package/components/autocomplete/autocomplete.component.d.ts +26 -5
- package/components/select/select.component.d.ts +21 -0
- package/components/shared/option-list/option-list.component.d.ts +14 -2
- package/fesm2022/quang-components-autocomplete.mjs +198 -101
- package/fesm2022/quang-components-autocomplete.mjs.map +1 -1
- package/fesm2022/quang-components-select.mjs +59 -2
- package/fesm2022/quang-components-select.mjs.map +1 -1
- package/fesm2022/quang-components-shared.mjs +36 -2
- package/fesm2022/quang-components-shared.mjs.map +1 -1
- package/package.json +27 -27
|
@@ -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
|
|
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<
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
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 (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
398
|
-
|
|
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
|
-
|
|
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
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
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
|
|
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 (
|
|
505
|
-
// On blur
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
});
|
|
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
|
/**
|