Skip to content

feat(SelectCity): bump version 9.0.9#671

Merged
ArgoZhang merged 3 commits intomasterfrom
refactor-city
Nov 11, 2025
Merged

feat(SelectCity): bump version 9.0.9#671
ArgoZhang merged 3 commits intomasterfrom
refactor-city

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Nov 11, 2025

Link issues

fixes #670

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

Add runtime toggle for SelectCity search input, refactor JS search management, and bump package version to 9.0.9.

New Features:

  • Allow dynamically enabling or disabling the search input in SelectCity component at runtime.

Bug Fixes:

Enhancements:

  • Refactor SelectCity JavaScript to centralize search initialization and disposal using initSearch, disposeSearch, and shared region state.

Build:

  • Bump BootstrapBlazor.Region package version to 9.0.9.

Copilot AI review requested due to automatic review settings November 11, 2025 05:48
@bb-auto bb-auto Bot added the enhancement New feature or request label Nov 11, 2025
@bb-auto bb-auto Bot added this to the v9.2.0 milestone Nov 11, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Nov 11, 2025

Reviewer's Guide

This PR refactors the SelectCity component to support dynamic search toggling by modularizing JS initialization/disposal logic, adds C# detection for ShowSearch changes to invoke the new resetSearch bridge, and bumps the package version to 9.0.9.

Sequence diagram for dynamic search toggle in SelectCity

sequenceDiagram
    participant Blazor as Blazor (C#)
    participant JS as JavaScript
    Blazor->>JS: init(id, invoke, options)
    JS->>JS: initSearch(region)
    JS->>JS: Data.set(id, region)
    Note over Blazor,JS: On ShowSearch change
    Blazor->>JS: resetSearch(id, ShowSearch)
    alt ShowSearch is true
        JS->>JS: initSearch(region)
    else ShowSearch is false
        JS->>JS: disposeSearch(region)
    end
Loading

Updated class diagram for SelectCity component

classDiagram
    class SelectCity {
        - HashSet<string> _values
        - string? _searchText
        - bool _showSearch
        + OnParametersSet()
        + OnAfterRenderAsync(bool firstRender)
    }
Loading

File-Level Changes

Change Details Files
Modularize JS search initialization and disposal
  • Introduce initSearch and disposeSearch helper functions
  • Add resetSearch function to toggle search on demand
  • Store component state in a region object via Data.set
  • Update dispose routine to delegate to disposeSearch
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js
Implement dynamic ShowSearch parameter handling in C#
  • Add private _showSearch field to track previous state
  • Clear search text in OnParametersSet when ShowSearch is disabled
  • Override OnAfterRenderAsync to detect ShowSearch changes
  • Invoke JS resetSearch when ShowSearch toggles
src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs
Bump component package version
  • Update project version to 9.0.9
src/components/BootstrapBlazor.Region/BootstrapBlazor.Region.csproj

Assessment against linked issues

Issue Objective Addressed Explanation
#670 Bump the version of SelectCity to 9.0.9. The diff does not show any change to a version number in code, configuration, or packaging files. There is no evidence that the version was updated to 9.0.9.

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

@ArgoZhang ArgoZhang merged commit 4f0b0e4 into master Nov 11, 2025
6 of 7 checks passed
@ArgoZhang ArgoZhang deleted the refactor-city branch November 11, 2025 05:48
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:100` </location>
<code_context>
+            _showSearch = ShowSearch;
+        }
+
+        if (!_showSearch != ShowSearch)
+        {
+            _showSearch = ShowSearch;
</code_context>

<issue_to_address>
**issue:** The logic in the conditional is confusing and may not behave as intended.

The condition uses a double negative, which is unclear and may cause logic errors. Simplifying it to `if (_showSearch != ShowSearch)` will improve readability and correctness.
</issue_to_address>

### Comment 2
<location> `src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js:26` </location>
<code_context>
+}
+
+export function resetSearch(id, search) {
+    const region = Data.get(id);
+
+    if (search) {
</code_context>

<issue_to_address>
**issue:** No null check for region object retrieved from Data.

Add a check to handle cases where Data.get(id) returns null or undefined to prevent runtime errors.
</issue_to_address>

### Comment 3
<location> `src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js:20` </location>
<code_context>
         }
     });

+    const region = { el, invoke, options, popover };
+    initSearch(region);
+    Data.set(id, region);
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring to use closures for search logic and only store necessary functions on the region object.

Here’s a way to collapse the extra indirection into two small closures and only store what you really need on your `region` object—no more raw `el`/`invoke`/`options` dancing around, just the popover and two self-contained functions:

```js
export function init(id, invoke, options) {
  const el = document.getElementById(id);
  if (!el) return;

  const popover = Popover.init(el, {
    shownCallback: () => searchInput?.focus()
  });

  const searchInput = el.querySelector('.search-text');
  const clearIcon   = el.querySelector('.dropdown-menu-search .clear-icon');

  // capture searchInput/clearIcon/invoke/options in closure
  const initSearch = () => {
    if (searchInput) {
      Input.composition(searchInput, v =>
        invoke.invokeMethodAsync(options.triggerSearch, v)
      );
    }
    if (clearIcon) {
      EventHandler.on(clearIcon, 'click', () => {
        searchInput.value = '';
        invoke.invokeMethodAsync(options.triggerSearch, '');
      });
    }
  };

  const disposeSearch = () => {
    if (searchInput) Input.dispose(searchInput);
    if (clearIcon)   EventHandler.off(clearIcon, 'click');
  };

  initSearch();
  Data.set(id, { popover, initSearch, disposeSearch });
}

export function resetSearch(id, enable) {
  const region = Data.get(id);
  region.disposeSearch();
  if (enable) region.initSearch();
}

export function hide(id) {
  const { popover } = Data.get(id) || {};
  popover?.hide();
}

export function dispose(id) {
  const region = Data.get(id);
  Data.remove(id);
  if (!region) return;
  Popover.dispose(region.popover);
  region.disposeSearch();
}
```

Benefits:

- No shared mutable `region` fields for `el`/`invoke`/`options`.  
- `initSearch` / `disposeSearch` are closures, so you only store two functions.  
- `resetSearch` stays simple (toggle via those closures).  
- All functionality is identical; flow is now linear and easier to follow.
</issue_to_address>

### Comment 4
<location> `src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs:91` </location>
<code_context>
+    /// </summary>
+    /// <param name="firstRender"></param>
+    /// <returns></returns>
+    protected override async Task OnAfterRenderAsync(bool firstRender)
+    {
+        await base.OnAfterRenderAsync(firstRender);
</code_context>

<issue_to_address>
**issue (complexity):** Consider removing the _showSearch field and handling ShowSearch changes in OnParametersSetAsync to simplify state management and JS invocation.

```csharp
// Remove the _showSearch field and the OnAfterRenderAsync override entirely.
// Instead, detect ShowSearch changes in OnParametersSetAsync and invoke JS after the render.
private bool _previousShowSearch;

protected override async Task OnParametersSetAsync()
{
    await base.OnParametersSetAsync();

    // Reset search text when ShowSearch is turned off
    if (ShowSearch == false)
    {
        _searchText = "";
    }

    // Only call resetSearch JS when ShowSearch actually changes
    if (_previousShowSearch != ShowSearch)
    {
        _previousShowSearch = ShowSearch;
        // This will run after the component has rendered the new state
        await InvokeVoidAsync("resetSearch", Id, ShowSearch);
    }
}
```
</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.

_showSearch = ShowSearch;
}

if (!_showSearch != ShowSearch)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: The logic in the conditional is confusing and may not behave as intended.

The condition uses a double negative, which is unclear and may cause logic errors. Simplifying it to if (_showSearch != ShowSearch) will improve readability and correctness.

/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider removing the _showSearch field and handling ShowSearch changes in OnParametersSetAsync to simplify state management and JS invocation.

// Remove the _showSearch field and the OnAfterRenderAsync override entirely.
// Instead, detect ShowSearch changes in OnParametersSetAsync and invoke JS after the render.
private bool _previousShowSearch;

protected override async Task OnParametersSetAsync()
{
    await base.OnParametersSetAsync();

    // Reset search text when ShowSearch is turned off
    if (ShowSearch == false)
    {
        _searchText = "";
    }

    // Only call resetSearch JS when ShowSearch actually changes
    if (_previousShowSearch != ShowSearch)
    {
        _previousShowSearch = ShowSearch;
        // This will run after the component has rendered the new state
        await InvokeVoidAsync("resetSearch", Id, ShowSearch);
    }
}

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

This PR bumps the version of the SelectCity component from 9.0.8 to 9.0.9 (and 10.0.0-rc.2.1.0 to 10.0.0-rc.2.1.1) and implements dynamic handling of the ShowSearch parameter, allowing search functionality to be toggled at runtime.

Key Changes:

  • Refactored JavaScript code to support dynamic enabling/disabling of search functionality
  • Added new resetSearch JavaScript function to handle search state changes
  • Implemented OnAfterRenderAsync in C# to detect and respond to ShowSearch parameter changes

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 13 comments.

File Description
SelectCity.razor.js Refactored search initialization into reusable helper functions (initSearch, disposeSearch) and added resetSearch export to handle dynamic search toggling
SelectCity.razor.cs Added _showSearch field and OnAfterRenderAsync method to track and respond to ShowSearch parameter changes; clears search text when ShowSearch is disabled
BootstrapBlazor.Region.csproj Version bumped from 9.0.8 to 9.0.9 for VS 17.0 and from 10.0.0-rc.2.1.0 to 10.0.0-rc.2.1.1 for VS 18.0
Comments suppressed due to low confidence (1)

src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs:216

  • Write to static field from instance method, property, or constructor.
        _provinceItems ??= [.. Provinces.Select(i => new ProvinceItem()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

_showSearch = ShowSearch;
}

if (!_showSearch != ShowSearch)
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

The boolean logic in this condition is incorrect. The expression !_showSearch != ShowSearch will be evaluated as (!_showSearch) != ShowSearch, which is likely not the intended behavior.

This should be changed to _showSearch != ShowSearch to properly detect when the ShowSearch property has changed.

Suggested change
if (!_showSearch != ShowSearch)
if (_showSearch != ShowSearch)

Copilot uses AI. Check for mistakes.

const popover = Popover.init(el, {
shownCallback: () => {
if (searchInput != null) {
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

The variable searchInput is referenced in the callback but is not defined in scope at this point. The searchInput variable is only created later in the initSearch function (line 38), which is called after this popover is initialized.

This will cause a runtime error when the popover is shown. Consider moving the popover initialization to occur after the search input is initialized, or capture the searchInput reference in a way that allows the callback to access it when executed.

Copilot uses AI. Check for mistakes.
const el = document.getElementById(id)
const el = document.getElementById(id);
if (el === null) {
return
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

Missing semicolon after the return statement. While JavaScript has automatic semicolon insertion, the codebase appears to use explicit semicolons consistently (see line 7, 22, etc.), so this should have a semicolon for consistency.

Suggested change
return
return;

Copilot uses AI. Check for mistakes.
}
if (search) {
EventHandler.off(search, 'click');
}
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

Missing semicolon after the statement. While JavaScript has automatic semicolon insertion, the codebase appears to use explicit semicolons consistently (see lines 53-54), so this should have a semicolon for consistency.

Copilot uses AI. Check for mistakes.
}

Data.set(id, { el, popover, searchInput, search });
region.searchInput = searchInput;
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

Missing semicolon after the statement. While JavaScript has automatic semicolon insertion, the codebase appears to use explicit semicolons consistently (see line 54, 60), so this should have a semicolon for consistency.

Copilot uses AI. Check for mistakes.

Data.set(id, { el, popover, searchInput, search });
region.searchInput = searchInput;
region.search = search;
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

Missing semicolon after the statement. While JavaScript has automatic semicolon insertion, the codebase appears to use explicit semicolons consistently (see lines 53-54, 60), so this should have a semicolon for consistency.

Copilot uses AI. Check for mistakes.
@@ -46,14 +76,10 @@ export function dispose(id) {
const region = Data.get(id)
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

Missing semicolon after the statement. While JavaScript has automatic semicolon insertion, the codebase appears to use explicit semicolons consistently (see line 77, 81), so this should have a semicolon for consistency.

Suggested change
const region = Data.get(id)
const region = Data.get(id);

Copilot uses AI. Check for mistakes.
const { popover, searchInput, search } = region;
const { popover } = region;
if (popover) {
Popover.dispose(popover);
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

Missing semicolon after the statement. While JavaScript has automatic semicolon insertion, the codebase appears to use explicit semicolons consistently (see line 76-77, 84), so this should have a semicolon for consistency.

Copilot uses AI. Check for mistakes.
EventHandler.off(search, 'click');
}

disposeSearch(region);
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

Missing semicolon after the statement. While JavaScript has automatic semicolon insertion, the codebase appears to use explicit semicolons consistently (see line 76-77, 81), so this should have a semicolon for consistency.

Copilot uses AI. Check for mistakes.
_values.Clear();
}

if (ShowSearch == false)
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

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

The expression 'A == false' can be simplified to '!A'.

Suggested change
if (ShowSearch == false)
if (!ShowSearch)

Copilot uses AI. Check for mistakes.
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): bump version 9.0.9

2 participants