@markjaquith/agency 2.32.1 → 2.32.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/package.json +1 -1
- package/src/utils/interactive.test.tsx +21 -1
- package/src/utils/interactive.tsx +16 -11
package/package.json
CHANGED
|
@@ -44,7 +44,7 @@ describe("OpenTUI interaction", () => {
|
|
|
44
44
|
test("uses the split-footer renderer contract", () => {
|
|
45
45
|
expect(interactiveRendererConfig).toMatchObject({
|
|
46
46
|
screenMode: "split-footer",
|
|
47
|
-
footerHeight:
|
|
47
|
+
footerHeight: 5,
|
|
48
48
|
externalOutputMode: "capture-stdout",
|
|
49
49
|
clearOnShutdown: false,
|
|
50
50
|
})
|
|
@@ -212,6 +212,26 @@ describe("OpenTUI interaction", () => {
|
|
|
212
212
|
}
|
|
213
213
|
})
|
|
214
214
|
|
|
215
|
+
test("wraps text prompt input onto another line", async () => {
|
|
216
|
+
const setup = await testRender(
|
|
217
|
+
() => <InteractiveTextPrompt prompt="Task ID" onDone={() => {}} />,
|
|
218
|
+
{ width: 16, height: 5 },
|
|
219
|
+
)
|
|
220
|
+
try {
|
|
221
|
+
await setup.renderer.setupTerminal()
|
|
222
|
+
await setup.renderOnce()
|
|
223
|
+
await Bun.sleep(0)
|
|
224
|
+
await setup.mockInput.typeText("alpha beta gamma delta")
|
|
225
|
+
await setup.flush()
|
|
226
|
+
const frame = setup.captureCharFrame()
|
|
227
|
+
expect(frame).toContain("alpha beta")
|
|
228
|
+
expect(frame).toContain("gamma")
|
|
229
|
+
expect(frame).toMatch(/alpha beta\s*\ngamma delta/)
|
|
230
|
+
} finally {
|
|
231
|
+
setup.renderer.destroy()
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
|
|
215
235
|
test("submits text and cancels with ctrl-c or escape", async () => {
|
|
216
236
|
let submitted: string | null | undefined
|
|
217
237
|
const input = await testRender(
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
createCliRenderer,
|
|
5
5
|
type CliRenderer,
|
|
6
6
|
type CliRendererConfig,
|
|
7
|
-
type
|
|
7
|
+
type TextareaRenderable,
|
|
8
8
|
} from "@opentui/core"
|
|
9
9
|
import { render, useKeyboard, type JSX } from "@opentui/solid"
|
|
10
10
|
import { createMemo, createSignal, For } from "solid-js"
|
|
@@ -16,7 +16,7 @@ export interface InteractiveChoice {
|
|
|
16
16
|
|
|
17
17
|
export const interactiveRendererConfig = {
|
|
18
18
|
screenMode: "split-footer",
|
|
19
|
-
footerHeight:
|
|
19
|
+
footerHeight: 5,
|
|
20
20
|
externalOutputMode: "capture-stdout",
|
|
21
21
|
consoleMode: "disabled",
|
|
22
22
|
clearOnShutdown: false,
|
|
@@ -35,7 +35,7 @@ const isCancel = (key: { name: string; ctrl: boolean }) =>
|
|
|
35
35
|
key.name === "escape" || (key.ctrl && key.name === "c")
|
|
36
36
|
|
|
37
37
|
export const InteractiveTextPrompt = (props: PromptProps<string>) => {
|
|
38
|
-
let input:
|
|
38
|
+
let input: TextareaRenderable | undefined
|
|
39
39
|
let value = ""
|
|
40
40
|
useKeyboard((key) => {
|
|
41
41
|
if (isCancel(key)) {
|
|
@@ -53,10 +53,13 @@ export const InteractiveTextPrompt = (props: PromptProps<string>) => {
|
|
|
53
53
|
return (
|
|
54
54
|
<box flexDirection="column" width="100%" height="100%">
|
|
55
55
|
<text fg="#7aa2f7">{props.prompt}</text>
|
|
56
|
-
<
|
|
56
|
+
<textarea
|
|
57
57
|
focused
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
height={2}
|
|
59
|
+
wrapMode="word"
|
|
60
|
+
keyBindings={[{ name: "return", action: "submit" }]}
|
|
61
|
+
onContentChange={() => {
|
|
62
|
+
value = input?.plainText ?? ""
|
|
60
63
|
}}
|
|
61
64
|
ref={(next) => {
|
|
62
65
|
input = next
|
|
@@ -140,7 +143,7 @@ export const fuzzyChoices = (
|
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
export const InteractiveSelectPrompt = (props: SelectPromptProps) => {
|
|
143
|
-
let input:
|
|
146
|
+
let input: TextareaRenderable | undefined
|
|
144
147
|
const [query, setQuery] = createSignal("")
|
|
145
148
|
const [selected, setSelected] = createSignal(0)
|
|
146
149
|
const choices = createMemo(() => fuzzyChoices(props.choices, query()))
|
|
@@ -195,13 +198,16 @@ export const InteractiveSelectPrompt = (props: SelectPromptProps) => {
|
|
|
195
198
|
{props.prompt}
|
|
196
199
|
</text>
|
|
197
200
|
<text fg="#7aa2f7">{" > "}</text>
|
|
198
|
-
<
|
|
201
|
+
<textarea
|
|
199
202
|
focused
|
|
200
203
|
flexGrow={1}
|
|
201
204
|
minWidth={8}
|
|
205
|
+
height={2}
|
|
206
|
+
wrapMode="word"
|
|
202
207
|
placeholder="filter"
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
keyBindings={[{ name: "return", action: "submit" }]}
|
|
209
|
+
onContentChange={() => {
|
|
210
|
+
setQuery(input?.plainText ?? "")
|
|
205
211
|
setSelected(0)
|
|
206
212
|
}}
|
|
207
213
|
ref={(next) => {
|
|
@@ -266,7 +272,6 @@ async function runInteractive<T>(
|
|
|
266
272
|
} finally {
|
|
267
273
|
if (renderer) {
|
|
268
274
|
await shutdown(renderer)
|
|
269
|
-
process.stdout.write("\n")
|
|
270
275
|
}
|
|
271
276
|
}
|
|
272
277
|
}
|