mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-07 12:33:47 +08:00
[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:
committed by
Commit Bot
parent
b265fb501a
commit
fdf2a0cecd
@@ -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();
|
||||
});
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user