UNPKG

chrome-devtools-frontend

Version:
181 lines (150 loc) 5.82 kB
// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../../ui/legacy/legacy.js'; import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import cssOverviewSidebarPanelStyles from './cssOverviewSidebarPanel.css.js'; const UIStrings = { /** *@description Label for the 'Clear overview' button in the CSS overview report */ clearOverview: 'Clear overview', /** * @description Accessible label for the CSS overview panel sidebar */ cssOverviewPanelSidebar: 'CSS overview panel sidebar', }; const str_ = i18n.i18n.registerUIStrings('panels/css_overview/CSSOverviewSidebarPanel.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); const ITEM_CLASS_NAME = 'overview-sidebar-panel-item'; const SELECTED_CLASS_NAME = 'selected'; export class CSSOverviewSidebarPanel extends Common.ObjectWrapper.eventMixin<EventTypes, typeof UI.Widget.VBox>( UI.Widget.VBox) { containerElement: HTMLDivElement; constructor() { super(true); this.registerRequiredCSS(cssOverviewSidebarPanelStyles); this.contentElement.classList.add('overview-sidebar-panel'); this.contentElement.addEventListener('click', this.#onItemClick.bind(this)); this.contentElement.addEventListener('keydown', this.#onItemKeyDown.bind(this)); // We need a container so that each item covers the full width of the // longest item, so that the selected item's background expands fully // even when the sidebar overflows. // Also see crbug/1408003 this.containerElement = this.contentElement.createChild('div', 'overview-sidebar-panel-container'); UI.ARIAUtils.setLabel(this.containerElement, i18nString(UIStrings.cssOverviewPanelSidebar)); UI.ARIAUtils.markAsTree(this.containerElement); // Clear overview. const clearResultsButton = new UI.Toolbar.ToolbarButton( i18nString(UIStrings.clearOverview), 'clear', undefined, 'css-overview.clear-overview'); clearResultsButton.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, this.#reset, this); // Toolbar. const toolbarElement = this.containerElement.createChild('div', 'overview-toolbar'); const toolbar = toolbarElement.createChild('devtools-toolbar'); toolbar.appendToolbarItem(clearResultsButton); } addItem(name: string, id: string): void { const item = this.containerElement.createChild('div', ITEM_CLASS_NAME); item.setAttribute( 'jslog', `${ VisualLogging.item() .track({click: true, keydown: 'Enter|ArrowUp|ArrowDown'}) .context(`css-overview.${id}`)}`); UI.ARIAUtils.markAsTreeitem(item); item.textContent = name; item.dataset.id = id; item.tabIndex = 0; } #reset(): void { this.dispatchEventToListeners(SidebarEvents.RESET); } #deselectAllItems(): void { const items = this.containerElement.querySelectorAll(`.${ITEM_CLASS_NAME}`); items.forEach(item => { item.classList.remove(SELECTED_CLASS_NAME); }); } #onItemClick(event: Event): void { const target = (event.composedPath()[0] as HTMLElement); if (!target.classList.contains(ITEM_CLASS_NAME)) { return; } const {id} = target.dataset; if (!id) { return; } this.select(id, false); this.dispatchEventToListeners(SidebarEvents.ITEM_SELECTED, {id, isMouseEvent: true, key: undefined}); } #onItemKeyDown(event: KeyboardEvent): void { if (event.key !== 'Enter' && event.key !== 'ArrowUp' && event.key !== 'ArrowDown') { return; } const target = (event.composedPath()[0] as HTMLElement); if (!target.classList.contains(ITEM_CLASS_NAME)) { return; } const {id} = target.dataset; if (!id) { return; } if (event.key === 'Enter') { this.select(id, false); this.dispatchEventToListeners(SidebarEvents.ITEM_SELECTED, {id, isMouseEvent: false, key: event.key}); } else { // arrow up/down key const items = this.containerElement.querySelectorAll(`.${ITEM_CLASS_NAME}`); let currItemIndex = -1; for (let idx = 0; idx < items.length; idx++) { if ((items[idx] as HTMLElement).dataset.id === id) { currItemIndex = idx; break; } } if (currItemIndex < 0) { return; } const moveTo = (event.key === 'ArrowDown' ? 1 : -1); const nextItemIndex = (currItemIndex + moveTo) % items.length; const nextItemId = (items[nextItemIndex] as HTMLElement).dataset.id; if (!nextItemId) { return; } this.select(nextItemId, true); this.dispatchEventToListeners(SidebarEvents.ITEM_SELECTED, {id: nextItemId, isMouseEvent: false, key: event.key}); } event.consume(true); } select(id: string, focus: boolean): void { const target = this.containerElement.querySelector(`[data-id=${CSS.escape(id)}]`) as HTMLElement; if (!target) { return; } if (target.classList.contains(SELECTED_CLASS_NAME)) { return; } this.#deselectAllItems(); target.classList.add(SELECTED_CLASS_NAME); if (focus) { target.contentEditable = 'true'; target.focus(); target.contentEditable = 'false'; } } } export const enum SidebarEvents { ITEM_SELECTED = 'ItemSelected', RESET = 'Reset', } export interface ItemSelectedEvent { id: string; isMouseEvent: boolean; key: string|undefined; } export interface EventTypes { [SidebarEvents.ITEM_SELECTED]: ItemSelectedEvent; [SidebarEvents.RESET]: void; }