@georapbox/files-dropzone-element 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +15 -0
- package/.nvmrc +1 -0
- package/.prettierignore +6 -0
- package/.prettierrc +11 -0
- package/.vscode/extensions.json +8 -0
- package/.vscode/settings.json +8 -0
- package/.zed/settings.json +9 -0
- package/CHANGELOG.md +144 -0
- package/README.md +119 -103
- package/custom-elements-manifest.config.mjs +48 -0
- package/dist/custom-elements.json +509 -0
- package/dist/files-dropzone-defined.d.ts +1 -1
- package/dist/files-dropzone-defined.d.ts.map +1 -1
- package/dist/files-dropzone-defined.js +5 -5
- package/dist/files-dropzone-defined.js.map +4 -4
- package/dist/files-dropzone.d.ts +55 -68
- package/dist/files-dropzone.d.ts.map +1 -1
- package/dist/files-dropzone.js +5 -5
- package/dist/files-dropzone.js.map +4 -4
- package/dist/files-selector.d.ts +9 -0
- package/dist/files-selector.d.ts.map +1 -0
- package/dist/{utils/is-valid-file.d.ts → is-valid-file.d.ts} +1 -1
- package/dist/is-valid-file.d.ts.map +1 -0
- package/docs/css/main.css +190 -0
- package/docs/css/water.css +674 -0
- package/docs/favicon-32x32.png +0 -0
- package/docs/index.html +144 -0
- package/docs/js/main.js +154 -0
- package/docs/lib/files-dropzone.js +93 -0
- package/docs/lib/highlight/highlight-github.css +160 -0
- package/docs/lib/highlight/highlight.min.js +1160 -0
- package/eslint.config.mjs +42 -0
- package/package.json +52 -30
- package/scripts/build.mjs +32 -0
- package/scripts/copy-build-to-docs.mjs +17 -0
- package/scripts/dev.mjs +24 -0
- package/src/files-dropzone-defined.js +1 -1
- package/src/files-dropzone.js +70 -89
- package/test/files-dropzone-upgrade.test.js +39 -0
- package/test/files-dropzone.test.js +397 -0
- package/test/files-selector.test.js +96 -0
- package/test/is-valid-file.test.js +78 -0
- package/tsconfig.json +15 -0
- package/web-test-runner.config.js +13 -0
- package/dist/utils/files-selector.d.ts +0 -2
- package/dist/utils/files-selector.d.ts.map +0 -1
- package/dist/utils/is-valid-file.d.ts.map +0 -1
- /package/src/{utils/files-selector.js → files-selector.js} +0 -0
- /package/src/{utils/is-valid-file.js → is-valid-file.js} +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import globals from 'globals';
|
|
2
|
+
import pluginJs from '@eslint/js';
|
|
3
|
+
import { configs as pluginWc } from 'eslint-plugin-wc';
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
languageOptions: {
|
|
8
|
+
globals: {
|
|
9
|
+
...globals.browser,
|
|
10
|
+
...globals.node,
|
|
11
|
+
...globals.chai,
|
|
12
|
+
...globals.mocha
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
pluginJs.configs.recommended,
|
|
17
|
+
pluginWc['flat/recommended'],
|
|
18
|
+
pluginWc['flat/best-practice'],
|
|
19
|
+
{
|
|
20
|
+
rules: {
|
|
21
|
+
'no-use-before-define': [
|
|
22
|
+
'error',
|
|
23
|
+
{
|
|
24
|
+
functions: false
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
curly: ['warn'],
|
|
28
|
+
eqeqeq: [
|
|
29
|
+
'error',
|
|
30
|
+
'always',
|
|
31
|
+
{
|
|
32
|
+
null: 'ignore'
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
'wc/no-child-traversal-in-connectedcallback': 'off',
|
|
36
|
+
'wc/no-child-traversal-in-attributechangedcallback': 'off'
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
ignores: ['dist', 'docs/lib', 'coverage']
|
|
41
|
+
}
|
|
42
|
+
];
|
package/package.json
CHANGED
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@georapbox/files-dropzone-element",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A custom element that creates a drag and drop zone for files",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/files-dropzone.js",
|
|
6
|
-
"module": "dist/files-dropzone.js",
|
|
7
7
|
"unpkg": "dist/files-dropzone.js",
|
|
8
|
-
"type": "module",
|
|
9
8
|
"types": "dist/files-dropzone.d.ts",
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
9
|
+
"customElements": "dist/custom-elements.json",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/files-dropzone.d.ts",
|
|
13
|
+
"import": "./dist/files-dropzone.js"
|
|
14
|
+
},
|
|
15
|
+
"./define": {
|
|
16
|
+
"types": "./dist/files-dropzone.d.ts",
|
|
17
|
+
"import": "./dist/files-dropzone-defined.js"
|
|
18
|
+
},
|
|
19
|
+
"./custom-elements.json": "./dist/custom-elements.json"
|
|
20
|
+
},
|
|
14
21
|
"scripts": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
22
|
+
"start": "npm-run-all --parallel \"serve:dev -- {1}\" dev",
|
|
23
|
+
"dev": "npm-run-all clean dev:esbuild",
|
|
24
|
+
"dev:esbuild": "node ./scripts/dev.mjs",
|
|
25
|
+
"serve:dev": "web-dev-server --node-resolve --open /docs/ --watch",
|
|
26
|
+
"build": "npm-run-all clean build:esbuild",
|
|
27
|
+
"build:esbuild": "node ./scripts/build.mjs",
|
|
28
|
+
"build:docs": "npm-run-all build build:docs:copy",
|
|
29
|
+
"build:docs:copy": "node ./scripts/copy-build-to-docs.mjs",
|
|
17
30
|
"test": "web-test-runner",
|
|
18
31
|
"test:watch": "npm run test -- --watch",
|
|
19
32
|
"test:coverage": "npm run test -- --coverage",
|
|
33
|
+
"lint": "eslint",
|
|
34
|
+
"format": "prettier --ignore-unknown --write .",
|
|
20
35
|
"types": "tsc --project tsconfig.json",
|
|
21
|
-
"
|
|
22
|
-
"dev": "npm-run-all clean dev:esbuild",
|
|
23
|
-
"serve:dev": "web-dev-server --node-resolve --open /docs/ --watch",
|
|
24
|
-
"start": "npm-run-all --parallel \"serve:dev -- {1}\" dev",
|
|
25
|
-
"build:esbuild": "node ./scripts/build.mjs",
|
|
26
|
-
"build": "npm-run-all clean build:esbuild",
|
|
36
|
+
"analyze": "custom-elements-manifest analyze",
|
|
27
37
|
"clean": "rimraf dist",
|
|
28
|
-
"
|
|
38
|
+
"prepublishOnly": "npm-run-all clean lint test build types analyze"
|
|
29
39
|
},
|
|
30
40
|
"keywords": [
|
|
31
41
|
"custom element",
|
|
@@ -46,23 +56,35 @@
|
|
|
46
56
|
},
|
|
47
57
|
"homepage": "https://github.com/georapbox/files-dropzone-element#readme",
|
|
48
58
|
"devDependencies": {
|
|
49
|
-
"@
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
-
"@
|
|
54
|
-
"@web/
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
+
"@custom-elements-manifest/analyzer": "~0.11.0",
|
|
60
|
+
"@eslint/js": "~10.0.1",
|
|
61
|
+
"@esm-bundle/chai": "~4.3.4-fix.0",
|
|
62
|
+
"@open-wc/testing": "~5.0.0",
|
|
63
|
+
"@types/node": "~26.6.5",
|
|
64
|
+
"@web/dev-server": "~1.0.0",
|
|
65
|
+
"@web/test-runner": "~1.0.0",
|
|
66
|
+
"@web/test-runner-playwright": "~1.0.0",
|
|
67
|
+
"esbuild": "~0.28.2",
|
|
68
|
+
"eslint": "~10.14.0",
|
|
69
|
+
"eslint-plugin-wc": "~3.1.0",
|
|
70
|
+
"globals": "~17.13.0",
|
|
59
71
|
"npm-run-all": "~4.1.5",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
72
|
+
"playwright": "~1.64.1",
|
|
73
|
+
"prettier": "~3.9.10",
|
|
74
|
+
"rimraf": "~6.1.3",
|
|
75
|
+
"sinon": "~22.1.0",
|
|
76
|
+
"typescript": "~7.0.2"
|
|
64
77
|
},
|
|
65
78
|
"publishConfig": {
|
|
66
79
|
"access": "public"
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=24",
|
|
83
|
+
"npm": ">=11.17.0"
|
|
84
|
+
},
|
|
85
|
+
"allowScripts": {
|
|
86
|
+
"esbuild@0.28.2": true,
|
|
87
|
+
"fsevents@2.3.2": true,
|
|
88
|
+
"rs-module-lexer@2.6.0": true
|
|
67
89
|
}
|
|
68
90
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import * as esbuild from 'esbuild';
|
|
3
|
+
|
|
4
|
+
const PACKAGE_NAME = 'files-dropzone';
|
|
5
|
+
|
|
6
|
+
const readPkg = async () => {
|
|
7
|
+
return JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url)));
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const pkg = await readPkg();
|
|
11
|
+
|
|
12
|
+
const makeBanner = pkg => `/*!
|
|
13
|
+
* ${pkg.name}
|
|
14
|
+
* ${pkg.description}
|
|
15
|
+
*
|
|
16
|
+
* @version ${pkg.version}
|
|
17
|
+
* @homepage ${pkg.homepage}
|
|
18
|
+
* @author ${pkg.author.name} <${pkg.author.email}>
|
|
19
|
+
* @license ${pkg.license}
|
|
20
|
+
*/`;
|
|
21
|
+
|
|
22
|
+
await esbuild.build({
|
|
23
|
+
entryPoints: [`src/${PACKAGE_NAME}.js`, `src/${PACKAGE_NAME}-defined.js`],
|
|
24
|
+
bundle: true,
|
|
25
|
+
minify: true,
|
|
26
|
+
sourcemap: true,
|
|
27
|
+
format: 'esm',
|
|
28
|
+
outdir: 'dist',
|
|
29
|
+
banner: {
|
|
30
|
+
js: makeBanner(pkg)
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { copyFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const PACKAGE_NAME = 'files-dropzone';
|
|
5
|
+
const files = [`${PACKAGE_NAME}.js`];
|
|
6
|
+
const sourceDir = path.resolve('dist');
|
|
7
|
+
const destinationDir = path.resolve('docs/lib');
|
|
8
|
+
|
|
9
|
+
await mkdir(destinationDir, { recursive: true });
|
|
10
|
+
|
|
11
|
+
const copyPromises = files.map(file => {
|
|
12
|
+
const source = path.join(sourceDir, file);
|
|
13
|
+
const destination = path.join(destinationDir, file);
|
|
14
|
+
return copyFile(source, destination);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await Promise.all(copyPromises);
|
package/scripts/dev.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as esbuild from 'esbuild';
|
|
2
|
+
|
|
3
|
+
const PACKAGE_NAME = 'files-dropzone';
|
|
4
|
+
|
|
5
|
+
const ctx = await esbuild.context({
|
|
6
|
+
entryPoints: [`src/${PACKAGE_NAME}.js`, `src/${PACKAGE_NAME}-defined.js`],
|
|
7
|
+
bundle: true,
|
|
8
|
+
sourcemap: true,
|
|
9
|
+
format: 'esm',
|
|
10
|
+
outdir: 'dist',
|
|
11
|
+
metafile: true,
|
|
12
|
+
plugins: [
|
|
13
|
+
{
|
|
14
|
+
name: 'rebuild-notify',
|
|
15
|
+
setup(build) {
|
|
16
|
+
build.onEnd(result => {
|
|
17
|
+
console.log(`Build ended with ${result.errors.length} errors`);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await ctx.watch();
|
package/src/files-dropzone.js
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @template T
|
|
7
|
-
* @typedef {T | null} Nullable
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Stable identifier for error codes used in the `files-dropzone-error` event.
|
|
4
|
+
* Stable identifier for error codes used in the `dropzone-error` event.
|
|
12
5
|
*
|
|
13
6
|
* @typedef {typeof FilesDropzone.ERROR_CODES[keyof typeof FilesDropzone.ERROR_CODES]} FilesDropzoneErrorCode
|
|
14
7
|
*/
|
|
15
8
|
|
|
16
9
|
/**
|
|
17
|
-
* The detail payload for the `
|
|
10
|
+
* The detail payload for the `dropzone-error` event.
|
|
18
11
|
*
|
|
19
12
|
* @typedef {Object} FilesDropzoneErrorDetail
|
|
20
13
|
* @property {FilesDropzoneErrorCode} code - Stable identifier for the type of error that occurred.
|
|
@@ -27,12 +20,11 @@
|
|
|
27
20
|
* @typedef {typeof FilesDropzone.REJECTION_CODES[keyof typeof FilesDropzone.REJECTION_CODES]} FilesDropzoneRejectionCode
|
|
28
21
|
*/
|
|
29
22
|
|
|
30
|
-
import { isValidFile } from './
|
|
31
|
-
import { getFilesFromEvent } from './
|
|
23
|
+
import { isValidFile } from './is-valid-file.js';
|
|
24
|
+
import { getFilesFromEvent } from './files-selector.js';
|
|
32
25
|
|
|
33
26
|
const css = String.raw;
|
|
34
27
|
const html = String.raw;
|
|
35
|
-
const COMPONENT_NAME = 'files-dropzone';
|
|
36
28
|
const template = document.createElement('template');
|
|
37
29
|
|
|
38
30
|
const styles = css`
|
|
@@ -126,7 +118,7 @@ template.innerHTML = html`
|
|
|
126
118
|
* @summary A custom element that allows users to drag and drop files into it.
|
|
127
119
|
* @documentation https://github.com/georapbox/files-dropzone-element
|
|
128
120
|
*
|
|
129
|
-
* @tagname files-dropzone - This is the default tag name, unless overridden by the `
|
|
121
|
+
* @tagname files-dropzone - This is the default tag name, unless overridden by the `define` method.
|
|
130
122
|
*
|
|
131
123
|
* @property {string} accept - A comma-separated list of unique file type specifiers describing file types to allow.
|
|
132
124
|
* @property {boolean} disabled - Determines whether the dropzone is disabled.
|
|
@@ -137,14 +129,14 @@ template.innerHTML = html`
|
|
|
137
129
|
* @property {boolean} autoFocus - Automatically focuses the dropzone when it's connected to the DOM.
|
|
138
130
|
* @property {boolean} noStyle - Prevents the dropzone from applying any styling.
|
|
139
131
|
*
|
|
140
|
-
* @attribute {string} accept -
|
|
141
|
-
* @attribute {boolean} disabled -
|
|
142
|
-
* @attribute {number} max-files -
|
|
143
|
-
* @attribute {number} max-size -
|
|
144
|
-
* @attribute {number} min-size -
|
|
145
|
-
* @attribute {boolean} multiple -
|
|
146
|
-
* @attribute {boolean} auto-focus -
|
|
147
|
-
* @attribute {boolean} no-style -
|
|
132
|
+
* @attribute {string} accept - A comma-separated list of unique file type specifiers describing file types to allow.
|
|
133
|
+
* @attribute {boolean} disabled - Determines whether the dropzone is disabled.
|
|
134
|
+
* @attribute {number} max-files - The maximum number of files allowed to be dropped.
|
|
135
|
+
* @attribute {number} max-size - The maximum file size allowed in bytes.
|
|
136
|
+
* @attribute {number} min-size - The minimum file size allowed in bytes.
|
|
137
|
+
* @attribute {boolean} multiple - Allows multiple files to be dropped.
|
|
138
|
+
* @attribute {boolean} auto-focus - Automatically focuses the dropzone when it's connected to the DOM.
|
|
139
|
+
* @attribute {boolean} no-style - Prevents the dropzone from applying any styling.
|
|
148
140
|
*
|
|
149
141
|
* @slot - The default slot content of the dropzone.
|
|
150
142
|
*
|
|
@@ -167,15 +159,15 @@ template.innerHTML = html`
|
|
|
167
159
|
* @cssproperty --dropzone-focus-box-shadow - The box shadow of the dropzone when focused.
|
|
168
160
|
* @cssproperty --dropzone-transition-duration - The transition's duration for the dropzone area.
|
|
169
161
|
*
|
|
170
|
-
* @event
|
|
171
|
-
* @event
|
|
172
|
-
* @event
|
|
173
|
-
* @event
|
|
174
|
-
* @event
|
|
175
|
-
* @event
|
|
176
|
-
* @event
|
|
162
|
+
* @event dropzone-drop - Dispatched when files are dropped.
|
|
163
|
+
* @event dropzone-drop-accepted - Dispatched when files dropped files are accepted.
|
|
164
|
+
* @event dropzone-drop-rejected - Dispatched when files dropped files are rejected.
|
|
165
|
+
* @event dropzone-dragenter - Dispatched when files are dragged into the dropzone.
|
|
166
|
+
* @event dropzone-dragover - Dispatched when files are dragged over the dropzone.
|
|
167
|
+
* @event dropzone-dragleave - Dispatched when files are dragged out of the dropzone.
|
|
168
|
+
* @event dropzone-error - Dispatched when there is any error in the process of reading dropped files or directories.
|
|
177
169
|
*
|
|
178
|
-
* @method
|
|
170
|
+
* @method define - Static method. Registers the custom element with the browser's CustomElementRegistry unless it has already been defined.
|
|
179
171
|
* @method openFileDialog - Instance method. Opens the file dialog programmatically.
|
|
180
172
|
*/
|
|
181
173
|
class FilesDropzone extends HTMLElement {
|
|
@@ -201,10 +193,10 @@ class FilesDropzone extends HTMLElement {
|
|
|
201
193
|
INVALID_MIME_TYPE: 'INVALID_MIME_TYPE'
|
|
202
194
|
});
|
|
203
195
|
|
|
204
|
-
/** @type {
|
|
196
|
+
/** @type {HTMLInputElement | null} */
|
|
205
197
|
#fileInput = null;
|
|
206
198
|
|
|
207
|
-
/** @type {
|
|
199
|
+
/** @type {HTMLElement | null} */
|
|
208
200
|
#dropzoneEl = null;
|
|
209
201
|
|
|
210
202
|
constructor() {
|
|
@@ -216,7 +208,7 @@ class FilesDropzone extends HTMLElement {
|
|
|
216
208
|
}
|
|
217
209
|
|
|
218
210
|
if (this.shadowRoot) {
|
|
219
|
-
this.#fileInput = /** @type {
|
|
211
|
+
this.#fileInput = /** @type {HTMLInputElement | null} */ (this.shadowRoot.getElementById('file-input'));
|
|
220
212
|
this.#dropzoneEl = this.shadowRoot.getElementById('dropzone');
|
|
221
213
|
}
|
|
222
214
|
}
|
|
@@ -293,7 +285,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
293
285
|
|
|
294
286
|
/**
|
|
295
287
|
* @type {string} - A comma-separated list of unique file type specifiers describing file types to allow.
|
|
296
|
-
* @attribute accept
|
|
288
|
+
* @attribute accept
|
|
289
|
+
* @default ''
|
|
297
290
|
*/
|
|
298
291
|
get accept() {
|
|
299
292
|
return this.getAttribute('accept') || '';
|
|
@@ -305,8 +298,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
305
298
|
|
|
306
299
|
/**
|
|
307
300
|
* @type {boolean} - Determines whether the dropzone is disabled.
|
|
301
|
+
* @attribute disabled
|
|
308
302
|
* @default false
|
|
309
|
-
* @attribute disabled - Reflects the disabled property.
|
|
310
303
|
*/
|
|
311
304
|
get disabled() {
|
|
312
305
|
return this.hasAttribute('disabled');
|
|
@@ -318,8 +311,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
318
311
|
|
|
319
312
|
/**
|
|
320
313
|
* @type {number} - The maximum number of files allowed to be dropped.
|
|
314
|
+
* @attribute max-files
|
|
321
315
|
* @default Infinity
|
|
322
|
-
* @attribute max-files - Reflects the maxFiles property.
|
|
323
316
|
*/
|
|
324
317
|
get maxFiles() {
|
|
325
318
|
const num = Number(this.getAttribute('max-files')) || 0;
|
|
@@ -337,8 +330,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
337
330
|
|
|
338
331
|
/**
|
|
339
332
|
* @type {number} - The maximum file size allowed in bytes.
|
|
333
|
+
* @attribute max-size
|
|
340
334
|
* @default Infinity
|
|
341
|
-
* @attribute max-size - Reflects the maxSize property.
|
|
342
335
|
*/
|
|
343
336
|
get maxSize() {
|
|
344
337
|
const value = this.getAttribute('max-size');
|
|
@@ -358,8 +351,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
358
351
|
|
|
359
352
|
/**
|
|
360
353
|
* @type {number} - The minimum file size allowed in bytes.
|
|
354
|
+
* @attribute min-size
|
|
361
355
|
* @default 0
|
|
362
|
-
* @attribute min-size - Reflects the minSize property.
|
|
363
356
|
*/
|
|
364
357
|
get minSize() {
|
|
365
358
|
const value = this.getAttribute('min-size');
|
|
@@ -379,8 +372,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
379
372
|
|
|
380
373
|
/**
|
|
381
374
|
* @type {boolean} - Allows multiple files to be dropped.
|
|
375
|
+
* @attribute multiple
|
|
382
376
|
* @default false
|
|
383
|
-
* @attribute multiple - Reflects the multiple property.
|
|
384
377
|
*/
|
|
385
378
|
get multiple() {
|
|
386
379
|
return this.hasAttribute('multiple');
|
|
@@ -392,8 +385,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
392
385
|
|
|
393
386
|
/**
|
|
394
387
|
* @type {boolean} - Automatically focuses the dropzone when it's connected to the DOM.
|
|
388
|
+
* @attribute auto-focus
|
|
395
389
|
* @default false
|
|
396
|
-
* @attribute auto-focus - Reflects the autoFocus property.
|
|
397
390
|
*/
|
|
398
391
|
get autoFocus() {
|
|
399
392
|
return this.hasAttribute('auto-focus');
|
|
@@ -405,8 +398,8 @@ class FilesDropzone extends HTMLElement {
|
|
|
405
398
|
|
|
406
399
|
/**
|
|
407
400
|
* @type {boolean} - Prevents the dropzone from applying any styling.
|
|
401
|
+
* @attribute no-style
|
|
408
402
|
* @default false
|
|
409
|
-
* @attribute no-style - Reflects the noStyle property.
|
|
410
403
|
*/
|
|
411
404
|
get noStyle() {
|
|
412
405
|
return this.hasAttribute('no-style');
|
|
@@ -417,37 +410,19 @@ class FilesDropzone extends HTMLElement {
|
|
|
417
410
|
}
|
|
418
411
|
|
|
419
412
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* @template D
|
|
423
|
-
* @param {string} name - The name of the event.
|
|
424
|
-
* @param {D} [detail] - The detail payload of the event.
|
|
425
|
-
* @param {CustomEventInit<D>} [init] - Override the default event initialization options.
|
|
426
|
-
* @returns {boolean} - Returns false if at least one event listener called `preventDefault()`, otherwise true.
|
|
427
|
-
*/
|
|
428
|
-
#emitEvent(name, detail, init) {
|
|
429
|
-
const options = {
|
|
430
|
-
bubbles: true,
|
|
431
|
-
composed: true,
|
|
432
|
-
cancelable: false,
|
|
433
|
-
...init,
|
|
434
|
-
detail
|
|
435
|
-
};
|
|
436
|
-
const evt = new CustomEvent(`${COMPONENT_NAME}-${name}`, options);
|
|
437
|
-
return this.dispatchEvent(evt);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Emit a standardized error event with typed detail.
|
|
413
|
+
* Dispatches an event, using CustomEvent when detail is provided.
|
|
442
414
|
*
|
|
443
|
-
* @
|
|
444
|
-
* @param {
|
|
415
|
+
* @template T
|
|
416
|
+
* @param {string} type - The event type.
|
|
417
|
+
* @param {T} [detail] - Optional event detail.
|
|
418
|
+
* @param {{ cancelable?: boolean }} [options] - Event options.
|
|
419
|
+
* @returns {boolean} Whether the event was not canceled.
|
|
445
420
|
*/
|
|
446
|
-
#
|
|
447
|
-
|
|
448
|
-
const
|
|
421
|
+
#emit(type, detail, { cancelable = false } = {}) {
|
|
422
|
+
const options = { bubbles: true, composed: true, cancelable };
|
|
423
|
+
const event = detail === undefined ? new Event(type, options) : new CustomEvent(type, { ...options, detail });
|
|
449
424
|
|
|
450
|
-
this
|
|
425
|
+
return this.dispatchEvent(event);
|
|
451
426
|
}
|
|
452
427
|
|
|
453
428
|
/**
|
|
@@ -459,7 +434,10 @@ class FilesDropzone extends HTMLElement {
|
|
|
459
434
|
try {
|
|
460
435
|
this.#handleFilesSelect(await getFilesFromEvent(evt));
|
|
461
436
|
} catch (err) {
|
|
462
|
-
this.#
|
|
437
|
+
this.#emit('dropzone-error', {
|
|
438
|
+
code: FilesDropzone.ERROR_CODES.FILE_INPUT_CHANGE_FAILED,
|
|
439
|
+
error: err
|
|
440
|
+
});
|
|
463
441
|
}
|
|
464
442
|
};
|
|
465
443
|
|
|
@@ -471,7 +449,7 @@ class FilesDropzone extends HTMLElement {
|
|
|
471
449
|
return;
|
|
472
450
|
}
|
|
473
451
|
|
|
474
|
-
this.#
|
|
452
|
+
this.#emit('dropzone-dragenter');
|
|
475
453
|
};
|
|
476
454
|
|
|
477
455
|
/**
|
|
@@ -494,7 +472,7 @@ class FilesDropzone extends HTMLElement {
|
|
|
494
472
|
this.#dropzoneEl.part.add('dropzone--dragover');
|
|
495
473
|
}
|
|
496
474
|
|
|
497
|
-
this.#
|
|
475
|
+
this.#emit('dropzone-dragover');
|
|
498
476
|
};
|
|
499
477
|
|
|
500
478
|
/**
|
|
@@ -510,7 +488,7 @@ class FilesDropzone extends HTMLElement {
|
|
|
510
488
|
this.#dropzoneEl.part.remove('dropzone--dragover');
|
|
511
489
|
}
|
|
512
490
|
|
|
513
|
-
this.#
|
|
491
|
+
this.#emit('dropzone-dragleave');
|
|
514
492
|
};
|
|
515
493
|
|
|
516
494
|
/**
|
|
@@ -533,7 +511,10 @@ class FilesDropzone extends HTMLElement {
|
|
|
533
511
|
try {
|
|
534
512
|
this.#handleFilesSelect(await getFilesFromEvent(evt));
|
|
535
513
|
} catch (err) {
|
|
536
|
-
this.#
|
|
514
|
+
this.#emit('dropzone-error', {
|
|
515
|
+
code: FilesDropzone.ERROR_CODES.DROP_EVENT_PROCESSING_FAILED,
|
|
516
|
+
error: err
|
|
517
|
+
});
|
|
537
518
|
}
|
|
538
519
|
};
|
|
539
520
|
|
|
@@ -643,14 +624,14 @@ class FilesDropzone extends HTMLElement {
|
|
|
643
624
|
}
|
|
644
625
|
}
|
|
645
626
|
|
|
646
|
-
this.#
|
|
627
|
+
this.#emit('dropzone-drop', { acceptedFiles, rejectedFiles });
|
|
647
628
|
|
|
648
629
|
if (acceptedFiles.length > 0) {
|
|
649
|
-
this.#
|
|
630
|
+
this.#emit('dropzone-drop-accepted', { acceptedFiles });
|
|
650
631
|
}
|
|
651
632
|
|
|
652
633
|
if (rejectedFiles.length > 0) {
|
|
653
|
-
this.#
|
|
634
|
+
this.#emit('dropzone-drop-rejected', { rejectedFiles });
|
|
654
635
|
}
|
|
655
636
|
|
|
656
637
|
if (this.#fileInput) {
|
|
@@ -673,7 +654,10 @@ class FilesDropzone extends HTMLElement {
|
|
|
673
654
|
try {
|
|
674
655
|
this.#fileInput.showPicker();
|
|
675
656
|
} catch (err) {
|
|
676
|
-
this.#
|
|
657
|
+
this.#emit('dropzone-error', {
|
|
658
|
+
code: FilesDropzone.ERROR_CODES.FILE_DIALOG_OPEN_FAILED,
|
|
659
|
+
error: err
|
|
660
|
+
});
|
|
677
661
|
}
|
|
678
662
|
return;
|
|
679
663
|
}
|
|
@@ -695,10 +679,9 @@ class FilesDropzone extends HTMLElement {
|
|
|
695
679
|
* @param {'accept' | 'disabled' | 'maxFiles' | 'maxSize' | 'minSize' | 'multiple' | 'autoFocus' | 'noStyle'} prop - The property name to upgrade.
|
|
696
680
|
*/
|
|
697
681
|
#upgradeProperty(prop) {
|
|
698
|
-
/** @type {
|
|
699
|
-
const instance = this;
|
|
682
|
+
const instance = /** @type {HTMLElement & Record<string, unknown>} */ (this);
|
|
700
683
|
|
|
701
|
-
if (Object.
|
|
684
|
+
if (Object.hasOwn(instance, prop)) {
|
|
702
685
|
const value = instance[prop];
|
|
703
686
|
delete instance[prop];
|
|
704
687
|
instance[prop] = value;
|
|
@@ -706,18 +689,16 @@ class FilesDropzone extends HTMLElement {
|
|
|
706
689
|
}
|
|
707
690
|
|
|
708
691
|
/**
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
* @param {string} [elementName='files-dropzone'] - The name of the custom element.
|
|
713
|
-
* @example
|
|
692
|
+
* Registers the custom element with the browser's CustomElementRegistry unless
|
|
693
|
+
* it has already been defined.
|
|
714
694
|
*
|
|
715
|
-
*
|
|
695
|
+
* @param {string} [tagName='files-dropzone'] - The tag name to use for the custom element.
|
|
716
696
|
*/
|
|
717
|
-
static
|
|
718
|
-
if (typeof window
|
|
719
|
-
|
|
697
|
+
static define(tagName = 'files-dropzone') {
|
|
698
|
+
if (typeof window === 'undefined' || window.customElements.get(tagName)) {
|
|
699
|
+
return;
|
|
720
700
|
}
|
|
701
|
+
window.customElements.define(tagName, FilesDropzone);
|
|
721
702
|
}
|
|
722
703
|
}
|
|
723
704
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expect, fixture, fixtureCleanup, html } from '@open-wc/testing';
|
|
2
|
+
import { FilesDropzone } from '../src/files-dropzone.js';
|
|
3
|
+
|
|
4
|
+
describe('files-dropzone upgrading', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
fixtureCleanup();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should preserve properties set before the custom element is defined', async () => {
|
|
10
|
+
const container = await fixture(html`<div></div>`);
|
|
11
|
+
const el = document.createElement('files-dropzone');
|
|
12
|
+
|
|
13
|
+
// Set properties before upgrading
|
|
14
|
+
el.accept = 'image/*';
|
|
15
|
+
el.disabled = true;
|
|
16
|
+
el.maxFiles = 5;
|
|
17
|
+
el.maxSize = 1024 * 1024;
|
|
18
|
+
el.minSize = 1024;
|
|
19
|
+
el.multiple = true;
|
|
20
|
+
el.autoFocus = true;
|
|
21
|
+
el.noStyle = true;
|
|
22
|
+
|
|
23
|
+
// Define and explicitly upgrade the custom element
|
|
24
|
+
FilesDropzone.define();
|
|
25
|
+
customElements.upgrade(el);
|
|
26
|
+
|
|
27
|
+
// Connect the element so connectedCallback runs
|
|
28
|
+
container.appendChild(el);
|
|
29
|
+
|
|
30
|
+
expect(el.getAttribute('accept')).to.equal('image/*');
|
|
31
|
+
expect(el.getAttribute('disabled')).to.equal('');
|
|
32
|
+
expect(el.getAttribute('max-files')).to.equal('5');
|
|
33
|
+
expect(el.getAttribute('max-size')).to.equal('1048576');
|
|
34
|
+
expect(el.getAttribute('min-size')).to.equal('1024');
|
|
35
|
+
expect(el.getAttribute('multiple')).to.equal('');
|
|
36
|
+
expect(el.getAttribute('auto-focus')).to.equal('');
|
|
37
|
+
expect(el.getAttribute('no-style')).to.equal('');
|
|
38
|
+
});
|
|
39
|
+
});
|