211 lines
5.6 KiB
JavaScript
211 lines
5.6 KiB
JavaScript
/* 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/. */
|
|
|
|
/* eslint-env browser */
|
|
"use strict";
|
|
|
|
// A command in a menu.
|
|
|
|
const {
|
|
createFactory,
|
|
createRef,
|
|
PureComponent,
|
|
} = require("resource://devtools/client/shared/vendor/react.mjs");
|
|
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
|
|
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
|
|
const { button, li, span } = dom;
|
|
loader.lazyGetter(this, "Localized", () =>
|
|
createFactory(
|
|
require("resource://devtools/client/shared/vendor/fluent-react.js")
|
|
.Localized
|
|
)
|
|
);
|
|
|
|
class MenuItem extends PureComponent {
|
|
static get propTypes() {
|
|
return {
|
|
// An optional keyboard shortcut to display next to the item.
|
|
// (This does not actually register the event listener for the key.)
|
|
accelerator: PropTypes.string,
|
|
|
|
// A tri-state value that may be true/false if item should be checkable,
|
|
// and undefined otherwise.
|
|
checked: PropTypes.bool,
|
|
|
|
// Any additional classes to assign to the button specified as
|
|
// a space-separated string.
|
|
className: PropTypes.string,
|
|
|
|
// A disabled state of the menu item.
|
|
disabled: PropTypes.bool,
|
|
|
|
// URL of the icon to associate with the MenuItem. (Optional)
|
|
//
|
|
// e.g. chrome://devtools/skim/image/foo.svg
|
|
//
|
|
// This may also be set in CSS using the --menuitem-icon-image variable.
|
|
// Note that in this case, the variable should specify the CSS <image> to
|
|
// use, not simply the URL (e.g.
|
|
// "url(chrome://devtools/skim/image/foo.svg)").
|
|
icon: PropTypes.string,
|
|
|
|
// An optional ID to be assigned to the item.
|
|
id: PropTypes.string,
|
|
|
|
// The item label for use with legacy localization systems.
|
|
label: PropTypes.string,
|
|
|
|
// The Fluent ID for localizing the label.
|
|
l10nID: PropTypes.string,
|
|
|
|
// An optional callback to be invoked when the item is selected.
|
|
onClick: PropTypes.func,
|
|
|
|
// Optional menu item role override. Use this property with a value
|
|
// "menuitemradio" if the menu item is a radio.
|
|
role: PropTypes.string,
|
|
|
|
// An optional text for the item tooltip.
|
|
tooltip: PropTypes.string,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Use this as a fallback `icon` prop if your MenuList contains MenuItems
|
|
* with or without icon in order to keep all MenuItems aligned.
|
|
*/
|
|
static get DUMMY_ICON() {
|
|
return `data:image/svg+xml,${encodeURIComponent(
|
|
'<svg height="16" width="16"></svg>'
|
|
)}`;
|
|
}
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.labelRef = createRef();
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (!this.labelRef.current) {
|
|
return;
|
|
}
|
|
|
|
// Pre-fetch any backgrounds specified for the item.
|
|
const win = this.labelRef.current.ownerDocument.defaultView;
|
|
this.preloadCallback = win.requestIdleCallback(() => {
|
|
this.preloadCallback = null;
|
|
if (!this.labelRef.current) {
|
|
return;
|
|
}
|
|
|
|
const backgrounds = win
|
|
.getComputedStyle(this.labelRef.current, ":before")
|
|
.getCSSImageURLs("background-image");
|
|
for (const background of backgrounds) {
|
|
const image = new Image();
|
|
image.src = background;
|
|
}
|
|
});
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (!this.labelRef.current || !this.preloadCallback) {
|
|
return;
|
|
}
|
|
|
|
const win = this.labelRef.current.ownerDocument.defaultView;
|
|
if (win) {
|
|
win.cancelIdleCallback(this.preloadCallback);
|
|
}
|
|
this.preloadCallback = null;
|
|
}
|
|
|
|
render() {
|
|
const attr = {
|
|
className: "command",
|
|
};
|
|
|
|
if (this.props.id) {
|
|
attr.id = this.props.id;
|
|
}
|
|
|
|
if (this.props.className) {
|
|
attr.className += " " + this.props.className;
|
|
}
|
|
|
|
if (this.props.icon) {
|
|
attr.className += " iconic";
|
|
attr.style = { "--menuitem-icon-image": "url(" + this.props.icon + ")" };
|
|
}
|
|
|
|
if (this.props.onClick) {
|
|
attr.onClick = this.props.onClick;
|
|
}
|
|
|
|
if (this.props.tooltip) {
|
|
attr.title = this.props.tooltip;
|
|
}
|
|
|
|
if (this.props.disabled) {
|
|
attr.disabled = this.props.disabled;
|
|
}
|
|
|
|
if (this.props.role) {
|
|
attr.role = this.props.role;
|
|
} else if (typeof this.props.checked !== "undefined") {
|
|
attr.role = "menuitemcheckbox";
|
|
} else {
|
|
attr.role = "menuitem";
|
|
}
|
|
|
|
if (this.props.checked) {
|
|
attr["aria-checked"] = true;
|
|
}
|
|
|
|
const children = [];
|
|
const className = "label";
|
|
|
|
// Add the text label.
|
|
if (this.props.l10nID) {
|
|
// Fluent localized label.
|
|
children.push(
|
|
Localized(
|
|
{ id: this.props.l10nID, key: "label" },
|
|
span({ className, ref: this.labelRef })
|
|
)
|
|
);
|
|
} else {
|
|
children.push(
|
|
span({ key: "label", className, ref: this.labelRef }, this.props.label)
|
|
);
|
|
}
|
|
|
|
if (this.props.l10nID && this.props.label) {
|
|
console.warn(
|
|
"<MenuItem> should only take either an l10nID or a label, not both"
|
|
);
|
|
}
|
|
if (!this.props.l10nID && !this.props.label) {
|
|
console.warn("<MenuItem> requires either an l10nID, or a label prop.");
|
|
}
|
|
|
|
if (typeof this.props.accelerator !== "undefined") {
|
|
const acceleratorLabel = span(
|
|
{ key: "accelerator", className: "accelerator" },
|
|
this.props.accelerator
|
|
);
|
|
children.push(acceleratorLabel);
|
|
}
|
|
|
|
return li(
|
|
{
|
|
className: "menuitem",
|
|
role: "presentation",
|
|
},
|
|
button(attr, children)
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = MenuItem;
|