UNPKG

ngx-dropzone-next

Version:

A highly configurable dropzone component for Angular.

523 lines (511 loc) 35 kB
import * as i0 from '@angular/core'; import { Directive, ChangeDetectionStrategy, Component, inject, input, booleanAttribute, output, HostBinding, HostListener, Injectable, Injector, contentChildren, computed, viewChild, numberAttribute, signal, DestroyRef, PendingTasks, ElementRef, afterNextRender, untracked } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; class NgxDropzoneLabelDirective { /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneLabelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.17", type: NgxDropzoneLabelDirective, isStandalone: true, selector: "ngx-dropzone-label", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneLabelDirective, decorators: [{ type: Directive, args: [{ selector: 'ngx-dropzone-label' }] }] }); class NgxDropzoneRemoveBadgeComponent { /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneRemoveBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: NgxDropzoneRemoveBadgeComponent, isStandalone: true, selector: "ngx-dropzone-remove-badge", ngImport: i0, template: ` <svg> <line x1="0" y1="0" x2="10" y2="10" /> <line x1="0" y1="10" x2="10" y2="0" /> </svg> `, isInline: true, styles: [":host{display:flex;justify-content:center;align-items:center;height:22px;width:22px;position:absolute;top:5px;right:5px;border-radius:50%;background:#bbb;color:#333;cursor:pointer}:host:hover{background:#aeaeae}:host>svg{height:10px;width:10px}:host>svg>line{stroke-width:2px;stroke:#fff}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneRemoveBadgeComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-dropzone-remove-badge', template: ` <svg> <line x1="0" y1="0" x2="10" y2="10" /> <line x1="0" y1="10" x2="10" y2="0" /> </svg> `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:flex;justify-content:center;align-items:center;height:22px;width:22px;position:absolute;top:5px;right:5px;border-radius:50%;background:#bbb;color:#333;cursor:pointer}:host:hover{background:#aeaeae}:host>svg{height:10px;width:10px}:host>svg>line{stroke-width:2px;stroke:#fff}\n"] }] }] }); class NgxDropzonePreviewComponent { constructor() { this.hostStyles = inject(DomSanitizer).bypassSecurityTrustStyle(` display: flex; height: 140px; min-height: 140px; min-width: 180px; max-width: 180px; justify-content: center; align-items: center; padding: 0 20px; margin: 10px; border-radius: 5px; position: relative; `); /** The file to preview. */ this.file = input(); /** Allow the user to remove files. */ this.removable = input(false, { transform: booleanAttribute }); /** Emitted when the element should be removed. */ this.removed = output(); /** Make the preview item focusable using the tab key. */ this.tabIndex = 0; } keyEvent(event) { if ( // Backspace event.keyCode === 8 || // Delete event.keyCode === 46) { this.remove(); } } /** Remove method to be used from the template. */ _remove(event) { event.stopPropagation(); this.remove(); } /** Remove the preview item (use from component code). */ remove() { if (this.removable()) { this.removed.emit(this.file()); } } async readFile() { return new Promise((resolve, reject) => { const file = this.file(); const reader = new FileReader(); const cleanup = () => { reader.removeEventListener('load', onLoad); reader.removeEventListener('error', onError); }; const onLoad = () => { cleanup(); resolve(reader.result); }; const onError = (error) => { cleanup(); reject(error); }; reader.addEventListener('load', onLoad); reader.addEventListener('error', onError); if (!file) { return reject('No file to read. Please provide a file using the [file] Input property.'); } reader.readAsDataURL(file); }); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzonePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: NgxDropzonePreviewComponent, isStandalone: true, selector: "ngx-dropzone-preview", inputs: { file: { classPropertyName: "file", publicName: "file", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed" }, host: { listeners: { "keyup": "keyEvent($event)" }, properties: { "style": "hostStyles", "tabindex": "this.tabIndex" } }, ngImport: i0, template: "<ng-content select=\"ngx-dropzone-label\"></ng-content>\n@if (removable()) {\n <ngx-dropzone-remove-badge (click)=\"_remove($event)\" />\n}\n", styles: [":host{background-image:linear-gradient(to top,#ededed,#efefef,#f1f1f1,#f4f4f4,#f6f6f6)}:host:hover,:host:focus{background-image:linear-gradient(to top,#e3e3e3,#ebeaea,#e8e7e7,#ebeaea,#f4f4f4);outline:0}:host:hover ngx-dropzone-remove-badge,:host:focus ngx-dropzone-remove-badge{opacity:1}:host ngx-dropzone-remove-badge{opacity:0}:host ::ng-deep ngx-dropzone-label{overflow-wrap:break-word}\n"], dependencies: [{ kind: "component", type: NgxDropzoneRemoveBadgeComponent, selector: "ngx-dropzone-remove-badge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzonePreviewComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-dropzone-preview', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxDropzoneRemoveBadgeComponent], host: { /** We use the HostBinding to pass these common styles to child components. */ '[style]': 'hostStyles', }, template: "<ng-content select=\"ngx-dropzone-label\"></ng-content>\n@if (removable()) {\n <ngx-dropzone-remove-badge (click)=\"_remove($event)\" />\n}\n", styles: [":host{background-image:linear-gradient(to top,#ededed,#efefef,#f1f1f1,#f4f4f4,#f6f6f6)}:host:hover,:host:focus{background-image:linear-gradient(to top,#e3e3e3,#ebeaea,#e8e7e7,#ebeaea,#f4f4f4);outline:0}:host:hover ngx-dropzone-remove-badge,:host:focus ngx-dropzone-remove-badge{opacity:1}:host ngx-dropzone-remove-badge{opacity:0}:host ::ng-deep ngx-dropzone-label{overflow-wrap:break-word}\n"] }] }], propDecorators: { keyEvent: [{ type: HostListener, args: ['keyup', ['$event']] }], tabIndex: [{ type: HostBinding, args: ['tabindex'] }] } }); /** * This service contains the filtering logic to be applied to * any dropped or selected file. If a file matches all criteria * like maximum size or accept type, it will be emitted in the * addedFiles array, otherwise in the rejectedFiles array. */ class NgxDropzoneService { parseFileList(files, accept, maxFileSize, multiple) { const addedFiles = []; const rejectedFiles = []; for (let i = 0; i < files.length; i++) { const file = files.item(i); if (!this.isAccepted(file, accept)) { this.rejectFile(rejectedFiles, file, 'type'); continue; } if (maxFileSize && file.size > maxFileSize) { this.rejectFile(rejectedFiles, file, 'size'); continue; } if (!multiple && addedFiles.length >= 1) { this.rejectFile(rejectedFiles, file, 'no_multiple'); continue; } addedFiles.push(file); } const result = { addedFiles, rejectedFiles, }; return result; } isAccepted(file, accept) { if (accept === '*') { return true; } // Sanitize filename first const safeName = this.sanitizeFilename(file.name); const filename = safeName.toLowerCase(); const filetype = file.type.toLowerCase(); const acceptFiletypes = accept .split(',') .map((it) => it.toLowerCase().trim()) .filter((it) => it.length > 0); // Remove empty strings return acceptFiletypes.some((acceptFiletype) => { // Wildcard MIME (more restrictive). if (acceptFiletype.endsWith('/*')) { const [acceptMain] = acceptFiletype.split('/'); const [fileMain] = filetype.split('/'); return acceptMain === fileMain && acceptMain.length > 0; } // Extension check - get LAST extension only. if (acceptFiletype.startsWith('.')) { const lastDot = filename.lastIndexOf('.'); if (lastDot === -1) return false; const ext = filename.substring(lastDot); return ext === acceptFiletype; } // Exact MIME match return acceptFiletype === filetype; }); } sanitizeFilename(name) { return name .replace(/[^a-zA-Z0-9._-]/g, '_') // Remove special chars .replace(/\.{2,}/g, '.') // No ".." .replace(/\0/g, '') // Remove null bytes .substring(0, 255); // Limit length } rejectFile(rejectedFiles, file, reason) { const rejectedFile = file; rejectedFile.reason = reason; rejectedFiles.push(rejectedFile); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); async function onDropImpl(ctx, dataTransfer) { const { files, items } = dataTransfer; // if processDirectoryDrop is not enabled or webkitGetAsEntry is not supported we handle the drop as usual if (!ctx.processDirectoryDrop() || !DataTransferItem.prototype.webkitGetAsEntry) { handleFileDrop(ctx, files); // if processDirectoryDrop is enabled and webkitGetAsEntry is supported we can extract files from a dropped directory } else if (items.length > 0) { const droppedFiles = []; const droppedDirectories = []; // Separate dropped files from dropped directories for (let index = 0; index < items.length; index++) { const entry = items[index].webkitGetAsEntry(); if (!entry) { continue; } if (entry.isFile) { // Get File directly from entry instead of relying on index mapping. const file = items[index].getAsFile(); if (file) { droppedFiles.push(file); } } else if (entry.isDirectory) { droppedDirectories.push(entry); } } const droppedFilesList = new DataTransfer(); for (const file of droppedFiles) { // Safari requires actual File objects, not Blobs. if (file instanceof File) { droppedFilesList.items.add(file); } } // if no directory is dropped we are done and can call handleFileDrop if (!droppedDirectories.length && droppedFilesList.items.length) { handleFileDrop(ctx, droppedFilesList.files); } // if directories are dropped we extract the files from these directories one-by-one and add it to droppedFilesList if (droppedDirectories.length) { const extractFilesFromDirectoryCalls = []; for (const droppedDirectory of droppedDirectories) { extractFilesFromDirectoryCalls.push(extractFilesFromDirectory(droppedDirectory)); } // wait for all directories to be proccessed to add the extracted files afterwards await Promise.all(extractFilesFromDirectoryCalls).then((allExtractedFiles) => { allExtractedFiles .reduce((a, b) => [...a, ...b]) .forEach((extractedFile) => { droppedFilesList.items.add(extractedFile); }); }); handleFileDrop(ctx, droppedFilesList.files); } } } async function getFileFromFileEntry(entry) { try { const fileSystemFileEntry = entry; return await new Promise((resolve, reject) => fileSystemFileEntry.file(resolve, reject)); } catch (error) { if (typeof ngDevMode !== 'undefined' && ngDevMode) { console.log('Error converting a fileEntry to a File: ', error); } return null; } } function extractFilesFromDirectory(directory) { return new Promise((resolve) => { const files = []; const reader = directory.createReader(); // we need this to be a recursion because of this issue: https://bugs.chromium.org/p/chromium/issues/detail?id=514087 const readEntries = () => { reader.readEntries(async (entries) => { if (entries.length > 0) { for (const entry of entries.filter(({ isFile }) => isFile)) { const file = await getFileFromFileEntry(entry); file && files.push(file); } readEntries(); } else { resolve(files); } }); }; readEntries(); }); } function handleFileDrop(ctx, files) { const service = ctx.injector.get(NgxDropzoneService); const result = service.parseFileList(files, ctx.accept(), ctx.maxFileSize(), ctx.multiple()); ctx.change.emit({ addedFiles: result.addedFiles, rejectedFiles: result.rejectedFiles, source: ctx, }); } class NgxDropzoneComponent { constructor() { this.injector = inject(Injector); /** A list of the content-projected preview children. */ this.previewChildren = contentChildren(NgxDropzonePreviewComponent, { descendants: true, }); this.hasPreviews = computed(() => !!this.previewChildren().length); /** A template reference to the native file input element. */ this.fileInput = viewChild.required('fileInput'); /** Emitted when any files were added or rejected. */ this.change = output(); /** Set the accepted file types. Defaults to '*'. */ this.accept = input('*'); /** Disable any user interaction with the component. */ this.disabled = input(false, { transform: booleanAttribute }); /** Allow the selection of multiple files. */ this.multiple = input(true, { transform: booleanAttribute }); /** Set the maximum size a single file may have. */ this.maxFileSize = input(undefined, { transform: numberAttribute }); /** Allow the dropzone container to expand vertically. */ this.expandable = input(false, { transform: booleanAttribute }); /** Open the file selector on click. */ this.disableClick = input(false, { transform: booleanAttribute }); /** Allow dropping directories. */ this.processDirectoryDrop = input(false, { transform: booleanAttribute }); /** Expose the id, aria-label, aria-labelledby and aria-describedby of the native file input for proper accessibility. */ this.id = input(''); this.ariaLabel = input('', { alias: 'aria-label' }); this.ariaLabelledby = input('', { alias: 'aria-labelledby' }); this.ariaDescribedBy = input('', { alias: 'aria-describedby' }); this.isHovered = signal(false); this._destroyRef = inject(DestroyRef); this._pendingTasks = inject(PendingTasks); this._host = inject(ElementRef); // Handles files selected via <input type="file">. this._onFilesSelected = (event) => { const target = event.target; const files = target.files; // fix(#32): Prevent the default event behaviour which caused the change event to emit twice. event.preventDefault(); event.stopPropagation(); if (files === null || files.length === 0) { return; } try { handleFileDrop(this, files); } finally { target.value = ''; } }; if (typeof ngServerMode === 'undefined' || ngServerMode) { return; } afterNextRender(() => this._setupEventListeners()); } ngOnChanges(changes) { if (changes['disabled'] && this.isHovered()) { this.isHovered.set(false); } } showFileSelector() { if (!this.disabled()) { this.fileInput().nativeElement.click(); } } _setupEventListeners() { const host = this._host.nativeElement; const fileInput = this.fileInput().nativeElement; const onClick = () => { if (!this.disableClick()) { this.showFileSelector(); } }; // Dragover - prevents default to allow drop & shows hover state. const onDragover = (event) => { if (this.disabled()) { return; } event.preventDefault(); event.stopPropagation(); this.isHovered.set(true); }; // Dragleave - removes hover state when drag leaves element. const onDragleave = (event) => { const target = event.currentTarget; const related = event.relatedTarget; if (target && (!related || !target.contains(related))) { this.isHovered.set(false); } }; const onDrop = (event) => { if (this.disabled()) { return; } // fix(#32): Prevent the default event behaviour which caused the change event to emit twice. event.preventDefault(); event.stopPropagation(); this.isHovered.set(false); const { dataTransfer } = event; if (dataTransfer === null) { return; } this._pendingTasks.run(() => onDropImpl(this, dataTransfer)); }; // Manual event listeners to avoid Angular's change detection overhead. host.addEventListener('click', onClick); // Show the native OS file explorer to select files. host.addEventListener('dragover', onDragover); host.addEventListener('dragleave', onDragleave); host.addEventListener('drop', onDrop); fileInput.addEventListener('change', this._onFilesSelected); this._destroyRef.onDestroy(() => { host.removeEventListener('click', onClick); host.removeEventListener('dragover', onDragover); host.removeEventListener('dragleave', onDragleave); host.removeEventListener('drop', onDrop); fileInput.removeEventListener('change', this._onFilesSelected); }); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: NgxDropzoneComponent, isStandalone: true, selector: "ngx-dropzone, [ngx-dropzone]", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, disableClick: { classPropertyName: "disableClick", publicName: "disableClick", isSignal: true, isRequired: false, transformFunction: null }, processDirectoryDrop: { classPropertyName: "processDirectoryDrop", publicName: "processDirectoryDrop", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change" }, host: { properties: { "class.ngx-dz-hovered": "isHovered()", "class.unclickable": "disableClick()", "class.expandable": "expandable()", "class.ngx-dz-disabled": "disabled()" } }, queries: [{ propertyName: "previewChildren", predicate: NgxDropzonePreviewComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<input\n #fileInput\n type=\"file\"\n [id]=\"id()\"\n [multiple]=\"multiple()\"\n [accept]=\"accept()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n/>\n\n@if (!hasPreviews()) {\n <ng-content select=\"ngx-dropzone-label\" />\n}\n\n<ng-content select=\"ngx-dropzone-preview\" />\n<ng-content />\n", styles: [":host{display:flex;align-items:center;height:180px;background:#fff;cursor:pointer;color:#717386;border:2px dashed #717386;border-radius:5px;font-size:16px}:host.ngx-dz-hovered{border-style:solid}:host.ngx-dz-disabled{opacity:.5;cursor:no-drop;pointer-events:none}:host{overflow-x:auto}:host.expandable{overflow:hidden;height:unset;min-height:180px;flex-wrap:wrap}:host.unclickable{cursor:default}:host ::ng-deep ngx-dropzone-label{text-align:center;z-index:10;margin:10px auto}:host input{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute;z-index:-1}:host input:focus+::ng-deep ngx-dropzone-label{outline:1px dotted #000;outline:-webkit-focus-ring-color auto 5px}\n"] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-dropzone, [ngx-dropzone]', host: { '[class.ngx-dz-hovered]': 'isHovered()', '[class.unclickable]': 'disableClick()', '[class.expandable]': 'expandable()', '[class.ngx-dz-disabled]': 'disabled()', }, template: "<input\n #fileInput\n type=\"file\"\n [id]=\"id()\"\n [multiple]=\"multiple()\"\n [accept]=\"accept()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n/>\n\n@if (!hasPreviews()) {\n <ng-content select=\"ngx-dropzone-label\" />\n}\n\n<ng-content select=\"ngx-dropzone-preview\" />\n<ng-content />\n", styles: [":host{display:flex;align-items:center;height:180px;background:#fff;cursor:pointer;color:#717386;border:2px dashed #717386;border-radius:5px;font-size:16px}:host.ngx-dz-hovered{border-style:solid}:host.ngx-dz-disabled{opacity:.5;cursor:no-drop;pointer-events:none}:host{overflow-x:auto}:host.expandable{overflow:hidden;height:unset;min-height:180px;flex-wrap:wrap}:host.unclickable{cursor:default}:host ::ng-deep ngx-dropzone-label{text-align:center;z-index:10;margin:10px auto}:host input{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute;z-index:-1}:host input:focus+::ng-deep ngx-dropzone-label{outline:1px dotted #000;outline:-webkit-focus-ring-color auto 5px}\n"] }] }], ctorParameters: () => [] }); class NgxDropzoneImagePreviewComponent extends NgxDropzonePreviewComponent { constructor() { super(); /** The image data source. */ this.defaultImgLoading = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiByZ2IoMjQxLCAyNDIsIDI0Mykgbm9uZSByZXBlYXQgc2Nyb2xsIDAlIDAlOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjIyNHB4IiBoZWlnaHQ9IjIyNHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPgo8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSIxNCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2U9IiM4NWEyYjYiIHN0cm9rZS1kYXNoYXJyYXk9IjIxLjk5MTE0ODU3NTEyODU1MiAyMS45OTExNDg1NzUxMjg1NTIiIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIGR1cj0iMS4xNjI3OTA2OTc2NzQ0MTg0cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGtleVRpbWVzPSIwOzEiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiPjwvYW5pbWF0ZVRyYW5zZm9ybT4KPC9jaXJjbGU+CjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjEwIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZT0iI2JiY2VkZCIgc3Ryb2tlLWRhc2hhcnJheT0iMTUuNzA3OTYzMjY3OTQ4OTY2IDE1LjcwNzk2MzI2Nzk0ODk2NiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjE1LjcwNzk2MzI2Nzk0ODk2NiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj4KICA8YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgZHVyPSIxLjE2Mjc5MDY5NzY3NDQxODRzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIga2V5VGltZXM9IjA7MSIgdmFsdWVzPSIwIDUwIDUwOy0zNjAgNTAgNTAiPjwvYW5pbWF0ZVRyYW5zZm9ybT4KPC9jaXJjbGU+CjwhLS0gW2xkaW9dIGdlbmVyYXRlZCBieSBodHRwczovL2xvYWRpbmcuaW8vIC0tPjwvc3ZnPg=='; this.imageSrc = signal(inject(DomSanitizer).bypassSecurityTrustUrl(this.defaultImgLoading)); /** The file to preview. */ this.file = input(); afterNextRender(() => { this.renderImage(); }); } ngOnChanges(changes) { if (changes['file']) { this.renderImage(); } } renderImage() { this.readFile() .then((img) => setTimeout(() => this.imageSrc.set(img))) .catch((err) => console.error(err)); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneImagePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: NgxDropzoneImagePreviewComponent, isStandalone: true, selector: "ngx-dropzone-image-preview", inputs: { file: { classPropertyName: "file", publicName: "file", isSignal: true, isRequired: false, transformFunction: null } }, providers: [ { provide: NgxDropzonePreviewComponent, useExisting: NgxDropzoneImagePreviewComponent, }, ], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<img [src]=\"imageSrc()\" />\n<ng-content select=\"ngx-dropzone-label\"></ng-content>\n@if (removable()) {\n <ngx-dropzone-remove-badge (click)=\"_remove($event)\" />\n}\n", styles: [":host{min-width:unset!important;max-width:unset!important;padding:0!important}:host:hover img,:host:focus img{opacity:.7}:host:hover ngx-dropzone-remove-badge,:host:focus ngx-dropzone-remove-badge{opacity:1}:host ngx-dropzone-remove-badge{opacity:0}:host img{max-height:100%;border-radius:5px;opacity:.8}:host ::ng-deep ngx-dropzone-label{position:absolute;overflow-wrap:break-word}\n"], dependencies: [{ kind: "component", type: NgxDropzoneRemoveBadgeComponent, selector: "ngx-dropzone-remove-badge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneImagePreviewComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-dropzone-image-preview', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxDropzoneRemoveBadgeComponent], providers: [ { provide: NgxDropzonePreviewComponent, useExisting: NgxDropzoneImagePreviewComponent, }, ], template: "<img [src]=\"imageSrc()\" />\n<ng-content select=\"ngx-dropzone-label\"></ng-content>\n@if (removable()) {\n <ngx-dropzone-remove-badge (click)=\"_remove($event)\" />\n}\n", styles: [":host{min-width:unset!important;max-width:unset!important;padding:0!important}:host:hover img,:host:focus img{opacity:.7}:host:hover ngx-dropzone-remove-badge,:host:focus ngx-dropzone-remove-badge{opacity:1}:host ngx-dropzone-remove-badge{opacity:0}:host img{max-height:100%;border-radius:5px;opacity:.8}:host ::ng-deep ngx-dropzone-label{position:absolute;overflow-wrap:break-word}\n"] }] }], ctorParameters: () => [] }); class NgxDropzoneVideoPreviewComponent extends NgxDropzonePreviewComponent { constructor() { super(); /** The video data source. */ this._sanitizedVideoSrc = signal(null); const destroyRef = inject(DestroyRef); const sanitizer = inject(DomSanitizer); afterNextRender(() => { const file = untracked(this.file); if (!file) return; /** * We sanitize the URL here to enable the preview. * Please note that this could cause security issues! **/ const videoObjectUrl = URL.createObjectURL(file); this._sanitizedVideoSrc.set(sanitizer.bypassSecurityTrustUrl(videoObjectUrl)); destroyRef.onDestroy(() => URL.revokeObjectURL(videoObjectUrl)); }); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneVideoPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: NgxDropzoneVideoPreviewComponent, isStandalone: true, selector: "ngx-dropzone-video-preview", providers: [ { provide: NgxDropzonePreviewComponent, useExisting: NgxDropzoneVideoPreviewComponent, }, ], usesInheritance: true, ngImport: i0, template: "@if (_sanitizedVideoSrc(); as sanitizedVideoSrc) {\n <video controls (click)=\"$event.stopPropagation()\">\n <source [src]=\"sanitizedVideoSrc\" />\n </video>\n}\n\n<ng-content select=\"ngx-dropzone-label\"></ng-content>\n\n@if (removable()) {\n <ngx-dropzone-remove-badge (click)=\"_remove($event)\" />\n}\n", styles: [":host{min-width:unset!important;max-width:unset!important;padding:0!important}:host:hover video,:host:focus video{opacity:.7}:host:hover ngx-dropzone-remove-badge,:host:focus ngx-dropzone-remove-badge{opacity:1}:host ngx-dropzone-remove-badge{opacity:0}:host video{max-height:100%;border-radius:5px}:host ::ng-deep ngx-dropzone-label{position:absolute;overflow-wrap:break-word}\n"], dependencies: [{ kind: "component", type: NgxDropzoneRemoveBadgeComponent, selector: "ngx-dropzone-remove-badge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: NgxDropzoneVideoPreviewComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-dropzone-video-preview', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxDropzoneRemoveBadgeComponent], providers: [ { provide: NgxDropzonePreviewComponent, useExisting: NgxDropzoneVideoPreviewComponent, }, ], template: "@if (_sanitizedVideoSrc(); as sanitizedVideoSrc) {\n <video controls (click)=\"$event.stopPropagation()\">\n <source [src]=\"sanitizedVideoSrc\" />\n </video>\n}\n\n<ng-content select=\"ngx-dropzone-label\"></ng-content>\n\n@if (removable()) {\n <ngx-dropzone-remove-badge (click)=\"_remove($event)\" />\n}\n", styles: [":host{min-width:unset!important;max-width:unset!important;padding:0!important}:host:hover video,:host:focus video{opacity:.7}:host:hover ngx-dropzone-remove-badge,:host:focus ngx-dropzone-remove-badge{opacity:1}:host ngx-dropzone-remove-badge{opacity:0}:host video{max-height:100%;border-radius:5px}:host ::ng-deep ngx-dropzone-label{position:absolute;overflow-wrap:break-word}\n"] }] }], ctorParameters: () => [] }); /* * Public API Surface of ngx-dropzone-next */ /** * Generated bundle index. Do not edit. */ export { NgxDropzoneComponent, NgxDropzoneImagePreviewComponent, NgxDropzoneLabelDirective, NgxDropzonePreviewComponent, NgxDropzoneRemoveBadgeComponent, NgxDropzoneVideoPreviewComponent }; //# sourceMappingURL=ngx-dropzone-next.mjs.map