src/Draggable/Plugins/Focusable/Focusable.js
import AbstractPlugin from 'shared/AbstractPlugin';
const onInitialize = Symbol('onInitialize');
const onDestroy = Symbol('onDestroy');
/**
* Focusable default options
* @property {Object} defaultOptions
* @type {Object}
*/
const defaultOptions = {};
/**
* Focusable plugin
* @class Focusable
* @module Focusable
* @extends AbstractPlugin
*/
export default class Focusable extends AbstractPlugin {
/**
* Focusable constructor.
* @constructs Focusable
* @param {Draggable} draggable - Draggable instance
*/
constructor(draggable) {
super(draggable);
/**
* Focusable options
* @property {Object} options
* @type {Object}
*/
this.options = {
...defaultOptions,
...this.getOptions(),
};
this[onInitialize] = this[onInitialize].bind(this);
this[onDestroy] = this[onDestroy].bind(this);
}
/**
* Attaches listeners to draggable
*/
attach() {
this.draggable.on('draggable:initialize', this[onInitialize]).on('draggable:destroy', this[onDestroy]);
}
/**
* Detaches listeners from draggable
*/
detach() {
this.draggable.off('draggable:initialize', this[onInitialize]).off('draggable:destroy', this[onDestroy]);
// Remove modified elements when detach
this[onDestroy]();
}
/**
* Returns options passed through draggable
* @return {Object}
*/
getOptions() {
return this.draggable.options.focusable || {};
}
/**
* Returns draggable containers and elements
* @return {HTMLElement[]}
*/
getElements() {
return [...this.draggable.containers, ...this.draggable.getDraggableElements()];
}
/**
* Intialize handler
* @private
*/
[onInitialize]() {
// Can wait until the next best frame is available
requestAnimationFrame(() => {
this.getElements().forEach((element) => decorateElement(element));
});
}
/**
* Destroy handler
* @private
*/
[onDestroy]() {
// Can wait until the next best frame is available
requestAnimationFrame(() => {
this.getElements().forEach((element) => stripElement(element));
});
}
}
/**
* Keeps track of all the elements that are missing tabindex attributes
* so they can be reset when draggable gets destroyed
* @const {HTMLElement[]} elementsWithMissingTabIndex
*/
const elementsWithMissingTabIndex = [];
/**
* Decorates element with tabindex attributes
* @param {HTMLElement} element
* @return {Object}
* @private
*/
function decorateElement(element) {
const hasMissingTabIndex = Boolean(!element.getAttribute('tabindex') && element.tabIndex === -1);
if (hasMissingTabIndex) {
elementsWithMissingTabIndex.push(element);
element.tabIndex = 0;
}
}
/**
* Removes elements tabindex attributes
* @param {HTMLElement} element
* @private
*/
function stripElement(element) {
const tabIndexElementPosition = elementsWithMissingTabIndex.indexOf(element);
if (tabIndexElementPosition !== -1) {
element.tabIndex = -1;
elementsWithMissingTabIndex.splice(tabIndexElementPosition, 1);
}
}