ngx-eiffage-material 0.3.0 → 0.5.0

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 { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
7
7
  import { Subject, EMPTY, merge, fromEvent, startWith, map, catchError, of, skip, debounceTime, distinctUntilChanged } from 'rxjs';
8
8
  import { toSignal, takeUntilDestroyed, rxResource, toObservable } from '@angular/core/rxjs-interop';
9
9
  import * as i3$1 from '@angular/forms';
10
- import { NgControl, FormGroupDirective, NgForm, Validators, FormControl, ReactiveFormsModule, FormArray, FormGroup, FormBuilder, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10
+ import { NgControl, FormGroupDirective, NgForm, Validators, FormControl, ReactiveFormsModule, FormArray, FormGroup, FormBuilder, FormsModule } from '@angular/forms';
11
11
  import * as i2$1 from '@angular/material/chips';
12
12
  import { MatChipGrid, MatChipsModule } from '@angular/material/chips';
13
13
  import * as i2$2 from '@angular/material/form-field';
@@ -22,7 +22,7 @@ import * as i3 from '@angular/material/select';
22
22
  import { MatSelectModule } from '@angular/material/select';
23
23
  import * as i11 from '@angular/common';
24
24
  import { NgTemplateOutlet, CommonModule, DatePipe, isPlatformBrowser, LowerCasePipe } from '@angular/common';
25
- import { MatDialogRef, MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDialog } from '@angular/material/dialog';
25
+ import { MatDialogRef, MatDialogTitle, MAT_DIALOG_DATA, MatDialogContent, MatDialogActions, MatDialogClose, MatDialog } from '@angular/material/dialog';
26
26
  import * as i14 from '@angular/material/checkbox';
27
27
  import { MatCheckboxModule } from '@angular/material/checkbox';
28
28
  import * as i10 from '@angular/material/datepicker';
@@ -471,7 +471,7 @@ const NGX_EIFFAGE_DEFAULT_TEXTS = {
471
471
  remove: name => `Eliminar ${name}`,
472
472
  removed: name => `Archivo ${name} eliminado`,
473
473
  added: count => plural(count, '1 archivo añadido', `${count} archivos añadidos`),
474
- maxFiles: max => `Máximo ${max} archivos permitidos`,
474
+ maxFiles: max => plural(max, 'Solo se permite 1 archivo', `Máximo ${max} archivos permitidos`),
475
475
  typeNotAllowed: type => `Tipo no permitido: ${type}`,
476
476
  tooLarge: maxSize => `Excede el tamaño máximo (${maxSize})`,
477
477
  },
@@ -730,7 +730,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
730
730
  }, template: "<!--\r\n Mientras carga: disabledInteractive mantiene el foco en el bot\u00F3n (el lector de pantalla sigue en \u00E9l\r\n y oye aria-busy). Material NO bloquea el clic de un <button> en ese estado: lo hace blockWhileLoading.\r\n Con disabled, el bot\u00F3n se deshabilita del todo y el navegador ya no emite clics.\r\n-->\r\n<button\r\n class=\"loadingButton\"\r\n [matButton]=\"matButton()\"\r\n [type]=\"type()\"\r\n [disabled]=\"disabled() || isLoading()\"\r\n [disabledInteractive]=\"isLoading() && !disabled()\"\r\n [attr.aria-busy]=\"isLoading() || null\"\r\n (click)=\"blockWhileLoading($event)\"\r\n>\r\n <span class=\"loadingButton__content\" [class.loadingButton__content--hidden]=\"overlaySpinner()\" [class.loadingButton__content--gone]=\"inlineSpinner()\">\r\n {{ label() }}\r\n <ng-content />\r\n </span>\r\n\r\n @if (inlineSpinner()) {\r\n <span class=\"loadingButton__content\">\r\n <mat-spinner class=\"loadingButton__inlineSpinner\" diameter=\"18\" strokeWidth=\"2.5\" aria-hidden=\"true\" />\r\n {{ loadingText() }}\r\n </span>\r\n }\r\n\r\n @if (overlaySpinner()) {\r\n <mat-spinner\r\n class=\"loadingButton__overlaySpinner\"\r\n [diameter]=\"spinnerDiameter()\"\r\n [strokeWidth]=\"spinnerStrokeWidth()\"\r\n [attr.aria-label]=\"resolvedBusyLabel()\"\r\n />\r\n }\r\n</button>\r\n", styles: [":host{display:inline-flex;vertical-align:middle}.loadingButton{width:100%}.loadingButton__content{display:inline-flex;align-items:center;justify-content:center;gap:8px}.loadingButton__content--hidden{visibility:hidden}.loadingButton__content--gone{display:none}.loadingButton__content ::ng-deep .mat-icon{width:18px;height:18px;font-size:18px}.loadingButton__overlaySpinner,.loadingButton__inlineSpinner{--mat-progress-spinner-active-indicator-color: currentColor}.loadingButton__overlaySpinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none}.loadingButton__inlineSpinner{flex:none}.loadingButton[aria-busy=true]{cursor:progress}:host(.ngx-loading-button--loading){--mat-button-filled-disabled-container-color: var(--mat-button-filled-container-color, var(--mat-sys-primary));--mat-button-filled-disabled-label-text-color: var(--mat-button-filled-label-text-color, var(--mat-sys-on-primary));--mat-button-tonal-disabled-container-color: var(--mat-button-tonal-container-color, var(--mat-sys-secondary-container));--mat-button-tonal-disabled-label-text-color: var(--mat-button-tonal-label-text-color, var(--mat-sys-on-secondary-container));--mat-button-protected-disabled-container-color: var(--mat-button-protected-container-color, var(--mat-sys-surface));--mat-button-protected-disabled-container-elevation-shadow: var(--mat-button-protected-container-elevation-shadow, var(--mat-sys-level1));--mat-button-protected-disabled-label-text-color: var(--mat-button-protected-label-text-color, var(--mat-sys-primary));--mat-button-outlined-disabled-outline-color: var(--mat-button-outlined-outline-color, var(--mat-sys-outline));--mat-button-outlined-disabled-label-text-color: var(--mat-button-outlined-label-text-color, var(--mat-sys-primary));--mat-button-text-disabled-label-text-color: var(--mat-button-text-label-text-color, var(--mat-sys-primary))}:host(.ngx-loading-button--secondary){--mat-button-filled-container-color: var(--mat-sys-secondary);--mat-button-filled-label-text-color: var(--mat-sys-on-secondary);--mat-button-filled-state-layer-color: var(--mat-sys-on-secondary);--mat-button-tonal-container-color: var(--mat-sys-secondary-container);--mat-button-tonal-label-text-color: var(--mat-sys-on-secondary-container);--mat-button-tonal-state-layer-color: var(--mat-sys-on-secondary-container);--mat-button-outlined-label-text-color: var(--mat-sys-secondary);--mat-button-outlined-state-layer-color: var(--mat-sys-secondary);--mat-button-outlined-ripple-color: var(--mat-sys-secondary-container);--mat-button-protected-label-text-color: var(--mat-sys-secondary);--mat-button-protected-state-layer-color: var(--mat-sys-secondary);--mat-button-protected-ripple-color: var(--mat-sys-secondary-container);--mat-button-text-label-text-color: var(--mat-sys-secondary);--mat-button-text-state-layer-color: var(--mat-sys-secondary);--mat-button-text-ripple-color: var(--mat-sys-secondary-container)}:host(.ngx-loading-button--tertiary){--mat-button-filled-container-color: var(--mat-sys-tertiary);--mat-button-filled-label-text-color: var(--mat-sys-on-tertiary);--mat-button-filled-state-layer-color: var(--mat-sys-on-tertiary);--mat-button-tonal-container-color: var(--mat-sys-tertiary-container);--mat-button-tonal-label-text-color: var(--mat-sys-on-tertiary-container);--mat-button-tonal-state-layer-color: var(--mat-sys-on-tertiary-container);--mat-button-outlined-label-text-color: var(--mat-sys-tertiary);--mat-button-outlined-state-layer-color: var(--mat-sys-tertiary);--mat-button-outlined-ripple-color: var(--mat-sys-tertiary-container);--mat-button-protected-label-text-color: var(--mat-sys-tertiary);--mat-button-protected-state-layer-color: var(--mat-sys-tertiary);--mat-button-protected-ripple-color: var(--mat-sys-tertiary-container);--mat-button-text-label-text-color: var(--mat-sys-tertiary);--mat-button-text-state-layer-color: var(--mat-sys-tertiary);--mat-button-text-ripple-color: var(--mat-sys-tertiary-container)}:host(.ngx-loading-button--error){--mat-button-filled-container-color: var(--mat-sys-error);--mat-button-filled-label-text-color: var(--mat-sys-on-error);--mat-button-filled-state-layer-color: var(--mat-sys-on-error);--mat-button-tonal-container-color: var(--mat-sys-error-container);--mat-button-tonal-label-text-color: var(--mat-sys-on-error-container);--mat-button-tonal-state-layer-color: var(--mat-sys-on-error-container);--mat-button-outlined-label-text-color: var(--mat-sys-error);--mat-button-outlined-state-layer-color: var(--mat-sys-error);--mat-button-outlined-ripple-color: var(--mat-sys-error-container);--mat-button-protected-label-text-color: var(--mat-sys-error);--mat-button-protected-state-layer-color: var(--mat-sys-error);--mat-button-protected-ripple-color: var(--mat-sys-error-container);--mat-button-text-label-text-color: var(--mat-sys-error);--mat-button-text-state-layer-color: var(--mat-sys-error);--mat-button-text-ripple-color: var(--mat-sys-error-container)}\n"] }]
731
731
  }], propDecorators: { isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], matButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "matButton", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], spinnerDiameter: [{ type: i0.Input, args: [{ isSignal: true, alias: "spinnerDiameter", required: false }] }], spinnerStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "spinnerStrokeWidth", required: false }] }], busyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "busyLabel", required: false }] }] } });
732
732
 
733
- // Utilidades internas de ngx-input-file (no se exportan en el barril).
733
+ // Utilidades internas de ngx-input-file y ngx-file-input (no se exportan en el barril).
734
734
  /** Normaliza `accept` (array o cadena separada por comas) a patrones en minúsculas. `*` o vacío = sin filtro. */
735
735
  function parseAccept(accept) {
736
736
  const parts = Array.isArray(accept) ? accept : String(accept ?? '').split(',');
@@ -786,6 +786,18 @@ function fileIcon(file) {
786
786
  return 'folder_zip';
787
787
  return 'draft';
788
788
  }
789
+ function fileKind(file) {
790
+ const type = file.type.toLowerCase();
791
+ if (type.startsWith('image/'))
792
+ return 'image';
793
+ if (type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf'))
794
+ return 'pdf';
795
+ if (type.startsWith('video/'))
796
+ return 'video';
797
+ if (type.startsWith('audio/'))
798
+ return 'audio';
799
+ return 'generic';
800
+ }
789
801
 
790
802
  /**
791
803
  * Selector de ficheros con forma de campo de formulario: un `mat-form-field` con los ficheros como chips.
@@ -1326,6 +1338,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1326
1338
  }]
1327
1339
  }], ctorParameters: () => [], propDecorators: { component: [{ type: i0.Input, args: [{ isSignal: true, alias: "component", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
1328
1340
 
1341
+ /**
1342
+ * Encabezado de diálogo con botón X para cerrar: el mismo que usa ngx-dialog, para diálogos propios
1343
+ * abiertos con MatDialog. Va en lugar de `<h2 mat-dialog-title>`:
1344
+ *
1345
+ * ```html
1346
+ * <ngx-dialog-header [title]="'Editar proyecto'" icon="edit" />
1347
+ * <!-- o con contenido propio -->
1348
+ * <ngx-dialog-header>{{ (isNew ? 'PROJECTS.create' : 'PROJECTS.edit') | translate }}</ngx-dialog-header>
1349
+ * ```
1350
+ */
1351
+ class NgxDialogHeader {
1352
+ _dialogRef = inject(MatDialogRef, { optional: true });
1353
+ texts = inject(NgxTexts).texts;
1354
+ /** Título. También se puede poner como contenido (`<ngx-dialog-header>…</ngx-dialog-header>`). */
1355
+ title = input(...(ngDevMode ? [undefined, { debugName: "title" }] : []));
1356
+ /** Línea secundaria bajo el título. */
1357
+ subtitle = input(...(ngDevMode ? [undefined, { debugName: "subtitle" }] : []));
1358
+ /** Icono de Material Symbols delante del título. */
1359
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : []));
1360
+ /** Color del icono (p. ej. `var(--mat-sys-error)`). */
1361
+ iconColor = input(...(ngDevMode ? [undefined, { debugName: "iconColor" }] : []));
1362
+ /**
1363
+ * Muestra la X. Sin indicar, se ve mientras el diálogo se pueda cerrar: se oculta si el diálogo tiene
1364
+ * `disableClose` (también si se activa después, p. ej. durante una subida) y vuelve al quitarlo.
1365
+ */
1366
+ showClose = input(undefined, ...(ngDevMode ? [{ debugName: "showClose" }] : []));
1367
+ /** Valor con el que se cierra al pulsar la X. Por defecto `undefined`, igual que con Esc o al pulsar fuera. */
1368
+ closeResult = input(undefined, ...(ngDevMode ? [{ debugName: "closeResult" }] : []));
1369
+ /** disableClose es una propiedad normal del MatDialogRef: se lee en cada comprobación. */
1370
+ _closable = signal(!this._dialogRef?.disableClose, ...(ngDevMode ? [{ debugName: "_closable" }] : []));
1371
+ closeVisible = computed(() => !!this._dialogRef && (this.showClose() ?? this._closable()), ...(ngDevMode ? [{ debugName: "closeVisible" }] : []));
1372
+ ngDoCheck() {
1373
+ this._closable.set(!this._dialogRef?.disableClose);
1374
+ }
1375
+ /** Cierra el diálogo con `closeResult`. */
1376
+ close() {
1377
+ this._dialogRef?.close(this.closeResult());
1378
+ }
1379
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxDialogHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
1380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: NgxDialogHeader, isStandalone: true, selector: "ngx-dialog-header", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconColor: { classPropertyName: "iconColor", publicName: "iconColor", isSignal: true, isRequired: false, transformFunction: null }, showClose: { classPropertyName: "showClose", publicName: "showClose", isSignal: true, isRequired: false, transformFunction: null }, closeResult: { classPropertyName: "closeResult", publicName: "closeResult", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n El h2 (mat-dialog-title) da el nombre accesible al di\u00E1logo, as\u00ED que la X va fuera de \u00E9l,\n posicionada encima, para que no forme parte de ese nombre.\n-->\n<div class=\"ngx-dialog__header\" [class.ngx-dialog__header--closable]=\"closeVisible()\">\n <h2 mat-dialog-title class=\"ngx-dialog__title\">\n @if (icon()) {\n <mat-icon class=\"ngx-dialog__icon\" [style.color]=\"iconColor() || null\" aria-hidden=\"true\">{{ icon() }}</mat-icon>\n }\n <span class=\"ngx-dialog__heading\">\n <span class=\"ngx-dialog__title-text\">{{ title() }}<ng-content /></span>\n @if (subtitle()) {\n <span class=\"ngx-dialog__subtitle\">{{ subtitle() }}</span>\n }\n </span>\n </h2>\n\n @if (closeVisible()) {\n <button\n type=\"button\"\n matIconButton\n class=\"ngx-dialog__close\"\n [attr.aria-label]=\"texts().dialog.close\"\n [matTooltip]=\"texts().dialog.close\"\n (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n }\n</div>\n", styles: [":host{display:block}.ngx-dialog__header{position:relative}.ngx-dialog__title.mat-mdc-dialog-title{display:flex;align-items:center;gap:12px;margin:0;padding:18px 24px 16px}.ngx-dialog__title.mat-mdc-dialog-title:before{display:none}.ngx-dialog__header--closable .ngx-dialog__title.mat-mdc-dialog-title{padding-right:64px}.ngx-dialog__icon{flex:none;width:28px;height:28px;font-size:28px}.ngx-dialog__heading{display:flex;flex-direction:column;gap:2px;min-width:0}.ngx-dialog__title-text{font:var(--mat-sys-title-large);color:var(--mat-sys-on-surface);overflow-wrap:anywhere}.ngx-dialog__subtitle{font:var(--mat-sys-body-medium);color:var(--mat-sys-on-surface-variant)}.ngx-dialog__close{position:absolute;top:50%;right:12px;transform:translateY(-50%);color:var(--mat-sys-on-surface-variant)}\n"], dependencies: [{ kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1381
+ }
1382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxDialogHeader, decorators: [{
1383
+ type: Component,
1384
+ args: [{ selector: 'ngx-dialog-header', imports: [MatDialogTitle, MatButtonModule, MatIconModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n El h2 (mat-dialog-title) da el nombre accesible al di\u00E1logo, as\u00ED que la X va fuera de \u00E9l,\n posicionada encima, para que no forme parte de ese nombre.\n-->\n<div class=\"ngx-dialog__header\" [class.ngx-dialog__header--closable]=\"closeVisible()\">\n <h2 mat-dialog-title class=\"ngx-dialog__title\">\n @if (icon()) {\n <mat-icon class=\"ngx-dialog__icon\" [style.color]=\"iconColor() || null\" aria-hidden=\"true\">{{ icon() }}</mat-icon>\n }\n <span class=\"ngx-dialog__heading\">\n <span class=\"ngx-dialog__title-text\">{{ title() }}<ng-content /></span>\n @if (subtitle()) {\n <span class=\"ngx-dialog__subtitle\">{{ subtitle() }}</span>\n }\n </span>\n </h2>\n\n @if (closeVisible()) {\n <button\n type=\"button\"\n matIconButton\n class=\"ngx-dialog__close\"\n [attr.aria-label]=\"texts().dialog.close\"\n [matTooltip]=\"texts().dialog.close\"\n (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n }\n</div>\n", styles: [":host{display:block}.ngx-dialog__header{position:relative}.ngx-dialog__title.mat-mdc-dialog-title{display:flex;align-items:center;gap:12px;margin:0;padding:18px 24px 16px}.ngx-dialog__title.mat-mdc-dialog-title:before{display:none}.ngx-dialog__header--closable .ngx-dialog__title.mat-mdc-dialog-title{padding-right:64px}.ngx-dialog__icon{flex:none;width:28px;height:28px;font-size:28px}.ngx-dialog__heading{display:flex;flex-direction:column;gap:2px;min-width:0}.ngx-dialog__title-text{font:var(--mat-sys-title-large);color:var(--mat-sys-on-surface);overflow-wrap:anywhere}.ngx-dialog__subtitle{font:var(--mat-sys-body-medium);color:var(--mat-sys-on-surface-variant)}.ngx-dialog__close{position:absolute;top:50%;right:12px;transform:translateY(-50%);color:var(--mat-sys-on-surface-variant)}\n"] }]
1385
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconColor", required: false }] }], showClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClose", required: false }] }], closeResult: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeResult", required: false }] }] } });
1386
+
1329
1387
  /**
1330
1388
  * Diálogo genérico que abre NgxDialogService. El contenido (texto, HTML, componente o plantilla)
1331
1389
  * puede usar NgxDialogContext para devolver un resultado o deshabilitar el botón de confirmar.
@@ -1344,7 +1402,6 @@ class NgxDialog {
1344
1402
  destructive = this._data.destructive ?? false;
1345
1403
  /** La X no tiene sentido en un diálogo con disableClose (está pensado para no poder cerrarse sin decidir). */
1346
1404
  showClose = this._data.showClose ?? !this._dialogRef.disableClose;
1347
- closeLabel = this._texts().dialog.close;
1348
1405
  cancelText = this._data.cancelText ?? this._texts().dialog.cancel;
1349
1406
  confirmText = this._data.confirmText ?? this._texts().dialog.accept;
1350
1407
  // ── NgxDialogContext ──────────────────────────────────────────────────────
@@ -1369,20 +1426,18 @@ class NgxDialog {
1369
1426
  this._dialogRef.close(result === undefined ? true : result);
1370
1427
  }
1371
1428
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1372
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: NgxDialog, isStandalone: true, selector: "ngx-dialog", providers: [{ provide: NgxDialogContext, useExisting: forwardRef(() => NgxDialog) }], ngImport: i0, template: "<!--\r\n Encabezado: el h2 (mat-dialog-title) da el nombre accesible al di\u00E1logo, as\u00ED que la X va fuera de \u00E9l,\r\n posicionada encima, para que no forme parte de ese nombre.\r\n-->\r\n<div class=\"ngx-dialog__header\" [class.ngx-dialog__header--closable]=\"showClose\">\r\n <h2 mat-dialog-title class=\"ngx-dialog__title\">\r\n @if (icon) {\r\n <mat-icon class=\"ngx-dialog__icon\" [style.color]=\"iconColor || null\" aria-hidden=\"true\">{{ icon }}</mat-icon>\r\n }\r\n <span class=\"ngx-dialog__heading\">\r\n <span class=\"ngx-dialog__title-text\">{{ title }}</span>\r\n @if (subtitle) {\r\n <span class=\"ngx-dialog__subtitle\">{{ subtitle }}</span>\r\n }\r\n </span>\r\n </h2>\r\n\r\n @if (showClose) {\r\n <button\r\n type=\"button\"\r\n matIconButton\r\n class=\"ngx-dialog__close\"\r\n [attr.aria-label]=\"closeLabel\"\r\n [matTooltip]=\"closeLabel\"\r\n (click)=\"close()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n</div>\r\n\r\n<mat-dialog-content>\r\n <div class=\"ngx-dialog__content\">\r\n @switch (body.type) {\r\n @case (DialogBodyType.TEXT) {\r\n <p class=\"ngx-dialog__text\">{{ body.content }}</p>\r\n }\r\n @case (DialogBodyType.HTML) {\r\n <div [innerHTML]=\"body.content\"></div>\r\n }\r\n @case (DialogBodyType.COMPONENT) {\r\n <ng-container\r\n ngxDynamicHost\r\n [component]=\"$any(body.content)\"\r\n [data]=\"body.data\"\r\n />\r\n }\r\n @case (DialogBodyType.TEMPLATE) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"$any(body.content)\"\r\n [ngTemplateOutletContext]=\"templateContext\"\r\n />\r\n }\r\n }\r\n </div>\r\n</mat-dialog-content>\r\n\r\n@if (showCancel || showConfirm) {\r\n <mat-dialog-actions align=\"end\" class=\"ngx-dialog__actions\">\r\n @if (showCancel) {\r\n <button type=\"button\" matButton=\"outlined\" (click)=\"onCancelClick()\">\r\n {{ cancelText }}\r\n </button>\r\n }\r\n @if (showConfirm) {\r\n <button\r\n type=\"button\"\r\n matButton=\"filled\"\r\n class=\"ngx-dialog__confirm\"\r\n [class.ngx-dialog__confirm--destructive]=\"destructive\"\r\n [disabled]=\"confirmDisabled()\"\r\n (click)=\"onConfirmClick()\">\r\n {{ confirmText }}\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n}\r\n", styles: [":host{display:block}.ngx-dialog__header{position:relative}.ngx-dialog__title.mat-mdc-dialog-title{display:flex;align-items:center;gap:12px;margin:0;padding:18px 24px 16px}.ngx-dialog__title.mat-mdc-dialog-title:before{display:none}.ngx-dialog__header--closable .ngx-dialog__title.mat-mdc-dialog-title{padding-right:64px}.ngx-dialog__icon{flex:none;width:28px;height:28px;font-size:28px}.ngx-dialog__heading{display:flex;flex-direction:column;gap:2px;min-width:0}.ngx-dialog__title-text{font:var(--mat-sys-title-large);color:var(--mat-sys-on-surface);overflow-wrap:anywhere}.ngx-dialog__subtitle{font:var(--mat-sys-body-medium);color:var(--mat-sys-on-surface-variant)}.ngx-dialog__close{position:absolute;top:50%;right:12px;transform:translateY(-50%);color:var(--mat-sys-on-surface-variant)}.ngx-dialog__content{padding-top:4px}.ngx-dialog__text{margin:0;white-space:pre-line}.ngx-dialog__actions{gap:8px}.ngx-dialog__confirm--destructive{--mat-button-filled-container-color: var(--mat-sys-error);--mat-button-filled-label-text-color: var(--mat-sys-on-error);--mat-button-filled-state-layer-color: var(--mat-sys-on-error)}\n"], dependencies: [{ kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: DynamicHostDirective, selector: "[ngxDynamicHost]", inputs: ["component", "data"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1429
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: NgxDialog, isStandalone: true, selector: "ngx-dialog", providers: [{ provide: NgxDialogContext, useExisting: forwardRef(() => NgxDialog) }], ngImport: i0, template: "<!-- Encabezado compartido con los di\u00E1logos propios (ngx-dialog-header) -->\r\n<ngx-dialog-header\r\n [title]=\"title\"\r\n [subtitle]=\"subtitle\"\r\n [icon]=\"icon\"\r\n [iconColor]=\"iconColor\"\r\n [showClose]=\"showClose\" />\r\n\r\n<mat-dialog-content>\r\n <div class=\"ngx-dialog__content\">\r\n @switch (body.type) {\r\n @case (DialogBodyType.TEXT) {\r\n <p class=\"ngx-dialog__text\">{{ body.content }}</p>\r\n }\r\n @case (DialogBodyType.HTML) {\r\n <div [innerHTML]=\"body.content\"></div>\r\n }\r\n @case (DialogBodyType.COMPONENT) {\r\n <ng-container\r\n ngxDynamicHost\r\n [component]=\"$any(body.content)\"\r\n [data]=\"body.data\"\r\n />\r\n }\r\n @case (DialogBodyType.TEMPLATE) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"$any(body.content)\"\r\n [ngTemplateOutletContext]=\"templateContext\"\r\n />\r\n }\r\n }\r\n </div>\r\n</mat-dialog-content>\r\n\r\n@if (showCancel || showConfirm) {\r\n <mat-dialog-actions align=\"end\" class=\"ngx-dialog__actions\">\r\n @if (showCancel) {\r\n <button type=\"button\" matButton=\"outlined\" (click)=\"onCancelClick()\">\r\n {{ cancelText }}\r\n </button>\r\n }\r\n @if (showConfirm) {\r\n <button\r\n type=\"button\"\r\n matButton=\"filled\"\r\n class=\"ngx-dialog__confirm\"\r\n [class.ngx-dialog__confirm--destructive]=\"destructive\"\r\n [disabled]=\"confirmDisabled()\"\r\n (click)=\"onConfirmClick()\">\r\n {{ confirmText }}\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n}\r\n", styles: [":host{display:block}.ngx-dialog__content{padding-top:4px}.ngx-dialog__text{margin:0;white-space:pre-line}.ngx-dialog__actions{gap:8px}.ngx-dialog__confirm--destructive{--mat-button-filled-container-color: var(--mat-sys-error);--mat-button-filled-label-text-color: var(--mat-sys-on-error);--mat-button-filled-state-layer-color: var(--mat-sys-on-error)}\n"], dependencies: [{ kind: "component", type: NgxDialogHeader, selector: "ngx-dialog-header", inputs: ["title", "subtitle", "icon", "iconColor", "showClose", "closeResult"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: DynamicHostDirective, selector: "[ngxDynamicHost]", inputs: ["component", "data"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1373
1430
  }
1374
1431
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxDialog, decorators: [{
1375
1432
  type: Component,
1376
1433
  args: [{ selector: 'ngx-dialog', imports: [
1377
- MatDialogTitle,
1434
+ NgxDialogHeader,
1378
1435
  MatDialogContent,
1379
1436
  MatDialogActions,
1380
1437
  MatButtonModule,
1381
- MatIconModule,
1382
- MatTooltipModule,
1383
1438
  NgTemplateOutlet,
1384
1439
  DynamicHostDirective,
1385
- ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: NgxDialogContext, useExisting: forwardRef(() => NgxDialog) }], template: "<!--\r\n Encabezado: el h2 (mat-dialog-title) da el nombre accesible al di\u00E1logo, as\u00ED que la X va fuera de \u00E9l,\r\n posicionada encima, para que no forme parte de ese nombre.\r\n-->\r\n<div class=\"ngx-dialog__header\" [class.ngx-dialog__header--closable]=\"showClose\">\r\n <h2 mat-dialog-title class=\"ngx-dialog__title\">\r\n @if (icon) {\r\n <mat-icon class=\"ngx-dialog__icon\" [style.color]=\"iconColor || null\" aria-hidden=\"true\">{{ icon }}</mat-icon>\r\n }\r\n <span class=\"ngx-dialog__heading\">\r\n <span class=\"ngx-dialog__title-text\">{{ title }}</span>\r\n @if (subtitle) {\r\n <span class=\"ngx-dialog__subtitle\">{{ subtitle }}</span>\r\n }\r\n </span>\r\n </h2>\r\n\r\n @if (showClose) {\r\n <button\r\n type=\"button\"\r\n matIconButton\r\n class=\"ngx-dialog__close\"\r\n [attr.aria-label]=\"closeLabel\"\r\n [matTooltip]=\"closeLabel\"\r\n (click)=\"close()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n</div>\r\n\r\n<mat-dialog-content>\r\n <div class=\"ngx-dialog__content\">\r\n @switch (body.type) {\r\n @case (DialogBodyType.TEXT) {\r\n <p class=\"ngx-dialog__text\">{{ body.content }}</p>\r\n }\r\n @case (DialogBodyType.HTML) {\r\n <div [innerHTML]=\"body.content\"></div>\r\n }\r\n @case (DialogBodyType.COMPONENT) {\r\n <ng-container\r\n ngxDynamicHost\r\n [component]=\"$any(body.content)\"\r\n [data]=\"body.data\"\r\n />\r\n }\r\n @case (DialogBodyType.TEMPLATE) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"$any(body.content)\"\r\n [ngTemplateOutletContext]=\"templateContext\"\r\n />\r\n }\r\n }\r\n </div>\r\n</mat-dialog-content>\r\n\r\n@if (showCancel || showConfirm) {\r\n <mat-dialog-actions align=\"end\" class=\"ngx-dialog__actions\">\r\n @if (showCancel) {\r\n <button type=\"button\" matButton=\"outlined\" (click)=\"onCancelClick()\">\r\n {{ cancelText }}\r\n </button>\r\n }\r\n @if (showConfirm) {\r\n <button\r\n type=\"button\"\r\n matButton=\"filled\"\r\n class=\"ngx-dialog__confirm\"\r\n [class.ngx-dialog__confirm--destructive]=\"destructive\"\r\n [disabled]=\"confirmDisabled()\"\r\n (click)=\"onConfirmClick()\">\r\n {{ confirmText }}\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n}\r\n", styles: [":host{display:block}.ngx-dialog__header{position:relative}.ngx-dialog__title.mat-mdc-dialog-title{display:flex;align-items:center;gap:12px;margin:0;padding:18px 24px 16px}.ngx-dialog__title.mat-mdc-dialog-title:before{display:none}.ngx-dialog__header--closable .ngx-dialog__title.mat-mdc-dialog-title{padding-right:64px}.ngx-dialog__icon{flex:none;width:28px;height:28px;font-size:28px}.ngx-dialog__heading{display:flex;flex-direction:column;gap:2px;min-width:0}.ngx-dialog__title-text{font:var(--mat-sys-title-large);color:var(--mat-sys-on-surface);overflow-wrap:anywhere}.ngx-dialog__subtitle{font:var(--mat-sys-body-medium);color:var(--mat-sys-on-surface-variant)}.ngx-dialog__close{position:absolute;top:50%;right:12px;transform:translateY(-50%);color:var(--mat-sys-on-surface-variant)}.ngx-dialog__content{padding-top:4px}.ngx-dialog__text{margin:0;white-space:pre-line}.ngx-dialog__actions{gap:8px}.ngx-dialog__confirm--destructive{--mat-button-filled-container-color: var(--mat-sys-error);--mat-button-filled-label-text-color: var(--mat-sys-on-error);--mat-button-filled-state-layer-color: var(--mat-sys-on-error)}\n"] }]
1440
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: NgxDialogContext, useExisting: forwardRef(() => NgxDialog) }], template: "<!-- Encabezado compartido con los di\u00E1logos propios (ngx-dialog-header) -->\r\n<ngx-dialog-header\r\n [title]=\"title\"\r\n [subtitle]=\"subtitle\"\r\n [icon]=\"icon\"\r\n [iconColor]=\"iconColor\"\r\n [showClose]=\"showClose\" />\r\n\r\n<mat-dialog-content>\r\n <div class=\"ngx-dialog__content\">\r\n @switch (body.type) {\r\n @case (DialogBodyType.TEXT) {\r\n <p class=\"ngx-dialog__text\">{{ body.content }}</p>\r\n }\r\n @case (DialogBodyType.HTML) {\r\n <div [innerHTML]=\"body.content\"></div>\r\n }\r\n @case (DialogBodyType.COMPONENT) {\r\n <ng-container\r\n ngxDynamicHost\r\n [component]=\"$any(body.content)\"\r\n [data]=\"body.data\"\r\n />\r\n }\r\n @case (DialogBodyType.TEMPLATE) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"$any(body.content)\"\r\n [ngTemplateOutletContext]=\"templateContext\"\r\n />\r\n }\r\n }\r\n </div>\r\n</mat-dialog-content>\r\n\r\n@if (showCancel || showConfirm) {\r\n <mat-dialog-actions align=\"end\" class=\"ngx-dialog__actions\">\r\n @if (showCancel) {\r\n <button type=\"button\" matButton=\"outlined\" (click)=\"onCancelClick()\">\r\n {{ cancelText }}\r\n </button>\r\n }\r\n @if (showConfirm) {\r\n <button\r\n type=\"button\"\r\n matButton=\"filled\"\r\n class=\"ngx-dialog__confirm\"\r\n [class.ngx-dialog__confirm--destructive]=\"destructive\"\r\n [disabled]=\"confirmDisabled()\"\r\n (click)=\"onConfirmClick()\">\r\n {{ confirmText }}\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n}\r\n", styles: [":host{display:block}.ngx-dialog__content{padding-top:4px}.ngx-dialog__text{margin:0;white-space:pre-line}.ngx-dialog__actions{gap:8px}.ngx-dialog__confirm--destructive{--mat-button-filled-container-color: var(--mat-sys-error);--mat-button-filled-label-text-color: var(--mat-sys-on-error);--mat-button-filled-state-layer-color: var(--mat-sys-on-error)}\n"] }]
1386
1441
  }] });
1387
1442
 
1388
1443
  /**
@@ -4653,313 +4708,310 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
4653
4708
  }, template: "<div class=\"ngx-skeleton-lines\" [style.gap]=\"isMultiLine() ? lineSpacing() : null\">\n @for (i of lineIndices(); track i; let last = $last) {\n <div\n class=\"ngx-skeleton-bone\"\n [class.ngx-skeleton-shimmer]=\"animation() === 'shimmer'\"\n [class.ngx-skeleton-pulse]=\"animation() === 'pulse'\"\n [style.width]=\"isMultiLine() && last ? lastLineWidth() : computedWidth()\"\n [style.height]=\"height()\"\n [style.border-radius]=\"computedBorderRadius()\"\n ></div>\n }\n</div>\n", styles: [":host{display:block}.ngx-skeleton-lines{display:flex;flex-direction:column}.ngx-skeleton-bone{position:relative;max-width:100%;overflow:hidden;background-color:var(--ngx-skeleton-color, var(--mat-sys-surface-container-highest))}.ngx-skeleton-bone.ngx-skeleton-shimmer:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent 0%,var(--ngx-skeleton-highlight, color-mix(in srgb, var(--mat-sys-surface) 60%, transparent)) 50%,transparent 100%);will-change:transform;animation:ngx-skeleton-shimmer var(--ngx-skeleton-duration, 1.5s) infinite ease-in-out}.ngx-skeleton-bone.ngx-skeleton-pulse{animation:ngx-skeleton-pulse var(--ngx-skeleton-duration, 1.5s) infinite ease-in-out}@keyframes ngx-skeleton-shimmer{to{transform:translate(100%)}}@keyframes ngx-skeleton-pulse{0%,to{opacity:1}50%{opacity:.4}}@media (prefers-reduced-motion: reduce){.ngx-skeleton-bone.ngx-skeleton-shimmer:after{animation:none;opacity:0}.ngx-skeleton-bone.ngx-skeleton-pulse{animation:none}}\n"] }]
4654
4709
  }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], lineSpacing: [{ type: i0.Input, args: [{ isSignal: true, alias: "lineSpacing", required: false }] }], lastLineWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "lastLineWidth", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], animationDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "animationDuration", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], decorative: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorative", required: false }] }] } });
4655
4710
 
4656
- let _uid = 0;
4711
+ let nextId = 0;
4712
+ /**
4713
+ * Zona para soltar o seleccionar ficheros, con lista de ficheros y previsualización de imágenes.
4714
+ * Se usa con `formControlName` / `[formControl]` / `ngModel` (valor: los `File[]` válidos) o con `[(files)]`.
4715
+ */
4657
4716
  class NgxFileInput {
4658
- _platform = inject(PLATFORM_ID);
4659
- _inputId = `ngx-fi-${++_uid}`;
4660
- _nativeInput = viewChild('nativeInput', ...(ngDevMode ? [{ debugName: "_nativeInput" }] : []));
4717
+ // Se registra como value accessor a mano para poder leer el control (obligatorio y error), como ngx-input-file
4718
+ _ngControl = inject(NgControl, { self: true, optional: true });
4719
+ _parentForm = inject(FormGroupDirective, { optional: true }) ?? inject(NgForm, { optional: true });
4720
+ _host = inject(ElementRef);
4721
+ _locale = inject(LOCALE_ID);
4722
+ _isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
4723
+ _nativeInput = viewChild.required('nativeInput');
4724
+ texts = inject(NgxTexts).texts;
4661
4725
  // ── Inputs ──────────────────────────────────────────────────────────────────
4662
- /** Accepted MIME types or extensions (e.g. `'image/*,.pdf'`). */
4726
+ /** Tipos admitidos: extensiones (`.pdf`), MIME (`application/pdf`) o comodines (`image/*`). `'*'` = cualquiera. */
4663
4727
  accept = input('*', ...(ngDevMode ? [{ debugName: "accept" }] : []));
4664
- /** Allow selecting multiple files. */
4728
+ /** Admite varios ficheros: cada selección se añade a la anterior. */
4665
4729
  multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4666
- /** Maximum file size in bytes per file. `null` means no limit. */
4730
+ /** Tamaño máximo por fichero, en bytes. `null` = sin límite. */
4667
4731
  maxSize = input(null, ...(ngDevMode ? [{ debugName: "maxSize" }] : []));
4668
- /** Maximum number of files when `multiple` is true. `null` means no limit. */
4732
+ /** Número máximo de ficheros válidos (con `multiple`). `null` = sin límite. */
4669
4733
  maxFiles = input(null, ...(ngDevMode ? [{ debugName: "maxFiles" }] : []));
4670
- /** Disables the component. */
4671
4734
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4672
- /** Label text shown inside the drop zone. Por defecto, el texto `fileInput.dropLabel` de la librería. */
4735
+ /** Texto principal. Por defecto, el texto `fileInput.dropLabel` de la librería. */
4673
4736
  label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : []));
4674
- texts = inject(NgxTexts).texts;
4675
- resolvedLabel = computed(() => this.label() ?? this.texts().fileInput.dropLabel, ...(ngDevMode ? [{ debugName: "resolvedLabel" }] : []));
4676
- /** Secondary hint text (formats, sizes…). */
4737
+ /** Texto secundario (formatos, tamaños…). */
4677
4738
  hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
4678
- /** Show the file preview list below the drop zone. */
4739
+ /** Mensaje de error. Se ve cuando el control es inválido y está tocado o el formulario se ha enviado. */
4740
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
4741
+ /** Muestra la lista de ficheros (con miniatura de las imágenes). */
4679
4742
  showPreview = input(true, ...(ngDevMode ? [{ debugName: "showPreview", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
4680
- /** Visual variant: `'default'` (large zone), `'compact'` (horizontal), or `'button'`. */
4743
+ /** `'default'` (zona grande), `'compact'` (barra horizontal) o `'button'` (solo un botón). */
4681
4744
  variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : []));
4745
+ /** Ficheros válidos seleccionados. Admite `[(files)]` para usarlo sin formularios. */
4746
+ files = model([], ...(ngDevMode ? [{ debugName: "files" }] : []));
4682
4747
  // ── Outputs ─────────────────────────────────────────────────────────────────
4683
- /** Emits when valid files are added. */
4748
+ /** Ficheros válidos añadidos en una selección o al soltar. */
4684
4749
  filesSelected = output();
4685
- /** Emits when a file is removed from the list. */
4750
+ /** Fichero quitado de la lista (válido o rechazado). */
4686
4751
  fileRemoved = output();
4687
- /** Emits for each file that fails validation. */
4752
+ /** Un fichero rechazado (tipo, tamaño o número). */
4688
4753
  validationError = output();
4689
- // ── Internal state ──────────────────────────────────────────────────────────
4690
- /** All file items (valid + invalid). */
4691
- items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : []));
4692
- /** Whether the user is dragging files over the drop zone. */
4693
- isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
4694
- /** Brief flash after a successful drop. */
4695
- showDropConfirm = signal(false, ...(ngDevMode ? [{ debugName: "showDropConfirm" }] : []));
4696
- /** Announcement text for screen readers. */
4697
- liveAnnouncement = signal('', ...(ngDevMode ? [{ debugName: "liveAnnouncement" }] : []));
4698
- /** Unique id for the hidden file input. */
4699
- inputId = this._inputId;
4700
- /** Disabled state coming from `setDisabledState()` (reactive forms). */
4754
+ // ── Estado ──────────────────────────────────────────────────────────────────
4755
+ id = `ngx-fi-${nextId++}`;
4756
+ _entries = signal([], ...(ngDevMode ? [{ debugName: "_entries" }] : []));
4701
4757
  _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : []));
4702
- /**
4703
- * Effective disabled state: the `disabled` input OR the state pushed by the
4704
- * parent FormControl through `setDisabledState()`.
4705
- */
4758
+ /** Deshabilitado por el input `disabled` o por el control del formulario. */
4706
4759
  isDisabled = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
4707
- /** Derived: valid files only (used as FormControl value). */
4708
- validFiles = computed(() => this.items().filter(i => i.status === 'valid').map(i => i.file), ...(ngDevMode ? [{ debugName: "validFiles" }] : []));
4709
- // ── ControlValueAccessor ────────────────────────────────────────────────────
4760
+ isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
4761
+ showDropConfirm = signal(false, ...(ngDevMode ? [{ debugName: "showDropConfirm" }] : []));
4762
+ announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : []));
4763
+ isRequired = signal(false, ...(ngDevMode ? [{ debugName: "isRequired" }] : []));
4764
+ showError = signal(false, ...(ngDevMode ? [{ debugName: "showError" }] : []));
4765
+ resolvedLabel = computed(() => this.label() ?? this.texts().fileInput.dropLabel, ...(ngDevMode ? [{ debugName: "resolvedLabel" }] : []));
4766
+ acceptAttr = computed(() => parseAccept(this.accept()).join(','), ...(ngDevMode ? [{ debugName: "acceptAttr" }] : []));
4767
+ describedBy = computed(() => [this.hint() ? `${this.id}-hint` : '', this.showError() && this.error() ? `${this.id}-error` : ''].filter(Boolean).join(' ') || null, ...(ngDevMode ? [{ debugName: "describedBy" }] : []));
4768
+ /** Lo que pinta la lista: icono, color, tamaño legible y mensaje de error ya traducido. */
4769
+ views = computed(() => this._entries().map(({ item, code }) => ({
4770
+ item,
4771
+ icon: fileIcon(item.file),
4772
+ kind: fileKind(item.file),
4773
+ size: formatFileSize(item.size, this._locale),
4774
+ error: code ? this._message(item.file, code) : null,
4775
+ })), ...(ngDevMode ? [{ debugName: "views" }] : []));
4710
4776
  _onChange = () => { };
4711
4777
  _onTouched = () => { };
4712
- writeValue(files) {
4713
- if (!files || files.length === 0) {
4714
- this.items.set([]);
4778
+ _controlSub;
4779
+ _confirmTimer;
4780
+ constructor() {
4781
+ if (this._ngControl)
4782
+ this._ngControl.valueAccessor = this;
4783
+ // [files] o [(files)] desde fuera: reconstruye la lista si no coincide con los válidos actuales
4784
+ effect(() => {
4785
+ const files = this.files();
4786
+ untracked(() => {
4787
+ const valid = this._validFiles();
4788
+ if (files.length !== valid.length || files.some((file, i) => file !== valid[i]))
4789
+ this._resetFrom(files);
4790
+ });
4791
+ });
4792
+ inject(DestroyRef).onDestroy(() => {
4793
+ this._controlSub?.unsubscribe();
4794
+ clearTimeout(this._confirmTimer);
4795
+ this._entries().forEach(({ item }) => this._revokePreview(item));
4796
+ });
4797
+ }
4798
+ ngAfterViewInit() {
4799
+ // formControlName asigna su control después de registerOnChange: aquí ya está
4800
+ if (this._ngControl?.control)
4801
+ this._bindControl(this._ngControl.control);
4802
+ }
4803
+ ngDoCheck() {
4804
+ // resetForm quita el «enviado» sin emitir evento: se recalcula en cada comprobación
4805
+ this._syncFormState();
4806
+ }
4807
+ // ── API pública ─────────────────────────────────────────────────────────────
4808
+ /** Abre el selector de ficheros del sistema (no hace nada si está deshabilitado). */
4809
+ openFilePicker() {
4810
+ if (!this.isDisabled())
4811
+ this._nativeInput().nativeElement.click();
4812
+ }
4813
+ /** Quita un fichero de la lista, válido o rechazado. */
4814
+ removeFile(item) {
4815
+ const entry = this._entries().find(e => e.item.id === item.id);
4816
+ if (!entry)
4715
4817
  return;
4716
- }
4717
- this.items.set(files.map(f => this._toFileItem(f)));
4718
- this._generatePreviews(this.items());
4818
+ this._revokePreview(item);
4819
+ this._entries.update(list => list.filter(e => e !== entry));
4820
+ this.fileRemoved.emit(item);
4821
+ this.announcement.set(this.texts().fileInput.removed(item.name));
4822
+ if (!entry.code)
4823
+ this._commit();
4719
4824
  }
4720
- registerOnChange(fn) {
4721
- this._onChange = fn;
4825
+ // ── Eventos de la plantilla ─────────────────────────────────────────────────
4826
+ onZoneKeydown(event) {
4827
+ if (event.key === 'Enter' || event.key === ' ') {
4828
+ event.preventDefault();
4829
+ this.openFilePicker();
4830
+ }
4722
4831
  }
4723
- registerOnTouched(fn) {
4724
- this._onTouched = fn;
4832
+ onZoneBlur() {
4833
+ // Al abrir el diálogo del sistema la ventana pierde el foco: eso no cuenta como salir del campo
4834
+ if (typeof document !== 'undefined' && document.hasFocus())
4835
+ this._markTouched();
4725
4836
  }
4726
- setDisabledState(isDisabled) {
4727
- this._cvaDisabled.set(isDisabled);
4837
+ onNativeChange(event) {
4838
+ const input = event.target;
4839
+ this._addFiles(Array.from(input.files ?? []));
4840
+ input.value = '';
4728
4841
  }
4729
- // ── Drag & Drop ─────────────────────────────────────────────────────────────
4730
4842
  onDragOver(event) {
4843
+ // Siempre: si no, el navegador abre el fichero al soltarlo
4731
4844
  event.preventDefault();
4732
- event.stopPropagation();
4845
+ if (event.dataTransfer)
4846
+ event.dataTransfer.dropEffect = this.isDisabled() ? 'none' : 'copy';
4733
4847
  if (!this.isDisabled())
4734
4848
  this.isDragOver.set(true);
4735
4849
  }
4736
4850
  onDragLeave(event) {
4737
- event.preventDefault();
4738
- event.stopPropagation();
4739
- this.isDragOver.set(false);
4851
+ // dragleave también salta al pasar por encima de un hijo: solo cuenta si sale del componente
4852
+ if (!this._host.nativeElement.contains(event.relatedTarget))
4853
+ this.isDragOver.set(false);
4740
4854
  }
4741
4855
  onDrop(event) {
4742
4856
  event.preventDefault();
4743
- event.stopPropagation();
4744
4857
  this.isDragOver.set(false);
4745
4858
  if (this.isDisabled())
4746
4859
  return;
4747
- const files = event.dataTransfer?.files;
4748
- if (files && files.length > 0) {
4749
- this._processFiles(Array.from(files));
4750
- this._flashDropConfirm();
4751
- }
4860
+ const files = Array.from(event.dataTransfer?.files ?? []);
4861
+ if (!files.length)
4862
+ return;
4863
+ this._addFiles(files);
4864
+ this._flashDropConfirm();
4752
4865
  }
4753
- // ── File input ──────────────────────────────────────────────────────────────
4754
- onFileInputChange(event) {
4755
- const input = event.target;
4756
- if (input.files && input.files.length > 0) {
4757
- this._processFiles(Array.from(input.files));
4758
- }
4759
- input.value = '';
4866
+ // ── ControlValueAccessor ────────────────────────────────────────────────────
4867
+ writeValue(files) {
4868
+ this.files.set(Array.isArray(files) ? files : []);
4760
4869
  }
4761
- openFilePicker() {
4762
- if (this.isDisabled())
4763
- return;
4764
- this._onTouched();
4765
- this._nativeInput()?.nativeElement.click();
4870
+ registerOnChange(fn) {
4871
+ this._onChange = fn;
4872
+ if (this._ngControl?.control)
4873
+ this._bindControl(this._ngControl.control);
4766
4874
  }
4767
- // ── Remove ──────────────────────────────────────────────────────────────────
4768
- removeFile(item) {
4769
- this.items.update(list => list.filter(i => i.id !== item.id));
4770
- this.fileRemoved.emit(item);
4771
- this._emitChange();
4772
- this.liveAnnouncement.set(this.texts().fileInput.removed(item.name));
4773
- }
4774
- // ── Helpers ─────────────────────────────────────────────────────────────────
4775
- /** Format bytes to human-readable string. */
4776
- formatSize(bytes) {
4777
- if (bytes < 1024)
4778
- return `${bytes} B`;
4779
- if (bytes < 1024 * 1024)
4780
- return `${(bytes / 1024).toFixed(1)} KB`;
4781
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4782
- }
4783
- /** Get a Material icon name based on MIME type. */
4784
- getFileIcon(type) {
4785
- if (type.startsWith('image/'))
4786
- return 'image';
4787
- if (type === 'application/pdf')
4788
- return 'picture_as_pdf';
4789
- if (type.startsWith('video/'))
4790
- return 'videocam';
4791
- if (type.startsWith('audio/'))
4792
- return 'audiotrack';
4793
- if (type.includes('spreadsheet') || type.includes('excel'))
4794
- return 'table_chart';
4795
- if (type.includes('document') || type.includes('word'))
4796
- return 'description';
4797
- if (type.includes('zip') || type.includes('compressed'))
4798
- return 'folder_zip';
4799
- return 'insert_drive_file';
4800
- }
4801
- /** Get an accent color class based on MIME type. */
4802
- getFileColorClass(type) {
4803
- if (type.startsWith('image/'))
4804
- return 'ngx-fi-icon--image';
4805
- if (type === 'application/pdf')
4806
- return 'ngx-fi-icon--pdf';
4807
- if (type.startsWith('video/'))
4808
- return 'ngx-fi-icon--video';
4809
- if (type.startsWith('audio/'))
4810
- return 'ngx-fi-icon--audio';
4811
- return 'ngx-fi-icon--generic';
4812
- }
4813
- // ── Private ─────────────────────────────────────────────────────────────────
4814
- _processFiles(files) {
4815
- this._onTouched();
4816
- let incoming = files;
4817
- const currentCount = this.items().length;
4818
- const max = this.maxFiles();
4819
- // Max files check
4820
- if (this.multiple() && max !== null && currentCount + incoming.length > max) {
4821
- const allowed = Math.max(0, max - currentCount);
4822
- const rejected = incoming.slice(allowed);
4823
- incoming = incoming.slice(0, allowed);
4824
- for (const f of rejected) {
4825
- const err = {
4826
- file: f,
4827
- code: 'MAX_FILES_EXCEEDED',
4828
- message: this.texts().fileInput.maxFiles(max),
4829
- };
4830
- this.validationError.emit(err);
4831
- }
4832
- }
4833
- // Single mode replaces current
4834
- if (!this.multiple()) {
4835
- incoming = incoming.slice(0, 1);
4875
+ registerOnTouched(fn) {
4876
+ this._onTouched = fn;
4877
+ }
4878
+ setDisabledState(isDisabled) {
4879
+ this._cvaDisabled.set(isDisabled);
4880
+ }
4881
+ // ── Privado ─────────────────────────────────────────────────────────────────
4882
+ _validFiles() {
4883
+ return this._entries().filter(e => !e.code).map(e => e.item.file);
4884
+ }
4885
+ _addFiles(incoming) {
4886
+ const patterns = parseAccept(this.accept());
4887
+ const maxSize = this.maxSize();
4888
+ const max = this.multiple() ? this.maxFiles() : 1;
4889
+ const current = this.multiple() ? this._entries() : [];
4890
+ let validCount = current.filter(e => !e.code).length;
4891
+ const added = [];
4892
+ for (const file of this.multiple() ? incoming : incoming.slice(0, 1)) {
4893
+ // Repetidos (ya en la lista o dos veces en la misma tanda): se ignoran
4894
+ if ([...current, ...added].some(e => isSameFile(e.item.file, file)))
4895
+ continue;
4896
+ let code;
4897
+ if (!matchesAccept(file, patterns))
4898
+ code = 'TYPE_NOT_ALLOWED';
4899
+ else if (maxSize != null && file.size > maxSize)
4900
+ code = 'SIZE_EXCEEDED';
4901
+ else if (max != null && validCount >= max)
4902
+ code = 'MAX_FILES_EXCEEDED';
4903
+ else
4904
+ validCount++;
4905
+ added.push({ item: this._toItem(file, code), code });
4836
4906
  }
4837
- const newItems = incoming.map(f => this._validateAndCreate(f));
4838
- if (this.multiple()) {
4839
- this.items.update(list => [...list, ...newItems]);
4907
+ if (!added.length)
4908
+ return;
4909
+ if (!this.multiple())
4910
+ this._entries().forEach(({ item }) => this._revokePreview(item));
4911
+ this._entries.set([...current, ...added]);
4912
+ const rejected = added.filter(e => e.code);
4913
+ for (const { item, code } of rejected)
4914
+ this.validationError.emit({ file: item.file, code: code, message: item.error });
4915
+ const valid = added.filter(e => !e.code).map(e => e.item);
4916
+ if (valid.length) {
4917
+ this.filesSelected.emit(valid);
4918
+ this.announcement.set(this.texts().fileInput.added(valid.length));
4840
4919
  }
4841
4920
  else {
4842
- this.items.set(newItems);
4843
- }
4844
- this._generatePreviews(newItems);
4845
- this._emitChange();
4846
- const validCount = newItems.filter(i => i.status === 'valid').length;
4847
- if (validCount > 0) {
4848
- this.filesSelected.emit(newItems.filter(i => i.status === 'valid'));
4849
- this.liveAnnouncement.set(this.texts().fileInput.added(validCount));
4921
+ this.announcement.set(rejected.map(e => `${e.item.name}: ${e.item.error}`).join('. '));
4850
4922
  }
4923
+ this._commit();
4851
4924
  }
4852
- _validateAndCreate(file) {
4853
- const item = this._toFileItem(file);
4854
- // Type validation
4855
- if (this.accept() !== '*' && !this._isTypeAllowed(file)) {
4856
- item.status = 'error';
4857
- item.error = this.texts().fileInput.typeNotAllowed(file.type || file.name.split('.').pop() || '');
4858
- this.validationError.emit({
4859
- file,
4860
- code: 'TYPE_NOT_ALLOWED',
4861
- message: item.error,
4862
- });
4863
- return item;
4864
- }
4865
- // Size validation
4866
- const maxSz = this.maxSize();
4867
- if (maxSz !== null && file.size > maxSz) {
4868
- item.status = 'error';
4869
- item.error = this.texts().fileInput.tooLarge(this.formatSize(maxSz));
4870
- this.validationError.emit({
4871
- file,
4872
- code: 'SIZE_EXCEEDED',
4873
- message: item.error,
4874
- });
4875
- return item;
4876
- }
4877
- return item;
4925
+ /** Publica los ficheros válidos: model, formulario y estado de tocado. */
4926
+ _commit() {
4927
+ const files = this._validFiles();
4928
+ this.files.set(files);
4929
+ this._onChange(files);
4930
+ this._markTouched();
4878
4931
  }
4879
- _toFileItem(file) {
4880
- return {
4932
+ /** Lista nueva a partir de un valor externo (writeValue o [files]); los rechazados se descartan. */
4933
+ _resetFrom(files) {
4934
+ const previous = this._entries();
4935
+ previous.filter(e => !files.includes(e.item.file)).forEach(({ item }) => this._revokePreview(item));
4936
+ this._entries.set(files.map(file => previous.find(e => e.item.file === file && !e.code) ?? { item: this._toItem(file) }));
4937
+ }
4938
+ _toItem(file, code) {
4939
+ const item = {
4881
4940
  file,
4882
- id: this._generateId(),
4941
+ id: `${this.id}-file-${nextId++}`,
4883
4942
  name: file.name,
4884
4943
  size: file.size,
4885
4944
  type: file.type,
4886
- status: 'valid',
4945
+ status: code ? 'error' : 'valid',
4887
4946
  };
4947
+ if (code)
4948
+ item.error = this._message(file, code);
4949
+ // URL temporal en vez de leer la imagen entera a base64; se libera al quitarla
4950
+ else if (this._isBrowser && file.type.startsWith('image/'))
4951
+ item.preview = URL.createObjectURL(file);
4952
+ return item;
4888
4953
  }
4889
- _isTypeAllowed(file) {
4890
- const accept = this.accept();
4891
- const parts = accept.split(',').map(p => p.trim().toLowerCase());
4892
- const fileType = file.type.toLowerCase();
4893
- const fileName = file.name.toLowerCase();
4894
- return parts.some(pattern => {
4895
- // Extension pattern like .pdf
4896
- if (pattern.startsWith('.')) {
4897
- return fileName.endsWith(pattern);
4898
- }
4899
- // Wildcard MIME like image/*
4900
- if (pattern.endsWith('/*')) {
4901
- const prefix = pattern.slice(0, -2);
4902
- return fileType.startsWith(prefix + '/');
4903
- }
4904
- // Exact MIME
4905
- return fileType === pattern;
4906
- });
4954
+ _revokePreview(item) {
4955
+ if (item.preview?.startsWith('blob:'))
4956
+ URL.revokeObjectURL(item.preview);
4907
4957
  }
4908
- _generatePreviews(items) {
4909
- if (!isPlatformBrowser(this._platform))
4910
- return;
4911
- for (const item of items) {
4912
- if (item.file.type.startsWith('image/') && item.status === 'valid') {
4913
- const reader = new FileReader();
4914
- reader.onload = () => {
4915
- this.items.update(list => list.map(i => i.id === item.id ? { ...i, preview: reader.result } : i));
4916
- };
4917
- reader.readAsDataURL(item.file);
4958
+ _message(file, code) {
4959
+ const texts = this.texts().fileInput;
4960
+ switch (code) {
4961
+ case 'TYPE_NOT_ALLOWED': {
4962
+ // La extensión («.exe») se entiende mejor que el MIME («application/x-msdownload»)
4963
+ const dot = file.name.lastIndexOf('.');
4964
+ return texts.typeNotAllowed(dot > 0 ? file.name.slice(dot).toLowerCase() : file.type || file.name);
4918
4965
  }
4966
+ case 'SIZE_EXCEEDED':
4967
+ return texts.tooLarge(formatFileSize(this.maxSize() ?? 0, this._locale));
4968
+ case 'MAX_FILES_EXCEEDED':
4969
+ return texts.maxFiles(this.maxFiles() ?? 1);
4919
4970
  }
4920
4971
  }
4921
- _emitChange() {
4922
- this._onChange(this.validFiles());
4972
+ _bindControl(control) {
4973
+ this._controlSub?.unsubscribe();
4974
+ const submit$ = this._parentForm?.ngSubmit ?? EMPTY;
4975
+ this._controlSub = merge(control.events, submit$).subscribe(() => this._syncFormState());
4976
+ this._syncFormState();
4977
+ }
4978
+ _syncFormState() {
4979
+ const control = this._ngControl?.control;
4980
+ // Signals con el mismo valor no repintan: se puede llamar en cada ngDoCheck
4981
+ this.isRequired.set(control?.hasValidator(Validators.required) ?? false);
4982
+ this.showError.set(!!control && control.invalid && (control.touched || !!this._parentForm?.submitted));
4983
+ }
4984
+ _markTouched() {
4985
+ this._onTouched();
4986
+ this._syncFormState();
4923
4987
  }
4924
4988
  _flashDropConfirm() {
4925
4989
  this.showDropConfirm.set(true);
4926
- setTimeout(() => this.showDropConfirm.set(false), 450);
4927
- }
4928
- _generateId() {
4929
- if (isPlatformBrowser(this._platform) && typeof crypto !== 'undefined' && crypto.randomUUID) {
4930
- return crypto.randomUUID();
4931
- }
4932
- return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
4990
+ clearTimeout(this._confirmTimer);
4991
+ this._confirmTimer = setTimeout(() => this.showDropConfirm.set(false), 450);
4933
4992
  }
4934
4993
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxFileInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
4935
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: NgxFileInput, isStandalone: true, selector: "ngx-file-input", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesSelected: "filesSelected", fileRemoved: "fileRemoved", validationError: "validationError" }, host: { properties: { "class.ngx-fi--disabled": "isDisabled()", "class.ngx-fi--default": "variant() === 'default'", "class.ngx-fi--compact": "variant() === 'compact'", "class.ngx-fi--button": "variant() === 'button'" }, classAttribute: "ngx-file-input" }, providers: [
4936
- {
4937
- provide: NG_VALUE_ACCESSOR,
4938
- useExisting: forwardRef(() => NgxFileInput),
4939
- multi: true,
4940
- },
4941
- ], viewQueries: [{ propertyName: "_nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Hidden native file input \u2014 always in DOM for accessibility -->\r\n<input\r\n #nativeInput\r\n [id]=\"inputId\"\r\n class=\"ngx-fi-native-input\"\r\n type=\"file\"\r\n [accept]=\"accept()\"\r\n [multiple]=\"multiple()\"\r\n [disabled]=\"isDisabled()\"\r\n (change)=\"onFileInputChange($event)\"\r\n tabindex=\"-1\"\r\n aria-hidden=\"true\"\r\n/>\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n VARIANT: DEFAULT \u2014 large drop zone\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (variant() === 'default') {\r\n <div\r\n class=\"ngx-fi-zone\"\r\n [class.ngx-fi-zone--dragover]=\"isDragOver()\"\r\n [class.ngx-fi-zone--disabled]=\"isDisabled()\"\r\n role=\"region\"\r\n [attr.aria-label]=\"resolvedLabel()\"\r\n [attr.aria-disabled]=\"isDisabled()\"\r\n tabindex=\"0\"\r\n (click)=\"openFilePicker()\"\r\n (keydown.enter)=\"openFilePicker()\"\r\n (keydown.space)=\"openFilePicker(); $event.preventDefault()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n >\r\n <mat-icon class=\"ngx-fi-zone__icon\">cloud_upload</mat-icon>\r\n <span class=\"ngx-fi-zone__label\">{{ resolvedLabel() }}</span>\r\n @if (hint()) {\r\n <span class=\"ngx-fi-zone__hint\">{{ hint() }}</span>\r\n }\r\n <span class=\"ngx-fi-zone__btn-text\">{{ texts().fileInput.browse }}</span>\r\n\r\n <!-- Drop confirmation flash -->\r\n @if (showDropConfirm()) {\r\n <div class=\"ngx-fi-zone__confirm\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n }\r\n </div>\r\n}\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n VARIANT: COMPACT \u2014 horizontal bar\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (variant() === 'compact') {\r\n <div\r\n class=\"ngx-fi-compact\"\r\n [class.ngx-fi-compact--dragover]=\"isDragOver()\"\r\n [class.ngx-fi-compact--disabled]=\"isDisabled()\"\r\n role=\"region\"\r\n [attr.aria-label]=\"resolvedLabel()\"\r\n [attr.aria-disabled]=\"isDisabled()\"\r\n tabindex=\"0\"\r\n (click)=\"openFilePicker()\"\r\n (keydown.enter)=\"openFilePicker()\"\r\n (keydown.space)=\"openFilePicker(); $event.preventDefault()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n >\r\n <mat-icon class=\"ngx-fi-compact__icon\">cloud_upload</mat-icon>\r\n <div class=\"ngx-fi-compact__text\">\r\n <span class=\"ngx-fi-compact__label\">{{ resolvedLabel() }}</span>\r\n @if (hint()) {\r\n <span class=\"ngx-fi-compact__hint\">{{ hint() }}</span>\r\n }\r\n </div>\r\n\r\n @if (showDropConfirm()) {\r\n <div class=\"ngx-fi-zone__confirm\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n }\r\n </div>\r\n}\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n VARIANT: BUTTON \u2014 only a button\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (variant() === 'button') {\r\n <div\r\n class=\"ngx-fi-button-wrap\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n [class.ngx-fi-button-wrap--dragover]=\"isDragOver()\"\r\n >\r\n <button\r\n mat-raised-button\r\n color=\"primary\"\r\n [disabled]=\"isDisabled()\"\r\n (click)=\"openFilePicker()\"\r\n type=\"button\"\r\n >\r\n <mat-icon>attach_file</mat-icon>\r\n {{ resolvedLabel() }}\r\n </button>\r\n </div>\r\n}\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n FILE PREVIEW LIST\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (showPreview() && items().length > 0) {\r\n <div class=\"ngx-fi-list\">\r\n @for (item of items(); track item.id) {\r\n <div\r\n class=\"ngx-fi-item\"\r\n [class.ngx-fi-item--error]=\"item.status === 'error'\"\r\n >\r\n <!-- Thumbnail / Icon -->\r\n <div class=\"ngx-fi-item__thumb\" [class]=\"getFileColorClass(item.type)\">\r\n @if (item.preview) {\r\n <img [src]=\"item.preview\" [alt]=\"item.name\" />\r\n } @else {\r\n <mat-icon>{{ getFileIcon(item.type) }}</mat-icon>\r\n }\r\n </div>\r\n\r\n <!-- Info -->\r\n <div class=\"ngx-fi-item__info\">\r\n <span class=\"ngx-fi-item__name\" [matTooltip]=\"item.name\">{{ item.name }}</span>\r\n <span class=\"ngx-fi-item__meta\">\r\n {{ formatSize(item.size) }}\r\n @if (item.error) {\r\n <span class=\"ngx-fi-item__error\">\r\n <mat-icon>warning</mat-icon> {{ item.error }}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n\r\n <!-- Remove -->\r\n <button\r\n class=\"ngx-fi-item__remove\"\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"removeFile(item); $event.stopPropagation()\"\r\n [matTooltip]=\"texts().fileInput.remove(item.name)\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n\r\n<!-- Screen reader announcements -->\r\n<div class=\"ngx-fi-sr-only\" aria-live=\"polite\">{{ liveAnnouncement() }}</div>\r\n", styles: [".ngx-fi-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.ngx-fi-native-input{position:absolute;width:0;height:0;opacity:0;pointer-events:none}.ngx-fi-zone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:180px;padding:2rem 1.5rem;border:2px dashed var(--mat-sys-outline-variant, #c4c4c4);border-radius:12px;background:var(--mat-sys-surface-container-low, #fafafa);cursor:pointer;transition:border-color .2s,background-color .2s,box-shadow .2s;outline:none;-webkit-user-select:none;user-select:none;overflow:hidden}.ngx-fi-zone:hover:not(.ngx-fi-zone--disabled){border-style:solid;background:var(--mat-sys-surface-container, #f0f0f0)}.ngx-fi-zone:focus-visible:not(.ngx-fi-zone--disabled){box-shadow:0 0 0 3px #5c6bc040;border-color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-zone--dragover{border-color:var(--mat-sys-primary, #5c6bc0);border-style:solid;background:var(--mat-sys-primary-container, rgba(92, 107, 192, .08));box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 16px #5c6bc033;animation:ngx-fi-glow 1s ease-in-out infinite}.ngx-fi-zone--dragover .ngx-fi-zone__icon{transform:scale(1.15);color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-zone--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ngx-fi-zone__icon{font-size:48px;width:48px;height:48px;color:var(--mat-sys-on-surface-variant, #666);transition:transform .2s,color .2s}.ngx-fi-zone__label{font-size:1.05rem;font-weight:500;color:var(--mat-sys-on-surface, #333);text-align:center}.ngx-fi-zone__hint{font-size:.82rem;color:var(--mat-sys-on-surface-variant, #666);text-align:center}.ngx-fi-zone__btn-text{font-size:.82rem;color:var(--mat-sys-primary, #5c6bc0);font-weight:500;margin-top:.25rem}.ngx-fi-zone__confirm{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#43a0471f;pointer-events:none;animation:ngx-fi-confirm-flash .4s ease-out forwards}.ngx-fi-zone__confirm mat-icon{font-size:56px;width:56px;height:56px;color:var(--mat-sys-tertiary, #43a047)}.ngx-fi-compact{position:relative;display:flex;align-items:center;gap:1rem;height:80px;padding:0 1.25rem;border:2px dashed var(--mat-sys-outline-variant, #c4c4c4);border-radius:12px;background:var(--mat-sys-surface-container-low, #fafafa);cursor:pointer;transition:border-color .2s,background-color .2s,box-shadow .2s;outline:none;-webkit-user-select:none;user-select:none;overflow:hidden}.ngx-fi-compact:hover:not(.ngx-fi-compact--disabled){border-style:solid;background:var(--mat-sys-surface-container, #f0f0f0)}.ngx-fi-compact:focus-visible:not(.ngx-fi-compact--disabled){box-shadow:0 0 0 3px #5c6bc040;border-color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-compact--dragover{border-color:var(--mat-sys-primary, #5c6bc0);border-style:solid;background:var(--mat-sys-primary-container, rgba(92, 107, 192, .08));box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 16px #5c6bc033;animation:ngx-fi-glow 1s ease-in-out infinite}.ngx-fi-compact--dragover .ngx-fi-compact__icon{transform:scale(1.1);color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-compact--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ngx-fi-compact__icon{font-size:32px;width:32px;height:32px;color:var(--mat-sys-on-surface-variant, #666);flex-shrink:0;transition:transform .2s,color .2s}.ngx-fi-compact__text{display:flex;flex-direction:column;gap:.15rem;min-width:0}.ngx-fi-compact__label{font-size:.95rem;font-weight:500;color:var(--mat-sys-on-surface, #333)}.ngx-fi-compact__hint{font-size:.78rem;color:var(--mat-sys-on-surface-variant, #666)}.ngx-fi-button-wrap{display:inline-block;position:relative;padding:4px;border-radius:8px;transition:background-color .2s,box-shadow .2s}.ngx-fi-button-wrap--dragover{background:var(--mat-sys-primary-container, rgba(92, 107, 192, .08));box-shadow:0 0 12px #5c6bc040}.ngx-fi-list{display:flex;flex-direction:column;gap:.5rem;margin-top:.75rem}.ngx-fi-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .65rem;border-radius:10px;background:var(--mat-sys-surface-container-low, #fafafa);border:1px solid var(--mat-sys-outline-variant, #e0e0e0);transition:border-color .2s,background-color .2s;animation:ngx-fi-item-enter .2s ease-out both}.ngx-fi-item--error{border-color:var(--mat-sys-error, #d32f2f);background:#d32f2f0a}.ngx-fi-item__thumb{width:42px;height:42px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden}.ngx-fi-item__thumb img{width:100%;height:100%;object-fit:cover;border-radius:8px}.ngx-fi-item__thumb mat-icon{font-size:24px;width:24px;height:24px}.ngx-fi-item__info{flex:1;min-width:0;display:flex;flex-direction:column;gap:.1rem}.ngx-fi-item__name{font-size:.88rem;font-weight:500;color:var(--mat-sys-on-surface, #333);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngx-fi-item__meta{font-size:.75rem;color:var(--mat-sys-on-surface-variant, #666);display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.ngx-fi-item__error{color:var(--mat-sys-error, #d32f2f);display:inline-flex;align-items:center;gap:.2rem;font-weight:500}.ngx-fi-item__error mat-icon{font-size:14px;width:14px;height:14px}.ngx-fi-item__remove{flex-shrink:0}.ngx-fi-icon--image{background:#5c6bc01a}.ngx-fi-icon--image mat-icon{color:#5c6bc0}.ngx-fi-icon--pdf{background:#d32f2f1a}.ngx-fi-icon--pdf mat-icon{color:#d32f2f}.ngx-fi-icon--video{background:#1e88e51a}.ngx-fi-icon--video mat-icon{color:#1e88e5}.ngx-fi-icon--audio{background:#8e24aa1a}.ngx-fi-icon--audio mat-icon{color:#8e24aa}.ngx-fi-icon--generic{background:#0000000f}.ngx-fi-icon--generic mat-icon{color:#757575}@keyframes ngx-fi-item-enter{0%{opacity:0;transform:translateY(8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes ngx-fi-confirm-flash{0%{opacity:1}to{opacity:0}}.ngx-fi-item:nth-child(1){animation-delay:0ms}.ngx-fi-item:nth-child(2){animation-delay:50ms}.ngx-fi-item:nth-child(3){animation-delay:.1s}.ngx-fi-item:nth-child(4){animation-delay:.15s}.ngx-fi-item:nth-child(5){animation-delay:.2s}.ngx-fi-item:nth-child(6){animation-delay:.25s}.ngx-fi-item:nth-child(7){animation-delay:.3s}.ngx-fi-item:nth-child(8){animation-delay:.35s}.ngx-fi-item:nth-child(9){animation-delay:.4s}.ngx-fi-item:nth-child(10){animation-delay:.45s}.ngx-fi-item:nth-child(11){animation-delay:.5s}.ngx-fi-item:nth-child(12){animation-delay:.55s}.ngx-fi-item:nth-child(13){animation-delay:.6s}.ngx-fi-item:nth-child(14){animation-delay:.65s}.ngx-fi-item:nth-child(15){animation-delay:.7s}.ngx-fi-item:nth-child(16){animation-delay:.75s}.ngx-fi-item:nth-child(17){animation-delay:.8s}.ngx-fi-item:nth-child(18){animation-delay:.85s}.ngx-fi-item:nth-child(19){animation-delay:.9s}.ngx-fi-item:nth-child(20){animation-delay:.95s}@keyframes ngx-fi-glow{0%,to{box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 12px #5c6bc02e}50%{box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 22px #5c6bc059}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], encapsulation: i0.ViewEncapsulation.None });
4994
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: NgxFileInput, isStandalone: true, selector: "ngx-file-input", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { files: "filesChange", filesSelected: "filesSelected", fileRemoved: "fileRemoved", validationError: "validationError" }, host: { listeners: { "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "drop": "onDrop($event)" }, properties: { "class.ngx-fi--default": "variant() === 'default'", "class.ngx-fi--compact": "variant() === 'compact'", "class.ngx-fi--button": "variant() === 'button'", "class.ngx-fi--disabled": "isDisabled()", "class.ngx-fi--dragover": "isDragOver()", "class.ngx-fi--invalid": "showError()" }, classAttribute: "ngx-file-input" }, viewQueries: [{ propertyName: "_nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Input nativo oculto: lo abre la zona o el bot\u00F3n -->\n<input\n #nativeInput\n class=\"ngx-fi__native\"\n type=\"file\"\n hidden\n tabindex=\"-1\"\n [accept]=\"acceptAttr()\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n (change)=\"onNativeChange($event)\"\n (cancel)=\"onZoneBlur()\" />\n\n@switch (variant()) {\n @case ('button') {\n <button\n matButton=\"filled\"\n type=\"button\"\n class=\"ngx-fi__button\"\n [disabled]=\"isDisabled()\"\n [attr.aria-describedby]=\"describedBy()\"\n (click)=\"openFilePicker()\"\n (blur)=\"onZoneBlur()\">\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabel() }}@if (isRequired()) {<span class=\"ngx-fi__required\" aria-hidden=\"true\">*</span>}\n </button>\n @if (hint()) {\n <span class=\"ngx-fi__hint\" [id]=\"id + '-hint'\">{{ hint() }}</span>\n }\n }\n\n @default {\n <!-- Zona de arrastre: es un bot\u00F3n (abre el selector) con la etiqueta como nombre accesible -->\n <div\n class=\"ngx-fi__zone\"\n [class.ngx-fi__zone--compact]=\"variant() === 'compact'\"\n role=\"button\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.aria-describedby]=\"describedBy()\"\n (click)=\"openFilePicker()\"\n (keydown)=\"onZoneKeydown($event)\"\n (blur)=\"onZoneBlur()\">\n <mat-icon class=\"ngx-fi__zone-icon\" aria-hidden=\"true\">cloud_upload</mat-icon>\n\n <span class=\"ngx-fi__zone-text\">\n <span class=\"ngx-fi__label\">\n {{ resolvedLabel() }}@if (isRequired()) {<span class=\"ngx-fi__required\" aria-hidden=\"true\">*</span>}\n </span>\n @if (hint()) {\n <span class=\"ngx-fi__hint\" [id]=\"id + '-hint'\">{{ hint() }}</span>\n }\n @if (variant() === 'default') {\n <span class=\"ngx-fi__browse\">{{ texts().fileInput.browse }}</span>\n }\n </span>\n\n @if (showDropConfirm()) {\n <span class=\"ngx-fi__confirm\" aria-hidden=\"true\"><mat-icon>check_circle</mat-icon></span>\n }\n </div>\n }\n}\n\n@if (showError() && error()) {\n <span class=\"ngx-fi__error\" [id]=\"id + '-error'\">{{ error() }}</span>\n}\n\n@if (showPreview() && views().length) {\n <ul class=\"ngx-fi__list\">\n @for (view of views(); track view.item.id) {\n <li class=\"ngx-fi__item\" [class.ngx-fi__item--error]=\"view.error\">\n <span class=\"ngx-fi__thumb\" [attr.data-kind]=\"view.kind\">\n @if (view.item.preview) {\n <img [src]=\"view.item.preview\" alt=\"\" />\n } @else {\n <mat-icon aria-hidden=\"true\">{{ view.icon }}</mat-icon>\n }\n </span>\n\n <span class=\"ngx-fi__info\">\n <span class=\"ngx-fi__name\" [title]=\"view.item.name\">{{ view.item.name }}</span>\n <span class=\"ngx-fi__meta\">\n {{ view.size }}\n @if (view.error) {\n <span class=\"ngx-fi__item-error\"><mat-icon aria-hidden=\"true\">error</mat-icon>{{ view.error }}</span>\n }\n </span>\n </span>\n\n <button\n matIconButton\n type=\"button\"\n class=\"ngx-fi__remove\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"texts().fileInput.remove(view.item.name)\"\n [matTooltip]=\"texts().fileInput.remove(view.item.name)\"\n (click)=\"removeFile(view.item)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n }\n </ul>\n}\n\n<!-- Regi\u00F3n viva fija: a\u00F1adidos, quitados y rechazados -->\n<div class=\"ngx-fi__live\" aria-live=\"polite\">{{ announcement() }}</div>\n", styles: [":host{--ngx-file-input-image-color: var(--mat-sys-primary);--ngx-file-input-pdf-color: var(--mat-sys-error);--ngx-file-input-video-color: var(--mat-sys-tertiary);--ngx-file-input-audio-color: var(--mat-sys-secondary);--ngx-file-input-generic-color: var(--mat-sys-on-surface-variant);display:flex;flex-direction:column;gap:8px}:host(.ngx-fi--button){display:inline-flex;align-items:flex-start}.ngx-fi__zone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;min-height:180px;padding:32px 24px;border:2px dashed var(--mat-sys-outline-variant);border-radius:var(--mat-sys-corner-large, 16px);background:var(--mat-sys-surface-container-low);color:var(--mat-sys-on-surface);text-align:center;cursor:pointer;-webkit-user-select:none;user-select:none;overflow:hidden;transition:border-color .15s,background-color .15s}.ngx-fi__zone:hover{border-color:var(--mat-sys-outline);background:var(--mat-sys-surface-container)}.ngx-fi__zone:focus-visible{outline:2px solid var(--mat-sys-primary);outline-offset:2px}.ngx-fi__zone--compact{flex-direction:row;justify-content:flex-start;gap:16px;min-height:0;padding:16px 20px;border-radius:var(--mat-sys-corner-medium, 12px);text-align:start}.ngx-fi__zone--compact .ngx-fi__zone-icon{width:32px;height:32px;font-size:32px}.ngx-fi__zone--compact .ngx-fi__zone-text{align-items:flex-start}.ngx-fi__zone-icon{flex:none;width:48px;height:48px;font-size:48px;color:var(--mat-sys-on-surface-variant);transition:transform .15s,color .15s}.ngx-fi__zone-text{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:0}.ngx-fi__label{font:var(--mat-sys-title-medium)}.ngx-fi__required{margin-inline-start:2px}.ngx-fi__hint{font:var(--mat-sys-body-small);color:var(--mat-sys-on-surface-variant)}.ngx-fi__browse{margin-top:4px;font:var(--mat-sys-label-large);color:var(--mat-sys-primary)}:host(.ngx-fi--dragover) .ngx-fi__zone{border-style:solid;border-color:var(--mat-sys-primary);background:color-mix(in srgb,var(--mat-sys-primary) 8%,var(--mat-sys-surface-container-low))}:host(.ngx-fi--dragover) .ngx-fi__zone .ngx-fi__zone-icon{color:var(--mat-sys-primary);transform:scale(1.1)}:host(.ngx-fi--dragover) .ngx-fi__button{outline:2px dashed var(--mat-sys-primary);outline-offset:3px}:host(.ngx-fi--invalid) .ngx-fi__zone{border-color:var(--mat-sys-error)}:host(.ngx-fi--invalid) .ngx-fi__zone .ngx-fi__label,:host(.ngx-fi--invalid) .ngx-fi__zone .ngx-fi__zone-icon{color:var(--mat-sys-error)}:host(.ngx-fi--disabled) .ngx-fi__zone{cursor:default;border-color:color-mix(in srgb,var(--mat-sys-on-surface) 12%,transparent);background:transparent;color:color-mix(in srgb,var(--mat-sys-on-surface) 38%,transparent)}:host(.ngx-fi--disabled) .ngx-fi__zone .ngx-fi__zone-icon,:host(.ngx-fi--disabled) .ngx-fi__zone .ngx-fi__hint,:host(.ngx-fi--disabled) .ngx-fi__zone .ngx-fi__browse{color:inherit}.ngx-fi__confirm{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--mat-sys-primary) 12%,transparent);pointer-events:none;animation:ngx-fi-flash .45s ease-out forwards}.ngx-fi__confirm mat-icon{width:56px;height:56px;font-size:56px;color:var(--mat-sys-primary)}.ngx-fi__error{padding-inline:4px;font:var(--mat-sys-body-small);color:var(--mat-sys-error)}.ngx-fi__list{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;list-style:none}:host(.ngx-fi--button) .ngx-fi__list{align-self:stretch}.ngx-fi__item{display:flex;align-items:center;gap:12px;padding:8px 4px 8px 8px;border:1px solid var(--mat-sys-outline-variant);border-radius:var(--mat-sys-corner-medium, 12px);background:var(--mat-sys-surface-container-lowest);animation:ngx-fi-enter .2s ease-out both}.ngx-fi__item--error{border-color:color-mix(in srgb,var(--mat-sys-error) 40%,transparent);background:color-mix(in srgb,var(--mat-sys-error) 6%,var(--mat-sys-surface-container-lowest))}.ngx-fi__thumb{--ngx-fi-kind-color: var(--ngx-file-input-generic-color);flex:none;display:flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:var(--mat-sys-corner-small, 8px);background:color-mix(in srgb,var(--ngx-fi-kind-color) 12%,var(--mat-sys-surface));overflow:hidden}.ngx-fi__thumb[data-kind=image]{--ngx-fi-kind-color: var(--ngx-file-input-image-color)}.ngx-fi__thumb[data-kind=pdf]{--ngx-fi-kind-color: var(--ngx-file-input-pdf-color)}.ngx-fi__thumb[data-kind=video]{--ngx-fi-kind-color: var(--ngx-file-input-video-color)}.ngx-fi__thumb[data-kind=audio]{--ngx-fi-kind-color: var(--ngx-file-input-audio-color)}.ngx-fi__thumb mat-icon{color:var(--ngx-fi-kind-color)}.ngx-fi__thumb img{width:100%;height:100%;object-fit:cover}.ngx-fi__info{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.ngx-fi__name{overflow:hidden;font:var(--mat-sys-body-medium);color:var(--mat-sys-on-surface);text-overflow:ellipsis;white-space:nowrap}.ngx-fi__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;font:var(--mat-sys-body-small);color:var(--mat-sys-on-surface-variant)}.ngx-fi__item-error{display:inline-flex;align-items:center;gap:4px;color:var(--mat-sys-error)}.ngx-fi__item-error mat-icon{width:16px;height:16px;font-size:16px}.ngx-fi__remove{flex:none}.ngx-fi__live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}@keyframes ngx-fi-enter{0%{opacity:0;transform:translateY(-4px)}}@keyframes ngx-fi-flash{0%{opacity:1}to{opacity:0}}@media (prefers-reduced-motion: reduce){.ngx-fi__item,.ngx-fi__confirm{animation:none}.ngx-fi__zone,.ngx-fi__zone-icon{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4942
4995
  }
4943
4996
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxFileInput, decorators: [{
4944
4997
  type: Component,
4945
- args: [{ selector: 'ngx-file-input', imports: [MatButtonModule, MatIconModule, MatTooltipModule], encapsulation: ViewEncapsulation.None, providers: [
4946
- {
4947
- provide: NG_VALUE_ACCESSOR,
4948
- useExisting: forwardRef(() => NgxFileInput),
4949
- multi: true,
4950
- },
4951
- ], host: {
4952
- 'class': 'ngx-file-input',
4953
- '[class.ngx-fi--disabled]': 'isDisabled()',
4998
+ args: [{ selector: 'ngx-file-input', imports: [MatButtonModule, MatIconModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, host: {
4999
+ class: 'ngx-file-input',
4954
5000
  '[class.ngx-fi--default]': "variant() === 'default'",
4955
5001
  '[class.ngx-fi--compact]': "variant() === 'compact'",
4956
5002
  '[class.ngx-fi--button]': "variant() === 'button'",
4957
- }, template: "<!-- Hidden native file input \u2014 always in DOM for accessibility -->\r\n<input\r\n #nativeInput\r\n [id]=\"inputId\"\r\n class=\"ngx-fi-native-input\"\r\n type=\"file\"\r\n [accept]=\"accept()\"\r\n [multiple]=\"multiple()\"\r\n [disabled]=\"isDisabled()\"\r\n (change)=\"onFileInputChange($event)\"\r\n tabindex=\"-1\"\r\n aria-hidden=\"true\"\r\n/>\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n VARIANT: DEFAULT \u2014 large drop zone\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (variant() === 'default') {\r\n <div\r\n class=\"ngx-fi-zone\"\r\n [class.ngx-fi-zone--dragover]=\"isDragOver()\"\r\n [class.ngx-fi-zone--disabled]=\"isDisabled()\"\r\n role=\"region\"\r\n [attr.aria-label]=\"resolvedLabel()\"\r\n [attr.aria-disabled]=\"isDisabled()\"\r\n tabindex=\"0\"\r\n (click)=\"openFilePicker()\"\r\n (keydown.enter)=\"openFilePicker()\"\r\n (keydown.space)=\"openFilePicker(); $event.preventDefault()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n >\r\n <mat-icon class=\"ngx-fi-zone__icon\">cloud_upload</mat-icon>\r\n <span class=\"ngx-fi-zone__label\">{{ resolvedLabel() }}</span>\r\n @if (hint()) {\r\n <span class=\"ngx-fi-zone__hint\">{{ hint() }}</span>\r\n }\r\n <span class=\"ngx-fi-zone__btn-text\">{{ texts().fileInput.browse }}</span>\r\n\r\n <!-- Drop confirmation flash -->\r\n @if (showDropConfirm()) {\r\n <div class=\"ngx-fi-zone__confirm\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n }\r\n </div>\r\n}\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n VARIANT: COMPACT \u2014 horizontal bar\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (variant() === 'compact') {\r\n <div\r\n class=\"ngx-fi-compact\"\r\n [class.ngx-fi-compact--dragover]=\"isDragOver()\"\r\n [class.ngx-fi-compact--disabled]=\"isDisabled()\"\r\n role=\"region\"\r\n [attr.aria-label]=\"resolvedLabel()\"\r\n [attr.aria-disabled]=\"isDisabled()\"\r\n tabindex=\"0\"\r\n (click)=\"openFilePicker()\"\r\n (keydown.enter)=\"openFilePicker()\"\r\n (keydown.space)=\"openFilePicker(); $event.preventDefault()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n >\r\n <mat-icon class=\"ngx-fi-compact__icon\">cloud_upload</mat-icon>\r\n <div class=\"ngx-fi-compact__text\">\r\n <span class=\"ngx-fi-compact__label\">{{ resolvedLabel() }}</span>\r\n @if (hint()) {\r\n <span class=\"ngx-fi-compact__hint\">{{ hint() }}</span>\r\n }\r\n </div>\r\n\r\n @if (showDropConfirm()) {\r\n <div class=\"ngx-fi-zone__confirm\">\r\n <mat-icon>check_circle</mat-icon>\r\n </div>\r\n }\r\n </div>\r\n}\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n VARIANT: BUTTON \u2014 only a button\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (variant() === 'button') {\r\n <div\r\n class=\"ngx-fi-button-wrap\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n [class.ngx-fi-button-wrap--dragover]=\"isDragOver()\"\r\n >\r\n <button\r\n mat-raised-button\r\n color=\"primary\"\r\n [disabled]=\"isDisabled()\"\r\n (click)=\"openFilePicker()\"\r\n type=\"button\"\r\n >\r\n <mat-icon>attach_file</mat-icon>\r\n {{ resolvedLabel() }}\r\n </button>\r\n </div>\r\n}\r\n\r\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n FILE PREVIEW LIST\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\r\n@if (showPreview() && items().length > 0) {\r\n <div class=\"ngx-fi-list\">\r\n @for (item of items(); track item.id) {\r\n <div\r\n class=\"ngx-fi-item\"\r\n [class.ngx-fi-item--error]=\"item.status === 'error'\"\r\n >\r\n <!-- Thumbnail / Icon -->\r\n <div class=\"ngx-fi-item__thumb\" [class]=\"getFileColorClass(item.type)\">\r\n @if (item.preview) {\r\n <img [src]=\"item.preview\" [alt]=\"item.name\" />\r\n } @else {\r\n <mat-icon>{{ getFileIcon(item.type) }}</mat-icon>\r\n }\r\n </div>\r\n\r\n <!-- Info -->\r\n <div class=\"ngx-fi-item__info\">\r\n <span class=\"ngx-fi-item__name\" [matTooltip]=\"item.name\">{{ item.name }}</span>\r\n <span class=\"ngx-fi-item__meta\">\r\n {{ formatSize(item.size) }}\r\n @if (item.error) {\r\n <span class=\"ngx-fi-item__error\">\r\n <mat-icon>warning</mat-icon> {{ item.error }}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n\r\n <!-- Remove -->\r\n <button\r\n class=\"ngx-fi-item__remove\"\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"removeFile(item); $event.stopPropagation()\"\r\n [matTooltip]=\"texts().fileInput.remove(item.name)\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n\r\n<!-- Screen reader announcements -->\r\n<div class=\"ngx-fi-sr-only\" aria-live=\"polite\">{{ liveAnnouncement() }}</div>\r\n", styles: [".ngx-fi-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.ngx-fi-native-input{position:absolute;width:0;height:0;opacity:0;pointer-events:none}.ngx-fi-zone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:180px;padding:2rem 1.5rem;border:2px dashed var(--mat-sys-outline-variant, #c4c4c4);border-radius:12px;background:var(--mat-sys-surface-container-low, #fafafa);cursor:pointer;transition:border-color .2s,background-color .2s,box-shadow .2s;outline:none;-webkit-user-select:none;user-select:none;overflow:hidden}.ngx-fi-zone:hover:not(.ngx-fi-zone--disabled){border-style:solid;background:var(--mat-sys-surface-container, #f0f0f0)}.ngx-fi-zone:focus-visible:not(.ngx-fi-zone--disabled){box-shadow:0 0 0 3px #5c6bc040;border-color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-zone--dragover{border-color:var(--mat-sys-primary, #5c6bc0);border-style:solid;background:var(--mat-sys-primary-container, rgba(92, 107, 192, .08));box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 16px #5c6bc033;animation:ngx-fi-glow 1s ease-in-out infinite}.ngx-fi-zone--dragover .ngx-fi-zone__icon{transform:scale(1.15);color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-zone--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ngx-fi-zone__icon{font-size:48px;width:48px;height:48px;color:var(--mat-sys-on-surface-variant, #666);transition:transform .2s,color .2s}.ngx-fi-zone__label{font-size:1.05rem;font-weight:500;color:var(--mat-sys-on-surface, #333);text-align:center}.ngx-fi-zone__hint{font-size:.82rem;color:var(--mat-sys-on-surface-variant, #666);text-align:center}.ngx-fi-zone__btn-text{font-size:.82rem;color:var(--mat-sys-primary, #5c6bc0);font-weight:500;margin-top:.25rem}.ngx-fi-zone__confirm{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#43a0471f;pointer-events:none;animation:ngx-fi-confirm-flash .4s ease-out forwards}.ngx-fi-zone__confirm mat-icon{font-size:56px;width:56px;height:56px;color:var(--mat-sys-tertiary, #43a047)}.ngx-fi-compact{position:relative;display:flex;align-items:center;gap:1rem;height:80px;padding:0 1.25rem;border:2px dashed var(--mat-sys-outline-variant, #c4c4c4);border-radius:12px;background:var(--mat-sys-surface-container-low, #fafafa);cursor:pointer;transition:border-color .2s,background-color .2s,box-shadow .2s;outline:none;-webkit-user-select:none;user-select:none;overflow:hidden}.ngx-fi-compact:hover:not(.ngx-fi-compact--disabled){border-style:solid;background:var(--mat-sys-surface-container, #f0f0f0)}.ngx-fi-compact:focus-visible:not(.ngx-fi-compact--disabled){box-shadow:0 0 0 3px #5c6bc040;border-color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-compact--dragover{border-color:var(--mat-sys-primary, #5c6bc0);border-style:solid;background:var(--mat-sys-primary-container, rgba(92, 107, 192, .08));box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 16px #5c6bc033;animation:ngx-fi-glow 1s ease-in-out infinite}.ngx-fi-compact--dragover .ngx-fi-compact__icon{transform:scale(1.1);color:var(--mat-sys-primary, #5c6bc0)}.ngx-fi-compact--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ngx-fi-compact__icon{font-size:32px;width:32px;height:32px;color:var(--mat-sys-on-surface-variant, #666);flex-shrink:0;transition:transform .2s,color .2s}.ngx-fi-compact__text{display:flex;flex-direction:column;gap:.15rem;min-width:0}.ngx-fi-compact__label{font-size:.95rem;font-weight:500;color:var(--mat-sys-on-surface, #333)}.ngx-fi-compact__hint{font-size:.78rem;color:var(--mat-sys-on-surface-variant, #666)}.ngx-fi-button-wrap{display:inline-block;position:relative;padding:4px;border-radius:8px;transition:background-color .2s,box-shadow .2s}.ngx-fi-button-wrap--dragover{background:var(--mat-sys-primary-container, rgba(92, 107, 192, .08));box-shadow:0 0 12px #5c6bc040}.ngx-fi-list{display:flex;flex-direction:column;gap:.5rem;margin-top:.75rem}.ngx-fi-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .65rem;border-radius:10px;background:var(--mat-sys-surface-container-low, #fafafa);border:1px solid var(--mat-sys-outline-variant, #e0e0e0);transition:border-color .2s,background-color .2s;animation:ngx-fi-item-enter .2s ease-out both}.ngx-fi-item--error{border-color:var(--mat-sys-error, #d32f2f);background:#d32f2f0a}.ngx-fi-item__thumb{width:42px;height:42px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden}.ngx-fi-item__thumb img{width:100%;height:100%;object-fit:cover;border-radius:8px}.ngx-fi-item__thumb mat-icon{font-size:24px;width:24px;height:24px}.ngx-fi-item__info{flex:1;min-width:0;display:flex;flex-direction:column;gap:.1rem}.ngx-fi-item__name{font-size:.88rem;font-weight:500;color:var(--mat-sys-on-surface, #333);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngx-fi-item__meta{font-size:.75rem;color:var(--mat-sys-on-surface-variant, #666);display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.ngx-fi-item__error{color:var(--mat-sys-error, #d32f2f);display:inline-flex;align-items:center;gap:.2rem;font-weight:500}.ngx-fi-item__error mat-icon{font-size:14px;width:14px;height:14px}.ngx-fi-item__remove{flex-shrink:0}.ngx-fi-icon--image{background:#5c6bc01a}.ngx-fi-icon--image mat-icon{color:#5c6bc0}.ngx-fi-icon--pdf{background:#d32f2f1a}.ngx-fi-icon--pdf mat-icon{color:#d32f2f}.ngx-fi-icon--video{background:#1e88e51a}.ngx-fi-icon--video mat-icon{color:#1e88e5}.ngx-fi-icon--audio{background:#8e24aa1a}.ngx-fi-icon--audio mat-icon{color:#8e24aa}.ngx-fi-icon--generic{background:#0000000f}.ngx-fi-icon--generic mat-icon{color:#757575}@keyframes ngx-fi-item-enter{0%{opacity:0;transform:translateY(8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes ngx-fi-confirm-flash{0%{opacity:1}to{opacity:0}}.ngx-fi-item:nth-child(1){animation-delay:0ms}.ngx-fi-item:nth-child(2){animation-delay:50ms}.ngx-fi-item:nth-child(3){animation-delay:.1s}.ngx-fi-item:nth-child(4){animation-delay:.15s}.ngx-fi-item:nth-child(5){animation-delay:.2s}.ngx-fi-item:nth-child(6){animation-delay:.25s}.ngx-fi-item:nth-child(7){animation-delay:.3s}.ngx-fi-item:nth-child(8){animation-delay:.35s}.ngx-fi-item:nth-child(9){animation-delay:.4s}.ngx-fi-item:nth-child(10){animation-delay:.45s}.ngx-fi-item:nth-child(11){animation-delay:.5s}.ngx-fi-item:nth-child(12){animation-delay:.55s}.ngx-fi-item:nth-child(13){animation-delay:.6s}.ngx-fi-item:nth-child(14){animation-delay:.65s}.ngx-fi-item:nth-child(15){animation-delay:.7s}.ngx-fi-item:nth-child(16){animation-delay:.75s}.ngx-fi-item:nth-child(17){animation-delay:.8s}.ngx-fi-item:nth-child(18){animation-delay:.85s}.ngx-fi-item:nth-child(19){animation-delay:.9s}.ngx-fi-item:nth-child(20){animation-delay:.95s}@keyframes ngx-fi-glow{0%,to{box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 12px #5c6bc02e}50%{box-shadow:inset 0 0 0 2px var(--mat-sys-primary, #5c6bc0),0 0 22px #5c6bc059}}\n"] }]
4958
- }], propDecorators: { _nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], showPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreview", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], filesSelected: [{ type: i0.Output, args: ["filesSelected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }], validationError: [{ type: i0.Output, args: ["validationError"] }] } });
5003
+ '[class.ngx-fi--disabled]': 'isDisabled()',
5004
+ '[class.ngx-fi--dragover]': 'isDragOver()',
5005
+ '[class.ngx-fi--invalid]': 'showError()',
5006
+ '(dragover)': 'onDragOver($event)',
5007
+ '(dragleave)': 'onDragLeave($event)',
5008
+ '(drop)': 'onDrop($event)',
5009
+ }, template: "<!-- Input nativo oculto: lo abre la zona o el bot\u00F3n -->\n<input\n #nativeInput\n class=\"ngx-fi__native\"\n type=\"file\"\n hidden\n tabindex=\"-1\"\n [accept]=\"acceptAttr()\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n (change)=\"onNativeChange($event)\"\n (cancel)=\"onZoneBlur()\" />\n\n@switch (variant()) {\n @case ('button') {\n <button\n matButton=\"filled\"\n type=\"button\"\n class=\"ngx-fi__button\"\n [disabled]=\"isDisabled()\"\n [attr.aria-describedby]=\"describedBy()\"\n (click)=\"openFilePicker()\"\n (blur)=\"onZoneBlur()\">\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabel() }}@if (isRequired()) {<span class=\"ngx-fi__required\" aria-hidden=\"true\">*</span>}\n </button>\n @if (hint()) {\n <span class=\"ngx-fi__hint\" [id]=\"id + '-hint'\">{{ hint() }}</span>\n }\n }\n\n @default {\n <!-- Zona de arrastre: es un bot\u00F3n (abre el selector) con la etiqueta como nombre accesible -->\n <div\n class=\"ngx-fi__zone\"\n [class.ngx-fi__zone--compact]=\"variant() === 'compact'\"\n role=\"button\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [attr.aria-disabled]=\"isDisabled()\"\n [attr.aria-describedby]=\"describedBy()\"\n (click)=\"openFilePicker()\"\n (keydown)=\"onZoneKeydown($event)\"\n (blur)=\"onZoneBlur()\">\n <mat-icon class=\"ngx-fi__zone-icon\" aria-hidden=\"true\">cloud_upload</mat-icon>\n\n <span class=\"ngx-fi__zone-text\">\n <span class=\"ngx-fi__label\">\n {{ resolvedLabel() }}@if (isRequired()) {<span class=\"ngx-fi__required\" aria-hidden=\"true\">*</span>}\n </span>\n @if (hint()) {\n <span class=\"ngx-fi__hint\" [id]=\"id + '-hint'\">{{ hint() }}</span>\n }\n @if (variant() === 'default') {\n <span class=\"ngx-fi__browse\">{{ texts().fileInput.browse }}</span>\n }\n </span>\n\n @if (showDropConfirm()) {\n <span class=\"ngx-fi__confirm\" aria-hidden=\"true\"><mat-icon>check_circle</mat-icon></span>\n }\n </div>\n }\n}\n\n@if (showError() && error()) {\n <span class=\"ngx-fi__error\" [id]=\"id + '-error'\">{{ error() }}</span>\n}\n\n@if (showPreview() && views().length) {\n <ul class=\"ngx-fi__list\">\n @for (view of views(); track view.item.id) {\n <li class=\"ngx-fi__item\" [class.ngx-fi__item--error]=\"view.error\">\n <span class=\"ngx-fi__thumb\" [attr.data-kind]=\"view.kind\">\n @if (view.item.preview) {\n <img [src]=\"view.item.preview\" alt=\"\" />\n } @else {\n <mat-icon aria-hidden=\"true\">{{ view.icon }}</mat-icon>\n }\n </span>\n\n <span class=\"ngx-fi__info\">\n <span class=\"ngx-fi__name\" [title]=\"view.item.name\">{{ view.item.name }}</span>\n <span class=\"ngx-fi__meta\">\n {{ view.size }}\n @if (view.error) {\n <span class=\"ngx-fi__item-error\"><mat-icon aria-hidden=\"true\">error</mat-icon>{{ view.error }}</span>\n }\n </span>\n </span>\n\n <button\n matIconButton\n type=\"button\"\n class=\"ngx-fi__remove\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"texts().fileInput.remove(view.item.name)\"\n [matTooltip]=\"texts().fileInput.remove(view.item.name)\"\n (click)=\"removeFile(view.item)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n }\n </ul>\n}\n\n<!-- Regi\u00F3n viva fija: a\u00F1adidos, quitados y rechazados -->\n<div class=\"ngx-fi__live\" aria-live=\"polite\">{{ announcement() }}</div>\n", styles: [":host{--ngx-file-input-image-color: var(--mat-sys-primary);--ngx-file-input-pdf-color: var(--mat-sys-error);--ngx-file-input-video-color: var(--mat-sys-tertiary);--ngx-file-input-audio-color: var(--mat-sys-secondary);--ngx-file-input-generic-color: var(--mat-sys-on-surface-variant);display:flex;flex-direction:column;gap:8px}:host(.ngx-fi--button){display:inline-flex;align-items:flex-start}.ngx-fi__zone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;min-height:180px;padding:32px 24px;border:2px dashed var(--mat-sys-outline-variant);border-radius:var(--mat-sys-corner-large, 16px);background:var(--mat-sys-surface-container-low);color:var(--mat-sys-on-surface);text-align:center;cursor:pointer;-webkit-user-select:none;user-select:none;overflow:hidden;transition:border-color .15s,background-color .15s}.ngx-fi__zone:hover{border-color:var(--mat-sys-outline);background:var(--mat-sys-surface-container)}.ngx-fi__zone:focus-visible{outline:2px solid var(--mat-sys-primary);outline-offset:2px}.ngx-fi__zone--compact{flex-direction:row;justify-content:flex-start;gap:16px;min-height:0;padding:16px 20px;border-radius:var(--mat-sys-corner-medium, 12px);text-align:start}.ngx-fi__zone--compact .ngx-fi__zone-icon{width:32px;height:32px;font-size:32px}.ngx-fi__zone--compact .ngx-fi__zone-text{align-items:flex-start}.ngx-fi__zone-icon{flex:none;width:48px;height:48px;font-size:48px;color:var(--mat-sys-on-surface-variant);transition:transform .15s,color .15s}.ngx-fi__zone-text{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:0}.ngx-fi__label{font:var(--mat-sys-title-medium)}.ngx-fi__required{margin-inline-start:2px}.ngx-fi__hint{font:var(--mat-sys-body-small);color:var(--mat-sys-on-surface-variant)}.ngx-fi__browse{margin-top:4px;font:var(--mat-sys-label-large);color:var(--mat-sys-primary)}:host(.ngx-fi--dragover) .ngx-fi__zone{border-style:solid;border-color:var(--mat-sys-primary);background:color-mix(in srgb,var(--mat-sys-primary) 8%,var(--mat-sys-surface-container-low))}:host(.ngx-fi--dragover) .ngx-fi__zone .ngx-fi__zone-icon{color:var(--mat-sys-primary);transform:scale(1.1)}:host(.ngx-fi--dragover) .ngx-fi__button{outline:2px dashed var(--mat-sys-primary);outline-offset:3px}:host(.ngx-fi--invalid) .ngx-fi__zone{border-color:var(--mat-sys-error)}:host(.ngx-fi--invalid) .ngx-fi__zone .ngx-fi__label,:host(.ngx-fi--invalid) .ngx-fi__zone .ngx-fi__zone-icon{color:var(--mat-sys-error)}:host(.ngx-fi--disabled) .ngx-fi__zone{cursor:default;border-color:color-mix(in srgb,var(--mat-sys-on-surface) 12%,transparent);background:transparent;color:color-mix(in srgb,var(--mat-sys-on-surface) 38%,transparent)}:host(.ngx-fi--disabled) .ngx-fi__zone .ngx-fi__zone-icon,:host(.ngx-fi--disabled) .ngx-fi__zone .ngx-fi__hint,:host(.ngx-fi--disabled) .ngx-fi__zone .ngx-fi__browse{color:inherit}.ngx-fi__confirm{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--mat-sys-primary) 12%,transparent);pointer-events:none;animation:ngx-fi-flash .45s ease-out forwards}.ngx-fi__confirm mat-icon{width:56px;height:56px;font-size:56px;color:var(--mat-sys-primary)}.ngx-fi__error{padding-inline:4px;font:var(--mat-sys-body-small);color:var(--mat-sys-error)}.ngx-fi__list{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;list-style:none}:host(.ngx-fi--button) .ngx-fi__list{align-self:stretch}.ngx-fi__item{display:flex;align-items:center;gap:12px;padding:8px 4px 8px 8px;border:1px solid var(--mat-sys-outline-variant);border-radius:var(--mat-sys-corner-medium, 12px);background:var(--mat-sys-surface-container-lowest);animation:ngx-fi-enter .2s ease-out both}.ngx-fi__item--error{border-color:color-mix(in srgb,var(--mat-sys-error) 40%,transparent);background:color-mix(in srgb,var(--mat-sys-error) 6%,var(--mat-sys-surface-container-lowest))}.ngx-fi__thumb{--ngx-fi-kind-color: var(--ngx-file-input-generic-color);flex:none;display:flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:var(--mat-sys-corner-small, 8px);background:color-mix(in srgb,var(--ngx-fi-kind-color) 12%,var(--mat-sys-surface));overflow:hidden}.ngx-fi__thumb[data-kind=image]{--ngx-fi-kind-color: var(--ngx-file-input-image-color)}.ngx-fi__thumb[data-kind=pdf]{--ngx-fi-kind-color: var(--ngx-file-input-pdf-color)}.ngx-fi__thumb[data-kind=video]{--ngx-fi-kind-color: var(--ngx-file-input-video-color)}.ngx-fi__thumb[data-kind=audio]{--ngx-fi-kind-color: var(--ngx-file-input-audio-color)}.ngx-fi__thumb mat-icon{color:var(--ngx-fi-kind-color)}.ngx-fi__thumb img{width:100%;height:100%;object-fit:cover}.ngx-fi__info{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.ngx-fi__name{overflow:hidden;font:var(--mat-sys-body-medium);color:var(--mat-sys-on-surface);text-overflow:ellipsis;white-space:nowrap}.ngx-fi__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;font:var(--mat-sys-body-small);color:var(--mat-sys-on-surface-variant)}.ngx-fi__item-error{display:inline-flex;align-items:center;gap:4px;color:var(--mat-sys-error)}.ngx-fi__item-error mat-icon{width:16px;height:16px;font-size:16px}.ngx-fi__remove{flex:none}.ngx-fi__live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}@keyframes ngx-fi-enter{0%{opacity:0;transform:translateY(-4px)}}@keyframes ngx-fi-flash{0%{opacity:1}to{opacity:0}}@media (prefers-reduced-motion: reduce){.ngx-fi__item,.ngx-fi__confirm{animation:none}.ngx-fi__zone,.ngx-fi__zone-icon{transition:none}}\n"] }]
5010
+ }], ctorParameters: () => [], propDecorators: { _nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], showPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreview", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }], filesSelected: [{ type: i0.Output, args: ["filesSelected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }], validationError: [{ type: i0.Output, args: ["validationError"] }] } });
4959
5011
 
4960
5012
  /**
4961
5013
  * Generated bundle index. Do not edit.
4962
5014
  */
4963
5015
 
4964
- export { AdvancedFilterService, AdvancedFilterUtils, ColumnConfigService, DatosPantallaService, DialogBodyType, DynamicHostDirective, NGX_EIFFAGE_DEFAULT_TEXTS, NGX_EIFFAGE_TEXTS, NgxAlert, NgxBasicSelect, NgxBasicTable, NgxConfirmDialogComponent, NgxDialog, NgxDialogContext, NgxDialogService, NgxFileInput, NgxInputFile, NgxLoadingButton, NgxPaginatedSelect, NgxPaginatedSelectBottomSheetComponent, NgxPaginatedTable, NgxSkeleton, NgxTablesIntl, NgxTexts, OPERATOR_BY_FILTER_TYPE, PaginatedSearchableSelectComponent, SearchableSelectComponent, USER_ID_FACTORY, base64ToUtf8, includeNoSensible, isFilterOption, provideNgxEiffageTexts };
5016
+ export { AdvancedFilterService, AdvancedFilterUtils, ColumnConfigService, DatosPantallaService, DialogBodyType, DynamicHostDirective, NGX_EIFFAGE_DEFAULT_TEXTS, NGX_EIFFAGE_TEXTS, NgxAlert, NgxBasicSelect, NgxBasicTable, NgxConfirmDialogComponent, NgxDialog, NgxDialogContext, NgxDialogHeader, NgxDialogService, NgxFileInput, NgxInputFile, NgxLoadingButton, NgxPaginatedSelect, NgxPaginatedSelectBottomSheetComponent, NgxPaginatedTable, NgxSkeleton, NgxTablesIntl, NgxTexts, OPERATOR_BY_FILTER_TYPE, PaginatedSearchableSelectComponent, SearchableSelectComponent, USER_ID_FACTORY, base64ToUtf8, includeNoSensible, isFilterOption, provideNgxEiffageTexts };
4965
5017
  //# sourceMappingURL=ngx-eiffage-material.mjs.map