3 * Polyfill for HTML5 details elements.
6 (function ($, Modernizr, Drupal) {
8 * The collapsible details object represents a single details element.
10 * @constructor Drupal.CollapsibleDetails
12 * @param {HTMLElement} node
13 * The details element.
15 function CollapsibleDetails(node) {
17 this.$node.data('details', this);
18 // Expand details if there are errors inside, or if it contains an
19 // element that is targeted by the URI fragment identifier.
20 const anchor = location.hash && location.hash !== '#' ? `, ${location.hash}` : '';
21 if (this.$node.find(`.error${anchor}`).length) {
22 this.$node.attr('open', true);
24 // Initialize and setup the summary,
26 // Initialize and setup the legend.
30 $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{
33 * Holds references to instantiated CollapsibleDetails objects.
35 * @type {Array.<Drupal.CollapsibleDetails>}
40 $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{
43 * Initialize and setup summary events and markup.
45 * @fires event:summaryUpdated
47 * @listens event:summaryUpdated
50 this.$summary = $('<span class="summary"></span>');
52 .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
53 .trigger('summaryUpdated');
57 * Initialize and setup legend markup.
60 // Turn the summary into a clickable link.
61 const $legend = this.$node.find('> summary');
63 $('<span class="details-summary-prefix visually-hidden"></span>')
64 .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
66 .after(document.createTextNode(' '));
68 // .wrapInner() does not retain bound events.
69 $('<a class="details-title"></a>')
70 .attr('href', `#${this.$node.attr('id')}`)
71 .prepend($legend.contents())
75 .append(this.$summary)
76 .on('click', $.proxy(this.onLegendClick, this));
80 * Handle legend clicks.
82 * @param {jQuery.Event} e
83 * The event triggered.
94 const text = $.trim(this.$node.drupalGetSummary());
95 this.$summary.html(text ? ` (${text})` : '');
99 * Toggle the visibility of a details element using smooth animations.
102 const isOpen = !!this.$node.attr('open');
103 const $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
105 $summaryPrefix.html(Drupal.t('Show'));
108 $summaryPrefix.html(Drupal.t('Hide'));
110 // Delay setting the attribute to emulate chrome behavior and make
111 // details-aria.js work as expected with this polyfill.
113 this.$node.attr('open', !isOpen);
119 * Polyfill HTML5 details element.
121 * @type {Drupal~behavior}
123 * @prop {Drupal~behaviorAttach} attach
124 * Attaches behavior for the details element.
126 Drupal.behaviors.collapse = {
128 if (Modernizr.details) {
131 const $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed');
132 if ($collapsibleDetails.length) {
133 for (let i = 0; i < $collapsibleDetails.length; i++) {
134 CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));
141 * Open parent details elements of a targeted page fragment.
143 * Opens all (nested) details element on a hash change or fragment link click
144 * when the target is a child element, in order to make sure the targeted
145 * element is visible. Aria attributes on the summary
146 * are set by triggering the click event listener in details-aria.js.
148 * @param {jQuery.Event} e
149 * The event triggered.
150 * @param {jQuery} $target
151 * The targeted node as a jQuery object.
153 const handleFragmentLinkClickOrHashChange = (e, $target) => {
154 $target.parents('details').not('[open]').find('> summary').trigger('click');
158 * Binds a listener to handle fragment link clicks and URL hash changes.
160 $('body').on('formFragmentLinkClickOrHashChange.details', handleFragmentLinkClickOrHashChange);
162 // Expose constructor in the public space.
163 Drupal.CollapsibleDetails = CollapsibleDetails;
164 }(jQuery, Modernizr, Drupal));