Bug 1475208 - (Part 4) Auto-increment font size when value reaches upper bound. r=gl
MozReview-Commit-ID: GOI1KRrWZlE
--- a/devtools/client/inspector/fonts/components/FontPropertyValue.js
+++ b/devtools/client/inspector/fonts/components/FontPropertyValue.js
@@ -3,71 +3,183 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+// Milliseconds between auto-increment interval iterations.
+const AUTOINCREMENT_DELAY = 300;
const UNITS = ["em", "rem", "%", "px", "vh", "vw"];
class FontPropertyValue extends PureComponent {
static get propTypes() {
return {
+ allowAutoIncrement: PropTypes.bool,
defaultValue: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
label: PropTypes.string.isRequired,
min: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
max: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
showUnit: PropTypes.bool,
step: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
unit: PropTypes.oneOfType([ PropTypes.string, null ]),
value: PropTypes.number,
};
}
constructor(props) {
super(props);
+ // Interval ID for the auto-increment operation.
+ this.interval = null;
this.state = {
// Whether the user is dragging the slider thumb or pressing on the numeric stepper.
- interactive: false
+ interactive: false,
+ value: null,
};
+
+ this.autoIncrement = this.autoIncrement.bind(this);
this.onChange = this.onChange.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onUnitChange = this.onUnitChange.bind(this);
}
+ componentDidUpdate(prevProps, prevState) {
+ // Clear the auto-increment interval if interactive state changed from true to false.
+ if (prevState.interactive && !this.state.interactive) {
+ this.stopAutoIncrement();
+ }
+ }
+
+ componentWillUnmount() {
+ this.stopAutoIncrement();
+ }
+
+ /**
+ * Increment the current value with a step of the next order of magnitude.
+ */
+ autoIncrement() {
+ const value = this.props.value + this.props.step * 10;
+ this.updateValue(value);
+ }
+
+ /**
+ * Check if the given value is at or exceeds the maximum value for the slider and number
+ * inputs. Using Math.floor() on maximum value because unit conversion can yield numbers
+ * with decimals that can't be reached with the step increment for the converted unit.
+ * For example: max = 1000.75% and step = 1
+ *
+ * @param {Number} value
+ * Numeric value.
+ * @return {Boolean}
+ */
+ isAtUpperBound(value) {
+ return value >= Math.floor(this.props.max);
+ }
+
+ /**
+ * Handler for "change" events from the range and number input fields. Calls the change
+ * handler provided with props and updates internal state with the current value.
+ * Begins auto-incrementing if the value is already at the upper bound.
+ *
+ * @param {Event} e
+ * Change event.
+ */
onChange(e) {
- this.props.onChange(this.props.name, e.target.value, this.props.unit);
- const value = e.target.value;
- this.setState((prevState) => {
- return { ...prevState, value };
- });
+ const value = parseFloat(e.target.value);
+ this.updateValue(value);
+
+ // Stop auto-incrementing when dragging back down from the upper bound.
+ if (value < this.props.max && this.interval) {
+ this.stopAutoIncrement();
+ }
+
+ // Begin auto-incrementing when reaching the upper bound.
+ if (this.isAtUpperBound(value) && this.state.interactive) {
+ this.startAutoIncrement();
+ }
}
onUnitChange(e) {
this.props.onChange(this.props.name, this.props.value, this.props.unit,
e.target.value);
+ // Reset internal state value and wait for converted value from props.
+ this.setState((prevState) => {
+ return {
+ ...prevState,
+ value: null
+ };
+ });
}
onMouseDown(e) {
this.setState((prevState, props) => {
return { ...prevState, interactive: true, value: props.value };
});
}
onMouseUp(e) {
this.setState((prevState, props) => {
return { ...prevState, interactive: false, value: props.value };
});
}
+ startAutoIncrement() {
+ // Do not set auto-increment interval if not allowed to or if one is already set.
+ if (!this.props.allowAutoIncrement || this.interval) {
+ return;
+ }
+
+ this.interval = setInterval(this.autoIncrement, AUTOINCREMENT_DELAY);
+ }
+
+ stopAutoIncrement() {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+
+ /**
+ * Toggle the "interactive" state which causes render() to use `value` fom internal
+ * state instead of from props to prevent jittering during continous dragging of the
+ * slider thumb or incrementing from the number input.
+ *
+ * @param {Boolean} isInteractive
+ * Whether to mark the interactive state on or off.
+ */
+ toggleInteractiveState(isInteractive) {
+ this.setState((prevState) => {
+ return {
+ ...prevState,
+ interactive: isInteractive
+ };
+ });
+ }
+
+ /**
+ * Calls the given `onChange` callback with the current property, value and unit.
+ * Updates the internal state with the current value which will be used while
+ * interactive to prevent jittering when receiving debounced props from outside.
+ *
+ * @param {Number} value
+ * Numeric property value.
+ */
+ updateValue(value) {
+ this.props.onChange(this.props.name, value, this.props.unit);
+ this.setState((prevState) => {
+ return {
+ ...prevState,
+ value
+ };
+ });
+ }
+
render() {
// Guard against bad axis data.
if (this.props.min === this.props.max) {
return null;
}
const defaults = {
min: this.props.min,
--- a/devtools/client/inspector/fonts/components/FontSize.js
+++ b/devtools/client/inspector/fonts/components/FontSize.js
@@ -53,16 +53,17 @@ class FontSize extends PureComponent {
// Ensure we store the max value ever reached for this unit type. This will be the
// max value of the input and slider. Without this memoization, the value and slider
// thumb get clamped at the upper bound while decrementing an out-of-bounds value.
this.historicMax[unit] = this.historicMax[unit]
? Math.max(this.historicMax[unit], max)
: max;
return FontPropertyValue({
+ allowAutoIncrement: true,
label: getStr("fontinspector.fontSizeLabel"),
min: 0,
max: this.historicMax[unit],
name: "font-size",
onChange: this.props.onChange,
showUnit: true,
step: getStepForUnit(unit),
unit,