Files
Yunfei He 8bc7976561 feat(components): scroll ScrollBox through an imperative handle (drop wheel/keyboard) (#242)
ScrollBox drops its `wheel`, `keyboard`, and `linesPerWheel` props and instead
exposes an imperative handle (`ScrollBoxExpose`): scrollToLine / scrollByLines /
scrollToTop / scrollToBottom. It listens to no mouse or keyboard input itself —
the consumer wires its own bindings to the handle.

Built-in input is deferred because best practice isn't settled: the mouse wheel
needs terminal mouse tracking (which suppresses native text selection
window-wide), and keyboard input is global (collides with a focused field).
Shipping only the scroll mechanism keeps the component honest and lets the app
own input policy. The core bounded, sticky-following viewport still works with
no props.
2026-07-05 16:09:09 +08:00
..

@vue-tui/components

High-level Vue components for vue-tui, composed from @vue-tui/runtime primitives.

Early days — the component set is small and growing. Currently: ScrollBox, Spinner.

Install

npm install @vue-tui/components
# peer deps: @vue-tui/runtime, vue ^3.4

Spinner

An animated loading spinner.

<script setup lang="ts">
import { Spinner } from "@vue-tui/components";
</script>

<template>
  <Spinner type="dots" label="Loading" color="green" />
</template>

Props

prop type default description
type preset name (e.g. "dots", "line") "dots" a built-in spinner animation
frames string[] custom animation frames (overrides type)
interval number preset's ms between frames
color string chalk color for the spinner glyph
label string text shown next to the spinner

ScrollBox

A bounded viewport that follows the bottom of its content. The core behavior — clip overflow and stick to the latest line as content grows — needs no props. It listens to no input itself: scroll it through the exposed imperative handle, and bind your own keys / mouse to that.

<script setup lang="ts">
import { shallowRef } from "vue";
import { ScrollBox, type ScrollBoxExpose } from "@vue-tui/components";
import { Text, useInput } from "@vue-tui/runtime";

const box = shallowRef<ScrollBoxExpose>();

// ScrollBox ships no built-in wheel/keyboard — wire your own keys to the handle.
useInput((_input, key) => {
  if (key.upArrow) box.value?.scrollByLines(-1);
  if (key.downArrow) box.value?.scrollByLines(1);
});
</script>

<template>
  <ScrollBox ref="box">
    <Text v-for="line in lines" :key="line">{{ line }}</Text>
  </ScrollBox>
</template>

Imperative handle (ScrollBoxExpose)

ScrollBox has no props; grab its handle with a template ref and drive scrolling:

action description
scrollToLine(line) scroll so content line line is at the top (clamped)
scrollByLines(lines) scroll by lines relative to the current position (+ = down)
scrollToTop() jump to the top
scrollToBottom() jump to the bottom and resume following new content

Why no built-in wheel / keyboard: the mouse wheel needs terminal mouse tracking, which breaks native text selection window-wide; keyboard input is global and collides with a focused field. So input policy is the app's to decide. For inline streaming output, prefer Static (let it flow into the terminal's own scrollback).

License

MIT