diff --git a/_includes/nav.html b/_includes/nav.html
index 4330a15b..c3e93175 100644
--- a/_includes/nav.html
+++ b/_includes/nav.html
@@ -1,4 +1,9 @@
-
+
{%- assign titled_pages = include.pages
| where_exp:"item", "item.title != nil" -%}
@@ -56,32 +61,64 @@
{%- endfor -%}
{%- assign pages_list = sorted_number_ordered_pages | concat: sorted_string_ordered_pages -%}
-
+
{%- for node in pages_list -%}
{%- if node.parent == nil -%}
{%- unless node.nav_exclude -%}
- -
+ {% assign nested_owned_tree_id = include.owned_tree_id | append: "_" | append: forloop.index | append: "_" | append: node.title | append: "_navitems" | replace: " ", "_" %}
+
-
{%- if node.has_children -%}
-
+
{%- endif -%}
- {{ node.title }}
+
+ {{ node.title }}
{%- if node.has_children -%}
{%- assign children_list = pages_list | where: "parent", node.title -%}
-
+
{%- for child in children_list -%}
{%- unless child.nav_exclude -%}
- -
+
-
{%- if child.has_children -%}
-
+ {% assign nested_nested_owned_tree_id = nested_owned_tree_id | append: "_" | append: forloop.index | append: "_" | append: child.title | append: "_navitems" | replace: " ", "_" %}
+
{%- endif -%}
- {{ child.title }}
+ {{ child.title }}
{%- if child.has_children -%}
{%- assign grand_children_list = pages_list | where: "parent", child.title | where: "grand_parent", node.title -%}
-
+
{%- for grand_child in grand_children_list -%}
{%- unless grand_child.nav_exclude -%}
- -
- {{ grand_child.title }}
+
-
+ {{ grand_child.title }}
{%- endunless -%}
{%- endfor -%}
diff --git a/_layouts/default.html b/_layouts/default.html
index 8cce32d7..32ff1130 100755
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -66,7 +66,7 @@ layout: table_wrappers
| where_exp:"item", "item.nav_exclude != true"
| size %}
{% if pages_top_size > 0 %}
- {% include nav.html pages=site.html_pages key=nil %}
+ {% include nav.html pages=site.html_pages key=nil expanded=true %}
{% endif %}
{% if site.just_the_docs.collections %}
{% assign collections_size = site.just_the_docs.collections | size %}
@@ -75,25 +75,49 @@ layout: table_wrappers
{% assign collection_value = collection_entry[1] %}
{% assign collection = site[collection_key] %}
{% if collection_value.nav_exclude != true %}
+ {% assign owned_tree_id = collection_key | append: "_" | append: forloop.index | append: "_navitems" | replace: " ", "_" %}
{% if collections_size > 1 or pages_top_size > 0 %}
{% if collection_value.nav_fold == true %}
-
- -
- {%- if collection.size > 0 -%}
-
- {%- endif -%}
+
+ -
+
{% assign collection_url_path = collection_key | append: "/index/" %}
-
- {% include nav.html pages=collection key=collection_key %}
+ {% assign category_comparison_url = "/" | append: collection_url_path %}
+ {%- if collection.size > 0 -%}
+
+ {%- endif -%}
+ {{ collection_value.name }}
+ {% include nav.html pages=collection key=collection_key owned_tree_id=owned_tree_id %}
{% else %}
{% assign collection_url_path = collection_key | append: "/index/" %}
-
- {% include nav.html pages=collection key=collection_key %}
+ {{ collection_value.name }}
+ {% include nav.html pages=collection key=collection_key owned_tree_id=owned_tree_id %}
{% endif %}
{% else %}
- {% include nav.html pages=collection key=collection_key %}
+ {% include nav.html pages=collection key=collection_key owned_tree_id=owned_tree_id %}
{% endif %}
{% endif %}
{% endfor %}
@@ -111,8 +135,8 @@ layout: table_wrappers
{% assign docs_version = "latest" %}
{% endif %}
+ placeholder="Search..." aria-label="Search {{ site.title }}"
+ data-docs-version="{{ docs_version }}" autocomplete="off">
diff --git a/_sass/custom/custom.scss b/_sass/custom/custom.scss
index d38f7d67..2f002720 100755
--- a/_sass/custom/custom.scss
+++ b/_sass/custom/custom.scss
@@ -54,10 +54,22 @@ code {
max-height: 100vh;
overflow-x: hidden;
min-width: 14rem;
+ padding-right: 1px;
+ padding-left: 1px;
+ padding-bottom: 1px;
+}
+
+nav#site-nav > .nav-list:nth-of-type(1) {
+ padding-top: 2px;
+}
+
+.nav-list {
+ margin-top: 1px;
}
.nav-category {
text-align: start;
+ display: block;
}
.main-content {
diff --git a/assets/js/nav-scroll.js b/assets/js/nav-scroll.js
index 6ffc181f..df74ba80 100644
--- a/assets/js/nav-scroll.js
+++ b/assets/js/nav-scroll.js
@@ -1,27 +1,212 @@
-let siteNav = document.querySelector('.site-nav');
-const key = 'scroll';
-
document.addEventListener('DOMContentLoaded', () => {
- const scroll = JSON.parse(sessionStorage.getItem(key));
+ const navParent = document.getElementById('site-nav');
+ if (!navParent) {
+ return;
+ }
- const currentDate = new Date();
+ // The business logic on navigation items in layouts/default.html and _includes/nav.html
+ // is much too complex to reliably make this determination correctly without overly complicating
+ // something that is already overly complicated. So, this will make the corrections at runtime.
+ navParent.querySelectorAll('ul').forEach((element) => {
+ const hasNestedList = element.querySelector('ul');
+ if (hasNestedList) {
+ element.setAttribute('role', 'tree');
+ } else {
+ element.setAttribute('role', 'group');
+ }
+ });
- if (scroll !== null && currentDate.getTime() < scroll.expiry) {
- siteNav.scrollTop = parseInt(scroll.value);
+ /**
+ * This function configures the aria-expanded attributes on the navigation items.
+ * Liquid's limited features make this challenging to do cleanly at build time
+ * requiring searching forward through the tree to determine if something in the
+ * depth of the currently rendering tree node has a descendant that is active.
+ */
+ function configureAriaAttributes() {
+
+ const setSubTreeAriaExpanded = (listItem, isExpanded) => {
+ const listItemAnchors = listItem.querySelectorAll('a');
+ listItemAnchors.forEach((element) => {
+ const parentLi = element.parentElement;
+ if (parentLi) {
+ const childUl = Array.from(parentLi.children).filter(element => element.tagName === 'UL')[0];
+ if (childUl) {
+ // If there is a child UL of the anchor element's parent LI then set the aria-expanded
+ // Otherwise delete the attribute, because there is no child UL to expand / collapse.
+ element.setAttribute('aria-expanded', isExpanded);
+ } else {
+ element.removeAttribute('aria-expanded');
+ }
+ }
+ });
+ };
+
+ const topLevelUls = Array.from(navParent.children).filter(element => element.tagName === 'UL');
+ topLevelUls.forEach((element) => {
+ const listItems = Array.from(element.children).filter(element => element.tagName === 'LI');
+ listItems.forEach((element) => {
+ const active = element.querySelector('a.active');
+ if (active) {
+ setSubTreeAriaExpanded(element, true);
+ } else {
+ setSubTreeAriaExpanded(element, false);
+ }
+ });
+ });
}
- else {
- sessionStorage.removeItem(key);
+
+ // Give keyboard focus to the active navigation item, and ensure
+ // it is scrolled into view.
+ const activeNavItem = navParent.querySelector('a.active');
+ if (activeNavItem) {
+
+ configureAriaAttributes();
+
+ // The active navigation item needs to have the tabindex="0" wheras all of the other items
+ // are excluded from the TAB order according.
+ activeNavItem.setAttribute('tabindex', '0');
+ navParent.querySelectorAll('a:not(.active)').forEach((element) => {
+ element.setAttribute('tabindex', '-1');
+ });
+
+ // If the active item is not in view, then scroll it into view.
+ const VERSION_WRAPPER_HEIGHT = 80;
+ const parentRect = navParent.getBoundingClientRect();
+ const activeRect = activeNavItem.getBoundingClientRect();
+
+ if (activeRect.top < (parentRect.top + VERSION_WRAPPER_HEIGHT)) {
+ const distanceToScroll = activeRect.top - parentRect.top - VERSION_WRAPPER_HEIGHT;
+ navParent.scrollTo(0, distanceToScroll);
+ } else if (activeRect.bottom > window.visualViewport.height) {
+ const distanceToScroll = activeRect.bottom - window.visualViewport.height + VERSION_WRAPPER_HEIGHT;
+ navParent.scrollTo(0, distanceToScroll);
+ }
}
+
+ navParent.addEventListener('keydown', (event) => {
+
+ const handleSpaceKey = () => {
+
+ //
+ // Space key functionality
+ // For reference: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/examples/treeview-navigation/
+ //
+
+ const expandCollapse = (element) => {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ event.stopPropagation();
+ element.click();
+ };
+
+ if (event.target.classList.contains('nav-list-expander')) {
+ // If the event target is the expand/collapse arrow then toggle the state of the sub tree.
+ expandCollapse(event.target);
+ } else if (event.target.tagName === 'A') {
+ // If the event target is a link then follow the link.
+ event.target.click();
+ }
+ };
+
+ const handleArrowKey = () => {
+
+ //
+ // Arrow key navigation implementation.
+ // For reference: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/examples/treeview-navigation/
+ //
+
+ const currentlyFocusedNavItem = navParent.querySelector('a:focus');
+ if (!currentlyFocusedNavItem) {
+ // If no item is focused then do nothing.
+ return;
+ }
+
+ // Preventing the default action prevents jankiness in the default scrolling
+ // of the navigation panel, and is left to the browser to handle it when
+ // .focus() is invoked instead.
+ event.preventDefault();
+
+ // Get all of the navigation items that are visible, and that are NOT the expand/collapse arrow.
+ const allNavItems = Array.from(
+ navParent.querySelectorAll('a:not(.nav-list-expander)')
+ ).filter(element => element.getBoundingClientRect().height > 0);
+
+ const currentlyFocusedNavItemIndex = allNavItems.indexOf(currentlyFocusedNavItem);
+
+ const toggleExpandCollapseState = () => {
+ const parentLi = currentlyFocusedNavItem.parentElement;
+ if (parentLi) {
+ const expander = Array.from(parentLi.children).find(element => element.classList.contains('nav-list-expander'));
+ if (expander) {
+ expander.click();
+ Array.from(parentLi.children).forEach((element) => {
+ const ariaExpanded = element.getAttribute('aria-expanded');
+ if (ariaExpanded === 'true') {
+ element.setAttribute('aria-expanded', 'false');
+ } else {
+ element.setAttribute('aria-expanded', 'true');
+ }
+ });
+ }
+ }
+ };
+
+ if (event.key === 'ArrowUp') {
+ if (currentlyFocusedNavItemIndex > 0) {
+ allNavItems[currentlyFocusedNavItemIndex - 1].focus();
+ }
+ } else if (event.key === 'ArrowDown') {
+ if (currentlyFocusedNavItemIndex < allNavItems.length - 1) {
+ allNavItems[currentlyFocusedNavItemIndex + 1].focus();
+ }
+ } else if (event.key === 'ArrowLeft') {
+ if (currentlyFocusedNavItem.getAttribute('aria-expanded') === 'true') {
+ toggleExpandCollapseState();
+ } else {
+ const parentLi = currentlyFocusedNavItem.parentElement;
+ if (parentLi) {
+ if (currentlyFocusedNavItemIndex > 0) {
+ // The parent of the target is a - which is a child of a