blob: 6881fabff6e7eb04c76b171705fa03631db52042 [file] [log] [blame]
{
"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.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": []
}