UNPKG

ngx-om-material-file-input

Version:
447 lines (436 loc) 17.7 kB
import * as i0 from '@angular/core'; import { Component, Optional, Self, Input, HostBinding, HostListener, InjectionToken, Pipe, Inject, NgModule } from '@angular/core'; import * as i1 from '@angular/cdk/a11y'; import { FocusMonitor } from '@angular/cdk/a11y'; import { MatFormFieldControl } from '@angular/material/form-field'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { Subject } from 'rxjs'; import * as i2 from '@angular/material/core'; import * as i3 from '@angular/forms'; /** * The files to be uploaded */ class FileInput { _files; delimiter; _fileNames; constructor(_files, delimiter = ', ') { this._files = _files; this.delimiter = delimiter; this._fileNames = (this._files || []).map((f) => f.name).join(delimiter); } get files() { return this._files || []; } get fileNames() { return this._fileNames; } } /** * Class that tracks the error state of a component. * @docs-private */ class ErrorStateTracker { _defaultMatcher; _parentFormGroup; _parentForm; _stateChanges; ngControl; errorState; matcher; constructor(_defaultMatcher, ngControl, _parentFormGroup, _parentForm, _stateChanges) { this._defaultMatcher = _defaultMatcher; this.ngControl = ngControl; this._parentFormGroup = _parentFormGroup; this._parentForm = _parentForm; this._stateChanges = _stateChanges; /** Whether the tracker is currently in an error state. */ this.errorState = false; } /** Updates the error state based on the provided error state matcher. */ updateErrorState() { const oldState = this.errorState; const parent = this._parentFormGroup || this._parentForm; const matcher = this.matcher || this._defaultMatcher; const control = this.ngControl ? this.ngControl.control : null; const newState = matcher?.isErrorState(control, parent) ?? false; if (newState !== oldState) { this.errorState = newState; this._stateChanges.next(); } } } function mixinErrorState(base) { return class extends base { /** Whether the component is in an error state. */ get errorState() { return this._getTracker().errorState; } set errorState(value) { this._getTracker().errorState = value; } /** An object used to control the error state of the component. */ get errorStateMatcher() { return this._getTracker().matcher; } set errorStateMatcher(value) { this._getTracker().matcher = value; } /** Updates the error state based on the provided error state matcher. */ updateErrorState() { this._getTracker().updateErrorState(); } _getTracker() { if (!this._tracker) { this._tracker = new ErrorStateTracker(this._defaultErrorStateMatcher, this.ngControl, this._parentFormGroup, this._parentForm, this.stateChanges); } return this._tracker; } constructor(...args) { super(...args); } }; } // Boilerplate for applying mixins to FileInput /** @docs-private */ class FileInputBase { _defaultErrorStateMatcher; _parentForm; _parentFormGroup; ngControl; stateChanges; constructor(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl, stateChanges) { this._defaultErrorStateMatcher = _defaultErrorStateMatcher; this._parentForm = _parentForm; this._parentFormGroup = _parentFormGroup; this.ngControl = ngControl; this.stateChanges = stateChanges; } } /** * Allows to use a custom ErrorStateMatcher with the file-input component */ const FileInputMixinBase = mixinErrorState(FileInputBase); class FileInputComponent extends FileInputMixinBase { fm; _elementRef; _renderer; _defaultErrorStateMatcher; ngControl; _parentForm; _parentFormGroup; static nextId = 0; focused = false; controlType = 'file-input'; autofilled = false; _placeholder; _required = false; _multiple; valuePlaceholder; accept = null; _errorStateMatcher; get errorStateMatcher() { return this._errorStateMatcher; } set errorStateMatcher(value) { this._errorStateMatcher = value; } id = `ngx-mat-file-input-${FileInputComponent.nextId++}`; describedBy = ''; setDescribedByIds(ids) { this.describedBy = ids.join(' '); } get value() { return this.empty ? null : new FileInput(this._elementRef.nativeElement.value || []); } set value(fileInput) { if (fileInput) { this.writeValue(fileInput); this.stateChanges.next(); } } get multiple() { return this._multiple; } set multiple(value) { this._multiple = coerceBooleanProperty(value); this.stateChanges.next(); } get placeholder() { return this._placeholder; } set placeholder(plh) { this._placeholder = plh; this.stateChanges.next(); } /** * Whether the current input has files */ get empty() { return !this._elementRef.nativeElement.value || this._elementRef.nativeElement.value.length === 0; } get shouldLabelFloat() { return this.focused || !this.empty || this.valuePlaceholder !== undefined; } get required() { return this._required; } set required(req) { this._required = coerceBooleanProperty(req); this.stateChanges.next(); } get isDisabled() { return this.disabled; } get disabled() { return this._elementRef.nativeElement.disabled; } set disabled(dis) { this.setDisabledState(coerceBooleanProperty(dis)); this.stateChanges.next(); } onContainerClick(event) { if (event.target.tagName.toLowerCase() !== 'input' && !this.disabled) { this._elementRef.nativeElement.querySelector('input').focus(); this.focused = true; this.open(); } } /** Whether the component is in an error state. */ _errorState = false; get errorState() { return this._errorState; } set errorState(value) { this._errorState = value; } /** * @see https://angular.io/api/forms/ControlValueAccessor */ constructor(fm, _elementRef, _renderer, _defaultErrorStateMatcher, ngControl, _parentForm, _parentFormGroup) { super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl, new Subject()); this.fm = fm; this._elementRef = _elementRef; this._renderer = _renderer; this._defaultErrorStateMatcher = _defaultErrorStateMatcher; this.ngControl = ngControl; this._parentForm = _parentForm; this._parentFormGroup = _parentFormGroup; if (this.ngControl != null) { this.ngControl.valueAccessor = this; } fm.monitor(_elementRef.nativeElement, true).subscribe((origin) => { this.focused = !!origin; this.stateChanges.next(); }); } _onChange = (_) => { }; _onTouched = () => { }; get fileNames() { return this.value ? this.value.fileNames : this.valuePlaceholder; } writeValue(obj) { this._renderer.setProperty(this._elementRef.nativeElement, 'value', obj instanceof FileInput ? obj.files : null); } registerOnChange(fn) { this._onChange = fn; } registerOnTouched(fn) { this._onTouched = fn; } /** * Remove all files from the file input component * @param [event] optional event that may have triggered the clear action */ clear(event) { if (event) { event.preventDefault(); event.stopPropagation(); } this.value = new FileInput([]); this._elementRef.nativeElement.querySelector('input').value = null; this._onChange(this.value); } change(event) { const fileList = event.target.files; const fileArray = []; if (fileList) { for (let i = 0; i < fileList.length; i++) { fileArray.push(fileList[i]); } } this.value = new FileInput(fileArray); this._onChange(this.value); } blur() { this.focused = false; this._onTouched(); } setDisabledState(isDisabled) { this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled); } ngOnInit() { this.multiple = coerceBooleanProperty(this.multiple); } open() { if (!this.disabled) { this._elementRef.nativeElement.querySelector('input').click(); } } ngOnDestroy() { this.stateChanges.complete(); this.fm.stopMonitoring(this._elementRef.nativeElement); } ngDoCheck() { if (this.ngControl) { // We need to re-evaluate this on every change detection cycle, because there are some // error triggers that we can't subscribe to (e.g. parent form submissions). This means // that whatever logic is in here has to be super lean or we risk destroying the performance. this.updateErrorState(); } } stateChanges = new Subject(); updateErrorState() { const parentSubmitted = this._parentFormGroup?.submitted || this._parentForm?.submitted; const touchedOrParentSubmitted = parentSubmitted; const newState = this.ngControl?.invalid && touchedOrParentSubmitted; if (this.errorState !== newState) { this.errorState = newState; this.stateChanges.next(); // Notify listeners of state changes. } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: FileInputComponent, deps: [{ token: i1.FocusMonitor }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i2.ErrorStateMatcher }, { token: i3.NgControl, optional: true, self: true }, { token: i3.NgForm, optional: true }, { token: i3.FormGroupDirective, optional: true }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.5", type: FileInputComponent, isStandalone: false, selector: "ngx-mat-file-input", inputs: { autofilled: "autofilled", valuePlaceholder: "valuePlaceholder", accept: "accept", _errorStateMatcher: "_errorStateMatcher", value: "value", multiple: "multiple", placeholder: "placeholder", required: "required", disabled: "disabled" }, host: { listeners: { "change": "change($event)", "focusout": "blur()" }, properties: { "id": "this.id", "attr.aria-describedby": "this.describedBy", "class.mat-form-field-should-float": "this.shouldLabelFloat", "class.file-input-disabled": "this.isDisabled" } }, providers: [{ provide: MatFormFieldControl, useExisting: FileInputComponent }], usesInheritance: true, ngImport: i0, template: "<input #input type=\"file\" [attr.multiple]=\"multiple? '' : null\" [attr.accept]=\"accept\">\r\n<span class=\"filename\" [title]=\"fileNames\">{{ fileNames }}</span>\r\n", styles: [":host{display:inline-block;width:100%}:host:not(.file-input-disabled){cursor:pointer}input{width:0;height:0;opacity:0;overflow:hidden;position:absolute;z-index:-1}.filename{display:inline-block;text-overflow:ellipsis;overflow:hidden;width:100%}\n"] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: FileInputComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-mat-file-input', providers: [{ provide: MatFormFieldControl, useExisting: FileInputComponent }], standalone: false, template: "<input #input type=\"file\" [attr.multiple]=\"multiple? '' : null\" [attr.accept]=\"accept\">\r\n<span class=\"filename\" [title]=\"fileNames\">{{ fileNames }}</span>\r\n", styles: [":host{display:inline-block;width:100%}:host:not(.file-input-disabled){cursor:pointer}input{width:0;height:0;opacity:0;overflow:hidden;position:absolute;z-index:-1}.filename{display:inline-block;text-overflow:ellipsis;overflow:hidden;width:100%}\n"] }] }], ctorParameters: () => [{ type: i1.FocusMonitor }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i2.ErrorStateMatcher }, { type: i3.NgControl, decorators: [{ type: Optional }, { type: Self }] }, { type: i3.NgForm, decorators: [{ type: Optional }] }, { type: i3.FormGroupDirective, decorators: [{ type: Optional }] }], propDecorators: { autofilled: [{ type: Input }], valuePlaceholder: [{ type: Input }], accept: [{ type: Input }], _errorStateMatcher: [{ type: Input }], id: [{ type: HostBinding }], describedBy: [{ type: HostBinding, args: ['attr.aria-describedby'] }], value: [{ type: Input }], multiple: [{ type: Input }], placeholder: [{ type: Input }], shouldLabelFloat: [{ type: HostBinding, args: ['class.mat-form-field-should-float'] }], required: [{ type: Input }], isDisabled: [{ type: HostBinding, args: ['class.file-input-disabled'] }], disabled: [{ type: Input }], change: [{ type: HostListener, args: ['change', ['$event']] }], blur: [{ type: HostListener, args: ['focusout'] }] } }); /** * Optional token to provide custom configuration to the module */ const NGX_MAT_FILE_INPUT_CONFIG = new InjectionToken('ngx-mat-file-input.config'); class ByteFormatPipe { config; unit; constructor(config) { this.config = config; this.unit = config ? config.sizeUnit : 'Byte'; } transform(value, args) { if (parseInt(value, 10) >= 0) { value = this.formatBytes(+value, +args); } return value; } formatBytes(bytes, decimals) { if (bytes === 0) { return '0 ' + this.unit; } const B = this.unit.charAt(0); const k = 1024; const dm = decimals || 2; const sizes = [this.unit, 'K' + B, 'M' + B, 'G' + B, 'T' + B, 'P' + B, 'E' + B, 'Z' + B, 'Y' + B]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: ByteFormatPipe, deps: [{ token: NGX_MAT_FILE_INPUT_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.5", ngImport: i0, type: ByteFormatPipe, isStandalone: false, name: "byteFormat" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: ByteFormatPipe, decorators: [{ type: Pipe, args: [{ name: 'byteFormat', standalone: false }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [NGX_MAT_FILE_INPUT_CONFIG] }] }] }); class MaterialFileInputModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: MaterialFileInputModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.5", ngImport: i0, type: MaterialFileInputModule, declarations: [FileInputComponent, ByteFormatPipe], exports: [FileInputComponent, ByteFormatPipe] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: MaterialFileInputModule, providers: [FocusMonitor] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: MaterialFileInputModule, decorators: [{ type: NgModule, args: [{ declarations: [FileInputComponent, ByteFormatPipe], providers: [FocusMonitor], exports: [FileInputComponent, ByteFormatPipe] }] }] }); class FileValidator { /** * Function to control content of files * * @param bytes max number of bytes allowed * * @returns */ static maxContentSize(bytes) { return (control) => { const size = control && control.value ? control.value.files.map(f => f.size).reduce((acc, i) => acc + i, 0) : 0; const condition = bytes >= size; return condition ? null : { maxContentSize: { actualSize: size, maxSize: bytes } }; }; } } // Module /** * Generated bundle index. Do not edit. */ export { ByteFormatPipe, FileInput, FileInputComponent, FileValidator, MaterialFileInputModule, NGX_MAT_FILE_INPUT_CONFIG }; //# sourceMappingURL=ngx-om-material-file-input.mjs.map