@pablozaiden/terminatui 0.2.0 → 0.3.0

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.
Files changed (181) hide show
  1. package/README.md +64 -43
  2. package/package.json +11 -8
  3. package/src/__tests__/application.test.ts +87 -68
  4. package/src/__tests__/buildCliCommand.test.ts +99 -119
  5. package/src/__tests__/builtins.test.ts +27 -75
  6. package/src/__tests__/command.test.ts +100 -131
  7. package/src/__tests__/configOnChange.test.ts +63 -0
  8. package/src/__tests__/context.test.ts +1 -26
  9. package/src/__tests__/helpCore.test.ts +227 -0
  10. package/src/__tests__/parser.test.ts +98 -244
  11. package/src/__tests__/registry.test.ts +33 -160
  12. package/src/__tests__/schemaToFields.test.ts +75 -158
  13. package/src/builtins/help.ts +12 -4
  14. package/src/builtins/settings.ts +18 -32
  15. package/src/builtins/version.ts +3 -3
  16. package/src/cli/output/colors.ts +1 -1
  17. package/src/cli/parser.ts +26 -95
  18. package/src/core/application.ts +192 -110
  19. package/src/core/command.ts +26 -9
  20. package/src/core/context.ts +31 -20
  21. package/src/core/help.ts +24 -18
  22. package/src/core/knownCommands.ts +13 -0
  23. package/src/core/logger.ts +39 -42
  24. package/src/core/registry.ts +5 -12
  25. package/src/index.ts +22 -137
  26. package/src/tui/TuiApplication.tsx +63 -120
  27. package/src/tui/TuiRoot.tsx +135 -0
  28. package/src/tui/adapters/factory.ts +19 -0
  29. package/src/tui/adapters/ink/InkRenderer.tsx +139 -0
  30. package/src/tui/adapters/ink/components/Button.tsx +12 -0
  31. package/src/tui/adapters/ink/components/Code.tsx +6 -0
  32. package/src/tui/adapters/ink/components/CodeHighlight.tsx +6 -0
  33. package/src/tui/adapters/ink/components/Container.tsx +5 -0
  34. package/src/tui/adapters/ink/components/Field.tsx +12 -0
  35. package/src/tui/adapters/ink/components/Label.tsx +24 -0
  36. package/src/tui/adapters/ink/components/MenuButton.tsx +12 -0
  37. package/src/tui/adapters/ink/components/MenuItem.tsx +17 -0
  38. package/src/tui/adapters/ink/components/Overlay.tsx +5 -0
  39. package/src/tui/adapters/ink/components/Panel.tsx +15 -0
  40. package/src/tui/adapters/ink/components/ScrollView.tsx +5 -0
  41. package/src/tui/adapters/ink/components/Select.tsx +44 -0
  42. package/src/tui/adapters/ink/components/Spacer.tsx +15 -0
  43. package/src/tui/adapters/ink/components/Spinner.tsx +5 -0
  44. package/src/tui/adapters/ink/components/TextInput.tsx +22 -0
  45. package/src/tui/adapters/ink/components/Value.tsx +7 -0
  46. package/src/tui/adapters/ink/keyboard.ts +97 -0
  47. package/src/tui/adapters/ink/utils.ts +16 -0
  48. package/src/tui/adapters/opentui/OpenTuiRenderer.tsx +119 -0
  49. package/src/tui/adapters/opentui/components/Button.tsx +13 -0
  50. package/src/tui/adapters/opentui/components/Code.tsx +12 -0
  51. package/src/tui/adapters/opentui/components/CodeHighlight.tsx +24 -0
  52. package/src/tui/adapters/opentui/components/Container.tsx +56 -0
  53. package/src/tui/adapters/opentui/components/Field.tsx +18 -0
  54. package/src/tui/adapters/opentui/components/Label.tsx +15 -0
  55. package/src/tui/adapters/opentui/components/MenuButton.tsx +14 -0
  56. package/src/tui/adapters/opentui/components/MenuItem.tsx +29 -0
  57. package/src/tui/adapters/opentui/components/Overlay.tsx +21 -0
  58. package/src/tui/adapters/opentui/components/Panel.tsx +78 -0
  59. package/src/tui/adapters/opentui/components/ScrollView.tsx +85 -0
  60. package/src/tui/adapters/opentui/components/Select.tsx +59 -0
  61. package/src/tui/adapters/opentui/components/Spacer.tsx +5 -0
  62. package/src/tui/adapters/opentui/components/Spinner.tsx +12 -0
  63. package/src/tui/adapters/opentui/components/TextInput.tsx +13 -0
  64. package/src/tui/adapters/opentui/components/Value.tsx +13 -0
  65. package/src/tui/{hooks → adapters/opentui/hooks}/useSpinner.ts +2 -11
  66. package/src/tui/adapters/opentui/keyboard.ts +61 -0
  67. package/src/tui/adapters/types.ts +71 -0
  68. package/src/tui/components/ActionButton.tsx +0 -36
  69. package/src/tui/components/CommandSelector.tsx +45 -92
  70. package/src/tui/components/ConfigForm.tsx +68 -42
  71. package/src/tui/components/FieldRow.tsx +0 -30
  72. package/src/tui/components/Header.tsx +14 -13
  73. package/src/tui/components/JsonHighlight.tsx +10 -17
  74. package/src/tui/components/ModalBase.tsx +38 -0
  75. package/src/tui/components/ResultsPanel.tsx +27 -36
  76. package/src/tui/components/StatusBar.tsx +24 -39
  77. package/src/tui/components/logColors.ts +12 -0
  78. package/src/tui/context/ClipboardContext.tsx +87 -0
  79. package/src/tui/context/ExecutorContext.tsx +139 -0
  80. package/src/tui/context/KeyboardContext.tsx +85 -71
  81. package/src/tui/context/LogsContext.tsx +35 -0
  82. package/src/tui/context/NavigationContext.tsx +194 -0
  83. package/src/tui/context/RendererContext.tsx +20 -0
  84. package/src/tui/context/TuiAppContext.tsx +58 -0
  85. package/src/tui/hooks/useActiveKeyHandler.ts +75 -0
  86. package/src/tui/hooks/useBackHandler.ts +34 -0
  87. package/src/tui/hooks/useClipboard.ts +40 -25
  88. package/src/tui/hooks/useClipboardProvider.ts +42 -0
  89. package/src/tui/hooks/useGlobalKeyHandler.ts +54 -0
  90. package/src/tui/modals/CliModal.tsx +82 -0
  91. package/src/tui/modals/EditorModal.tsx +207 -0
  92. package/src/tui/modals/LogsModal.tsx +98 -0
  93. package/src/tui/registry.ts +102 -0
  94. package/src/tui/screens/CommandSelectScreen.tsx +162 -0
  95. package/src/tui/screens/ConfigScreen.tsx +165 -0
  96. package/src/tui/screens/ErrorScreen.tsx +58 -0
  97. package/src/tui/screens/ResultsScreen.tsx +68 -0
  98. package/src/tui/screens/RunningScreen.tsx +72 -0
  99. package/src/tui/screens/ScreenBase.ts +6 -0
  100. package/src/tui/semantic/Button.tsx +7 -0
  101. package/src/tui/semantic/Code.tsx +7 -0
  102. package/src/tui/semantic/CodeHighlight.tsx +7 -0
  103. package/src/tui/semantic/Container.tsx +7 -0
  104. package/src/tui/semantic/Field.tsx +7 -0
  105. package/src/tui/semantic/Label.tsx +7 -0
  106. package/src/tui/semantic/MenuButton.tsx +7 -0
  107. package/src/tui/semantic/MenuItem.tsx +7 -0
  108. package/src/tui/semantic/Overlay.tsx +7 -0
  109. package/src/tui/semantic/Panel.tsx +7 -0
  110. package/src/tui/semantic/ScrollView.tsx +9 -0
  111. package/src/tui/semantic/Select.tsx +7 -0
  112. package/src/tui/semantic/Spacer.tsx +7 -0
  113. package/src/tui/semantic/Spinner.tsx +7 -0
  114. package/src/tui/semantic/TextInput.tsx +7 -0
  115. package/src/tui/semantic/Value.tsx +7 -0
  116. package/src/tui/semantic/types.ts +195 -0
  117. package/src/tui/theme.ts +25 -14
  118. package/src/tui/utils/buildCliCommand.ts +1 -0
  119. package/src/tui/utils/getEnumKeys.ts +3 -0
  120. package/src/tui/utils/parameterPersistence.ts +1 -0
  121. package/src/types/command.ts +0 -60
  122. package/.devcontainer/devcontainer.json +0 -19
  123. package/.devcontainer/install-prerequisites.sh +0 -49
  124. package/.github/workflows/copilot-setup-steps.yml +0 -32
  125. package/.github/workflows/pull-request.yml +0 -27
  126. package/.github/workflows/release-npm-package.yml +0 -81
  127. package/AGENTS.md +0 -31
  128. package/bun.lock +0 -236
  129. package/examples/tui-app/commands/config/app/get.ts +0 -66
  130. package/examples/tui-app/commands/config/app/index.ts +0 -27
  131. package/examples/tui-app/commands/config/app/set.ts +0 -86
  132. package/examples/tui-app/commands/config/index.ts +0 -32
  133. package/examples/tui-app/commands/config/user/get.ts +0 -65
  134. package/examples/tui-app/commands/config/user/index.ts +0 -27
  135. package/examples/tui-app/commands/config/user/set.ts +0 -61
  136. package/examples/tui-app/commands/greet.ts +0 -76
  137. package/examples/tui-app/commands/index.ts +0 -4
  138. package/examples/tui-app/commands/math.ts +0 -115
  139. package/examples/tui-app/commands/status.ts +0 -77
  140. package/examples/tui-app/index.ts +0 -35
  141. package/guides/01-hello-world.md +0 -96
  142. package/guides/02-adding-options.md +0 -103
  143. package/guides/03-multiple-commands.md +0 -163
  144. package/guides/04-subcommands.md +0 -206
  145. package/guides/05-interactive-tui.md +0 -194
  146. package/guides/06-config-validation.md +0 -264
  147. package/guides/07-async-cancellation.md +0 -336
  148. package/guides/08-complete-application.md +0 -537
  149. package/guides/README.md +0 -74
  150. package/src/__tests__/colors.test.ts +0 -127
  151. package/src/__tests__/commandClass.test.ts +0 -130
  152. package/src/__tests__/help.test.ts +0 -412
  153. package/src/__tests__/registryNew.test.ts +0 -160
  154. package/src/__tests__/table.test.ts +0 -146
  155. package/src/__tests__/tui.test.ts +0 -26
  156. package/src/builtins/index.ts +0 -4
  157. package/src/cli/help.ts +0 -174
  158. package/src/cli/index.ts +0 -3
  159. package/src/cli/output/index.ts +0 -2
  160. package/src/cli/output/table.ts +0 -141
  161. package/src/commands/help.ts +0 -50
  162. package/src/commands/index.ts +0 -1
  163. package/src/components/index.ts +0 -147
  164. package/src/core/index.ts +0 -15
  165. package/src/hooks/index.ts +0 -131
  166. package/src/registry/commandRegistry.ts +0 -77
  167. package/src/registry/index.ts +0 -1
  168. package/src/tui/TuiApp.tsx +0 -619
  169. package/src/tui/app.ts +0 -29
  170. package/src/tui/components/CliModal.tsx +0 -81
  171. package/src/tui/components/EditorModal.tsx +0 -177
  172. package/src/tui/components/LogsPanel.tsx +0 -86
  173. package/src/tui/components/index.ts +0 -13
  174. package/src/tui/context/index.ts +0 -7
  175. package/src/tui/hooks/index.ts +0 -35
  176. package/src/tui/hooks/useKeyboardHandler.ts +0 -91
  177. package/src/tui/hooks/useLogStream.ts +0 -96
  178. package/src/tui/index.ts +0 -65
  179. package/src/tui/utils/index.ts +0 -13
  180. package/src/types/index.ts +0 -1
  181. package/tsconfig.json +0 -25
@@ -1,81 +0,0 @@
1
- import { Theme } from "../theme.ts";
2
- import { useKeyboardHandler, KeyboardPriority } from "../hooks/useKeyboardHandler.ts";
3
-
4
- interface CliModalProps {
5
- /** CLI command to display */
6
- command: string;
7
- /** Whether the modal is visible */
8
- visible: boolean;
9
- /** Called when the modal should close */
10
- onClose: () => void;
11
- /** Called when the command should be copied */
12
- onCopy?: (content: string, label: string) => void;
13
- }
14
-
15
- /**
16
- * Modal displaying the CLI command equivalent of the current config.
17
- */
18
- export function CliModal({
19
- command,
20
- visible,
21
- onClose,
22
- onCopy,
23
- }: CliModalProps) {
24
- // Modal keyboard handler
25
- useKeyboardHandler(
26
- (event) => {
27
- const { key } = event;
28
-
29
- if (key.name === "escape" || key.name === "return" || key.name === "enter") {
30
- onClose();
31
- event.stopPropagation();
32
- return;
33
- }
34
-
35
- // Y to copy
36
- if (key.name === "y") {
37
- onCopy?.(command, "CLI command");
38
- event.stopPropagation();
39
- return;
40
- }
41
- },
42
- KeyboardPriority.Modal,
43
- { enabled: visible, modal: true }
44
- );
45
-
46
- if (!visible) {
47
- return null;
48
- }
49
-
50
- return (
51
- <box
52
- position="absolute"
53
- top={4}
54
- left={4}
55
- width="80%"
56
- height={10}
57
- backgroundColor={Theme.overlay}
58
- border={true}
59
- borderStyle="rounded"
60
- borderColor={Theme.overlayTitle}
61
- padding={1}
62
- flexDirection="column"
63
- gap={1}
64
- zIndex={20}
65
- >
66
- <text fg={Theme.overlayTitle}>
67
- <strong>CLI Command</strong>
68
- </text>
69
-
70
- <scrollbox scrollX={true} height={3}>
71
- <text fg={Theme.value}>
72
- {command}
73
- </text>
74
- </scrollbox>
75
-
76
- <text fg={Theme.statusText}>
77
- Ctrl+Y to copy • Enter or Esc to close
78
- </text>
79
- </box>
80
- );
81
- }
@@ -1,177 +0,0 @@
1
- import { useState, useEffect } from "react";
2
- import type { SelectOption } from "@opentui/core";
3
- import { Theme } from "../theme.ts";
4
- import { useKeyboardHandler, KeyboardPriority } from "../hooks/useKeyboardHandler.ts";
5
- import type { FieldConfig } from "./types.ts";
6
-
7
- interface EditorModalProps {
8
- /** The key of the field being edited */
9
- fieldKey: string | null;
10
- /** The current value of the field */
11
- currentValue: unknown;
12
- /** Whether the modal is visible */
13
- visible: boolean;
14
- /** Called when the user submits a new value */
15
- onSubmit: (value: unknown) => void;
16
- /** Called when the user cancels editing */
17
- onCancel: () => void;
18
- /** Field configurations */
19
- fieldConfigs: FieldConfig[];
20
- }
21
-
22
- /**
23
- * Modal for editing field values.
24
- * Supports text, number, enum, and boolean types.
25
- */
26
- export function EditorModal({
27
- fieldKey,
28
- currentValue,
29
- visible,
30
- onSubmit,
31
- onCancel,
32
- fieldConfigs,
33
- }: EditorModalProps) {
34
- const [inputValue, setInputValue] = useState("");
35
- const [selectIndex, setSelectIndex] = useState(0);
36
-
37
- // Reset state when field changes
38
- useEffect(() => {
39
- if (fieldKey && visible) {
40
- setInputValue(String(currentValue ?? ""));
41
-
42
- // For enums, find current index
43
- const fieldConfig = fieldConfigs.find((f) => f.key === fieldKey);
44
- if (fieldConfig?.options) {
45
- const idx = fieldConfig.options.findIndex((o) => o.value === currentValue);
46
- setSelectIndex(idx >= 0 ? idx : 0);
47
- }
48
- }
49
- }, [fieldKey, currentValue, visible, fieldConfigs]);
50
-
51
- // Modal keyboard handler - blocks all keys from bubbling out of the modal
52
- useKeyboardHandler(
53
- (event) => {
54
- if (event.key.name === "escape") {
55
- onCancel();
56
- }
57
- },
58
- KeyboardPriority.Modal,
59
- { enabled: visible, modal: true }
60
- );
61
-
62
- if (!visible || !fieldKey) {
63
- return null;
64
- }
65
-
66
- const fieldConfig = fieldConfigs.find((f) => f.key === fieldKey);
67
- if (!fieldConfig) {
68
- return null;
69
- }
70
-
71
- const isEnum = fieldConfig.type === "enum" && fieldConfig.options;
72
- const isBoolean = fieldConfig.type === "boolean";
73
- const isNumber = fieldConfig.type === "number";
74
-
75
- const handleInputSubmit = (value: string) => {
76
- if (isNumber) {
77
- onSubmit(parseInt(value.replace(/[^0-9-]/g, ""), 10) || 0);
78
- } else {
79
- onSubmit(value);
80
- }
81
- };
82
-
83
- const handleSelectIndexChange = (index: number, _option: SelectOption | null) => {
84
- setSelectIndex(index);
85
- };
86
-
87
- const handleSelectSubmit = (_index: number, option: SelectOption | null) => {
88
- if (option) {
89
- onSubmit(option.value);
90
- }
91
- };
92
-
93
- const handleBooleanSubmit = (_index: number, option: SelectOption | null) => {
94
- if (option) {
95
- onSubmit(option.value === true);
96
- }
97
- };
98
-
99
- // Boolean uses select with True/False options
100
- const booleanOptions: SelectOption[] = [
101
- { name: "False", description: "", value: false },
102
- { name: "True", description: "", value: true },
103
- ];
104
-
105
- return (
106
- <box
107
- position="absolute"
108
- top={4}
109
- left={6}
110
- width="60%"
111
- height={12}
112
- backgroundColor={Theme.overlay}
113
- border={true}
114
- borderStyle="rounded"
115
- borderColor={Theme.overlayTitle}
116
- padding={1}
117
- flexDirection="column"
118
- gap={1}
119
- zIndex={20}
120
- >
121
- <text fg={Theme.overlayTitle}>
122
- <strong>Edit: {fieldConfig.label}</strong>
123
- </text>
124
-
125
- {isEnum && fieldConfig.options && (
126
- <select
127
- options={fieldConfig.options.map((o) => ({
128
- name: o.name,
129
- value: o.value,
130
- description: "",
131
- }))}
132
- selectedIndex={selectIndex}
133
- focused={true}
134
- onChange={handleSelectIndexChange}
135
- onSelect={handleSelectSubmit}
136
- showScrollIndicator={true}
137
- showDescription={false}
138
- height={6}
139
- width="100%"
140
- wrapSelection={true}
141
- selectedBackgroundColor="#61afef"
142
- selectedTextColor="#1e2127"
143
- />
144
- )}
145
-
146
- {isBoolean && (
147
- <select
148
- options={booleanOptions}
149
- selectedIndex={currentValue ? 1 : 0}
150
- focused={true}
151
- onSelect={handleBooleanSubmit}
152
- showScrollIndicator={false}
153
- showDescription={false}
154
- height={2}
155
- width="100%"
156
- wrapSelection={true}
157
- selectedBackgroundColor="#61afef"
158
- selectedTextColor="#1e2127"
159
- />
160
- )}
161
-
162
- {!isEnum && !isBoolean && (
163
- <input
164
- value={inputValue}
165
- placeholder={fieldConfig.placeholder ?? `Enter ${fieldConfig.label.toLowerCase()}...`}
166
- focused={true}
167
- onInput={(value) => setInputValue(value)}
168
- onSubmit={handleInputSubmit}
169
- />
170
- )}
171
-
172
- <text fg={Theme.statusText}>
173
- Enter to save, Esc to cancel
174
- </text>
175
- </box>
176
- );
177
- }
@@ -1,86 +0,0 @@
1
- import { Theme } from "../theme.ts";
2
- import { LogLevel, type LogEntry } from "../hooks/useLogStream.ts";
3
-
4
- // Colors for different log levels
5
- const LogColors: Record<LogLevel, string> = {
6
- [LogLevel.Silly]: "#8c8c8c",
7
- [LogLevel.Trace]: "#6dd6ff",
8
- [LogLevel.Debug]: "#7bdcb5",
9
- [LogLevel.Info]: "#d6dde6",
10
- [LogLevel.Warn]: "#f5c542",
11
- [LogLevel.Error]: "#f78888",
12
- [LogLevel.Fatal]: "#ff5c8d",
13
- };
14
-
15
- interface LogsPanelProps {
16
- /** Log entries to display */
17
- logs: LogEntry[];
18
- /** Whether the panel is visible */
19
- visible: boolean;
20
- /** Whether the panel is focused */
21
- focused: boolean;
22
- /** Whether to expand to fill available space */
23
- expanded?: boolean;
24
- }
25
-
26
- /**
27
- * Panel displaying log entries with color-coded levels.
28
- */
29
- export function LogsPanel({
30
- logs,
31
- visible,
32
- focused,
33
- expanded = false,
34
- }: LogsPanelProps) {
35
- if (!visible) {
36
- return null;
37
- }
38
-
39
- const borderColor = focused ? Theme.borderFocused : Theme.border;
40
- const title = `Logs - ${logs.length}`;
41
-
42
- // When expanded, grow to fill. Otherwise fixed height.
43
- const boxProps = expanded
44
- ? { flexGrow: 1 }
45
- : { height: 10, flexShrink: 0 };
46
-
47
- return (
48
- <box
49
- flexDirection="column"
50
- border={true}
51
- borderStyle="rounded"
52
- borderColor={borderColor}
53
- title={title}
54
- padding={1}
55
- {...boxProps}
56
- >
57
- <scrollbox
58
- scrollY={true}
59
- flexGrow={1}
60
- stickyScroll={true}
61
- stickyStart="bottom"
62
- focused={focused}
63
- >
64
- <box flexDirection="column" gap={0}>
65
- {logs.map((log, idx) => {
66
- const color = LogColors[log.level] ?? Theme.statusText;
67
- // Strip ANSI codes but preserve line breaks
68
- const sanitized = typeof Bun !== "undefined"
69
- ? Bun.stripANSI(log.message).trim()
70
- : log.message.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").trim();
71
-
72
- return (
73
- <text key={`${log.timestamp.getTime()}-${idx}`} fg={color}>
74
- {sanitized}
75
- </text>
76
- );
77
- })}
78
-
79
- {logs.length === 0 && (
80
- <text fg={Theme.label}>No logs yet...</text>
81
- )}
82
- </box>
83
- </scrollbox>
84
- </box>
85
- );
86
- }
@@ -1,13 +0,0 @@
1
- export { FieldRow } from "./FieldRow.tsx";
2
- export { ActionButton } from "./ActionButton.tsx";
3
- export { Header } from "./Header.tsx";
4
- export { StatusBar } from "./StatusBar.tsx";
5
- export { LogsPanel } from "./LogsPanel.tsx";
6
- export { ResultsPanel } from "./ResultsPanel.tsx";
7
- export { ConfigForm } from "./ConfigForm.tsx";
8
- export { EditorModal } from "./EditorModal.tsx";
9
- export { CliModal } from "./CliModal.tsx";
10
- export { CommandSelector } from "./CommandSelector.tsx";
11
- export { JsonHighlight, type JsonHighlightProps } from "./JsonHighlight.tsx";
12
-
13
- export type { FieldType, FieldOption, FieldConfig } from "./types.ts";
@@ -1,7 +0,0 @@
1
- export {
2
- KeyboardProvider,
3
- useKeyboardContext,
4
- KeyboardPriority,
5
- type KeyboardEvent,
6
- type KeyboardHandler,
7
- } from "./KeyboardContext.tsx";
@@ -1,35 +0,0 @@
1
- export {
2
- useKeyboardHandler,
3
- KeyboardPriority,
4
- type KeyboardEvent,
5
- } from "./useKeyboardHandler.ts";
6
-
7
- export {
8
- useClipboard,
9
- type UseClipboardResult,
10
- } from "./useClipboard.ts";
11
-
12
- export {
13
- useSpinner,
14
- type UseSpinnerResult,
15
- } from "./useSpinner.ts";
16
-
17
- export {
18
- useConfigState,
19
- type UseConfigStateOptions,
20
- type UseConfigStateResult,
21
- } from "./useConfigState.ts";
22
-
23
- export {
24
- useCommandExecutor,
25
- type UseCommandExecutorResult,
26
- } from "./useCommandExecutor.ts";
27
-
28
- export {
29
- useLogStream,
30
- LogLevel,
31
- type LogEntry,
32
- type LogEvent,
33
- type LogSource,
34
- type UseLogStreamResult,
35
- } from "./useLogStream.ts";
@@ -1,91 +0,0 @@
1
- import { useEffect, useId, useRef } from "react";
2
- import {
3
- useKeyboardContext,
4
- KeyboardPriority,
5
- type KeyboardHandler,
6
- type KeyboardEvent,
7
- } from "../context/KeyboardContext.tsx";
8
-
9
- interface UseKeyboardHandlerOptions {
10
- /**
11
- * Whether the handler is currently enabled.
12
- * When false, the handler is unregistered.
13
- * Useful for conditionally handling keys only when focused.
14
- * @default true
15
- */
16
- enabled?: boolean;
17
-
18
- /**
19
- * When true, automatically calls stopPropagation() after the handler runs,
20
- * blocking keys from reaching lower-priority handlers.
21
- * Does NOT block OpenTUI primitives (input/select) from receiving keys.
22
- * Use this for modal dialogs that should capture keyboard focus.
23
- * @default false
24
- */
25
- modal?: boolean;
26
- }
27
-
28
- /**
29
- * Register a keyboard handler with the KeyboardProvider.
30
- *
31
- * @param handler - Callback invoked on keyboard events.
32
- * - Call `event.stopPropagation()` to stop our handlers from receiving the event.
33
- * - Call `event.key.preventDefault()` to also block OpenTUI primitives.
34
- * @param priority - Handler priority level. Higher priorities are called first.
35
- * @param options - Optional configuration (e.g., enabled flag, modal behavior).
36
- *
37
- * @example
38
- * ```tsx
39
- * // Modal handler - blocks lower-priority handlers but lets OpenTUI primitives work
40
- * useKeyboardHandler(
41
- * (event) => {
42
- * if (event.key.name === "escape") {
43
- * onClose();
44
- * }
45
- * // Other keys pass through to <input>/<select> but not to ConfigForm
46
- * },
47
- * KeyboardPriority.Modal,
48
- * { enabled: isVisible, modal: true }
49
- * );
50
- * ```
51
- */
52
- export function useKeyboardHandler(
53
- handler: KeyboardHandler,
54
- priority: KeyboardPriority,
55
- options: UseKeyboardHandlerOptions = {}
56
- ): void {
57
- const { enabled = true, modal = false } = options;
58
- const { register, unregister } = useKeyboardContext();
59
- const id = useId();
60
-
61
- // Keep handler ref stable to avoid re-registrations on every render
62
- const handlerRef = useRef(handler);
63
- handlerRef.current = handler;
64
-
65
- // Keep modal ref stable
66
- const modalRef = useRef(modal);
67
- modalRef.current = modal;
68
-
69
- useEffect(() => {
70
- if (!enabled) {
71
- unregister(id);
72
- return;
73
- }
74
-
75
- // Register with a stable wrapper that calls the current handler
76
- register(id, (event: KeyboardEvent) => {
77
- handlerRef.current(event);
78
- // For modals, always stop propagation to our handlers (but not OpenTUI primitives)
79
- if (modalRef.current) {
80
- event.stopPropagation();
81
- }
82
- }, priority);
83
-
84
- return () => {
85
- unregister(id);
86
- };
87
- }, [id, priority, enabled, register, unregister]);
88
- }
89
-
90
- export { KeyboardPriority };
91
- export type { KeyboardEvent };
@@ -1,96 +0,0 @@
1
- import { useState, useEffect, useCallback } from "react";
2
-
3
- /**
4
- * Log levels for display styling.
5
- */
6
- export enum LogLevel {
7
- Silly = "silly",
8
- Trace = "trace",
9
- Debug = "debug",
10
- Info = "info",
11
- Warn = "warn",
12
- Error = "error",
13
- Fatal = "fatal",
14
- }
15
-
16
- /**
17
- * Log entry for display.
18
- */
19
- export interface LogEntry {
20
- timestamp: Date;
21
- level: LogLevel;
22
- message: string;
23
- }
24
-
25
- /**
26
- * Log event emitted by the log source.
27
- */
28
- export interface LogEvent {
29
- level: LogLevel;
30
- message: string;
31
- }
32
-
33
- /**
34
- * Log source that can be subscribed to.
35
- */
36
- export interface LogSource {
37
- /** Subscribe to log events, returns unsubscribe function */
38
- subscribe: (callback: (event: LogEvent) => void) => () => void;
39
- }
40
-
41
- export interface UseLogStreamResult {
42
- /** All collected log entries */
43
- logs: LogEntry[];
44
- /** Clear all logs */
45
- clearLogs: () => void;
46
- /** Add a log entry manually */
47
- addLog: (level: LogLevel, message: string) => void;
48
- }
49
-
50
- /**
51
- * Hook for subscribing to a log stream.
52
- *
53
- * @param source - Optional log source to subscribe to
54
- * @returns Log stream state and functions
55
- *
56
- * @example
57
- * ```tsx
58
- * const { logs, clearLogs } = useLogStream(myLogSource);
59
- * ```
60
- */
61
- export function useLogStream(source?: LogSource): UseLogStreamResult {
62
- const [logs, setLogs] = useState<LogEntry[]>([]);
63
-
64
- // Subscribe to log source
65
- useEffect(() => {
66
- if (!source) return;
67
-
68
- const unsubscribe = source.subscribe((event: LogEvent) => {
69
- setLogs((prev) => {
70
- const newEntry: LogEntry = {
71
- timestamp: new Date(),
72
- level: event.level,
73
- message: event.message,
74
- };
75
- return [...prev, newEntry];
76
- });
77
- });
78
-
79
- return () => {
80
- unsubscribe?.();
81
- };
82
- }, [source]);
83
-
84
- const clearLogs = useCallback(() => {
85
- setLogs([]);
86
- }, []);
87
-
88
- const addLog = useCallback((level: LogLevel, message: string) => {
89
- setLogs((prev) => [
90
- ...prev,
91
- { timestamp: new Date(), level, message },
92
- ]);
93
- }, []);
94
-
95
- return { logs, clearLogs, addLog };
96
- }
package/src/tui/index.ts DELETED
@@ -1,65 +0,0 @@
1
- // Main TUI components
2
- export { TuiApp } from "./TuiApp.tsx";
3
- export { TuiApplication, type TuiApplicationConfig, type CustomField } from "./TuiApplication.tsx";
4
-
5
- // Theme
6
- export { Theme, type ThemeColors } from "./theme.ts";
7
-
8
- // Context
9
- export {
10
- KeyboardProvider,
11
- useKeyboardContext,
12
- KeyboardPriority,
13
- type KeyboardEvent,
14
- type KeyboardHandler,
15
- } from "./context/index.ts";
16
-
17
- // Hooks
18
- export {
19
- useKeyboardHandler,
20
- useClipboard,
21
- useSpinner,
22
- useConfigState,
23
- useCommandExecutor,
24
- useLogStream,
25
- LogLevel,
26
- type UseClipboardResult,
27
- type UseSpinnerResult,
28
- type UseConfigStateOptions,
29
- type UseConfigStateResult,
30
- type UseCommandExecutorResult,
31
- type LogEntry,
32
- type LogEvent,
33
- type LogSource,
34
- type UseLogStreamResult,
35
- } from "./hooks/index.ts";
36
-
37
- // Components
38
- export {
39
- FieldRow,
40
- ActionButton,
41
- Header,
42
- StatusBar,
43
- LogsPanel,
44
- ResultsPanel,
45
- ConfigForm,
46
- EditorModal,
47
- CliModal,
48
- CommandSelector,
49
- JsonHighlight,
50
- type FieldType,
51
- type FieldOption,
52
- type FieldConfig,
53
- type JsonHighlightProps,
54
- } from "./components/index.ts";
55
-
56
- // Utilities
57
- export {
58
- schemaToFieldConfigs,
59
- groupFieldConfigs,
60
- getFieldDisplayValue,
61
- buildCliCommand,
62
- } from "./utils/index.ts";
63
-
64
- // Legacy export for backward compatibility
65
- export * from "./app.ts";
@@ -1,13 +0,0 @@
1
- export {
2
- schemaToFieldConfigs,
3
- groupFieldConfigs,
4
- getFieldDisplayValue,
5
- } from "./schemaToFields.ts";
6
-
7
- export { buildCliCommand } from "./buildCliCommand.ts";
8
-
9
- export {
10
- loadPersistedParameters,
11
- savePersistedParameters,
12
- clearPersistedParameters,
13
- } from "./parameterPersistence.ts";
@@ -1 +0,0 @@
1
- export * from "./command.ts";
package/tsconfig.json DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": ["ESNext"],
4
- "target": "ESNext",
5
- "module": "ESNext",
6
- "moduleDetection": "force",
7
- "jsx": "react-jsx",
8
- "jsxImportSource": "@opentui/react",
9
- "allowJs": true,
10
- "moduleResolution": "bundler",
11
- "allowImportingTsExtensions": true,
12
- "verbatimModuleSyntax": true,
13
- "noEmit": true,
14
- "strict": true,
15
- "skipLibCheck": true,
16
- "noFallthroughCasesInSwitch": true,
17
- "noUnusedLocals": true,
18
- "noUnusedParameters": true,
19
- "noPropertyAccessFromIndexSignature": true,
20
- "noUncheckedIndexedAccess": true,
21
- "esModuleInterop": true,
22
- "types": ["bun"]
23
- },
24
- "include": ["src/**/*", "examples/**/*"]
25
- }