Skip to content

feat(x-markdown): support animation inside custom components - #2022

Open
sq844 wants to merge 1 commit into
ant-design:mainfrom
sq844:feat/x-markdown-animate-inside-components
Open

feat(x-markdown): support animation inside custom components#2022
sq844 wants to merge 1 commit into
ant-design:mainfrom
sq844:feat/x-markdown-animate-inside-components

Conversation

@sq844

@sq844 sq844 commented Aug 11, 2026

Copy link
Copy Markdown

Summary

Add animateInsideComponents option to StreamingOption interface. When enabled, text nodes inside custom components (contentRender) also get the AnimationText typewriter effect.

Changes

  • interface.ts: Add animateInsideComponents?: boolean to StreamingOption
  • Renderer.ts: Modify shouldReplaceText logic to allow animation inside custom components when the flag is set
  • Renderer.test.ts: Add 6 unit tests covering on/off/default/nested/disable scenarios

Behavior

animateInsideComponents Text outside custom component Text inside custom component
false (default) ✅ Animated ❌ No animation
true ✅ Animated ✅ Animated

Verification

  • Manual logic tests pass (7/7)
  • Demo page at packages/x-markdown/demo.html shows side-by-side comparison

Closes #1950

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新功能

    • 新增配置项,可控制自定义组件内部文字是否启用逐字淡入动画。
    • 提供动画效果演示页面,支持播放、重置及双栏对比查看。
  • 测试

    • 增加多种组件嵌套、动画开关及默认配置场景的验证,确保文字动画行为符合预期。

Add animateInsideComponents option to StreamingOption interface.
When enabled, text nodes inside custom components (contentRender)
also get the AnimationText typewriter effect.

- Add animateInsideComponents boolean to StreamingOption
- Modify shouldReplaceText logic in Renderer.ts
- Add 6 unit tests covering on/off/default/nested scenarios
- Add manual verification script and demo page

Closes ant-design#1950

Co-Authored-By: Claude <noreply@anthropic.com>
@dosubot dosubot Bot added enhancement New feature or request javascript Pull requests that update Javascript code labels Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4affa99b-e601-4b74-b1b9-a8a4c6be7976

📥 Commits

Reviewing files that changed from the base of the PR and between 25aad7b and da47f24.

📒 Files selected for processing (5)
  • packages/x-markdown/demo.html
  • packages/x-markdown/manual-test.ts
  • packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts
  • packages/x-markdown/src/XMarkdown/core/Renderer.ts
  • packages/x-markdown/src/XMarkdown/interface.ts
 ____________________________________________________
< Veni, Vidi, Verificavi. I came, I saw, I verified. >
 ----------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/x-markdown/demo.html`:
- Line 135: Update animationTimer and the animation flow around resetAll to
track every setTimeout return value rather than a single unused timer reference.
Add each timer ID to the collection, clear all stored timers and empty the
collection in resetAll, and ensure replaying or resetting prevents callbacks
from prior animations from appending output.
- Around line 97-104: 更新 renderSegment 及其调用方以接收 charDelay,并在 animate
字符循环中根据字符索引设置递增的 animationDelay,而不是固定为 0s;确保该延迟仅用于字符淡入,同时保留背景闪烁的现有延迟行为,使字符按顺序淡入。

In `@packages/x-markdown/manual-test.ts`:
- Around line 22-29: Update the manual test runner around test so it tracks the
number of failed cases when exceptions are caught, sets a non-zero process exit
status if any failures occurred, and suppresses the “All tests passed!” and
“logic is CORRECT” success summaries whenever failures exist.

In `@packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts`:
- Around line 1642-1648: Update the AnimationText lookup expectations in
packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts at lines 1642-1648
so the Before and After values include their preserved surrounding spaces:
“Before ” and “ After”. Apply the same expectation change at lines 1707-1713 for
Level1 and End: “Level1 ” and “ End”; leave the Inside expectation unchanged.

In `@packages/x-markdown/src/XMarkdown/core/Renderer.ts`:
- Around line 185-188: Update the text replacement logic in Renderer around
parentTagName and shouldReplaceText to detect whether any ancestor, not only the
direct parent, is a configured custom component. Propagate this context through
processChildren if needed, so nested elements remain non-animated when
animateInsideComponents is disabled while preserving animation when it is
enabled; add coverage for both cases with ordinary nested elements.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4affa99b-e601-4b74-b1b9-a8a4c6be7976

📥 Commits

Reviewing files that changed from the base of the PR and between 25aad7b and da47f24.

📒 Files selected for processing (5)
  • packages/x-markdown/demo.html
  • packages/x-markdown/manual-test.ts
  • packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts
  • packages/x-markdown/src/XMarkdown/core/Renderer.ts
  • packages/x-markdown/src/XMarkdown/interface.ts

Comment on lines +97 to +104
// Simulate AnimationText component: split into individual chars with fade-in
if (animate) {
for (let i = 0; i < text.length; i++) {
const chunk = document.createElement('span');
chunk.className = 'text-chunk';
chunk.textContent = text[i];
chunk.style.animationDelay = '0s';
wrapper.appendChild(chunk);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

让字符淡入使用 charDelay

每个字符都使用 animationDelay = '0s'。因此,同一文本片段中的字符会同时淡入。charDelay 只延迟背景闪烁,不会延迟淡入动画。

charDelay 传入 renderSegment,并为每个字符设置递增的 animationDelay。否则演示页面与“逐字淡入”的说明不一致。

Also applies to: 152-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x-markdown/demo.html` around lines 97 - 104, 更新 renderSegment
及其调用方以接收 charDelay,并在 animate 字符循环中根据字符索引设置递增的 animationDelay,而不是固定为
0s;确保该延迟仅用于字符淡入,同时保留背景闪烁的现有延迟行为,使字符按顺序淡入。

setTimeout(() => { el.style.background = origBg; }, 500);
}

let animationTimer = null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

取消上一轮播放的所有定时器。

animationTimer 从未保存任何 setTimeout 返回值。用户在动画完成前点击“重置”或再次点击“播放”时,上一轮回调仍会向输出区域追加文本。

保存全部定时器 ID。resetAll 时清除这些定时器并清空集合。

Also applies to: 149-170, 176-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x-markdown/demo.html` at line 135, Update animationTimer and the
animation flow around resetAll to track every setTimeout return value rather
than a single unused timer reference. Add each timer ID to the collection, clear
all stored timers and empty the collection in resetAll, and ensure replaying or
resetting prevents callbacks from prior animations from appending output.

Comment on lines +22 to +29
function test(name: string, fn: () => void) {
try {
fn();
console.log(` ✅ ${name}`);
} catch (e) {
console.log(` ❌ ${name}: ${(e as Error).message}`);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

失败时返回失败状态。

test 捕获错误后只输出日志。即使用例失败,脚本仍会输出 “All tests passed!” 和 “logic is CORRECT”,并以成功状态结束。

记录失败数量。存在失败时设置非零退出码,并且不要输出成功摘要。

Also applies to: 79-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x-markdown/manual-test.ts` around lines 22 - 29, Update the manual
test runner around test so it tracks the number of failed cases when exceptions
are caught, sets a non-zero process exit status if any failures occurred, and
suppresses the “All tests passed!” and “logic is CORRECT” success summaries
whenever failures exist.

Comment on lines +1642 to +1648
const beforeCall = animationCalls.find((call) => call[1]?.text === 'Before');
const afterCall = animationCalls.find((call) => call[1]?.text === 'After');
const insideCall = animationCalls.find((call) => call[1]?.text === 'Inside');

expect(beforeCall).toBeDefined();
expect(afterCall).toBeDefined();
expect(insideCall).toBeDefined();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

修正包含空格的 AnimationText 断言。

Renderer 将未裁剪的 domNode.data 传给 AnimationText。这两个 HTML 字符串中的外围文本包含前导或尾随空格。当前断言会找不到对应调用。

  • packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts#L1642-L1648: 将期望值改为 Before After
  • packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts#L1707-L1713: 将期望值改为 Level1 End
📍 Affects 1 file
  • packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts#L1642-L1648 (this comment)
  • packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts#L1707-L1713
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts` around lines
1642 - 1648, Update the AnimationText lookup expectations in
packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts at lines 1642-1648
so the Before and After values include their preserved surrounding spaces:
“Before ” and “ After”. Apply the same expectation change at lines 1707-1713 for
Level1 and End: “Level1 ” and “ End”; leave the Inside expectation unchanged.

Comment on lines 185 to +188
const parentTagName = (domNode.parent as Element)?.name;
const isParentCustomComponent = parentTagName && this.options.components?.[parentTagName];
const shouldReplaceText = enableAnimation && isValidTextNode && !isParentCustomComponent;
const shouldReplaceText =
enableAnimation && isValidTextNode && (!isParentCustomComponent || animateInsideComponents);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

检查自定义组件祖先,而不是只检查直接父节点。

当输入为 <custom-wrapper><strong>World</strong></custom-wrapper> 时,World 的直接父节点是 strong。此条件会将 isParentCustomComponent 设为 false。因此,即使 animateInsideComponentsfalse 或未配置,World 仍会被替换为 AnimationText

遍历祖先节点,或在 processChildren 中传递“位于自定义组件内部”的上下文。增加包含普通嵌套元素的启用和禁用测试。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x-markdown/src/XMarkdown/core/Renderer.ts` around lines 185 - 188,
Update the text replacement logic in Renderer around parentTagName and
shouldReplaceText to detect whether any ancestor, not only the direct parent, is
a configured custom component. Propagate this context through processChildren if
needed, so nested elements remain non-animated when animateInsideComponents is
disabled while preserving animation when it is enabled; add coverage for both
cases with ordinary nested elements.

@sq844

sq844 commented Aug 11, 2026

Copy link
Copy Markdown
Author

#1950

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

Labels

enhancement New feature or request javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Good First Issue] XMarkdown contentRender 自定义渲染支持打字机效果

1 participant