diff --git a/changelogs/upcoming/7601.md b/changelogs/upcoming/7601.md new file mode 100644 index 00000000000..fa7e7be1bd8 --- /dev/null +++ b/changelogs/upcoming/7601.md @@ -0,0 +1,3 @@ +**Dependency updates** + +- Updated `remark-rehype` to v8.1.0 \ No newline at end of file diff --git a/changelogs/upcoming/7672.md b/changelogs/upcoming/7672.md new file mode 100644 index 00000000000..2e4d197f73c --- /dev/null +++ b/changelogs/upcoming/7672.md @@ -0,0 +1,3 @@ +**Accessibility** + +- Improved `EuiBasicTable` and `EuiInMemoryTable`'s selection checkboxes to have unique aria-labels per row diff --git a/changelogs/upcoming/7676.md b/changelogs/upcoming/7676.md new file mode 100644 index 00000000000..74353df485e --- /dev/null +++ b/changelogs/upcoming/7676.md @@ -0,0 +1,5 @@ +- Updated `getDefaultEuiMarkdownPlugins()` to allow excluding the following plugins in addition to `tooltip`: + - `checkbox` + - `linkValidator` + - `lineBreaks` + - `emoji` diff --git a/changelogs/upcoming/7678.md b/changelogs/upcoming/7678.md new file mode 100644 index 00000000000..e8d28d7fb38 --- /dev/null +++ b/changelogs/upcoming/7678.md @@ -0,0 +1,5 @@ +**Bug fixes** + +- Visual fixes for `EuiRange`s with `showInput`: + - Longer `append`/`prepend` labels no longer cause a background bug + - Inputs can no longer overwhelm the actual range in width diff --git a/changelogs/upcoming/7681.md b/changelogs/upcoming/7681.md new file mode 100644 index 00000000000..cfc8b0b8ce4 --- /dev/null +++ b/changelogs/upcoming/7681.md @@ -0,0 +1,3 @@ +**Bug fixes** + +- Fixed a visual text alignment regression in `EuiTableRowCell`s with the `row` header scope diff --git a/changelogs/upcoming/7683.md b/changelogs/upcoming/7683.md new file mode 100644 index 00000000000..600f59376ec --- /dev/null +++ b/changelogs/upcoming/7683.md @@ -0,0 +1 @@ +- Updated `EuiSelectable`'s `isPreFiltered` prop to allow passing a configuration object, which allows disabling search highlighting in addition to search filtering diff --git a/i18ntokens.json b/i18ntokens.json index b7f7fe182c6..bf47576d721 100644 --- a/i18ntokens.json +++ b/i18ntokens.json @@ -127,18 +127,18 @@ }, { "token": "euiBasicTable.selectThisRow", - "defString": "Select this row", + "defString": "Select row {index}", "highlighting": "string", "loc": { "start": { - "line": 1103, + "line": 1109, "column": 8, - "index": 30673 + "index": 30781 }, "end": { - "line": 1103, - "column": 79, - "index": 30744 + "line": 1113, + "column": 9, + "index": 30936 } }, "filepath": "src/components/basic_table/basic_table.tsx" @@ -149,14 +149,14 @@ "highlighting": "string", "loc": { "start": { - "line": 1329, + "line": 1339, "column": 8, - "index": 37489 + "index": 37663 }, "end": { - "line": 1333, + "line": 1343, "column": 9, - "index": 37648 + "index": 37822 } }, "filepath": "src/components/basic_table/basic_table.tsx" diff --git a/package.json b/package.json index 6f26daecd9c..6407227a07d 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "remark-breaks": "^2.0.2", "remark-emoji": "^2.1.0", "remark-parse-no-trim": "^8.0.4", - "remark-rehype": "^8.0.0", + "remark-rehype": "^8.1.0", "tabbable": "^5.3.3", "text-diff": "^1.0.1", "unified": "^9.2.2", @@ -152,7 +152,7 @@ "@wojtekmaj/enzyme-adapter-react-17": "^0.6.6", "assert": "^2.0.0", "autoprefixer": "^9.8.6", - "axe-core": "^4.8.2", + "axe-core": "^4.9.0", "babel-jest": "^24.1.0", "babel-loader": "^9.1.2", "babel-plugin-add-module-exports": "^1.0.4", diff --git a/src-docs/src/views/markdown_editor/markdown_editor.js b/src-docs/src/views/markdown_editor/markdown_editor.js index 3e6b98a12c0..aa88476100c 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor.js +++ b/src-docs/src/views/markdown_editor/markdown_editor.js @@ -21,7 +21,6 @@ The editor also ships with some built in plugins. For example it can handle chec - [ ] Or empty It can also handle emojis! :smile: - And it can render !{tooltip[tooltips like this](Look! I'm a very helpful tooltip content!)} `; diff --git a/src-docs/src/views/markdown_editor/markdown_editor_example.js b/src-docs/src/views/markdown_editor/markdown_editor_example.js index baa91ddf511..81f795fb07d 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_example.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_example.js @@ -60,7 +60,15 @@ const markdownEditorNoPluginsSnippet = `const { parsingPlugins, processingPlugins, uiPlugins, -} = getDefaultEuiMarkdownPlugins({ exclude: ['tooltip'] }); +} = getDefaultEuiMarkdownPlugins({ + exclude: [ + 'tooltip', + 'checkbox', + 'linkValidator', + 'lineBreaks', + 'emoji', + ], +}); - The EuiMarkdownEditor comes with a default plugin for{' '} - tooltip support. However, this may be unfamiliar or - unnecessary in some contexts, and you can unregister this plugin by - excluding it from the - parsingPlugins,{' '} + The EuiMarkdownEditor comes with several default + plugins, demo'd below. If these defaults are unnecessary for your + use-case or context, you can unregister these plugins by excluding + them from the parsingPlugins,{' '} processingPlugins and uiPlugins{' '} options with a single exclude parameter passed to{' '} getDefaultEuiMarkdownPlugins(). This will ensure - the syntax won't be identified or rendered and no additional UI, - like the button and help syntax, will be displayed. + the syntax won't be identified or rendered, and no additional UI (like + toolbar buttons or help syntax) will be displayed by the unregistered + plugins.

), props: { diff --git a/src-docs/src/views/markdown_editor/markdown_editor_no_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_no_plugins.js index 5b4dc9b2e64..f77e9207cc0 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_no_plugins.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_no_plugins.js @@ -1,35 +1,112 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { EuiMarkdownEditor, getDefaultEuiMarkdownPlugins, + EuiFlexGroup, + EuiFlexItem, + EuiSwitch, } from '../../../../src/components'; -const initialContent = `## This is how we do it :smile: +const initialContent = ` +### tooltip -In this example, we unregistered the built in **tooltip** plugin. So the button in the toolbar and the help syntax won't be displayed. - -And the following syntax no longer works. +When disabled, the button in the toolbar and the help syntax won't be displayed, and the following syntax will no longer works. !{tooltip[anchor text](Tooltip content)} -`; -const { parsingPlugins, processingPlugins, uiPlugins } = - getDefaultEuiMarkdownPlugins({ exclude: ['tooltip'] }); +### checkbox + +When disabled, a EuiCheckbox will no longer render. + +- [ ] TODO: Disable some default plugins + +### emoji + +When disabled, text will render instead of an emoji. + +:smile: + +### linkValidator + +When disabled, all links will render as links instead of as text. + +[This is a sus link](file://) + +### lineBreaks + +When disabled, these sentence will be in the same line. +When enabled, these sentences will be separated by a line break. + +Two blank lines in a row will create a new paragraph as usual. +`; export default () => { const [value, setValue] = useState(initialContent); + const [tooltip, excludeTooltips] = useState(false); + const [checkbox, excludeCheckboxes] = useState(true); + const [emoji, excludeEmojis] = useState(true); + const [linkValidator, excludeLinkValidator] = useState(true); + const [lineBreaks, excludeLineBreaks] = useState(false); + + const { parsingPlugins, processingPlugins, uiPlugins } = useMemo(() => { + const excludedPlugins = []; + if (!tooltip) excludedPlugins.push('tooltip'); + if (!checkbox) excludedPlugins.push('checkbox'); + if (!emoji) excludedPlugins.push('emoji'); + if (!linkValidator) excludedPlugins.push('linkValidator'); + if (!lineBreaks) excludedPlugins.push('lineBreaks'); + + return getDefaultEuiMarkdownPlugins({ + exclude: excludedPlugins, + }); + }, [tooltip, checkbox, emoji, linkValidator, lineBreaks]); + return ( <> - + + + excludeTooltips(!tooltip)} + /> + excludeCheckboxes(!checkbox)} + /> + excludeEmojis(!emoji)} + /> + excludeLinkValidator(!linkValidator)} + /> + excludeLineBreaks(!lineBreaks)} + /> + + + + + ); }; diff --git a/src-docs/src/views/selectable/selectable_sizing.tsx b/src-docs/src/views/selectable/selectable_sizing.tsx index db6cb59549f..088824723ad 100644 --- a/src-docs/src/views/selectable/selectable_sizing.tsx +++ b/src-docs/src/views/selectable/selectable_sizing.tsx @@ -9,102 +9,154 @@ import { EuiPopoverFooter, EuiPopoverTitle, EuiSelectable, - EuiSelectableOption, + type EuiSelectableOption, + type EuiSelectableProps, EuiSpacer, EuiTitle, + EuiInputPopover, } from '../../../../src'; -export default () => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); +const OPTIONS: EuiSelectableOption[] = [ + { label: 'Titan' }, + { label: 'Enceladus' }, + { label: 'Mimas', checked: 'on' }, + { label: 'Dione' }, + { label: 'Iapetus' }, + { label: 'Phoebe' }, + { label: 'Rhea' }, + { label: 'Pandora' }, + { label: 'Tethys' }, + { label: 'Hyperion' }, + { label: 'Pan' }, + { label: 'Atlas' }, + { label: 'Prometheus' }, + { label: 'Janus' }, + { label: 'Epimetheus' }, + { label: 'Amalthea' }, + { label: 'Thebe' }, + { label: 'Io' }, + { label: 'Europa' }, + { label: 'Ganymede' }, + { label: 'Callisto' }, + { label: 'Himalia' }, + { label: 'Phobos' }, + { label: 'Deimos' }, + { label: 'Puck' }, + { label: 'Miranda' }, + { label: 'Ariel' }, + { label: 'Umbriel' }, + { label: 'Titania' }, + { label: 'Oberon' }, + { label: 'Despina' }, + { label: 'Galatea' }, + { label: 'Larissa' }, + { label: 'Triton' }, + { label: 'Nereid' }, + { label: 'Charon' }, + { label: 'Styx' }, + { label: 'Nix' }, + { label: 'Kerberos' }, + { label: 'Hydra' }, +]; - const [options, setOptions] = useState([ - { label: 'Titan' }, - { label: 'Enceladus' }, - { label: 'Mimas', checked: 'on' }, - { label: 'Dione' }, - { label: 'Iapetus', checked: 'on' }, - { label: 'Phoebe' }, - { label: 'Rhea' }, - { label: 'Pandora' }, - { label: 'Tethys' }, - { label: 'Hyperion' }, - { label: 'Pan' }, - { label: 'Atlas' }, - { label: 'Prometheus' }, - { label: 'Janus' }, - { label: 'Epimetheus' }, - { label: 'Amalthea' }, - { label: 'Thebe' }, - { label: 'Io' }, - { label: 'Europa' }, - { label: 'Ganymede' }, - { label: 'Callisto' }, - { label: 'Himalia' }, - { label: 'Phobos' }, - { label: 'Deimos' }, - { label: 'Puck' }, - { label: 'Miranda' }, - { label: 'Ariel' }, - { label: 'Umbriel' }, - { label: 'Titania' }, - { label: 'Oberon' }, - { label: 'Despina' }, - { label: 'Galatea' }, - { label: 'Larissa' }, - { label: 'Triton' }, - { label: 'Nereid' }, - { label: 'Charon' }, - { label: 'Styx' }, - { label: 'Nix' }, - { label: 'Kerberos' }, - { label: 'Hydra' }, - ]); +export default () => { + const [options, setOptions] = useState(OPTIONS); const onChange = (options: EuiSelectableOption[]) => { setOptions(options); }; return ( <> - setIsPopoverOpen(!isPopoverOpen)} - > - Show popover - - } - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} + + + + + + + +

In an input popover

+
+ + + + + +

+ Using listProps.bordered=true and{' '} + + listProps.paddingSize="none" + +

+
+ + - list} + + + ); +}; + +const SelectablePopover = ( + props: Pick +) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const { options, onChange } = props; + + return ( + setIsPopoverOpen(!isPopoverOpen)} > - {(list, search) => ( -
- {search} - {list} - - - Manage this list - - -
- )} -
-
+ Show popover + + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + > + + {(list, search) => ( +
+ {search} + {list} + + + Manage this list + + +
+ )} +
+ + ); +}; - +const SelectableFlyout = ( + props: Pick +) => { + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const { options, onChange } = props; + return ( + <> setIsFlyoutVisible(true)}> Show flyout @@ -116,7 +168,7 @@ export default () => { aria-labelledby="selectableFlyout" > { )} + + ); +}; - - - -

- Using listProps.bordered=true and{' '} - - listProps.paddingSize="none" - -

-
+const SelectableInputPopover = () => { + const [options, setOptions] = useState(OPTIONS); + const [isOpen, setIsOpen] = useState(false); + const [inputValue, setInputValue] = useState(''); + const [isSearching, setIsSearching] = useState(true); - + return ( + { + setOptions(newOptions); + setIsOpen(false); - - {(list) => list} - - + if (changedOption.checked === 'on') { + setInputValue(changedOption.label); + setIsSearching(false); + } else { + setInputValue(''); + } + }} + singleSelection + searchable + searchProps={{ + value: inputValue, + onChange: (value) => { + setInputValue(value); + setIsSearching(true); + }, + onKeyDown: (event) => { + if (event.key === 'Tab') return setIsOpen(false); + if (event.key !== 'Escape') return setIsOpen(true); + }, + onClick: () => setIsOpen(true), + onFocus: () => setIsOpen(true), + }} + isPreFiltered={isSearching ? false : { highlightSearch: false }} // Shows the full list when not actively typing to search + listProps={{ + css: { '.euiSelectableList__list': { maxBlockSize: 200 } }, + }} + > + {(list, search) => ( + setIsOpen(false)} + disableFocusTrap + closeOnScroll + isOpen={isOpen} + input={search!} + panelPaddingSize="none" + > + {list} + + )} + ); }; diff --git a/src-docs/src/views/tables/auto/auto.tsx b/src-docs/src/views/tables/auto/auto.tsx index e0f98b12dc5..c82018ec59a 100644 --- a/src-docs/src/views/tables/auto/auto.tsx +++ b/src-docs/src/views/tables/auto/auto.tsx @@ -217,6 +217,7 @@ export default () => { tableCaption="Demo of EuiBasicTable's table layout options" items={users} columns={columns} + rowHeader="firstName" tableLayout={tableLayout === 'tableLayoutAuto' ? 'auto' : 'fixed'} /> diff --git a/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap b/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap index 3fa583c6f2a..741253a831f 100644 --- a/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -271,11 +271,11 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin class="euiCheckbox euiCheckbox--inList euiCheckbox--noLabel emotion-euiCheckbox" >
extends Component< hasPagination(this.props) && this.pageSize > 0 ? this.props.pagination.pageIndex * this.pageSize + index : index; - return this.renderItemRow(item, tableItemIndex); + return this.renderItemRow(item, tableItemIndex, index); }); } @@ -950,7 +950,7 @@ export class EuiBasicTable extends Component< ); } - renderItemRow(item: T, rowIndex: number) { + renderItemRow(item: T, rowIndex: number, displayedRowIndex: number) { const { columns, selection, rowHeader, itemIdToExpandedRowMap } = this.props; @@ -974,7 +974,8 @@ export class EuiBasicTable extends Component< const [checkboxCell, isDisabled] = this.renderItemSelectionCell( itemId, item, - selected + selected, + displayedRowIndex ); cells.push(checkboxCell); rowSelectionDisabled = !!isDisabled; @@ -1075,7 +1076,12 @@ export class EuiBasicTable extends Component< ); } - renderItemSelectionCell(itemId: ItemId, item: T, selected: boolean) { + renderItemSelectionCell( + itemId: ItemId, + item: T, + selected: boolean, + displayedRowIndex: number + ) { const { selection } = this.props; const key = `_selection_column_${itemId}`; const checked = selected; @@ -1100,7 +1106,11 @@ export class EuiBasicTable extends Component< }; return [ - + {(selectThisRow: string) => ( { display: flex; align-items: center; + /* TODO: When converting forms to Emotion, allow passing wrapperProps + to EuiFieldNumber and then move this CSS to range_input.styles.ts */ > .euiFormControlLayout { - /* There's no way to target the layout of the extra input, so we must - * use the descendant selector to allow the width to shrink. */ + /* Allow the width to shrink */ inline-size: auto; + /* Don't allow inputs to overwhelm the actual range in width */ + max-inline-size: 50%; + + /* The input should take priority over prepend/append labels */ + .euiFormControlLayout__childrenWrapper { + flex-shrink: 0; + } + + .euiFormControlLayout__prepend, + .euiFormControlLayout__append { + flex-shrink: 1; + /* Remove the default 50% max-width on prepend/appends, as a max-width is + already set on the wrapper, and a static width already set on the input */ + max-inline-size: none; + } } `, regular: css` diff --git a/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts b/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts index b2a9e708b79..d0ebcb12d82 100644 --- a/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts +++ b/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts @@ -32,30 +32,40 @@ import { euiMarkdownLinkValidator, EuiMarkdownLinkValidatorOptions, } from '../markdown_link_validator'; +import type { ExcludableDefaultPlugins, DefaultPluginsConfig } from './plugins'; export type DefaultEuiMarkdownParsingPlugins = PluggableList; +const DEFAULT_PARSING_PLUGINS: Record< + ExcludableDefaultPlugins, + DefaultEuiMarkdownParsingPlugins[0] +> = { + emoji: [emoji, { emoticon: false }], + lineBreaks: [breaks, {}], + linkValidator: [ + euiMarkdownLinkValidator, + { + allowRelative: true, + allowProtocols: ['https:', 'http:', 'mailto:'], + } as EuiMarkdownLinkValidatorOptions, + ], + checkbox: [MarkdownCheckbox.parser, {}], + tooltip: [MarkdownTooltip.parser, {}], +}; + export const getDefaultEuiMarkdownParsingPlugins = ({ exclude, -}: { exclude?: Array<'tooltip'> } = {}): DefaultEuiMarkdownParsingPlugins => { - const excludeSet = new Set(exclude); +}: DefaultPluginsConfig = {}): DefaultEuiMarkdownParsingPlugins => { const parsingPlugins: PluggableList = [ [markdown, {}], [highlight, {}], - [emoji, { emoticon: false }], - [breaks, {}], - [ - euiMarkdownLinkValidator, - { - allowRelative: true, - allowProtocols: ['https:', 'http:', 'mailto:'], - } as EuiMarkdownLinkValidatorOptions, - ], - [MarkdownCheckbox.parser, {}], ]; - if (!excludeSet.has('tooltip')) - parsingPlugins.push([MarkdownTooltip.parser, {}]); + Object.entries(DEFAULT_PARSING_PLUGINS).forEach(([pluginName, plugin]) => { + if (!exclude?.includes(pluginName as ExcludableDefaultPlugins)) { + parsingPlugins.push(plugin); + } + }); return parsingPlugins; }; diff --git a/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.test.ts b/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.test.ts new file mode 100644 index 00000000000..211b95a6f76 --- /dev/null +++ b/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.test.ts @@ -0,0 +1,62 @@ +/* + * 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 { getDefaultEuiMarkdownPlugins } from './plugins'; + +describe('default plugins', () => { + test('no exclusions', () => { + const { parsingPlugins, processingPlugins, uiPlugins } = + getDefaultEuiMarkdownPlugins(); + + expect(parsingPlugins).toHaveLength(7); + expect(Object.keys(processingPlugins[1][1].components)).toHaveLength(8); + expect(uiPlugins).toHaveLength(1); + + expect(processingPlugins[1][1].components.tooltipPlugin).toBeDefined(); + expect(processingPlugins[1][1].components.checkboxPlugin).toBeDefined(); + }); + + test('exclude tooltips', () => { + const { parsingPlugins, processingPlugins, uiPlugins } = + getDefaultEuiMarkdownPlugins({ + exclude: ['tooltip'], + }); + + expect(parsingPlugins).toHaveLength(6); + expect(processingPlugins[1][1].components.tooltipPlugin).toBeUndefined(); + expect(uiPlugins).toHaveLength(0); + }); + + test('exclude checkboxes', () => { + const { parsingPlugins, processingPlugins, uiPlugins } = + getDefaultEuiMarkdownPlugins({ + exclude: ['checkbox'], + }); + + expect(parsingPlugins).toHaveLength(6); + expect(processingPlugins[1][1].components.checkboxPlugin).toBeUndefined(); + expect(uiPlugins).toHaveLength(1); + }); + + test('all exclusions', () => { + const { parsingPlugins, processingPlugins, uiPlugins } = + getDefaultEuiMarkdownPlugins({ + exclude: [ + 'tooltip', + 'checkbox', + 'lineBreaks', + 'linkValidator', + 'emoji', + ], + }); + + expect(parsingPlugins).toHaveLength(2); + expect(Object.keys(processingPlugins[1][1].components)).toHaveLength(6); + expect(uiPlugins).toHaveLength(0); + }); +}); diff --git a/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts b/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts index 77c658f14c2..9a8c3001d61 100644 --- a/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts +++ b/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts @@ -19,8 +19,19 @@ import { DefaultEuiMarkdownProcessingPlugins, } from './processing_plugins'; +export type ExcludableDefaultPlugins = + | 'emoji' + | 'lineBreaks' + | 'linkValidator' + | 'checkbox' + | 'tooltip'; + +export type DefaultPluginsConfig = + | undefined + | { exclude?: ExcludableDefaultPlugins[] }; + export const getDefaultEuiMarkdownPlugins = ( - config: undefined | { exclude?: Array<'tooltip'> } + config?: DefaultPluginsConfig ): { parsingPlugins: DefaultEuiMarkdownParsingPlugins; processingPlugins: DefaultEuiMarkdownProcessingPlugins; diff --git a/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx b/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx index af79901d268..04c575d826a 100644 --- a/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx +++ b/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx @@ -28,13 +28,16 @@ import { Options as Remark2RehypeOptions, Handler } from 'mdast-util-to-hast'; import all from 'mdast-util-to-hast/lib/all'; import rehype2react from 'rehype-react'; import remark2rehype from 'remark-rehype'; -import * as MarkdownTooltip from '../markdown_tooltip'; -import * as MarkdownCheckbox from '../markdown_checkbox'; -import { FENCED_CLASS } from '../remark/remark_prismjs'; + import { EuiLink } from '../../../link'; import { EuiCodeBlock, EuiCode } from '../../../code'; import { EuiHorizontalRule } from '../../../horizontal_rule'; +import { FENCED_CLASS } from '../remark/remark_prismjs'; +import * as MarkdownTooltip from '../markdown_tooltip'; +import * as MarkdownCheckbox from '../markdown_checkbox'; +import type { ExcludableDefaultPlugins, DefaultPluginsConfig } from './plugins'; + const unknownHandler: Handler = (h, node) => { return h(node, node.type, node, all(h, node)); }; @@ -50,12 +53,26 @@ export type DefaultEuiMarkdownProcessingPlugins = [ ...PluggableList // any additional are generic ]; +const DEFAULT_COMPONENT_RENDERERS: Partial< + Record> +> = { + checkbox: MarkdownCheckbox.renderer, + tooltip: MarkdownTooltip.renderer, +}; + export const getDefaultEuiMarkdownProcessingPlugins = ({ exclude, -}: { - exclude?: Array<'tooltip'>; -} = {}): DefaultEuiMarkdownProcessingPlugins => { - const excludeSet = new Set(exclude); +}: DefaultPluginsConfig = {}): DefaultEuiMarkdownProcessingPlugins => { + const componentPluginsWithExclusions: Rehype2ReactOptions['components'] = {}; + + Object.entries(DEFAULT_COMPONENT_RENDERERS).forEach( + ([excludeName, renderer]) => { + if (!exclude?.includes(excludeName as ExcludableDefaultPlugins)) { + const pluginName = `${excludeName}Plugin`; + componentPluginsWithExclusions[pluginName] = renderer; + } + } + ); const plugins: DefaultEuiMarkdownProcessingPlugins = [ [ @@ -99,15 +116,12 @@ export const getDefaultEuiMarkdownProcessingPlugins = ({ ), hr: (props) => , - checkboxPlugin: MarkdownCheckbox.renderer, + ...componentPluginsWithExclusions, }, }, ], ]; - if (!excludeSet.has('tooltip')) - plugins[1][1].components.tooltipPlugin = MarkdownTooltip.renderer; - return plugins; }; diff --git a/src/components/markdown_editor/plugins/markdown_default_plugins/ui_plugins.ts b/src/components/markdown_editor/plugins/markdown_default_plugins/ui_plugins.ts index f5dc92cb9c8..131f3daed9a 100644 --- a/src/components/markdown_editor/plugins/markdown_default_plugins/ui_plugins.ts +++ b/src/components/markdown_editor/plugins/markdown_default_plugins/ui_plugins.ts @@ -6,18 +6,28 @@ * Side Public License, v 1. */ -import * as MarkdownTooltip from '../markdown_tooltip'; import { EuiMarkdownEditorUiPlugin } from './../../markdown_types'; +import * as MarkdownTooltip from '../markdown_tooltip'; +import type { ExcludableDefaultPlugins, DefaultPluginsConfig } from './plugins'; export type DefaultEuiMarkdownUiPlugins = EuiMarkdownEditorUiPlugin[]; +const DEFAULT_UI_PLUGINS: Partial< + Record +> = { + tooltip: MarkdownTooltip.plugin, +}; + export const getDefaultEuiMarkdownUiPlugins = ({ exclude, -}: { exclude?: Array<'tooltip'> } = {}): DefaultEuiMarkdownUiPlugins => { - const excludeSet = new Set(exclude); - const uiPlugins = []; +}: DefaultPluginsConfig = {}): DefaultEuiMarkdownUiPlugins => { + const uiPlugins: EuiMarkdownEditorUiPlugin[] = []; - if (!excludeSet.has('tooltip')) uiPlugins.push(MarkdownTooltip.plugin); + Object.entries(DEFAULT_UI_PLUGINS).forEach(([pluginName, plugin]) => { + if (!exclude?.includes(pluginName as ExcludableDefaultPlugins)) { + uiPlugins.push(plugin); + } + }); return uiPlugins; }; diff --git a/src/components/selectable/selectable.tsx b/src/components/selectable/selectable.tsx index 938b8788a4a..819e265570e 100644 --- a/src/components/selectable/selectable.tsx +++ b/src/components/selectable/selectable.tsx @@ -168,10 +168,17 @@ export type EuiSelectableProps = CommonProps & */ errorMessage?: ReactElement | string | null; /** - * Control whether or not options get filtered internally or if consumer will filter - * Default: false + * Control whether or not options get filtered internally (i.e., whether filtering is + * handled by EUI or by you, the consumer). + * If set to `true`, all passed `options` will be displayed regardless of the user's + * search input. + * + * Additionally allows passing a configuration object which enables turning off + * search highlighting if needed. + * + * @default false */ - isPreFiltered?: boolean; + isPreFiltered?: boolean | { highlightSearch?: boolean }; /** * Optional screen reader instructions to announce upon focus/interaction. This text is read out * after the `EuiSelectable` label and a brief pause, but before the default keyboard instructions for @@ -222,7 +229,7 @@ export class EuiSelectable extends Component< const visibleOptions = getMatchingOptions( options, initialSearchValue, - isPreFiltered + !!isPreFiltered ); searchProps?.onChange?.(initialSearchValue, visibleOptions); @@ -262,7 +269,7 @@ export class EuiSelectable extends Component< stateUpdate.visibleOptions = getMatchingOptions( options, stateUpdate.searchValue ?? '', - isPreFiltered + !!isPreFiltered ); if ( @@ -482,7 +489,7 @@ export class EuiSelectable extends Component< const visibleOptions = getMatchingOptions( options, searchValue, - isPreFiltered + !!isPreFiltered ); this.setState({ visibleOptions }); @@ -712,7 +719,7 @@ export class EuiSelectable extends Component< listId={this.optionsListRef.current ? this.listId : undefined} // Only pass the listId if it exists on the page aria-activedescendant={this.makeOptionId(activeOptionIndex)} // the current faux-focused option placeholder={placeholderName} - isPreFiltered={isPreFiltered ?? false} + isPreFiltered={!!isPreFiltered} inputRef={(node) => { this.inputRef = node; searchProps?.inputRef?.(node); @@ -781,6 +788,7 @@ export class EuiSelectable extends Component< options={options} visibleOptions={visibleOptions} searchValue={searchValue} + isPreFiltered={isPreFiltered} activeOptionIndex={activeOptionIndex} setActiveOptionIndex={(index, cb) => { this.setState({ activeOptionIndex: index }, cb); diff --git a/src/components/selectable/selectable_list/selectable_list.test.tsx b/src/components/selectable/selectable_list/selectable_list.test.tsx index 897721906bd..aa6484b7d88 100644 --- a/src/components/selectable/selectable_list/selectable_list.test.tsx +++ b/src/components/selectable/selectable_list/selectable_list.test.tsx @@ -99,6 +99,19 @@ describe('EuiSelectableListItem', () => { container.querySelector('.euiTextTruncate') ); }); + + it('does not highlight/mark the current `searchValue` if `isPreFiltered.highlightSearch` is false', () => { + const { container } = render( + + ); + + expect(container.querySelector('.euiMark')).not.toBeInTheDocument(); + }); }); test('renderOption', () => { diff --git a/src/components/selectable/selectable_list/selectable_list.tsx b/src/components/selectable/selectable_list/selectable_list.tsx index 5f9a4a540b7..874344e333e 100644 --- a/src/components/selectable/selectable_list/selectable_list.tsx +++ b/src/components/selectable/selectable_list/selectable_list.tsx @@ -31,8 +31,11 @@ import { EuiHighlight } from '../../highlight'; import { EuiMark } from '../../mark'; import { EuiTextTruncate } from '../../text_truncate'; -import { EuiSelectableOption } from '../selectable_option'; -import { EuiSelectableOnChangeEvent } from '../selectable'; +import type { EuiSelectableOption } from '../selectable_option'; +import type { + EuiSelectableOnChangeEvent, + EuiSelectableProps, +} from '../selectable'; import { EuiSelectableListItem, EuiSelectableListItemProps, @@ -153,6 +156,7 @@ export type EuiSelectableListProps = EuiSelectableOptionsListProps & { */ allowExclusions?: boolean; searchable?: boolean; + isPreFiltered?: EuiSelectableProps['isPreFiltered']; makeOptionId: (index: number | undefined) => string; listId: string; setActiveOptionIndex: (index: number, cb?: () => void) => void; @@ -329,6 +333,7 @@ export class EuiSelectableList extends Component< setActiveOptionIndex, searchable, searchValue, + isPreFiltered, isVirtualized, } = this.props; @@ -351,6 +356,14 @@ export class EuiSelectableList extends Component< const id = makeOptionId(index); const isFocused = activeOptionIndex === index; + // Search highlighting + const hasSearch = !!searchValue; + const highlightSearch = + hasSearch && + (typeof isPreFiltered === 'object' + ? isPreFiltered.highlightSearch !== false + : true); + // Text wrapping const canWrap = !isVirtualized; const _textWrap = option.textWrap ?? this.props.textWrap; @@ -359,7 +372,7 @@ export class EuiSelectableList extends Component< // Truncation config (if any). If none, CSS truncation is used const truncationProps = textWrap === 'truncate' - ? this.getTruncationProps(option, isFocused) + ? this.getTruncationProps(option, highlightSearch, isFocused) : undefined; return ( @@ -397,7 +410,7 @@ export class EuiSelectableList extends Component< { ..._option, ...optionData }, searchValue ) - : searchValue + : highlightSearch ? this.renderSearchedText(label, truncationProps) : truncationProps ? this.renderTruncatedText(label, truncationProps) @@ -513,7 +526,11 @@ export class EuiSelectableList extends Component< }); }; - getTruncationProps = (option: EuiSelectableOption, isFocused: boolean) => { + getTruncationProps = ( + option: EuiSelectableOption, + highlightSearch: boolean, + isFocused: boolean + ) => { // Individual truncation settings should override component-wide settings const truncationProps = { ...this.props.truncationProps, @@ -522,7 +539,7 @@ export class EuiSelectableList extends Component< // If we're not actually using EuiTextTruncate, no need to continue const hasComplexTruncation = - this.props.searchValue || Object.keys(truncationProps).length > 0; + highlightSearch || Object.keys(truncationProps).length > 0; if (!hasComplexTruncation) return undefined; // Determine whether we can use the optimized default option width @@ -618,6 +635,7 @@ export class EuiSelectableList extends Component< 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, role, + isPreFiltered, isVirtualized, textWrap, truncationProps, diff --git a/src/components/table/table_row_cell.styles.ts b/src/components/table/table_row_cell.styles.ts index 0ef75bb310e..1524075e5ca 100644 --- a/src/components/table/table_row_cell.styles.ts +++ b/src/components/table/table_row_cell.styles.ts @@ -14,6 +14,7 @@ import { euiFontSize, euiTextTruncate, logicalCSS, + logicalTextAlignCSS, } from '../../global_styling'; import { euiTableVariables } from './table.styles'; @@ -32,8 +33,9 @@ export const euiTableRowCellStyles = (euiThemeContext: UseEuiTheme) => { color: ${euiTheme.colors.text}; `, rowHeader: css` - /* Unset the automatic browser bolding applied to [th] elements */ + /* Unset the automatic browser bolding and center alignment applied to [th] elements */ font-weight: ${euiTheme.font.weight.regular}; + ${logicalTextAlignCSS('left')} `, isExpander: css` ${hasIcons} diff --git a/yarn.lock b/yarn.lock index 7d5af2fd5da..7e311289a6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6535,10 +6535,10 @@ axe-core@^4.6.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.2.tgz#040a7342b20765cb18bb50b628394c21bccc17a0" integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== -axe-core@^4.8.2: - version "4.8.2" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae" - integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g== +axe-core@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.0.tgz#b18971494551ab39d4ff5f7d4c6411bd20cc7c2a" + integrity sha512-H5orY+M2Fr56DWmMFpMrq5Ge93qjNdPVqzBv5gWK3aD1OvjBEJlEzxf09z93dGVQeI0LiW+aCMIx1QtShC/zUw== axios@^0.18.0, axios@^0.18.1: version "0.18.1" @@ -15107,20 +15107,6 @@ mdast-util-definitions@^4.0.0: dependencies: unist-util-visit "^2.0.0" -mdast-util-to-hast@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.0.tgz#744dfe7907bac0263398a68af5aba16d104a9a08" - integrity sha512-dRyAC5S4eDcIOdkz4jg0wXbUdlf+5YFu7KppJNHOsMaD7ql5bKIqVcvXYYkcrKjzUkfX8JsKFVMthsU8OWxQ+w== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - mdast-util-definitions "^4.0.0" - mdurl "^1.0.0" - unist-builder "^2.0.0" - unist-util-generated "^1.0.0" - unist-util-position "^3.0.0" - unist-util-visit "^2.0.0" - mdast-util-to-hast@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz#61875526a017d8857b71abc9333942700b2d3604" @@ -18965,12 +18951,12 @@ remark-parse-no-trim@^8.0.4: vfile-location "^3.0.0" xtend "^4.0.1" -remark-rehype@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-8.0.0.tgz#5a8afc8262a59d205fba21dafb27a673fb3b92fa" - integrity sha512-gVvOH02TMFqXOWoL6iXU7NXMsDJguNkNuMrzfkQeA4V6WCyHQnOKptn+IQBVVPuIH2sMJBwo8hlrmtn1MLTh9w== +remark-rehype@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-8.1.0.tgz#610509a043484c1e697437fa5eb3fd992617c945" + integrity sha512-EbCu9kHgAxKmW1yEYjx3QafMyGY3q8noUbNUI5xyKbaFP89wbhDrKxyIQNukNYthzjNHZu6J7hwFg7hRm1svYA== dependencies: - mdast-util-to-hast "^10.0.0" + mdast-util-to-hast "^10.2.0" remote-origin-url@^0.4.0: version "0.4.0" @@ -20329,7 +20315,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20356,15 +20342,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -20518,7 +20495,7 @@ stringify-entities@^3.0.1: is-decimal "^1.0.2" is-hexadecimal "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20546,13 +20523,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -22708,7 +22678,7 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22743,15 +22713,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3"