drupal/core/misc/tabbingmanager.js

192 lines
4.9 KiB
JavaScript

/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, {
tabbable,
isTabbable
}) {
function TabbingManager() {
this.stack = [];
}
function TabbingContext(options) {
$.extend(this, {
level: null,
$tabbableElements: $(),
$disabledElements: $(),
released: false,
active: false,
trapFocus: false
}, options);
}
$.extend(TabbingManager.prototype, {
constrain(elements, {
trapFocus = false
} = {}) {
const il = this.stack.length;
for (let i = 0; i < il; i++) {
this.stack[i].deactivate();
}
let tabbableElements = [];
$(elements).each((index, rootElement) => {
tabbableElements = [...tabbableElements, ...tabbable(rootElement)];
if (isTabbable(rootElement)) {
tabbableElements = [...tabbableElements, rootElement];
}
});
const tabbingContext = new TabbingContext({
level: this.stack.length,
$tabbableElements: $(tabbableElements),
trapFocus
});
this.stack.push(tabbingContext);
tabbingContext.activate();
$(document).trigger('drupalTabbingConstrained', tabbingContext);
return tabbingContext;
},
release() {
let toActivate = this.stack.length - 1;
while (toActivate >= 0 && this.stack[toActivate].released) {
toActivate--;
}
this.stack.splice(toActivate + 1);
if (toActivate >= 0) {
this.stack[toActivate].activate();
}
},
activate(tabbingContext) {
const $set = tabbingContext.$tabbableElements;
const level = tabbingContext.level;
const $disabledSet = $(tabbable(document.body)).not($set);
tabbingContext.$disabledElements = $disabledSet;
const il = $disabledSet.length;
for (let i = 0; i < il; i++) {
this.recordTabindex($disabledSet.eq(i), level);
}
$disabledSet.prop('tabindex', -1).prop('autofocus', false);
let $hasFocus = $set.filter('[autofocus]').eq(-1);
if ($hasFocus.length === 0) {
$hasFocus = $set.eq(0);
}
$hasFocus.trigger('focus');
if ($set.length && tabbingContext.trapFocus) {
$set.last().on('keydown.focus-trap', event => {
if (event.key === 'Tab' && !event.shiftKey) {
event.preventDefault();
$set.first().focus();
}
});
$set.first().on('keydown.focus-trap', event => {
if (event.key === 'Tab' && event.shiftKey) {
event.preventDefault();
$set.last().focus();
}
});
}
},
deactivate(tabbingContext) {
const $set = tabbingContext.$disabledElements;
const level = tabbingContext.level;
const il = $set.length;
tabbingContext.$tabbableElements.first().off('keydown.focus-trap');
tabbingContext.$tabbableElements.last().off('keydown.focus-trap');
for (let i = 0; i < il; i++) {
this.restoreTabindex($set.eq(i), level);
}
},
recordTabindex($el, level) {
const tabInfo = $el.data('drupalOriginalTabIndices') || {};
tabInfo[level] = {
tabindex: $el[0].getAttribute('tabindex'),
autofocus: $el[0].hasAttribute('autofocus')
};
$el.data('drupalOriginalTabIndices', tabInfo);
},
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);
} else {
$el[0].removeAttribute('tabindex');
}
if (data.autofocus) {
$el[0].setAttribute('autofocus', 'autofocus');
}
if (level === 0) {
$el.removeData('drupalOriginalTabIndices');
} else {
let levelToDelete = level;
while (tabInfo.hasOwnProperty(levelToDelete)) {
delete tabInfo[levelToDelete];
levelToDelete++;
}
$el.data('drupalOriginalTabIndices', tabInfo);
}
}
}
});
$.extend(TabbingContext.prototype, {
release() {
if (!this.released) {
this.deactivate();
this.released = true;
Drupal.tabbingManager.release(this);
$(document).trigger('drupalTabbingContextReleased', this);
}
},
activate() {
if (!this.active && !this.released) {
this.active = true;
Drupal.tabbingManager.activate(this);
$(document).trigger('drupalTabbingContextActivated', this);
}
},
deactivate() {
if (this.active) {
this.active = false;
Drupal.tabbingManager.deactivate(this);
$(document).trigger('drupalTabbingContextDeactivated', this);
}
}
});
if (Drupal.tabbingManager) {
return;
}
Drupal.tabbingManager = new TabbingManager();
})(jQuery, Drupal, window.tabbable);