@guajiritos/general-autocomplete 19.0.1 → 19.0.2
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.
@@ -7,7 +7,7 @@ import { Injectable, Pipe, signal, EventEmitter, forwardRef, Input, Output, View
|
|
7
7
|
import * as i6 from '@angular/forms';
|
8
8
|
import { UntypedFormControl, Validators, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
9
9
|
import * as i10 from '@angular/material/autocomplete';
|
10
|
-
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
10
|
+
import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
11
11
|
import * as i8 from '@angular/material/button';
|
12
12
|
import { MatButtonModule } from '@angular/material/button';
|
13
13
|
import * as i4 from '@angular/material/form-field';
|
@@ -20,7 +20,7 @@ import * as i9 from '@angular/material/progress-spinner';
|
|
20
20
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
21
21
|
import * as i2 from '@ngx-translate/core';
|
22
22
|
import { TranslateModule } from '@ngx-translate/core';
|
23
|
-
import { debounceTime, finalize } from 'rxjs';
|
23
|
+
import { Subject, debounceTime, tap, from, switchMap, finalize } from 'rxjs';
|
24
24
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
25
25
|
|
26
26
|
const GENERAL_DISPLAY_OPTIONS = {
|
@@ -46,10 +46,10 @@ class UtilsService {
|
|
46
46
|
return prev ? prev[curr] : null;
|
47
47
|
}, obj || self);
|
48
48
|
}
|
49
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
50
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.
|
49
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.12", ngImport: i0, type: UtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
50
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.12", ngImport: i0, type: UtilsService, providedIn: 'root' }); }
|
51
51
|
}
|
52
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
52
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.12", ngImport: i0, type: UtilsService, decorators: [{
|
53
53
|
type: Injectable,
|
54
54
|
args: [{
|
55
55
|
providedIn: 'root'
|
@@ -78,10 +78,10 @@ class ResolvePropertyPath {
|
|
78
78
|
});
|
79
79
|
return result;
|
80
80
|
}
|
81
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
82
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.
|
81
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.12", ngImport: i0, type: ResolvePropertyPath, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
82
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.12", ngImport: i0, type: ResolvePropertyPath, isStandalone: true, name: "resolvePropertyPath" }); }
|
83
83
|
}
|
84
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
84
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.12", ngImport: i0, type: ResolvePropertyPath, decorators: [{
|
85
85
|
type: Pipe,
|
86
86
|
args: [{
|
87
87
|
name: 'resolvePropertyPath',
|
@@ -95,276 +95,444 @@ class GuajiritosGeneralAutocomplete {
|
|
95
95
|
this._zone = _zone;
|
96
96
|
this._destroyRef = _destroyRef;
|
97
97
|
this.translateService = translateService;
|
98
|
-
|
99
|
-
|
100
|
-
this.
|
98
|
+
// --- Private Properties ---
|
99
|
+
// Indica si la última actualización del input fue por una selección explícita desde el autocomplete
|
100
|
+
this._isOptionSelected = false;
|
101
|
+
this._clearDataSubject = new Subject();
|
102
|
+
this._doFocusSubject = new Subject();
|
103
|
+
this._selectedElement = null; // Almacena el objeto completo seleccionado
|
101
104
|
this._url = null;
|
102
|
-
this.
|
105
|
+
this._limit = 20;
|
106
|
+
this._offset = 0;
|
107
|
+
this._restrictionsFilters = [];
|
108
|
+
this._lastSearchText = ""; // Almacena el texto usado en la última búsqueda de API
|
109
|
+
// --- Public Signals ---
|
103
110
|
this.disabled = signal(false);
|
104
111
|
this.loading = signal(false);
|
105
112
|
this.required = signal(false);
|
106
113
|
this.filteredOptions = signal([]);
|
107
114
|
this.originalOptions = signal([]);
|
108
115
|
this.notAllowedOption = signal(null);
|
109
|
-
this.component = new UntypedFormControl({
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
this.
|
114
|
-
|
115
|
-
this.
|
116
|
-
this.
|
116
|
+
this.component = new UntypedFormControl({
|
117
|
+
value: null,
|
118
|
+
disabled: false,
|
119
|
+
});
|
120
|
+
this.hasMore = signal(true); // Indica si hay más datos para cargar
|
121
|
+
// --- Inputs ---
|
122
|
+
this.floatLabel = "auto";
|
123
|
+
this.color = "accent";
|
124
|
+
this.appearance = "outline";
|
125
|
+
this.subscriptSizing = "dynamic";
|
117
126
|
this.debounceTimeValue = 300;
|
118
|
-
this.label =
|
127
|
+
this.label = "Seleccione";
|
119
128
|
this.showLabel = true;
|
120
|
-
this.placeholder =
|
121
|
-
this.field = [
|
122
|
-
this.filterString =
|
129
|
+
this.placeholder = "Seleccione un elemento";
|
130
|
+
this.field = ["name"];
|
131
|
+
this.filterString = "filter[$and][name][$like]";
|
123
132
|
this.displayOptions = GENERAL_DISPLAY_OPTIONS;
|
124
133
|
this.withoutPaddingBottom = true;
|
125
134
|
this.valueId = false;
|
126
135
|
this.showSuffix = false;
|
127
136
|
this.requireSelection = false;
|
128
|
-
this.suffixIcon =
|
137
|
+
this.suffixIcon = "search";
|
129
138
|
this.removeProperties = [];
|
130
|
-
this.modifyResultFn = () =>
|
131
|
-
|
139
|
+
this.modifyResultFn = (options) => options;
|
140
|
+
// --- Outputs ---
|
141
|
+
this.selectElement = new EventEmitter();
|
132
142
|
this.clearElement = new EventEmitter();
|
133
|
-
|
134
|
-
};
|
143
|
+
// --- ControlValueAccessor methods ---
|
144
|
+
this.propagateChange = (_) => { };
|
135
145
|
/**
|
136
|
-
*
|
137
|
-
*
|
138
|
-
* @param value - Valor a mostrar
|
146
|
+
* Function to display selected elements
|
139
147
|
*/
|
140
148
|
this.displayFn = (value) => {
|
141
|
-
if (value) {
|
142
|
-
|
143
|
-
|
149
|
+
if (!value) {
|
150
|
+
return "";
|
151
|
+
}
|
152
|
+
// Si el valor ya es una cadena, devuélvelo directamente
|
153
|
+
if (typeof value === "string") {
|
154
|
+
return value;
|
155
|
+
}
|
156
|
+
let displayText = "";
|
157
|
+
const options = this.displayOptions || GENERAL_DISPLAY_OPTIONS;
|
158
|
+
options?.firthLabel?.forEach((field) => {
|
159
|
+
if (field?.type === DisplayOptionItemType.PATH) {
|
160
|
+
displayText +=
|
161
|
+
UtilsService.resolvePropertyByPath(value, field?.path) || "";
|
144
162
|
}
|
145
|
-
|
146
|
-
|
147
|
-
this.displayOptions = GENERAL_DISPLAY_OPTIONS;
|
163
|
+
else {
|
164
|
+
displayText += field?.divider || "";
|
148
165
|
}
|
149
|
-
|
150
|
-
|
151
|
-
displayText += UtilsService.resolvePropertyByPath(value, field?.path);
|
152
|
-
}
|
153
|
-
else {
|
154
|
-
displayText += field?.divider;
|
155
|
-
}
|
156
|
-
});
|
157
|
-
return displayText;
|
158
|
-
}
|
166
|
+
});
|
167
|
+
return displayText;
|
159
168
|
};
|
160
169
|
}
|
170
|
+
// --- Setters for Inputs ---
|
161
171
|
set url(data) {
|
162
172
|
if (data) {
|
163
173
|
this._url = data;
|
164
|
-
|
174
|
+
// Suscribirse a los cambios del componente una vez que la URL está disponible
|
175
|
+
this.subscribeToComponentChanges();
|
165
176
|
}
|
166
177
|
}
|
167
|
-
set
|
168
|
-
|
169
|
-
|
170
|
-
this.clearData$
|
171
|
-
.pipe(takeUntilDestroyed(this._destroyRef))
|
172
|
-
.subscribe({
|
173
|
-
next: () => {
|
174
|
-
this.component.setValue(null, { emitEvent: false });
|
175
|
-
this.selectedElement = null;
|
176
|
-
this.SelectElement.emit(null);
|
177
|
-
this.filteredOptions.set([]);
|
178
|
-
this.propagateChange(null);
|
179
|
-
}
|
180
|
-
});
|
178
|
+
set limit(value) {
|
179
|
+
if (value && !isNaN(value)) {
|
180
|
+
this._limit = value;
|
181
181
|
}
|
182
182
|
}
|
183
|
+
set clearData(value) {
|
184
|
+
this._clearDataSubject = value;
|
185
|
+
this._clearDataSubject
|
186
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
187
|
+
.subscribe({
|
188
|
+
next: () => {
|
189
|
+
this.resetAutocompleteState();
|
190
|
+
this.component.setValue(null, { emitEvent: false }); // No emitir para evitar ciclo de valueChanges
|
191
|
+
this.propagateChange(null);
|
192
|
+
this.selectElement.emit(null);
|
193
|
+
},
|
194
|
+
});
|
195
|
+
}
|
183
196
|
set initialValue(value) {
|
184
|
-
|
197
|
+
// Si el valor inicial es el mismo que el valor actual del control, no hacer nada para evitar bucles
|
198
|
+
if (value === this.component.value) {
|
199
|
+
return;
|
200
|
+
}
|
201
|
+
this._selectedElement = value;
|
202
|
+
this._isOptionSelected = !!value; // Marcar como seleccionado si hay un valor inicial
|
203
|
+
// Establecer el valor en el formControl sin emitir el evento
|
204
|
+
// para evitar que subscribeToComponentChanges reaccione a esta carga inicial.
|
205
|
+
this.component.setValue(value, { emitEvent: false });
|
206
|
+
// Actualizar _lastSearchText basado en el valor inicial
|
207
|
+
if (value) {
|
208
|
+
this._lastSearchText = this.displayFn(value);
|
209
|
+
console.log("initialValue set: _lastSearchText =", this._lastSearchText);
|
210
|
+
}
|
211
|
+
else {
|
212
|
+
this._lastSearchText = "";
|
213
|
+
}
|
185
214
|
}
|
186
215
|
/**
|
187
|
-
*
|
188
|
-
*
|
189
|
-
* @param restrictions - Restricciones para la búsqueda
|
216
|
+
* Adds or removes search restrictions
|
190
217
|
*/
|
191
218
|
set restrictions(restrictions) {
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
219
|
+
this._restrictionsFilters = restrictions?.length ? [...restrictions] : [];
|
220
|
+
this.resetAutocompleteState();
|
221
|
+
// Re-ejecutar búsqueda si el input tiene texto y hay restricciones
|
222
|
+
const currentSearchText = this.getAutocompleteSearchText();
|
223
|
+
if (currentSearchText) {
|
224
|
+
this.getAutocompleteByTextHandler(currentSearchText);
|
197
225
|
}
|
198
226
|
}
|
199
227
|
/**
|
200
|
-
*
|
201
|
-
*
|
202
|
-
* @param required - Define si es requerido o no
|
228
|
+
* Adds or removes required validation
|
203
229
|
*/
|
204
230
|
set isRequired(required) {
|
205
231
|
this.required.set(required);
|
232
|
+
const validators = [autocompleteValidator];
|
206
233
|
if (required) {
|
207
|
-
|
208
|
-
}
|
209
|
-
else {
|
210
|
-
this.component.clearValidators();
|
211
|
-
this.component.setValidators([autocompleteValidator]);
|
234
|
+
validators.push(Validators.required);
|
212
235
|
}
|
236
|
+
this.component.setValidators(validators);
|
213
237
|
this.component.updateValueAndValidity();
|
214
238
|
}
|
215
239
|
/**
|
216
|
-
*
|
217
|
-
*
|
218
|
-
* @param focusSubject - Observable para la subscripción al evento Focus
|
240
|
+
* Defines whether to perform a search when the element is in focus
|
219
241
|
*/
|
220
242
|
set doFocus(focusSubject) {
|
221
|
-
this.
|
222
|
-
|
223
|
-
this.
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
243
|
+
this._doFocusSubject = focusSubject;
|
244
|
+
this._doFocusSubject
|
245
|
+
.pipe(debounceTime(this.debounceTimeValue), // Retrasar para evitar llamadas excesivas
|
246
|
+
takeUntilDestroyed(this._destroyRef))
|
247
|
+
.subscribe({
|
248
|
+
next: () => {
|
249
|
+
this._zone.run(() => {
|
250
|
+
setTimeout(() => {
|
251
|
+
this.inputText?.nativeElement?.focus();
|
252
|
+
// Forzar la apertura del panel y una posible carga si es necesario
|
253
|
+
// Simular input para disparar valueChanges, lo que a menudo abre el panel si el texto cambia
|
254
|
+
this.inputText?.nativeElement?.dispatchEvent(new Event("input"));
|
255
|
+
// Abrir explícitamente el panel usando el MatAutocompleteTrigger
|
256
|
+
this.autocompleteTrigger?.openPanel(); // <--- EL CAMBIO ES AQUÍ
|
257
|
+
}, 50); // Un pequeño retraso para asegurar el foco y la apertura
|
258
|
+
});
|
259
|
+
},
|
260
|
+
});
|
235
261
|
}
|
236
262
|
set notAllowedElements(element) {
|
237
263
|
if (element) {
|
238
264
|
this.notAllowedOption.set(element);
|
239
|
-
|
240
|
-
this.filteredOptions.set(this.originalOptions()?.filter((option) => JSON.stringify(option) !== JSON.stringify(this.notAllowedOption())));
|
241
|
-
}
|
265
|
+
this.filterOptionsBasedOnNotAllowed();
|
242
266
|
}
|
243
267
|
}
|
244
268
|
/**
|
245
|
-
*
|
269
|
+
* Resets the autocomplete pagination and options state.
|
270
|
+
* Centralizes the logic to avoid duplication.
|
246
271
|
*/
|
247
|
-
|
272
|
+
resetAutocompleteState() {
|
273
|
+
this._offset = 0;
|
274
|
+
this.originalOptions.set([]);
|
275
|
+
this.filteredOptions.set([]);
|
276
|
+
this.hasMore.set(true);
|
277
|
+
// IMPORTANTE: _selectedElement NO se resetea aquí, solo si se borra explícitamente
|
278
|
+
// _lastSearchText se actualiza en writeValue y optionSelected
|
279
|
+
console.log("Autocomplete state reset (pagination/options).");
|
280
|
+
}
|
281
|
+
/**
|
282
|
+
* Subscription to search input changes
|
283
|
+
*/
|
284
|
+
subscribeToComponentChanges() {
|
248
285
|
this.component.valueChanges
|
249
|
-
.pipe(
|
286
|
+
.pipe(
|
287
|
+
// Utiliza tap para observar el valor antes del debounce y el switchMap
|
288
|
+
tap((value) => {
|
289
|
+
// Si el cambio viene de una selección programática (e.g., this.component.setValue(obj))
|
290
|
+
// O de una selección del usuario en el autocomplete, marcamos que no necesitamos buscar.
|
291
|
+
if (typeof value === "object" &&
|
292
|
+
value !== null &&
|
293
|
+
this._isOptionSelected) {
|
294
|
+
console.log("Value change detected: Object selected, skipping search.");
|
295
|
+
// Resetear la bandera después de procesar
|
296
|
+
this._isOptionSelected = false;
|
297
|
+
return; // Salir del tap, el debounce/switchMap no se ejecutará para este valor.
|
298
|
+
}
|
299
|
+
}), debounceTime(this.debounceTimeValue), takeUntilDestroyed(this._destroyRef))
|
250
300
|
.subscribe({
|
251
|
-
next: () => {
|
252
|
-
|
253
|
-
|
301
|
+
next: (value) => {
|
302
|
+
const currentSearchText = this.getAutocompleteSearchText();
|
303
|
+
// Si el valor actual es un objeto (ya seleccionado), no necesitamos buscar
|
304
|
+
// A menos que el texto en el input haya sido modificado por el usuario
|
305
|
+
if (typeof value === "object" && value !== null) {
|
306
|
+
const displayText = this.displayFn(value);
|
307
|
+
if (currentSearchText === displayText &&
|
308
|
+
this._selectedElement === value) {
|
309
|
+
console.log("Value is selected object and matches display text, skipping search.");
|
310
|
+
return;
|
311
|
+
}
|
312
|
+
}
|
313
|
+
// Si el valor del input es un string (el usuario está escribiendo o borró)
|
314
|
+
// Y el texto actual es diferente al último texto buscado
|
315
|
+
// O si el texto está vacío (para cargar las opciones iniciales si se borra)
|
316
|
+
if ((typeof value === "string" &&
|
317
|
+
currentSearchText !== this._lastSearchText) ||
|
318
|
+
(currentSearchText === "" && this._lastSearchText !== "") // Para cuando el usuario borra todo
|
319
|
+
) {
|
320
|
+
this.resetAutocompleteState(); // Reiniciar paginación y opciones
|
321
|
+
this._lastSearchText = currentSearchText; // Actualizar el último texto buscado
|
322
|
+
console.log("Value changed, initiating new search for:", currentSearchText);
|
323
|
+
this.getAutocompleteByTextHandler(currentSearchText);
|
324
|
+
}
|
325
|
+
else if (currentSearchText === "" && this._lastSearchText === "") {
|
326
|
+
// Si el input está vacío y ya lo estaba, y el componente no tiene valor seleccionado,
|
327
|
+
// podría ser un buen momento para recargar las opciones iniciales si el panel está abierto
|
328
|
+
if (this.matAutocomplete._isOpen &&
|
329
|
+
this.originalOptions().length === 0 &&
|
330
|
+
!this.loading()) {
|
331
|
+
// <--- CORRECCIÓN APLICADA AQUÍ
|
332
|
+
console.log("Input empty, no options, panel open. Loading initial set.");
|
333
|
+
this.getAutocompleteByTextHandler("");
|
334
|
+
}
|
335
|
+
}
|
336
|
+
},
|
337
|
+
});
|
338
|
+
}
|
339
|
+
ngAfterViewInit() {
|
340
|
+
// Suscribirse a la apertura del panel para añadir el listener de scroll
|
341
|
+
this.matAutocomplete.opened
|
342
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
343
|
+
.subscribe(() => {
|
344
|
+
if (this.matAutocomplete?.panel) {
|
345
|
+
// Asegurarse de que el listener sea removido antes de añadirlo para prevenir duplicados
|
346
|
+
this.matAutocomplete.panel.nativeElement.removeEventListener("scroll", this.onScroll.bind(this));
|
347
|
+
this.matAutocomplete.panel.nativeElement.addEventListener("scroll", this.onScroll.bind(this));
|
348
|
+
// Si el panel se abre y no hay opciones cargadas, o si el input está vacío
|
349
|
+
// y no hay una selección activa, cargar las opciones iniciales.
|
350
|
+
const currentSearchText = this.getAutocompleteSearchText();
|
351
|
+
if (this.originalOptions().length === 0 ||
|
352
|
+
(!this._selectedElement && currentSearchText === "")) {
|
353
|
+
console.log("Panel opened, loading initial set of options.");
|
354
|
+
this.getAutocompleteByTextHandler(currentSearchText);
|
355
|
+
}
|
356
|
+
}
|
357
|
+
});
|
358
|
+
// Suscribirse al cierre del panel para remover el listener de scroll y manejar la deselección
|
359
|
+
this.matAutocomplete.closed
|
360
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
361
|
+
.subscribe(() => {
|
362
|
+
if (this.matAutocomplete?.panel) {
|
363
|
+
this.matAutocomplete.panel.nativeElement.removeEventListener("scroll", this.onScroll.bind(this));
|
364
|
+
}
|
365
|
+
const currentInputValue = this.component.value;
|
366
|
+
const currentInputText = this.getAutocompleteSearchText();
|
367
|
+
// Lógica para borrar el input si no hay una selección válida
|
368
|
+
// 1. Si se requiere selección Y el valor actual NO es un objeto (es texto libre)
|
369
|
+
// 2. O si no hay _selectedElement Y el input tiene texto
|
370
|
+
if ((this.requireSelection && typeof currentInputValue === "string") ||
|
371
|
+
(!this._selectedElement && currentInputText !== "")) {
|
372
|
+
// Además, verificar si el texto actual NO coincide con el displayFn del _selectedElement
|
373
|
+
// Esto maneja el caso donde el usuario edita el texto de un elemento seleccionado
|
374
|
+
if (!this._selectedElement ||
|
375
|
+
currentInputText !== this.displayFn(this._selectedElement)) {
|
376
|
+
console.warn("Autocomplete closed: Input cleared due to no valid selection or text mismatch.");
|
377
|
+
this.component.setValue(null);
|
378
|
+
this.propagateChange(null);
|
379
|
+
this.selectElement.emit(null);
|
380
|
+
this._selectedElement = null;
|
381
|
+
this._lastSearchText = "";
|
382
|
+
this.resetAutocompleteState(); // Resetear también las opciones cargadas
|
254
383
|
}
|
255
|
-
this.firstCall = false;
|
256
|
-
this.wasSelected = false;
|
257
384
|
}
|
258
385
|
});
|
259
386
|
}
|
260
387
|
/**
|
261
|
-
*
|
262
|
-
*
|
263
|
-
* @param text -
|
388
|
+
* Searches for elements to display in the autocomplete component.
|
389
|
+
* Now supports pagination.
|
390
|
+
* @param text - Text to search
|
264
391
|
*/
|
265
392
|
getAutocompleteByTextHandler(text) {
|
393
|
+
// Si no hay más datos Y ya cargamos algo (offset > 0) Y el texto de búsqueda no ha cambiado,
|
394
|
+
// y no estamos actualmente cargando, salir.
|
395
|
+
if (!this.hasMore() &&
|
396
|
+
this._offset > 0 &&
|
397
|
+
text === this._lastSearchText && // Verificar que el texto de búsqueda actual sea el mismo
|
398
|
+
!this.loading()) {
|
399
|
+
console.log("No more data for current search text, stopping further loads.");
|
400
|
+
return;
|
401
|
+
}
|
402
|
+
if (this.loading()) {
|
403
|
+
console.log("Already loading, exiting getAutocompleteByTextHandler.");
|
404
|
+
return;
|
405
|
+
}
|
266
406
|
this.loading.set(true);
|
267
|
-
this.
|
268
|
-
|
269
|
-
this.
|
270
|
-
|
271
|
-
this.
|
272
|
-
|
273
|
-
|
274
|
-
resp?.pipe(finalize(() => this.loading.set(false))).subscribe({
|
275
|
-
next: (result) => {
|
276
|
-
this.originalOptions.set(result?.payload?.data ?? result?.data);
|
277
|
-
// Modifica los options con una función que se le pase como parámetro
|
278
|
-
const modifiedOptions = this.modifyResultFn(this.originalOptions());
|
279
|
-
if (modifiedOptions)
|
280
|
-
this.filteredOptions.set(modifiedOptions);
|
281
|
-
if (this.notAllowedOption()) {
|
282
|
-
this.filteredOptions.set(this.originalOptions()?.filter((option) => JSON.stringify(option) !== JSON.stringify(this.notAllowedOption())));
|
283
|
-
}
|
284
|
-
else {
|
285
|
-
this.filteredOptions.set(this.originalOptions());
|
286
|
-
}
|
287
|
-
}
|
288
|
-
});
|
289
|
-
});
|
407
|
+
const currentOffset = this._offset; // Capturar el offset actual antes de la llamada
|
408
|
+
const currentSearchTextForApi = text; // Usar el texto pasado, no el global _lastSearchText
|
409
|
+
if (!this._url && !this.serviceConfig) {
|
410
|
+
console.warn("Autocomplete: 'url' or 'serviceConfig' input is required.");
|
411
|
+
this.loading.set(false);
|
412
|
+
this.hasMore.set(false);
|
413
|
+
return;
|
290
414
|
}
|
291
|
-
|
415
|
+
let apiCall;
|
416
|
+
if (this._url) {
|
417
|
+
apiCall = from(this._autocompleteService.getAutocompleteByText(this._url, currentSearchTextForApi, // Usar el texto de búsqueda actual
|
418
|
+
this.filterString, this._restrictionsFilters, this.removeProperties, this.order, this.bodyRequest, { limit: this._limit, offset: currentOffset })).pipe(switchMap((innerObservable) => innerObservable));
|
419
|
+
}
|
420
|
+
else if (this.serviceConfig) {
|
292
421
|
let body = { ...this.serviceConfig };
|
293
|
-
body[this.serviceConfig.searchProperty] =
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
422
|
+
body[this.serviceConfig.searchProperty] = currentSearchTextForApi; // Usar el texto de búsqueda actual
|
423
|
+
body["limit"] = this._limit;
|
424
|
+
body["offset"] = currentOffset;
|
425
|
+
apiCall = this.serviceConfig.service[this.serviceConfig.method](body);
|
426
|
+
}
|
427
|
+
else {
|
428
|
+
this.loading.set(false);
|
429
|
+
this.hasMore.set(false);
|
430
|
+
return;
|
431
|
+
}
|
432
|
+
// Guardar la posición de scroll antes de la actualización
|
433
|
+
let panelScrollTop = 0;
|
434
|
+
if (this.matAutocomplete?.panel) {
|
435
|
+
panelScrollTop = this.matAutocomplete.panel.nativeElement.scrollTop;
|
436
|
+
}
|
437
|
+
apiCall
|
438
|
+
.pipe(finalize(() => {
|
439
|
+
this.loading.set(false);
|
440
|
+
}), takeUntilDestroyed(this._destroyRef))
|
441
|
+
.subscribe({
|
442
|
+
next: (result) => {
|
443
|
+
const newData = result?.payload?.data ?? result?.data ?? [];
|
444
|
+
console.log("New Data received:", newData.length, "items. Current offset before update:", this._offset);
|
445
|
+
this._offset += newData.length; // Actualizar offset con la cantidad de nuevos ítems
|
446
|
+
if (newData.length < this._limit) {
|
447
|
+
this.hasMore.set(false);
|
448
|
+
console.log("No more data, hasMore = false");
|
313
449
|
}
|
314
|
-
|
450
|
+
else {
|
451
|
+
this.hasMore.set(true);
|
452
|
+
console.log("Potentially more data, hasMore = true");
|
453
|
+
}
|
454
|
+
// Actualizar las opciones originales
|
455
|
+
this.originalOptions.update((currentOptions) => {
|
456
|
+
const updatedOptions = [...currentOptions, ...newData];
|
457
|
+
console.log("Total options after merge:", updatedOptions.length);
|
458
|
+
return updatedOptions;
|
459
|
+
});
|
460
|
+
this.filterOptionsBasedOnNotAllowed();
|
461
|
+
// Restaurar la posición de scroll después de que Angular haya actualizado el DOM
|
462
|
+
// Esto debe hacerse fuera de la zona de Angular o con un pequeño retardo.
|
463
|
+
if (this.matAutocomplete?.panel && panelScrollTop > 0) {
|
464
|
+
this._zone.runOutsideAngular(() => {
|
465
|
+
setTimeout(() => {
|
466
|
+
this.matAutocomplete.panel.nativeElement.scrollTop =
|
467
|
+
panelScrollTop;
|
468
|
+
console.log("Scroll position restored to:", panelScrollTop);
|
469
|
+
}, 0); // Un pequeño retardo para asegurar que el DOM se haya renderizado
|
470
|
+
});
|
471
|
+
}
|
472
|
+
},
|
473
|
+
error: (error) => {
|
474
|
+
console.error("Error fetching autocomplete data:", error);
|
475
|
+
this.loading.set(false);
|
476
|
+
this.hasMore.set(false);
|
477
|
+
},
|
478
|
+
});
|
479
|
+
}
|
480
|
+
filterOptionsBasedOnNotAllowed() {
|
481
|
+
let currentOptions = this.originalOptions();
|
482
|
+
const modifiedOptions = this.modifyResultFn(currentOptions);
|
483
|
+
if (modifiedOptions !== null && modifiedOptions !== undefined) {
|
484
|
+
currentOptions = modifiedOptions;
|
485
|
+
}
|
486
|
+
if (this.notAllowedOption()) {
|
487
|
+
this.filteredOptions.set(currentOptions.filter((option) => JSON.stringify(option) !== JSON.stringify(this.notAllowedOption())));
|
488
|
+
}
|
489
|
+
else {
|
490
|
+
this.filteredOptions.set(currentOptions);
|
315
491
|
}
|
316
492
|
}
|
317
493
|
/**
|
318
|
-
*
|
319
|
-
*
|
320
|
-
* @return {string} Texto de la búsqueda
|
494
|
+
* Defines the text to be used for the search
|
321
495
|
*/
|
322
496
|
getAutocompleteSearchText() {
|
323
|
-
|
324
|
-
if (
|
325
|
-
|
326
|
-
|
327
|
-
if (typeof componentValue === 'object') {
|
328
|
-
let lang = 'es';
|
329
|
-
if (this.field?.[1]) {
|
330
|
-
lang = this.field?.[1];
|
331
|
-
}
|
332
|
-
text = componentValue?.[lang];
|
333
|
-
}
|
334
|
-
}
|
335
|
-
else if (typeof this.component.value === 'string') {
|
336
|
-
text = this.component.value;
|
337
|
-
}
|
497
|
+
const value = this.component.value;
|
498
|
+
if (typeof value === "object" && value !== null) {
|
499
|
+
// Si el valor es un objeto, usa displayFn para obtener el texto
|
500
|
+
return this.displayFn(value);
|
338
501
|
}
|
339
|
-
|
502
|
+
else if (typeof value === "string") {
|
503
|
+
return value;
|
504
|
+
}
|
505
|
+
return "";
|
340
506
|
}
|
341
507
|
registerOnChange(fn) {
|
342
508
|
this.propagateChange = fn;
|
343
509
|
}
|
344
|
-
registerOnTouched() {
|
345
|
-
}
|
510
|
+
registerOnTouched() { }
|
346
511
|
/**
|
347
|
-
* Recibe el valor
|
348
|
-
*
|
349
|
-
* @param value - Valor entrado por FormControl
|
512
|
+
* Recibe el valor desde el FormControl externo.
|
350
513
|
*/
|
351
514
|
writeValue(value) {
|
515
|
+
// Establecer la bandera para indicar que el cambio viene de un setValue programático
|
516
|
+
this._isOptionSelected = typeof value === "object" && value !== null;
|
517
|
+
this.component.setValue(value, { emitEvent: false }); // No emitir evento para evitar bucles.
|
518
|
+
this._selectedElement = value
|
519
|
+
? this.valueId && typeof value === "object"
|
520
|
+
? value?.id
|
521
|
+
: value
|
522
|
+
: null;
|
523
|
+
// Actualizar _lastSearchText basado en el valor que se está escribiendo.
|
352
524
|
if (value) {
|
353
|
-
this.
|
354
|
-
|
355
|
-
this.selectedElement = value?.id;
|
356
|
-
}
|
357
|
-
else {
|
358
|
-
this.selectedElement = value;
|
359
|
-
}
|
525
|
+
this._lastSearchText = this.displayFn(value);
|
526
|
+
console.log("writeValue: Last search text set to:", this._lastSearchText);
|
360
527
|
}
|
361
528
|
else {
|
362
|
-
this.
|
529
|
+
this._lastSearchText = "";
|
530
|
+
console.log("writeValue: Last search text cleared.");
|
363
531
|
}
|
364
532
|
}
|
365
533
|
setDisabledState(isDisabled) {
|
366
534
|
this.disabled.set(isDisabled);
|
367
|
-
if (
|
535
|
+
if (isDisabled) {
|
368
536
|
this.component.disable();
|
369
537
|
}
|
370
538
|
else {
|
@@ -372,58 +540,108 @@ class GuajiritosGeneralAutocomplete {
|
|
372
540
|
}
|
373
541
|
}
|
374
542
|
/**
|
375
|
-
*
|
376
|
-
*
|
377
|
-
* @param trigger
|
543
|
+
* Action on clearing the input value
|
378
544
|
*/
|
379
545
|
clear(trigger) {
|
380
546
|
this.clearElement.emit(this.component.value);
|
381
|
-
this.
|
382
|
-
this.
|
383
|
-
this.SelectElement.emit(null);
|
547
|
+
this.resetAutocompleteState(); // Resetear paginación y opciones
|
548
|
+
this.component.setValue(null); // Esto disparará valueChanges
|
384
549
|
this.propagateChange(null);
|
550
|
+
this.selectElement.emit(null);
|
551
|
+
this._selectedElement = null; // Asegurarse de que el elemento seleccionado se borre
|
552
|
+
this._lastSearchText = ""; // Borrar el último texto de búsqueda
|
385
553
|
this._zone.run(() => {
|
386
554
|
setTimeout(() => {
|
387
|
-
trigger.openPanel();
|
555
|
+
trigger.openPanel(); // Reabrir el panel después de borrar
|
556
|
+
this.getAutocompleteByTextHandler(""); // Cargar opciones iniciales (o todas) después de borrar
|
388
557
|
}, 100);
|
389
558
|
});
|
390
559
|
}
|
391
560
|
/**
|
392
|
-
*
|
561
|
+
* Action on element Focus
|
393
562
|
*/
|
394
563
|
onFocus() {
|
395
|
-
|
396
|
-
|
564
|
+
const currentSearchText = this.getAutocompleteSearchText();
|
565
|
+
// Si no hay opciones cargadas O el input está vacío y no hay un elemento seleccionado
|
566
|
+
if (this.originalOptions().length === 0 ||
|
567
|
+
(currentSearchText === "" && !this._selectedElement)) {
|
568
|
+
this.resetAutocompleteState(); // Reiniciar estado antes de una nueva búsqueda
|
569
|
+
this._lastSearchText = currentSearchText;
|
570
|
+
console.log("On focus, initiating search for:", currentSearchText);
|
571
|
+
this.getAutocompleteByTextHandler(currentSearchText);
|
572
|
+
}
|
573
|
+
else if (this.matAutocomplete._isOpen &&
|
574
|
+
currentSearchText === this._lastSearchText) {
|
575
|
+
// Si el panel ya está abierto y el texto no ha cambiado, no hacer nada para evitar re-cargas innecesarias.
|
576
|
+
// Si el panel no estaba abierto, la apertura de `matAutocomplete.opened` se encargará de la carga.
|
577
|
+
console.log("On focus, panel already open with same text, skipping search.");
|
397
578
|
}
|
398
579
|
}
|
399
580
|
optionSelected($event) {
|
400
|
-
|
401
|
-
|
402
|
-
this.
|
403
|
-
this.
|
404
|
-
|
405
|
-
|
581
|
+
const selectedValue = $event?.option?.value;
|
582
|
+
if (selectedValue) {
|
583
|
+
this._isOptionSelected = true; // Establecer la bandera de selección
|
584
|
+
this._selectedElement = selectedValue;
|
585
|
+
this.selectElement.emit(selectedValue);
|
586
|
+
if (this.valueId && typeof selectedValue === "object") {
|
587
|
+
this.propagateChange(selectedValue?.id);
|
406
588
|
}
|
407
589
|
else {
|
408
|
-
this.propagateChange(
|
590
|
+
this.propagateChange(selectedValue);
|
409
591
|
}
|
592
|
+
// Cuando un ítem es seleccionado, actualizar _lastSearchText con su valor de display.
|
593
|
+
// Esto es crucial para que el valueChanges no vuelva a disparar una búsqueda para este valor.
|
594
|
+
this._lastSearchText = this.displayFn(selectedValue);
|
595
|
+
console.log("Option selected. Last search text set to:", this._lastSearchText);
|
410
596
|
}
|
411
597
|
else {
|
412
|
-
|
598
|
+
// Si se deselecciona o selecciona nulo
|
599
|
+
this.propagateChange(null);
|
600
|
+
this.selectElement.emit(null);
|
601
|
+
this._selectedElement = null;
|
602
|
+
this._lastSearchText = "";
|
603
|
+
this.resetAutocompleteState(); // Resetear estado si se "deselecciona" explícitamente
|
413
604
|
}
|
605
|
+
// Después de la selección, cerrar el panel de forma programática.
|
606
|
+
// Esto asegura que la lógica de `closed` se ejecute correctamente.
|
607
|
+
// this.matAutocomplete.closePanel(); // Descomentar si el panel no se cierra automáticamente
|
414
608
|
}
|
415
|
-
|
416
|
-
|
609
|
+
/**
|
610
|
+
* Handles the scroll event in the autocomplete panel to load more data.
|
611
|
+
*/
|
612
|
+
onScroll(event) {
|
613
|
+
const element = event.target;
|
614
|
+
const scrollPosition = element.scrollTop + element.clientHeight;
|
615
|
+
const scrollHeight = element.scrollHeight;
|
616
|
+
const threshold = 50; // Pixels before the end to load more
|
617
|
+
// console.log("--- Scroll Event ---");
|
618
|
+
// console.log("Scroll Top:", element.scrollTop);
|
619
|
+
// console.log("Client Height:", element.clientHeight);
|
620
|
+
// console.log("Scroll Position (Top + Client):", scrollPosition);
|
621
|
+
// console.log("Scroll Height (Total):", scrollHeight);
|
622
|
+
// console.log("Remaining distance to bottom:", scrollHeight - scrollPosition);
|
623
|
+
// console.log("Is Loading:", this.loading());
|
624
|
+
// console.log("Has More:", this.hasMore());
|
625
|
+
if (scrollHeight - scrollPosition <= threshold &&
|
626
|
+
!this.loading() &&
|
627
|
+
this.hasMore()) {
|
628
|
+
console.log("Loading more data for infinity scroll...");
|
629
|
+
// Usar el texto de búsqueda actual (que puede ser el texto del ítem seleccionado o lo que el usuario escribió)
|
630
|
+
this.getAutocompleteByTextHandler(this.getAutocompleteSearchText());
|
631
|
+
}
|
632
|
+
}
|
633
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.12", ngImport: i0, type: GuajiritosGeneralAutocomplete, deps: [{ token: i1.AutocompleteService }, { token: i0.NgZone }, { token: i0.DestroyRef }, { token: i2.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); }
|
634
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.12", type: GuajiritosGeneralAutocomplete, isStandalone: true, selector: "guajiritos-general-autocomplete", inputs: { floatLabel: "floatLabel", color: "color", appearance: "appearance", subscriptSizing: "subscriptSizing", bodyRequest: "bodyRequest", debounceTimeValue: "debounceTimeValue", detailsTemplate: "detailsTemplate", label: "label", showLabel: "showLabel", placeholder: "placeholder", field: "field", filterString: "filterString", displayOptions: "displayOptions", withoutPaddingBottom: "withoutPaddingBottom", valueId: "valueId", showSuffix: "showSuffix", requireSelection: "requireSelection", order: "order", serviceConfig: "serviceConfig", suffixIcon: "suffixIcon", removeProperties: "removeProperties", modifyResultFn: "modifyResultFn", url: "url", limit: "limit", clearData: "clearData", initialValue: "initialValue", restrictions: "restrictions", isRequired: "isRequired", doFocus: "doFocus", notAllowedElements: "notAllowedElements" }, outputs: { selectElement: "selectElement", clearElement: "clearElement" }, providers: [
|
417
635
|
{
|
418
636
|
provide: NG_VALUE_ACCESSOR,
|
419
637
|
useExisting: forwardRef(() => GuajiritosGeneralAutocomplete),
|
420
|
-
multi: true
|
421
|
-
}
|
422
|
-
], viewQueries: [{ propertyName: "inputText", first: true, predicate: ["inputText"], descendants: true, static: true }], ngImport: i0, template: "<mat-form-field [floatLabel]=\"floatLabel\" class=\"w-100\" [appearance]=\"appearance\" [color]=\"color\"\n [subscriptSizing]=\"subscriptSizing\">\n\n @if (showLabel) {\n <mat-label>{{ label | translate }}</mat-label>\n }\n @if (showSuffix) {\n <mat-icon matSuffix>{{ suffixIcon ?? \"search\" }}</mat-icon>\n }\n <input #inputText #trigger=\"matAutocompleteTrigger\" (focus)=\"onFocus()\" [formControl]=\"component\" type=\"text\"\n [matAutocomplete]=\"
|
638
|
+
multi: true,
|
639
|
+
},
|
640
|
+
], viewQueries: [{ propertyName: "inputText", first: true, predicate: ["inputText"], descendants: true, static: true }, { propertyName: "matAutocomplete", first: true, predicate: ["auto"], descendants: true }, { propertyName: "autocompleteTrigger", first: true, predicate: MatAutocompleteTrigger, descendants: true }], ngImport: i0, template: "<mat-form-field [floatLabel]=\"floatLabel\" class=\"w-100\" [appearance]=\"appearance\" [color]=\"color\"\n [subscriptSizing]=\"subscriptSizing\">\n\n @if (showLabel) {\n <mat-label>{{ label | translate }}</mat-label>\n }\n @if (showSuffix) {\n <mat-icon matSuffix>{{ suffixIcon ?? \"search\" }}</mat-icon>\n }\n <input #inputText #trigger=\"matAutocompleteTrigger\" (focus)=\"onFocus()\" [formControl]=\"component\" type=\"text\"\n [matAutocomplete]=\"auto\" [placeholder]=\"placeholder | translate\" aria-label=\"autocomplete\"\n autocomplete=\"off\" matInput [required]=\"required()\">\n @if (!loading() && component.value) {\n <button (click)=\"clear(trigger)\" [disabled]=\"disabled()\"\n aria-label=\"Clear\" mat-icon-button matSuffix>\n <mat-icon>close</mat-icon>\n </button>\n }\n @if (loading()) {\n <button aria-label=\"search\" mat-icon-button matSuffix>\n <mat-spinner [value]=\"90\" color=\"accent\" diameter=\"25\"></mat-spinner>\n </button>\n }\n <mat-autocomplete #auto=\"matAutocomplete\" [displayWith]=\"displayFn\" [requireSelection]=\"requireSelection\"\n (optionSelected)=\"optionSelected($event)\">\n @for (option of filteredOptions(); track option) {\n <mat-option [value]=\"option\">\n @if (!displayOptions && !detailsTemplate) {\n {{ option?.name | i18n: translateService.currentLang }}\n }\n @if (!detailsTemplate) {\n <div class=\"display-options\">\n <span [ngStyle]=\"{'line-height': displayOptions?.secondLabel ? '16px' : ''}\">\n {{ option | resolvePropertyPath:displayOptions.firthLabel | i18n: translateService.currentLang }}\n </span>\n @if (displayOptions?.secondLabel) {\n <span class=\"mat-caption\">\n {{ option | resolvePropertyPath: displayOptions.secondLabel | i18n: translateService.currentLang }}\n </span>\n }\n </div>\n }\n @if (detailsTemplate) {\n <ng-container *ngTemplateOutlet=\"detailsTemplate;context:{$implicit: option }\"></ng-container>\n }\n </mat-option>\n }\n </mat-autocomplete>\n\n @if (component.invalid) {\n <mat-error>\n {{ 'Este campo es requerido.' | translate }}\n </mat-error>\n }\n</mat-form-field>\n", styles: [".w-100{width:100%}.display-options{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i6.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: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i6.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i8.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i9.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i10.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i10.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i10.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "pipe", type: I18nPipe, name: "i18n" }, { kind: "pipe", type: ResolvePropertyPath, name: "resolvePropertyPath" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
423
641
|
}
|
424
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
642
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.12", ngImport: i0, type: GuajiritosGeneralAutocomplete, decorators: [{
|
425
643
|
type: Component,
|
426
|
-
args: [{ selector:
|
644
|
+
args: [{ selector: "guajiritos-general-autocomplete", changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
427
645
|
CommonModule,
|
428
646
|
MatFormFieldModule,
|
429
647
|
TranslateModule,
|
@@ -434,17 +652,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
|
|
434
652
|
MatProgressSpinnerModule,
|
435
653
|
MatAutocompleteModule,
|
436
654
|
I18nPipe,
|
437
|
-
ResolvePropertyPath
|
655
|
+
ResolvePropertyPath,
|
438
656
|
], providers: [
|
439
657
|
{
|
440
658
|
provide: NG_VALUE_ACCESSOR,
|
441
659
|
useExisting: forwardRef(() => GuajiritosGeneralAutocomplete),
|
442
|
-
multi: true
|
443
|
-
}
|
444
|
-
], template: "<mat-form-field [floatLabel]=\"floatLabel\" class=\"w-100\" [appearance]=\"appearance\" [color]=\"color\"\n [subscriptSizing]=\"subscriptSizing\">\n\n @if (showLabel) {\n <mat-label>{{ label | translate }}</mat-label>\n }\n @if (showSuffix) {\n <mat-icon matSuffix>{{ suffixIcon ?? \"search\" }}</mat-icon>\n }\n <input #inputText #trigger=\"matAutocompleteTrigger\" (focus)=\"onFocus()\" [formControl]=\"component\" type=\"text\"\n [matAutocomplete]=\"
|
660
|
+
multi: true,
|
661
|
+
},
|
662
|
+
], template: "<mat-form-field [floatLabel]=\"floatLabel\" class=\"w-100\" [appearance]=\"appearance\" [color]=\"color\"\n [subscriptSizing]=\"subscriptSizing\">\n\n @if (showLabel) {\n <mat-label>{{ label | translate }}</mat-label>\n }\n @if (showSuffix) {\n <mat-icon matSuffix>{{ suffixIcon ?? \"search\" }}</mat-icon>\n }\n <input #inputText #trigger=\"matAutocompleteTrigger\" (focus)=\"onFocus()\" [formControl]=\"component\" type=\"text\"\n [matAutocomplete]=\"auto\" [placeholder]=\"placeholder | translate\" aria-label=\"autocomplete\"\n autocomplete=\"off\" matInput [required]=\"required()\">\n @if (!loading() && component.value) {\n <button (click)=\"clear(trigger)\" [disabled]=\"disabled()\"\n aria-label=\"Clear\" mat-icon-button matSuffix>\n <mat-icon>close</mat-icon>\n </button>\n }\n @if (loading()) {\n <button aria-label=\"search\" mat-icon-button matSuffix>\n <mat-spinner [value]=\"90\" color=\"accent\" diameter=\"25\"></mat-spinner>\n </button>\n }\n <mat-autocomplete #auto=\"matAutocomplete\" [displayWith]=\"displayFn\" [requireSelection]=\"requireSelection\"\n (optionSelected)=\"optionSelected($event)\">\n @for (option of filteredOptions(); track option) {\n <mat-option [value]=\"option\">\n @if (!displayOptions && !detailsTemplate) {\n {{ option?.name | i18n: translateService.currentLang }}\n }\n @if (!detailsTemplate) {\n <div class=\"display-options\">\n <span [ngStyle]=\"{'line-height': displayOptions?.secondLabel ? '16px' : ''}\">\n {{ option | resolvePropertyPath:displayOptions.firthLabel | i18n: translateService.currentLang }}\n </span>\n @if (displayOptions?.secondLabel) {\n <span class=\"mat-caption\">\n {{ option | resolvePropertyPath: displayOptions.secondLabel | i18n: translateService.currentLang }}\n </span>\n }\n </div>\n }\n @if (detailsTemplate) {\n <ng-container *ngTemplateOutlet=\"detailsTemplate;context:{$implicit: option }\"></ng-container>\n }\n </mat-option>\n }\n </mat-autocomplete>\n\n @if (component.invalid) {\n <mat-error>\n {{ 'Este campo es requerido.' | translate }}\n </mat-error>\n }\n</mat-form-field>\n", styles: [".w-100{width:100%}.display-options{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start}\n"] }]
|
445
663
|
}], ctorParameters: () => [{ type: i1.AutocompleteService }, { type: i0.NgZone }, { type: i0.DestroyRef }, { type: i2.TranslateService }], propDecorators: { inputText: [{
|
446
664
|
type: ViewChild,
|
447
|
-
args: [
|
665
|
+
args: ["inputText", { static: true }]
|
666
|
+
}], matAutocomplete: [{
|
667
|
+
type: ViewChild,
|
668
|
+
args: ["auto"]
|
669
|
+
}], autocompleteTrigger: [{
|
670
|
+
type: ViewChild,
|
671
|
+
args: [MatAutocompleteTrigger]
|
448
672
|
}], floatLabel: [{
|
449
673
|
type: Input
|
450
674
|
}], color: [{
|
@@ -489,12 +713,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
|
|
489
713
|
type: Input
|
490
714
|
}], modifyResultFn: [{
|
491
715
|
type: Input
|
492
|
-
}],
|
716
|
+
}], selectElement: [{
|
493
717
|
type: Output
|
494
718
|
}], clearElement: [{
|
495
719
|
type: Output
|
496
720
|
}], url: [{
|
497
721
|
type: Input
|
722
|
+
}], limit: [{
|
723
|
+
type: Input
|
498
724
|
}], clearData: [{
|
499
725
|
type: Input
|
500
726
|
}], initialValue: [{
|
@@ -509,13 +735,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
|
|
509
735
|
type: Input
|
510
736
|
}] } });
|
511
737
|
/**
|
512
|
-
*
|
738
|
+
* Custom validation for element selection
|
513
739
|
*/
|
514
740
|
function autocompleteValidator(control) {
|
515
|
-
|
516
|
-
|
741
|
+
// Un valor válido es un objeto no nulo que tiene una propiedad 'id'.
|
742
|
+
// Esto asegura que realmente se ha seleccionado un objeto del autocomplete, no solo texto libre.
|
743
|
+
if (typeof control?.value === "object" &&
|
744
|
+
control?.value !== null &&
|
745
|
+
"id" in control.value) {
|
746
|
+
return null;
|
517
747
|
}
|
518
|
-
return
|
748
|
+
return { invalidSelection: true };
|
519
749
|
}
|
520
750
|
|
521
751
|
/*
|