chrome-devtools-frontend
Version:
Chrome DevTools UI
165 lines (146 loc) • 6.01 kB
text/typescript
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
* Copyright (C) 2009 Joseph Pecoraro
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import type * as SDK from '../../core/sdk/sdk.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Lit from '../../ui/lit/lit.js';
import * as VisualElements from '../../ui/visual_logging/visual_logging.js';
import * as ElementsComponents from './components/components.js';
import {adornerRef, ElementsTreeElement} from './ElementsTreeElement.js';
import {ElementsTreeOutline} from './ElementsTreeOutline.js';
const {html, render} = Lit;
const UIStrings = {
/**
* @description Link text content in Elements Tree Outline of the Elements panel
*/
reveal: 'reveal',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/elements/ShortcutTreeElement.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
interface ViewInput {
title: string;
onRevealAdornerClick: (e: Event) => void;
}
export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement): void => {
// clang-format off
render(html`
<div class="selection fill"></div>
<span class="elements-tree-shortcut-title">\u21AA ${input.title}</span>
<devtools-adorner
.name=${ElementsComponents.AdornerManager.RegisteredAdorners.REVEAL}
class="adorner-reveal"
jslog=${VisualElements.adorner('reveal')}
aria-label=${i18nString(UIStrings.reveal)}
=${input.onRevealAdornerClick}
=${(e: Event) => e.consume()}
${adornerRef()}>
<span class="adorner-with-icon">
<devtools-icon name="select-element"></devtools-icon>
<span>${ElementsComponents.AdornerManager.RegisteredAdorners.REVEAL}</span>
</span>
</devtools-adorner>
`, target);
// clang-format on
};
export class ShortcutTreeElement extends UI.TreeOutline.TreeElement {
private readonly nodeShortcut: SDK.DOMModel.DOMNodeShortcut;
#hovered?: boolean;
#view: typeof DEFAULT_VIEW;
constructor(nodeShortcut: SDK.DOMModel.DOMNodeShortcut, view = DEFAULT_VIEW) {
super('');
this.nodeShortcut = nodeShortcut;
this.#view = view;
this.performUpdate();
}
get hovered(): boolean {
return Boolean(this.#hovered);
}
set hovered(x: boolean) {
if (this.#hovered === x) {
return;
}
this.#hovered = x;
this.listItemElement.classList.toggle('hovered', x);
}
deferredNode(): SDK.DOMModel.DeferredDOMNode {
return this.nodeShortcut.deferredNode;
}
domModel(): SDK.DOMModel.DOMModel {
return this.nodeShortcut.deferredNode.domModel();
}
private setLeftIndentOverlay(): void {
// We use parent's `--indent` value and add 24px to account for an extra level of indent.
let indent = 24;
if (this.parent && this.parent instanceof ElementsTreeElement) {
const parentIndent = parseFloat(this.parent.listItemElement.style.getPropertyValue('--indent')) || 0;
indent += parentIndent;
}
this.listItemElement.style.setProperty('--indent', indent + 'px');
}
override onattach(): void {
this.setLeftIndentOverlay();
}
override onselect(selectedByUser?: boolean): boolean {
if (!selectedByUser) {
return true;
}
this.nodeShortcut.deferredNode.highlight();
this.nodeShortcut.deferredNode.resolve(resolved.bind(this));
function resolved(this: ShortcutTreeElement, node: SDK.DOMModel.DOMNode|null): void {
if (node && this.treeOutline instanceof ElementsTreeOutline) {
this.treeOutline.selectedDOMNodeInternal = node;
this.treeOutline.selectedNodeChanged(false);
}
}
return true;
}
private onRevealAdornerClick(event: Event): void {
event.stopPropagation();
this.nodeShortcut.deferredNode.resolve(node => {
void Common.Revealer.reveal(node);
});
}
private performUpdate(): void {
let text = this.nodeShortcut.nodeName.toLowerCase();
if (this.nodeShortcut.nodeType === Node.ELEMENT_NODE) {
text = '<' + text + '>';
}
this.#view(
{
title: text,
onRevealAdornerClick: this.onRevealAdornerClick.bind(this),
},
undefined, this.listItemElement);
}
}