--- a/devtools/client/inspector/fonts/components/FontPropertyValue.js
+++ b/devtools/client/inspector/fonts/components/FontPropertyValue.js
@@ -2,16 +2,17 @@
* 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("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");
+const { KeyCodes } = require("devtools/client/shared/keycodes");
// 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 {
@@ -35,20 +36,24 @@ class FontPropertyValue extends PureComp
this.interval = null;
this.state = {
// Whether the user is dragging the slider thumb or pressing on the numeric stepper.
interactive: false,
value: null,
};
this.autoIncrement = this.autoIncrement.bind(this);
+ this.onBlur = this.onBlur.bind(this);
this.onChange = this.onChange.bind(this);
+ this.onKeyDown = this.onKeyDown.bind(this);
+ this.onKeyUp = this.onKeyUp.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onUnitChange = this.onUnitChange.bind(this);
+ this.stopAutoIncrement = this.stopAutoIncrement.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();
}
}
@@ -74,16 +79,20 @@ class FontPropertyValue extends PureComp
* @param {Number} value
* Numeric value.
* @return {Boolean}
*/
isAtUpperBound(value) {
return value >= Math.floor(this.props.max);
}
+ onBlur() {
+ this.toggleInteractiveState(false);
+ }
+
/**
* 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.
*/
@@ -97,38 +106,89 @@ class FontPropertyValue extends PureComp
}
// Begin auto-incrementing when reaching the upper bound.
if (this.isAtUpperBound(value) && this.state.interactive) {
this.startAutoIncrement();
}
}
+ /**
+ * Handler for "keydown" events from the range and number input fields.
+ * Toggles on the "interactive" state. @See toggleInteractiveState();
+ * Begins auto-incrementing if the value is already at the upper bound.
+ *
+ * @param {Event} e
+ * KeyDown event.
+ */
+ onKeyDown(e) {
+ const inputType = e.target.type;
+
+ if ([
+ KeyCodes.DOM_VK_UP,
+ KeyCodes.DOM_VK_DOWN,
+ KeyCodes.DOM_VK_RIGHT,
+ KeyCodes.DOM_VK_LEFT
+ ].includes(e.keyCode)) {
+ this.toggleInteractiveState(true);
+ }
+
+ // Begin auto-incrementing if the value is already at the upper bound
+ // and the user gesture requests a higher value.
+ if (this.isAtUpperBound(this.props.value)) {
+ if ((inputType === "range" &&
+ e.keyCode === KeyCodes.DOM_VK_UP || e.keyCode === KeyCodes.DOM_VK_RIGHT) ||
+ (inputType === "number" &&
+ e.keyCode === KeyCodes.DOM_VK_UP)) {
+ this.startAutoIncrement();
+ }
+ }
+ }
+
+ onKeyUp(e) {
+ if ([
+ KeyCodes.DOM_VK_UP,
+ KeyCodes.DOM_VK_DOWN,
+ KeyCodes.DOM_VK_RIGHT,
+ KeyCodes.DOM_VK_LEFT
+ ].includes(e.keyCode)) {
+ this.toggleInteractiveState(false);
+ }
+ }
+
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
};
});
}
+ /**
+ * Handler for "keydown" events from the sider and input fields.
+ * Toggles on the "interactive" state. @See toggleInteractiveState();
+ * Begins auto-incrementing if the value is already at the upper bound.
+ *
+ * @param {Event} e
+ * MouseDown event.
+ */
onMouseDown(e) {
- this.setState((prevState, props) => {
- return { ...prevState, interactive: true, value: props.value };
- });
+ // Begin auto-incrementing if the value is already at the upper bound.
+ if (this.isAtUpperBound(this.props.value) && e.target.type === "range") {
+ this.startAutoIncrement();
+ }
+ this.toggleInteractiveState(true);
}
onMouseUp(e) {
- this.setState((prevState, props) => {
- return { ...prevState, interactive: false, value: props.value };
- });
+ this.toggleInteractiveState(false);
}
startAutoIncrement() {
// Do not set auto-increment interval if not allowed to or if one is already set.
if (!this.props.allowAutoIncrement || this.interval) {
return;
}
@@ -138,17 +198,17 @@ class FontPropertyValue extends PureComp
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.
+ * range input 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,
@@ -179,30 +239,33 @@ class FontPropertyValue extends PureComp
// Guard against bad axis data.
if (this.props.min === this.props.max) {
return null;
}
const defaults = {
min: this.props.min,
max: this.props.max,
+ onBlur: this.onBlur,
onChange: this.onChange,
- onMouseDown: this.onMouseDown,
- onMouseUp: this.onMouseUp,
+ onKeyUp: this.onKeyUp,
+ onKeyDown: this.onKeyDown,
step: this.props.step || 1,
// While interacting with the slider or numeric stepper, prevent updating value from
// outside props which may be debounced and could cause jitter when rendering.
value: this.state.interactive
? this.state.value
: this.props.value || this.props.defaultValue,
};
const range = dom.input(
{
...defaults,
+ onMouseDown: this.onMouseDown,
+ onMouseUp: this.onMouseUp,
className: "font-value-slider",
name: this.props.name,
title: this.props.label,
type: "range",
}
);
const input = dom.input(