(text: string, search: string)
| 230 | // --------------------------------------------------------------------------- |
| 231 | |
| 232 | const highlightMatch = (text: string, search: string) => { |
| 233 | if (!search.trim()) return text; |
| 234 | |
| 235 | const terms = search.trim().toLowerCase().split(/\s+/); |
| 236 | const lower = text.toLowerCase(); |
| 237 | const ranges: [number, number][] = []; |
| 238 | |
| 239 | for (const term of terms) { |
| 240 | let idx = 0; |
| 241 | while (idx < lower.length) { |
| 242 | const found = lower.indexOf(term, idx); |
| 243 | if (found === -1) break; |
| 244 | ranges.push([found, found + term.length]); |
| 245 | idx = found + 1; |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | if (ranges.length === 0) return text; |
| 250 | |
| 251 | ranges.sort((a, b) => a[0] - b[0]); |
| 252 | const merged: [number, number][] = [ranges[0]!]; |
| 253 | for (let i = 1; i < ranges.length; i++) { |
| 254 | const last = merged[merged.length - 1]!; |
| 255 | const cur = ranges[i]!; |
| 256 | if (cur[0] <= last[1]) { |
| 257 | last[1] = Math.max(last[1], cur[1]); |
| 258 | } else { |
| 259 | merged.push(cur); |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | const parts: { text: string; hl: boolean }[] = []; |
| 264 | let cursor = 0; |
| 265 | for (const [start, end] of merged) { |
| 266 | if (cursor < start) parts.push({ text: text.slice(cursor, start), hl: false }); |
| 267 | parts.push({ text: text.slice(start, end), hl: true }); |
| 268 | cursor = end; |
| 269 | } |
| 270 | if (cursor < text.length) parts.push({ text: text.slice(cursor), hl: false }); |
| 271 | |
| 272 | return ( |
| 273 | <> |
| 274 | {parts.map((p, i) => |
| 275 | p.hl ? ( |
| 276 | <mark key={i} className="rounded-sm bg-primary/25 px-px text-foreground"> |
| 277 | {p.text} |
| 278 | </mark> |
| 279 | ) : ( |
| 280 | <span key={i}>{p.text}</span> |
| 281 | ), |
| 282 | )} |
| 283 | </> |
| 284 | ); |
| 285 | }; |
| 286 | |
| 287 | // --------------------------------------------------------------------------- |
| 288 | // ToolTree — main export |
no test coverage detected