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
18 changes: 18 additions & 0 deletions packages/dropzone/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## 6.3.4

### Patch Changes

- [#2377](https://github.com/enyo/dropzone/pull/2377) [`0b302ee`](https://github.com/enyo/dropzone/commit/0b302eee30dd709ea00f58ad43d33e3a48502cbb) - Stop `destroy()` removing a different instance. `Dropzone.instances.splice(indexOf(this), 1)` dropped the last entry whenever `indexOf` returned -1 — calling `destroy()` twice was enough — evicting an unrelated live Dropzone from the registry.

- [#2377](https://github.com/enyo/dropzone/pull/2377) [`dc5d7bf`](https://github.com/enyo/dropzone/commit/dc5d7bfb1dd450baa467d4682d9fb3d12be30758) - Set `enctype="multipart/form-data"` on a form again. `init()` compared `tagName` against lower-case `"form"`, which never matches, so the attribute was never set. Only affects forms that are also submitted natively; the XHR upload is unchanged.

## 6.3.3

### Patch Changes

- [#2375](https://github.com/enyo/dropzone/pull/2375) [`6a03691`](https://github.com/enyo/dropzone/commit/6a036914eae46748baed12983b51e63bf1906837) - Fix the declared type of `clickable` and `hiddenInputContainer`.

Both options are derived from their default values, so `clickable: true` was inferred as `boolean` and `hiddenInputContainer: "body"` as `string`. That contradicted what each one documents and what both have always accepted at runtime, and TypeScript rejected the documented forms.

`clickable` is now `boolean | string | HTMLElement | (string | HTMLElement)[]` and `hiddenInputContainer` is `string | HTMLElement`. Nothing changes at runtime.

## 6.3.2

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/dropzone/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dropzone",
"version": "6.3.2",
"version": "6.3.4",
"description": "Handles drag and drop of files for you.",
"keywords": [
"drag and drop",
Expand Down Expand Up @@ -52,7 +52,7 @@
"build": "vite build && vite build --config vite.config.global.mjs && pnpm run css && pnpm run build:types",
"css": "lightningcss --minify --sourcemap --browserslist src/dropzone.css -o dist/dropzone.css && lightningcss --minify --sourcemap --browserslist src/basic.css -o dist/basic.css",
"build:types": "tsc",
"typecheck": "tsc --noEmit",
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest",
Expand Down
11 changes: 8 additions & 3 deletions packages/dropzone/src/dropzone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,9 @@ export default class Dropzone extends Emitter {
// The function that gets called when Dropzone is initialized. You
// can (and should) setup event listeners inside this function.
init() {
// In case it isn't set already
if (this.element.tagName === "form") {
// In case it isn't set already. `tagName` is upper case on HTML elements,
// so this never matched and the attribute was never set.
if (this.element.tagName === "FORM") {
this.element.setAttribute("enctype", "multipart/form-data");
}

Expand Down Expand Up @@ -547,7 +548,11 @@ export default class Dropzone extends Emitter {
this.hiddenFileInput = null;
}
delete this.element.dropzone;
return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1);
// `indexOf` returns -1 for an instance that is no longer registered --
// destroying twice is enough -- and `splice(-1, 1)` would then quietly
// drop the last entry, which is a different, live Dropzone.
let index = Dropzone.instances.indexOf(this);
return index === -1 ? [] : Dropzone.instances.splice(index, 1);
}

updateTotalUploadProgress() {
Expand Down
4 changes: 2 additions & 2 deletions packages/dropzone/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ let defaultOptions = {
* or an array of those. In that case, all of those elements will trigger an
* upload when clicked.
*/
clickable: true,
clickable: true as boolean | string | HTMLElement | (string | HTMLElement)[],

/**
* Whether hidden files in directories should be ignored.
Expand Down Expand Up @@ -274,7 +274,7 @@ let defaultOptions = {
*
* Can be a selector string, or an element directly.
*/
hiddenInputContainer: "body",
hiddenInputContainer: "body" as string | HTMLElement,

/**
* If null, no capture type will be specified
Expand Down
44 changes: 44 additions & 0 deletions packages/dropzone/test/types/options.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Assertions about the option types, checked by `pnpm typecheck` and never
// emitted -- see tsconfig.typecheck.json.
//
// `DropzoneOptions` is derived from the default values, so an option is typed
// by whatever its default happens to be. Where a default is narrower than what
// the option accepts, the widening in options.ts is the only thing keeping the
// declaration honest, and nothing else would notice it being dropped. Hence
// these. A `@ts-expect-error` is itself an assertion: it fails the build if the
// line it marks stops being an error, so it also pins the type against being
// widened all the way to `any`.
import type { DropzoneOptions } from "../../src/options";

let element: HTMLElement = document.createElement("div");

// clickable: true for the dropzone element, false for nothing, or an element,
// a CSS selector, or an array of those. See #2373.
export const clickableTrue: DropzoneOptions = { clickable: true };
export const clickableFalse: DropzoneOptions = { clickable: false };
export const clickableElement: DropzoneOptions = { clickable: element };
export const clickableSelector: DropzoneOptions = { clickable: ".pick-files" };
export const clickableList: DropzoneOptions = { clickable: [element, ".pick-files"] };

// @ts-expect-error clickable is not a number
export const clickableNumber: DropzoneOptions = { clickable: 42 };

// hiddenInputContainer: a selector string or an element.
export const hiddenInputSelector: DropzoneOptions = { hiddenInputContainer: "body" };
export const hiddenInputElement: DropzoneOptions = { hiddenInputContainer: element };

// @ts-expect-error hiddenInputContainer is not a number
export const hiddenInputNumber: DropzoneOptions = { hiddenInputContainer: 42 };

// previewsContainer: a selector, an element, or false to opt out.
export const previewsSelector: DropzoneOptions = { previewsContainer: ".previews" };
export const previewsElement: DropzoneOptions = { previewsContainer: element };
export const previewsFalse: DropzoneOptions = { previewsContainer: false };

// @ts-expect-error previewsContainer is not a number
export const previewsNumber: DropzoneOptions = { previewsContainer: 42 };

// The lack of an index signature is deliberate: a misspelled option has to be
// an error rather than an unused custom key. See the note on DropzoneOptions.
// @ts-expect-error maxFileSize is the wrong capitalisation of maxFilesize
export const misspelled: DropzoneOptions = { maxFileSize: 4 };
30 changes: 30 additions & 0 deletions packages/dropzone/test/unit-tests/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,24 @@ describe("Dropzone", function () {
}, 10);
})));

describe("init()", function () {
it("should set the enctype on a form element", function () {
let form = Dropzone.createElement("<form></form>");
let formDropzone = new Dropzone(form, { url: "url" });

expect(form.getAttribute("enctype")).toBe("multipart/form-data");
formDropzone.destroy();
});

it("should leave an existing enctype alone on a non-form element", function () {
let div = Dropzone.createElement("<div></div>");
let divDropzone = new Dropzone(div, { url: "url" });

expect(div.getAttribute("enctype")).toBe(null);
divDropzone.destroy();
});
});

describe(".destroy()", function () {
it("should properly cancel all pending uploads and remove all file references", () =>
new Promise((done) => {
Expand Down Expand Up @@ -807,6 +825,18 @@ describe("Dropzone", function () {
dropzone.destroy();
return expect(Dropzone.instances.indexOf(dropzone) === -1).toBeTruthy();
});

it("should leave other instances alone when destroyed twice", function () {
let other = new Dropzone(Dropzone.createElement("<div></div>"), { url: "url" });

dropzone.destroy();
// The second call finds nothing to remove. `splice(-1, 1)` used to
// take the last entry regardless, evicting an unrelated live instance.
dropzone.destroy();

expect(Dropzone.instances.indexOf(other) !== -1).toBeTruthy();
other.destroy();
});
});

describe(".filesize()", function () {
Expand Down
13 changes: 13 additions & 0 deletions packages/dropzone/tsconfig.typecheck.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// Type checking only, so it can reach files the build must not emit:
// test/types holds assertions about the public types, which would otherwise
// land in dist/ as declarations of their own.
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"declaration": false,
"emitDeclarationOnly": false,
"rootDir": "."
},
"include": ["src/**/*.ts", "src/types.d.ts", "test/types/**/*.ts"]
}
Loading