Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Dropzone/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.31

- Support for multiple files drag and drop and peviews

## 2.30

- Ensure compatibility with PHP 8.5
Expand Down
25 changes: 18 additions & 7 deletions src/Dropzone/assets/dist/controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,30 @@ import { Controller } from '@hotwired/stimulus';
declare class export_default extends Controller {
readonly inputTarget: HTMLInputElement;
readonly placeholderTarget: HTMLDivElement;
readonly previewTarget: HTMLDivElement;
readonly previewClearButtonTarget: HTMLButtonElement;
readonly previewFilenameTarget: HTMLDivElement;
readonly previewImageTarget: HTMLDivElement;
readonly previewTargets: HTMLDivElement[];
readonly previewContainerTarget: HTMLDivElement;
static targets: string[];
files: Map<string, File>;
initialize(): void;
connect(): void;
disconnect(): void;
clear(): void;
onInputChange(event: any): void;
_populateImagePreview(file: Blob): void;
clear(event?: {
target?: HTMLElement;
params?: {
filename?: string;
};
}): void;
onInputChange(): void;
private renderPreview;
private clearPreviewContainer;
private buildPreview;
_populateImagePreview(element: HTMLElement, file: File): void;
onDragEnter(): void;
onDragLeave(event: any): void;
private updateFileInput;
private addFiles;
private isImage;
private get isMultiple();
private dispatchEvent;
}

Expand Down
139 changes: 102 additions & 37 deletions src/Dropzone/assets/dist/controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// src/controller.ts
import { Controller } from "@hotwired/stimulus";
var controller_default = class extends Controller {
constructor() {
super(...arguments);
this.files = /* @__PURE__ */ new Map();
}
initialize() {
this.clear = this.clear.bind(this);
this.onInputChange = this.onInputChange.bind(this);
Expand All @@ -9,72 +13,133 @@ var controller_default = class extends Controller {
}
connect() {
this.clear();
this.previewClearButtonTarget.addEventListener("click", this.clear);
this.inputTarget.addEventListener("change", this.onInputChange);
this.element.addEventListener("dragenter", this.onDragEnter);
this.element.addEventListener("dragleave", this.onDragLeave);
this.dispatchEvent("connect");
}
disconnect() {
this.previewClearButtonTarget.removeEventListener("click", this.clear);
this.inputTarget.removeEventListener("change", this.onInputChange);
this.element.removeEventListener("dragenter", this.onDragEnter);
this.element.removeEventListener("dragleave", this.onDragLeave);
}
clear() {
this.inputTarget.value = "";
this.inputTarget.style.display = "block";
this.placeholderTarget.style.display = "block";
this.previewTarget.style.display = "none";
this.previewImageTarget.style.display = "none";
this.previewImageTarget.style.backgroundImage = "none";
this.previewFilenameTarget.textContent = "";
clear(event) {
if (event?.params) {
const filename = event.params.filename;
if (filename && this.files.has(filename)) {
this.files.delete(filename);
this.updateFileInput();
this.renderPreview();
}
}
if (!this.inputTarget || !this.inputTarget.files || this.inputTarget?.files?.length === 0) {
this.placeholderTarget.style.display = "block";
if (!this.isMultiple) {
this.inputTarget.style.display = "block";
}
}
this.dispatchEvent("clear");
}
onInputChange(event) {
const file = event.target.files[0];
if (typeof file === "undefined") {
onInputChange() {
const files = this.inputTarget.files;
if (!files || files.length <= 0) {
return;
}
this.inputTarget.style.display = "none";
this.placeholderTarget.style.display = "none";
this.previewFilenameTarget.textContent = file.name;
this.previewTarget.style.display = "flex";
this.previewImageTarget.style.display = "none";
if (file.type && file.type.indexOf("image") !== -1) {
this._populateImagePreview(file);
if (!this.isMultiple && this.files.size > 0) {
this.inputTarget.style.display = "none";
}
this.dispatchEvent("change", file);
const selectedFiles = this.isMultiple ? Array.from(files) : Array.from(files).slice(0, 1);
this.addFiles(selectedFiles);
this.updateFileInput();
this.renderPreview();
this.dispatchEvent("change", files);
}
_populateImagePreview(file) {
if (typeof FileReader === "undefined") {
return;
renderPreview() {
this.clearPreviewContainer();
for (const file of this.files.values()) {
const preview = this.buildPreview(file);
if (preview) {
this.previewContainerTarget.appendChild(preview);
}
}
if (this.previewTargets.length > 1) {
this.placeholderTarget.style.display = "none";
if (!this.isMultiple) {
this.inputTarget.style.display = "none";
} else {
this.inputTarget.style.display = "block";
}
}
}
clearPreviewContainer() {
const previews = this.previewTargets;
previews.slice(1).forEach((el) => el.remove());
}
buildPreview(file, element) {
if (!element) {
element = this.previewContainerTarget.firstElementChild?.cloneNode(true);
}
element.style.display = "flex";
const fileName = element.querySelector(".dropzone-preview-filename");
if (fileName) {
fileName.textContent = file.name;
}
const button = element.querySelector(".dropzone-preview-button");
if (button) {
button.setAttribute("data-symfony--ux-dropzone--dropzone-filename-param", file.name);
}
this._populateImagePreview(element, file);
return element;
}
_populateImagePreview(element, file) {
const image = element.querySelector(".dropzone-preview-image");
if (image && this.isImage(file) && typeof FileReader !== "undefined") {
const reader = new FileReader();
reader.addEventListener("load", (event) => {
image.querySelector(".dropzone-preview-image")?.remove();
image.style.backgroundImage = `url('${event.target.result}')`;
image.style.display = "block";
});
reader.readAsDataURL(file);
}
const reader = new FileReader();
reader.addEventListener("load", (event) => {
this.previewImageTarget.style.display = "block";
this.previewImageTarget.style.backgroundImage = `url("${event.target.result}")`;
});
reader.readAsDataURL(file);
}
onDragEnter() {
this.inputTarget.style.display = "block";
this.placeholderTarget.style.display = "block";
this.previewTarget.style.display = "none";
}
onDragLeave(event) {
event.preventDefault();
if (!this.element.contains(event.relatedTarget)) {
this.inputTarget.style.display = "none";
this.placeholderTarget.style.display = "none";
this.previewTarget.style.display = "block";
}
updateFileInput() {
const dataTransfer = new DataTransfer();
for (const file of this.files.values()) {
dataTransfer.items.add(file);
}
this.inputTarget.files = dataTransfer.files;
}
addFiles(files) {
for (const file of files) {
this.files.set(file.name, file);
}
}
isImage(file) {
return typeof file.type !== "undefined" && file.type.indexOf("image") !== -1;
}
get isMultiple() {
return this.inputTarget.multiple;
}
dispatchEvent(name, payload = {}) {
this.dispatch(name, { detail: payload, prefix: "dropzone" });
}
};
controller_default.targets = ["input", "placeholder", "preview", "previewClearButton", "previewFilename", "previewImage"];
controller_default.targets = [
"input",
"placeholder",
"preview",
"previewClearButton",
"previewFilename",
"previewImage",
"previewContainer"
];
export {
controller_default as default
};
2 changes: 1 addition & 1 deletion src/Dropzone/assets/dist/style.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Dropzone/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@symfony/ux-dropzone",
"description": "File input dropzones for Symfony Forms",
"license": "MIT",
"version": "2.31.0",
"version": "2.30.0",
"keywords": [
"symfony-ux"
],
Expand Down
Loading
Loading