@georapbox/files-dropzone-element 1.1.1 → 1.2.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.
@@ -1,3 +1,12 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Represents a value that may be of type T, or null.
5
+ *
6
+ * @template T
7
+ * @typedef {T | null} Nullable
8
+ */
9
+
1
10
  import { isValidFile } from './utils/is-valid-file.js';
2
11
  import { getFilesFromEvent } from './utils/files-selector.js';
3
12
 
@@ -8,66 +17,83 @@ const FILE_TOO_SMALL = 'FILE_TOO_SMALL';
8
17
  const INVALID_MIME_TYPE = 'INVALID_MIME_TYPE';
9
18
  const template = document.createElement('template');
10
19
 
11
- template.innerHTML = /* html */`
12
- <style>
13
- *,
14
- *::before,
15
- *::after {
16
- box-sizing: border-box;
17
- }
20
+ const styles = /* css */`
21
+ *,
22
+ *::before,
23
+ *::after {
24
+ box-sizing: border-box;
25
+ }
18
26
 
19
- :host([hidden]),
20
- [hidden] {
21
- display: none !important;
22
- }
27
+ :host([hidden]),
28
+ [hidden] {
29
+ display: none !important;
30
+ }
23
31
 
24
- :host {
25
- --dropzone-border-width: 2px;
26
- --dropzone-border-style: dashed;
27
- --dropzone-border-radius: 0.25rem;
28
- --dropzone-border-color: #6c757d;
29
- --dropzone-border-color-dragover: #0d6efd;
30
- --dropzone-background-color: #ffffff;
31
- --dropzone-background-color-dragover: #f4f4f5;
32
- --dropzone-body-color: #3f3f46;
33
- --dropzone-body-color-dragover: var(--dropzone-body-color);
34
- --dropzone-focus-shadow-rgb: 49,132,253;
35
- --transition-duration: 0.2s;
36
-
37
- display: block;
38
- }
32
+ :host {
33
+ --dropzone-border-width: 2px;
34
+ --dropzone-border-style: dashed;
35
+ --dropzone-border-radius: 0.25rem;
36
+ --dropzone-border-color: #6c757d;
37
+ --dropzone-border-color-dragover: #0d6efd;
38
+ --dropzone-border-color-hover: var(--dropzone-border-color-dragover);
39
+ --dropzone-background-color: #ffffff;
40
+ --dropzone-background-color-dragover: #f4f4f5;
41
+ --dropzone-background-color-hover: var(--dropzone-background-color-dragover);
42
+ --dropzone-body-color: #3f3f46;
43
+ --dropzone-body-color-dragover: var(--dropzone-body-color);
44
+ --dropzone-body-color-hover: var(--dropzone-body-color-dragover);
45
+ --dropzone-focus-shadow-rgb: 49,132,253;
46
+ --dropzone-focus-box-shadow: 0 0 0 0.25rem rgba(var(--dropzone-focus-shadow-rgb), 0.5);
47
+ --transition-duration: 0.2s; /* for backwards compatibility */
48
+ --dropzone-transition-duration: var(--transition-duration);
49
+
50
+ display: block;
51
+ }
39
52
 
40
- :host(:not([no-style])) .dropzone {
41
- border: var(--dropzone-border-width) var(--dropzone-border-style) var(--dropzone-border-color);
42
- border-radius: var(--dropzone-border-radius);
43
- padding: 3rem 1rem;
44
- overflow: hidden;
45
- background-color: var(--dropzone-background-color);
46
- color: var(--dropzone-body-color);
47
- text-align: center;
48
- cursor: pointer;
49
- transition: border var(--transition-duration) ease-in-out, background-color var(--transition-duration) ease-in-out, color var(--transition-duration) ease-in-out, box-shadow var(--transition-duration) ease-in-out;
50
- }
53
+ :host(:not([no-style])) .dropzone {
54
+ border: var(--dropzone-border-width) var(--dropzone-border-style) var(--dropzone-border-color);
55
+ border-radius: var(--dropzone-border-radius);
56
+ padding: 3rem 1rem;
57
+ overflow: hidden;
58
+ background-color: var(--dropzone-background-color);
59
+ color: var(--dropzone-body-color);
60
+ text-align: center;
61
+ cursor: pointer;
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
+ }
51
64
 
52
- :host(:not([no-style])[no-click]) .dropzone {
53
- cursor: default;
54
- }
65
+ :host(:not([no-style])[no-click]) .dropzone {
66
+ cursor: default;
67
+ }
55
68
 
56
- :host(:not([no-style])[disabled]) .dropzone {
57
- opacity: 0.8;
58
- cursor: not-allowed;
59
- }
69
+ :host(:not([no-style])[disabled]) .dropzone {
70
+ opacity: 0.8;
71
+ cursor: not-allowed;
72
+ }
60
73
 
61
- :host(:not([no-style]):not([disabled])) .dropzone--dragover {
62
- border-color: var(--dropzone-border-color-dragover);
63
- background-color: var(--dropzone-background-color-dragover);
64
- color: var(--dropzone-body-color-dragover);
65
- }
74
+ :host(:not([no-style]):not([disabled])) .dropzone--dragover {
75
+ border-color: var(--dropzone-border-color-dragover);
76
+ background-color: var(--dropzone-background-color-dragover);
77
+ color: var(--dropzone-body-color-dragover);
78
+ }
79
+
80
+ :host(:not([no-style]):not([disabled])) .dropzone:focus-visible {
81
+ outline: none;
82
+ box-shadow: var(--dropzone-focus-box-shadow);
83
+ }
66
84
 
67
- :host(:not([no-style]):not([disabled])) .dropzone:focus-visible {
68
- outline: none;
69
- box-shadow: 0 0 0 0.25rem rgba(var(--dropzone-focus-shadow-rgb), 0.5);
85
+ @media (hover: hover) {
86
+ :host(:not([no-style]):not([disabled])) .dropzone:not(.dropzone--dragover):hover {
87
+ border-color: var(--dropzone-border-color-hover);
88
+ background-color: var(--dropzone-background-color-hover);
89
+ color: var(--dropzone-body-color-hover);
70
90
  }
91
+ }
92
+ `;
93
+
94
+ template.innerHTML = /* html */`
95
+ <style>
96
+ ${styles}
71
97
  </style>
72
98
 
73
99
  <input type="file" id="fileInput" hidden>
@@ -77,26 +103,100 @@ template.innerHTML = /* html */`
77
103
  </div>
78
104
  `;
79
105
 
106
+ /**
107
+ * @summary A custom element that allows users to drag and drop files into it.
108
+ * @documentation https://github.com/georapbox/files-dropzone-element
109
+ *
110
+ * @tagname files-dropzone - This is the default tag name, unless overridden by the `defineCustomElement` method.
111
+ *
112
+ * @property {string} accept - A comma-separated list of unique file type specifiers describing file types to allow.
113
+ * @property {boolean} disabled - Determines whether the dropzone is disabled.
114
+ * @property {number} maxFiles - The maximum number of files allowed to be dropped.
115
+ * @property {number} maxSize - The maximum file size allowed in bytes.
116
+ * @property {number} minSize - The minimum file size allowed in bytes.
117
+ * @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
+ * @property {boolean} autoFocus - Automatically focuses the dropzone when it's connected to the DOM.
122
+ * @property {boolean} noStyle - Prevents the dropzone from applying any styling.
123
+ *
124
+ * @attribute {string} accept - Reflects the accept property.
125
+ * @attribute {boolean} disabled - Reflects the disabled property.
126
+ * @attribute {number} max-files - Reflects the maxFiles property.
127
+ * @attribute {number} max-size - Reflects the maxSize property.
128
+ * @attribute {number} min-size - Reflects the minSize property.
129
+ * @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
+ * @attribute {boolean} auto-focus - Reflects the autoFocus property.
134
+ * @attribute {boolean} no-style - Reflects the noStyle property.
135
+ *
136
+ * @slot - The default slot content of the dropzone.
137
+ *
138
+ * @csspart dropzone - The dropzone element.
139
+ * @csspart dropzone--dragover - The state of the dropzone when dragging over it.
140
+ *
141
+ * @cssproperty --dropzone-border-width - The border width of the dropzone.
142
+ * @cssproperty --dropzone-border-style - The border style of the dropzone.
143
+ * @cssproperty --dropzone-border-radius - The border radius of the dropzone.
144
+ * @cssproperty --dropzone-border-color - The border color of the dropzone.
145
+ * @cssproperty --dropzone-border-color-dragover - The border color of the dropzone when dragging over it.
146
+ * @cssproperty --dropzone-border-color-hover - The border color of the dropzone when hovering over it.
147
+ * @cssproperty --dropzone-background-color - The background color of the dropzone.
148
+ * @cssproperty --dropzone-background-color-dragover - The background color of the dropzone when dragging over it.
149
+ * @cssproperty --dropzone-background-color-hover - The background color of the dropzone when hovering over it.
150
+ * @cssproperty --dropzone-body-color - The text color of the dropzone.
151
+ * @cssproperty --dropzone-body-color-dragover - The text color of the dropzone when dragging over it.
152
+ * @cssproperty --dropzone-body-color-hover - The text color of the dropzone when hovering over it.
153
+ * @cssproperty --dropzone-focus-shadow-rgb - The RGB value of the dropzone's focus shadow.
154
+ * @cssproperty --dropzone-focus-box-shadow - The box shadow of the dropzone when focused.
155
+ * @cssproperty --dropzone-transition-duration - The transition's duration for the dropzone area.
156
+ *
157
+ * @event files-dropzone-drop - Fired when files are dropped.
158
+ * @event files-dropzone-drop-accepted - Fired when files dropped files are accepted.
159
+ * @event files-dropzone-drop-rejected - Fired when files dropped files are rejected.
160
+ * @event files-dropzone-dragenter - Fired when files are dragged into the dropzone.
161
+ * @event files-dropzone-dragover - Fired when files are dragged over the dropzone.
162
+ * @event files-dropzone-dragleave - Fired when files are dragged out of the dropzone.
163
+ * @event files-dropzone-error - Fired when there is any error in the process of reading dropped files or directories.
164
+ *
165
+ * @method defineCustomElement - Static method. Defines a custom element with the given name.
166
+ * @method openFileDialog - Instance method. Opens the file dialog programmatically.
167
+ */
80
168
  class FilesDropzone extends HTMLElement {
81
- #fileInput;
82
- #dropzoneEl;
169
+ /** @type {Nullable<HTMLInputElement>} */
170
+ #fileInput = null;
171
+
172
+ /** @type {Nullable<HTMLElement>} */
173
+ #dropzoneEl = null;
83
174
 
84
175
  constructor() {
85
176
  super();
86
177
 
87
178
  if (!this.shadowRoot) {
88
- this.attachShadow({ mode: 'open' });
89
- this.shadowRoot.appendChild(template.content.cloneNode(true));
179
+ const shadowRoot = this.attachShadow({ mode: 'open' });
180
+ shadowRoot.appendChild(template.content.cloneNode(true));
90
181
  }
91
182
 
92
- this.#fileInput = this.shadowRoot.getElementById('fileInput');
93
- this.#dropzoneEl = this.shadowRoot.getElementById('dropzoneEl');
183
+ if (this.shadowRoot) {
184
+ this.#fileInput = /** @type {Nullable<HTMLInputElement>} */(this.shadowRoot.getElementById('fileInput'));
185
+ this.#dropzoneEl = this.shadowRoot.getElementById('dropzoneEl');
186
+ }
94
187
  }
95
188
 
96
189
  static get observedAttributes() {
97
190
  return ['accept', 'disabled', 'multiple', 'no-keyboard'];
98
191
  }
99
192
 
193
+ /**
194
+ * Lifecycle method that is called when attributes are changed, added, removed, or replaced.
195
+ *
196
+ * @param {string} name - The name of the attribute.
197
+ * @param {string} oldValue - The old value of the attribute.
198
+ * @param {string} newValue - The new value of the attribute.
199
+ */
100
200
  attributeChangedCallback(name, oldValue, newValue) {
101
201
  if (name === 'accept' && oldValue !== newValue && this.#fileInput) {
102
202
  this.#fileInput.accept = this.accept;
@@ -106,9 +206,9 @@ class FilesDropzone extends HTMLElement {
106
206
  this.#fileInput.disabled = this.disabled;
107
207
 
108
208
  if (this.disabled) {
109
- this.#dropzoneEl.removeAttribute('tabindex');
209
+ this.#dropzoneEl?.removeAttribute('tabindex');
110
210
  } else {
111
- this.#dropzoneEl.setAttribute('tabindex', '0');
211
+ this.#dropzoneEl?.setAttribute('tabindex', '0');
112
212
  }
113
213
  }
114
214
 
@@ -125,6 +225,9 @@ class FilesDropzone extends HTMLElement {
125
225
  }
126
226
  }
127
227
 
228
+ /**
229
+ * Lifecycle method that is called when the element is added to the DOM.
230
+ */
128
231
  connectedCallback() {
129
232
  this.#upgradeProperty('accept');
130
233
  this.#upgradeProperty('disabled');
@@ -138,47 +241,60 @@ class FilesDropzone extends HTMLElement {
138
241
  this.#upgradeProperty('autoFocus');
139
242
  this.#upgradeProperty('noStyle');
140
243
 
141
- this.#fileInput.addEventListener('change', this.#onFileInputChange);
142
- this.#dropzoneEl.addEventListener('dragenter', this.#onDragEnter);
143
- this.#dropzoneEl.addEventListener('dragover', this.#onDragOver);
144
- this.#dropzoneEl.addEventListener('dragleave', this.#onDragLeave);
145
- this.#dropzoneEl.addEventListener('drop', this.#onDrop);
146
- this.#dropzoneEl.addEventListener('click', this.#onClick);
147
- this.#dropzoneEl.addEventListener('keyup', this.#onKeyUp);
244
+ this.#fileInput?.addEventListener('change', this.#handleFileInputChange);
245
+ this.#dropzoneEl?.addEventListener('dragenter', this.#handleDragEnter);
246
+ this.#dropzoneEl?.addEventListener('dragover', this.#handleDragOver);
247
+ this.#dropzoneEl?.addEventListener('dragleave', this.#handleDragLeave);
248
+ this.#dropzoneEl?.addEventListener('drop', this.#handleDrop);
249
+ this.#dropzoneEl?.addEventListener('click', this.#handleClick);
250
+ this.#dropzoneEl?.addEventListener('keyup', this.#handleKeyUp);
148
251
 
149
- this.autoFocus && this.#dropzoneEl.focus();
252
+ this.autoFocus && this.#dropzoneEl?.focus();
150
253
  }
151
254
 
255
+ /**
256
+ * Lifecycle method that is called when the element is removed from the DOM.
257
+ */
152
258
  disconnectedCallback() {
153
- this.#fileInput.removeEventListener('change', this.#onFileInputChange);
154
- this.#dropzoneEl.removeEventListener('dragenter', this.#onDragEnter);
155
- this.#dropzoneEl.removeEventListener('dragover', this.#onDragOver);
156
- this.#dropzoneEl.removeEventListener('dragleave', this.#onDragLeave);
157
- this.#dropzoneEl.removeEventListener('drop', this.#onDrop);
158
- this.#dropzoneEl.removeEventListener('click', this.#onClick);
159
- this.#dropzoneEl.removeEventListener('keyup', this.#onKeyUp);
259
+ this.#fileInput?.removeEventListener('change', this.#handleFileInputChange);
260
+ this.#dropzoneEl?.removeEventListener('dragenter', this.#handleDragEnter);
261
+ this.#dropzoneEl?.removeEventListener('dragover', this.#handleDragOver);
262
+ this.#dropzoneEl?.removeEventListener('dragleave', this.#handleDragLeave);
263
+ this.#dropzoneEl?.removeEventListener('drop', this.#handleDrop);
264
+ this.#dropzoneEl?.removeEventListener('click', this.#handleClick);
265
+ this.#dropzoneEl?.removeEventListener('keyup', this.#handleKeyUp);
160
266
  }
161
267
 
268
+ /**
269
+ * @type {string} - A comma-separated list of unique file type specifiers describing file types to allow.
270
+ * @attribute accept - Reflects the accept property.
271
+ */
162
272
  get accept() {
163
- return this.getAttribute('accept');
273
+ return this.getAttribute('accept') || '';
164
274
  }
165
275
 
166
276
  set accept(value) {
167
- this.setAttribute('accept', value);
277
+ this.setAttribute('accept', value != null ? value.toString() : value);
168
278
  }
169
279
 
280
+ /**
281
+ * @type {boolean} - Determines whether the dropzone is disabled.
282
+ * @default false
283
+ * @attribute disabled - Reflects the disabled property.
284
+ */
170
285
  get disabled() {
171
286
  return this.hasAttribute('disabled');
172
287
  }
173
288
 
174
289
  set disabled(value) {
175
- if (value) {
176
- this.setAttribute('disabled', '');
177
- } else {
178
- this.removeAttribute('disabled');
179
- }
290
+ this.toggleAttribute('disabled', !!value);
180
291
  }
181
292
 
293
+ /**
294
+ * @type {number} - The maximum number of files allowed to be dropped.
295
+ * @default Infinity
296
+ * @attribute max-files - Reflects the maxFiles property.
297
+ */
182
298
  get maxFiles() {
183
299
  const num = Number(this.getAttribute('max-files')) || 0;
184
300
 
@@ -190,9 +306,14 @@ class FilesDropzone extends HTMLElement {
190
306
  }
191
307
 
192
308
  set maxFiles(value) {
193
- this.setAttribute('max-files', value);
309
+ this.setAttribute('max-files', value != null ? value.toString() : value);
194
310
  }
195
311
 
312
+ /**
313
+ * @type {number} - The maximum file size allowed in bytes.
314
+ * @default Infinity
315
+ * @attribute max-size - Reflects the maxSize property.
316
+ */
196
317
  get maxSize() {
197
318
  const value = this.getAttribute('max-size');
198
319
 
@@ -206,9 +327,14 @@ class FilesDropzone extends HTMLElement {
206
327
  }
207
328
 
208
329
  set maxSize(value) {
209
- this.setAttribute('max-size', value);
330
+ this.setAttribute('max-size', value != null ? value.toString() : value);
210
331
  }
211
332
 
333
+ /**
334
+ * @type {number} - The minimum file size allowed in bytes.
335
+ * @default 0
336
+ * @attribute min-size - Reflects the minSize property.
337
+ */
212
338
  get minSize() {
213
339
  const value = this.getAttribute('min-size');
214
340
 
@@ -222,82 +348,93 @@ class FilesDropzone extends HTMLElement {
222
348
  }
223
349
 
224
350
  set minSize(value) {
225
- this.setAttribute('min-size', value);
351
+ this.setAttribute('min-size', value != null ? value.toString() : value);
226
352
  }
227
353
 
354
+ /**
355
+ * @type {boolean} - Allows multiple files to be dropped.
356
+ * @default false
357
+ * @attribute multiple - Reflects the multiple property.
358
+ */
228
359
  get multiple() {
229
360
  return this.hasAttribute('multiple');
230
361
  }
231
362
 
232
363
  set multiple(value) {
233
- if (value) {
234
- this.setAttribute('multiple', '');
235
- } else {
236
- this.removeAttribute('multiple');
237
- }
364
+ this.toggleAttribute('multiple', !!value);
238
365
  }
239
366
 
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
+ */
240
372
  get noClick() {
241
373
  return this.hasAttribute('no-click');
242
374
  }
243
375
 
244
376
  set noClick(value) {
245
- if (value) {
246
- this.setAttribute('no-click', '');
247
- } else {
248
- this.removeAttribute('no-click');
249
- }
377
+ this.toggleAttribute('no-click', !!value);
250
378
  }
251
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
+ */
252
385
  get noDrag() {
253
386
  return this.hasAttribute('no-drag');
254
387
  }
255
388
 
256
389
  set noDrag(value) {
257
- if (value) {
258
- this.setAttribute('no-drag', '');
259
- } else {
260
- this.removeAttribute('no-drag');
261
- }
390
+ this.toggleAttribute('no-drag', !!value);
262
391
  }
263
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
+ */
264
398
  get noKeyboard() {
265
399
  return this.hasAttribute('no-keyboard');
266
400
  }
267
401
 
268
402
  set noKeyboard(value) {
269
- if (value) {
270
- this.setAttribute('no-keyboard', '');
271
- } else {
272
- this.removeAttribute('no-keyboard');
273
- }
403
+ this.toggleAttribute('no-keyboard', !!value);
274
404
  }
275
405
 
406
+ /**
407
+ * @type {boolean} - Automatically focuses the dropzone when it's connected to the DOM.
408
+ * @default false
409
+ * @attribute auto-focus - Reflects the autoFocus property.
410
+ */
276
411
  get autoFocus() {
277
412
  return this.hasAttribute('auto-focus');
278
413
  }
279
414
 
280
415
  set autoFocus(value) {
281
- if (value) {
282
- this.setAttribute('auto-focus', '');
283
- } else {
284
- this.removeAttribute('auto-focus');
285
- }
416
+ this.toggleAttribute('auto-focus', !!value);
286
417
  }
287
418
 
419
+ /**
420
+ * @type {boolean} - Prevents the dropzone from applying any styling.
421
+ * @default false
422
+ * @attribute no-style - Reflects the noStyle property.
423
+ */
288
424
  get noStyle() {
289
425
  return this.hasAttribute('no-style');
290
426
  }
291
427
 
292
428
  set noStyle(value) {
293
- if (value) {
294
- this.setAttribute('no-style', '');
295
- } else {
296
- this.removeAttribute('no-style');
297
- }
429
+ this.toggleAttribute('no-style', !!value);
298
430
  }
299
431
 
300
- #onFileInputChange = async evt => {
432
+ /**
433
+ * Handles the change event of the file input.
434
+ *
435
+ * @param {*} evt - The event object.
436
+ */
437
+ #handleFileInputChange = async evt => {
301
438
  try {
302
439
  this.#handleFilesSelect(await getFilesFromEvent(evt));
303
440
  } catch (error) {
@@ -309,7 +446,10 @@ class FilesDropzone extends HTMLElement {
309
446
  }
310
447
  };
311
448
 
312
- #onDragEnter = () => {
449
+ /**
450
+ * Handles the dragenter event of the dropzone.
451
+ */
452
+ #handleDragEnter = () => {
313
453
  if (this.disabled || this.noDrag) {
314
454
  return;
315
455
  }
@@ -320,7 +460,12 @@ class FilesDropzone extends HTMLElement {
320
460
  }));
321
461
  };
322
462
 
323
- #onDragOver = evt => {
463
+ /**
464
+ * Handles the dragover event of the dropzone.
465
+ *
466
+ * @param {*} evt - The event object.
467
+ */
468
+ #handleDragOver = evt => {
324
469
  evt.preventDefault();
325
470
 
326
471
  if (this.disabled || this.noDrag) {
@@ -330,8 +475,10 @@ class FilesDropzone extends HTMLElement {
330
475
 
331
476
  evt.dataTransfer.dropEffect = 'copy';
332
477
 
333
- this.#dropzoneEl.classList.add('dropzone--dragover');
334
- this.#dropzoneEl.part.add('dropzone--dragover');
478
+ if (this.#dropzoneEl) {
479
+ this.#dropzoneEl.classList.add('dropzone--dragover');
480
+ this.#dropzoneEl.part.add('dropzone--dragover');
481
+ }
335
482
 
336
483
  this.dispatchEvent(new Event(`${COMPONENT_NAME}-dragover`, {
337
484
  bubbles: true,
@@ -339,13 +486,18 @@ class FilesDropzone extends HTMLElement {
339
486
  }));
340
487
  };
341
488
 
342
- #onDragLeave = () => {
489
+ /**
490
+ * Handles the dragleave event of the dropzone.
491
+ */
492
+ #handleDragLeave = () => {
343
493
  if (this.disabled || this.noDrag) {
344
494
  return;
345
495
  }
346
496
 
347
- this.#dropzoneEl.classList.remove('dropzone--dragover');
348
- this.#dropzoneEl.part.remove('dropzone--dragover');
497
+ if (this.#dropzoneEl) {
498
+ this.#dropzoneEl.classList.remove('dropzone--dragover');
499
+ this.#dropzoneEl.part.remove('dropzone--dragover');
500
+ }
349
501
 
350
502
  this.dispatchEvent(new Event(`${COMPONENT_NAME}-dragleave`, {
351
503
  bubbles: true,
@@ -353,15 +505,22 @@ class FilesDropzone extends HTMLElement {
353
505
  }));
354
506
  };
355
507
 
356
- #onDrop = async evt => {
508
+ /**
509
+ * Handles the drop event of the dropzone.
510
+ *
511
+ * @param {*} evt - The event object.
512
+ */
513
+ #handleDrop = async evt => {
357
514
  if (this.disabled || this.noDrag) {
358
515
  return;
359
516
  }
360
517
 
361
518
  evt.preventDefault();
362
519
 
363
- this.#dropzoneEl.classList.remove('dropzone--dragover');
364
- this.#dropzoneEl.part.remove('dropzone--dragover');
520
+ if (this.#dropzoneEl) {
521
+ this.#dropzoneEl.classList.remove('dropzone--dragover');
522
+ this.#dropzoneEl.part.remove('dropzone--dragover');
523
+ }
365
524
 
366
525
  try {
367
526
  this.#handleFilesSelect(await getFilesFromEvent(evt));
@@ -374,24 +533,37 @@ class FilesDropzone extends HTMLElement {
374
533
  }
375
534
  };
376
535
 
377
- #onClick = () => {
536
+ /**
537
+ * Handles the click event of the dropzone.
538
+ */
539
+ #handleClick = () => {
378
540
  if (this.disabled || this.noClick) {
379
541
  return;
380
542
  }
381
543
 
382
- this.#fileInput.click();
544
+ this.#fileInput?.click();
383
545
  };
384
546
 
385
- #onKeyUp = evt => {
547
+ /**
548
+ * Handles the keyup event of the dropzone.
549
+ *
550
+ * @param {*} evt - The event object.
551
+ */
552
+ #handleKeyUp = evt => {
386
553
  if (this.disabled || this.noKeyboard) {
387
554
  return;
388
555
  }
389
556
 
390
557
  if (evt.key === ' ' || evt.key === 'Enter') {
391
- this.#fileInput.click();
558
+ this.#fileInput?.click();
392
559
  }
393
560
  };
394
561
 
562
+ /**
563
+ * Handles the selection of files.
564
+ *
565
+ * @param {File[]} files - The files to handle.
566
+ */
395
567
  #handleFilesSelect(files) {
396
568
  if (!Array.isArray(files) || !files.length) {
397
569
  return;
@@ -492,31 +664,51 @@ class FilesDropzone extends HTMLElement {
492
664
  }));
493
665
  }
494
666
 
495
- this.#fileInput.value = this.#fileInput.defaultValue;
667
+ if (this.#fileInput) {
668
+ this.#fileInput.value = this.#fileInput.defaultValue;
669
+ }
496
670
  }
497
671
 
672
+ /**
673
+ * Opens the file dialog programmatically.
674
+ */
498
675
  openFileDialog() {
499
676
  if (this.disabled) {
500
677
  return;
501
678
  }
502
679
 
503
- this.#fileInput.click();
680
+ this.#fileInput?.click();
504
681
  }
505
682
 
506
683
  /**
507
- * https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
508
684
  * This is to safe guard against cases where, for instance, a framework may have added the element to the page and set a
509
685
  * value on one of its properties, but lazy loaded its definition. Without this guard, the upgraded element would miss that
510
686
  * property and the instance property would prevent the class property setter from ever being called.
687
+ *
688
+ * https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
689
+ *
690
+ * @param {'accept' | 'disabled' | 'maxFiles' | 'maxSize' | 'minSize' | 'multiple' | 'noClick' | 'noDrag' | 'noKeyboard' | 'autoFocus' | 'noStyle'} prop - The property name to upgrade.
511
691
  */
512
692
  #upgradeProperty(prop) {
513
- if (Object.prototype.hasOwnProperty.call(this, prop)) {
514
- const value = this[prop];
515
- delete this[prop];
516
- this[prop] = value;
693
+ /** @type {any} */
694
+ const instance = this;
695
+
696
+ if (Object.prototype.hasOwnProperty.call(instance, prop)) {
697
+ const value = instance[prop];
698
+ delete instance[prop];
699
+ instance[prop] = value;
517
700
  }
518
701
  }
519
702
 
703
+ /**
704
+ * Defines a custom element with the given name.
705
+ * The name must contain a dash (-).
706
+ *
707
+ * @param {string} [elementName='files-dropzone'] - The name of the custom element.
708
+ * @example
709
+ *
710
+ * FilesDropzone.defineCustomElement('my-dropzone');
711
+ */
520
712
  static defineCustomElement(elementName = COMPONENT_NAME) {
521
713
  if (typeof window !== 'undefined' && !window.customElements.get(elementName)) {
522
714
  window.customElements.define(elementName, FilesDropzone);