@earendil-works/pi-tui 0.84.1 → 0.84.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 +1 -1
- package/dist/alt-screen-search.d.ts +26 -0
- package/dist/alt-screen-search.d.ts.map +1 -0
- package/dist/alt-screen-search.js +122 -0
- package/dist/alt-screen-search.js.map +1 -0
- package/dist/components/scroll-view.d.ts +6 -1
- package/dist/components/scroll-view.d.ts.map +1 -1
- package/dist/components/scroll-view.js +23 -7
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts +30 -0
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +24 -0
- package/dist/keybindings.js.map +1 -1
- package/dist/latex.d.ts.map +1 -1
- package/dist/latex.js +8 -1
- package/dist/latex.js.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +9 -2
- package/dist/layout.js.map +1 -1
- package/dist/stdin-buffer.d.ts +8 -2
- package/dist/stdin-buffer.d.ts.map +1 -1
- package/dist/stdin-buffer.js +7 -2
- package/dist/stdin-buffer.js.map +1 -1
- package/dist/terminal.d.ts +6 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +18 -1
- package/dist/terminal.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +21 -0
- package/dist/tui-alt-screen.d.ts.map +1 -1
- package/dist/tui-alt-screen.js +247 -5
- package/dist/tui-alt-screen.js.map +1 -1
- package/dist/tui.d.ts +2 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +4 -0
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ if (isViewportTUI(tui)) {
|
|
|
121
121
|
}
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
Stack entries support `basis`, `grow`, `shrink`, `minSize`, `maxSize`, and responsive `visible` callbacks. Mouse-wheel input targets the scroll view under the pointer and unused delta chains to outer scroll views by default. The primary scroll view receives the alternate-screen keyboard navigation actions and wheel input over non-scrollable regions. It can also jump between OSC 133 semantic prompt markers, matching common terminal prompt-navigation shortcuts.
|
|
124
|
+
Stack entries support `basis`, `grow`, `shrink`, `minSize`, `maxSize`, and responsive `visible` callbacks. Mouse-wheel input targets the scroll view under the pointer and unused delta chains to outer scroll views by default. The primary scroll view receives the alternate-screen keyboard navigation actions and wheel input over non-scrollable regions. It can also jump between OSC 133 semantic prompt markers, matching common terminal prompt-navigation shortcuts. Press `Ctrl+Shift+F` to search its rendered content, `Enter`/`Ctrl+G` and `Shift+Enter`/`Ctrl+Shift+G` to move between matches, and `Escape` to close search. `TuiAltScreenOptions.searchMatchStyle` and `searchCurrentMatchStyle` customize match highlighting.
|
|
125
125
|
|
|
126
126
|
Layout geometry is rebuilt for each requested frame. Stateful components are retained, and their existing rendered-line caches remain effective. Calling `render(width)` directly on these layout components produces an unbounded document, which is also used when alt mode restores the main screen.
|
|
127
127
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Component, Focusable } from "./tui.ts";
|
|
2
|
+
export interface AltScreenSearchSegment {
|
|
3
|
+
row: number;
|
|
4
|
+
startCol: number;
|
|
5
|
+
endCol: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AltScreenSearchMatch {
|
|
8
|
+
segments: AltScreenSearchSegment[];
|
|
9
|
+
}
|
|
10
|
+
export declare function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[];
|
|
11
|
+
export declare function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string;
|
|
12
|
+
export declare class AltScreenSearchComponent implements Component, Focusable {
|
|
13
|
+
private readonly input;
|
|
14
|
+
private readonly onQueryChange;
|
|
15
|
+
private resultCount;
|
|
16
|
+
private resultIndex;
|
|
17
|
+
private _focused;
|
|
18
|
+
constructor(onQueryChange: (query: string) => void);
|
|
19
|
+
get focused(): boolean;
|
|
20
|
+
set focused(value: boolean);
|
|
21
|
+
setResult(index: number, count: number): void;
|
|
22
|
+
handleInput(data: string): void;
|
|
23
|
+
invalidate(): void;
|
|
24
|
+
render(width: number): string[];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=alt-screen-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alt-screen-search.d.ts","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAWrD,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAkDD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,EAAE,CA0B1G;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAI9E;AAED,qBAAa,wBAAyB,YAAW,SAAS,EAAE,SAAS;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAEjD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK9B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAe9B;CACD","sourcesContent":["import { Input } from \"./components/input.ts\";\nimport type { Component, Focusable } from \"./tui.ts\";\nimport { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from \"./utils.ts\";\n\nconst segmenter = getGraphemeSegmenter();\n\ninterface SearchSourceSpan {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchSegment {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchMatch {\n\tsegments: AltScreenSearchSegment[];\n}\n\nfunction appendMappedText(\n\ttext: string,\n\tspan: SearchSourceSpan | undefined,\n\tcorpus: { text: string; source: Array<SearchSourceSpan | undefined> },\n): void {\n\tcorpus.text += text;\n\tfor (let index = 0; index < text.length; index++) corpus.source.push(span);\n}\n\nfunction buildSearchCorpus(lines: readonly string[]): {\n\ttext: string;\n\tsource: Array<SearchSourceSpan | undefined>;\n} {\n\tconst corpus: { text: string; source: Array<SearchSourceSpan | undefined> } = { text: \"\", source: [] };\n\tlet pendingSeparator = false;\n\n\tfor (let row = 0; row < lines.length; row++) {\n\t\tconst line = stripTerminalSequences(lines[row] ?? \"\");\n\t\tlet column = 0;\n\t\tfor (const grapheme of segmenter.segment(line)) {\n\t\t\tconst text = grapheme.segment;\n\t\t\tconst width = visibleWidth(text);\n\t\t\tif (/^\\s+$/u.test(text)) {\n\t\t\t\tif (corpus.text.length > 0) pendingSeparator = true;\n\t\t\t\tcolumn += width;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (pendingSeparator) {\n\t\t\t\tappendMappedText(\" \", undefined, corpus);\n\t\t\t\tpendingSeparator = false;\n\t\t\t}\n\t\t\tappendMappedText(text, { row, startCol: column, endCol: column + width }, corpus);\n\t\t\tcolumn += width;\n\t\t}\n\t\tif (corpus.text.length > 0) pendingSeparator = true;\n\t}\n\n\treturn corpus;\n}\n\nfunction normalizeQuery(query: string): string {\n\treturn query.replace(/\\s+/gu, \" \").trim();\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[] {\n\tconst normalizedQuery = normalizeQuery(query);\n\tif (!normalizedQuery) return [];\n\n\tconst corpus = buildSearchCorpus(lines);\n\tconst expression = new RegExp(escapeRegExp(normalizedQuery), \"giu\");\n\tconst matches: AltScreenSearchMatch[] = [];\n\n\tfor (const match of corpus.text.matchAll(expression)) {\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\t\tconst segments: AltScreenSearchSegment[] = [];\n\t\tfor (let index = start; index < end; index++) {\n\t\t\tconst span = corpus.source[index];\n\t\t\tif (!span) continue;\n\t\t\tconst previous = segments[segments.length - 1];\n\t\t\tif (previous && previous.row === span.row && span.startCol <= previous.endCol) {\n\t\t\t\tprevious.endCol = Math.max(previous.endCol, span.endCol);\n\t\t\t} else {\n\t\t\t\tsegments.push({ ...span });\n\t\t\t}\n\t\t}\n\t\tif (segments.length > 0) matches.push({ segments });\n\t}\n\n\treturn matches;\n}\n\nexport function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string {\n\tconst first = match.segments[0];\n\tconst last = match.segments[match.segments.length - 1];\n\treturn first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : \"\";\n}\n\nexport class AltScreenSearchComponent implements Component, Focusable {\n\tprivate readonly input = new Input();\n\tprivate readonly onQueryChange: (query: string) => void;\n\tprivate resultCount = 0;\n\tprivate resultIndex = -1;\n\tprivate _focused = false;\n\n\tconstructor(onQueryChange: (query: string) => void) {\n\t\tthis.onQueryChange = onQueryChange;\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tsetResult(index: number, count: number): void {\n\t\tthis.resultIndex = index;\n\t\tthis.resultCount = count;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst previous = this.input.getValue();\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue();\n\t\tif (query !== previous) this.onQueryChange(query);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst label = \" Find transcript\";\n\t\tconst query = this.input.getValue();\n\t\tconst status = !query\n\t\t\t? \"\"\n\t\t\t: this.resultCount === 0\n\t\t\t\t? \"No matches \"\n\t\t\t\t: `${this.resultIndex + 1}/${this.resultCount} `;\n\t\tconst labelWidth = visibleWidth(label);\n\t\tconst statusWidth = visibleWidth(status);\n\t\tconst gap = \" \".repeat(Math.max(1, safeWidth - labelWidth - statusWidth));\n\t\tconst title = truncateToWidth(`${label}${gap}${status}`, safeWidth, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, safeWidth - visibleWidth(title)));\n\t\treturn [`\\x1b[7m${title}${padding}\\x1b[27m`, ...this.input.render(safeWidth)];\n\t}\n}\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Input } from "./components/input.js";
|
|
2
|
+
import { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from "./utils.js";
|
|
3
|
+
const segmenter = getGraphemeSegmenter();
|
|
4
|
+
function appendMappedText(text, span, corpus) {
|
|
5
|
+
corpus.text += text;
|
|
6
|
+
for (let index = 0; index < text.length; index++)
|
|
7
|
+
corpus.source.push(span);
|
|
8
|
+
}
|
|
9
|
+
function buildSearchCorpus(lines) {
|
|
10
|
+
const corpus = { text: "", source: [] };
|
|
11
|
+
let pendingSeparator = false;
|
|
12
|
+
for (let row = 0; row < lines.length; row++) {
|
|
13
|
+
const line = stripTerminalSequences(lines[row] ?? "");
|
|
14
|
+
let column = 0;
|
|
15
|
+
for (const grapheme of segmenter.segment(line)) {
|
|
16
|
+
const text = grapheme.segment;
|
|
17
|
+
const width = visibleWidth(text);
|
|
18
|
+
if (/^\s+$/u.test(text)) {
|
|
19
|
+
if (corpus.text.length > 0)
|
|
20
|
+
pendingSeparator = true;
|
|
21
|
+
column += width;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (pendingSeparator) {
|
|
25
|
+
appendMappedText(" ", undefined, corpus);
|
|
26
|
+
pendingSeparator = false;
|
|
27
|
+
}
|
|
28
|
+
appendMappedText(text, { row, startCol: column, endCol: column + width }, corpus);
|
|
29
|
+
column += width;
|
|
30
|
+
}
|
|
31
|
+
if (corpus.text.length > 0)
|
|
32
|
+
pendingSeparator = true;
|
|
33
|
+
}
|
|
34
|
+
return corpus;
|
|
35
|
+
}
|
|
36
|
+
function normalizeQuery(query) {
|
|
37
|
+
return query.replace(/\s+/gu, " ").trim();
|
|
38
|
+
}
|
|
39
|
+
function escapeRegExp(text) {
|
|
40
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41
|
+
}
|
|
42
|
+
export function findAltScreenSearchMatches(lines, query) {
|
|
43
|
+
const normalizedQuery = normalizeQuery(query);
|
|
44
|
+
if (!normalizedQuery)
|
|
45
|
+
return [];
|
|
46
|
+
const corpus = buildSearchCorpus(lines);
|
|
47
|
+
const expression = new RegExp(escapeRegExp(normalizedQuery), "giu");
|
|
48
|
+
const matches = [];
|
|
49
|
+
for (const match of corpus.text.matchAll(expression)) {
|
|
50
|
+
const start = match.index;
|
|
51
|
+
const end = start + match[0].length;
|
|
52
|
+
const segments = [];
|
|
53
|
+
for (let index = start; index < end; index++) {
|
|
54
|
+
const span = corpus.source[index];
|
|
55
|
+
if (!span)
|
|
56
|
+
continue;
|
|
57
|
+
const previous = segments[segments.length - 1];
|
|
58
|
+
if (previous && previous.row === span.row && span.startCol <= previous.endCol) {
|
|
59
|
+
previous.endCol = Math.max(previous.endCol, span.endCol);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
segments.push({ ...span });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (segments.length > 0)
|
|
66
|
+
matches.push({ segments });
|
|
67
|
+
}
|
|
68
|
+
return matches;
|
|
69
|
+
}
|
|
70
|
+
export function getAltScreenSearchMatchKey(match) {
|
|
71
|
+
const first = match.segments[0];
|
|
72
|
+
const last = match.segments[match.segments.length - 1];
|
|
73
|
+
return first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : "";
|
|
74
|
+
}
|
|
75
|
+
export class AltScreenSearchComponent {
|
|
76
|
+
input = new Input();
|
|
77
|
+
onQueryChange;
|
|
78
|
+
resultCount = 0;
|
|
79
|
+
resultIndex = -1;
|
|
80
|
+
_focused = false;
|
|
81
|
+
constructor(onQueryChange) {
|
|
82
|
+
this.onQueryChange = onQueryChange;
|
|
83
|
+
}
|
|
84
|
+
get focused() {
|
|
85
|
+
return this._focused;
|
|
86
|
+
}
|
|
87
|
+
set focused(value) {
|
|
88
|
+
this._focused = value;
|
|
89
|
+
this.input.focused = value;
|
|
90
|
+
}
|
|
91
|
+
setResult(index, count) {
|
|
92
|
+
this.resultIndex = index;
|
|
93
|
+
this.resultCount = count;
|
|
94
|
+
}
|
|
95
|
+
handleInput(data) {
|
|
96
|
+
const previous = this.input.getValue();
|
|
97
|
+
this.input.handleInput(data);
|
|
98
|
+
const query = this.input.getValue();
|
|
99
|
+
if (query !== previous)
|
|
100
|
+
this.onQueryChange(query);
|
|
101
|
+
}
|
|
102
|
+
invalidate() {
|
|
103
|
+
this.input.invalidate();
|
|
104
|
+
}
|
|
105
|
+
render(width) {
|
|
106
|
+
const safeWidth = Math.max(1, width);
|
|
107
|
+
const label = " Find transcript";
|
|
108
|
+
const query = this.input.getValue();
|
|
109
|
+
const status = !query
|
|
110
|
+
? ""
|
|
111
|
+
: this.resultCount === 0
|
|
112
|
+
? "No matches "
|
|
113
|
+
: `${this.resultIndex + 1}/${this.resultCount} `;
|
|
114
|
+
const labelWidth = visibleWidth(label);
|
|
115
|
+
const statusWidth = visibleWidth(status);
|
|
116
|
+
const gap = " ".repeat(Math.max(1, safeWidth - labelWidth - statusWidth));
|
|
117
|
+
const title = truncateToWidth(`${label}${gap}${status}`, safeWidth, "");
|
|
118
|
+
const padding = " ".repeat(Math.max(0, safeWidth - visibleWidth(title)));
|
|
119
|
+
return [`\x1b[7m${title}${padding}\x1b[27m`, ...this.input.render(safeWidth)];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=alt-screen-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alt-screen-search.js","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"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","sourcesContent":["import { Input } from \"./components/input.ts\";\nimport type { Component, Focusable } from \"./tui.ts\";\nimport { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from \"./utils.ts\";\n\nconst segmenter = getGraphemeSegmenter();\n\ninterface SearchSourceSpan {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchSegment {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchMatch {\n\tsegments: AltScreenSearchSegment[];\n}\n\nfunction appendMappedText(\n\ttext: string,\n\tspan: SearchSourceSpan | undefined,\n\tcorpus: { text: string; source: Array<SearchSourceSpan | undefined> },\n): void {\n\tcorpus.text += text;\n\tfor (let index = 0; index < text.length; index++) corpus.source.push(span);\n}\n\nfunction buildSearchCorpus(lines: readonly string[]): {\n\ttext: string;\n\tsource: Array<SearchSourceSpan | undefined>;\n} {\n\tconst corpus: { text: string; source: Array<SearchSourceSpan | undefined> } = { text: \"\", source: [] };\n\tlet pendingSeparator = false;\n\n\tfor (let row = 0; row < lines.length; row++) {\n\t\tconst line = stripTerminalSequences(lines[row] ?? \"\");\n\t\tlet column = 0;\n\t\tfor (const grapheme of segmenter.segment(line)) {\n\t\t\tconst text = grapheme.segment;\n\t\t\tconst width = visibleWidth(text);\n\t\t\tif (/^\\s+$/u.test(text)) {\n\t\t\t\tif (corpus.text.length > 0) pendingSeparator = true;\n\t\t\t\tcolumn += width;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (pendingSeparator) {\n\t\t\t\tappendMappedText(\" \", undefined, corpus);\n\t\t\t\tpendingSeparator = false;\n\t\t\t}\n\t\t\tappendMappedText(text, { row, startCol: column, endCol: column + width }, corpus);\n\t\t\tcolumn += width;\n\t\t}\n\t\tif (corpus.text.length > 0) pendingSeparator = true;\n\t}\n\n\treturn corpus;\n}\n\nfunction normalizeQuery(query: string): string {\n\treturn query.replace(/\\s+/gu, \" \").trim();\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[] {\n\tconst normalizedQuery = normalizeQuery(query);\n\tif (!normalizedQuery) return [];\n\n\tconst corpus = buildSearchCorpus(lines);\n\tconst expression = new RegExp(escapeRegExp(normalizedQuery), \"giu\");\n\tconst matches: AltScreenSearchMatch[] = [];\n\n\tfor (const match of corpus.text.matchAll(expression)) {\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\t\tconst segments: AltScreenSearchSegment[] = [];\n\t\tfor (let index = start; index < end; index++) {\n\t\t\tconst span = corpus.source[index];\n\t\t\tif (!span) continue;\n\t\t\tconst previous = segments[segments.length - 1];\n\t\t\tif (previous && previous.row === span.row && span.startCol <= previous.endCol) {\n\t\t\t\tprevious.endCol = Math.max(previous.endCol, span.endCol);\n\t\t\t} else {\n\t\t\t\tsegments.push({ ...span });\n\t\t\t}\n\t\t}\n\t\tif (segments.length > 0) matches.push({ segments });\n\t}\n\n\treturn matches;\n}\n\nexport function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string {\n\tconst first = match.segments[0];\n\tconst last = match.segments[match.segments.length - 1];\n\treturn first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : \"\";\n}\n\nexport class AltScreenSearchComponent implements Component, Focusable {\n\tprivate readonly input = new Input();\n\tprivate readonly onQueryChange: (query: string) => void;\n\tprivate resultCount = 0;\n\tprivate resultIndex = -1;\n\tprivate _focused = false;\n\n\tconstructor(onQueryChange: (query: string) => void) {\n\t\tthis.onQueryChange = onQueryChange;\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tsetResult(index: number, count: number): void {\n\t\tthis.resultIndex = index;\n\t\tthis.resultCount = count;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst previous = this.input.getValue();\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue();\n\t\tif (query !== previous) this.onQueryChange(query);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst label = \" Find transcript\";\n\t\tconst query = this.input.getValue();\n\t\tconst status = !query\n\t\t\t? \"\"\n\t\t\t: this.resultCount === 0\n\t\t\t\t? \"No matches \"\n\t\t\t\t: `${this.resultIndex + 1}/${this.resultCount} `;\n\t\tconst labelWidth = visibleWidth(label);\n\t\tconst statusWidth = visibleWidth(status);\n\t\tconst gap = \" \".repeat(Math.max(1, safeWidth - labelWidth - statusWidth));\n\t\tconst title = truncateToWidth(`${label}${gap}${status}`, safeWidth, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, safeWidth - visibleWidth(title)));\n\t\treturn [`\\x1b[7m${title}${padding}\\x1b[27m`, ...this.input.render(safeWidth)];\n\t}\n}\n"]}
|
|
@@ -10,6 +10,10 @@ export interface ScrollViewOptions {
|
|
|
10
10
|
scrollbarStyle?: (text: string) => string;
|
|
11
11
|
scrollbarHideDelayMs?: number;
|
|
12
12
|
}
|
|
13
|
+
export interface ScrollViewScrollToOptions {
|
|
14
|
+
/** Keep follow-end disabled even when the target is the current content end. */
|
|
15
|
+
disableFollow?: boolean;
|
|
16
|
+
}
|
|
13
17
|
export declare class ScrollView extends Container {
|
|
14
18
|
private readonly child;
|
|
15
19
|
private readonly followEnd;
|
|
@@ -22,6 +26,7 @@ export declare class ScrollView extends Container {
|
|
|
22
26
|
private contentHeight;
|
|
23
27
|
private currentViewportHeight;
|
|
24
28
|
private followingEnd;
|
|
29
|
+
private followSuppressedAtEnd;
|
|
25
30
|
private requestRenderCallback;
|
|
26
31
|
private transientScrollbarVisible;
|
|
27
32
|
private scrollbarActive;
|
|
@@ -37,7 +42,7 @@ export declare class ScrollView extends Container {
|
|
|
37
42
|
private markScrollbarActivity;
|
|
38
43
|
private hideTransientScrollbar;
|
|
39
44
|
setScrollbarActive(active: boolean): void;
|
|
40
|
-
scrollTo(scrollTop: number): void;
|
|
45
|
+
scrollTo(scrollTop: number, options?: ScrollViewScrollToOptions): void;
|
|
41
46
|
scrollBy(lines: number): number;
|
|
42
47
|
scrollToStart(): void;
|
|
43
48
|
scrollToEnd(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-view.d.ts","sourceRoot":"","sources":["../../src/components/scroll-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,qBAAa,UAAW,SAAQ,SAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,kBAAkB,CAA6B;IAEvD,YAAY,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,iBAAsB,EAchE;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,SAAS,IAAI,mBAAmB,CAEnC;IAED,IAAI,kBAAkB,IAAI,OAAO,CAKhC;IAED,YAAY,CAAC,SAAS,EAAE,mBAAmB,GAAG,IAAI,CAMjD;IAED,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErC;IAED,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAO9B,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAIxC;IAED,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAShC;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAc9B;IAED,aAAa,IAAI,IAAI,CAUpB;IAED,WAAW,IAAI,IAAI,CASlB;IAED,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,IAAI,GAAG,IAAI,CAS3F;IAEQ,QAAQ,CAAC,UAAU,EAAE,SAAS,GAAG,IAAI,CAE7C;IAEQ,WAAW,CAAC,UAAU,EAAE,SAAS,GAAG,IAAI,CAEhD;IAEQ,KAAK,IAAI,IAAI,CAErB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAIvC;IAED,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAEhC;CACD","sourcesContent":["import { LAYOUT_NODE, type ScrollLayoutNode } from \"../layout-node.ts\";\nimport { type Component, Container } from \"../tui.ts\";\n\nexport type ScrollViewScrollbar = \"hidden\" | \"auto\" | \"always\";\n\nexport interface ScrollViewOptions {\n\taxis?: \"vertical\";\n\tfollow?: \"none\" | \"end\";\n\tprimary?: boolean;\n\toverscroll?: \"chain\" | \"contain\";\n\tscrollbar?: ScrollViewScrollbar;\n\tscrollbarStyle?: (text: string) => string;\n\tscrollbarHideDelayMs?: number;\n}\n\nexport class ScrollView extends Container {\n\tprivate readonly child: Component;\n\tprivate readonly followEnd: boolean;\n\treadonly primary: boolean;\n\treadonly overscroll: \"chain\" | \"contain\";\n\treadonly scrollbarStyle: (text: string) => string;\n\tprivate currentScrollbar: ScrollViewScrollbar;\n\tprivate readonly scrollbarHideDelayMs: number;\n\tprivate currentScrollTop = 0;\n\tprivate contentHeight = 0;\n\tprivate currentViewportHeight = 0;\n\tprivate followingEnd: boolean;\n\tprivate requestRenderCallback: (() => void) | undefined;\n\tprivate transientScrollbarVisible = false;\n\tprivate scrollbarActive = false;\n\tprivate scrollbarHideTimer: NodeJS.Timeout | undefined;\n\n\tconstructor(component: Component, options: ScrollViewOptions = {}) {\n\t\tsuper();\n\t\tif (options.axis !== undefined && options.axis !== \"vertical\") {\n\t\t\tthrow new Error(`Unsupported ScrollView axis: ${options.axis}`);\n\t\t}\n\t\tthis.child = component;\n\t\tthis.children.push(component);\n\t\tthis.followEnd = (options.follow ?? \"none\") === \"end\";\n\t\tthis.followingEnd = this.followEnd;\n\t\tthis.primary = options.primary ?? false;\n\t\tthis.overscroll = options.overscroll ?? \"chain\";\n\t\tthis.currentScrollbar = options.scrollbar ?? \"hidden\";\n\t\tthis.scrollbarStyle = options.scrollbarStyle ?? ((text) => `\\x1b[100m${text}\\x1b[49m`);\n\t\tthis.scrollbarHideDelayMs = Math.max(0, Math.floor(options.scrollbarHideDelayMs ?? 1000));\n\t}\n\n\tget scrollTop(): number {\n\t\treturn this.currentScrollTop;\n\t}\n\n\tget isFollowingEnd(): boolean {\n\t\treturn this.followingEnd;\n\t}\n\n\tget viewportHeight(): number {\n\t\treturn this.currentViewportHeight;\n\t}\n\n\tget scrollbar(): ScrollViewScrollbar {\n\t\treturn this.currentScrollbar;\n\t}\n\n\tget isScrollbarVisible(): boolean {\n\t\tif (this.scrollbar === \"always\") return this.currentViewportHeight > 0;\n\t\treturn (\n\t\t\tthis.scrollbar === \"auto\" && this.contentHeight > this.currentViewportHeight && this.transientScrollbarVisible\n\t\t);\n\t}\n\n\tsetScrollbar(scrollbar: ScrollViewScrollbar): void {\n\t\tif (scrollbar === this.currentScrollbar) return;\n\t\tthis.currentScrollbar = scrollbar;\n\t\tif (scrollbar !== \"auto\") this.hideTransientScrollbar();\n\t\telse if (this.scrollbarActive) this.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tgetContentWidth(width: number): number {\n\t\treturn this.scrollbar === \"always\" && width > 1 ? width - 1 : width;\n\t}\n\n\tprivate markScrollbarActivity(): void {\n\t\tif (this.scrollbar !== \"auto\" || this.contentHeight <= this.currentViewportHeight) return;\n\t\tthis.transientScrollbarVisible = true;\n\t\tif (this.scrollbarHideTimer) {\n\t\t\tclearTimeout(this.scrollbarHideTimer);\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t}\n\t\tif (this.scrollbarActive) return;\n\t\tthis.scrollbarHideTimer = setTimeout(() => {\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t\tthis.transientScrollbarVisible = false;\n\t\t\tthis.requestRenderCallback?.();\n\t\t}, this.scrollbarHideDelayMs);\n\t\tthis.scrollbarHideTimer.unref();\n\t}\n\n\tprivate hideTransientScrollbar(): void {\n\t\tthis.transientScrollbarVisible = false;\n\t\tif (!this.scrollbarHideTimer) return;\n\t\tclearTimeout(this.scrollbarHideTimer);\n\t\tthis.scrollbarHideTimer = undefined;\n\t}\n\n\tsetScrollbarActive(active: boolean): void {\n\t\tif (active === this.scrollbarActive) return;\n\t\tthis.scrollbarActive = active;\n\t\tthis.markScrollbarActivity();\n\t}\n\n\tscrollTo(scrollTop: number): void {\n\t\tconst requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : this.currentScrollTop;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, requested));\n\t\tif (next === this.currentScrollTop) return;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd && next === maxScrollTop;\n\t\tthis.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tscrollBy(lines: number): number {\n\t\tconst requested = Number.isFinite(lines) ? Math.trunc(lines) : 0;\n\t\tif (requested === 0) return 0;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst start = this.followingEnd ? maxScrollTop : this.currentScrollTop;\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, start + requested));\n\t\tconst moved = next - start;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd && next === maxScrollTop;\n\t\tif (moved !== 0) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t\treturn requested - moved;\n\t}\n\n\tscrollToStart(): void {\n\t\tconst changed =\n\t\t\tthis.currentScrollTop !== 0 ||\n\t\t\tthis.followingEnd !== (this.followEnd && this.contentHeight <= this.currentViewportHeight);\n\t\tthis.currentScrollTop = 0;\n\t\tthis.followingEnd = this.followEnd && this.contentHeight <= this.currentViewportHeight;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tscrollToEnd(): void {\n\t\tconst next = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst changed = this.currentScrollTop !== next || this.followingEnd !== this.followEnd;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tupdateLayout(contentHeight: number, viewportHeight: number, requestRender: () => void): void {\n\t\tthis.contentHeight = Math.max(0, Math.floor(contentHeight));\n\t\tthis.currentViewportHeight = Math.max(0, Math.floor(viewportHeight));\n\t\tthis.requestRenderCallback = requestRender;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tif (this.followingEnd) this.currentScrollTop = maxScrollTop;\n\t\telse this.currentScrollTop = Math.max(0, Math.min(this.currentScrollTop, maxScrollTop));\n\t\tif (this.followEnd && this.currentScrollTop === maxScrollTop) this.followingEnd = true;\n\t\tif (this.contentHeight <= this.currentViewportHeight) this.hideTransientScrollbar();\n\t}\n\n\toverride addChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView has exactly one child\");\n\t}\n\n\toverride removeChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView child cannot be removed\");\n\t}\n\n\toverride clear(): void {\n\t\tthrow new Error(\"ScrollView child cannot be cleared\");\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst contentWidth = this.getContentWidth(width);\n\t\tconst lines = this.child.render(contentWidth);\n\t\treturn contentWidth === width ? lines : lines.map((line) => `${line} `);\n\t}\n\n\t[LAYOUT_NODE](): ScrollLayoutNode {\n\t\treturn { type: \"scroll\", component: this.child, state: this };\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scroll-view.d.ts","sourceRoot":"","sources":["../../src/components/scroll-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,yBAAyB;IACzC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,UAAW,SAAQ,SAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,kBAAkB,CAA6B;IAEvD,YAAY,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,iBAAsB,EAchE;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,SAAS,IAAI,mBAAmB,CAEnC;IAED,IAAI,kBAAkB,IAAI,OAAO,CAKhC;IAED,YAAY,CAAC,SAAS,EAAE,mBAAmB,GAAG,IAAI,CAMjD;IAED,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErC;IAED,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAO9B,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAIxC;IAED,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B,GAAG,IAAI,CAmBzE;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAc9B;IAED,aAAa,IAAI,IAAI,CAWpB;IAED,WAAW,IAAI,IAAI,CAUlB;IAED,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,IAAI,GAAG,IAAI,CAY3F;IAEQ,QAAQ,CAAC,UAAU,EAAE,SAAS,GAAG,IAAI,CAE7C;IAEQ,WAAW,CAAC,UAAU,EAAE,SAAS,GAAG,IAAI,CAEhD;IAEQ,KAAK,IAAI,IAAI,CAErB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAIvC;IAED,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAEhC;CACD","sourcesContent":["import { LAYOUT_NODE, type ScrollLayoutNode } from \"../layout-node.ts\";\nimport { type Component, Container } from \"../tui.ts\";\n\nexport type ScrollViewScrollbar = \"hidden\" | \"auto\" | \"always\";\n\nexport interface ScrollViewOptions {\n\taxis?: \"vertical\";\n\tfollow?: \"none\" | \"end\";\n\tprimary?: boolean;\n\toverscroll?: \"chain\" | \"contain\";\n\tscrollbar?: ScrollViewScrollbar;\n\tscrollbarStyle?: (text: string) => string;\n\tscrollbarHideDelayMs?: number;\n}\n\nexport interface ScrollViewScrollToOptions {\n\t/** Keep follow-end disabled even when the target is the current content end. */\n\tdisableFollow?: boolean;\n}\n\nexport class ScrollView extends Container {\n\tprivate readonly child: Component;\n\tprivate readonly followEnd: boolean;\n\treadonly primary: boolean;\n\treadonly overscroll: \"chain\" | \"contain\";\n\treadonly scrollbarStyle: (text: string) => string;\n\tprivate currentScrollbar: ScrollViewScrollbar;\n\tprivate readonly scrollbarHideDelayMs: number;\n\tprivate currentScrollTop = 0;\n\tprivate contentHeight = 0;\n\tprivate currentViewportHeight = 0;\n\tprivate followingEnd: boolean;\n\tprivate followSuppressedAtEnd = false;\n\tprivate requestRenderCallback: (() => void) | undefined;\n\tprivate transientScrollbarVisible = false;\n\tprivate scrollbarActive = false;\n\tprivate scrollbarHideTimer: NodeJS.Timeout | undefined;\n\n\tconstructor(component: Component, options: ScrollViewOptions = {}) {\n\t\tsuper();\n\t\tif (options.axis !== undefined && options.axis !== \"vertical\") {\n\t\t\tthrow new Error(`Unsupported ScrollView axis: ${options.axis}`);\n\t\t}\n\t\tthis.child = component;\n\t\tthis.children.push(component);\n\t\tthis.followEnd = (options.follow ?? \"none\") === \"end\";\n\t\tthis.followingEnd = this.followEnd;\n\t\tthis.primary = options.primary ?? false;\n\t\tthis.overscroll = options.overscroll ?? \"chain\";\n\t\tthis.currentScrollbar = options.scrollbar ?? \"hidden\";\n\t\tthis.scrollbarStyle = options.scrollbarStyle ?? ((text) => `\\x1b[100m${text}\\x1b[49m`);\n\t\tthis.scrollbarHideDelayMs = Math.max(0, Math.floor(options.scrollbarHideDelayMs ?? 1000));\n\t}\n\n\tget scrollTop(): number {\n\t\treturn this.currentScrollTop;\n\t}\n\n\tget isFollowingEnd(): boolean {\n\t\treturn this.followingEnd;\n\t}\n\n\tget viewportHeight(): number {\n\t\treturn this.currentViewportHeight;\n\t}\n\n\tget scrollbar(): ScrollViewScrollbar {\n\t\treturn this.currentScrollbar;\n\t}\n\n\tget isScrollbarVisible(): boolean {\n\t\tif (this.scrollbar === \"always\") return this.currentViewportHeight > 0;\n\t\treturn (\n\t\t\tthis.scrollbar === \"auto\" && this.contentHeight > this.currentViewportHeight && this.transientScrollbarVisible\n\t\t);\n\t}\n\n\tsetScrollbar(scrollbar: ScrollViewScrollbar): void {\n\t\tif (scrollbar === this.currentScrollbar) return;\n\t\tthis.currentScrollbar = scrollbar;\n\t\tif (scrollbar !== \"auto\") this.hideTransientScrollbar();\n\t\telse if (this.scrollbarActive) this.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tgetContentWidth(width: number): number {\n\t\treturn this.scrollbar === \"always\" && width > 1 ? width - 1 : width;\n\t}\n\n\tprivate markScrollbarActivity(): void {\n\t\tif (this.scrollbar !== \"auto\" || this.contentHeight <= this.currentViewportHeight) return;\n\t\tthis.transientScrollbarVisible = true;\n\t\tif (this.scrollbarHideTimer) {\n\t\t\tclearTimeout(this.scrollbarHideTimer);\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t}\n\t\tif (this.scrollbarActive) return;\n\t\tthis.scrollbarHideTimer = setTimeout(() => {\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t\tthis.transientScrollbarVisible = false;\n\t\t\tthis.requestRenderCallback?.();\n\t\t}, this.scrollbarHideDelayMs);\n\t\tthis.scrollbarHideTimer.unref();\n\t}\n\n\tprivate hideTransientScrollbar(): void {\n\t\tthis.transientScrollbarVisible = false;\n\t\tif (!this.scrollbarHideTimer) return;\n\t\tclearTimeout(this.scrollbarHideTimer);\n\t\tthis.scrollbarHideTimer = undefined;\n\t}\n\n\tsetScrollbarActive(active: boolean): void {\n\t\tif (active === this.scrollbarActive) return;\n\t\tthis.scrollbarActive = active;\n\t\tthis.markScrollbarActivity();\n\t}\n\n\tscrollTo(scrollTop: number, options: ScrollViewScrollToOptions = {}): void {\n\t\tconst requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : this.currentScrollTop;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, requested));\n\t\tconst nextFollowSuppressedAtEnd = options.disableFollow === true && next === maxScrollTop;\n\t\tconst nextFollowingEnd = !nextFollowSuppressedAtEnd && this.followEnd && next === maxScrollTop;\n\t\tif (\n\t\t\tnext === this.currentScrollTop &&\n\t\t\tnextFollowingEnd === this.followingEnd &&\n\t\t\tnextFollowSuppressedAtEnd === this.followSuppressedAtEnd\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tconst moved = next !== this.currentScrollTop;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = nextFollowingEnd;\n\t\tthis.followSuppressedAtEnd = nextFollowSuppressedAtEnd;\n\t\tif (moved) this.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tscrollBy(lines: number): number {\n\t\tconst requested = Number.isFinite(lines) ? Math.trunc(lines) : 0;\n\t\tif (requested === 0) return 0;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst start = this.followingEnd ? maxScrollTop : this.currentScrollTop;\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, start + requested));\n\t\tconst moved = next - start;\n\t\tconst wasFollowingEnd = this.followingEnd;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd && next === maxScrollTop;\n\t\tthis.followSuppressedAtEnd = false;\n\t\tif (moved !== 0) this.markScrollbarActivity();\n\t\tif (moved !== 0 || this.followingEnd !== wasFollowingEnd) this.requestRenderCallback?.();\n\t\treturn requested - moved;\n\t}\n\n\tscrollToStart(): void {\n\t\tconst changed =\n\t\t\tthis.currentScrollTop !== 0 ||\n\t\t\tthis.followingEnd !== (this.followEnd && this.contentHeight <= this.currentViewportHeight);\n\t\tthis.currentScrollTop = 0;\n\t\tthis.followingEnd = this.followEnd && this.contentHeight <= this.currentViewportHeight;\n\t\tthis.followSuppressedAtEnd = false;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tscrollToEnd(): void {\n\t\tconst next = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst changed = this.currentScrollTop !== next || this.followingEnd !== this.followEnd;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd;\n\t\tthis.followSuppressedAtEnd = false;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tupdateLayout(contentHeight: number, viewportHeight: number, requestRender: () => void): void {\n\t\tthis.contentHeight = Math.max(0, Math.floor(contentHeight));\n\t\tthis.currentViewportHeight = Math.max(0, Math.floor(viewportHeight));\n\t\tthis.requestRenderCallback = requestRender;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tif (this.followingEnd) this.currentScrollTop = maxScrollTop;\n\t\telse this.currentScrollTop = Math.max(0, Math.min(this.currentScrollTop, maxScrollTop));\n\t\tif (this.currentScrollTop < maxScrollTop) this.followSuppressedAtEnd = false;\n\t\tif (this.followEnd && this.currentScrollTop === maxScrollTop && !this.followSuppressedAtEnd) {\n\t\t\tthis.followingEnd = true;\n\t\t}\n\t\tif (this.contentHeight <= this.currentViewportHeight) this.hideTransientScrollbar();\n\t}\n\n\toverride addChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView has exactly one child\");\n\t}\n\n\toverride removeChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView child cannot be removed\");\n\t}\n\n\toverride clear(): void {\n\t\tthrow new Error(\"ScrollView child cannot be cleared\");\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst contentWidth = this.getContentWidth(width);\n\t\tconst lines = this.child.render(contentWidth);\n\t\treturn contentWidth === width ? lines : lines.map((line) => `${line} `);\n\t}\n\n\t[LAYOUT_NODE](): ScrollLayoutNode {\n\t\treturn { type: \"scroll\", component: this.child, state: this };\n\t}\n}\n"]}
|
|
@@ -12,6 +12,7 @@ export class ScrollView extends Container {
|
|
|
12
12
|
contentHeight = 0;
|
|
13
13
|
currentViewportHeight = 0;
|
|
14
14
|
followingEnd;
|
|
15
|
+
followSuppressedAtEnd = false;
|
|
15
16
|
requestRenderCallback;
|
|
16
17
|
transientScrollbarVisible = false;
|
|
17
18
|
scrollbarActive = false;
|
|
@@ -91,15 +92,23 @@ export class ScrollView extends Container {
|
|
|
91
92
|
this.scrollbarActive = active;
|
|
92
93
|
this.markScrollbarActivity();
|
|
93
94
|
}
|
|
94
|
-
scrollTo(scrollTop) {
|
|
95
|
+
scrollTo(scrollTop, options = {}) {
|
|
95
96
|
const requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : this.currentScrollTop;
|
|
96
97
|
const maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);
|
|
97
98
|
const next = Math.max(0, Math.min(maxScrollTop, requested));
|
|
98
|
-
|
|
99
|
+
const nextFollowSuppressedAtEnd = options.disableFollow === true && next === maxScrollTop;
|
|
100
|
+
const nextFollowingEnd = !nextFollowSuppressedAtEnd && this.followEnd && next === maxScrollTop;
|
|
101
|
+
if (next === this.currentScrollTop &&
|
|
102
|
+
nextFollowingEnd === this.followingEnd &&
|
|
103
|
+
nextFollowSuppressedAtEnd === this.followSuppressedAtEnd) {
|
|
99
104
|
return;
|
|
105
|
+
}
|
|
106
|
+
const moved = next !== this.currentScrollTop;
|
|
100
107
|
this.currentScrollTop = next;
|
|
101
|
-
this.followingEnd =
|
|
102
|
-
this.
|
|
108
|
+
this.followingEnd = nextFollowingEnd;
|
|
109
|
+
this.followSuppressedAtEnd = nextFollowSuppressedAtEnd;
|
|
110
|
+
if (moved)
|
|
111
|
+
this.markScrollbarActivity();
|
|
103
112
|
this.requestRenderCallback?.();
|
|
104
113
|
}
|
|
105
114
|
scrollBy(lines) {
|
|
@@ -110,12 +119,14 @@ export class ScrollView extends Container {
|
|
|
110
119
|
const start = this.followingEnd ? maxScrollTop : this.currentScrollTop;
|
|
111
120
|
const next = Math.max(0, Math.min(maxScrollTop, start + requested));
|
|
112
121
|
const moved = next - start;
|
|
122
|
+
const wasFollowingEnd = this.followingEnd;
|
|
113
123
|
this.currentScrollTop = next;
|
|
114
124
|
this.followingEnd = this.followEnd && next === maxScrollTop;
|
|
115
|
-
|
|
125
|
+
this.followSuppressedAtEnd = false;
|
|
126
|
+
if (moved !== 0)
|
|
116
127
|
this.markScrollbarActivity();
|
|
128
|
+
if (moved !== 0 || this.followingEnd !== wasFollowingEnd)
|
|
117
129
|
this.requestRenderCallback?.();
|
|
118
|
-
}
|
|
119
130
|
return requested - moved;
|
|
120
131
|
}
|
|
121
132
|
scrollToStart() {
|
|
@@ -123,6 +134,7 @@ export class ScrollView extends Container {
|
|
|
123
134
|
this.followingEnd !== (this.followEnd && this.contentHeight <= this.currentViewportHeight);
|
|
124
135
|
this.currentScrollTop = 0;
|
|
125
136
|
this.followingEnd = this.followEnd && this.contentHeight <= this.currentViewportHeight;
|
|
137
|
+
this.followSuppressedAtEnd = false;
|
|
126
138
|
if (changed) {
|
|
127
139
|
this.markScrollbarActivity();
|
|
128
140
|
this.requestRenderCallback?.();
|
|
@@ -133,6 +145,7 @@ export class ScrollView extends Container {
|
|
|
133
145
|
const changed = this.currentScrollTop !== next || this.followingEnd !== this.followEnd;
|
|
134
146
|
this.currentScrollTop = next;
|
|
135
147
|
this.followingEnd = this.followEnd;
|
|
148
|
+
this.followSuppressedAtEnd = false;
|
|
136
149
|
if (changed) {
|
|
137
150
|
this.markScrollbarActivity();
|
|
138
151
|
this.requestRenderCallback?.();
|
|
@@ -147,8 +160,11 @@ export class ScrollView extends Container {
|
|
|
147
160
|
this.currentScrollTop = maxScrollTop;
|
|
148
161
|
else
|
|
149
162
|
this.currentScrollTop = Math.max(0, Math.min(this.currentScrollTop, maxScrollTop));
|
|
150
|
-
if (this.
|
|
163
|
+
if (this.currentScrollTop < maxScrollTop)
|
|
164
|
+
this.followSuppressedAtEnd = false;
|
|
165
|
+
if (this.followEnd && this.currentScrollTop === maxScrollTop && !this.followSuppressedAtEnd) {
|
|
151
166
|
this.followingEnd = true;
|
|
167
|
+
}
|
|
152
168
|
if (this.contentHeight <= this.currentViewportHeight)
|
|
153
169
|
this.hideTransientScrollbar();
|
|
154
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-view.js","sourceRoot":"","sources":["../../src/components/scroll-view.ts"],"names":[],"mappings":"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","sourcesContent":["import { LAYOUT_NODE, type ScrollLayoutNode } from \"../layout-node.ts\";\nimport { type Component, Container } from \"../tui.ts\";\n\nexport type ScrollViewScrollbar = \"hidden\" | \"auto\" | \"always\";\n\nexport interface ScrollViewOptions {\n\taxis?: \"vertical\";\n\tfollow?: \"none\" | \"end\";\n\tprimary?: boolean;\n\toverscroll?: \"chain\" | \"contain\";\n\tscrollbar?: ScrollViewScrollbar;\n\tscrollbarStyle?: (text: string) => string;\n\tscrollbarHideDelayMs?: number;\n}\n\nexport class ScrollView extends Container {\n\tprivate readonly child: Component;\n\tprivate readonly followEnd: boolean;\n\treadonly primary: boolean;\n\treadonly overscroll: \"chain\" | \"contain\";\n\treadonly scrollbarStyle: (text: string) => string;\n\tprivate currentScrollbar: ScrollViewScrollbar;\n\tprivate readonly scrollbarHideDelayMs: number;\n\tprivate currentScrollTop = 0;\n\tprivate contentHeight = 0;\n\tprivate currentViewportHeight = 0;\n\tprivate followingEnd: boolean;\n\tprivate requestRenderCallback: (() => void) | undefined;\n\tprivate transientScrollbarVisible = false;\n\tprivate scrollbarActive = false;\n\tprivate scrollbarHideTimer: NodeJS.Timeout | undefined;\n\n\tconstructor(component: Component, options: ScrollViewOptions = {}) {\n\t\tsuper();\n\t\tif (options.axis !== undefined && options.axis !== \"vertical\") {\n\t\t\tthrow new Error(`Unsupported ScrollView axis: ${options.axis}`);\n\t\t}\n\t\tthis.child = component;\n\t\tthis.children.push(component);\n\t\tthis.followEnd = (options.follow ?? \"none\") === \"end\";\n\t\tthis.followingEnd = this.followEnd;\n\t\tthis.primary = options.primary ?? false;\n\t\tthis.overscroll = options.overscroll ?? \"chain\";\n\t\tthis.currentScrollbar = options.scrollbar ?? \"hidden\";\n\t\tthis.scrollbarStyle = options.scrollbarStyle ?? ((text) => `\\x1b[100m${text}\\x1b[49m`);\n\t\tthis.scrollbarHideDelayMs = Math.max(0, Math.floor(options.scrollbarHideDelayMs ?? 1000));\n\t}\n\n\tget scrollTop(): number {\n\t\treturn this.currentScrollTop;\n\t}\n\n\tget isFollowingEnd(): boolean {\n\t\treturn this.followingEnd;\n\t}\n\n\tget viewportHeight(): number {\n\t\treturn this.currentViewportHeight;\n\t}\n\n\tget scrollbar(): ScrollViewScrollbar {\n\t\treturn this.currentScrollbar;\n\t}\n\n\tget isScrollbarVisible(): boolean {\n\t\tif (this.scrollbar === \"always\") return this.currentViewportHeight > 0;\n\t\treturn (\n\t\t\tthis.scrollbar === \"auto\" && this.contentHeight > this.currentViewportHeight && this.transientScrollbarVisible\n\t\t);\n\t}\n\n\tsetScrollbar(scrollbar: ScrollViewScrollbar): void {\n\t\tif (scrollbar === this.currentScrollbar) return;\n\t\tthis.currentScrollbar = scrollbar;\n\t\tif (scrollbar !== \"auto\") this.hideTransientScrollbar();\n\t\telse if (this.scrollbarActive) this.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tgetContentWidth(width: number): number {\n\t\treturn this.scrollbar === \"always\" && width > 1 ? width - 1 : width;\n\t}\n\n\tprivate markScrollbarActivity(): void {\n\t\tif (this.scrollbar !== \"auto\" || this.contentHeight <= this.currentViewportHeight) return;\n\t\tthis.transientScrollbarVisible = true;\n\t\tif (this.scrollbarHideTimer) {\n\t\t\tclearTimeout(this.scrollbarHideTimer);\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t}\n\t\tif (this.scrollbarActive) return;\n\t\tthis.scrollbarHideTimer = setTimeout(() => {\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t\tthis.transientScrollbarVisible = false;\n\t\t\tthis.requestRenderCallback?.();\n\t\t}, this.scrollbarHideDelayMs);\n\t\tthis.scrollbarHideTimer.unref();\n\t}\n\n\tprivate hideTransientScrollbar(): void {\n\t\tthis.transientScrollbarVisible = false;\n\t\tif (!this.scrollbarHideTimer) return;\n\t\tclearTimeout(this.scrollbarHideTimer);\n\t\tthis.scrollbarHideTimer = undefined;\n\t}\n\n\tsetScrollbarActive(active: boolean): void {\n\t\tif (active === this.scrollbarActive) return;\n\t\tthis.scrollbarActive = active;\n\t\tthis.markScrollbarActivity();\n\t}\n\n\tscrollTo(scrollTop: number): void {\n\t\tconst requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : this.currentScrollTop;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, requested));\n\t\tif (next === this.currentScrollTop) return;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd && next === maxScrollTop;\n\t\tthis.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tscrollBy(lines: number): number {\n\t\tconst requested = Number.isFinite(lines) ? Math.trunc(lines) : 0;\n\t\tif (requested === 0) return 0;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst start = this.followingEnd ? maxScrollTop : this.currentScrollTop;\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, start + requested));\n\t\tconst moved = next - start;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd && next === maxScrollTop;\n\t\tif (moved !== 0) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t\treturn requested - moved;\n\t}\n\n\tscrollToStart(): void {\n\t\tconst changed =\n\t\t\tthis.currentScrollTop !== 0 ||\n\t\t\tthis.followingEnd !== (this.followEnd && this.contentHeight <= this.currentViewportHeight);\n\t\tthis.currentScrollTop = 0;\n\t\tthis.followingEnd = this.followEnd && this.contentHeight <= this.currentViewportHeight;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tscrollToEnd(): void {\n\t\tconst next = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst changed = this.currentScrollTop !== next || this.followingEnd !== this.followEnd;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tupdateLayout(contentHeight: number, viewportHeight: number, requestRender: () => void): void {\n\t\tthis.contentHeight = Math.max(0, Math.floor(contentHeight));\n\t\tthis.currentViewportHeight = Math.max(0, Math.floor(viewportHeight));\n\t\tthis.requestRenderCallback = requestRender;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tif (this.followingEnd) this.currentScrollTop = maxScrollTop;\n\t\telse this.currentScrollTop = Math.max(0, Math.min(this.currentScrollTop, maxScrollTop));\n\t\tif (this.followEnd && this.currentScrollTop === maxScrollTop) this.followingEnd = true;\n\t\tif (this.contentHeight <= this.currentViewportHeight) this.hideTransientScrollbar();\n\t}\n\n\toverride addChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView has exactly one child\");\n\t}\n\n\toverride removeChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView child cannot be removed\");\n\t}\n\n\toverride clear(): void {\n\t\tthrow new Error(\"ScrollView child cannot be cleared\");\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst contentWidth = this.getContentWidth(width);\n\t\tconst lines = this.child.render(contentWidth);\n\t\treturn contentWidth === width ? lines : lines.map((line) => `${line} `);\n\t}\n\n\t[LAYOUT_NODE](): ScrollLayoutNode {\n\t\treturn { type: \"scroll\", component: this.child, state: this };\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scroll-view.js","sourceRoot":"","sources":["../../src/components/scroll-view.ts"],"names":[],"mappings":"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","sourcesContent":["import { LAYOUT_NODE, type ScrollLayoutNode } from \"../layout-node.ts\";\nimport { type Component, Container } from \"../tui.ts\";\n\nexport type ScrollViewScrollbar = \"hidden\" | \"auto\" | \"always\";\n\nexport interface ScrollViewOptions {\n\taxis?: \"vertical\";\n\tfollow?: \"none\" | \"end\";\n\tprimary?: boolean;\n\toverscroll?: \"chain\" | \"contain\";\n\tscrollbar?: ScrollViewScrollbar;\n\tscrollbarStyle?: (text: string) => string;\n\tscrollbarHideDelayMs?: number;\n}\n\nexport interface ScrollViewScrollToOptions {\n\t/** Keep follow-end disabled even when the target is the current content end. */\n\tdisableFollow?: boolean;\n}\n\nexport class ScrollView extends Container {\n\tprivate readonly child: Component;\n\tprivate readonly followEnd: boolean;\n\treadonly primary: boolean;\n\treadonly overscroll: \"chain\" | \"contain\";\n\treadonly scrollbarStyle: (text: string) => string;\n\tprivate currentScrollbar: ScrollViewScrollbar;\n\tprivate readonly scrollbarHideDelayMs: number;\n\tprivate currentScrollTop = 0;\n\tprivate contentHeight = 0;\n\tprivate currentViewportHeight = 0;\n\tprivate followingEnd: boolean;\n\tprivate followSuppressedAtEnd = false;\n\tprivate requestRenderCallback: (() => void) | undefined;\n\tprivate transientScrollbarVisible = false;\n\tprivate scrollbarActive = false;\n\tprivate scrollbarHideTimer: NodeJS.Timeout | undefined;\n\n\tconstructor(component: Component, options: ScrollViewOptions = {}) {\n\t\tsuper();\n\t\tif (options.axis !== undefined && options.axis !== \"vertical\") {\n\t\t\tthrow new Error(`Unsupported ScrollView axis: ${options.axis}`);\n\t\t}\n\t\tthis.child = component;\n\t\tthis.children.push(component);\n\t\tthis.followEnd = (options.follow ?? \"none\") === \"end\";\n\t\tthis.followingEnd = this.followEnd;\n\t\tthis.primary = options.primary ?? false;\n\t\tthis.overscroll = options.overscroll ?? \"chain\";\n\t\tthis.currentScrollbar = options.scrollbar ?? \"hidden\";\n\t\tthis.scrollbarStyle = options.scrollbarStyle ?? ((text) => `\\x1b[100m${text}\\x1b[49m`);\n\t\tthis.scrollbarHideDelayMs = Math.max(0, Math.floor(options.scrollbarHideDelayMs ?? 1000));\n\t}\n\n\tget scrollTop(): number {\n\t\treturn this.currentScrollTop;\n\t}\n\n\tget isFollowingEnd(): boolean {\n\t\treturn this.followingEnd;\n\t}\n\n\tget viewportHeight(): number {\n\t\treturn this.currentViewportHeight;\n\t}\n\n\tget scrollbar(): ScrollViewScrollbar {\n\t\treturn this.currentScrollbar;\n\t}\n\n\tget isScrollbarVisible(): boolean {\n\t\tif (this.scrollbar === \"always\") return this.currentViewportHeight > 0;\n\t\treturn (\n\t\t\tthis.scrollbar === \"auto\" && this.contentHeight > this.currentViewportHeight && this.transientScrollbarVisible\n\t\t);\n\t}\n\n\tsetScrollbar(scrollbar: ScrollViewScrollbar): void {\n\t\tif (scrollbar === this.currentScrollbar) return;\n\t\tthis.currentScrollbar = scrollbar;\n\t\tif (scrollbar !== \"auto\") this.hideTransientScrollbar();\n\t\telse if (this.scrollbarActive) this.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tgetContentWidth(width: number): number {\n\t\treturn this.scrollbar === \"always\" && width > 1 ? width - 1 : width;\n\t}\n\n\tprivate markScrollbarActivity(): void {\n\t\tif (this.scrollbar !== \"auto\" || this.contentHeight <= this.currentViewportHeight) return;\n\t\tthis.transientScrollbarVisible = true;\n\t\tif (this.scrollbarHideTimer) {\n\t\t\tclearTimeout(this.scrollbarHideTimer);\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t}\n\t\tif (this.scrollbarActive) return;\n\t\tthis.scrollbarHideTimer = setTimeout(() => {\n\t\t\tthis.scrollbarHideTimer = undefined;\n\t\t\tthis.transientScrollbarVisible = false;\n\t\t\tthis.requestRenderCallback?.();\n\t\t}, this.scrollbarHideDelayMs);\n\t\tthis.scrollbarHideTimer.unref();\n\t}\n\n\tprivate hideTransientScrollbar(): void {\n\t\tthis.transientScrollbarVisible = false;\n\t\tif (!this.scrollbarHideTimer) return;\n\t\tclearTimeout(this.scrollbarHideTimer);\n\t\tthis.scrollbarHideTimer = undefined;\n\t}\n\n\tsetScrollbarActive(active: boolean): void {\n\t\tif (active === this.scrollbarActive) return;\n\t\tthis.scrollbarActive = active;\n\t\tthis.markScrollbarActivity();\n\t}\n\n\tscrollTo(scrollTop: number, options: ScrollViewScrollToOptions = {}): void {\n\t\tconst requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : this.currentScrollTop;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, requested));\n\t\tconst nextFollowSuppressedAtEnd = options.disableFollow === true && next === maxScrollTop;\n\t\tconst nextFollowingEnd = !nextFollowSuppressedAtEnd && this.followEnd && next === maxScrollTop;\n\t\tif (\n\t\t\tnext === this.currentScrollTop &&\n\t\t\tnextFollowingEnd === this.followingEnd &&\n\t\t\tnextFollowSuppressedAtEnd === this.followSuppressedAtEnd\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tconst moved = next !== this.currentScrollTop;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = nextFollowingEnd;\n\t\tthis.followSuppressedAtEnd = nextFollowSuppressedAtEnd;\n\t\tif (moved) this.markScrollbarActivity();\n\t\tthis.requestRenderCallback?.();\n\t}\n\n\tscrollBy(lines: number): number {\n\t\tconst requested = Number.isFinite(lines) ? Math.trunc(lines) : 0;\n\t\tif (requested === 0) return 0;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst start = this.followingEnd ? maxScrollTop : this.currentScrollTop;\n\t\tconst next = Math.max(0, Math.min(maxScrollTop, start + requested));\n\t\tconst moved = next - start;\n\t\tconst wasFollowingEnd = this.followingEnd;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd && next === maxScrollTop;\n\t\tthis.followSuppressedAtEnd = false;\n\t\tif (moved !== 0) this.markScrollbarActivity();\n\t\tif (moved !== 0 || this.followingEnd !== wasFollowingEnd) this.requestRenderCallback?.();\n\t\treturn requested - moved;\n\t}\n\n\tscrollToStart(): void {\n\t\tconst changed =\n\t\t\tthis.currentScrollTop !== 0 ||\n\t\t\tthis.followingEnd !== (this.followEnd && this.contentHeight <= this.currentViewportHeight);\n\t\tthis.currentScrollTop = 0;\n\t\tthis.followingEnd = this.followEnd && this.contentHeight <= this.currentViewportHeight;\n\t\tthis.followSuppressedAtEnd = false;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tscrollToEnd(): void {\n\t\tconst next = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tconst changed = this.currentScrollTop !== next || this.followingEnd !== this.followEnd;\n\t\tthis.currentScrollTop = next;\n\t\tthis.followingEnd = this.followEnd;\n\t\tthis.followSuppressedAtEnd = false;\n\t\tif (changed) {\n\t\t\tthis.markScrollbarActivity();\n\t\t\tthis.requestRenderCallback?.();\n\t\t}\n\t}\n\n\tupdateLayout(contentHeight: number, viewportHeight: number, requestRender: () => void): void {\n\t\tthis.contentHeight = Math.max(0, Math.floor(contentHeight));\n\t\tthis.currentViewportHeight = Math.max(0, Math.floor(viewportHeight));\n\t\tthis.requestRenderCallback = requestRender;\n\t\tconst maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);\n\t\tif (this.followingEnd) this.currentScrollTop = maxScrollTop;\n\t\telse this.currentScrollTop = Math.max(0, Math.min(this.currentScrollTop, maxScrollTop));\n\t\tif (this.currentScrollTop < maxScrollTop) this.followSuppressedAtEnd = false;\n\t\tif (this.followEnd && this.currentScrollTop === maxScrollTop && !this.followSuppressedAtEnd) {\n\t\t\tthis.followingEnd = true;\n\t\t}\n\t\tif (this.contentHeight <= this.currentViewportHeight) this.hideTransientScrollbar();\n\t}\n\n\toverride addChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView has exactly one child\");\n\t}\n\n\toverride removeChild(_component: Component): void {\n\t\tthrow new Error(\"ScrollView child cannot be removed\");\n\t}\n\n\toverride clear(): void {\n\t\tthrow new Error(\"ScrollView child cannot be cleared\");\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst contentWidth = this.getContentWidth(width);\n\t\tconst lines = this.child.render(contentWidth);\n\t\treturn contentWidth === width ? lines : lines.map((line) => `${line} `);\n\t}\n\n\t[LAYOUT_NODE](): ScrollLayoutNode {\n\t\treturn { type: \"scroll\", component: this.child, state: this };\n\t}\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { Image, type ImageOptions, type ImageTheme } from "./components/image.ts
|
|
|
8
8
|
export { Input } from "./components/input.ts";
|
|
9
9
|
export { Loader, type LoaderIndicatorOptions } from "./components/loader.ts";
|
|
10
10
|
export { type DefaultTextStyle, Markdown, type MarkdownOptions, type MarkdownTheme } from "./components/markdown.ts";
|
|
11
|
-
export { ScrollView, type ScrollViewOptions, type ScrollViewScrollbar } from "./components/scroll-view.ts";
|
|
11
|
+
export { ScrollView, type ScrollViewOptions, type ScrollViewScrollbar, type ScrollViewScrollToOptions, } from "./components/scroll-view.ts";
|
|
12
12
|
export { type SelectItem, SelectList, type SelectListLayoutOptions, type SelectListTheme, type SelectListTruncatePrimaryContext, } from "./components/select-list.ts";
|
|
13
13
|
export { type SettingItem, SettingsList, type SettingsListTheme } from "./components/settings-list.ts";
|
|
14
14
|
export { Spacer } from "./components/spacer.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzD,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,4BAA4B,EAC5B,KAAK,YAAY,GACjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,KAAK,gBAAgB,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACrH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Core TUI interfaces and classes\n\nexport { Marked, type Token, type Tokens } from \"marked\";\n// Autocomplete support\nexport {\n\ttype AutocompleteItem,\n\ttype AutocompleteProvider,\n\ttype AutocompleteSuggestions,\n\tCombinedAutocompleteProvider,\n\ttype SlashCommand,\n} from \"./autocomplete.ts\";\n// Components\nexport { Box } from \"./components/box.ts\";\nexport { CancellableLoader } from \"./components/cancellable-loader.ts\";\nexport { Editor, type EditorOptions, type EditorTheme } from \"./components/editor.ts\";\nexport { HStack } from \"./components/h-stack.ts\";\nexport { Image, type ImageOptions, type ImageTheme } from \"./components/image.ts\";\nexport { Input } from \"./components/input.ts\";\nexport { Loader, type LoaderIndicatorOptions } from \"./components/loader.ts\";\nexport { type DefaultTextStyle, Markdown, type MarkdownOptions, type MarkdownTheme } from \"./components/markdown.ts\";\nexport {\n\tScrollView,\n\ttype ScrollViewOptions,\n\ttype ScrollViewScrollbar,\n\ttype ScrollViewScrollToOptions,\n} from \"./components/scroll-view.ts\";\nexport {\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\ttype SelectListTheme,\n\ttype SelectListTruncatePrimaryContext,\n} from \"./components/select-list.ts\";\nexport { type SettingItem, SettingsList, type SettingsListTheme } from \"./components/settings-list.ts\";\nexport { Spacer } from \"./components/spacer.ts\";\nexport { Text } from \"./components/text.ts\";\nexport { TruncatedText } from \"./components/truncated-text.ts\";\nexport {\n\ttype StackChild,\n\ttype StackEntry,\n\ttype StackEntryOptions,\n\ttype StackOptions,\n\tVStack,\n} from \"./components/v-stack.ts\";\n// Editor component interface (for custom editors)\nexport type { EditorComponent } from \"./editor-component.ts\";\n// Fuzzy matching\nexport { type FuzzyMatch, fuzzyFilter, fuzzyMatch } from \"./fuzzy.ts\";\n// Keybindings\nexport {\n\tgetKeybindings,\n\ttype Keybinding,\n\ttype KeybindingConflict,\n\ttype KeybindingDefinition,\n\ttype KeybindingDefinitions,\n\ttype Keybindings,\n\ttype KeybindingsConfig,\n\tKeybindingsManager,\n\tsetKeybindings,\n\tTUI_KEYBINDINGS,\n} from \"./keybindings.ts\";\n// Keyboard input handling\nexport {\n\tdecodeKittyPrintable,\n\tisKeyRelease,\n\tisKeyRepeat,\n\tisKittyProtocolActive,\n\tKey,\n\ttype KeyEventType,\n\ttype KeyId,\n\tmatchesKey,\n\tparseKey,\n\tsetKittyProtocolActive,\n} from \"./keys.ts\";\n// LaTeX rendering\nexport { type RenderLatexOptions, renderLatex } from \"./latex.ts\";\n// Input buffering for batch splitting\nexport { StdinBuffer, type StdinBufferEventMap, type StdinBufferOptions } from \"./stdin-buffer.ts\";\n// Terminal interface and implementations\nexport { ProcessTerminal, type Terminal } from \"./terminal.ts\";\n// Terminal colors\nexport {\n\tparseOsc11BackgroundColor,\n\tparseTerminalColorSchemeReport,\n\ttype RgbColor,\n\ttype TerminalColorScheme,\n} from \"./terminal-colors.ts\";\n// Terminal image support\nexport {\n\tallocateImageId,\n\ttype CellDimensions,\n\tcalculateImageRows,\n\tdeleteAllKittyImages,\n\tdeleteKittyImage,\n\tdetectCapabilities,\n\tencodeITerm2,\n\tencodeKitty,\n\tgetCapabilities,\n\tgetCellDimensions,\n\tgetGifDimensions,\n\tgetImageDimensions,\n\tgetJpegDimensions,\n\tgetPngDimensions,\n\tgetWebpDimensions,\n\thyperlink,\n\ttype ImageDimensions,\n\ttype ImageProtocol,\n\ttype ImageRenderOptions,\n\timageFallback,\n\trenderImage,\n\tresetCapabilitiesCache,\n\tsetCapabilities,\n\tsetCellDimensions,\n\ttype TerminalCapabilities,\n} from \"./terminal-image.ts\";\nexport {\n\ttype Component,\n\tContainer,\n\tCURSOR_MARKER,\n\tcompositeTuiLine,\n\ttype Focusable,\n\tisFocusable,\n\tisViewportTUI,\n\ttype OverlayAnchor,\n\ttype OverlayHandle,\n\ttype OverlayMargin,\n\ttype OverlayOptions,\n\ttype OverlayUnfocusOptions,\n\ttype SizeValue,\n\ttype TUI,\n\ttype TuiInputListener,\n\ttype TuiInputListenerResult,\n\ttype TuiMode,\n\ttype TuiStopOptions,\n\ttype ViewportTUI,\n} from \"./tui.ts\";\nexport { TuiAltScreen, type TuiAltScreenOptions } from \"./tui-alt-screen.ts\";\nexport { TuiMainScreen, type TuiMainScreenRenderState } from \"./tui-main-screen.ts\";\n// Utilities\nexport {\n\tgetOsc8LinkAtColumn,\n\tsliceByColumn,\n\tstripTerminalSequences,\n\ttruncateToWidth,\n\tvisibleWidth,\n\twrapTextWithAnsi,\n} from \"./utils.ts\";\n"]}
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { Image } from "./components/image.js";
|
|
|
11
11
|
export { Input } from "./components/input.js";
|
|
12
12
|
export { Loader } from "./components/loader.js";
|
|
13
13
|
export { Markdown } from "./components/markdown.js";
|
|
14
|
-
export { ScrollView } from "./components/scroll-view.js";
|
|
14
|
+
export { ScrollView, } from "./components/scroll-view.js";
|
|
15
15
|
export { SelectList, } from "./components/select-list.js";
|
|
16
16
|
export { SettingsList } from "./components/settings-list.js";
|
|
17
17
|
export { Spacer } from "./components/spacer.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,OAAO,EAAE,MAAM,EAA2B,MAAM,QAAQ,CAAC;AACzD,uBAAuB;AACvB,OAAO,EAIN,4BAA4B,GAE5B,MAAM,mBAAmB,CAAC;AAC3B,aAAa;AACb,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,MAAM,EAAwC,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAsC,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAA+B,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAyB,QAAQ,EAA4C,MAAM,0BAA0B,CAAC;AACrH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,OAAO,EAAE,MAAM,EAA2B,MAAM,QAAQ,CAAC;AACzD,uBAAuB;AACvB,OAAO,EAIN,4BAA4B,GAE5B,MAAM,mBAAmB,CAAC;AAC3B,aAAa;AACb,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,MAAM,EAAwC,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAsC,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAA+B,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAyB,QAAQ,EAA4C,MAAM,0BAA0B,CAAC;AACrH,OAAO,EACN,UAAU,GAIV,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEN,UAAU,GAIV,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAoB,YAAY,EAA0B,MAAM,+BAA+B,CAAC;AACvG,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAKN,MAAM,GACN,MAAM,yBAAyB,CAAC;AAGjC,iBAAiB;AACjB,OAAO,EAAmB,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtE,cAAc;AACd,OAAO,EACN,cAAc,EAOd,kBAAkB,EAClB,cAAc,EACd,eAAe,GACf,MAAM,kBAAkB,CAAC;AAC1B,0BAA0B;AAC1B,OAAO,EACN,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,GAAG,EAGH,UAAU,EACV,QAAQ,EACR,sBAAsB,GACtB,MAAM,WAAW,CAAC;AACnB,kBAAkB;AAClB,OAAO,EAA2B,WAAW,EAAE,MAAM,YAAY,CAAC;AAClE,sCAAsC;AACtC,OAAO,EAAE,WAAW,EAAqD,MAAM,mBAAmB,CAAC;AACnG,yCAAyC;AACzC,OAAO,EAAE,eAAe,EAAiB,MAAM,eAAe,CAAC;AAC/D,kBAAkB;AAClB,OAAO,EACN,yBAAyB,EACzB,8BAA8B,GAG9B,MAAM,sBAAsB,CAAC;AAC9B,yBAAyB;AACzB,OAAO,EACN,eAAe,EAEf,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EAIT,aAAa,EACb,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,iBAAiB,GAEjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEN,SAAS,EACT,aAAa,EACb,gBAAgB,EAEhB,WAAW,EACX,aAAa,GAab,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,YAAY,EAA4B,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAiC,MAAM,sBAAsB,CAAC;AACpF,YAAY;AACZ,OAAO,EACN,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,YAAY,EACZ,gBAAgB,GAChB,MAAM,YAAY,CAAC","sourcesContent":["// Core TUI interfaces and classes\n\nexport { Marked, type Token, type Tokens } from \"marked\";\n// Autocomplete support\nexport {\n\ttype AutocompleteItem,\n\ttype AutocompleteProvider,\n\ttype AutocompleteSuggestions,\n\tCombinedAutocompleteProvider,\n\ttype SlashCommand,\n} from \"./autocomplete.ts\";\n// Components\nexport { Box } from \"./components/box.ts\";\nexport { CancellableLoader } from \"./components/cancellable-loader.ts\";\nexport { Editor, type EditorOptions, type EditorTheme } from \"./components/editor.ts\";\nexport { HStack } from \"./components/h-stack.ts\";\nexport { Image, type ImageOptions, type ImageTheme } from \"./components/image.ts\";\nexport { Input } from \"./components/input.ts\";\nexport { Loader, type LoaderIndicatorOptions } from \"./components/loader.ts\";\nexport { type DefaultTextStyle, Markdown, type MarkdownOptions, type MarkdownTheme } from \"./components/markdown.ts\";\nexport {\n\tScrollView,\n\ttype ScrollViewOptions,\n\ttype ScrollViewScrollbar,\n\ttype ScrollViewScrollToOptions,\n} from \"./components/scroll-view.ts\";\nexport {\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\ttype SelectListTheme,\n\ttype SelectListTruncatePrimaryContext,\n} from \"./components/select-list.ts\";\nexport { type SettingItem, SettingsList, type SettingsListTheme } from \"./components/settings-list.ts\";\nexport { Spacer } from \"./components/spacer.ts\";\nexport { Text } from \"./components/text.ts\";\nexport { TruncatedText } from \"./components/truncated-text.ts\";\nexport {\n\ttype StackChild,\n\ttype StackEntry,\n\ttype StackEntryOptions,\n\ttype StackOptions,\n\tVStack,\n} from \"./components/v-stack.ts\";\n// Editor component interface (for custom editors)\nexport type { EditorComponent } from \"./editor-component.ts\";\n// Fuzzy matching\nexport { type FuzzyMatch, fuzzyFilter, fuzzyMatch } from \"./fuzzy.ts\";\n// Keybindings\nexport {\n\tgetKeybindings,\n\ttype Keybinding,\n\ttype KeybindingConflict,\n\ttype KeybindingDefinition,\n\ttype KeybindingDefinitions,\n\ttype Keybindings,\n\ttype KeybindingsConfig,\n\tKeybindingsManager,\n\tsetKeybindings,\n\tTUI_KEYBINDINGS,\n} from \"./keybindings.ts\";\n// Keyboard input handling\nexport {\n\tdecodeKittyPrintable,\n\tisKeyRelease,\n\tisKeyRepeat,\n\tisKittyProtocolActive,\n\tKey,\n\ttype KeyEventType,\n\ttype KeyId,\n\tmatchesKey,\n\tparseKey,\n\tsetKittyProtocolActive,\n} from \"./keys.ts\";\n// LaTeX rendering\nexport { type RenderLatexOptions, renderLatex } from \"./latex.ts\";\n// Input buffering for batch splitting\nexport { StdinBuffer, type StdinBufferEventMap, type StdinBufferOptions } from \"./stdin-buffer.ts\";\n// Terminal interface and implementations\nexport { ProcessTerminal, type Terminal } from \"./terminal.ts\";\n// Terminal colors\nexport {\n\tparseOsc11BackgroundColor,\n\tparseTerminalColorSchemeReport,\n\ttype RgbColor,\n\ttype TerminalColorScheme,\n} from \"./terminal-colors.ts\";\n// Terminal image support\nexport {\n\tallocateImageId,\n\ttype CellDimensions,\n\tcalculateImageRows,\n\tdeleteAllKittyImages,\n\tdeleteKittyImage,\n\tdetectCapabilities,\n\tencodeITerm2,\n\tencodeKitty,\n\tgetCapabilities,\n\tgetCellDimensions,\n\tgetGifDimensions,\n\tgetImageDimensions,\n\tgetJpegDimensions,\n\tgetPngDimensions,\n\tgetWebpDimensions,\n\thyperlink,\n\ttype ImageDimensions,\n\ttype ImageProtocol,\n\ttype ImageRenderOptions,\n\timageFallback,\n\trenderImage,\n\tresetCapabilitiesCache,\n\tsetCapabilities,\n\tsetCellDimensions,\n\ttype TerminalCapabilities,\n} from \"./terminal-image.ts\";\nexport {\n\ttype Component,\n\tContainer,\n\tCURSOR_MARKER,\n\tcompositeTuiLine,\n\ttype Focusable,\n\tisFocusable,\n\tisViewportTUI,\n\ttype OverlayAnchor,\n\ttype OverlayHandle,\n\ttype OverlayMargin,\n\ttype OverlayOptions,\n\ttype OverlayUnfocusOptions,\n\ttype SizeValue,\n\ttype TUI,\n\ttype TuiInputListener,\n\ttype TuiInputListenerResult,\n\ttype TuiMode,\n\ttype TuiStopOptions,\n\ttype ViewportTUI,\n} from \"./tui.ts\";\nexport { TuiAltScreen, type TuiAltScreenOptions } from \"./tui-alt-screen.ts\";\nexport { TuiMainScreen, type TuiMainScreenRenderState } from \"./tui-main-screen.ts\";\n// Utilities\nexport {\n\tgetOsc8LinkAtColumn,\n\tsliceByColumn,\n\tstripTerminalSequences,\n\ttruncateToWidth,\n\tvisibleWidth,\n\twrapTextWithAnsi,\n} from \"./utils.ts\";\n"]}
|
package/dist/keybindings.d.ts
CHANGED
|
@@ -41,8 +41,14 @@ export interface Keybindings {
|
|
|
41
41
|
"tui.altScreen.pageDown": true;
|
|
42
42
|
"tui.altScreen.halfPageUp": true;
|
|
43
43
|
"tui.altScreen.halfPageDown": true;
|
|
44
|
+
"tui.altScreen.lineUp": true;
|
|
45
|
+
"tui.altScreen.lineDown": true;
|
|
44
46
|
"tui.altScreen.previousPrompt": true;
|
|
45
47
|
"tui.altScreen.nextPrompt": true;
|
|
48
|
+
"tui.altScreen.search": true;
|
|
49
|
+
"tui.altScreen.searchNext": true;
|
|
50
|
+
"tui.altScreen.searchPrevious": true;
|
|
51
|
+
"tui.altScreen.searchClose": true;
|
|
46
52
|
"tui.altScreen.top": true;
|
|
47
53
|
"tui.altScreen.bottom": true;
|
|
48
54
|
}
|
|
@@ -202,6 +208,14 @@ export declare const TUI_KEYBINDINGS: {
|
|
|
202
208
|
readonly defaultKeys: [];
|
|
203
209
|
readonly description: "Scroll viewport down half a page";
|
|
204
210
|
};
|
|
211
|
+
readonly "tui.altScreen.lineUp": {
|
|
212
|
+
readonly defaultKeys: [];
|
|
213
|
+
readonly description: "Scroll viewport up one line";
|
|
214
|
+
};
|
|
215
|
+
readonly "tui.altScreen.lineDown": {
|
|
216
|
+
readonly defaultKeys: [];
|
|
217
|
+
readonly description: "Scroll viewport down one line";
|
|
218
|
+
};
|
|
205
219
|
readonly "tui.altScreen.previousPrompt": {
|
|
206
220
|
readonly defaultKeys: "ctrl+shift+up";
|
|
207
221
|
readonly description: "Jump to previous semantic prompt";
|
|
@@ -210,6 +224,22 @@ export declare const TUI_KEYBINDINGS: {
|
|
|
210
224
|
readonly defaultKeys: "ctrl+shift+down";
|
|
211
225
|
readonly description: "Jump to next semantic prompt";
|
|
212
226
|
};
|
|
227
|
+
readonly "tui.altScreen.search": {
|
|
228
|
+
readonly defaultKeys: "ctrl+shift+f";
|
|
229
|
+
readonly description: "Search the primary scroll view";
|
|
230
|
+
};
|
|
231
|
+
readonly "tui.altScreen.searchNext": {
|
|
232
|
+
readonly defaultKeys: ["enter", "ctrl+g"];
|
|
233
|
+
readonly description: "Select the next search match";
|
|
234
|
+
};
|
|
235
|
+
readonly "tui.altScreen.searchPrevious": {
|
|
236
|
+
readonly defaultKeys: ["shift+enter", "ctrl+shift+g"];
|
|
237
|
+
readonly description: "Select the previous search match";
|
|
238
|
+
};
|
|
239
|
+
readonly "tui.altScreen.searchClose": {
|
|
240
|
+
readonly defaultKeys: "escape";
|
|
241
|
+
readonly description: "Close transcript search";
|
|
242
|
+
};
|
|
213
243
|
readonly "tui.altScreen.top": {
|
|
214
244
|
readonly defaultKeys: "home";
|
|
215
245
|
readonly description: "Scroll viewport to top";
|