Security update for Core, with self-updated composer
[yaffs-website] / web / core / misc / collapse.es6.js
diff --git a/web/core/misc/collapse.es6.js b/web/core/misc/collapse.es6.js
new file mode 100644 (file)
index 0000000..374af0c
--- /dev/null
@@ -0,0 +1,164 @@
+/**
+ * @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);
+    // Expand details if there are errors inside, or if it contains an
+    // element that is targeted by the URI fragment identifier.
+    const anchor = location.hash && location.hash !== '#' ? `, ${location.hash}` : '';
+    if (this.$node.find(`.error${anchor}`).length) {
+      this.$node.attr('open', true);
+    }
+    // Initialize and setup the summary,
+    this.setupSummary();
+    // Initialize and setup the legend.
+    this.setupLegend();
+  }
+
+  $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{
+
+    /**
+     * Holds references to instantiated CollapsibleDetails objects.
+     *
+     * @type {Array.<Drupal.CollapsibleDetails>}
+     */
+    instances: [],
+  });
+
+  $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{
+
+    /**
+     * Initialize and setup summary events and markup.
+     *
+     * @fires event:summaryUpdated
+     *
+     * @listens event:summaryUpdated
+     */
+    setupSummary() {
+      this.$summary = $('<span class="summary"></span>');
+      this.$node
+        .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
+        .trigger('summaryUpdated');
+    },
+
+    /**
+     * Initialize and setup legend markup.
+     */
+    setupLegend() {
+      // Turn the summary into a clickable link.
+      const $legend = this.$node.find('> summary');
+
+      $('<span class="details-summary-prefix visually-hidden"></span>')
+        .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
+        .prependTo($legend)
+        .after(document.createTextNode(' '));
+
+      // .wrapInner() does not retain bound events.
+      $('<a class="details-title"></a>')
+        .attr('href', `#${this.$node.attr('id')}`)
+        .prepend($legend.contents())
+        .appendTo($legend);
+
+      $legend
+        .append(this.$summary)
+        .on('click', $.proxy(this.onLegendClick, this));
+    },
+
+    /**
+     * Handle legend clicks.
+     *
+     * @param {jQuery.Event} e
+     *   The event triggered.
+     */
+    onLegendClick(e) {
+      this.toggle();
+      e.preventDefault();
+    },
+
+    /**
+     * Update summary.
+     */
+    onSummaryUpdated() {
+      const text = $.trim(this.$node.drupalGetSummary());
+      this.$summary.html(text ? ` (${text})` : '');
+    },
+
+    /**
+     * 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;
+      }
+      const $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed');
+      if ($collapsibleDetails.length) {
+        for (let i = 0; i < $collapsibleDetails.length; i++) {
+          CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));
+        }
+      }
+    },
+  };
+
+  /**
+   * 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');
+  };
+
+  /**
+   * 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));