chrome-devtools-frontend
Version:
Chrome DevTools UI
181 lines (150 loc) • 5.82 kB
text/typescript
// 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;
}