Skip to content

feat(SelectCity): add ShowSearch parameter#610

Merged
ArgoZhang merged 13 commits intomasterfrom
feat-city
Oct 17, 2025
Merged

feat(SelectCity): add ShowSearch parameter#610
ArgoZhang merged 13 commits intomasterfrom
feat-city

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Oct 17, 2025

Link issues

fixes #609

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Enable searchable city selection by adding ShowSearch, SearchIcon, and AutoClose parameters, integrating pinyin filtering via a new PinYinService, and refactoring markup, CSS, and JS interop accordingly

New Features:

  • Added ShowSearch parameter and search UI to SelectCity
  • Introduced AutoClose and SearchIcon parameters for city selection
  • Implemented pinyin-based filtering with PinYinService and JS interop

Enhancements:

  • Refactored SelectCity rendering to use RenderFragment for dynamic province/city lists
  • Scoped and updated CSS selectors for SelectCity, SelectProvince, and SelectRegion dropdown menus
  • Updated JS module to manage search input events and dropdown visibility

Chores:

  • Added data models ProvinceItem and CityItem for structured region data

Copilot AI review requested due to automatic review settings October 17, 2025 10:44
@bb-auto bb-auto Bot added the enhancement New feature or request label Oct 17, 2025
@bb-auto bb-auto Bot added this to the v9.2.0 milestone Oct 17, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Oct 17, 2025

Reviewer's Guide

Adds ShowSearch parameter to SelectCity enabling optional search/filtering with pinyin support via a new PinYinService and JS interop, and updates related CSS and templates for consistent dropdown styling.

Sequence diagram for city search interaction in SelectCity

sequenceDiagram
    actor User
    participant SelectCityComponent
    participant JSInterop
    participant PinYinService
    User->>SelectCityComponent: Type in search input
    SelectCityComponent->>JSInterop: TriggerSearch(text)
    JSInterop->>SelectCityComponent: JSInvokable TriggerSearch(text)
    SelectCityComponent->>PinYinService: GetFirstLetters(text)
    PinYinService-->>SelectCityComponent: pinyin result
    SelectCityComponent->>SelectCityComponent: Filter provinces/cities
    SelectCityComponent->>User: Display filtered results
Loading

Class diagram for new and updated types in SelectCity

classDiagram
    class SelectCity {
        +bool ShowSearch
        +string? SearchIcon
        +bool AutoClose
        -string? _searchText
        +void TriggerSearch(string v)
        +HashSet<ProvinceItem> GenerateProvincePinYin()
        +HashSet<CityItem> GenerateCityPinYin(string provinceName)
        +HashSet<string> GetProvinces()
        +HashSet<string> GetCities(string provinceName)
        +bool IsChinese(string text)
    }
    class PinYinService {
        +string GetFirstLetters(string text)
        +Dictionary<string, string> Cache
    }
    class ProvinceItem {
        +string Name
        +string PinYin
        +HashSet<CityItem> Cities
    }
    class CityItem {
        +string Name
        +string PinYin
    }
    SelectCity --> PinYinService : uses
    SelectCity --> ProvinceItem : uses
    ProvinceItem --> CityItem : contains
    SelectCity --> CityItem : uses
Loading

File-Level Changes

Change Details Files
Introduce search feature in SelectCity component
  • Added ShowSearch, SearchIcon and AutoClose parameters with defaults
  • Overrode OnParametersSet to initialize SearchIcon
  • Implemented TriggerSearch JSInvokable and searchText backing field
  • Added InvokeInitAsync to wire up JS popover and search callbacks
  • Enhanced GetActiveClass to include ‘prev’ state for pinyin matches
  • Replaced inline item loops with RenderCities/RenderItem RenderFragments
  • Updated OnSelectCity to async and auto-hide popover when AutoClose is true
  • Conditionally render search input and clear icon in the razor template
  • Updated JSModuleAutoLoader attribute to load SelectCity.razor.js
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs
src/components/BootstrapBlazor.Region/Components/SelectCity.razor
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js
Add pinyin-based filtering support
  • Created PinYinService to compute first-letter pinyin from Chinese text
  • Introduced ProvinceItem and CityItem record structs for name/pinyin mapping
  • Refactored GetProvinces and GetCities to filter via pinyin or text match
  • Built cached province/city pin yin collections for efficient lookups
src/components/BootstrapBlazor.Region/Services/PinYinService.cs
src/components/BootstrapBlazor.Region/Data/ProvinceItem.cs
src/components/BootstrapBlazor.Region/Data/CityItem.cs
Refactor dropdown CSS and add search styles
  • Renamed .dropdown-menu to .dropdown-menu-city/province/region for clarity
  • Added variables and rules for search input, icons, and ‘prev’ states
  • Adjusted flex layouts, padding and colors to support the search UI
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.css
src/components/BootstrapBlazor.Region/Components/SelectProvince.razor.css
src/components/BootstrapBlazor.Region/Components/SelectRegion.razor.css
Update SelectProvince and SelectRegion templates
  • Applied new dropdown-menu-province and dropdown-menu-region classes
  • Aligned markup indentation and container class names to match CSS changes
src/components/BootstrapBlazor.Region/Components/SelectProvince.razor
src/components/BootstrapBlazor.Region/Components/SelectRegion.razor

Assessment against linked issues

Issue Objective Addressed Explanation
#609 Add a ShowSearch parameter to the SelectCity component to control whether the search functionality is displayed.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs:66` </location>
<code_context>
+    /// <summary>
+    /// <inheritdoc/>
+    /// </summary>
+    protected override void OnParametersSet()
+    {
+        base.OnParametersSet();
</code_context>

<issue_to_address>
**suggestion:** OnParametersSet sets SearchIcon only if null.

Using string.IsNullOrWhiteSpace(SearchIcon) will ensure the fallback icon is used when SearchIcon is empty or whitespace.

```suggestion
        if (string.IsNullOrWhiteSpace(SearchIcon))
        {
            SearchIcon = IconTheme.GetIconByKey(ComponentIcons.SelectSearchIcon);
        }
```
</issue_to_address>

### Comment 2
<location> `src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js:34` </location>
<code_context>
+        });
+    }
+
+    Data.set(id, { el, popover, searchInput, search });
+}
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Dispose function does not check for region existence.

Add a null check for region in dispose() to prevent errors when called with an invalid id.
</issue_to_address>

### Comment 3
<location> `src/components/BootstrapBlazor.Region/Services/PinYinService.cs:11` </location>
<code_context>
+
+static class PinYinService
+{
+    public static string GetFirstLetters(string text)
+    {
+        var ret = new StringBuilder();
</code_context>

<issue_to_address>
**issue (complexity):** Consider pre-computing a reverse lookup dictionary and loading the mapping data from a JSON file to reduce per-character lookup complexity and keep the source file small.

Consider pre-computing a reverse lookup so you only scan your huge map once, rather than on every character, and move the raw data into a JSON/resource file.

Example – invert the map at startup and load the data from a JSON file:

```csharp
// PinYinService.cs
using System.IO;
using System.Text.Json;

static class PinYinService
{
    // holds char → first-letter
    private static readonly Dictionary<char, char> _reverseMap;

    static PinYinService()
    {
        // load your large map from an external pinyin.json
        var json = File.ReadAllText("pinyin.json");
        var cache = JsonSerializer.Deserialize<Dictionary<string, string>>(json);

        _reverseMap = cache
            .SelectMany(kv => kv.Value.Select(ch => (ch, letter: kv.Key[0])))
            .ToDictionary(x => x.ch, x => x.letter);
    }

    public static string GetFirstLetters(string text)
    {
        var sb = new StringBuilder(text.Length);
        foreach (var c in text)
        {
            if (_reverseMap.TryGetValue(c, out var letter))
                sb.Append(letter);
        }
        return sb.ToString().ToUpperInvariant();
    }
}
```

pinyin.json (partial):

```json
{
  "a": "阿啊吖嗄腌锕",
  "ai": "爱埃碍矮挨唉哎哀皑癌蔼艾隘捱嗳嗌嫒瑷暧砹锿霭",
  
}
```

Steps:

1. Move the inline `Cache = new Dictionary<string,string>` block into `pinyin.json`.
2. In the static constructor, read and deserialize that file.
3. Build `_reverseMap` once by flattening the JSON map.
4. Update `GetFirstLetters` to do a single `TryGetValue` per character.
5. Remove the old `Cache.Where(…)` loop.

This preserves behavior but cuts the per-character lookup from O(N) to O(1), and keeps your source file tiny.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/components/BootstrapBlazor.Region/Services/PinYinService.cs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Add search capability to SelectCity, including Pinyin-first-letter filtering, and scope dropdown styles to avoid global collisions.

  • Add ShowSearch and AutoClose parameters to SelectCity with JS interop to filter cities by text or Pinyin initials.
  • Introduce PinYinService for first-letter generation and CityItem/ProvinceItem models to support filtering.
  • Scope dropdown CSS classes for Region/Province/City components.

Reviewed Changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/components/BootstrapBlazor.Region/Components/SelectCity.razor Adds search UI and rendering changes for city lists.
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs Adds search logic, Pinyin filtering, AutoClose behavior, and JS init.
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js Adds client-side search wiring, popover management, and cleanup helpers.
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.css Adds styles for search UI and scoping via .dropdown-menu-city.
src/components/BootstrapBlazor.Region/Components/SelectProvince.razor Scopes dropdown menu class name.
src/components/BootstrapBlazor.Region/Components/SelectProvince.razor.css Scopes dropdown styles via .dropdown-menu-province and updates selectors.
src/components/BootstrapBlazor.Region/Components/SelectRegion.razor Scopes dropdown menu class name.
src/components/BootstrapBlazor.Region/Components/SelectRegion.razor.css Scopes dropdown styles via .dropdown-menu-region and updates selectors.
src/components/BootstrapBlazor.Region/Services/PinYinService.cs New service for computing first-letter Pinyin from Chinese characters.
src/components/BootstrapBlazor.Region/Data/CityItem.cs New record struct for city name and Pinyin.
src/components/BootstrapBlazor.Region/Data/ProvinceItem.cs New record struct for province name, Pinyin, and city set.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

var ret = new StringBuilder();
foreach (var c in text)
{
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.Contains(char, StringComparison) is not a valid overload and will not compile. Replace with item.Value.Contains(c) (or item.Value.AsSpan().IndexOf(c) >= 0) since comparison culture is irrelevant for a single char.

Suggested change
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
foreach (var item in Cache.Where(item => item.Value.Contains(c)))

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +23
Input.composition(searchInput, async v => {
await invoke.invokeMethodAsync(options.triggerSearch, v);
});
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Property name casing mismatch: options.triggerSearch is undefined because the .NET side passes TriggerSearch. Use options.TriggerSearch to successfully invoke the .NET method.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +31
searchInput.value = '';
await invoke.invokeMethodAsync(options.triggerSearch, '');
});
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same casing issue as above; change options.triggerSearch to options.TriggerSearch so the clear action calls the correct .NET method.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +41
const region = Data.get(id)
const { popover } = region;
if (popover) {
popover.hide();
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential runtime error if Data.get(id) returns undefined (e.g., init didn't run). Guard before destructuring: const region = Data.get(id); if (!region) return; const { popover } = region;.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +58
export function dispose(id) {
const region = Data.get(id)
Data.remove(id)

const { popover, searchInput, search } = region;
if (popover) {
Popover.dispose(popover);
}
if (searchInput) {
Input.dispose(searchInput);
}
if (search) {
EventHandler.off(search, 'click');
}
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If region is undefined, destructuring throws. Add a null check before Data.remove and destructuring: const region = Data.get(id); if (!region) return;.

Copilot uses AI. Check for mistakes.
return ret.ToString().ToUpperInvariant();
}

public static Dictionary<string, string> Cache = new()
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expose the mapping as private static readonly to avoid external mutation and reduce API surface. For example: private static readonly Dictionary<string, string> Cache = new();

Suggested change
public static Dictionary<string, string> Cache = new()
private static readonly Dictionary<string, string> Cache = new()

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +21
foreach (var c in text)
{
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
{
ret.Append(item.Key.First());
break;
}
}
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetFirstLetters does an O(n × m) scan across the entire Cache for each character. Consider building a one-time Dictionary<char, char> lookup (char -> first-letter) at startup and then do O(1) lookups inside the loop.

Copilot uses AI. Check for mistakes.
@ArgoZhang ArgoZhang merged commit 61ca51d into master Oct 17, 2025
1 of 2 checks passed
@ArgoZhang ArgoZhang deleted the feat-city branch October 17, 2025 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(SelectCity): add ShowSearch parameter

2 participants