tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MenuItem.js (5701B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 // A command in a menu.
      8 
      9 const {
     10  createFactory,
     11  createRef,
     12  PureComponent,
     13 } = require("resource://devtools/client/shared/vendor/react.mjs");
     14 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     15 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     16 const { button, li, span } = dom;
     17 loader.lazyGetter(this, "Localized", () =>
     18  createFactory(
     19    require("resource://devtools/client/shared/vendor/fluent-react.js")
     20      .Localized
     21  )
     22 );
     23 
     24 class MenuItem extends PureComponent {
     25  static get propTypes() {
     26    return {
     27      // An optional keyboard shortcut to display next to the item.
     28      // (This does not actually register the event listener for the key.)
     29      accelerator: PropTypes.string,
     30 
     31      // A tri-state value that may be true/false if item should be checkable,
     32      // and undefined otherwise.
     33      checked: PropTypes.bool,
     34 
     35      // Any additional classes to assign to the button specified as
     36      // a space-separated string.
     37      className: PropTypes.string,
     38 
     39      // A disabled state of the menu item.
     40      disabled: PropTypes.bool,
     41 
     42      // URL of the icon to associate with the MenuItem. (Optional)
     43      //
     44      //   e.g. chrome://devtools/skim/image/foo.svg
     45      //
     46      // This may also be set in CSS using the --menuitem-icon-image variable.
     47      // Note that in this case, the variable should specify the CSS <image> to
     48      // use, not simply the URL (e.g.
     49      // "url(chrome://devtools/skim/image/foo.svg)").
     50      icon: PropTypes.string,
     51 
     52      // An optional ID to be assigned to the item.
     53      id: PropTypes.string,
     54 
     55      // The item label for use with legacy localization systems.
     56      label: PropTypes.string,
     57 
     58      // The Fluent ID for localizing the label.
     59      l10nID: PropTypes.string,
     60 
     61      // An optional callback to be invoked when the item is selected.
     62      onClick: PropTypes.func,
     63 
     64      // Optional menu item role override. Use this property with a value
     65      // "menuitemradio" if the menu item is a radio.
     66      role: PropTypes.string,
     67 
     68      // An optional text for the item tooltip.
     69      tooltip: PropTypes.string,
     70    };
     71  }
     72 
     73  /**
     74   * Use this as a fallback `icon` prop if your MenuList contains MenuItems
     75   * with or without icon in order to keep all MenuItems aligned.
     76   */
     77  static get DUMMY_ICON() {
     78    return `data:image/svg+xml,${encodeURIComponent(
     79      '<svg height="16" width="16"></svg>'
     80    )}`;
     81  }
     82 
     83  constructor(props) {
     84    super(props);
     85    this.labelRef = createRef();
     86  }
     87 
     88  componentDidMount() {
     89    if (!this.labelRef.current) {
     90      return;
     91    }
     92 
     93    // Pre-fetch any backgrounds specified for the item.
     94    const win = this.labelRef.current.ownerDocument.defaultView;
     95    this.preloadCallback = win.requestIdleCallback(() => {
     96      this.preloadCallback = null;
     97      if (!this.labelRef.current) {
     98        return;
     99      }
    100 
    101      const backgrounds = win
    102        .getComputedStyle(this.labelRef.current, ":before")
    103        .getCSSImageURLs("background-image");
    104      for (const background of backgrounds) {
    105        const image = new Image();
    106        image.src = background;
    107      }
    108    });
    109  }
    110 
    111  componentWillUnmount() {
    112    if (!this.labelRef.current || !this.preloadCallback) {
    113      return;
    114    }
    115 
    116    const win = this.labelRef.current.ownerDocument.defaultView;
    117    if (win) {
    118      win.cancelIdleCallback(this.preloadCallback);
    119    }
    120    this.preloadCallback = null;
    121  }
    122 
    123  render() {
    124    const attr = {
    125      className: "command",
    126    };
    127 
    128    if (this.props.id) {
    129      attr.id = this.props.id;
    130    }
    131 
    132    if (this.props.className) {
    133      attr.className += " " + this.props.className;
    134    }
    135 
    136    if (this.props.icon) {
    137      attr.className += " iconic";
    138      attr.style = { "--menuitem-icon-image": "url(" + this.props.icon + ")" };
    139    }
    140 
    141    if (this.props.onClick) {
    142      attr.onClick = this.props.onClick;
    143    }
    144 
    145    if (this.props.tooltip) {
    146      attr.title = this.props.tooltip;
    147    }
    148 
    149    if (this.props.disabled) {
    150      attr.disabled = this.props.disabled;
    151    }
    152 
    153    if (this.props.role) {
    154      attr.role = this.props.role;
    155    } else if (typeof this.props.checked !== "undefined") {
    156      attr.role = "menuitemcheckbox";
    157    } else {
    158      attr.role = "menuitem";
    159    }
    160 
    161    if (this.props.checked) {
    162      attr["aria-checked"] = true;
    163    }
    164 
    165    const children = [];
    166    const className = "label";
    167 
    168    // Add the text label.
    169    if (this.props.l10nID) {
    170      // Fluent localized label.
    171      children.push(
    172        Localized(
    173          { id: this.props.l10nID, key: "label" },
    174          span({ className, ref: this.labelRef })
    175        )
    176      );
    177    } else {
    178      children.push(
    179        span({ key: "label", className, ref: this.labelRef }, this.props.label)
    180      );
    181    }
    182 
    183    if (this.props.l10nID && this.props.label) {
    184      console.warn(
    185        "<MenuItem> should only take either an l10nID or a label, not both"
    186      );
    187    }
    188    if (!this.props.l10nID && !this.props.label) {
    189      console.warn("<MenuItem> requires either an l10nID, or a label prop.");
    190    }
    191 
    192    if (typeof this.props.accelerator !== "undefined") {
    193      const acceleratorLabel = span(
    194        { key: "accelerator", className: "accelerator" },
    195        this.props.accelerator
    196      );
    197      children.push(acceleratorLabel);
    198    }
    199 
    200    return li(
    201      {
    202        className: "menuitem",
    203        role: "presentation",
    204      },
    205      button(attr, children)
    206    );
    207  }
    208 }
    209 
    210 module.exports = MenuItem;