import React, { ReactNode, FormEvent, MouseEvent, ChangeEvent } from 'react'; import type { DebouncedFunc } from 'lodash'; import { css } from '@emotion/react'; import { Select } from '@redux-devtools/ui'; import { QueryFormValues } from '../types.js'; import { StyleUtilsContext } from '../styles/themes.js'; import { SelectOption } from '../types.js'; import debounce from 'lodash.debounce'; import { sortQueryOptions, QueryComparators } from '../utils/comparators.js'; import { QueryFilters, filterQueryOptions } from '../utils/filters.js'; import { SortOrderButton } from './SortOrderButton.js'; import { RegexIcon } from './RegexIcon.js'; const srOnlyCss = css({ position: 'absolute', width: 1, height: 1, padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0,0,0,0)', border: 0, }); export interface QueryFormProps { values: QueryFormValues; searchQueryRegex: RegExp | null; onFormValuesChange: (values: Partial) => void; } interface QueryFormState { searchValue: string; } const selectId = 'rtk-query-comp-select'; const searchId = 'rtk-query-search-query'; const filterSelectId = 'rtk-query-search-query-select'; const searchPlaceholder = 'filter query by...'; const labels = { regexToggle: { info: 'Use regular expression search', error: 'Invalid regular expression provided', }, }; export class QueryForm extends React.PureComponent< QueryFormProps, QueryFormState > { constructor(props: QueryFormProps) { super(props); this.state = { searchValue: props.values.searchValue, }; } inputSearchRef = React.createRef(); handleSubmit = (evt: FormEvent): void => { evt.preventDefault(); }; handleButtonGroupClick = (isAsc: boolean): void => { this.props.onFormValuesChange({ isAscendingQueryComparatorOrder: isAsc }); }; handleSelectComparatorChange = ( option: SelectOption | undefined | null, ): void => { if (typeof option?.value === 'string') { this.props.onFormValuesChange({ queryComparator: option.value }); } }; handleSelectFilterChange = ( option: SelectOption | undefined | null, ): void => { if (typeof option?.value === 'string') { this.props.onFormValuesChange({ queryFilter: option.value }); } }; handleRegexSearchClick = (): void => { this.props.onFormValuesChange({ isRegexSearch: !this.props.values.isRegexSearch, }); }; restoreCaretPosition = (start: number | null, end: number | null): void => { window.requestAnimationFrame(() => { if (this.inputSearchRef.current) { this.inputSearchRef.current.selectionStart = start; this.inputSearchRef.current.selectionEnd = end; } }); }; invalidateSearchValueFromProps: DebouncedFunc<() => void> = debounce(() => { this.props.onFormValuesChange({ searchValue: this.state.searchValue, }); }, 150); handleSearchChange = (evt: ChangeEvent): void => { const searchValue = evt.target.value.trim(); this.setState({ searchValue }); this.invalidateSearchValueFromProps(); }; handleClearSearchClick = (evt: MouseEvent): void => { evt.preventDefault(); if (this.state.searchValue) { this.setState({ searchValue: '' }); this.invalidateSearchValueFromProps(); } }; render(): ReactNode { const { searchQueryRegex, values: { isAscendingQueryComparatorOrder: isAsc, queryComparator, searchValue, queryFilter, isRegexSearch, }, } = this.props; const isRegexInvalid = isRegexSearch && searchValue.length > 0 && searchQueryRegex == null; const regexToggleType = isRegexInvalid ? 'error' : 'info'; const regexToggleLabel = labels.regexToggle[regexToggleType]; return ( {({ base16Theme }) => { return (
({ display: 'flex', padding: 4, flex: '0 0 auto', alignItems: 'center', borderBottomWidth: '1px', borderBottomStyle: 'solid', borderColor: theme.LIST_BORDER_COLOR, })} >
({ maxWidth: '65%', backgroundColor: theme.BACKGROUND_COLOR, display: 'flex', alignItems: 'center', flexFlow: 'row nowrap', flex: '1 1 auto', paddingRight: 6, '& input': { outline: 'none', border: 'none', width: '100%', flex: '1 1 auto', padding: '5px 10px', fontSize: '1em', position: 'relative', fontFamily: 'monaco, Consolas, "Lucida Console", monospace', backgroundColor: theme.BACKGROUND_COLOR, color: theme.TEXT_COLOR, '&::-webkit-input-placeholder': { color: theme.TEXT_PLACEHOLDER_COLOR, }, '&::-moz-placeholder': { color: theme.TEXT_PLACEHOLDER_COLOR, }, '&::-webkit-search-cancel-button': { WebkitAppearance: 'none', }, }, })} >
> id={filterSelectId} isSearchable={false} options={filterQueryOptions} theme={base16Theme as any} value={filterQueryOptions.find( (opt) => opt?.value === queryFilter, )} onChange={this.handleSelectFilterChange} />
:last-child': { flex: '0 0 auto', marginLeft: '0.4em', }, }} > > id={selectId} isSearchable={false} theme={base16Theme as any} value={sortQueryOptions.find( (opt) => opt?.value === queryComparator, )} options={sortQueryOptions} onChange={this.handleSelectComparatorChange} />
); }}
); } }