diff --git a/devtools/client/shared/components/devtools-components.css b/devtools/client/shared/components/devtools-components.css new file mode 100644 index 000000000000..47d6f03eb0dc --- /dev/null +++ b/devtools/client/shared/components/devtools-components.css @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +.tree { + overflow: auto; +} + +.tree.inline { + display: inline-block; +} + +.tree.nowrap { + white-space: nowrap; +} + +.tree.noselect { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.tree button { + display: block; +} + +.tree .tree-node { + display: flex; +} + +.tree-indent { + display: inline-block; + width: 12px; + margin-inline-start: 5px; + border-inline-start: 1px solid #A2D1FF; + flex-shrink: 0; +} + +/* Align with expandables siblings (where we have the arrow) */ +.tree-node[data-expandable="false"] .tree-indent:last-of-type { + margin-inline-end: 15px; +} + +.tree .tree-node[data-expandable="true"] { + cursor: default; +} + +.tree .tree-node:not(.focused):hover { + background-color: #F0F9FE; +} + +.tree .tree-node.focused { + color: white; + background-color: var(--theme-selection-background, #0a84ff); +} + +.tree-node.focused .arrow svg { + fill: currentColor; +} + +.arrow svg { + fill: var(--theme-splitter-color, #9B9B9B); + transition: transform 0.125s ease; + width: 10px; + margin-inline-end: 5px; + transform: rotate(-90deg); +} + +html[dir="rtl"] .arrow svg, +.arrow svg:dir(rtl), +.arrow svg:-moz-locale-dir(rtl) { + transform: rotate(90deg); +} + +.arrow.expanded.expanded svg { + transform: rotate(0deg); +} diff --git a/devtools/client/shared/components/devtools-components.js b/devtools/client/shared/components/devtools-components.js new file mode 100644 index 000000000000..907233fa2ba5 --- /dev/null +++ b/devtools/client/shared/components/devtools-components.js @@ -0,0 +1,1074 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("devtools/client/shared/vendor/react"), require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/react-prop-types")); + else if(typeof define === 'function' && define.amd) + define(["devtools/client/shared/vendor/react", "devtools/client/shared/vendor/react-dom-factories", "devtools/client/shared/vendor/react-prop-types"], factory); + else { + var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/react-prop-types")) : factory(root["devtools/client/shared/vendor/react"], root["devtools/client/shared/vendor/react-dom-factories"], root["devtools/client/shared/vendor/react-prop-types"]); + for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; + } +})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_1__) { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "/assets/build"; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 2); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_0__; + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_1__; + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _tree = __webpack_require__(3); + +var _tree2 = _interopRequireDefault(_tree); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +module.exports = { + Tree: _tree2.default +}; /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = __webpack_require__(0); + +var _react2 = _interopRequireDefault(_react); + +var _reactDomFactories = __webpack_require__(4); + +var _reactDomFactories2 = _interopRequireDefault(_reactDomFactories); + +var _propTypes = __webpack_require__(1); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _svgInlineReact = __webpack_require__(5); + +var _svgInlineReact2 = _interopRequireDefault(_svgInlineReact); + +var _arrow = __webpack_require__(7); + +var _arrow2 = _interopRequireDefault(_arrow); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const { Component, createFactory, createElement } = _react2.default; /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +__webpack_require__(8); + +const AUTO_EXPAND_DEPTH = 0; // depth + +/** + * An arrow that displays whether its node is expanded (▼) or collapsed + * (▶). When its node has no children, it is hidden. + */ +class ArrowExpander extends Component { + static get propTypes() { + return { + expanded: _propTypes2.default.bool + }; + } + + shouldComponentUpdate(nextProps, nextState) { + return this.props.expanded !== nextProps.expanded; + } + + render() { + const { + expanded + } = this.props; + + const classNames = ["arrow"]; + if (expanded) { + classNames.push("expanded"); + } + return createElement(_svgInlineReact2.default, { + className: classNames.join(" "), + src: _arrow2.default + }); + } +} + +const treeIndent = _reactDomFactories2.default.span({ className: "tree-indent" }, "\u200B"); + +class TreeNode extends Component { + static get propTypes() { + return { + id: _propTypes2.default.any.isRequired, + index: _propTypes2.default.number.isRequired, + depth: _propTypes2.default.number.isRequired, + focused: _propTypes2.default.bool.isRequired, + expanded: _propTypes2.default.bool.isRequired, + item: _propTypes2.default.any.isRequired, + isExpandable: _propTypes2.default.bool.isRequired, + onClick: _propTypes2.default.func, + renderItem: _propTypes2.default.func.isRequired + }; + } + + shouldComponentUpdate(nextProps) { + return this.props.item !== nextProps.item || this.props.focused !== nextProps.focused || this.props.expanded !== nextProps.expanded; + } + + render() { + const { + depth, + id, + item, + focused, + expanded, + renderItem, + isExpandable + } = this.props; + + const arrow = isExpandable ? ArrowExpanderFactory({ + item, + expanded + }) : null; + + let ariaExpanded; + if (this.props.isExpandable) { + ariaExpanded = false; + } + if (this.props.expanded) { + ariaExpanded = true; + } + + const indents = Array.from({ length: depth }).fill(treeIndent); + let items = indents.concat(renderItem(item, depth, focused, arrow, expanded)); + + return _reactDomFactories2.default.div({ + id, + className: "tree-node" + (focused ? " focused" : ""), + onClick: this.props.onClick, + role: "treeitem", + "aria-level": depth, + "aria-expanded": ariaExpanded, + "data-expandable": this.props.isExpandable + }, ...items); + } +} + +const ArrowExpanderFactory = createFactory(ArrowExpander); +const TreeNodeFactory = createFactory(TreeNode); + +/** + * Create a function that calls the given function `fn` only once per animation + * frame. + * + * @param {Function} fn + * @returns {Function} + */ +function oncePerAnimationFrame(fn) { + let animationId = null; + let argsToPass = null; + return function (...args) { + argsToPass = args; + if (animationId !== null) { + return; + } + + animationId = requestAnimationFrame(() => { + fn.call(this, ...argsToPass); + animationId = null; + argsToPass = null; + }); + }; +} + +/** + * A generic tree component. See propTypes for the public API. + * + * This tree component doesn't make any assumptions about the structure of your + * tree data. Whether children are computed on demand, or stored in an array in + * the parent's `_children` property, it doesn't matter. We only require the + * implementation of `getChildren`, `getRoots`, `getParent`, and `isExpanded` + * functions. + * + * This tree component is well tested and reliable. See the tests in ./tests + * and its usage in the performance and memory panels in mozilla-central. + * + * This tree component doesn't make any assumptions about how to render items in + * the tree. You provide a `renderItem` function, and this component will ensure + * that only those items whose parents are expanded and which are visible in the + * viewport are rendered. The `renderItem` function could render the items as a + * "traditional" tree or as rows in a table or anything else. It doesn't + * 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 which can be override in CSS. + * + * ### Example Usage + * + * Suppose we have some tree data where each item has this form: + * + * { + * id: Number, + * label: String, + * parent: Item or null, + * children: Array of child items, + * expanded: bool, + * } + * + * Here is how we could render that data with this component: + * + * class MyTree extends Component { + * static get propTypes() { + * // The root item of the tree, with the form described above. + * return { + * root: PropTypes.object.isRequired + * }; + * }, + * + * render() { + * return Tree({ + * itemHeight: 20, // px + * + * getRoots: () => [this.props.root], + * + * getParent: item => item.parent, + * getChildren: item => item.children, + * getKey: item => item.id, + * isExpanded: item => item.expanded, + * + * renderItem: (item, depth, isFocused, arrow, isExpanded) => { + * let className = "my-tree-item"; + * if (isFocused) { + * className += " focused"; + * } + * return dom.div({ + * className, + * }, + * arrow, + * // And here is the label for this item. + * dom.span({ className: "my-tree-item-label" }, item.label) + * ); + * }, + * + * onExpand: item => dispatchExpandActionToRedux(item), + * onCollapse: item => dispatchCollapseActionToRedux(item), + * }); + * } + * } + */ +class Tree extends Component { + static get propTypes() { + return { + // Required props + + // A function to get an item's parent, or null if it is a root. + // + // Type: getParent(item: Item) -> Maybe + // + // Example: + // + // // The parent of this item is stored in its `parent` property. + // getParent: item => item.parent + getParent: _propTypes2.default.func.isRequired, + + // A function to get an item's children. + // + // Type: getChildren(item: Item) -> [Item] + // + // Example: + // + // // This item's children are stored in its `children` property. + // getChildren: item => item.children + getChildren: _propTypes2.default.func.isRequired, + + // A function which takes an item and ArrowExpander component instance and + // returns a component, or text, or anything else that React considers + // renderable. + // + // Type: renderItem(item: Item, + // depth: Number, + // isFocused: Boolean, + // arrow: ReactComponent, + // isExpanded: Boolean) -> ReactRenderable + // + // Example: + // + // renderItem: (item, depth, isFocused, arrow, isExpanded) => { + // let className = "my-tree-item"; + // if (isFocused) { + // className += " focused"; + // } + // return dom.div( + // { + // className, + // style: { marginLeft: depth * 10 + "px" } + // }, + // arrow, + // dom.span({ className: "my-tree-item-label" }, item.label) + // ); + // }, + renderItem: _propTypes2.default.func.isRequired, + + // A function which returns the roots of the tree (forest). + // + // Type: getRoots() -> [Item] + // + // Example: + // + // // In this case, we only have one top level, root item. You could + // // return multiple items if you have many top level items in your + // // tree. + // getRoots: () => [this.props.rootOfMyTree] + getRoots: _propTypes2.default.func.isRequired, + + // A function to get a unique key for the given item. This helps speed up + // React's rendering a *TON*. + // + // Type: getKey(item: Item) -> String + // + // Example: + // + // getKey: item => `my-tree-item-${item.uniqueId}` + getKey: _propTypes2.default.func.isRequired, + + // A function to get whether an item is expanded or not. If an item is not + // expanded, then it must be collapsed. + // + // Type: isExpanded(item: Item) -> Boolean + // + // Example: + // + // isExpanded: item => item.expanded, + isExpanded: _propTypes2.default.func.isRequired, + + // Optional props + + // The currently focused item, if any such item exists. + focused: _propTypes2.default.any, + + // Handle when a new item is focused. + onFocus: _propTypes2.default.func, + + // The depth to which we should automatically expand new items. + autoExpandDepth: _propTypes2.default.number, + // Should auto expand all new items or just the new items under the first + // root item. + autoExpandAll: _propTypes2.default.bool, + + // Note: the two properties below are mutually exclusive. Only one of the + // label properties is necessary. + // ID of an element whose textual content serves as an accessible label for + // a tree. + labelledby: _propTypes2.default.string, + // Accessibility label for a tree widget. + label: _propTypes2.default.string, + + // Optional event handlers for when items are expanded or collapsed. Useful + // for dispatching redux events and updating application state, maybe lazily + // loading subtrees from a worker, etc. + // + // Type: + // onExpand(item: Item) + // onCollapse(item: Item) + // + // Example: + // + // onExpand: item => dispatchExpandActionToRedux(item) + onExpand: _propTypes2.default.func, + onCollapse: _propTypes2.default.func, + isExpandable: _propTypes2.default.func, + // Additional classes to add to the root element. + className: _propTypes2.default.string, + // style object to be applied to the root element. + style: _propTypes2.default.object + }; + } + + static get defaultProps() { + return { + autoExpandDepth: AUTO_EXPAND_DEPTH, + autoExpandAll: true + }; + } + + constructor(props) { + super(props); + + this.state = { + seen: new Set() + }; + + this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this); + this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this); + this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this); + this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this); + this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this); + + this._autoExpand = this._autoExpand.bind(this); + this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this); + this._dfs = this._dfs.bind(this); + this._dfsFromRoots = this._dfsFromRoots.bind(this); + this._focus = this._focus.bind(this); + this._scrollNodeIntoView = this._scrollNodeIntoView.bind(this); + this._onBlur = this._onBlur.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); + this._nodeIsExpandable = this._nodeIsExpandable.bind(this); + } + + componentDidMount() { + this._autoExpand(); + if (this.props.focused) { + this._scrollNodeIntoView(this.props.focused); + } + } + + componentWillReceiveProps(nextProps) { + this._autoExpand(); + } + + componentDidUpdate(prevProps, prevState) { + if (prevProps.focused !== this.props.focused) { + this._scrollNodeIntoView(this.props.focused); + } + } + + _autoExpand() { + if (!this.props.autoExpandDepth) { + return; + } + + // Automatically expand the first autoExpandDepth levels for new items. Do + // not use the usual DFS infrastructure because we don't want to ignore + // collapsed nodes. + const autoExpand = (item, currentDepth) => { + if (currentDepth >= this.props.autoExpandDepth || this.state.seen.has(item)) { + return; + } + + this.props.onExpand(item); + this.state.seen.add(item); + + const children = this.props.getChildren(item); + const length = children.length; + for (let i = 0; i < length; i++) { + autoExpand(children[i], currentDepth + 1); + } + }; + + const roots = this.props.getRoots(); + const length = roots.length; + if (this.props.autoExpandAll) { + for (let i = 0; i < length; i++) { + autoExpand(roots[i], 0); + } + } else if (length != 0) { + autoExpand(roots[0], 0); + } + } + + _preventArrowKeyScrolling(e) { + switch (e.key) { + case "ArrowUp": + case "ArrowDown": + case "ArrowLeft": + case "ArrowRight": + e.preventDefault(); + e.stopPropagation(); + if (e.nativeEvent) { + if (e.nativeEvent.preventDefault) { + e.nativeEvent.preventDefault(); + } + if (e.nativeEvent.stopPropagation) { + e.nativeEvent.stopPropagation(); + } + } + } + } + + /** + * Perform a pre-order depth-first search from item. + */ + _dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) { + traversal.push({ item, depth: _depth }); + + if (!this.props.isExpanded(item)) { + return traversal; + } + + const nextDepth = _depth + 1; + + if (nextDepth > maxDepth) { + return traversal; + } + + const children = this.props.getChildren(item); + const length = children.length; + for (let i = 0; i < length; i++) { + this._dfs(children[i], maxDepth, traversal, nextDepth); + } + + return traversal; + } + + /** + * Perform a pre-order depth-first search over the whole forest. + */ + _dfsFromRoots(maxDepth = Infinity) { + const traversal = []; + + const roots = this.props.getRoots(); + const length = roots.length; + for (let i = 0; i < length; i++) { + this._dfs(roots[i], maxDepth, traversal); + } + + return traversal; + } + + /** + * Expands current row. + * + * @param {Object} item + * @param {Boolean} expandAllChildren + */ + _onExpand(item, expandAllChildren) { + if (this.props.onExpand) { + this.props.onExpand(item); + + if (expandAllChildren) { + const children = this._dfs(item); + const length = children.length; + for (let i = 0; i < length; i++) { + this.props.onExpand(children[i].item); + } + } + } + } + + /** + * Collapses current row. + * + * @param {Object} item + */ + _onCollapse(item) { + if (this.props.onCollapse) { + this.props.onCollapse(item); + } + } + + /** + * Sets the passed in item to be the focused item. + * + * @param {Object|undefined} item + * The item to be focused, or undefined to focus no item. + * + * @param {Object|undefined} options + * An options object which can contain: + * - dir: "up" or "down" to indicate if we should scroll the element to the + * top or the bottom of the scrollable container when the element is + * off canvas. + */ + _focus(item, options) { + this._scrollNodeIntoView(item, options); + if (this.props.onFocus) { + this.props.onFocus(item); + } + } + + /** + * Sets the passed in item to be the focused item. + * + * @param {Object|undefined} item + * The item to be scrolled to. + * + * @param {Object|undefined} options + * An options object which can contain: + * - dir: "up" or "down" to indicate if we should scroll the element to the + * top or the bottom of the scrollable container when the element is + * off canvas. + */ + _scrollNodeIntoView(item, options = {}) { + if (item !== undefined) { + const treeElement = this.treeRef; + const element = document.getElementById(this.props.getKey(item)); + if (element) { + const { top, bottom } = element.getBoundingClientRect(); + const closestScrolledParent = node => { + if (node == null) { + return null; + } + + if (node.scrollHeight > node.clientHeight) { + return node; + } + return closestScrolledParent(node.parentNode); + }; + const scrolledParent = closestScrolledParent(treeElement); + const isVisible = !scrolledParent || top >= 0 && bottom <= scrolledParent.clientHeight; + + if (!isVisible) { + let scrollToTop = !options.alignTo && top < 0 || options.alignTo === "top"; + element.scrollIntoView(scrollToTop); + } + } + } + } + + /** + * Sets the state to have no focused item. + */ + _onBlur() { + this._focus(undefined); + } + + /** + * Handles key down events in the tree's container. + * + * @param {Event} e + */ + _onKeyDown(e) { + if (this.props.focused == null) { + return; + } + + // Allow parent nodes to use navigation arrows with modifiers. + if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { + return; + } + + this._preventArrowKeyScrolling(e); + + switch (e.key) { + case "ArrowUp": + this._focusPrevNode(); + return; + + case "ArrowDown": + this._focusNextNode(); + return; + + case "ArrowLeft": + if (this.props.isExpanded(this.props.focused) && this._nodeIsExpandable(this.props.focused)) { + this._onCollapse(this.props.focused); + } else { + this._focusParentNode(); + } + return; + + case "ArrowRight": + if (this._nodeIsExpandable(this.props.focused) && !this.props.isExpanded(this.props.focused)) { + this._onExpand(this.props.focused); + } else { + this._focusNextNode(); + } + } + } + + /** + * Sets the previous node relative to the currently focused item, to focused. + */ + _focusPrevNode() { + // Start a depth first search and keep going until we reach the currently + // focused node. Focus the previous node in the DFS, if it exists. If it + // doesn't exist, we're at the first node already. + + let prev; + + const traversal = this._dfsFromRoots(); + const length = traversal.length; + for (let i = 0; i < length; i++) { + const item = traversal[i].item; + if (item === this.props.focused) { + break; + } + prev = item; + } + if (prev === undefined) { + return; + } + + this._focus(prev, { alignTo: "top" }); + } + + /** + * Handles the down arrow key which will focus either the next child + * or sibling row. + */ + _focusNextNode() { + // Start a depth first search and keep going until we reach the currently + // focused node. Focus the next node in the DFS, if it exists. If it + // doesn't exist, we're at the last node already. + const traversal = this._dfsFromRoots(); + const length = traversal.length; + let i = 0; + + while (i < length) { + if (traversal[i].item === this.props.focused) { + break; + } + i++; + } + + if (i + 1 < traversal.length) { + this._focus(traversal[i + 1].item, { alignTo: "bottom" }); + } + } + + /** + * Handles the left arrow key, going back up to the current rows' + * parent row. + */ + _focusParentNode() { + const parent = this.props.getParent(this.props.focused); + if (!parent) { + this._focusPrevNode(this.props.focused); + return; + } + + const traversal = this._dfsFromRoots(); + const length = traversal.length; + let parentIndex = 0; + for (; parentIndex < length; parentIndex++) { + if (traversal[parentIndex].item === parent) { + break; + } + } + + this._focus(parent, { alignTo: "top" }); + } + + _nodeIsExpandable(item) { + return this.props.isExpandable ? this.props.isExpandable(item) : !!this.props.getChildren(item).length; + } + + render() { + const traversal = this._dfsFromRoots(); + const { + focused + } = this.props; + + const nodes = traversal.map((v, i) => { + const { item, depth } = traversal[i]; + const key = this.props.getKey(item, i); + return TreeNodeFactory({ + key, + id: key, + index: i, + item, + depth, + renderItem: this.props.renderItem, + focused: focused === item, + expanded: this.props.isExpanded(item), + isExpandable: this._nodeIsExpandable(item), + onExpand: this._onExpand, + onCollapse: this._onCollapse, + onClick: e => { + this._focus(item); + if (this.props.isExpanded(item)) { + this.props.onCollapse(item); + } else { + this.props.onExpand(item, e.altKey); + } + } + }); + }); + + const style = Object.assign({}, this.props.style || {}, { + padding: 0, + margin: 0 + }); + + return _reactDomFactories2.default.div({ + className: `tree ${this.props.className ? this.props.className : ""}`, + ref: el => { + this.treeRef = el; + }, + role: "tree", + tabIndex: "0", + onKeyDown: this._onKeyDown, + onKeyPress: this._preventArrowKeyScrolling, + onKeyUp: this._preventArrowKeyScrolling, + onFocus: ({ nativeEvent }) => { + if (focused || !nativeEvent || !this.treeRef) { + return; + } + + let { explicitOriginalTarget } = nativeEvent; + // Only set default focus to the first tree node if the focus came + // from outside the tree (e.g. by tabbing to the tree from other + // external elements). + if (explicitOriginalTarget !== this.treeRef && !this.treeRef.contains(explicitOriginalTarget)) { + this._focus(traversal[0].item); + } + }, + onBlur: this._onBlur, + onClick: () => { + // Focus should always remain on the tree container itself. + this.treeRef.focus(); + }, + "aria-label": this.props.label, + "aria-labelledby": this.props.labelledby, + "aria-activedescendant": focused && this.props.getKey(focused), + style + }, nodes); + } +} + +exports.default = Tree; + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_4__; + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _react = __webpack_require__(0); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = __webpack_require__(1); + +var _util = __webpack_require__(6); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var process = process || { env: {} }; + +var InlineSVG = function (_React$Component) { + _inherits(InlineSVG, _React$Component); + + function InlineSVG() { + _classCallCheck(this, InlineSVG); + + return _possibleConstructorReturn(this, (InlineSVG.__proto__ || Object.getPrototypeOf(InlineSVG)).apply(this, arguments)); + } + + _createClass(InlineSVG, [{ + key: 'componentWillReceiveProps', + value: function componentWillReceiveProps(_ref) { + var children = _ref.children; + + if ("production" !== process.env.NODE_ENV && children != null) { + console.info(': `children` prop will be ignored.'); + } + } + }, { + key: 'render', + value: function render() { + var Element = void 0, + __html = void 0, + svgProps = void 0; + + var _props = this.props, + element = _props.element, + raw = _props.raw, + src = _props.src, + otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']); + + if (raw === true) { + Element = 'svg'; + svgProps = (0, _util.extractSVGProps)(src); + __html = (0, _util.getSVGFromSource)(src).innerHTML; + } + __html = __html || src; + Element = Element || element; + svgProps = svgProps || {}; + + return _react2.default.createElement(Element, _extends({}, svgProps, otherProps, { src: null, children: null, + dangerouslySetInnerHTML: { __html: __html } })); + } + }]); + + return InlineSVG; +}(_react2.default.Component); + +exports.default = InlineSVG; + + +InlineSVG.defaultProps = { + element: 'i', + raw: false, + src: '' +}; + +InlineSVG.propTypes = { + src: _propTypes.string.isRequired, + element: _propTypes.string, + raw: _propTypes.bool +}; + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.convertReactSVGDOMProperty = convertReactSVGDOMProperty; +exports.startsWith = startsWith; +exports.serializeAttrs = serializeAttrs; +exports.getSVGFromSource = getSVGFromSource; +exports.extractSVGProps = extractSVGProps; +// Transform DOM prop/attr names applicable to `` element but react-limited + +function convertReactSVGDOMProperty(str) { + return str.replace(/[-|:]([a-z])/g, function (g) { + return g[1].toUpperCase(); + }); +} + +function startsWith(str, substring) { + return str.indexOf(substring) === 0; +} + +var DataPropPrefix = 'data-'; +// Serialize `Attr` objects in `NamedNodeMap` +function serializeAttrs(map) { + var ret = {}; + for (var prop, i = 0; i < map.length; i++) { + var key = map[i].name; + if (!startsWith(key, DataPropPrefix)) { + prop = convertReactSVGDOMProperty(key); + } + ret[prop] = map[i].value; + } + return ret; +} + +function getSVGFromSource(src) { + var svgContainer = document.createElement('div'); + svgContainer.innerHTML = src; + var svg = svgContainer.firstElementChild; + svg.remove(); // deref from parent element + return svg; +} + +// get element props +function extractSVGProps(src) { + var map = getSVGFromSource(src).attributes; + return map.length > 0 ? serializeAttrs(map) : null; +} + +/***/ }), +/* 7 */ +/***/ (function(module, exports) { + +module.exports = "" + +/***/ }), +/* 8 */ +/***/ (function(module, exports) { + +// removed by extract-text-webpack-plugin + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/devtools/client/shared/components/moz.build b/devtools/client/shared/components/moz.build index 4bd59c9b07b2..ff6a60dbd220 100644 --- a/devtools/client/shared/components/moz.build +++ b/devtools/client/shared/components/moz.build @@ -13,6 +13,8 @@ DIRS += [ DevToolsModules( 'AutoCompletePopup.js', + 'devtools-components.css', + 'devtools-components.js', 'Frame.js', 'HSplitBox.js', 'NotificationBox.css',