first commit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @file
|
||||
* Ajax theme overrides for Claro.
|
||||
*/
|
||||
|
||||
(Drupal => {
|
||||
/**
|
||||
* Theme override of the ajax progress indicator for full screen.
|
||||
*
|
||||
* @return {string}
|
||||
* The HTML markup for the throbber.
|
||||
*/
|
||||
Drupal.theme.ajaxProgressIndicatorFullscreen = () =>
|
||||
'<div class="ajax-progress ajax-progress--fullscreen"><div class="ajax-progress__throbber ajax-progress__throbber--fullscreen"> </div></div>';
|
||||
|
||||
/**
|
||||
* Theme override of the ajax progress indicator.
|
||||
*
|
||||
* @param {string} message
|
||||
* The message shown on the UI.
|
||||
* @return {string}
|
||||
* The HTML markup for the throbber.
|
||||
*/
|
||||
Drupal.theme.ajaxProgressThrobber = message => {
|
||||
// Build markup without adding extra white space since it affects rendering.
|
||||
const messageMarkup =
|
||||
typeof message === 'string'
|
||||
? Drupal.theme('ajaxProgressMessage', message)
|
||||
: '';
|
||||
const throbber = '<div class="ajax-progress__throbber"> </div>';
|
||||
|
||||
return `<div class="ajax-progress ajax-progress--throbber">${throbber}${messageMarkup}</div>`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Theme override of the ajax progress message.
|
||||
*
|
||||
* @param {string} message
|
||||
* The message shown on the UI.
|
||||
* @return {string}
|
||||
* The HTML markup for the throbber.
|
||||
*/
|
||||
Drupal.theme.ajaxProgressMessage = message =>
|
||||
`<div class="ajax-progress__message">${message}</div>`;
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal) {
|
||||
Drupal.theme.ajaxProgressIndicatorFullscreen = function () {
|
||||
return '<div class="ajax-progress ajax-progress--fullscreen"><div class="ajax-progress__throbber ajax-progress__throbber--fullscreen"> </div></div>';
|
||||
};
|
||||
|
||||
Drupal.theme.ajaxProgressThrobber = function (message) {
|
||||
var messageMarkup = typeof message === 'string' ? Drupal.theme('ajaxProgressMessage', message) : '';
|
||||
var throbber = '<div class="ajax-progress__throbber"> </div>';
|
||||
|
||||
return '<div class="ajax-progress ajax-progress--throbber">' + throbber + messageMarkup + '</div>';
|
||||
};
|
||||
|
||||
Drupal.theme.ajaxProgressMessage = function (message) {
|
||||
return '<div class="ajax-progress__message">' + message + '</div>';
|
||||
};
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @file
|
||||
* Claro's enhancement for autocomplete form element.
|
||||
*/
|
||||
|
||||
(($, Drupal) => {
|
||||
Drupal.behaviors.claroAutoCompete = {
|
||||
attach(context) {
|
||||
$(context)
|
||||
.find('input.form-autocomplete')
|
||||
.once('claroAutoComplete')
|
||||
.each((index, value) => {
|
||||
const $input = $(value);
|
||||
const timeout = 400;
|
||||
let classRemoveTimeout;
|
||||
const classRemove = $autoCompleteElem => {
|
||||
$autoCompleteElem.removeClass('is-autocompleting');
|
||||
};
|
||||
|
||||
$input.on('input autocompletesearch autocompleteresponses', event => {
|
||||
if (event && event.type && event.type === 'autocompletesearch') {
|
||||
$(event.target).addClass('is-autocompleting');
|
||||
}
|
||||
clearTimeout(classRemoveTimeout);
|
||||
classRemoveTimeout = setTimeout(
|
||||
classRemove,
|
||||
timeout,
|
||||
$(event.target),
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
Drupal.behaviors.claroAutoCompete = {
|
||||
attach: function attach(context) {
|
||||
$(context).find('input.form-autocomplete').once('claroAutoComplete').each(function (index, value) {
|
||||
var $input = $(value);
|
||||
var timeout = 400;
|
||||
var classRemoveTimeout = void 0;
|
||||
var classRemove = function classRemove($autoCompleteElem) {
|
||||
$autoCompleteElem.removeClass('is-autocompleting');
|
||||
};
|
||||
|
||||
$input.on('input autocompletesearch autocompleteresponses', function (event) {
|
||||
if (event && event.type && event.type === 'autocompletesearch') {
|
||||
$(event.target).addClass('is-autocompleting');
|
||||
}
|
||||
clearTimeout(classRemoveTimeout);
|
||||
classRemoveTimeout = setTimeout(classRemove, timeout, $(event.target));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @file
|
||||
* Theme override for checkbox.
|
||||
*/
|
||||
|
||||
(Drupal => {
|
||||
/**
|
||||
* Constucts a checkbox input element.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing a DOM fragment.
|
||||
*/
|
||||
Drupal.theme.checkbox = () =>
|
||||
'<input type="checkbox" class="form-checkbox form-boolean form-boolean--type-checkbox"/>';
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal) {
|
||||
Drupal.theme.checkbox = function () {
|
||||
return '<input type="checkbox" class="form-checkbox form-boolean form-boolean--type-checkbox"/>';
|
||||
};
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,12 @@
|
||||
WHAT IS THIS DIRECTORY FOR?
|
||||
--------------------------------
|
||||
This directory is for JS files previously inherited from the Classy theme.
|
||||
|
||||
WHY ARE CLASSY JS FILES BEING COPIED HERE?
|
||||
-------------------------------------------
|
||||
Classy will be deprecated during the Drupal 9 lifecycle. To prepare for Classy's
|
||||
removal, JS files that would otherwise be inherited from Classy are copied
|
||||
here.
|
||||
|
||||
JS files that differ from the Classy versions should not be placed in this
|
||||
directory or any subdirectory.
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file
|
||||
* Classy theme overrides for the Media Embed CKEditor plugin.
|
||||
*/
|
||||
|
||||
(Drupal => {
|
||||
/**
|
||||
* Themes the error displayed when the media embed preview fails.
|
||||
*
|
||||
* @param {string} error
|
||||
* The error message to display
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing a DOM fragment.
|
||||
*
|
||||
* @see media-embed-error.html.twig
|
||||
*/
|
||||
Drupal.theme.mediaEmbedPreviewError = () =>
|
||||
`<div class="media-embed-error media-embed-error--preview-error">${Drupal.t(
|
||||
'An error occurred while trying to preview the media. Please save your work and reload this page.',
|
||||
)}</div>`;
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal) {
|
||||
Drupal.theme.mediaEmbedPreviewError = function () {
|
||||
return '<div class="media-embed-error media-embed-error--preview-error">' + Drupal.t('An error occurred while trying to preview the media. Please save your work and reload this page.') + '</div>';
|
||||
};
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @file
|
||||
* Claro's polyfill enhancements for HTML5 details.
|
||||
*/
|
||||
|
||||
(($, Modernizr, Drupal) => {
|
||||
/**
|
||||
* Workaround for Firefox.
|
||||
*
|
||||
* Firefox applies the focus state only for keyboard navigation.
|
||||
* We have to manually trigger focus to make the behavior consistent across
|
||||
* browsers.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*/
|
||||
Drupal.behaviors.claroDetails = {
|
||||
attach(context) {
|
||||
$(context)
|
||||
.once('claroDetails')
|
||||
.on('click', event => {
|
||||
if (event.target.nodeName === 'SUMMARY') {
|
||||
$(event.target).trigger('focus');
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Workaround for non-supporting browsers.
|
||||
*
|
||||
* This shim extends HTML5 Shiv used by core.
|
||||
*
|
||||
* HTML5 Shiv toggles focused details for hitting enter. We copy that for
|
||||
* space key as well to make the behavior consistent across browsers.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*/
|
||||
Drupal.behaviors.claroDetailsToggleShim = {
|
||||
attach(context) {
|
||||
if (Modernizr.details || !Drupal.CollapsibleDetails.instances.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(context)
|
||||
.find('details .details-title')
|
||||
.once('claroDetailsToggleShim')
|
||||
.on('keypress', event => {
|
||||
const keyCode = event.keyCode || event.charCode;
|
||||
if (keyCode === 32) {
|
||||
$(event.target)
|
||||
.closest('summary')
|
||||
.trigger('click');
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
})(jQuery, Modernizr, Drupal);
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Modernizr, Drupal) {
|
||||
Drupal.behaviors.claroDetails = {
|
||||
attach: function attach(context) {
|
||||
$(context).once('claroDetails').on('click', function (event) {
|
||||
if (event.target.nodeName === 'SUMMARY') {
|
||||
$(event.target).trigger('focus');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.behaviors.claroDetailsToggleShim = {
|
||||
attach: function attach(context) {
|
||||
if (Modernizr.details || !Drupal.CollapsibleDetails.instances.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(context).find('details .details-title').once('claroDetailsToggleShim').on('keypress', function (event) {
|
||||
var keyCode = event.keyCode || event.charCode;
|
||||
if (keyCode === 32) {
|
||||
$(event.target).closest('summary').trigger('click');
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery, Modernizr, Drupal);
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file
|
||||
* Theme overrides for Claro.
|
||||
*/
|
||||
|
||||
(Drupal => {
|
||||
/**
|
||||
* Overrides the dropbutton toggle markup.
|
||||
*
|
||||
* We have to keep the 'dropbutton-toggle' CSS class because the dropbutton JS
|
||||
* operates with that one.
|
||||
*
|
||||
* @param {object} options
|
||||
* Options object.
|
||||
* @param {string} [options.title]
|
||||
* The button text.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing a DOM fragment.
|
||||
*/
|
||||
Drupal.theme.dropbuttonToggle = options =>
|
||||
`<li class="dropbutton-toggle"><button type="button" class="dropbutton__toggle"><span class="visually-hidden">${options.title}</span></button></li>`;
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal) {
|
||||
Drupal.theme.dropbuttonToggle = function (options) {
|
||||
return "<li class=\"dropbutton-toggle\"><button type=\"button\" class=\"dropbutton__toggle\"><span class=\"visually-hidden\">" + options.title + "</span></button></li>";
|
||||
};
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file
|
||||
* Message template overrides.
|
||||
*/
|
||||
|
||||
(Drupal => {
|
||||
/**
|
||||
* Overrides message theme function.
|
||||
*
|
||||
* @param {object} message
|
||||
* The message object.
|
||||
* @param {string} message.text
|
||||
* The message text.
|
||||
* @param {object} options
|
||||
* The message context.
|
||||
* @param {string} options.type
|
||||
* The message type.
|
||||
* @param {string} options.id
|
||||
* ID of the message, for reference.
|
||||
*
|
||||
* @return {HTMLElement}
|
||||
* A DOM Node.
|
||||
*/
|
||||
Drupal.theme.message = ({ text }, { type, id }) => {
|
||||
const messagesTypes = Drupal.Message.getMessageTypeLabels();
|
||||
const messageWrapper = document.createElement('div');
|
||||
|
||||
messageWrapper.setAttribute('class', `messages messages--${type}`);
|
||||
messageWrapper.setAttribute(
|
||||
'role',
|
||||
type === 'error' || type === 'warning' ? 'alert' : 'status',
|
||||
);
|
||||
messageWrapper.setAttribute('aria-labelledby', `${id}-title`);
|
||||
messageWrapper.setAttribute('data-drupal-message-id', id);
|
||||
messageWrapper.setAttribute('data-drupal-message-type', type);
|
||||
|
||||
messageWrapper.innerHTML = `
|
||||
<div class="messages__header">
|
||||
<h2 id="${id}-title" class="messages__title">
|
||||
${messagesTypes[type]}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="messages__content">
|
||||
${text}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return messageWrapper;
|
||||
};
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal) {
|
||||
Drupal.theme.message = function (_ref, _ref2) {
|
||||
var text = _ref.text;
|
||||
var type = _ref2.type,
|
||||
id = _ref2.id;
|
||||
|
||||
var messagesTypes = Drupal.Message.getMessageTypeLabels();
|
||||
var messageWrapper = document.createElement('div');
|
||||
|
||||
messageWrapper.setAttribute('class', 'messages messages--' + type);
|
||||
messageWrapper.setAttribute('role', type === 'error' || type === 'warning' ? 'alert' : 'status');
|
||||
messageWrapper.setAttribute('aria-labelledby', id + '-title');
|
||||
messageWrapper.setAttribute('data-drupal-message-id', id);
|
||||
messageWrapper.setAttribute('data-drupal-message-type', type);
|
||||
|
||||
messageWrapper.innerHTML = '\n <div class="messages__header">\n <h2 id="' + id + '-title" class="messages__title">\n ' + messagesTypes[type] + '\n </h2>\n </div>\n <div class="messages__content">\n ' + text + '\n </div>\n ';
|
||||
|
||||
return messageWrapper;
|
||||
};
|
||||
})(Drupal);
|
||||
@@ -0,0 +1,29 @@
|
||||
(() => {
|
||||
function findActiveStep(steps) {
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
if (steps[i].className === 'is-active') {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
// The final "Finished" step is never "active".
|
||||
if (steps[steps.length - 1].className === 'done') {
|
||||
return steps.length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function installStepsSetup() {
|
||||
const steps = document.querySelectorAll('.task-list li');
|
||||
if (steps.length) {
|
||||
const header = document.querySelector('header[role="banner"]');
|
||||
const stepIndicator = document.createElement('div');
|
||||
stepIndicator.className = 'step-indicator';
|
||||
stepIndicator.innerHTML = `${findActiveStep(steps)}/${steps.length}`;
|
||||
header.appendChild(stepIndicator);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.addEventListener) {
|
||||
document.addEventListener('DOMContentLoaded', installStepsSetup);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function () {
|
||||
function findActiveStep(steps) {
|
||||
for (var i = 0; i < steps.length; i++) {
|
||||
if (steps[i].className === 'is-active') {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (steps[steps.length - 1].className === 'done') {
|
||||
return steps.length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function installStepsSetup() {
|
||||
var steps = document.querySelectorAll('.task-list li');
|
||||
if (steps.length) {
|
||||
var header = document.querySelector('header[role="banner"]');
|
||||
var stepIndicator = document.createElement('div');
|
||||
stepIndicator.className = 'step-indicator';
|
||||
stepIndicator.innerHTML = findActiveStep(steps) + '/' + steps.length;
|
||||
header.appendChild(stepIndicator);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.addEventListener) {
|
||||
document.addEventListener('DOMContentLoaded', installStepsSetup);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file
|
||||
* Responsive navigation tabs.
|
||||
*
|
||||
* This also supports collapsible navigable is the 'is-collapsible' class is
|
||||
* added to the main element, and a target element is included.
|
||||
*/
|
||||
(($, Drupal) => {
|
||||
function init(i, tab) {
|
||||
const $tab = $(tab);
|
||||
const $target = $tab.find('[data-drupal-nav-tabs-target]');
|
||||
const $active = $target.find('.js-active-tab');
|
||||
|
||||
const openMenu = () => {
|
||||
$target.toggleClass('is-open');
|
||||
};
|
||||
|
||||
const toggleOrder = reset => {
|
||||
const current = $active.index();
|
||||
const original = $active.data('original-order');
|
||||
|
||||
// Do not change order if already first or if already reset.
|
||||
if (original === 0 || reset === (current === original)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const siblings = {
|
||||
first: '[data-original-order="0"]',
|
||||
previous: `[data-original-order="${original - 1}"]`,
|
||||
};
|
||||
|
||||
const $first = $target.find(siblings.first);
|
||||
const $previous = $target.find(siblings.previous);
|
||||
|
||||
if (reset && current !== original) {
|
||||
$active.insertAfter($previous);
|
||||
} else if (!reset && current === original) {
|
||||
$active.insertBefore($first);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCollapsed = () => {
|
||||
if (window.matchMedia('(min-width: 48em)').matches) {
|
||||
if ($tab.hasClass('is-horizontal') && !$tab.attr('data-width')) {
|
||||
let width = 0;
|
||||
|
||||
$target.find('.js-tabs-link').each((index, value) => {
|
||||
width += $(value).outerWidth();
|
||||
});
|
||||
$tab.attr('data-width', width);
|
||||
}
|
||||
|
||||
// Collapse the tabs if the combined width of the tabs is greater than
|
||||
// the width of the parent container.
|
||||
const isHorizontal = $tab.attr('data-width') <= $tab.outerWidth();
|
||||
$tab.toggleClass('is-horizontal', isHorizontal);
|
||||
toggleOrder(isHorizontal);
|
||||
} else {
|
||||
toggleOrder(false);
|
||||
}
|
||||
};
|
||||
|
||||
$tab.addClass('position-container is-horizontal-enabled');
|
||||
|
||||
$target.find('.js-tab').each((index, element) => {
|
||||
const $item = $(element);
|
||||
$item.attr('data-original-order', $item.index());
|
||||
});
|
||||
|
||||
$tab.on('click.tabs', '[data-drupal-nav-tabs-trigger]', openMenu);
|
||||
$(window)
|
||||
.on('resize.tabs', Drupal.debounce(toggleCollapsed, 150))
|
||||
.trigger('resize.tabs');
|
||||
}
|
||||
/**
|
||||
* Initialize the tabs JS.
|
||||
*/
|
||||
Drupal.behaviors.navTabs = {
|
||||
attach(context) {
|
||||
$(context)
|
||||
.find('[data-drupal-nav-tabs].is-collapsible')
|
||||
.once('nav-tabs')
|
||||
.each((i, value) => {
|
||||
$(value).each(init);
|
||||
});
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
function init(i, tab) {
|
||||
var $tab = $(tab);
|
||||
var $target = $tab.find('[data-drupal-nav-tabs-target]');
|
||||
var $active = $target.find('.js-active-tab');
|
||||
|
||||
var openMenu = function openMenu() {
|
||||
$target.toggleClass('is-open');
|
||||
};
|
||||
|
||||
var toggleOrder = function toggleOrder(reset) {
|
||||
var current = $active.index();
|
||||
var original = $active.data('original-order');
|
||||
|
||||
if (original === 0 || reset === (current === original)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var siblings = {
|
||||
first: '[data-original-order="0"]',
|
||||
previous: '[data-original-order="' + (original - 1) + '"]'
|
||||
};
|
||||
|
||||
var $first = $target.find(siblings.first);
|
||||
var $previous = $target.find(siblings.previous);
|
||||
|
||||
if (reset && current !== original) {
|
||||
$active.insertAfter($previous);
|
||||
} else if (!reset && current === original) {
|
||||
$active.insertBefore($first);
|
||||
}
|
||||
};
|
||||
|
||||
var toggleCollapsed = function toggleCollapsed() {
|
||||
if (window.matchMedia('(min-width: 48em)').matches) {
|
||||
if ($tab.hasClass('is-horizontal') && !$tab.attr('data-width')) {
|
||||
var width = 0;
|
||||
|
||||
$target.find('.js-tabs-link').each(function (index, value) {
|
||||
width += $(value).outerWidth();
|
||||
});
|
||||
$tab.attr('data-width', width);
|
||||
}
|
||||
|
||||
var isHorizontal = $tab.attr('data-width') <= $tab.outerWidth();
|
||||
$tab.toggleClass('is-horizontal', isHorizontal);
|
||||
toggleOrder(isHorizontal);
|
||||
} else {
|
||||
toggleOrder(false);
|
||||
}
|
||||
};
|
||||
|
||||
$tab.addClass('position-container is-horizontal-enabled');
|
||||
|
||||
$target.find('.js-tab').each(function (index, element) {
|
||||
var $item = $(element);
|
||||
$item.attr('data-original-order', $item.index());
|
||||
});
|
||||
|
||||
$tab.on('click.tabs', '[data-drupal-nav-tabs-trigger]', openMenu);
|
||||
$(window).on('resize.tabs', Drupal.debounce(toggleCollapsed, 150)).trigger('resize.tabs');
|
||||
}
|
||||
|
||||
Drupal.behaviors.navTabs = {
|
||||
attach: function attach(context) {
|
||||
$(context).find('[data-drupal-nav-tabs].is-collapsible').once('nav-tabs').each(function (i, value) {
|
||||
$(value).each(init);
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @file
|
||||
* Provides responsive behaviors to HTML details elements.
|
||||
*/
|
||||
|
||||
(($, Drupal) => {
|
||||
/**
|
||||
* Initializes the responsive behaviors for details elements.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches the responsive behavior to status report specific details elements.
|
||||
*/
|
||||
Drupal.behaviors.responsiveDetails = {
|
||||
attach(context) {
|
||||
const $details = $(context)
|
||||
.find('details')
|
||||
.once('responsive-details');
|
||||
|
||||
if (!$details.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $summaries = $details.find('> summary');
|
||||
|
||||
function detailsToggle(matches) {
|
||||
if (matches) {
|
||||
$details.attr('open', true);
|
||||
$summaries.attr('aria-expanded', true);
|
||||
$summaries.on('click.details-open', false);
|
||||
} else {
|
||||
// If user explicitly opened one, leave it alone.
|
||||
const $notPressed = $details
|
||||
.find('> summary[aria-pressed!=true]')
|
||||
.attr('aria-expanded', false);
|
||||
$notPressed.parent('details').attr('open', false);
|
||||
// After resize, allow user to close previously opened details.
|
||||
$summaries.off('.details-open');
|
||||
}
|
||||
}
|
||||
|
||||
function handleDetailsMQ(event) {
|
||||
detailsToggle(event.matches);
|
||||
}
|
||||
|
||||
const mql = window.matchMedia('(min-width:48em)');
|
||||
mql.addListener(handleDetailsMQ);
|
||||
detailsToggle(mql.matches);
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
Drupal.behaviors.responsiveDetails = {
|
||||
attach: function attach(context) {
|
||||
var $details = $(context).find('details').once('responsive-details');
|
||||
|
||||
if (!$details.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $summaries = $details.find('> summary');
|
||||
|
||||
function detailsToggle(matches) {
|
||||
if (matches) {
|
||||
$details.attr('open', true);
|
||||
$summaries.attr('aria-expanded', true);
|
||||
$summaries.on('click.details-open', false);
|
||||
} else {
|
||||
var $notPressed = $details.find('> summary[aria-pressed!=true]').attr('aria-expanded', false);
|
||||
$notPressed.parent('details').attr('open', false);
|
||||
|
||||
$summaries.off('.details-open');
|
||||
}
|
||||
}
|
||||
|
||||
function handleDetailsMQ(event) {
|
||||
detailsToggle(event.matches);
|
||||
}
|
||||
|
||||
var mql = window.matchMedia('(min-width:48em)');
|
||||
mql.addListener(handleDetailsMQ);
|
||||
detailsToggle(mql.matches);
|
||||
}
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,344 @@
|
||||
/**
|
||||
* @file
|
||||
* Overrides Drupal core user.js that provides password strength indicator.
|
||||
*
|
||||
* @todo remove these overrides after
|
||||
* https://www.drupal.org/project/drupal/issues/3067523 has been resolved.
|
||||
*/
|
||||
|
||||
(($, Drupal) => {
|
||||
/**
|
||||
* This overrides the default Drupal.behaviors.password functionality.
|
||||
*
|
||||
* - Markup has been moved to theme functions so that to enable customizations
|
||||
* needed for matching Claro's design requirements
|
||||
* (https://www.drupal.org/project/drupal/issues/3067523).
|
||||
* - Modified classes so that same class names are not being used for different
|
||||
* elements (https://www.drupal.org/project/drupal/issues/3061265).
|
||||
*/
|
||||
Drupal.behaviors.password = {
|
||||
attach(context, settings) {
|
||||
const $passwordInput = $(context)
|
||||
.find('input.js-password-field')
|
||||
.once('password');
|
||||
|
||||
if ($passwordInput.length) {
|
||||
// Settings and translated messages added by
|
||||
// user_form_process_password_confirm().
|
||||
const translate = settings.password;
|
||||
|
||||
// The form element object of the password input.
|
||||
const $passwordInputParent = $passwordInput.parent();
|
||||
|
||||
// The password_confirm form element object.
|
||||
const $passwordWidget = $passwordInput.closest(
|
||||
'.js-form-type-password-confirm',
|
||||
);
|
||||
|
||||
// The password confirm input.
|
||||
const $passwordConfirmInput = $passwordWidget.find(
|
||||
'input.js-password-confirm',
|
||||
);
|
||||
|
||||
// The strength feedback element for the password input.
|
||||
const $passwordInputHelp = $(
|
||||
Drupal.theme.passwordInputHelp(translate.strengthTitle),
|
||||
);
|
||||
|
||||
// The password match feedback for the password confirm input.
|
||||
const $passwordConfirmHelp = $(
|
||||
Drupal.theme.passwordConfirmHelp(translate.confirmTitle),
|
||||
);
|
||||
|
||||
const $passwordInputStrengthBar = $passwordInputHelp.find(
|
||||
'.js-password-strength-bar',
|
||||
);
|
||||
const $passwordInputStrengthMessageWrapper = $passwordInputHelp.find(
|
||||
'.js-password-strength-text',
|
||||
);
|
||||
const $passwordConfirmMatch = $passwordConfirmHelp.find(
|
||||
'.js-password-match-text',
|
||||
);
|
||||
let $passwordSuggestionsTips = $(
|
||||
Drupal.theme.passwordSuggestionsTips('', ''),
|
||||
).hide();
|
||||
|
||||
// If the password strength indicator is enabled, add its markup.
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
$passwordConfirmInput
|
||||
.after($passwordConfirmHelp)
|
||||
.parent()
|
||||
.after($passwordSuggestionsTips);
|
||||
|
||||
$passwordInputParent.append($passwordInputHelp);
|
||||
}
|
||||
|
||||
// Check that password and confirmation inputs match.
|
||||
const passwordCheckMatch = confirmInputVal => {
|
||||
if (confirmInputVal) {
|
||||
const success = $passwordInput.val() === confirmInputVal;
|
||||
const confirmClass = success ? 'ok' : 'error';
|
||||
const confirmMatchMessage = success
|
||||
? translate.confirmSuccess
|
||||
: translate.confirmFailure;
|
||||
|
||||
// Update the success message and set the class accordingly if
|
||||
// needed.
|
||||
if (
|
||||
!$passwordConfirmMatch.hasClass(confirmClass) ||
|
||||
!$passwordConfirmMatch.html() === confirmMatchMessage
|
||||
) {
|
||||
$passwordConfirmMatch
|
||||
.html(confirmMatchMessage)
|
||||
.removeClass('ok error')
|
||||
.addClass(confirmClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check the password strength.
|
||||
const passwordCheck = () => {
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
// Evaluate the password strength.
|
||||
const result = Drupal.evaluatePasswordStrength(
|
||||
$passwordInput.val(),
|
||||
settings.password,
|
||||
);
|
||||
const $newSuggestions = $(
|
||||
Drupal.theme.passwordSuggestionsTips(
|
||||
translate.hasWeaknesses,
|
||||
result.tips,
|
||||
),
|
||||
);
|
||||
|
||||
// Update the suggestions for how to improve the password.
|
||||
if ($newSuggestions.html() !== $passwordSuggestionsTips.html()) {
|
||||
$passwordSuggestionsTips.replaceWith($newSuggestions);
|
||||
$passwordSuggestionsTips = $newSuggestions;
|
||||
|
||||
// Only show the description box if a weakness exists in the
|
||||
// password.
|
||||
$passwordSuggestionsTips.toggle(result.strength !== 100);
|
||||
}
|
||||
|
||||
// Adjust the length of the strength indicator.
|
||||
$passwordInputStrengthBar
|
||||
.css('width', `${result.strength}%`)
|
||||
.removeClass('is-weak is-fair is-good is-strong')
|
||||
.addClass(result.indicatorClass);
|
||||
|
||||
// Update the strength indication text if needed.
|
||||
if (
|
||||
!$passwordInputStrengthMessageWrapper.hasClass(
|
||||
result.indicatorClass,
|
||||
) ||
|
||||
!$passwordInputStrengthMessageWrapper.html() ===
|
||||
result.indicatorText
|
||||
) {
|
||||
$passwordInputStrengthMessageWrapper
|
||||
.html(result.indicatorText)
|
||||
.removeClass('is-weak is-fair is-good is-strong')
|
||||
.addClass(result.indicatorClass);
|
||||
}
|
||||
}
|
||||
|
||||
$passwordWidget
|
||||
.removeClass('is-initial')
|
||||
.removeClass('is-password-empty is-password-filled')
|
||||
.removeClass('is-confirm-empty is-confirm-filled');
|
||||
|
||||
// Check the value of the password input and add the proper classes.
|
||||
$passwordWidget.addClass(
|
||||
$passwordInput.val() ? 'is-password-filled' : 'is-password-empty',
|
||||
);
|
||||
|
||||
// Check the value in the confirm input and show results.
|
||||
passwordCheckMatch($passwordConfirmInput.val());
|
||||
$passwordWidget.addClass(
|
||||
$passwordConfirmInput.val()
|
||||
? 'is-confirm-filled'
|
||||
: 'is-confirm-empty',
|
||||
);
|
||||
};
|
||||
|
||||
// Add initial classes.
|
||||
$passwordWidget
|
||||
.addClass(
|
||||
$passwordInput.val() ? 'is-password-filled' : 'is-password-empty',
|
||||
)
|
||||
.addClass(
|
||||
$passwordConfirmInput.val()
|
||||
? 'is-confirm-filled'
|
||||
: 'is-confirm-empty',
|
||||
);
|
||||
|
||||
// Monitor input events.
|
||||
$passwordInput.on('input', passwordCheck);
|
||||
$passwordConfirmInput.on('input', passwordCheck);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the default Drupal.evaluatePasswordStrength.
|
||||
*
|
||||
* The default implementation of this function hard codes some markup inside
|
||||
* this function. Rendering markup is now handled by
|
||||
* Drupal.behaviors.password.
|
||||
*
|
||||
* @param {string} password
|
||||
* Password to evaluate the strength.
|
||||
*
|
||||
* @param {Array.<string>} translate
|
||||
* Settings and translated messages added by user_form_process_password_confirm().
|
||||
*
|
||||
* @return {Array.<string>}
|
||||
* Array containing the strength, tips, indicators text and class.
|
||||
*/
|
||||
Drupal.evaluatePasswordStrength = (password, translate) => {
|
||||
password = password.trim();
|
||||
let indicatorText;
|
||||
let indicatorClass;
|
||||
let weaknesses = 0;
|
||||
let strength = 100;
|
||||
const tips = [];
|
||||
const hasLowercase = /[a-z]/.test(password);
|
||||
const hasUppercase = /[A-Z]/.test(password);
|
||||
const hasNumbers = /[0-9]/.test(password);
|
||||
const hasPunctuation = /[^a-zA-Z0-9]/.test(password);
|
||||
|
||||
// If there is a username edit box on the page, compare password to that,
|
||||
// otherwise use value from the database.
|
||||
const $usernameBox = $('input.username');
|
||||
const username =
|
||||
$usernameBox.length > 0 ? $usernameBox.val() : translate.username;
|
||||
|
||||
// Lose 5 points for every character less than 12, plus a 30 point penalty.
|
||||
if (password.length < 12) {
|
||||
tips.push(translate.tooShort);
|
||||
strength -= (12 - password.length) * 5 + 30;
|
||||
}
|
||||
|
||||
// Count weaknesses.
|
||||
if (!hasLowercase) {
|
||||
tips.push(translate.addLowerCase);
|
||||
weaknesses += 1;
|
||||
}
|
||||
if (!hasUppercase) {
|
||||
tips.push(translate.addUpperCase);
|
||||
weaknesses += 1;
|
||||
}
|
||||
if (!hasNumbers) {
|
||||
tips.push(translate.addNumbers);
|
||||
weaknesses += 1;
|
||||
}
|
||||
if (!hasPunctuation) {
|
||||
tips.push(translate.addPunctuation);
|
||||
weaknesses += 1;
|
||||
}
|
||||
|
||||
// Apply penalty for each weakness (balanced against length penalty).
|
||||
switch (weaknesses) {
|
||||
case 1:
|
||||
strength -= 12.5;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
strength -= 25;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
strength -= 40;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
strength -= 40;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Default: 0. Nothing to do.
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if password is the same as the username.
|
||||
if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
|
||||
tips.push(translate.sameAsUsername);
|
||||
// Passwords the same as username are always very weak.
|
||||
strength = 5;
|
||||
}
|
||||
|
||||
// Based on the strength, work out what text should be shown by the
|
||||
// password strength meter.
|
||||
if (strength < 60) {
|
||||
indicatorText = translate.weak;
|
||||
indicatorClass = 'is-weak';
|
||||
} else if (strength < 70) {
|
||||
indicatorText = translate.fair;
|
||||
indicatorClass = 'is-fair';
|
||||
} else if (strength < 80) {
|
||||
indicatorText = translate.good;
|
||||
indicatorClass = 'is-good';
|
||||
} else if (strength <= 100) {
|
||||
indicatorText = translate.strong;
|
||||
indicatorClass = 'is-strong';
|
||||
}
|
||||
|
||||
return {
|
||||
strength,
|
||||
tips,
|
||||
indicatorText,
|
||||
indicatorClass,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Password strength feedback for password confirm's main input.
|
||||
*
|
||||
* @param {string} message
|
||||
* The prefix text for the strength feedback word.
|
||||
*
|
||||
* @return {string}
|
||||
* The string representing the DOM fragment.
|
||||
*/
|
||||
Drupal.theme.passwordInputHelp = message =>
|
||||
`<div class="password-strength">
|
||||
<div class="password-strength__track">
|
||||
<div class="password-strength__bar js-password-strength-bar"></div>
|
||||
</div>
|
||||
<div aria-live="polite" aria-atomic="true" class="password-strength__title">
|
||||
${message} <span class="password-strength__text js-password-strength-text"></span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
/**
|
||||
* Password match feedback for password confirm input.
|
||||
*
|
||||
* @param {string} message
|
||||
* The message that precedes the yes|no text.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing the DOM fragment.
|
||||
*/
|
||||
Drupal.theme.passwordConfirmHelp = message =>
|
||||
`<div aria-live="polite" aria-atomic="true" class="password-match-message">${message} <span class="password-match-message__text js-password-match-text"></span></div>`;
|
||||
|
||||
/**
|
||||
* Password suggestions tips.
|
||||
*
|
||||
* @param {string} title
|
||||
* The title that precedes tips.
|
||||
* @param {Array.<string>} tips
|
||||
* Array containing the tips.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing the DOM fragment.
|
||||
*/
|
||||
Drupal.theme.passwordSuggestionsTips = (title, tips) =>
|
||||
`<div class="password-suggestions">${
|
||||
tips.length
|
||||
? `${title}<ul class="password-suggestions__tips"><li class="password-suggestions__tip">${tips.join(
|
||||
'</li><li class="password-suggestions__tip">',
|
||||
)}</li></ul>`
|
||||
: ''
|
||||
}</div>`;
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
Drupal.behaviors.password = {
|
||||
attach: function attach(context, settings) {
|
||||
var $passwordInput = $(context).find('input.js-password-field').once('password');
|
||||
|
||||
if ($passwordInput.length) {
|
||||
var translate = settings.password;
|
||||
|
||||
var $passwordInputParent = $passwordInput.parent();
|
||||
|
||||
var $passwordWidget = $passwordInput.closest('.js-form-type-password-confirm');
|
||||
|
||||
var $passwordConfirmInput = $passwordWidget.find('input.js-password-confirm');
|
||||
|
||||
var $passwordInputHelp = $(Drupal.theme.passwordInputHelp(translate.strengthTitle));
|
||||
|
||||
var $passwordConfirmHelp = $(Drupal.theme.passwordConfirmHelp(translate.confirmTitle));
|
||||
|
||||
var $passwordInputStrengthBar = $passwordInputHelp.find('.js-password-strength-bar');
|
||||
var $passwordInputStrengthMessageWrapper = $passwordInputHelp.find('.js-password-strength-text');
|
||||
var $passwordConfirmMatch = $passwordConfirmHelp.find('.js-password-match-text');
|
||||
var $passwordSuggestionsTips = $(Drupal.theme.passwordSuggestionsTips('', '')).hide();
|
||||
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
$passwordConfirmInput.after($passwordConfirmHelp).parent().after($passwordSuggestionsTips);
|
||||
|
||||
$passwordInputParent.append($passwordInputHelp);
|
||||
}
|
||||
|
||||
var passwordCheckMatch = function passwordCheckMatch(confirmInputVal) {
|
||||
if (confirmInputVal) {
|
||||
var success = $passwordInput.val() === confirmInputVal;
|
||||
var confirmClass = success ? 'ok' : 'error';
|
||||
var confirmMatchMessage = success ? translate.confirmSuccess : translate.confirmFailure;
|
||||
|
||||
if (!$passwordConfirmMatch.hasClass(confirmClass) || !$passwordConfirmMatch.html() === confirmMatchMessage) {
|
||||
$passwordConfirmMatch.html(confirmMatchMessage).removeClass('ok error').addClass(confirmClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var passwordCheck = function passwordCheck() {
|
||||
if (settings.password.showStrengthIndicator) {
|
||||
var result = Drupal.evaluatePasswordStrength($passwordInput.val(), settings.password);
|
||||
var $newSuggestions = $(Drupal.theme.passwordSuggestionsTips(translate.hasWeaknesses, result.tips));
|
||||
|
||||
if ($newSuggestions.html() !== $passwordSuggestionsTips.html()) {
|
||||
$passwordSuggestionsTips.replaceWith($newSuggestions);
|
||||
$passwordSuggestionsTips = $newSuggestions;
|
||||
|
||||
$passwordSuggestionsTips.toggle(result.strength !== 100);
|
||||
}
|
||||
|
||||
$passwordInputStrengthBar.css('width', result.strength + '%').removeClass('is-weak is-fair is-good is-strong').addClass(result.indicatorClass);
|
||||
|
||||
if (!$passwordInputStrengthMessageWrapper.hasClass(result.indicatorClass) || !$passwordInputStrengthMessageWrapper.html() === result.indicatorText) {
|
||||
$passwordInputStrengthMessageWrapper.html(result.indicatorText).removeClass('is-weak is-fair is-good is-strong').addClass(result.indicatorClass);
|
||||
}
|
||||
}
|
||||
|
||||
$passwordWidget.removeClass('is-initial').removeClass('is-password-empty is-password-filled').removeClass('is-confirm-empty is-confirm-filled');
|
||||
|
||||
$passwordWidget.addClass($passwordInput.val() ? 'is-password-filled' : 'is-password-empty');
|
||||
|
||||
passwordCheckMatch($passwordConfirmInput.val());
|
||||
$passwordWidget.addClass($passwordConfirmInput.val() ? 'is-confirm-filled' : 'is-confirm-empty');
|
||||
};
|
||||
|
||||
$passwordWidget.addClass($passwordInput.val() ? 'is-password-filled' : 'is-password-empty').addClass($passwordConfirmInput.val() ? 'is-confirm-filled' : 'is-confirm-empty');
|
||||
|
||||
$passwordInput.on('input', passwordCheck);
|
||||
$passwordConfirmInput.on('input', passwordCheck);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.evaluatePasswordStrength = function (password, translate) {
|
||||
password = password.trim();
|
||||
var indicatorText = void 0;
|
||||
var indicatorClass = void 0;
|
||||
var weaknesses = 0;
|
||||
var strength = 100;
|
||||
var tips = [];
|
||||
var hasLowercase = /[a-z]/.test(password);
|
||||
var hasUppercase = /[A-Z]/.test(password);
|
||||
var hasNumbers = /[0-9]/.test(password);
|
||||
var hasPunctuation = /[^a-zA-Z0-9]/.test(password);
|
||||
|
||||
var $usernameBox = $('input.username');
|
||||
var username = $usernameBox.length > 0 ? $usernameBox.val() : translate.username;
|
||||
|
||||
if (password.length < 12) {
|
||||
tips.push(translate.tooShort);
|
||||
strength -= (12 - password.length) * 5 + 30;
|
||||
}
|
||||
|
||||
if (!hasLowercase) {
|
||||
tips.push(translate.addLowerCase);
|
||||
weaknesses += 1;
|
||||
}
|
||||
if (!hasUppercase) {
|
||||
tips.push(translate.addUpperCase);
|
||||
weaknesses += 1;
|
||||
}
|
||||
if (!hasNumbers) {
|
||||
tips.push(translate.addNumbers);
|
||||
weaknesses += 1;
|
||||
}
|
||||
if (!hasPunctuation) {
|
||||
tips.push(translate.addPunctuation);
|
||||
weaknesses += 1;
|
||||
}
|
||||
|
||||
switch (weaknesses) {
|
||||
case 1:
|
||||
strength -= 12.5;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
strength -= 25;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
strength -= 40;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
strength -= 40;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
|
||||
tips.push(translate.sameAsUsername);
|
||||
|
||||
strength = 5;
|
||||
}
|
||||
|
||||
if (strength < 60) {
|
||||
indicatorText = translate.weak;
|
||||
indicatorClass = 'is-weak';
|
||||
} else if (strength < 70) {
|
||||
indicatorText = translate.fair;
|
||||
indicatorClass = 'is-fair';
|
||||
} else if (strength < 80) {
|
||||
indicatorText = translate.good;
|
||||
indicatorClass = 'is-good';
|
||||
} else if (strength <= 100) {
|
||||
indicatorText = translate.strong;
|
||||
indicatorClass = 'is-strong';
|
||||
}
|
||||
|
||||
return {
|
||||
strength: strength,
|
||||
tips: tips,
|
||||
indicatorText: indicatorText,
|
||||
indicatorClass: indicatorClass
|
||||
};
|
||||
};
|
||||
|
||||
Drupal.theme.passwordInputHelp = function (message) {
|
||||
return '<div class="password-strength">\n <div class="password-strength__track">\n <div class="password-strength__bar js-password-strength-bar"></div>\n </div>\n <div aria-live="polite" aria-atomic="true" class="password-strength__title">\n ' + message + ' <span class="password-strength__text js-password-strength-text"></span>\n </div>\n </div>';
|
||||
};
|
||||
|
||||
Drupal.theme.passwordConfirmHelp = function (message) {
|
||||
return '<div aria-live="polite" aria-atomic="true" class="password-match-message">' + message + ' <span class="password-match-message__text js-password-match-text"></span></div>';
|
||||
};
|
||||
|
||||
Drupal.theme.passwordSuggestionsTips = function (title, tips) {
|
||||
return '<div class="password-suggestions">' + (tips.length ? title + '<ul class="password-suggestions__tips"><li class="password-suggestions__tip">' + tips.join('</li><li class="password-suggestions__tip">') + '</li></ul>' : '') + '</div>';
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,470 @@
|
||||
/**
|
||||
* @file
|
||||
* Defines vertical tabs functionality.
|
||||
*
|
||||
* This file replaces core/misc/vertical-tabs.js to fix some bugs in the
|
||||
* original implementation, as well as makes minor changes to enable Claro
|
||||
* designs:
|
||||
* 1. Replaces hard-coded markup and adds 'js-' prefixed CSS classes for the
|
||||
* JavaScript functionality (https://www.drupal.org/node/3081489).
|
||||
* - The original Drupal.behavior and Drupal.verticalTab object hard-code
|
||||
* markup of the tab list and (the outermost) wrapper of the vertical tabs
|
||||
* component.
|
||||
* - The original Drupal.verticalTab object is built on the same (unprefixed)
|
||||
* CSS classes that should be used only for theming the component:
|
||||
* - .vertical-tabs__pane - replaced by .js-vertical-tabs-pane;
|
||||
* - .vertical-tabs__menu-item - replaced by .js-vertical-tabs-menu-item;
|
||||
* - .vertical-tab--hidden - replaced by .js-vertical-tab-hidden.
|
||||
* 2. Fixes accessibility bugs (https://www.drupal.org/node/3081500):
|
||||
* - The original Drupal.verticalTab object doesn't take care of the right
|
||||
* aria attributes. Every details summary element is described with
|
||||
* aria-expanded="false" and aria-pressed="false".
|
||||
* - The original Drupal.verticalTab object uses a non-unique CSS id
|
||||
* '#active-vertical-tab' for the marker of the active menu tab. This leads
|
||||
* to broken behavior on filter format and editor configuration form where
|
||||
* multiple vertical tabs may appear
|
||||
* (/admin/config/content/formats/manage/basic_html).
|
||||
* - Auto-focus bug: if the vertical tab is activated by pressing enter on
|
||||
* the vertical tab menu link, the original Drupal.verticalTab object tries
|
||||
* to focus the first visible :input element in a vertical tab content. The
|
||||
* implementation doesn't work in all scenarios. For example, on the
|
||||
* 'Filter format and editor' form
|
||||
* (/admin/config/content/formats/manage/basic_html), if the user presses
|
||||
* the enter key on the last vertical tabs element's menu link ('Filter
|
||||
* settings'), the focused element will be the first vertical tabs
|
||||
* ('CKEditor plugin settings') active input, and not the expected one.
|
||||
* 3. Consistency between browsers (https://www.drupal.org/node/3081508):
|
||||
* We have to display the setting summary on the 'accordion look' as well.
|
||||
* Using the original file, these are displayed only on browsers without
|
||||
* HTML5 details support, where core's built-in core/misc/collapse.js HTML5
|
||||
* details polyfill is in action.
|
||||
* 4. Help fulfill our custom needs (https://www.drupal.org/node/3081519):
|
||||
* The original behavior applies its features only when the actual screen
|
||||
* width is bigger than 640 pixels (or the value of the
|
||||
* drupalSettings.widthBreakpoint). But we want to switch between the
|
||||
* 'accordion look' and 'tab look' dynamically, right after the browser
|
||||
* viewport was resized, and not only on page load.
|
||||
* This would be possible even by defining drupalSettings.widthBreakpoint
|
||||
* with '0' value. But since the name of this configuration does not suggest
|
||||
* that it is (and will be) used only by vertical tabs, it is much cleaner
|
||||
* to remove the unneeded condition from the functionality.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Triggers when form values inside a vertical tab changes.
|
||||
*
|
||||
* This is used to update the summary in vertical tabs in order to know what
|
||||
* are the important fields' values.
|
||||
*
|
||||
* @event summaryUpdated
|
||||
*/
|
||||
|
||||
(($, Drupal) => {
|
||||
/**
|
||||
* Show the parent vertical tab pane of a targeted page fragment.
|
||||
*
|
||||
* In order to make sure a targeted element inside a vertical tab pane is
|
||||
* visible on a hash change or fragment link click, show all parent panes.
|
||||
*
|
||||
* @param {jQuery.Event} e
|
||||
* The event triggered.
|
||||
* @param {jQuery} $target
|
||||
* The targeted node as a jQuery object.
|
||||
*/
|
||||
const handleFragmentLinkClickOrHashChange = (e, $target) => {
|
||||
$target.parents('.js-vertical-tabs-pane').each((index, pane) => {
|
||||
$(pane)
|
||||
.data('verticalTab')
|
||||
.focus();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This script transforms a set of details into a stack of vertical tabs.
|
||||
*
|
||||
* Each tab may have a summary which can be updated by another
|
||||
* script. For that to work, each details element has an associated
|
||||
* 'verticalTabCallback' (with jQuery.data() attached to the details),
|
||||
* which is called every time the user performs an update to a form
|
||||
* element inside the tab pane.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches behaviors for vertical tabs.
|
||||
*/
|
||||
Drupal.behaviors.claroVerticalTabs = {
|
||||
attach(context) {
|
||||
/**
|
||||
* Binds a listener to handle fragment link clicks and URL hash changes.
|
||||
*/
|
||||
$('body')
|
||||
.once('vertical-tabs-fragments')
|
||||
.on(
|
||||
'formFragmentLinkClickOrHashChange.verticalTabs',
|
||||
handleFragmentLinkClickOrHashChange,
|
||||
);
|
||||
|
||||
$(context)
|
||||
.find('[data-vertical-tabs-panes]')
|
||||
.once('vertical-tabs')
|
||||
.each(function initializeVerticalTabs() {
|
||||
const $this = $(this).addClass('vertical-tabs__items--processed');
|
||||
const focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
|
||||
let tabFocus;
|
||||
|
||||
// Check if there are some details that can be converted to
|
||||
// vertical-tabs.
|
||||
const $details = $this.find('> details');
|
||||
if ($details.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the tab column.
|
||||
const tabList = $(Drupal.theme.verticalTabListWrapper());
|
||||
$this
|
||||
.wrap(
|
||||
$(Drupal.theme.verticalTabsWrapper()).addClass(
|
||||
'js-vertical-tabs',
|
||||
),
|
||||
)
|
||||
.before(tabList);
|
||||
|
||||
// Transform each details into a tab.
|
||||
$details.each(function initializeVerticalTabItems() {
|
||||
const $that = $(this);
|
||||
/* eslint-disable new-cap */
|
||||
const verticalTab = new Drupal.verticalTab({
|
||||
title: $that.find('> summary').text(),
|
||||
details: $that,
|
||||
});
|
||||
/* eslint-enable new-cap */
|
||||
tabList.append(verticalTab.item);
|
||||
$that
|
||||
// prop() can't be used on browsers not supporting details
|
||||
// element, the style won't apply to them if prop() is used.
|
||||
.removeAttr('open')
|
||||
.addClass('js-vertical-tabs-pane')
|
||||
.data('verticalTab', verticalTab);
|
||||
if (this.id === focusID) {
|
||||
tabFocus = $that;
|
||||
}
|
||||
});
|
||||
|
||||
if (!tabFocus) {
|
||||
// If the current URL has a fragment and one of the tabs contains an
|
||||
// element that matches the URL fragment, activate that tab.
|
||||
const $locationHash = $this.find(window.location.hash);
|
||||
if (window.location.hash && $locationHash.length) {
|
||||
tabFocus = $locationHash.is('.js-vertical-tabs-pane')
|
||||
? $locationHash
|
||||
: $locationHash.closest('.js-vertical-tabs-pane');
|
||||
} else {
|
||||
tabFocus = $this.find('> .js-vertical-tabs-pane').eq(0);
|
||||
}
|
||||
}
|
||||
if (tabFocus.length) {
|
||||
tabFocus.data('verticalTab').focus(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The vertical tab object represents a single tab within a tab group.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} settings
|
||||
* Settings object.
|
||||
* @param {string} settings.title
|
||||
* The name of the tab.
|
||||
* @param {jQuery} settings.details
|
||||
* The jQuery object of the details element that is the tab pane.
|
||||
*
|
||||
* @fires event:summaryUpdated
|
||||
*
|
||||
* @listens event:summaryUpdated
|
||||
*/
|
||||
Drupal.verticalTab = function verticalTab(settings) {
|
||||
const self = this;
|
||||
$.extend(this, settings, Drupal.theme('verticalTab', settings));
|
||||
|
||||
this.item.addClass('js-vertical-tabs-menu-item');
|
||||
|
||||
this.link.attr('href', `#${settings.details.attr('id')}`);
|
||||
|
||||
this.detailsSummaryDescription = $(
|
||||
Drupal.theme.verticalTabDetailsDescription(),
|
||||
).appendTo(this.details.find('> summary'));
|
||||
|
||||
this.link.on('click', event => {
|
||||
event.preventDefault();
|
||||
self.focus();
|
||||
});
|
||||
|
||||
this.details.on('toggle', event => {
|
||||
// We will control this by summary clicks.
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Open the tab for every browser, with or without details support.
|
||||
this.details
|
||||
.find('> summary')
|
||||
.on('click', event => {
|
||||
event.preventDefault();
|
||||
self.details.attr('open', true);
|
||||
if (self.details.hasClass('collapse-processed')) {
|
||||
setTimeout(() => {
|
||||
self.focus();
|
||||
}, 10);
|
||||
} else {
|
||||
self.focus();
|
||||
}
|
||||
})
|
||||
.on('keydown', event => {
|
||||
if (event.keyCode === 13) {
|
||||
// Set focus on the first input field of the current visible details/tab
|
||||
// pane.
|
||||
setTimeout(() => {
|
||||
self.details
|
||||
.find(':input:visible:enabled')
|
||||
.eq(0)
|
||||
.trigger('focus');
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard events added:
|
||||
// Pressing the Enter key will open the tab pane.
|
||||
this.link.on('keydown', event => {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
self.focus();
|
||||
// Set focus on the first input field of the current visible details/tab
|
||||
// pane.
|
||||
self.details
|
||||
.find(':input:visible:enabled')
|
||||
.eq(0)
|
||||
.trigger('focus');
|
||||
}
|
||||
});
|
||||
|
||||
this.details
|
||||
.on('summaryUpdated', () => {
|
||||
self.updateSummary();
|
||||
})
|
||||
.trigger('summaryUpdated');
|
||||
};
|
||||
|
||||
Drupal.verticalTab.prototype = {
|
||||
/**
|
||||
* Displays the tab's content pane.
|
||||
*
|
||||
* @param {bool} triggerFocus
|
||||
* Whether focus should be triggered for the summary element.
|
||||
*/
|
||||
focus(triggerFocus = true) {
|
||||
this.details
|
||||
.siblings('.js-vertical-tabs-pane')
|
||||
.each(function closeOtherTabs() {
|
||||
const tab = $(this).data('verticalTab');
|
||||
if (tab.details.attr('open')) {
|
||||
tab.details
|
||||
.removeAttr('open')
|
||||
.find('> summary')
|
||||
.attr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-pressed': 'false',
|
||||
});
|
||||
tab.item.removeClass('is-selected');
|
||||
}
|
||||
})
|
||||
.end()
|
||||
.siblings(':hidden.vertical-tabs__active-tab')
|
||||
.val(this.details.attr('id'));
|
||||
|
||||
this.details
|
||||
.attr('open', true)
|
||||
.find('> summary')
|
||||
.attr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-pressed': 'true',
|
||||
})
|
||||
.closest('.js-vertical-tabs')
|
||||
.find('.js-vertical-tab-active')
|
||||
.remove();
|
||||
|
||||
if (triggerFocus) {
|
||||
const $summary = this.details.find('> summary');
|
||||
if ($summary.is(':visible')) {
|
||||
$summary.trigger('focus');
|
||||
}
|
||||
}
|
||||
this.item.addClass('is-selected');
|
||||
// Mark the active tab for screen readers.
|
||||
this.title.after(
|
||||
$(Drupal.theme.verticalTabActiveTabIndicator()).addClass(
|
||||
'js-vertical-tab-active',
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the tab's summary.
|
||||
*/
|
||||
updateSummary() {
|
||||
const summary = this.details.drupalGetSummary();
|
||||
this.detailsSummaryDescription.html(summary);
|
||||
this.summary.html(summary);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a vertical tab pane.
|
||||
*
|
||||
* @return {Drupal.verticalTab}
|
||||
* The verticalTab instance.
|
||||
*/
|
||||
tabShow() {
|
||||
// Display the tab.
|
||||
this.item.removeClass('vertical-tabs__menu-item--hidden').show();
|
||||
// Show the vertical tabs.
|
||||
this.item.closest('.js-form-type-vertical-tabs').show();
|
||||
// Display the details element.
|
||||
this.details
|
||||
.removeClass('vertical-tab--hidden js-vertical-tab-hidden')
|
||||
.show();
|
||||
// Update first and last CSS classes for details.
|
||||
this.details
|
||||
.parent()
|
||||
.children('.js-vertical-tabs-pane')
|
||||
.removeClass('vertical-tabs__item--first vertical-tabs__item--last')
|
||||
.filter(':visible')
|
||||
.eq(0)
|
||||
.addClass('vertical-tabs__item--first');
|
||||
this.details
|
||||
.parent()
|
||||
.children('.js-vertical-tabs-pane')
|
||||
.filter(':visible')
|
||||
.eq(-1)
|
||||
.addClass('vertical-tabs__item--last');
|
||||
// Make tab active, but without triggering focus.
|
||||
this.focus(false);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides a vertical tab pane.
|
||||
*
|
||||
* @return {Drupal.verticalTab}
|
||||
* The verticalTab instance.
|
||||
*/
|
||||
tabHide() {
|
||||
// Hide this tab.
|
||||
this.item.addClass('vertical-tabs__menu-item--hidden').hide();
|
||||
// Hide the details element.
|
||||
this.details
|
||||
.addClass('vertical-tab--hidden js-vertical-tab-hidden')
|
||||
.hide();
|
||||
// Update first and last CSS classes for details.
|
||||
this.details
|
||||
.parent()
|
||||
.children('.js-vertical-tabs-pane')
|
||||
.removeClass('vertical-tabs__item--first vertical-tabs__item--last')
|
||||
.filter(':visible')
|
||||
.eq(0)
|
||||
.addClass('vertical-tabs__item--first');
|
||||
this.details
|
||||
.parent()
|
||||
.children('.js-vertical-tabs-pane')
|
||||
.filter(':visible')
|
||||
.eq(-1)
|
||||
.addClass('vertical-tabs__item--last');
|
||||
// Focus the first visible tab (if there is one).
|
||||
const $firstTab = this.details
|
||||
.siblings('.js-vertical-tabs-pane:not(.js-vertical-tab-hidden)')
|
||||
.eq(0);
|
||||
if ($firstTab.length) {
|
||||
$firstTab.data('verticalTab').focus(false);
|
||||
}
|
||||
// Hide the vertical tabs (if no tabs remain).
|
||||
else {
|
||||
this.item.closest('.js-form-type-vertical-tabs').hide();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Theme function for a vertical tab.
|
||||
*
|
||||
* @param {object} settings
|
||||
* An object with the following keys:
|
||||
* @param {string} settings.title
|
||||
* The name of the tab.
|
||||
*
|
||||
* @return {object}
|
||||
* This function has to return an object with at least these keys:
|
||||
* - item: The root tab jQuery element
|
||||
* - link: The anchor tag that acts as the clickable area of the tab
|
||||
* (jQuery version)
|
||||
* - summary: The jQuery element that contains the tab summary
|
||||
*/
|
||||
Drupal.theme.verticalTab = settings => {
|
||||
const tab = {};
|
||||
tab.item = $(
|
||||
'<li class="vertical-tabs__menu-item" tabindex="-1"></li>',
|
||||
).append(
|
||||
(tab.link = $('<a href="#" class="vertical-tabs__menu-link"></a>').append(
|
||||
$('<span class="vertical-tabs__menu-link-content"></span>')
|
||||
.append(
|
||||
(tab.title = $(
|
||||
'<strong class="vertical-tabs__menu-link-title"></strong>',
|
||||
).text(settings.title)),
|
||||
)
|
||||
.append(
|
||||
(tab.summary = $(
|
||||
'<span class="vertical-tabs__menu-link-summary"></span>',
|
||||
)),
|
||||
),
|
||||
)),
|
||||
);
|
||||
return tab;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper of the menu and the panes.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing the DOM fragment.
|
||||
*/
|
||||
Drupal.theme.verticalTabsWrapper = () =>
|
||||
'<div class="vertical-tabs clearfix"></div>';
|
||||
|
||||
/**
|
||||
* The wrapper of the vertical tab menu items.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing the DOM fragment.
|
||||
*/
|
||||
Drupal.theme.verticalTabListWrapper = () =>
|
||||
'<ul class="vertical-tabs__menu"></ul>';
|
||||
|
||||
/**
|
||||
* The wrapper of the details summary message added to the summary element.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing the DOM fragment.
|
||||
*/
|
||||
Drupal.theme.verticalTabDetailsDescription = () =>
|
||||
'<span class="vertical-tabs__details-summary-summary"></span>';
|
||||
|
||||
/**
|
||||
* Themes the active vertical tab menu item message.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing the DOM fragment.
|
||||
*/
|
||||
Drupal.theme.verticalTabActiveTabIndicator = () =>
|
||||
`<span class="visually-hidden">${Drupal.t('(active tab)')}</span>`;
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
var handleFragmentLinkClickOrHashChange = function handleFragmentLinkClickOrHashChange(e, $target) {
|
||||
$target.parents('.js-vertical-tabs-pane').each(function (index, pane) {
|
||||
$(pane).data('verticalTab').focus();
|
||||
});
|
||||
};
|
||||
|
||||
Drupal.behaviors.claroVerticalTabs = {
|
||||
attach: function attach(context) {
|
||||
$('body').once('vertical-tabs-fragments').on('formFragmentLinkClickOrHashChange.verticalTabs', handleFragmentLinkClickOrHashChange);
|
||||
|
||||
$(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function initializeVerticalTabs() {
|
||||
var $this = $(this).addClass('vertical-tabs__items--processed');
|
||||
var focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
|
||||
var tabFocus = void 0;
|
||||
|
||||
var $details = $this.find('> details');
|
||||
if ($details.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tabList = $(Drupal.theme.verticalTabListWrapper());
|
||||
$this.wrap($(Drupal.theme.verticalTabsWrapper()).addClass('js-vertical-tabs')).before(tabList);
|
||||
|
||||
$details.each(function initializeVerticalTabItems() {
|
||||
var $that = $(this);
|
||||
|
||||
var verticalTab = new Drupal.verticalTab({
|
||||
title: $that.find('> summary').text(),
|
||||
details: $that
|
||||
});
|
||||
|
||||
tabList.append(verticalTab.item);
|
||||
$that.removeAttr('open').addClass('js-vertical-tabs-pane').data('verticalTab', verticalTab);
|
||||
if (this.id === focusID) {
|
||||
tabFocus = $that;
|
||||
}
|
||||
});
|
||||
|
||||
if (!tabFocus) {
|
||||
var $locationHash = $this.find(window.location.hash);
|
||||
if (window.location.hash && $locationHash.length) {
|
||||
tabFocus = $locationHash.is('.js-vertical-tabs-pane') ? $locationHash : $locationHash.closest('.js-vertical-tabs-pane');
|
||||
} else {
|
||||
tabFocus = $this.find('> .js-vertical-tabs-pane').eq(0);
|
||||
}
|
||||
}
|
||||
if (tabFocus.length) {
|
||||
tabFocus.data('verticalTab').focus(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.verticalTab = function verticalTab(settings) {
|
||||
var self = this;
|
||||
$.extend(this, settings, Drupal.theme('verticalTab', settings));
|
||||
|
||||
this.item.addClass('js-vertical-tabs-menu-item');
|
||||
|
||||
this.link.attr('href', '#' + settings.details.attr('id'));
|
||||
|
||||
this.detailsSummaryDescription = $(Drupal.theme.verticalTabDetailsDescription()).appendTo(this.details.find('> summary'));
|
||||
|
||||
this.link.on('click', function (event) {
|
||||
event.preventDefault();
|
||||
self.focus();
|
||||
});
|
||||
|
||||
this.details.on('toggle', function (event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
this.details.find('> summary').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
self.details.attr('open', true);
|
||||
if (self.details.hasClass('collapse-processed')) {
|
||||
setTimeout(function () {
|
||||
self.focus();
|
||||
}, 10);
|
||||
} else {
|
||||
self.focus();
|
||||
}
|
||||
}).on('keydown', function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
setTimeout(function () {
|
||||
self.details.find(':input:visible:enabled').eq(0).trigger('focus');
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
|
||||
this.link.on('keydown', function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
self.focus();
|
||||
|
||||
self.details.find(':input:visible:enabled').eq(0).trigger('focus');
|
||||
}
|
||||
});
|
||||
|
||||
this.details.on('summaryUpdated', function () {
|
||||
self.updateSummary();
|
||||
}).trigger('summaryUpdated');
|
||||
};
|
||||
|
||||
Drupal.verticalTab.prototype = {
|
||||
focus: function focus() {
|
||||
var triggerFocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
||||
|
||||
this.details.siblings('.js-vertical-tabs-pane').each(function closeOtherTabs() {
|
||||
var tab = $(this).data('verticalTab');
|
||||
if (tab.details.attr('open')) {
|
||||
tab.details.removeAttr('open').find('> summary').attr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-pressed': 'false'
|
||||
});
|
||||
tab.item.removeClass('is-selected');
|
||||
}
|
||||
}).end().siblings(':hidden.vertical-tabs__active-tab').val(this.details.attr('id'));
|
||||
|
||||
this.details.attr('open', true).find('> summary').attr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-pressed': 'true'
|
||||
}).closest('.js-vertical-tabs').find('.js-vertical-tab-active').remove();
|
||||
|
||||
if (triggerFocus) {
|
||||
var $summary = this.details.find('> summary');
|
||||
if ($summary.is(':visible')) {
|
||||
$summary.trigger('focus');
|
||||
}
|
||||
}
|
||||
this.item.addClass('is-selected');
|
||||
|
||||
this.title.after($(Drupal.theme.verticalTabActiveTabIndicator()).addClass('js-vertical-tab-active'));
|
||||
},
|
||||
updateSummary: function updateSummary() {
|
||||
var summary = this.details.drupalGetSummary();
|
||||
this.detailsSummaryDescription.html(summary);
|
||||
this.summary.html(summary);
|
||||
},
|
||||
tabShow: function tabShow() {
|
||||
this.item.removeClass('vertical-tabs__menu-item--hidden').show();
|
||||
|
||||
this.item.closest('.js-form-type-vertical-tabs').show();
|
||||
|
||||
this.details.removeClass('vertical-tab--hidden js-vertical-tab-hidden').show();
|
||||
|
||||
this.details.parent().children('.js-vertical-tabs-pane').removeClass('vertical-tabs__item--first vertical-tabs__item--last').filter(':visible').eq(0).addClass('vertical-tabs__item--first');
|
||||
this.details.parent().children('.js-vertical-tabs-pane').filter(':visible').eq(-1).addClass('vertical-tabs__item--last');
|
||||
|
||||
this.focus(false);
|
||||
return this;
|
||||
},
|
||||
tabHide: function tabHide() {
|
||||
this.item.addClass('vertical-tabs__menu-item--hidden').hide();
|
||||
|
||||
this.details.addClass('vertical-tab--hidden js-vertical-tab-hidden').hide();
|
||||
|
||||
this.details.parent().children('.js-vertical-tabs-pane').removeClass('vertical-tabs__item--first vertical-tabs__item--last').filter(':visible').eq(0).addClass('vertical-tabs__item--first');
|
||||
this.details.parent().children('.js-vertical-tabs-pane').filter(':visible').eq(-1).addClass('vertical-tabs__item--last');
|
||||
|
||||
var $firstTab = this.details.siblings('.js-vertical-tabs-pane:not(.js-vertical-tab-hidden)').eq(0);
|
||||
if ($firstTab.length) {
|
||||
$firstTab.data('verticalTab').focus(false);
|
||||
} else {
|
||||
this.item.closest('.js-form-type-vertical-tabs').hide();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.theme.verticalTab = function (settings) {
|
||||
var tab = {};
|
||||
tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>').append(tab.link = $('<a href="#" class="vertical-tabs__menu-link"></a>').append($('<span class="vertical-tabs__menu-link-content"></span>').append(tab.title = $('<strong class="vertical-tabs__menu-link-title"></strong>').text(settings.title)).append(tab.summary = $('<span class="vertical-tabs__menu-link-summary"></span>'))));
|
||||
return tab;
|
||||
};
|
||||
|
||||
Drupal.theme.verticalTabsWrapper = function () {
|
||||
return '<div class="vertical-tabs clearfix"></div>';
|
||||
};
|
||||
|
||||
Drupal.theme.verticalTabListWrapper = function () {
|
||||
return '<ul class="vertical-tabs__menu"></ul>';
|
||||
};
|
||||
|
||||
Drupal.theme.verticalTabDetailsDescription = function () {
|
||||
return '<span class="vertical-tabs__details-summary-summary"></span>';
|
||||
};
|
||||
|
||||
Drupal.theme.verticalTabActiveTabIndicator = function () {
|
||||
return '<span class="visually-hidden">' + Drupal.t('(active tab)') + '</span>';
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
Reference in New Issue
Block a user