summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/flexbox/components
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/inspector/flexbox/components')
-rw-r--r--devtools/client/inspector/flexbox/components/FlexContainer.js122
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItem.js60
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemList.js62
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemSelector.js81
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js173
-rw-r--r--devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js326
-rw-r--r--devtools/client/inspector/flexbox/components/Flexbox.js128
-rw-r--r--devtools/client/inspector/flexbox/components/Header.js142
-rw-r--r--devtools/client/inspector/flexbox/components/moz.build16
9 files changed, 1110 insertions, 0 deletions
diff --git a/devtools/client/inspector/flexbox/components/FlexContainer.js b/devtools/client/inspector/flexbox/components/FlexContainer.js
new file mode 100644
index 0000000000..94b3ae123d
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexContainer.js
@@ -0,0 +1,122 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ createElement,
+ createRef,
+ Fragment,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+loader.lazyRequireGetter(
+ this,
+ "getNodeRep",
+ "resource://devtools/client/inspector/shared/node-reps.js"
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const {
+ highlightNode,
+ unhighlightNode,
+} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js");
+
+class FlexContainer extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ color: PropTypes.string.isRequired,
+ flexContainer: PropTypes.shape(Types.flexContainer).isRequired,
+ getSwatchColorPickerTooltip: PropTypes.func.isRequired,
+ onSetFlexboxOverlayColor: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.swatchEl = createRef();
+
+ this.setFlexboxColor = this.setFlexboxColor.bind(this);
+ }
+
+ componentDidMount() {
+ const tooltip = this.props.getSwatchColorPickerTooltip();
+
+ let previousColor;
+ tooltip.addSwatch(this.swatchEl.current, {
+ onCommit: this.setFlexboxColor,
+ onPreview: this.setFlexboxColor,
+ onRevert: () => {
+ this.props.onSetFlexboxOverlayColor(previousColor);
+ },
+ onShow: () => {
+ previousColor = this.props.color;
+ },
+ });
+ }
+
+ componentWillUnmount() {
+ const tooltip = this.props.getSwatchColorPickerTooltip();
+ tooltip.removeSwatch(this.swatchEl.current);
+ }
+
+ setFlexboxColor() {
+ const color = this.swatchEl.current.dataset.color;
+ this.props.onSetFlexboxOverlayColor(color);
+ }
+
+ render() {
+ const { color, flexContainer, dispatch } = this.props;
+ const { nodeFront, properties } = flexContainer;
+
+ return createElement(
+ Fragment,
+ null,
+ dom.div(
+ {
+ className: "flex-header-container-label",
+ },
+ getNodeRep(nodeFront, {
+ onDOMNodeMouseOut: () => dispatch(unhighlightNode()),
+ onDOMNodeMouseOver: () => dispatch(highlightNode(nodeFront)),
+ }),
+ dom.div({
+ className: "layout-color-swatch",
+ "data-color": color,
+ ref: this.swatchEl,
+ style: {
+ backgroundColor: color,
+ },
+ title: color,
+ })
+ ),
+ dom.div(
+ { className: "flex-header-container-properties" },
+ dom.div(
+ {
+ className: "inspector-badge",
+ role: "figure",
+ title: `flex-direction: ${properties["flex-direction"]}`,
+ },
+ properties["flex-direction"]
+ ),
+ dom.div(
+ {
+ className: "inspector-badge",
+ role: "figure",
+ title: `flex-wrap: ${properties["flex-wrap"]}`,
+ },
+ properties["flex-wrap"]
+ )
+ )
+ );
+ }
+}
+
+module.exports = FlexContainer;
diff --git a/devtools/client/inspector/flexbox/components/FlexItem.js b/devtools/client/inspector/flexbox/components/FlexItem.js
new file mode 100644
index 0000000000..b29da8243e
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItem.js
@@ -0,0 +1,60 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+loader.lazyRequireGetter(
+ this,
+ "getNodeRep",
+ "resource://devtools/client/inspector/shared/node-reps.js"
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const {
+ highlightNode,
+ unhighlightNode,
+} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js");
+
+class FlexItem extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ index: PropTypes.number.isRequired,
+ scrollToTop: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ render() {
+ const { dispatch, flexItem, index, scrollToTop, setSelectedNode } =
+ this.props;
+ const { nodeFront } = flexItem;
+
+ return dom.button(
+ {
+ className: "devtools-button devtools-monospace",
+ onClick: e => {
+ e.stopPropagation();
+ scrollToTop();
+ setSelectedNode(nodeFront);
+ dispatch(unhighlightNode());
+ },
+ onMouseOut: () => dispatch(unhighlightNode()),
+ onMouseOver: () => dispatch(highlightNode(nodeFront)),
+ },
+ dom.span({ className: "flex-item-index" }, index),
+ getNodeRep(nodeFront)
+ );
+ }
+}
+
+module.exports = FlexItem;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemList.js b/devtools/client/inspector/flexbox/components/FlexItemList.js
new file mode 100644
index 0000000000..0824ee1175
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemList.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+const FlexItem = createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItem.js")
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+class FlexItemList extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired,
+ scrollToTop: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ render() {
+ const { dispatch, flexItems, scrollToTop, setSelectedNode } = this.props;
+
+ return dom.div(
+ { className: "flex-item-list" },
+ dom.div(
+ {
+ className: "flex-item-list-header",
+ role: "heading",
+ "aria-level": "3",
+ },
+ !flexItems.length
+ ? getStr("flexbox.noFlexItems")
+ : getStr("flexbox.flexItems")
+ ),
+ flexItems.map((flexItem, index) =>
+ FlexItem({
+ key: flexItem.actorID,
+ dispatch,
+ flexItem,
+ index: index + 1,
+ scrollToTop,
+ setSelectedNode,
+ })
+ )
+ );
+ }
+}
+
+module.exports = FlexItemList;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemSelector.js b/devtools/client/inspector/flexbox/components/FlexItemSelector.js
new file mode 100644
index 0000000000..d8dd2db7a3
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemSelector.js
@@ -0,0 +1,81 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getSelectorFromGrip,
+ translateNodeFrontToGrip,
+} = require("resource://devtools/client/inspector/shared/utils.js");
+
+loader.lazyRequireGetter(
+ this,
+ "getNodeRep",
+ "resource://devtools/client/inspector/shared/node-reps.js"
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+loader.lazyRequireGetter(
+ this,
+ "showMenu",
+ "resource://devtools/client/shared/components/menu/utils.js",
+ true
+);
+
+class FlexItemSelector extends PureComponent {
+ static get propTypes() {
+ return {
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+ this.onShowFlexItemMenu = this.onShowFlexItemMenu.bind(this);
+ }
+
+ onShowFlexItemMenu(event) {
+ event.stopPropagation();
+
+ const { flexItem, flexItems, setSelectedNode } = this.props;
+ const menuItems = [];
+
+ for (const item of flexItems) {
+ const grip = translateNodeFrontToGrip(item.nodeFront);
+ menuItems.push({
+ label: getSelectorFromGrip(grip),
+ type: "checkbox",
+ checked: item === flexItem,
+ click: () => setSelectedNode(item.nodeFront),
+ });
+ }
+
+ showMenu(menuItems, {
+ button: event.target,
+ });
+ }
+
+ render() {
+ const { flexItem } = this.props;
+
+ return dom.button(
+ {
+ id: "flex-item-selector",
+ className: "devtools-button devtools-dropdown-button",
+ onClick: this.onShowFlexItemMenu,
+ },
+ getNodeRep(flexItem.nodeFront)
+ );
+ }
+}
+
+module.exports = FlexItemSelector;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
new file mode 100644
index 0000000000..f0c35cd6be
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
@@ -0,0 +1,173 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+class FlexItemSizingOutline extends PureComponent {
+ static get propTypes() {
+ return {
+ flexDirection: PropTypes.string.isRequired,
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ };
+ }
+
+ renderBasisOutline(mainBaseSize) {
+ return dom.div({
+ className: "flex-outline-basis" + (!mainBaseSize ? " zero-basis" : ""),
+ });
+ }
+
+ renderDeltaOutline(mainDeltaSize) {
+ if (!mainDeltaSize) {
+ return null;
+ }
+
+ return dom.div({
+ className: "flex-outline-delta",
+ });
+ }
+
+ renderFinalOutline(isClamped) {
+ return dom.div({
+ className: "flex-outline-final" + (isClamped ? " clamped" : ""),
+ });
+ }
+
+ renderPoint(className, label = className) {
+ return dom.div({
+ key: className,
+ className: `flex-outline-point ${className}`,
+ "data-label": label,
+ });
+ }
+
+ render() {
+ const { flexItemSizing } = this.props.flexItem;
+ const {
+ mainAxisDirection,
+ mainBaseSize,
+ mainDeltaSize,
+ mainMaxSize,
+ mainMinSize,
+ clampState,
+ } = flexItemSizing;
+
+ // Calculate the final size. This is base + delta, then clamped by min or max.
+ let mainFinalSize = mainBaseSize + mainDeltaSize;
+ mainFinalSize = Math.max(mainFinalSize, mainMinSize);
+ mainFinalSize =
+ mainMaxSize === null
+ ? mainFinalSize
+ : Math.min(mainFinalSize, mainMaxSize);
+
+ // Just don't display anything if there isn't anything useful.
+ if (!mainFinalSize && !mainBaseSize && !mainDeltaSize) {
+ return null;
+ }
+
+ // The max size is only interesting to show if it did clamp the item.
+ const showMax = clampState === "clamped_to_max";
+
+ // The min size is only really interesting if it actually clamped the item.
+ // TODO: We might also want to check if the min-size property is defined.
+ const showMin = clampState === "clamped_to_min";
+
+ // Create an array of all of the sizes we want to display that we can use to create
+ // a grid track template.
+ let sizes = [
+ { name: "basis-start", size: 0 },
+ { name: "basis-end", size: mainBaseSize },
+ { name: "final-start", size: 0 },
+ { name: "final-end", size: mainFinalSize },
+ ];
+
+ // Because mainDeltaSize is just a delta from base, make sure to make it absolute like
+ // the other sizes in the array, so we can later sort all sizes in the same way.
+ if (mainDeltaSize > 0) {
+ sizes.push({ name: "delta-start", size: mainBaseSize });
+ sizes.push({ name: "delta-end", size: mainBaseSize + mainDeltaSize });
+ } else {
+ sizes.push({ name: "delta-start", size: mainBaseSize + mainDeltaSize });
+ sizes.push({ name: "delta-end", size: mainBaseSize });
+ }
+
+ if (showMax) {
+ sizes.push({ name: "max", size: mainMaxSize });
+ }
+ if (showMin) {
+ sizes.push({ name: "min", size: mainMinSize });
+ }
+
+ // Sort all of the dimensions so we can create the grid track template correctly.
+ sizes = sizes.sort((a, b) => a.size - b.size);
+
+ // In some cases, the delta-start may be negative (when an item wanted to shrink more
+ // than the item's base size). As a negative value would break the grid track template
+ // offset all values so they're all positive.
+ const offsetBy = sizes.reduce(
+ (acc, curr) => (curr.size < acc ? curr.size : acc),
+ 0
+ );
+ sizes = sizes.map(entry => ({
+ size: entry.size - offsetBy,
+ name: entry.name,
+ }));
+
+ let gridTemplateColumns = "[";
+ let accumulatedSize = 0;
+ for (const { name, size } of sizes) {
+ const breadth = Math.round(size - accumulatedSize);
+ if (breadth === 0) {
+ gridTemplateColumns += ` ${name}`;
+ continue;
+ }
+
+ gridTemplateColumns += `] ${breadth}fr [${name}`;
+ accumulatedSize = size;
+ }
+ gridTemplateColumns += "]";
+
+ // Check the final and basis points to see if they are the same and if so, combine
+ // them into a single rendered point.
+ const renderedBaseAndFinalPoints = [];
+ if (mainFinalSize === mainBaseSize) {
+ renderedBaseAndFinalPoints.push(
+ this.renderPoint("basisfinal", "basis/final")
+ );
+ } else {
+ renderedBaseAndFinalPoints.push(this.renderPoint("basis"));
+ renderedBaseAndFinalPoints.push(this.renderPoint("final"));
+ }
+
+ return dom.div(
+ { className: "flex-outline-container" },
+ dom.div(
+ {
+ className:
+ `flex-outline ${mainAxisDirection}` +
+ (mainDeltaSize > 0 ? " growing" : " shrinking"),
+ style: {
+ gridTemplateColumns,
+ },
+ },
+ renderedBaseAndFinalPoints,
+ showMin ? this.renderPoint("min") : null,
+ showMax ? this.renderPoint("max") : null,
+ this.renderBasisOutline(mainBaseSize),
+ this.renderDeltaOutline(mainDeltaSize),
+ this.renderFinalOutline(clampState !== "unclamped")
+ )
+ );
+ }
+}
+
+module.exports = FlexItemSizingOutline;
diff --git a/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
new file mode 100644
index 0000000000..00bea31e57
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
@@ -0,0 +1,326 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const getFlexibilityReasons = ({
+ lineGrowthState,
+ computedFlexGrow,
+ computedFlexShrink,
+ grew,
+ shrank,
+}) => {
+ const reasons = [];
+
+ // Tell users whether the item was set to grow or shrink.
+ if (computedFlexGrow && lineGrowthState === "growing") {
+ reasons.push(getStr("flexbox.itemSizing.setToGrow"));
+ }
+ if (computedFlexShrink && lineGrowthState === "shrinking") {
+ reasons.push(getStr("flexbox.itemSizing.setToShrink"));
+ }
+ if (!computedFlexGrow && !grew && !shrank && lineGrowthState === "growing") {
+ reasons.push(getStr("flexbox.itemSizing.notSetToGrow"));
+ }
+ if (
+ !computedFlexShrink &&
+ !grew &&
+ !shrank &&
+ lineGrowthState === "shrinking"
+ ) {
+ reasons.push(getStr("flexbox.itemSizing.notSetToShrink"));
+ }
+
+ return reasons;
+};
+
+class FlexItemSizingProperties extends PureComponent {
+ static get propTypes() {
+ return {
+ flexDirection: PropTypes.string.isRequired,
+ flexItem: PropTypes.shape(Types.flexItem).isRequired,
+ };
+ }
+
+ /**
+ * Rounds some size in pixels and render it.
+ * The rendered value will end with 'px' (unless the dimension is 0 in which case the
+ * unit will be omitted)
+ *
+ * @param {Number} value
+ * The number to be rounded
+ * @param {Boolean} prependPlusSign
+ * If set to true, the + sign will be printed before a positive value
+ * @return {Object}
+ * The React component representing this rounded size
+ */
+ renderSize(value, prependPlusSign) {
+ if (value == 0) {
+ return dom.span({ className: "value" }, "0");
+ }
+
+ value = Math.round(value * 100) / 100;
+ if (prependPlusSign && value > 0) {
+ value = "+" + value;
+ }
+
+ return dom.span(
+ { className: "value" },
+ value,
+ dom.span({ className: "unit" }, "px")
+ );
+ }
+
+ /**
+ * Render an authored CSS property.
+ *
+ * @param {String} name
+ * The name for this CSS property
+ * @param {String} value
+ * The property value
+ * @param {Booleam} isDefaultValue
+ * Whether the value come from the browser default style
+ * @return {Object}
+ * The React component representing this CSS property
+ */
+ renderCssProperty(name, value, isDefaultValue) {
+ return dom.span({ className: "css-property-link" }, `(${name}: ${value})`);
+ }
+
+ /**
+ * Render a list of sentences to be displayed in the UI as reasons why a certain sizing
+ * value happened.
+ *
+ * @param {Array} sentences
+ * The list of sentences as Strings
+ * @return {Object}
+ * The React component representing these sentences
+ */
+ renderReasons(sentences) {
+ return dom.ul(
+ { className: "reasons" },
+ sentences.map(sentence => dom.li({}, sentence))
+ );
+ }
+
+ renderBaseSizeSection({ mainBaseSize, clampState }, properties, dimension) {
+ const flexBasisValue = properties["flex-basis"];
+ const dimensionValue = properties[dimension];
+
+ let title = getStr("flexbox.itemSizing.baseSizeSectionHeader");
+ let property = null;
+
+ if (flexBasisValue) {
+ // If flex-basis is defined, then that's what is used for the base size.
+ property = this.renderCssProperty("flex-basis", flexBasisValue);
+ } else if (dimensionValue) {
+ // If not and width/height is defined, then that's what defines the base size.
+ property = this.renderCssProperty(dimension, dimensionValue);
+ } else {
+ // Finally, if nothing is set, then the base size is the max-content size.
+ // In this case replace the section's title.
+ title = getStr("flexbox.itemSizing.itemContentSize");
+ }
+
+ const className = "section base";
+ return dom.li(
+ { className: className + (property ? "" : " no-property") },
+ dom.span({ className: "name" }, title, property),
+ this.renderSize(mainBaseSize)
+ );
+ }
+
+ renderFlexibilitySection(
+ flexItemSizing,
+ mainFinalSize,
+ properties,
+ computedStyle
+ ) {
+ const { mainDeltaSize, mainBaseSize, lineGrowthState } = flexItemSizing;
+
+ // Don't display anything if all interesting sizes are 0.
+ if (!mainFinalSize && !mainBaseSize && !mainDeltaSize) {
+ return null;
+ }
+
+ // Also don't display anything if the item did not grow or shrink.
+ const grew = mainDeltaSize > 0;
+ const shrank = mainDeltaSize < 0;
+ if (!grew && !shrank) {
+ return null;
+ }
+
+ const definedFlexGrow = properties["flex-grow"];
+ const computedFlexGrow = computedStyle.flexGrow;
+ const definedFlexShrink = properties["flex-shrink"];
+ const computedFlexShrink = computedStyle.flexShrink;
+
+ const reasons = getFlexibilityReasons({
+ lineGrowthState,
+ computedFlexGrow,
+ computedFlexShrink,
+ grew,
+ shrank,
+ });
+
+ let property = null;
+
+ if (grew && definedFlexGrow && computedFlexGrow) {
+ // If the item grew it's normally because it was set to grow (flex-grow is non 0).
+ property = this.renderCssProperty("flex-grow", definedFlexGrow);
+ } else if (shrank && definedFlexShrink && computedFlexShrink) {
+ // If the item shrank it's either because flex-shrink is non 0.
+ property = this.renderCssProperty("flex-shrink", definedFlexShrink);
+ } else if (shrank && computedFlexShrink) {
+ // Or also because it's default value is 1 anyway.
+ property = this.renderCssProperty(
+ "flex-shrink",
+ computedFlexShrink,
+ true
+ );
+ }
+
+ // Don't display the section at all if there's nothing useful to show users.
+ if (!property && !reasons.length) {
+ return null;
+ }
+
+ const className = "section flexibility";
+ return dom.li(
+ { className: className + (property ? "" : " no-property") },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.flexibilitySectionHeader"),
+ property
+ ),
+ this.renderSize(mainDeltaSize, true),
+ this.renderReasons(reasons)
+ );
+ }
+
+ renderMinimumSizeSection(flexItemSizing, properties, dimension) {
+ const { clampState, mainMinSize, mainDeltaSize } = flexItemSizing;
+ const grew = mainDeltaSize > 0;
+ const shrank = mainDeltaSize < 0;
+ const minDimensionValue = properties[`min-${dimension}`];
+
+ // We only display the minimum size when the item actually violates that size during
+ // layout & is clamped.
+ if (clampState !== "clamped_to_min") {
+ return null;
+ }
+
+ const reasons = [];
+ if (grew || shrank) {
+ // The item may have wanted to grow less, but was min-clamped to a larger size.
+ // Or the item may have wanted to shrink more but was min-clamped to a larger size.
+ reasons.push(getStr("flexbox.itemSizing.clampedToMin"));
+ }
+
+ return dom.li(
+ { className: "section min" },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.minSizeSectionHeader"),
+ minDimensionValue.length
+ ? this.renderCssProperty(`min-${dimension}`, minDimensionValue)
+ : null
+ ),
+ this.renderSize(mainMinSize),
+ this.renderReasons(reasons)
+ );
+ }
+
+ renderMaximumSizeSection(flexItemSizing, properties, dimension) {
+ const { clampState, mainMaxSize, mainDeltaSize } = flexItemSizing;
+ const grew = mainDeltaSize > 0;
+ const shrank = mainDeltaSize < 0;
+ const maxDimensionValue = properties[`max-${dimension}`];
+
+ if (clampState !== "clamped_to_max") {
+ return null;
+ }
+
+ const reasons = [];
+ if (grew || shrank) {
+ // The item may have wanted to grow more than it did, because it was max-clamped.
+ // Or the item may have wanted shrink more, but it was clamped to its max size.
+ reasons.push(getStr("flexbox.itemSizing.clampedToMax"));
+ }
+
+ return dom.li(
+ { className: "section max" },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.maxSizeSectionHeader"),
+ maxDimensionValue.length
+ ? this.renderCssProperty(`max-${dimension}`, maxDimensionValue)
+ : null
+ ),
+ this.renderSize(mainMaxSize),
+ this.renderReasons(reasons)
+ );
+ }
+
+ renderFinalSizeSection(mainFinalSize) {
+ return dom.li(
+ { className: "section final no-property" },
+ dom.span(
+ { className: "name" },
+ getStr("flexbox.itemSizing.finalSizeSectionHeader")
+ ),
+ this.renderSize(mainFinalSize)
+ );
+ }
+
+ render() {
+ const { flexItem } = this.props;
+ const { computedStyle, flexItemSizing, properties } = flexItem;
+ const {
+ mainAxisDirection,
+ mainBaseSize,
+ mainDeltaSize,
+ mainMaxSize,
+ mainMinSize,
+ } = flexItemSizing;
+ const dimension = mainAxisDirection.startsWith("horizontal")
+ ? "width"
+ : "height";
+
+ // Calculate the final size. This is base + delta, then clamped by min or max.
+ let mainFinalSize = mainBaseSize + mainDeltaSize;
+ mainFinalSize = Math.max(mainFinalSize, mainMinSize);
+ mainFinalSize =
+ mainMaxSize === null
+ ? mainFinalSize
+ : Math.min(mainFinalSize, mainMaxSize);
+
+ return dom.ul(
+ { className: "flex-item-sizing" },
+ this.renderBaseSizeSection(flexItemSizing, properties, dimension),
+ this.renderFlexibilitySection(
+ flexItemSizing,
+ mainFinalSize,
+ properties,
+ computedStyle
+ ),
+ this.renderMinimumSizeSection(flexItemSizing, properties, dimension),
+ this.renderMaximumSizeSection(flexItemSizing, properties, dimension),
+ this.renderFinalSizeSection(mainFinalSize)
+ );
+ }
+}
+
+module.exports = FlexItemSizingProperties;
diff --git a/devtools/client/inspector/flexbox/components/Flexbox.js b/devtools/client/inspector/flexbox/components/Flexbox.js
new file mode 100644
index 0000000000..a0d79d92eb
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/Flexbox.js
@@ -0,0 +1,128 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ createElement,
+ createFactory,
+ Fragment,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+loader.lazyGetter(this, "FlexItemList", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemList.js")
+ );
+});
+loader.lazyGetter(this, "FlexItemSizingOutline", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js")
+ );
+});
+loader.lazyGetter(this, "FlexItemSizingProperties", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js")
+ );
+});
+loader.lazyGetter(this, "Header", function () {
+ return createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/Header.js")
+ );
+});
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+class Flexbox extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ flexbox: PropTypes.shape(Types.flexbox).isRequired,
+ flexContainer: PropTypes.shape(Types.flexContainer).isRequired,
+ getSwatchColorPickerTooltip: PropTypes.func.isRequired,
+ onSetFlexboxOverlayColor: PropTypes.func.isRequired,
+ scrollToTop: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ renderFlexItemList() {
+ const { dispatch, scrollToTop, setSelectedNode } = this.props;
+ const { flexItems } = this.props.flexContainer;
+
+ return FlexItemList({
+ dispatch,
+ flexItems,
+ scrollToTop,
+ setSelectedNode,
+ });
+ }
+
+ renderFlexItemSizing() {
+ const { flexItems, flexItemShown, properties } = this.props.flexContainer;
+
+ const flexItem = flexItems.find(
+ item => item.nodeFront.actorID === flexItemShown
+ );
+ if (!flexItem) {
+ return null;
+ }
+
+ return createElement(
+ Fragment,
+ null,
+ FlexItemSizingOutline({
+ flexDirection: properties["flex-direction"],
+ flexItem,
+ }),
+ FlexItemSizingProperties({
+ flexDirection: properties["flex-direction"],
+ flexItem,
+ })
+ );
+ }
+
+ render() {
+ const {
+ dispatch,
+ flexbox,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ onSetFlexboxOverlayColor,
+ setSelectedNode,
+ } = this.props;
+
+ if (!flexContainer.actorID) {
+ return dom.div(
+ { className: "devtools-sidepanel-no-result" },
+ getStr("flexbox.noFlexboxeOnThisPage")
+ );
+ }
+
+ const { flexItemShown } = flexContainer;
+ const { color, highlighted } = flexbox;
+
+ return dom.div(
+ { className: "layout-flexbox-wrapper" },
+ Header({
+ color,
+ dispatch,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ highlighted,
+ onSetFlexboxOverlayColor,
+ setSelectedNode,
+ }),
+ !flexItemShown ? this.renderFlexItemList() : null,
+ flexItemShown ? this.renderFlexItemSizing() : null
+ );
+ }
+}
+
+module.exports = Flexbox;
diff --git a/devtools/client/inspector/flexbox/components/Header.js b/devtools/client/inspector/flexbox/components/Header.js
new file mode 100644
index 0000000000..44b19703e3
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/Header.js
@@ -0,0 +1,142 @@
+/* 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/. */
+
+"use strict";
+
+const {
+ createElement,
+ createFactory,
+ Fragment,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/layout/utils/l10n.js");
+
+const FlexContainer = createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexContainer.js")
+);
+const FlexItemSelector = createFactory(
+ require("resource://devtools/client/inspector/flexbox/components/FlexItemSelector.js")
+);
+
+const Types = require("resource://devtools/client/inspector/flexbox/types.js");
+
+const {
+ toggleFlexboxHighlighter,
+} = require("resource://devtools/client/inspector/flexbox/actions/flexbox-highlighter.js");
+
+class Header extends PureComponent {
+ static get propTypes() {
+ return {
+ color: PropTypes.string.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ flexContainer: PropTypes.shape(Types.flexContainer).isRequired,
+ getSwatchColorPickerTooltip: PropTypes.func.isRequired,
+ highlighted: PropTypes.bool.isRequired,
+ onSetFlexboxOverlayColor: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ };
+ }
+
+ renderFlexboxHighlighterToggle() {
+ const { dispatch, flexContainer, highlighted } = this.props;
+ // Don't show the flexbox highlighter toggle for the parent flex container of the
+ // selected element.
+ if (flexContainer.isFlexItemContainer) {
+ return null;
+ }
+
+ return createElement(
+ Fragment,
+ null,
+ dom.div({ className: "devtools-separator" }),
+ dom.input({
+ id: "flexbox-checkbox-toggle",
+ className: "devtools-checkbox-toggle",
+ checked: highlighted,
+ onChange: () =>
+ dispatch(toggleFlexboxHighlighter(flexContainer.nodeFront, "layout")),
+ title: getStr("flexbox.togglesFlexboxHighlighter2"),
+ type: "checkbox",
+ })
+ );
+ }
+
+ renderFlexContainer() {
+ if (this.props.flexContainer.flexItemShown) {
+ return null;
+ }
+
+ const {
+ color,
+ dispatch,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ onSetFlexboxOverlayColor,
+ } = this.props;
+
+ return FlexContainer({
+ color,
+ dispatch,
+ flexContainer,
+ getSwatchColorPickerTooltip,
+ onSetFlexboxOverlayColor,
+ });
+ }
+
+ renderFlexItemSelector() {
+ if (!this.props.flexContainer.flexItemShown) {
+ return null;
+ }
+
+ const { flexContainer, setSelectedNode } = this.props;
+ const { flexItems, flexItemShown } = flexContainer;
+ const flexItem = flexItems.find(
+ item => item.nodeFront.actorID === flexItemShown
+ );
+
+ if (!flexItem) {
+ return null;
+ }
+
+ return FlexItemSelector({
+ flexItem,
+ flexItems,
+ setSelectedNode,
+ });
+ }
+
+ render() {
+ const { flexContainer, setSelectedNode } = this.props;
+ const { flexItemShown, nodeFront } = flexContainer;
+
+ return dom.div(
+ { className: "flex-header devtools-monospace" },
+ flexItemShown
+ ? dom.button({
+ className: "flex-header-button-prev devtools-button",
+ "aria-label": getStr("flexbox.backButtonLabel"),
+ onClick: e => {
+ e.stopPropagation();
+ setSelectedNode(nodeFront);
+ },
+ })
+ : null,
+ dom.div(
+ {
+ className:
+ "flex-header-content" + (flexItemShown ? " flex-item-shown" : ""),
+ },
+ this.renderFlexContainer(),
+ this.renderFlexItemSelector()
+ ),
+ this.renderFlexboxHighlighterToggle()
+ );
+ }
+}
+
+module.exports = Header;
diff --git a/devtools/client/inspector/flexbox/components/moz.build b/devtools/client/inspector/flexbox/components/moz.build
new file mode 100644
index 0000000000..3e077a217f
--- /dev/null
+++ b/devtools/client/inspector/flexbox/components/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+ "Flexbox.js",
+ "FlexContainer.js",
+ "FlexItem.js",
+ "FlexItemList.js",
+ "FlexItemSelector.js",
+ "FlexItemSizingOutline.js",
+ "FlexItemSizingProperties.js",
+ "Header.js",
+)