Security update for Core, with self-updated composer
[yaffs-website] / web / core / misc / collapse.es6.js
1 /**
2  * @file
3  * Polyfill for HTML5 details elements.
4  */
5
6 (function ($, Modernizr, Drupal) {
7   /**
8    * The collapsible details object represents a single details element.
9    *
10    * @constructor Drupal.CollapsibleDetails
11    *
12    * @param {HTMLElement} node
13    *   The details element.
14    */
15   function CollapsibleDetails(node) {
16     this.$node = $(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);
23     }
24     // Initialize and setup the summary,
25     this.setupSummary();
26     // Initialize and setup the legend.
27     this.setupLegend();
28   }
29
30   $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{
31
32     /**
33      * Holds references to instantiated CollapsibleDetails objects.
34      *
35      * @type {Array.<Drupal.CollapsibleDetails>}
36      */
37     instances: [],
38   });
39
40   $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{
41
42     /**
43      * Initialize and setup summary events and markup.
44      *
45      * @fires event:summaryUpdated
46      *
47      * @listens event:summaryUpdated
48      */
49     setupSummary() {
50       this.$summary = $('<span class="summary"></span>');
51       this.$node
52         .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
53         .trigger('summaryUpdated');
54     },
55
56     /**
57      * Initialize and setup legend markup.
58      */
59     setupLegend() {
60       // Turn the summary into a clickable link.
61       const $legend = this.$node.find('> summary');
62
63       $('<span class="details-summary-prefix visually-hidden"></span>')
64         .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
65         .prependTo($legend)
66         .after(document.createTextNode(' '));
67
68       // .wrapInner() does not retain bound events.
69       $('<a class="details-title"></a>')
70         .attr('href', `#${this.$node.attr('id')}`)
71         .prepend($legend.contents())
72         .appendTo($legend);
73
74       $legend
75         .append(this.$summary)
76         .on('click', $.proxy(this.onLegendClick, this));
77     },
78
79     /**
80      * Handle legend clicks.
81      *
82      * @param {jQuery.Event} e
83      *   The event triggered.
84      */
85     onLegendClick(e) {
86       this.toggle();
87       e.preventDefault();
88     },
89
90     /**
91      * Update summary.
92      */
93     onSummaryUpdated() {
94       const text = $.trim(this.$node.drupalGetSummary());
95       this.$summary.html(text ? ` (${text})` : '');
96     },
97
98     /**
99      * Toggle the visibility of a details element using smooth animations.
100      */
101     toggle() {
102       const isOpen = !!this.$node.attr('open');
103       const $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
104       if (isOpen) {
105         $summaryPrefix.html(Drupal.t('Show'));
106       }
107       else {
108         $summaryPrefix.html(Drupal.t('Hide'));
109       }
110       // Delay setting the attribute to emulate chrome behavior and make
111       // details-aria.js work as expected with this polyfill.
112       setTimeout(() => {
113         this.$node.attr('open', !isOpen);
114       }, 0);
115     },
116   });
117
118   /**
119    * Polyfill HTML5 details element.
120    *
121    * @type {Drupal~behavior}
122    *
123    * @prop {Drupal~behaviorAttach} attach
124    *   Attaches behavior for the details element.
125    */
126   Drupal.behaviors.collapse = {
127     attach(context) {
128       if (Modernizr.details) {
129         return;
130       }
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]));
135         }
136       }
137     },
138   };
139
140   /**
141    * Open parent details elements of a targeted page fragment.
142    *
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.
147    *
148    * @param {jQuery.Event} e
149    *   The event triggered.
150    * @param {jQuery} $target
151    *   The targeted node as a jQuery object.
152    */
153   const handleFragmentLinkClickOrHashChange = (e, $target) => {
154     $target.parents('details').not('[open]').find('> summary').trigger('click');
155   };
156
157   /**
158    * Binds a listener to handle fragment link clicks and URL hash changes.
159    */
160   $('body').on('formFragmentLinkClickOrHashChange.details', handleFragmentLinkClickOrHashChange);
161
162   // Expose constructor in the public space.
163   Drupal.CollapsibleDetails = CollapsibleDetails;
164 }(jQuery, Modernizr, Drupal));