diff --git a/.changeset/forty-coins-wait.md b/.changeset/forty-coins-wait.md new file mode 100644 index 00000000000..2a4786d0467 --- /dev/null +++ b/.changeset/forty-coins-wait.md @@ -0,0 +1,13 @@ +--- +'braid-design-system': patch +--- + +--- +updated: + - Autosuggest +--- + +**Autosuggest**: Improve handling of `suggestionHighlight` prop when set to `remaining` + +Fixes a bug in `Autosuggest` when using `suggestionHighlight` prop set to `remaining`. +If the input contained multiple words, the highlighted portion would be appended to the end of matching suggestions. diff --git a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx index 8d5be7a03ff..328818f3df1 100644 --- a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx +++ b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx @@ -14,7 +14,6 @@ import { Column, Columns, TextField, - Inline, } from '../'; import { IconHelp, IconLanguage } from '../icons'; import { highlightSuggestions } from './Autosuggest'; @@ -478,7 +477,7 @@ const docs: ComponentDocs = { source( <> {setDefaultState('textfield', 'App')} - {setDefaultState('suggestion', 'Apples')} + {setDefaultState('suggestion', 'Apples and Bananas')} Highlight {highlightType} - + {parseHighlights( getState('suggestion'), highlightSuggestions( @@ -504,15 +503,14 @@ const docs: ComponentDocs = { ? 'matching' : 'remaining', ).map(({ start, end }) => [start, end]), - ).map((part, index) => ( - - {part.text} - - ))} - + ).map((part, index) => + part.highlight ? ( + {part.text} + ) : ( + part.text + ), + )} + ))} diff --git a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx index 96fe31f5713..e38f9b5d14f 100644 --- a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx +++ b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx @@ -44,6 +44,7 @@ import { type AutosuggestTranslations, autosuggest, } from '../../translations/en'; +import { reverseMatches } from './reverseMatches'; import * as styles from './Autosuggest.css'; @@ -346,14 +347,13 @@ export function highlightSuggestions( value: string, variant: HighlightOptions = 'matching', ): SuggestionMatch { - const matches = matchHighlights(suggestion, value); + const matchedHighlights = matchHighlights(suggestion, value); + const matches = + variant === 'matching' + ? matchedHighlights + : reverseMatches(suggestion, matchedHighlights); - const formattedMatches = - variant === 'remaining' - ? matches.map(([_, end]) => ({ start: end, end: suggestion.length })) - : matches.map(([start, end]) => ({ start, end })); - - return formattedMatches; + return matches.map(([start, end]) => ({ start, end })); } export const Autosuggest = forwardRef(function ( diff --git a/packages/braid-design-system/src/lib/components/Autosuggest/reverseMatches.test.ts b/packages/braid-design-system/src/lib/components/Autosuggest/reverseMatches.test.ts new file mode 100644 index 00000000000..e46b087a02f --- /dev/null +++ b/packages/braid-design-system/src/lib/components/Autosuggest/reverseMatches.test.ts @@ -0,0 +1,58 @@ +import { type Match, reverseMatches } from './reverseMatches'; + +describe('reverseMatches', () => { + it('should return intervals for non-matching parts of the suggestion', () => { + const suggestion = 'Apples etc'; + const matches: Match[] = [ + [2, 4], + [6, 8], + ]; + const expected: Match[] = [ + [0, 2], + [4, 6], + [8, 10], + ]; + + expect(reverseMatches(suggestion, matches)).toEqual(expected); + }); + + it('should handle no matches', () => { + const suggestion = 'Apple'; + const matches: Match[] = []; + const expected: Match[] = [[0, 5]]; + + expect(reverseMatches(suggestion, matches)).toEqual(expected); + }); + + it('should handle matches that cover the entire suggestion', () => { + const suggestion = 'Apple'; + const matches: Match[] = [[0, 5]]; + const expected: Match[] = []; + + expect(reverseMatches(suggestion, matches)).toEqual(expected); + }); + + it('should handle matches at the start of the suggestion', () => { + const suggestion = 'Apple'; + const matches: Match[] = [[0, 2]]; + const expected: Match[] = [[2, 5]]; + + expect(reverseMatches(suggestion, matches)).toEqual(expected); + }); + + it('should handle matches at the end of the suggestion', () => { + const suggestion = 'Apple'; + const matches: Match[] = [[3, 5]]; + const expected: Match[] = [[0, 3]]; + + expect(reverseMatches(suggestion, matches)).toEqual(expected); + }); + + it('should handle matches for a single character suggestion', () => { + const suggestion = 'A'; + const matches: Match[] = [[0, 1]]; + const expected: Match[] = []; + + expect(reverseMatches(suggestion, matches)).toEqual(expected); + }); +}); diff --git a/packages/braid-design-system/src/lib/components/Autosuggest/reverseMatches.ts b/packages/braid-design-system/src/lib/components/Autosuggest/reverseMatches.ts new file mode 100644 index 00000000000..9e96d74d709 --- /dev/null +++ b/packages/braid-design-system/src/lib/components/Autosuggest/reverseMatches.ts @@ -0,0 +1,31 @@ +/* + In each Match, + - First number: index of the first highlighted character in the match + - Second number: index of the last highlighted character in the match *plus one* + + This matches the format expected by the parse function + See https://github.com/moroshko/autosuggest-highlight?tab=readme-ov-file#parsetext-matches +*/ + +export type Match = [number, number]; + +export function reverseMatches(suggestion: string, matches: Match[]): Match[] { + const suggestionLength = suggestion.length; + const reversedMatches: Match[] = []; + + let currentStart = 0; + + for (const [start, end] of matches) { + if (currentStart < start) { + reversedMatches.push([currentStart, start]); + } + + currentStart = end; + } + + if (currentStart < suggestionLength) { + reversedMatches.push([currentStart, suggestionLength]); + } + + return reversedMatches; +}