@georapbox/files-dropzone-element 2.2.0 → 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.
Files changed (49) hide show
  1. package/.editorconfig +15 -0
  2. package/.nvmrc +1 -0
  3. package/.prettierignore +6 -0
  4. package/.prettierrc +11 -0
  5. package/.vscode/extensions.json +8 -0
  6. package/.vscode/settings.json +8 -0
  7. package/.zed/settings.json +9 -0
  8. package/CHANGELOG.md +144 -0
  9. package/README.md +119 -103
  10. package/custom-elements-manifest.config.mjs +48 -0
  11. package/dist/custom-elements.json +35 -61
  12. package/dist/files-dropzone-defined.d.ts +1 -1
  13. package/dist/files-dropzone-defined.d.ts.map +1 -1
  14. package/dist/files-dropzone-defined.js +5 -5
  15. package/dist/files-dropzone-defined.js.map +4 -4
  16. package/dist/files-dropzone.d.ts +38 -52
  17. package/dist/files-dropzone.d.ts.map +1 -1
  18. package/dist/files-dropzone.js +5 -5
  19. package/dist/files-dropzone.js.map +4 -4
  20. package/dist/files-selector.d.ts +9 -0
  21. package/dist/files-selector.d.ts.map +1 -0
  22. package/dist/{utils/is-valid-file.d.ts → is-valid-file.d.ts} +1 -1
  23. package/dist/is-valid-file.d.ts.map +1 -0
  24. package/docs/css/main.css +190 -0
  25. package/docs/css/water.css +674 -0
  26. package/docs/favicon-32x32.png +0 -0
  27. package/docs/index.html +144 -0
  28. package/docs/js/main.js +154 -0
  29. package/docs/lib/files-dropzone.js +93 -0
  30. package/docs/lib/highlight/highlight-github.css +160 -0
  31. package/docs/lib/highlight/highlight.min.js +1160 -0
  32. package/eslint.config.mjs +42 -0
  33. package/package.json +48 -29
  34. package/scripts/build.mjs +32 -0
  35. package/scripts/copy-build-to-docs.mjs +17 -0
  36. package/scripts/dev.mjs +24 -0
  37. package/src/files-dropzone-defined.js +1 -1
  38. package/src/files-dropzone.js +53 -73
  39. package/test/files-dropzone-upgrade.test.js +39 -0
  40. package/test/files-dropzone.test.js +397 -0
  41. package/test/files-selector.test.js +96 -0
  42. package/test/is-valid-file.test.js +78 -0
  43. package/tsconfig.json +15 -0
  44. package/web-test-runner.config.js +13 -0
  45. package/dist/utils/files-selector.d.ts +0 -2
  46. package/dist/utils/files-selector.d.ts.map +0 -1
  47. package/dist/utils/is-valid-file.d.ts.map +0 -1
  48. /package/src/{utils/files-selector.js → files-selector.js} +0 -0
  49. /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,30 +1,38 @@
1
1
  {
2
2
  "name": "@georapbox/files-dropzone-element",
3
- "version": "2.2.0",
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
9
  "customElements": "dist/custom-elements.json",
11
- "files": [
12
- "/src",
13
- "/dist"
14
- ],
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
+ },
15
21
  "scripts": {
16
- "lint": "eslint",
17
- "format": "prettier --ignore-unknown --write .",
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",
18
30
  "test": "web-test-runner",
19
31
  "test:watch": "npm run test -- --watch",
20
32
  "test:coverage": "npm run test -- --coverage",
33
+ "lint": "eslint",
34
+ "format": "prettier --ignore-unknown --write .",
21
35
  "types": "tsc --project tsconfig.json",
22
- "dev:esbuild": "node ./scripts/dev.mjs",
23
- "dev": "npm-run-all clean dev:esbuild",
24
- "serve:dev": "web-dev-server --node-resolve --open /docs/ --watch",
25
- "start": "npm-run-all --parallel \"serve:dev -- {1}\" dev",
26
- "build:esbuild": "node ./scripts/build.mjs",
27
- "build": "npm-run-all clean build:esbuild",
28
36
  "analyze": "custom-elements-manifest analyze",
29
37
  "clean": "rimraf dist",
30
38
  "prepublishOnly": "npm-run-all clean lint test build types analyze"
@@ -49,23 +57,34 @@
49
57
  "homepage": "https://github.com/georapbox/files-dropzone-element#readme",
50
58
  "devDependencies": {
51
59
  "@custom-elements-manifest/analyzer": "~0.11.0",
52
- "@eslint/js": "~9.34.0",
53
- "@esm-bundle/chai": "~4.3.4",
54
- "@open-wc/testing": "~4.0.0",
55
- "@web/dev-server": "~0.4.6",
56
- "@web/test-runner": "~0.20.2",
57
- "@web/test-runner-playwright": "~0.11.0",
58
- "esbuild": "~0.25.9",
59
- "eslint": "~9.34.0",
60
- "eslint-plugin-wc": "~3.0.1",
61
- "globals": "~16.3.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",
62
71
  "npm-run-all": "~4.1.5",
63
- "prettier": "~3.6.2",
64
- "rimraf": "~6.0.1",
65
- "sinon": "~21.0.0",
66
- "typescript": "~5.9.2"
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"
67
77
  },
68
78
  "publishConfig": {
69
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
70
89
  }
71
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);
@@ -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();
@@ -1,5 +1,5 @@
1
1
  import { FilesDropzone } from './files-dropzone.js';
2
2
 
3
- FilesDropzone.defineCustomElement();
3
+ FilesDropzone.define();
4
4
 
5
5
  export { FilesDropzone };
@@ -1,20 +1,13 @@
1
1
  // @ts-check
2
2
 
3
3
  /**
4
- * Represents a value that may be of type T, or null.
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 `files-dropzone-error` event.
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 './utils/is-valid-file.js';
31
- import { getFilesFromEvent } from './utils/files-selector.js';
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 `defineCustomElement` method.
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.
@@ -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 files-dropzone-drop - Fired when files are dropped.
171
- * @event files-dropzone-drop-accepted - Fired when files dropped files are accepted.
172
- * @event files-dropzone-drop-rejected - Fired when files dropped files are rejected.
173
- * @event files-dropzone-dragenter - Fired when files are dragged into the dropzone.
174
- * @event files-dropzone-dragover - Fired when files are dragged over the dropzone.
175
- * @event files-dropzone-dragleave - Fired when files are dragged out of the dropzone.
176
- * @event files-dropzone-error - Fired when there is any error in the process of reading dropped files or directories.
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 defineCustomElement - Static method. Defines a custom element with the given name.
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 {Nullable<HTMLInputElement>} */
196
+ /** @type {HTMLInputElement | null} */
205
197
  #fileInput = null;
206
198
 
207
- /** @type {Nullable<HTMLElement>} */
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 {Nullable<HTMLInputElement>} */ (this.shadowRoot.getElementById('file-input'));
211
+ this.#fileInput = /** @type {HTMLInputElement | null} */ (this.shadowRoot.getElementById('file-input'));
220
212
  this.#dropzoneEl = this.shadowRoot.getElementById('dropzone');
221
213
  }
222
214
  }
@@ -418,37 +410,19 @@ class FilesDropzone extends HTMLElement {
418
410
  }
419
411
 
420
412
  /**
421
- * Emit a custom event with the given name and detail.
422
- *
423
- * @template D
424
- * @param {string} name - The name of the event.
425
- * @param {D} [detail] - The detail payload of the event.
426
- * @param {CustomEventInit<D>} [init] - Override the default event initialization options.
427
- * @returns {boolean} - Returns false if at least one event listener called `preventDefault()`, otherwise true.
428
- */
429
- #emitEvent(name, detail, init) {
430
- const options = {
431
- bubbles: true,
432
- composed: true,
433
- cancelable: false,
434
- ...init,
435
- detail
436
- };
437
- const evt = new CustomEvent(`${COMPONENT_NAME}-${name}`, options);
438
- return this.dispatchEvent(evt);
439
- }
440
-
441
- /**
442
- * Emit a standardized error event with typed detail.
413
+ * Dispatches an event, using CustomEvent when detail is provided.
443
414
  *
444
- * @param {FilesDropzoneErrorCode} code - Stable identifier for the type of error that occurred.
445
- * @param {unknown} error - The underlying error that triggered the event.
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.
446
420
  */
447
- #emitErrorEvent(code, error) {
448
- /** @type {FilesDropzoneErrorDetail} */
449
- const detail = { code, error };
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 });
450
424
 
451
- this.#emitEvent('error', detail);
425
+ return this.dispatchEvent(event);
452
426
  }
453
427
 
454
428
  /**
@@ -460,7 +434,10 @@ class FilesDropzone extends HTMLElement {
460
434
  try {
461
435
  this.#handleFilesSelect(await getFilesFromEvent(evt));
462
436
  } catch (err) {
463
- this.#emitErrorEvent(FilesDropzone.ERROR_CODES.FILE_INPUT_CHANGE_FAILED, err);
437
+ this.#emit('dropzone-error', {
438
+ code: FilesDropzone.ERROR_CODES.FILE_INPUT_CHANGE_FAILED,
439
+ error: err
440
+ });
464
441
  }
465
442
  };
466
443
 
@@ -472,7 +449,7 @@ class FilesDropzone extends HTMLElement {
472
449
  return;
473
450
  }
474
451
 
475
- this.#emitEvent('dragenter');
452
+ this.#emit('dropzone-dragenter');
476
453
  };
477
454
 
478
455
  /**
@@ -495,7 +472,7 @@ class FilesDropzone extends HTMLElement {
495
472
  this.#dropzoneEl.part.add('dropzone--dragover');
496
473
  }
497
474
 
498
- this.#emitEvent('dragover');
475
+ this.#emit('dropzone-dragover');
499
476
  };
500
477
 
501
478
  /**
@@ -511,7 +488,7 @@ class FilesDropzone extends HTMLElement {
511
488
  this.#dropzoneEl.part.remove('dropzone--dragover');
512
489
  }
513
490
 
514
- this.#emitEvent('dragleave');
491
+ this.#emit('dropzone-dragleave');
515
492
  };
516
493
 
517
494
  /**
@@ -534,7 +511,10 @@ class FilesDropzone extends HTMLElement {
534
511
  try {
535
512
  this.#handleFilesSelect(await getFilesFromEvent(evt));
536
513
  } catch (err) {
537
- this.#emitErrorEvent(FilesDropzone.ERROR_CODES.DROP_EVENT_PROCESSING_FAILED, err);
514
+ this.#emit('dropzone-error', {
515
+ code: FilesDropzone.ERROR_CODES.DROP_EVENT_PROCESSING_FAILED,
516
+ error: err
517
+ });
538
518
  }
539
519
  };
540
520
 
@@ -644,14 +624,14 @@ class FilesDropzone extends HTMLElement {
644
624
  }
645
625
  }
646
626
 
647
- this.#emitEvent('drop', { acceptedFiles, rejectedFiles });
627
+ this.#emit('dropzone-drop', { acceptedFiles, rejectedFiles });
648
628
 
649
629
  if (acceptedFiles.length > 0) {
650
- this.#emitEvent('drop-accepted', { acceptedFiles });
630
+ this.#emit('dropzone-drop-accepted', { acceptedFiles });
651
631
  }
652
632
 
653
633
  if (rejectedFiles.length > 0) {
654
- this.#emitEvent('drop-rejected', { rejectedFiles });
634
+ this.#emit('dropzone-drop-rejected', { rejectedFiles });
655
635
  }
656
636
 
657
637
  if (this.#fileInput) {
@@ -674,7 +654,10 @@ class FilesDropzone extends HTMLElement {
674
654
  try {
675
655
  this.#fileInput.showPicker();
676
656
  } catch (err) {
677
- this.#emitErrorEvent(FilesDropzone.ERROR_CODES.FILE_DIALOG_OPEN_FAILED, err);
657
+ this.#emit('dropzone-error', {
658
+ code: FilesDropzone.ERROR_CODES.FILE_DIALOG_OPEN_FAILED,
659
+ error: err
660
+ });
678
661
  }
679
662
  return;
680
663
  }
@@ -696,10 +679,9 @@ class FilesDropzone extends HTMLElement {
696
679
  * @param {'accept' | 'disabled' | 'maxFiles' | 'maxSize' | 'minSize' | 'multiple' | 'autoFocus' | 'noStyle'} prop - The property name to upgrade.
697
680
  */
698
681
  #upgradeProperty(prop) {
699
- /** @type {any} */
700
- const instance = this;
682
+ const instance = /** @type {HTMLElement & Record<string, unknown>} */ (this);
701
683
 
702
- if (Object.prototype.hasOwnProperty.call(instance, prop)) {
684
+ if (Object.hasOwn(instance, prop)) {
703
685
  const value = instance[prop];
704
686
  delete instance[prop];
705
687
  instance[prop] = value;
@@ -707,18 +689,16 @@ class FilesDropzone extends HTMLElement {
707
689
  }
708
690
 
709
691
  /**
710
- * Defines a custom element with the given name.
711
- * The name must contain a dash (-).
712
- *
713
- * @param {string} [elementName='files-dropzone'] - The name of the custom element.
714
- * @example
692
+ * Registers the custom element with the browser's CustomElementRegistry unless
693
+ * it has already been defined.
715
694
  *
716
- * FilesDropzone.defineCustomElement('my-dropzone');
695
+ * @param {string} [tagName='files-dropzone'] - The tag name to use for the custom element.
717
696
  */
718
- static defineCustomElement(elementName = COMPONENT_NAME) {
719
- if (typeof window !== 'undefined' && !window.customElements.get(elementName)) {
720
- window.customElements.define(elementName, FilesDropzone);
697
+ static define(tagName = 'files-dropzone') {
698
+ if (typeof window === 'undefined' || window.customElements.get(tagName)) {
699
+ return;
721
700
  }
701
+ window.customElements.define(tagName, FilesDropzone);
722
702
  }
723
703
  }
724
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
+ });