diff --git a/packages/devtools-components/src/tests/tree.js b/packages/devtools-components/src/tests/tree.js index e0186e1df..5710ad0c3 100644 --- a/packages/devtools-components/src/tests/tree.js +++ b/packages/devtools-components/src/tests/tree.js @@ -368,7 +368,7 @@ describe("Tree", () => { expect(formatTree(wrapper)).toMatchSnapshot(); getTreeNodes(wrapper).forEach(n => { - if ("ABECDMN".split("").includes(n.text())) { + if ("ABECDMN".split("").includes(getSanitizedNodeText(n))) { expect(n.find(".arrow.expanded").exists()).toBe(true); } else { expect(n.find(".arrow").exists()).toBe(false); @@ -446,14 +446,18 @@ function formatTree(wrapper) { let arrowStr = " "; if (arrow.exists()) { arrowStr = arrow.hasClass("expanded") ? "▼ " : "▶︎ "; - } else { - arrowStr = " "; } - return `${indentStr}${arrowStr}${node.text()}`; + + return `${indentStr}${arrowStr}${getSanitizedNodeText(node)}`; }) .join("\n"); } +function getSanitizedNodeText(node) { + // Stripping off the invisible space used in the indent. + return node.text().replace(/^\u200B+/, ""); +} + // Encoding of the following tree/forest: // // A diff --git a/packages/devtools-components/src/tree.css b/packages/devtools-components/src/tree.css index 92939b7b0..6605d5bcc 100644 --- a/packages/devtools-components/src/tree.css +++ b/packages/devtools-components/src/tree.css @@ -3,16 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ .tree { - --arrow-width: 10px; - --arrow-single-margin: 5px; - --arrow-total-width: calc(var(--arrow-width) + var(--arrow-single-margin)); - --arrow-fill-color: var(--theme-splitter-color, #9B9B9B); - --tree-indent-width: 1em; - --tree-indent-border-color: #A2D1FF; - --tree-indent-border-width: 1px; - --tree-node-hover-background-color: #F0F9FE; - --tree-node-focus-color: white; - --tree-node-focus-background-color: var(--theme-selection-background, #0a84ff); overflow: auto; } @@ -36,25 +26,35 @@ display: block; } +.tree-indent { + display: inline-block; + width: 12px; + margin-inline-start: 5px; + border-inline-start: 1px solid #A2D1FF; +} + .tree .tree-node[data-expandable="true"] { cursor: default; } .tree .tree-node:not(.focused):hover { - background-color: var(--tree-node-hover-background-color); + background-color: #F0F9FE; } .tree .tree-node.focused { - color: var(--tree-node-focus-color); - background-color: var(--tree-node-focus-background-color); - --arrow-fill-color: currentColor; + color: white; + background-color: var(--theme-selection-background, #0a84ff); +} + +.tree-node.focused .arrow svg { + fill: currentColor; } .arrow svg { - fill: var(--arrow-fill-color); + fill: var(--theme-splitter-color, #9B9B9B); transition: transform 0.125s ease; - width: var(--arrow-width); - margin-inline-end: var(--arrow-single-margin); + width: 10px; + margin-inline-end: 5px; transform: rotate(-90deg); } diff --git a/packages/devtools-components/src/tree.js b/packages/devtools-components/src/tree.js index 1c44652e0..11c60026f 100644 --- a/packages/devtools-components/src/tree.js +++ b/packages/devtools-components/src/tree.js @@ -44,6 +44,8 @@ class ArrowExpander extends Component { } } +const treeIndent = dom.span({className: "tree-indent"}, "\u200B"); + class TreeNode extends Component { static get propTypes() { return { @@ -83,38 +85,6 @@ class TreeNode extends Component { }) : null; - const treeIndentWidthVar = "var(--tree-indent-width)"; - const treeBorderColorVar = "var(--tree-indent-border-color, black)"; - const treeBorderWidthVar = "var(--tree-indent-border-width, 1px)"; - - const paddingInlineStart = `calc( - (${treeIndentWidthVar} * ${depth}) - ${(isExpandable ? "" : "+ var(--arrow-total-width)")} - )`; - - // This is the computed border that will mimic a border on tree nodes. - // This allow us to have as many "borders" as we need without adding - // specific elements for that purpose only. - // it's a gradient with "hard stops" which will give us as much plain - // lines as we need given the depth of the node. - // The gradient uses CSS custom properties so everything is customizable - // by consumers if needed. - const backgroundBorder = depth === 0 - ? null - : "linear-gradient(90deg, " + - Array.from({length: depth}).map((_, i) => { - const indentWidth = `(${i} * ${treeIndentWidthVar})`; - const alignIndent = `(var(--arrow-width) / 2)`; - const start = `calc(${indentWidth} + ${alignIndent})`; - const end = `calc(${indentWidth} + ${alignIndent} + ${treeBorderWidthVar})`; - - return `transparent ${start}, - ${treeBorderColorVar} ${start}, - ${treeBorderColorVar} ${end}, - transparent ${end}`; - }).join(",") + - ")"; - let ariaExpanded; if (this.props.isExpandable) { ariaExpanded = false; @@ -123,21 +93,20 @@ class TreeNode extends Component { ariaExpanded = true; } + const indents = Array.from({length: depth}).fill(treeIndent); + let items = indents.concat(renderItem(item, depth, focused, arrow, expanded)); + return dom.div( { id, className: "tree-node" + (focused ? " focused" : ""), - style: { - paddingInlineStart, - backgroundImage: backgroundBorder, - }, onClick: this.props.onClick, role: "treeitem", "aria-level": depth, "aria-expanded": ariaExpanded, - "data-expandable": this.props.isExpandable, + "data-expandable": this.props.isExpandable }, - renderItem(item, depth, focused, arrow, expanded) + ...items ); } } @@ -189,19 +158,7 @@ function oncePerAnimationFrame(fn) { * restrict you to only one certain kind of tree. * * The tree comes with basic styling for the indent, the arrow, as well as hovered - * and focused styles. - * All of this can be customize on the customer end, by overriding the following - * CSS custom properties : - * --arrow-width: the width of the arrow. - * --arrow-single-margin: the end margin between the arrow and the item that follows. - * --arrow-fill-color: the fill-color of the arrow. - * --tree-indent-width: the width of a 1-level-deep item. - * --tree-indent-border-color: the color of the indent border. - * --tree-indent-border-width: the width of the indent border. - * --tree-node-hover-background-color: the background color of a hovered node. - * --tree-node-focus-color: the color of a focused node. - * --tree-node-focus-background-color: the background color of a focused node. - * + * and focused styles which can be override in CSS. * * ### Example Usage * diff --git a/packages/devtools-components/stories/tree.js b/packages/devtools-components/stories/tree.js index 6cdcd8e58..366d6f8d9 100644 --- a/packages/devtools-components/stories/tree.js +++ b/packages/devtools-components/stories/tree.js @@ -4,7 +4,6 @@ import React from "react"; const { Component, createFactory, createElement } = React; -import dom from "react-dom-factories"; import Components from "../index"; const Tree = createFactory(Components.Tree); @@ -85,6 +84,15 @@ storiesOf("Tree", module) getRoots: () => nodes, }, {}); }) + .add("1000 items tree", () => { + const nodes = Array.from({length: 1000}).map((_, i) => `item-${i + 1}`); + return renderTree({ + getRoots: () => ["ROOT"], + expanded: new Set() + }, { + children: {"ROOT": nodes} + }); + }) .add("30,000 items tree", () => { const nodes = Array.from({length: 1000}).map((_, i) => `item-${i + 1}`); return renderTree({ @@ -185,13 +193,9 @@ function createTreeElement(props, context, tree) { getChildren: x => tree.children && tree.children[x] ? tree.children[x] : [], - renderItem: (x, depth, focused, arrow, expanded) => dom.div({}, - arrow, - x, - ), + renderItem: (x, depth, focused, arrow, expanded) => [arrow, x], getRoots: () => ["A"], getKey: x => "key-" + x, - itemHeight: 1, onFocus: x => { context.setState(previousState => { return {focused: x};