diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /devtools/client/debugger/src/components/shared/SearchInput.js | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/debugger/src/components/shared/SearchInput.js')
-rw-r--r-- | devtools/client/debugger/src/components/shared/SearchInput.js | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/shared/SearchInput.js b/devtools/client/debugger/src/components/shared/SearchInput.js new file mode 100644 index 0000000000..18f6ffbebb --- /dev/null +++ b/devtools/client/debugger/src/components/shared/SearchInput.js @@ -0,0 +1,362 @@ +/* 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/>. */ + +import React, { Component } from "devtools/client/shared/vendor/react"; +import { + button, + div, + label, + input, + span, +} from "devtools/client/shared/vendor/react-dom-factories"; +import PropTypes from "devtools/client/shared/vendor/react-prop-types"; +import { connect } from "devtools/client/shared/vendor/react-redux"; +import { CloseButton } from "./Button/index"; + +import AccessibleImage from "./AccessibleImage"; +import actions from "../../actions/index"; +import { getSearchOptions } from "../../selectors/index"; + +const classnames = require("resource://devtools/client/shared/classnames.js"); +const SearchModifiers = require("resource://devtools/client/shared/components/SearchModifiers.js"); + +const arrowBtn = (onClick, type, className, tooltip) => { + const props = { + className, + key: type, + onClick, + title: tooltip, + type, + }; + return button( + props, + React.createElement(AccessibleImage, { + className: type, + }) + ); +}; + +export class SearchInput extends Component { + static defaultProps = { + expanded: false, + hasPrefix: false, + selectedItemId: "", + size: "", + showClose: true, + }; + + constructor(props) { + super(props); + this.state = { + history: [], + excludePatterns: props.searchOptions.excludePatterns, + }; + } + + static get propTypes() { + return { + count: PropTypes.number.isRequired, + expanded: PropTypes.bool.isRequired, + handleClose: PropTypes.func, + handleNext: PropTypes.func, + handlePrev: PropTypes.func, + hasPrefix: PropTypes.bool.isRequired, + isLoading: PropTypes.bool.isRequired, + onBlur: PropTypes.func, + onChange: PropTypes.func, + onFocus: PropTypes.func, + onHistoryScroll: PropTypes.func, + onKeyDown: PropTypes.func, + onKeyUp: PropTypes.func, + placeholder: PropTypes.string, + query: PropTypes.string, + selectedItemId: PropTypes.string, + shouldFocus: PropTypes.bool, + showClose: PropTypes.bool.isRequired, + showExcludePatterns: PropTypes.bool.isRequired, + excludePatternsLabel: PropTypes.string, + excludePatternsPlaceholder: PropTypes.string, + showErrorEmoji: PropTypes.bool.isRequired, + size: PropTypes.string, + summaryMsg: PropTypes.string, + searchKey: PropTypes.string.isRequired, + searchOptions: PropTypes.object, + setSearchOptions: PropTypes.func, + showSearchModifiers: PropTypes.bool.isRequired, + onToggleSearchModifier: PropTypes.func, + }; + } + + componentDidMount() { + this.setFocus(); + } + + componentDidUpdate(prevProps) { + if (this.props.shouldFocus && !prevProps.shouldFocus) { + this.setFocus(); + } + } + + setFocus() { + if (this.$input) { + const _input = this.$input; + _input.focus(); + + if (!_input.value) { + return; + } + + // omit prefix @:# from being selected + const selectStartPos = this.props.hasPrefix ? 1 : 0; + _input.setSelectionRange(selectStartPos, _input.value.length + 1); + } + } + + renderArrowButtons() { + const { handleNext, handlePrev } = this.props; + + return [ + arrowBtn( + handlePrev, + "arrow-up", + classnames("nav-btn", "prev"), + L10N.getFormatStr("editor.searchResults.prevResult") + ), + arrowBtn( + handleNext, + "arrow-down", + classnames("nav-btn", "next"), + L10N.getFormatStr("editor.searchResults.nextResult") + ), + ]; + } + + onFocus = e => { + const { onFocus } = this.props; + + if (onFocus) { + onFocus(e); + } + }; + + onBlur = e => { + const { onBlur } = this.props; + + if (onBlur) { + onBlur(e); + } + }; + + onKeyDown = e => { + const { onHistoryScroll, onKeyDown } = this.props; + if (!onHistoryScroll) { + onKeyDown(e); + return; + } + + const inputValue = e.target.value; + const { history } = this.state; + const currentHistoryIndex = history.indexOf(inputValue); + + if (e.key === "Enter") { + this.saveEnteredTerm(inputValue); + onKeyDown(e); + return; + } + + if (e.key === "ArrowUp") { + const previous = + currentHistoryIndex > -1 ? currentHistoryIndex - 1 : history.length - 1; + const previousInHistory = history[previous]; + if (previousInHistory) { + e.preventDefault(); + onHistoryScroll(previousInHistory); + } + return; + } + + if (e.key === "ArrowDown") { + const next = currentHistoryIndex + 1; + const nextInHistory = history[next]; + if (nextInHistory) { + onHistoryScroll(nextInHistory); + } + } + }; + + onExcludeKeyDown = e => { + if (e.key === "Enter") { + this.props.setSearchOptions(this.props.searchKey, { + excludePatterns: this.state.excludePatterns, + }); + this.props.onKeyDown(e); + } + }; + + saveEnteredTerm(query) { + const { history } = this.state; + const previousIndex = history.indexOf(query); + if (previousIndex !== -1) { + history.splice(previousIndex, 1); + } + history.push(query); + this.setState({ history }); + } + + renderSummaryMsg() { + const { summaryMsg } = this.props; + + if (!summaryMsg) { + return null; + } + return div( + { + className: "search-field-summary", + }, + summaryMsg + ); + } + + renderSpinner() { + const { isLoading } = this.props; + if (!isLoading) { + return null; + } + return React.createElement(AccessibleImage, { + className: "loader spin", + }); + } + + renderNav() { + const { count, handleNext, handlePrev } = this.props; + if ((!handleNext && !handlePrev) || !count || count == 1) { + return null; + } + return div( + { + className: "search-nav-buttons", + }, + this.renderArrowButtons() + ); + } + + renderSearchModifiers() { + if (!this.props.showSearchModifiers) { + return null; + } + return React.createElement(SearchModifiers, { + modifiers: this.props.searchOptions, + onToggleSearchModifier: updatedOptions => { + this.props.setSearchOptions(this.props.searchKey, updatedOptions); + this.props.onToggleSearchModifier(); + }, + }); + } + + renderExcludePatterns() { + if (!this.props.showExcludePatterns) { + return null; + } + return div( + { + className: classnames("exclude-patterns-field", this.props.size), + }, + label(null, this.props.excludePatternsLabel), + input({ + placeholder: this.props.excludePatternsPlaceholder, + value: this.state.excludePatterns, + onKeyDown: this.onExcludeKeyDown, + onChange: e => + this.setState({ + excludePatterns: e.target.value, + }), + }) + ); + } + + renderClose() { + if (!this.props.showClose) { + return null; + } + return React.createElement( + React.Fragment, + null, + span({ + className: "pipe-divider", + }), + React.createElement(CloseButton, { + handleClick: this.props.handleClose, + buttonClass: this.props.size, + }) + ); + } + + render() { + const { + expanded, + onChange, + onKeyUp, + placeholder, + query, + selectedItemId, + showErrorEmoji, + size, + } = this.props; + + const inputProps = { + className: classnames({ + empty: showErrorEmoji, + }), + onChange, + onKeyDown: e => this.onKeyDown(e), + onKeyUp, + onFocus: e => this.onFocus(e), + onBlur: e => this.onBlur(e), + "aria-autocomplete": "list", + "aria-controls": "result-list", + "aria-activedescendant": + expanded && selectedItemId ? `${selectedItemId}-title` : "", + placeholder, + value: query, + spellCheck: false, + ref: c => (this.$input = c), + }; + return div( + { + className: "search-outline", + }, + div( + { + className: classnames("search-field", size), + role: "combobox", + "aria-haspopup": "listbox", + "aria-owns": "result-list", + "aria-expanded": expanded, + }, + React.createElement(AccessibleImage, { + className: "search", + }), + input(inputProps), + this.renderSpinner(), + this.renderSummaryMsg(), + this.renderNav(), + div( + { + className: "search-buttons-bar", + }, + this.renderSearchModifiers(), + this.renderClose() + ) + ), + this.renderExcludePatterns() + ); + } +} +const mapStateToProps = (state, props) => ({ + searchOptions: getSearchOptions(state, props.searchKey), +}); + +export default connect(mapStateToProps, { + setSearchOptions: actions.setSearchOptions, +})(SearchInput); |