Skip to content

feat(IpAddress): support paste function#7276

Merged
ArgoZhang merged 2 commits intomainfrom
feat-ip
Dec 9, 2025
Merged

feat(IpAddress): support paste function#7276
ArgoZhang merged 2 commits intomainfrom
feat-ip

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Dec 9, 2025

Link issues

fixes #7275

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 IP address input enhancements to better handle clipboard-driven input and navigation behavior.

New Features:

  • Support pasting IPv4 addresses into the IP address component, parsing and distributing the segments across input cells.

Enhancements:

  • Normalize key handling to allow composed events alongside existing navigation and edit keys in the IP address input.

Copilot AI review requested due to automatic review settings December 9, 2025 02:25
@bb-auto bb-auto bot added the enhancement New feature or request label Dec 9, 2025
@bb-auto bb-auto bot added this to the v10.1.0 milestone Dec 9, 2025
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Dec 9, 2025

Reviewer's Guide

Adds paste handling to the IpAddress component’s IPv4 input cells, allowing users to paste an IP-like string which is parsed, sanitized, and distributed across the four octet fields, and slightly adjusts key handling conditions to better support composed events.

File-Level Changes

Change Details Files
Add paste support on IPv4 cells to parse clipboard text into up to four octets and populate the input fields with sanitized values.
  • Register a new 'paste' event handler on each IPv4 cell using the shared EventHandler utility.
  • Prevent the default paste behavior and retrieve plain text from the clipboard, supporting both modern and legacy clipboard APIs.
  • Normalize clipboard text by stripping non-digit and non-dot characters, splitting on '.', and filtering out empty segments.
  • Iterate over up to four segments, parse each as an integer, clamp values to the 0–255 range, and assign them to the corresponding IPv4 cells.
  • Update the component’s internal prevValues cache to stay in sync with the pasted values.
src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js
Relax keydown logic to treat composed events like navigation keys, preventing interference with IME/composed input behavior.
  • Extend the conditional branch that previously only checked specific keys (Delete, Tab, ArrowLeft, ArrowRight) to also short-circuit when the event is composed.
  • Leave the branch body intentionally empty to continue skipping custom processing for these events.
src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js

Assessment against linked issues

Issue Objective Addressed Explanation
#7275 Add paste support to the IpAddress component so users can paste an IPv4 address into the control.

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
Contributor

@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 found some issues that need to be addressed.

  • The else if (e.composed || e.key === 'Delete' || 'Tab'...) branch looks suspicious: e.composed is a boolean unrelated to keyboard shortcuts, and the branch body is empty, so it’s unclear what behavior is intended here (e.g., paste via Ctrl+V?)—consider either removing this condition or wiring it to an explicit use case.
  • In the paste handler, you currently update the visible .ipv4-cell values and ip.prevValues but don’t appear to trigger any change/blur logic used elsewhere in the component; if other logic depends on those events or a consolidated value, consider reusing the same update path to keep behavior consistent with manual input.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `else if (e.composed || e.key === 'Delete' || 'Tab'...)` branch looks suspicious: `e.composed` is a boolean unrelated to keyboard shortcuts, and the branch body is empty, so it’s unclear what behavior is intended here (e.g., paste via Ctrl+V?)—consider either removing this condition or wiring it to an explicit use case.
- In the paste handler, you currently update the visible `.ipv4-cell` values and `ip.prevValues` but don’t appear to trigger any change/blur logic used elsewhere in the component; if other logic depends on those events or a consolidated value, consider reusing the same update path to keep behavior consistent with manual input.

## Individual Comments

### Comment 1
<location> `src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js:76` </location>
<code_context>
                 selectCell(el, index - 1)
             }
-            else if (e.key === 'Delete' || e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
+            else if (e.composed || e.key === 'Delete' || e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {

             }
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `e.composed` here may unintentionally swallow most key events.

Because many keyboard events have `composed === true` by default, this condition will match far more often than intended and prevent the final `else` from ever handling normal key input. If you’re trying to special-case IME/composition or modifier shortcuts, use something more specific (e.g. `e.isComposing`, `e.ctrlKey`, etc.) instead of `e.composed`.
</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.

@codecov
Copy link
Copy Markdown

codecov bot commented Dec 9, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (19da5ef) to head (75aa55d).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7276   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          745       745           
  Lines        32621     32621           
  Branches      4522      4522           
=========================================
  Hits         32621     32621           
Flag Coverage Δ
BB 100.00% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ArgoZhang ArgoZhang merged commit 8e4db1c into main Dec 9, 2025
12 checks passed
@ArgoZhang ArgoZhang deleted the feat-ip branch December 9, 2025 02:29
Copy link
Copy Markdown
Contributor

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 adds paste functionality to the IpAddress component, allowing users to paste IP addresses from their clipboard into the input fields. The implementation includes a new paste event handler that parses clipboard text and populates the IP address cells.

Key Changes:

  • Added a paste event handler that parses IP address strings from clipboard data
  • Modified the keydown event handler to include e.composed in the allowed keys condition

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

Comment on lines +93 to +111
EventHandler.on(c, 'paste', e => {
e.preventDefault();
const raw = (e.clipboardData || window.clipboardData)?.getData('text') ?? '';
if (!raw) {
return;
}
const parts = raw.replace(/[^\d.]/g, '').split('.').filter(p => p.length);
const cells = el.querySelectorAll(".ipv4-cell");
let pos = 0;
parts.forEach(p => {
if (pos > 3) {
return;
}
const num = Math.max(0, Math.min(255, parseInt(p, 10) || 0));
cells[pos].value = num.toString();
ip.prevValues[pos] = cells[pos].value;
pos++;
});
});
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The paste event handler always starts pasting from position 0, ignoring which cell the user is actually focused on. When a user pastes "192.168" while focused on the second cell, it overwrites the first two cells instead of filling from the second cell onward.

Consider using the index parameter (available in the forEach scope) to start pasting from the currently focused cell:

let pos = index;
parts.forEach(p => {
    if (pos > 3) {
        return;
    }
    const num = Math.max(0, Math.min(255, parseInt(p, 10) || 0));
    cells[pos].value = num.toString();
    ip.prevValues[pos] = cells[pos].value;
    pos++;
});

Copilot uses AI. Check for mistakes.
selectCell(el, index - 1)
}
else if (e.key === 'Delete' || e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
else if (e.composed || e.key === 'Delete' || e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The addition of e.composed to this condition is incorrect. The composed property is a boolean that indicates whether the event can bubble across the shadow DOM boundary, not whether the event is from IME composition. This will allow all composed events (which is most standard events) to bypass validation, potentially allowing invalid input.

If the intent is to allow IME composition input, this is already handled by the separate compositionend event handler at line 25. The e.composed check should be removed.

Suggested change
else if (e.composed || e.key === 'Delete' || e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
else if (e.key === 'Delete' || e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +110
const parts = raw.replace(/[^\d.]/g, '').split('.').filter(p => p.length);
const cells = el.querySelectorAll(".ipv4-cell");
let pos = 0;
parts.forEach(p => {
if (pos > 3) {
return;
}
const num = Math.max(0, Math.min(255, parseInt(p, 10) || 0));
cells[pos].value = num.toString();
ip.prevValues[pos] = cells[pos].value;
pos++;
});
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The regex /[^\d.]/g allows multiple consecutive dots and doesn't validate IP address format. For example, "192...168" would be parsed as ["192", "168"], silently ignoring the malformed input. Consider adding validation or using a more robust parsing approach that validates IP address format before processing.

Suggested change
const parts = raw.replace(/[^\d.]/g, '').split('.').filter(p => p.length);
const cells = el.querySelectorAll(".ipv4-cell");
let pos = 0;
parts.forEach(p => {
if (pos > 3) {
return;
}
const num = Math.max(0, Math.min(255, parseInt(p, 10) || 0));
cells[pos].value = num.toString();
ip.prevValues[pos] = cells[pos].value;
pos++;
});
// Validate IPv4 address format: four octets, each 0-255, separated by dots
const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/;
if (!ipv4Regex.test(raw.trim())) {
// Invalid IP address format; ignore paste
return;
}
const parts = raw.trim().split('.');
const cells = el.querySelectorAll(".ipv4-cell");
for (let pos = 0; pos < 4; pos++) {
cells[pos].value = parts[pos];
ip.prevValues[pos] = cells[pos].value;
}

Copilot uses AI. Check for mistakes.
}
})

EventHandler.on(c, 'paste', e => {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The new 'paste' event handler is not being cleaned up in the dispose function (lines 115-125). This creates a memory leak as the event listener will persist after the component is disposed.

The dispose function should include:

EventHandler.off(c, 'paste')

Copilot uses AI. Check for mistakes.
ArgoZhang added a commit that referenced this pull request Dec 9, 2025
* feat(IpAddress): support paste function

* refactor: 代码格式化
# Conflicts:
#	src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js
ArgoZhang added a commit that referenced this pull request Feb 8, 2026
* feat: 增加 Key 参数

* refactor: 使用回调方法 GetKeyByITem 获得行 Key 值

* chore: bump version 9.6.5

* feat: 增加 PdfOptions 配置项

* chore: bump version 9.6.6

* refactor: 调整 IconTemplate 优先级别

* feat: 增加 ActionButtonTemplate 模板

* refactor: 增加条件

* chore: bump version 9.6.7

* feat(IpAddress): support paste function (#7276)

* feat(IpAddress): support paste function

* refactor: 代码格式化
# Conflicts:
#	src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js

* chore: bump version 9.6.8

* feat(IpAddress): trigger ValueChanged on paste event (#7280)

* refactor: 增加 JSInvoke 能力

* refactor: 更改样式

* refactor: 增加客户端更改 IP 回调方法

* test: 更新单元测试

* chore: bump version 10.1.3
# Conflicts:
#	src/BootstrapBlazor/BootstrapBlazor.csproj
#	src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.scss
#	test/UnitTest/Components/IpAddressTest.cs

* chore: bump version 9.6.9

* chore: 支持 Interop 参数

* chore: bump version 9.6.10

* feat: 增加 OnBeforeTreeItemClick 方法

* chore: bump version 9.6.11

* refactor:  兼容嵌套问题

* chore: bump version 9.6.12

* feat: 支持端口

* chore: bump version 9.6-13-beta01

* chore: bump version 9.6.13

* feat(ZipArchiveService): add ArchiveDirectoryAsync method

# Conflicts:
#	src/BootstrapBlazor/Services/DefaultZipArchiveService.cs
#	src/BootstrapBlazor/Services/IZipArchiveService.cs

* test: 增加单元测试

# Conflicts:
#	test/UnitTest/Services/ZipArchiveServiceTest.cs

* chore: bump version 9.6.14

* refactor: 增加 Key 参数

* refactor: 取消冗余代码

* revert: 撤销更改

* refactor: 移动 SetKey 位置

* refactor: 调整序号

* test: 增加单元测试
ArgoZhang added a commit that referenced this pull request Feb 8, 2026
* feat: 增加 Key 参数

* refactor: 使用回调方法 GetKeyByITem 获得行 Key 值

* chore: bump version 9.6.5

* feat: 增加 PdfOptions 配置项

* chore: bump version 9.6.6

* refactor: 调整 IconTemplate 优先级别

* feat: 增加 ActionButtonTemplate 模板

* refactor: 增加条件

* chore: bump version 9.6.7

* feat(IpAddress): support paste function (#7276)

* feat(IpAddress): support paste function

* refactor: 代码格式化
# Conflicts:
#	src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js

* chore: bump version 9.6.8

* feat(IpAddress): trigger ValueChanged on paste event (#7280)

* refactor: 增加 JSInvoke 能力

* refactor: 更改样式

* refactor: 增加客户端更改 IP 回调方法

* test: 更新单元测试

* chore: bump version 10.1.3
# Conflicts:
#	src/BootstrapBlazor/BootstrapBlazor.csproj
#	src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.scss
#	test/UnitTest/Components/IpAddressTest.cs

* chore: bump version 9.6.9

* chore: 支持 Interop 参数

* chore: bump version 9.6.10

* feat: 增加 OnBeforeTreeItemClick 方法

* chore: bump version 9.6.11

* refactor:  兼容嵌套问题

* chore: bump version 9.6.12

* feat: 支持端口

* chore: bump version 9.6-13-beta01

* chore: bump version 9.6.13

* feat(ZipArchiveService): add ArchiveDirectoryAsync method

# Conflicts:
#	src/BootstrapBlazor/Services/DefaultZipArchiveService.cs
#	src/BootstrapBlazor/Services/IZipArchiveService.cs

* test: 增加单元测试

# Conflicts:
#	test/UnitTest/Services/ZipArchiveServiceTest.cs

* chore: bump version 9.6.14

* refactor: 增加 Key 参数

* refactor: 取消冗余代码

* revert: 撤销更改

* refactor: 移动 SetKey 位置

* refactor: 调整序号

* test: 增加单元测试

* refactor: 更新代码

* refactor: 重构代码

* refactor: 重构代码

* refactor: 重构代码

* refactor: 重构代码

* refactor: 更新代码
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(IpAddress): support paste function

2 participants