UNPKG

@progress/kendo-angular-upload

Version:

Kendo UI Angular Upload Component

439 lines (428 loc) 19 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Component, HostBinding, forwardRef, Renderer2, ViewChild, ElementRef, NgZone, ChangeDetectorRef, Injector, Input, Output, EventEmitter } from "@angular/core"; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n'; import { KendoInput, Keys, isDocumentAvailable } from '@progress/kendo-angular-common'; import { fromEvent, merge } from 'rxjs'; import { filter } from 'rxjs/operators'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from './package-metadata'; import { UploadService } from './upload.service'; import { NavigationService } from './navigation.service'; import { UPLOAD_CLASSES, hasClasses, isFocusable, IGNORE_TARGET_CLASSES, validateInitialFileSelectFile } from './common/util'; import { FileState } from './types/file-state'; import { DropZoneService } from './dropzone.service'; import { UploadFileSelectBase } from "./common/base"; import { FileListComponent } from "./rendering/file-list.component"; import { NgIf } from "@angular/common"; import { FileSelectDirective } from "./file-select.directive"; import { ButtonComponent } from "@progress/kendo-angular-buttons"; import { DropZoneInternalDirective } from "./dropzone-internal.directive"; import { LocalizedMessagesDirective } from "./localization/localized-messages.directive"; import * as i0 from "@angular/core"; import * as i1 from "./upload.service"; import * as i2 from "@progress/kendo-angular-l10n"; import * as i3 from "./navigation.service"; import * as i4 from "./dropzone.service"; /** * @hidden */ export const FILESELECT_VALUE_ACCESSOR = { multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FileSelectComponent) }; let idx = 0; export class FileSelectComponent extends UploadFileSelectBase { uploadService; localization; navigation; dropZoneService; ngZone; renderer; cdr; injector; fileSelectInput; get dir() { return this.direction; } /** * Sets the `name` attribute of the `input` element of the FileSelect. */ set name(name) { this.uploadService.async.saveField = name; } get name() { return this.uploadService.async.saveField; } /** * Fires when the value of the component has changed as a result of a successful `select` or `remove` operation. */ valueChange = new EventEmitter(); /** * @hidden */ _restrictions = { allowedExtensions: [], maxFileSize: 0, minFileSize: 0 }; direction; wrapper; fileListId; documentClick; // eslint-disable-line @typescript-eslint/ban-types blurSubscription; wrapperFocusSubscription; selectButtonFocusSubscription; localizationChangeSubscription; subs; constructor(uploadService, localization, navigation, dropZoneService, ngZone, renderer, cdr, wrapper, injector) { super(uploadService, navigation, cdr, injector, ngZone); this.uploadService = uploadService; this.localization = localization; this.navigation = navigation; this.dropZoneService = dropZoneService; this.ngZone = ngZone; this.renderer = renderer; this.cdr = cdr; this.injector = injector; validatePackage(packageMetadata); this.wrapper = wrapper.nativeElement; this.direction = localization.rtl ? 'rtl' : 'ltr'; this.navigation.computeKeys(); this.localizationChangeSubscription = localization.changes.subscribe(({ rtl }) => { this.direction = rtl ? 'rtl' : 'ltr'; }); this.subscribeBlur(); this.subscribeFocus(); this.attachEventHandlers(); this.setDefaultSettings(); } ngOnInit() { const { buttonId, fileListId } = this.getIds(); this.focusableId = buttonId; this.fileListId = fileListId; if (this.zoneId) { this.dropZoneService.addComponent(this, this.zoneId); } this.subs.add(this.renderer.listen(this.fileSelectInput.nativeElement, 'mouseenter', () => { this.renderer.addClass(this.fileSelectButton.nativeElement, 'k-hover'); })); this.subs.add(this.renderer.listen(this.fileSelectInput.nativeElement, 'mouseleave', () => { this.renderer.removeClass(this.fileSelectButton.nativeElement, 'k-hover'); })); this.ngZone.runOutsideAngular(() => { this.subs.add(this.renderer.listen(this.wrapper, 'keydown', event => this.handleKeydown(event))); }); } /** * @hidden */ textFor(key) { return this.localization.get(key); } ngOnDestroy() { this.fileList.clear(); if (this.blurSubscription) { this.blurSubscription.unsubscribe(); } if (this.wrapperFocusSubscription) { this.wrapperFocusSubscription.unsubscribe(); } if (this.selectButtonFocusSubscription) { this.selectButtonFocusSubscription.unsubscribe(); } if (this.localizationChangeSubscription) { this.localizationChangeSubscription.unsubscribe(); } if (this.subs) { this.subs.unsubscribe(); } } /** * Removes specific file from the file list. */ removeFileByUid(uid) { this.uploadService.removeFiles(uid); } /** * Visually clears all files from the UI. */ clearFiles() { this.uploadService.clearFiles(); } /** * @hidden * Used to determine if the component is empty. */ isEmpty() { return false; } /** * @hidden * Used by the external dropzone to add files to the FileSelect */ addFiles(files) { this.uploadService.addFiles(files); } /** * @hidden */ get selectButtonTabIndex() { return this.disabled ? undefined : this.tabindex; } /** * @hidden */ getIds() { const id = ++idx; const buttonId = `k-fileselect-button-${id}`; const fileListId = `k-fileselect-file-list-${id}`; return { buttonId, fileListId }; } /** * @hidden */ writeValue(newValue) { super.writeValue(newValue, validateInitialFileSelectFile, 'addInitialFileSelectFiles'); } subscribeBlur() { if (!isDocumentAvailable()) { return; } this.ngZone.runOutsideAngular(() => { this.documentClick = fromEvent(document, 'click').pipe(filter((event) => { return !(this.wrapper !== event.target && this.wrapper.contains(event.target)); })); this.blurSubscription = merge(this.documentClick, this.navigation.onTabOut).subscribe(() => { if (this.navigation.focused) { this.ngZone.run(() => { this.navigation.focused = false; this.onTouchedCallback(); this.onBlur.emit(); }); } }); }); } subscribeFocus() { this.wrapperFocusSubscription = this.navigation.onWrapperFocus.subscribe(() => { this.onFocus.emit(); }); this.selectButtonFocusSubscription = this.navigation.onSelectButtonFocus.subscribe(() => { this.fileSelectButton.nativeElement.focus(); }); } handleKeydown(event) { if (this.disabled) { return; } if (event.target === this.fileSelectButton.nativeElement && (event.keyCode === Keys.Enter || event.keyCode === Keys.Space)) { event.preventDefault(); this.fileSelectInput.nativeElement.click(); return; } if (hasClasses(event.target, UPLOAD_CLASSES) || (!isFocusable(event.target) && !hasClasses(event.target, IGNORE_TARGET_CLASSES))) { this.navigation.process(event, 'fileselect'); } } attachEventHandlers() { this.subs = this.uploadService.changeEvent.subscribe((files) => { let model = []; if (files !== null) { files.forEach((file) => { if (file.state === FileState.Initial) { model.push(file); } if (file.state === FileState.Selected && file.rawFile && !file.validationErrors) { model.push(file.rawFile); } }); } if (model.length === 0) { model = null; } this.onChangeCallback(model); this.valueChange.emit(model); }); this.subs.add(this.uploadService.removeEvent.subscribe((args) => { this.remove.emit(args); })); this.subs.add(this.uploadService.selectEvent.subscribe((args) => { this.select.emit(args); })); } setDefaultSettings() { this.uploadService.async.autoUpload = false; this.uploadService.component = 'FileSelect'; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FileSelectComponent, deps: [{ token: i1.UploadService }, { token: i2.LocalizationService }, { token: i3.NavigationService }, { token: i4.DropZoneService }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: FileSelectComponent, isStandalone: true, selector: "kendo-fileselect", inputs: { name: "name" }, outputs: { valueChange: "valueChange" }, host: { properties: { "attr.dir": "this.dir" } }, providers: [ LocalizationService, NavigationService, UploadService, DropZoneService, FILESELECT_VALUE_ACCESSOR, { provide: L10N_PREFIX, useValue: 'kendo.fileselect' }, { provide: KendoInput, useExisting: forwardRef(() => FileSelectComponent) } ], viewQueries: [{ propertyName: "fileSelectInput", first: true, predicate: ["fileSelectInput"], descendants: true, static: true }], exportAs: ["kendoFileSelect"], usesInheritance: true, ngImport: i0, template: ` <ng-container kendoFileSelectLocalizedMessages i18n-dropFilesHere="kendo.fileselect.dropFilesHere|The drop zone hint" dropFilesHere="Drop files here to select" i18n-invalidFileExtension="kendo.fileselect.invalidFileExtension|The text for the invalid allowed extensions restriction message" invalidFileExtension="File type not allowed." i18n-invalidMaxFileSize="kendo.fileselect.invalidMaxFileSize|The text for the invalid max file size restriction message" invalidMaxFileSize="File size too large." i18n-invalidMinFileSize="kendo.fileselect.invalidMinFileSize|The text for the invalid min file size restriction message" invalidMinFileSize="File size too small." i18n-remove="kendo.fileselect.remove|The text for the Remove button" remove="Remove" i18n-select="kendo.fileselect.select|The text for the Select button" select="Select files..." > </ng-container> <div kendoFileSelectInternalDropZone [restrictions]="restrictions" [multiple]="multiple" [disabled]="disabled"> <div class="k-upload-button-wrap"> <button kendoButton #fileSelectButton class="k-upload-button" type="button" role="button" (click)="fileSelectInput.click()" (focus)="onFileSelectButtonFocus()" [id]="focusableId" [attr.aria-label]="textFor('select')" [attr.tabindex]="tabindex" [attr.aria-expanded]="hasFileList" [attr.aria-controls]="hasFileList ? fileListId : undefined" > {{textFor('select')}} </button> <input kendoFileSelect #fileSelectInput [dir]="direction" [accept]="accept" [restrictions]="restrictions" [multiple]="multiple" [disabled]="disabled" [required]="isControlRequired" /> </div> <div class="k-dropzone-hint">{{textFor('dropFilesHere')}}</div> </div> <ul kendo-upload-file-list class="k-upload-files k-reset" *ngIf="hasFileList" [disabled]="disabled" [fileList]="fileList.files" [fileTemplate]="fileTemplate" [fileInfoTemplate]="fileInfoTemplate" [id]="fileListId"> </ul> `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective, selector: "\n [kendoUploadLocalizedMessages],\n [kendoFileSelectLocalizedMessages],\n [kendoUploadDropZoneLocalizedMessages]\n " }, { kind: "directive", type: DropZoneInternalDirective, selector: "\n [kendoUploadInternalDropZone],\n [kendoFileSelectInternalDropZone]\n ", inputs: ["disabled", "multiple", "restrictions"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: FileSelectDirective, selector: "[kendoFileSelect]", inputs: ["dir", "disabled", "multiple", "restrictions", "accept", "required"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FileListComponent, selector: "[kendo-upload-file-list]", inputs: ["disabled", "fileList", "fileTemplate", "fileInfoTemplate"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FileSelectComponent, decorators: [{ type: Component, args: [{ exportAs: 'kendoFileSelect', providers: [ LocalizationService, NavigationService, UploadService, DropZoneService, FILESELECT_VALUE_ACCESSOR, { provide: L10N_PREFIX, useValue: 'kendo.fileselect' }, { provide: KendoInput, useExisting: forwardRef(() => FileSelectComponent) } ], selector: 'kendo-fileselect', template: ` <ng-container kendoFileSelectLocalizedMessages i18n-dropFilesHere="kendo.fileselect.dropFilesHere|The drop zone hint" dropFilesHere="Drop files here to select" i18n-invalidFileExtension="kendo.fileselect.invalidFileExtension|The text for the invalid allowed extensions restriction message" invalidFileExtension="File type not allowed." i18n-invalidMaxFileSize="kendo.fileselect.invalidMaxFileSize|The text for the invalid max file size restriction message" invalidMaxFileSize="File size too large." i18n-invalidMinFileSize="kendo.fileselect.invalidMinFileSize|The text for the invalid min file size restriction message" invalidMinFileSize="File size too small." i18n-remove="kendo.fileselect.remove|The text for the Remove button" remove="Remove" i18n-select="kendo.fileselect.select|The text for the Select button" select="Select files..." > </ng-container> <div kendoFileSelectInternalDropZone [restrictions]="restrictions" [multiple]="multiple" [disabled]="disabled"> <div class="k-upload-button-wrap"> <button kendoButton #fileSelectButton class="k-upload-button" type="button" role="button" (click)="fileSelectInput.click()" (focus)="onFileSelectButtonFocus()" [id]="focusableId" [attr.aria-label]="textFor('select')" [attr.tabindex]="tabindex" [attr.aria-expanded]="hasFileList" [attr.aria-controls]="hasFileList ? fileListId : undefined" > {{textFor('select')}} </button> <input kendoFileSelect #fileSelectInput [dir]="direction" [accept]="accept" [restrictions]="restrictions" [multiple]="multiple" [disabled]="disabled" [required]="isControlRequired" /> </div> <div class="k-dropzone-hint">{{textFor('dropFilesHere')}}</div> </div> <ul kendo-upload-file-list class="k-upload-files k-reset" *ngIf="hasFileList" [disabled]="disabled" [fileList]="fileList.files" [fileTemplate]="fileTemplate" [fileInfoTemplate]="fileInfoTemplate" [id]="fileListId"> </ul> `, standalone: true, imports: [LocalizedMessagesDirective, DropZoneInternalDirective, ButtonComponent, FileSelectDirective, NgIf, FileListComponent] }] }], ctorParameters: function () { return [{ type: i1.UploadService }, { type: i2.LocalizationService }, { type: i3.NavigationService }, { type: i4.DropZoneService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.Injector }]; }, propDecorators: { fileSelectInput: [{ type: ViewChild, args: ['fileSelectInput', { static: true }] }], dir: [{ type: HostBinding, args: ['attr.dir'] }], name: [{ type: Input }], valueChange: [{ type: Output }] } });