X-Git-Url: http://www.aleph1.co.uk/gitweb/?a=blobdiff_plain;ds=sidebyside;f=web%2Fcore%2Fmisc%2Ftabbingmanager.es6.js;fp=web%2Fcore%2Fmisc%2Ftabbingmanager.es6.js;h=5f4147174fa7d50fd6325b56b94e39f05cef2b72;hb=0bf8d09d2542548982e81a441b1f16e75873a04f;hp=075dda5177d0c911ec15cc442e83a06ffb97ff16;hpb=74df008bdbb3a11eeea356744f39b802369bda3c;p=yaffs-website diff --git a/web/core/misc/tabbingmanager.es6.js b/web/core/misc/tabbingmanager.es6.js index 075dda517..5f4147174 100644 --- a/web/core/misc/tabbingmanager.es6.js +++ b/web/core/misc/tabbingmanager.es6.js @@ -27,7 +27,7 @@ * @event drupalTabbingContextDeactivated */ -(function ($, Drupal) { +(function($, Drupal) { /** * Provides an API for managing page tabbing order modifications. * @@ -46,202 +46,6 @@ this.stack = []; } - /** - * Add public methods to the TabbingManager class. - */ - $.extend(TabbingManager.prototype, /** @lends Drupal~TabbingManager# */{ - - /** - * Constrain tabbing to the specified set of elements only. - * - * Makes elements outside of the specified set of elements unreachable via - * the tab key. - * - * @param {jQuery} elements - * The set of elements to which tabbing should be constrained. Can also - * be a jQuery-compatible selector string. - * - * @return {Drupal~TabbingContext} - * The TabbingContext instance. - * - * @fires event:drupalTabbingConstrained - */ - constrain(elements) { - // Deactivate all tabbingContexts to prepare for the new constraint. A - // tabbingContext instance will only be reactivated if the stack is - // unwound to it in the _unwindStack() method. - const il = this.stack.length; - for (let i = 0; i < il; i++) { - this.stack[i].deactivate(); - } - - // The "active tabbing set" are the elements tabbing should be constrained - // to. - const $elements = $(elements).find(':tabbable').addBack(':tabbable'); - - const tabbingContext = new TabbingContext({ - // The level is the current height of the stack before this new - // tabbingContext is pushed on top of the stack. - level: this.stack.length, - $tabbableElements: $elements, - }); - - this.stack.push(tabbingContext); - - // Activates the tabbingContext; this will manipulate the DOM to constrain - // tabbing. - tabbingContext.activate(); - - // Allow modules to respond to the constrain event. - $(document).trigger('drupalTabbingConstrained', tabbingContext); - - return tabbingContext; - }, - - /** - * Restores a former tabbingContext when an active one is released. - * - * The TabbingManager stack of tabbingContext instances will be unwound - * from the top-most released tabbingContext down to the first non-released - * tabbingContext instance. This non-released instance is then activated. - */ - release() { - // Unwind as far as possible: find the topmost non-released - // tabbingContext. - let toActivate = this.stack.length - 1; - while (toActivate >= 0 && this.stack[toActivate].released) { - toActivate--; - } - - // Delete all tabbingContexts after the to be activated one. They have - // already been deactivated, so their effect on the DOM has been reversed. - this.stack.splice(toActivate + 1); - - // Get topmost tabbingContext, if one exists, and activate it. - if (toActivate >= 0) { - this.stack[toActivate].activate(); - } - }, - - /** - * Makes all elements outside of the tabbingContext's set untabbable. - * - * Elements made untabbable have their original tabindex and autofocus - * values stored so that they might be restored later when this - * tabbingContext is deactivated. - * - * @param {Drupal~TabbingContext} tabbingContext - * The TabbingContext instance that has been activated. - */ - activate(tabbingContext) { - const $set = tabbingContext.$tabbableElements; - const level = tabbingContext.level; - // Determine which elements are reachable via tabbing by default. - const $disabledSet = $(':tabbable') - // Exclude elements of the active tabbing set. - .not($set); - // Set the disabled set on the tabbingContext. - tabbingContext.$disabledElements = $disabledSet; - // Record the tabindex for each element, so we can restore it later. - const il = $disabledSet.length; - for (let i = 0; i < il; i++) { - this.recordTabindex($disabledSet.eq(i), level); - } - // Make all tabbable elements outside of the active tabbing set - // unreachable. - $disabledSet - .prop('tabindex', -1) - .prop('autofocus', false); - - // Set focus on an element in the tabbingContext's set of tabbable - // elements. First, check if there is an element with an autofocus - // attribute. Select the last one from the DOM order. - let $hasFocus = $set.filter('[autofocus]').eq(-1); - // If no element in the tabbable set has an autofocus attribute, select - // the first element in the set. - if ($hasFocus.length === 0) { - $hasFocus = $set.eq(0); - } - $hasFocus.trigger('focus'); - }, - - /** - * Restores that tabbable state of a tabbingContext's disabled elements. - * - * Elements that were made untabbable have their original tabindex and - * autofocus values restored. - * - * @param {Drupal~TabbingContext} tabbingContext - * The TabbingContext instance that has been deactivated. - */ - deactivate(tabbingContext) { - const $set = tabbingContext.$disabledElements; - const level = tabbingContext.level; - const il = $set.length; - for (let i = 0; i < il; i++) { - this.restoreTabindex($set.eq(i), level); - } - }, - - /** - * Records the tabindex and autofocus values of an untabbable element. - * - * @param {jQuery} $el - * The set of elements that have been disabled. - * @param {number} level - * The stack level for which the tabindex attribute should be recorded. - */ - recordTabindex($el, level) { - const tabInfo = $el.data('drupalOriginalTabIndices') || {}; - tabInfo[level] = { - tabindex: $el[0].getAttribute('tabindex'), - autofocus: $el[0].hasAttribute('autofocus'), - }; - $el.data('drupalOriginalTabIndices', tabInfo); - }, - - /** - * Restores the tabindex and autofocus values of a reactivated element. - * - * @param {jQuery} $el - * The element that is being reactivated. - * @param {number} level - * The stack level for which the tabindex attribute should be restored. - */ - restoreTabindex($el, level) { - const tabInfo = $el.data('drupalOriginalTabIndices'); - if (tabInfo && tabInfo[level]) { - const data = tabInfo[level]; - if (data.tabindex) { - $el[0].setAttribute('tabindex', data.tabindex); - } - // If the element did not have a tabindex at this stack level then - // remove it. - else { - $el[0].removeAttribute('tabindex'); - } - if (data.autofocus) { - $el[0].setAttribute('autofocus', 'autofocus'); - } - - // Clean up $.data. - if (level === 0) { - // Remove all data. - $el.removeData('drupalOriginalTabIndices'); - } - else { - // Remove the data for this stack level and higher. - let levelToDelete = level; - while (tabInfo.hasOwnProperty(levelToDelete)) { - delete tabInfo[levelToDelete]; - levelToDelete++; - } - $el.data('drupalOriginalTabIndices', tabInfo); - } - } - }, - }); - /** * Stores a set of tabbable elements. * @@ -268,87 +72,289 @@ * tabbingContext can be active at a time. */ function TabbingContext(options) { - $.extend(this, /** @lends Drupal~TabbingContext# */{ + $.extend( + this, + /** @lends Drupal~TabbingContext# */ { + /** + * @type {?number} + */ + level: null, + + /** + * @type {jQuery} + */ + $tabbableElements: $(), + + /** + * @type {jQuery} + */ + $disabledElements: $(), + + /** + * @type {bool} + */ + released: false, + + /** + * @type {bool} + */ + active: false, + }, + options, + ); + } + /** + * Add public methods to the TabbingManager class. + */ + $.extend( + TabbingManager.prototype, + /** @lends Drupal~TabbingManager# */ { /** - * @type {?number} + * Constrain tabbing to the specified set of elements only. + * + * Makes elements outside of the specified set of elements unreachable via + * the tab key. + * + * @param {jQuery} elements + * The set of elements to which tabbing should be constrained. Can also + * be a jQuery-compatible selector string. + * + * @return {Drupal~TabbingContext} + * The TabbingContext instance. + * + * @fires event:drupalTabbingConstrained */ - level: null, + constrain(elements) { + // Deactivate all tabbingContexts to prepare for the new constraint. A + // tabbingContext instance will only be reactivated if the stack is + // unwound to it in the _unwindStack() method. + const il = this.stack.length; + for (let i = 0; i < il; i++) { + this.stack[i].deactivate(); + } + + // The "active tabbing set" are the elements tabbing should be constrained + // to. + const $elements = $(elements) + .find(':tabbable') + .addBack(':tabbable'); + + const tabbingContext = new TabbingContext({ + // The level is the current height of the stack before this new + // tabbingContext is pushed on top of the stack. + level: this.stack.length, + $tabbableElements: $elements, + }); + + this.stack.push(tabbingContext); + + // Activates the tabbingContext; this will manipulate the DOM to constrain + // tabbing. + tabbingContext.activate(); + + // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingConstrained', tabbingContext); + + return tabbingContext; + }, /** - * @type {jQuery} + * Restores a former tabbingContext when an active one is released. + * + * The TabbingManager stack of tabbingContext instances will be unwound + * from the top-most released tabbingContext down to the first non-released + * tabbingContext instance. This non-released instance is then activated. */ - $tabbableElements: $(), + release() { + // Unwind as far as possible: find the topmost non-released + // tabbingContext. + let toActivate = this.stack.length - 1; + while (toActivate >= 0 && this.stack[toActivate].released) { + toActivate--; + } + + // Delete all tabbingContexts after the to be activated one. They have + // already been deactivated, so their effect on the DOM has been reversed. + this.stack.splice(toActivate + 1); + + // Get topmost tabbingContext, if one exists, and activate it. + if (toActivate >= 0) { + this.stack[toActivate].activate(); + } + }, /** - * @type {jQuery} + * Makes all elements outside of the tabbingContext's set untabbable. + * + * Elements made untabbable have their original tabindex and autofocus + * values stored so that they might be restored later when this + * tabbingContext is deactivated. + * + * @param {Drupal~TabbingContext} tabbingContext + * The TabbingContext instance that has been activated. */ - $disabledElements: $(), + activate(tabbingContext) { + const $set = tabbingContext.$tabbableElements; + const level = tabbingContext.level; + // Determine which elements are reachable via tabbing by default. + const $disabledSet = $(':tabbable') + // Exclude elements of the active tabbing set. + .not($set); + // Set the disabled set on the tabbingContext. + tabbingContext.$disabledElements = $disabledSet; + // Record the tabindex for each element, so we can restore it later. + const il = $disabledSet.length; + for (let i = 0; i < il; i++) { + this.recordTabindex($disabledSet.eq(i), level); + } + // Make all tabbable elements outside of the active tabbing set + // unreachable. + $disabledSet.prop('tabindex', -1).prop('autofocus', false); + + // Set focus on an element in the tabbingContext's set of tabbable + // elements. First, check if there is an element with an autofocus + // attribute. Select the last one from the DOM order. + let $hasFocus = $set.filter('[autofocus]').eq(-1); + // If no element in the tabbable set has an autofocus attribute, select + // the first element in the set. + if ($hasFocus.length === 0) { + $hasFocus = $set.eq(0); + } + $hasFocus.trigger('focus'); + }, /** - * @type {bool} + * Restores that tabbable state of a tabbingContext's disabled elements. + * + * Elements that were made untabbable have their original tabindex and + * autofocus values restored. + * + * @param {Drupal~TabbingContext} tabbingContext + * The TabbingContext instance that has been deactivated. */ - released: false, + deactivate(tabbingContext) { + const $set = tabbingContext.$disabledElements; + const level = tabbingContext.level; + const il = $set.length; + for (let i = 0; i < il; i++) { + this.restoreTabindex($set.eq(i), level); + } + }, /** - * @type {bool} + * Records the tabindex and autofocus values of an untabbable element. + * + * @param {jQuery} $el + * The set of elements that have been disabled. + * @param {number} level + * The stack level for which the tabindex attribute should be recorded. */ - active: false, - }, options); - } + recordTabindex($el, level) { + const tabInfo = $el.data('drupalOriginalTabIndices') || {}; + tabInfo[level] = { + tabindex: $el[0].getAttribute('tabindex'), + autofocus: $el[0].hasAttribute('autofocus'), + }; + $el.data('drupalOriginalTabIndices', tabInfo); + }, + + /** + * Restores the tabindex and autofocus values of a reactivated element. + * + * @param {jQuery} $el + * The element that is being reactivated. + * @param {number} level + * The stack level for which the tabindex attribute should be restored. + */ + restoreTabindex($el, level) { + const tabInfo = $el.data('drupalOriginalTabIndices'); + if (tabInfo && tabInfo[level]) { + const data = tabInfo[level]; + if (data.tabindex) { + $el[0].setAttribute('tabindex', data.tabindex); + } + // If the element did not have a tabindex at this stack level then + // remove it. + else { + $el[0].removeAttribute('tabindex'); + } + if (data.autofocus) { + $el[0].setAttribute('autofocus', 'autofocus'); + } + + // Clean up $.data. + if (level === 0) { + // Remove all data. + $el.removeData('drupalOriginalTabIndices'); + } else { + // Remove the data for this stack level and higher. + let levelToDelete = level; + while (tabInfo.hasOwnProperty(levelToDelete)) { + delete tabInfo[levelToDelete]; + levelToDelete++; + } + $el.data('drupalOriginalTabIndices', tabInfo); + } + } + }, + }, + ); /** * Add public methods to the TabbingContext class. */ - $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{ - - /** - * Releases this TabbingContext. - * - * Once a TabbingContext object is released, it can never be activated - * again. - * - * @fires event:drupalTabbingContextReleased - */ - release() { - if (!this.released) { - this.deactivate(); - this.released = true; - Drupal.tabbingManager.release(this); - // Allow modules to respond to the tabbingContext release event. - $(document).trigger('drupalTabbingContextReleased', this); - } - }, + $.extend( + TabbingContext.prototype, + /** @lends Drupal~TabbingContext# */ { + /** + * Releases this TabbingContext. + * + * Once a TabbingContext object is released, it can never be activated + * again. + * + * @fires event:drupalTabbingContextReleased + */ + release() { + if (!this.released) { + this.deactivate(); + this.released = true; + Drupal.tabbingManager.release(this); + // Allow modules to respond to the tabbingContext release event. + $(document).trigger('drupalTabbingContextReleased', this); + } + }, - /** - * Activates this TabbingContext. - * - * @fires event:drupalTabbingContextActivated - */ - activate() { - // A released TabbingContext object can never be activated again. - if (!this.active && !this.released) { - this.active = true; - Drupal.tabbingManager.activate(this); - // Allow modules to respond to the constrain event. - $(document).trigger('drupalTabbingContextActivated', this); - } - }, + /** + * Activates this TabbingContext. + * + * @fires event:drupalTabbingContextActivated + */ + activate() { + // A released TabbingContext object can never be activated again. + if (!this.active && !this.released) { + this.active = true; + Drupal.tabbingManager.activate(this); + // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingContextActivated', this); + } + }, - /** - * Deactivates this TabbingContext. - * - * @fires event:drupalTabbingContextDeactivated - */ - deactivate() { - if (this.active) { - this.active = false; - Drupal.tabbingManager.deactivate(this); - // Allow modules to respond to the constrain event. - $(document).trigger('drupalTabbingContextDeactivated', this); - } + /** + * Deactivates this TabbingContext. + * + * @fires event:drupalTabbingContextDeactivated + */ + deactivate() { + if (this.active) { + this.active = false; + Drupal.tabbingManager.deactivate(this); + // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingContextDeactivated', this); + } + }, }, - }); + ); // Mark this behavior as processed on the first pass and return if it is // already processed. @@ -360,4 +366,4 @@ * @type {Drupal~TabbingManager} */ Drupal.tabbingManager = new TabbingManager(); -}(jQuery, Drupal)); +})(jQuery, Drupal);