@gridland/demo 0.2.11 → 0.2.13

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/dist/run.js ADDED
@@ -0,0 +1,1920 @@
1
+ // src/run.tsx
2
+ import { createCliRenderer } from "@opentui/core";
3
+ import { createRoot, useKeyboard as useKeyboard2 } from "@opentui/react";
4
+
5
+ // ../ui/scripts/demo-apps.tsx
6
+ import { useState as useState9, useCallback as useCallback2, useRef as useRef6 } from "react";
7
+ import { useKeyboard } from "@opentui/react";
8
+
9
+ // ../ui/components/link/link.tsx
10
+ import { jsx } from "react/jsx-runtime";
11
+ var UNDERLINE = 1 << 3;
12
+ var UNDERLINE_DASHED = 1 << 4;
13
+ var UNDERLINE_DOTTED = 1 << 6;
14
+ function Link({ children, url, underline = "solid" }) {
15
+ let attributes = 0;
16
+ if (underline === "solid") {
17
+ attributes = UNDERLINE;
18
+ } else if (underline === "dashed") {
19
+ attributes = UNDERLINE | UNDERLINE_DASHED;
20
+ } else if (underline === "dotted") {
21
+ attributes = UNDERLINE | UNDERLINE_DOTTED;
22
+ }
23
+ return /* @__PURE__ */ jsx("text", { children: /* @__PURE__ */ jsx("a", { href: url, style: { attributes }, children }) });
24
+ }
25
+
26
+ // ../ui/components/link/link-demo.tsx
27
+ import { useState } from "react";
28
+
29
+ // ../ui/components/text-style.ts
30
+ var BOLD = 1 << 0;
31
+ var DIM = 1 << 1;
32
+ var ITALIC = 1 << 2;
33
+ var UNDERLINE2 = 1 << 3;
34
+ var INVERSE = 1 << 5;
35
+ function textStyle(opts) {
36
+ let attributes = 0;
37
+ if (opts.bold) attributes |= BOLD;
38
+ if (opts.dim) attributes |= DIM;
39
+ if (opts.italic) attributes |= ITALIC;
40
+ if (opts.underline) attributes |= UNDERLINE2;
41
+ if (opts.inverse) attributes |= INVERSE;
42
+ return {
43
+ ...opts.fg ? { fg: opts.fg } : {},
44
+ ...opts.bg ? { bg: opts.bg } : {},
45
+ ...attributes ? { attributes } : {}
46
+ };
47
+ }
48
+
49
+ // ../ui/components/status-bar/status-bar.tsx
50
+ import { jsx as jsx2 } from "react/jsx-runtime";
51
+ function StatusBar({ items, extra }) {
52
+ const parts = [];
53
+ if (extra !== void 0) {
54
+ parts.push(
55
+ /* @__PURE__ */ jsx2("span", { children: extra }, "extra")
56
+ );
57
+ parts.push(
58
+ /* @__PURE__ */ jsx2("span", { style: textStyle({ dim: true }), children: " \u2502 " }, "pipe")
59
+ );
60
+ }
61
+ items.forEach((item, i) => {
62
+ if (i > 0) {
63
+ parts.push(/* @__PURE__ */ jsx2("span", { children: " " }, `gap-${i}`));
64
+ }
65
+ parts.push(
66
+ /* @__PURE__ */ jsx2("span", { style: textStyle({ inverse: true, bold: true }), children: ` ${item.key} ` }, `key-${i}`)
67
+ );
68
+ parts.push(
69
+ /* @__PURE__ */ jsx2("span", { style: textStyle({ dim: true }), children: ` ${item.label}` }, `label-${i}`)
70
+ );
71
+ });
72
+ if (parts.length === 0) {
73
+ return null;
74
+ }
75
+ return /* @__PURE__ */ jsx2("text", { children: parts });
76
+ }
77
+
78
+ // ../ui/components/link/link-demo.tsx
79
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
80
+ var MODES = ["solid", "dashed", "dotted", "none"];
81
+ function LinkDemo({
82
+ url = "https://opentui.com",
83
+ label = "Visit opentui.com",
84
+ useKeyboard: useKeyboard3
85
+ }) {
86
+ const [modeIndex, setModeIndex] = useState(0);
87
+ const mode = MODES[modeIndex];
88
+ useKeyboard3?.((event) => {
89
+ if (event.name === "right") {
90
+ setModeIndex((i) => (i + 1) % MODES.length);
91
+ } else if (event.name === "left") {
92
+ setModeIndex((i) => (i - 1 + MODES.length) % MODES.length);
93
+ }
94
+ });
95
+ return /* @__PURE__ */ jsxs("box", { flexDirection: "column", gap: 1, children: [
96
+ /* @__PURE__ */ jsx3(Link, { url, underline: mode, children: label }),
97
+ /* @__PURE__ */ jsx3(
98
+ StatusBar,
99
+ {
100
+ extra: /* @__PURE__ */ jsx3("span", { style: textStyle({ bold: true }), children: mode.padEnd(6) }),
101
+ items: [
102
+ { key: "\u2190\u2192", label: "underline style" }
103
+ ]
104
+ }
105
+ )
106
+ ] });
107
+ }
108
+
109
+ // ../ui/components/ascii/ascii.tsx
110
+ import { jsx as jsx4 } from "react/jsx-runtime";
111
+
112
+ // ../ui/components/spinner/spinner.tsx
113
+ import { useEffect, useState as useState2 } from "react";
114
+
115
+ // ../ui/components/theme/themes.ts
116
+ var darkTheme = {
117
+ primary: "#FF71CE",
118
+ accent: "#01CDFE",
119
+ secondary: "#B967FF",
120
+ muted: "#7B6F8E",
121
+ border: "#B967FF",
122
+ text: "#F0E6FF",
123
+ success: "#05FFA1",
124
+ error: "#FF6B6B",
125
+ warning: "#FFC164"
126
+ };
127
+
128
+ // ../ui/components/theme/theme-context.tsx
129
+ import { createContext, useContext } from "react";
130
+ import { jsx as jsx5 } from "react/jsx-runtime";
131
+ var ThemeContext = createContext(null);
132
+ function useTheme() {
133
+ return useContext(ThemeContext) ?? darkTheme;
134
+ }
135
+
136
+ // ../ui/components/spinner/spinner.tsx
137
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
138
+ var VARIANTS = {
139
+ dots: { frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"], interval: 83 },
140
+ pulse: { frames: ["\xB7", "\u2219", "\u25CF", "\u2219", "\xB7", "\xB7", "\xB7"], interval: 180 },
141
+ meter: { frames: ["\u25B1\u25B1\u25B1", "\u25B0\u25B1\u25B1", "\u25B0\u25B0\u25B1", "\u25B0\u25B0\u25B0", "\u25B0\u25B0\u25B1", "\u25B0\u25B1\u25B1", "\u25B1\u25B1\u25B1"], interval: 143 },
142
+ bloom: { frames: ["\xB7", "\u2726", "\u2727", "\u2739", "\u273A", "\u274B", "\u2738", "\u2735", "\u2738", "\u274B", "\u273A", "\u2739", "\u2727", "\u2726", "\xB7", "\xB7"], interval: 100 },
143
+ ellipsis: { frames: [" ", ". ", ".. ", "..."], interval: 333 }
144
+ };
145
+ var VARIANT_NAMES = Object.keys(VARIANTS);
146
+ function Spinner({ variant = "dots", text, color }) {
147
+ const theme = useTheme();
148
+ const resolvedColor = color ?? theme.accent;
149
+ const { frames, interval } = VARIANTS[variant];
150
+ const [frame, setFrame] = useState2(0);
151
+ useEffect(() => {
152
+ setFrame(0);
153
+ const timer = setInterval(() => {
154
+ setFrame((prev) => (prev + 1) % frames.length);
155
+ }, interval);
156
+ return () => clearInterval(timer);
157
+ }, [variant]);
158
+ return /* @__PURE__ */ jsxs2("text", { children: [
159
+ /* @__PURE__ */ jsx6("span", { style: { fg: resolvedColor }, children: frames[frame] }),
160
+ text ? /* @__PURE__ */ jsxs2("span", { children: [
161
+ " ",
162
+ text
163
+ ] }) : null
164
+ ] });
165
+ }
166
+
167
+ // ../ui/components/spinner/spinner-showcase.tsx
168
+ import { useState as useState3 } from "react";
169
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
170
+ function SpinnerPicker({ useKeyboard: useKeyboard3 }) {
171
+ const theme = useTheme();
172
+ const [selected, setSelected] = useState3(0);
173
+ useKeyboard3?.((event) => {
174
+ if (event.name === "left") {
175
+ setSelected((s) => s > 0 ? s - 1 : VARIANT_NAMES.length - 1);
176
+ } else if (event.name === "right") {
177
+ setSelected((s) => s < VARIANT_NAMES.length - 1 ? s + 1 : 0);
178
+ }
179
+ });
180
+ const selectedName = VARIANT_NAMES[selected];
181
+ return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", flexGrow: 1, padding: 1, children: [
182
+ /* @__PURE__ */ jsx7("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: /* @__PURE__ */ jsx7(Spinner, { variant: selectedName, color: theme.accent }) }),
183
+ /* @__PURE__ */ jsx7(
184
+ StatusBar,
185
+ {
186
+ items: [{ key: "\u2190\u2192", label: "change spinner type" }],
187
+ extra: /* @__PURE__ */ jsx7("span", { style: textStyle({ fg: theme.accent, bold: true }), children: selectedName.padEnd(8) })
188
+ }
189
+ )
190
+ ] });
191
+ }
192
+
193
+ // ../ui/components/text-input/text-input.tsx
194
+ import { useState as useState4, useCallback } from "react";
195
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
196
+ function TextInput({
197
+ value: controlledValue,
198
+ onChange,
199
+ onSubmit,
200
+ placeholder = "",
201
+ prompt = "> ",
202
+ promptColor,
203
+ focus = true,
204
+ maxLength
205
+ }) {
206
+ const theme = useTheme();
207
+ const resolvedPromptColor = promptColor ?? theme.accent;
208
+ const [internalValue, setInternalValue] = useState4("");
209
+ const isControlled = controlledValue !== void 0;
210
+ const displayValue = isControlled ? controlledValue : internalValue;
211
+ const handleInput = useCallback(
212
+ (newValue) => {
213
+ if (!isControlled) setInternalValue(newValue);
214
+ onChange?.(newValue);
215
+ },
216
+ [isControlled, onChange]
217
+ );
218
+ const handleSubmit = useCallback(
219
+ (value) => {
220
+ onSubmit?.(value);
221
+ if (!isControlled) setInternalValue("");
222
+ },
223
+ [isControlled, onSubmit]
224
+ );
225
+ return /* @__PURE__ */ jsxs4("box", { children: [
226
+ prompt && /* @__PURE__ */ jsx8("text", { style: { fg: resolvedPromptColor }, children: prompt }),
227
+ /* @__PURE__ */ jsx8(
228
+ "input",
229
+ {
230
+ value: displayValue,
231
+ placeholder,
232
+ maxLength,
233
+ focused: focus,
234
+ onInput: handleInput,
235
+ onSubmit: handleSubmit
236
+ }
237
+ )
238
+ ] });
239
+ }
240
+
241
+ // ../ui/components/select-input/select-input.tsx
242
+ import { useReducer, useMemo, useRef } from "react";
243
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
244
+ function reducer(state, action) {
245
+ switch (action.type) {
246
+ case "MOVE": {
247
+ let next = state.cursor + action.direction;
248
+ if (next < 0) next = action.max - 1;
249
+ if (next >= action.max) next = 0;
250
+ return { ...state, cursor: next };
251
+ }
252
+ case "SUBMIT":
253
+ return { ...state, submitted: true };
254
+ default:
255
+ return state;
256
+ }
257
+ }
258
+ var VISIBLE = 12;
259
+ var BAR = "\u2502";
260
+ var RADIO = "\u25CB";
261
+ var CURSOR = "\u25B8";
262
+ var SEPARATOR = "\u2500";
263
+ function SelectInput({
264
+ items = [],
265
+ defaultValue,
266
+ value: controlledValue,
267
+ onChange,
268
+ disabled = false,
269
+ invalid = false,
270
+ required = false,
271
+ placeholder,
272
+ title = "Select",
273
+ submittedStatus = "submitted",
274
+ limit,
275
+ highlightColor,
276
+ onSubmit,
277
+ useKeyboard: useKeyboard3
278
+ }) {
279
+ const theme = useTheme();
280
+ const resolvedHighlight = highlightColor ?? theme.primary;
281
+ const isControlled = controlledValue !== void 0;
282
+ const controlledRef = useRef(isControlled);
283
+ if (controlledRef.current !== isControlled) {
284
+ console.warn("SelectInput: switching between controlled and uncontrolled is not supported.");
285
+ }
286
+ const initialIndex = items.findIndex((i) => i.value === (isControlled ? controlledValue : defaultValue));
287
+ const [state, dispatch] = useReducer(reducer, {
288
+ cursor: Math.max(0, initialIndex),
289
+ submitted: false
290
+ });
291
+ const { flatRows, selectableItems } = useMemo(() => {
292
+ const rows = [];
293
+ const selectable = [];
294
+ let index = 0;
295
+ const grouped = /* @__PURE__ */ new Map();
296
+ for (const item of items) {
297
+ const group = item.group ?? "";
298
+ const list = grouped.get(group) ?? [];
299
+ list.push(item);
300
+ grouped.set(group, list);
301
+ }
302
+ let first = true;
303
+ for (const [group, groupItems] of grouped) {
304
+ if (!first) rows.push({ type: "separator" });
305
+ first = false;
306
+ if (group) {
307
+ rows.push({ type: "group", label: group });
308
+ }
309
+ for (const item of groupItems) {
310
+ rows.push({ type: "item", item, index });
311
+ selectable.push({ item, index });
312
+ index++;
313
+ }
314
+ }
315
+ return { flatRows: rows, selectableItems: selectable };
316
+ }, [items]);
317
+ const visibleCount = limit ?? VISIBLE;
318
+ const cursorRowIndex = flatRows.findIndex((r) => r.type === "item" && r.index === state.cursor);
319
+ const scrollOffset = Math.max(0, Math.min(cursorRowIndex - Math.floor(visibleCount / 2), flatRows.length - visibleCount));
320
+ const visibleRows = flatRows.slice(scrollOffset, scrollOffset + visibleCount);
321
+ const diamondColor = invalid ? theme.error : disabled ? theme.muted : theme.accent;
322
+ useKeyboard3?.((event) => {
323
+ if (state.submitted || disabled) return;
324
+ if (event.name === "up" || event.name === "k") {
325
+ const direction = -1;
326
+ let next = state.cursor + direction;
327
+ if (next < 0) next = selectableItems.length - 1;
328
+ dispatch({ type: "MOVE", direction, max: selectableItems.length });
329
+ const nextItem = selectableItems[next];
330
+ if (nextItem && !nextItem.item.disabled) {
331
+ onChange?.(nextItem.item.value);
332
+ }
333
+ } else if (event.name === "down" || event.name === "j") {
334
+ const direction = 1;
335
+ let next = state.cursor + direction;
336
+ if (next >= selectableItems.length) next = 0;
337
+ dispatch({ type: "MOVE", direction, max: selectableItems.length });
338
+ const nextItem = selectableItems[next];
339
+ if (nextItem && !nextItem.item.disabled) {
340
+ onChange?.(nextItem.item.value);
341
+ }
342
+ } else if (event.name === "return") {
343
+ const current = selectableItems[state.cursor];
344
+ if (current && !current.item.disabled) {
345
+ dispatch({ type: "SUBMIT" });
346
+ onSubmit?.(isControlled ? controlledValue : current.item.value);
347
+ }
348
+ }
349
+ });
350
+ if (state.submitted) {
351
+ const selectedItem = isControlled ? items.find((i) => i.value === controlledValue) : selectableItems[state.cursor]?.item;
352
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
353
+ /* @__PURE__ */ jsxs5("text", { children: [
354
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ fg: theme.success }), children: "\u25C6 " }),
355
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ bold: true }), children: title })
356
+ ] }),
357
+ selectedItem && /* @__PURE__ */ jsxs5("text", { children: [
358
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.success }), children: [
359
+ BAR,
360
+ " "
361
+ ] }),
362
+ /* @__PURE__ */ jsx9("span", { children: " " }),
363
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ fg: theme.success }), children: "\u25CF " }),
364
+ /* @__PURE__ */ jsx9("span", { children: selectedItem.label })
365
+ ] }),
366
+ /* @__PURE__ */ jsx9("text", { children: " " }),
367
+ /* @__PURE__ */ jsx9("text", { children: /* @__PURE__ */ jsx9("span", { style: textStyle({ dim: true }), children: submittedStatus }) })
368
+ ] });
369
+ }
370
+ const hasItems = selectableItems.length > 0;
371
+ return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
372
+ /* @__PURE__ */ jsxs5("text", { children: [
373
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ fg: diamondColor, dim: disabled }), children: "\u25C6 " }),
374
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ bold: true, dim: disabled }), children: title }),
375
+ required && /* @__PURE__ */ jsx9("span", { style: textStyle({ fg: theme.error }), children: " *" })
376
+ ] }),
377
+ invalid && /* @__PURE__ */ jsxs5("text", { children: [
378
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.muted }), children: [
379
+ BAR,
380
+ " "
381
+ ] }),
382
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ fg: theme.error }), children: " Please select an option" })
383
+ ] }),
384
+ !hasItems && placeholder && /* @__PURE__ */ jsxs5("text", { children: [
385
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.muted }), children: [
386
+ BAR,
387
+ " "
388
+ ] }),
389
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ dim: true }), children: [
390
+ " ",
391
+ placeholder
392
+ ] })
393
+ ] }),
394
+ visibleRows.map((row, i) => {
395
+ if (row.type === "separator") {
396
+ return /* @__PURE__ */ jsxs5("text", { children: [
397
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.muted }), children: [
398
+ BAR,
399
+ " "
400
+ ] }),
401
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.muted }), children: [
402
+ " ",
403
+ SEPARATOR.repeat(20)
404
+ ] })
405
+ ] }, `sep-${i}`);
406
+ }
407
+ if (row.type === "group") {
408
+ return /* @__PURE__ */ jsxs5("text", { children: [
409
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.muted }), children: [
410
+ BAR,
411
+ " "
412
+ ] }),
413
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ bold: true, fg: theme.muted }), children: ` ${row.label}` })
414
+ ] }, `group-${row.label}`);
415
+ }
416
+ const { item, index: itemIndex } = row;
417
+ const isHighlighted = !disabled && itemIndex === state.cursor;
418
+ const isItemDisabled = disabled || !!item.disabled;
419
+ const itemColor = isItemDisabled ? theme.muted : isHighlighted ? resolvedHighlight : void 0;
420
+ return /* @__PURE__ */ jsxs5("text", { children: [
421
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.muted }), children: [
422
+ BAR,
423
+ " "
424
+ ] }),
425
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: isHighlighted ? resolvedHighlight : void 0 }), children: [
426
+ isHighlighted ? CURSOR : " ",
427
+ " "
428
+ ] }),
429
+ /* @__PURE__ */ jsxs5("span", { style: textStyle({ fg: theme.muted, dim: isItemDisabled }), children: [
430
+ RADIO,
431
+ " "
432
+ ] }),
433
+ /* @__PURE__ */ jsx9("span", { style: textStyle({ fg: itemColor, dim: isItemDisabled }), children: item.label })
434
+ ] }, item.key ?? String(item.value));
435
+ })
436
+ ] });
437
+ }
438
+
439
+ // ../ui/components/multi-select/multi-select.tsx
440
+ import { useReducer as useReducer2, useMemo as useMemo2, useRef as useRef2 } from "react";
441
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
442
+ function reducer2(state, action) {
443
+ switch (action.type) {
444
+ case "MOVE": {
445
+ let next = state.cursor + action.direction;
446
+ if (next < 0) next = action.max - 1;
447
+ if (next >= action.max) next = 0;
448
+ return { ...state, cursor: next };
449
+ }
450
+ case "SET_SELECTED":
451
+ return { ...state, selected: new Set(action.values) };
452
+ case "SUBMIT":
453
+ return { ...state, submitted: true };
454
+ default:
455
+ return state;
456
+ }
457
+ }
458
+ var VISIBLE2 = 12;
459
+ var BAR2 = "\u2502";
460
+ var CHECKED = "\u25CF";
461
+ var UNCHECKED = "\u25CB";
462
+ var CURSOR2 = "\u25B8";
463
+ var SEPARATOR2 = "\u2500";
464
+ function MultiSelect({
465
+ items = [],
466
+ defaultSelected = [],
467
+ selected: controlledSelected,
468
+ onChange,
469
+ disabled = false,
470
+ invalid = false,
471
+ required = false,
472
+ placeholder,
473
+ maxCount,
474
+ title = "Select",
475
+ submittedStatus = "submitted",
476
+ limit,
477
+ enableSelectAll = true,
478
+ enableClear = true,
479
+ highlightColor,
480
+ checkboxColor,
481
+ allowEmpty = false,
482
+ onSubmit,
483
+ useKeyboard: useKeyboard3
484
+ }) {
485
+ const theme = useTheme();
486
+ const resolvedHighlight = highlightColor ?? theme.primary;
487
+ const resolvedCheckbox = checkboxColor ?? theme.accent;
488
+ const isControlled = controlledSelected !== void 0;
489
+ const controlledRef = useRef2(isControlled);
490
+ if (controlledRef.current !== isControlled) {
491
+ console.warn("MultiSelect: switching between controlled and uncontrolled is not supported.");
492
+ }
493
+ const [state, dispatch] = useReducer2(reducer2, {
494
+ cursor: 0,
495
+ selected: new Set(isControlled ? controlledSelected : defaultSelected),
496
+ submitted: false
497
+ });
498
+ const cursorRef = useRef2(0);
499
+ const currentSelected = isControlled ? new Set(controlledSelected) : state.selected;
500
+ const { flatRows, selectableItems } = useMemo2(() => {
501
+ const rows = [];
502
+ const selectable = [];
503
+ let index = 0;
504
+ const grouped = /* @__PURE__ */ new Map();
505
+ for (const item of items) {
506
+ const group = item.group ?? "";
507
+ const list = grouped.get(group) ?? [];
508
+ list.push(item);
509
+ grouped.set(group, list);
510
+ }
511
+ let first = true;
512
+ for (const [group, groupItems] of grouped) {
513
+ if (!first) rows.push({ type: "separator" });
514
+ first = false;
515
+ if (group) {
516
+ rows.push({ type: "group", label: group });
517
+ }
518
+ for (const item of groupItems) {
519
+ rows.push({ type: "item", item, index });
520
+ selectable.push({ item, index });
521
+ index++;
522
+ }
523
+ }
524
+ return { flatRows: rows, selectableItems: selectable };
525
+ }, [items]);
526
+ const hasSubmitRow = allowEmpty || currentSelected.size > 0;
527
+ const totalPositions = selectableItems.length + (hasSubmitRow ? 1 : 0);
528
+ const isOnSubmit = hasSubmitRow && state.cursor === selectableItems.length;
529
+ const visibleCount = limit ?? VISIBLE2;
530
+ const cursorRowIndex = flatRows.findIndex((r) => r.type === "item" && r.index === state.cursor);
531
+ const scrollOffset = Math.max(0, Math.min(cursorRowIndex - Math.floor(visibleCount / 2), flatRows.length - visibleCount));
532
+ const visibleRows = flatRows.slice(scrollOffset, scrollOffset + visibleCount);
533
+ const setSelected = (values) => {
534
+ if (isControlled) {
535
+ onChange?.(values);
536
+ } else {
537
+ dispatch({ type: "SET_SELECTED", values });
538
+ }
539
+ };
540
+ const diamondColor = invalid ? theme.error : disabled ? theme.muted : theme.accent;
541
+ useKeyboard3?.((event) => {
542
+ if (state.submitted || disabled) return;
543
+ const move = (direction) => {
544
+ let next = cursorRef.current + direction;
545
+ if (next < 0) next = totalPositions - 1;
546
+ if (next >= totalPositions) next = 0;
547
+ cursorRef.current = next;
548
+ dispatch({ type: "MOVE", direction, max: totalPositions });
549
+ };
550
+ if (event.name === "up" || event.name === "k") {
551
+ move(-1);
552
+ } else if (event.name === "down" || event.name === "j") {
553
+ move(1);
554
+ } else if (event.name === "return") {
555
+ const onSubmitRow = hasSubmitRow && cursorRef.current === selectableItems.length;
556
+ if (onSubmitRow) {
557
+ dispatch({ type: "SUBMIT" });
558
+ onSubmit?.(Array.from(currentSelected));
559
+ } else {
560
+ const current = selectableItems[cursorRef.current];
561
+ if (current && !current.item.disabled) {
562
+ const isDeselecting = currentSelected.has(current.item.value);
563
+ if (!isDeselecting && maxCount !== void 0 && currentSelected.size >= maxCount) return;
564
+ const next = new Set(currentSelected);
565
+ if (isDeselecting) next.delete(current.item.value);
566
+ else next.add(current.item.value);
567
+ setSelected(Array.from(next));
568
+ }
569
+ }
570
+ } else if (event.name === "a" && enableSelectAll) {
571
+ const enabledValues = items.filter((i) => !i.disabled).map((i) => i.value);
572
+ setSelected(maxCount !== void 0 ? enabledValues.slice(0, maxCount) : enabledValues);
573
+ } else if (event.name === "x" && enableClear) {
574
+ setSelected([]);
575
+ }
576
+ });
577
+ if (state.submitted) {
578
+ const selectedItems = items.filter((i) => currentSelected.has(i.value));
579
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
580
+ /* @__PURE__ */ jsxs6("text", { children: [
581
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: theme.success }), children: "\u25C6 " }),
582
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ bold: true }), children: title })
583
+ ] }),
584
+ selectedItems.map((item) => /* @__PURE__ */ jsxs6("text", { children: [
585
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.success }), children: [
586
+ BAR2,
587
+ " "
588
+ ] }),
589
+ /* @__PURE__ */ jsx10("span", { children: " " }),
590
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.success }), children: [
591
+ CHECKED,
592
+ " "
593
+ ] }),
594
+ /* @__PURE__ */ jsx10("span", { children: item.label })
595
+ ] }, item.key ?? String(item.value))),
596
+ /* @__PURE__ */ jsx10("text", { children: " " }),
597
+ /* @__PURE__ */ jsx10("text", { children: /* @__PURE__ */ jsxs6("span", { style: textStyle({ dim: true }), children: [
598
+ selectedItems.length,
599
+ " selected \u2014 ",
600
+ submittedStatus
601
+ ] }) })
602
+ ] });
603
+ }
604
+ const hasItems = selectableItems.length > 0;
605
+ return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
606
+ /* @__PURE__ */ jsxs6("text", { children: [
607
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: diamondColor, dim: disabled }), children: "\u25C6 " }),
608
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ bold: true, dim: disabled }), children: title }),
609
+ required && /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: theme.error }), children: " *" }),
610
+ maxCount !== void 0 && /* @__PURE__ */ jsx10("span", { style: textStyle({ dim: true }), children: ` (${currentSelected.size}/${maxCount})` })
611
+ ] }),
612
+ invalid && /* @__PURE__ */ jsxs6("text", { children: [
613
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
614
+ BAR2,
615
+ " "
616
+ ] }),
617
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: theme.error }), children: " Please select at least one option" })
618
+ ] }),
619
+ !hasItems && placeholder && /* @__PURE__ */ jsxs6("text", { children: [
620
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
621
+ BAR2,
622
+ " "
623
+ ] }),
624
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ dim: true }), children: [
625
+ " ",
626
+ placeholder
627
+ ] })
628
+ ] }),
629
+ visibleRows.map((row, i) => {
630
+ if (row.type === "separator") {
631
+ return /* @__PURE__ */ jsxs6("text", { children: [
632
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
633
+ BAR2,
634
+ " "
635
+ ] }),
636
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
637
+ " ",
638
+ SEPARATOR2.repeat(20)
639
+ ] })
640
+ ] }, `sep-${i}`);
641
+ }
642
+ if (row.type === "group") {
643
+ return /* @__PURE__ */ jsxs6("text", { children: [
644
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
645
+ BAR2,
646
+ " "
647
+ ] }),
648
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ bold: true, fg: theme.muted }), children: ` ${row.label}` })
649
+ ] }, `group-${row.label}`);
650
+ }
651
+ const { item, index: itemIndex } = row;
652
+ const isHighlighted = !disabled && itemIndex === state.cursor;
653
+ const isSelected = currentSelected.has(item.value);
654
+ const isItemDisabled = disabled || !!item.disabled;
655
+ const itemColor = isItemDisabled ? theme.muted : isHighlighted ? resolvedHighlight : isSelected ? resolvedCheckbox : void 0;
656
+ return /* @__PURE__ */ jsxs6("text", { children: [
657
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
658
+ BAR2,
659
+ " "
660
+ ] }),
661
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: isHighlighted ? resolvedHighlight : void 0 }), children: [
662
+ isHighlighted ? CURSOR2 : " ",
663
+ " "
664
+ ] }),
665
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: isSelected && !isItemDisabled ? resolvedCheckbox : theme.muted, dim: isItemDisabled }), children: [
666
+ isSelected ? CHECKED : UNCHECKED,
667
+ " "
668
+ ] }),
669
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: itemColor, dim: isItemDisabled }), children: item.label })
670
+ ] }, item.key ?? String(item.value));
671
+ }),
672
+ hasSubmitRow && /* @__PURE__ */ jsxs6("text", { children: [
673
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
674
+ BAR2,
675
+ " "
676
+ ] }),
677
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: isOnSubmit ? resolvedHighlight : void 0 }), children: [
678
+ isOnSubmit ? CURSOR2 : " ",
679
+ " "
680
+ ] }),
681
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: isOnSubmit ? resolvedHighlight : theme.muted }), children: "\u21B3 " }),
682
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: isOnSubmit ? resolvedHighlight : theme.text }), children: "Submit" })
683
+ ] })
684
+ ] });
685
+ }
686
+
687
+ // ../ui/components/table/table.tsx
688
+ import { Fragment } from "react";
689
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
690
+ function getColumns(data, columnsProp) {
691
+ if (columnsProp) return columnsProp;
692
+ const keys = /* @__PURE__ */ new Set();
693
+ for (const row of data) {
694
+ for (const key in row) keys.add(key);
695
+ }
696
+ return Array.from(keys);
697
+ }
698
+ function calculateColumnWidths(columns, data, padding) {
699
+ return columns.map((field) => {
700
+ const headerWidth = String(field).length;
701
+ const maxDataWidth = data.reduce((max, row) => {
702
+ const val = row[field];
703
+ return Math.max(max, val == null ? 0 : String(val).length);
704
+ }, 0);
705
+ return { field, width: Math.max(headerWidth, maxDataWidth) + padding * 2 };
706
+ });
707
+ }
708
+ function padCell(value, width, padding) {
709
+ const rightPad = width - value.length - padding;
710
+ return " ".repeat(padding) + value + " ".repeat(Math.max(0, rightPad));
711
+ }
712
+ function Table({
713
+ data,
714
+ columns: columnsProp,
715
+ padding = 1,
716
+ headerColor,
717
+ borderColor
718
+ }) {
719
+ const theme = useTheme();
720
+ const resolvedHeaderColor = headerColor ?? theme.primary;
721
+ const resolvedBorderColor = borderColor ?? theme.border;
722
+ const columns = getColumns(data, columnsProp);
723
+ const colInfo = calculateColumnWidths(columns, data, padding);
724
+ const borderLine = (left, mid, right) => {
725
+ const inner = colInfo.map((c) => "\u2500".repeat(c.width)).join(mid);
726
+ return /* @__PURE__ */ jsx11("text", { children: /* @__PURE__ */ jsxs7("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: [
727
+ left,
728
+ inner,
729
+ right
730
+ ] }) });
731
+ };
732
+ const contentRow = (rowData, isHeader) => {
733
+ const parts = [];
734
+ parts.push(
735
+ /* @__PURE__ */ jsx11("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: "\u2502" }, "left-border")
736
+ );
737
+ colInfo.forEach((col, i) => {
738
+ const val = rowData[col.field];
739
+ const str = val == null ? "" : String(val);
740
+ const padded = padCell(str, col.width, padding);
741
+ if (isHeader) {
742
+ parts.push(
743
+ /* @__PURE__ */ jsx11("span", { style: textStyle({ fg: resolvedHeaderColor, bold: true }), children: padded }, `cell-${i}`)
744
+ );
745
+ } else {
746
+ parts.push(/* @__PURE__ */ jsx11("span", { children: padded }, `cell-${i}`));
747
+ }
748
+ if (i < colInfo.length - 1) {
749
+ parts.push(
750
+ /* @__PURE__ */ jsx11("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: "\u2502" }, `sep-${i}`)
751
+ );
752
+ }
753
+ });
754
+ parts.push(
755
+ /* @__PURE__ */ jsx11("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: "\u2502" }, "right-border")
756
+ );
757
+ return /* @__PURE__ */ jsx11("text", { children: parts });
758
+ };
759
+ const headerData = columns.reduce(
760
+ (acc, col) => ({ ...acc, [col]: col }),
761
+ {}
762
+ );
763
+ return /* @__PURE__ */ jsxs7("box", { children: [
764
+ borderLine("\u250C", "\u252C", "\u2510"),
765
+ contentRow(headerData, true),
766
+ data.map((row, index) => /* @__PURE__ */ jsxs7(Fragment, { children: [
767
+ borderLine("\u251C", "\u253C", "\u2524"),
768
+ contentRow(row, false)
769
+ ] }, index)),
770
+ borderLine("\u2514", "\u2534", "\u2518")
771
+ ] });
772
+ }
773
+
774
+ // ../ui/components/gradient/gradient.tsx
775
+ import { Fragment as Fragment2, jsx as jsx12 } from "react/jsx-runtime";
776
+ var GRADIENTS = {
777
+ cristal: ["#bdfff3", "#4ac29a"],
778
+ teen: ["#77a1d3", "#79cbca", "#e684ae"],
779
+ mind: ["#473b7b", "#3584a7", "#30d2be"],
780
+ morning: ["#ff5f6d", "#ffc371"],
781
+ vice: ["#5ee7df", "#b490ca"],
782
+ passion: ["#f43b47", "#453a94"],
783
+ fruit: ["#ff4e50", "#f9d423"],
784
+ instagram: ["#833ab4", "#fd1d1d", "#fcb045"],
785
+ atlas: ["#feac5e", "#c779d0", "#4bc0c8"],
786
+ retro: ["#3f51b1", "#5a55ae", "#7b5fac", "#8f6aae", "#a86aa4", "#cc6b8e", "#f18271", "#f3a469", "#f7c978"],
787
+ summer: ["#fdbb2d", "#22c1c3"],
788
+ rainbow: ["#ff0000", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff", "#ff0000"],
789
+ pastel: ["#74ebd5", "#ACB6E5"]
790
+ };
791
+ function hexToRgb(hex) {
792
+ const normalized = hex.replace("#", "");
793
+ return {
794
+ r: parseInt(normalized.substring(0, 2), 16),
795
+ g: parseInt(normalized.substring(2, 4), 16),
796
+ b: parseInt(normalized.substring(4, 6), 16)
797
+ };
798
+ }
799
+ function rgbToHex(rgb) {
800
+ const r = rgb.r.toString(16).padStart(2, "0");
801
+ const g = rgb.g.toString(16).padStart(2, "0");
802
+ const b = rgb.b.toString(16).padStart(2, "0");
803
+ return `#${r}${g}${b}`;
804
+ }
805
+ function lerp(a, b, t) {
806
+ return a + (b - a) * t;
807
+ }
808
+ function interpolateColor(color1, color2, t) {
809
+ return {
810
+ r: Math.round(lerp(color1.r, color2.r, t)),
811
+ g: Math.round(lerp(color1.g, color2.g, t)),
812
+ b: Math.round(lerp(color1.b, color2.b, t))
813
+ };
814
+ }
815
+ function generateGradient(colors, steps) {
816
+ if (colors.length === 0) throw new Error("At least one color is required");
817
+ if (colors.length === 1 || steps <= 1) return Array(steps).fill(colors[0]);
818
+ const rgbColors = colors.map(hexToRgb);
819
+ const result = [];
820
+ const segmentLength = (steps - 1) / (rgbColors.length - 1);
821
+ for (let i = 0; i < steps; i++) {
822
+ const segmentIndex = Math.min(Math.floor(i / segmentLength), rgbColors.length - 2);
823
+ const segmentProgress = segmentLength > 0 ? (i - segmentIndex * segmentLength) / segmentLength : 0;
824
+ const color = interpolateColor(rgbColors[segmentIndex], rgbColors[segmentIndex + 1], Math.min(segmentProgress, 1));
825
+ result.push(rgbToHex(color));
826
+ }
827
+ return result;
828
+ }
829
+ function Gradient({ children, name, colors }) {
830
+ if (name && colors) throw new Error("The `name` and `colors` props are mutually exclusive");
831
+ if (!name && !colors) throw new Error("Either `name` or `colors` prop must be provided");
832
+ const gradientColors = name ? GRADIENTS[name] : colors;
833
+ const lines = children.split("\n");
834
+ const maxLength = Math.max(...lines.map((l) => l.length));
835
+ if (maxLength === 0) return /* @__PURE__ */ jsx12("text", { children });
836
+ const hexColors = generateGradient(gradientColors, maxLength);
837
+ return /* @__PURE__ */ jsx12(Fragment2, { children: lines.map((line, lineIndex) => /* @__PURE__ */ jsx12("text", { children: line.split("").map((char, charIndex) => /* @__PURE__ */ jsx12("span", { style: { fg: hexColors[charIndex] }, children: char }, charIndex)) }, lineIndex)) });
838
+ }
839
+
840
+ // ../ui/components/tab-bar/tab-bar.tsx
841
+ import { jsx as jsx13 } from "react/jsx-runtime";
842
+ function TabBar({
843
+ label,
844
+ options,
845
+ selectedIndex,
846
+ focused = true,
847
+ activeColor
848
+ }) {
849
+ const theme = useTheme();
850
+ const resolvedActiveColor = activeColor ?? theme.accent;
851
+ const parts = [];
852
+ if (label !== void 0) {
853
+ parts.push(
854
+ /* @__PURE__ */ jsx13(
855
+ "span",
856
+ {
857
+ style: focused ? textStyle({ bold: true }) : textStyle({ dim: true }),
858
+ children: label
859
+ },
860
+ "label"
861
+ )
862
+ );
863
+ }
864
+ parts.push(/* @__PURE__ */ jsx13("span", { children: " " }, "sep"));
865
+ options.forEach((option, i) => {
866
+ const isSelected = i === selectedIndex;
867
+ const padded = ` ${option} `;
868
+ if (isSelected && focused) {
869
+ parts.push(
870
+ /* @__PURE__ */ jsx13(
871
+ "span",
872
+ {
873
+ style: textStyle({ inverse: true, bold: true, fg: resolvedActiveColor }),
874
+ children: padded
875
+ },
876
+ `opt-${i}`
877
+ )
878
+ );
879
+ } else if (isSelected && !focused) {
880
+ parts.push(
881
+ /* @__PURE__ */ jsx13(
882
+ "span",
883
+ {
884
+ style: textStyle({ inverse: true, bold: true, dim: true }),
885
+ children: padded
886
+ },
887
+ `opt-${i}`
888
+ )
889
+ );
890
+ } else if (!focused) {
891
+ parts.push(
892
+ /* @__PURE__ */ jsx13("span", { style: textStyle({ dim: true }), children: padded }, `opt-${i}`)
893
+ );
894
+ } else {
895
+ parts.push(/* @__PURE__ */ jsx13("span", { children: padded }, `opt-${i}`));
896
+ }
897
+ });
898
+ return /* @__PURE__ */ jsx13("text", { children: parts });
899
+ }
900
+
901
+ // ../ui/components/modal/modal.tsx
902
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
903
+ function Modal({
904
+ children,
905
+ title,
906
+ borderColor,
907
+ borderStyle = "rounded",
908
+ onClose,
909
+ useKeyboard: useKeyboard3
910
+ }) {
911
+ const theme = useTheme();
912
+ const resolvedBorderColor = borderColor ?? theme.border;
913
+ useKeyboard3?.((event) => {
914
+ if (event.name === "escape" && onClose) {
915
+ onClose();
916
+ }
917
+ });
918
+ return /* @__PURE__ */ jsx14("box", { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx14(
919
+ "box",
920
+ {
921
+ flexDirection: "column",
922
+ flexGrow: 1,
923
+ border: true,
924
+ borderStyle,
925
+ borderColor: resolvedBorderColor,
926
+ children: title ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
927
+ /* @__PURE__ */ jsx14("box", { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsx14("text", { style: textStyle({ bold: true, fg: resolvedBorderColor }), children: title }) }),
928
+ children
929
+ ] }) : children
930
+ }
931
+ ) });
932
+ }
933
+
934
+ // ../ui/components/chat/chat.tsx
935
+ import { useState as useState5, useRef as useRef3 } from "react";
936
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
937
+ var STATUS_ICONS = {
938
+ pending: "\u2022",
939
+ in_progress: "\u280B",
940
+ completed: "\u2713",
941
+ failed: "\u2717"
942
+ };
943
+ function getStatusColors(theme) {
944
+ return {
945
+ pending: theme.muted,
946
+ in_progress: theme.warning,
947
+ completed: theme.success,
948
+ failed: theme.error
949
+ };
950
+ }
951
+ function MessageBubble({
952
+ message,
953
+ userColor,
954
+ assistantColor
955
+ }) {
956
+ const isUser = message.role === "user";
957
+ const prefix = isUser ? "> " : "< ";
958
+ const color = isUser ? userColor : assistantColor;
959
+ return /* @__PURE__ */ jsxs9("text", { wrapMode: "word", children: [
960
+ /* @__PURE__ */ jsx15("span", { style: textStyle({ bold: true, fg: color }), children: prefix }),
961
+ /* @__PURE__ */ jsx15("span", { children: message.content })
962
+ ] });
963
+ }
964
+ function StreamingTextDisplay({
965
+ text,
966
+ assistantColor,
967
+ cursorChar = "_"
968
+ }) {
969
+ if (!text) return null;
970
+ return /* @__PURE__ */ jsxs9("text", { wrapMode: "word", children: [
971
+ /* @__PURE__ */ jsx15("span", { style: textStyle({ bold: true, fg: assistantColor }), children: "< " }),
972
+ /* @__PURE__ */ jsx15("span", { children: text }),
973
+ /* @__PURE__ */ jsx15("span", { style: textStyle({ dim: true }), children: cursorChar })
974
+ ] });
975
+ }
976
+ function ToolCallCard({ toolCall, statusColors }) {
977
+ const icon = STATUS_ICONS[toolCall.status] || "\u2022";
978
+ const color = statusColors[toolCall.status] || "gray";
979
+ const showEllipsis = toolCall.status === "pending" || toolCall.status === "in_progress";
980
+ return /* @__PURE__ */ jsxs9("text", { children: [
981
+ /* @__PURE__ */ jsx15("span", { children: " " }),
982
+ /* @__PURE__ */ jsx15("span", { style: textStyle({ fg: color }), children: icon }),
983
+ /* @__PURE__ */ jsx15("span", { children: " " }),
984
+ /* @__PURE__ */ jsx15("span", { style: textStyle({ fg: color }), children: toolCall.title }),
985
+ showEllipsis && /* @__PURE__ */ jsx15("span", { style: textStyle({ dim: true }), children: " ..." })
986
+ ] });
987
+ }
988
+ function ChatInput({
989
+ onSubmit,
990
+ placeholder = "Type a message...",
991
+ prompt = "> ",
992
+ promptColor,
993
+ disabled = false,
994
+ useKeyboard: useKeyboard3
995
+ }) {
996
+ const [value, setValue] = useState5("");
997
+ const valueRef = useRef3("");
998
+ const updateValue = (newValue) => {
999
+ valueRef.current = newValue;
1000
+ setValue(newValue);
1001
+ };
1002
+ useKeyboard3?.((event) => {
1003
+ if (disabled) return;
1004
+ if (event.name === "return") {
1005
+ const trimmed = valueRef.current.trim();
1006
+ if (trimmed) {
1007
+ onSubmit(trimmed);
1008
+ updateValue("");
1009
+ }
1010
+ return;
1011
+ }
1012
+ if (event.name === "backspace" || event.name === "delete") {
1013
+ updateValue(valueRef.current.slice(0, -1));
1014
+ return;
1015
+ }
1016
+ if (event.ctrl || event.meta) return;
1017
+ if (event.name && event.name.length === 1) {
1018
+ updateValue(valueRef.current + event.name);
1019
+ return;
1020
+ }
1021
+ if (event.name === "space") {
1022
+ updateValue(valueRef.current + " ");
1023
+ return;
1024
+ }
1025
+ });
1026
+ const showPlaceholder = value.length === 0;
1027
+ return /* @__PURE__ */ jsxs9("text", { children: [
1028
+ /* @__PURE__ */ jsx15("span", { style: textStyle({ fg: promptColor }), children: prompt }),
1029
+ showPlaceholder ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
1030
+ /* @__PURE__ */ jsx15("span", { style: textStyle({ dim: true }), children: placeholder }),
1031
+ !disabled && /* @__PURE__ */ jsx15("span", { style: textStyle({ inverse: true }), children: " " })
1032
+ ] }) : /* @__PURE__ */ jsxs9(Fragment4, { children: [
1033
+ /* @__PURE__ */ jsx15("span", { children: value }),
1034
+ !disabled && /* @__PURE__ */ jsx15("span", { style: textStyle({ inverse: true }), children: " " })
1035
+ ] })
1036
+ ] });
1037
+ }
1038
+ function ChatPanel({
1039
+ messages,
1040
+ streamingText = "",
1041
+ isLoading = false,
1042
+ activeToolCalls = [],
1043
+ onSendMessage,
1044
+ onCancel,
1045
+ placeholder = "Type a message...",
1046
+ promptChar = "> ",
1047
+ promptColor,
1048
+ userColor,
1049
+ assistantColor,
1050
+ loadingText = "Thinking...",
1051
+ useKeyboard: useKeyboard3
1052
+ }) {
1053
+ const theme = useTheme();
1054
+ const resolvedUserColor = userColor ?? theme.secondary;
1055
+ const resolvedAssistantColor = assistantColor ?? theme.primary;
1056
+ const resolvedPromptColor = promptColor ?? theme.secondary;
1057
+ const statusColors = getStatusColors(theme);
1058
+ const inputDisabled = isLoading || !!streamingText;
1059
+ const wrappedUseKeyboard = useKeyboard3 ? (handler) => {
1060
+ useKeyboard3((event) => {
1061
+ if (event.name === "escape" && inputDisabled && onCancel) {
1062
+ onCancel();
1063
+ return;
1064
+ }
1065
+ handler(event);
1066
+ });
1067
+ } : void 0;
1068
+ return /* @__PURE__ */ jsxs9("box", { flexDirection: "column", paddingX: 1, children: [
1069
+ messages.map((msg) => /* @__PURE__ */ jsx15(
1070
+ MessageBubble,
1071
+ {
1072
+ message: msg,
1073
+ userColor: resolvedUserColor,
1074
+ assistantColor: resolvedAssistantColor
1075
+ },
1076
+ msg.id
1077
+ )),
1078
+ activeToolCalls.map((tc) => /* @__PURE__ */ jsx15(ToolCallCard, { toolCall: tc, statusColors }, tc.id)),
1079
+ streamingText ? /* @__PURE__ */ jsx15(
1080
+ StreamingTextDisplay,
1081
+ {
1082
+ text: streamingText,
1083
+ assistantColor: resolvedAssistantColor
1084
+ }
1085
+ ) : isLoading ? /* @__PURE__ */ jsxs9("text", { style: textStyle({ dim: true }), children: [
1086
+ " ",
1087
+ loadingText
1088
+ ] }) : null,
1089
+ /* @__PURE__ */ jsx15("box", { marginTop: 1, children: /* @__PURE__ */ jsx15(
1090
+ ChatInput,
1091
+ {
1092
+ onSubmit: onSendMessage,
1093
+ placeholder,
1094
+ prompt: promptChar,
1095
+ promptColor: resolvedPromptColor,
1096
+ disabled: inputDisabled,
1097
+ useKeyboard: wrappedUseKeyboard
1098
+ }
1099
+ ) })
1100
+ ] });
1101
+ }
1102
+
1103
+ // ../ui/components/terminal-window/terminal-window.tsx
1104
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1105
+
1106
+ // ../ui/components/made-with-opentui/made-with-opentui.tsx
1107
+ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1108
+
1109
+ // ../ui/components/made-with-gridland/made-with-gridland.tsx
1110
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1111
+
1112
+ // ../ui/components/breakpoints/use-breakpoints.ts
1113
+ import { useTerminalDimensions } from "@opentui/react";
1114
+ var BREAKPOINTS = {
1115
+ tiny: 40,
1116
+ narrow: 60,
1117
+ mobile: 70
1118
+ };
1119
+ function useBreakpoints() {
1120
+ const { width, height } = useTerminalDimensions();
1121
+ return {
1122
+ isTiny: width < BREAKPOINTS.tiny,
1123
+ isNarrow: width < BREAKPOINTS.narrow,
1124
+ isMobile: width < BREAKPOINTS.mobile,
1125
+ isDesktop: width >= BREAKPOINTS.mobile,
1126
+ width,
1127
+ height
1128
+ };
1129
+ }
1130
+
1131
+ // ../docs/components/landing/landing-app.tsx
1132
+ import { useState as useState8 } from "react";
1133
+
1134
+ // ../docs/components/landing/logo.tsx
1135
+ import { useState as useState6, useEffect as useEffect2, useRef as useRef4, useMemo as useMemo3 } from "react";
1136
+ import figlet from "figlet";
1137
+ import ansiShadow from "figlet/importable-fonts/ANSI Shadow.js";
1138
+ import { Fragment as Fragment5, jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1139
+ figlet.parseFont("ANSI Shadow", ansiShadow);
1140
+ function makeArt(text) {
1141
+ return figlet.textSync(text, { font: "ANSI Shadow" }).split("\n").filter((l) => l.trimEnd().length > 0).join("\n");
1142
+ }
1143
+ var fullArt = makeArt("gridland");
1144
+ var gridArt = makeArt("grid");
1145
+ var landArt = makeArt("land");
1146
+ var ART_HEIGHT = 6;
1147
+ function useAnimation(duration = 1e3) {
1148
+ const isBrowser = typeof document !== "undefined";
1149
+ const [progress, setProgress] = useState6(isBrowser ? 0 : 1);
1150
+ const startTime = useRef4(null);
1151
+ useEffect2(() => {
1152
+ if (!isBrowser) return;
1153
+ let raf;
1154
+ const tick = (time) => {
1155
+ if (startTime.current === null) startTime.current = time;
1156
+ const elapsed = time - startTime.current;
1157
+ const t = Math.min(1, elapsed / duration);
1158
+ const eased = 1 - Math.pow(1 - t, 3);
1159
+ setProgress(eased);
1160
+ if (t < 1) raf = requestAnimationFrame(tick);
1161
+ };
1162
+ raf = requestAnimationFrame(tick);
1163
+ return () => cancelAnimationFrame(raf);
1164
+ }, []);
1165
+ return progress;
1166
+ }
1167
+ function RevealGradient({ children, revealCol }) {
1168
+ const gradientColors = GRADIENTS.instagram;
1169
+ const lines = children.split("\n");
1170
+ const maxLength = Math.max(...lines.map((l) => l.length));
1171
+ if (maxLength === 0) return /* @__PURE__ */ jsx19("text", { children });
1172
+ const hexColors = useMemo3(() => generateGradient(gradientColors, maxLength), [maxLength]);
1173
+ return /* @__PURE__ */ jsx19("box", { position: "relative", width: maxLength, height: lines.length, shouldFill: false, children: lines.map((line, lineIndex) => {
1174
+ const runs = [];
1175
+ let current = null;
1176
+ for (let i = 0; i < line.length; i++) {
1177
+ const revealed = i <= revealCol;
1178
+ const char = line[i];
1179
+ const isVisible = revealed && char !== " ";
1180
+ if (isVisible) {
1181
+ if (!current) {
1182
+ current = { start: i, chars: [] };
1183
+ }
1184
+ current.chars.push(char);
1185
+ } else {
1186
+ if (current) {
1187
+ runs.push(current);
1188
+ current = null;
1189
+ }
1190
+ }
1191
+ }
1192
+ if (current) runs.push(current);
1193
+ return runs.map((run, runIndex) => /* @__PURE__ */ jsx19(
1194
+ "box",
1195
+ {
1196
+ position: "absolute",
1197
+ top: lineIndex,
1198
+ left: run.start,
1199
+ shouldFill: false,
1200
+ children: /* @__PURE__ */ jsx19("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx19(
1201
+ "span",
1202
+ {
1203
+ style: { fg: hexColors[run.start + ci] },
1204
+ children: char
1205
+ },
1206
+ ci
1207
+ )) })
1208
+ },
1209
+ `${lineIndex}-${runIndex}`
1210
+ ));
1211
+ }) });
1212
+ }
1213
+ function Logo({ compact, narrow, mobile }) {
1214
+ const isBrowser = typeof document !== "undefined";
1215
+ const progress = useAnimation(900);
1216
+ const artHeight = compact ? 1 : narrow ? ART_HEIGHT * 2 : ART_HEIGHT;
1217
+ const dropOffset = Math.round((1 - progress) * -artHeight);
1218
+ const revealProgress = Math.max(0, Math.min(1, (progress - 0.1) / 0.7));
1219
+ const maxWidth = compact ? 8 : narrow ? 40 : 62;
1220
+ const revealCol = Math.round(revealProgress * (maxWidth + 4)) - 2;
1221
+ const taglineOpacity = Math.max(0, Math.min(1, (progress - 0.7) / 0.3));
1222
+ const subtitle = /* @__PURE__ */ jsxs13(Fragment5, { children: [
1223
+ /* @__PURE__ */ jsx19("text", { children: " " }),
1224
+ /* @__PURE__ */ jsx19("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs13("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
1225
+ "A framework for building terminal apps, built on ",
1226
+ /* @__PURE__ */ jsx19("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" }, children: "OpenTUI" }),
1227
+ " + React." + (mobile ? " " : "\n") + "(Gridland apps, like this website, work in the browser and terminal.)"
1228
+ ] }) })
1229
+ ] });
1230
+ if (!isBrowser) {
1231
+ const art = compact ? "gridland" : narrow ? gridArt + "\n" + landArt : fullArt;
1232
+ return /* @__PURE__ */ jsxs13("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", children: [
1233
+ /* @__PURE__ */ jsx19(Gradient, { name: "instagram", children: art }),
1234
+ /* @__PURE__ */ jsx19("text", { children: " " }),
1235
+ /* @__PURE__ */ jsx19("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs13("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false, children: [
1236
+ "A framework for building terminal apps, built on OpenTUI + React.",
1237
+ "\n",
1238
+ "(Gridland apps, like this website, work in the browser and terminal.)"
1239
+ ] }) })
1240
+ ] });
1241
+ }
1242
+ if (compact) {
1243
+ return /* @__PURE__ */ jsxs13("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1244
+ /* @__PURE__ */ jsx19("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx19("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx19(RevealGradient, { revealCol, children: "gridland" }) }) }),
1245
+ subtitle
1246
+ ] });
1247
+ }
1248
+ if (narrow) {
1249
+ return /* @__PURE__ */ jsxs13("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1250
+ /* @__PURE__ */ jsx19("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs13("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
1251
+ /* @__PURE__ */ jsx19(RevealGradient, { revealCol, children: gridArt }),
1252
+ /* @__PURE__ */ jsx19(RevealGradient, { revealCol, children: landArt })
1253
+ ] }) }),
1254
+ subtitle
1255
+ ] });
1256
+ }
1257
+ return /* @__PURE__ */ jsxs13("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1258
+ /* @__PURE__ */ jsx19("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx19("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx19(RevealGradient, { revealCol, children: fullArt }) }) }),
1259
+ subtitle
1260
+ ] });
1261
+ }
1262
+
1263
+ // ../docs/components/landing/install-box.tsx
1264
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
1265
+ function InstallBox() {
1266
+ const theme = useTheme();
1267
+ return /* @__PURE__ */ jsx20(
1268
+ "box",
1269
+ {
1270
+ border: true,
1271
+ borderStyle: "rounded",
1272
+ borderColor: theme.border,
1273
+ paddingX: 1,
1274
+ flexDirection: "column",
1275
+ flexShrink: 0,
1276
+ children: /* @__PURE__ */ jsxs14("text", { children: [
1277
+ /* @__PURE__ */ jsx20("span", { style: textStyle({ dim: true }), children: "$ " }),
1278
+ /* @__PURE__ */ jsx20("span", { style: textStyle({ bold: true }), children: "bun create " }),
1279
+ /* @__PURE__ */ jsx20("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
1280
+ ] })
1281
+ }
1282
+ );
1283
+ }
1284
+
1285
+ // ../docs/components/landing/links-box.tsx
1286
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1287
+ var UNDERLINE3 = 1 << 3;
1288
+ function LinksBox() {
1289
+ const theme = useTheme();
1290
+ return /* @__PURE__ */ jsx21(
1291
+ "box",
1292
+ {
1293
+ border: true,
1294
+ borderStyle: "rounded",
1295
+ borderColor: theme.border,
1296
+ paddingX: 1,
1297
+ flexDirection: "column",
1298
+ flexShrink: 0,
1299
+ children: /* @__PURE__ */ jsxs15("text", { children: [
1300
+ /* @__PURE__ */ jsx21("span", { children: "\u{1F431}" }),
1301
+ /* @__PURE__ */ jsx21("a", { href: "https://github.com/cjroth/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
1302
+ /* @__PURE__ */ jsx21("span", { children: " " }),
1303
+ /* @__PURE__ */ jsx21("span", { children: "\u{1F4D6}" }),
1304
+ /* @__PURE__ */ jsx21("a", { href: "/docs", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
1305
+ ] })
1306
+ }
1307
+ );
1308
+ }
1309
+
1310
+ // ../docs/components/landing/matrix-background.tsx
1311
+ import { useMemo as useMemo4 } from "react";
1312
+
1313
+ // ../docs/components/landing/use-matrix.ts
1314
+ import { useState as useState7, useEffect as useEffect3, useRef as useRef5 } from "react";
1315
+ var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789@#$%^&*(){}[]|;:<>,.?/~`";
1316
+ function randomChar() {
1317
+ return CHARS[Math.floor(Math.random() * CHARS.length)];
1318
+ }
1319
+ function createDrop(height, seeded = false) {
1320
+ const length = Math.floor(Math.random() * Math.floor(height * 0.6)) + 4;
1321
+ return {
1322
+ y: seeded ? Math.floor(Math.random() * (height + length)) : -Math.floor(Math.random() * height),
1323
+ speed: 1,
1324
+ length,
1325
+ chars: Array.from({ length }, randomChar)
1326
+ };
1327
+ }
1328
+ function buildGrid(columns, width, height) {
1329
+ const grid = Array.from({ length: height }, () => Array(width).fill(" "));
1330
+ const brightness = Array.from({ length: height }, () => Array(width).fill(0));
1331
+ for (let x = 0; x < width; x++) {
1332
+ const drop = columns[x];
1333
+ if (!drop) continue;
1334
+ for (let i = 0; i < drop.length; i++) {
1335
+ const row = Math.floor(drop.y) - i;
1336
+ if (row < 0 || row >= height) continue;
1337
+ grid[row][x] = drop.chars[i];
1338
+ if (i === 0) {
1339
+ brightness[row][x] = 1;
1340
+ } else {
1341
+ brightness[row][x] = Math.max(0.15, 1 - i / drop.length);
1342
+ }
1343
+ }
1344
+ }
1345
+ return { grid, brightness };
1346
+ }
1347
+ function useMatrix(width, height) {
1348
+ const columnsRef = useRef5([]);
1349
+ const [state, setState] = useState7(() => {
1350
+ const columns = Array.from(
1351
+ { length: width },
1352
+ () => Math.random() < 0.5 ? createDrop(height, true) : null
1353
+ );
1354
+ columnsRef.current = columns;
1355
+ return buildGrid(columns, width, height);
1356
+ });
1357
+ useEffect3(() => {
1358
+ if (width < 2 || height < 2) return;
1359
+ const id = setInterval(() => {
1360
+ const columns = columnsRef.current;
1361
+ for (let x = 0; x < width; x++) {
1362
+ if (columns[x] === null || columns[x] === void 0) {
1363
+ if (Math.random() < 0.03) {
1364
+ columns[x] = createDrop(height);
1365
+ }
1366
+ continue;
1367
+ }
1368
+ const drop = columns[x];
1369
+ drop.y += drop.speed;
1370
+ if (Math.random() < 0.1) {
1371
+ const idx = Math.floor(Math.random() * drop.chars.length);
1372
+ drop.chars[idx] = randomChar();
1373
+ }
1374
+ if (drop.y - drop.length > height) {
1375
+ columns[x] = null;
1376
+ }
1377
+ }
1378
+ setState(buildGrid(columns, width, height));
1379
+ }, 80);
1380
+ return () => clearInterval(id);
1381
+ }, [width, height]);
1382
+ useEffect3(() => {
1383
+ columnsRef.current = Array.from(
1384
+ { length: width },
1385
+ () => Math.random() < 0.5 ? createDrop(height, true) : null
1386
+ );
1387
+ setState(buildGrid(columnsRef.current, width, height));
1388
+ }, [width, height]);
1389
+ return state;
1390
+ }
1391
+
1392
+ // ../docs/components/landing/matrix-background.tsx
1393
+ import { jsx as jsx22 } from "react/jsx-runtime";
1394
+ var MUTE_LEVELS = [0.12, 0.18, 0.24, 0.3, 0.38];
1395
+ var BG = hexToRgb("#1a1a2e");
1396
+ function buildMutedColors(baseHex) {
1397
+ const fg = hexToRgb(baseHex);
1398
+ return MUTE_LEVELS.map((factor) => rgbToHex({
1399
+ r: Math.round(BG.r + (fg.r - BG.r) * factor),
1400
+ g: Math.round(BG.g + (fg.g - BG.g) * factor),
1401
+ b: Math.round(BG.b + (fg.b - BG.b) * factor)
1402
+ }));
1403
+ }
1404
+ function colorForCell(mutedColors, b) {
1405
+ if (b >= 1) return mutedColors[4];
1406
+ const idx = Math.min(Math.floor(b * (MUTE_LEVELS.length - 1)), MUTE_LEVELS.length - 2);
1407
+ return mutedColors[idx];
1408
+ }
1409
+ function MatrixBackground({ width, height, clearRect, clearRects }) {
1410
+ const { grid, brightness } = useMatrix(width, height);
1411
+ const theme = useTheme();
1412
+ const columnColors = useMemo4(
1413
+ () => width > 0 ? generateGradient([theme.accent, theme.secondary, theme.primary], width) : [],
1414
+ [width, theme.accent, theme.secondary, theme.primary]
1415
+ );
1416
+ const columnMutedColors = useMemo4(
1417
+ () => columnColors.map(buildMutedColors),
1418
+ [columnColors]
1419
+ );
1420
+ return /* @__PURE__ */ jsx22("box", { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx22("text", { children: row.map((cell, x) => {
1421
+ const inClearRect = clearRect && y >= clearRect.top && y < clearRect.top + clearRect.height && x >= clearRect.left && x < clearRect.left + clearRect.width || clearRects && clearRects.some(
1422
+ (r) => y >= r.top && y < r.top + r.height && x >= r.left && x < r.left + r.width
1423
+ );
1424
+ const mutedColors = columnMutedColors[x];
1425
+ if (cell === " " || inClearRect || !mutedColors) {
1426
+ return /* @__PURE__ */ jsx22("span", { children: " " }, x);
1427
+ }
1428
+ return /* @__PURE__ */ jsx22(
1429
+ "span",
1430
+ {
1431
+ style: {
1432
+ fg: colorForCell(mutedColors, brightness[y][x])
1433
+ },
1434
+ children: cell
1435
+ },
1436
+ x
1437
+ );
1438
+ }) }, y)) });
1439
+ }
1440
+
1441
+ // ../docs/components/landing/about-modal.tsx
1442
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
1443
+ function AboutModal({ onClose, useKeyboard: useKeyboard3 }) {
1444
+ const theme = useTheme();
1445
+ return /* @__PURE__ */ jsx23(Modal, { title: "About Gridland", useKeyboard: useKeyboard3, onClose, children: /* @__PURE__ */ jsxs16("box", { paddingX: 1, flexDirection: "column", gap: 1, children: [
1446
+ /* @__PURE__ */ jsx23("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "What is Gridland?" }),
1447
+ /* @__PURE__ */ jsx23("text", { children: "Gridland renders terminal UIs to HTML5 Canvas with React." }),
1448
+ /* @__PURE__ */ jsx23("text", { children: "No xterm.js. No terminal emulator. Just pixels." }),
1449
+ /* @__PURE__ */ jsx23("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Features" }),
1450
+ /* @__PURE__ */ jsxs16("text", { children: [
1451
+ /* @__PURE__ */ jsxs16("span", { style: textStyle({ dim: true }), children: [
1452
+ "\u2022",
1453
+ " "
1454
+ ] }),
1455
+ "Canvas-rendered TUI components"
1456
+ ] }),
1457
+ /* @__PURE__ */ jsxs16("text", { children: [
1458
+ /* @__PURE__ */ jsxs16("span", { style: textStyle({ dim: true }), children: [
1459
+ "\u2022",
1460
+ " "
1461
+ ] }),
1462
+ "React reconciler with JSX"
1463
+ ] }),
1464
+ /* @__PURE__ */ jsxs16("text", { children: [
1465
+ /* @__PURE__ */ jsxs16("span", { style: textStyle({ dim: true }), children: [
1466
+ "\u2022",
1467
+ " "
1468
+ ] }),
1469
+ "Yoga flexbox layout engine"
1470
+ ] }),
1471
+ /* @__PURE__ */ jsxs16("text", { children: [
1472
+ /* @__PURE__ */ jsxs16("span", { style: textStyle({ dim: true }), children: [
1473
+ "\u2022",
1474
+ " "
1475
+ ] }),
1476
+ "Keyboard, mouse, and clipboard support"
1477
+ ] }),
1478
+ /* @__PURE__ */ jsxs16("text", { children: [
1479
+ /* @__PURE__ */ jsxs16("span", { style: textStyle({ dim: true }), children: [
1480
+ "\u2022",
1481
+ " "
1482
+ ] }),
1483
+ "Next.js and Vite plugins"
1484
+ ] }),
1485
+ /* @__PURE__ */ jsx23("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Tech Stack" }),
1486
+ /* @__PURE__ */ jsx23("text", { children: "React + opentui engine + yoga-layout + HTML5 Canvas" }),
1487
+ /* @__PURE__ */ jsx23("text", { style: textStyle({ dim: true }), children: "Press q to close" })
1488
+ ] }) });
1489
+ }
1490
+
1491
+ // ../docs/components/landing/landing-app.tsx
1492
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
1493
+ function LandingApp({ useKeyboard: useKeyboard3 }) {
1494
+ const theme = useTheme();
1495
+ const { width, height, isNarrow, isTiny, isMobile } = useBreakpoints();
1496
+ const [showAbout, setShowAbout] = useState8(false);
1497
+ useKeyboard3((event) => {
1498
+ if (event.name === "a" && !showAbout) {
1499
+ setShowAbout(true);
1500
+ }
1501
+ if (event.name === "q" && showAbout) {
1502
+ setShowAbout(false);
1503
+ }
1504
+ });
1505
+ if (showAbout) {
1506
+ return /* @__PURE__ */ jsxs17("box", { flexDirection: "column", width: "100%", height: "100%", children: [
1507
+ /* @__PURE__ */ jsx24("box", { flexGrow: 1, children: /* @__PURE__ */ jsx24(AboutModal, { onClose: () => setShowAbout(false), useKeyboard: useKeyboard3 }) }),
1508
+ /* @__PURE__ */ jsx24(StatusBar, { items: [{ key: "q", label: "close" }] })
1509
+ ] });
1510
+ }
1511
+ const isBrowser = typeof document !== "undefined";
1512
+ const logoHeight = isTiny ? 2 : isNarrow ? 13 : 7;
1513
+ const logoExtra = isBrowser ? 1 : 0;
1514
+ const gap = isMobile ? 0 : 1;
1515
+ const installLinksTop = 3 + logoHeight + logoExtra + gap;
1516
+ const installLinksHeight = 3;
1517
+ const boxTop = installLinksTop + installLinksHeight + gap + 1;
1518
+ const boxHeight = height - boxTop - 1 - 1;
1519
+ const clearRect = { top: boxTop, left: 1, width: width - 2, height: boxHeight };
1520
+ const installLinksClearRect = { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight };
1521
+ return /* @__PURE__ */ jsxs17("box", { width: "100%", height: "100%", position: "relative", children: [
1522
+ /* @__PURE__ */ jsx24(MatrixBackground, { width, height, clearRect, clearRects: [installLinksClearRect] }),
1523
+ /* @__PURE__ */ jsxs17(
1524
+ "box",
1525
+ {
1526
+ position: "absolute",
1527
+ top: 0,
1528
+ left: 0,
1529
+ width,
1530
+ height,
1531
+ zIndex: 1,
1532
+ flexDirection: "column",
1533
+ shouldFill: false,
1534
+ children: [
1535
+ /* @__PURE__ */ jsxs17("box", { flexGrow: 1, flexDirection: "column", paddingTop: 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
1536
+ /* @__PURE__ */ jsx24("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx24(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
1537
+ /* @__PURE__ */ jsxs17("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
1538
+ /* @__PURE__ */ jsx24(
1539
+ "box",
1540
+ {
1541
+ border: true,
1542
+ borderStyle: "rounded",
1543
+ borderColor: theme.border,
1544
+ paddingX: 1,
1545
+ flexDirection: "column",
1546
+ flexShrink: 0,
1547
+ children: /* @__PURE__ */ jsxs17("text", { children: [
1548
+ /* @__PURE__ */ jsx24("span", { style: textStyle({ dim: true }), children: "$ " }),
1549
+ /* @__PURE__ */ jsx24("span", { style: textStyle({ bold: true }), children: "bunx " }),
1550
+ /* @__PURE__ */ jsx24("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
1551
+ ] })
1552
+ }
1553
+ ),
1554
+ /* @__PURE__ */ jsx24(InstallBox, {}),
1555
+ /* @__PURE__ */ jsx24(LinksBox, {})
1556
+ ] }),
1557
+ /* @__PURE__ */ jsx24(
1558
+ "box",
1559
+ {
1560
+ flexGrow: 1,
1561
+ border: true,
1562
+ borderStyle: "rounded",
1563
+ borderColor: theme.border
1564
+ }
1565
+ )
1566
+ ] }),
1567
+ /* @__PURE__ */ jsx24(
1568
+ StatusBar,
1569
+ {
1570
+ items: [
1571
+ { key: "a", label: "about" }
1572
+ ]
1573
+ }
1574
+ )
1575
+ ]
1576
+ }
1577
+ )
1578
+ ] });
1579
+ }
1580
+
1581
+ // ../docs/components/landing/matrix-rain.tsx
1582
+ import { jsx as jsx25 } from "react/jsx-runtime";
1583
+
1584
+ // ../ui/scripts/demo-apps.tsx
1585
+ import figlet2 from "figlet";
1586
+ import ansiShadow2 from "figlet/importable-fonts/ANSI Shadow.js";
1587
+ import big from "figlet/importable-fonts/Big.js";
1588
+ import doom from "figlet/importable-fonts/Doom.js";
1589
+ import slant from "figlet/importable-fonts/Slant.js";
1590
+ import speed from "figlet/importable-fonts/Speed.js";
1591
+ import standard from "figlet/importable-fonts/Standard.js";
1592
+ import block from "figlet/importable-fonts/Block.js";
1593
+ import colossal from "figlet/importable-fonts/Colossal.js";
1594
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
1595
+ var fonts = [
1596
+ { name: "ANSI Shadow", data: ansiShadow2 },
1597
+ { name: "Standard", data: standard },
1598
+ { name: "Big", data: big },
1599
+ { name: "Doom", data: doom },
1600
+ { name: "Slant", data: slant },
1601
+ { name: "Speed", data: speed },
1602
+ { name: "Block", data: block },
1603
+ { name: "Colossal", data: colossal }
1604
+ ];
1605
+ for (const f of fonts) {
1606
+ figlet2.parseFont(f.name, f.data);
1607
+ }
1608
+ function getFigletLines(fontName, text = "gridland") {
1609
+ const art = figlet2.textSync(text, { font: fontName });
1610
+ return art.split("\n").filter((l) => l.trimEnd().length > 0);
1611
+ }
1612
+ var gradientNames = Object.keys(GRADIENTS);
1613
+ function GradientApp() {
1614
+ const [index, setIndex] = useState9(0);
1615
+ const name = gradientNames[index];
1616
+ const lines = getFigletLines("ANSI Shadow");
1617
+ useKeyboard((event) => {
1618
+ if (event.name === "left") setIndex((i) => i > 0 ? i - 1 : gradientNames.length - 1);
1619
+ if (event.name === "right") setIndex((i) => i < gradientNames.length - 1 ? i + 1 : 0);
1620
+ });
1621
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1622
+ /* @__PURE__ */ jsx26("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: /* @__PURE__ */ jsx26(Gradient, { name, children: lines.join("\n") }) }),
1623
+ /* @__PURE__ */ jsx26(
1624
+ StatusBar,
1625
+ {
1626
+ items: [{ key: "\u2190\u2192", label: "gradient" }, { key: "q", label: "quit" }],
1627
+ extra: /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: "cyan", bold: true }), children: name.padEnd(11) })
1628
+ }
1629
+ )
1630
+ ] });
1631
+ }
1632
+ function AsciiApp() {
1633
+ const [fontIndex, setFontIndex] = useState9(0);
1634
+ const font = fonts[fontIndex];
1635
+ const lines = getFigletLines(font.name);
1636
+ useKeyboard((event) => {
1637
+ if (event.name === "left") setFontIndex((i) => i > 0 ? i - 1 : fonts.length - 1);
1638
+ if (event.name === "right") setFontIndex((i) => i < fonts.length - 1 ? i + 1 : 0);
1639
+ });
1640
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1641
+ /* @__PURE__ */ jsx26("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx26("text", { fg: "#88c0d0", bold: true, children: line }, i)) }),
1642
+ /* @__PURE__ */ jsx26(
1643
+ StatusBar,
1644
+ {
1645
+ items: [{ key: "\u2190\u2192", label: "change font" }, { key: "q", label: "quit" }],
1646
+ extra: /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: "cyan", bold: true }), children: font.name.padEnd(11) })
1647
+ }
1648
+ )
1649
+ ] });
1650
+ }
1651
+ function TableApp() {
1652
+ const data = [
1653
+ { name: "Alice", role: "Engineer", status: "Active" },
1654
+ { name: "Bob", role: "Designer", status: "Active" },
1655
+ { name: "Charlie", role: "PM", status: "Away" }
1656
+ ];
1657
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1658
+ /* @__PURE__ */ jsx26("box", { padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx26(Table, { data, headerColor: "cyan", borderColor: "#5e81ac" }) }),
1659
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "quit" }] })
1660
+ ] });
1661
+ }
1662
+ function SpinnerApp() {
1663
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1664
+ /* @__PURE__ */ jsx26("box", { flexGrow: 1, children: /* @__PURE__ */ jsx26(SpinnerPicker, { useKeyboard }) }),
1665
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "\u2190\u2192", label: "change variant" }, { key: "q", label: "quit" }] })
1666
+ ] });
1667
+ }
1668
+ function SelectInputApp() {
1669
+ const [submitted, setSubmitted] = useState9(false);
1670
+ const items = [
1671
+ { label: "TypeScript", value: "ts" },
1672
+ { label: "JavaScript", value: "js" },
1673
+ { label: "Python", value: "py" },
1674
+ { label: "Rust", value: "rs" }
1675
+ ];
1676
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1677
+ /* @__PURE__ */ jsx26("box", { padding: 1, flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx26(SelectInput, { items, title: "Choose a language", useKeyboard, onSubmit: () => setSubmitted(true) }) }),
1678
+ /* @__PURE__ */ jsx26(StatusBar, { items: submitted ? [{ key: "q", label: "quit" }] : [
1679
+ { key: "\u2191\u2193", label: "select" },
1680
+ { key: "enter", label: "submit" },
1681
+ { key: "q", label: "quit" }
1682
+ ] })
1683
+ ] });
1684
+ }
1685
+ function MultiSelectApp() {
1686
+ const [submitted, setSubmitted] = useState9(false);
1687
+ const items = [
1688
+ { label: "TypeScript", value: "ts" },
1689
+ { label: "JavaScript", value: "js" },
1690
+ { label: "Python", value: "py" },
1691
+ { label: "Rust", value: "rs" }
1692
+ ];
1693
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1694
+ /* @__PURE__ */ jsx26("box", { padding: 1, flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx26(MultiSelect, { items, title: "Select languages", useKeyboard, onSubmit: () => setSubmitted(true) }) }),
1695
+ /* @__PURE__ */ jsx26(StatusBar, { items: submitted ? [{ key: "q", label: "quit" }] : [
1696
+ { key: "\u2191\u2193", label: "move" },
1697
+ { key: "enter", label: "select" },
1698
+ { key: "a", label: "all" },
1699
+ { key: "x", label: "clear" },
1700
+ { key: "q", label: "quit" }
1701
+ ] })
1702
+ ] });
1703
+ }
1704
+ function TextInputApp() {
1705
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1706
+ /* @__PURE__ */ jsxs18("box", { padding: 1, flexDirection: "column", gap: 1, flexGrow: 1, children: [
1707
+ /* @__PURE__ */ jsx26("text", { fg: "#d8dee9", bold: true, children: "Enter your name:" }),
1708
+ /* @__PURE__ */ jsx26(TextInput, { placeholder: "Type something...", prompt: "> " })
1709
+ ] }),
1710
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "quit" }] })
1711
+ ] });
1712
+ }
1713
+ function LinkApp() {
1714
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1715
+ /* @__PURE__ */ jsx26("box", { padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx26(LinkDemo, { useKeyboard }) }),
1716
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "quit" }] })
1717
+ ] });
1718
+ }
1719
+ function TabBarApp() {
1720
+ const tabs = ["Files", "Search", "Git", "Debug"];
1721
+ const [selectedIndex, setSelectedIndex] = useState9(0);
1722
+ useKeyboard((event) => {
1723
+ if (event.name === "left") setSelectedIndex((i) => i > 0 ? i - 1 : tabs.length - 1);
1724
+ if (event.name === "right") setSelectedIndex((i) => i < tabs.length - 1 ? i + 1 : 0);
1725
+ });
1726
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1727
+ /* @__PURE__ */ jsxs18("box", { flexDirection: "column", gap: 1, padding: 1, flexGrow: 1, children: [
1728
+ /* @__PURE__ */ jsx26(TabBar, { label: "View", options: tabs, selectedIndex }),
1729
+ /* @__PURE__ */ jsx26("text", { style: textStyle({ dim: true }), children: "Use \u2190/\u2192 arrow keys to switch tabs" })
1730
+ ] }),
1731
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "\u2190\u2192", label: "switch tab" }, { key: "q", label: "quit" }] })
1732
+ ] });
1733
+ }
1734
+ function StatusBarApp() {
1735
+ const shortcuts = [
1736
+ { key: "Tab", label: "switch focus" },
1737
+ { key: "\u2190\u2192", label: "cycle" },
1738
+ { key: "b", label: "back" },
1739
+ { key: "z", label: "reset" }
1740
+ ];
1741
+ const [lastKey, setLastKey] = useState9(null);
1742
+ useKeyboard((event) => {
1743
+ if (event.name === "tab") setLastKey("switch focus (Tab)");
1744
+ else if (event.name === "left") setLastKey("cycle (\u2190)");
1745
+ else if (event.name === "right") setLastKey("cycle (\u2192)");
1746
+ else if (event.name === "b") setLastKey("back (b)");
1747
+ else if (event.name === "z") setLastKey("reset (z)");
1748
+ });
1749
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", gap: 1, padding: 1, children: [
1750
+ lastKey ? /* @__PURE__ */ jsxs18("text", { children: [
1751
+ /* @__PURE__ */ jsx26("span", { children: "Pressed: " }),
1752
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ bold: true, fg: "cyan" }), children: lastKey })
1753
+ ] }) : /* @__PURE__ */ jsx26("text", { style: textStyle({ dim: true }), children: "Press a key to trigger an action" }),
1754
+ /* @__PURE__ */ jsx26(
1755
+ StatusBar,
1756
+ {
1757
+ items: [...shortcuts, { key: "q", label: "quit" }],
1758
+ extra: /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: "green" }), children: "\u25CF Ready" })
1759
+ }
1760
+ )
1761
+ ] });
1762
+ }
1763
+ function ModalApp() {
1764
+ const [isOpen, setIsOpen] = useState9(false);
1765
+ useKeyboard((event) => {
1766
+ if (!isOpen && event.name === "m") setIsOpen(true);
1767
+ if (isOpen && (event.name === "q" || event.name === "escape")) setIsOpen(false);
1768
+ });
1769
+ if (isOpen) {
1770
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1771
+ /* @__PURE__ */ jsx26(Modal, { title: "Example Modal", borderColor: "blue", useKeyboard, onClose: () => setIsOpen(false), children: /* @__PURE__ */ jsxs18("box", { paddingX: 1, flexDirection: "column", children: [
1772
+ /* @__PURE__ */ jsx26("text", { children: "This is a modal overlay component." }),
1773
+ /* @__PURE__ */ jsx26("text", { children: " " }),
1774
+ /* @__PURE__ */ jsx26("text", { style: textStyle({ dim: true }), children: "It stretches to fill the full terminal height." })
1775
+ ] }) }),
1776
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "close" }, { key: "Esc", label: "quit" }] })
1777
+ ] });
1778
+ }
1779
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1780
+ /* @__PURE__ */ jsx26("box", { flexDirection: "column", flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs18("text", { children: [
1781
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ dim: true }), children: "Press " }),
1782
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ inverse: true, bold: true }), children: " m " }),
1783
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ dim: true }), children: " to open modal" })
1784
+ ] }) }),
1785
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "m", label: "open modal" }, { key: "q", label: "quit" }] })
1786
+ ] });
1787
+ }
1788
+ function PrimitivesApp() {
1789
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1790
+ /* @__PURE__ */ jsxs18("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: [
1791
+ /* @__PURE__ */ jsx26("box", { border: true, borderStyle: "rounded", borderColor: "#5e81ac", title: "Layout", titleAlignment: "center", padding: 1, children: /* @__PURE__ */ jsxs18("box", { flexDirection: "row", gap: 2, children: [
1792
+ /* @__PURE__ */ jsx26("box", { border: true, borderStyle: "single", borderColor: "#a3be8c", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx26("text", { fg: "#a3be8c", bold: true, children: "Box 1" }) }),
1793
+ /* @__PURE__ */ jsx26("box", { border: true, borderStyle: "single", borderColor: "#ebcb8b", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx26("text", { fg: "#ebcb8b", bold: true, children: "Box 2" }) }),
1794
+ /* @__PURE__ */ jsx26("box", { border: true, borderStyle: "single", borderColor: "#b48ead", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx26("text", { fg: "#b48ead", bold: true, children: "Box 3" }) })
1795
+ ] }) }),
1796
+ /* @__PURE__ */ jsx26("text", { fg: "#d8dee9", dim: true, children: " Nested boxes with borders, colors & flexbox layout" })
1797
+ ] }),
1798
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "quit" }] })
1799
+ ] });
1800
+ }
1801
+ function TerminalWindowApp() {
1802
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1803
+ /* @__PURE__ */ jsxs18("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: [
1804
+ /* @__PURE__ */ jsx26("text", { style: textStyle({ fg: "green" }), children: '$ echo "Hello from OpenTUI"' }),
1805
+ /* @__PURE__ */ jsx26("text", { children: "Hello from OpenTUI" }),
1806
+ /* @__PURE__ */ jsx26("text", { style: textStyle({ fg: "green" }), children: "$ _" })
1807
+ ] }),
1808
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "quit" }] })
1809
+ ] });
1810
+ }
1811
+ var initialChatMessages = [
1812
+ { id: "1", role: "user", content: "Show me my portfolio" },
1813
+ { id: "2", role: "assistant", content: "Here's your current portfolio allocation:" },
1814
+ { id: "3", role: "user", content: "Calculate rebalancing trades" },
1815
+ { id: "4", role: "assistant", content: "I've calculated the optimal trades to rebalance your portfolio." }
1816
+ ];
1817
+ var chatNextId = 5;
1818
+ function ChatApp() {
1819
+ const [messages, setMessages] = useState9(initialChatMessages);
1820
+ const [isLoading, setIsLoading] = useState9(false);
1821
+ const [streamingText, setStreamingText] = useState9("");
1822
+ const [activeToolCalls, setActiveToolCalls] = useState9([]);
1823
+ const intervalRef = useRef6(null);
1824
+ const handleSend = useCallback2((text) => {
1825
+ const userMsg = { id: String(chatNextId++), role: "user", content: text };
1826
+ setMessages((prev) => [...prev, userMsg]);
1827
+ setIsLoading(true);
1828
+ const toolCallId = `tc-${chatNextId}`;
1829
+ setTimeout(() => {
1830
+ setIsLoading(false);
1831
+ setActiveToolCalls([{ id: toolCallId, title: "process_request", status: "in_progress" }]);
1832
+ }, 500);
1833
+ setTimeout(() => {
1834
+ setActiveToolCalls([{ id: toolCallId, title: "process_request", status: "completed" }]);
1835
+ }, 1200);
1836
+ const response = `You said: "${text}". This is a demo response.`;
1837
+ let charIndex = 0;
1838
+ setTimeout(() => {
1839
+ intervalRef.current = setInterval(() => {
1840
+ charIndex = Math.min(charIndex + 3, response.length);
1841
+ if (charIndex < response.length) {
1842
+ setStreamingText(response.slice(0, charIndex));
1843
+ } else {
1844
+ if (intervalRef.current) clearInterval(intervalRef.current);
1845
+ setStreamingText("");
1846
+ setActiveToolCalls([]);
1847
+ setMessages((prev) => [
1848
+ ...prev,
1849
+ { id: String(chatNextId++), role: "assistant", content: response }
1850
+ ]);
1851
+ }
1852
+ }, 50);
1853
+ }, 1400);
1854
+ }, []);
1855
+ const handleCancel = useCallback2(() => {
1856
+ if (intervalRef.current) clearInterval(intervalRef.current);
1857
+ setIsLoading(false);
1858
+ setStreamingText("");
1859
+ setActiveToolCalls([]);
1860
+ }, []);
1861
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
1862
+ /* @__PURE__ */ jsx26(
1863
+ ChatPanel,
1864
+ {
1865
+ messages,
1866
+ streamingText,
1867
+ isLoading,
1868
+ activeToolCalls,
1869
+ onSendMessage: handleSend,
1870
+ onCancel: handleCancel,
1871
+ placeholder: "Ask about your portfolio...",
1872
+ useKeyboard
1873
+ }
1874
+ ),
1875
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "quit" }] })
1876
+ ] });
1877
+ }
1878
+ var demos = [
1879
+ { name: "gradient", app: () => /* @__PURE__ */ jsx26(GradientApp, {}) },
1880
+ { name: "ascii", app: () => /* @__PURE__ */ jsx26(AsciiApp, {}) },
1881
+ { name: "table", app: () => /* @__PURE__ */ jsx26(TableApp, {}) },
1882
+ { name: "spinner", app: () => /* @__PURE__ */ jsx26(SpinnerApp, {}) },
1883
+ { name: "select-input", app: () => /* @__PURE__ */ jsx26(SelectInputApp, {}) },
1884
+ { name: "multi-select", app: () => /* @__PURE__ */ jsx26(MultiSelectApp, {}) },
1885
+ { name: "text-input", app: () => /* @__PURE__ */ jsx26(TextInputApp, {}) },
1886
+ { name: "link", app: () => /* @__PURE__ */ jsx26(LinkApp, {}) },
1887
+ { name: "tab-bar", app: () => /* @__PURE__ */ jsx26(TabBarApp, {}) },
1888
+ { name: "status-bar", app: () => /* @__PURE__ */ jsx26(StatusBarApp, {}) },
1889
+ { name: "modal", app: () => /* @__PURE__ */ jsx26(ModalApp, {}) },
1890
+ { name: "primitives", app: () => /* @__PURE__ */ jsx26(PrimitivesApp, {}) },
1891
+ { name: "chat", app: () => /* @__PURE__ */ jsx26(ChatApp, {}) },
1892
+ { name: "terminal", app: () => /* @__PURE__ */ jsx26(TerminalWindowApp, {}) },
1893
+ { name: "landing", app: () => /* @__PURE__ */ jsx26(LandingApp, { useKeyboard }) }
1894
+ ];
1895
+
1896
+ // src/run.tsx
1897
+ import { jsx as jsx27 } from "react/jsx-runtime";
1898
+ var _renderer;
1899
+ function DemoShell({ children }) {
1900
+ useKeyboard2((event) => {
1901
+ if (event.name === "q" || event.name === "escape") {
1902
+ _renderer.destroy();
1903
+ }
1904
+ });
1905
+ return /* @__PURE__ */ jsx27("box", { flexDirection: "column", flexGrow: 1, children });
1906
+ }
1907
+ async function runDemo(name) {
1908
+ const demo = demos.find((d) => d.name === name);
1909
+ if (!demo) {
1910
+ console.error(`Unknown demo: "${name}"`);
1911
+ console.error(`Available: ${demos.map((d) => d.name).join(", ")}`);
1912
+ process.exit(1);
1913
+ }
1914
+ _renderer = await createCliRenderer({ exitOnCtrlC: true });
1915
+ createRoot(_renderer).render(/* @__PURE__ */ jsx27(DemoShell, { children: demo.app() }));
1916
+ }
1917
+ export {
1918
+ demos,
1919
+ runDemo
1920
+ };