Skip to content

vue-tui 0.3

Latest

Choose a tag to compare

@hyfdev hyfdev released this 02 Aug 09:47
· 17 commits to main since this release
b4a7582

Public beta. 0.3.0 is the first coordinated release of @vue-tui/runtime, @vue-tui/components, @vue-tui/use, @vue-tui/testing, and @vue-tui/vite. The Runtime API is stabilizing toward 1.0; dev-mode HMR remains experimental.

Try it

pnpm dlx tiged vuejs-ai/vue-tui/templates/vite my-app
cd my-app
pnpm install

pnpm dev
pnpm build
node dist/main.mjs

Development runs through Vite with terminal HMR. Production builds one self-contained Node ESM file.

Tagged input and explicit rendering

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

const count = shallowRef(0);

useInput((event) => {
  if (event.type === "key") {
    if (event.key.name === "up") count.value++;
    if (event.key.name === "down") count.value--;
  }
});
</script>

<template>
  <Box>
    <Text>Count: </Text>
    <Text bold color="green">{{ count }}</Text>
  </Box>
</template>
import { createApp } from "@vue-tui/runtime";
import App from "./app.vue";

createApp(App).mount({
  mode: "fullscreen",
  color: true,
  exitOnCtrlC: true,
});

useInput() now delivers one frozen text, key, or paste event. Runtime also adds explicit color control, host-based v-show, text alignment, terminal-default colors, and expanded Box and border styling.

Components

@vue-tui/components adds Table and ScrollBox; Newline and Spacer move here from Runtime.

<script setup lang="ts">
import { Table, type TableColumn } from "@vue-tui/components";

interface Process {
  pid: number;
  name: string;
}

const rows: Process[] = [
  { pid: 1042, name: "vite" },
  { pid: 1088, name: "node" },
];

const columns = [
  { key: "pid", label: "PID", align: "right" },
  {
    key: "name",
    label: "Command",
    headerStyle: { bold: true },
    cellStyle: (_value, row) => ({
      color: row.pid === 1042 ? "green" : "yellow",
    }),
  },
] satisfies readonly TableColumn<Process>[];
</script>

<template>
  <Table :data="rows" :columns="columns" />
</template>

Table supports terminal display width, multiline cells, wrapping, alignment, formatting, and structured text styles. ScrollBox provides an application-controlled bounded viewport.

@vue-tui/use is published for the first time with useInputWhileMounted() and <UseInputWhileMounted>.

Terminal testing

import { defineComponent } from "vue";
import { expect, test } from "vitest";
import { Text } from "@vue-tui/runtime";
import { render } from "@vue-tui/testing";

const App = defineComponent(() => () => <Text>Ready</Text>);

test("renders on a fullscreen terminal", async () => {
  const result = await render(App, {
    mode: "fullscreen",
    columns: 80,
    rows: 24,
  });

  try {
    expect(result.lastFrame()).toBe("Ready");
    expect((await result.screen()).activeBuffer).toBe("alternate");
  } finally {
    result.dispose();
  }
});

lastFrame() exposes renderer content. screen() exposes the terminal-emulated cell surface after cursor, screen, stdout, and stderr behavior is applied.

HMR in development, one file in production

// vite.config.ts
import { defineConfig } from "vite";
import vue from "unplugin-vue/vite";
import { vueTui } from "@vue-tui/vite";

export default defineConfig({
  plugins: [vue(), vueTui()],
});
// tsdown.config.ts
import { defineConfig } from "tsdown";
import vue from "unplugin-vue/rolldown";

export default defineConfig({
  entry: ["src/main.ts"],
  platform: "node",
  format: "esm",
  deps: { alwaysBundle: [/./], onlyBundle: false },
  plugins: [vue()],
});

vueTui() is now dev-only. Compile, evaluation, and render failures appear in the terminal and recover after the next valid edit.

SFC projects using @vue-tui/vite must switch to unplugin-vue/vite; its default client transform produces the render functions required by the terminal renderer. JSX projects continue to use @vitejs/plugin-vue-jsx.

SFC production builds use tsdown with unplugin-vue/rolldown; JSX production builds use unplugin-vue-jsx/rolldown.

Upgrade notes

Vue ^3.5.0 is required, and all official vue-tui packages are now version 0.3.0.

- useInput((input, key) => {
-   if (key.upArrow) count.value++;
- });
+ useInput((event) => {
+   if (event.type === "key" && event.key.name === "up") {
+     count.value++;
+   }
+ });

usePaste() is folded into useInput() as event.type === "paste".

- import { Newline, Spacer, Static } from "@vue-tui/runtime";
+ import { Newline, Spacer } from "@vue-tui/components";
+ import { Static } from "@vue-tui/runtime/inline";

Static now uses ordinary Vue iteration:

- <Static :items="completed">
-   <template #default="{ item }">
-     <Entry :entry="item" />
-   </template>
- </Static>
+ <Static v-for="item in completed" :key="item.id">
+   <Entry :entry="item" />
+ </Static>

Static is Inline-only and is rejected on a Fullscreen surface.

- renderToString(App, { columns: 80 });
+ renderToString(App, { width: 80, height: Infinity });

The old renderer was vertically unbounded. In 0.3, omitting height models a 24-line surface; use Infinity to retain unbounded output.

- createApp(App).mount({ alternateScreen: true });
+ createApp(App).mount({ mode: "fullscreen" });
  • exitOnCtrlC now defaults to false; opt in with exitOnCtrlC: true.
  • useWindowSize() was removed. Use useLayoutSize() only when root layout bounds—not physical terminal dimensions—are what the application needs.
  • Transform was removed without replacement. Other published experimental animation, cursor, focus-manager, output-stream, and accessibility APIs were also removed.

Full details: Runtime, Components, Testing, Use, and Vite.