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);
}