summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/fonts/components/LetterSpacing.js
blob: 42d6ddfa611c8534fd6595ea50ba8180ef1436e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/* 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;