diff --git a/src/components/combo_box/_combo_box.scss b/src/components/combo_box/_combo_box.scss index 6c0ecb3611a..f87585d60fa 100644 --- a/src/components/combo_box/_combo_box.scss +++ b/src/components/combo_box/_combo_box.scss @@ -45,9 +45,32 @@ height: auto; /* 3 */ flex-wrap: wrap; /* 1 */ align-content: flex-start; + cursor: text; + } + + &.euiComboBox__inputWrap--noWrap { + align-items: center; + } + + // Match placeholder and plain text position to EuiFieldText + &.euiComboBox__inputWrap--plainText { + padding-inline-start: $euiSizeS; + } + + .euiComboBoxPlainTextSelection { + &__prepend, + &__append { + flex-shrink: 0; + margin-inline: $euiSizeXS / 2; + display: flex; /* Vertically centers any icons */ + } + + &__prepend { + margin-inline-start: $euiSizeXS; + } - &:hover { - cursor: text; + &__append { + margin-inline-end: $euiSizeXS; } } } @@ -56,6 +79,7 @@ * 1. Force field height to match other field heights. * 2. Force input height to expand to fill this element. * 3. Reset input appearance to mimic text + * 4. Ensure that no input states are visible on the hidden input */ .euiComboBox__input { block-size: $euiSizeL; /* 2 */ @@ -64,11 +88,20 @@ margin: $euiSizeXS; /* 3 */ - appearance: none; - outline: none; - border: none; background: transparent; - color: $euiTextColor; + @include euiFormControlText; + + &:disabled { + @include euiFormControlDisabledStyle; + } + + /* 4 */ + // stylelint-disable declaration-no-important + appearance: none !important; + border: none !important; + box-shadow: none !important; + outline: none !important; + // stylelint-enable declaration-no-important } &.euiComboBox-isOpen { @@ -94,13 +127,9 @@ -webkit-text-fill-color: unset; // overrides $euiFormControlDisabledColor because the color doesn't work with multiple background colors } - .euiComboBoxPlaceholder, - .euiComboBoxPill--plainText { - @include euiFormControlDisabledTextStyle; - } - // overrides the `cursor: text;` that displays on hover for combo boxes that allow multiple pills - .euiComboBox__inputWrap:not(.euiComboBox__inputWrap--noWrap):hover { + .euiComboBox__inputWrap, + .euiComboBoxPill { cursor: not-allowed; } } @@ -108,8 +137,11 @@ &.euiComboBox--compressed { .euiComboBox__inputWrap { line-height: $euiFormControlCompressedHeight; /* 2 */ - padding-top: 0; - padding-bottom: 0; + padding-block: 0; + + &--plainText { + padding-inline-start: $euiSizeXS; + } } } } diff --git a/src/components/combo_box/combo_box.spec.tsx b/src/components/combo_box/combo_box.spec.tsx index c9f45a66836..dee351c7075 100644 --- a/src/components/combo_box/combo_box.spec.tsx +++ b/src/components/combo_box/combo_box.spec.tsx @@ -232,6 +232,7 @@ describe('EuiComboBox', () => { }); }); }); + describe('selection', () => { const defaultOptions: Array> = [ { label: 'Item 1' }, @@ -311,6 +312,51 @@ describe('EuiComboBox', () => { }); }); + describe('single selection', () => { + describe('closes the combobox on dropdown selection, and re-opens on input click', () => { + it('as pill', () => { + cy.mount(); + cy.get('[data-test-subj="comboBoxInput"]').click(); + + cy.get('[data-test-subj="comboBoxOptionsList"]') + .find('button') + .first() + .click(); + cy.get('[data-test-subj="comboBoxOptionsList"]').should('not.exist'); + cy.focused().should( + 'have.attr', + 'data-test-subj', + 'comboBoxSearchInput' + ); + + cy.get('[data-test-subj="comboBoxInput"]').click(); + cy.get('[data-test-subj="comboBoxOptionsList"]').should('be.visible'); + }); + + it('as plain text', () => { + cy.mount( + // @ts-ignore - not totally sure why TS is kicking up a fuss here + + ); + cy.get('[data-test-subj="comboBoxSearchInput"]').click(); + + cy.get('[data-test-subj="comboBoxOptionsList"]') + .find('button') + .first() + .click(); + cy.get('[data-test-subj="comboBoxOptionsList"]').should('not.exist'); + cy.focused().should( + 'have.attr', + 'data-test-subj', + 'comboBoxSearchInput' + ); + + cy.get('[data-test-subj="comboBoxSearchInput"]').click(); + cy.get('[data-test-subj="comboBoxOptionsList"]').should('be.visible'); + }); + }); + }); + describe('backspace to delete last pill', () => { it('does not delete the last pill if there is search text', () => { cy.realMount(); @@ -361,6 +407,28 @@ describe('EuiComboBox', () => { cy.get('.euiComboBoxPill').should('have.length', 1); }); + it('`asPlainText`: deletes the selection and only a single character', () => { + cy.realMount( + // @ts-ignore - not totally sure why TS is kicking up a fuss here + + ); + cy.get('[data-test-subj=comboBoxSearchInput]').realClick(); + cy.realPress('{downarrow}'); + cy.realPress('Enter'); + cy.get('[data-test-subj=comboBoxSearchInput]').should( + 'have.value', + 'Item 1' + ); + cy.get('[data-test-subj="comboBoxClearButton"]').should('exist'); // indicates selection + + cy.get('[data-test-subj=comboBoxSearchInput]').realPress('Backspace'); + cy.get('[data-test-subj="comboBoxClearButton"]').should('not.exist'); // selection removed + cy.get('[data-test-subj=comboBoxSearchInput]').should( + 'have.value', + 'Item ' + ); + }); + it('opens up the selection list again after deleting the active single selection ', () => { cy.realMount(); cy.get('[data-test-subj=comboBoxSearchInput]').realClick(); diff --git a/src/components/combo_box/combo_box.test.tsx b/src/components/combo_box/combo_box.test.tsx index 4dcdcc9274d..9824ba43a32 100644 --- a/src/components/combo_box/combo_box.test.tsx +++ b/src/components/combo_box/combo_box.test.tsx @@ -111,114 +111,24 @@ describe('EuiComboBox', () => { ]; it('renders in pills', () => { - const { getByTestSubject, getAllByTestSubject } = render( + const { getByTestSubject } = render( ); expect(getByTestSubject('prepend')).toBeInTheDocument(); expect(getByTestSubject('append')).toBeInTheDocument(); - - expect(getAllByTestSubject('euiComboBoxPill')[0]) - .toMatchInlineSnapshot(` - - - - - - Pre - - - - 1 - - - - - - `); }); test('renders in the options dropdown', async () => { - const { getByTestSubject, getAllByRole } = render( - - ); + const { getByTestSubject } = render(); await showEuiComboBoxOptions(); - const dropdown = getByTestSubject('comboBoxOptionsList'); - expect( - dropdown.querySelector('.euiComboBoxOption__prepend') - ).toBeInTheDocument(); - expect( - dropdown.querySelector('.euiComboBoxOption__append') - ).toBeInTheDocument(); - - expect(getAllByRole('option')[0]).toMatchInlineSnapshot(` - - `); + expect(getByTestSubject('prepend')).toBeInTheDocument(); + expect(getByTestSubject('append')).toBeInTheDocument(); }); test('renders in single selection', () => { - const { getByTestSubject } = render( + const { getByTestSubject, queryByTestSubject } = render( { /> ); - expect(getByTestSubject('euiComboBoxPill')).toMatchInlineSnapshot(` - - - - Pre - - - - 1 - - - `); + expect(getByTestSubject('prepend')).toBeInTheDocument(); + expect(queryByTestSubject('append')).not.toBeInTheDocument(); }); }); @@ -308,8 +199,8 @@ describe('EuiComboBox', () => { ).toBeInTheDocument(); }); - it('renders as plain text', () => { - const { getByTestSubject } = render( + it('renders `asPlainText` in the search input, not as a pill', () => { + const { queryByTestSubject, getByTestSubject } = render( { /> ); - expect(getByTestSubject('euiComboBoxPill').className).toContain( - 'euiComboBoxPill--plainText' + const searchInput = getByTestSubject('comboBoxSearchInput'); + expect(searchInput).toHaveValue('Mimas'); + expect(searchInput).toHaveStyle('inline-size: 100%'); + + expect(queryByTestSubject('euiComboBoxPill')).not.toBeInTheDocument(); + }); + }); + + describe('placeholder', () => { + it('renders', () => { + const { getByTestSubject } = render( + + ); + const searchInput = getByTestSubject('comboBoxSearchInput'); + + expect(searchInput).toHaveAttribute('placeholder', 'Select something'); + expect(searchInput).toHaveStyle('inline-size: 100%'); + }); + + it('does not render the placeholder if a selection has been made', () => { + const { getByTestSubject } = render( + ); + const searchInput = getByTestSubject('comboBoxSearchInput'); + expect(searchInput).not.toHaveAttribute('placeholder'); + }); + + it('does not render the placeholder if a search value exists', () => { + const { getByTestSubject } = render( + + ); + const searchInput = getByTestSubject('comboBoxSearchInput'); + expect(searchInput).toHaveAttribute('placeholder'); + + fireEvent.change(searchInput, { target: { value: 'some search' } }); + expect(searchInput).not.toHaveAttribute('placeholder'); }); }); diff --git a/src/components/combo_box/combo_box_input/_combo_box_input.scss b/src/components/combo_box/combo_box_input/_combo_box_input.scss deleted file mode 100644 index 963872b94a6..00000000000 --- a/src/components/combo_box/combo_box_input/_combo_box_input.scss +++ /dev/null @@ -1,12 +0,0 @@ -.euiComboBox__input { - max-width: 100%; - - // Ensure that no input states are visible on the hidden input - input { - // stylelint-disable declaration-no-important - border: none !important; - box-shadow: none !important; - outline: none !important; - // stylelint-enable declaration-no-important - } -} diff --git a/src/components/combo_box/combo_box_input/_combo_box_pill.scss b/src/components/combo_box/combo_box_input/_combo_box_pill.scss index e5352921575..9eccbd409db 100644 --- a/src/components/combo_box/combo_box_input/_combo_box_pill.scss +++ b/src/components/combo_box/combo_box_input/_combo_box_pill.scss @@ -18,22 +18,6 @@ margin: ($euiSizeXS + 1px) $euiSizeXS 0 0; } - .euiComboBox--compressed &--plainText { - margin-top: $euiSizeXS; - } - - &--plainText { - @include euiFont; - - line-height: $euiSizeL; - font-size: $euiFontSizeS; - padding: 0; - color: $euiTextColor; - vertical-align: middle; - display: inline-flex; - align-items: center; - } - &__prepend { margin-right: $euiSizeXS; } @@ -43,7 +27,6 @@ } /* Fix append/prepend vertical alignment */ - &--plainText, .euiBadge__text { display: flex; align-items: center; diff --git a/src/components/combo_box/combo_box_input/_combo_box_placeholder.scss b/src/components/combo_box/combo_box_input/_combo_box_placeholder.scss deleted file mode 100644 index 0fafa39d05b..00000000000 --- a/src/components/combo_box/combo_box_input/_combo_box_placeholder.scss +++ /dev/null @@ -1,11 +0,0 @@ -.euiComboBoxPlaceholder { - @include euiTextTruncate; - // Afford for the caret. The loading state is accounted for in _combo_box.scss - @include euiFormControlLayoutPadding(1, 'right'); - position: absolute; - pointer-events: none; - padding-left: $euiSizeXS; - line-height: $euiSizeXL; - color: $euiFormControlPlaceholderText; - margin-bottom: 0 !important; // stylelint-disable-line declaration-no-important -} diff --git a/src/components/combo_box/combo_box_input/_index.scss b/src/components/combo_box/combo_box_input/_index.scss index aae81c07191..742addb8c4e 100644 --- a/src/components/combo_box/combo_box_input/_index.scss +++ b/src/components/combo_box/combo_box_input/_index.scss @@ -1,3 +1 @@ -@import 'combo_box_input'; @import 'combo_box_pill'; -@import 'combo_box_placeholder'; diff --git a/src/components/combo_box/combo_box_input/combo_box_input.tsx b/src/components/combo_box/combo_box_input/combo_box_input.tsx index 004112c7e8e..4453cf2379b 100644 --- a/src/components/combo_box/combo_box_input/combo_box_input.tsx +++ b/src/components/combo_box/combo_box_input/combo_box_input.tsx @@ -24,12 +24,13 @@ import { import { EuiFormControlLayoutIconsProps } from '../../form/form_control_layout/form_control_layout_icons'; import { getFormControlClassNameForIconCount } from '../../form/form_control_layout/_num_icons'; -import { EuiComboBoxPill } from './combo_box_pill'; import { EuiComboBoxOptionOption, EuiComboBoxSingleSelectionShape, OptionHandler, } from '../types'; +import { EuiComboBoxOptionAppendPrepend } from '../utils'; +import { EuiComboBoxPill } from './combo_box_pill'; export interface EuiComboBoxInputProps extends CommonProps { compressed: boolean; @@ -88,6 +89,7 @@ export class EuiComboBoxInput extends Component< updateInputSize = (inputValue: string) => { if (!this.widthUtils) return; + if (this.asPlainText) return; this.widthUtils.setTextToCheck(inputValue); // Canvas has minute subpixel differences in rendering compared to DOM @@ -122,26 +124,94 @@ export class EuiComboBoxInput extends Component< onKeyDown: KeyboardEventHandler = (event) => { const { searchValue, + hasSelectedOptions, selectedOptions, onRemoveOption, singleSelection, isListOpen, onOpenListClick, + onChange, } = this.props; - // When backspacing from an empty input, delete the last pill option in the list const searchIsEmpty = !searchValue.length; - const hasPills = selectedOptions.length; - if (event.key === keys.BACKSPACE && searchIsEmpty && hasPills) { - onRemoveOption(selectedOptions[selectedOptions.length - 1]); + if (event.key === keys.BACKSPACE) { + // When backspacing in a plain text combobox, change normally and remove the selection + if (this.asPlainText) { + onChange(event.currentTarget.value); + + if (hasSelectedOptions) { + onRemoveOption(selectedOptions[selectedOptions.length - 1]); + } + } + // When backspacing from an empty input, delete the last pill option in the list + else if (searchIsEmpty && hasSelectedOptions) { + onRemoveOption(selectedOptions[selectedOptions.length - 1]); - if (!!singleSelection && !isListOpen) { - onOpenListClick(); + if (!!singleSelection && !isListOpen) { + onOpenListClick(); + } } } }; + get asPlainText() { + const { singleSelection } = this.props; + const isSingleSelectionConfig = + singleSelection && typeof singleSelection === 'object'; + + return !!(isSingleSelectionConfig && singleSelection.asPlainText); + } + + get searchValue() { + const { searchValue, selectedOptions } = this.props; + if (this.asPlainText) { + return searchValue || selectedOptions?.[0]?.label || ''; + } else { + return searchValue; + } + } + + renderPills = () => { + // Don't render a pill for plain text comboboxes - use the input instead + if (this.asPlainText) return null; + // Don't render the single pill selection while searching + if (this.props.singleSelection && this.props.searchValue) return null; + + const { selectedOptions, isDisabled, onRemoveOption } = this.props; + if (!selectedOptions || !selectedOptions.length) return null; + + return selectedOptions.map((option) => { + const { + key, + label, + color, + onClick, + append, + prepend, + truncationProps, + ...rest + } = option; + const pillOnClose = + isDisabled || this.props.singleSelection || onClick + ? undefined + : onRemoveOption; + return ( + + {label} + + ); + }); + }; + render() { const { compressed, @@ -155,14 +225,14 @@ export class EuiComboBoxInput extends Component< onChange, onClear, onClick, + onFocus, onCloseListClick, onOpenListClick, - onRemoveOption, placeholder, rootId, searchValue, selectedOptions, - singleSelection: singleSelectionProp, + singleSelection, value, prepend, append, @@ -173,46 +243,6 @@ export class EuiComboBoxInput extends Component< 'aria-labelledby': ariaLabelledby, } = this.props; - const singleSelection = Boolean(singleSelectionProp); - const asPlainText = - (singleSelectionProp && - typeof singleSelectionProp === 'object' && - singleSelectionProp.asPlainText) || - false; - - const pills = selectedOptions - ? selectedOptions.map((option) => { - const { - key, - label, - color, - onClick, - append, - prepend, - truncationProps, - ...rest - } = option; - const pillOnClose = - isDisabled || singleSelection || onClick - ? undefined - : onRemoveOption; - return ( - - {label} - - ); - }) - : null; - let removeOptionMessage; let removeOptionMessageId; @@ -245,18 +275,8 @@ export class EuiComboBoxInput extends Component< ); } - let placeholderMessage; - - if ( - placeholder && - selectedOptions && - !selectedOptions.length && - !searchValue - ) { - placeholderMessage = ( -

{placeholder}

- ); - } + const showPlaceholder = + placeholder && !selectedOptions?.length && !searchValue; const clickProps: EuiFormControlLayoutIconsProps = {}; if (!isDisabled && onClear && hasSelectedOptions) { @@ -292,6 +312,7 @@ export class EuiComboBoxInput extends Component< 'euiComboBox__inputWrap--compressed': compressed, 'euiComboBox__inputWrap--fullWidth': fullWidth, 'euiComboBox__inputWrap--noWrap': singleSelection, + 'euiComboBox__inputWrap--plainText': this.asPlainText || showPlaceholder, 'euiComboBox__inputWrap--inGroup': prepend || append, }); @@ -313,30 +334,42 @@ export class EuiComboBoxInput extends Component< onClick={onClick} tabIndex={-1} // becomes onBlur event's relatedTarget, otherwise relatedTarget is null when clicking on this div > - {!singleSelection || !searchValue ? pills : null} - {placeholderMessage} - onChange(event.target.value)} - onFocus={this.onFocus} - onKeyDown={this.onKeyDown} - ref={this.inputRefCallback} - role="combobox" - style={{ inlineSize: this.state.inputWidth }} - value={searchValue} - autoFocus={autoFocus} - /> + {this.renderPills()} + + onChange(event.target.value)} + onFocus={this.onFocus} + onKeyDown={this.onKeyDown} + ref={this.inputRefCallback} + role="combobox" + style={{ + inlineSize: + this.asPlainText || showPlaceholder + ? '100%' + : this.state.inputWidth, + }} + placeholder={showPlaceholder ? placeholder : undefined} + value={this.searchValue} + autoFocus={autoFocus} + // Force the menu to re-open on every input click - only necessary when plain text + onClick={this.asPlainText ? (onFocus as any) : undefined} // Type shenanigans - event should be mostly the same + /> + {removeOptionMessage} diff --git a/src/components/combo_box/combo_box_input/combo_box_pill.tsx b/src/components/combo_box/combo_box_input/combo_box_pill.tsx index d9c288c405f..7b164d898c4 100644 --- a/src/components/combo_box/combo_box_input/combo_box_pill.tsx +++ b/src/components/combo_box/combo_box_input/combo_box_pill.tsx @@ -14,8 +14,9 @@ import { EuiI18n } from '../../i18n'; import { EuiComboBoxOptionOption, OptionHandler } from '../types'; import { CommonProps } from '../../common'; +import { EuiComboBoxOptionAppendPrepend } from '../utils'; + export interface EuiComboBoxPillProps extends CommonProps { - asPlainText?: boolean; children?: string; className?: string; color?: string; @@ -39,7 +40,6 @@ export class EuiComboBoxPill extends Component> { render() { const { - asPlainText, children, className, color, @@ -50,13 +50,7 @@ export class EuiComboBoxPill extends Component> { ...rest } = this.props; - const classes = classNames( - 'euiComboBoxPill', - { - 'euiComboBoxPill--plainText': asPlainText, - }, - className - ); + const classes = classNames('euiComboBoxPill', className); const onClickProps = onClick && onClickAriaLabel @@ -67,18 +61,15 @@ export class EuiComboBoxPill extends Component> { : {}; const content = ( - <> - {option.prepend && ( - {option.prepend} - )} + {/* .euiBadge__text normally text truncates, but because we set it to flex to align prepend/append it breaks and we need to restore it manually */} {children} - {option.append && ( - {option.append} - )} - + ); if (onClose) { @@ -108,14 +99,6 @@ export class EuiComboBoxPill extends Component> { ); } - if (asPlainText) { - return ( - - {content} - - ); - } - return ( = CommonProps & { activeOptionIndex?: number; @@ -206,21 +207,20 @@ export class EuiComboBoxOptionsList extends Component< {...rest} > - {prepend && ( - {prepend} - )} - - {renderOption - ? renderOption( - option, - searchValue, - 'euiComboBoxOption__renderOption' - ) - : this.renderTruncatedOption(label, truncationProps)} - - {append && ( - {append} - )} + + + {renderOption + ? renderOption( + option, + searchValue, + 'euiComboBoxOption__renderOption' + ) + : this.renderTruncatedOption(label, truncationProps)} + + {optionIsFocused && !optionIsDisabled ? hitEnterBadge : null} diff --git a/src/components/combo_box/utils.test.tsx b/src/components/combo_box/utils.test.tsx new file mode 100644 index 00000000000..03423f5272a --- /dev/null +++ b/src/components/combo_box/utils.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render } from '../../test/rtl'; + +import { EuiComboBoxOptionAppendPrepend } from './utils'; + +describe('EuiComboBoxOptionAppendPrepend', () => { + it('renders the option append/prepend content if they exist', () => { + expect( + render( + + Hello world + + ).container + ).toMatchInlineSnapshot(` +
+ + Hello + + Hello world +
+ `); + + expect( + render( + + Hello world + + ).container + ).toMatchInlineSnapshot(` +
+ Hello world + + world + +
+ `); + + expect( + render( + + Hello world + + ).container + ).toMatchInlineSnapshot(` +
+ + Hello + + Hello world + + world + +
+ `); + }); + + it('only renders children if no option exists', () => { + const { container } = render( + + Hello world + + ); + + expect(container).toMatchInlineSnapshot(` +
+ Hello world +
+ `); + }); +}); diff --git a/src/components/combo_box/utils.tsx b/src/components/combo_box/utils.tsx new file mode 100644 index 00000000000..56c892bd485 --- /dev/null +++ b/src/components/combo_box/utils.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { PropsWithChildren } from 'react'; + +import { EuiComboBoxOptionOption } from './types'; + +/** + * DRY util for rendering an option with its prepend and append properties + */ +export const EuiComboBoxOptionAppendPrepend = ({ + children, + option, + classNamePrefix, +}: PropsWithChildren & { + option?: EuiComboBoxOptionOption; + classNamePrefix?: string; +}) => { + return ( + <> + {option?.prepend && ( + {option.prepend} + )} + {children} + {option?.append && ( + {option.append} + )} + + ); +}; diff --git a/src/global_styling/variables/_typography.scss b/src/global_styling/variables/_typography.scss index 922dc4f05cd..1ca62f3248c 100644 --- a/src/global_styling/variables/_typography.scss +++ b/src/global_styling/variables/_typography.scss @@ -1,6 +1,6 @@ // Families -$euiFontFamily: 'Inter UI', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' !default; -$euiCodeFontFamily: 'Roboto Mono', Consolas, Menlo, Courier, monospace !default; +$euiFontFamily: 'Inter', BlinkMacSystemFont, Helvetica, Arial, sans-serif !default; +$euiCodeFontFamily: 'Roboto Mono', Menlo, Courier, monospace !default; // Careful using ligatures. Code editors like ACE will often error because of width calculations $euiFontFeatureSettings: 'calt' 1, 'kern' 1, 'liga' 1 !default; diff --git a/upcoming_changelogs/7332.md b/upcoming_changelogs/7332.md new file mode 100644 index 00000000000..1ddc97d23d2 --- /dev/null +++ b/upcoming_changelogs/7332.md @@ -0,0 +1,5 @@ +- Plain text `EuiComboBox`es now behave more like a normal text field/input. Backspacing will no longer delete the entire value, and selected values can now be double clicked and copied. + +**CSS-in-JS conversions** + +- Updated `$euiFontFamily` and `$euiCodeFontFamily` to match Emotion fonts