Skip to content

refactor(input): merge textarea into rc-input and deduplicate shared logic#163

Open
QDyanbing wants to merge 5 commits intoreact-component:masterfrom
QDyanbing:textarea
Open

refactor(input): merge textarea into rc-input and deduplicate shared logic#163
QDyanbing wants to merge 5 commits intoreact-component:masterfrom
QDyanbing:textarea

Conversation

@QDyanbing
Copy link
Contributor

@QDyanbing QDyanbing commented Mar 18, 2026

背景

本次 PR 包含两部分工作:

  1. textarea 能力正式并入 rc-input
  2. 在并入之后,继续收敛 InputTextArea 之间的重复实现

在迁移完成后,rc-input 已经成为 inputtextarea 后续维护的主要承接位置。基于这个前提,这次进一步对内部实现做整理,目标是在不改变既有行为的前提下,降低重复代码和后续维护成本。

主要改动

1. 将 textarea 能力并入 rc-input

将原先 textarea 的核心实现迁移到 rc-input,使 rc-input 能够直接承接 textarea 的相关能力,包括:

  • TextArea 主组件实现
  • ResizableTextArea
  • 自适应高度相关逻辑
  • 类型定义
  • 样式
  • 文档与 demo
  • 测试用例

同时在 rc-input 中提供:

  • Input.TextArea
  • 命名导出 TextArea

这意味着后续 textarea 相关能力可以直接在 rc-input 中维护和演进。

2. 收敛 Input 与 TextArea 的共享逻辑

在 textarea 合入后,InputTextArea 在以下方面存在较多重复实现:

  • 受控 / 非受控值处理
  • showCount / count 配置归并
  • count.max 超限裁剪
  • exceedFormatter
  • selection 恢复
  • count 展示相关派生状态

本次将这些可以稳定复用、且不会抹平组件差异的部分进行了收敛,主要抽离为几类共享能力:

  • 值合并与格式化逻辑
  • count 配置归并逻辑
  • 超限裁剪与 selection 恢复逻辑
  • count 展示派生状态计算逻辑

这样可以避免 InputTextArea 长期各自维护一套相近实现。

3. 保留组件之间原本存在的行为差异

这次重构没有强行把 InputTextArea 抽成一套完全统一的事件流程。

像 composition / IME、事件触发时机、交互细节等存在已知差异的部分,仍然保留在各自组件内部处理,避免因为过度抽象而引入行为偏差。

也就是说,这次抽离的是“适合共享的纯逻辑”,而不是把所有实现都揉成一层通用逻辑。

行为影响说明

本次 PR 的原则是:

  • 不新增对外 API
  • 不改变既有使用方式
  • 不主动修改组件现有语义
  • 以内部重构和能力收敛为主

对上层使用方的影响

从上层接入方式看,这次改动风险较低。

以 antd 当前使用方式为例:

  • Input 使用 @rc-component/input
  • TextArea 当前仍有自己的包装层

因此,这次 rc-input 的内部整理不会直接改变上层的 API 形状。对于未来逐步将 textarea 维护进一步收口到 rc-input,这次改动也为后续继续演进打下了更稳定的内部基础。

测试与验证

已完成以下验证:

  • npm test -- --runInBand
  • npx tsc -p tsconfig.json --noEmit

结果均通过。

另外,针对这次重构涉及的关键路径,也补充和确认了回归覆盖,重点包括:

  • count.maxmaxLength 同时存在时的优先级
  • Input / TextArea 的 count 展示路径
  • TextArea 在未开启 showCount 时不会产生额外 count 展示副作用

Summary by CodeRabbit

发布说明

  • 新功能

    • 新增可复用的 TextArea 组件(可通过 Input.TextArea 或独立 TextArea 导入),支持 autoSize、allowClear、showCount、onPressEnter、受控/非受控与 IME 场景,暴露 focus()/blur()/nativeElement 等 ref 方法并支持动态尺寸变化与 onResize 回调。
  • 文档

    • 补充完整 TextArea 使用说明、API 表、textareaRef 示例及多种示例页面(基本、自动高度、清除、计数)。
  • 样式

    • 新增 TextArea 相关样式与超出范围视觉指示。
  • 测试

    • 大量新增单测覆盖计数、超出、聚焦、大小调整、清除与行为边界。
  • 依赖更新

    • 增加运行时支持以实现自动调整高度与观察器功能。

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Walkthrough

此 PR 在包 @rc-component/input 中新增并导出完整 TextArea 功能:包含 ResizableTextArea、TextArea 组件、自动高度计算工具、计数/超出处理钩子、样式、示例文档与大量测试,并调整默认导出以包含 Input.TextArea

Changes

Cohort / File(s) Summary
核心组件
src/TextArea.tsx, src/ResizableTextArea.tsx, src/calculateNodeHeight.tsx
新增 ResizableTextArea 与 TextArea,实现自动尺寸计算、测量工具与渲染/事件/refs 暴露(包括 autoSize 支持 与 onResize 回调)。
类型与 Hooks
src/interface.ts, src/hooks/useCountDisplay.ts, src/hooks/useCountExceed.ts, src/hooks/useMergedValue.ts, src/hooks/useCount.ts
新增并导出 TextArea 相关类型(AutoSizeType、TextAreaProps、TextAreaRef、ResizableTextAreaRef 等);新增 useCountDisplay、useCountExceed、useMergedValue 钩子;将 ForcedCountConfig 导出。
Input 调整
src/Input.tsx
内部值管理改用 useMergedValue,集成计数/超出钩子(useCountDisplay/useCountExceed),简化计数与超出逻辑。
导出与入口
src/index.tsx
将 TextArea 作为命名导出并附加到默认导出(Input.TextArea);扩展并重新导出相关类型,默认导出改为带 .TextArea 的复合 Input。
样式
assets/index.less
新增 @textarea-prefix-cls.rc-textarea-affix-wrapper、嵌套 textarea 样式及 .rc-textarea-out-of-range 超出样式。
文档与示例
README.md, docs/demo/textarea.md, docs/examples/textarea-*.tsx
更新 README 迁移与使用示例;新增 textarea 演示页与多个示例文件(basic/auto-size/allow-clear/show-count)。
测试与测试工具
tests/*.test.tsx, tests/utils.ts, tests/setupFilesAfterEnv.js
新增大量关于 TextArea 的单元测试(计数、清除、焦点、IME、自动尺寸等);添加 triggerResize/wait 工具与 ResizeObserver/RAF 测试 shim。
包依赖
package.json
新增运行时依赖 @rc-component/resize-observer: ^1.1.1

Sequence Diagram(s)

sequenceDiagram
    participant U as User / App
    participant TextArea as TextArea (wrapper)
    participant Resizable as ResizableTextArea
    participant Calc as calculateNodeHeight
    participant RO as ResizeObserver

    U->>TextArea: render / 输入事件
    TextArea->>Resizable: 转发 props、value、ref
    Resizable->>Calc: 请求 auto-size(content, minRows/maxRows)
    Calc-->>Resizable: 返回 计算高度/样式
    Resizable->>RO: observe(textarea)
    RO-->>Resizable: resize 事件
    Resizable->>TextArea: 触发 onResize / 更新高度
    TextArea->>U: 暴露 ref 方法(focus、blur、nativeElement)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 我是地里的小白兔,跳来庆佳作,
新增文本域能伸缩,行数随心活,
计数与超出都记着,清除一键走,
文档示例齐备好,测试覆盖不落角,
胡萝卜在前,代码采花我先尝。

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地总结了主要变更:将textarea功能合并到rc-input中并消除共享逻辑的重复,与大量涉及TextArea组件、ResizableTextArea、类型定义和文档的changeset完全相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the input component library by integrating the textarea functionality directly into rc-input and consolidating shared logic between Input and TextArea. The primary objective is to reduce code duplication and maintenance overhead without altering the external API or existing component behaviors, thereby improving the internal architecture and preparing for future feature development.

Highlights

  • Textarea Capability Merged into rc-input: The textarea functionality, previously a separate component, has been fully integrated into rc-input. This includes the TextArea main component, ResizableTextArea, auto-sizing logic, type definitions, styles, documentation, and test cases. rc-input now provides Input.TextArea and a named TextArea export, centralizing maintenance and evolution.
  • Shared Logic Deduplication: Significant duplication between Input and TextArea implementations has been resolved. Common logic for controlled/uncontrolled value handling, showCount/count configuration, count.max truncation, exceedFormatter, selection restoration, and count display derived states has been extracted into reusable hooks and utilities.
  • Preservation of Component Behavior Differences: The refactoring intentionally preserves existing behavioral differences between Input and TextArea, such as composition/IME handling, event triggering timings, and interaction details. The goal was to abstract only 'pure logic suitable for sharing' to avoid introducing behavioral discrepancies through over-abstraction.
  • No External API Changes or Breaking Changes: This refactoring is an internal improvement. It introduces no new external APIs, does not alter existing usage patterns, and maintains current component semantics. The changes are designed to be low-risk for consumers, laying a stable foundation for future enhancements.
  • New Dependencies and Test Coverage: The @rc-component/resize-observer package was added to support the ResizableTextArea functionality. Comprehensive test coverage has been added and verified for the new TextArea component, including allowClear, showCount, count configurations, focus management, and auto-sizing behavior.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@socket-security
Copy link

socket-security bot commented Mar 18, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​rc-component/​resize-observer@​1.1.1981008886100

View full report

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

本次 PR 是一次重要的重构,将 textarea 的功能整合进了 rc-input,并对共享逻辑进行了出色的抽象和提取。通过引入 useMergedValueuseCountDisplayuseCountExceed 等自定义 Hooks,代码的复用性、可读性和可维护性都得到了显著提升。同时,详尽的测试用例和文档更新也保证了这次重构的质量和稳定性。我主要在文档细节、测试代码健壮性和一些潜在的健壮性方面提出了一些改进建议。

Comment on lines +92 to +93
| allowClear | boolean \| { clearIcon } | false | If allow to remove textarea content with clear icon |
| showCount | boolean \| { formatter } | false | Whether show text count |
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

TextArea API 文档中 allowClearshowCount 的类型定义可以更详细一些,与 Input 组件的文档保持一致,为开发者提供更清晰的信息。

Suggested change
| allowClear | boolean \| { clearIcon } | false | If allow to remove textarea content with clear icon |
| showCount | boolean \| { formatter } | false | Whether show text count |
| allowClear | boolean \| { clearIcon: ReactNode } | false | If allow to remove textarea content with clear icon |
| showCount | boolean \| { formatter: ({ value: string, count: number, maxLength?: number }) => ReactNode } | false | Whether show text count |

}

const computedStyleCache: Record<string, NodeType> = {};
let hiddenTextarea: HTMLTextAreaElement;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这里的 hiddenTextarea 是一个模块级别的变量,在第一次调用 calculateAutoSizeStyle 时被创建并添加到 document.body 中,但之后从未被移除。这在单页应用中可能会导致微小的内存泄漏,并且在测试环境中可能会对其他测试产生副作用。虽然这是为了性能优化(避免重复创建元素),但建议考虑一种在不需要时进行清理的机制,例如在模块卸载时移除该元素。

@@ -0,0 +1,487 @@
// @ts-nocheck
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

文件顶部的 // @ts-nocheck 注释会禁用整个文件的 TypeScript 类型检查,这可能会隐藏潜在的类型错误。建议移除此注释,并修复可能出现的任何类型问题,以提高测试代码的健壮性。在 tests/TextArea.focus.test.tsxtests/utils.ts 中也存在同样的问题。

@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 99.18367% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.35%. Comparing base (c1e9676) to head (7a885db).

Files with missing lines Patch % Lines
src/calculateNodeHeight.tsx 96.82% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #163      +/-   ##
==========================================
+ Coverage   97.57%   98.35%   +0.78%     
==========================================
  Files           4       12       +8     
  Lines         206      426     +220     
  Branches       80      137      +57     
==========================================
+ Hits          201      419     +218     
- Misses          5        7       +2     

☔ 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (10)
src/hooks/useMergedValue.ts (1)

5-17: 实现正确,类型定义可以更精确

Hook 的逻辑正确:

  • 正确处理了 undefinednull 的情况,转换为空字符串
  • formatValue 始终返回 string,满足下游 useCountDisplay 的类型要求

小建议:MergedValue 类型未包含 null,但代码检查了它。虽然这是防御性编程(useControlledState 可能在某些边缘情况返回 null),可以考虑在类型中显式包含:

♻️ 可选的类型改进
-type MergedValue = string | number | readonly string[] | bigint | undefined;
+type MergedValue = string | number | readonly string[] | bigint | undefined | null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useMergedValue.ts` around lines 5 - 17, The hook handles null at
runtime but the generic constraint ValueType extends MergedValue doesn't include
null, so update the types to reflect that defensive check: modify the generic or
the MergedValue alias so it explicitly allows null (e.g. ValueType extends
MergedValue | null or add null to MergedValue), and ensure useControlledState
and downstream consumers like useCountDisplay remain compatible with the widened
type; update the type signature in useMergedValue to reference the adjusted
MergedValue so formatValue logic and typings align.
tests/utils.ts (2)

25-31: wait() 循环次数的选择

100 次迭代是经验值还是有特定原因?如果是为了确保所有微任务和定时器都被处理,建议添加注释说明。过多的迭代可能影响测试性能,过少则可能导致间歇性失败。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils.ts` around lines 25 - 31, The wait() helper currently loops 100
times with no explanation; either document why 100 iterations are required or
make it configurable: update the wait function to accept an optional iterations
parameter (default 100) and add a concise comment above wait() explaining that
the loop ensures all microtasks and timers flush (and why 100 was chosen), or
adjust the default to a smaller, justified value; reference the wait() function
and the loop that calls act()/jest.runAllTimers()/Promise.resolve() so reviewers
can find and update the exact implementation.

3-6: 重复导入同一函数

onEsResizeonLibResize 都从同一路径导入 _rs,它们是完全相同的引用。如果这是为了兼容 ES/CommonJS 模块格式,应该从不同的路径导入:

♻️ 如果确实需要两种格式
 import {
   _rs as onEsResize,
-  _rs as onLibResize,
-} from '@rc-component/resize-observer/lib/utils/observerUtil';
+} from '@rc-component/resize-observer/es/utils/observerUtil';
+import {
+  _rs as onLibResize,
+} from '@rc-component/resize-observer/lib/utils/observerUtil';

如果不需要区分,可以简化:

♻️ 简化导入
 import {
-  _rs as onEsResize,
-  _rs as onLibResize,
+  _rs as onResize,
 } from '@rc-component/resize-observer/lib/utils/observerUtil';

 // ... 
-  onLibResize([{ target }]);
-  onEsResize([{ target }]);
+  onResize([{ target }]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils.ts` around lines 3 - 6, The file imports the same symbol twice
(_rs) under two names (onEsResize, onLibResize) causing redundant references;
either import the CommonJS and ESM variants from their respective module paths
(use '@rc-component/resize-observer/lib/utils/observerUtil' for the lib/CommonJS
one and the ESM entry for the ES variant) so onLibResize and onEsResize point to
different actual exports, or simplify by removing one alias and use a single
import (keep just onEsResize or onLibResize) referencing _rs; update the import
line(s) that mention _rs, onEsResize, and onLibResize in tests/utils.ts
accordingly.
tests/setupFilesAfterEnv.js (1)

3-4: RAF shim 的类型签名不匹配

window.requestAnimationFrame 的签名是 (callback: FrameRequestCallback) => number,而 window.setTimeout 的签名是 (handler: TimerHandler, timeout?: number) => number。虽然在 JavaScript 运行时能正常工作,但如果启用严格 TypeScript 检查,可能会报类型错误。

建议使用类型断言来明确意图:

♻️ 建议的修改
-window.requestAnimationFrame = window.setTimeout;
-window.cancelAnimationFrame = window.clearTimeout;
+window.requestAnimationFrame = window.setTimeout as unknown as typeof window.requestAnimationFrame;
+window.cancelAnimationFrame = window.clearTimeout as unknown as typeof window.cancelAnimationFrame;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/setupFilesAfterEnv.js` around lines 3 - 4, The RAF shim assigns
window.requestAnimationFrame and window.cancelAnimationFrame to
setTimeout/clearTimeout causing TypeScript signature mismatch; update the
assignments in tests/setupFilesAfterEnv.js to use explicit type assertions so
signatures match (e.g., cast window.setTimeout to the FrameRequestCallback =>
number type for requestAnimationFrame and cast window.clearTimeout to the
(handle: number) => void type for cancelAnimationFrame) so the runtime shim
remains the same but TypeScript no longer reports type errors for
requestAnimationFrame and cancelAnimationFrame.
src/calculateNodeHeight.tsx (1)

52-59: 缓存键可能为 undefined 导致意外共享

当元素没有 iddata-reactidname 属性时,nodeRef 将为 undefined。虽然第 87-88 行的条件判断 useCache && nodeRef 防止了 undefined 键被存储,但 as string 类型断言掩盖了这个问题。

建议明确处理这种情况:

♻️ 更安全的实现
 export function calculateNodeStyling(node: HTMLElement, useCache = false) {
-  const nodeRef = (node.getAttribute('id') ||
+  const nodeRef: string | null = node.getAttribute('id') ||
     node.getAttribute('data-reactid') ||
-    node.getAttribute('name')) as string;
+    node.getAttribute('name');

-  if (useCache && computedStyleCache[nodeRef]) {
+  if (useCache && nodeRef && computedStyleCache[nodeRef]) {
     return computedStyleCache[nodeRef];
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/calculateNodeHeight.tsx` around lines 52 - 59, In calculateNodeStyling,
nodeRef is currently asserted as a string which can mask a real undefined when
the element lacks id/data-reactid/name; stop using the "as string" assertion,
treat nodeRef as possibly undefined, and change cache accesses to guarded checks
(e.g., if (useCache && nodeRef && computedStyleCache[nodeRef]) return ...), and
likewise only write into computedStyleCache[nodeRef] when nodeRef is present;
update all references in calculateNodeStyling and any caching logic to avoid
using undefined as a cache key.
assets/index.less (1)

38-49: 样式选择器命名不一致

第 38 行硬编码了 .rc-textarea-affix-wrapper,而第 51 行使用了 LESS 变量插值 .@{textarea-prefix-cls}-out-of-range。建议保持一致性:

♻️ 建议使用变量统一管理
-@textarea-prefix-cls: rc-textarea;

-.rc-textarea-affix-wrapper {
+@textarea-prefix-cls: rc-textarea;
+
+.@{textarea-prefix-cls}-affix-wrapper {
   display: inline-block;
   box-sizing: border-box;

   textarea {

此外,根据 src/BaseInput.tsx 的实现,affixWrapperPrefixCls 是动态计算的(${prefixCls}-affix-wrapper)。如果用户传入自定义 prefixCls,硬编码的选择器将不会生效。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/index.less` around lines 38 - 49, The stylesheet hardcodes the
selector .rc-textarea-affix-wrapper which can break when a custom prefixCls is
used; replace the hardcoded selector with the interpolated variable used
elsewhere (e.g. .@{textarea-prefix-cls}-affix-wrapper) so it matches the
existing .@{textarea-prefix-cls}-out-of-range usage and the dynamic
affixWrapperPrefixCls (`${prefixCls}-affix-wrapper`) computed in
src/BaseInput.tsx; update the nested textarea block accordingly to use the
variable-based selector so styles apply for custom prefixCls values.
tests/TextArea.focus.test.tsx (1)

1-1: 不要用 @ts-nocheck 关闭整文件类型保护。

Line 1 会把这组新增测试里对 TextAreaRef、DOM 查询和可空值的类型回归一起静默掉。更稳妥的是把可空值显式收窄后移除这行,并在去掉后跑一次仓库现有的 typecheck。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/TextArea.focus.test.tsx` at line 1, 删除文件顶部的 "@ts-nocheck"
并修复因其被忽略的类型错误:在测试中显式收窄可空类型(例如对 TextAreaRef 的引用、DOM 查询结果和任何可能为 null/undefined
的变量),使用适当的 type guards 或断言(例如 if (!node) throw 或 expect(node).not.toBeNull()
再访问属性,或使用 non-null assertion Только 在确定安全时),更新涉及 TextAreaRef 和所有 DOM 查询 的变量以满足
TypeScript(例如 getByRole/getByTestId 返回可能为 null 的情况),然后移除注释后运行整个仓库的 typecheck
确认没有回归。
docs/examples/textarea-show-count.tsx (1)

40-54: Count.exceedFormatter 这个示例当前看不出效果。

这里的初始值没有超过 max: 5,页面上不会进入“超限裁剪”的分支,读者很难理解 formatter 的作用。建议换成一个实际超限的默认值。

📝 建议调整示例
-      <TextArea
-        defaultValue="🔥"
+      <TextArea
+        defaultValue="🔥🔥🔥"
         count={{
           show: true,
           max: 5,
           exceedFormatter: (val, { max }) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/examples/textarea-show-count.tsx` around lines 40 - 54, The example for
TextArea.count.exceedFormatter doesn't demonstrate its behavior because
defaultValue is not over the max; update the TextArea props so defaultValue
exceeds count.max (e.g., set defaultValue to a string longer than 5
characters/emojis) so the exceedFormatter branch is executed and readers can see
the trimming behavior for the exceedFormatter function on the TextArea
component.
tests/TextArea.test.tsx (1)

160-179: 存在重复测试场景,建议合并为参数化用例。

这两段都在验证 onPressEnteronKeyDown 交互,语义重复且用例名也重复,后续维护成本较高。

Also applies to: 297-315

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/TextArea.test.tsx` around lines 160 - 179, The two tests duplicate
verifying TextArea's onKeyDown and onPressEnter behavior; replace them with a
single parameterized test (e.g., using test.each or a small loop) that iterates
over key events (e.g., keyCode 65 -> expect onKeyDown called only; keyCode 13 ->
expect onKeyDown and onPressEnter called) to cover both scenarios in one case;
update the test name to reflect parameterization, reuse the existing
fakeHandleKeyDown and fakeHandlePressEnter mocks and the TextArea render, and
remove the redundant block around lines 297-315 so only the new parameterized
test remains.
src/TextArea.tsx (1)

60-60: Line 60 的非空断言模式可以改进,但当前崩溃风险已被消除。

Line 60 仍保留了 ?.textArea! 的非空断言模式,但实际上调用点已通过防御性编程妥善处理:

  • Line 75: getTextArea()?.focus() 使用可选链
  • Line 82: getTextArea()?.blur() 使用可选链
  • Line 146: 显式空值检查 if (textarea) { ... }
  • Line 169: getTextArea()?.style.height 使用可选链

现有代码不存在运行时崩溃风险。建议改进 Line 60 的实现为最佳实践:

-    const getTextArea = () => resizableTextAreaRef.current?.textArea!;
+    const getTextArea = () => resizableTextAreaRef.current?.textArea ?? null;

这样可消除非空断言,代码意图更清晰。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/TextArea.tsx` at line 60, The getTextArea function currently uses a
non-null assertion (resizableTextAreaRef.current?.textArea!)—remove the non-null
assertion and return the possibly-undefined textArea directly
(resizableTextAreaRef.current?.textArea) so the function's signature reflects
that it may be undefined; callers (getTextArea()?.focus(),
getTextArea()?.blur(), getTextArea()?.style.height and the explicit if
(textarea) checks) already defensively handle undefined, so no further call-site
changes are needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 75-97: The README TextArea API table is missing common
passthrough/native textarea props (maxLength, value, defaultValue, onChange)
which the examples/tests use; update the TextArea docs (referencing
Input.TextArea and TextArea) to include at least these properties in the table
and/or add a note stating that all other native textarea attributes are
forwarded to the underlying element (e.g., "Other native textarea props such as
maxLength, value, defaultValue, onChange are supported and passed through").
Ensure the names match the component exports (TextArea / Input.TextArea) so
readers can find them.

In `@src/calculateNodeHeight.tsx`:
- Around line 100-108: Replace the nonstandard attribute name used when creating
the hidden textarea: change the setAttribute call that currently uses
'tab-index' to the standard 'tabindex' (or set the element's tabIndex property)
on the hiddenTextarea variable so the element uses the correct HTML global
attribute; keep the existing aria-hidden and name assignments and append to
document.body as before.

In `@src/hooks/useCountDisplay.ts`:
- Around line 23-34: The code treats mergedMax as falsy which misinterprets an
explicit 0 max as "unset"; update checks to test for null/undefined instead of
falsiness: compute mergedMax as before, then set isOutOfRange to (mergedMax !==
undefined && mergedMax !== null) && valueLength > Number(mergedMax), and set
hasMaxLength to mergedMax !== undefined && mergedMax !== null so the formatter
and fallback string (`${valueLength}${hasMaxLength ? \` / ${mergedMax}\` : ''}`)
will correctly show " / 0" and mark out-of-range when max is 0; keep using
countConfig.showFormatter when present (pass Number(mergedMax) or mergedMax
consistently if needed).

In `@src/hooks/useCountExceed.ts`:
- Line 2: 当前文件的导入 `CountConfig` 在 src/hooks/useCountExceed.ts 中未被使用,触发
no-unused-vars 导致 CI 失败;请在代码中移除该未使用的导入行或将其用于相应的类型注解(例如为函数 useCountExceed
的参数或返回值添加 CountConfig 类型),确保唯一标识符 CountConfig 不再未使用。
- Around line 21-25: The effect that restores the selection uses
getTarget()?.setSelectionRange(...selection) but never clears the saved
selection, so subsequent renders reapply the old range; after calling
setSelectionRange inside the React.useEffect (the effect in useCountExceed.ts
that depends on getTarget and selection), clear the stored selection state (for
example call the selection setter like setSelection(null) or an equivalent reset
function) so the selection is applied once and not reapplied on later renders.

In `@src/Input.tsx`:
- Around line 69-77: The input's effective maxLength from count config isn't
being applied to the native <input> because you compute mergedMax via
useCountDisplay but continue to pass the original maxLength through otherProps;
update the Input component to override/replace the maxLength prop with mergedMax
(or undefined when no limit) before spreading otherProps to the input, and
ensure any references like mergedMax, maxLength, useCountDisplay,
useCountExceed, inputRef are used/kept in that block so the unused-vars warning
on mergedMax disappears; apply the same fix to the other occurrence around lines
188-216 where mergedMax must be injected into the native input props instead of
the raw maxLength.

In `@src/ResizableTextArea.tsx`:
- Around line 39-50: The autoSize recalculation only listens to the external
value prop and thus doesn't update for uncontrolled usage where
internalValue/mergedValue changes; update the effect that measures/resizes
(inside ResizableTextArea) to depend on mergedValue (or internalValue) instead
of just value so that typing triggers a re-measure, and/or call the same
resize/measure routine from onInternalChange after setMergedValue; reference
useControlledState, mergedValue/internalValue, setMergedValue, onInternalChange
and the resize/measure effect in ResizableTextArea to locate where to add
mergedValue to the effect dependencies or invoke the resize function.

In `@src/TextArea.tsx`:
- Around line 64-68: The textarea's native maxLength is still using the original
maxLength instead of the resolved mergedMax from useCountDisplay, breaking
intended priority; update the TextArea component to pass mergedMax (not
maxLength) into the rendered <textarea> props so browser enforcement matches the
count logic (change the prop usage where the textarea is rendered—refer to
mergedMax and useCountDisplay in the TextArea component).

In `@tests/TextArea.allowClear.test.tsx`:
- Around line 15-17: Fix the typo in the visibility class used in the assertion:
change the selector '.rc-textarea-clear-icon-hidhen' to
'.rc-textarea-clear-icon-hidden' in the test (the expect call that uses
container.querySelector('.rc-textarea-clear-icon-hidhen')). This will ensure the
"icon should be visible after input" assertion actually checks the correct DOM
class.

In `@tests/TextArea.focus.test.tsx`:
- Around line 45-75: 当前两个用例分别调用 ref.current.focus() / ref.current.blur() 但对
onFocus / onBlur 的断言仍然依赖随后手动 fireEvent,这没有覆盖“imperative 调用触发回调”的路径。修改
tests/TextArea.focus.test.tsx 中相关用例(涉及 TextArea, TextAreaRef,
ref.current.focus(), ref.current.blur(), onFocus, onBlur 和
container.querySelector('textarea'))使断言直接验证在调用 ref.current.focus() /
ref.current.blur() 后回调被触发(并移除或合并随后单独的 fireEvent 调用),确保测试集中在同一路径上;如果需要保留 DOM
事件用例,把 imperative 调用的断言移到一个独立的测试中以避免相互掩盖。

In `@tests/TextArea.test.tsx`:
- Around line 103-158: The test creates an errorSpy via jest.spyOn(console,
'error').mockImplementation but never restores it, causing global test
pollution; update the test to ensure errorSpy.mockRestore() is called after the
assertions (or in a finally/afterEach cleanup) so console.error is returned to
its original implementation—locate the spy instantiation (errorSpy) near the top
of the test and add a mockRestore call after the final expect (or wrap the test
body to always restore errorSpy) to guarantee no leakage to other tests.

---

Nitpick comments:
In `@assets/index.less`:
- Around line 38-49: The stylesheet hardcodes the selector
.rc-textarea-affix-wrapper which can break when a custom prefixCls is used;
replace the hardcoded selector with the interpolated variable used elsewhere
(e.g. .@{textarea-prefix-cls}-affix-wrapper) so it matches the existing
.@{textarea-prefix-cls}-out-of-range usage and the dynamic affixWrapperPrefixCls
(`${prefixCls}-affix-wrapper`) computed in src/BaseInput.tsx; update the nested
textarea block accordingly to use the variable-based selector so styles apply
for custom prefixCls values.

In `@docs/examples/textarea-show-count.tsx`:
- Around line 40-54: The example for TextArea.count.exceedFormatter doesn't
demonstrate its behavior because defaultValue is not over the max; update the
TextArea props so defaultValue exceeds count.max (e.g., set defaultValue to a
string longer than 5 characters/emojis) so the exceedFormatter branch is
executed and readers can see the trimming behavior for the exceedFormatter
function on the TextArea component.

In `@src/calculateNodeHeight.tsx`:
- Around line 52-59: In calculateNodeStyling, nodeRef is currently asserted as a
string which can mask a real undefined when the element lacks
id/data-reactid/name; stop using the "as string" assertion, treat nodeRef as
possibly undefined, and change cache accesses to guarded checks (e.g., if
(useCache && nodeRef && computedStyleCache[nodeRef]) return ...), and likewise
only write into computedStyleCache[nodeRef] when nodeRef is present; update all
references in calculateNodeStyling and any caching logic to avoid using
undefined as a cache key.

In `@src/hooks/useMergedValue.ts`:
- Around line 5-17: The hook handles null at runtime but the generic constraint
ValueType extends MergedValue doesn't include null, so update the types to
reflect that defensive check: modify the generic or the MergedValue alias so it
explicitly allows null (e.g. ValueType extends MergedValue | null or add null to
MergedValue), and ensure useControlledState and downstream consumers like
useCountDisplay remain compatible with the widened type; update the type
signature in useMergedValue to reference the adjusted MergedValue so formatValue
logic and typings align.

In `@src/TextArea.tsx`:
- Line 60: The getTextArea function currently uses a non-null assertion
(resizableTextAreaRef.current?.textArea!)—remove the non-null assertion and
return the possibly-undefined textArea directly
(resizableTextAreaRef.current?.textArea) so the function's signature reflects
that it may be undefined; callers (getTextArea()?.focus(),
getTextArea()?.blur(), getTextArea()?.style.height and the explicit if
(textarea) checks) already defensively handle undefined, so no further call-site
changes are needed.

In `@tests/setupFilesAfterEnv.js`:
- Around line 3-4: The RAF shim assigns window.requestAnimationFrame and
window.cancelAnimationFrame to setTimeout/clearTimeout causing TypeScript
signature mismatch; update the assignments in tests/setupFilesAfterEnv.js to use
explicit type assertions so signatures match (e.g., cast window.setTimeout to
the FrameRequestCallback => number type for requestAnimationFrame and cast
window.clearTimeout to the (handle: number) => void type for
cancelAnimationFrame) so the runtime shim remains the same but TypeScript no
longer reports type errors for requestAnimationFrame and cancelAnimationFrame.

In `@tests/TextArea.focus.test.tsx`:
- Line 1: 删除文件顶部的 "@ts-nocheck" 并修复因其被忽略的类型错误:在测试中显式收窄可空类型(例如对 TextAreaRef
的引用、DOM 查询结果和任何可能为 null/undefined 的变量),使用适当的 type guards 或断言(例如 if (!node) throw
或 expect(node).not.toBeNull() 再访问属性,或使用 non-null assertion Только 在确定安全时),更新涉及
TextAreaRef 和所有 DOM 查询 的变量以满足 TypeScript(例如 getByRole/getByTestId 返回可能为 null
的情况),然后移除注释后运行整个仓库的 typecheck 确认没有回归。

In `@tests/TextArea.test.tsx`:
- Around line 160-179: The two tests duplicate verifying TextArea's onKeyDown
and onPressEnter behavior; replace them with a single parameterized test (e.g.,
using test.each or a small loop) that iterates over key events (e.g., keyCode 65
-> expect onKeyDown called only; keyCode 13 -> expect onKeyDown and onPressEnter
called) to cover both scenarios in one case; update the test name to reflect
parameterization, reuse the existing fakeHandleKeyDown and fakeHandlePressEnter
mocks and the TextArea render, and remove the redundant block around lines
297-315 so only the new parameterized test remains.

In `@tests/utils.ts`:
- Around line 25-31: The wait() helper currently loops 100 times with no
explanation; either document why 100 iterations are required or make it
configurable: update the wait function to accept an optional iterations
parameter (default 100) and add a concise comment above wait() explaining that
the loop ensures all microtasks and timers flush (and why 100 was chosen), or
adjust the default to a smaller, justified value; reference the wait() function
and the loop that calls act()/jest.runAllTimers()/Promise.resolve() so reviewers
can find and update the exact implementation.
- Around line 3-6: The file imports the same symbol twice (_rs) under two names
(onEsResize, onLibResize) causing redundant references; either import the
CommonJS and ESM variants from their respective module paths (use
'@rc-component/resize-observer/lib/utils/observerUtil' for the lib/CommonJS one
and the ESM entry for the ES variant) so onLibResize and onEsResize point to
different actual exports, or simplify by removing one alias and use a single
import (keep just onEsResize or onLibResize) referencing _rs; update the import
line(s) that mention _rs, onEsResize, and onLibResize in tests/utils.ts
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c84889c-9f92-4eda-827a-f1956fba416e

📥 Commits

Reviewing files that changed from the base of the PR and between c1e9676 and 7a95903.

⛔ Files ignored due to path filters (2)
  • tests/__snapshots__/TextArea.allowClear.test.tsx.snap is excluded by !**/*.snap
  • tests/__snapshots__/TextArea.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (27)
  • README.md
  • assets/index.less
  • docs/demo/textarea.md
  • docs/examples/textarea-allow-clear.tsx
  • docs/examples/textarea-auto-size.tsx
  • docs/examples/textarea-basic.tsx
  • docs/examples/textarea-show-count.tsx
  • package.json
  • src/Input.tsx
  • src/ResizableTextArea.tsx
  • src/TextArea.tsx
  • src/calculateNodeHeight.tsx
  • src/hooks/useCount.ts
  • src/hooks/useCountDisplay.ts
  • src/hooks/useCountExceed.ts
  • src/hooks/useMergedValue.ts
  • src/index.tsx
  • src/interface.ts
  • tests/ResizableTextArea.test.tsx
  • tests/TextArea.allowClear.test.tsx
  • tests/TextArea.count.test.tsx
  • tests/TextArea.focus.test.tsx
  • tests/TextArea.showCount.test.tsx
  • tests/TextArea.test.tsx
  • tests/count.test.tsx
  • tests/setupFilesAfterEnv.js
  • tests/utils.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
src/hooks/useCountExceed.ts (1)

20-24: ⚠️ Potential issue | 🟠 Major

恢复选区后需要立即清空缓存。

这里在 setSelectionRange 后没有重置 selection。结合 src/Input.tsx 的 Line 50-77,getTarget 目前是每次渲染都会新建的回调;只要后续发生重渲染,这个 effect 就会再次执行,把光标反复拉回旧位置。

建议修改
   React.useEffect(() => {
-    if (selection) {
-      getTarget()?.setSelectionRange(...selection);
-    }
+    if (!selection) {
+      return;
+    }
+
+    getTarget()?.setSelectionRange(...selection);
+    setSelection(null);
   }, [getTarget, selection]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useCountExceed.ts` around lines 20 - 24, The effect in
useCountExceed.ts calls getTarget()?.setSelectionRange(...selection) but never
clears the saved selection, so on subsequent renders the effect re-applies the
old range; after restoring the range you must reset the stored selection to
avoid re-running — update the effect to call the setter that clears selection
(e.g., setSelection(null) or setSelection(undefined)) immediately after
getTarget()?.setSelectionRange(...selection), referencing the useCountExceed
hook's selection and its setter so the cached selection is cleared once applied.
src/TextArea.tsx (1)

64-68: ⚠️ Potential issue | 🟠 Major

不要绕过 count.max / maxLength 的合并结果。

当前版本只是把 mergedMax 的解构去掉了,但 Line 215 仍然把原始 maxLength 传给 ResizableTextArea。当两者同时存在且取值不一致时,浏览器原生限制会先于计数逻辑生效,导致截断优先级和 dataCount 展示不一致。

建议修改
-    const { isOutOfRange, dataCount } = useCountDisplay({
+    const { mergedMax, isOutOfRange, dataCount } = useCountDisplay({
       countConfig,
       value: formatValue,
       maxLength,
     });
@@
-          maxLength={maxLength}
+          maxLength={mergedMax}

Also applies to: 212-215

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/TextArea.tsx` around lines 64 - 68, The code currently ignores the merged
max length from useCountDisplay and still forwards the original maxLength to
ResizableTextArea, causing native browser truncation to disagree with dataCount;
update the destructuring from useCountDisplay to include mergedMax (e.g., const
{ isOutOfRange, dataCount, mergedMax } = useCountDisplay({...})) and pass
mergedMax into the ResizableTextArea prop instead of maxLength so the textarea's
native limit matches the merged/count logic (ensure you keep passing
countConfig, value/formatValue as before).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/hooks/useCountExceed.ts`:
- Around line 20-24: The effect in useCountExceed.ts calls
getTarget()?.setSelectionRange(...selection) but never clears the saved
selection, so on subsequent renders the effect re-applies the old range; after
restoring the range you must reset the stored selection to avoid re-running —
update the effect to call the setter that clears selection (e.g.,
setSelection(null) or setSelection(undefined)) immediately after
getTarget()?.setSelectionRange(...selection), referencing the useCountExceed
hook's selection and its setter so the cached selection is cleared once applied.

In `@src/TextArea.tsx`:
- Around line 64-68: The code currently ignores the merged max length from
useCountDisplay and still forwards the original maxLength to ResizableTextArea,
causing native browser truncation to disagree with dataCount; update the
destructuring from useCountDisplay to include mergedMax (e.g., const {
isOutOfRange, dataCount, mergedMax } = useCountDisplay({...})) and pass
mergedMax into the ResizableTextArea prop instead of maxLength so the textarea's
native limit matches the merged/count logic (ensure you keep passing
countConfig, value/formatValue as before).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac370b85-a16c-48ea-8abe-30add1591b37

📥 Commits

Reviewing files that changed from the base of the PR and between 7a95903 and 8e6cc71.

📒 Files selected for processing (3)
  • src/Input.tsx
  • src/TextArea.tsx
  • src/hooks/useCountExceed.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Input.tsx

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/hooks/useCountExceed.ts (1)

47-50: 可选优化:缓存目标元素,减少重复调用并提升可读性。

Line 47-50 可以先取一次 target 再读取 selectionStart/selectionEnd,让意图更直接。

建议修改
         if (currentValue !== nextValue) {
+          const target = getTargetRef.current();
           setSelection([
-            getTargetRef.current()?.selectionStart || 0,
-            getTargetRef.current()?.selectionEnd || 0,
+            target?.selectionStart || 0,
+            target?.selectionEnd || 0,
           ]);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useCountExceed.ts` around lines 47 - 50, Cache the target element
returned by getTargetRef.current() into a local variable before reading
selectionStart/selectionEnd to avoid repeated calls and improve readability;
e.g., assign const target = getTargetRef.current(), then call
setSelection([target?.selectionStart || 0, target?.selectionEnd || 0]) so the
logic still defaults to 0 when target is null while only accessing
getTargetRef.current() once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/hooks/useCountExceed.ts`:
- Around line 47-50: Cache the target element returned by getTargetRef.current()
into a local variable before reading selectionStart/selectionEnd to avoid
repeated calls and improve readability; e.g., assign const target =
getTargetRef.current(), then call setSelection([target?.selectionStart || 0,
target?.selectionEnd || 0]) so the logic still defaults to 0 when target is null
while only accessing getTargetRef.current() once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 087742c5-b1ec-40b8-a4bc-18aaa49192b1

📥 Commits

Reviewing files that changed from the base of the PR and between 8e6cc71 and 7a885db.

📒 Files selected for processing (3)
  • src/hooks/useCountExceed.ts
  • tests/TextArea.allowClear.test.tsx
  • tests/count.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/count.test.tsx
  • tests/TextArea.allowClear.test.tsx

@zombieJ
Copy link
Member

zombieJ commented Mar 18, 2026

@gemini-code-assist,重新 CR 一下。这个 PR 是把 https://github.com/react-component/textarea 的逻辑迁移到这个项目中。所以最最要的是比较迁移时是否有 break change,而不是语法问题。

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully refactors the rc-input component to integrate textarea capabilities and deduplicate shared logic. The introduction of Input.TextArea and a named TextArea export provides a clean and consistent API. The refactoring into specialized hooks (useCountDisplay, useCountExceed, useMergedValue) significantly improves maintainability and separation of concerns. Comprehensive test coverage for the new TextArea component and its features ensures stability. Overall, this is a well-executed refactoring that enhances the component's architecture without altering existing external behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants