ngx-eiffage-material 0.3.0 → 0.4.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.
- package/fesm2022/ngx-eiffage-material.mjs +249 -240
- package/fesm2022/ngx-eiffage-material.mjs.map +1 -1
- package/index.d.ts +79 -57
- package/package.json +1 -1
|
@@ -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
|
|
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';
|
|
@@ -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.
|
|
@@ -4653,309 +4665,306 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
4653
4665
|
}, 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
4666
|
}], 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
4667
|
|
|
4656
|
-
let
|
|
4668
|
+
let nextId = 0;
|
|
4669
|
+
/**
|
|
4670
|
+
* Zona para soltar o seleccionar ficheros, con lista de ficheros y previsualización de imágenes.
|
|
4671
|
+
* Se usa con `formControlName` / `[formControl]` / `ngModel` (valor: los `File[]` válidos) o con `[(files)]`.
|
|
4672
|
+
*/
|
|
4657
4673
|
class NgxFileInput {
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4674
|
+
// Se registra como value accessor a mano para poder leer el control (obligatorio y error), como ngx-input-file
|
|
4675
|
+
_ngControl = inject(NgControl, { self: true, optional: true });
|
|
4676
|
+
_parentForm = inject(FormGroupDirective, { optional: true }) ?? inject(NgForm, { optional: true });
|
|
4677
|
+
_host = inject(ElementRef);
|
|
4678
|
+
_locale = inject(LOCALE_ID);
|
|
4679
|
+
_isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
4680
|
+
_nativeInput = viewChild.required('nativeInput');
|
|
4681
|
+
texts = inject(NgxTexts).texts;
|
|
4661
4682
|
// ── Inputs ──────────────────────────────────────────────────────────────────
|
|
4662
|
-
/**
|
|
4683
|
+
/** Tipos admitidos: extensiones (`.pdf`), MIME (`application/pdf`) o comodines (`image/*`). `'*'` = cualquiera. */
|
|
4663
4684
|
accept = input('*', ...(ngDevMode ? [{ debugName: "accept" }] : []));
|
|
4664
|
-
/**
|
|
4685
|
+
/** Admite varios ficheros: cada selección se añade a la anterior. */
|
|
4665
4686
|
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
4666
|
-
/**
|
|
4687
|
+
/** Tamaño máximo por fichero, en bytes. `null` = sin límite. */
|
|
4667
4688
|
maxSize = input(null, ...(ngDevMode ? [{ debugName: "maxSize" }] : []));
|
|
4668
|
-
/**
|
|
4689
|
+
/** Número máximo de ficheros válidos (con `multiple`). `null` = sin límite. */
|
|
4669
4690
|
maxFiles = input(null, ...(ngDevMode ? [{ debugName: "maxFiles" }] : []));
|
|
4670
|
-
/** Disables the component. */
|
|
4671
4691
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
4672
|
-
/**
|
|
4692
|
+
/** Texto principal. Por defecto, el texto `fileInput.dropLabel` de la librería. */
|
|
4673
4693
|
label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : []));
|
|
4674
|
-
|
|
4675
|
-
resolvedLabel = computed(() => this.label() ?? this.texts().fileInput.dropLabel, ...(ngDevMode ? [{ debugName: "resolvedLabel" }] : []));
|
|
4676
|
-
/** Secondary hint text (formats, sizes…). */
|
|
4694
|
+
/** Texto secundario (formatos, tamaños…). */
|
|
4677
4695
|
hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
4678
|
-
/**
|
|
4696
|
+
/** Mensaje de error. Se ve cuando el control es inválido y está tocado o el formulario se ha enviado. */
|
|
4697
|
+
error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
4698
|
+
/** Muestra la lista de ficheros (con miniatura de las imágenes). */
|
|
4679
4699
|
showPreview = input(true, ...(ngDevMode ? [{ debugName: "showPreview", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
4680
|
-
/**
|
|
4700
|
+
/** `'default'` (zona grande), `'compact'` (barra horizontal) o `'button'` (solo un botón). */
|
|
4681
4701
|
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : []));
|
|
4702
|
+
/** Ficheros válidos seleccionados. Admite `[(files)]` para usarlo sin formularios. */
|
|
4703
|
+
files = model([], ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
4682
4704
|
// ── Outputs ─────────────────────────────────────────────────────────────────
|
|
4683
|
-
/**
|
|
4705
|
+
/** Ficheros válidos añadidos en una selección o al soltar. */
|
|
4684
4706
|
filesSelected = output();
|
|
4685
|
-
/**
|
|
4707
|
+
/** Fichero quitado de la lista (válido o rechazado). */
|
|
4686
4708
|
fileRemoved = output();
|
|
4687
|
-
/**
|
|
4709
|
+
/** Un fichero rechazado (tipo, tamaño o número). */
|
|
4688
4710
|
validationError = output();
|
|
4689
|
-
// ──
|
|
4690
|
-
|
|
4691
|
-
|
|
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). */
|
|
4711
|
+
// ── Estado ──────────────────────────────────────────────────────────────────
|
|
4712
|
+
id = `ngx-fi-${nextId++}`;
|
|
4713
|
+
_entries = signal([], ...(ngDevMode ? [{ debugName: "_entries" }] : []));
|
|
4701
4714
|
_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
|
-
*/
|
|
4715
|
+
/** Deshabilitado por el input `disabled` o por el control del formulario. */
|
|
4706
4716
|
isDisabled = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4717
|
+
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
4718
|
+
showDropConfirm = signal(false, ...(ngDevMode ? [{ debugName: "showDropConfirm" }] : []));
|
|
4719
|
+
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : []));
|
|
4720
|
+
isRequired = signal(false, ...(ngDevMode ? [{ debugName: "isRequired" }] : []));
|
|
4721
|
+
showError = signal(false, ...(ngDevMode ? [{ debugName: "showError" }] : []));
|
|
4722
|
+
resolvedLabel = computed(() => this.label() ?? this.texts().fileInput.dropLabel, ...(ngDevMode ? [{ debugName: "resolvedLabel" }] : []));
|
|
4723
|
+
acceptAttr = computed(() => parseAccept(this.accept()).join(','), ...(ngDevMode ? [{ debugName: "acceptAttr" }] : []));
|
|
4724
|
+
describedBy = computed(() => [this.hint() ? `${this.id}-hint` : '', this.showError() && this.error() ? `${this.id}-error` : ''].filter(Boolean).join(' ') || null, ...(ngDevMode ? [{ debugName: "describedBy" }] : []));
|
|
4725
|
+
/** Lo que pinta la lista: icono, color, tamaño legible y mensaje de error ya traducido. */
|
|
4726
|
+
views = computed(() => this._entries().map(({ item, code }) => ({
|
|
4727
|
+
item,
|
|
4728
|
+
icon: fileIcon(item.file),
|
|
4729
|
+
kind: fileKind(item.file),
|
|
4730
|
+
size: formatFileSize(item.size, this._locale),
|
|
4731
|
+
error: code ? this._message(item.file, code) : null,
|
|
4732
|
+
})), ...(ngDevMode ? [{ debugName: "views" }] : []));
|
|
4710
4733
|
_onChange = () => { };
|
|
4711
4734
|
_onTouched = () => { };
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4735
|
+
_controlSub;
|
|
4736
|
+
_confirmTimer;
|
|
4737
|
+
constructor() {
|
|
4738
|
+
if (this._ngControl)
|
|
4739
|
+
this._ngControl.valueAccessor = this;
|
|
4740
|
+
// [files] o [(files)] desde fuera: reconstruye la lista si no coincide con los válidos actuales
|
|
4741
|
+
effect(() => {
|
|
4742
|
+
const files = this.files();
|
|
4743
|
+
untracked(() => {
|
|
4744
|
+
const valid = this._validFiles();
|
|
4745
|
+
if (files.length !== valid.length || files.some((file, i) => file !== valid[i]))
|
|
4746
|
+
this._resetFrom(files);
|
|
4747
|
+
});
|
|
4748
|
+
});
|
|
4749
|
+
inject(DestroyRef).onDestroy(() => {
|
|
4750
|
+
this._controlSub?.unsubscribe();
|
|
4751
|
+
clearTimeout(this._confirmTimer);
|
|
4752
|
+
this._entries().forEach(({ item }) => this._revokePreview(item));
|
|
4753
|
+
});
|
|
4754
|
+
}
|
|
4755
|
+
ngAfterViewInit() {
|
|
4756
|
+
// formControlName asigna su control después de registerOnChange: aquí ya está
|
|
4757
|
+
if (this._ngControl?.control)
|
|
4758
|
+
this._bindControl(this._ngControl.control);
|
|
4759
|
+
}
|
|
4760
|
+
ngDoCheck() {
|
|
4761
|
+
// resetForm quita el «enviado» sin emitir evento: se recalcula en cada comprobación
|
|
4762
|
+
this._syncFormState();
|
|
4763
|
+
}
|
|
4764
|
+
// ── API pública ─────────────────────────────────────────────────────────────
|
|
4765
|
+
/** Abre el selector de ficheros del sistema (no hace nada si está deshabilitado). */
|
|
4766
|
+
openFilePicker() {
|
|
4767
|
+
if (!this.isDisabled())
|
|
4768
|
+
this._nativeInput().nativeElement.click();
|
|
4769
|
+
}
|
|
4770
|
+
/** Quita un fichero de la lista, válido o rechazado. */
|
|
4771
|
+
removeFile(item) {
|
|
4772
|
+
const entry = this._entries().find(e => e.item.id === item.id);
|
|
4773
|
+
if (!entry)
|
|
4715
4774
|
return;
|
|
4716
|
-
|
|
4717
|
-
this.
|
|
4718
|
-
this.
|
|
4775
|
+
this._revokePreview(item);
|
|
4776
|
+
this._entries.update(list => list.filter(e => e !== entry));
|
|
4777
|
+
this.fileRemoved.emit(item);
|
|
4778
|
+
this.announcement.set(this.texts().fileInput.removed(item.name));
|
|
4779
|
+
if (!entry.code)
|
|
4780
|
+
this._commit();
|
|
4719
4781
|
}
|
|
4720
|
-
|
|
4721
|
-
|
|
4782
|
+
// ── Eventos de la plantilla ─────────────────────────────────────────────────
|
|
4783
|
+
onZoneKeydown(event) {
|
|
4784
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
4785
|
+
event.preventDefault();
|
|
4786
|
+
this.openFilePicker();
|
|
4787
|
+
}
|
|
4722
4788
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4789
|
+
onZoneBlur() {
|
|
4790
|
+
// Al abrir el diálogo del sistema la ventana pierde el foco: eso no cuenta como salir del campo
|
|
4791
|
+
if (typeof document !== 'undefined' && document.hasFocus())
|
|
4792
|
+
this._markTouched();
|
|
4725
4793
|
}
|
|
4726
|
-
|
|
4727
|
-
|
|
4794
|
+
onNativeChange(event) {
|
|
4795
|
+
const input = event.target;
|
|
4796
|
+
this._addFiles(Array.from(input.files ?? []));
|
|
4797
|
+
input.value = '';
|
|
4728
4798
|
}
|
|
4729
|
-
// ── Drag & Drop ─────────────────────────────────────────────────────────────
|
|
4730
4799
|
onDragOver(event) {
|
|
4800
|
+
// Siempre: si no, el navegador abre el fichero al soltarlo
|
|
4731
4801
|
event.preventDefault();
|
|
4732
|
-
event.
|
|
4802
|
+
if (event.dataTransfer)
|
|
4803
|
+
event.dataTransfer.dropEffect = this.isDisabled() ? 'none' : 'copy';
|
|
4733
4804
|
if (!this.isDisabled())
|
|
4734
4805
|
this.isDragOver.set(true);
|
|
4735
4806
|
}
|
|
4736
4807
|
onDragLeave(event) {
|
|
4737
|
-
|
|
4738
|
-
event.
|
|
4739
|
-
|
|
4808
|
+
// dragleave también salta al pasar por encima de un hijo: solo cuenta si sale del componente
|
|
4809
|
+
if (!this._host.nativeElement.contains(event.relatedTarget))
|
|
4810
|
+
this.isDragOver.set(false);
|
|
4740
4811
|
}
|
|
4741
4812
|
onDrop(event) {
|
|
4742
4813
|
event.preventDefault();
|
|
4743
|
-
event.stopPropagation();
|
|
4744
4814
|
this.isDragOver.set(false);
|
|
4745
4815
|
if (this.isDisabled())
|
|
4746
4816
|
return;
|
|
4747
|
-
const files = event.dataTransfer?.files;
|
|
4748
|
-
if (files
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4817
|
+
const files = Array.from(event.dataTransfer?.files ?? []);
|
|
4818
|
+
if (!files.length)
|
|
4819
|
+
return;
|
|
4820
|
+
this._addFiles(files);
|
|
4821
|
+
this._flashDropConfirm();
|
|
4752
4822
|
}
|
|
4753
|
-
// ──
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
if (input.files && input.files.length > 0) {
|
|
4757
|
-
this._processFiles(Array.from(input.files));
|
|
4758
|
-
}
|
|
4759
|
-
input.value = '';
|
|
4823
|
+
// ── ControlValueAccessor ────────────────────────────────────────────────────
|
|
4824
|
+
writeValue(files) {
|
|
4825
|
+
this.files.set(Array.isArray(files) ? files : []);
|
|
4760
4826
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
this._nativeInput()?.nativeElement.click();
|
|
4827
|
+
registerOnChange(fn) {
|
|
4828
|
+
this._onChange = fn;
|
|
4829
|
+
if (this._ngControl?.control)
|
|
4830
|
+
this._bindControl(this._ngControl.control);
|
|
4766
4831
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
this.
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
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);
|
|
4832
|
+
registerOnTouched(fn) {
|
|
4833
|
+
this._onTouched = fn;
|
|
4834
|
+
}
|
|
4835
|
+
setDisabledState(isDisabled) {
|
|
4836
|
+
this._cvaDisabled.set(isDisabled);
|
|
4837
|
+
}
|
|
4838
|
+
// ── Privado ─────────────────────────────────────────────────────────────────
|
|
4839
|
+
_validFiles() {
|
|
4840
|
+
return this._entries().filter(e => !e.code).map(e => e.item.file);
|
|
4841
|
+
}
|
|
4842
|
+
_addFiles(incoming) {
|
|
4843
|
+
const patterns = parseAccept(this.accept());
|
|
4844
|
+
const maxSize = this.maxSize();
|
|
4845
|
+
const max = this.multiple() ? this.maxFiles() : 1;
|
|
4846
|
+
const current = this.multiple() ? this._entries() : [];
|
|
4847
|
+
let validCount = current.filter(e => !e.code).length;
|
|
4848
|
+
const added = [];
|
|
4849
|
+
for (const file of this.multiple() ? incoming : incoming.slice(0, 1)) {
|
|
4850
|
+
// Repetidos (ya en la lista o dos veces en la misma tanda): se ignoran
|
|
4851
|
+
if ([...current, ...added].some(e => isSameFile(e.item.file, file)))
|
|
4852
|
+
continue;
|
|
4853
|
+
let code;
|
|
4854
|
+
if (!matchesAccept(file, patterns))
|
|
4855
|
+
code = 'TYPE_NOT_ALLOWED';
|
|
4856
|
+
else if (maxSize != null && file.size > maxSize)
|
|
4857
|
+
code = 'SIZE_EXCEEDED';
|
|
4858
|
+
else if (max != null && validCount >= max)
|
|
4859
|
+
code = 'MAX_FILES_EXCEEDED';
|
|
4860
|
+
else
|
|
4861
|
+
validCount++;
|
|
4862
|
+
added.push({ item: this._toItem(file, code), code });
|
|
4836
4863
|
}
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4864
|
+
if (!added.length)
|
|
4865
|
+
return;
|
|
4866
|
+
if (!this.multiple())
|
|
4867
|
+
this._entries().forEach(({ item }) => this._revokePreview(item));
|
|
4868
|
+
this._entries.set([...current, ...added]);
|
|
4869
|
+
const rejected = added.filter(e => e.code);
|
|
4870
|
+
for (const { item, code } of rejected)
|
|
4871
|
+
this.validationError.emit({ file: item.file, code: code, message: item.error });
|
|
4872
|
+
const valid = added.filter(e => !e.code).map(e => e.item);
|
|
4873
|
+
if (valid.length) {
|
|
4874
|
+
this.filesSelected.emit(valid);
|
|
4875
|
+
this.announcement.set(this.texts().fileInput.added(valid.length));
|
|
4840
4876
|
}
|
|
4841
4877
|
else {
|
|
4842
|
-
this.
|
|
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));
|
|
4878
|
+
this.announcement.set(rejected.map(e => `${e.item.name}: ${e.item.error}`).join('. '));
|
|
4850
4879
|
}
|
|
4880
|
+
this._commit();
|
|
4851
4881
|
}
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
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;
|
|
4882
|
+
/** Publica los ficheros válidos: model, formulario y estado de tocado. */
|
|
4883
|
+
_commit() {
|
|
4884
|
+
const files = this._validFiles();
|
|
4885
|
+
this.files.set(files);
|
|
4886
|
+
this._onChange(files);
|
|
4887
|
+
this._markTouched();
|
|
4878
4888
|
}
|
|
4879
|
-
|
|
4880
|
-
|
|
4889
|
+
/** Lista nueva a partir de un valor externo (writeValue o [files]); los rechazados se descartan. */
|
|
4890
|
+
_resetFrom(files) {
|
|
4891
|
+
const previous = this._entries();
|
|
4892
|
+
previous.filter(e => !files.includes(e.item.file)).forEach(({ item }) => this._revokePreview(item));
|
|
4893
|
+
this._entries.set(files.map(file => previous.find(e => e.item.file === file && !e.code) ?? { item: this._toItem(file) }));
|
|
4894
|
+
}
|
|
4895
|
+
_toItem(file, code) {
|
|
4896
|
+
const item = {
|
|
4881
4897
|
file,
|
|
4882
|
-
id: this.
|
|
4898
|
+
id: `${this.id}-file-${nextId++}`,
|
|
4883
4899
|
name: file.name,
|
|
4884
4900
|
size: file.size,
|
|
4885
4901
|
type: file.type,
|
|
4886
|
-
status: 'valid',
|
|
4902
|
+
status: code ? 'error' : 'valid',
|
|
4887
4903
|
};
|
|
4904
|
+
if (code)
|
|
4905
|
+
item.error = this._message(file, code);
|
|
4906
|
+
// URL temporal en vez de leer la imagen entera a base64; se libera al quitarla
|
|
4907
|
+
else if (this._isBrowser && file.type.startsWith('image/'))
|
|
4908
|
+
item.preview = URL.createObjectURL(file);
|
|
4909
|
+
return item;
|
|
4888
4910
|
}
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
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
|
-
});
|
|
4911
|
+
_revokePreview(item) {
|
|
4912
|
+
if (item.preview?.startsWith('blob:'))
|
|
4913
|
+
URL.revokeObjectURL(item.preview);
|
|
4907
4914
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
const
|
|
4914
|
-
|
|
4915
|
-
this.items.update(list => list.map(i => i.id === item.id ? { ...i, preview: reader.result } : i));
|
|
4916
|
-
};
|
|
4917
|
-
reader.readAsDataURL(item.file);
|
|
4915
|
+
_message(file, code) {
|
|
4916
|
+
const texts = this.texts().fileInput;
|
|
4917
|
+
switch (code) {
|
|
4918
|
+
case 'TYPE_NOT_ALLOWED': {
|
|
4919
|
+
// La extensión («.exe») se entiende mejor que el MIME («application/x-msdownload»)
|
|
4920
|
+
const dot = file.name.lastIndexOf('.');
|
|
4921
|
+
return texts.typeNotAllowed(dot > 0 ? file.name.slice(dot).toLowerCase() : file.type || file.name);
|
|
4918
4922
|
}
|
|
4923
|
+
case 'SIZE_EXCEEDED':
|
|
4924
|
+
return texts.tooLarge(formatFileSize(this.maxSize() ?? 0, this._locale));
|
|
4925
|
+
case 'MAX_FILES_EXCEEDED':
|
|
4926
|
+
return texts.maxFiles(this.maxFiles() ?? 1);
|
|
4919
4927
|
}
|
|
4920
4928
|
}
|
|
4921
|
-
|
|
4922
|
-
this.
|
|
4929
|
+
_bindControl(control) {
|
|
4930
|
+
this._controlSub?.unsubscribe();
|
|
4931
|
+
const submit$ = this._parentForm?.ngSubmit ?? EMPTY;
|
|
4932
|
+
this._controlSub = merge(control.events, submit$).subscribe(() => this._syncFormState());
|
|
4933
|
+
this._syncFormState();
|
|
4934
|
+
}
|
|
4935
|
+
_syncFormState() {
|
|
4936
|
+
const control = this._ngControl?.control;
|
|
4937
|
+
// Signals con el mismo valor no repintan: se puede llamar en cada ngDoCheck
|
|
4938
|
+
this.isRequired.set(control?.hasValidator(Validators.required) ?? false);
|
|
4939
|
+
this.showError.set(!!control && control.invalid && (control.touched || !!this._parentForm?.submitted));
|
|
4940
|
+
}
|
|
4941
|
+
_markTouched() {
|
|
4942
|
+
this._onTouched();
|
|
4943
|
+
this._syncFormState();
|
|
4923
4944
|
}
|
|
4924
4945
|
_flashDropConfirm() {
|
|
4925
4946
|
this.showDropConfirm.set(true);
|
|
4926
|
-
|
|
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)}`;
|
|
4947
|
+
clearTimeout(this._confirmTimer);
|
|
4948
|
+
this._confirmTimer = setTimeout(() => this.showDropConfirm.set(false), 450);
|
|
4933
4949
|
}
|
|
4934
4950
|
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 });
|
|
4951
|
+
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
4952
|
}
|
|
4943
4953
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NgxFileInput, decorators: [{
|
|
4944
4954
|
type: Component,
|
|
4945
|
-
args: [{ selector: 'ngx-file-input', imports: [MatButtonModule, MatIconModule, MatTooltipModule],
|
|
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()',
|
|
4955
|
+
args: [{ selector: 'ngx-file-input', imports: [MatButtonModule, MatIconModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
4956
|
+
class: 'ngx-file-input',
|
|
4954
4957
|
'[class.ngx-fi--default]': "variant() === 'default'",
|
|
4955
4958
|
'[class.ngx-fi--compact]': "variant() === 'compact'",
|
|
4956
4959
|
'[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
|
-
|
|
4960
|
+
'[class.ngx-fi--disabled]': 'isDisabled()',
|
|
4961
|
+
'[class.ngx-fi--dragover]': 'isDragOver()',
|
|
4962
|
+
'[class.ngx-fi--invalid]': 'showError()',
|
|
4963
|
+
'(dragover)': 'onDragOver($event)',
|
|
4964
|
+
'(dragleave)': 'onDragLeave($event)',
|
|
4965
|
+
'(drop)': 'onDrop($event)',
|
|
4966
|
+
}, 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"] }]
|
|
4967
|
+
}], 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
4968
|
|
|
4960
4969
|
/**
|
|
4961
4970
|
* Generated bundle index. Do not edit.
|