Skip to content

Commit

Permalink
Autosuggest: Fix bug in suggestionHighlight prop when using `remain…
Browse files Browse the repository at this point in the history
…ing` (#1572)

Co-authored-by: Michael Taranto <[email protected]>
  • Loading branch information
felixhabib and michaeltaranto authored Sep 13, 2024
1 parent f554c4f commit ca72b87
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 19 deletions.
13 changes: 13 additions & 0 deletions .changeset/forty-coins-wait.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
Column,
Columns,
TextField,
Inline,
} from '../';
import { IconHelp, IconLanguage } from '../icons';
import { highlightSuggestions } from './Autosuggest';
Expand Down Expand Up @@ -478,7 +477,7 @@ const docs: ComponentDocs = {
source(
<>
{setDefaultState('textfield', 'App')}
{setDefaultState('suggestion', 'Apples')}
{setDefaultState('suggestion', 'Apples and Bananas')}

<Stack space="large">
<TextField
Expand All @@ -494,7 +493,7 @@ const docs: ComponentDocs = {
<Text size="small" tone="secondary">
Highlight <Strong>{highlightType}</Strong>
</Text>
<Inline space="none">
<Text>
{parseHighlights(
getState('suggestion'),
highlightSuggestions(
Expand All @@ -504,15 +503,14 @@ const docs: ComponentDocs = {
? 'matching'
: 'remaining',
).map(({ start, end }) => [start, end]),
).map((part, index) => (
<Text
key={index}
weight={part.highlight ? 'strong' : 'regular'}
>
{part.text}
</Text>
))}
</Inline>
).map((part, index) =>
part.highlight ? (
<Strong key={index}>{part.text}</Strong>
) : (
part.text
),
)}
</Text>
</Stack>
</Column>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
type AutosuggestTranslations,
autosuggest,
} from '../../translations/en';
import { reverseMatches } from './reverseMatches';

import * as styles from './Autosuggest.css';

Expand Down Expand Up @@ -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 <Value>(
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit ca72b87

Please sign in to comment.