first commit

This commit is contained in:
2020-06-08 23:57:36 +02:00
commit 6277454f7a
16057 changed files with 1715382 additions and 0 deletions
+45
View File
@@ -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">&nbsp;</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">&nbsp;</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);
+23
View File
@@ -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">&nbsp;</div></div>';
};
Drupal.theme.ajaxProgressThrobber = function (message) {
var messageMarkup = typeof message === 'string' ? Drupal.theme('ajaxProgressMessage', message) : '';
var throbber = '<div class="ajax-progress__throbber">&nbsp;</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);
+29
View File
@@ -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);
+15
View File
@@ -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);
+12
View File
@@ -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);
+58
View File
@@ -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);
+34
View File
@@ -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);
+12
View File
@@ -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);
+50
View File
@@ -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);
+27
View File
@@ -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);
}
})();
+88
View File
@@ -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);
+78
View File
@@ -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
+344
View File
@@ -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);
+181
View File
@@ -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);
+200
View File
@@ -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);