Repository: Megabit/Blazorise
Pull request #6058: Highlighter: List of HighlightedTexts
Author: login: tesar-tech
Created: 2025-04-04T19:46:01Z | Merged: 2025-04-15T08:30:05Z

## Description

Closes #6053

Linked issue #6053: Allow Highlighter component to accept a list of texts to be highlighted
**Is your feature request related to a problem? Please describe.**
Currently the Highlighter component only allows one string to be inputted as the text to be highlighted.

**Describe the solution you'd like**
Create a property called HighlightedTexts which accepts a list of strings to be highlighted.


Diff:
diff --git a/Documentation/Blazorise.Docs/Models/Snippets.generated.cs b/Documentation/Blazorise.Docs/Models/Snippets.generated.cs
index a71f18defb..2d222c91a4 100644
--- a/Documentation/Blazorise.Docs/Models/Snippets.generated.cs
+++ b/Documentation/Blazorise.Docs/Models/Snippets.generated.cs
@@ -2755,6 +2755,37 @@ void OnProgressed( FileProgressedEventArgs e )
     string sentence = ""\""There will be no foolish wand-waving or silly incantations in this class. As such, I don't expect many of you to appreciate the subtle science and exact art that is potion-making. However, for those select few who possess the predisposition, I can teach you how to bewitch the mind and ensnare the senses. I can tell you how to bottle fame, brew glory, and even put a stopper in death. Then again, maybe some of you have come to Hogwarts in possession of abilities so formidable that you feel confident enough to not pay attention!\"" — Severus Snape"";
 }";
 
+        public const string MultipleTextsHighlighterExample = @"<Field>
+    <FieldLabel>Search values (comma separated)</FieldLabel>
+    <FieldBody>
+        <TextEdit @bind-Text=""@searchValue"" />
+    </FieldBody>
+</Field>
+
+<ListGroup>
+    @foreach ( var sentence in sentences )
+    {
+        <ListGroupItem @key=""sentence"">
+            <Highlighter Text=""@sentence"" HighlightedTexts=""@highlightedWords"" />
+        </ListGroupItem>
+    }
+</ListGroup>
+
+@code {
+    string searchValue = ""the,item"";
+
+    IEnumerable<string> sentences = new List<string>
+    {
+        ""This is the first item"",
+        ""This is the second item"",
+        ""This is the third item""
+    };
+
+    string[] highlightedWords =>
+        ( searchValue ?? string.Empty )
+        .Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ).ToArray();
+}";
+
         public const string BasicImageExample = @"<Image Source=""_content/Blazorise.Docs/assets/img/animals/animal-01.jpg"" Text=""A lovely animal..."" />";
 
         public const string ImageFluidExample = @"<Image Source=""_content/Blazorise.Docs/assets/img/animals/animal-02-large.jpg"" Text=""A lovely animal..."" Fluid />";
diff --git a/Documentation/Blazorise.Docs/NewFilesToBuild.txt b/Documentation/Blazorise.Docs/NewFilesToBuild.txt
index f108be3970..e69de29bb2 100644
--- a/Documentation/Blazorise.Docs/NewFilesToBuild.txt
+++ b/Documentation/Blazorise.Docs/NewFilesToBuild.txt
@@ -1 +0,0 @@
-D:\Projects\Megabit\Blazorise\Documentation\Blazorise.Docs\Pages\Docs\Components\Dates\Code\DatePickerShowWeekNumbersExampleCode.html
diff --git a/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/Code/MultipleTextsHighlighterExampleCode.html b/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/Code/MultipleTextsHighlighterExampleCode.html
new file mode 100644
index 0000000000..17b490499c
--- /dev/null
+++ b/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/Code/MultipleTextsHighlighterExampleCode.html
@@ -0,0 +1,35 @@
+<div class="blazorise-codeblock">
+<div class="html"><pre>
+<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">Field</span><span class="htmlTagDelimiter">&gt;</span>
+    <span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">FieldLabel</span><span class="htmlTagDelimiter">&gt;</span>Search values (comma separated)<span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">FieldLabel</span><span class="htmlTagDelimiter">&gt;</span>
+    <span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">FieldBody</span><span class="htmlTagDelimiter">&gt;</span>
+        <span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">TextEdit</span> <span class="htmlAttributeName"><span class="atSign">&#64;</span>bind-Text</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="sharpVariable"><span class="atSign">&#64;</span>searchValue</span><span class="quot">&quot;</span> <span class="htmlTagDelimiter">/&gt;</span>
+    <span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">FieldBody</span><span class="htmlTagDelimiter">&gt;</span>
+<span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">Field</span><span class="htmlTagDelimiter">&gt;</span>
+
+<span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">ListGroup</span><span class="htmlTagDelimiter">&gt;</span>
+    <span class="atSign">&#64;</span>foreach ( var sentence in sentences )
+    {
+        <span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">ListGroupItem</span> <span class="htmlAttributeName"><span class="atSign">&#64;</span>key</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="htmlAttributeValue">sentence</span><span class="quot">&quot;</span><span class="htmlTagDelimiter">&gt;</span>
+            <span class="htmlTagDelimiter">&lt;</span><span class="htmlElementName">Highlighter</span> <span class="htmlAttributeName">Text</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="sharpVariable"><span class="atSign">&#64;</span>sentence</span><span class="quot">&quot;</span> <span class="htmlAttributeName">HighlightedTexts</span><span class="htmlOperator">=</span><span class="quot">&quot;</span><span class="sharpVariable"><span class="atSign">&#64;</span>highlightedWords</span><span class="quot">&quot;</span> <span class="htmlTagDelimiter">/&gt;</span>
+        <span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">ListGroupItem</span><span class="htmlTagDelimiter">&gt;</span>
+    }
+<span class="htmlTagDelimiter">&lt;/</span><span class="htmlElementName">ListGroup</span><span class="htmlTagDelimiter">&gt;</span>
+</pre></div>
+<div class="csharp"><pre>
+<span class="atSign">&#64;</span>code {
+    <span class="keyword">string</span> searchValue = <span class="string">&quot;the,item&quot;</span>;
+
+    IEnumerable&lt;<span class="keyword">string</span>&gt; sentences = <span class="keyword">new</span> List&lt;<span class="keyword">string</span>&gt;
+    {
+        <span class="string">&quot;This is the first item&quot;</span>,
+        <span class="string">&quot;This is the second item&quot;</span>,
+        <span class="string">&quot;This is the third item&quot;</span>
+    };
+
+    <span class="keyword">string</span>[] highlightedWords =&gt;
+        ( searchValue ?? <span class="keyword">string</span>.Empty )
+        .Split( <span class="string">&#39;,&#39;</span>, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ).ToArray();
+}
+</pre></div>
+</div>
diff --git a/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/Examples/MultipleTextsHighlighterExample.razor b/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/Examples/MultipleTextsHighlighterExample.razor
new file mode 100644
index 0000000000..baecf2cd1f
--- /dev/null
+++ b/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/Examples/MultipleTextsHighlighterExample.razor
@@ -0,0 +1,32 @@
+﻿@namespace Blazorise.Docs.Docs.Examples
+
+<Field>
+    <FieldLabel>Search values (comma separated)</FieldLabel>
+    <FieldBody>
+        <TextEdit @bind-Text="@searchValue" />
+    </FieldBody>
+</Field>
+
+<ListGroup>
+    @foreach ( var sentence in sentences )
+    {
+        <ListGroupItem @key="sentence">
+            <Highlighter Text="@sentence" HighlightedTexts="@highlightedWords" />
+        </ListGroupItem>
+    }
+</ListGroup>
+
+@code {
+    string searchValue = "the,item";
+
+    IEnumerable<string> sentences = new List<string>
+    {
+        "This is the first item",
+        "This is the second item",
+        "This is the third item"
+    };
+
+    string[] highlightedWords =>
+        ( searchValue ?? string.Empty )
+        .Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ).ToArray();
+}
diff --git a/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/HighlighterPage.razor b/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/HighlighterPage.razor
index 9f8dc095c8..99ec1e1447 100644
--- a/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/HighlighterPage.razor
+++ b/Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/HighlighterPage.razor
@@ -27,6 +27,15 @@
     <DocsPageSectionSource Code="BasicHighlighterExample" />
 </DocsPageSection>
 
+<DocsPageSection>
+    <DocsPageSectionHeader Title="Multiple Highlights">
+        Use the <Code>HighlightedTexts</Code> parameter to highlight multiple words or phrases within the same block of text. This is useful for multi-term search or emphasizing several keywords at once.
+    </DocsPageSectionHeader>
+    <DocsPageSectionContent Outlined FullWidth>
+        <MultipleTextsHighlighterExample />
+    </DocsPageSectionContent>
+    <DocsPageSectionSource Code="MultipleTextsHighlighterExample" />
+</DocsPageSection>
 
 <DocsPageSection>
     <DocsPageSectionHeader Title="Boundaries">
diff --git a/Documentation/Blazorise.Docs/Pages/News/2025-03-10-release-notes-180.razor b/Documentation/Blazorise.Docs/Pages/News/2025-03-10-release-notes-180.razor
index 65a75f1f8f..d773ffd5bd 100644
--- a/Documentation/Blazorise.Docs/Pages/News/2025-03-10-release-notes-180.razor
+++ b/Documentation/Blazorise.Docs/Pages/News/2025-03-10-release-notes-180.razor
@@ -154,4 +154,12 @@
     The <Code>RichTextEdit</Code> component now includes an <Code>GetHtmlAsync</Code> overload that allows you to export the current content as semantic HTML. This is particularly useful for applications that require clean and structured HTML output, such as when saving or sharing content.
 </Paragraph>
 
+<Heading Size="HeadingSize.Is3">
+    Multiple Highlights Support for Highlighter
+</Heading>
+
+<Paragraph>
+    The <code>Highlighter</code> component now supports highlighting multiple phrases within the same text using the new <code>HighlightedTexts</code> parameter. This enhancement allows developers to pass a list of strings to be highlighted simultaneously, replacing the previous limitation of only a single <code>HighlightedText</code>. The update enables more dynamic and flexible highlighting scenarios, such as multi-word search, filtering, and complex keyword matching — all with minimal changes to existing implementations.
+</Paragraph>
+
 <NewsPagePostInfo UserName="Mladen Macanović" ImageName="mladen" PostedOn="March 10th, 2025" Read="7 min" />
\ No newline at end of file
diff --git a/Source/Blazorise/Components/Highlighter/Highlighter.razor b/Source/Blazorise/Components/Highlighter/Highlighter.razor
index 3d2e05e69b..905ea60941 100644
--- a/Source/Blazorise/Components/Highlighter/Highlighter.razor
+++ b/Source/Blazorise/Components/Highlighter/Highlighter.razor
@@ -1,14 +1,15 @@
 ﻿@namespace Blazorise
-@using System.Text.RegularExpressions
 @inherits BaseComponent
 @foreach ( var fragment in fragments )
 {
-    if ( !string.IsNullOrWhiteSpace( HighlightedText ) && Regex.IsMatch( fragment, Regex.Escape( HighlightedText ), CaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase ) )
+    if ( fragment.IsMatch )
     {
-        <mark @ref="@ElementRef" id="@ElementId" class="@ClassNames" style="@StyleNames" @attributes="@Attributes">@fragment</mark>
+        <mark @ref="@ElementRef" id="@ElementId" class="@ClassNames" style="@StyleNames" @attributes="@Attributes">
+            @fragment.Text
+        </mark>
     }
     else
     {
-        @fragment
+        @fragment.Text
     }
 }
\ No newline at end of file
diff --git a/Source/Blazorise/Components/Highlighter/Highlighter.razor.cs b/Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
index f92976ed80..50f8af8811 100644
--- a/Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
+++ b/Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
@@ -2,6 +2,8 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Blazorise.Extensions;
 using Microsoft.AspNetCore.Components;
 #endregion
 
@@ -12,42 +14,69 @@ namespace Blazorise;
 /// </summary>
 public partial class Highlighter : BaseComponent
 {
+    #region Objects
+
+    /// <summary>
+    /// Defines a fragment of the text.
+    /// </summary>
+    /// <param name="Text">The text fragment.</param>
+    /// <param name="IsMatch">Whether or not the text fragment is a match.</param>
+    record Fragment( string Text, bool IsMatch );
+
+    #endregion
+
     #region Members
 
-    IEnumerable<string> fragments;
+    IEnumerable<Fragment> fragments;
+
+    private List<string> allHighlightedTexts = new();
 
     #endregion
 
     #region Methods
 
-    /// <inheritdoc/>
-    protected override void OnParametersSet()
+    /// <inheritdoc />
+    public override async Task SetParametersAsync( ParameterView parameters )
     {
-        fragments = GetFragments( Text, HighlightedText, CaseSensitive, UntilNextBoundary, NextBoundary );
+        var changed =
+            ( parameters.TryGetValue<string>( nameof( Text ), out var text ) && Text != text )
+            || ( parameters.TryGetValue<string>( nameof( HighlightedText ), out var highlightedText ) && HighlightedText != highlightedText )
+            || ( parameters.TryGetValue<bool>( nameof( CaseSensitive ), out var caseSensitive ) && CaseSensitive != caseSensitive )
+            || ( parameters.TryGetValue<bool>( nameof( UntilNextBoundary ), out var untilNext ) && UntilNextBoundary != untilNext )
+            || ( parameters.TryGetValue<string>( nameof( NextBoundary ), out var nextBoundary ) && NextBoundary != nextBoundary )
+            || ( parameters.TryGetValue<IEnumerable<string>>( nameof( HighlightedTexts ), out var highlightedTexts ) && HighlightedTexts != highlightedTexts );
+
+        await base.SetParametersAsync( parameters );
+
+        if ( changed )
+        {
+            allHighlightedTexts = new( HighlightedTexts ?? Enumerable.Empty<string>() ) { HighlightedText };
+            fragments = GetFragments( Text, allHighlightedTexts, CaseSensitive, UntilNextBoundary, NextBoundary );
+        }
     }
 
-    private static IEnumerable<string> GetFragments( string text, string highlightedText, bool caseSensitive = false, bool untilNextBoundary = false, string nextBoundary = null )
+    static IEnumerable<Fragment> GetFragments( string text, List<string> highlightedTexts, bool caseSensitive = false, bool untilNextBoundary = false, string nextBoundary = null )
     {
         if ( string.IsNullOrWhiteSpace( text ) )
-        {
-            return new List<string>();
-        }
+            return new List<Fragment>();
 
-        if ( string.IsNullOrWhiteSpace( highlightedText ) )
-        {
-            return new List<string> { text };
-        }
+        if ( highlightedTexts == null || highlightedTexts.Count == 0 || highlightedTexts.All( string.IsNullOrWhiteSpace ) )
+            return new List<Fragment> { new( text, false ) };
 
-        highlightedText = Regex.Escape( highlightedText );
+        var escaped = highlightedTexts
+                      .Where( s => !string.IsNullOrWhiteSpace( s ) )
+                      .Select( Regex.Escape ).ToList();
 
-        if ( untilNextBoundary )
-        {
-            highlightedText += nextBoundary;
-        }
+        string pattern = untilNextBoundary
+            ? string.Join( "|", escaped.Select( h => h + nextBoundary ) )
+            : string.Join( "|", escaped );
+
+        var regex = new Regex( $"({pattern})", caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase );
 
-        return Regex
-            .Split( text, $"({highlightedText})", caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase )
-            .Where( s => !string.IsNullOrEmpty( s ) );
+        return regex
+               .Split( text )
+               .Where( s => !string.IsNullOrEmpty( s ) )
+               .Select( s => new Fragment( s, regex.IsMatch( s ) ) );
     }
 
     #endregion
@@ -64,6 +93,15 @@ private static IEnumerable<string> GetFragments( string text, string highlighted
     /// </summary>
     [Parameter] public string HighlightedText { get; set; }
 
+    /// <summary>
+    /// Array of search terms to be highlighted.
+    /// </summary>
+    /// <remarks>
+    /// Although this is an array, mutating it in-place (e.g. <c>HighlightedTexts[1] = "new value"</c>)
+    /// will not trigger a change. To update highlights, always reassign the entire array.
+    /// </remarks>
+    [Parameter] public string[] HighlightedTexts { get; set; }
+
     /// <summary>
     /// Whether or not the search term will be case sensitive.
     /// </summary>


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.




state: COMMENTED
author_association: NONE
submitted_at: 2025-04-04T19:46:17Z
user: login: stsrki
body: The API seems good to me. Need to add docs and release notes.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T21:01:09Z
user: login: tesar-tech
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T21:34:00Z
user: login: tesar-tech
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T21:34:03Z
user: login: stsrki
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T06:01:57Z
user: login: stsrki
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T08:45:50Z
user: login: tesar-tech
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T09:34:40Z
user: login: stsrki
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T09:41:49Z
user: login: stsrki
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-15T08:28:22Z

Review comments:
diff_hunk: @@ -80,4 +90,7 @@ private static IEnumerable<string> GetFragments( string text, string highlighted
     [Parameter] public bool UntilNextBoundary { get; set; }
 
     #endregion
+    
+    record Fragment(string Text, bool IsMatch);
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: stsrki
body: Fragment is too generic name. Better to make it more specific. Like, **HighlighterFragment**? Also, move it to a separate file.
created_at: 2025-04-04T20:59:13Z
author_association: COLLABORATOR
diff_hunk: @@ -23,31 +26,33 @@ public partial class Highlighter : BaseComponent
     /// <inheritdoc/>
     protected override void OnParametersSet()
     {
-        fragments = GetFragments( Text, HighlightedText, CaseSensitive, UntilNextBoundary, NextBoundary );
+        allHighlightedTexts = new List<string>(HighlightedTexts ?? Enumerable.Empty<string>()) { HighlightedText };
+        fragments = GetFragments( Text,allHighlightedTexts, CaseSensitive, UntilNextBoundary, NextBoundary );
     }
 
-    private static IEnumerable<string> GetFragments( string text, string highlightedText, bool caseSensitive = false, bool untilNextBoundary = false, string nextBoundary = null )
+    static IEnumerable<Fragment> GetFragments( string text, List<string> highlightedTexts, bool caseSensitive = false, bool untilNextBoundary = false, string nextBoundary = null)
     {
-        if ( string.IsNullOrWhiteSpace( text ) )
-        {
-            return new List<string>();
-        }
-
-        if ( string.IsNullOrWhiteSpace( highlightedText ) )
-        {
-            return new List<string> { text };
-        }
-
-        highlightedText = Regex.Escape( highlightedText );
-
-        if ( untilNextBoundary )
-        {
-            highlightedText += nextBoundary;
-        }
-
-        return Regex
-            .Split( text, $"({highlightedText})", caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase )
-            .Where( s => !string.IsNullOrEmpty( s ) );
+        if (string.IsNullOrWhiteSpace(text))
+            return new List<Fragment>();
+
+        if (highlightedTexts == null || highlightedTexts.Count == 0 || highlightedTexts.All(string.IsNullOrWhiteSpace))
+            return new List<Fragment> { new(text, false) };
+
+        var escaped = highlightedTexts
+                      .Where(s => !string.IsNullOrWhiteSpace(s))
+                      .Select(Regex.Escape).ToList();
+
+        string pattern = untilNextBoundary
+          ? string.Join("|", escaped.Select(h => h + nextBoundary))
+          : string.Join("|", escaped);
+
+        
+        var regex = new Regex($"({pattern})", caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: stsrki
body: It's usually good to cache regex variable instead of recreating it every time.
created_at: 2025-04-04T21:00:22Z
author_association: COLLABORATOR
diff_hunk: @@ -80,4 +90,7 @@ private static IEnumerable<string> GetFragments( string text, string highlighted
     [Parameter] public bool UntilNextBoundary { get; set; }
 
     #endregion
+    
+    record Fragment(string Text, bool IsMatch);
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: tesar-tech
body: There is quite a good reason for  `Fragment`.

It belongs to the class and will be used only there. That's why it's private and inside the class file. It's not a dto that is used project wise.

That also explains the naming - it's only used in Highlighter, so no benefit from naming it Highlighter again. (we have GetFragments method too).

Or why you think it should move?

created_at: 2025-04-04T21:34:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,31 +26,33 @@ public partial class Highlighter : BaseComponent
     /// <inheritdoc/>
     protected override void OnParametersSet()
     {
-        fragments = GetFragments( Text, HighlightedText, CaseSensitive, UntilNextBoundary, NextBoundary );
+        allHighlightedTexts = new List<string>(HighlightedTexts ?? Enumerable.Empty<string>()) { HighlightedText };
+        fragments = GetFragments( Text,allHighlightedTexts, CaseSensitive, UntilNextBoundary, NextBoundary );
     }
 
-    private static IEnumerable<string> GetFragments( string text, string highlightedText, bool caseSensitive = false, bool untilNextBoundary = false, string nextBoundary = null )
+    static IEnumerable<Fragment> GetFragments( string text, List<string> highlightedTexts, bool caseSensitive = false, bool untilNextBoundary = false, string nextBoundary = null)
     {
-        if ( string.IsNullOrWhiteSpace( text ) )
-        {
-            return new List<string>();
-        }
-
-        if ( string.IsNullOrWhiteSpace( highlightedText ) )
-        {
-            return new List<string> { text };
-        }
-
-        highlightedText = Regex.Escape( highlightedText );
-
-        if ( untilNextBoundary )
-        {
-            highlightedText += nextBoundary;
-        }
-
-        return Regex
-            .Split( text, $"({highlightedText})", caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase )
-            .Where( s => !string.IsNullOrEmpty( s ) );
+        if (string.IsNullOrWhiteSpace(text))
+            return new List<Fragment>();
+
+        if (highlightedTexts == null || highlightedTexts.Count == 0 || highlightedTexts.All(string.IsNullOrWhiteSpace))
+            return new List<Fragment> { new(text, false) };
+
+        var escaped = highlightedTexts
+                      .Where(s => !string.IsNullOrWhiteSpace(s))
+                      .Select(Regex.Escape).ToList();
+
+        string pattern = untilNextBoundary
+          ? string.Join("|", escaped.Select(h => h + nextBoundary))
+          : string.Join("|", escaped);
+
+        
+        var regex = new Regex($"({pattern})", caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: tesar-tech
body: But how? It can change every time the method is called.

I can somehow create a dictionary with `(pattern,caseSensitive)` key, but I don't think it's worth it.


created_at: 2025-04-04T21:34:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,11 +36,26 @@ record Fragment( string Text, bool IsMatch );
 
     #region Methods
 
-    /// <inheritdoc/>
-    protected override void OnParametersSet()
+    /// <inheritdoc />
+    public override async Task SetParametersAsync(ParameterView parameters)
     {
-        allHighlightedTexts = new List<string>( HighlightedTexts ?? Enumerable.Empty<string>() ) { HighlightedText };
-        fragments = GetFragments( Text, allHighlightedTexts, CaseSensitive, UntilNextBoundary, NextBoundary );
+        var changed =
+        ( parameters.TryGetValue<string>( nameof( Text ), out var text ) && Text != text )
+        || ( parameters.TryGetValue<string>( nameof( HighlightedText ), out var highlightedText ) && HighlightedText != highlightedText ) 
+        || ( parameters.TryGetValue<bool>( nameof( CaseSensitive ), out var caseSensitive ) && CaseSensitive != caseSensitive ) 
+        || ( parameters.TryGetValue<bool>( nameof( UntilNextBoundary ), out var untilNext ) && UntilNextBoundary != untilNext ) 
+        || ( parameters.TryGetValue<string>( nameof( NextBoundary ), out var nextBoundary ) && NextBoundary != nextBoundary )
+        || ( parameters.TryGetValue<IEnumerable<string>>( nameof( HighlightedTexts ), out var highlightedTexts ) 
+             && !(HighlightedTexts ?? Enumerable.Empty<string>()).SequenceEqual( highlightedTexts ?? Enumerable.Empty<string>() ) );
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: stsrki
body: We have `AreEqual` extension method to compare collections.
created_at: 2025-04-12T06:00:52Z
author_association: COLLABORATOR
diff_hunk: @@ -27,6 +27,14 @@
     <DocsPageSectionSource Code="BasicHighlighterExample" />
 </DocsPageSection>
 
+<DocsPageSection>
+    <DocsPageSectionHeader Title="Multiple Highlights">
+    </DocsPageSectionHeader>
path: Documentation/Blazorise.Docs/Pages/Docs/Components/Highlighters/HighlighterPage.razor
user: login: stsrki
body: Add a description of the example.
created_at: 2025-04-12T06:01:51Z
author_association: COLLABORATOR
diff_hunk: @@ -64,6 +93,11 @@ private static IEnumerable<string> GetFragments( string text, string highlighted
     /// </summary>
     [Parameter] public string HighlightedText { get; set; }
 
+    /// <summary>
+    /// Array of search terms to be highlighted.
+    /// </summary>
+    [Parameter] public List<string> HighlightedTexts { get; set; }
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: stsrki
body: I think it would be best to have it as an array

`public string[] HighlightedTexts`

That way we will force users to reasign HighlightedTexts parameter and we will not have problems with mutating of list and comparing reference types as we have in other component.
created_at: 2025-04-14T08:45:37Z
author_association: COLLABORATOR
diff_hunk: @@ -64,6 +93,11 @@ private static IEnumerable<string> GetFragments( string text, string highlighted
     /// </summary>
     [Parameter] public string HighlightedText { get; set; }
 
+    /// <summary>
+    /// Array of search terms to be highlighted.
+    /// </summary>
+    [Parameter] public List<string> HighlightedTexts { get; set; }
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: tesar-tech
body: I am afraid array won't be sufficient. You can still mutate an array: `myArray[1] = "something else";`.

The only chance is `IImutableArray` (or list), but that's and extra nuget package.

List and Array are the same for this topic.

created_at: 2025-04-14T09:34:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,6 +93,11 @@ private static IEnumerable<string> GetFragments( string text, string highlighted
     /// </summary>
     [Parameter] public string HighlightedText { get; set; }
 
+    /// <summary>
+    /// Array of search terms to be highlighted.
+    /// </summary>
+    [Parameter] public List<string> HighlightedTexts { get; set; }
path: Source/Blazorise/Components/Highlighter/Highlighter.razor.cs
user: login: stsrki
body: They would be less likely to update an array as `myArray[1] = "something else";` I agree with you. The problem still occurs. 

I think the best way to say it in the documentation is that mutation is generally not supported for reference types and that users need to recreate a new list if they wish to change the parameter.
created_at: 2025-04-14T09:41:49Z
author_association: COLLABORATOR

PR comments:
user: login: tesar-tech
created_at: 2025-04-04T21:39:35Z
body: There is also the `protected override void OnParametersSet()` consideration. Now the method is called twice, I guess it's because of the List being a parameter and Blazor does call the OnParamSet "just to be sure" when type is not a primitive one.

Yeah - I think this should be handled better. Probably with `SetParams` override???


author_association: CONTRIBUTOR
user: login: stsrki
created_at: 2025-04-08T08:07:28Z
body: > There is also the `protected override void OnParametersSet()` consideration. Now the method is called twice, I guess it's because of the List being a parameter and Blazor does call the OnParamSet "just to be sure" when type is not a primitive one.
> 
> Yeah - I think this should be handled better. Probably with `SetParams` override???

Maybe override `SetParametersAsync` and compare new parameter to see if they have changed?
author_association: COLLABORATOR
user: login: stsrki
created_at: 2025-04-08T08:10:04Z
body: It's left to do:

- Try to optimize fragment builder to recalculate only when needed
- Add example in the docs
- Add release notes
author_association: COLLABORATOR