diff options
Diffstat (limited to 'core/misc/collapse.js')
-rw-r--r-- | core/misc/collapse.js | 163 |
1 files changed, 122 insertions, 41 deletions
diff --git a/core/misc/collapse.js b/core/misc/collapse.js index 32193ea1730..f2e07b65329 100644 --- a/core/misc/collapse.js +++ b/core/misc/collapse.js @@ -1,74 +1,155 @@ /** -* DO NOT EDIT THIS FILE. -* See the following change record for more information, -* https://www.drupal.org/node/2815083 -* @preserve -**/ + * @file + * Polyfill for HTML5 details elements. + */ (function ($, Modernizr, Drupal) { + /** + * The collapsible details object represents a single details element. + * + * @constructor Drupal.CollapsibleDetails + * + * @param {HTMLElement} node + * The details element. + */ function CollapsibleDetails(node) { this.$node = $(node); this.$node.data('details', this); - const anchor = window.location.hash && window.location.hash !== '#' ? `, ${window.location.hash}` : ''; - + // Expand details if there are errors inside, or if it contains an + // element that is targeted by the URI fragment identifier. + const anchor = + window.location.hash && window.location.hash !== '#' + ? `, ${window.location.hash}` + : ''; if (this.$node.find(`.error${anchor}`).length) { this.$node.attr('open', true); } - + // Initialize and set up the summary polyfill. this.setupSummaryPolyfill(); } - $.extend(CollapsibleDetails, { - instances: [] - }); - $.extend(CollapsibleDetails.prototype, { - setupSummaryPolyfill() { - const $summary = this.$node.find('> summary'); - $summary.attr('tabindex', '-1'); - $('<span class="details-summary-prefix visually-hidden"></span>').append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show')).prependTo($summary).after(document.createTextNode(' ')); - $('<a class="details-title"></a>').attr('href', `#${this.$node.attr('id')}`).prepend($summary.contents()).appendTo($summary); - $summary.append(this.$summary).on('click', $.proxy(this.onSummaryClick, this)); + $.extend( + CollapsibleDetails, + /** @lends Drupal.CollapsibleDetails */ { + /** + * Holds references to instantiated CollapsibleDetails objects. + * + * @type {Array.<Drupal.CollapsibleDetails>} + */ + instances: [], }, + ); - onSummaryClick(e) { - this.toggle(); - e.preventDefault(); - }, + $.extend( + CollapsibleDetails.prototype, + /** @lends Drupal.CollapsibleDetails# */ { + /** + * Initialize and setup summary markup. + */ + setupSummaryPolyfill() { + // Turn the summary into a clickable link. + const $summary = this.$node.find('> summary'); - toggle() { - const isOpen = !!this.$node.attr('open'); - const $summaryPrefix = this.$node.find('> summary span.details-summary-prefix'); + // If this polyfill is in use, the browser does not recognize + // <summary> as a focusable element. The tabindex is set to -1 so the + // tabbable library does not incorrectly identify it as tabbable. + $summary.attr('tabindex', '-1'); - if (isOpen) { - $summaryPrefix.html(Drupal.t('Show')); - } else { - $summaryPrefix.html(Drupal.t('Hide')); - } + $('<span class="details-summary-prefix visually-hidden"></span>') + .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show')) + .prependTo($summary) + .after(document.createTextNode(' ')); - setTimeout(() => { - this.$node.attr('open', !isOpen); - }, 0); - } + // .wrapInner() does not retain bound events. + $('<a class="details-title"></a>') + .attr('href', `#${this.$node.attr('id')}`) + .prepend($summary.contents()) + .appendTo($summary); + + $summary + .append(this.$summary) + .on('click', $.proxy(this.onSummaryClick, this)); + }, + + /** + * Handle summary clicks. + * + * @param {jQuery.Event} e + * The event triggered. + */ + onSummaryClick(e) { + this.toggle(); + e.preventDefault(); + }, - }); + /** + * Toggle the visibility of a details element using smooth animations. + */ + toggle() { + const isOpen = !!this.$node.attr('open'); + const $summaryPrefix = this.$node.find( + '> summary span.details-summary-prefix', + ); + if (isOpen) { + $summaryPrefix.html(Drupal.t('Show')); + } else { + $summaryPrefix.html(Drupal.t('Hide')); + } + // Delay setting the attribute to emulate chrome behavior and make + // details-aria.js work as expected with this polyfill. + setTimeout(() => { + this.$node.attr('open', !isOpen); + }, 0); + }, + }, + ); + + /** + * Polyfill HTML5 details element. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches behavior for the details element. + */ Drupal.behaviors.collapse = { attach(context) { if (Modernizr.details) { return; } - - once('collapse', 'details', context).forEach(detail => { + once('collapse', 'details', context).forEach((detail) => { + // This class is used for styling purpose only. detail.classList.add('collapse-processed'); CollapsibleDetails.instances.push(new CollapsibleDetails(detail)); }); - } - + }, }; + /** + * Open parent details elements of a targeted page fragment. + * + * Opens all (nested) details element on a hash change or fragment link click + * when the target is a child element, in order to make sure the targeted + * element is visible. Aria attributes on the summary + * are set by triggering the click event listener in details-aria.js. + * + * @param {jQuery.Event} e + * The event triggered. + * @param {jQuery} $target + * The targeted node as a jQuery object. + */ const handleFragmentLinkClickOrHashChange = (e, $target) => { $target.parents('details').not('[open]').find('> summary').trigger('click'); }; - $('body').on('formFragmentLinkClickOrHashChange.details', handleFragmentLinkClickOrHashChange); + /** + * Binds a listener to handle fragment link clicks and URL hash changes. + */ + $('body').on( + 'formFragmentLinkClickOrHashChange.details', + handleFragmentLinkClickOrHashChange, + ); + + // Expose constructor in the public space. Drupal.CollapsibleDetails = CollapsibleDetails; -})(jQuery, Modernizr, Drupal);
\ No newline at end of file +})(jQuery, Modernizr, Drupal); |