@vaadin/upload 25.0.3 → 25.1.0-alpha2

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.
@@ -12,13 +12,73 @@ import { uploadFileListStyles } from './styles/vaadin-upload-file-list-base-styl
12
12
  import { UploadFileListMixin } from './vaadin-upload-file-list-mixin.js';
13
13
 
14
14
  /**
15
- * An element used internally by `<vaadin-upload>`. Not intended to be used separately.
15
+ * `<vaadin-upload-file-list>` is a Web Component that displays a list of uploaded files.
16
+ * It automatically syncs files from the manager and forwards file events back to it.
17
+ *
18
+ * ```html
19
+ * <vaadin-upload-file-list></vaadin-upload-file-list>
20
+ * ```
21
+ *
22
+ * The file list must be linked to an UploadManager by setting the `manager` property:
23
+ *
24
+ * ```javascript
25
+ * import { UploadManager } from '@vaadin/upload/vaadin-upload-manager.js';
26
+ *
27
+ * const manager = new UploadManager({ target: '/api/upload' });
28
+ * const fileList = document.querySelector('vaadin-upload-file-list');
29
+ * fileList.manager = manager;
30
+ * ```
31
+ *
32
+ * ### Styling
33
+ *
34
+ * The following shadow DOM parts are available for styling:
35
+ *
36
+ * Part name | Description
37
+ * ----------|-------------
38
+ * `list` | The `<ul>` element wrapping the file items
39
+ *
40
+ * The following state attributes are available for styling:
41
+ *
42
+ * Attribute | Description
43
+ * -----------|-------------
44
+ * `disabled` | Set when the element is disabled
45
+ *
46
+ * The following custom CSS properties are available for styling:
47
+ *
48
+ * Custom CSS property |
49
+ * :--------------------------------------------|
50
+ * `--vaadin-upload-file-list-divider-color` |
51
+ * `--vaadin-upload-file-list-divider-width` |
52
+ * `--vaadin-upload-file-border-radius` |
53
+ * `--vaadin-upload-file-button-background` |
54
+ * `--vaadin-upload-file-button-border-color` |
55
+ * `--vaadin-upload-file-button-border-radius` |
56
+ * `--vaadin-upload-file-button-border-width` |
57
+ * `--vaadin-upload-file-button-text-color` |
58
+ * `--vaadin-upload-file-button-padding` |
59
+ * `--vaadin-upload-file-done-color` |
60
+ * `--vaadin-upload-file-error-color` |
61
+ * `--vaadin-upload-file-error-font-size` |
62
+ * `--vaadin-upload-file-error-font-weight` |
63
+ * `--vaadin-upload-file-error-line-height` |
64
+ * `--vaadin-upload-file-gap` |
65
+ * `--vaadin-upload-file-name-color` |
66
+ * `--vaadin-upload-file-name-font-size` |
67
+ * `--vaadin-upload-file-name-font-weight` |
68
+ * `--vaadin-upload-file-name-line-height` |
69
+ * `--vaadin-upload-file-padding` |
70
+ * `--vaadin-upload-file-status-color` |
71
+ * `--vaadin-upload-file-status-font-size` |
72
+ * `--vaadin-upload-file-status-font-weight` |
73
+ * `--vaadin-upload-file-status-line-height` |
74
+ * `--vaadin-upload-file-warning-color` |
75
+ *
76
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
16
77
  *
17
78
  * @customElement
18
79
  * @extends HTMLElement
19
80
  * @mixes ThemableMixin
20
81
  * @mixes UploadFileListMixin
21
- * @private
22
82
  */
23
83
  class UploadFileList extends UploadFileListMixin(ThemableMixin(PolylitMixin(LitElement))) {
24
84
  static get is() {
@@ -95,6 +95,9 @@ class UploadFile extends UploadFileMixin(ThemableMixin(PolylitMixin(LumoInjectio
95
95
 
96
96
  /** @protected */
97
97
  render() {
98
+ const isFileStartVisible = this.held && !this.uploading && !this.complete;
99
+ const isFileRetryVisible = this.errorMessage;
100
+
98
101
  return html`
99
102
  <div part="done-icon" ?hidden="${!this.complete}" aria-hidden="true"></div>
100
103
  <div part="warning-icon" ?hidden="${!this.errorMessage}" aria-hidden="true"></div>
@@ -111,7 +114,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(PolylitMixin(LumoInjectio
111
114
  part="start-button"
112
115
  file-event="file-start"
113
116
  @click="${this._fireFileEvent}"
114
- ?hidden="${!this.held}"
117
+ ?hidden="${!isFileStartVisible}"
115
118
  ?disabled="${this.disabled}"
116
119
  aria-label="${this.i18n ? this.i18n.file.start : nothing}"
117
120
  aria-describedby="name"
@@ -121,7 +124,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(PolylitMixin(LumoInjectio
121
124
  part="retry-button"
122
125
  file-event="file-retry"
123
126
  @click="${this._fireFileEvent}"
124
- ?hidden="${!this.errorMessage}"
127
+ ?hidden="${!isFileRetryVisible}"
125
128
  ?disabled="${this.disabled}"
126
129
  aria-label="${this.i18n ? this.i18n.file.retry : nothing}"
127
130
  aria-describedby="name"
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2000 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+
7
+ /**
8
+ * Get the files from the drop event. The dropped items may contain a
9
+ * combination of files and directories. If a dropped item is a directory,
10
+ * it will be recursively traversed to get all files.
11
+ *
12
+ * @param {DragEvent} dropEvent - The drop event
13
+ * @returns {Promise<File[]>} - The files from the drop event
14
+ * @private
15
+ */
16
+ export function getFilesFromDropEvent(dropEvent) {
17
+ async function getFilesFromEntry(entry) {
18
+ if (entry.isFile) {
19
+ return new Promise((resolve) => {
20
+ entry.file(resolve, () => resolve([]));
21
+ });
22
+ } else if (entry.isDirectory) {
23
+ const reader = entry.createReader();
24
+ const entries = await new Promise((resolve) => {
25
+ reader.readEntries(resolve, () => resolve([]));
26
+ });
27
+ const files = await Promise.all(entries.map(getFilesFromEntry));
28
+ return files.flat();
29
+ }
30
+ }
31
+
32
+ const containsFolders = Array.from(dropEvent.dataTransfer.items)
33
+ .filter((item) => !!item)
34
+ .filter((item) => typeof item.webkitGetAsEntry === 'function')
35
+ .map((item) => item.webkitGetAsEntry())
36
+ .some((entry) => !!entry && entry.isDirectory);
37
+
38
+ if (!containsFolders) {
39
+ return Promise.resolve(dropEvent.dataTransfer.files ? Array.from(dropEvent.dataTransfer.files) : []);
40
+ }
41
+
42
+ const filePromises = Array.from(dropEvent.dataTransfer.items)
43
+ .map((item) => item.webkitGetAsEntry())
44
+ .filter((entry) => !!entry)
45
+ .map(getFilesFromEntry);
46
+
47
+ return Promise.all(filePromises).then((files) => files.flat());
48
+ }
@@ -0,0 +1,345 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2000 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ export type UploadMethod = 'POST' | 'PUT';
7
+
8
+ export type UploadFormat = 'raw' | 'multipart';
9
+
10
+ export type FileRejectError = 'tooManyFiles' | 'fileIsTooBig' | 'incorrectFileType';
11
+
12
+ export type UploadErrorKey = 'timeout' | 'serverUnavailable' | 'unexpectedServerError' | 'forbidden' | 'sendFailed';
13
+
14
+ export interface UploadFile extends File {
15
+ uploadTarget: string;
16
+ elapsed: number;
17
+ remaining: number;
18
+ progress: number;
19
+ speed: number;
20
+ total: number;
21
+ loaded: number;
22
+ status: string;
23
+ errorKey: UploadErrorKey;
24
+ abort?: boolean;
25
+ complete?: boolean;
26
+ held?: boolean;
27
+ uploading?: boolean;
28
+ indeterminate?: boolean;
29
+ stalled?: boolean;
30
+ formDataName?: string;
31
+ xhr?: XMLHttpRequest;
32
+ }
33
+
34
+ export interface UploadManagerOptions {
35
+ /**
36
+ * The server URL. The default value is an empty string, which means that
37
+ * _window.location_ will be used.
38
+ * @default ''
39
+ */
40
+ target?: string;
41
+
42
+ /**
43
+ * HTTP Method used to send the files. Only POST and PUT are allowed.
44
+ * @default 'POST'
45
+ */
46
+ method?: UploadMethod;
47
+
48
+ /**
49
+ * Key-Value map to send to the server. If you set this property as an
50
+ * attribute, use a valid JSON string, for example:
51
+ * ```html
52
+ * <vaadin-upload headers='{"X-Foo": "Bar"}'></vaadin-upload>
53
+ * ```
54
+ * @default {}
55
+ */
56
+ headers?: Record<string, string>;
57
+
58
+ /**
59
+ * Max time in milliseconds for the entire upload process, if exceeded the
60
+ * request will be aborted. Zero means that there is no timeout.
61
+ * @default 0
62
+ */
63
+ timeout?: number;
64
+
65
+ /**
66
+ * Limit of files to upload, by default it is unlimited. If the value is
67
+ * set to one, native file browser will prevent selecting multiple files.
68
+ * @default Infinity
69
+ */
70
+ maxFiles?: number;
71
+
72
+ /**
73
+ * Specifies the maximum file size in bytes allowed to upload.
74
+ * Notice that it is a client-side constraint, which will be checked before
75
+ * sending the request. Obviously you need to do the same validation in
76
+ * the server-side and be sure that they are aligned.
77
+ * @default Infinity
78
+ */
79
+ maxFileSize?: number;
80
+
81
+ /**
82
+ * Specifies the types of files that the server accepts.
83
+ * Syntax: a comma-separated list of MIME type patterns (wildcards are
84
+ * allowed) or file extensions.
85
+ * Notice that MIME types are widely supported, while file extensions
86
+ * are only implemented in certain browsers, so avoid using it.
87
+ * Example: accept="video/*,image/tiff" or accept=".pdf,audio/mp3"
88
+ * @default ''
89
+ */
90
+ accept?: string;
91
+
92
+ /**
93
+ * Prevents upload(s) from immediately uploading upon adding file(s).
94
+ * When set, you must manually trigger uploads using the `uploadFiles` method.
95
+ * @default false
96
+ */
97
+ noAuto?: boolean;
98
+
99
+ /**
100
+ * Set the withCredentials flag on the request.
101
+ * @default false
102
+ */
103
+ withCredentials?: boolean;
104
+
105
+ /**
106
+ * Specifies the upload format to use when sending files to the server.
107
+ * - 'raw': Send file as raw binary data with the file's MIME type as Content-Type (default)
108
+ * - 'multipart': Send file using multipart/form-data encoding
109
+ * @default 'raw'
110
+ */
111
+ uploadFormat?: UploadFormat;
112
+
113
+ /**
114
+ * Specifies the maximum number of files that can be uploaded simultaneously.
115
+ * This helps prevent browser performance degradation and XHR limitations when
116
+ * uploading large numbers of files. Files exceeding this limit will be queued
117
+ * and uploaded as active uploads complete.
118
+ * @default 3
119
+ */
120
+ maxConcurrentUploads?: number;
121
+
122
+ /**
123
+ * Specifies the 'name' property at Content-Disposition for multipart uploads.
124
+ * This property is ignored when uploadFormat is 'raw'.
125
+ * @default 'file'
126
+ */
127
+ formDataName?: string;
128
+ }
129
+
130
+ export interface UploadManagerEventMap {
131
+ 'file-reject': CustomEvent<{ file: File; error: FileRejectError }>;
132
+ 'file-remove': CustomEvent<{ file: UploadFile; fileIndex: number }>;
133
+ 'upload-before': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
134
+ 'upload-request': CustomEvent<{
135
+ file: UploadFile;
136
+ xhr: XMLHttpRequest;
137
+ uploadFormat: UploadFormat;
138
+ requestBody: UploadFile | FormData;
139
+ formData?: FormData;
140
+ }>;
141
+ 'upload-start': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
142
+ 'upload-progress': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
143
+ 'upload-response': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
144
+ 'upload-success': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
145
+ 'upload-error': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
146
+ 'upload-retry': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
147
+ 'upload-abort': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
148
+ 'files-changed': CustomEvent<{ value: UploadFile[] }>;
149
+ 'max-files-reached-changed': CustomEvent<{ value: boolean }>;
150
+ }
151
+
152
+ /**
153
+ * A pure JavaScript class that manages file upload state and XHR requests.
154
+ * It has no knowledge of UI components - components should listen to events
155
+ * and call methods to interact with the manager.
156
+ *
157
+ * @example
158
+ * ```javascript
159
+ * import { UploadManager } from '@vaadin/upload';
160
+ *
161
+ * const manager = new UploadManager({
162
+ * target: '/api/upload',
163
+ * maxFiles: 5
164
+ * });
165
+ *
166
+ * // Listen to state changes
167
+ * manager.addEventListener('files-changed', (e) => {
168
+ * myFileList.items = e.detail.value;
169
+ * });
170
+ *
171
+ * manager.addEventListener('upload-success', (e) => {
172
+ * console.log('File uploaded:', e.detail.file.name);
173
+ * });
174
+ *
175
+ * // Add files (e.g., from a file input or drop event)
176
+ * fileInput.addEventListener('change', (e) => {
177
+ * manager.addFiles(e.target.files);
178
+ * });
179
+ * ```
180
+ */
181
+ export class UploadManager extends EventTarget {
182
+ /**
183
+ * Create an UploadManager instance.
184
+ */
185
+ constructor(options?: UploadManagerOptions);
186
+
187
+ /**
188
+ * The server URL. The default value is an empty string, which means that
189
+ * _window.location_ will be used.
190
+ */
191
+ target: string;
192
+
193
+ /**
194
+ * HTTP Method used to send the files. Only POST and PUT are allowed.
195
+ */
196
+ method: UploadMethod;
197
+
198
+ /**
199
+ * Key-Value map to send to the server. If you set this property as an
200
+ * attribute, use a valid JSON string, for example:
201
+ * ```html
202
+ * <vaadin-upload headers='{"X-Foo": "Bar"}'></vaadin-upload>
203
+ * ```
204
+ */
205
+ headers: Record<string, string>;
206
+
207
+ /**
208
+ * Max time in milliseconds for the entire upload process, if exceeded the
209
+ * request will be aborted. Zero means that there is no timeout.
210
+ */
211
+ timeout: number;
212
+
213
+ /**
214
+ * Limit of files to upload, by default it is unlimited. If the value is
215
+ * set to one, native file browser will prevent selecting multiple files.
216
+ */
217
+ maxFiles: number;
218
+
219
+ /**
220
+ * Specifies the maximum file size in bytes allowed to upload.
221
+ * Notice that it is a client-side constraint, which will be checked before
222
+ * sending the request. Obviously you need to do the same validation in
223
+ * the server-side and be sure that they are aligned.
224
+ */
225
+ maxFileSize: number;
226
+
227
+ /**
228
+ * Specifies the types of files that the server accepts.
229
+ * Syntax: a comma-separated list of MIME type patterns (wildcards are
230
+ * allowed) or file extensions.
231
+ * Notice that MIME types are widely supported, while file extensions
232
+ * are only implemented in certain browsers, so avoid using it.
233
+ * Example: accept="video/*,image/tiff" or accept=".pdf,audio/mp3"
234
+ */
235
+ accept: string;
236
+
237
+ /**
238
+ * Prevents upload(s) from immediately uploading upon adding file(s).
239
+ * When set, you must manually trigger uploads using the `uploadFiles` method.
240
+ */
241
+ noAuto: boolean;
242
+
243
+ /**
244
+ * Set the withCredentials flag on the request.
245
+ */
246
+ withCredentials: boolean;
247
+
248
+ /**
249
+ * Specifies the upload format to use when sending files to the server.
250
+ * - 'raw': Send file as raw binary data with the file's MIME type as Content-Type (default)
251
+ * - 'multipart': Send file using multipart/form-data encoding
252
+ */
253
+ uploadFormat: UploadFormat;
254
+
255
+ /**
256
+ * Specifies the maximum number of files that can be uploaded simultaneously.
257
+ * This helps prevent browser performance degradation and XHR limitations when
258
+ * uploading large numbers of files. Files exceeding this limit will be queued
259
+ * and uploaded as active uploads complete.
260
+ */
261
+ maxConcurrentUploads: number;
262
+
263
+ /**
264
+ * Specifies the 'name' property at Content-Disposition for multipart uploads.
265
+ * This property is ignored when uploadFormat is 'raw'.
266
+ */
267
+ formDataName: string;
268
+
269
+ /**
270
+ * The array of files being processed, or already uploaded.
271
+ *
272
+ * Each element is a [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File)
273
+ * object with a number of extra properties to track the upload process:
274
+ * - `uploadTarget`: The target URL used to upload this file.
275
+ * - `elapsed`: Elapsed time since the upload started.
276
+ * - `remaining`: Number of seconds remaining for the upload to finish.
277
+ * - `progress`: Percentage of the file already uploaded.
278
+ * - `speed`: Upload speed in kB/s.
279
+ * - `size`: File size in bytes.
280
+ * - `total`: The total size of the data being transmitted or processed
281
+ * - `loaded`: Bytes transferred so far.
282
+ * - `status`: Status of the upload process.
283
+ * - `errorKey`: Error key in case the upload failed.
284
+ * - `abort`: True if the file was canceled by the user.
285
+ * - `complete`: True when the file was transferred to the server.
286
+ * - `uploading`: True while transferring data to the server.
287
+ */
288
+ files: UploadFile[];
289
+
290
+ /**
291
+ * Specifies if the maximum number of files have been uploaded.
292
+ */
293
+ readonly maxFilesReached: boolean;
294
+
295
+ /**
296
+ * Add files to the upload list.
297
+ */
298
+ addFiles(files: FileList | File[]): void;
299
+
300
+ /**
301
+ * Triggers the upload of any files that are not completed.
302
+ *
303
+ * @param files Files being uploaded. Defaults to all outstanding files.
304
+ */
305
+ uploadFiles(files?: UploadFile | UploadFile[]): void;
306
+
307
+ /**
308
+ * Retry a failed upload.
309
+ */
310
+ retryUpload(file: UploadFile): void;
311
+
312
+ /**
313
+ * Abort an upload.
314
+ */
315
+ abortUpload(file: UploadFile): void;
316
+
317
+ /**
318
+ * Remove a file from the list.
319
+ */
320
+ removeFile(file: UploadFile): void;
321
+
322
+ addEventListener<K extends keyof UploadManagerEventMap>(
323
+ type: K,
324
+ listener: (this: UploadManager, ev: UploadManagerEventMap[K]) => void,
325
+ options?: boolean | AddEventListenerOptions,
326
+ ): void;
327
+
328
+ addEventListener(
329
+ type: string,
330
+ listener: EventListenerOrEventListenerObject,
331
+ options?: boolean | AddEventListenerOptions,
332
+ ): void;
333
+
334
+ removeEventListener<K extends keyof UploadManagerEventMap>(
335
+ type: K,
336
+ listener: (this: UploadManager, ev: UploadManagerEventMap[K]) => void,
337
+ options?: boolean | EventListenerOptions,
338
+ ): void;
339
+
340
+ removeEventListener(
341
+ type: string,
342
+ listener: EventListenerOrEventListenerObject,
343
+ options?: boolean | EventListenerOptions,
344
+ ): void;
345
+ }