@deeby/dottmoon-media-dropzone
v0.10.1
Published
A standalone Angular library for file uploads with drag & drop, paste, and configurable validation for images, videos, and more
Maintainers
Readme
@deeby/dottmoon-media-dropzone
A standalone Angular 21+ library for file uploads with drag & drop, paste from clipboard, and click-to-browse functionality. Includes built-in validation, preview generation, and full Reactive Forms support.
✨ Features
- 📤 Drag & Drop — Visual drop zone with hover feedback
- 📋 Paste from Clipboard — Ctrl+V/Cmd+V to paste images directly
- 🖱️ Click to Browse — Traditional file picker support
- ✂️ Image Crop & Rotate — Built-in canvas-based crop tool with rotation (NEW in v0.0.9)
- 📁 Multi-file Support — Single or multiple file uploads
- 🖼️ Auto Previews — Image and video thumbnails
- ✅ File Validation — Type, size, dimensions, and duration checks
- 🔄 Reactive Forms — Full
ControlValueAccessorimplementation - 🎨 Standalone Component — No UI framework dependencies
- 🌍 i18n Ready — Fully customizable labels
- 📦 Tree-shakeable — ESM modules for optimal bundle size
📥 Installation
npm install @deeby/dottmoon-media-dropzoneRequirements: Angular 21+
🚀 Quick Start
Basic Usage
import { Component } from '@angular/core';
import { MediaDropzoneComponent } from '@deeby/dottmoon-media-dropzone';
@Component({
selector: 'app-upload',
standalone: true,
imports: [MediaDropzoneComponent],
template: `
<lib-media-dropzone
[config]="uploadConfig"
(filesSelected)="onFilesSelected($event)"
/>
`
})
export class UploadComponent {
uploadConfig = {
accept: 'image/*',
maxFileSize: 5_000_000, // 5MB
showPreview: true
};
onFilesSelected(files: MediaFile[]) {
console.log('Selected files:', files);
// Files include preview URLs ready for display/upload
}
}Reactive Forms Integration
import { Component } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { MediaDropzoneComponent, MediaFile } from '@deeby/dottmoon-media-dropzone';
@Component({
selector: 'app-form',
standalone: true,
imports: [ReactiveFormsModule, MediaDropzoneComponent],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<lib-media-dropzone
formControlName="profileImage"
[config]="uploadConfig"
/>
<button type="submit" [disabled]="form.invalid">Submit</button>
</form>
`
})
export class FormComponent {
form = this.fb.group({
username: ['', Validators.required],
profileImage: [null, Validators.required]
});
uploadConfig = {
accept: 'image/*',
maxFileSize: 10_000_000,
showPreview: true
};
constructor(private fb: FormBuilder) {}
onSubmit() {
if (this.form.valid) {
const mediaFile = this.form.value.profileImage as MediaFile;
// Use mediaFile.preview for display or mediaFile.file for upload
console.log('Form data:', this.form.value);
}
}
}📖 API Reference
Component Selector
<lib-media-dropzone />Inputs
@Input() config: Partial<MediaDropzoneConfig>
Configuration object for the dropzone. All properties are optional.
Outputs
| Output | Type | Description |
|--------|------|-------------|
| filesSelected | Output<MediaFile[]> | Emits when files are selected (after validation) |
| fileRemoved | Output<MediaFile> | Emits when a file is removed |
| validationError | Output<string[]> | Emits validation error messages |
⚙️ Configuration Options
MediaDropzoneConfig
interface MediaDropzoneConfig {
// File Type Configuration
accept?: string; // MIME types (e.g., 'image/*,video/*')
allowedExtensions?: string[]; // Specific extensions (e.g., ['.jpg', '.png'])
// Size Limits
maxFileSize?: number; // Max file size in bytes (default: 10485760 = 10MB)
maxTotalSize?: number; // Total size limit for all files combined
maxFiles?: number; // Max number of files (default: 1)
// Image Validation
maxImageWidth?: number; // Max width in pixels
maxImageHeight?: number; // Max height in pixels
// Crop & Rotation (NEW in v0.0.9)
enableCrop?: boolean; // Enable crop modal for images (default: false)
cropAspectRatio?: number; // Lock aspect ratio (e.g., 16/9, 1, 4/3)
cropOutputWidth?: number; // Final output width after crop
cropOutputHeight?: number; // Final output height after crop
enableRotation?: boolean; // Enable 90° rotation in crop modal (default: true when crop enabled)
// Video Validation
maxVideoDuration?: number; // Max duration in seconds
// Features
enableDragDrop?: boolean; // Enable drag & drop (default: true)
enablePaste?: boolean; // Enable paste from clipboard (default: true)
enableClick?: boolean; // Enable click to browse (default: true)
// Display Options
showPreview?: boolean; // Show file previews (default: true)
showFileSize?: boolean; // Show file size (default: true)
showProgressBar?: boolean; // Show progress bar (default: true)
// Output Format
outputFormat?: 'file' | 'base64' | 'both'; // Default: 'both'
// Internationalization
labels?: Partial<MediaDropzoneLabels>;
}Output Formats
'file'— Returns nativeFileobjects'base64'— Returns base64-encoded preview strings'both'(default) — ReturnsMediaFileobjects with both
MediaFile Interface
interface MediaFile {
id: string; // Unique identifier
file: File; // Original File object
preview?: string; // Base64 preview URL (for images/videos)
type: 'image' | 'video' | 'other'; // Detected file type
isValid: boolean; // Validation result
errors: string[]; // Validation error messages (if any)
progress: number; // Upload progress (0-100)
}MediaDropzoneLabels (i18n)
interface MediaDropzoneLabels {
dropHere: string; // "Drop files here"
orClickToUpload: string; // "or click to upload"
pasteFromClipboard: string; // "Paste from clipboard (Ctrl+V)"
remove: string; // "Remove"
invalidFileType: string; // "Invalid file type"
fileTooLarge: string; // "File is too large"
imageTooWide: string; // "Image width exceeds limit"
imageTooTall: string; // "Image height exceeds limit"
videoTooLong: string; // "Video duration exceeds limit"
maxFilesReached: string; // "Maximum number of files reached"
totalSizeExceeded: string; // "Total file size exceeds limit"
}💡 Usage Examples
Single Image Upload
uploadConfig = {
accept: 'image/*',
maxFileSize: 5_000_000, // 5MB
maxFiles: 1,
showPreview: true
};Multiple Files with Size Limit
<lib-media-dropzone
[config]="{
accept: 'image/*,application/pdf',
maxFiles: 5,
maxFileSize: 10_000_000,
maxTotalSize: 50_000_000
}"
(filesSelected)="handleFiles($event)"
/>Video with Duration Limit
uploadConfig = {
accept: 'video/*',
maxFileSize: 100_000_000, // 100MB
maxVideoDuration: 120, // 2 minutes
showPreview: true
};Image with Dimension Constraints
uploadConfig = {
accept: 'image/*',
maxImageWidth: 1920,
maxImageHeight: 1080,
maxFileSize: 5_000_000
};Image Crop with Aspect Ratio Lock (NEW v0.0.9)
uploadConfig = {
accept: 'image/*',
enableCrop: true,
maxImageWidth: 1920, // Crop locks to 16:9 aspect ratio
maxImageHeight: 1080, // Output resized to fit these dimensions
cropOutputWidth: 1920, // Final cropped image width
cropOutputHeight: 1080 // Final cropped image height
};How it works:
- When
enableCrop: trueandmaxImageWidth/maxImageHeightare set, the crop rectangle locks to that aspect ratio - User can drag, resize (maintaining ratio), and rotate the image
- Output is lossless PNG resized to the specified dimensions
Free-Form Image Crop
uploadConfig = {
accept: 'image/*',
enableCrop: true, // Enable crop modal
enableRotation: true // Allow 90° rotation (default: true)
// No aspect ratio constraints - free crop
};Square Crop (1:1)
uploadConfig = {
accept: 'image/*',
enableCrop: true,
cropAspectRatio: 1, // Force 1:1 square crop
cropOutputWidth: 500, // Output as 500×500px
cropOutputHeight: 500
};Custom Labels (Internationalization)
uploadConfig = {
accept: 'image/*',
labels: {
dropHere: 'اسحب الملفات هنا',
orClickToUpload: 'أو انقر للتحميل',
remove: 'إزالة',
fileTooLarge: 'الملف كبير جدًا',
maxFilesReached: 'تم الوصول إلى الحد الأقصى للملفات'
}
};Specific File Extensions Only
uploadConfig = {
allowedExtensions: ['.jpg', '.jpeg', '.png', '.webp'],
maxFileSize: 5_000_000
};File Objects Only (No Previews)
uploadConfig = {
accept: 'application/pdf,.docx',
outputFormat: 'file', // Returns File[] instead of MediaFile[]
showPreview: false
};🎯 Advanced Usage
Handling Validation Errors
<lib-media-dropzone
[config]="uploadConfig"
(filesSelected)="onFiles($event)"
(validationError)="onValidationError($event)"
/>onValidationError(errors: string[]) {
// Display errors to user
errors.forEach(error => {
this.toastService.error(error);
});
}
onFiles(files: MediaFile[]) {
// Filter only valid files
const validFiles = files.filter(f => f.isValid);
// Upload to server
this.uploadToServer(validFiles);
}Using Directives Separately
Build custom UI with the underlying directives:
import {
DropZoneDirective,
PasteHandlerDirective
} from '@deeby/dottmoon-media-dropzone';
@Component({
template: `
<div
class="custom-dropzone"
libDropZone
libPasteHandler
(filesDropped)="onDrop($event)"
(filesPasted)="onPaste($event)"
tabindex="0">
Custom styled drop zone
</div>
`,
imports: [DropZoneDirective, PasteHandlerDirective]
})
export class CustomDropzoneComponent {
onDrop(files: File[]) {
console.log('Dropped:', files);
}
onPaste(files: File[]) {
console.log('Pasted:', files);
}
}TypeScript Types
import type {
MediaFile,
MediaDropzoneConfig,
MediaDropzoneLabels
} from '@deeby/dottmoon-media-dropzone';🎨 Styling & Theming
The component supports full theming via CSS custom properties. All colors have sensible defaults — override only what you need.
Available CSS Variables
lib-media-dropzone {
/* Primary / accent colors */
--mdz-primary: #2196f3; /* Button background, hover border */
--mdz-primary-hover: #1976d2; /* Button hover background */
/* Backgrounds */
--mdz-bg: #fafafa; /* Drop zone background */
--mdz-card-bg: #fff; /* File preview card background */
--mdz-hover-bg: #e3f2fd; /* Drop zone hover background */
--mdz-drag-bg: #e8f5e9; /* Drop zone drag-over background */
--mdz-preview-bg: #f5f5f5; /* Image/video preview container */
--mdz-error-bg: #fff5f5; /* Invalid file background */
/* Borders */
--mdz-border-color: #bdbdbd; /* Default border color */
--mdz-hover-border: var(--mdz-primary); /* Hover border color */
/* Text */
--mdz-text: #424242; /* Primary text color */
--mdz-text-secondary: #757575; /* Secondary text, file size */
--mdz-icon-color: #757575; /* Upload icon color */
--mdz-btn-text: white; /* Button text color */
/* Feedback */
--mdz-error: #f44336; /* Error text and border */
--mdz-success: #4caf50; /* Progress bar fill */
--mdz-disabled: #bdbdbd; /* Disabled button */
/* Remove button */
--mdz-remove-bg: rgba(0, 0, 0, 0.5);
--mdz-remove-color: white;
--mdz-remove-hover-bg: rgba(0, 0, 0, 0.7);
/* Crop Modal */
--mdz-dialog-bg: #fff;
--mdz-toolbar-bg: #fafafa;
--mdz-crop-bg: #1a1a1a;
--mdz-btn-bg: #f0f0f0;
--mdz-btn-hover-bg: #e0e0e0;
}Example: Dark Mode
.dark-mode lib-media-dropzone {
--mdz-bg: #1e293b;
--mdz-card-bg: #1e293b;
--mdz-border-color: #334155;
--mdz-text: #f1f5f9;
--mdz-text-secondary: #94a3b8;
--mdz-error-bg: rgba(244, 67, 54, 0.1);
--mdz-remove-bg: rgba(255, 255, 255, 0.2);
--mdz-remove-hover-bg: rgba(255, 255, 255, 0.35);
--mdz-dialog-bg: #1e293b;
--mdz-toolbar-bg: #1e293b;
--mdz-btn-bg: #334155;
--mdz-btn-hover-bg: #475569;
}Example: Custom Brand Colors
lib-media-dropzone {
--mdz-primary: #6366f1; /* Indigo */
--mdz-primary-hover: #4f46e5;
--mdz-hover-bg: #eef2ff;
--mdz-success: #6366f1;
}📝 License
MIT © Deebyy
🤝 Contributing
Contributions, issues, and feature requests are welcome!
Feel free to check issues page.
📚 Resources
Made with ❤️ using Angular 21
