@gajae-code/tui 0.7.8 → 0.7.10
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/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.9] - 2026-07-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Reverted the composer Enter/Shift+Enter handling to the 0.7.8 behavior. A recent restructure (#1298 and follow-ups) changed how plain Enter, Ctrl+Enter, and bare-LF Enter were routed and regressed prompt submission for some terminals; the submit/newline branches are now restored to their 0.7.8 form so Enter submits and Shift+Enter inserts a newline.
|
|
10
|
+
|
|
5
11
|
## [0.7.8] - 2026-06-30
|
|
6
12
|
|
|
7
13
|
### Fixed
|
|
8
14
|
|
|
9
15
|
- The render loop now isolates a component whose `render()` throws: the failure is logged once and replaced with a `[render error: <Name>]` fallback line instead of escaping the frame and tripping the process-level fail-fast `uncaughtException` exit. Previously any unguarded renderer fault (e.g. a tool renderer fed an undefined field) crashed the whole app on whatever triggered the next frame — a keystroke, resize, or command such as `/background` (#1291).
|
|
16
|
+
- `truncateToWidth` now coerces its required napi `text` argument to a safe string, mirroring the existing nullish guards for `maxWidth`/`ellipsis`/`pad`. Renderers that passed an optional/possibly-undefined field no longer crash with a napi String conversion / `undefined is not an object` error (#1290).
|
|
10
17
|
|
|
11
18
|
- Resolved terminal dimensions from the live TTY window size before stream defaults so wide Windows Terminal/PowerShell sessions render against the actual viewport width (#1239).
|
|
12
19
|
|
|
@@ -22,7 +22,7 @@ export interface SettingsListTheme {
|
|
|
22
22
|
}
|
|
23
23
|
export declare class SettingsList implements Component {
|
|
24
24
|
#private;
|
|
25
|
-
constructor(items: SettingItem[], maxVisible: number, theme: SettingsListTheme, onChange: (id: string, newValue: string) => void, onCancel: () => void, onSelectionChange?: (item: SettingItem | undefined) => void);
|
|
25
|
+
constructor(items: SettingItem[], maxVisible: number, theme: SettingsListTheme, onChange: (id: string, newValue: string) => void, onCancel: () => void, onSelectionChange?: (item: SettingItem | undefined) => void, descriptionRows?: number);
|
|
26
26
|
/** Update an item's currentValue */
|
|
27
27
|
updateValue(id: string, newValue: string): void;
|
|
28
28
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/tui",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.10",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"fmt": "biome format --write ."
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@gajae-code/natives": "0.7.
|
|
42
|
-
"@gajae-code/utils": "0.7.
|
|
41
|
+
"@gajae-code/natives": "0.7.10",
|
|
42
|
+
"@gajae-code/utils": "0.7.10",
|
|
43
43
|
"lru-cache": "11.3.6",
|
|
44
44
|
"marked": "^18.0.3"
|
|
45
45
|
},
|
|
@@ -33,6 +33,7 @@ export class SettingsList implements Component {
|
|
|
33
33
|
#onChange: (id: string, newValue: string) => void;
|
|
34
34
|
#onCancel: () => void;
|
|
35
35
|
#onSelectionChange?: (item: SettingItem | undefined) => void;
|
|
36
|
+
#descriptionRows: number;
|
|
36
37
|
|
|
37
38
|
// Submenu state
|
|
38
39
|
#submenuComponent: Component | null = null;
|
|
@@ -45,6 +46,7 @@ export class SettingsList implements Component {
|
|
|
45
46
|
onChange: (id: string, newValue: string) => void,
|
|
46
47
|
onCancel: () => void,
|
|
47
48
|
onSelectionChange?: (item: SettingItem | undefined) => void,
|
|
49
|
+
descriptionRows = 0,
|
|
48
50
|
) {
|
|
49
51
|
this.#items = items;
|
|
50
52
|
this.#maxVisible = maxVisible;
|
|
@@ -52,6 +54,7 @@ export class SettingsList implements Component {
|
|
|
52
54
|
this.#onChange = onChange;
|
|
53
55
|
this.#onCancel = onCancel;
|
|
54
56
|
this.#onSelectionChange = onSelectionChange;
|
|
57
|
+
this.#descriptionRows = Math.max(0, descriptionRows);
|
|
55
58
|
this.#notifySelectionChange();
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -147,9 +150,18 @@ export class SettingsList implements Component {
|
|
|
147
150
|
lines.push(this.#theme.hint(truncateToWidth(scrollText, width - 2, Ellipsis.Omit)));
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
// Add description for selected item
|
|
153
|
+
// Add description for selected item. Some hosts reserve a fixed
|
|
154
|
+
// description area so keyboard navigation does not resize the TUI when
|
|
155
|
+
// moving between described and undescribed rows.
|
|
151
156
|
const selectedItem = this.#items[this.#selectedIndex];
|
|
152
|
-
if (
|
|
157
|
+
if (this.#descriptionRows > 0) {
|
|
158
|
+
lines.push("");
|
|
159
|
+
const wrappedDesc = selectedItem?.description ? wrapTextWithAnsi(selectedItem.description, width - 4) : [];
|
|
160
|
+
for (let i = 0; i < this.#descriptionRows; i++) {
|
|
161
|
+
const line = wrappedDesc[i] ?? "";
|
|
162
|
+
lines.push(line ? this.#theme.description(` ${line}`) : "");
|
|
163
|
+
}
|
|
164
|
+
} else if (selectedItem?.description) {
|
|
153
165
|
lines.push("");
|
|
154
166
|
const wrappedDesc = wrapTextWithAnsi(selectedItem.description, width - 4);
|
|
155
167
|
for (const line of wrappedDesc) {
|
package/src/utils.ts
CHANGED
|
@@ -45,14 +45,18 @@ export function truncateToWidth(
|
|
|
45
45
|
// Guard nullish napi inputs: napi-rs 3 on the Windows prebuilt rejects
|
|
46
46
|
// `null` for `Option<u8>` (Ellipsis) / `Option<bool>` (pad) (issue #848),
|
|
47
47
|
// and `maxWidth` is a required `u32` that throws on `null`/`undefined`
|
|
48
|
-
// everywhere.
|
|
48
|
+
// everywhere. The `text` arg is a required `String` that likewise throws on
|
|
49
|
+
// `null`/`undefined` on every platform, which crashed renderers that passed
|
|
50
|
+
// an optional/possibly-undefined field. Pass concrete defaults that mirror
|
|
51
|
+
// the Rust `unwrap_or`s.
|
|
52
|
+
const safeText = typeof text === "string" ? text : String(text ?? "");
|
|
49
53
|
const safeWidth = Number.isFinite(maxWidth) ? Math.max(0, Math.trunc(maxWidth)) : 0;
|
|
50
54
|
let resolvedEllipsis: Ellipsis | null | undefined | string = ellipsisKind;
|
|
51
55
|
if (typeof resolvedEllipsis === "string") {
|
|
52
56
|
resolvedEllipsis = resolvedEllipsis === "" ? Ellipsis.Omit : Ellipsis.Unicode;
|
|
53
57
|
}
|
|
54
58
|
return nativeTruncateToWidth(
|
|
55
|
-
|
|
59
|
+
safeText,
|
|
56
60
|
safeWidth,
|
|
57
61
|
resolvedEllipsis ?? Ellipsis.Unicode,
|
|
58
62
|
pad ?? false,
|