@simon_he/vue-tui 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -20
- package/dist/{TSelect-BwJJbuTt.js → TSelect-DR0FomSu.js} +90 -41
- package/dist/{TTree-DK_DQBYm.js → TTree-kzhlB5P0.js} +110 -28
- package/dist/agent.cjs +673 -165
- package/dist/agent.js +3 -3
- package/dist/cli.cjs +1081 -166
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +846 -164
- package/dist/core/buffer/buffer.d.ts +2 -2
- package/dist/core/types.d.ts +0 -4
- package/dist/core.cjs +20 -5
- package/dist/core.js +1 -1
- package/dist/{create-terminal-C2oiWFJU.js → create-terminal-DBFNYr8f.js} +15 -6
- package/dist/{dom-renderer-BqGijQID.js → dom-renderer-BniJSQyX.js} +1 -1
- package/dist/experimental.cjs +583 -121
- package/dist/experimental.js +1 -1
- package/dist/index.cjs +235 -81
- package/dist/index.js +4 -4
- package/dist/renderer/cli/index.d.ts +1 -1
- package/dist/renderer/cli/stdout-renderer.d.ts +50 -7
- package/dist/renderer-dom.js +2 -2
- package/dist/{tlog-theme-qPBt5rge.js → tlog-theme-CvLFNMwk.js} +539 -100
- package/dist/vue/components/TVirtualRows.d.ts +18 -0
- package/dist/vue/transcript/types.d.ts +4 -0
- package/dist/vue.cjs +235 -81
- package/dist/vue.js +4 -4
- package/package.json +30 -11
package/README.md
CHANGED
|
@@ -1,18 +1,47 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="./docs/public/brand/vue-tui-logo-on-dark.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="./docs/public/brand/vue-tui-logo-on-light.svg">
|
|
5
|
+
<img alt="Vue TUI" src="./docs/public/brand/vue-tui-logo-on-light.svg" width="360">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
1
9
|
# @simon_he/vue-tui
|
|
2
10
|
|
|
3
|
-
|
|
11
|
+
[](https://www.npmjs.com/package/@simon_he/vue-tui)
|
|
12
|
+
[](https://www.npmjs.com/package/@simon_he/vue-tui)
|
|
13
|
+
[](https://github.com/Simon-He95/vue-tui/actions/workflows/ci.yml)
|
|
14
|
+
[](https://github.com/Simon-He95/vue-tui/actions/workflows/runtime-compat.yml)
|
|
15
|
+
[](https://github.com/Simon-He95/vue-tui/actions/workflows/security.yml)
|
|
16
|
+

|
|
17
|
+

|
|
18
|
+
[](./license)
|
|
19
|
+
|
|
20
|
+
[Docs](https://vue-tui.pages.dev/) | [npm](https://www.npmjs.com/package/@simon_he/vue-tui) | [GitHub](https://github.com/Simon-He95/vue-tui) | [Issues](https://github.com/Simon-He95/vue-tui/issues)
|
|
21
|
+
|
|
22
|
+
Vue TUI is a Vue 3 terminal UI toolkit for building terminal-style interfaces that render to browser DOM, real CLI stdout, and headless tests.
|
|
23
|
+
|
|
24
|
+
Use it to build browser terminal dashboards, Vue-powered CLI apps, streaming markdown transcripts, log viewers, virtual lists, and AI agent consoles with one shared component model.
|
|
25
|
+
|
|
26
|
+
## Why Vue TUI?
|
|
27
|
+
|
|
28
|
+
- **Vue component model for terminal UIs**: build with `TerminalProvider`, `TBox`, `TInput`, `TList`, `TTable`, and more.
|
|
29
|
+
- **Browser + CLI renderers**: render the same UI model to browser DOM or real stdout.
|
|
30
|
+
- **High-throughput surfaces**: virtual lists, append-only logs, streaming markdown, and agent transcripts.
|
|
31
|
+
- **Clear host boundaries**: browser-safe root entrypoint, CLI-only APIs under `/cli`, sanitized links, and explicit host permissions.
|
|
32
|
+
|
|
33
|
+
## Agent UI Showcase
|
|
34
|
+
|
|
35
|
+
A real agent console built on `@simon_he/vue-tui` can stream agent output, markdown content, tool-call status, and input chrome in one terminal surface.
|
|
4
36
|
|
|
5
|
-
|
|
37
|
+
[](./docs/public/agent-console-rendering.mp4)
|
|
6
38
|
|
|
7
|
-
|
|
8
|
-
- CLI apps that use Vue component composition instead of imperative stdout drawing.
|
|
9
|
-
- Shared UI code that can run against a DOM renderer, a stdout renderer, or headless tests.
|
|
10
|
-
- Large terminal surfaces such as virtual lists, append-only logs, markdown transcripts, and agent console UIs.
|
|
39
|
+
[Watch the rendering demo](./docs/public/agent-console-rendering.mp4)
|
|
11
40
|
|
|
12
41
|
## Install
|
|
13
42
|
|
|
14
43
|
```bash
|
|
15
|
-
pnpm add @simon_he/vue-tui
|
|
44
|
+
pnpm add @simon_he/vue-tui vue
|
|
16
45
|
```
|
|
17
46
|
|
|
18
47
|
Vue is a peer dependency. The current package supports Vue `>=3.3.0 <4`.
|
|
@@ -217,19 +246,25 @@ This table is a category overview. The generated API reference for root, `/vue`,
|
|
|
217
246
|
|
|
218
247
|
## Documentation
|
|
219
248
|
|
|
220
|
-
| Page
|
|
221
|
-
|
|
|
222
|
-
| [Docs home](./docs/index.md)
|
|
223
|
-
| [
|
|
224
|
-
| [
|
|
225
|
-
| [
|
|
226
|
-
| [
|
|
227
|
-
| [
|
|
228
|
-
| [
|
|
229
|
-
| [
|
|
230
|
-
| [
|
|
231
|
-
| [
|
|
232
|
-
| [
|
|
249
|
+
| Page | Purpose |
|
|
250
|
+
| ------------------------------------------------------------------ | -------------------------------------------------------------------- |
|
|
251
|
+
| [Docs home](./docs/index.md) | Product overview and reading path |
|
|
252
|
+
| [Vue Terminal UI](./docs/guide/vue-terminal-ui.md) | English landing page for terminal-style Vue interfaces |
|
|
253
|
+
| [Vue CLI UI](./docs/guide/vue-cli-ui.md) | CLI app model with Vue component composition |
|
|
254
|
+
| [CLI stdout renderer](./docs/guide/cli-stdout-renderer.md) | Stdout renderer, stdin driver, cleanup, and terminal output |
|
|
255
|
+
| [Terminal log viewer](./docs/guide/terminal-log-viewer.md) | Append-only logs, retained windows, wrapping, links, and search |
|
|
256
|
+
| [Markdown transcripts](./docs/guide/markdown-transcript.md) | Static and streaming markdown transcript rendering |
|
|
257
|
+
| [Examples index](./docs/examples.md) | Browser, terminal, and smoke example commands |
|
|
258
|
+
| [Core API](./docs/api.md) | Terminal, renderer, events, runtime, planes, and scheduler contracts |
|
|
259
|
+
| [Terminal UI best practices](./docs/terminal-ui-best-practices.md) | Cell layout, input focus, render invalidation, transcripts, tests |
|
|
260
|
+
| [Performance](./docs/performance.md) | Practical performance guidance |
|
|
261
|
+
| [Benchmarks](./docs/benchmarks.md) | Release benchmark budgets, sample results, and comparison boundaries |
|
|
262
|
+
| [OpenTUI Solid comparison](./docs/compare-opentui-solid.md) | Same-scenario comparison protocol and public claim boundaries |
|
|
263
|
+
| [High-throughput rendering](./docs/high-throughput-rendering.md) | Scheduler, dirty rows, mailbox, log, and renderer architecture |
|
|
264
|
+
| [Component acceptance](./docs/components-acceptance.md) | Release readiness checks for component API and behavior |
|
|
265
|
+
| [Agent Console](./docs/agent-console.md) | Streaming transcript example stack |
|
|
266
|
+
| [Release candidate](./docs/release-candidate.md) | 1.0 RC validation, package export checks, and migration notes |
|
|
267
|
+
| [Security policy](./SECURITY.md) | Vulnerability reporting and terminal permission boundaries |
|
|
233
268
|
|
|
234
269
|
Run the docs locally:
|
|
235
270
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getPlaneTerminal } from "./create-terminal-
|
|
1
|
+
import { getPlaneTerminal } from "./create-terminal-DBFNYr8f.js";
|
|
2
2
|
import { charCellWidth, hasEncodedControl } from "./hyperlink-Bn02UXJb.js";
|
|
3
3
|
import { DialogContextKey, EventZIndexContextKey, ImeAnchorContextKey, LayoutContextKey, RenderPlaneContextKey, RenderStackKey, TInputPluginsContextKey, TerminalContextKey, forEachTextCellSegment, graphemeRangeAt, hasTextWidthAsciiFastPath, intersectRect, padEndByCells$1 as padEndByCells, repeatChar, sanitizeInlineText, sanitizeTextBlock, sliceByCells, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useRenderStack, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider, wrapByCells } from "./wheel-scroll-vBRaQ7p-.js";
|
|
4
4
|
import { TView } from "./linkify-D_gzn9Xq.js";
|
|
@@ -4466,18 +4466,45 @@ function normalizeVisibleLimit$1(value) {
|
|
|
4466
4466
|
function isCommandPaletteRowSelectable(item) {
|
|
4467
4467
|
return Boolean(item && item.kind !== "separator" && item.kind !== "group" && !item.disabled);
|
|
4468
4468
|
}
|
|
4469
|
-
function
|
|
4470
|
-
const q = query.trim()
|
|
4471
|
-
if (!q) return
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4469
|
+
function defaultSubstringEntries(sourceItems, query) {
|
|
4470
|
+
const q = query.trim();
|
|
4471
|
+
if (!q) return sourceItems.map((item, sourceIndex) => ({
|
|
4472
|
+
item,
|
|
4473
|
+
sourceIndex,
|
|
4474
|
+
match: { score: 0 }
|
|
4475
|
+
}));
|
|
4476
|
+
const needle = q.toLowerCase();
|
|
4477
|
+
const labelMatches = [];
|
|
4478
|
+
const detailMatches = [];
|
|
4479
|
+
const keywordMatches = [];
|
|
4480
|
+
for (let sourceIndex = 0; sourceIndex < sourceItems.length; sourceIndex++) {
|
|
4481
|
+
const item = sourceItems[sourceIndex];
|
|
4482
|
+
if (item.kind === "separator" || item.kind === "group") continue;
|
|
4483
|
+
const label = String(item.label ?? "");
|
|
4484
|
+
const detail = item.detail == null ? "" : String(item.detail);
|
|
4485
|
+
if (label.toLowerCase().includes(needle)) {
|
|
4486
|
+
labelMatches.push({
|
|
4487
|
+
item,
|
|
4488
|
+
sourceIndex,
|
|
4489
|
+
match: { score: 100 }
|
|
4490
|
+
});
|
|
4491
|
+
continue;
|
|
4492
|
+
}
|
|
4493
|
+
if (detail.toLowerCase().includes(needle)) {
|
|
4494
|
+
detailMatches.push({
|
|
4495
|
+
item,
|
|
4496
|
+
sourceIndex,
|
|
4497
|
+
match: { score: 50 }
|
|
4498
|
+
});
|
|
4499
|
+
continue;
|
|
4500
|
+
}
|
|
4501
|
+
if ((item.keywords ?? []).some((keyword) => String(keyword ?? "").toLowerCase().includes(needle))) keywordMatches.push({
|
|
4502
|
+
item,
|
|
4503
|
+
sourceIndex,
|
|
4504
|
+
match: { score: 10 }
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
return labelMatches.concat(detailMatches, keywordMatches);
|
|
4481
4508
|
}
|
|
4482
4509
|
function fuzzyMatcher(item, query) {
|
|
4483
4510
|
const q = query.trim().toLowerCase();
|
|
@@ -4732,20 +4759,26 @@ const TCommandPalette = defineComponent({
|
|
|
4732
4759
|
const filteredEntries = computed(() => {
|
|
4733
4760
|
const q = query.value.trim();
|
|
4734
4761
|
const sourceItems = props.itemsProvider ? providerItems.value ?? [] : props.items;
|
|
4735
|
-
|
|
4736
|
-
const
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4762
|
+
if (!props.matcher && props.filterStrategy === "substring") return defaultSubstringEntries(sourceItems, q);
|
|
4763
|
+
const matcher = props.matcher ?? fuzzyMatcher;
|
|
4764
|
+
const entries = [];
|
|
4765
|
+
for (let sourceIndex = 0; sourceIndex < sourceItems.length; sourceIndex++) {
|
|
4766
|
+
const item = sourceItems[sourceIndex];
|
|
4767
|
+
if (item.kind === "separator" || item.kind === "group") {
|
|
4768
|
+
if (!q) entries.push({
|
|
4769
|
+
item,
|
|
4770
|
+
sourceIndex,
|
|
4771
|
+
match: { score: 0 }
|
|
4772
|
+
});
|
|
4773
|
+
continue;
|
|
4774
|
+
}
|
|
4742
4775
|
const match = matcher(item, q);
|
|
4743
|
-
|
|
4776
|
+
if (match) entries.push({
|
|
4744
4777
|
item,
|
|
4745
4778
|
sourceIndex,
|
|
4746
4779
|
match
|
|
4747
|
-
}
|
|
4748
|
-
}
|
|
4780
|
+
});
|
|
4781
|
+
}
|
|
4749
4782
|
entries.sort((a, b) => b.match.score - a.match.score || a.sourceIndex - b.sourceIndex);
|
|
4750
4783
|
return entries;
|
|
4751
4784
|
});
|
|
@@ -5383,6 +5416,40 @@ const TSelect = defineComponent({
|
|
|
5383
5416
|
const typeaheadQuery = ref("");
|
|
5384
5417
|
const query = computed(() => props.query ?? innerQuery.value);
|
|
5385
5418
|
const options = computed(() => providerOptions.value ?? props.options);
|
|
5419
|
+
function getOptionLabel(opt) {
|
|
5420
|
+
return isOptionObject(opt) ? opt.label : opt;
|
|
5421
|
+
}
|
|
5422
|
+
function getOptionKind(opt) {
|
|
5423
|
+
return isOptionObject(opt) ? opt.kind : void 0;
|
|
5424
|
+
}
|
|
5425
|
+
function getOptionValue(opt, index) {
|
|
5426
|
+
if (props.valueMode === "index") return index;
|
|
5427
|
+
if (props.valueMode === "option") return opt;
|
|
5428
|
+
return isOptionObject(opt) && "value" in opt ? opt.value : getOptionLabel(opt);
|
|
5429
|
+
}
|
|
5430
|
+
const optionValueIndex = computed(() => {
|
|
5431
|
+
if (props.valueMode === "index") return null;
|
|
5432
|
+
const indexed = new Map();
|
|
5433
|
+
const opts = options.value;
|
|
5434
|
+
for (let index = 0; index < opts.length; index++) {
|
|
5435
|
+
const value = getOptionValue(opts[index], index);
|
|
5436
|
+
if (typeof value === "number" && value === 0) continue;
|
|
5437
|
+
if (!indexed.has(value)) indexed.set(value, index);
|
|
5438
|
+
}
|
|
5439
|
+
return indexed;
|
|
5440
|
+
});
|
|
5441
|
+
function valuesEqual(a, b) {
|
|
5442
|
+
return Object.is(a, b);
|
|
5443
|
+
}
|
|
5444
|
+
function modelIndex(value, useValueIndex = props.multiple) {
|
|
5445
|
+
if (props.valueMode === "index") return normalizeModelIndex(value);
|
|
5446
|
+
if (useValueIndex) {
|
|
5447
|
+
const indexed = optionValueIndex.value;
|
|
5448
|
+
const cached = indexed?.get(value);
|
|
5449
|
+
if (cached != null) return cached;
|
|
5450
|
+
}
|
|
5451
|
+
return options.value.findIndex((opt, optIndex) => valuesEqual(getOptionValue(opt, optIndex), value));
|
|
5452
|
+
}
|
|
5386
5453
|
const initialActive = (() => {
|
|
5387
5454
|
const max = Math.max(0, options.value.length - 1);
|
|
5388
5455
|
if (!props.multiple) {
|
|
@@ -5442,24 +5509,6 @@ const TSelect = defineComponent({
|
|
|
5442
5509
|
}
|
|
5443
5510
|
innerActive.value = clamp(active.value, 0, max);
|
|
5444
5511
|
});
|
|
5445
|
-
function getOptionLabel(opt) {
|
|
5446
|
-
return isOptionObject(opt) ? opt.label : opt;
|
|
5447
|
-
}
|
|
5448
|
-
function getOptionKind(opt) {
|
|
5449
|
-
return isOptionObject(opt) ? opt.kind : void 0;
|
|
5450
|
-
}
|
|
5451
|
-
function getOptionValue(opt, index) {
|
|
5452
|
-
if (props.valueMode === "index") return index;
|
|
5453
|
-
if (props.valueMode === "option") return opt;
|
|
5454
|
-
return isOptionObject(opt) && "value" in opt ? opt.value : getOptionLabel(opt);
|
|
5455
|
-
}
|
|
5456
|
-
function valuesEqual(a, b) {
|
|
5457
|
-
return Object.is(a, b);
|
|
5458
|
-
}
|
|
5459
|
-
function modelIndex(value) {
|
|
5460
|
-
if (props.valueMode === "index") return normalizeModelIndex(value);
|
|
5461
|
-
return options.value.findIndex((opt, optIndex) => valuesEqual(getOptionValue(opt, optIndex), value));
|
|
5462
|
-
}
|
|
5463
5512
|
function isOptionInteractive(opt) {
|
|
5464
5513
|
if (!opt) return false;
|
|
5465
5514
|
const kind = getOptionKind(opt);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { TERMINAL_RENDER_PLANES, createTerminal, getPlaneTerminal, resetPlaneRowsForRender, scrollPlaneRows } from "./create-terminal-
|
|
1
|
+
import { TERMINAL_RENDER_PLANES, createTerminal, getPlaneTerminal, resetPlaneRowsForRender, scrollPlaneRows } from "./create-terminal-DBFNYr8f.js";
|
|
2
2
|
import { sanitizeDomHref } from "./hyperlink-Bn02UXJb.js";
|
|
3
|
-
import { DOM_RENDERER_CAPABILITIES, createDomRenderer } from "./dom-renderer-
|
|
3
|
+
import { DOM_RENDERER_CAPABILITIES, createDomRenderer } from "./dom-renderer-BniJSQyX.js";
|
|
4
4
|
import { EventZIndexContextKey, ImeAnchorContextKey, LayoutContextKey, RenderStackKey, TInputPluginsContextKey, TPathPickerProviderContextKey, TerminalContextKey, VisibilityContextKey, applyWheelScroll, clearTextCaches, createWheelScrollState, forEachTextCellSegment, formatInlineCellLine, injectionKey, intersectRect, normalizeCellRect, padEndByCells$1 as padEndByCells, resetWheelScrollState, sanitizeInlineText, sanitizeTextBlock, sliceByCells, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextRenderPass } from "./wheel-scroll-vBRaQ7p-.js";
|
|
5
5
|
import { TView, TuiThemeContextKey, createFrameMailbox, createTheme, defaultActiveStyle, defaultDimStyle, linkifyTextSegments, tuiDefaultTheme } from "./linkify-D_gzn9Xq.js";
|
|
6
6
|
import { SUPPRESS_TERMINAL_POINTER_DOWN, SUPPRESS_TERMINAL_POINTER_MOVE, SUPPRESS_TERMINAL_POINTER_UP, createEventManager, createRuntime } from "./runtime-rfcZCyJq.js";
|
|
7
7
|
import { createFramePerfStore, createTraceStore } from "./trace-CjzYeIxN.js";
|
|
8
8
|
import { envFlag, envString, firstNonEmptyEnv } from "./env-Cl8qmNFN.js";
|
|
9
9
|
import { createTerminalSelectionController } from "./terminal-selection-DBinO6p5.js";
|
|
10
|
-
import { TBox, TInput, TRenderPlane, TText, clamp, fitCellText, mergeStyle, repeatToCells, resolveOverlayPlacement } from "./TSelect-
|
|
10
|
+
import { TBox, TInput, TRenderPlane, TText, clamp, fitCellText, mergeStyle, repeatToCells, resolveOverlayPlacement } from "./TSelect-DR0FomSu.js";
|
|
11
11
|
import { framePerfNow, mergeFramePerfReason } from "./frame-perf-C6Xfg85O.js";
|
|
12
12
|
import { computed, defineComponent, effectScope, getCurrentInstance, h, inject, nextTick, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, provide, ref, shallowReactive, shallowRef, toRef, watch, watchEffect } from "vue";
|
|
13
13
|
|
|
@@ -2991,6 +2991,17 @@ function rowKey$1(row, index, key) {
|
|
|
2991
2991
|
function sameRowKey(a, b) {
|
|
2992
2992
|
return Object.is(a, b);
|
|
2993
2993
|
}
|
|
2994
|
+
function selectedRowKeyIndex(keys) {
|
|
2995
|
+
if (!keys?.length) return null;
|
|
2996
|
+
const indexed = new Set();
|
|
2997
|
+
for (let index = 0; index < keys.length; index++) {
|
|
2998
|
+
if (!(index in keys)) continue;
|
|
2999
|
+
const key = keys[index];
|
|
3000
|
+
if (typeof key === "number" && key === 0) return null;
|
|
3001
|
+
indexed.add(key);
|
|
3002
|
+
}
|
|
3003
|
+
return indexed;
|
|
3004
|
+
}
|
|
2994
3005
|
const TTable = defineComponent({
|
|
2995
3006
|
name: "TTable",
|
|
2996
3007
|
props: {
|
|
@@ -3099,10 +3110,19 @@ const TTable = defineComponent({
|
|
|
3099
3110
|
const rowStyle = computed(() => mergeStyle(defaultStyle.value, theme.value.components.TTable?.rowStyle, props.style));
|
|
3100
3111
|
const selectedRowStyle = computed(() => mergeStyle(rowStyle.value, theme.value.components.TTable?.selectedStyle, props.selectedStyle));
|
|
3101
3112
|
const activeRowStyle = computed(() => mergeStyle(theme.value.components.TTable?.activeStyle, props.activeStyle));
|
|
3113
|
+
const selectedRowKeysIndex = computed(() => selectedRowKeyIndex(props.selectedRowKeys));
|
|
3102
3114
|
const bodyRows = computed(() => {
|
|
3103
3115
|
const top = props.header ? 2 : 0;
|
|
3104
3116
|
return props.rows.slice(0, Math.max(0, props.h - top));
|
|
3105
3117
|
});
|
|
3118
|
+
function isRowSelected(key) {
|
|
3119
|
+
if (props.selectedRowKey !== void 0 && sameRowKey(key, props.selectedRowKey)) return true;
|
|
3120
|
+
const selectedKeys = props.selectedRowKeys;
|
|
3121
|
+
if (!selectedKeys?.length) return false;
|
|
3122
|
+
const indexed = selectedRowKeysIndex.value;
|
|
3123
|
+
if (indexed) return indexed.has(key);
|
|
3124
|
+
return selectedKeys.some((candidate) => sameRowKey(candidate, key));
|
|
3125
|
+
}
|
|
3106
3126
|
return () => {
|
|
3107
3127
|
const children = [];
|
|
3108
3128
|
if (props.header && props.h > 0) {
|
|
@@ -3169,7 +3189,7 @@ const TTable = defineComponent({
|
|
|
3169
3189
|
const row = bodyRows.value[index];
|
|
3170
3190
|
const y = (props.header ? 2 : 0) + index;
|
|
3171
3191
|
const key = rowKey$1(row, index, props.rowKey);
|
|
3172
|
-
const selected =
|
|
3192
|
+
const selected = isRowSelected(key);
|
|
3173
3193
|
const active = props.activeRowKey !== void 0 && sameRowKey(key, props.activeRowKey);
|
|
3174
3194
|
children.push(h(TView, {
|
|
3175
3195
|
key: `row:${String(key)}`,
|
|
@@ -3387,34 +3407,45 @@ const TDataTable = defineComponent({
|
|
|
3387
3407
|
},
|
|
3388
3408
|
setup(props, { emit }) {
|
|
3389
3409
|
const innerScrollTop = ref(0);
|
|
3390
|
-
const
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3410
|
+
const hasLocalFilter = computed(() => !props.manualFilter && props.filterable && Boolean(props.filter.trim()));
|
|
3411
|
+
const hasLocalSort = computed(() => !props.manualSort && props.sortable && Boolean(props.sortBy));
|
|
3412
|
+
const indexedRows = computed(() => {
|
|
3413
|
+
if (!hasLocalFilter.value && !hasLocalSort.value) return null;
|
|
3414
|
+
return props.rows.map((row, originalIndex) => ({
|
|
3415
|
+
row,
|
|
3416
|
+
originalIndex
|
|
3417
|
+
}));
|
|
3418
|
+
});
|
|
3394
3419
|
const filteredRows = computed(() => {
|
|
3395
|
-
|
|
3396
|
-
if (!
|
|
3420
|
+
const rows = indexedRows.value;
|
|
3421
|
+
if (!rows) return null;
|
|
3422
|
+
if (!hasLocalFilter.value) return rows;
|
|
3397
3423
|
const query = props.filter.trim().toLowerCase();
|
|
3398
|
-
if (props.filterPredicate) return
|
|
3399
|
-
return
|
|
3424
|
+
if (props.filterPredicate) return rows.filter(({ row, originalIndex }) => props.filterPredicate(row, query, props.columns, originalIndex));
|
|
3425
|
+
return rows.filter(({ row, originalIndex }) => props.columns.some((column) => displayValue(column, row, originalIndex).toLowerCase().includes(query)));
|
|
3400
3426
|
});
|
|
3401
3427
|
const sortedRows = computed(() => {
|
|
3402
|
-
const rows =
|
|
3403
|
-
if (
|
|
3428
|
+
const rows = filteredRows.value;
|
|
3429
|
+
if (!rows) return null;
|
|
3430
|
+
if (!hasLocalSort.value) return rows;
|
|
3431
|
+
const sorted = [...rows];
|
|
3404
3432
|
const column = props.columns.find((candidate) => candidate.key === props.sortBy);
|
|
3405
|
-
|
|
3433
|
+
sorted.sort((a, b) => {
|
|
3406
3434
|
const result = props.sorter && column ? props.sorter(a.row, b.row, column) : compareValues(a.row[props.sortBy], b.row[props.sortBy]);
|
|
3407
3435
|
return props.sortDirection === "desc" ? -result : result;
|
|
3408
3436
|
});
|
|
3409
|
-
return
|
|
3437
|
+
return sorted;
|
|
3410
3438
|
});
|
|
3411
3439
|
function nonNegativeInteger(value) {
|
|
3412
3440
|
const n = Math.floor(Number(value));
|
|
3413
3441
|
return Number.isFinite(n) ? Math.max(0, n) : 0;
|
|
3414
3442
|
}
|
|
3415
3443
|
const visibleRowCapacity = computed(() => Math.max(0, nonNegativeInteger(props.h) - 2));
|
|
3444
|
+
function processedRowCount() {
|
|
3445
|
+
return sortedRows.value?.length ?? props.rows.length;
|
|
3446
|
+
}
|
|
3416
3447
|
function maxScrollTop$1() {
|
|
3417
|
-
return Math.max(0,
|
|
3448
|
+
return Math.max(0, processedRowCount() - visibleRowCapacity.value);
|
|
3418
3449
|
}
|
|
3419
3450
|
function clampScrollTop$1(value) {
|
|
3420
3451
|
return Math.min(maxScrollTop$1(), nonNegativeInteger(value));
|
|
@@ -3430,7 +3461,28 @@ const TDataTable = defineComponent({
|
|
|
3430
3461
|
watch(() => [normalizedScrollTop.value, requestedScrollTop.value], ([next, requested]) => {
|
|
3431
3462
|
if (next !== requested) setScrollTop(next);
|
|
3432
3463
|
}, { immediate: true });
|
|
3433
|
-
|
|
3464
|
+
function dataEntryAt(absoluteIndex) {
|
|
3465
|
+
const rows = sortedRows.value;
|
|
3466
|
+
if (rows) return rows[absoluteIndex];
|
|
3467
|
+
const row = props.rows[absoluteIndex];
|
|
3468
|
+
return row ? {
|
|
3469
|
+
row,
|
|
3470
|
+
originalIndex: absoluteIndex
|
|
3471
|
+
} : void 0;
|
|
3472
|
+
}
|
|
3473
|
+
function originalIndexForAbsoluteIndex(absoluteIndex) {
|
|
3474
|
+
return dataEntryAt(absoluteIndex)?.originalIndex ?? absoluteIndex;
|
|
3475
|
+
}
|
|
3476
|
+
const visibleRows = computed(() => {
|
|
3477
|
+
const top = normalizedScrollTop.value;
|
|
3478
|
+
const bottom = top + visibleRowCapacity.value;
|
|
3479
|
+
const rows = sortedRows.value;
|
|
3480
|
+
if (rows) return rows.slice(top, bottom);
|
|
3481
|
+
return props.rows.slice(top, bottom).map((row, index) => ({
|
|
3482
|
+
row,
|
|
3483
|
+
originalIndex: top + index
|
|
3484
|
+
}));
|
|
3485
|
+
});
|
|
3434
3486
|
const tableRows = computed(() => visibleRows.value.map(({ row }) => row));
|
|
3435
3487
|
const activeAbsoluteIndex = ref(null);
|
|
3436
3488
|
const activeRowIdentity = ref(NO_ACTIVE_ROW);
|
|
@@ -3438,7 +3490,7 @@ const TDataTable = defineComponent({
|
|
|
3438
3490
|
const columns = computed(() => props.columns.map((column) => {
|
|
3439
3491
|
const format = column.format ? (value, row, visibleIndex) => {
|
|
3440
3492
|
const absoluteIndex = normalizedScrollTop.value + visibleIndex;
|
|
3441
|
-
const originalIndex =
|
|
3493
|
+
const originalIndex = originalIndexForAbsoluteIndex(absoluteIndex);
|
|
3442
3494
|
return column.format(value, row, originalIndex);
|
|
3443
3495
|
} : void 0;
|
|
3444
3496
|
if (!props.sortable || column.key !== props.sortBy) return format ? {
|
|
@@ -3454,7 +3506,7 @@ const TDataTable = defineComponent({
|
|
|
3454
3506
|
}));
|
|
3455
3507
|
function originalIndexAt(index) {
|
|
3456
3508
|
const absoluteIndex = normalizedScrollTop.value + index;
|
|
3457
|
-
return
|
|
3509
|
+
return originalIndexForAbsoluteIndex(absoluteIndex);
|
|
3458
3510
|
}
|
|
3459
3511
|
function dataTableRowKey(row, index) {
|
|
3460
3512
|
return rowKey(row, originalIndexAt(index), props.rowKey);
|
|
@@ -3472,18 +3524,42 @@ const TDataTable = defineComponent({
|
|
|
3472
3524
|
return new Set(props.selectedRowKey === void 0 ? [] : [props.selectedRowKey]);
|
|
3473
3525
|
}
|
|
3474
3526
|
const activeRowKey = computed(() => activeRowIdentity.value === NO_ACTIVE_ROW ? void 0 : activeRowIdentity.value);
|
|
3475
|
-
|
|
3527
|
+
const activeRawRowKey = computed(() => {
|
|
3528
|
+
if (activeAbsoluteIndex.value == null || sortedRows.value) return NO_ACTIVE_ROW;
|
|
3529
|
+
const index = activeAbsoluteIndex.value;
|
|
3530
|
+
const row = props.rows[index];
|
|
3531
|
+
return row ? rowKey(row, index, props.rowKey) : NO_ACTIVE_ROW;
|
|
3532
|
+
});
|
|
3533
|
+
function findDataIndexByKey(key) {
|
|
3534
|
+
const rows = sortedRows.value;
|
|
3535
|
+
if (rows) return rows.findIndex((entry) => Object.is(dataRowKey(entry), key));
|
|
3536
|
+
return props.rows.findIndex((row, index) => Object.is(rowKey(row, index, props.rowKey), key));
|
|
3537
|
+
}
|
|
3538
|
+
watch(() => [
|
|
3539
|
+
props.rowKey,
|
|
3540
|
+
props.rows,
|
|
3541
|
+
sortedRows.value,
|
|
3542
|
+
activeRowIdentity.value
|
|
3543
|
+
], () => {
|
|
3476
3544
|
if (activeRowIdentity.value === NO_ACTIVE_ROW) return;
|
|
3477
|
-
const nextIndex =
|
|
3545
|
+
const nextIndex = findDataIndexByKey(activeRowIdentity.value);
|
|
3478
3546
|
if (nextIndex < 0) {
|
|
3479
3547
|
clearActiveRow();
|
|
3480
3548
|
return;
|
|
3481
3549
|
}
|
|
3482
3550
|
activeAbsoluteIndex.value = nextIndex;
|
|
3483
3551
|
});
|
|
3552
|
+
watch(() => [props.rowKey, activeRawRowKey.value], ([, key]) => {
|
|
3553
|
+
if (activeRowIdentity.value === NO_ACTIVE_ROW) return;
|
|
3554
|
+
if (key === NO_ACTIVE_ROW) {
|
|
3555
|
+
clearActiveRow();
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
if (!Object.is(activeRowIdentity.value, key)) activeRowIdentity.value = key;
|
|
3559
|
+
});
|
|
3484
3560
|
function setActiveAbsoluteIndex(absoluteIndex) {
|
|
3485
|
-
const clamped = Math.max(0, Math.min(
|
|
3486
|
-
const entry =
|
|
3561
|
+
const clamped = Math.max(0, Math.min(processedRowCount() - 1, absoluteIndex));
|
|
3562
|
+
const entry = dataEntryAt(clamped);
|
|
3487
3563
|
if (!entry) {
|
|
3488
3564
|
clearActiveRow();
|
|
3489
3565
|
return null;
|
|
@@ -3550,7 +3626,7 @@ const TDataTable = defineComponent({
|
|
|
3550
3626
|
if (event.key === "Enter" || event.key === " ") {
|
|
3551
3627
|
const active = setActiveAbsoluteIndex(current);
|
|
3552
3628
|
if (!active) return;
|
|
3553
|
-
const entry =
|
|
3629
|
+
const entry = dataEntryAt(active.dataIndex);
|
|
3554
3630
|
if (!entry) return;
|
|
3555
3631
|
const visibleIndex = active.dataIndex - active.scrollTop;
|
|
3556
3632
|
commitSelection(entry, Math.max(0, Math.min(Math.max(0, visibleRowCapacity.value - 1), visibleIndex)), active.dataIndex);
|
|
@@ -6315,8 +6391,10 @@ const TLinkifyText = defineComponent({
|
|
|
6315
6391
|
|
|
6316
6392
|
//#endregion
|
|
6317
6393
|
//#region src/vue/components/TTree.ts
|
|
6318
|
-
function flattenTree(nodes, expanded, depth = 0, out = []) {
|
|
6394
|
+
function flattenTree(nodes, expanded, limit, depth = 0, out = []) {
|
|
6395
|
+
if (out.length >= limit) return out;
|
|
6319
6396
|
for (const node of nodes) {
|
|
6397
|
+
if (out.length >= limit) break;
|
|
6320
6398
|
const expandable = Boolean(node.children?.length);
|
|
6321
6399
|
const isExpanded = expanded.has(node.id);
|
|
6322
6400
|
out.push({
|
|
@@ -6325,10 +6403,14 @@ function flattenTree(nodes, expanded, depth = 0, out = []) {
|
|
|
6325
6403
|
expandable,
|
|
6326
6404
|
expanded: isExpanded
|
|
6327
6405
|
});
|
|
6328
|
-
if (expandable && isExpanded) flattenTree(node.children, expanded, depth + 1, out);
|
|
6406
|
+
if (expandable && isExpanded) flattenTree(node.children, expanded, limit, depth + 1, out);
|
|
6329
6407
|
}
|
|
6330
6408
|
return out;
|
|
6331
6409
|
}
|
|
6410
|
+
function visibleRowLimit(height) {
|
|
6411
|
+
const limit = Math.floor(height);
|
|
6412
|
+
return Number.isFinite(limit) ? Math.max(0, limit) : 0;
|
|
6413
|
+
}
|
|
6332
6414
|
const TTree = defineComponent({
|
|
6333
6415
|
name: "TTree",
|
|
6334
6416
|
props: {
|
|
@@ -6395,7 +6477,7 @@ const TTree = defineComponent({
|
|
|
6395
6477
|
const { defaultStyle } = useTerminal();
|
|
6396
6478
|
const baseStyle = computed(() => mergeStyle(defaultStyle.value, props.style));
|
|
6397
6479
|
const expandedSet = computed(() => new Set(props.expandedIds));
|
|
6398
|
-
const rows = computed(() => flattenTree(props.nodes, expandedSet.value
|
|
6480
|
+
const rows = computed(() => flattenTree(props.nodes, expandedSet.value, visibleRowLimit(props.h)));
|
|
6399
6481
|
function toggle(item) {
|
|
6400
6482
|
if (item.node.disabled || !item.expandable) return;
|
|
6401
6483
|
const ids = new Set(props.expandedIds);
|