@pokit/tabs-core 0.0.1
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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/constants/help-content.d.ts +19 -0
- package/dist/constants/help-content.d.ts.map +1 -0
- package/dist/constants/help-content.js +38 -0
- package/dist/constants/index.d.ts +9 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +7 -0
- package/dist/constants/keyboard.d.ts +29 -0
- package/dist/constants/keyboard.d.ts.map +1 -0
- package/dist/constants/keyboard.js +34 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/use-keyboard-handler.d.ts +114 -0
- package/dist/hooks/use-keyboard-handler.d.ts.map +1 -0
- package/dist/hooks/use-keyboard-handler.js +224 -0
- package/dist/hooks/use-tabs-state.d.ts +47 -0
- package/dist/hooks/use-tabs-state.d.ts.map +1 -0
- package/dist/hooks/use-tabs-state.js +92 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/process-manager.d.ts +78 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +171 -0
- package/dist/ring-buffer.d.ts +143 -0
- package/dist/ring-buffer.d.ts.map +1 -0
- package/dist/ring-buffer.js +270 -0
- package/dist/state-reducer.d.ts +25 -0
- package/dist/state-reducer.d.ts.map +1 -0
- package/dist/state-reducer.js +175 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +21 -0
- package/package.json +64 -0
- package/src/index.ts +87 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daniel Grant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @pokit/tabs-core
|
|
2
|
+
|
|
3
|
+
Shared logic for pok tabbed terminal UI adapters.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package provides framework-agnostic types, state management, and process handling used by tabs adapter implementations like `@pokit/tabs-ink`.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add @pokit/tabs-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Note: This is typically a dependency of adapter packages, not installed directly.
|
|
16
|
+
|
|
17
|
+
## Exports
|
|
18
|
+
|
|
19
|
+
### State Management
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { createInitialState, reducer } from '@pokit/tabs-core';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Process Manager
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { ProcessManager } from '@pokit/tabs-core';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Types
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import type { TabStatus, TabProcess, EventDrivenState } from '@pokit/tabs-core';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Status Indicators
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { STATUS_INDICATORS, getStatusIndicator } from '@pokit/tabs-core';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
See the [full documentation](https://github.com/openpok/pok/blob/main/docs/packages/tabs-core.md).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Help Content for CLI Tabs
|
|
3
|
+
*
|
|
4
|
+
* Keyboard shortcut definitions and help overlay content used by all adapters.
|
|
5
|
+
*/
|
|
6
|
+
export type Shortcut = {
|
|
7
|
+
key: string;
|
|
8
|
+
description: string;
|
|
9
|
+
};
|
|
10
|
+
export type ShortcutGroup = {
|
|
11
|
+
title: string;
|
|
12
|
+
shortcuts: Shortcut[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Standard keyboard shortcut help content.
|
|
16
|
+
* Used by both Ink and OpenTUI help overlays.
|
|
17
|
+
*/
|
|
18
|
+
export declare const HELP_CONTENT: ShortcutGroup[];
|
|
19
|
+
//# sourceMappingURL=help-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help-content.d.ts","sourceRoot":"","sources":["../../src/constants/help-content.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,aAAa,EA4BvC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Help Content for CLI Tabs
|
|
3
|
+
*
|
|
4
|
+
* Keyboard shortcut definitions and help overlay content used by all adapters.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Standard keyboard shortcut help content.
|
|
8
|
+
* Used by both Ink and OpenTUI help overlays.
|
|
9
|
+
*/
|
|
10
|
+
export const HELP_CONTENT = [
|
|
11
|
+
{
|
|
12
|
+
title: 'Navigation',
|
|
13
|
+
shortcuts: [
|
|
14
|
+
{ key: '\u2191/\u2193', description: 'Scroll output up/down' },
|
|
15
|
+
{ key: 'Page Up/Dn', description: 'Scroll by page' },
|
|
16
|
+
{ key: 'Tab', description: 'Next tab' },
|
|
17
|
+
{ key: 'Shift+Tab', description: 'Previous tab' },
|
|
18
|
+
{ key: '1-9', description: 'Jump to tab by number' },
|
|
19
|
+
{ key: 'Meta+\u2190/\u2192', description: 'Previous/next tab' },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: 'Process Control',
|
|
24
|
+
shortcuts: [
|
|
25
|
+
{ key: 'r', description: 'Restart current process' },
|
|
26
|
+
{ key: 'k', description: 'Kill current process' },
|
|
27
|
+
{ key: 'q', description: 'Quit (with confirmation)' },
|
|
28
|
+
{ key: 'Ctrl+C', description: 'Force quit immediately' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
title: 'Input Mode',
|
|
33
|
+
shortcuts: [
|
|
34
|
+
{ key: 'i', description: 'Enter input mode' },
|
|
35
|
+
{ key: 'Escape', description: 'Exit input mode' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all shared constants for CLI tabs.
|
|
5
|
+
*/
|
|
6
|
+
export type { Shortcut, ShortcutGroup } from './help-content.js';
|
|
7
|
+
export { HELP_CONTENT } from './help-content.js';
|
|
8
|
+
export { KEY_SEQUENCES, ctrlKeyToSequence, HELP_HINT_DURATION_MS } from './keyboard.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard Constants for CLI Tabs
|
|
3
|
+
*
|
|
4
|
+
* Escape sequences and key mappings for terminal input handling.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Common terminal escape sequences for special keys.
|
|
8
|
+
* Used when forwarding input to child processes in focus mode.
|
|
9
|
+
*/
|
|
10
|
+
export declare const KEY_SEQUENCES: {
|
|
11
|
+
readonly RETURN: "\n";
|
|
12
|
+
readonly TAB: "\t";
|
|
13
|
+
readonly BACKSPACE: "";
|
|
14
|
+
readonly DELETE: "\u001B[3~";
|
|
15
|
+
readonly ARROW_UP: "\u001B[A";
|
|
16
|
+
readonly ARROW_DOWN: "\u001B[B";
|
|
17
|
+
readonly ARROW_RIGHT: "\u001B[C";
|
|
18
|
+
readonly ARROW_LEFT: "\u001B[D";
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Convert a Ctrl+key combination to its terminal escape sequence.
|
|
22
|
+
* Ctrl+A = 1, Ctrl+B = 2, ..., Ctrl+Z = 26
|
|
23
|
+
*/
|
|
24
|
+
export declare function ctrlKeyToSequence(key: string): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Duration to show help hint on startup (in milliseconds).
|
|
27
|
+
*/
|
|
28
|
+
export declare const HELP_HINT_DURATION_MS = 5000;
|
|
29
|
+
//# sourceMappingURL=keyboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../src/constants/keyboard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;;;;;;CAShB,CAAC;AAEX;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5D;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard Constants for CLI Tabs
|
|
3
|
+
*
|
|
4
|
+
* Escape sequences and key mappings for terminal input handling.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Common terminal escape sequences for special keys.
|
|
8
|
+
* Used when forwarding input to child processes in focus mode.
|
|
9
|
+
*/
|
|
10
|
+
export const KEY_SEQUENCES = {
|
|
11
|
+
RETURN: '\n',
|
|
12
|
+
TAB: '\t',
|
|
13
|
+
BACKSPACE: '\x7f',
|
|
14
|
+
DELETE: '\x1b[3~',
|
|
15
|
+
ARROW_UP: '\x1b[A',
|
|
16
|
+
ARROW_DOWN: '\x1b[B',
|
|
17
|
+
ARROW_RIGHT: '\x1b[C',
|
|
18
|
+
ARROW_LEFT: '\x1b[D',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Convert a Ctrl+key combination to its terminal escape sequence.
|
|
22
|
+
* Ctrl+A = 1, Ctrl+B = 2, ..., Ctrl+Z = 26
|
|
23
|
+
*/
|
|
24
|
+
export function ctrlKeyToSequence(key) {
|
|
25
|
+
const code = key.toUpperCase().charCodeAt(0) - 64;
|
|
26
|
+
if (code >= 1 && code <= 26) {
|
|
27
|
+
return String.fromCharCode(code);
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Duration to show help hint on startup (in milliseconds).
|
|
33
|
+
*/
|
|
34
|
+
export const HELP_HINT_DURATION_MS = 5000;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all shared hooks for CLI tabs.
|
|
5
|
+
*/
|
|
6
|
+
export type { UseTabsStateOptions, TabsState, TabsActions } from './use-tabs-state.js';
|
|
7
|
+
export { useTabsState } from './use-tabs-state.js';
|
|
8
|
+
export type { KeyboardCallbacks, KeyboardState, NormalizedKeyEvent, KeyboardAction, } from './use-keyboard-handler.js';
|
|
9
|
+
export { processKeyEvent, useKeyboardCallbackRefs, executeKeyboardAction, } from './use-keyboard-handler.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Keyboard Handler Logic
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic keyboard action handling for tabbed terminal interfaces.
|
|
5
|
+
* Provides normalized keyboard actions that can be called by framework-specific handlers.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Callbacks for keyboard actions in the tabbed view.
|
|
9
|
+
*/
|
|
10
|
+
export type KeyboardCallbacks = {
|
|
11
|
+
onQuit: () => void;
|
|
12
|
+
onQuitRequest: () => void;
|
|
13
|
+
onRestart: (index: number) => void;
|
|
14
|
+
onKill: (index: number) => void;
|
|
15
|
+
onEnterFocusMode: () => void;
|
|
16
|
+
onExitFocusMode: () => void;
|
|
17
|
+
onSendInput: (data: string) => void;
|
|
18
|
+
onToggleHelp: () => void;
|
|
19
|
+
onCloseHelp: () => void;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Current state needed for keyboard handling decisions.
|
|
23
|
+
*/
|
|
24
|
+
export type KeyboardState = {
|
|
25
|
+
helpVisible: boolean;
|
|
26
|
+
focusMode: boolean;
|
|
27
|
+
quitConfirmPending: boolean;
|
|
28
|
+
activeIndex: number;
|
|
29
|
+
tabCount: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Normalized key event structure.
|
|
33
|
+
* Provides a unified interface for different terminal keyboard APIs.
|
|
34
|
+
*/
|
|
35
|
+
export type NormalizedKeyEvent = {
|
|
36
|
+
/** The character(s) typed, if any */
|
|
37
|
+
char?: string;
|
|
38
|
+
/** Named key (e.g., 'escape', 'tab', 'up', 'down') */
|
|
39
|
+
name?: string;
|
|
40
|
+
/** Whether Ctrl was held */
|
|
41
|
+
ctrl?: boolean;
|
|
42
|
+
/** Whether Shift was held */
|
|
43
|
+
shift?: boolean;
|
|
44
|
+
/** Whether Meta/Alt was held */
|
|
45
|
+
meta?: boolean;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Action results from keyboard handling.
|
|
49
|
+
*/
|
|
50
|
+
export type KeyboardAction = {
|
|
51
|
+
type: 'toggle-help';
|
|
52
|
+
} | {
|
|
53
|
+
type: 'close-help';
|
|
54
|
+
} | {
|
|
55
|
+
type: 'exit-focus-mode';
|
|
56
|
+
} | {
|
|
57
|
+
type: 'send-input';
|
|
58
|
+
data: string;
|
|
59
|
+
} | {
|
|
60
|
+
type: 'quit';
|
|
61
|
+
} | {
|
|
62
|
+
type: 'quit-request';
|
|
63
|
+
} | {
|
|
64
|
+
type: 'cancel-quit';
|
|
65
|
+
} | {
|
|
66
|
+
type: 'enter-focus-mode';
|
|
67
|
+
} | {
|
|
68
|
+
type: 'restart';
|
|
69
|
+
index: number;
|
|
70
|
+
} | {
|
|
71
|
+
type: 'kill';
|
|
72
|
+
index: number;
|
|
73
|
+
} | {
|
|
74
|
+
type: 'switch-tab';
|
|
75
|
+
index: number;
|
|
76
|
+
} | {
|
|
77
|
+
type: 'next-tab';
|
|
78
|
+
} | {
|
|
79
|
+
type: 'prev-tab';
|
|
80
|
+
} | {
|
|
81
|
+
type: 'scroll';
|
|
82
|
+
delta: number;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'none';
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Process a normalized key event and return the appropriate action.
|
|
88
|
+
*
|
|
89
|
+
* This is the core keyboard handling logic, extracted to be framework-agnostic.
|
|
90
|
+
* Each adapter (Ink, OpenTUI) normalizes their keyboard events and calls this function.
|
|
91
|
+
*/
|
|
92
|
+
export declare function processKeyEvent(event: NormalizedKeyEvent, state: KeyboardState, viewHeight: number): KeyboardAction;
|
|
93
|
+
/**
|
|
94
|
+
* Hook to create stable references for keyboard callbacks.
|
|
95
|
+
*
|
|
96
|
+
* This solves the common React issue where callbacks in keyboard handlers
|
|
97
|
+
* may have stale closures. The refs always point to the current callback.
|
|
98
|
+
*/
|
|
99
|
+
export declare function useKeyboardCallbackRefs(callbacks: KeyboardCallbacks): {
|
|
100
|
+
onQuitRef: import("react").RefObject<() => void>;
|
|
101
|
+
onQuitRequestRef: import("react").RefObject<() => void>;
|
|
102
|
+
onRestartRef: import("react").RefObject<(index: number) => void>;
|
|
103
|
+
onKillRef: import("react").RefObject<(index: number) => void>;
|
|
104
|
+
onEnterFocusModeRef: import("react").RefObject<() => void>;
|
|
105
|
+
onExitFocusModeRef: import("react").RefObject<() => void>;
|
|
106
|
+
onSendInputRef: import("react").RefObject<(data: string) => void>;
|
|
107
|
+
onToggleHelpRef: import("react").RefObject<() => void>;
|
|
108
|
+
onCloseHelpRef: import("react").RefObject<() => void>;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Execute a keyboard action using the provided callback refs.
|
|
112
|
+
*/
|
|
113
|
+
export declare function executeKeyboardAction(action: KeyboardAction, refs: ReturnType<typeof useKeyboardCallbackRefs>, scrollBy: (delta: number) => void, switchTab: (index: number) => void, nextTab: () => void, prevTab: () => void): void;
|
|
114
|
+
//# sourceMappingURL=use-keyboard-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-keyboard-handler.d.ts","sourceRoot":"","sources":["../../src/hooks/use-keyboard-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Keyboard Handler Logic
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic keyboard action handling for tabbed terminal interfaces.
|
|
5
|
+
* Provides normalized keyboard actions that can be called by framework-specific handlers.
|
|
6
|
+
*/
|
|
7
|
+
import { useRef, useEffect } from 'react';
|
|
8
|
+
import { KEY_SEQUENCES, ctrlKeyToSequence } from '../constants/keyboard.js';
|
|
9
|
+
/**
|
|
10
|
+
* Process a normalized key event and return the appropriate action.
|
|
11
|
+
*
|
|
12
|
+
* This is the core keyboard handling logic, extracted to be framework-agnostic.
|
|
13
|
+
* Each adapter (Ink, OpenTUI) normalizes their keyboard events and calls this function.
|
|
14
|
+
*/
|
|
15
|
+
export function processKeyEvent(event, state, viewHeight) {
|
|
16
|
+
const { char, name, ctrl, shift, meta } = event;
|
|
17
|
+
const { helpVisible, focusMode, quitConfirmPending, activeIndex, tabCount } = state;
|
|
18
|
+
// Help toggle takes priority
|
|
19
|
+
if (char === '?') {
|
|
20
|
+
return { type: 'toggle-help' };
|
|
21
|
+
}
|
|
22
|
+
// Escape closes help if visible
|
|
23
|
+
if (name === 'escape' && helpVisible) {
|
|
24
|
+
return { type: 'close-help' };
|
|
25
|
+
}
|
|
26
|
+
// Don't process other keys when help is visible
|
|
27
|
+
if (helpVisible) {
|
|
28
|
+
return { type: 'none' };
|
|
29
|
+
}
|
|
30
|
+
// Focus mode: forward most input to child process
|
|
31
|
+
if (focusMode) {
|
|
32
|
+
if (name === 'escape') {
|
|
33
|
+
return { type: 'exit-focus-mode' };
|
|
34
|
+
}
|
|
35
|
+
const rawInput = getFocusModeInput(event);
|
|
36
|
+
if (rawInput) {
|
|
37
|
+
return { type: 'send-input', data: rawInput };
|
|
38
|
+
}
|
|
39
|
+
return { type: 'none' };
|
|
40
|
+
}
|
|
41
|
+
// Normal mode: handle UI navigation
|
|
42
|
+
// Quit confirmation handling
|
|
43
|
+
if (quitConfirmPending) {
|
|
44
|
+
if (char === 'q' || name === 'q') {
|
|
45
|
+
return { type: 'quit' };
|
|
46
|
+
}
|
|
47
|
+
return { type: 'cancel-quit' };
|
|
48
|
+
}
|
|
49
|
+
// Quit request
|
|
50
|
+
if (char === 'q' || name === 'q') {
|
|
51
|
+
return { type: 'quit-request' };
|
|
52
|
+
}
|
|
53
|
+
// Ctrl+C for instant quit
|
|
54
|
+
if ((char === 'c' || name === 'c') && ctrl) {
|
|
55
|
+
return { type: 'quit' };
|
|
56
|
+
}
|
|
57
|
+
// Enter focus/input mode
|
|
58
|
+
if (char === 'i' || name === 'i') {
|
|
59
|
+
return { type: 'enter-focus-mode' };
|
|
60
|
+
}
|
|
61
|
+
// Restart current tab
|
|
62
|
+
if (char === 'r' || name === 'r') {
|
|
63
|
+
return { type: 'restart', index: activeIndex };
|
|
64
|
+
}
|
|
65
|
+
// Kill current tab's process
|
|
66
|
+
if (char === 'k' || name === 'k') {
|
|
67
|
+
return { type: 'kill', index: activeIndex };
|
|
68
|
+
}
|
|
69
|
+
// Number keys 1-9 for direct tab access
|
|
70
|
+
const input = char || name || '';
|
|
71
|
+
const num = parseInt(input, 10);
|
|
72
|
+
if (num >= 1 && num <= tabCount) {
|
|
73
|
+
return { type: 'switch-tab', index: num - 1 };
|
|
74
|
+
}
|
|
75
|
+
// Tab navigation
|
|
76
|
+
if (name === 'tab' && shift) {
|
|
77
|
+
return { type: 'prev-tab' };
|
|
78
|
+
}
|
|
79
|
+
if (name === 'tab') {
|
|
80
|
+
return { type: 'next-tab' };
|
|
81
|
+
}
|
|
82
|
+
// Arrow key tab navigation (with meta)
|
|
83
|
+
if ((name === 'left' || name === 'leftArrow') && meta) {
|
|
84
|
+
return { type: 'prev-tab' };
|
|
85
|
+
}
|
|
86
|
+
if ((name === 'right' || name === 'rightArrow') && meta) {
|
|
87
|
+
return { type: 'next-tab' };
|
|
88
|
+
}
|
|
89
|
+
// Scrolling
|
|
90
|
+
if (name === 'up' || name === 'upArrow') {
|
|
91
|
+
return { type: 'scroll', delta: -1 };
|
|
92
|
+
}
|
|
93
|
+
if (name === 'down' || name === 'downArrow') {
|
|
94
|
+
return { type: 'scroll', delta: 1 };
|
|
95
|
+
}
|
|
96
|
+
if (name === 'pageup' || name === 'pageUp') {
|
|
97
|
+
return { type: 'scroll', delta: -viewHeight };
|
|
98
|
+
}
|
|
99
|
+
if (name === 'pagedown' || name === 'pageDown') {
|
|
100
|
+
return { type: 'scroll', delta: viewHeight };
|
|
101
|
+
}
|
|
102
|
+
return { type: 'none' };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get the raw input to send to child process when in focus mode.
|
|
106
|
+
*/
|
|
107
|
+
function getFocusModeInput(event) {
|
|
108
|
+
const { char, name, ctrl } = event;
|
|
109
|
+
// Map special keys to escape sequences
|
|
110
|
+
if (name === 'return')
|
|
111
|
+
return KEY_SEQUENCES.RETURN;
|
|
112
|
+
if (name === 'tab')
|
|
113
|
+
return KEY_SEQUENCES.TAB;
|
|
114
|
+
if (name === 'backspace')
|
|
115
|
+
return KEY_SEQUENCES.BACKSPACE;
|
|
116
|
+
if (name === 'delete')
|
|
117
|
+
return KEY_SEQUENCES.DELETE;
|
|
118
|
+
if (name === 'up' || name === 'upArrow')
|
|
119
|
+
return KEY_SEQUENCES.ARROW_UP;
|
|
120
|
+
if (name === 'down' || name === 'downArrow')
|
|
121
|
+
return KEY_SEQUENCES.ARROW_DOWN;
|
|
122
|
+
if (name === 'right' || name === 'rightArrow')
|
|
123
|
+
return KEY_SEQUENCES.ARROW_RIGHT;
|
|
124
|
+
if (name === 'left' || name === 'leftArrow')
|
|
125
|
+
return KEY_SEQUENCES.ARROW_LEFT;
|
|
126
|
+
// Ctrl+key combinations
|
|
127
|
+
if (ctrl && name && name.length === 1) {
|
|
128
|
+
return ctrlKeyToSequence(name);
|
|
129
|
+
}
|
|
130
|
+
// Regular character input
|
|
131
|
+
if (char) {
|
|
132
|
+
return char;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Hook to create stable references for keyboard callbacks.
|
|
138
|
+
*
|
|
139
|
+
* This solves the common React issue where callbacks in keyboard handlers
|
|
140
|
+
* may have stale closures. The refs always point to the current callback.
|
|
141
|
+
*/
|
|
142
|
+
export function useKeyboardCallbackRefs(callbacks) {
|
|
143
|
+
const onQuitRef = useRef(callbacks.onQuit);
|
|
144
|
+
const onQuitRequestRef = useRef(callbacks.onQuitRequest);
|
|
145
|
+
const onRestartRef = useRef(callbacks.onRestart);
|
|
146
|
+
const onKillRef = useRef(callbacks.onKill);
|
|
147
|
+
const onEnterFocusModeRef = useRef(callbacks.onEnterFocusMode);
|
|
148
|
+
const onExitFocusModeRef = useRef(callbacks.onExitFocusMode);
|
|
149
|
+
const onSendInputRef = useRef(callbacks.onSendInput);
|
|
150
|
+
const onToggleHelpRef = useRef(callbacks.onToggleHelp);
|
|
151
|
+
const onCloseHelpRef = useRef(callbacks.onCloseHelp);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
onQuitRef.current = callbacks.onQuit;
|
|
154
|
+
onQuitRequestRef.current = callbacks.onQuitRequest;
|
|
155
|
+
onRestartRef.current = callbacks.onRestart;
|
|
156
|
+
onKillRef.current = callbacks.onKill;
|
|
157
|
+
onEnterFocusModeRef.current = callbacks.onEnterFocusMode;
|
|
158
|
+
onExitFocusModeRef.current = callbacks.onExitFocusMode;
|
|
159
|
+
onSendInputRef.current = callbacks.onSendInput;
|
|
160
|
+
onToggleHelpRef.current = callbacks.onToggleHelp;
|
|
161
|
+
onCloseHelpRef.current = callbacks.onCloseHelp;
|
|
162
|
+
}, [callbacks]);
|
|
163
|
+
return {
|
|
164
|
+
onQuitRef,
|
|
165
|
+
onQuitRequestRef,
|
|
166
|
+
onRestartRef,
|
|
167
|
+
onKillRef,
|
|
168
|
+
onEnterFocusModeRef,
|
|
169
|
+
onExitFocusModeRef,
|
|
170
|
+
onSendInputRef,
|
|
171
|
+
onToggleHelpRef,
|
|
172
|
+
onCloseHelpRef,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Execute a keyboard action using the provided callback refs.
|
|
177
|
+
*/
|
|
178
|
+
export function executeKeyboardAction(action, refs, scrollBy, switchTab, nextTab, prevTab) {
|
|
179
|
+
switch (action.type) {
|
|
180
|
+
case 'toggle-help':
|
|
181
|
+
refs.onToggleHelpRef.current();
|
|
182
|
+
break;
|
|
183
|
+
case 'close-help':
|
|
184
|
+
refs.onCloseHelpRef.current();
|
|
185
|
+
break;
|
|
186
|
+
case 'exit-focus-mode':
|
|
187
|
+
refs.onExitFocusModeRef.current();
|
|
188
|
+
break;
|
|
189
|
+
case 'send-input':
|
|
190
|
+
refs.onSendInputRef.current(action.data);
|
|
191
|
+
break;
|
|
192
|
+
case 'quit':
|
|
193
|
+
refs.onQuitRef.current();
|
|
194
|
+
break;
|
|
195
|
+
case 'quit-request':
|
|
196
|
+
case 'cancel-quit':
|
|
197
|
+
refs.onQuitRequestRef.current();
|
|
198
|
+
break;
|
|
199
|
+
case 'enter-focus-mode':
|
|
200
|
+
refs.onEnterFocusModeRef.current();
|
|
201
|
+
break;
|
|
202
|
+
case 'restart':
|
|
203
|
+
refs.onRestartRef.current(action.index);
|
|
204
|
+
break;
|
|
205
|
+
case 'kill':
|
|
206
|
+
refs.onKillRef.current(action.index);
|
|
207
|
+
break;
|
|
208
|
+
case 'switch-tab':
|
|
209
|
+
switchTab(action.index);
|
|
210
|
+
break;
|
|
211
|
+
case 'next-tab':
|
|
212
|
+
nextTab();
|
|
213
|
+
break;
|
|
214
|
+
case 'prev-tab':
|
|
215
|
+
prevTab();
|
|
216
|
+
break;
|
|
217
|
+
case 'scroll':
|
|
218
|
+
scrollBy(action.delta);
|
|
219
|
+
break;
|
|
220
|
+
case 'none':
|
|
221
|
+
// Do nothing
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Tab State Management Hook
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic state management for tabbed terminal interfaces.
|
|
5
|
+
* Handles tab selection, scroll offsets, auto-scroll behavior, and help hint visibility.
|
|
6
|
+
*/
|
|
7
|
+
import type { TabProcess } from '../types.js';
|
|
8
|
+
export type UseTabsStateOptions = {
|
|
9
|
+
/** List of tab processes */
|
|
10
|
+
tabs: TabProcess[];
|
|
11
|
+
/** Current active tab index */
|
|
12
|
+
activeIndex: number;
|
|
13
|
+
/** Callback when active index changes */
|
|
14
|
+
onActiveIndexChange: (index: number) => void;
|
|
15
|
+
/** Height of the output view in lines */
|
|
16
|
+
viewHeight: number;
|
|
17
|
+
};
|
|
18
|
+
export type TabsState = {
|
|
19
|
+
/** Whether to show the help hint */
|
|
20
|
+
showHelpHint: boolean;
|
|
21
|
+
/** Current scroll offset for the active tab */
|
|
22
|
+
activeScrollOffset: number;
|
|
23
|
+
/** Whether the user can scroll up */
|
|
24
|
+
canScrollUp: boolean;
|
|
25
|
+
/** Whether the user can scroll down */
|
|
26
|
+
canScrollDown: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type TabsActions = {
|
|
29
|
+
/** Scroll by a given delta (positive = down, negative = up) */
|
|
30
|
+
scrollBy: (delta: number) => void;
|
|
31
|
+
/** Switch to a specific tab by index */
|
|
32
|
+
switchTab: (index: number) => void;
|
|
33
|
+
/** Switch to the next tab (wraps around) */
|
|
34
|
+
nextTab: () => void;
|
|
35
|
+
/** Switch to the previous tab (wraps around) */
|
|
36
|
+
prevTab: () => void;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Hook for managing tabbed interface state.
|
|
40
|
+
*
|
|
41
|
+
* Provides:
|
|
42
|
+
* - Per-tab scroll offsets with auto-scroll behavior
|
|
43
|
+
* - Tab navigation helpers
|
|
44
|
+
* - Help hint auto-dismiss timer
|
|
45
|
+
*/
|
|
46
|
+
export declare function useTabsState({ tabs, activeIndex, onActiveIndexChange, viewHeight, }: UseTabsStateOptions): TabsState & TabsActions;
|
|
47
|
+
//# sourceMappingURL=use-tabs-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-tabs-state.d.ts","sourceRoot":"","sources":["../../src/hooks/use-tabs-state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,oCAAoC;IACpC,YAAY,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IACrC,WAAW,EAAE,OAAO,CAAC;IACrB,uCAAuC;IACvC,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,+DAA+D;IAC/D,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,wCAAwC;IACxC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,4CAA4C;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gDAAgD;IAChD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,WAAW,EACX,mBAAmB,EACnB,UAAU,GACX,EAAE,mBAAmB,GAAG,SAAS,GAAG,WAAW,CA4F/C"}
|