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 {
createFactory,
PureComponent,
} = require("resource://devtools/client/shared/vendor/react.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const FontPropertyValue = createFactory(
require("resource://devtools/client/inspector/fonts/components/FontPropertyValue.js")
);
const {
getStr,
} = require("resource://devtools/client/inspector/fonts/utils/l10n.js");
const {
getUnitFromValue,
getStepForUnit,
} = require("resource://devtools/client/inspector/fonts/utils/font-utils.js");
class LetterSpacing extends PureComponent {
static get propTypes() {
return {
disabled: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
};
}
constructor(props) {
super(props);
// Local state for min/max bounds indexed by unit to allow user input that
// goes out-of-bounds while still providing a meaningful default range. The indexing
// by unit is needed to account for unit conversion (ex: em to px) where the operation
// may result in out-of-bounds values. Avoiding React's state and setState() because
// `value` is a prop coming from the Redux store while min/max are local. Reconciling
// value/unit changes is needlessly complicated and adds unnecessary re-renders.
this.historicMin = {};
this.historicMax = {};
}
getDefaultMinMax(unit) {
let min;
let max;
switch (unit) {
case "px":
min = -10;
max = 10;
break;
default:
min = -0.2;
max = 0.6;
break;
}
return { min, max };
}
render() {
// For a unitless or a NaN value, default unit to "em".
const unit = getUnitFromValue(this.props.value) || "em";
// When the initial value of "letter-spacing" is "normal", the parsed value
// is not a number (NaN). Guard by setting the default value to 0.
const isKeywordValue = this.props.value === "normal";
const value = isKeywordValue ? 0 : parseFloat(this.props.value);
let { min, max } = this.getDefaultMinMax(unit);
min = Math.min(min, value);
max = Math.max(max, value);
// Allow lower and upper bounds to move to accomodate the incoming value.
this.historicMin[unit] = this.historicMin[unit]
? Math.min(this.historicMin[unit], min)
: min;
this.historicMax[unit] = this.historicMax[unit]
? Math.max(this.historicMax[unit], max)
: max;
return FontPropertyValue({
allowOverflow: true,
allowUnderflow: true,
disabled: this.props.disabled,
label: getStr("fontinspector.letterSpacingLabel"),
min: this.historicMin[unit],
max: this.historicMax[unit],
name: "letter-spacing",
onChange: this.props.onChange,
// Increase the increment granularity because letter spacing is very sensitive.
step: getStepForUnit(unit) / 100,
// Show the value input and unit only when the value is not a keyword.
showInput: !isKeywordValue,
showUnit: !isKeywordValue,
unit,
unitOptions: ["em", "rem", "px"],
value,
// Show the value as a read-only label if it's a keyword.
valueLabel: isKeywordValue ? this.props.value : null,
});
}
}
module.exports = LetterSpacing;