Download packages/pi-tui/src/components/stack.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 5.22 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/pi-tui/src/components/stack.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/pi-tui/src/components/stack.ts
-
curl -L -o stack.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/pi-tui/src/components/stack.ts
5.22 kB
| import { LAYOUT_NODE, type LayoutViewport, type StackLayoutEntry, type StackLayoutNode } from "../layout-node.ts"; | |
| import { type Component, Container } from "../tui.ts"; | |
| export interface StackEntryOptions { | |
| basis?: number | "auto"; | |
| grow?: number; | |
| shrink?: number; | |
| minSize?: number; | |
| maxSize?: number; | |
| visible?: (viewport: LayoutViewport) => boolean; | |
| } | |
| export interface StackEntry extends StackEntryOptions { | |
| component: Component; | |
| } | |
| export type StackChild = Component | StackEntry; | |
| export interface StackOptions { | |
| gap?: number; | |
| align?: "stretch" | "start" | "center" | "end"; | |
| } | |
| function isStackEntry(child: StackChild): child is StackEntry { | |
| return !("render" in child); | |
| } | |
| function normalizeSize(value: number | undefined, fallback: number): number { | |
| return value === undefined || !Number.isFinite(value) ? fallback : Math.max(0, Math.floor(value)); | |
| } | |
| export abstract class Stack extends Container { | |
| protected readonly entries: StackLayoutEntry[] = []; | |
| protected readonly gap: number; | |
| protected readonly align: "stretch" | "start" | "center" | "end"; | |
| protected abstract readonly layoutType: "vstack" | "hstack"; | |
| constructor(children: StackChild[] = [], options: StackOptions = {}) { | |
| super(); | |
| this.gap = normalizeSize(options.gap, 0); | |
| this.align = options.align ?? "stretch"; | |
| for (const child of children) { | |
| if (isStackEntry(child)) this.addChild(child.component, child); | |
| else this.addChild(child); | |
| } | |
| } | |
| override addChild(component: Component, options: StackEntryOptions = {}): void { | |
| super.addChild(component); | |
| this.entries.push({ | |
| component, | |
| ...(options.basis === undefined ? {} : { basis: options.basis }), | |
| ...(options.grow === undefined ? {} : { grow: normalizeSize(options.grow, 0) }), | |
| ...(options.shrink === undefined ? {} : { shrink: normalizeSize(options.shrink, 1) }), | |
| ...(options.minSize === undefined ? {} : { minSize: normalizeSize(options.minSize, 0) }), | |
| ...(options.maxSize === undefined ? {} : { maxSize: normalizeSize(options.maxSize, Number.MAX_SAFE_INTEGER) }), | |
| ...(options.visible === undefined ? {} : { visible: options.visible }), | |
| }); | |
| } | |
| override removeChild(component: Component): void { | |
| super.removeChild(component); | |
| const index = this.entries.findIndex((entry) => entry.component === component); | |
| if (index !== -1) this.entries.splice(index, 1); | |
| } | |
| override clear(): void { | |
| super.clear(); | |
| this.entries.length = 0; | |
| } | |
| [LAYOUT_NODE](): StackLayoutNode { | |
| return { | |
| type: this.layoutType, | |
| entries: this.entries, | |
| gap: this.gap, | |
| align: this.align, | |
| }; | |
| } | |
| } | |
| export function visibleStackEntries( | |
| entries: readonly StackLayoutEntry[], | |
| viewport: LayoutViewport, | |
| ): StackLayoutEntry[] { | |
| return entries.filter((entry) => entry.visible?.(viewport) ?? true); | |
| } | |
| function clampSize(size: number, entry: StackLayoutEntry): number { | |
| const min = Math.max(0, Math.floor(entry.minSize ?? 0)); | |
| const max = Math.max(min, Math.floor(entry.maxSize ?? Number.MAX_SAFE_INTEGER)); | |
| return Math.max(min, Math.min(max, Math.max(0, Math.floor(size)))); | |
| } | |
| function distribute( | |
| sizes: number[], | |
| entries: readonly StackLayoutEntry[], | |
| amount: number, | |
| mode: "grow" | "shrink", | |
| ): void { | |
| let remaining = amount; | |
| while (remaining > 0) { | |
| const candidates = entries | |
| .map((entry, index) => ({ entry, index })) | |
| .filter(({ entry, index }) => { | |
| if (mode === "grow") { | |
| return (entry.grow ?? 0) > 0 && sizes[index]! < (entry.maxSize ?? Number.MAX_SAFE_INTEGER); | |
| } | |
| return (entry.shrink ?? 1) > 0 && sizes[index]! > (entry.minSize ?? 0); | |
| }); | |
| if (candidates.length === 0) return; | |
| const totalWeight = candidates.reduce((sum, { entry, index }) => { | |
| return sum + (mode === "grow" ? (entry.grow ?? 0) : (entry.shrink ?? 1) * Math.max(1, sizes[index]!)); | |
| }, 0); | |
| let distributed = 0; | |
| for (const { entry, index } of candidates) { | |
| if (remaining <= 0) break; | |
| const weight = mode === "grow" ? (entry.grow ?? 0) : (entry.shrink ?? 1) * Math.max(1, sizes[index]!); | |
| const proposed = Math.max(1, Math.floor((remaining * weight) / totalWeight)); | |
| const capacity = | |
| mode === "grow" | |
| ? (entry.maxSize ?? Number.MAX_SAFE_INTEGER) - sizes[index]! | |
| : sizes[index]! - (entry.minSize ?? 0); | |
| const delta = Math.min(remaining, proposed, capacity); | |
| if (delta <= 0) continue; | |
| sizes[index] = sizes[index]! + (mode === "grow" ? delta : -delta); | |
| remaining -= delta; | |
| distributed += delta; | |
| } | |
| if (distributed === 0) return; | |
| } | |
| } | |
| export function allocateStackSizes( | |
| entries: readonly StackLayoutEntry[], | |
| intrinsicSizes: readonly number[], | |
| availableSize: number | undefined, | |
| gap: number, | |
| ): number[] { | |
| const sizes = entries.map((entry, index) => | |
| clampSize( | |
| entry.basis === undefined || entry.basis === "auto" ? (intrinsicSizes[index] ?? 0) : entry.basis, | |
| entry, | |
| ), | |
| ); | |
| if (availableSize === undefined) return sizes; | |
| const contentSize = Math.max(0, Math.floor(availableSize) - Math.max(0, entries.length - 1) * gap); | |
| const total = sizes.reduce((sum, size) => sum + size, 0); | |
| if (total < contentSize) distribute(sizes, entries, contentSize - total, "grow"); | |
| else if (total > contentSize) distribute(sizes, entries, total - contentSize, "shrink"); | |
| return sizes; | |
| } | |