diff -u b/core/misc/collapse.es6.js b/core/misc/collapse.es6.js --- b/core/misc/collapse.es6.js +++ b/core/misc/collapse.es6.js @@ -138,48 +138,26 @@ }; /** - * Show parent details elements or vertical tab pane of a fragment target. + * Show parent details elements of a targeted page fragment. * - * Opens a vertical tab pane or 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. + * 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 target fragment. */ - const showGroupingElementChild = (e) => { - const $item = e.type === 'click' ? $(e.currentTarget.hash) : $(`#${location.hash.substr(1)}`); - - if ($item.length) { - /** - * Open all (nested) parent details and set aria attributes on the summary - * by triggering the click event listener in details-aria.js. - */ - $item.parents('details').not('[open]').find('> summary').trigger('click'); - - /** - * Loop over all (nested) parent vertical tabs and focus them in order - * to reveal the children. - */ - $item.parents('.vertical-tabs__pane').each((index, pane) => { - $(pane).data('verticalTab').focus(); - }); - - /** - * Clicking the anchor should set focus, but event timing issues in Edge - * require a timeout. - */ - setTimeout(() => { - $item.focus() - }, 300, $item); - } + const handleFragmentInteraction = function openDetailsOnFragmentInteracion(e, $target) { + $target.parents('details').not('[open]').find('> summary').trigger('click'); }; - // Binds a listener on fragment links to handle clicks. - $(document).on('click.grouping-element-fragments', 'a[href^="#"]', showGroupingElementChild); - - // Binds a listener to handle location hash changes. - $(window).on('hashchange.grouping-element-fragments', showGroupingElementChild); + /** + * Binds a listener to handle page fragment identifier interactions. + */ + $('body').on('formFragmentInteraction.details', handleFragmentInteraction); // Expose constructor in the public space. Drupal.CollapsibleDetails = CollapsibleDetails; diff -u b/core/misc/collapse.js b/core/misc/collapse.js --- b/core/misc/collapse.js +++ b/core/misc/collapse.js @@ -77,25 +77,11 @@ } }; - var showGroupingElementChild = function showGroupingElementChild(e) { - var $item = e.type === 'click' ? $(e.currentTarget.hash) : $('#' + location.hash.substr(1)); - - if ($item.length) { - $item.parents('details').not('[open]').find('> summary').trigger('click'); - - $item.parents('.vertical-tabs__pane').each(function (index, pane) { - $(pane).data('verticalTab').focus(); - }); - - setTimeout(function () { - $item.focus(); - }, 300, $item); - } + var handleFragmentInteraction = function openDetailsOnFragmentInteracion(e, $target) { + $target.parents('details').not('[open]').find('> summary').trigger('click'); }; - $(document).on('click.grouping-element-fragments', 'a[href^="#"]', showGroupingElementChild); - - $(window).on('hashchange.grouping-element-fragments', showGroupingElementChild); + $('body').on('formFragmentInteraction.details', handleFragmentInteraction); Drupal.CollapsibleDetails = CollapsibleDetails; })(jQuery, Modernizr, Drupal); \ No newline at end of file only in patch2: unchanged: --- a/core/misc/form.es6.js +++ b/core/misc/form.es6.js @@ -12,6 +12,16 @@ * @event formUpdated */ +/** + * Triggers when a hash change or click on a page fragment link is detected. + * + * The event triggers when the fragment in the URL changes (a hash change) and + * when a link containing a fragment identifier is clicked. In case the hash + * changes due to a click this event will only be triggered once. + * + * @event formFragmentInteraction + */ + (function ($, Drupal, debounce) { /** * Retrieves the summary for the first element. @@ -245,4 +255,38 @@ }); }, }; + + /** + * Sends a fragment interaction event on a hash change or fragment link click. + * + * @param {jQuery.Event} e + * The event triggered. + * + * @fires event:formFragmentInteraction + */ + const handleFragmentInteraction = function triggerFragmentInteraction(e) { + const $target = e.type === 'click' ? (e.currentTarget.location ? $(e.currentTarget.location.hash) : $(e.currentTarget.hash)) : $(`#${location.hash.substr(1)}`); + + $('body').trigger('formFragmentInteraction', [$target]); + + /** + * Interaction with the page fragment should set focus, but event timing + * issues in multiple browsers require a timeout. + */ + setTimeout(() => { + $target.focus(); + }, 300, $target); + }; + + // Binds a listener to handle URL fragment changes. + $(window).on('hashchange.form-fragment', debounce(handleFragmentInteraction, 20)); + + /** + * Binds a listener to handle clicks on fragment links and absolute URL links + * containing a fragment, this is needed next to the hash change listener + * because clicking such links doesn't trigger a hash change when the fragment + * is already in the URL. + */ + $(document).on('click.form-fragment', 'a[href*="#"]', debounce(handleFragmentInteraction, 20)); + }(jQuery, Drupal, Drupal.debounce)); only in patch2: unchanged: --- a/core/misc/form.js +++ b/core/misc/form.js @@ -124,4 +124,18 @@ }); } }; + + var handleFragmentInteraction = function triggerFragmentInteraction(e) { + var $target = e.type === 'click' ? e.currentTarget.location ? $(e.currentTarget.location.hash) : $(e.currentTarget.hash) : $('#' + location.hash.substr(1)); + + $('body').trigger('formFragmentInteraction', [$target]); + + setTimeout(function () { + $target.focus(); + }, 300, $target); + }; + + $(window).on('hashchange.form-fragment', debounce(handleFragmentInteraction, 20)); + + $(document).on('click.form-fragment', 'a[href*="#"]', debounce(handleFragmentInteraction, 20)); })(jQuery, Drupal, Drupal.debounce); \ No newline at end of file only in patch2: unchanged: --- a/core/misc/vertical-tabs.es6.js +++ b/core/misc/vertical-tabs.es6.js @@ -14,6 +14,23 @@ (function ($, Drupal, drupalSettings) { /** + * 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 target fragment. + */ + const handleFragmentInteraction = function showVerticalTabsPanesOnFragmentInteracion(e, $target) { + $target.parents('.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 @@ -36,6 +53,11 @@ return; } + /** + * Binds a listener to handle page fragment identifier interactions. + */ + $('body').once('vertical-tabs-fragments').on('formFragmentInteraction.verticalTabs', handleFragmentInteraction); + $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () { const $this = $(this).addClass('vertical-tabs__panes'); const focusID = $this.find(':hidden.vertical-tabs__active-tab').val(); only in patch2: unchanged: --- a/core/misc/vertical-tabs.js +++ b/core/misc/vertical-tabs.js @@ -6,6 +6,12 @@ **/ (function ($, Drupal, drupalSettings) { + var handleFragmentInteraction = function showVerticalTabsPanesOnFragmentInteracion(e, $target) { + $target.parents('.vertical-tabs__pane').each(function (index, pane) { + $(pane).data('verticalTab').focus(); + }); + }; + Drupal.behaviors.verticalTabs = { attach: function attach(context) { var width = drupalSettings.widthBreakpoint || 640; @@ -15,6 +21,8 @@ return; } + $('body').once('vertical-tabs-fragments').on('formFragmentInteraction.verticalTabs', handleFragmentInteraction); + $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () { var $this = $(this).addClass('vertical-tabs__panes'); var focusID = $this.find(':hidden.vertical-tabs__active-tab').val();