mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 10:23:14 -04:00
fix unified search for long field inputs (#166024)
## Summary
Fixes https://github.com/elastic/kibana/issues/166019

### Checklist
Delete any items that are not applicable to this PR.
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---------
Co-authored-by: nickofthyme <nicholas.partridge@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
This commit is contained in:
parent
011a9d1e2c
commit
c6770af9a6
7 changed files with 52 additions and 32 deletions
|
@ -20,6 +20,7 @@ export interface GenericComboBoxProps<T> {
|
||||||
searchValue: string,
|
searchValue: string,
|
||||||
OPTION_CONTENT_CLASSNAME: string
|
OPTION_CONTENT_CLASSNAME: string
|
||||||
) => React.ReactNode;
|
) => React.ReactNode;
|
||||||
|
inputRef?: ((instance: HTMLInputElement | null) => void) | undefined;
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,12 +31,8 @@ const COMBOBOX_PADDINGS = 10;
|
||||||
const DEFAULT_FONT = '14px Inter';
|
const DEFAULT_FONT = '14px Inter';
|
||||||
|
|
||||||
class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
|
class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
|
||||||
comboBoxRef: React.RefObject<HTMLInputElement>;
|
comboBoxWrapperRef = React.createRef<HTMLDivElement>();
|
||||||
|
inputRef: HTMLInputElement | null = null;
|
||||||
constructor(props: PhraseValueInputProps) {
|
|
||||||
super(props);
|
|
||||||
this.comboBoxRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
|
@ -69,8 +65,11 @@ class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
|
||||||
const valueAsStr = String(value);
|
const valueAsStr = String(value);
|
||||||
const options = value ? uniq([valueAsStr, ...suggestions]) : suggestions;
|
const options = value ? uniq([valueAsStr, ...suggestions]) : suggestions;
|
||||||
return (
|
return (
|
||||||
<div ref={this.comboBoxRef}>
|
<div ref={this.comboBoxWrapperRef}>
|
||||||
<StringComboBox
|
<StringComboBox
|
||||||
|
inputRef={(ref) => {
|
||||||
|
this.inputRef = ref;
|
||||||
|
}}
|
||||||
isDisabled={this.props.disabled}
|
isDisabled={this.props.disabled}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
compressed={this.props.compressed}
|
compressed={this.props.compressed}
|
||||||
|
@ -85,7 +84,13 @@ class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
|
||||||
options={options}
|
options={options}
|
||||||
getLabel={(option) => option}
|
getLabel={(option) => option}
|
||||||
selectedOptions={value ? [valueAsStr] : []}
|
selectedOptions={value ? [valueAsStr] : []}
|
||||||
onChange={([newValue = '']) => onChange(newValue)}
|
onChange={([newValue = '']) => {
|
||||||
|
onChange(newValue);
|
||||||
|
setTimeout(() => {
|
||||||
|
// Note: requires a tick skip to correctly blur element focus
|
||||||
|
this.inputRef?.blur();
|
||||||
|
});
|
||||||
|
}}
|
||||||
onSearchChange={this.onSearchChange}
|
onSearchChange={this.onSearchChange}
|
||||||
singleSelection={{ asPlainText: true }}
|
singleSelection={{ asPlainText: true }}
|
||||||
onCreateOption={onChange}
|
onCreateOption={onChange}
|
||||||
|
@ -98,7 +103,7 @@ class PhraseValueInputUI extends PhraseSuggestorUI<PhraseValueInputProps> {
|
||||||
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
|
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
|
||||||
defaultFont={DEFAULT_FONT}
|
defaultFont={DEFAULT_FONT}
|
||||||
comboboxPaddings={COMBOBOX_PADDINGS}
|
comboboxPaddings={COMBOBOX_PADDINGS}
|
||||||
comboBoxRef={this.comboBoxRef}
|
comboBoxWrapperRef={this.comboBoxWrapperRef}
|
||||||
label={option.label}
|
label={option.label}
|
||||||
search={searchValue}
|
search={searchValue}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -33,12 +33,7 @@ const COMBOBOX_PADDINGS = 20;
|
||||||
const DEFAULT_FONT = '14px Inter';
|
const DEFAULT_FONT = '14px Inter';
|
||||||
|
|
||||||
class PhrasesValuesInputUI extends PhraseSuggestorUI<PhrasesValuesInputProps> {
|
class PhrasesValuesInputUI extends PhraseSuggestorUI<PhrasesValuesInputProps> {
|
||||||
comboBoxRef: React.RefObject<HTMLInputElement>;
|
comboBoxWrapperRef = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
constructor(props: PhrasesValuesInputProps) {
|
|
||||||
super(props);
|
|
||||||
this.comboBoxRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { suggestions } = this.state;
|
const { suggestions } = this.state;
|
||||||
|
@ -46,7 +41,7 @@ class PhrasesValuesInputUI extends PhraseSuggestorUI<PhrasesValuesInputProps> {
|
||||||
const options = values ? uniq([...values, ...suggestions]) : suggestions;
|
const options = values ? uniq([...values, ...suggestions]) : suggestions;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={this.comboBoxRef}>
|
<div ref={this.comboBoxWrapperRef}>
|
||||||
<StringComboBox
|
<StringComboBox
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
compressed={compressed}
|
compressed={compressed}
|
||||||
|
@ -79,7 +74,7 @@ class PhrasesValuesInputUI extends PhraseSuggestorUI<PhrasesValuesInputProps> {
|
||||||
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
|
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
|
||||||
defaultFont={DEFAULT_FONT}
|
defaultFont={DEFAULT_FONT}
|
||||||
comboboxPaddings={COMBOBOX_PADDINGS}
|
comboboxPaddings={COMBOBOX_PADDINGS}
|
||||||
comboBoxRef={this.comboBoxRef}
|
comboBoxWrapperRef={this.comboBoxWrapperRef}
|
||||||
label={option.label}
|
label={option.label}
|
||||||
search={searchValue}
|
search={searchValue}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,18 +6,16 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { TruncatedLabel } from './truncated_label';
|
import { TruncatedLabel } from './truncated_label';
|
||||||
|
|
||||||
describe('truncated_label', () => {
|
describe('truncated_label', () => {
|
||||||
const defaultProps = {
|
const defaultProps: ComponentProps<typeof TruncatedLabel> = {
|
||||||
defaultFont: '14px Inter',
|
defaultFont: '14px Inter',
|
||||||
// jest-canvas-mock mocks measureText as the number of string characters, thats why the width is so low
|
|
||||||
width: 30,
|
|
||||||
defaultComboboxWidth: 130,
|
defaultComboboxWidth: 130,
|
||||||
comboboxPaddings: 100,
|
comboboxPaddings: 100,
|
||||||
comboBoxRef: React.createRef<HTMLInputElement>(),
|
comboBoxWrapperRef: React.createRef<HTMLDivElement>(),
|
||||||
search: '',
|
search: '',
|
||||||
label: 'example_field',
|
label: 'example_field',
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { throttle } from 'lodash';
|
||||||
interface TruncatedLabelProps {
|
interface TruncatedLabelProps {
|
||||||
label: string;
|
label: string;
|
||||||
search: string;
|
search: string;
|
||||||
comboBoxRef: RefObject<HTMLInputElement>;
|
comboBoxWrapperRef: RefObject<HTMLDivElement | null>;
|
||||||
defaultFont: string;
|
defaultFont: string;
|
||||||
defaultComboboxWidth: number;
|
defaultComboboxWidth: number;
|
||||||
comboboxPaddings: number;
|
comboboxPaddings: number;
|
||||||
|
@ -56,7 +56,7 @@ const truncateLabel = (
|
||||||
|
|
||||||
export const TruncatedLabel = React.memo(function TruncatedLabel({
|
export const TruncatedLabel = React.memo(function TruncatedLabel({
|
||||||
label,
|
label,
|
||||||
comboBoxRef,
|
comboBoxWrapperRef,
|
||||||
search,
|
search,
|
||||||
defaultFont,
|
defaultFont,
|
||||||
defaultComboboxWidth,
|
defaultComboboxWidth,
|
||||||
|
@ -69,15 +69,14 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({
|
||||||
width: defaultComboboxWidth - comboboxPaddings,
|
width: defaultComboboxWidth - comboboxPaddings,
|
||||||
font: defaultFont,
|
font: defaultFont,
|
||||||
});
|
});
|
||||||
|
|
||||||
const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => {
|
const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => {
|
||||||
if (comboBoxRef.current) {
|
if (comboBoxWrapperRef.current) {
|
||||||
const current = {
|
const current = {
|
||||||
...labelProps,
|
...labelProps,
|
||||||
width: comboBoxRef.current?.clientWidth - comboboxPaddings,
|
width: comboBoxWrapperRef.current.clientWidth - comboboxPaddings,
|
||||||
};
|
};
|
||||||
if (shouldRecomputeAll) {
|
if (shouldRecomputeAll) {
|
||||||
current.font = window.getComputedStyle(comboBoxRef.current).font;
|
current.font = window.getComputedStyle(comboBoxWrapperRef.current).font;
|
||||||
}
|
}
|
||||||
setLabelProps(current);
|
setLabelProps(current);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +87,7 @@ export const TruncatedLabel = React.memo(function TruncatedLabel({
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
if (comboBoxRef.current) {
|
if (comboBoxWrapperRef.current) {
|
||||||
handleResize(undefined, true);
|
handleResize(undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,8 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
|
||||||
const { disabled, suggestionsAbstraction } = useContext(FiltersBuilderContextType);
|
const { disabled, suggestionsAbstraction } = useContext(FiltersBuilderContextType);
|
||||||
const fields = dataView ? getFilterableFields(dataView) : [];
|
const fields = dataView ? getFilterableFields(dataView) : [];
|
||||||
const id = useGeneratedHtmlId({ prefix: 'fieldInput' });
|
const id = useGeneratedHtmlId({ prefix: 'fieldInput' });
|
||||||
const comboBoxRef = useRef<HTMLInputElement>(null);
|
const comboBoxWrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const onFieldChange = useCallback(
|
const onFieldChange = useCallback(
|
||||||
([selectedField]: DataViewField[]) => {
|
([selectedField]: DataViewField[]) => {
|
||||||
|
@ -77,12 +78,25 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
|
||||||
({ label }) => fields[optionFields.findIndex((optionField) => optionField.label === label)]
|
({ label }) => fields[optionFields.findIndex((optionField) => optionField.label === label)]
|
||||||
);
|
);
|
||||||
onFieldChange(newValues);
|
onFieldChange(newValues);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// Note: requires a tick skip to correctly blur element focus
|
||||||
|
inputRef?.current?.blur();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus: React.FocusEventHandler<HTMLDivElement> = () => {
|
||||||
|
// Force focus on input due to https://github.com/elastic/eui/issues/7170
|
||||||
|
inputRef?.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={comboBoxRef}>
|
<div ref={comboBoxWrapperRef}>
|
||||||
<EuiComboBox
|
<EuiComboBox
|
||||||
id={id}
|
id={id}
|
||||||
|
inputRef={(ref) => {
|
||||||
|
inputRef.current = ref;
|
||||||
|
}}
|
||||||
options={euiOptions}
|
options={euiOptions}
|
||||||
selectedOptions={selectedEuiOptions}
|
selectedOptions={selectedEuiOptions}
|
||||||
onChange={onComboBoxChange}
|
onChange={onComboBoxChange}
|
||||||
|
@ -94,6 +108,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
compressed
|
compressed
|
||||||
fullWidth
|
fullWidth
|
||||||
|
onFocus={handleFocus}
|
||||||
data-test-subj="filterFieldSuggestionList"
|
data-test-subj="filterFieldSuggestionList"
|
||||||
renderOption={(option, searchValue) => (
|
renderOption={(option, searchValue) => (
|
||||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||||
|
@ -105,7 +120,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
|
||||||
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
|
defaultComboboxWidth={DEFAULT_COMBOBOX_WIDTH}
|
||||||
defaultFont={DEFAULT_FONT}
|
defaultFont={DEFAULT_FONT}
|
||||||
comboboxPaddings={COMBOBOX_PADDINGS}
|
comboboxPaddings={COMBOBOX_PADDINGS}
|
||||||
comboBoxRef={comboBoxRef}
|
comboBoxWrapperRef={comboBoxWrapperRef}
|
||||||
label={option.label}
|
label={option.label}
|
||||||
search={searchValue}
|
search={searchValue}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -22,6 +22,13 @@ export const cursorOrCss = css`
|
||||||
|
|
||||||
export const fieldAndParamCss = (euiTheme: EuiThemeComputed) => css`
|
export const fieldAndParamCss = (euiTheme: EuiThemeComputed) => css`
|
||||||
min-width: calc(${euiTheme.size.xl} * 5);
|
min-width: calc(${euiTheme.size.xl} * 5);
|
||||||
|
flex-grow: 1;
|
||||||
|
.euiFormRow {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
&:focus-within {
|
||||||
|
flex-grow: 4;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const operationCss = (euiTheme: EuiThemeComputed) => css`
|
export const operationCss = (euiTheme: EuiThemeComputed) => css`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue