diff --git a/core/misc/collapse.js b/core/misc/collapse.js
index 767325e..1348d8e 100644
--- a/core/misc/collapse.js
+++ b/core/misc/collapse.js
@@ -140,6 +140,32 @@
}
};
+ /**
+ * Show parent details elements or vertical tab pane of a fragment target.
+ *
+ * @param {jQuery.Event} e
+ * The event triggered.
+ */
+ function showGroupingElementParentOfFragmentTarget(e) {
+ var $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.detailsAria');
+
+ // Loop over all (nested) parent vertical tabs and focus them.
+ $item.parents('.vertical-tabs__pane').each(function(index, pane) {
+ $(pane).data('verticalTab').focus();
+ });
+ }
+ }
+
+ // Open or focus a vertical tab or details element on a hash change or
+ // fragment link click when the target is a child element.
+ $(document).on('click.show-grouping-fragment-target', 'a[href^="#"]', showGroupingElementParentOfFragmentTarget);
+ $(window).on('hashchange.show-grouping-fragment-target', showGroupingElementParentOfFragmentTarget);
+
// Expose constructor in the public space.
Drupal.CollapsibleDetails = CollapsibleDetails;
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php
new file mode 100644
index 0000000..8dbaaa1
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php
@@ -0,0 +1,133 @@
+drupalCreateUser();
+ $this->drupalLogin($account);
+ }
+
+ /**
+ * Tests that vertical tab children become visible.
+ *
+ * Makes sure that a child element of a vertical tab that is not visible,
+ * becomes visible when the tab is clicked, a fragment link to the child is
+ * clicked or when the URI fragment pointing to that child changes.
+ */
+ public function testVerticalTabChildVisibility() {
+ $session = $this->getSession();
+
+ // Request the group vertical tabs testing page with a fragment identifier
+ // to the second element.
+ $this->drupalGet('form-test/group-vertical-tabs', ['fragment' => 'edit-element-2']);
+
+ $page = $session->getPage();
+
+ $tab_link_1 = $page->find('css', '.vertical-tabs__menu-item > a');
+
+ $child_1_selector = '#edit-element';
+ $child_1 = $page->find('css', $child_1_selector);
+
+ $child_2_selector = '#edit-element-2';
+ $child_2 = $page->find('css', $child_2_selector);
+
+ // Assert that the child in the second vertical tab becomes visible.
+ // It should be visible after initial load due to the fragment in the URI.
+ $this->assertTrue($child_2->isVisible(), 'Child 2 is visible due to a URI fragment');
+
+ // Click on a fragment link pointing to an invisible child inside an
+ // inactive vertical tab.
+ $session->executeScript("jQuery('').insertAfter('h1')[0].click()");
+
+ // Assert that the child in the first vertical tab is visible.
+ $this->assertTrue($child_1->isVisible(), 'Child 1 is visible after a fragment link click');
+
+ // Trigger a URI fragment change (hashchange) to show the second vertical
+ // tab again.
+ $session->executeScript("location.replace('$child_2_selector')");
+
+ // Assert that the child in the second vertical tab is visible again.
+ $this->assertTrue($child_2->isVisible(), 'Child 2 is visible after a fragment change');
+
+ $tab_link_1->click();
+
+ // Assert that the child in the first vertical tab is visible again after
+ // a click on the first tab.
+ $this->assertTrue($child_1->isVisible(), 'Child 1 is visible after clicking the parent tab');
+ }
+
+ /**
+ * Tests that details element children become visible.
+ *
+ * Makes sure that a child element of a details element that is not visible,
+ * becomes visible when a fragment link to the child is clicked or when the
+ * URI fragment pointing to that child changes.
+ */
+ public function testDetailsChildVisibility() {
+ $session = $this->getSession();
+
+ // Store reusable JavaScript code to remove the current URI fragment and
+ // close all details.
+ $reset_js = "location.replace('#'); jQuery('details').removeAttr('open')";
+
+ // Request the group details testing page.
+ $this->drupalGet('form-test/group-details');
+
+ $page = $session->getPage();
+
+ $session->executeScript($reset_js);
+
+ $child_selector = '#edit-element';
+ $child = $page->find('css', $child_selector);
+
+ // Assert that the child is not visible.
+ $this->assertFalse($child->isVisible(), 'Child is not visible');
+
+ // Trigger a URI fragment change (hashchange) to open all parent details
+ // elements of the child.
+ $session->executeScript("location.replace('$child_selector')");
+
+ // Assert that the child is visible again.
+ $this->assertTrue($child->isVisible(), 'Child became visible after a fragment change');
+
+ $this->createScreenshot('/tmp/drupal/wel.jpg');
+
+ $session->executeScript($reset_js);
+
+ // Click on a fragment link pointing to an invisible child inside a closed
+ // details element.
+ $session->executeScript("jQuery('').insertAfter('h1')[0].click()");
+
+ // Assert that the child is visible again.
+ $this->assertTrue($child->isVisible(), 'Child became visible after a fragment link click');
+
+ // Find the summary belonging to the closest details element.
+ $summary = $page->find('css', '#edit-meta > summary');
+
+ // Assert that both aria-expanded and aria-pressed are true.
+ $this->assertTrue($summary->getAttribute('aria-expanded'));
+ $this->assertTrue($summary->getAttribute('aria-pressed'));
+ }
+
+}