mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
* 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>
190 lines
4.8 KiB
Markdown
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`.
|