Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/Dropzone/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 3.7.0

- Add a `dropzone:pre-add` event to transform, replace or reject the selected files before they are added, and a `data-state="processing"` attribute on the container while it is pending
- Add a `data-state` attribute on the container: `empty`, `filled`, `dragging` or `processing`
- Show and hide the parts of the Dropzone with the `hidden` attribute instead of inline styles, see [UPGRADE.md](UPGRADE.md)

## 3.5.0

Expand Down
24 changes: 24 additions & 0 deletions src/Dropzone/UPGRADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# UPGRADE

## FROM 3.6 to 3.7

- The controller shows and hides the input, the placeholder, the preview and the image preview with the `hidden` attribute instead of inline styles, and the form theme renders `hidden` instead of `style="display: none"`.

If you disabled the default stylesheet and your own CSS sets `display` on these parts (`.dropzone-input`, `.dropzone-placeholder`, `.dropzone-preview`, `.dropzone-preview-image`), your rule wins over the `hidden` attribute and the parts stay visible. Add this rule to your stylesheet:

```css
.dropzone-container [hidden] {
display: none !important;
}
```

Before 3.7, the controller set `display: flex` inline on the preview and `display: block` inline on the input when it showed them. It no longer does this. If you disabled the default stylesheet, set these `display` values in your own CSS to keep the same layout. The rule above still hides the parts when they must be hidden.

If you override the `dropzone_widget` block in your form theme, replace `style="display: none"` with `hidden` on the preview and the image preview:

```diff
-<div class="dropzone-preview" data-symfony--ux-dropzone--dropzone-target="preview" style="display: none">
+<div class="dropzone-preview" data-symfony--ux-dropzone--dropzone-target="preview" hidden>
```

The controller still removes that inline style when it connects, so the preview keeps working until you do.
3 changes: 3 additions & 0 deletions src/Dropzone/assets/dist/controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default class export_default extends Controller {
};
private dataTransfer;
private processingCount;
private filled;
private dragging;
private queue;
private generation;
initialize(): void;
Expand All @@ -41,5 +43,6 @@ export default class export_default extends Controller {
private containsFile;
private renderList;
private buildListItem;
private updateState;
private dispatchEvent;
}
71 changes: 41 additions & 30 deletions src/Dropzone/assets/dist/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var controller_default = class extends Controller {
};
dataTransfer;
processingCount = 0;
filled = false;
dragging = false;
queue = Promise.resolve();
generation = 0;
initialize() {
Expand All @@ -29,44 +31,44 @@ var controller_default = class extends Controller {
this.onMultipleChange = this.onMultipleChange.bind(this);
}
connect() {
if (this.processingCount === 0) this.element.removeAttribute("data-state");
this.dragging = false;
this.element.addEventListener("dragenter", this.onDragEnter);
this.element.addEventListener("dragleave", this.onDragLeave);
this.element.addEventListener("drop", this.onDrop);
if (this.multipleValue) {
this.connectMultiple();
this.dispatchEvent("connect");
return;
}
for (const target of [this.previewTarget, this.previewImageTarget]) if (target.style.display === "none") target.style.removeProperty("display");
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.element.addEventListener("drop", this.onDrop);
this.dispatchEvent("connect");
}
disconnect() {
this.generation++;
this.element.removeEventListener("dragenter", this.onDragEnter);
this.element.removeEventListener("dragleave", this.onDragLeave);
this.element.removeEventListener("drop", this.onDrop);
if (this.multipleValue) {
this.inputTarget.removeEventListener("change", this.onMultipleChange);
return;
}
this.previewClearButtonTarget.removeEventListener("click", this.clear);
this.inputTarget.removeEventListener("change", this.onInputChange);
this.element.removeEventListener("dragenter", this.onDragEnter);
this.element.removeEventListener("dragleave", this.onDragLeave);
this.element.removeEventListener("drop", this.onDrop);
}
clear() {
this.generation++;
this.reset();
}
reset() {
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.previewImageTarget.hidden = true;
this.previewImageTarget.style.removeProperty("background-image");
this.previewFilenameTarget.textContent = "";
this.filled = false;
this.updateState();
this.dispatchEvent("clear");
}
onInputChange(event) {
Expand All @@ -90,37 +92,34 @@ var controller_default = class extends Controller {
if (typeof FileReader === "undefined") return;
const reader = new FileReader();
reader.addEventListener("load", (event) => {
target.style.display = "block";
target.hidden = false;
target.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";
this.dragging = true;
this.updateState();
}
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";
this.dragging = false;
this.updateState();
}
}
onDrop() {
setTimeout(() => {
if (!this.inputTarget.files?.length || this.processingCount > 0) return;
this.inputTarget.style.display = "none";
this.placeholderTarget.style.display = "none";
this.previewTarget.style.display = "flex";
this.dragging = false;
this.updateState();
});
}
connectMultiple() {
this.dataTransfer = new DataTransfer();
for (const file of Array.from(this.inputTarget.files ?? [])) this.dataTransfer.items.add(file);
this.inputTarget.addEventListener("change", this.onMultipleChange);
this.renderList();
this.updateState();
}
onMultipleChange() {
const files = Array.from(this.inputTarget.files ?? []);
Expand All @@ -131,12 +130,11 @@ var controller_default = class extends Controller {
});
}
showFile(file) {
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";
this.previewImageTarget.hidden = true;
if (file.type && file.type.indexOf("image") !== -1) this._populateImagePreview(this.previewImageTarget, file);
this.filled = true;
this.updateState();
this.dispatchEvent("change", file);
}
preAdd(files, add) {
Expand Down Expand Up @@ -165,7 +163,7 @@ var controller_default = class extends Controller {
}
const generation = this.generation;
this.processingCount++;
this.element.setAttribute("data-state", "processing");
this.updateState();
const result = (promise ?? Promise.resolve(selected)).then((resolved) => {
if (!Array.isArray(resolved) || !resolved.every((file) => file instanceof File)) throw new TypeError("The promise passed to waitUntil() must resolve to an array of files.");
return resolved;
Expand All @@ -177,12 +175,13 @@ var controller_default = class extends Controller {
if (generation === this.generation) add(resolved);
}).finally(() => {
this.processingCount--;
if (this.processingCount === 0) this.element.removeAttribute("data-state");
this.updateState();
});
}
syncMultiple() {
this.inputTarget.files = this.dataTransfer.files;
this.renderList();
this.updateState();
this.dispatchEvent("change", this.inputTarget.files);
}
removeFileAt(index, file) {
Expand All @@ -203,7 +202,7 @@ var controller_default = class extends Controller {
item.className = "dropzone-preview-list-item";
const image = document.createElement("div");
image.className = "dropzone-preview-image";
image.style.display = "none";
image.hidden = true;
if (file.type && file.type.indexOf("image") !== -1) this._populateImagePreview(image, file);
const filename = document.createElement("span");
filename.className = "dropzone-preview-filename";
Expand All @@ -216,6 +215,18 @@ var controller_default = class extends Controller {
item.append(image, filename, remove);
return item;
}
updateState() {
const filled = this.multipleValue ? this.dataTransfer.files.length > 0 : this.filled;
let state = filled ? "filled" : "empty";
if (this.dragging) state = "dragging";
if (this.processingCount > 0) state = "processing";
this.element.setAttribute("data-state", state);
if (this.multipleValue) return;
const showPreview = filled && !this.dragging;
this.inputTarget.hidden = showPreview;
this.placeholderTarget.hidden = showPreview;
this.previewTarget.hidden = !showPreview;
}
dispatchEvent(name, payload = {}, cancelable = false) {
return this.dispatch(name, {
detail: payload,
Expand Down
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.

Loading
Loading