npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swiftlyme/image-uploader

v1.0.1

Published

Angular image uploader with crop, drag & reorder support

Downloads

58

Readme

ImageUploaderLib

A powerful, feature-rich Angular image uploader component with drag-and-drop, cropping, editing, PDF support, and replacement capabilities.

Angular TypeScript License

🚀 Features

  • Drag & Drop - Reorder images with smooth animations using Angular CDK
  • Advanced Crop Editor - Crop, rotate, and flip images with live canvas preview
  • Image Replacement - Replace existing images without losing position
  • PDF Support - Upload, preview, and view PDF documents
  • Multiple Aspect Ratios - 1:1, 16:9, 4:3, 9:16, or custom ratios
  • Free Crop Mode - Crop images to any size without constraints
  • Slider Layout - Horizontal scrollable carousel with navigation buttons
  • Grid Layout - Responsive grid that adapts to container size
  • Image Compression - Reduce file sizes with adjustable compression
  • Download Support - Download edited/cropped images
  • Granular Permissions - Toggle edit, replace, delete, download, and drag features
  • Event Emissions - Track upload, replace, delete, and change events
  • Pre-load URLs - Initialize with existing image URLs
  • Container Queries - Smart responsive behavior based on parent container size
  • Mobile Responsive - Optimized for all screen sizes, including micro views
  • Accessibility - ARIA labels and keyboard support

📦 Installation

npm install @swiftlyme/image-uploader

Dependencies

Ensure you have the following peer dependencies installed:

npm install @angular/cdk
npm install bootstrap@5
npm install bootstrap-icons

Style Requirements

Add Bootstrap 5 and Bootstrap Icons to your angular.json:

{
  "styles": ["node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/bootstrap-icons/font/bootstrap-icons.css", "src/styles.css"]
}

🎯 Quick Start

1. Import the Module

import { ImageUploaderLibComponent } from "@swiftlyme/image-uploader";

@NgModule({
  imports: [
    ImageUploaderLibComponent,
    // ... other imports
  ],
})
export class AppModule {}

2. Add to Your Template

<lib-image-uploader-lib [maxImages]="6" [aspectRatio]="1" [layoutMode]="'grid'" (imagesChange)="handleImagesChange($event)" (imageUpload)="onImageUpload($event)" (imageReplace)="onImageReplace($event)" (imageDelete)="onImageDelete($event)" (imageEdit)="onImageEdit($event)"> </lib-image-uploader-lib>

3. Handle Events in Component

export class YourComponent {
  handleImagesChange(images: any[]) {
    console.log("Images updated:", images);
  }

  onImageUpload(image: any) {
    console.log("New image uploaded:", image);
  }

  onImageReplace(event: any) {
    console.log("Image replaced:", event);
    // event contains: { old, new, index }
  }

  onImageDelete(image: any) {
    console.log("Image deleted:", image);
  }

  onImageEdit(image: any) {
    console.log("Image edited:", image);
  }
}

📚 API Reference

Input Properties

| Property | Type | Default | Description | | ----------------- | ---------- | -------- | ---------------------------------------------------------------------- | | maxImages | number | 1 | Maximum number of images/files allowed | | aspectRatio | number | 1 | Aspect ratio for cropping (e.g., 1, 16/9, 4/3) | | enableFreeCrop | boolean | false | Allow free-form cropping without ratio constraints | | layoutMode | string | 'grid' | Layout style: 'grid' or 'slider' | | allowDrag | boolean | true | Enable drag-and-drop reordering | | allowEdit | boolean | true | Show edit button for cropping/rotating images (not available for PDFs) | | allowReplace | boolean | true | Show replace button to swap images or PDFs | | allowDelete | boolean | true | Show delete button | | allowDownload | boolean | true | Show download button | | compressionRate | number | 0 | Image compression percentage (0-95, 0 = no compression) | | imageUrls | string[] | [] | Pre-load images from URLs |

Output Events

| Event | Payload | Description | | -------------- | --------------------- | --------------------------------------------------------- | | imagesChange | any[] | Emitted when images array changes (any operation) | | imageUpload | any | Emitted when a new image/PDF is uploaded to an empty slot | | imageReplace | { old, new, index } | Emitted when an existing image/PDF is replaced | | imageDelete | any | Emitted when an image/PDF is deleted | | imageEdit | any | Emitted when an image/PDF is edited (any crop) |

🎨 Layout Modes

Grid (Default)

Responsive grid that automatically adapts to container size using CSS Container Queries.

<lib-image-uploader-lib [layoutMode]="'grid'" [maxImages]="6"></lib-image-uploader-lib>

Smart Responsive Features:

  • Wide containers (>350px): Shows multiple columns based on screen size
  • Narrow containers (<350px): Automatically stacks images vertically
  • Micro containers (<150px): Hides text, shows icon-only controls

Slider

Horizontal scrollable carousel with navigation buttons and snap-to-center behavior.

<lib-image-uploader-lib [layoutMode]="'slider'" [maxImages]="10"></lib-image-uploader-lib>

Features:

  • Smooth scroll-snap navigation
  • Previous/Next buttons with auto-disable at boundaries
  • Swipe support on mobile devices
  • Hides scrollbar for clean appearance

🎨 Column Sizing

The grid layout dynamically adjusts based on image index and screen size:

| Image Position | Desktop (lg+) | Tablet (md) | Mobile (sm) | | -------------- | ------------- | -------------- | -------------- | | First Image | 6 cols (50%) | 12 cols (100%) | 12 cols (100%) | | Other Images | 3 cols (25%) | 6 cols (50%) | 12 cols (100%) |

For slider mode, all images maintain consistent sizing.

📄 PDF Support

The component fully supports PDF uploads with special handling:

Upload PDF

<lib-image-uploader-lib [maxImages]="5" accept="image/*,application/pdf"> </lib-image-uploader-lib>

PDF Features

  • ✅ PDF icon preview with filename
  • ✅ View button opens PDF in new tab
  • ✅ Replace functionality
  • ✅ Delete functionality
  • ✅ Drag and reorder alongside images
  • ❌ Edit/crop not available (images only)

Detect PDF in Events

onImageUpload(file: any) {
  if (file.file?.type === 'application/pdf') {
    console.log('PDF uploaded:', file.file.name);
  } else {
    console.log('Image uploaded');
  }
}

🔧 Advanced Usage

Custom Aspect Ratio

<lib-image-uploader-lib [aspectRatio]="21/9" [maxImages]="4"> </lib-image-uploader-lib>

Free Crop Mode

<lib-image-uploader-lib [enableFreeCrop]="true" [maxImages]="3"> </lib-image-uploader-lib>

Image Compression

<lib-image-uploader-lib [compressionRate]="50" [maxImages]="5"> </lib-image-uploader-lib>

Compression ranges from 0 (no compression) to 95 (maximum compression)

Pre-loaded Images

export class YourComponent {
  existingImages = ["https://example.com/image1.jpg", "https://example.com/image2.jpg", "https://example.com/image3.jpg"];
}
<lib-image-uploader-lib [imageUrls]="existingImages" [maxImages]="5"> </lib-image-uploader-lib>

Granular Permissions

<lib-image-uploader-lib [allowDrag]="true" [allowEdit]="true" [allowReplace]="true" [allowDelete]="true" [allowDownload]="false"> </lib-image-uploader-lib>

Replace Event Handling

export class YourComponent {
  onImageReplace(event: any) {
    const { old, new: newImage, index } = event;

    // Delete old file from server
    this.deleteFromServer(old.originalUrl);

    // Upload new file to server
    this.uploadToServer(newImage.file, index);

    // Update database
    this.updateDatabase(index, newImage);

    // Show notification
    this.showNotification(`File ${index + 1} replaced successfully`);
  }
}

🎯 Real-World Examples

E-commerce Product Gallery

<lib-image-uploader-lib [maxImages]="8" [aspectRatio]="1" [layoutMode]="'slider'" [allowDrag]="true" [allowReplace]="true" [compressionRate]="30" (imagesChange)="updateProductImages($event)" (imageReplace)="handleProductImageReplace($event)"> </lib-image-uploader-lib>

User Profile Picture

<lib-image-uploader-lib [maxImages]="1" [aspectRatio]="1" [layoutMode]="'grid'" [allowDelete]="false" [allowReplace]="true" (imageUpload)="uploadAvatar($event)" (imageReplace)="updateAvatar($event)"> </lib-image-uploader-lib>

Photo Album

<lib-image-uploader-lib [maxImages]="20" [enableFreeCrop]="true" [layoutMode]="'grid'" [allowDrag]="true" [allowDownload]="true" (imagesChange)="saveAlbum($event)"> </lib-image-uploader-lib>

Document Management (Images + PDFs)

<lib-image-uploader-lib [maxImages]="15" [aspectRatio]="210/297" [layoutMode]="'grid'" [allowReplace]="true" [compressionRate]="60" (imagesChange)="processDocuments($event)"> </lib-image-uploader-lib>

Sidebar Upload (Narrow Container)

<div class="col-lg-2">
  <lib-image-uploader-lib [maxImages]="5" [layoutMode]="'grid'" [aspectRatio]="1"> </lib-image-uploader-lib>
</div>

Automatically stacks vertically and adjusts controls for narrow spaces

🎨 Edit Modal Features

The fullscreen edit modal provides comprehensive image editing:

Controls Available

  • Crop - Drag overlay to reposition, drag corners to resize
  • Rotate Left - Rotate 90° counter-clockwise
  • Rotate Right - Rotate 90° clockwise
  • Flip Horizontal - Mirror image horizontally
  • Flip Vertical - Mirror image vertically

Modal Features

  • ✅ Fullscreen overlay (95vw × 95vh)
  • ✅ Canvas-based rendering
  • ✅ Live preview with grid overlay
  • ✅ Resize handles on all corners
  • ✅ Dark background for better visibility
  • ✅ Keyboard support (ESC to close)

Usage

<!-- Edit button appears automatically when allowEdit is true -->
<lib-image-uploader-lib [allowEdit]="true"></lib-image-uploader-lib>

🖱️ Drag & Drop Features

Reordering

  • Drag images to reorder them
  • Visual drag preview with shadow
  • Smooth animations during drop
  • Works in both grid and slider modes

Drag Handles

  • Full card is draggable when allowDrag is true
  • Controls remain clickable during drag operations
  • Automatic z-index management

Disable Dragging

<lib-image-uploader-lib [allowDrag]="false"></lib-image-uploader-lib>

📐 Container Query Responsiveness

The component uses CSS Container Queries for intelligent responsive behavior:

Breakpoints

  • >350px: Normal multi-column grid
  • <350px: Single column vertical stack
  • <150px: Micro view (icon-only controls)

Micro View Optimizations

  • Upload text hidden
  • Icon scaled down to 1.5rem
  • Edit/Delete buttons reduced to 24×24px
  • 4px button spacing

Example: Responsive Sidebar

<div class="sidebar" style="width: 200px;">
  <lib-image-uploader-lib [maxImages]="3"></lib-image-uploader-lib>
  <!-- Automatically uses single-column layout -->
</div>

🎨 Styling & Customization

The component uses Bootstrap 5 for styling. Customize appearance by:

1. Override Bootstrap Variables

// styles.scss
$primary: #6366f1;
$warning: #f59e0b;
$danger: #ef4444;
$info: #0dcaf0;

@import "bootstrap/scss/bootstrap";

2. Custom CSS Classes

/* Customize upload slot */
.upload-slot {
  border-color: #8b5cf6 !important;
}

.upload-slot:hover {
  background-color: #f3e8ff !important;
}

/* Customize control buttons */
.controls-overlay button {
  opacity: 0.95 !important;
}

/* Customize edit modal */
.modal-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

3. Card Customization

/* Adjust card styling */
.card {
  border-radius: 12px !important;
  overflow: hidden;
}

.card-body {
  padding: 0.5rem !important;
}

📊 Image Object Structure

When images/PDFs are emitted through events, they have this structure:

{
  id: string;                    // Unique identifier
  originalUrl: string;           // Base64 data URL or external URL
  croppedUrl: string | null;     // Base64 data URL of cropped image (null for PDFs)
  previewUrl?: string;           // Optional preview URL for PDFs
  file: File | null;             // Original File object (null for URL-based items)
  isRemote?: boolean;            // Flag indicating if loaded from URL
}

Check File Type

function isPdf(slot: any): boolean {
  return slot?.file?.type === "application/pdf";
}

🚦 Event Flow

User Action              →  Event Emitted       →  Payload
─────────────────────────────────────────────────────────────────
Upload to empty slot     →  imageUpload        →  image/pdf
Replace existing         →  imageReplace       →  { old, new, index }
Delete image/PDF         →  imageDelete        →  image/pdf
Drag to reorder          →  imagesChange       →  images[]
Crop/edit save           →  imagesChange       →  images[]

⚙️ Building the Library

Development Build

ng build image-uploader-lib

Production Build

ng build image-uploader-lib --configuration production

Build artifacts will be placed in the dist/ directory.

📤 Publishing to NPM

  1. Build the library:

    ng build image-uploader-lib --configuration production
  2. Navigate to dist directory:

    cd dist/image-uploader-lib
  3. Update version in package.json (if needed)

  4. Publish to npm:

    npm publish

🧪 Testing

Unit Tests

ng test image-uploader-lib

End-to-End Tests

ng e2e

🛠 Troubleshooting

Images not uploading?

  • Check that maxImages is not exceeded
  • Verify file type is an image or PDF
  • Check browser console for errors

Replace button not showing?

  • Ensure allowReplace is true
  • Verify the slot has an image/PDF (not empty)
  • Check that the file has finished loading

Crop editor not working?

  • Make sure allowEdit is true
  • Crop is only available for images (not PDFs)
  • For external URLs, ensure CORS is enabled on the image server
  • Check that the image has loaded successfully

Download not working?

  • External images may have CORS restrictions
  • Check browser console for fetch errors
  • For remote images, the browser may open them instead of downloading

Slider buttons not working?

  • Ensure layoutMode is set to 'slider'
  • Check that there are enough images to scroll
  • Buttons auto-disable at scroll boundaries

PDF not viewing?

  • Check that popup blockers are not preventing the new tab
  • Verify the PDF file is valid
  • Check browser console for errors

Controls not visible in narrow containers?

  • Container queries require modern browser support
  • Check that CSS is properly loaded
  • Verify Bootstrap Icons are included

📋 Browser Support

  • ✅ Chrome (latest) - Full support
  • ✅ Firefox (latest) - Full support
  • ✅ Safari (latest) - Full support
  • ✅ Edge (latest) - Full support
  • ✅ Mobile browsers (iOS Safari, Chrome Mobile) - Full support
  • ⚠️ Container Queries require modern browsers (Chrome 105+, Safari 16+, Firefox 110+)

🎯 Performance Tips

Image Compression

  • Use compressionRate to reduce file sizes
  • Recommended: 30-50 for web, 60-70 for thumbnails

Lazy Loading

For large galleries, consider loading images on demand:

// Load first batch
initialImages = imageUrls.slice(0, 6);

// Load more on scroll
loadMore() {
  this.images.push(...imageUrls.slice(6, 12));
}

Optimize Canvas Rendering

  • Crop operations use canvas for better performance
  • Automatically handles high-DPI displays

🤝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Commit your changes: git commit -am 'Add new feature'
  4. Push to the branch: git push origin feature/my-feature
  5. Submit a pull request

📝 Changelog

v2.1.0 (Latest)

  • ✨ Added PDF upload and preview support
  • ✨ Added slider layout mode with navigation buttons
  • ✨ Implemented CSS Container Queries for smart responsiveness
  • ✨ Added micro-view optimizations for narrow containers
  • ✨ Enhanced scroll-snap behavior in slider mode
  • 🎨 Improved upload placeholder with responsive text
  • 🎨 Added gradient background to modal header
  • 🐛 Fixed fullscreen modal positioning
  • 🐛 Fixed resize handles visibility
  • 🐛 Fixed control buttons in micro views

v2.0.0

  • ✨ Added image replacement feature
  • ✨ New imageReplace event emission
  • ✨ New allowReplace input property
  • 🎨 Yellow warning button for replace action
  • 🐛 Fixed file input handling for replacements
  • 📚 Updated documentation

v1.0.0

  • 🎉 Initial release
  • ✨ Drag and drop support
  • ✨ Crop/edit functionality
  • ✨ Grid layout mode
  • ✨ Image compression

📄 License

MIT License - feel free to use this library in your projects.

🙏 Acknowledgments

📞 Support

For issues, questions, or feature requests, please visit ([email protected])

🔗 Links


Made with ❤️ using Angular