Skip to content

Commit

Permalink
feat: add focus-ring attribute to vaadin-upload-file
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan committed Dec 7, 2021
1 parent 1352f4b commit 81f8131
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 33 deletions.
93 changes: 70 additions & 23 deletions packages/upload/src/vaadin-upload-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import '@vaadin/progress-bar/src/vaadin-progress-bar.js';
import './vaadin-upload-icons.js';
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';

/**
Expand All @@ -15,36 +16,40 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
*
* The following shadow DOM parts are available for styling:
*
* Part name | Description
* ---|---
* `row` | File container
* `info` | Container for file status icon, file name, status and error messages
* `done-icon` | File done status icon
* `warning-icon` | File warning status icon
* `meta` | Container for file name, status and error messages
* `name` | File name
* `error` | Error message, shown when error happens
* `status` | Status message
* `commands` | Container for file command icons
* `start-button` | Start file upload button
* `retry-button` | Retry file upload button
* `remove-button` | Remove file button
* `progress`| Progress bar
* Part name | Description
* -----------------|-------------
* `row` | File container
* `info` | Container for file status icon, file name, status and error messages
* `done-icon` | File done status icon
* `warning-icon` | File warning status icon
* `meta` | Container for file name, status and error messages
* `name` | File name
* `error` | Error message, shown when error happens
* `status` | Status message
* `commands` | Container for file command buttons
* `start-button` | Start file upload button
* `retry-button` | Retry file upload button
* `remove-button` | Remove file button
* `progress` | Progress bar
*
* The following state attributes are available for styling:
*
* Attribute | Description | Part name
* ---|---|---
* `error` | An error has happened during uploading | `:host`
* `indeterminate` | Uploading is in progress, but the progress value is unknown | `:host`
* `uploading` | Uploading is in progress | `:host`
* `complete` | Uploading has finished successfully | `:host`
* Attribute | Description
* -----------------|-------------
* `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.
* `indeterminate` | Uploading is in progress, but the progress value is unknown.
* `uploading` | Uploading is in progress.
* `complete` | Uploading has finished successfully.
*
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
*
* @extends HTMLElement
* @mixes FocusMixin
* @mixes ThemableMixin
*/
class UploadFile extends ThemableMixin(PolymerElement) {
class UploadFile extends FocusMixin(ThemableMixin(PolymerElement)) {
static get template() {
return html`
<style>
Expand Down Expand Up @@ -129,7 +134,17 @@ class UploadFile extends ThemableMixin(PolymerElement) {
return {
file: Object,

i18n: Object
i18n: Object,

/**
* Indicates whether the element can be focused and where it participates in sequential keyboard navigation.
* @protected
*/
tabindex: {
type: Number,
value: 0,
reflectToAttribute: true
}
};
}

Expand All @@ -143,6 +158,38 @@ class UploadFile extends ThemableMixin(PolymerElement) {
];
}

/** @protected */
ready() {
super.ready();

// Handle moving focus to the button on Tab.
this.shadowRoot.addEventListener('focusin', (e) => {
const target = e.composedPath()[0];

if (target.getAttribute('part').endsWith('button')) {
this._setFocused(false);
}
});

// Handle moving focus from the button on Shift Tab.
this.shadowRoot.addEventListener('focusout', (e) => {
if (e.relatedTarget === this) {
this._setFocused(true);
}
});
}

/**
* Override method inherited from `FocusMixin` to mark the file as focused
* only when the host is focused.
* @param {Event} event
* @return {boolean}
* @protected
*/
_shouldSetFocus(event) {
return event.composedPath()[0] === this;
}

/** @private */
_fileAborted(abort) {
if (abort) {
Expand Down
2 changes: 1 addition & 1 deletion packages/upload/src/vaadin-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class Upload extends ElementMixin(ThemableMixin(PolymerElement)) {
<ul id="fileList" part="file-list">
<template is="dom-repeat" items="[[files]]" as="file">
<li>
<vaadin-upload-file tabindex="0" file="[[file]]" i18n="[[i18n]]"></vaadin-upload-file>
<vaadin-upload-file file="[[file]]" i18n="[[i18n]]"></vaadin-upload-file>
</li>
</template>
</ul>
Expand Down
39 changes: 39 additions & 0 deletions packages/upload/test/file.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync } from '@vaadin/testing-helpers';
import { sendKeys } from '@web/test-runner-commands';
import '../vaadin-upload.js';
import { createFile } from './common.js';

Expand Down Expand Up @@ -49,4 +50,42 @@ describe('<vaadin-upload-file> element', () => {
expect(fileElement.hasAttribute('error')).to.be.true;
});
});

describe('focus', () => {
beforeEach(() => {
// Show the "Start" button
fileElement.set('file.held', true);
});

it('should not add focus-ring to the host on programmatic focus', () => {
fileElement.focus();
expect(fileElement.hasAttribute('focus-ring')).to.be.false;
});

it('should add focus-ring to the host on keyboard focus', async () => {
await sendKeys({ press: 'Tab' });
expect(fileElement.hasAttribute('focus-ring')).to.be.true;
});

it('should remove focus-ring when a button is focused', async () => {
await sendKeys({ press: 'Tab' });

// Focus the button
await sendKeys({ press: 'Tab' });

expect(fileElement.hasAttribute('focus-ring')).to.be.false;
});

it('should restore focus-ring when focus moves back', async () => {
const button = fileElement.shadowRoot.querySelector('button');
button.focus();

// Move focus back to the upload file.
await sendKeys({ down: 'Shift' });
await sendKeys({ press: 'Tab' });
await sendKeys({ up: 'Shift' });

expect(fileElement.hasAttribute('focus-ring')).to.be.true;
});
});
});
5 changes: 1 addition & 4 deletions packages/upload/theme/lumo/vaadin-upload-styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,10 @@ registerStyles(
const uploadFile = css`
:host {
padding: var(--lumo-space-s) 0;
}
:host(:focus) {
outline: none;
}
:host(:focus) [part='row'] {
:host([focus-ring]) [part='row'] {
border-radius: var(--lumo-border-radius-s);
box-shadow: 0 0 0 2px var(--lumo-primary-color-50pct);
}
Expand Down
10 changes: 5 additions & 5 deletions packages/upload/theme/material/vaadin-upload-styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,18 @@ registerStyles(
registerStyles(
'vaadin-upload-file',
css`
:host {
outline: none;
}
[part='row'] {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 8px;
}
:host(:focus) {
outline: none;
}
:host(:focus) [part='row'] {
:host([focus-ring]) [part='row'] {
background-color: var(--material-divider-color);
}
Expand Down

0 comments on commit 81f8131

Please sign in to comment.