Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add KeyboardMixin and make other mixins use it #2432

Merged
merged 7 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/button/src/vaadin-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ class Button extends ActiveMixin(TabindexMixin(FocusMixin(ElementMixin(ThemableM
`;
}

/**
* By default, `Space` is the only possible activation key for a focusable HTML element.
* Nonetheless, the button is an exception as it can be also activated by pressing `Enter`.
* See the "Keyboard Support" section in https://www.w3.org/TR/wai-aria-practices/examples/button/button.html.
*
* @protected
* @override
*/
get _activeKeys() {
return ['Enter', ' '];
}

/** @protected */
ready() {
super.ready();
Expand Down
1 change: 1 addition & 0 deletions packages/field-base/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export { TextFieldMixin } from './src/text-field-mixin.js';
export { ValidateMixin } from './src/validate-mixin.js';
export { TabindexMixin } from './src/tabindex-mixin.js';
export { ActiveMixin } from './src/active-mixin.js';
export { KeyboardMixin } from './src/keyboard-mixin.js';
1 change: 1 addition & 0 deletions packages/field-base/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export { TextFieldMixin } from './src/text-field-mixin.js';
export { ValidateMixin } from './src/validate-mixin.js';
export { TabindexMixin } from './src/tabindex-mixin.js';
export { ActiveMixin } from './src/active-mixin.js';
export { KeyboardMixin } from './src/keyboard-mixin.js';
3 changes: 2 additions & 1 deletion packages/field-base/src/active-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { DisabledMixin } from './disabled-mixin.js';
import { KeyboardMixin } from './keyboard-mixin.js';

/**
* A mixin to toggle the `active` attribute.
Expand All @@ -20,6 +21,6 @@ interface ActiveMixinConstructor {
new (...args: any[]): ActiveMixin;
}

interface ActiveMixin extends DisabledMixin {}
interface ActiveMixin extends DisabledMixin, KeyboardMixin {}

export { ActiveMixinConstructor, ActiveMixin };
72 changes: 47 additions & 25 deletions packages/field-base/src/active-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
*/
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
import { GestureEventListeners } from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import { KeyboardMixin } from './keyboard-mixin.js';
import { DisabledMixin } from './disabled-mixin.js';

const ActiveMixinImplementation = (superclass) =>
class ActiveMixinClass extends DisabledMixin(GestureEventListeners(superclass)) {
class ActiveMixinClass extends DisabledMixin(GestureEventListeners(KeyboardMixin(superclass))) {
/**
* An array of activation keys.
*
Expand All @@ -19,44 +20,25 @@ const ActiveMixinImplementation = (superclass) =>
* @return {!Array<!string>}
*/
get _activeKeys() {
return ['Enter', ' '];
return [' '];
}

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

// POINTERDOWN
this._addEventListenerToNode(this, 'down', () => {
if (this.disabled) return;

this.setAttribute('active', '');
this._setActive(true);
});

// POINTERUP
this._addEventListenerToNode(this, 'up', () => {
this.removeAttribute('active');
this._setActive(false);
});

// KEYDOWN
this.addEventListener('keydown', (event) => {
if (this.disabled) return;

if (this._activeKeys.includes(event.key)) {
this.setAttribute('active', '');
}
});

// KEYUP
this.addEventListener('keyup', (event) => {
if (this._activeKeys.includes(event.key)) {
this.removeAttribute('active');
}
});

// BLUR
this.addEventListener('blur', () => {
this.removeAttribute('active');
this._setActive(false);
});
}

Expand All @@ -68,7 +50,47 @@ const ActiveMixinImplementation = (superclass) =>
// the `active` attribute needs to be manually removed from the element.
// Otherwise, it will preserve on the element until the element is activated once again.
// The case reproduces for `<vaadin-date-picker>` when closing on `Cancel` or `Today` click.
this.removeAttribute('active');
this._setActive(false);
}

/**
* Sets the `active` attribute on the element if an activation key is pressed.
*
* @param {KeyboardEvent} event
* @protected
* @override
*/
_onKeyDown(event) {
super._onKeyDown(event);

if (!this.disabled && this._activeKeys.includes(event.key)) {
this._setActive(true);
}
}

/**
* Removes the `active` attribute from the element if the activation key is released.
*
* @param {KeyboardEvent} event
* @protected
* @override
*/
_onKeyUp(event) {
super._onKeyUp(event);

if (this._activeKeys.includes(event.key)) {
this._setActive(false);
}
}

/**
* Toggles the `active` attribute on the element.
*
* @param {boolean} active
* @protected
*/
_setActive(active) {
this.toggleAttribute('active', active);
}
};

Expand Down
3 changes: 2 additions & 1 deletion packages/field-base/src/clear-button-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { InputMixin } from './input-mixin.js';
import { KeyboardMixin } from './keyboard-mixin.js';

/**
* A mixin to add clear button support to field components.
Expand All @@ -14,7 +15,7 @@ interface ClearButtonMixinConstructor {
new (...args: any[]): ClearButtonMixin;
}

interface ClearButtonMixin extends InputMixin {
interface ClearButtonMixin extends InputMixin, KeyboardMixin {
/**
* Set to true to display the clear icon which clears the input.
* @attr {boolean} clear-button-visible
Expand Down
10 changes: 6 additions & 4 deletions packages/field-base/src/clear-button-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
*/
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
import { InputMixin } from './input-mixin.js';
import { KeyboardMixin } from './keyboard-mixin.js';

const ClearButtonMixinImplementation = (superclass) =>
class ClearButtonMixinClass extends InputMixin(superclass) {
class ClearButtonMixinClass extends InputMixin(KeyboardMixin(superclass)) {
static get properties() {
return {
/**
Expand Down Expand Up @@ -42,8 +43,6 @@ const ClearButtonMixinImplementation = (superclass) =>
ready() {
super.ready();

this.addEventListener('keydown', (e) => this._onKeyDown(e));

if (this.clearElement) {
this.clearElement.addEventListener('click', (e) => this._onClearButtonClick(e));
}
Expand All @@ -62,10 +61,13 @@ const ClearButtonMixinImplementation = (superclass) =>
}

/**
* @param {Event} event
* @param {KeyboardEvent} event
* @protected
* @override
*/
_onKeyDown(event) {
super._onKeyDown(event);

if (event.key === 'Escape' && this.clearButtonVisible && this._clearOnEsc) {
const dispatchChange = !!this.value;
this.clear();
Expand Down
32 changes: 32 additions & 0 deletions packages/field-base/src/keyboard-mixin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @license
* Copyright (c) 2021 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/

/**
web-padawan marked this conversation as resolved.
Show resolved Hide resolved
* A mixin that manages keyboard handling.
* The mixin subscribes to the keyboard events while an actual implementation
* for the event handlers is left to the client (a component or another mixin).
*/
declare function KeyboardMixin<T extends new (...args: any[]) => {}>(base: T): T & KeyboardMixinConstructor;

interface KeyboardMixinConstructor {
new (...args: any[]): KeyboardMixin;
}

interface KeyboardMixin {
/**
* A handler for the `keydown` event. By default, it does nothing.
* Override the method to implement your own behavior.
*/
_onKeyDown(event: KeyboardEvent): void;

/**
* A handler for the `keyup` event. By default, it does nothing.
* Override the method to implement your own behavior.
*/
_onKeyUp(event: KeyboardEvent): void;
}

export { KeyboardMixinConstructor, KeyboardMixin };
51 changes: 51 additions & 0 deletions packages/field-base/src/keyboard-mixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright (c) 2021 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';

const KeyboardMixinImplementation = (superclass) =>
class KeyboardMixinClass extends superclass {
/** @protected */
ready() {
web-padawan marked this conversation as resolved.
Show resolved Hide resolved
super.ready();

this.addEventListener('keydown', (event) => {
this._onKeyDown(event);
});

this.addEventListener('keyup', (event) => {
this._onKeyUp(event);
});
}

/**
* A handler for the `keydown` event. By default, it does nothing.
* Override the method to implement your own behavior.
*
* @param {KeyboardEvent} _event
* @protected
*/
_onKeyDown(_event) {
// To be implemented.
}

/**
* A handler for the `keyup` event. By default, it does nothing.
* Override the method to implement your own behavior.
*
* @param {KeyboardEvent} _event
* @protected
*/
_onKeyUp(_event) {
// To be implemented.
}
};

/**
* A mixin that manages keyboard handling.
* The mixin subscribes to the keyboard events while an actual implementation
* for the event handlers is left to the client (a component or another mixin).
*/
export const KeyboardMixin = dedupingMixin(KeyboardMixinImplementation);
Loading