diff --git a/CHANGELOG.md b/CHANGELOG.md index 82abcf003a5..56b17fc6072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Added `highlightAll` prop to `EuiHighlight` to highlight all matches ([#2957](https://github.com/elastic/eui/pull/2957)) - Added `showOnFocus` prop to `EuiScreenReaderOnly` to force display on keyboard focus ([#2976](https://github.com/elastic/eui/pull/2976)) - Added `EuiSkipLink` component ([#2976](https://github.com/elastic/eui/pull/2976)) - Created `EuiBadgeGroup` component ([#2921](https://github.com/elastic/eui/pull/2921)) diff --git a/src-docs/src/views/highlight/highlight.js b/src-docs/src/views/highlight/highlight.js index fcebbe155d0..0406f5daf92 100644 --- a/src-docs/src/views/highlight/highlight.js +++ b/src-docs/src/views/highlight/highlight.js @@ -4,6 +4,7 @@ import { EuiHighlight, EuiFieldSearch, EuiSpacer, + EuiSwitch, } from '../../../../src/components'; export class Highlight extends Component { @@ -12,6 +13,7 @@ export class Highlight extends Component { this.state = { searchValue: 'jumped over', + isHighlightAll: false, }; } @@ -22,15 +24,24 @@ export class Highlight extends Component { }); }; + setHighlightAll = e => { + this.setState({ isHighlightAll: e.target.checked }); + }; + render() { - const { searchValue } = this.state; + const { searchValue, isHighlightAll } = this.state; return ( - - + this.setHighlightAll(e)} + /> + + The quick brown fox jumped over the lazy dog diff --git a/src-docs/src/views/highlight/highlight_example.js b/src-docs/src/views/highlight/highlight_example.js index c22ae02fd41..87ebc10f4c7 100644 --- a/src-docs/src/views/highlight/highlight_example.js +++ b/src-docs/src/views/highlight/highlight_example.js @@ -31,6 +31,7 @@ export const HighlightExample = { string, typically in response to user input.

), + props: { EuiHighlight }, components: { EuiHighlight }, demo: , }, diff --git a/src/components/highlight/highlight.tsx b/src/components/highlight/highlight.tsx index 645d9691586..18fa7b408f7 100644 --- a/src/components/highlight/highlight.tsx +++ b/src/components/highlight/highlight.tsx @@ -1,6 +1,21 @@ import React, { Fragment, HTMLAttributes, FunctionComponent } from 'react'; import { CommonProps } from '../common'; +interface EuiHighlightChunk { + /** + * Start of the chunk + */ + start: number; + /** + * End of the chunk + */ + end: number; + /** + * Whether to highlight chunk or not + */ + highlight?: boolean; +} + export type EuiHighlightProps = HTMLAttributes & CommonProps & { children: string; @@ -14,12 +29,18 @@ export type EuiHighlightProps = HTMLAttributes & * Should the search be strict or not */ strict?: boolean; + + /** + * Should highlight all matches + */ + highlightAll?: boolean; }; const highlight = ( searchSubject: string, searchValue: string, - isStrict: boolean = false + isStrict: boolean, + highlightAll: boolean ) => { if (!searchValue) { return searchSubject; @@ -29,6 +50,22 @@ const highlight = ( return null; } + if (highlightAll) { + const chunks = getHightlightWords(searchSubject, searchValue, isStrict); + return ( + + {chunks.map(chunk => { + const { end, highlight, start } = chunk; + const value = searchSubject.substr(start, end - start); + if (highlight) { + return {value}; + } + return value; + })} + + ); + } + const normalizedSearchSubject: string = isStrict ? searchSubject : searchSubject.toLowerCase(); @@ -58,16 +95,62 @@ const highlight = ( ); }; +const getHightlightWords = ( + searchSubject: string, + searchValue: string, + isStrict: boolean +) => { + const regex = new RegExp(searchValue, isStrict ? 'g' : 'gi'); + const matches = []; + let match; + while ((match = regex.exec(searchSubject)) !== null) { + matches.push({ + start: match.index, + end: (match.index || 0) + match[0].length, + }); + } + return fillInChunks(matches, searchSubject.length); +}; + +const fillInChunks = ( + chunksToHighlight: EuiHighlightChunk[], + totalLength: number +) => { + const allChunks: EuiHighlightChunk[] = []; + const append = (start: number, end: number, highlight: boolean) => { + if (end - start > 0) { + allChunks.push({ + start, + end, + highlight, + }); + } + }; + if (chunksToHighlight.length === 0) { + append(0, totalLength, false); + } else { + let lastIndex = 0; + chunksToHighlight.forEach(chunk => { + append(lastIndex, chunk.start, false); + append(chunk.start, chunk.end, true); + lastIndex = chunk.end; + }); + append(lastIndex, totalLength, false); + } + return allChunks; +}; + export const EuiHighlight: FunctionComponent = ({ children, className, search, - strict, + strict = false, + highlightAll = false, ...rest }) => { return ( - {highlight(children, search, strict)} + {highlight(children, search, strict, highlightAll)} ); };