ngx-dropzone-next
Version:
A highly configurable dropzone component for Angular.
523 lines (511 loc) • 35 kB
JavaScript
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