mirror of
https://github.com/HeyPuter/firefox.git
synced 2026-11-13 20:14:57 -10:00
This should allow the number of hidden messages to be notified to screen reader users. We update the Console FilterBar behavior to always show the "X hidden" message when the input is focused, so screen reader can also pick up when no messages are hidden. Differential Revision: https://phabricator.services.mozilla.com/D213181
278 lines
6.8 KiB
JavaScript
278 lines
6.8 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/. */
|
|
|
|
/* global window */
|
|
|
|
"use strict";
|
|
|
|
const {
|
|
createFactory,
|
|
createRef,
|
|
PureComponent,
|
|
} = require("resource://devtools/client/shared/vendor/react.js");
|
|
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
|
|
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
|
|
|
|
const { LocalizationHelper } = require("resource://devtools/shared/l10n.js");
|
|
const l10n = new LocalizationHelper(
|
|
"devtools/client/locales/components.properties"
|
|
);
|
|
|
|
loader.lazyGetter(this, "SearchBoxAutocompletePopup", function () {
|
|
return createFactory(
|
|
require("resource://devtools/client/shared/components/SearchBoxAutocompletePopup.js")
|
|
);
|
|
});
|
|
loader.lazyGetter(this, "MDNLink", function () {
|
|
return createFactory(
|
|
require("resource://devtools/client/shared/components/MdnLink.js")
|
|
);
|
|
});
|
|
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"KeyShortcuts",
|
|
"resource://devtools/client/shared/key-shortcuts.js"
|
|
);
|
|
|
|
class SearchBox extends PureComponent {
|
|
static get propTypes() {
|
|
return {
|
|
autocompleteProvider: PropTypes.func,
|
|
delay: PropTypes.number,
|
|
keyShortcut: PropTypes.string,
|
|
learnMoreTitle: PropTypes.string,
|
|
learnMoreUrl: PropTypes.string,
|
|
onBlur: PropTypes.func,
|
|
onChange: PropTypes.func.isRequired,
|
|
onClearButtonClick: PropTypes.func,
|
|
onFocus: PropTypes.func,
|
|
// Optional function that will be called on the focus keyboard shortcut, before
|
|
// setting the focus to the input. If the function returns false, the input won't
|
|
// get focused.
|
|
onFocusKeyboardShortcut: PropTypes.func,
|
|
onKeyDown: PropTypes.func,
|
|
placeholder: PropTypes.string.isRequired,
|
|
summary: PropTypes.string,
|
|
summaryId: PropTypes.string,
|
|
summaryTooltip: PropTypes.string,
|
|
type: PropTypes.string,
|
|
value: PropTypes.string,
|
|
};
|
|
}
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
value: props.value || "",
|
|
focused: false,
|
|
};
|
|
|
|
this.autocompleteRef = createRef();
|
|
this.inputRef = createRef();
|
|
|
|
this.onBlur = this.onBlur.bind(this);
|
|
this.onChange = this.onChange.bind(this);
|
|
this.onClearButtonClick = this.onClearButtonClick.bind(this);
|
|
this.onFocus = this.onFocus.bind(this);
|
|
this.onKeyDown = this.onKeyDown.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (!this.props.keyShortcut) {
|
|
return;
|
|
}
|
|
|
|
this.shortcuts = new KeyShortcuts({
|
|
window,
|
|
});
|
|
this.shortcuts.on(this.props.keyShortcut, event => {
|
|
if (this.props.onFocusKeyboardShortcut?.(event)) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
this.focus();
|
|
});
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.shortcuts) {
|
|
this.shortcuts.destroy();
|
|
}
|
|
|
|
// Clean up an existing timeout.
|
|
if (this.searchTimeout) {
|
|
clearTimeout(this.searchTimeout);
|
|
}
|
|
}
|
|
|
|
focus() {
|
|
if (this.inputRef) {
|
|
this.inputRef.current.focus();
|
|
}
|
|
}
|
|
|
|
onChange(inputValue = "") {
|
|
if (this.state.value !== inputValue) {
|
|
this.setState({
|
|
focused: true,
|
|
value: inputValue,
|
|
});
|
|
}
|
|
|
|
if (!this.props.delay) {
|
|
this.props.onChange(inputValue);
|
|
return;
|
|
}
|
|
|
|
// Clean up an existing timeout before creating a new one.
|
|
if (this.searchTimeout) {
|
|
clearTimeout(this.searchTimeout);
|
|
}
|
|
|
|
// Execute the search after a timeout. It makes the UX
|
|
// smoother if the user is typing quickly.
|
|
this.searchTimeout = setTimeout(() => {
|
|
this.searchTimeout = null;
|
|
this.props.onChange(this.state.value);
|
|
}, this.props.delay);
|
|
}
|
|
|
|
onClearButtonClick() {
|
|
this.onChange("");
|
|
|
|
if (this.props.onClearButtonClick) {
|
|
this.props.onClearButtonClick();
|
|
}
|
|
}
|
|
|
|
onFocus() {
|
|
if (this.props.onFocus) {
|
|
this.props.onFocus();
|
|
}
|
|
|
|
this.setState({ focused: true });
|
|
}
|
|
|
|
onBlur() {
|
|
if (this.props.onBlur) {
|
|
this.props.onBlur();
|
|
}
|
|
|
|
this.setState({ focused: false });
|
|
}
|
|
|
|
onKeyDown(e) {
|
|
if (this.props.onKeyDown) {
|
|
this.props.onKeyDown(e);
|
|
}
|
|
|
|
const autocomplete = this.autocompleteRef.current;
|
|
if (!autocomplete || autocomplete.state.list.length <= 0) {
|
|
return;
|
|
}
|
|
|
|
switch (e.key) {
|
|
case "ArrowDown":
|
|
e.preventDefault();
|
|
autocomplete.jumpBy(1);
|
|
break;
|
|
case "ArrowUp":
|
|
e.preventDefault();
|
|
autocomplete.jumpBy(-1);
|
|
break;
|
|
case "PageDown":
|
|
e.preventDefault();
|
|
autocomplete.jumpBy(5);
|
|
break;
|
|
case "PageUp":
|
|
e.preventDefault();
|
|
autocomplete.jumpBy(-5);
|
|
break;
|
|
case "Enter":
|
|
case "Tab":
|
|
e.preventDefault();
|
|
autocomplete.select();
|
|
break;
|
|
case "Escape":
|
|
e.preventDefault();
|
|
this.onBlur();
|
|
break;
|
|
case "Home":
|
|
e.preventDefault();
|
|
autocomplete.jumpToTop();
|
|
break;
|
|
case "End":
|
|
e.preventDefault();
|
|
autocomplete.jumpToBottom();
|
|
break;
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
autocompleteProvider,
|
|
summary,
|
|
summaryId,
|
|
summaryTooltip,
|
|
learnMoreTitle,
|
|
learnMoreUrl,
|
|
placeholder,
|
|
type = "search",
|
|
} = this.props;
|
|
const { value } = this.state;
|
|
const showAutocomplete =
|
|
autocompleteProvider && this.state.focused && value !== "";
|
|
const showLearnMoreLink = learnMoreUrl && value === "";
|
|
|
|
return dom.div(
|
|
{ className: "devtools-searchbox" },
|
|
dom.input({
|
|
className: `devtools-${type}input`,
|
|
onBlur: this.onBlur,
|
|
onChange: e => this.onChange(e.target.value),
|
|
onFocus: this.onFocus,
|
|
onKeyDown: this.onKeyDown,
|
|
placeholder,
|
|
ref: this.inputRef,
|
|
value,
|
|
type: "search",
|
|
"aria-describedby": (summary && summaryId) || undefined,
|
|
}),
|
|
showLearnMoreLink &&
|
|
MDNLink({
|
|
title: learnMoreTitle,
|
|
url: learnMoreUrl,
|
|
}),
|
|
summary
|
|
? dom.span(
|
|
{
|
|
className: "devtools-searchinput-summary",
|
|
id: summaryId,
|
|
title: summaryTooltip,
|
|
},
|
|
summary
|
|
)
|
|
: null,
|
|
dom.button({
|
|
className: "devtools-searchinput-clear",
|
|
hidden: value === "",
|
|
onClick: this.onClearButtonClick,
|
|
title: l10n.getStr("searchBox.clearButtonTitle"),
|
|
}),
|
|
showAutocomplete &&
|
|
SearchBoxAutocompletePopup({
|
|
autocompleteProvider,
|
|
filter: value,
|
|
onItemSelected: itemValue => this.onChange(itemValue),
|
|
ref: this.autocompleteRef,
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = SearchBox;
|