@vaadin/upload 25.1.0-alpha6 → 25.1.0-alpha8

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/upload",
3
- "version": "25.1.0-alpha6",
3
+ "version": "25.1.0-alpha8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,6 +23,7 @@
23
23
  "src",
24
24
  "vaadin-*.d.ts",
25
25
  "vaadin-*.js",
26
+ "custom-elements.json",
26
27
  "web-types.json",
27
28
  "web-types.lit.json"
28
29
  ],
@@ -34,24 +35,25 @@
34
35
  ],
35
36
  "dependencies": {
36
37
  "@open-wc/dedupe-mixin": "^1.3.0",
37
- "@vaadin/a11y-base": "25.1.0-alpha6",
38
- "@vaadin/button": "25.1.0-alpha6",
39
- "@vaadin/component-base": "25.1.0-alpha6",
40
- "@vaadin/progress-bar": "25.1.0-alpha6",
41
- "@vaadin/vaadin-themable-mixin": "25.1.0-alpha6",
38
+ "@vaadin/a11y-base": "25.1.0-alpha8",
39
+ "@vaadin/button": "25.1.0-alpha8",
40
+ "@vaadin/component-base": "25.1.0-alpha8",
41
+ "@vaadin/progress-bar": "25.1.0-alpha8",
42
+ "@vaadin/vaadin-themable-mixin": "25.1.0-alpha8",
42
43
  "lit": "^3.0.0"
43
44
  },
44
45
  "devDependencies": {
45
- "@vaadin/aura": "25.1.0-alpha6",
46
- "@vaadin/chai-plugins": "25.1.0-alpha6",
47
- "@vaadin/test-runner-commands": "25.1.0-alpha6",
46
+ "@vaadin/aura": "25.1.0-alpha8",
47
+ "@vaadin/chai-plugins": "25.1.0-alpha8",
48
+ "@vaadin/test-runner-commands": "25.1.0-alpha8",
48
49
  "@vaadin/testing-helpers": "^2.0.0",
49
- "@vaadin/vaadin-lumo-styles": "25.1.0-alpha6",
50
+ "@vaadin/vaadin-lumo-styles": "25.1.0-alpha8",
50
51
  "sinon": "^21.0.0"
51
52
  },
53
+ "customElements": "custom-elements.json",
52
54
  "web-types": [
53
55
  "web-types.json",
54
56
  "web-types.lit.json"
55
57
  ],
56
- "gitHead": "da6f4194492cbd77d18c6c1cd8d4d9f072e9ce8d"
58
+ "gitHead": "810590c9c7682a9326c9352df795b5ea4891a71f"
57
59
  }
@@ -70,7 +70,7 @@ import { UploadManager } from './vaadin-upload-manager.js';
70
70
  *
71
71
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
72
72
  *
73
- * @customElement
73
+ * @customElement vaadin-upload-button
74
74
  * @extends HTMLElement
75
75
  * @mixes ButtonMixin
76
76
  * @mixes ElementMixin
@@ -128,7 +128,7 @@ class UploadButton extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(L
128
128
 
129
129
  /**
130
130
  * Whether the button is disabled.
131
- * Returns true if either explicitly disabled or maxFilesReached is true.
131
+ * Returns true if either explicitly disabled, manager is disabled, or maxFilesReached is true.
132
132
  * @type {boolean}
133
133
  * @override
134
134
  */
@@ -138,8 +138,7 @@ class UploadButton extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(L
138
138
 
139
139
  set disabled(value) {
140
140
  this.__explicitDisabled = Boolean(value);
141
- // Set super.disabled to effective value - this triggers Lit's property system correctly
142
- super.disabled = this.__explicitDisabled || this.maxFilesReached;
141
+ super.disabled = this.__effectiveDisabled;
143
142
  }
144
143
 
145
144
  /** @protected */
@@ -182,6 +181,7 @@ class UploadButton extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(L
182
181
  // Clean up manager listener to prevent memory leaks
183
182
  if (this.manager instanceof UploadManager) {
184
183
  this.manager.removeEventListener('max-files-reached-changed', this.__syncFromManager);
184
+ this.manager.removeEventListener('disabled-changed', this.__syncFromManager);
185
185
  }
186
186
  }
187
187
 
@@ -192,8 +192,9 @@ class UploadButton extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(L
192
192
  // Re-attach manager listener when reconnected to DOM
193
193
  if (this.manager instanceof UploadManager) {
194
194
  this.manager.addEventListener('max-files-reached-changed', this.__syncFromManager);
195
- this.__syncFromManager();
195
+ this.manager.addEventListener('disabled-changed', this.__syncFromManager);
196
196
  }
197
+ this.__syncFromManager();
197
198
  }
198
199
 
199
200
  /**
@@ -250,11 +251,13 @@ class UploadButton extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(L
250
251
  // Remove listener from old manager
251
252
  if (oldManager instanceof UploadManager) {
252
253
  oldManager.removeEventListener('max-files-reached-changed', this.__syncFromManager);
254
+ oldManager.removeEventListener('disabled-changed', this.__syncFromManager);
253
255
  }
254
256
 
255
257
  // Add listener to new manager and sync state only when connected
256
258
  if (this.isConnected && manager instanceof UploadManager) {
257
259
  manager.addEventListener('max-files-reached-changed', this.__syncFromManager);
260
+ manager.addEventListener('disabled-changed', this.__syncFromManager);
258
261
  this.__syncFromManager();
259
262
  } else if (this.isConnected) {
260
263
  // No manager - reset state
@@ -262,6 +265,13 @@ class UploadButton extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(L
262
265
  }
263
266
  }
264
267
 
268
+ /** @private */
269
+ get __effectiveDisabled() {
270
+ const noManager = !(this.manager instanceof UploadManager);
271
+ const managerDisabled = !noManager && this.manager.disabled;
272
+ return this.__explicitDisabled || noManager || managerDisabled || this.maxFilesReached;
273
+ }
274
+
265
275
  /** @private */
266
276
  __syncFromManager() {
267
277
  if (this.manager instanceof UploadManager) {
@@ -270,11 +280,7 @@ class UploadButton extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(L
270
280
  this.maxFilesReached = false;
271
281
  }
272
282
 
273
- // Sync effective disabled state
274
- const effectiveDisabled = this.__explicitDisabled || this.maxFilesReached;
275
- if (super.disabled !== effectiveDisabled) {
276
- super.disabled = effectiveDisabled;
277
- }
283
+ super.disabled = this.__effectiveDisabled;
278
284
  }
279
285
 
280
286
  /** @override */
@@ -36,7 +36,7 @@ import type { UploadManager } from './vaadin-upload-manager.js';
36
36
  * Attribute | Description
37
37
  * -------------------|--------------------------------------------
38
38
  * `dragover` | Set when files are being dragged over the element
39
- * `disabled` | Set when the drop zone is explicitly disabled
39
+ * `disabled` | Set when the drop zone is effectively disabled
40
40
  * `max-files-reached`| Set when the manager has reached maxFiles
41
41
  *
42
42
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
@@ -50,6 +50,7 @@ declare class UploadDropZone extends HTMLElement {
50
50
 
51
51
  /**
52
52
  * Whether the drop zone is disabled.
53
+ * Returns true if either explicitly disabled, manager is disabled, or no manager is set.
53
54
  */
54
55
  disabled: boolean;
55
56
 
@@ -42,12 +42,12 @@ import { UploadManager } from './vaadin-upload-manager.js';
42
42
  * Attribute | Description
43
43
  * -------------------|--------------------------------------------
44
44
  * `dragover` | Set when files are being dragged over the element
45
- * `disabled` | Set when the drop zone is explicitly disabled
45
+ * `disabled` | Set when the drop zone is effectively disabled
46
46
  * `max-files-reached`| Set when the manager has reached maxFiles
47
47
  *
48
48
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
49
49
  *
50
- * @customElement
50
+ * @customElement vaadin-upload-drop-zone
51
51
  * @extends HTMLElement
52
52
  * @mixes ElementMixin
53
53
  * @mixes ThemableMixin
@@ -80,12 +80,12 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
80
80
 
81
81
  /**
82
82
  * Whether the drop zone is disabled.
83
+ * Returns true if either explicitly disabled, manager is disabled, or no manager is set.
83
84
  * @type {boolean}
84
85
  */
85
86
  disabled: {
86
87
  type: Boolean,
87
88
  value: false,
88
- reflect: true,
89
89
  },
90
90
 
91
91
  /**
@@ -110,9 +110,27 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
110
110
  };
111
111
  }
112
112
 
113
+ /**
114
+ * Whether the drop zone is disabled.
115
+ * Returns true if either explicitly disabled, manager is disabled, or no manager is set.
116
+ * @type {boolean}
117
+ * @override
118
+ */
119
+ get disabled() {
120
+ return this.__effectiveDisabled;
121
+ }
122
+
123
+ set disabled(value) {
124
+ if (this.__syncingDisabled) return;
125
+ this.__explicitDisabled = Boolean(value);
126
+ this.__syncDisabledState();
127
+ }
128
+
113
129
  constructor() {
114
130
  super();
131
+ this.__explicitDisabled = false;
115
132
  this.__onMaxFilesReachedChanged = this.__onMaxFilesReachedChanged.bind(this);
133
+ this.__syncDisabledState = this.__syncDisabledState.bind(this);
116
134
  }
117
135
 
118
136
  /** @protected */
@@ -128,9 +146,10 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
128
146
  disconnectedCallback() {
129
147
  super.disconnectedCallback();
130
148
 
131
- // Clean up manager listener to prevent memory leaks
149
+ // Clean up manager listeners to prevent memory leaks
132
150
  if (this.manager instanceof UploadManager) {
133
151
  this.manager.removeEventListener('max-files-reached-changed', this.__onMaxFilesReachedChanged);
152
+ this.manager.removeEventListener('disabled-changed', this.__syncDisabledState);
134
153
  }
135
154
  }
136
155
 
@@ -138,13 +157,15 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
138
157
  connectedCallback() {
139
158
  super.connectedCallback();
140
159
 
141
- // Re-attach manager listener when reconnected to DOM
160
+ // Re-attach manager listeners when reconnected to DOM
142
161
  if (this.manager instanceof UploadManager) {
143
162
  this.manager.addEventListener('max-files-reached-changed', this.__onMaxFilesReachedChanged);
163
+ this.manager.addEventListener('disabled-changed', this.__syncDisabledState);
144
164
 
145
- // Sync maxFilesReached state with current manager state
165
+ // Sync state with current manager state
146
166
  this.maxFilesReached = !!this.manager.maxFilesReached;
147
167
  }
168
+ this.__syncDisabledState();
148
169
  }
149
170
 
150
171
  /** @protected */
@@ -152,14 +173,19 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
152
173
  return html`<slot></slot>`;
153
174
  }
154
175
 
176
+ /** @private */
177
+ get __effectiveDisabled() {
178
+ const noManager = !(this.manager instanceof UploadManager);
179
+ return this.__explicitDisabled || noManager || this.manager.disabled || this.maxFilesReached;
180
+ }
181
+
155
182
  /** @private */
156
183
  __onDragover(event) {
157
184
  event.preventDefault();
158
- const effectiveDisabled = this.disabled || this.maxFilesReached;
159
- if (!effectiveDisabled) {
185
+ if (!this.__effectiveDisabled) {
160
186
  this.__dragover = true;
161
187
  }
162
- event.dataTransfer.dropEffect = effectiveDisabled ? 'none' : 'copy';
188
+ event.dataTransfer.dropEffect = this.__effectiveDisabled ? 'none' : 'copy';
163
189
  }
164
190
 
165
191
  /** @private */
@@ -167,7 +193,7 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
167
193
  event.preventDefault();
168
194
  // Only remove dragover if we're actually leaving the drop zone
169
195
  // (not just entering a child element)
170
- if (event.relatedTarget && this.contains(event.relatedTarget)) {
196
+ if (event.target !== this) {
171
197
  return;
172
198
  }
173
199
  this.__dragover = false;
@@ -178,9 +204,7 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
178
204
  event.preventDefault();
179
205
  this.__dragover = false;
180
206
 
181
- // If we have a manager and not disabled, add the files
182
- const effectiveDisabled = this.disabled || this.maxFilesReached;
183
- if (!effectiveDisabled && this.manager instanceof UploadManager) {
207
+ if (!this.__effectiveDisabled) {
184
208
  const files = await getFilesFromDropEvent(event);
185
209
  this.manager.addFiles(files);
186
210
  }
@@ -188,25 +212,40 @@ class UploadDropZone extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjecti
188
212
 
189
213
  /** @private */
190
214
  __managerChanged(manager, oldManager) {
191
- // Remove listener from old manager
215
+ // Remove listeners from old manager
192
216
  if (oldManager instanceof UploadManager) {
193
217
  oldManager.removeEventListener('max-files-reached-changed', this.__onMaxFilesReachedChanged);
218
+ oldManager.removeEventListener('disabled-changed', this.__syncDisabledState);
194
219
  }
195
220
 
196
- // Add listener to new manager
221
+ // Add listeners to new manager
197
222
  if (this.isConnected && manager instanceof UploadManager) {
198
223
  manager.addEventListener('max-files-reached-changed', this.__onMaxFilesReachedChanged);
224
+ manager.addEventListener('disabled-changed', this.__syncDisabledState);
199
225
 
200
- // Sync initial state if manager has maxFilesReached property
226
+ // Sync initial state
201
227
  this.maxFilesReached = !!manager.maxFilesReached;
202
228
  } else {
203
229
  this.maxFilesReached = false;
204
230
  }
231
+
232
+ if (this.isConnected) {
233
+ this.__syncDisabledState();
234
+ }
205
235
  }
206
236
 
207
237
  /** @private */
208
238
  __onMaxFilesReachedChanged(event) {
209
239
  this.maxFilesReached = event.detail.value;
240
+ this.__syncDisabledState();
241
+ }
242
+
243
+ /** @private */
244
+ __syncDisabledState() {
245
+ if (!this.isConnected) return;
246
+ this.__syncingDisabled = true;
247
+ this.toggleAttribute('disabled', this.__effectiveDisabled);
248
+ this.__syncingDisabled = false;
210
249
  }
211
250
  }
212
251
 
@@ -34,6 +34,7 @@ const DEFAULT_I18N = {
34
34
  serverUnavailable: 'Upload failed, please try again later',
35
35
  unexpectedServerError: 'Upload failed due to server error',
36
36
  forbidden: 'Upload forbidden',
37
+ fileTooLarge: 'File is too large',
37
38
  },
38
39
  },
39
40
  units: {
@@ -88,6 +89,7 @@ export const UploadFileListMixin = (superClass) =>
88
89
  constructor() {
89
90
  super();
90
91
  this.__onManagerFilesChanged = this.__onManagerFilesChanged.bind(this);
92
+ this.__onManagerDisabledChanged = this.__onManagerDisabledChanged.bind(this);
91
93
  this.__onFileRetry = this.__onFileRetry.bind(this);
92
94
  this.__onFileAbort = this.__onFileAbort.bind(this);
93
95
  this.__onFileStart = this.__onFileStart.bind(this);
@@ -112,6 +114,7 @@ export const UploadFileListMixin = (superClass) =>
112
114
  // Clean up manager listener to prevent memory leaks
113
115
  if (this.manager instanceof UploadManager) {
114
116
  this.manager.removeEventListener('files-changed', this.__onManagerFilesChanged);
117
+ this.manager.removeEventListener('disabled-changed', this.__onManagerDisabledChanged);
115
118
  }
116
119
  }
117
120
 
@@ -122,8 +125,9 @@ export const UploadFileListMixin = (superClass) =>
122
125
  // Re-attach manager listener when reconnected to DOM
123
126
  if (this.manager instanceof UploadManager) {
124
127
  this.manager.addEventListener('files-changed', this.__onManagerFilesChanged);
128
+ this.manager.addEventListener('disabled-changed', this.__onManagerDisabledChanged);
125
129
 
126
- // Sync state with current manager files
130
+ // Sync state with current manager
127
131
  this.__syncFromManager();
128
132
  }
129
133
  }
@@ -133,11 +137,13 @@ export const UploadFileListMixin = (superClass) =>
133
137
  // Remove listeners from old manager
134
138
  if (oldManager instanceof UploadManager) {
135
139
  oldManager.removeEventListener('files-changed', this.__onManagerFilesChanged);
140
+ oldManager.removeEventListener('disabled-changed', this.__onManagerDisabledChanged);
136
141
  }
137
142
 
138
143
  // Add listeners to new manager only when connected
139
144
  if (this.isConnected && manager instanceof UploadManager) {
140
145
  manager.addEventListener('files-changed', this.__onManagerFilesChanged);
146
+ manager.addEventListener('disabled-changed', this.__onManagerDisabledChanged);
141
147
 
142
148
  // Sync initial state
143
149
  this.__syncFromManager();
@@ -152,6 +158,11 @@ export const UploadFileListMixin = (superClass) =>
152
158
  this.__syncFromManager();
153
159
  }
154
160
 
161
+ /** @private */
162
+ __onManagerDisabledChanged() {
163
+ this.requestContentUpdate();
164
+ }
165
+
155
166
  /** @private */
156
167
  __syncFromManager() {
157
168
  if (this.manager instanceof UploadManager) {
@@ -322,6 +333,8 @@ export const UploadFileListMixin = (superClass) =>
322
333
  /** @private */
323
334
  requestContentUpdate() {
324
335
  const { items, __effectiveI18n: i18n, disabled } = this;
336
+ const managerDisabled = this.manager instanceof UploadManager && this.manager.disabled;
337
+ const effectiveDisabled = disabled || managerDisabled;
325
338
 
326
339
  render(
327
340
  html`
@@ -329,7 +342,7 @@ export const UploadFileListMixin = (superClass) =>
329
342
  (file) => html`
330
343
  <li>
331
344
  <vaadin-upload-file
332
- .disabled="${disabled}"
345
+ .disabled="${effectiveDisabled}"
333
346
  .file="${file}"
334
347
  .complete="${file.complete}"
335
348
  .errorMessage="${file.error}"
@@ -340,7 +353,11 @@ export const UploadFileListMixin = (superClass) =>
340
353
  .status="${file.status}"
341
354
  .uploading="${file.uploading}"
342
355
  .i18n="${i18n}"
343
- theme="${ifDefined(this._theme)}"
356
+ theme="${ifDefined(
357
+ window.Vaadin.featureFlags.modularUpload || window.Vaadin.featureFlags.aiComponents
358
+ ? this._theme
359
+ : undefined,
360
+ )}"
344
361
  ></vaadin-upload-file>
345
362
  </li>
346
363
  `,
@@ -75,7 +75,7 @@ import { UploadFileListMixin } from './vaadin-upload-file-list-mixin.js';
75
75
  *
76
76
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
77
77
  *
78
- * @customElement
78
+ * @customElement vaadin-upload-file-list
79
79
  * @extends HTMLElement
80
80
  * @mixes ThemableMixin
81
81
  * @mixes UploadFileListMixin
@@ -77,7 +77,7 @@ import { UploadFileMixin } from './vaadin-upload-file-mixin.js';
77
77
  *
78
78
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
79
79
  *
80
- * @customElement
80
+ * @customElement vaadin-upload-file
81
81
  * @extends HTMLElement
82
82
  * @mixes UploadFileMixin
83
83
  * @mixes ThemableMixin
@@ -12,7 +12,7 @@ import { uploadIconStyles } from './styles/vaadin-upload-icon-base-styles.js';
12
12
  /**
13
13
  * An element used internally by `<vaadin-upload>`. Not intended to be used separately.
14
14
  *
15
- * @customElement
15
+ * @customElement vaadin-upload-icon
16
16
  * @extends HTMLElement
17
17
  * @private
18
18
  */
@@ -9,7 +9,13 @@ export type UploadFormat = 'raw' | 'multipart';
9
9
 
10
10
  export type FileRejectError = 'tooManyFiles' | 'fileIsTooBig' | 'incorrectFileType';
11
11
 
12
- export type UploadErrorKey = 'timeout' | 'serverUnavailable' | 'unexpectedServerError' | 'forbidden' | 'sendFailed';
12
+ export type UploadErrorKey =
13
+ | 'timeout'
14
+ | 'serverUnavailable'
15
+ | 'unexpectedServerError'
16
+ | 'forbidden'
17
+ | 'sendFailed'
18
+ | 'fileTooLarge';
13
19
 
14
20
  export interface UploadFile extends File {
15
21
  uploadTarget: string;
@@ -125,6 +131,13 @@ export interface UploadManagerOptions {
125
131
  * @default 'file'
126
132
  */
127
133
  formDataName?: string;
134
+
135
+ /**
136
+ * Whether the upload manager is disabled.
137
+ * When true, connected components (upload-button, upload-drop-zone) will be automatically disabled.
138
+ * @default false
139
+ */
140
+ disabled?: boolean;
128
141
  }
129
142
 
130
143
  export interface UploadManagerEventMap {
@@ -147,6 +160,7 @@ export interface UploadManagerEventMap {
147
160
  'upload-abort': CustomEvent<{ file: UploadFile; xhr: XMLHttpRequest }>;
148
161
  'files-changed': CustomEvent<{ value: UploadFile[] }>;
149
162
  'max-files-reached-changed': CustomEvent<{ value: boolean }>;
163
+ 'disabled-changed': CustomEvent<{ value: boolean }>;
150
164
  }
151
165
 
152
166
  /**
@@ -292,6 +306,12 @@ export class UploadManager extends EventTarget {
292
306
  */
293
307
  readonly maxFilesReached: boolean;
294
308
 
309
+ /**
310
+ * Whether the upload manager is disabled.
311
+ * When true, connected components (upload-button, upload-drop-zone) will be automatically disabled.
312
+ */
313
+ disabled: boolean;
314
+
295
315
  /**
296
316
  * Add files to the upload list.
297
317
  */
@@ -4,11 +4,19 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
 
7
+ window.Vaadin = window.Vaadin || {};
8
+ window.Vaadin.featureFlags = window.Vaadin.featureFlags || {};
9
+
7
10
  /**
8
11
  * A pure JavaScript class that manages file upload state and XHR requests.
9
12
  * It has no knowledge of UI components - components should listen to events and
10
13
  * call methods to interact with the manager.
11
14
  *
15
+ * **Note:** This class is experimental and requires the `modularUpload` or `aiComponents` feature flag to be enabled:
16
+ * ```javascript
17
+ * window.Vaadin.featureFlags.modularUpload = true;
18
+ * ```
19
+ *
12
20
  * @example
13
21
  * ```javascript
14
22
  * import { UploadManager } from '@vaadin/upload';
@@ -50,6 +58,7 @@
50
58
  * @fires {CustomEvent} upload-abort - Fired when abort is requested
51
59
  * @fires {CustomEvent} files-changed - Fired when the files array changes
52
60
  * @fires {CustomEvent} max-files-reached-changed - Fired when maxFilesReached changes
61
+ * @fires {CustomEvent} disabled-changed - Fired when disabled changes
53
62
  */
54
63
  export class UploadManager extends EventTarget {
55
64
  /** @type {Array<UploadFile>} */
@@ -58,6 +67,9 @@ export class UploadManager extends EventTarget {
58
67
  /** @type {boolean} */
59
68
  #maxFilesReached = false;
60
69
 
70
+ /** @type {boolean} */
71
+ #disabled = false;
72
+
61
73
  /** @type {Array<UploadFile>} */
62
74
  #uploadQueue = [];
63
75
 
@@ -91,10 +103,17 @@ export class UploadManager extends EventTarget {
91
103
  * @param {string} [options.uploadFormat='raw'] - Specifies the upload format to use when sending files to the server. 'raw': Send file as raw binary data with the file's MIME type as Content-Type (default). 'multipart': Send file using multipart/form-data encoding.
92
104
  * @param {number} [options.maxConcurrentUploads=3] - Specifies the maximum number of files that can be uploaded simultaneously. This helps prevent browser performance degradation and XHR limitations when uploading large numbers of files. Files exceeding this limit will be queued and uploaded as active uploads complete.
93
105
  * @param {string} [options.formDataName='file'] - Specifies the 'name' property at Content-Disposition for multipart uploads. This property is ignored when uploadFormat is 'raw'.
106
+ * @param {boolean} [options.disabled=false] - Whether the upload manager is disabled. When true, connected components (upload-button, upload-drop-zone) will be automatically disabled.
94
107
  */
95
108
  constructor(options = {}) {
96
109
  super();
97
110
 
111
+ if (!window.Vaadin.featureFlags.modularUpload && !window.Vaadin.featureFlags.aiComponents) {
112
+ throw new Error(
113
+ 'UploadManager requires the modularUpload feature flag. Enable it with: window.Vaadin.featureFlags.modularUpload = true',
114
+ );
115
+ }
116
+
98
117
  // Configuration properties - use setters for validation
99
118
  this.target = options.target || '';
100
119
  this.method = options.method || 'POST';
@@ -108,6 +127,7 @@ export class UploadManager extends EventTarget {
108
127
  this.uploadFormat = options.uploadFormat || 'raw';
109
128
  this.maxConcurrentUploads = options.maxConcurrentUploads === undefined ? 3 : options.maxConcurrentUploads;
110
129
  this.formDataName = options.formDataName || 'file';
130
+ this.disabled = options.disabled === undefined ? false : options.disabled;
111
131
  }
112
132
 
113
133
  /**
@@ -241,6 +261,27 @@ export class UploadManager extends EventTarget {
241
261
  return this.#maxFilesReached;
242
262
  }
243
263
 
264
+ /**
265
+ * Whether the upload manager is disabled.
266
+ * When true, connected components (upload-button, upload-drop-zone) will be automatically disabled.
267
+ * @type {boolean}
268
+ */
269
+ get disabled() {
270
+ return this.#disabled;
271
+ }
272
+
273
+ set disabled(value) {
274
+ const disabled = Boolean(value);
275
+ if (disabled !== this.#disabled) {
276
+ this.#disabled = disabled;
277
+ this.dispatchEvent(
278
+ new CustomEvent('disabled-changed', {
279
+ detail: { value: disabled },
280
+ }),
281
+ );
282
+ }
283
+ }
284
+
244
285
  /**
245
286
  * Add files to the upload list.
246
287
  * @param {FileList|File[]} files - Files to add
@@ -503,6 +544,8 @@ export class UploadManager extends EventTarget {
503
544
  file.errorKey = 'serverUnavailable';
504
545
  } else if (xhr.status >= 500) {
505
546
  file.errorKey = 'unexpectedServerError';
547
+ } else if (xhr.status === 413) {
548
+ file.errorKey = 'fileTooLarge';
506
549
  } else if (xhr.status >= 400) {
507
550
  file.errorKey = 'forbidden';
508
551
  }
@@ -54,6 +54,7 @@ export interface UploadI18n {
54
54
  serverUnavailable?: string;
55
55
  unexpectedServerError?: string;
56
56
  forbidden?: string;
57
+ fileTooLarge?: string;
57
58
  };
58
59
  };
59
60
  units?: {
@@ -39,6 +39,7 @@ export const DEFAULT_I18N = {
39
39
  serverUnavailable: 'Upload failed, please try again later',
40
40
  unexpectedServerError: 'Upload failed due to server error',
41
41
  forbidden: 'Upload forbidden',
42
+ fileTooLarge: 'File is too large',
42
43
  },
43
44
  },
44
45
  file: {
@@ -590,7 +591,12 @@ export const UploadMixin = (superClass) =>
590
591
  list.items = [...files];
591
592
  list.i18n = effectiveI18n;
592
593
  list.disabled = disabled;
593
- if (this._theme) {
594
+ if (
595
+ window.Vaadin &&
596
+ window.Vaadin.featureFlags &&
597
+ (window.Vaadin.featureFlags.modularUpload || window.Vaadin.featureFlags.aiComponents) &&
598
+ this._theme
599
+ ) {
594
600
  list.setAttribute('theme', this._theme);
595
601
  } else {
596
602
  list.removeAttribute('theme');
@@ -790,6 +796,8 @@ export const UploadMixin = (superClass) =>
790
796
  file.error = this.__effectiveI18n.uploading.error.serverUnavailable;
791
797
  } else if (xhr.status >= 500) {
792
798
  file.error = this.__effectiveI18n.uploading.error.unexpectedServerError;
799
+ } else if (xhr.status === 413) {
800
+ file.error = this.__effectiveI18n.uploading.error.fileTooLarge;
793
801
  } else if (xhr.status >= 400) {
794
802
  file.error = this.__effectiveI18n.uploading.error.forbidden;
795
803
  }
@@ -101,7 +101,7 @@ import { UploadMixin } from './vaadin-upload-mixin.js';
101
101
  * @fires {CustomEvent} upload-retry - Fired when retry upload is requested.
102
102
  * @fires {CustomEvent} upload-abort - Fired when upload abort is requested.
103
103
  *
104
- * @customElement
104
+ * @customElement vaadin-upload
105
105
  * @extends HTMLElement
106
106
  * @mixes ThemableMixin
107
107
  * @mixes ElementMixin