Files
claude-code/packages/@ant/ink/docs/06-scrolling.md
claude-code-best 2fb1c9dcd8 feat: 工具层及 mcp 大重构 (#252)
* feat: 第一版大重构

* fix: 修复类型问题

* chore: 更新版本到 1.3.2

* Add brave as alternative WebSearchTool

* fix: 修正顺序

* fix: 修复对穷鬼模式的 auto dream 和 session memory 越过

* feat: 穷鬼模式去除 session-summary

* feat: 创建 builtin-tools 包,搬运所有工具实现

将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/,
内部导入路径已更新为 src/ alias 模式。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/

- src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/
- 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock

- tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射
- 新增 packages/builtin-tools/src 至 include

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀

所有包名及 import 路径统一添加 @claude-code-best/ 前缀:
- builtin-tools → @claude-code-best/builtin-tools
- mcp-client → @claude-code-best/mcp-client
- agent-tools → @claude-code-best/agent-tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复 node 环境没有 bun 的问题

---------

Co-authored-by: Eric-Guo <eric.guocz@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 09:52:05 +08:00

190 lines
4.8 KiB
Markdown

# Chapter 6: Scrolling
## ScrollBox
A scrollable container with imperative scroll API, viewport culling, and sticky scroll support.
```tsx
import { ScrollBox } from '@anthropic/ink'
import type { ScrollBoxHandle } from '@anthropic/ink'
function MessageList({ messages }) {
const scrollRef = useRef<ScrollBoxHandle>(null)
// Auto-scroll to bottom on new messages
useEffect(() => {
scrollRef.current?.scrollToBottom()
}, [messages.length])
return (
<ScrollBox ref={scrollRef} stickyScroll flexDirection="column" height={20}>
{messages.map(msg => (
<Text key={msg.id}>{msg.text}</Text>
))}
</ScrollBox>
)
}
```
### Props
ScrollBox accepts all Box layout props except `textWrap`, `overflow`, `overflowX`, `overflowY` (these are managed internally):
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `ref` | `Ref<ScrollBoxHandle>` | - | Imperative handle |
| `stickyScroll` | `boolean` | `false` | Auto-follow new content |
| *(layout props)* | `Styles` | - | Width, height, padding, etc. |
### ScrollBoxHandle (Imperative API)
```ts
interface ScrollBoxHandle {
// Absolute positioning
scrollTo(y: number): void
scrollToElement(el: DOMElement, offset?: number): void
scrollToBottom(): void
// Relative positioning
scrollBy(dy: number): void
// Query state
getScrollTop(): number
getPendingDelta(): number
getScrollHeight(): number
getFreshScrollHeight(): number
getViewportHeight(): number
getViewportTop(): number
isSticky(): boolean
// Events
subscribe(listener: () => void): () => void
// Virtual scroll support
setClampBounds(min?: number, max?: number): void
}
```
### Method Details
#### `scrollTo(y)`
Jump to an absolute position. Breaks sticky scroll.
```tsx
scrollRef.current?.scrollTo(0) // Scroll to top
```
#### `scrollBy(dy)`
Scroll by a relative amount. Accumulates deltas for smooth scrolling.
```tsx
scrollRef.current?.scrollBy(3) // Scroll down 3 rows
scrollRef.current?.scrollBy(-5) // Scroll up 5 rows
```
#### `scrollToElement(el, offset?)`
Scroll so a specific DOM element is at the viewport top. More reliable than `scrollTo` because it reads the element's position at render time (avoids stale layout values).
```tsx
const elementRef = useRef<DOMElement>(null)
scrollRef.current?.scrollToElement(elementRef.current!, 2)
```
#### `scrollToBottom()`
Pin scroll to bottom. Enables sticky mode.
```tsx
scrollRef.current?.scrollToBottom()
```
#### `isSticky()`
Returns `true` when scroll is pinned to the bottom.
```tsx
if (scrollRef.current?.isSticky()) {
// User hasn't scrolled up
}
```
#### `subscribe(listener)`
Subscribe to imperative scroll changes. Returns unsubscribe function.
```tsx
useEffect(() => {
return scrollRef.current?.subscribe(() => {
console.log('Scroll position changed')
})
}, [])
```
### Sticky Scroll
When `stickyScroll` is enabled:
1. Scroll automatically follows new content at the bottom
2. User scroll (via `scrollBy`/`scrollTo`) breaks stickiness
3. `scrollToBottom()` re-enables stickiness
4. Content growth at the bottom is detected and followed automatically
```tsx
<ScrollBox stickyScroll height={20}>
{/* New items auto-scroll to bottom */}
{items.map(renderItem)}
</ScrollBox>
```
### Viewport Culling
ScrollBox only renders children that intersect the visible viewport. Children outside the viewport are still mounted in React but skipped during terminal rendering. This makes large lists performant.
### Virtual Scrolling
For very large lists, use `setClampBounds` in combination with a virtual scrolling hook:
```tsx
const scrollRef = useRef<ScrollBoxHandle>(null)
// After computing visible range
scrollRef.current?.setClampBounds(firstVisibleRow, lastVisibleRow)
```
This prevents burst `scrollTo` calls from showing blank space beyond mounted content.
### Scroll Events
ScrollBox bypasses React state for scroll operations. Instead:
1. `scrollTo`/`scrollBy` mutate `scrollTop` directly on the DOM node
2. The node is marked dirty
3. A microtask-deferred render fires to coalesce multiple scroll events
4. The Ink renderer reads `scrollTop` during layout
This avoids React reconciler overhead per wheel event.
### Integration with Mouse Wheel
In alt-screen mode, mouse wheel events are captured by the `App` component and forwarded to the focused ScrollBox:
```
Wheel event → App.handleMouseEvent → ScrollBox.scrollBy(delta)
```
### Layout Structure
ScrollBox creates a two-level DOM structure:
```
ink-box (overflow: scroll, constrained height)
└── Box (flexGrow: 1, flexShrink: 0, width: 100%)
├── Child 1
├── Child 2
└── ...
```
The outer `ink-box` is the viewport with constrained size. The inner `Box` grows to fit all content. The renderer computes `scrollHeight` from the inner box and translates content by `-scrollTop`.