Bug 1475208 - (Part 5) Add keydown/keyup handlers for font size auto-increment; refine mousedown/mouseup handlers. r=gl draft
authorRazvan Caliman <rcaliman@mozilla.com>
Mon, 30 Jul 2018 23:46:48 +0200
changeset 827673 2f15451b9a9a85b3fca1965ec13e0d72523a4464
parent 827672 bf5afc8d975045ea82f7864ac8cfdc0add99be4b
child 827674 e3e1554fe62a6aef919bb6ae55b6ee80243e37ff
push id118557
push userbmo:rcaliman@mozilla.com
push dateWed, 08 Aug 2018 16:44:17 +0000
reviewersgl
bugs1475208
milestone63.0a1
Bug 1475208 - (Part 5) Add keydown/keyup handlers for font size auto-increment; refine mousedown/mouseup handlers. r=gl MozReview-Commit-ID: JUdXfhw4Ca0
devtools/client/inspector/fonts/components/FontPropertyValue.js
--- 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(