После нескольких попыток у меня есть рабочее решение с Highlight
реактивный компонент. Я не уверен, что алгоритм очень чист или эффективен, особенно когда речь идет о сохранении необязательного цвета рядом с совпадениями.
В принципе, mark
реквизит { color: string, rule: string }
с правилом, являющимся допустимым регулярным выражением или массивом указанного типа.
import { memo, useMemo } from 'react'
export function Highlight({ mark, children, ...props }) {
const highlights = useMemo(() => highlight(children, mark), [children, mark])
return <span {...props}>{ highlights }</span>
}
function highlight(text, mark) {
const marks = Array.isArray(mark) ? mark : [mark]
const matches = marks
.flatMap(mark => [...text.matchAll(mark.rule ?? mark)].map(match => ({ color: mark.color, match })))
.filter(({ match }) => match && match.length)
.sort(({ match }) => match.index)
let index = 0
let highlights = []
for (let { match, color } of matches) {
highlights.push(text.slice(index, match.index))
highlights.push(<mark className={color}>{ match[0] }</mark>)
index = match.index + match[0].length
}
if (index < text.length)
highlights.push(text.slice(index))
return highlights
}
export default memo(Highlight)
Я знаю, что этот способ ведения дел не обрабатывает перекрывающиеся совпадения, но я даже не знаю, с чего начать.