@georapbox/files-dropzone-element 1.2.1 → 2.0.1

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/package.json CHANGED
@@ -1,51 +1,30 @@
1
1
  {
2
2
  "name": "@georapbox/files-dropzone-element",
3
- "version": "1.2.1",
3
+ "version": "2.0.1",
4
4
  "description": "A custom element that creates a drag and drop zone for files",
5
5
  "main": "dist/files-dropzone.js",
6
6
  "module": "dist/files-dropzone.js",
7
7
  "unpkg": "dist/files-dropzone.js",
8
8
  "type": "module",
9
9
  "types": "dist/files-dropzone.d.ts",
10
- "targets": {
11
- "files-dropzone": {
12
- "context": "browser",
13
- "outputFormat": "esmodule",
14
- "source": "src/files-dropzone.js",
15
- "optimize": true,
16
- "isLibrary": true,
17
- "distDir": "dist",
18
- "engines": {
19
- "browsers": "> 0.5%, last 2 versions, not dead"
20
- }
21
- },
22
- "files-dropzone-defined": {
23
- "context": "browser",
24
- "outputFormat": "esmodule",
25
- "source": "src/files-dropzone-defined.js",
26
- "optimize": true,
27
- "isLibrary": true,
28
- "distDir": "dist",
29
- "engines": {
30
- "browsers": "> 0.5%, last 2 versions, not dead"
31
- }
32
- }
33
- },
34
10
  "files": [
35
11
  "/src",
36
12
  "/dist"
37
13
  ],
38
14
  "scripts": {
39
- "lint": "eslint . --ext .js",
15
+ "lint": "eslint",
16
+ "format": "prettier --ignore-unknown --write .",
40
17
  "test": "web-test-runner",
41
18
  "test:watch": "npm run test -- --watch",
42
19
  "test:coverage": "npm run test -- --coverage",
43
20
  "types": "tsc --project tsconfig.json",
44
- "dev:parcel": "parcel watch",
45
- "dev": "npm-run-all clean dev:parcel",
46
- "build:parcel": "parcel build",
47
- "build": "npm-run-all clean build:parcel",
48
- "clean": "rimraf dist .parcel-cache",
21
+ "dev:esbuild": "node ./scripts/dev.mjs",
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",
27
+ "clean": "rimraf dist",
49
28
  "prepare": "npm-run-all clean lint test build types"
50
29
  },
51
30
  "keywords": [
@@ -67,16 +46,23 @@
67
46
  },
68
47
  "homepage": "https://github.com/georapbox/files-dropzone-element#readme",
69
48
  "devDependencies": {
49
+ "@eslint/js": "~9.9.0",
70
50
  "@esm-bundle/chai": "~4.3.4-fix.0",
71
- "@open-wc/testing": "~3.2.2",
72
- "@web/test-runner": "~0.18.0",
51
+ "@open-wc/testing": "~4.0.0",
52
+ "@web/dev-server": "~0.4.6",
53
+ "@web/test-runner": "~0.18.3",
73
54
  "@web/test-runner-playwright": "~0.11.0",
74
- "eslint": "~8.55.0",
75
- "eslint-plugin-wc": "~2.0.4",
55
+ "esbuild": "~0.23.1",
56
+ "eslint": "~9.9.0",
57
+ "eslint-plugin-wc": "~2.1.1",
58
+ "globals": "~15.9.0",
76
59
  "npm-run-all": "~4.1.5",
77
- "parcel": "~2.9.3",
78
- "rimraf": "~5.0.5",
79
- "sinon": "~17.0.1",
80
- "typescript": "~5.3.3"
60
+ "prettier": "~3.3.3",
61
+ "rimraf": "~6.0.1",
62
+ "sinon": "~18.0.0",
63
+ "typescript": "~5.5.4"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
81
67
  }
82
- }
68
+ }
@@ -17,7 +17,7 @@ const FILE_TOO_SMALL = 'FILE_TOO_SMALL';
17
17
  const INVALID_MIME_TYPE = 'INVALID_MIME_TYPE';
18
18
  const template = document.createElement('template');
19
19
 
20
- const styles = /* css */`
20
+ const styles = /* css */ `
21
21
  *,
22
22
  *::before,
23
23
  *::after {
@@ -62,13 +62,10 @@ const styles = /* css */`
62
62
  transition: border var(--dropzone-transition-duration) ease-in-out, background-color var(--dropzone-transition-duration) ease-in-out, color var(--dropzone-transition-duration) ease-in-out, box-shadow var(--dropzone-transition-duration) ease-in-out;
63
63
  }
64
64
 
65
- :host(:not([no-style])[no-click]) .dropzone {
66
- cursor: default;
67
- }
68
-
69
65
  :host(:not([no-style])[disabled]) .dropzone {
70
66
  opacity: 0.8;
71
67
  cursor: not-allowed;
68
+ user-select: none;
72
69
  }
73
70
 
74
71
  :host(:not([no-style]):not([disabled])) .dropzone--dragover {
@@ -91,14 +88,14 @@ const styles = /* css */`
91
88
  }
92
89
  `;
93
90
 
94
- template.innerHTML = /* html */`
91
+ template.innerHTML = /* html */ `
95
92
  <style>
96
93
  ${styles}
97
94
  </style>
98
95
 
99
- <input type="file" id="fileInput" hidden>
96
+ <input type="file" id="file-input" hidden>
100
97
 
101
- <div part="dropzone" class="dropzone" id="dropzoneEl" tabindex="0" role="presentation">
98
+ <div part="dropzone" class="dropzone" id="dropzone" tabindex="0" role="button" aria-disabled="false">
102
99
  <slot>Drag 'n' drop files here, or click to select files</slot>
103
100
  </div>
104
101
  `;
@@ -115,9 +112,6 @@ template.innerHTML = /* html */`
115
112
  * @property {number} maxSize - The maximum file size allowed in bytes.
116
113
  * @property {number} minSize - The minimum file size allowed in bytes.
117
114
  * @property {boolean} multiple - Allows multiple files to be dropped.
118
- * @property {boolean} noClick - Prevents the file dialog from opening when the dropzone is clicked.
119
- * @property {boolean} noDrag - Prevents the dropzone from reacting to drag events.
120
- * @property {boolean} noKeyboard - Prevents the dropzone from reacting to keyboard events.
121
115
  * @property {boolean} autoFocus - Automatically focuses the dropzone when it's connected to the DOM.
122
116
  * @property {boolean} noStyle - Prevents the dropzone from applying any styling.
123
117
  *
@@ -127,9 +121,6 @@ template.innerHTML = /* html */`
127
121
  * @attribute {number} max-size - Reflects the maxSize property.
128
122
  * @attribute {number} min-size - Reflects the minSize property.
129
123
  * @attribute {boolean} multiple - Reflects the multiple property.
130
- * @attribute {boolean} no-click - Reflects the noClick property.
131
- * @attribute {boolean} no-drag - Reflects the noDrag property.
132
- * @attribute {boolean} no-keyboard - Reflects the noKeyboard property.
133
124
  * @attribute {boolean} auto-focus - Reflects the autoFocus property.
134
125
  * @attribute {boolean} no-style - Reflects the noStyle property.
135
126
  *
@@ -176,18 +167,18 @@ class FilesDropzone extends HTMLElement {
176
167
  super();
177
168
 
178
169
  if (!this.shadowRoot) {
179
- const shadowRoot = this.attachShadow({ mode: 'open' });
170
+ const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true });
180
171
  shadowRoot.appendChild(template.content.cloneNode(true));
181
172
  }
182
173
 
183
174
  if (this.shadowRoot) {
184
- this.#fileInput = /** @type {Nullable<HTMLInputElement>} */(this.shadowRoot.getElementById('fileInput'));
185
- this.#dropzoneEl = this.shadowRoot.getElementById('dropzoneEl');
175
+ this.#fileInput = /** @type {Nullable<HTMLInputElement>} */ (this.shadowRoot.getElementById('file-input'));
176
+ this.#dropzoneEl = this.shadowRoot.getElementById('dropzone');
186
177
  }
187
178
  }
188
179
 
189
180
  static get observedAttributes() {
190
- return ['accept', 'disabled', 'multiple', 'no-keyboard'];
181
+ return ['accept', 'disabled', 'multiple'];
191
182
  }
192
183
 
193
184
  /**
@@ -207,22 +198,16 @@ class FilesDropzone extends HTMLElement {
207
198
 
208
199
  if (this.disabled) {
209
200
  this.#dropzoneEl?.removeAttribute('tabindex');
201
+ this.#dropzoneEl?.setAttribute('aria-disabled', 'true');
210
202
  } else {
211
203
  this.#dropzoneEl?.setAttribute('tabindex', '0');
204
+ this.#dropzoneEl?.setAttribute('aria-disabled', 'false');
212
205
  }
213
206
  }
214
207
 
215
208
  if (name === 'multiple' && oldValue !== newValue && this.#fileInput) {
216
209
  this.#fileInput.multiple = this.multiple;
217
210
  }
218
-
219
- if (name === 'no-keyboard' && oldValue !== newValue && this.#dropzoneEl) {
220
- if (this.noKeyboard) {
221
- this.#dropzoneEl.removeAttribute('tabindex');
222
- } else {
223
- this.#dropzoneEl.setAttribute('tabindex', '0');
224
- }
225
- }
226
211
  }
227
212
 
228
213
  /**
@@ -235,9 +220,6 @@ class FilesDropzone extends HTMLElement {
235
220
  this.#upgradeProperty('maxSize');
236
221
  this.#upgradeProperty('minSize');
237
222
  this.#upgradeProperty('multiple');
238
- this.#upgradeProperty('noClick');
239
- this.#upgradeProperty('noDrag');
240
- this.#upgradeProperty('noKeyboard');
241
223
  this.#upgradeProperty('autoFocus');
242
224
  this.#upgradeProperty('noStyle');
243
225
 
@@ -364,45 +346,6 @@ class FilesDropzone extends HTMLElement {
364
346
  this.toggleAttribute('multiple', !!value);
365
347
  }
366
348
 
367
- /**
368
- * @type {boolean} - Prevents the file dialog from opening when the dropzone is clicked.
369
- * @default false
370
- * @attribute no-click - Reflects the noClick property.
371
- */
372
- get noClick() {
373
- return this.hasAttribute('no-click');
374
- }
375
-
376
- set noClick(value) {
377
- this.toggleAttribute('no-click', !!value);
378
- }
379
-
380
- /**
381
- * @type {boolean} - Prevents the dropzone from reacting to drag events.
382
- * @default false
383
- * @attribute no-drag - Reflects the noDrag property.
384
- */
385
- get noDrag() {
386
- return this.hasAttribute('no-drag');
387
- }
388
-
389
- set noDrag(value) {
390
- this.toggleAttribute('no-drag', !!value);
391
- }
392
-
393
- /**
394
- * @type {boolean} - Prevents the dropzone from reacting to keyboard events.
395
- * @default false
396
- * @attribute no-keyboard - Reflects the noKeyboard property.
397
- */
398
- get noKeyboard() {
399
- return this.hasAttribute('no-keyboard');
400
- }
401
-
402
- set noKeyboard(value) {
403
- this.toggleAttribute('no-keyboard', !!value);
404
- }
405
-
406
349
  /**
407
350
  * @type {boolean} - Automatically focuses the dropzone when it's connected to the DOM.
408
351
  * @default false
@@ -438,11 +381,13 @@ class FilesDropzone extends HTMLElement {
438
381
  try {
439
382
  this.#handleFilesSelect(await getFilesFromEvent(evt));
440
383
  } catch (error) {
441
- this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}-error`, {
442
- bubbles: true,
443
- composed: true,
444
- detail: { error }
445
- }));
384
+ this.dispatchEvent(
385
+ new CustomEvent(`${COMPONENT_NAME}-error`, {
386
+ bubbles: true,
387
+ composed: true,
388
+ detail: { error }
389
+ })
390
+ );
446
391
  }
447
392
  };
448
393
 
@@ -450,14 +395,16 @@ class FilesDropzone extends HTMLElement {
450
395
  * Handles the dragenter event of the dropzone.
451
396
  */
452
397
  #handleDragEnter = () => {
453
- if (this.disabled || this.noDrag) {
398
+ if (this.disabled) {
454
399
  return;
455
400
  }
456
401
 
457
- this.dispatchEvent(new Event(`${COMPONENT_NAME}-dragenter`, {
458
- bubbles: true,
459
- composed: true
460
- }));
402
+ this.dispatchEvent(
403
+ new Event(`${COMPONENT_NAME}-dragenter`, {
404
+ bubbles: true,
405
+ composed: true
406
+ })
407
+ );
461
408
  };
462
409
 
463
410
  /**
@@ -468,7 +415,7 @@ class FilesDropzone extends HTMLElement {
468
415
  #handleDragOver = evt => {
469
416
  evt.preventDefault();
470
417
 
471
- if (this.disabled || this.noDrag) {
418
+ if (this.disabled) {
472
419
  evt.dataTransfer.dropEffect = 'none';
473
420
  return;
474
421
  }
@@ -480,17 +427,19 @@ class FilesDropzone extends HTMLElement {
480
427
  this.#dropzoneEl.part.add('dropzone--dragover');
481
428
  }
482
429
 
483
- this.dispatchEvent(new Event(`${COMPONENT_NAME}-dragover`, {
484
- bubbles: true,
485
- composed: true
486
- }));
430
+ this.dispatchEvent(
431
+ new Event(`${COMPONENT_NAME}-dragover`, {
432
+ bubbles: true,
433
+ composed: true
434
+ })
435
+ );
487
436
  };
488
437
 
489
438
  /**
490
439
  * Handles the dragleave event of the dropzone.
491
440
  */
492
441
  #handleDragLeave = () => {
493
- if (this.disabled || this.noDrag) {
442
+ if (this.disabled) {
494
443
  return;
495
444
  }
496
445
 
@@ -499,10 +448,12 @@ class FilesDropzone extends HTMLElement {
499
448
  this.#dropzoneEl.part.remove('dropzone--dragover');
500
449
  }
501
450
 
502
- this.dispatchEvent(new Event(`${COMPONENT_NAME}-dragleave`, {
503
- bubbles: true,
504
- composed: true
505
- }));
451
+ this.dispatchEvent(
452
+ new Event(`${COMPONENT_NAME}-dragleave`, {
453
+ bubbles: true,
454
+ composed: true
455
+ })
456
+ );
506
457
  };
507
458
 
508
459
  /**
@@ -511,7 +462,7 @@ class FilesDropzone extends HTMLElement {
511
462
  * @param {*} evt - The event object.
512
463
  */
513
464
  #handleDrop = async evt => {
514
- if (this.disabled || this.noDrag) {
465
+ if (this.disabled) {
515
466
  return;
516
467
  }
517
468
 
@@ -525,11 +476,13 @@ class FilesDropzone extends HTMLElement {
525
476
  try {
526
477
  this.#handleFilesSelect(await getFilesFromEvent(evt));
527
478
  } catch (error) {
528
- this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}-error`, {
529
- bubbles: true,
530
- composed: true,
531
- detail: { error }
532
- }));
479
+ this.dispatchEvent(
480
+ new CustomEvent(`${COMPONENT_NAME}-error`, {
481
+ bubbles: true,
482
+ composed: true,
483
+ detail: { error }
484
+ })
485
+ );
533
486
  }
534
487
  };
535
488
 
@@ -537,7 +490,7 @@ class FilesDropzone extends HTMLElement {
537
490
  * Handles the click event of the dropzone.
538
491
  */
539
492
  #handleClick = () => {
540
- if (this.disabled || this.noClick) {
493
+ if (this.disabled) {
541
494
  return;
542
495
  }
543
496
 
@@ -550,7 +503,7 @@ class FilesDropzone extends HTMLElement {
550
503
  * @param {*} evt - The event object.
551
504
  */
552
505
  #handleKeyUp = evt => {
553
- if (this.disabled || this.noKeyboard) {
506
+ if (this.disabled) {
554
507
  return;
555
508
  }
556
509
 
@@ -578,10 +531,12 @@ class FilesDropzone extends HTMLElement {
578
531
  for (const file of files) {
579
532
  rejectedFiles.push({
580
533
  file,
581
- errors: [{
582
- code: TOO_MANY_FILES,
583
- message: `Too many files selected. Only 1 file is allowed.`
584
- }]
534
+ errors: [
535
+ {
536
+ code: TOO_MANY_FILES,
537
+ message: `Too many files selected. Only 1 file is allowed.`
538
+ }
539
+ ]
585
540
  });
586
541
  }
587
542
  } else if (this.multiple && filesLength > this.maxFiles) {
@@ -590,10 +545,12 @@ class FilesDropzone extends HTMLElement {
590
545
  for (const file of files) {
591
546
  rejectedFiles.push({
592
547
  file,
593
- errors: [{
594
- code: TOO_MANY_FILES,
595
- message: `Too many files selected. Only ${this.maxFiles} ${this.maxFiles > 1 ? 'files are' : 'file is'} allowed.`
596
- }]
548
+ errors: [
549
+ {
550
+ code: TOO_MANY_FILES,
551
+ message: `Too many files selected. Only ${this.maxFiles} ${this.maxFiles > 1 ? 'files are' : 'file is'} allowed.`
552
+ }
553
+ ]
597
554
  });
598
555
  }
599
556
  } else {
@@ -635,33 +592,39 @@ class FilesDropzone extends HTMLElement {
635
592
  }
636
593
  }
637
594
 
638
- this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}-drop`, {
639
- bubbles: true,
640
- composed: true,
641
- detail: {
642
- acceptedFiles,
643
- rejectedFiles
644
- }
645
- }));
646
-
647
- if (acceptedFiles.length > 0) {
648
- this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}-drop-accepted`, {
595
+ this.dispatchEvent(
596
+ new CustomEvent(`${COMPONENT_NAME}-drop`, {
649
597
  bubbles: true,
650
598
  composed: true,
651
599
  detail: {
652
- acceptedFiles
600
+ acceptedFiles,
601
+ rejectedFiles
653
602
  }
654
- }));
603
+ })
604
+ );
605
+
606
+ if (acceptedFiles.length > 0) {
607
+ this.dispatchEvent(
608
+ new CustomEvent(`${COMPONENT_NAME}-drop-accepted`, {
609
+ bubbles: true,
610
+ composed: true,
611
+ detail: {
612
+ acceptedFiles
613
+ }
614
+ })
615
+ );
655
616
  }
656
617
 
657
618
  if (rejectedFiles.length > 0) {
658
- this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}-drop-rejected`, {
659
- bubbles: true,
660
- composed: true,
661
- detail: {
662
- rejectedFiles
663
- }
664
- }));
619
+ this.dispatchEvent(
620
+ new CustomEvent(`${COMPONENT_NAME}-drop-rejected`, {
621
+ bubbles: true,
622
+ composed: true,
623
+ detail: {
624
+ rejectedFiles
625
+ }
626
+ })
627
+ );
665
628
  }
666
629
 
667
630
  if (this.#fileInput) {
@@ -687,7 +650,7 @@ class FilesDropzone extends HTMLElement {
687
650
  *
688
651
  * https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
689
652
  *
690
- * @param {'accept' | 'disabled' | 'maxFiles' | 'maxSize' | 'minSize' | 'multiple' | 'noClick' | 'noDrag' | 'noKeyboard' | 'autoFocus' | 'noStyle'} prop - The property name to upgrade.
653
+ * @param {'accept' | 'disabled' | 'maxFiles' | 'maxSize' | 'minSize' | 'multiple' | 'autoFocus' | 'noStyle'} prop - The property name to upgrade.
691
654
  */
692
655
  #upgradeProperty(prop) {
693
656
  /** @type {any} */
@@ -239,7 +239,7 @@ const getFilesFromDataTransferItemList = async dataTransferItemList => {
239
239
  files.push(file);
240
240
  }
241
241
  } else if (entry.isDirectory) {
242
- queue.push(...await readAllDirectoryEntries(entry.createReader()));
242
+ queue.push(...(await readAllDirectoryEntries(entry.createReader())));
243
243
  }
244
244
  }
245
245
 
@@ -13,7 +13,12 @@ export function isValidFile(file, acceptedTypeSpecifiers = '') {
13
13
  }
14
14
 
15
15
  const acceptedMimeTypesList = [
16
- ...new Set(acceptedTypeSpecifiers.split(',').map(v => v.trim()).filter(Boolean))
16
+ ...new Set(
17
+ acceptedTypeSpecifiers
18
+ .split(',')
19
+ .map(v => v.trim())
20
+ .filter(Boolean)
21
+ )
17
22
  ];
18
23
 
19
24
  const fileMimeType = file.type;