@oh-my-pi/pi-tui 15.11.2 → 15.11.4
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 +39 -0
- package/dist/types/components/select-list.d.ts +10 -0
- package/dist/types/components/settings-list.d.ts +79 -2
- package/dist/types/components/tab-bar.d.ts +36 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mouse.d.ts +41 -0
- package/dist/types/stdin-buffer.d.ts +6 -0
- package/dist/types/terminal.d.ts +2 -0
- package/dist/types/tui.d.ts +4 -0
- package/package.json +3 -3
- package/src/components/select-list.ts +39 -1
- package/src/components/settings-list.ts +508 -70
- package/src/components/tab-bar.ts +152 -27
- package/src/index.ts +2 -0
- package/src/mouse.ts +55 -0
- package/src/stdin-buffer.ts +143 -19
- package/src/terminal.ts +14 -2
- package/src/tui.ts +20 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.11.4] - 2026-06-12
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added `partialHoldTimeout` to `StdinBufferOptions` to control the maximum extra delay held for unambiguous incomplete escape sequences before they are flushed
|
|
9
|
+
- Added `SettingsList.sidebarWidth` option for a fixed split-layout sidebar width
|
|
10
|
+
- Added mouse pointer support APIs to `SettingsList` with `setHoverItem`, `hitTest`, `hoverTest`, and `routeSubmenuMouse` for row targeting and submenu routing
|
|
11
|
+
- Added `SettingsList.setMaxVisible(rows)` and `SettingsList.handleWheel(delta)` for dynamic viewport sizing and mouse-wheel step selection
|
|
12
|
+
- Added compact tab features with new `Tab.short` labels and `TabBar.selectTab(id)` for id-based activation of non-muted tabs
|
|
13
|
+
- Added pointer-hover and hit-testing APIs to `TabBar` with `setHoverTab`, `tabAt`, and `hoverTab` theme
|
|
14
|
+
- Added exported SGR mouse utilities `parseSgrMouse`, `SgrMouseEvent`, and `MouseRoutable`
|
|
15
|
+
- Added section support to `SettingsList`: `SettingItem.heading` rows split the list into sections, PgUp/PgDn (`tui.select.pageUp`/`pageDown`) jump between sections (or page when none exist), and wide renders use a split layout — section sidebar on the left, the active section's items on the right — falling back to inline heading rows when the width cannot fit both panes. Headings are skipped by navigation, excluded from search, and styled through the optional `SettingsListTheme.heading` (which receives a `dimmed` flag for headings outside the active section) and `section`.
|
|
16
|
+
- Added a host-integration surface to `SettingsList`: a `SettingsListOptions` constructor arg (`layout` to force the flat layout, `typeToSearch: false` to hand the query to a parent, `emptyText`, `hint`), `selectItem(id)`, `getSelectedItem()`, `onSelectionChange`, `hasOpenSubmenu()`, and the exported `getSettingItemFilterText` helper.
|
|
17
|
+
- Added keyboard section focus to `SettingsList`: `toggleSectionFocus()` / `sectionFocused` / `hasSectionFocusTargets()` flip Up/Down between row navigation and whole-section jumps — the cursor glyph parks on the active sidebar entry (or the active heading row in the flat layout) while the row cursor hides, Enter/Esc drop focus back to the rows, and any explicit row selection (`selectItem`, wheel, filtering) exits it.
|
|
18
|
+
- Added muted tabs to `TabBar` (`Tab.muted` + `TabBarTheme.mutedTab`, skipped by keyboard navigation), `setTabs(tabs, activeId?)`/`setActiveById(id)` for re-rendering the strip without firing `onTabChange`, an optional empty label (drops the `Label:` prefix), and a `showHint` switch for the trailing "(tab to cycle)" hint.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Changed `SettingsList` section-focused keyboard handling so `Up`/`Down` now jump between sections and `Enter`/`Escape` exit section focus before confirming or cancelling a setting
|
|
23
|
+
- Changed `SettingsList` split layout at wide widths to render the full list in the right pane and dim items outside the active section instead of showing only the active-section rows
|
|
24
|
+
- Changed `SettingsList` to omit the default hint row (and preceding blank line) when `options.hint` is set to an empty string
|
|
25
|
+
- Changed tab-bar overflow handling to collapse tabs to their `short` forms before wrapping to multiple lines
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed `StdinBuffer` handling of split SGR mouse reports so fragmented sequences are reassembled instead of leaking their tail bytes as literal input
|
|
30
|
+
- Fixed Esc being unreliable (or seconds-slow) inside fullscreen overlays such as `/settings` on kitty-protocol terminals (Ghostty/kitty): the kitty keyboard mode stack is per-screen, so entering the alternate screen silently reverted keys to legacy encoding while the app still parsed them as kitty input. The TUI now re-pushes the active kitty flags right after `\x1b[?1049h` and pops them before `\x1b[?1049l`.
|
|
31
|
+
- Fixed `StdinBuffer` tearing a buffered bare `ESC` followed by another escape sequence: the `\x1b\x1b` candidate was consumed as alt+esc before the CSI/SS3 continuation byte was ever inspected, swallowing the Esc keypress and leaking the follower's tail (`[B`, `[<35;22;17M`) as typed text into focused components. Meta-CSI chords (`\x1b\x1b[A`) now stay whole, and `ESC` + SGR mouse report is split into a real Esc keypress plus a parseable report.
|
|
32
|
+
- Lowered `PARTIAL_HOLD_MAX_MS` from 500ms to 150ms so a dangling escape partial that never completes (e.g. a bare `ESC` arriving while the kitty-active flag is stale) is delivered after at most ~200ms instead of half a second.
|
|
33
|
+
- Fixed deferred partial-flush behavior so pending incomplete escapes are not split across timer boundaries and can still complete when the next chunk arrives
|
|
34
|
+
- Fixed kitty keyboard-mode handling of a dangling `ESC` so it can be joined with subsequent CSI mouse/kitty input instead of being emitted as a standalone sequence
|
|
35
|
+
- Fixed `SettingsList` to clear section-focus state when filtering items, changing data, scrolling with the mouse wheel, or selecting by ID so stale heading focus does not persist across interactions
|
|
36
|
+
- `SettingsList` now renders every state — list, open submenu, filtered results, empty — at one stable height, so interacting with a bottom-anchored settings panel no longer resizes the live terminal region on each keystroke (which forced re-anchoring and could strand stale scrollback rows).
|
|
37
|
+
|
|
38
|
+
## [15.11.3] - 2026-06-11
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Fixed the root compose letting a lower child's native-scrollback live seam overwrite a higher one: the topmost seam (and its commit-safe extension) now defines the commit boundary, so a status loader below a streaming transcript can no longer cause still-mutable transcript rows to be committed as stale history ([#2328](https://github.com/can1357/oh-my-pi/pull/2328)).
|
|
43
|
+
|
|
5
44
|
## [15.11.2] - 2026-06-11
|
|
6
45
|
|
|
7
46
|
### Fixed
|
|
@@ -14,6 +14,8 @@ export interface SelectListTheme {
|
|
|
14
14
|
scrollInfo: (text: string) => string;
|
|
15
15
|
noMatch: (text: string) => string;
|
|
16
16
|
symbols: SymbolTheme;
|
|
17
|
+
/** Hover band applied to the full row under the mouse pointer. */
|
|
18
|
+
hovered?: (text: string) => string;
|
|
17
19
|
}
|
|
18
20
|
export interface SelectListTruncatePrimaryContext {
|
|
19
21
|
text: string;
|
|
@@ -49,6 +51,14 @@ export declare class SelectList implements Component {
|
|
|
49
51
|
constructor(items: ReadonlyArray<SelectItem>, maxVisible: number, theme: SelectListTheme, layout?: SelectListLayoutOptions);
|
|
50
52
|
setFilter(filter: string): void;
|
|
51
53
|
setSelectedIndex(index: number): void;
|
|
54
|
+
/** Resolve a 0-based rendered-line index to a filtered-item index. */
|
|
55
|
+
hitTest(line: number): number | undefined;
|
|
56
|
+
/** Highlight the item under the pointer (null clears). */
|
|
57
|
+
setHoverIndex(index: number | null): void;
|
|
58
|
+
/** Move the selection one step for a wheel notch. */
|
|
59
|
+
handleWheel(delta: -1 | 1): void;
|
|
60
|
+
/** Mouse click: select the item under the pointer and confirm it. */
|
|
61
|
+
clickItem(index: number): void;
|
|
52
62
|
invalidate(): void;
|
|
53
63
|
render(width: number): readonly string[];
|
|
54
64
|
handleInput(keyData: string): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SgrMouseEvent } from "../mouse";
|
|
1
2
|
import type { Component } from "../tui";
|
|
2
3
|
export interface SettingItem {
|
|
3
4
|
/** Unique identifier for this setting */
|
|
@@ -14,6 +15,8 @@ export interface SettingItem {
|
|
|
14
15
|
submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
|
|
15
16
|
/** True when the displayed setting differs from its default value. */
|
|
16
17
|
changed?: boolean;
|
|
18
|
+
/** Render as a non-interactive section heading. Skipped by navigation and search. */
|
|
19
|
+
heading?: boolean;
|
|
17
20
|
}
|
|
18
21
|
export interface SettingsListTheme {
|
|
19
22
|
label: (text: string, selected: boolean, changed: boolean) => string;
|
|
@@ -21,10 +24,84 @@ export interface SettingsListTheme {
|
|
|
21
24
|
description: (text: string) => string;
|
|
22
25
|
cursor: string;
|
|
23
26
|
hint: (text: string) => string;
|
|
27
|
+
/** Style for section heading rows (dimmed when outside the active section). Falls back to `hint` when omitted. */
|
|
28
|
+
heading?: (text: string, dimmed: boolean) => string;
|
|
29
|
+
/** Style for sidebar section names in the split layout. Falls back to label/hint. */
|
|
30
|
+
section?: (text: string, active: boolean) => string;
|
|
31
|
+
/** Hover band applied to the full row under the mouse pointer. */
|
|
32
|
+
hovered?: (text: string) => string;
|
|
24
33
|
}
|
|
34
|
+
/** Optional behavior overrides for {@link SettingsList}. */
|
|
35
|
+
export interface SettingsListOptions {
|
|
36
|
+
/**
|
|
37
|
+
* "auto" (default) renders the section sidebar layout when headings exist
|
|
38
|
+
* and the width allows; "flat" always renders inline heading rows.
|
|
39
|
+
*/
|
|
40
|
+
layout?: "auto" | "flat";
|
|
41
|
+
/**
|
|
42
|
+
* When false, printable input is ignored (no internal type-to-filter) and
|
|
43
|
+
* the search status line is never rendered. Use when a parent component
|
|
44
|
+
* owns the query. Default true.
|
|
45
|
+
*/
|
|
46
|
+
typeToSearch?: boolean;
|
|
47
|
+
/** Text shown when the list has no items at all. */
|
|
48
|
+
emptyText?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Footer hint line (hint-styled, replaces the default navigation hint).
|
|
51
|
+
* An empty string removes the hint row and its leading blank entirely —
|
|
52
|
+
* use when the host renders its own footer.
|
|
53
|
+
*/
|
|
54
|
+
hint?: string;
|
|
55
|
+
/** Fixed split-sidebar width (columns incl. indent+gap); default derives from section names. */
|
|
56
|
+
sidebarWidth?: number;
|
|
57
|
+
}
|
|
58
|
+
/** Searchable text for a setting item: label, id, value, description, and cycle values. */
|
|
59
|
+
export declare function getSettingItemFilterText(item: SettingItem): string;
|
|
25
60
|
export declare class SettingsList implements Component {
|
|
26
61
|
#private;
|
|
27
|
-
|
|
62
|
+
/** Fired when the selected item changes (navigation, filtering, or setItems). */
|
|
63
|
+
onSelectionChange?: (item: SettingItem | undefined) => void;
|
|
64
|
+
constructor(items: SettingItem[], maxVisible: number, theme: SettingsListTheme, onChange: (id: string, newValue: string) => void, onCancel: () => void, options?: SettingsListOptions);
|
|
65
|
+
/** The currently selected item, or undefined when empty or on a heading. */
|
|
66
|
+
getSelectedItem(): SettingItem | undefined;
|
|
67
|
+
/** Move selection to the item with `id`. Returns false when it is not visible. */
|
|
68
|
+
selectItem(id: string): boolean;
|
|
69
|
+
/** True while keyboard focus is on the section headings instead of the setting rows. */
|
|
70
|
+
get sectionFocused(): boolean;
|
|
71
|
+
/** Whether section focus has anywhere to go: 2+ derived sections in the current view. */
|
|
72
|
+
hasSectionFocusTargets(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Toggle keyboard focus between section headings and setting rows. While
|
|
75
|
+
* focused, Up/Down jump whole sections and Enter/Esc return to the rows.
|
|
76
|
+
* Engages only when {@link hasSectionFocusTargets}; returns the new state.
|
|
77
|
+
*/
|
|
78
|
+
toggleSectionFocus(): boolean;
|
|
79
|
+
/** True while an item submenu owns input. */
|
|
80
|
+
hasOpenSubmenu(): boolean;
|
|
81
|
+
/** Resize the visible viewport (fullscreen hosts call this every render). */
|
|
82
|
+
setMaxVisible(rows: number): void;
|
|
83
|
+
/** Move the selection one step for a wheel notch. */
|
|
84
|
+
handleWheel(delta: -1 | 1): void;
|
|
85
|
+
/** Highlight the item under the pointer (null clears). */
|
|
86
|
+
setHoverItem(id: string | null): void;
|
|
87
|
+
/**
|
|
88
|
+
* Resolve a pointer position against the last rendered frame. `line` is the
|
|
89
|
+
* 0-based content-line index within this component's render output, `col`
|
|
90
|
+
* the 0-based column. Sidebar rows resolve to the section's first item.
|
|
91
|
+
*/
|
|
92
|
+
hitTest(line: number, col: number): string | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Like {@link hitTest}, but only rows the pointer is visually on: sidebar
|
|
95
|
+
* jump targets are excluded so hovering section names does not light up
|
|
96
|
+
* pane rows.
|
|
97
|
+
*/
|
|
98
|
+
hoverTest(line: number, col: number): string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Route a mouse event into an open submenu (coordinates are local to this
|
|
101
|
+
* list's rendered lines). Returns false when no submenu is open; submenus
|
|
102
|
+
* that do not implement {@link MouseRoutable} consume the event silently.
|
|
103
|
+
*/
|
|
104
|
+
routeSubmenuMouse(event: SgrMouseEvent, line: number, col: number): boolean;
|
|
28
105
|
getSearchQuery(): string;
|
|
29
106
|
hasSearchQuery(): boolean;
|
|
30
107
|
clearSearch(): void;
|
|
@@ -35,7 +112,7 @@ export declare class SettingsList implements Component {
|
|
|
35
112
|
* the previous selection still survives the active filter, otherwise
|
|
36
113
|
* clamped to the last filtered item (or 0 if there are no matches).
|
|
37
114
|
* An open submenu is left untouched — its lifetime is bounded by its own
|
|
38
|
-
* done callback, and `#closeSubmenu` re-
|
|
115
|
+
* done callback, and `#closeSubmenu` re-resolves the restored item on exit.
|
|
39
116
|
*/
|
|
40
117
|
setItems(items: SettingItem[]): void;
|
|
41
118
|
invalidate(): void;
|
|
@@ -5,6 +5,10 @@ export interface Tab {
|
|
|
5
5
|
id: string;
|
|
6
6
|
/** Display label shown in the tab bar */
|
|
7
7
|
label: string;
|
|
8
|
+
/** Compact form (e.g. just the icon) used when the bar must shrink to fit one line. */
|
|
9
|
+
short?: string;
|
|
10
|
+
/** Render with the muted style and skip during keyboard navigation. */
|
|
11
|
+
muted?: boolean;
|
|
8
12
|
}
|
|
9
13
|
/** Theme for styling the tab bar */
|
|
10
14
|
export interface TabBarTheme {
|
|
@@ -16,6 +20,10 @@ export interface TabBarTheme {
|
|
|
16
20
|
inactiveTab: (text: string) => string;
|
|
17
21
|
/** Style for the hint text (e.g., "(tab to cycle)") */
|
|
18
22
|
hint: (text: string) => string;
|
|
23
|
+
/** Style for muted tabs. Falls back to `inactiveTab` when omitted. */
|
|
24
|
+
mutedTab?: (text: string) => string;
|
|
25
|
+
/** Style for the tab under the mouse pointer. Falls back to `inactiveTab` when omitted. */
|
|
26
|
+
hoverTab?: (text: string) => string;
|
|
19
27
|
}
|
|
20
28
|
/**
|
|
21
29
|
* Horizontal tab bar component.
|
|
@@ -34,6 +42,8 @@ export declare class TabBar implements Component {
|
|
|
34
42
|
#private;
|
|
35
43
|
/** Callback fired when the active tab changes */
|
|
36
44
|
onTabChange?: (tab: Tab, index: number) => void;
|
|
45
|
+
/** Render the trailing "(tab to cycle)" hint. Disable when the host folds the hint into its own footer. */
|
|
46
|
+
showHint: boolean;
|
|
37
47
|
constructor(label: string, tabs: Tab[], theme: TabBarTheme, initialIndex?: number);
|
|
38
48
|
/** Get the currently active tab */
|
|
39
49
|
getActiveTab(): Tab;
|
|
@@ -41,9 +51,19 @@ export declare class TabBar implements Component {
|
|
|
41
51
|
getActiveIndex(): number;
|
|
42
52
|
/** Set the active tab by index (clamped to valid range) */
|
|
43
53
|
setActiveIndex(index: number): void;
|
|
44
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Replace the tab set without firing onTabChange. The active tab is
|
|
56
|
+
* preserved by id when it survives the swap (or forced via `activeId`);
|
|
57
|
+
* otherwise the index is clamped.
|
|
58
|
+
*/
|
|
59
|
+
setTabs(tabs: Tab[], activeId?: string): void;
|
|
60
|
+
/** Set the active tab by id without firing onTabChange. Returns false when the id is unknown. */
|
|
61
|
+
setActiveById(id: string): boolean;
|
|
62
|
+
/** Activate the tab with `id`, firing onTabChange when it changes. Muted tabs are ignored. */
|
|
63
|
+
selectTab(id: string): boolean;
|
|
64
|
+
/** Move to the next non-muted tab (wraps to first tab after last) */
|
|
45
65
|
nextTab(): void;
|
|
46
|
-
/** Move to the previous tab (wraps to last tab before first) */
|
|
66
|
+
/** Move to the previous non-muted tab (wraps to last tab before first) */
|
|
47
67
|
prevTab(): void;
|
|
48
68
|
invalidate(): void;
|
|
49
69
|
/**
|
|
@@ -51,6 +71,19 @@ export declare class TabBar implements Component {
|
|
|
51
71
|
* @returns true if the input was handled, false otherwise
|
|
52
72
|
*/
|
|
53
73
|
handleInput(data: string): boolean;
|
|
54
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Render the tab bar. When the full labels overflow the width, tabs are
|
|
76
|
+
* collapsed to their `short` form one by one — starting with the tabs
|
|
77
|
+
* farthest from the active one — until the bar fits on a single line.
|
|
78
|
+
* Wrapping to multiple lines is the last resort.
|
|
79
|
+
*/
|
|
55
80
|
render(width: number): readonly string[];
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a pointer position against the last rendered frame. `line` is the
|
|
83
|
+
* 0-based line index within this component's render output, `col` the
|
|
84
|
+
* 0-based column.
|
|
85
|
+
*/
|
|
86
|
+
tabAt(line: number, col: number): Tab | undefined;
|
|
87
|
+
/** Highlight the tab under the pointer (null clears). */
|
|
88
|
+
setHoverTab(id: string | null): void;
|
|
56
89
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SGR mouse report parsing (`\x1b[<button;col;rowM` / `…m`).
|
|
3
|
+
*
|
|
4
|
+
* Mouse tracking is enabled only while a fullscreen overlay holds the
|
|
5
|
+
* alternate screen (see tui.ts MOUSE_TRACKING_ON), so consumers are
|
|
6
|
+
* fullscreen components hit-testing against their own rendered frame:
|
|
7
|
+
* the frame paints from screen row 0, hence `row`/`col` are exposed
|
|
8
|
+
* 0-based for direct indexing into rendered lines.
|
|
9
|
+
*/
|
|
10
|
+
/** A decoded SGR mouse report. */
|
|
11
|
+
export interface SgrMouseEvent {
|
|
12
|
+
/** Raw button code (bit 32 = motion, bit 64 = wheel, low bits = button). */
|
|
13
|
+
button: number;
|
|
14
|
+
/** 0-based column of the event. */
|
|
15
|
+
col: number;
|
|
16
|
+
/** 0-based row of the event. */
|
|
17
|
+
row: number;
|
|
18
|
+
/** True for a release report (`m` suffix). */
|
|
19
|
+
release: boolean;
|
|
20
|
+
/** Wheel direction: -1 up, 1 down, null when not a wheel event. */
|
|
21
|
+
wheel: -1 | 1 | null;
|
|
22
|
+
/** True when the pointer moved (hover or drag) rather than clicked. */
|
|
23
|
+
motion: boolean;
|
|
24
|
+
/** True for a left-button press (not motion, not release, not wheel). */
|
|
25
|
+
leftClick: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Decode an SGR mouse report, or return null when `data` is not one.
|
|
29
|
+
* Callers on hot keypress paths should pre-check `data.startsWith("\x1b[<")`
|
|
30
|
+
* before paying for the regex.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseSgrMouse(data: string): SgrMouseEvent | null;
|
|
33
|
+
/**
|
|
34
|
+
* Implemented by components that accept routed mouse events at frame-local
|
|
35
|
+
* coordinates. Hosts translate screen coordinates to the component's own
|
|
36
|
+
* rendered lines before forwarding.
|
|
37
|
+
*/
|
|
38
|
+
export interface MouseRoutable {
|
|
39
|
+
/** `line`/`col` are 0-based within the component's rendered output. */
|
|
40
|
+
routeMouse(event: SgrMouseEvent, line: number, col: number): void;
|
|
41
|
+
}
|
|
@@ -23,6 +23,12 @@ export type StdinBufferOptions = {
|
|
|
23
23
|
* After this time, a genuinely incomplete escape is flushed.
|
|
24
24
|
*/
|
|
25
25
|
timeout?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum extra time (default: 150ms) an unambiguous escape partial — an
|
|
28
|
+
* SGR mouse prefix, or any dangling escape while the kitty keyboard
|
|
29
|
+
* protocol is active — is held past `timeout` waiting for its tail.
|
|
30
|
+
*/
|
|
31
|
+
partialHoldTimeout?: number;
|
|
26
32
|
/**
|
|
27
33
|
* Paste-mode inactivity watchdog (default: 1000ms). If no input arrives for
|
|
28
34
|
* this long while waiting for the bracketed-paste end marker, the paste is
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface Terminal {
|
|
|
42
42
|
get columns(): number;
|
|
43
43
|
get rows(): number;
|
|
44
44
|
get kittyProtocolActive(): boolean;
|
|
45
|
+
get kittyEnableSequence(): string | null;
|
|
45
46
|
moveBy(lines: number): void;
|
|
46
47
|
hideCursor(): void;
|
|
47
48
|
showCursor(): void;
|
|
@@ -79,6 +80,7 @@ export declare function isConPTYHosted(): boolean;
|
|
|
79
80
|
export declare class ProcessTerminal implements Terminal {
|
|
80
81
|
#private;
|
|
81
82
|
get kittyProtocolActive(): boolean;
|
|
83
|
+
get kittyEnableSequence(): string | null;
|
|
82
84
|
get appearance(): TerminalAppearance | undefined;
|
|
83
85
|
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
|
|
84
86
|
onPrivateModeReport(callback: (mode: number, supported: boolean) => void): void;
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -83,6 +83,10 @@ export interface Component {
|
|
|
83
83
|
* of history until it finalizes. Volatile live blocks (tool previews that
|
|
84
84
|
* collapse) omit it. Defaults to `liveRegionStart` when absent; a root that
|
|
85
85
|
* reports no seam at all commits everything that scrolls (shell semantics).
|
|
86
|
+
*
|
|
87
|
+
* When several root children report a seam in the same frame, the topmost
|
|
88
|
+
* one (and its commit-safe extension) defines the boundary: commits are
|
|
89
|
+
* prefix-only, so everything below the first seam is already excluded.
|
|
86
90
|
*/
|
|
87
91
|
export interface NativeScrollbackLiveRegion {
|
|
88
92
|
getNativeScrollbackLiveRegionStart(): number | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "15.11.
|
|
4
|
+
"version": "15.11.4",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "15.11.
|
|
41
|
-
"@oh-my-pi/pi-utils": "15.11.
|
|
40
|
+
"@oh-my-pi/pi-natives": "15.11.4",
|
|
41
|
+
"@oh-my-pi/pi-utils": "15.11.4",
|
|
42
42
|
"lru-cache": "11.5.1",
|
|
43
43
|
"marked": "^18.0.4"
|
|
44
44
|
},
|
|
@@ -34,6 +34,8 @@ export interface SelectListTheme {
|
|
|
34
34
|
scrollInfo: (text: string) => string;
|
|
35
35
|
noMatch: (text: string) => string;
|
|
36
36
|
symbols: SymbolTheme;
|
|
37
|
+
/** Hover band applied to the full row under the mouse pointer. */
|
|
38
|
+
hovered?: (text: string) => string;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export interface SelectListTruncatePrimaryContext {
|
|
@@ -81,6 +83,9 @@ export class SelectList implements Component {
|
|
|
81
83
|
#filteredItems: ReadonlyArray<SelectItem>;
|
|
82
84
|
#filterQuery = "";
|
|
83
85
|
#selectedIndex: number = 0;
|
|
86
|
+
#hoveredIndex: number | null = null;
|
|
87
|
+
/** Per-render map of 0-based output line → filtered-item index. */
|
|
88
|
+
#hitRows: (number | undefined)[] = [];
|
|
84
89
|
|
|
85
90
|
onSelect?: (item: SelectItem) => void;
|
|
86
91
|
onCancel?: () => void;
|
|
@@ -103,12 +108,43 @@ export class SelectList implements Component {
|
|
|
103
108
|
this.#selectedIndex = Math.max(0, Math.min(index, this.#filteredItems.length - 1));
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
/** Resolve a 0-based rendered-line index to a filtered-item index. */
|
|
112
|
+
hitTest(line: number): number | undefined {
|
|
113
|
+
return this.#hitRows[line];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Highlight the item under the pointer (null clears). */
|
|
117
|
+
setHoverIndex(index: number | null): void {
|
|
118
|
+
this.#hoveredIndex = index;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Move the selection one step for a wheel notch. */
|
|
122
|
+
handleWheel(delta: -1 | 1): void {
|
|
123
|
+
if (this.#filteredItems.length === 0) return;
|
|
124
|
+
const next = clamp(this.#selectedIndex + delta, 0, this.#filteredItems.length - 1);
|
|
125
|
+
if (next === this.#selectedIndex) return;
|
|
126
|
+
this.#selectedIndex = next;
|
|
127
|
+
this.#notifySelectionChange();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Mouse click: select the item under the pointer and confirm it. */
|
|
131
|
+
clickItem(index: number): void {
|
|
132
|
+
const item = this.#filteredItems[index];
|
|
133
|
+
if (!item) return;
|
|
134
|
+
if (index !== this.#selectedIndex) {
|
|
135
|
+
this.#selectedIndex = index;
|
|
136
|
+
this.#notifySelectionChange();
|
|
137
|
+
}
|
|
138
|
+
this.onSelect?.(item);
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
invalidate(): void {
|
|
107
142
|
// No cached state to invalidate currently
|
|
108
143
|
}
|
|
109
144
|
|
|
110
145
|
render(width: number): readonly string[] {
|
|
111
146
|
const lines: string[] = [];
|
|
147
|
+
this.#hitRows = [];
|
|
112
148
|
const showSearchStatus = this.#shouldRenderSearchStatus();
|
|
113
149
|
|
|
114
150
|
// If no items match filter, show message
|
|
@@ -159,10 +195,12 @@ export class SelectList implements Component {
|
|
|
159
195
|
for (let i = startIndex; i < endIndex && rows.length < visualBudget; i++) {
|
|
160
196
|
const item = this.#filteredItems[i];
|
|
161
197
|
if (!item) continue;
|
|
198
|
+
const hovered = this.theme.hovered !== undefined && i === this.#hoveredIndex && i !== this.#selectedIndex;
|
|
162
199
|
const itemRows = this.#renderItem(item, i === this.#selectedIndex, rowWidth, primaryColumnWidth);
|
|
163
200
|
for (const row of itemRows) {
|
|
164
201
|
if (rows.length >= visualBudget) break;
|
|
165
|
-
rows.
|
|
202
|
+
this.#hitRows[rows.length] = i;
|
|
203
|
+
rows.push(hovered && this.theme.hovered ? this.theme.hovered(row) : row);
|
|
166
204
|
}
|
|
167
205
|
}
|
|
168
206
|
|