content/static: readme outline reflects heading structure from markdown

The structure of the headings in the readme is now reflected
in the readme outline up to three levels of depth.

For golang/go#43325

Change-Id: Iee639fea299e93bfd5223dac3a50b663ce0ca89b
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/310131
Trust: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/content/static/html/helpers/_unit_outline.tmpl b/content/static/html/helpers/_unit_outline.tmpl
index 46395ad..52c86db 100644
--- a/content/static/html/helpers/_unit_outline.tmpl
+++ b/content/static/html/helpers/_unit_outline.tmpl
@@ -18,16 +18,7 @@
               aria-level="1" aria-owns="readme-outline" tabindex="0">
             README
           </a>
-          <ul role="group" id="readme-outline">
-            {{range .ReadmeOutline}}
-              <li role="none">
-                <a href="#{{.ID}}" role="treeitem" aria-selected="false" aria-level="2"
-                    tabindex="-1">
-                  {{.Text}}
-                </a>
-              </li>
-            {{end}}
-          </ul>
+          {{template "readme_outline" .ReadmeOutline}}
         </li>
       {{end}}
       {{if .IsPackage}}
@@ -58,3 +49,43 @@
     </ul>
   </div>
 {{end}}
+
+{{define "readme_outline"}}
+  <ul role="group" id="readme-outline">
+    {{range .}}
+      <li role="none">
+        <a href="#{{.ID}}" role="treeitem" aria-level="2" aria-owns="nav-group-readme"
+            tabindex="-1" {{if gt (len .Children) 0}}aria-expanded="false"{{end}}>
+          {{.Text}}
+        </a>
+        <ul role="group" id="nav-group-readme">
+          {{range .Children}}
+            {{$tname := .Text}}
+            <li role="none">
+              {{if .Children}}
+                {{$navgroupid := (printf "nav.group.%s" $tname)}}
+                <a href="#{{.ID}}" role="treeitem" aria-expanded="false" aria-level="3"
+                    tabindex="-1" data-aria-owns="{{$navgroupid}}">
+                  {{$tname}}
+                </a>
+                <ul role="group" >
+                  {{range .Children}}
+                    <li role="none">
+                      <a href="#{{.ID}}" role="treeitem" aria-level="4" tabindex="-1">
+                        {{.Text}}
+                      </a>
+                    </li>
+                  {{end}} {{/* range .Children */}}
+                </ul>
+              {{else}}
+                <a href="#{{.ID}}" role="treeitem" aria-level="3" tabindex="-1">
+                  {{$tname}}
+                </a>
+              {{end}} {{/* if .Children */}}
+            </li>
+          {{end}} {{/* range .Children */}}
+        </ul>
+      </li>
+    {{end}}
+  </ul>
+{{end}}
\ No newline at end of file
diff --git a/content/static/js/sidenav.js b/content/static/js/sidenav.js
index e8f71d7..2f327f2 100644
--- a/content/static/js/sidenav.js
+++ b/content/static/js/sidenav.js
@@ -3,5 +3,5 @@
  * Copyright 2019-2021 The Go Authors. All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the LICENSE file.
- */const Key={UP:"ArrowUp",DOWN:"ArrowDown",LEFT:"ArrowLeft",RIGHT:"ArrowRight",ENTER:"Enter",ASTERISK:"*",SPACE:" ",END:"End",HOME:"Home",Y:"y",FORWARD_SLASH:"/",QUESTION_MARK:"?"};class DocNavTreeController{constructor(e){this.el=e;this.focusedIndex=0;this.visibleItems=[];this.searchString="";this.lastKeyDownTimeStamp=-Infinity;this.el=e,this.selectedEl=null,this.focusedIndex=0,this.visibleItems=[],this.searchString="",this.lastKeyDownTimeStamp=-Infinity,this.addEventListeners(),this.updateVisibleItems(),this.initialize()}initialize(){this.el.querySelectorAll("[role='treeitem']").forEach(e=>{e.addEventListener("click",t=>this.handleItemClick(t))}),this.el.querySelectorAll("[data-aria-owns]").forEach(e=>{e.setAttribute("aria-owns",e.getAttribute("data-aria-owns")??"")})}addEventListeners(){this.el.addEventListener("keydown",e=>this.handleKeyDown(e))}setFocusedIndex(e){if(e===this.focusedIndex||e===-1)return;let t=this.visibleItems[this.focusedIndex];t.setAttribute("tabindex","-1"),t=this.visibleItems[e],t.setAttribute("tabindex","0"),t.focus(),this.focusedIndex=e}setSelectedId(e){if(this.selectedEl&&(this.selectedEl.removeAttribute("aria-selected"),this.selectedEl=null),e?this.selectedEl=this.el.querySelector(`[role='treeitem'][href='#${e}']`):this.visibleItems.length>0&&(this.selectedEl=this.visibleItems[0]),!this.selectedEl)return;const t=this.el.querySelector('[aria-level="1"][aria-expanded="true"]');t&&!t.contains(this.selectedEl)&&this.collapseItem(t),this.selectedEl.getAttribute("aria-level")==="1"&&this.selectedEl.setAttribute("aria-expanded","true"),this.selectedEl.setAttribute("aria-selected","true"),this.expandAllParents(this.selectedEl),this.scrollElementIntoView(this.selectedEl)}expandAllSiblingItems(e){const t=e.getAttribute("aria-level");this.el.querySelectorAll(`[aria-level='${t}'][aria-expanded='false']`).forEach(i=>{i.setAttribute("aria-expanded","true")}),this.updateVisibleItems(),this.focusedIndex=this.visibleItems.indexOf(e)}expandAllParents(e){if(!this.visibleItems.includes(e)){let t=this.owningItem(e);for(;t;)this.expandItem(t),t=this.owningItem(t)}}scrollElementIntoView(e){const t=55,i=document.documentElement.clientHeight,s=e.getBoundingClientRect(),r=(i-t)/2;if(s.top<t)this.el.scrollTop-=t-s.top-s.height+r;else if(s.bottom>i)this.el.scrollTop=s.bottom-i+r;else return}handleItemClick(e){const t=e.target;this.setFocusedIndex(this.visibleItems.indexOf(t)),t.hasAttribute("aria-expanded")&&this.toggleItemExpandedState(t),this.closeInactiveDocNavGroups(t)}closeInactiveDocNavGroups(e){if(e.hasAttribute("aria-expanded")){const t=e.getAttribute("aria-level");document.querySelectorAll(`[aria-level="${t}"]`).forEach(i=>{i.getAttribute("aria-expanded")==="true"&&i!==e&&i.setAttribute("aria-expanded","false")}),this.updateVisibleItems(),this.focusedIndex=this.visibleItems.indexOf(e)}}handleKeyDown(e){const t=e.target;switch(e.key){case Key.ASTERISK:t&&this.expandAllSiblingItems(t),e.stopPropagation(),e.preventDefault();return;case Key.FORWARD_SLASH:case Key.QUESTION_MARK:return;case Key.DOWN:this.focusNextItem();break;case Key.UP:this.focusPreviousItem();break;case Key.LEFT:t?.getAttribute("aria-expanded")==="true"?this.collapseItem(t):this.focusParentItem(t);break;case Key.RIGHT:{switch(t?.getAttribute("aria-expanded")){case"false":this.expandItem(t);break;case"true":this.focusNextItem();break}break}case Key.HOME:this.setFocusedIndex(0);break;case Key.END:this.setFocusedIndex(this.visibleItems.length-1);break;case Key.ENTER:if(t?.tagName==="A")return;case Key.SPACE:t?.click();break;default:this.handleSearch(e);return}e.preventDefault(),e.stopPropagation()}handleSearch(e){if(e.metaKey||e.altKey||e.ctrlKey||e.isComposing||e.key.length>1||!e.key.match(/\S/))return;const t=1e3;e.timeStamp-this.lastKeyDownTimeStamp>t&&(this.searchString=""),this.lastKeyDownTimeStamp=e.timeStamp,this.searchString+=e.key.toLocaleLowerCase();const i=this.visibleItems[this.focusedIndex].textContent?.toLocaleLowerCase();(this.searchString.length===1||!i?.startsWith(this.searchString))&&this.focusNextItemWithPrefix(this.searchString),e.stopPropagation(),e.preventDefault()}focusNextItemWithPrefix(e){let t=this.focusedIndex+1;for(t>this.visibleItems.length-1&&(t=0);t!==this.focusedIndex;){if(this.visibleItems[t].textContent?.toLocaleLowerCase().startsWith(e)){this.setFocusedIndex(t);return}t>=this.visibleItems.length-1?t=0:t++}}toggleItemExpandedState(e){e.getAttribute("aria-expanded")==="true"?this.collapseItem(e):this.expandItem(e)}focusPreviousItem(){this.setFocusedIndex(Math.max(0,this.focusedIndex-1))}focusNextItem(){this.setFocusedIndex(Math.min(this.visibleItems.length-1,this.focusedIndex+1))}collapseItem(e){e.setAttribute("aria-expanded","false"),this.updateVisibleItems()}expandItem(e){e.setAttribute("aria-expanded","true"),this.updateVisibleItems()}focusParentItem(e){const t=this.owningItem(e);t&&this.setFocusedIndex(this.visibleItems.indexOf(t))}owningItem(e){const t=e?.closest("[role='group']");return t?t.parentElement?.querySelector(`[aria-owns='${t.id}']`):null}updateVisibleItems(){const e=Array.from(this.el.querySelectorAll("[role='treeitem']")),t=Array.from(this.el.querySelectorAll("[aria-expanded='false'] + [role='group'] [role='treeitem']"));this.visibleItems=e.filter(i=>!t.includes(i))}}class DocPageController{constructor(e,t,i){this.contentEl=i;if(!e||!i){console.warn("Unable to find all elements needed for navigation");return}this.navController=new DocNavTreeController(e),t&&(this.mobileNavController=new MobileNavController(t)),window.addEventListener("hashchange",()=>this.handleHashChange()),this.updateSelectedIdFromWindowHash()}handleHashChange(){this.updateSelectedIdFromWindowHash()}updateSelectedIdFromWindowHash(){const e=this.targetIdFromLocationHash();if(this.navController?.setSelectedId(e),this.mobileNavController&&this.mobileNavController.setSelectedId(e),e!==""){const t=this.contentEl?.querySelector(`[id='${e}']`);t&&t.focus()}}targetIdFromLocationHash(){return window.location.hash&&window.location.hash.substr(1)}}class MobileNavController{constructor(e){this.el=e;this.selectEl=e.querySelector("select"),this.labelTextEl=e.querySelector(".js-mobileNavSelectText"),this.selectEl?.addEventListener("change",i=>this.handleSelectChange(i));const t="-57px";this.intersectionObserver=new IntersectionObserver(i=>this.intersectionObserverCallback(i),{rootMargin:`${t} 0px 0px 0px`,threshold:1}),this.intersectionObserver.observe(this.el)}setSelectedId(e){!this.selectEl||(this.selectEl.value=e,this.updateLabelText())}updateLabelText(){if(!this.labelTextEl||!this.selectEl)return;const e=this.selectEl?.selectedIndex;if(e===-1){this.labelTextEl.textContent="";return}this.labelTextEl.textContent=this.selectEl.options[e].textContent}handleSelectChange(e){window.location.hash=`#${e.target.value}`,this.updateLabelText()}intersectionObserverCallback(e){const t="DocNavMobile--withShadow";e.forEach(i=>{const s=i.intersectionRatio===1;i.target.classList.toggle(t,!s)})}}new DocPageController(document.querySelector(".js-tree"),document.querySelector(".js-mobileNav"),document.querySelector(".js-unitDetailsContent"));
+ */const Key={UP:"ArrowUp",DOWN:"ArrowDown",LEFT:"ArrowLeft",RIGHT:"ArrowRight",ENTER:"Enter",ASTERISK:"*",SPACE:" ",END:"End",HOME:"Home",Y:"y",FORWARD_SLASH:"/",QUESTION_MARK:"?"};class DocNavTreeController{constructor(e){this.el=e;this.focusedIndex=0;this.visibleItems=[];this.searchString="";this.lastKeyDownTimeStamp=-Infinity;this.el=e,this.selectedEl=null,this.focusedIndex=0,this.visibleItems=[],this.searchString="",this.lastKeyDownTimeStamp=-Infinity,this.addEventListeners(),this.updateVisibleItems(),this.initialize()}initialize(){this.el.querySelectorAll("[role='treeitem']").forEach(e=>{e.addEventListener("click",t=>this.handleItemClick(t))}),this.el.querySelectorAll("[data-aria-owns]").forEach(e=>{e.setAttribute("aria-owns",e.getAttribute("data-aria-owns")??"")})}addEventListeners(){this.el.addEventListener("keydown",e=>this.handleKeyDown(e))}setFocusedIndex(e){if(e===this.focusedIndex||e===-1)return;let t=this.visibleItems[this.focusedIndex];t.setAttribute("tabindex","-1"),t=this.visibleItems[e],t.setAttribute("tabindex","0"),t.focus(),this.focusedIndex=e}setSelectedId(e){if(this.selectedEl&&(this.selectedEl.removeAttribute("aria-selected"),this.selectedEl=null),e?this.selectedEl=this.el.querySelector(`[role='treeitem'][href='#${e}']`):this.visibleItems.length>0&&(this.selectedEl=this.visibleItems[0]),!this.selectedEl)return;const t=this.el.querySelector('[aria-level="1"][aria-expanded="true"]');t&&!t.parentElement?.contains(this.selectedEl)&&this.collapseItem(t),this.selectedEl.getAttribute("aria-level")==="1"&&this.selectedEl.setAttribute("aria-expanded","true"),this.selectedEl.setAttribute("aria-selected","true"),this.expandAllParents(this.selectedEl),this.scrollElementIntoView(this.selectedEl)}expandAllSiblingItems(e){const t=e.getAttribute("aria-level");this.el.querySelectorAll(`[aria-level='${t}'][aria-expanded='false']`).forEach(i=>{i.setAttribute("aria-expanded","true")}),this.updateVisibleItems(),this.focusedIndex=this.visibleItems.indexOf(e)}expandAllParents(e){if(!this.visibleItems.includes(e)){let t=this.owningItem(e);for(;t;)this.expandItem(t),t=this.owningItem(t)}}scrollElementIntoView(e){const t=55,i=document.documentElement.clientHeight,s=e.getBoundingClientRect(),r=(i-t)/2;if(s.top<t)this.el.scrollTop-=t-s.top-s.height+r;else if(s.bottom>i)this.el.scrollTop=s.bottom-i+r;else return}handleItemClick(e){const t=e.target;this.setFocusedIndex(this.visibleItems.indexOf(t)),t.hasAttribute("aria-expanded")&&this.toggleItemExpandedState(t),this.closeInactiveDocNavGroups(t)}closeInactiveDocNavGroups(e){if(e.hasAttribute("aria-expanded")){const t=e.getAttribute("aria-level");document.querySelectorAll(`[aria-level="${t}"]`).forEach(i=>{i.getAttribute("aria-expanded")==="true"&&i!==e&&i.setAttribute("aria-expanded","false")}),this.updateVisibleItems(),this.focusedIndex=this.visibleItems.indexOf(e)}}handleKeyDown(e){const t=e.target;switch(e.key){case Key.ASTERISK:t&&this.expandAllSiblingItems(t),e.stopPropagation(),e.preventDefault();return;case Key.FORWARD_SLASH:case Key.QUESTION_MARK:return;case Key.DOWN:this.focusNextItem();break;case Key.UP:this.focusPreviousItem();break;case Key.LEFT:t?.getAttribute("aria-expanded")==="true"?this.collapseItem(t):this.focusParentItem(t);break;case Key.RIGHT:{switch(t?.getAttribute("aria-expanded")){case"false":this.expandItem(t);break;case"true":this.focusNextItem();break}break}case Key.HOME:this.setFocusedIndex(0);break;case Key.END:this.setFocusedIndex(this.visibleItems.length-1);break;case Key.ENTER:if(t?.tagName==="A")return;case Key.SPACE:t?.click();break;default:this.handleSearch(e);return}e.preventDefault(),e.stopPropagation()}handleSearch(e){if(e.metaKey||e.altKey||e.ctrlKey||e.isComposing||e.key.length>1||!e.key.match(/\S/))return;const t=1e3;e.timeStamp-this.lastKeyDownTimeStamp>t&&(this.searchString=""),this.lastKeyDownTimeStamp=e.timeStamp,this.searchString+=e.key.toLocaleLowerCase();const i=this.visibleItems[this.focusedIndex].textContent?.toLocaleLowerCase();(this.searchString.length===1||!i?.startsWith(this.searchString))&&this.focusNextItemWithPrefix(this.searchString),e.stopPropagation(),e.preventDefault()}focusNextItemWithPrefix(e){let t=this.focusedIndex+1;for(t>this.visibleItems.length-1&&(t=0);t!==this.focusedIndex;){if(this.visibleItems[t].textContent?.toLocaleLowerCase().startsWith(e)){this.setFocusedIndex(t);return}t>=this.visibleItems.length-1?t=0:t++}}toggleItemExpandedState(e){e.getAttribute("aria-expanded")==="true"?this.collapseItem(e):this.expandItem(e)}focusPreviousItem(){this.setFocusedIndex(Math.max(0,this.focusedIndex-1))}focusNextItem(){this.setFocusedIndex(Math.min(this.visibleItems.length-1,this.focusedIndex+1))}collapseItem(e){e.setAttribute("aria-expanded","false"),this.updateVisibleItems()}expandItem(e){e.setAttribute("aria-expanded","true"),this.updateVisibleItems()}focusParentItem(e){const t=this.owningItem(e);t&&this.setFocusedIndex(this.visibleItems.indexOf(t))}owningItem(e){const t=e?.closest("[role='group']");return t?t.parentElement?.querySelector(`[aria-owns='${t.id}']`):null}updateVisibleItems(){const e=Array.from(this.el.querySelectorAll("[role='treeitem']")),t=Array.from(this.el.querySelectorAll("[aria-expanded='false'] + [role='group'] [role='treeitem']"));this.visibleItems=e.filter(i=>!t.includes(i))}}class DocPageController{constructor(e,t,i){this.contentEl=i;if(!e||!i){console.warn("Unable to find all elements needed for navigation");return}this.navController=new DocNavTreeController(e),t&&(this.mobileNavController=new MobileNavController(t)),window.addEventListener("hashchange",()=>this.handleHashChange()),this.updateSelectedIdFromWindowHash()}handleHashChange(){this.updateSelectedIdFromWindowHash()}updateSelectedIdFromWindowHash(){const e=this.targetIdFromLocationHash();if(this.navController?.setSelectedId(e),this.mobileNavController&&this.mobileNavController.setSelectedId(e),e!==""){const t=this.contentEl?.querySelector(`[id='${e}']`);t&&t.focus()}}targetIdFromLocationHash(){return window.location.hash&&window.location.hash.substr(1)}}class MobileNavController{constructor(e){this.el=e;this.selectEl=e.querySelector("select"),this.labelTextEl=e.querySelector(".js-mobileNavSelectText"),this.selectEl?.addEventListener("change",i=>this.handleSelectChange(i));const t="-57px";this.intersectionObserver=new IntersectionObserver(i=>this.intersectionObserverCallback(i),{rootMargin:`${t} 0px 0px 0px`,threshold:1}),this.intersectionObserver.observe(this.el)}setSelectedId(e){!this.selectEl||(this.selectEl.value=e,this.updateLabelText())}updateLabelText(){if(!this.labelTextEl||!this.selectEl)return;const e=this.selectEl?.selectedIndex;if(e===-1){this.labelTextEl.textContent="";return}this.labelTextEl.textContent=this.selectEl.options[e].textContent}handleSelectChange(e){window.location.hash=`#${e.target.value}`,this.updateLabelText()}intersectionObserverCallback(e){const t="DocNavMobile--withShadow";e.forEach(i=>{const s=i.intersectionRatio===1;i.target.classList.toggle(t,!s)})}}new DocPageController(document.querySelector(".js-tree"),document.querySelector(".js-mobileNav"),document.querySelector(".js-unitDetailsContent"));
 //# sourceMappingURL=sidenav.js.map
diff --git a/content/static/js/sidenav.js.map b/content/static/js/sidenav.js.map
index 7dfb1bd..6881fab 100644
--- a/content/static/js/sidenav.js.map
+++ b/content/static/js/sidenav.js.map
@@ -1,7 +1,7 @@
 {
   "version": 3,
   "sources": ["sidenav.ts"],
-  "sourcesContent": ["/*!\n * @license\n * Copyright 2019-2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/**\n * Possible KeyboardEvent key values.\n * @private @enum {string}\n */\nconst Key = {\n  UP: 'ArrowUp',\n  DOWN: 'ArrowDown',\n  LEFT: 'ArrowLeft',\n  RIGHT: 'ArrowRight',\n  ENTER: 'Enter',\n  ASTERISK: '*',\n  SPACE: ' ',\n  END: 'End',\n  HOME: 'Home',\n\n  // Global keyboard shortcuts.\n  // TODO(golang.org/issue/40246): consolidate keyboard shortcut handling to avoid\n  // this duplication.\n  Y: 'y',\n  FORWARD_SLASH: '/',\n  QUESTION_MARK: '?',\n};\n\n/**\n * The navigation tree component of the documentation page.\n */\nclass DocNavTreeController {\n  /**\n   * The currently selected element.\n   */\n  private selectedEl: HTMLElement | null;\n  /**\n   * The index of the currently focused item. Used when navigating the tree\n   * using the keyboard.\n   */\n  private focusedIndex = 0;\n  /**\n   * The elements currently visible (not within a collapsed node of the tree).\n   */\n  private visibleItems: HTMLElement[] = [];\n  /**\n   * The current search string.\n   */\n  private searchString = '';\n  /**\n   * The timestamp of the last keydown event. Used to track whether to use the\n   * current search string.\n   */\n  private lastKeyDownTimeStamp = -Infinity;\n\n  /**\n   * Instantiates a navigation tree.\n   */\n  constructor(private el: Element) {\n    this.el = el;\n    this.selectedEl = null;\n    this.focusedIndex = 0;\n    this.visibleItems = [];\n    this.searchString = '';\n    this.lastKeyDownTimeStamp = -Infinity;\n    this.addEventListeners();\n    this.updateVisibleItems();\n    this.initialize();\n  }\n\n  /**\n   * Initializes the tree. Should be called only once.\n   */\n  private initialize() {\n    this.el.querySelectorAll(`[role='treeitem']`).forEach(el => {\n      el.addEventListener('click', e => this.handleItemClick(e as MouseEvent));\n    });\n\n    // TODO: remove once safehtml supports aria-owns with dynamic values.\n    this.el.querySelectorAll('[data-aria-owns]').forEach(el => {\n      el.setAttribute('aria-owns', el.getAttribute('data-aria-owns') ?? '');\n    });\n  }\n\n  private addEventListeners() {\n    this.el.addEventListener('keydown', e => this.handleKeyDown(e as KeyboardEvent));\n  }\n\n  /**\n   * Sets the visible item with the given index with the proper tabindex and\n   * focuses it.\n   */\n  setFocusedIndex(index: number) {\n    if (index === this.focusedIndex || index === -1) {\n      return;\n    }\n\n    let itemEl = this.visibleItems[this.focusedIndex];\n    itemEl.setAttribute('tabindex', '-1');\n\n    itemEl = this.visibleItems[index];\n    itemEl.setAttribute('tabindex', '0');\n    itemEl.focus();\n\n    this.focusedIndex = index;\n  }\n\n  /**\n   * Marks the navigation node with the given ID as selected. If no ID is\n   * provided, the first visible item in the tree is used.\n   */\n  setSelectedId(opt_id: string) {\n    if (this.selectedEl) {\n      this.selectedEl.removeAttribute('aria-selected');\n      this.selectedEl = null;\n    }\n    if (opt_id) {\n      this.selectedEl = this.el.querySelector(`[role='treeitem'][href='#${opt_id}']`);\n    } else if (this.visibleItems.length > 0) {\n      this.selectedEl = this.visibleItems[0];\n    }\n\n    if (!this.selectedEl) {\n      return;\n    }\n\n    // Close inactive top level item if selected id is not in its tree.\n    const topLevelExpanded = this.el.querySelector<HTMLElement>(\n      '[aria-level=\"1\"][aria-expanded=\"true\"]'\n    );\n    if (topLevelExpanded && !topLevelExpanded.contains(this.selectedEl)) {\n      this.collapseItem(topLevelExpanded);\n    }\n\n    if (this.selectedEl.getAttribute('aria-level') === '1') {\n      this.selectedEl.setAttribute('aria-expanded', 'true');\n    }\n    this.selectedEl.setAttribute('aria-selected', 'true');\n    this.expandAllParents(this.selectedEl);\n    this.scrollElementIntoView(this.selectedEl);\n  }\n\n  /**\n   * Expands all sibling items of the given element.\n   */\n  private expandAllSiblingItems(el: HTMLElement) {\n    const level = el.getAttribute('aria-level');\n    this.el.querySelectorAll(`[aria-level='${level}'][aria-expanded='false']`).forEach(el => {\n      el.setAttribute('aria-expanded', 'true');\n    });\n    this.updateVisibleItems();\n    this.focusedIndex = this.visibleItems.indexOf(el);\n  }\n\n  /**\n   * Expands all parent items of the given element.\n   */\n  private expandAllParents(el: HTMLElement) {\n    if (!this.visibleItems.includes(el)) {\n      let owningItemEl = this.owningItem(el);\n      while (owningItemEl) {\n        this.expandItem(owningItemEl);\n        owningItemEl = this.owningItem(owningItemEl);\n      }\n    }\n  }\n\n  /**\n   * Scrolls the given element into view, aligning the element in the center\n   * of the viewport. If the element is already in view, no scrolling occurs.\n   */\n  private scrollElementIntoView(el: HTMLElement) {\n    const STICKY_HEADER_HEIGHT_PX = 55;\n    const viewportHeightPx = document.documentElement.clientHeight;\n    const elRect = el.getBoundingClientRect();\n    const verticalCenterPointPx = (viewportHeightPx - STICKY_HEADER_HEIGHT_PX) / 2;\n    if (elRect.top < STICKY_HEADER_HEIGHT_PX) {\n      // Element is occluded at top of view by header or by being offscreen.\n      this.el.scrollTop -=\n        STICKY_HEADER_HEIGHT_PX - elRect.top - elRect.height + verticalCenterPointPx;\n    } else if (elRect.bottom > viewportHeightPx) {\n      // Element is below viewport.\n      this.el.scrollTop = elRect.bottom - viewportHeightPx + verticalCenterPointPx;\n    } else {\n      return;\n    }\n  }\n\n  /**\n   * Handles when a tree item is clicked.\n   */\n  private handleItemClick(e: MouseEvent) {\n    const el = e.target as HTMLSelectElement;\n    this.setFocusedIndex(this.visibleItems.indexOf(el));\n    if (el.hasAttribute('aria-expanded')) {\n      this.toggleItemExpandedState(el);\n    }\n    this.closeInactiveDocNavGroups(el);\n  }\n\n  /**\n   * Closes inactive top level nav groups when a new tree item clicked.\n   */\n  private closeInactiveDocNavGroups(el: HTMLElement) {\n    if (el.hasAttribute('aria-expanded')) {\n      const level = el.getAttribute('aria-level');\n      document.querySelectorAll(`[aria-level=\"${level}\"]`).forEach(nav => {\n        if (nav.getAttribute('aria-expanded') === 'true' && nav !== el) {\n          nav.setAttribute('aria-expanded', 'false');\n        }\n      });\n      this.updateVisibleItems();\n      this.focusedIndex = this.visibleItems.indexOf(el);\n    }\n  }\n\n  /**\n   * Handles when a key is pressed when the component is in focus.\n   */\n  handleKeyDown(e: KeyboardEvent) {\n    const targetEl = e.target as HTMLElement | null;\n\n    switch (e.key) {\n      case Key.ASTERISK:\n        if (targetEl) {\n          this.expandAllSiblingItems(targetEl);\n        }\n        e.stopPropagation();\n        e.preventDefault();\n        return;\n\n      // Global keyboard shortcuts.\n      // TODO(golang.org/issue/40246): consolidate keyboard shortcut handling\n      // to avoid this duplication.\n      case Key.FORWARD_SLASH:\n      case Key.QUESTION_MARK:\n        return;\n\n      case Key.DOWN:\n        this.focusNextItem();\n        break;\n\n      case Key.UP:\n        this.focusPreviousItem();\n        break;\n\n      case Key.LEFT:\n        if (targetEl?.getAttribute('aria-expanded') === 'true') {\n          this.collapseItem(targetEl);\n        } else {\n          this.focusParentItem(targetEl);\n        }\n        break;\n\n      case Key.RIGHT: {\n        switch (targetEl?.getAttribute('aria-expanded')) {\n          case 'false':\n            this.expandItem(targetEl);\n            break;\n          case 'true':\n            // Select the first child.\n            this.focusNextItem();\n            break;\n        }\n        break;\n      }\n\n      case Key.HOME:\n        this.setFocusedIndex(0);\n        break;\n\n      case Key.END:\n        this.setFocusedIndex(this.visibleItems.length - 1);\n        break;\n\n      case Key.ENTER:\n        if (targetEl?.tagName === 'A') {\n          // Enter triggers desired behavior by itself.\n          return;\n        }\n      // Fall through for non-anchor items to be handled the same as when\n      // the space key is pressed.\n      // eslint-disable-next-line no-fallthrough\n      case Key.SPACE:\n        targetEl?.click();\n        break;\n\n      default:\n        // Could be a typeahead search.\n        this.handleSearch(e);\n        return;\n    }\n    e.preventDefault();\n    e.stopPropagation();\n  }\n\n  /**\n   * Handles when a key event isn\u2019t matched by shortcut handling, indicating\n   * that the user may be attempting a typeahead search.\n   */\n  private handleSearch(e: KeyboardEvent) {\n    if (\n      e.metaKey ||\n      e.altKey ||\n      e.ctrlKey ||\n      e.isComposing ||\n      e.key.length > 1 ||\n      !e.key.match(/\\S/)\n    ) {\n      return;\n    }\n\n    // KeyDown events should be within one second of each other to be considered\n    // part of the same typeahead search string.\n    const MAX_TYPEAHEAD_THRESHOLD_MS = 1000;\n    if (e.timeStamp - this.lastKeyDownTimeStamp > MAX_TYPEAHEAD_THRESHOLD_MS) {\n      this.searchString = '';\n    }\n    this.lastKeyDownTimeStamp = e.timeStamp;\n    this.searchString += e.key.toLocaleLowerCase();\n    const focusedElementText = this.visibleItems[\n      this.focusedIndex\n    ].textContent?.toLocaleLowerCase();\n    if (this.searchString.length === 1 || !focusedElementText?.startsWith(this.searchString)) {\n      this.focusNextItemWithPrefix(this.searchString);\n    }\n    e.stopPropagation();\n    e.preventDefault();\n  }\n\n  /**\n   * Focuses on the next visible tree item (after the currently focused element,\n   * wrapping the tree) that has a prefix equal to the given search string.\n   */\n  focusNextItemWithPrefix(prefix: string) {\n    let i = this.focusedIndex + 1;\n    if (i > this.visibleItems.length - 1) {\n      i = 0;\n    }\n    while (i !== this.focusedIndex) {\n      if (this.visibleItems[i].textContent?.toLocaleLowerCase().startsWith(prefix)) {\n        this.setFocusedIndex(i);\n        return;\n      }\n      if (i >= this.visibleItems.length - 1) {\n        i = 0;\n      } else {\n        i++;\n      }\n    }\n  }\n\n  private toggleItemExpandedState(el: HTMLElement) {\n    el.getAttribute('aria-expanded') === 'true' ? this.collapseItem(el) : this.expandItem(el);\n  }\n\n  private focusPreviousItem() {\n    this.setFocusedIndex(Math.max(0, this.focusedIndex - 1));\n  }\n\n  private focusNextItem() {\n    this.setFocusedIndex(Math.min(this.visibleItems.length - 1, this.focusedIndex + 1));\n  }\n\n  private collapseItem(el: HTMLElement) {\n    el.setAttribute('aria-expanded', 'false');\n    this.updateVisibleItems();\n  }\n\n  private expandItem(el: HTMLElement) {\n    el.setAttribute('aria-expanded', 'true');\n    this.updateVisibleItems();\n  }\n\n  private focusParentItem(el: HTMLElement | null) {\n    const owningItemEl = this.owningItem(el);\n    if (owningItemEl) {\n      this.setFocusedIndex(this.visibleItems.indexOf(owningItemEl));\n    }\n  }\n\n  /**\n   * @returnThe first parent item that \u201Cowns\u201D the group that el is a member of,\n   * or null if there is none.\n   */\n  owningItem(el: HTMLElement | null) {\n    const groupEl = el?.closest(`[role='group']`);\n    if (!groupEl) {\n      return null;\n    }\n    return groupEl.parentElement?.querySelector<HTMLElement>(`[aria-owns='${groupEl.id}']`);\n  }\n\n  /**\n   * Updates which items are visible (not a child of a collapsed item).\n   */\n  private updateVisibleItems() {\n    const allEls = Array.from(this.el.querySelectorAll<HTMLElement>(`[role='treeitem']`));\n    const hiddenEls = Array.from(\n      this.el.querySelectorAll(`[aria-expanded='false'] + [role='group'] [role='treeitem']`)\n    );\n    this.visibleItems = allEls.filter(el => !hiddenEls.includes(el));\n  }\n}\n\n/**\n * Primary controller for the documentation page, handling coordination between\n * the navigation and content components. This class ensures that any\n * documentation elements in view are properly shown/highlighted in the\n * navigation components.\n *\n * Since navigation is essentially handled by anchor tags with fragment IDs as\n * hrefs, the fragment ID (referenced in this code as simply \u201CID\u201D) is used to\n * look up both navigation and content nodes.\n */\nclass DocPageController {\n  private navController?: DocNavTreeController;\n  private mobileNavController?: MobileNavController;\n  /**\n   * Instantiates the controller, setting up the navigation controller (both\n   * desktop and mobile), and event listeners. This should only be called once.\n   */\n  constructor(\n    sideNavEl: HTMLElement | null,\n    mobileNavEl: HTMLElement | null,\n    private contentEl: HTMLElement | null\n  ) {\n    if (!sideNavEl || !contentEl) {\n      console.warn('Unable to find all elements needed for navigation');\n      return;\n    }\n\n    this.navController = new DocNavTreeController(sideNavEl);\n\n    if (mobileNavEl) {\n      this.mobileNavController = new MobileNavController(mobileNavEl);\n    }\n    window.addEventListener('hashchange', () => this.handleHashChange());\n\n    this.updateSelectedIdFromWindowHash();\n  }\n\n  /**\n   * Handles when the location hash changes.\n   */\n  private handleHashChange() {\n    this.updateSelectedIdFromWindowHash();\n  }\n\n  private updateSelectedIdFromWindowHash() {\n    const targetId = this.targetIdFromLocationHash();\n    this.navController?.setSelectedId(targetId);\n    if (this.mobileNavController) {\n      this.mobileNavController.setSelectedId(targetId);\n    }\n    if (targetId !== '') {\n      const targetEl = this.contentEl?.querySelector<HTMLElement>(`[id='${targetId}']`);\n      if (targetEl) {\n        targetEl.focus();\n      }\n    }\n  }\n\n  targetIdFromLocationHash() {\n    return window.location.hash && window.location.hash.substr(1);\n  }\n}\n\n/**\n * Controller for the navigation element used on smaller viewports. It utilizes\n * a native <select> element for interactivity and a styled <label> for\n * displaying the selected option.\n *\n * It presumes a fixed header and that the container for the control will be\n * sticky right below the header when scrolled enough.\n */\nclass MobileNavController {\n  private selectEl: HTMLSelectElement | null;\n  private labelTextEl: HTMLElement | null;\n  private intersectionObserver: IntersectionObserver;\n\n  constructor(private el: HTMLElement) {\n    this.selectEl = el.querySelector<HTMLSelectElement>('select');\n    this.labelTextEl = el.querySelector<HTMLElement>('.js-mobileNavSelectText');\n\n    this.selectEl?.addEventListener('change', e => this.handleSelectChange(e));\n\n    // We use a slight hack to detect if the mobile nav container is pinned to\n    // the bottom of the site header. The root viewport of an IntersectionObserver\n    // is inset by the header height plus one pixel to ensure that the container is\n    // considered \u201Cout of view\u201D when in a fixed position and can be styled appropriately.\n    const ROOT_TOP_MARGIN = '-57px';\n\n    this.intersectionObserver = new IntersectionObserver(\n      entries => this.intersectionObserverCallback(entries),\n      {\n        rootMargin: `${ROOT_TOP_MARGIN} 0px 0px 0px`,\n        threshold: 1.0,\n      }\n    );\n    this.intersectionObserver.observe(this.el);\n  }\n\n  setSelectedId(id: string) {\n    if (!this.selectEl) return;\n    this.selectEl.value = id;\n    this.updateLabelText();\n  }\n\n  private updateLabelText() {\n    if (!this.labelTextEl || !this.selectEl) return;\n    const selectedIndex = this.selectEl?.selectedIndex;\n    if (selectedIndex === -1) {\n      this.labelTextEl.textContent = '';\n      return;\n    }\n    this.labelTextEl.textContent = this.selectEl.options[selectedIndex].textContent;\n  }\n\n  private handleSelectChange(e: Event) {\n    window.location.hash = `#${(e.target as HTMLSelectElement).value}`;\n    this.updateLabelText();\n  }\n\n  private intersectionObserverCallback(entries: IntersectionObserverEntry[]) {\n    const SHADOW_CSS_CLASS = 'DocNavMobile--withShadow';\n    entries.forEach(entry => {\n      // entry.isIntersecting isn\u2019t reliable on Firefox.\n      const fullyInView = entry.intersectionRatio === 1.0;\n      entry.target.classList.toggle(SHADOW_CSS_CLASS, !fullyInView);\n    });\n  }\n}\n\nnew DocPageController(\n  document.querySelector('.js-tree'),\n  document.querySelector('.js-mobileNav'),\n  document.querySelector('.js-unitDetailsContent')\n);\n"],
-  "mappings": "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",
+  "sourcesContent": ["/*!\n * @license\n * Copyright 2019-2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/**\n * Possible KeyboardEvent key values.\n * @private @enum {string}\n */\nconst Key = {\n  UP: 'ArrowUp',\n  DOWN: 'ArrowDown',\n  LEFT: 'ArrowLeft',\n  RIGHT: 'ArrowRight',\n  ENTER: 'Enter',\n  ASTERISK: '*',\n  SPACE: ' ',\n  END: 'End',\n  HOME: 'Home',\n\n  // Global keyboard shortcuts.\n  // TODO(golang.org/issue/40246): consolidate keyboard shortcut handling to avoid\n  // this duplication.\n  Y: 'y',\n  FORWARD_SLASH: '/',\n  QUESTION_MARK: '?',\n};\n\n/**\n * The navigation tree component of the documentation page.\n */\nclass DocNavTreeController {\n  /**\n   * The currently selected element.\n   */\n  private selectedEl: HTMLElement | null;\n  /**\n   * The index of the currently focused item. Used when navigating the tree\n   * using the keyboard.\n   */\n  private focusedIndex = 0;\n  /**\n   * The elements currently visible (not within a collapsed node of the tree).\n   */\n  private visibleItems: HTMLElement[] = [];\n  /**\n   * The current search string.\n   */\n  private searchString = '';\n  /**\n   * The timestamp of the last keydown event. Used to track whether to use the\n   * current search string.\n   */\n  private lastKeyDownTimeStamp = -Infinity;\n\n  /**\n   * Instantiates a navigation tree.\n   */\n  constructor(private el: Element) {\n    this.el = el;\n    this.selectedEl = null;\n    this.focusedIndex = 0;\n    this.visibleItems = [];\n    this.searchString = '';\n    this.lastKeyDownTimeStamp = -Infinity;\n    this.addEventListeners();\n    this.updateVisibleItems();\n    this.initialize();\n  }\n\n  /**\n   * Initializes the tree. Should be called only once.\n   */\n  private initialize() {\n    this.el.querySelectorAll(`[role='treeitem']`).forEach(el => {\n      el.addEventListener('click', e => this.handleItemClick(e as MouseEvent));\n    });\n\n    // TODO: remove once safehtml supports aria-owns with dynamic values.\n    this.el.querySelectorAll('[data-aria-owns]').forEach(el => {\n      el.setAttribute('aria-owns', el.getAttribute('data-aria-owns') ?? '');\n    });\n  }\n\n  private addEventListeners() {\n    this.el.addEventListener('keydown', e => this.handleKeyDown(e as KeyboardEvent));\n  }\n\n  /**\n   * Sets the visible item with the given index with the proper tabindex and\n   * focuses it.\n   */\n  setFocusedIndex(index: number) {\n    if (index === this.focusedIndex || index === -1) {\n      return;\n    }\n\n    let itemEl = this.visibleItems[this.focusedIndex];\n    itemEl.setAttribute('tabindex', '-1');\n\n    itemEl = this.visibleItems[index];\n    itemEl.setAttribute('tabindex', '0');\n    itemEl.focus();\n\n    this.focusedIndex = index;\n  }\n\n  /**\n   * Marks the navigation node with the given ID as selected. If no ID is\n   * provided, the first visible item in the tree is used.\n   */\n  setSelectedId(opt_id: string) {\n    if (this.selectedEl) {\n      this.selectedEl.removeAttribute('aria-selected');\n      this.selectedEl = null;\n    }\n    if (opt_id) {\n      this.selectedEl = this.el.querySelector(`[role='treeitem'][href='#${opt_id}']`);\n    } else if (this.visibleItems.length > 0) {\n      this.selectedEl = this.visibleItems[0];\n    }\n\n    if (!this.selectedEl) {\n      return;\n    }\n\n    // Close inactive top level item if selected id is not in its tree.\n    const topLevelExpanded = this.el.querySelector<HTMLElement>(\n      '[aria-level=\"1\"][aria-expanded=\"true\"]'\n    );\n    if (topLevelExpanded && !topLevelExpanded.parentElement?.contains(this.selectedEl)) {\n      this.collapseItem(topLevelExpanded);\n    }\n\n    if (this.selectedEl.getAttribute('aria-level') === '1') {\n      this.selectedEl.setAttribute('aria-expanded', 'true');\n    }\n    this.selectedEl.setAttribute('aria-selected', 'true');\n    this.expandAllParents(this.selectedEl);\n    this.scrollElementIntoView(this.selectedEl);\n  }\n\n  /**\n   * Expands all sibling items of the given element.\n   */\n  private expandAllSiblingItems(el: HTMLElement) {\n    const level = el.getAttribute('aria-level');\n    this.el.querySelectorAll(`[aria-level='${level}'][aria-expanded='false']`).forEach(el => {\n      el.setAttribute('aria-expanded', 'true');\n    });\n    this.updateVisibleItems();\n    this.focusedIndex = this.visibleItems.indexOf(el);\n  }\n\n  /**\n   * Expands all parent items of the given element.\n   */\n  private expandAllParents(el: HTMLElement) {\n    if (!this.visibleItems.includes(el)) {\n      let owningItemEl = this.owningItem(el);\n      while (owningItemEl) {\n        this.expandItem(owningItemEl);\n        owningItemEl = this.owningItem(owningItemEl);\n      }\n    }\n  }\n\n  /**\n   * Scrolls the given element into view, aligning the element in the center\n   * of the viewport. If the element is already in view, no scrolling occurs.\n   */\n  private scrollElementIntoView(el: HTMLElement) {\n    const STICKY_HEADER_HEIGHT_PX = 55;\n    const viewportHeightPx = document.documentElement.clientHeight;\n    const elRect = el.getBoundingClientRect();\n    const verticalCenterPointPx = (viewportHeightPx - STICKY_HEADER_HEIGHT_PX) / 2;\n    if (elRect.top < STICKY_HEADER_HEIGHT_PX) {\n      // Element is occluded at top of view by header or by being offscreen.\n      this.el.scrollTop -=\n        STICKY_HEADER_HEIGHT_PX - elRect.top - elRect.height + verticalCenterPointPx;\n    } else if (elRect.bottom > viewportHeightPx) {\n      // Element is below viewport.\n      this.el.scrollTop = elRect.bottom - viewportHeightPx + verticalCenterPointPx;\n    } else {\n      return;\n    }\n  }\n\n  /**\n   * Handles when a tree item is clicked.\n   */\n  private handleItemClick(e: MouseEvent) {\n    const el = e.target as HTMLSelectElement;\n    this.setFocusedIndex(this.visibleItems.indexOf(el));\n    if (el.hasAttribute('aria-expanded')) {\n      this.toggleItemExpandedState(el);\n    }\n    this.closeInactiveDocNavGroups(el);\n  }\n\n  /**\n   * Closes inactive top level nav groups when a new tree item clicked.\n   */\n  private closeInactiveDocNavGroups(el: HTMLElement) {\n    if (el.hasAttribute('aria-expanded')) {\n      const level = el.getAttribute('aria-level');\n      document.querySelectorAll(`[aria-level=\"${level}\"]`).forEach(nav => {\n        if (nav.getAttribute('aria-expanded') === 'true' && nav !== el) {\n          nav.setAttribute('aria-expanded', 'false');\n        }\n      });\n      this.updateVisibleItems();\n      this.focusedIndex = this.visibleItems.indexOf(el);\n    }\n  }\n\n  /**\n   * Handles when a key is pressed when the component is in focus.\n   */\n  handleKeyDown(e: KeyboardEvent) {\n    const targetEl = e.target as HTMLElement | null;\n\n    switch (e.key) {\n      case Key.ASTERISK:\n        if (targetEl) {\n          this.expandAllSiblingItems(targetEl);\n        }\n        e.stopPropagation();\n        e.preventDefault();\n        return;\n\n      // Global keyboard shortcuts.\n      // TODO(golang.org/issue/40246): consolidate keyboard shortcut handling\n      // to avoid this duplication.\n      case Key.FORWARD_SLASH:\n      case Key.QUESTION_MARK:\n        return;\n\n      case Key.DOWN:\n        this.focusNextItem();\n        break;\n\n      case Key.UP:\n        this.focusPreviousItem();\n        break;\n\n      case Key.LEFT:\n        if (targetEl?.getAttribute('aria-expanded') === 'true') {\n          this.collapseItem(targetEl);\n        } else {\n          this.focusParentItem(targetEl);\n        }\n        break;\n\n      case Key.RIGHT: {\n        switch (targetEl?.getAttribute('aria-expanded')) {\n          case 'false':\n            this.expandItem(targetEl);\n            break;\n          case 'true':\n            // Select the first child.\n            this.focusNextItem();\n            break;\n        }\n        break;\n      }\n\n      case Key.HOME:\n        this.setFocusedIndex(0);\n        break;\n\n      case Key.END:\n        this.setFocusedIndex(this.visibleItems.length - 1);\n        break;\n\n      case Key.ENTER:\n        if (targetEl?.tagName === 'A') {\n          // Enter triggers desired behavior by itself.\n          return;\n        }\n      // Fall through for non-anchor items to be handled the same as when\n      // the space key is pressed.\n      // eslint-disable-next-line no-fallthrough\n      case Key.SPACE:\n        targetEl?.click();\n        break;\n\n      default:\n        // Could be a typeahead search.\n        this.handleSearch(e);\n        return;\n    }\n    e.preventDefault();\n    e.stopPropagation();\n  }\n\n  /**\n   * Handles when a key event isn\u2019t matched by shortcut handling, indicating\n   * that the user may be attempting a typeahead search.\n   */\n  private handleSearch(e: KeyboardEvent) {\n    if (\n      e.metaKey ||\n      e.altKey ||\n      e.ctrlKey ||\n      e.isComposing ||\n      e.key.length > 1 ||\n      !e.key.match(/\\S/)\n    ) {\n      return;\n    }\n\n    // KeyDown events should be within one second of each other to be considered\n    // part of the same typeahead search string.\n    const MAX_TYPEAHEAD_THRESHOLD_MS = 1000;\n    if (e.timeStamp - this.lastKeyDownTimeStamp > MAX_TYPEAHEAD_THRESHOLD_MS) {\n      this.searchString = '';\n    }\n    this.lastKeyDownTimeStamp = e.timeStamp;\n    this.searchString += e.key.toLocaleLowerCase();\n    const focusedElementText = this.visibleItems[\n      this.focusedIndex\n    ].textContent?.toLocaleLowerCase();\n    if (this.searchString.length === 1 || !focusedElementText?.startsWith(this.searchString)) {\n      this.focusNextItemWithPrefix(this.searchString);\n    }\n    e.stopPropagation();\n    e.preventDefault();\n  }\n\n  /**\n   * Focuses on the next visible tree item (after the currently focused element,\n   * wrapping the tree) that has a prefix equal to the given search string.\n   */\n  focusNextItemWithPrefix(prefix: string) {\n    let i = this.focusedIndex + 1;\n    if (i > this.visibleItems.length - 1) {\n      i = 0;\n    }\n    while (i !== this.focusedIndex) {\n      if (this.visibleItems[i].textContent?.toLocaleLowerCase().startsWith(prefix)) {\n        this.setFocusedIndex(i);\n        return;\n      }\n      if (i >= this.visibleItems.length - 1) {\n        i = 0;\n      } else {\n        i++;\n      }\n    }\n  }\n\n  private toggleItemExpandedState(el: HTMLElement) {\n    el.getAttribute('aria-expanded') === 'true' ? this.collapseItem(el) : this.expandItem(el);\n  }\n\n  private focusPreviousItem() {\n    this.setFocusedIndex(Math.max(0, this.focusedIndex - 1));\n  }\n\n  private focusNextItem() {\n    this.setFocusedIndex(Math.min(this.visibleItems.length - 1, this.focusedIndex + 1));\n  }\n\n  private collapseItem(el: HTMLElement) {\n    el.setAttribute('aria-expanded', 'false');\n    this.updateVisibleItems();\n  }\n\n  private expandItem(el: HTMLElement) {\n    el.setAttribute('aria-expanded', 'true');\n    this.updateVisibleItems();\n  }\n\n  private focusParentItem(el: HTMLElement | null) {\n    const owningItemEl = this.owningItem(el);\n    if (owningItemEl) {\n      this.setFocusedIndex(this.visibleItems.indexOf(owningItemEl));\n    }\n  }\n\n  /**\n   * @returnThe first parent item that \u201Cowns\u201D the group that el is a member of,\n   * or null if there is none.\n   */\n  owningItem(el: HTMLElement | null) {\n    const groupEl = el?.closest(`[role='group']`);\n    if (!groupEl) {\n      return null;\n    }\n    return groupEl.parentElement?.querySelector<HTMLElement>(`[aria-owns='${groupEl.id}']`);\n  }\n\n  /**\n   * Updates which items are visible (not a child of a collapsed item).\n   */\n  private updateVisibleItems() {\n    const allEls = Array.from(this.el.querySelectorAll<HTMLElement>(`[role='treeitem']`));\n    const hiddenEls = Array.from(\n      this.el.querySelectorAll(`[aria-expanded='false'] + [role='group'] [role='treeitem']`)\n    );\n    this.visibleItems = allEls.filter(el => !hiddenEls.includes(el));\n  }\n}\n\n/**\n * Primary controller for the documentation page, handling coordination between\n * the navigation and content components. This class ensures that any\n * documentation elements in view are properly shown/highlighted in the\n * navigation components.\n *\n * Since navigation is essentially handled by anchor tags with fragment IDs as\n * hrefs, the fragment ID (referenced in this code as simply \u201CID\u201D) is used to\n * look up both navigation and content nodes.\n */\nclass DocPageController {\n  private navController?: DocNavTreeController;\n  private mobileNavController?: MobileNavController;\n  /**\n   * Instantiates the controller, setting up the navigation controller (both\n   * desktop and mobile), and event listeners. This should only be called once.\n   */\n  constructor(\n    sideNavEl: HTMLElement | null,\n    mobileNavEl: HTMLElement | null,\n    private contentEl: HTMLElement | null\n  ) {\n    if (!sideNavEl || !contentEl) {\n      console.warn('Unable to find all elements needed for navigation');\n      return;\n    }\n\n    this.navController = new DocNavTreeController(sideNavEl);\n\n    if (mobileNavEl) {\n      this.mobileNavController = new MobileNavController(mobileNavEl);\n    }\n    window.addEventListener('hashchange', () => this.handleHashChange());\n\n    this.updateSelectedIdFromWindowHash();\n  }\n\n  /**\n   * Handles when the location hash changes.\n   */\n  private handleHashChange() {\n    this.updateSelectedIdFromWindowHash();\n  }\n\n  private updateSelectedIdFromWindowHash() {\n    const targetId = this.targetIdFromLocationHash();\n    this.navController?.setSelectedId(targetId);\n    if (this.mobileNavController) {\n      this.mobileNavController.setSelectedId(targetId);\n    }\n    if (targetId !== '') {\n      const targetEl = this.contentEl?.querySelector<HTMLElement>(`[id='${targetId}']`);\n      if (targetEl) {\n        targetEl.focus();\n      }\n    }\n  }\n\n  targetIdFromLocationHash() {\n    return window.location.hash && window.location.hash.substr(1);\n  }\n}\n\n/**\n * Controller for the navigation element used on smaller viewports. It utilizes\n * a native <select> element for interactivity and a styled <label> for\n * displaying the selected option.\n *\n * It presumes a fixed header and that the container for the control will be\n * sticky right below the header when scrolled enough.\n */\nclass MobileNavController {\n  private selectEl: HTMLSelectElement | null;\n  private labelTextEl: HTMLElement | null;\n  private intersectionObserver: IntersectionObserver;\n\n  constructor(private el: HTMLElement) {\n    this.selectEl = el.querySelector<HTMLSelectElement>('select');\n    this.labelTextEl = el.querySelector<HTMLElement>('.js-mobileNavSelectText');\n\n    this.selectEl?.addEventListener('change', e => this.handleSelectChange(e));\n\n    // We use a slight hack to detect if the mobile nav container is pinned to\n    // the bottom of the site header. The root viewport of an IntersectionObserver\n    // is inset by the header height plus one pixel to ensure that the container is\n    // considered \u201Cout of view\u201D when in a fixed position and can be styled appropriately.\n    const ROOT_TOP_MARGIN = '-57px';\n\n    this.intersectionObserver = new IntersectionObserver(\n      entries => this.intersectionObserverCallback(entries),\n      {\n        rootMargin: `${ROOT_TOP_MARGIN} 0px 0px 0px`,\n        threshold: 1.0,\n      }\n    );\n    this.intersectionObserver.observe(this.el);\n  }\n\n  setSelectedId(id: string) {\n    if (!this.selectEl) return;\n    this.selectEl.value = id;\n    this.updateLabelText();\n  }\n\n  private updateLabelText() {\n    if (!this.labelTextEl || !this.selectEl) return;\n    const selectedIndex = this.selectEl?.selectedIndex;\n    if (selectedIndex === -1) {\n      this.labelTextEl.textContent = '';\n      return;\n    }\n    this.labelTextEl.textContent = this.selectEl.options[selectedIndex].textContent;\n  }\n\n  private handleSelectChange(e: Event) {\n    window.location.hash = `#${(e.target as HTMLSelectElement).value}`;\n    this.updateLabelText();\n  }\n\n  private intersectionObserverCallback(entries: IntersectionObserverEntry[]) {\n    const SHADOW_CSS_CLASS = 'DocNavMobile--withShadow';\n    entries.forEach(entry => {\n      // entry.isIntersecting isn\u2019t reliable on Firefox.\n      const fullyInView = entry.intersectionRatio === 1.0;\n      entry.target.classList.toggle(SHADOW_CSS_CLASS, !fullyInView);\n    });\n  }\n}\n\nnew DocPageController(\n  document.querySelector('.js-tree'),\n  document.querySelector('.js-mobileNav'),\n  document.querySelector('.js-unitDetailsContent')\n);\n"],
+  "mappings": "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",
   "names": []
 }
diff --git a/content/static/js/sidenav.ts b/content/static/js/sidenav.ts
index 210151d..8791f3c 100644
--- a/content/static/js/sidenav.ts
+++ b/content/static/js/sidenav.ts
@@ -130,7 +130,7 @@
     const topLevelExpanded = this.el.querySelector<HTMLElement>(
       '[aria-level="1"][aria-expanded="true"]'
     );
-    if (topLevelExpanded && !topLevelExpanded.contains(this.selectedEl)) {
+    if (topLevelExpanded && !topLevelExpanded.parentElement?.contains(this.selectedEl)) {
       this.collapseItem(topLevelExpanded);
     }