Download packages/pi-tui/src/components/box.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 4.48 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/pi-tui/src/components/box.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/pi-tui/src/components/box.ts
-
curl -L -o box.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/pi-tui/src/components/box.ts
4.48 kB
| import { type Component, dispatchMouseEvent, type TuiMouseDispatchResult, type TuiMouseEvent } from "../tui.ts"; | |
| import { applyBackgroundToLine, visibleWidth } from "../utils.ts"; | |
| type RenderCache = { | |
| childLines: string[]; | |
| width: number; | |
| bgSample: string | undefined; | |
| lines: string[]; | |
| }; | |
| /** | |
| * Box component - a container that applies padding and background to all children | |
| */ | |
| export class Box implements Component { | |
| children: Component[] = []; | |
| private paddingX: number; | |
| private paddingY: number; | |
| private bgFn?: (text: string) => string; | |
| // Cache for rendered output | |
| private cache?: RenderCache; | |
| private mouseLayout?: { width: number; children: Array<{ component: Component; height: number }> }; | |
| constructor(paddingX = 1, paddingY = 1, bgFn?: (text: string) => string) { | |
| this.paddingX = paddingX; | |
| this.paddingY = paddingY; | |
| this.bgFn = bgFn; | |
| } | |
| addChild(component: Component): void { | |
| this.children.push(component); | |
| this.invalidateCache(); | |
| } | |
| removeChild(component: Component): void { | |
| const index = this.children.indexOf(component); | |
| if (index !== -1) { | |
| this.children.splice(index, 1); | |
| this.invalidateCache(); | |
| } | |
| } | |
| clear(): void { | |
| this.children = []; | |
| this.invalidateCache(); | |
| } | |
| setBgFn(bgFn?: (text: string) => string): void { | |
| this.bgFn = bgFn; | |
| // Don't invalidate here - we'll detect bgFn changes by sampling output | |
| } | |
| private invalidateCache(): void { | |
| this.cache = undefined; | |
| } | |
| private matchCache(width: number, childLines: string[], bgSample: string | undefined): boolean { | |
| const cache = this.cache; | |
| return ( | |
| !!cache && | |
| cache.width === width && | |
| cache.bgSample === bgSample && | |
| cache.childLines.length === childLines.length && | |
| cache.childLines.every((line, i) => line === childLines[i]) | |
| ); | |
| } | |
| invalidate(): void { | |
| this.invalidateCache(); | |
| for (const child of this.children) { | |
| child.invalidate?.(); | |
| } | |
| } | |
| handleMouse(event: TuiMouseEvent): TuiMouseDispatchResult | undefined { | |
| const contentWidth = Math.max(1, event.width - this.paddingX * 2); | |
| const contentY = event.y - this.paddingY; | |
| const contentX = event.x - this.paddingX; | |
| if (contentY < 0 || contentX < 0 || contentX >= contentWidth) return undefined; | |
| const mouseChildren = | |
| this.mouseLayout?.width === contentWidth | |
| ? this.mouseLayout.children | |
| : this.children.map((component) => ({ component, height: component.render(contentWidth).length })); | |
| let childY = 0; | |
| for (const { component: child, height: childHeight } of mouseChildren) { | |
| if (contentY >= childY && contentY < childY + childHeight) { | |
| return dispatchMouseEvent(child, { | |
| ...event, | |
| x: contentX, | |
| y: contentY - childY, | |
| width: contentWidth, | |
| height: childHeight, | |
| }); | |
| } | |
| childY += childHeight; | |
| } | |
| return undefined; | |
| } | |
| render(width: number): string[] { | |
| if (this.children.length === 0) { | |
| return []; | |
| } | |
| const contentWidth = Math.max(1, width - this.paddingX * 2); | |
| const leftPad = " ".repeat(this.paddingX); | |
| // Render all children | |
| const childLines: string[] = []; | |
| const mouseChildren: Array<{ component: Component; height: number }> = []; | |
| for (const child of this.children) { | |
| const lines = child.render(contentWidth); | |
| mouseChildren.push({ component: child, height: lines.length }); | |
| for (const line of lines) { | |
| childLines.push(leftPad + line); | |
| } | |
| } | |
| this.mouseLayout = { width: contentWidth, children: mouseChildren }; | |
| if (childLines.length === 0) { | |
| return []; | |
| } | |
| // Check if bgFn output changed by sampling | |
| const bgSample = this.bgFn ? this.bgFn("test") : undefined; | |
| // Check cache validity | |
| if (this.matchCache(width, childLines, bgSample)) { | |
| return this.cache!.lines; | |
| } | |
| // Apply background and padding | |
| const result: string[] = []; | |
| // Top padding | |
| for (let i = 0; i < this.paddingY; i++) { | |
| result.push(this.applyBg("", width)); | |
| } | |
| // Content | |
| for (const line of childLines) { | |
| result.push(this.applyBg(line, width)); | |
| } | |
| // Bottom padding | |
| for (let i = 0; i < this.paddingY; i++) { | |
| result.push(this.applyBg("", width)); | |
| } | |
| // Update cache | |
| this.cache = { childLines, width, bgSample, lines: result }; | |
| return result; | |
| } | |
| private applyBg(line: string, width: number): string { | |
| const visLen = visibleWidth(line); | |
| const padNeeded = Math.max(0, width - visLen); | |
| const padded = line + " ".repeat(padNeeded); | |
| if (this.bgFn) { | |
| return applyBackgroundToLine(padded, width, this.bgFn); | |
| } | |
| return padded; | |
| } | |
| } | |