[TreeOutline] custom renderer support

Adds the basic support for custom renderers to tree nodes.

Bug: chromium:1166669
Change-Id: Iaa271384ea1798c234d131b27cd08ecb8a49623d
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2696651
Commit-Queue: Jack Franklin <jacktfranklin@chromium.org>
Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
This commit is contained in:
Jack Franklin
2021-02-17 11:18:59 +00:00
committed by Commit Bot
parent b265fb501a
commit fdf2a0cecd
8 changed files with 153 additions and 13 deletions
@@ -9,6 +9,7 @@ ts_library("ts") {
testonly = true
sources = [
"basic.ts",
"custom-renderers.ts",
"lazy-children.ts",
"sample-data.ts",
]
@@ -16,6 +17,7 @@ ts_library("ts") {
deps = [
"../../../test/unittests/front_end/helpers",
"../../component_helpers:bundle",
"../../third_party/lit-html:bundle",
"../../ui/components:bundle",
]
}
@@ -24,6 +26,7 @@ copy_to_gen("tree_outline") {
testonly = true
sources = [
"basic.html",
"custom-renderers.html",
"lazy-children.html",
]
@@ -0,0 +1,32 @@
<!--
Copyright 2020 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.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>TreeOutline with custom renderers</title>
<style>
#container {
width: 80%;
border: 1px solid black;
padding: 20px;
height: 300px;
}
</style>
</head>
<body>
<div id="container">
</div>
<button id="recursively-expand">Recursively expand</button>
<script type="module" src="./custom-renderers.js"></script>
</body>
</html>
@@ -0,0 +1,70 @@
// Copyright 2020 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 * as FrontendHelpers from '../../../test/unittests/front_end/helpers/EnvironmentHelpers.js';
import * as ComponentHelpers from '../../component_helpers/component_helpers.js';
import * as LitHtml from '../../third_party/lit-html/lit-html.js';
import * as Components from '../../ui/components/components.js';
await ComponentHelpers.ComponentServerSetup.setup();
await FrontendHelpers.initializeGlobalVars();
const data: Components.TreeOutline.TreeOutlineData = {
tree: [
{
key: 'Offices',
renderer: (key: string): LitHtml.TemplateResult =>
LitHtml.html`<strong style="color:red;">${key.toUpperCase()}</strong>`,
children: (): Promise<Components.TreeOutlineUtils.TreeNode[]> => Promise.resolve([
{
key: 'Europe',
children: (): Promise<Components.TreeOutlineUtils.TreeNode[]> => Promise.resolve([
{
key: 'UK',
children: (): Promise<Components.TreeOutlineUtils.TreeNode[]> => Promise.resolve([
{
key: 'LON',
children: (): Promise<Components.TreeOutlineUtils.TreeNode[]> =>
Promise.resolve([{key: '6PS'}, {key: 'CSG'}, {key: 'BEL'}]),
},
]),
},
{
key: 'Germany',
children: (): Promise<Components.TreeOutlineUtils.TreeNode[]> => Promise.resolve([
{key: 'MUC'},
{key: 'BER'},
]),
},
]),
},
]),
},
{
key: 'Products',
children: (): Promise<Components.TreeOutlineUtils.TreeNode[]> => Promise.resolve([
{
key: 'Chrome',
},
{
key: 'YouTube',
},
{
key: 'Drive',
},
{
key: 'Calendar',
},
]),
},
],
};
const component = new Components.TreeOutline.TreeOutline();
component.data = data;
document.getElementById('container')?.appendChild(component);
document.getElementById('recursively-expand')?.addEventListener('click', () => {
component.expandRecursively();
});
+3 -1
View File
@@ -234,6 +234,8 @@ export class TreeOutline extends HTMLElement {
});
const ariaExpandedAttribute = LitHtml.Directives.ifDefined(isExpandableNode(node) ? String(nodeIsExpanded) : undefined);
const renderedNodeKey = node.renderer ? node.renderer(node.key) : node.key;
return LitHtml.html`
<li role="treeitem"
tabindex=${tabIndex}
@@ -248,7 +250,7 @@ export class TreeOutline extends HTMLElement {
<span class="arrow-and-key-wrapper">
<span class="arrow-icon" @click=${this.onArrowClick(node)}>
</span>
<span class="tree-node-key" data-node-key>${node.key}</span>
<span class="tree-node-key" data-node-key=${node.key}>${renderedNodeKey}</span>
</span>
${childrenToRender}
</li>
@@ -6,6 +6,7 @@ import * as LitHtml from '../../third_party/lit-html/lit-html.js';
interface BaseTreeNode {
key: string;
renderer?: (key: string) => LitHtml.TemplateResult;
}
export interface TreeNodeWithChildren extends BaseTreeNode {
@@ -189,3 +189,13 @@ export async function doubleRaf() {
export async function raf() {
return new Promise(resolve => requestAnimationFrame(resolve));
}
/**
* It's useful to use innerHTML in the tests to have full confidence in the
* renderer output, but LitHtml uses comment nodes to split dynamic from
* static parts of a template, and we don't want our tests full of noise
* from those.
*/
export function stripLitHtmlCommentNodes(text: string) {
return text.replaceAll('<!---->', '');
}
@@ -6,7 +6,7 @@ import * as Platform from '../../../../../front_end/platform/platform.js';
import * as Coordinator from '../../../../../front_end/render_coordinator/render_coordinator.js';
import * as LitHtml from '../../../../../front_end/third_party/lit-html/lit-html.js';
import * as UIComponents from '../../../../../front_end/ui/components/components.js';
import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, getEventPromise, renderElementIntoDOM} from '../../helpers/DOMHelpers.js';
import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, getEventPromise, renderElementIntoDOM, stripLitHtmlCommentNodes} from '../../helpers/DOMHelpers.js';
import {withMutations} from '../../helpers/MutationHelpers.js';
import {assertCurrentFocusedCellIs, emulateUserFocusingCellAt, emulateUserKeyboardNavigation, focusTableCell, getAllRows, getCellByIndexes, getFocusableCell, getHeaderCellForColumnId, getHeaderCells, getValuesOfAllBodyRows, getValuesOfBodyRowByAriaIndex} from './DataGridHelpers.js';
@@ -60,14 +60,6 @@ Object.freeze(columns);
Object.freeze(columnsWithNoneSortable);
Object.freeze(rows);
/**
* It's useful to use innerHTML in the tests to have full confidence in the
* renderer output, but LitHtml uses comment nodes to split dynamic from
* static parts of a template, and we don't want our tests full of noise
* from those.
*/
const stripLitHtmlCommentNodes = (text: string) => text.replaceAll('<!---->', '');
const renderDataGrid = (data: Partial<UIComponents.DataGrid.DataGridData>): UIComponents.DataGrid.DataGrid => {
const component = new UIComponents.DataGrid.DataGrid();
component.data = {
@@ -3,8 +3,9 @@
// found in the LICENSE file.
import * as Coordinator from '../../../../../front_end/render_coordinator/render_coordinator.js';
import * as LitHtml from '../../../../../front_end/third_party/lit-html/lit-html.js';
import * as UIComponents from '../../../../../front_end/ui/components/components.js';
import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, renderElementIntoDOM} from '../../helpers/DOMHelpers.js';
import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, renderElementIntoDOM, stripLitHtmlCommentNodes} from '../../helpers/DOMHelpers.js';
const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance();
const {assert} = chai;
@@ -135,8 +136,7 @@ function treeNodeKeyText(node: HTMLLIElement) {
if (!keyNode) {
throw new Error('Found tree node without a key within it.');
}
// Trim to remove whitespace that made it into the DOM that we don't care about.
return (keyNode.textContent || '').trim();
return keyNode.getAttribute('data-node-key') || '';
}
function getVisibleTreeNodeByText(shadowRoot: ShadowRoot, text: string): HTMLLIElement {
@@ -307,6 +307,36 @@ describe('TreeOutline', () => {
]);
});
it('allows a node to have a custom renderer', async () => {
const tinyTree: UIComponents.TreeOutlineUtils.TreeNode[] = [{
key: 'Offices',
renderer: key => LitHtml.html`<h2 class="top-node">${key.toUpperCase()}</h2>`,
children: () => Promise.resolve([
{
key: 'EMEA',
},
{
key: 'USA',
},
{
key: 'APAC',
},
]),
}];
const {component, shadowRoot} = await renderTreeOutline({
tree: tinyTree,
});
await component.expandRecursively(Number.POSITIVE_INFINITY);
await coordinator.done();
const officeNode = getVisibleTreeNodeByText(shadowRoot, 'Offices');
const key = officeNode.querySelector('[data-node-key]');
assertElement(key, HTMLElement);
const renderedKey = stripLitHtmlCommentNodes(key.innerHTML);
assert.strictEqual(renderedKey, '<h2 class="top-node">OFFICES</h2>');
});
describe('navigating with keyboard', () => {
it('defaults to the first root node as active', async () => {
const {shadowRoot} = await renderTreeOutline({