Skip to content

Commit

Permalink
feat: upload disabled state (#8090)
Browse files Browse the repository at this point in the history
* disable buttons

* prevent drag and drop when disabled

* update theme for disabled state

* prevent focus when disabled

* update docs and types

* update snapshot

* disabled state for material theme

* improve test naming

* remove tabindex to disable focus

* improve test naming

* update snapshot
  • Loading branch information
sissbruecker authored Nov 5, 2024
1 parent 194af49 commit 8eb78da
Show file tree
Hide file tree
Showing 22 changed files with 279 additions and 13 deletions.
3 changes: 3 additions & 0 deletions packages/upload/src/vaadin-lit-upload-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(PolylitMixin(LitElement))
file-event="file-start"
@click="${this._fireFileEvent}"
?hidden="${!this.held}"
?disabled="${this.disabled}"
aria-label="${this.i18n.file.start}"
aria-describedby="name"
></button>
Expand All @@ -60,6 +61,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(PolylitMixin(LitElement))
file-event="file-retry"
@click="${this._fireFileEvent}"
?hidden="${!this.errorMessage}"
?disabled="${this.disabled}"
aria-label="${this.i18n.file.retry}"
aria-describedby="name"
></button>
Expand All @@ -68,6 +70,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(PolylitMixin(LitElement))
part="remove-button"
file-event="file-abort"
@click="${this._fireFileEvent}"
?disabled="${this.disabled}"
aria-label="${this.i18n.file.remove}"
aria-describedby="name"
></button>
Expand Down
14 changes: 12 additions & 2 deletions packages/upload/src/vaadin-upload-file-list-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,20 @@ export const UploadFileListMixin = (superClass) =>
i18n: {
type: Object,
},

/**
* If true, the user cannot interact with this element.
*/
disabled: {
type: Boolean,
value: false,
reflectToAttribute: true,
},
};
}

static get observers() {
return ['__updateItems(items, i18n)'];
return ['__updateItems(items, i18n, disabled)'];
}

/** @private */
Expand All @@ -45,14 +54,15 @@ export const UploadFileListMixin = (superClass) =>
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
*/
requestContentUpdate() {
const { items, i18n } = this;
const { items, i18n, disabled } = this;

render(
html`
${items.map(
(file) => html`
<li>
<vaadin-upload-file
.disabled="${disabled}"
.file="${file}"
.complete="${file.complete}"
.errorMessage="${file.error}"
Expand Down
5 changes: 5 additions & 0 deletions packages/upload/src/vaadin-upload-file-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export declare function UploadFileMixin<T extends Constructor<HTMLElement>>(
): Constructor<FocusMixinClass> & Constructor<UploadFileMixinClass> & T;

export declare class UploadFileMixinClass {
/**
* If true, the user cannot interact with this element.
*/
disabled: boolean;

/**
* True if uploading is completed, false otherwise.
*/
Expand Down
30 changes: 28 additions & 2 deletions packages/upload/src/vaadin-upload-file-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ export const UploadFileMixin = (superClass) =>
class UploadFileMixin extends FocusMixin(superClass) {
static get properties() {
return {
/**
* If true, the user cannot interact with this element.
*/
disabled: {
type: Boolean,
value: false,
reflectToAttribute: true,
},

/**
* True if uploading is completed, false otherwise.
*/
Expand Down Expand Up @@ -91,7 +100,6 @@ export const UploadFileMixin = (superClass) =>
tabindex: {
type: Number,
value: 0,
reflectToAttribute: true,
},

/**
Expand All @@ -111,7 +119,7 @@ export const UploadFileMixin = (superClass) =>
}

static get observers() {
return ['__updateProgress(_progress, progress, indeterminate)'];
return ['__updateTabindex(tabindex, disabled)', '__updateProgress(_progress, progress, indeterminate)'];
}

/** @protected */
Expand Down Expand Up @@ -154,11 +162,29 @@ export const UploadFileMixin = (superClass) =>
return event.composedPath()[0] === this;
}

/** @private */
__disabledChanged(disabled) {
if (disabled) {
this.removeAttribute('tabindex');
} else {
this.setAttribute('tabindex', this.tabindex);
}
}

/** @private */
_errorMessageChanged(errorMessage) {
this.toggleAttribute('error', Boolean(errorMessage));
}

/** @private */
__updateTabindex(tabindex, disabled) {
if (disabled) {
this.removeAttribute('tabindex');
} else {
this.setAttribute('tabindex', tabindex);
}
}

/** @private */
__updateProgress(progress, value, indeterminate) {
if (progress) {
Expand Down
1 change: 1 addition & 0 deletions packages/upload/src/vaadin-upload-file.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface UploadFileEventMap extends HTMLElementEventMap, UploadFileCusto
*
* Attribute | Description
* -----------------|-------------
* `disabled` | Set when the element is disabled
* `focus-ring` | Set when the element is focused using the keyboard.
* `focused` | Set when the element is focused.
* `error` | An error has happened during uploading.
Expand Down
4 changes: 4 additions & 0 deletions packages/upload/src/vaadin-upload-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ registerStyles('vaadin-upload-file', uploadFileStyles, { moduleId: 'vaadin-uploa
*
* Attribute | Description
* -----------------|-------------
* `disabled` | Set when the element is disabled
* `focus-ring` | Set when the element is focused using the keyboard.
* `focused` | Set when the element is focused.
* `error` | An error has happened during uploading.
Expand Down Expand Up @@ -76,6 +77,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(ControllerMixin(PolymerEl
file-event="file-start"
on-click="_fireFileEvent"
hidden$="[[!held]]"
disabled$="[[disabled]]"
aria-label$="[[i18n.file.start]]"
aria-describedby="name"
></button>
Expand All @@ -85,6 +87,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(ControllerMixin(PolymerEl
file-event="file-retry"
on-click="_fireFileEvent"
hidden$="[[!errorMessage]]"
disabled$="[[disabled]]"
aria-label$="[[i18n.file.retry]]"
aria-describedby="name"
></button>
Expand All @@ -93,6 +96,7 @@ class UploadFile extends UploadFileMixin(ThemableMixin(ControllerMixin(PolymerEl
part="remove-button"
file-event="file-abort"
on-click="_fireFileEvent"
disabled$="[[disabled]]"
aria-label$="[[i18n.file.remove]]"
aria-describedby="name"
></button>
Expand Down
5 changes: 5 additions & 0 deletions packages/upload/src/vaadin-upload-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ export type UploadMethod = 'POST' | 'PUT';
export declare function UploadMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<UploadMixinClass> & T;

export declare class UploadMixinClass {
/**
* If true, the user cannot interact with this element.
*/
disabled: boolean;

/**
* Define whether the element supports dropping files on it for uploading.
* By default it's enabled in desktop and disabled in touch devices
Expand Down
25 changes: 18 additions & 7 deletions packages/upload/src/vaadin-upload-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ export const UploadMixin = (superClass) =>
class UploadMixin extends superClass {
static get properties() {
return {
/**
* If true, the user cannot interact with this element.
* @type {boolean}
*/
disabled: {
type: Boolean,
value: false,
reflectToAttribute: true,
},

/**
* Define whether the element supports dropping files on it for uploading.
* By default it's enabled in desktop and disabled in touch devices
Expand Down Expand Up @@ -392,9 +402,9 @@ export const UploadMixin = (superClass) =>

static get observers() {
return [
'__updateAddButton(_addButton, maxFiles, i18n, maxFilesReached)',
'__updateAddButton(_addButton, maxFiles, i18n, maxFilesReached, disabled)',
'__updateDropLabel(_dropLabel, maxFiles, i18n)',
'__updateFileList(_fileList, files, i18n)',
'__updateFileList(_fileList, files, i18n, disabled)',
'__updateMaxFilesReached(maxFiles, files)',
];
}
Expand Down Expand Up @@ -512,9 +522,9 @@ export const UploadMixin = (superClass) =>
}

/** @private */
__updateAddButton(addButton, maxFiles, i18n, maxFilesReached) {
__updateAddButton(addButton, maxFiles, i18n, maxFilesReached, disabled) {
if (addButton) {
addButton.disabled = maxFilesReached;
addButton.disabled = disabled || maxFilesReached;

// Only update text content for the default button element
if (addButton === this._addButtonController.defaultNode) {
Expand All @@ -532,18 +542,19 @@ export const UploadMixin = (superClass) =>
}

/** @private */
__updateFileList(list, files, i18n) {
__updateFileList(list, files, i18n, disabled) {
if (list) {
list.items = [...files];
list.i18n = i18n;
list.disabled = disabled;
}
}

/** @private */
_onDragover(event) {
event.preventDefault();
if (!this.nodrop && !this._dragover) {
this._dragoverValid = !this.maxFilesReached;
this._dragoverValid = !this.maxFilesReached && !this.disabled;
this._dragover = true;
}
event.dataTransfer.dropEffect = !this._dragoverValid || this.nodrop ? 'none' : 'copy';
Expand All @@ -559,7 +570,7 @@ export const UploadMixin = (superClass) =>

/** @private */
async _onDrop(event) {
if (!this.nodrop) {
if (!this.nodrop && !this.disabled) {
event.preventDefault();
this._dragover = this._dragoverValid = false;

Expand Down
1 change: 1 addition & 0 deletions packages/upload/src/vaadin-upload.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export interface UploadEventMap extends HTMLElementEventMap, UploadCustomEventMa
*
* Attribute | Description | Part name
* ---|---|---
* `disabled` | Set when the element is disabled | `:host`
* `nodrop` | Set when drag and drop is disabled (e. g., on touch devices) | `:host`
* `dragover` | A file is being dragged over the element | `:host`
* `dragover-valid` | A dragged file is valid with `maxFiles` and `accept` criteria | `:host`
Expand Down
1 change: 1 addition & 0 deletions packages/upload/src/vaadin-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { UploadMixin } from './vaadin-upload-mixin.js';
*
* Attribute | Description | Part name
* ---|---|---
* `disabled` | Set when the element is disabled | `:host`
* `nodrop` | Set when drag and drop is disabled (e. g., on touch devices) | `:host`
* `dragover` | A file is being dragged over the element | `:host`
* `dragover-valid` | A dragged file is valid with `maxFiles` and `accept` criteria | `:host`
Expand Down
26 changes: 25 additions & 1 deletion packages/upload/test/adding-files.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ describe('adding files', () => {
expect(upload.hasAttribute('dragover')).to.be.false;
});

it('should not have dragover property when max files added', async () => {
it('should not have dragoverValid property when max files added', async () => {
upload.maxFiles = 1;
upload._addFile(createFile(100, 'image/jpeg'));
await nextUpdate(upload);
Expand All @@ -102,6 +102,18 @@ describe('adding files', () => {
expect(upload._dragoverValid).to.be.false;
});

it('should not have dragoverValid property when disabled', async () => {
upload.disabled = true;
upload._addFile(createFile(100, 'image/jpeg'));
await nextUpdate(upload);

upload.dispatchEvent(createDndEvent('dragover'));
await nextUpdate(upload);

expect(upload._dragover).to.be.true;
expect(upload._dragoverValid).to.be.false;
});

it('should add files on drop', async () => {
const entry1 = createFileSystemFileEntry(100, 'image/jpeg');
const entry2 = createFileSystemFileEntry(200, 'text/plain');
Expand Down Expand Up @@ -167,6 +179,18 @@ describe('adding files', () => {
expect(upload.files).to.include(fileEntry._file);
});

it('should not add files on drop when disabled', async () => {
upload.disabled = true;
const entry1 = createFileSystemFileEntry(100, 'image/jpeg');
const entry2 = createFileSystemFileEntry(200, 'text/plain');
const dropEvent = createDndEvent('drop', [entry1, entry2]);
upload.dispatchEvent(dropEvent);
await nextUpdate(upload);
await nextFrame();

expect(upload.files.length).to.equal(0);
});

describe('nodrop flag', () => {
let fileAddSpy, dropEvent;

Expand Down
67 changes: 67 additions & 0 deletions packages/upload/test/dom/__snapshots__/vaadin-upload.test.snap.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,70 @@ snapshots["vaadin-upload shadow default"] =
`;
/* end snapshot vaadin-upload shadow default */

snapshots["vaadin-upload host disabled"] =
`<vaadin-upload disabled="">
<vaadin-button
aria-disabled="true"
disabled=""
role="button"
slot="add-button"
tabindex="-1"
>
Upload Files...
</vaadin-button>
<span slot="drop-label">
Drop files here
</span>
<vaadin-upload-file-list
disabled=""
slot="file-list"
>
<li>
<vaadin-upload-file
complete=""
disabled=""
>
<vaadin-progress-bar
aria-valuemax="1"
aria-valuemin="0"
aria-valuenow="0"
role="progressbar"
slot="progress"
>
</vaadin-progress-bar>
</vaadin-upload-file>
</li>
<li>
<vaadin-upload-file disabled="">
<vaadin-progress-bar
aria-valuemax="1"
aria-valuemin="0"
aria-valuenow="0.6"
role="progressbar"
slot="progress"
>
</vaadin-progress-bar>
</vaadin-upload-file>
</li>
<li>
<vaadin-upload-file
disabled=""
error=""
>
<vaadin-progress-bar
aria-valuemax="1"
aria-valuemin="0"
aria-valuenow="0"
role="progressbar"
slot="progress"
>
</vaadin-progress-bar>
</vaadin-upload-file>
</li>
</vaadin-upload-file-list>
<vaadin-upload-icon slot="drop-label-icon">
</vaadin-upload-icon>
</vaadin-upload>
`;
/* end snapshot vaadin-upload host disabled */

Loading

0 comments on commit 8eb78da

Please sign in to comment.