FlexItemSizingProperties.js (10088B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 PureComponent, 9 } = require("resource://devtools/client/shared/vendor/react.mjs"); 10 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 12 const { 13 getStr, 14 } = require("resource://devtools/client/inspector/layout/utils/l10n.js"); 15 16 const Types = require("resource://devtools/client/inspector/flexbox/types.js"); 17 18 const getFlexibilityReasons = ({ 19 lineGrowthState, 20 computedFlexGrow, 21 computedFlexShrink, 22 grew, 23 shrank, 24 }) => { 25 const reasons = []; 26 27 // Tell users whether the item was set to grow or shrink. 28 if (computedFlexGrow && lineGrowthState === "growing") { 29 reasons.push(getStr("flexbox.itemSizing.setToGrow")); 30 } 31 if (computedFlexShrink && lineGrowthState === "shrinking") { 32 reasons.push(getStr("flexbox.itemSizing.setToShrink")); 33 } 34 if (!computedFlexGrow && !grew && !shrank && lineGrowthState === "growing") { 35 reasons.push(getStr("flexbox.itemSizing.notSetToGrow")); 36 } 37 if ( 38 !computedFlexShrink && 39 !grew && 40 !shrank && 41 lineGrowthState === "shrinking" 42 ) { 43 reasons.push(getStr("flexbox.itemSizing.notSetToShrink")); 44 } 45 46 return reasons; 47 }; 48 49 class FlexItemSizingProperties extends PureComponent { 50 static get propTypes() { 51 return { 52 flexDirection: PropTypes.string.isRequired, 53 flexItem: PropTypes.shape(Types.flexItem).isRequired, 54 }; 55 } 56 57 /** 58 * Rounds some size in pixels and render it. 59 * The rendered value will end with 'px' (unless the dimension is 0 in which case the 60 * unit will be omitted) 61 * 62 * @param {number} value 63 * The number to be rounded 64 * @param {boolean} prependPlusSign 65 * If set to true, the + sign will be printed before a positive value 66 * @return {object} 67 * The React component representing this rounded size 68 */ 69 renderSize(value, prependPlusSign) { 70 if (value == 0) { 71 return dom.span({ className: "value" }, "0"); 72 } 73 74 value = Math.round(value * 100) / 100; 75 if (prependPlusSign && value > 0) { 76 value = "+" + value; 77 } 78 79 return dom.span( 80 { className: "value" }, 81 value, 82 dom.span({ className: "unit" }, "px") 83 ); 84 } 85 86 /** 87 * Render an authored CSS property. 88 * 89 * @param {string} name 90 * The name for this CSS property 91 * @param {string} value 92 * The property value 93 * @return {object} 94 * The React component representing this CSS property 95 */ 96 renderCssProperty(name, value) { 97 return dom.span({ className: "css-property-link" }, `(${name}: ${value})`); 98 } 99 100 /** 101 * Render a list of sentences to be displayed in the UI as reasons why a certain sizing 102 * value happened. 103 * 104 * @param {Array} sentences 105 * The list of sentences as Strings 106 * @return {object} 107 * The React component representing these sentences 108 */ 109 renderReasons(sentences) { 110 return dom.ul( 111 { className: "reasons" }, 112 sentences.map(sentence => dom.li({}, sentence)) 113 ); 114 } 115 116 renderBaseSizeSection({ mainBaseSize }, properties, dimension) { 117 const flexBasisValue = properties["flex-basis"]; 118 const dimensionValue = properties[dimension]; 119 120 let title = getStr("flexbox.itemSizing.baseSizeSectionHeader"); 121 let property = null; 122 123 if (flexBasisValue) { 124 // If flex-basis is defined, then that's what is used for the base size. 125 property = this.renderCssProperty("flex-basis", flexBasisValue); 126 } else if (dimensionValue) { 127 // If not and width/height is defined, then that's what defines the base size. 128 property = this.renderCssProperty(dimension, dimensionValue); 129 } else { 130 // Finally, if nothing is set, then the base size is the max-content size. 131 // In this case replace the section's title. 132 title = getStr("flexbox.itemSizing.itemContentSize"); 133 } 134 135 const className = "section base"; 136 return dom.li( 137 { className: className + (property ? "" : " no-property") }, 138 dom.span({ className: "name" }, title, property), 139 this.renderSize(mainBaseSize) 140 ); 141 } 142 143 renderFlexibilitySection( 144 flexItemSizing, 145 mainFinalSize, 146 properties, 147 computedStyle 148 ) { 149 const { mainDeltaSize, mainBaseSize, lineGrowthState } = flexItemSizing; 150 151 // Don't display anything if all interesting sizes are 0. 152 if (!mainFinalSize && !mainBaseSize && !mainDeltaSize) { 153 return null; 154 } 155 156 // Also don't display anything if the item did not grow or shrink. 157 const grew = mainDeltaSize > 0; 158 const shrank = mainDeltaSize < 0; 159 if (!grew && !shrank) { 160 return null; 161 } 162 163 const definedFlexGrow = properties["flex-grow"]; 164 const computedFlexGrow = computedStyle.flexGrow; 165 const definedFlexShrink = properties["flex-shrink"]; 166 const computedFlexShrink = computedStyle.flexShrink; 167 168 const reasons = getFlexibilityReasons({ 169 lineGrowthState, 170 computedFlexGrow, 171 computedFlexShrink, 172 grew, 173 shrank, 174 }); 175 176 let property = null; 177 178 if (grew && definedFlexGrow && computedFlexGrow) { 179 // If the item grew it's normally because it was set to grow (flex-grow is non 0). 180 property = this.renderCssProperty("flex-grow", definedFlexGrow); 181 } else if (shrank && definedFlexShrink && computedFlexShrink) { 182 // If the item shrank it's either because flex-shrink is non 0. 183 property = this.renderCssProperty("flex-shrink", definedFlexShrink); 184 } else if (shrank && computedFlexShrink) { 185 // Or also because it's default value is 1 anyway. 186 property = this.renderCssProperty( 187 "flex-shrink", 188 computedFlexShrink, 189 true 190 ); 191 } 192 193 // Don't display the section at all if there's nothing useful to show users. 194 if (!property && !reasons.length) { 195 return null; 196 } 197 198 const className = "section flexibility"; 199 return dom.li( 200 { className: className + (property ? "" : " no-property") }, 201 dom.span( 202 { className: "name" }, 203 getStr("flexbox.itemSizing.flexibilitySectionHeader"), 204 property 205 ), 206 this.renderSize(mainDeltaSize, true), 207 this.renderReasons(reasons) 208 ); 209 } 210 211 renderMinimumSizeSection(flexItemSizing, properties, dimension) { 212 const { clampState, mainMinSize, mainDeltaSize } = flexItemSizing; 213 const grew = mainDeltaSize > 0; 214 const shrank = mainDeltaSize < 0; 215 const minDimensionValue = properties[`min-${dimension}`]; 216 217 // We only display the minimum size when the item actually violates that size during 218 // layout & is clamped. 219 if (clampState !== "clamped_to_min") { 220 return null; 221 } 222 223 const reasons = []; 224 if (grew || shrank) { 225 // The item may have wanted to grow less, but was min-clamped to a larger size. 226 // Or the item may have wanted to shrink more but was min-clamped to a larger size. 227 reasons.push(getStr("flexbox.itemSizing.clampedToMin")); 228 } 229 230 return dom.li( 231 { className: "section min" }, 232 dom.span( 233 { className: "name" }, 234 getStr("flexbox.itemSizing.minSizeSectionHeader"), 235 minDimensionValue.length 236 ? this.renderCssProperty(`min-${dimension}`, minDimensionValue) 237 : null 238 ), 239 this.renderSize(mainMinSize), 240 this.renderReasons(reasons) 241 ); 242 } 243 244 renderMaximumSizeSection(flexItemSizing, properties, dimension) { 245 const { clampState, mainMaxSize, mainDeltaSize } = flexItemSizing; 246 const grew = mainDeltaSize > 0; 247 const shrank = mainDeltaSize < 0; 248 const maxDimensionValue = properties[`max-${dimension}`]; 249 250 if (clampState !== "clamped_to_max") { 251 return null; 252 } 253 254 const reasons = []; 255 if (grew || shrank) { 256 // The item may have wanted to grow more than it did, because it was max-clamped. 257 // Or the item may have wanted shrink more, but it was clamped to its max size. 258 reasons.push(getStr("flexbox.itemSizing.clampedToMax")); 259 } 260 261 return dom.li( 262 { className: "section max" }, 263 dom.span( 264 { className: "name" }, 265 getStr("flexbox.itemSizing.maxSizeSectionHeader"), 266 maxDimensionValue.length 267 ? this.renderCssProperty(`max-${dimension}`, maxDimensionValue) 268 : null 269 ), 270 this.renderSize(mainMaxSize), 271 this.renderReasons(reasons) 272 ); 273 } 274 275 renderFinalSizeSection(mainFinalSize) { 276 return dom.li( 277 { className: "section final no-property" }, 278 dom.span( 279 { className: "name" }, 280 getStr("flexbox.itemSizing.finalSizeSectionHeader") 281 ), 282 this.renderSize(mainFinalSize) 283 ); 284 } 285 286 render() { 287 const { flexItem } = this.props; 288 const { computedStyle, flexItemSizing, properties } = flexItem; 289 const { 290 mainAxisDirection, 291 mainBaseSize, 292 mainDeltaSize, 293 mainMaxSize, 294 mainMinSize, 295 } = flexItemSizing; 296 const dimension = mainAxisDirection.startsWith("horizontal") 297 ? "width" 298 : "height"; 299 300 // Calculate the final size. This is base + delta, then clamped by min or max. 301 let mainFinalSize = mainBaseSize + mainDeltaSize; 302 mainFinalSize = Math.max(mainFinalSize, mainMinSize); 303 mainFinalSize = 304 mainMaxSize === null 305 ? mainFinalSize 306 : Math.min(mainFinalSize, mainMaxSize); 307 308 return dom.ul( 309 { className: "flex-item-sizing" }, 310 this.renderBaseSizeSection(flexItemSizing, properties, dimension), 311 this.renderFlexibilitySection( 312 flexItemSizing, 313 mainFinalSize, 314 properties, 315 computedStyle 316 ), 317 this.renderMinimumSizeSection(flexItemSizing, properties, dimension), 318 this.renderMaximumSizeSection(flexItemSizing, properties, dimension), 319 this.renderFinalSizeSection(mainFinalSize) 320 ); 321 } 322 } 323 324 module.exports = FlexItemSizingProperties;