Skip to content

Commit

Permalink
feat: add KeyboardMixin and make other mixins use it (#2432)
Browse files Browse the repository at this point in the history
  • Loading branch information
vursen authored Sep 3, 2021
1 parent 263e2df commit 389d1ec
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 79 deletions.
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/
*/

/**
* 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() {
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

0 comments on commit 389d1ec

Please sign in to comment.