Source code

Revision control

Copy as Markdown

Other Tools

/* 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
* @return {Object}
* The React component representing this CSS property
*/
renderCssProperty(name, value) {
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 }, 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;