@seniorsistemas/angular-components 17.27.1-feature-sds-110-1bb20c20 → 17.27.1-feature-sds-110-57186492
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/autocomplete/index.d.ts +5 -0
- package/autocomplete/lib/autocomplete/autocomplete.component.d.ts +250 -0
- package/autocomplete/lib/autocomplete/components/autocomplete-chip/autocomplete-chip.component.d.ts +9 -0
- package/autocomplete/public-api.d.ts +1 -0
- package/button/lib/button/button.component.d.ts +1 -1
- package/dynamic-form/lib/dynamic-form/components/lookup/lookup.component.d.ts +1 -2
- package/dynamic-form/lib/dynamic-form/dynamic-form.module.d.ts +3 -2
- package/dynamic-form/lib/dynamic-form/form-field/fields/currency/currency-field.component.d.ts +3 -2
- package/esm2022/autocomplete/lib/autocomplete/autocomplete.component.mjs +554 -0
- package/esm2022/autocomplete/lib/autocomplete/components/autocomplete-chip/autocomplete-chip.component.mjs +17 -0
- package/esm2022/autocomplete/public-api.mjs +2 -0
- package/esm2022/autocomplete/seniorsistemas-angular-components-autocomplete.mjs +5 -0
- package/esm2022/button/lib/button/button.component.mjs +4 -10
- package/esm2022/chips/lib/chips/chips/chips.component.mjs +2 -2
- package/esm2022/control-errors/lib/control-errors/control-errors.component.mjs +2 -2
- package/esm2022/country-phone-picker/lib/country-phone-picker/country-phone-picker.component.mjs +2 -2
- package/esm2022/dynamic-form/lib/dynamic-form/components/lookup/lookup.component.mjs +16 -10
- package/esm2022/dynamic-form/lib/dynamic-form/dynamic-form.module.mjs +5 -1
- package/esm2022/dynamic-form/lib/dynamic-form/form-field/fields/autocomplete/autocomplete-field.component.mjs +7 -6
- package/esm2022/dynamic-form/lib/dynamic-form/form-field/fields/currency/currency-field.component.mjs +18 -8
- package/esm2022/dynamic-form/lib/dynamic-form/form-field/fields/lookup/lookup-field.component.mjs +1 -1
- package/esm2022/dynamic-form/lib/dynamic-form/form-field/fields/password/password-field.component.mjs +2 -2
- package/esm2022/dynamic-form/lib/dynamic-form/form-field/fields/select/select-field.component.mjs +1 -1
- package/esm2022/inline-edit/lib/inline-edit/components/fields/inline-edit-lookup/inline-edit-lookup.component.mjs +1 -1
- package/esm2022/lib/locale/fallback.mjs +2 -1
- package/esm2022/object-card/lib/object-card/object-card.component.mjs +3 -3
- package/esm2022/paginator/lib/paginator/paginator.component.mjs +2 -2
- package/esm2022/panel/lib/panel/panel.component.mjs +3 -3
- package/esm2022/select/lib/select/select.component.mjs +234 -336
- package/esm2022/text-area/lib/text-area/text-area.component.mjs +4 -4
- package/esm2022/text-area-ia/lib/text-area-ia/text-area-ia.component.mjs +4 -4
- package/esm2022/toast/lib/toast/toast.component.mjs +13 -13
- package/esm2022/token-list/lib/token-list/token-list.component.mjs +3 -3
- package/fesm2022/seniorsistemas-angular-components-autocomplete.mjs +575 -0
- package/fesm2022/seniorsistemas-angular-components-autocomplete.mjs.map +1 -0
- package/fesm2022/seniorsistemas-angular-components-button.mjs +3 -9
- package/fesm2022/seniorsistemas-angular-components-button.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-chips.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-chips.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-control-errors.mjs +1 -1
- package/fesm2022/seniorsistemas-angular-components-control-errors.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-country-phone-picker.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-country-phone-picker.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs +38 -19
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-inline-edit.mjs +1 -1
- package/fesm2022/seniorsistemas-angular-components-inline-edit.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-object-card.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-object-card.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-paginator.mjs +1 -1
- package/fesm2022/seniorsistemas-angular-components-paginator.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-panel.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-panel.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-select.mjs +232 -334
- package/fesm2022/seniorsistemas-angular-components-select.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-text-area-ia.mjs +3 -3
- package/fesm2022/seniorsistemas-angular-components-text-area-ia.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-text-area.mjs +3 -3
- package/fesm2022/seniorsistemas-angular-components-text-area.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-toast.mjs +12 -12
- package/fesm2022/seniorsistemas-angular-components-toast.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-token-list.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-token-list.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components.mjs +1 -0
- package/fesm2022/seniorsistemas-angular-components.mjs.map +1 -1
- package/package.json +19 -13
- package/select/lib/select/select.component.d.ts +148 -157
- package/styles.css +1 -0
- package/text-area-ia/lib/text-area-ia/text-area-ia.component.d.ts +1 -1
- package/toast/lib/toast/toast.component.d.ts +4 -4
- package/src/lib/styles/tailwind.scss +0 -3
- package/tailwind.css +0 -1167
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import * as i3 from '@angular/cdk/overlay';
|
|
2
|
+
import { OverlayModule } from '@angular/cdk/overlay';
|
|
3
|
+
import * as i4 from '@angular/cdk/scrolling';
|
|
4
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
5
|
+
import * as i1 from '@angular/common';
|
|
6
|
+
import { CommonModule } from '@angular/common';
|
|
7
|
+
import * as i0 from '@angular/core';
|
|
8
|
+
import { input, output, Component, model, signal, viewChild, inject, DestroyRef, computed, forwardRef } from '@angular/core';
|
|
9
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
10
|
+
import * as i2 from '@angular/forms';
|
|
11
|
+
import { NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
12
|
+
import * as i5 from '@ngx-translate/core';
|
|
13
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
14
|
+
import { Subject, Observable } from 'rxjs';
|
|
15
|
+
import { debounceTime, distinctUntilChanged, switchMap, filter } from 'rxjs/operators';
|
|
16
|
+
|
|
17
|
+
class AutocompleteChipComponent {
|
|
18
|
+
suggestion = input.required();
|
|
19
|
+
suggestionLabel = input();
|
|
20
|
+
suggestionRemoved = output();
|
|
21
|
+
removeSuggestion() {
|
|
22
|
+
this.suggestionRemoved.emit(this.suggestion());
|
|
23
|
+
}
|
|
24
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AutocompleteChipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
25
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: AutocompleteChipComponent, isStandalone: true, selector: "s-autocomplete-chip", inputs: { suggestion: { classPropertyName: "suggestion", publicName: "suggestion", isSignal: true, isRequired: true, transformFunction: null }, suggestionLabel: { classPropertyName: "suggestionLabel", publicName: "suggestionLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { suggestionRemoved: "suggestionRemoved" }, ngImport: i0, template: "<div class=\"flex items-center gap-2 rounded-[4px] border border-grayscale-50 bg-grayscale-10 px-2\">\n <span class=\"text-nowrap\">{{ suggestionLabel() ? suggestion()[suggestionLabel()!] : suggestion() }}</span>\n <button (click)=\"removeSuggestion()\">\n <i class=\"far fa-times\"></i>\n </button>\n</div>\n" });
|
|
26
|
+
}
|
|
27
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AutocompleteChipComponent, decorators: [{
|
|
28
|
+
type: Component,
|
|
29
|
+
args: [{ selector: 's-autocomplete-chip', standalone: true, template: "<div class=\"flex items-center gap-2 rounded-[4px] border border-grayscale-50 bg-grayscale-10 px-2\">\n <span class=\"text-nowrap\">{{ suggestionLabel() ? suggestion()[suggestionLabel()!] : suggestion() }}</span>\n <button (click)=\"removeSuggestion()\">\n <i class=\"far fa-times\"></i>\n </button>\n</div>\n" }]
|
|
30
|
+
}] });
|
|
31
|
+
|
|
32
|
+
class AutocompleteComponent {
|
|
33
|
+
static nextId = 0;
|
|
34
|
+
suggestions = input([]);
|
|
35
|
+
placeholder = input();
|
|
36
|
+
suggestionValue = input();
|
|
37
|
+
suggestionLabel = input();
|
|
38
|
+
readonly = input(false);
|
|
39
|
+
emptyMessage = input();
|
|
40
|
+
delay = input(300);
|
|
41
|
+
invalid = input(false);
|
|
42
|
+
virtualScroll = input(false);
|
|
43
|
+
virtualScrollItemSize = input(37);
|
|
44
|
+
lazy = input(false);
|
|
45
|
+
dropdown = input(false);
|
|
46
|
+
forceSelection = input(false);
|
|
47
|
+
checkmark = input(false);
|
|
48
|
+
multiple = input(false);
|
|
49
|
+
minLengthToSearch = input(1);
|
|
50
|
+
inputClass = input('');
|
|
51
|
+
value = model();
|
|
52
|
+
values = model([]);
|
|
53
|
+
disabled = model(false);
|
|
54
|
+
lazyLoad = output();
|
|
55
|
+
selected = output();
|
|
56
|
+
unselected = output();
|
|
57
|
+
blurred = output();
|
|
58
|
+
cleared = output();
|
|
59
|
+
keyUp = output();
|
|
60
|
+
focused = output();
|
|
61
|
+
completeMethod = output();
|
|
62
|
+
focusedIndex = signal(-1);
|
|
63
|
+
isOpen = signal(false);
|
|
64
|
+
filteredSuggestions = signal([]);
|
|
65
|
+
showEmptyMessage = signal(false);
|
|
66
|
+
filterText = signal('');
|
|
67
|
+
inputValue = signal('');
|
|
68
|
+
loading = signal(false);
|
|
69
|
+
componentId = `autocomplete_${AutocompleteComponent.nextId++}`;
|
|
70
|
+
inputEl = viewChild('inputEl');
|
|
71
|
+
_inputSubject$ = new Subject();
|
|
72
|
+
_lazyLoadSubject$ = new Subject();
|
|
73
|
+
_destroyRef = inject(DestroyRef);
|
|
74
|
+
_onChange;
|
|
75
|
+
_onTouched;
|
|
76
|
+
/**
|
|
77
|
+
* Computed que retorna uma lista de sugestões do tipo `SuggestionWrapper<T>`,
|
|
78
|
+
* adicionando um identificador único a cada sugestão.
|
|
79
|
+
* @returns Uma lista de sugestões processadas com identificadores únicos.
|
|
80
|
+
*/
|
|
81
|
+
internalSuggestion = computed(() => {
|
|
82
|
+
const opts = this.suggestions();
|
|
83
|
+
return this._addIdToSuggestions(opts);
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* Retorna um conjunto (`Set`) com os IDs das sugestões selecionadas.
|
|
87
|
+
*/
|
|
88
|
+
selectedSuggestionIds = computed(() => {
|
|
89
|
+
if (!this.multiple()) {
|
|
90
|
+
return new Set();
|
|
91
|
+
}
|
|
92
|
+
const currentValues = this.values();
|
|
93
|
+
return new Set(currentValues.map((value) => {
|
|
94
|
+
return this.internalSuggestion().find((s) => JSON.stringify(s.data) === JSON.stringify(value))?.id;
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Retorna uma lista de sugestões filtradas, enriquecidas com a propriedade `isSelected`.
|
|
99
|
+
* Cada sugestão recebe um campo booleano `isSelected` indicando se ela está selecionada,
|
|
100
|
+
* com base nos IDs das sugestões selecionadas e no valor da sugestão.
|
|
101
|
+
* @returns Uma lista de sugestões filtradas, cada uma com a propriedade adicional `isSelected`.
|
|
102
|
+
*/
|
|
103
|
+
enrichedFilteredSuggestions = computed(() => {
|
|
104
|
+
const filtered = this.filteredSuggestions();
|
|
105
|
+
const selectedIds = this.selectedSuggestionIds();
|
|
106
|
+
return filtered.map((suggestion) => ({
|
|
107
|
+
...suggestion,
|
|
108
|
+
isSelected: selectedIds.has(suggestion.id),
|
|
109
|
+
}));
|
|
110
|
+
});
|
|
111
|
+
constructor() {
|
|
112
|
+
this._validateInputs();
|
|
113
|
+
this._setupInputDebounce();
|
|
114
|
+
this._setupLazyLoadDebounce();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Define o valor do autocomplete.
|
|
118
|
+
* @param value Valor a ser definido, único ou array, conforme seleção múltipla.
|
|
119
|
+
* @throws Se múltiplo for falso e value for array.
|
|
120
|
+
*/
|
|
121
|
+
writeValue(value) {
|
|
122
|
+
const suggestionLabel = this.suggestionLabel();
|
|
123
|
+
if (value && this.multiple()) {
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
this.values.set(value);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.values.set([value]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
if (Array.isArray(value)) {
|
|
133
|
+
throw new Error('The value must not be an array when multiple is false.');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.value.set(value);
|
|
137
|
+
}
|
|
138
|
+
this.inputValue.set(!value ? '' : suggestionLabel ? String(value[suggestionLabel]) : String(value));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Registra uma função de callback que será chamada sempre que o valor do componente mudar.
|
|
143
|
+
* @param onChange Função a ser chamada quando o valor mudar. Recebe o novo valor como argumento, que pode ser um único valor ou um array de valores.
|
|
144
|
+
*/
|
|
145
|
+
registerOnChange(onChange) {
|
|
146
|
+
this._onChange = onChange;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Registra uma função de callback que será chamada quando o componente for tocado (perder o foco).
|
|
150
|
+
* @param onTouched Função de callback a ser chamada quando o componente for tocado.
|
|
151
|
+
*/
|
|
152
|
+
registerOnTouched(onTouched) {
|
|
153
|
+
this._onTouched = onTouched;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Define o estado de desabilitado do componente.
|
|
157
|
+
* @param disabled Indica se o componente deve ser desabilitado (`true`) ou habilitado (`false`).
|
|
158
|
+
*/
|
|
159
|
+
setDisabledState(disabled) {
|
|
160
|
+
this.disabled.set(disabled);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Manipula o evento de entrada do usuário no campo de texto do autocomplete.
|
|
164
|
+
* Este método é chamado sempre que o usuário digita algo no campo de entrada.
|
|
165
|
+
* Ele executa as seguintes ações:
|
|
166
|
+
* - Dispara o callback de "touched" para controle de formulários.
|
|
167
|
+
* - Obtém o valor atual do campo de entrada.
|
|
168
|
+
* - Se o modo lazy estiver ativado, emite o evento de lazyLoad com a consulta e abre o painel de sugestões.
|
|
169
|
+
* - Caso contrário, atualiza o texto de filtro, notifica os observadores e abre o painel de sugestões se houver sugestões disponíveis.
|
|
170
|
+
* @param event Evento de entrada do tipo `Event` proveniente do campo de texto.
|
|
171
|
+
*/
|
|
172
|
+
onInput(event) {
|
|
173
|
+
this._onTouched?.();
|
|
174
|
+
const value = event.target.value;
|
|
175
|
+
if (this.lazy()) {
|
|
176
|
+
this._lazyLoadSubject$.next(value);
|
|
177
|
+
this.isOpen.set(true);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.filterText.set(value);
|
|
181
|
+
this.completeMethod.emit({ query: value });
|
|
182
|
+
this._inputSubject$.next(value);
|
|
183
|
+
if (this.suggestions()) {
|
|
184
|
+
this.isOpen.set(true);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Manipula o evento de perda de foco (blur) do campo de entrada do autocomplete.
|
|
189
|
+
* Este método verifica se o valor digitado pelo usuário corresponde exatamente a algum dos itens sugeridos.
|
|
190
|
+
* - Se houver correspondência:
|
|
191
|
+
* - No modo múltiplo (`multiple`), adiciona o item selecionado à lista de valores, caso ainda não esteja presente.
|
|
192
|
+
* - No modo simples, seleciona o item correspondente se ainda não estiver selecionado.
|
|
193
|
+
* - Se não houver correspondência, limpa o campo de entrada.
|
|
194
|
+
*/
|
|
195
|
+
onInputBlur(event) {
|
|
196
|
+
const inputValue = String(this.inputValue()).toLowerCase();
|
|
197
|
+
const suggestions = this.internalSuggestion();
|
|
198
|
+
const match = suggestions.find(({ data }) => {
|
|
199
|
+
const label = this.suggestionLabel() ? data[this.suggestionLabel()] : data;
|
|
200
|
+
return String(label).toLowerCase() === inputValue;
|
|
201
|
+
});
|
|
202
|
+
if (match) {
|
|
203
|
+
if (this.multiple()) {
|
|
204
|
+
const currentValues = this.values();
|
|
205
|
+
const exists = currentValues.some((value) => (this.suggestionValue() ? value[this.suggestionValue()] : value) ===
|
|
206
|
+
(this.suggestionValue() ? match.data[this.suggestionValue()] : match.data));
|
|
207
|
+
if (!exists) {
|
|
208
|
+
this.values.set([...currentValues, match.data]);
|
|
209
|
+
this._onChange?.(this.values());
|
|
210
|
+
}
|
|
211
|
+
this.inputValue.set('');
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
if (this.value() !== match.data) {
|
|
215
|
+
this.selectSuggestion(match.data);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.inputValue.set('');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Dispara o evento de foco quando o campo de entrada recebe foco.
|
|
225
|
+
* @param event O evento de foco disparado pelo elemento de entrada.
|
|
226
|
+
*/
|
|
227
|
+
onInputFocus(event) {
|
|
228
|
+
this.focused.emit(event);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Manipula eventos de teclado no componente de autocomplete.
|
|
232
|
+
* Este método gerencia a navegação e seleção de sugestões usando as teclas do teclado.
|
|
233
|
+
* - 'ArrowDown': Move o foco para a próxima sugestão.
|
|
234
|
+
* - 'ArrowUp': Move o foco para a sugestão anterior.
|
|
235
|
+
* - 'Enter' ou 'Tab': Seleciona a sugestão atualmente focada e fecha a lista de sugestões.
|
|
236
|
+
* - 'Escape': Fecha a lista de sugestões sem selecionar nenhum item.
|
|
237
|
+
* @param event O evento de teclado disparado pelo usuário.
|
|
238
|
+
*/
|
|
239
|
+
onKeyDown(event) {
|
|
240
|
+
const suggestions = this.filteredSuggestions();
|
|
241
|
+
this._onTouched?.();
|
|
242
|
+
const _selectSuggestion = () => {
|
|
243
|
+
if (this.focusedIndex() >= 0 && this.focusedIndex() < suggestions.length) {
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
this.selectSuggestion(suggestions[this.focusedIndex()].data);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
switch (event.key) {
|
|
249
|
+
case 'ArrowDown':
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
this._focusNextSuggestion(suggestions);
|
|
252
|
+
break;
|
|
253
|
+
case 'ArrowUp':
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
this._focusPreviousSuggestion();
|
|
256
|
+
break;
|
|
257
|
+
case 'Enter':
|
|
258
|
+
case 'Tab':
|
|
259
|
+
_selectSuggestion();
|
|
260
|
+
this.isOpen.set(false);
|
|
261
|
+
break;
|
|
262
|
+
case 'Escape':
|
|
263
|
+
this.isOpen.set(false);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Emite um evento quando uma tecla é liberada no componente. *
|
|
269
|
+
* @param event O evento de teclado associado à tecla liberada.
|
|
270
|
+
*/
|
|
271
|
+
onKeyUp(event) {
|
|
272
|
+
this.keyUp.emit(event);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Fecha o dropdown em eventos de redimensionamento de janela.
|
|
276
|
+
*/
|
|
277
|
+
onWindowResize() {
|
|
278
|
+
if (this.isOpen()) {
|
|
279
|
+
this.isOpen.set(false);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Seleciona uma sugestão da lista de sugestões.
|
|
284
|
+
* Se o modo múltiplo estiver ativado (`multiple()` retorna true), adiciona a sugestão à lista de suggestões selecionadas,
|
|
285
|
+
* caso ela ainda não esteja presente. Em seguida, limpa o valor do campo de entrada.
|
|
286
|
+
* Se o modo múltiplo não estiver ativado, define a sugestão como valor selecionado e atualiza o campo de entrada
|
|
287
|
+
* com o rótulo correspondente.
|
|
288
|
+
* Em ambos os casos, dispara o callback `_onChange` com o novo valor e fecha o painel de sugestões.
|
|
289
|
+
* @param suggestion - A sugestão a ser selecionada.
|
|
290
|
+
*/
|
|
291
|
+
selectSuggestion(suggestion) {
|
|
292
|
+
if (this.multiple()) {
|
|
293
|
+
const currentValues = this.values();
|
|
294
|
+
const exists = currentValues.some((value) => (this.suggestionValue() ? value[this.suggestionValue()] : value) ===
|
|
295
|
+
(this.suggestionValue() ? suggestion[this.suggestionValue()] : suggestion));
|
|
296
|
+
if (!exists) {
|
|
297
|
+
this.values.set([...currentValues, suggestion]);
|
|
298
|
+
this._onChange?.(this.values());
|
|
299
|
+
}
|
|
300
|
+
this.inputValue.set('');
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.value.set(suggestion);
|
|
304
|
+
const _value = this.suggestionValue() ? suggestion[this.suggestionValue()] : suggestion;
|
|
305
|
+
const _inputValue = String(this.suggestionLabel() ? suggestion[this.suggestionLabel()] : suggestion);
|
|
306
|
+
this.inputValue.set(_inputValue);
|
|
307
|
+
this._onChange?.(_value);
|
|
308
|
+
}
|
|
309
|
+
this.selected.emit(suggestion);
|
|
310
|
+
this.isOpen.set(false);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Limpa o valor selecionado no componente de autocomplete.
|
|
314
|
+
* Se o modo múltiplo estiver ativado, remove todos os valores selecionados.
|
|
315
|
+
* Caso contrário, define o valor como nulo.
|
|
316
|
+
* Também limpa as sugestões filtradas, fecha o painel de sugestões
|
|
317
|
+
* e limpa o valor do campo de entrada.
|
|
318
|
+
*/
|
|
319
|
+
clear() {
|
|
320
|
+
if (this.multiple()) {
|
|
321
|
+
this.values.set([]);
|
|
322
|
+
this._onChange?.(this.values());
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
this.value.set(null);
|
|
326
|
+
this._onChange?.(this.value());
|
|
327
|
+
}
|
|
328
|
+
this.filteredSuggestions.set([]);
|
|
329
|
+
this.isOpen.set(false);
|
|
330
|
+
this.inputValue.set('');
|
|
331
|
+
this.cleared.emit();
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Alterna a exibição do dropdown de sugestões do autocomplete.
|
|
335
|
+
* Este método configura o debounce do input, verifica se há sugestões filtradas
|
|
336
|
+
* e, caso não haja sugestões e o texto do filtro esteja vazio, redefine as sugestões filtradas.
|
|
337
|
+
* Por fim, alterna o estado de abertura do dropdown.
|
|
338
|
+
*/
|
|
339
|
+
toggleDropdown() {
|
|
340
|
+
this._setupInputDebounce();
|
|
341
|
+
if (this.filterText() === '') {
|
|
342
|
+
if (this.lazy()) {
|
|
343
|
+
this._lazyLoadSubject$.next('');
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.resetFilteredSuggestions();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
this.isOpen.set(!this.isOpen());
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Função utilizada para otimizar a renderização de listas no Angular, permitindo que o framework
|
|
353
|
+
* identifique cada item de forma única pelo seu `id`. Deve ser utilizada como função de `trackBy`
|
|
354
|
+
* em diretivas como `*ngFor`.
|
|
355
|
+
* @param _ - Índice do item na lista (não utilizado).
|
|
356
|
+
* @param item - Objeto do tipo `SuggestionWrapper<T>` que representa o item atual.
|
|
357
|
+
* @returns O identificador único (`id`) do item.
|
|
358
|
+
*/
|
|
359
|
+
trackById(_, item) {
|
|
360
|
+
return item.id;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Remove uma sugestão da lista de valores selecionados.
|
|
364
|
+
* @param suggestion O item a ser removido da lista de sugestões.
|
|
365
|
+
*/
|
|
366
|
+
removeSuggestion(suggestion) {
|
|
367
|
+
const suggestionValue = this.suggestionValue();
|
|
368
|
+
this.values.set(this.values().filter((value) => {
|
|
369
|
+
if (suggestionValue) {
|
|
370
|
+
return value[suggestionValue] !== suggestion[suggestionValue];
|
|
371
|
+
}
|
|
372
|
+
return value !== suggestion;
|
|
373
|
+
}));
|
|
374
|
+
this._onChange?.(this.values());
|
|
375
|
+
this.unselected.emit(suggestion);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Reinicia a lista de sugestões filtradas.
|
|
379
|
+
* Se o modo lazy estiver ativado, emite um evento para carregar sugestões com uma consulta vazia.
|
|
380
|
+
* Em seguida, redefine as sugestões filtradas para o valor retornado por `internalSuggestion()`.
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
383
|
+
resetFilteredSuggestions() {
|
|
384
|
+
if (this.lazy()) {
|
|
385
|
+
this._lazyLoadSubject$.next('');
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
this.filteredSuggestions.set(this.internalSuggestion());
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Move o foco para a próxima sugestão na lista de sugestões, se houver.
|
|
393
|
+
* @private
|
|
394
|
+
* @param suggestions Lista de sugestões disponíveis.
|
|
395
|
+
*/
|
|
396
|
+
_focusNextSuggestion(suggestions) {
|
|
397
|
+
let nextIndex = this.focusedIndex() + 1;
|
|
398
|
+
if (nextIndex < suggestions.length) {
|
|
399
|
+
this.focusedIndex.set(nextIndex);
|
|
400
|
+
this._scrollToFocusedSuggestion();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Move o foco para a sugestão anterior na lista de sugestões, se houver.
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
_focusPreviousSuggestion() {
|
|
408
|
+
let prevIndex = this.focusedIndex() - 1;
|
|
409
|
+
if (prevIndex >= 0) {
|
|
410
|
+
this.focusedIndex.set(prevIndex);
|
|
411
|
+
this._scrollToFocusedSuggestion();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Rola a lista de sugestões para garantir que a sugestão atualmente focada esteja visível.
|
|
416
|
+
* @private
|
|
417
|
+
*/
|
|
418
|
+
_scrollToFocusedSuggestion() {
|
|
419
|
+
const id = this.filteredSuggestions()[this.focusedIndex()]?.id;
|
|
420
|
+
if (!id) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const el = document.getElementById(id);
|
|
424
|
+
el?.scrollIntoView({ block: 'nearest' });
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Adiciona um identificador único a cada sugestão fornecida.
|
|
428
|
+
* @private
|
|
429
|
+
* @param suggestions - Um array de sugestões do tipo T.
|
|
430
|
+
* @returns Um array de objetos `SuggestionWrapper<T>`, cada um contendo a sugestão original e um identificador único.
|
|
431
|
+
*/
|
|
432
|
+
_addIdToSuggestions(suggestions) {
|
|
433
|
+
const baseId = `${this.componentId}_suggestion`;
|
|
434
|
+
return suggestions?.map((suggestion, index) => {
|
|
435
|
+
return {
|
|
436
|
+
data: suggestion,
|
|
437
|
+
id: `${baseId}_${index++}`,
|
|
438
|
+
};
|
|
439
|
+
}) ?? [];
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Configura o debounce e switchMap para lazy loading, resolvendo race conditions.
|
|
443
|
+
* Este método utiliza switchMap para cancelar requisições anteriores automaticamente
|
|
444
|
+
* quando uma nova busca é iniciada, evitando que resultados antigos sobrescrevam
|
|
445
|
+
* resultados mais recentes.
|
|
446
|
+
* @private
|
|
447
|
+
*/
|
|
448
|
+
_setupLazyLoadDebounce() {
|
|
449
|
+
this._lazyLoadSubject$
|
|
450
|
+
.pipe(debounceTime(this.delay()), distinctUntilChanged(), switchMap((query) => {
|
|
451
|
+
this.loading.set(true);
|
|
452
|
+
// TODO: Manter fechado o dropdownd até a busca estiver concluída
|
|
453
|
+
return new Observable((subscriber) => {
|
|
454
|
+
const timeoutId = setTimeout(() => {
|
|
455
|
+
if (!subscriber.closed) {
|
|
456
|
+
subscriber.error(new Error('Lazy load timeout'));
|
|
457
|
+
}
|
|
458
|
+
}, 30000);
|
|
459
|
+
const responseCallback = (data) => {
|
|
460
|
+
clearTimeout(timeoutId);
|
|
461
|
+
if (!subscriber.closed) {
|
|
462
|
+
subscriber.next(data);
|
|
463
|
+
subscriber.complete();
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
this.lazyLoad.emit({ query, response: responseCallback });
|
|
467
|
+
});
|
|
468
|
+
}), takeUntilDestroyed(this._destroyRef))
|
|
469
|
+
.subscribe({
|
|
470
|
+
next: (data) => {
|
|
471
|
+
this.loading.set(false);
|
|
472
|
+
// TODO: Abrir o dropdown somente aqui
|
|
473
|
+
const wrappedSuggestions = this._addIdToSuggestions(data);
|
|
474
|
+
this.filteredSuggestions.set(wrappedSuggestions);
|
|
475
|
+
this.showEmptyMessage.set(wrappedSuggestions.length === 0);
|
|
476
|
+
},
|
|
477
|
+
error: (error) => {
|
|
478
|
+
this.loading.set(false);
|
|
479
|
+
// TODO: Abrir o dropdown somente aqui
|
|
480
|
+
console.error('Lazy load error:', error);
|
|
481
|
+
this.filteredSuggestions.set([]);
|
|
482
|
+
this.showEmptyMessage.set(true);
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Configura o debounce para o input do autocomplete.
|
|
488
|
+
* Este método utiliza um Subject para escutar as mudanças no valor do input,
|
|
489
|
+
* aplicando um debounce com o tempo definido por `this.delay()`. Quando um novo valor é emitido:
|
|
490
|
+
* - Se houver valor, filtra as sugestões com base no texto digitado e atualiza a lista de sugestões filtradas.
|
|
491
|
+
* - Se não houver valor e o dropdown estiver ativo, reseta as sugestões filtradas.
|
|
492
|
+
* - Caso contrário, limpa a lista de sugestões filtradas.
|
|
493
|
+
* Também atualiza o estado da mensagem de lista vazia e, se a seleção for livre (`forceSelection` for falso),
|
|
494
|
+
* propaga a mudança de valor para o formulário.
|
|
495
|
+
* @private
|
|
496
|
+
*/
|
|
497
|
+
_setupInputDebounce() {
|
|
498
|
+
this._inputSubject$
|
|
499
|
+
.pipe(debounceTime(this.delay()), distinctUntilChanged(), filter((value) => value.length >= this.minLengthToSearch() || value.length === 0), takeUntilDestroyed(this._destroyRef))
|
|
500
|
+
.subscribe((value) => {
|
|
501
|
+
if (value) {
|
|
502
|
+
this.filteredSuggestions.set(this.internalSuggestion().filter(({ data }) => {
|
|
503
|
+
const label = String(this.suggestionLabel() ? data[this.suggestionLabel()] : data);
|
|
504
|
+
return label.toLowerCase().includes(value.toLowerCase());
|
|
505
|
+
}));
|
|
506
|
+
this.showEmptyMessage.set(this.filteredSuggestions().length === 0);
|
|
507
|
+
}
|
|
508
|
+
else if (this.dropdown()) {
|
|
509
|
+
this.resetFilteredSuggestions();
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
this.filteredSuggestions.set([]);
|
|
513
|
+
}
|
|
514
|
+
if (!this.forceSelection()) {
|
|
515
|
+
this._onChange?.(value);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Valida as entradas do componente de autocomplete.
|
|
521
|
+
* @private
|
|
522
|
+
* @throws {Error} Se as sugestões forem objetos e `suggestionLabel` não for fornecido.
|
|
523
|
+
*/
|
|
524
|
+
_validateInputs() {
|
|
525
|
+
const suggestions = this.suggestions();
|
|
526
|
+
if (!suggestions.length) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const hasObjectSuggestions = suggestions.some((suggestion) => typeof suggestion === 'object' && suggestion !== null);
|
|
530
|
+
if (hasObjectSuggestions) {
|
|
531
|
+
if (!this.suggestionLabel()) {
|
|
532
|
+
throw new Error('The suggestionLabel input must be provided when suggestions are objects.');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
537
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AutocompleteComponent, isStandalone: true, selector: "s-autocomplete", inputs: { suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, suggestionValue: { classPropertyName: "suggestionValue", publicName: "suggestionValue", isSignal: true, isRequired: false, transformFunction: null }, suggestionLabel: { classPropertyName: "suggestionLabel", publicName: "suggestionLabel", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, virtualScrollItemSize: { classPropertyName: "virtualScrollItemSize", publicName: "virtualScrollItemSize", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, dropdown: { classPropertyName: "dropdown", publicName: "dropdown", isSignal: true, isRequired: false, transformFunction: null }, forceSelection: { classPropertyName: "forceSelection", publicName: "forceSelection", isSignal: true, isRequired: false, transformFunction: null }, checkmark: { classPropertyName: "checkmark", publicName: "checkmark", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, minLengthToSearch: { classPropertyName: "minLengthToSearch", publicName: "minLengthToSearch", isSignal: true, isRequired: false, transformFunction: null }, inputClass: { classPropertyName: "inputClass", publicName: "inputClass", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange", disabled: "disabledChange", lazyLoad: "lazyLoad", selected: "selected", unselected: "unselected", blurred: "blurred", cleared: "cleared", keyUp: "keyUp", focused: "focused", completeMethod: "completeMethod" }, host: { attributes: { "role": "combobox" }, listeners: { "window:resize": "onWindowResize()" }, properties: { "attr.aria-expanded": "isOpen()", "attr.aria-haspopup": "\"listbox\"", "attr.aria-owns": "isOpen() ? \"dropdown-container-\" + componentId : null" } }, providers: [
|
|
538
|
+
{
|
|
539
|
+
provide: NG_VALUE_ACCESSOR,
|
|
540
|
+
useExisting: forwardRef(() => AutocompleteComponent),
|
|
541
|
+
multi: true,
|
|
542
|
+
},
|
|
543
|
+
], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #containerDiv\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"flex h-[35px] w-full items-center overflow-hidden rounded-[4px] border focus-within:border-2 focus-within:border-primary hover:bg-grayscale-10\"\n [class]=\"inputClass()\"\n [ngClass]=\"{\n 'pointer-events-none border-grayscale-20 bg-grayscale-5 text-grayscale-30': disabled(),\n 'pointer-events-auto border-grayscale-30 bg-grayscale-0 text-grayscale-90': !disabled(),\n 'border-criticality-red': invalid(),\n }\"\n>\n <div\n class=\"flex flex-grow gap-2 px-2\"\n [ngClass]=\"{\n 'border-r': dropdown(),\n 'border-grayscale-20': dropdown() && disabled(),\n 'border-grayscale-30 focus-within:border-r-2 focus-within:border-r-primary': dropdown() && !disabled(),\n 'bg-grayscale-5': disabled(),\n 'bg-grayscale-0': !disabled(),\n }\"\n >\n @if (multiple() && values().length > 0) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (suggestion of values(); track suggestion) {\n <s-autocomplete-chip\n [suggestion]=\"suggestion\"\n [suggestionLabel]=\"suggestionLabel()\"\n (suggestionRemoved)=\"removeSuggestion($event)\"\n ></s-autocomplete-chip>\n }\n </div>\n }\n <input\n #inputEl\n class=\"min-h-[35px] grow outline-none\"\n type=\"text\"\n [placeholder]=\"placeholder() ?? 'platform.angular_components.type_to_search' | translate\"\n [readOnly]=\"readonly()\"\n [(ngModel)]=\"inputValue\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-autocomplete]=\"multiple() ? 'list' : 'both'\"\n [attr.aria-activedescendant]=\"focusedIndex() >= 0 ? filteredSuggestions()[focusedIndex()].id : null\"\n role=\"searchbox\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($event)\"\n (keyUp)=\"onKeyUp($event)\"\n (blur)=\"onInputBlur($event)\"\n (focus)=\"onInputFocus($event)\"\n />\n\n @if (loading()) {\n <div class=\"flex items-center justify-center\">\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n xmlns=\"http://www.w3.org/2000/svg\"\n class=\"animate-spin\"\n >\n <circle\n cx=\"10\"\n cy=\"10\"\n r=\"9\"\n stroke=\"#428bca\"\n stroke-width=\"2\"\n fill=\"none\"\n stroke-dasharray=\"56.5\"\n stroke-dashoffset=\"10\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n } @else {\n @if (value() && !this.disabled()) {\n <button\n class=\"px-2\"\n (click)=\"clear()\"\n tabindex=\"-1\"\n >\n <i class=\"far fa-times text-grayscale-60\"></i>\n </button>\n }\n }\n </div>\n\n @if (dropdown()) {\n <button\n class=\"flex h-full w-[35px] flex-shrink-0 flex-grow-0 items-center justify-center hover:bg-grayscale-10\"\n (click)=\"toggleDropdown()\"\n >\n <i\n class=\"fas text-grayscale-90\"\n [class.fa-caret-down]=\"!isOpen()\"\n [class.fa-caret-up]=\"isOpen()\"\n ></i>\n </button>\n }\n</div>\n\n@if (showEmptyMessage() || (filteredSuggestions().length > 0 && !readonly())) {\n <ng-template\n #dropdownTemplate\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n (backdropClick)=\"isOpen.set(false)\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (detach)=\"isOpen.set(false)\"\n >\n <div\n [id]=\"'dropdown-container-' + componentId\"\n class=\"mt-0.5 rounded-[4px] bg-grayscale-0 py-1 shadow\"\n [style.width.px]=\"containerDiv.offsetWidth\"\n >\n <ul\n class=\"max-h-52 overflow-auto\"\n role=\"listbox\"\n >\n <!-- Virtual scroll -->\n @if (virtualScroll() && filteredSuggestions().length > 10) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize()\"\n class=\"h-52 overflow-auto\"\n >\n <ng-container\n *cdkVirtualFor=\"\n let suggestion of enrichedFilteredSuggestions();\n let i = $index;\n trackBy: trackById\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"suggestionItemTemplate\"\n [ngTemplateOutletContext]=\"{ suggestion, i }\"\n ></ng-container>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n }\n\n <!-- Normal list -->\n @if (!virtualScroll() || filteredSuggestions().length <= 10) {\n @for (suggestion of enrichedFilteredSuggestions(); track suggestion.id; let i = $index) {\n <ng-container\n [ngTemplateOutlet]=\"suggestionItemTemplate\"\n [ngTemplateOutletContext]=\"{ suggestion, i }\"\n ></ng-container>\n }\n }\n\n @if (showEmptyMessage()) {\n <span class=\"p-3 text-grayscale-60\">{{\n emptyMessage() ?? 'platform.angular_components.no_records_found' | translate\n }}</span>\n }\n </ul>\n </div>\n </ng-template>\n}\n\n<ng-template\n #suggestionItemTemplate\n let-suggestion=\"suggestion\"\n let-i=\"i\"\n>\n <li\n [id]=\"suggestion.id\"\n class=\"rounded-sm flex cursor-pointer items-center gap-2 px-3 py-2 text-grayscale-70\"\n [class.bg-grayscale-10]=\"i === focusedIndex()\"\n [class.bg-grayscale-20]=\"suggestion.isSelected\"\n [attr.aria-selected]=\"suggestion.isSelected\"\n role=\"option\"\n (click)=\"selectSuggestion(suggestion.data)\"\n >\n @if (checkmark() && multiple() && suggestion.isSelected) {\n <i class=\"far fa-check\"></i>\n }\n {{ suggestionLabel() ? suggestion.data[suggestionLabel()!] : suggestion.data }}\n </li>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i3.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i3.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: i4.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i4.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i4.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }, { kind: "component", type: AutocompleteChipComponent, selector: "s-autocomplete-chip", inputs: ["suggestion", "suggestionLabel"], outputs: ["suggestionRemoved"] }] });
|
|
544
|
+
}
|
|
545
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AutocompleteComponent, decorators: [{
|
|
546
|
+
type: Component,
|
|
547
|
+
args: [{ selector: 's-autocomplete', standalone: true, imports: [
|
|
548
|
+
CommonModule,
|
|
549
|
+
FormsModule,
|
|
550
|
+
ReactiveFormsModule,
|
|
551
|
+
OverlayModule,
|
|
552
|
+
ScrollingModule,
|
|
553
|
+
TranslateModule,
|
|
554
|
+
AutocompleteChipComponent,
|
|
555
|
+
], providers: [
|
|
556
|
+
{
|
|
557
|
+
provide: NG_VALUE_ACCESSOR,
|
|
558
|
+
useExisting: forwardRef(() => AutocompleteComponent),
|
|
559
|
+
multi: true,
|
|
560
|
+
},
|
|
561
|
+
], host: {
|
|
562
|
+
role: 'combobox',
|
|
563
|
+
'[attr.aria-expanded]': 'isOpen()',
|
|
564
|
+
'[attr.aria-haspopup]': '"listbox"',
|
|
565
|
+
'[attr.aria-owns]': 'isOpen() ? "dropdown-container-" + componentId : null',
|
|
566
|
+
'(window:resize)': 'onWindowResize()',
|
|
567
|
+
}, template: "<div\n #containerDiv\n cdkOverlayOrigin\n #trigger=\"cdkOverlayOrigin\"\n class=\"flex h-[35px] w-full items-center overflow-hidden rounded-[4px] border focus-within:border-2 focus-within:border-primary hover:bg-grayscale-10\"\n [class]=\"inputClass()\"\n [ngClass]=\"{\n 'pointer-events-none border-grayscale-20 bg-grayscale-5 text-grayscale-30': disabled(),\n 'pointer-events-auto border-grayscale-30 bg-grayscale-0 text-grayscale-90': !disabled(),\n 'border-criticality-red': invalid(),\n }\"\n>\n <div\n class=\"flex flex-grow gap-2 px-2\"\n [ngClass]=\"{\n 'border-r': dropdown(),\n 'border-grayscale-20': dropdown() && disabled(),\n 'border-grayscale-30 focus-within:border-r-2 focus-within:border-r-primary': dropdown() && !disabled(),\n 'bg-grayscale-5': disabled(),\n 'bg-grayscale-0': !disabled(),\n }\"\n >\n @if (multiple() && values().length > 0) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (suggestion of values(); track suggestion) {\n <s-autocomplete-chip\n [suggestion]=\"suggestion\"\n [suggestionLabel]=\"suggestionLabel()\"\n (suggestionRemoved)=\"removeSuggestion($event)\"\n ></s-autocomplete-chip>\n }\n </div>\n }\n <input\n #inputEl\n class=\"min-h-[35px] grow outline-none\"\n type=\"text\"\n [placeholder]=\"placeholder() ?? 'platform.angular_components.type_to_search' | translate\"\n [readOnly]=\"readonly()\"\n [(ngModel)]=\"inputValue\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-autocomplete]=\"multiple() ? 'list' : 'both'\"\n [attr.aria-activedescendant]=\"focusedIndex() >= 0 ? filteredSuggestions()[focusedIndex()].id : null\"\n role=\"searchbox\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($event)\"\n (keyUp)=\"onKeyUp($event)\"\n (blur)=\"onInputBlur($event)\"\n (focus)=\"onInputFocus($event)\"\n />\n\n @if (loading()) {\n <div class=\"flex items-center justify-center\">\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n xmlns=\"http://www.w3.org/2000/svg\"\n class=\"animate-spin\"\n >\n <circle\n cx=\"10\"\n cy=\"10\"\n r=\"9\"\n stroke=\"#428bca\"\n stroke-width=\"2\"\n fill=\"none\"\n stroke-dasharray=\"56.5\"\n stroke-dashoffset=\"10\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n } @else {\n @if (value() && !this.disabled()) {\n <button\n class=\"px-2\"\n (click)=\"clear()\"\n tabindex=\"-1\"\n >\n <i class=\"far fa-times text-grayscale-60\"></i>\n </button>\n }\n }\n </div>\n\n @if (dropdown()) {\n <button\n class=\"flex h-full w-[35px] flex-shrink-0 flex-grow-0 items-center justify-center hover:bg-grayscale-10\"\n (click)=\"toggleDropdown()\"\n >\n <i\n class=\"fas text-grayscale-90\"\n [class.fa-caret-down]=\"!isOpen()\"\n [class.fa-caret-up]=\"isOpen()\"\n ></i>\n </button>\n }\n</div>\n\n@if (showEmptyMessage() || (filteredSuggestions().length > 0 && !readonly())) {\n <ng-template\n #dropdownTemplate\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n (backdropClick)=\"isOpen.set(false)\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (detach)=\"isOpen.set(false)\"\n >\n <div\n [id]=\"'dropdown-container-' + componentId\"\n class=\"mt-0.5 rounded-[4px] bg-grayscale-0 py-1 shadow\"\n [style.width.px]=\"containerDiv.offsetWidth\"\n >\n <ul\n class=\"max-h-52 overflow-auto\"\n role=\"listbox\"\n >\n <!-- Virtual scroll -->\n @if (virtualScroll() && filteredSuggestions().length > 10) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"virtualScrollItemSize()\"\n class=\"h-52 overflow-auto\"\n >\n <ng-container\n *cdkVirtualFor=\"\n let suggestion of enrichedFilteredSuggestions();\n let i = $index;\n trackBy: trackById\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"suggestionItemTemplate\"\n [ngTemplateOutletContext]=\"{ suggestion, i }\"\n ></ng-container>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n }\n\n <!-- Normal list -->\n @if (!virtualScroll() || filteredSuggestions().length <= 10) {\n @for (suggestion of enrichedFilteredSuggestions(); track suggestion.id; let i = $index) {\n <ng-container\n [ngTemplateOutlet]=\"suggestionItemTemplate\"\n [ngTemplateOutletContext]=\"{ suggestion, i }\"\n ></ng-container>\n }\n }\n\n @if (showEmptyMessage()) {\n <span class=\"p-3 text-grayscale-60\">{{\n emptyMessage() ?? 'platform.angular_components.no_records_found' | translate\n }}</span>\n }\n </ul>\n </div>\n </ng-template>\n}\n\n<ng-template\n #suggestionItemTemplate\n let-suggestion=\"suggestion\"\n let-i=\"i\"\n>\n <li\n [id]=\"suggestion.id\"\n class=\"rounded-sm flex cursor-pointer items-center gap-2 px-3 py-2 text-grayscale-70\"\n [class.bg-grayscale-10]=\"i === focusedIndex()\"\n [class.bg-grayscale-20]=\"suggestion.isSelected\"\n [attr.aria-selected]=\"suggestion.isSelected\"\n role=\"option\"\n (click)=\"selectSuggestion(suggestion.data)\"\n >\n @if (checkmark() && multiple() && suggestion.isSelected) {\n <i class=\"far fa-check\"></i>\n }\n {{ suggestionLabel() ? suggestion.data[suggestionLabel()!] : suggestion.data }}\n </li>\n</ng-template>\n" }]
|
|
568
|
+
}], ctorParameters: () => [] });
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Generated bundle index. Do not edit.
|
|
572
|
+
*/
|
|
573
|
+
|
|
574
|
+
export { AutocompleteComponent };
|
|
575
|
+
//# sourceMappingURL=seniorsistemas-angular-components-autocomplete.mjs.map
|