@alcyone-labs/arg-parser 2.13.1 → 2.13.3

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/tui.cjs ADDED
@@ -0,0 +1,1137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const solid = require("@opentui/solid");
4
+ const solidJs = require("solid-js");
5
+ const EXIT_GUARD_STATE_KEY = /* @__PURE__ */ Symbol.for(
6
+ "@alcyone-labs/arg-parser/tui/ExitGuardState"
7
+ );
8
+ function getExitGuardState() {
9
+ return process[EXIT_GUARD_STATE_KEY];
10
+ }
11
+ function setExitGuardState(state) {
12
+ if (state) {
13
+ process[EXIT_GUARD_STATE_KEY] = state;
14
+ } else {
15
+ delete process[EXIT_GUARD_STATE_KEY];
16
+ }
17
+ }
18
+ function ExitGuard(props) {
19
+ const renderer = solid.useRenderer();
20
+ let state = getExitGuardState();
21
+ if (!state) {
22
+ const originalExit = process.exit.bind(process);
23
+ state = {
24
+ originalExit,
25
+ renderers: /* @__PURE__ */ new Set(),
26
+ activeCount: 0,
27
+ isExiting: false
28
+ };
29
+ setExitGuardState(state);
30
+ const guardedExit = ((code) => {
31
+ const currentState = getExitGuardState();
32
+ if (!currentState) {
33
+ return originalExit(code);
34
+ }
35
+ if (currentState.isExiting) {
36
+ return currentState.originalExit(code);
37
+ }
38
+ currentState.isExiting = true;
39
+ try {
40
+ if (typeof code === "number") {
41
+ process.exitCode = code;
42
+ }
43
+ for (const r of currentState.renderers) {
44
+ try {
45
+ r.destroy();
46
+ } catch {
47
+ }
48
+ }
49
+ } finally {
50
+ return currentState.originalExit(code);
51
+ }
52
+ });
53
+ process.exit = guardedExit;
54
+ }
55
+ state.activeCount++;
56
+ state.renderers.add(renderer);
57
+ solidJs.onCleanup(() => {
58
+ const state2 = getExitGuardState();
59
+ if (!state2) {
60
+ return;
61
+ }
62
+ state2.renderers.delete(renderer);
63
+ state2.activeCount = Math.max(0, state2.activeCount - 1);
64
+ if (state2.activeCount === 0) {
65
+ process.exit = state2.originalExit;
66
+ setExitGuardState(void 0);
67
+ }
68
+ });
69
+ return props.children;
70
+ }
71
+ const ShortcutContext = solidJs.createContext();
72
+ function ShortcutProvider(props) {
73
+ const [registeredBindings, setRegisteredBindings] = solidJs.createSignal(props.bindings ?? []);
74
+ const [pending, setPending] = solidJs.createSignal(null);
75
+ const register = (binding) => {
76
+ setRegisteredBindings((prev) => [...prev, binding]);
77
+ return () => {
78
+ setRegisteredBindings((prev) => prev.filter((b) => b !== binding));
79
+ };
80
+ };
81
+ const value = {
82
+ register,
83
+ pending,
84
+ bindings: registeredBindings
85
+ };
86
+ return solid.createComponent(ShortcutContext.Provider, {
87
+ value,
88
+ get children() {
89
+ return props.children;
90
+ }
91
+ });
92
+ }
93
+ function useShortcuts() {
94
+ const context = solidJs.useContext(ShortcutContext);
95
+ if (!context) {
96
+ throw new Error("useShortcuts must be used within a ShortcutProvider");
97
+ }
98
+ return context;
99
+ }
100
+ const TuiThemes = {
101
+ dark: {
102
+ name: "dark",
103
+ colors: {
104
+ text: "#ffffff",
105
+ muted: "#888888",
106
+ background: "#1a1a1a",
107
+ accent: "#00d4ff",
108
+ success: "#00ff88",
109
+ warning: "#ffaa00",
110
+ error: "#ff4444",
111
+ border: "#444444",
112
+ selection: "#0066cc"
113
+ }
114
+ },
115
+ light: {
116
+ name: "light",
117
+ colors: {
118
+ text: "#000000",
119
+ // Black text for readability
120
+ muted: "#333333",
121
+ // Dark gray muted
122
+ background: "#e8e8e8",
123
+ // Light gray background
124
+ accent: "#0044aa",
125
+ // Deep blue accent
126
+ success: "#005500",
127
+ // Dark green
128
+ warning: "#885500",
129
+ // Dark orange
130
+ error: "#880000",
131
+ // Dark red
132
+ border: "#888888",
133
+ selection: "#0044aa"
134
+ }
135
+ },
136
+ monokai: {
137
+ name: "monokai",
138
+ colors: {
139
+ text: "#f8f8f2",
140
+ muted: "#75715e",
141
+ background: "#272822",
142
+ accent: "#ae81ff",
143
+ success: "#a6e22e",
144
+ warning: "#e6db74",
145
+ error: "#f92672",
146
+ border: "#49483e",
147
+ selection: "#49483e"
148
+ }
149
+ },
150
+ dracula: {
151
+ name: "dracula",
152
+ colors: {
153
+ text: "#f8f8f2",
154
+ muted: "#6272a4",
155
+ background: "#282a36",
156
+ accent: "#bd93f9",
157
+ success: "#50fa7b",
158
+ warning: "#f1fa8c",
159
+ error: "#ff5555",
160
+ border: "#44475a",
161
+ selection: "#44475a"
162
+ }
163
+ },
164
+ nord: {
165
+ name: "nord",
166
+ colors: {
167
+ text: "#eceff4",
168
+ muted: "#4c566a",
169
+ background: "#2e3440",
170
+ accent: "#88c0d0",
171
+ success: "#a3be8c",
172
+ warning: "#ebcb8b",
173
+ error: "#bf616a",
174
+ border: "#3b4252",
175
+ selection: "#4c566a"
176
+ }
177
+ },
178
+ solarized: {
179
+ name: "solarized",
180
+ colors: {
181
+ text: "#839496",
182
+ muted: "#586e75",
183
+ background: "#002b36",
184
+ accent: "#268bd2",
185
+ success: "#859900",
186
+ warning: "#b58900",
187
+ error: "#dc322f",
188
+ border: "#073642",
189
+ selection: "#073642"
190
+ }
191
+ }
192
+ };
193
+ const THEMES = TuiThemes;
194
+ const Theme = {
195
+ /**
196
+ * Start building a theme from an existing base theme.
197
+ */
198
+ from: (base) => ({
199
+ /**
200
+ * Extend the base theme with overrides.
201
+ * Color overrides are shallow-merged with the base colors.
202
+ */
203
+ extend: (overrides) => ({
204
+ name: overrides.name ?? `${base.name}-extended`,
205
+ colors: { ...base.colors, ...overrides.colors }
206
+ })
207
+ }),
208
+ /**
209
+ * Create a new theme from scratch.
210
+ */
211
+ create: (theme) => theme,
212
+ /**
213
+ * Get all available theme names.
214
+ */
215
+ names: () => Object.keys(TuiThemes),
216
+ /**
217
+ * Get a theme by name, with fallback to dark theme.
218
+ */
219
+ get: (name) => TuiThemes[name] ?? TuiThemes["dark"]
220
+ };
221
+ const ThemeContext = solidJs.createContext();
222
+ function ThemeProvider(props) {
223
+ const themes = { ...TuiThemes };
224
+ const initialTheme = themes[props.initial ?? "dark"] ?? themes["dark"];
225
+ const [current, setCurrent] = solidJs.createSignal(initialTheme);
226
+ const setTheme = (name) => {
227
+ if (themes[name]) {
228
+ setCurrent(themes[name]);
229
+ }
230
+ };
231
+ const cycle = () => {
232
+ const currentName = current().name;
233
+ const names2 = Object.keys(themes);
234
+ const currentIndex = names2.indexOf(currentName);
235
+ const nextIndex = (currentIndex + 1) % names2.length;
236
+ setCurrent(themes[names2[nextIndex]]);
237
+ };
238
+ const register = (theme) => {
239
+ themes[theme.name] = theme;
240
+ };
241
+ const names = () => Object.keys(themes);
242
+ const value = {
243
+ current,
244
+ setTheme,
245
+ cycle,
246
+ register,
247
+ names
248
+ };
249
+ return solid.createComponent(ThemeContext.Provider, {
250
+ value,
251
+ get children() {
252
+ return props.children;
253
+ }
254
+ });
255
+ }
256
+ function useTheme() {
257
+ const context = solidJs.useContext(ThemeContext);
258
+ if (!context) {
259
+ throw new Error("useTheme must be used within a ThemeProvider");
260
+ }
261
+ return context;
262
+ }
263
+ const ToastContext = solidJs.createContext();
264
+ const DEFAULT_DURATION = 3e3;
265
+ function ToastProvider(props) {
266
+ const [state, setState] = solidJs.createSignal({
267
+ message: "",
268
+ type: "info",
269
+ visible: false
270
+ });
271
+ let hideTimeout = null;
272
+ const show = (message, type, duration) => {
273
+ if (hideTimeout) {
274
+ clearTimeout(hideTimeout);
275
+ }
276
+ setState({ message, type, visible: true });
277
+ hideTimeout = setTimeout(() => {
278
+ setState((prev) => ({ ...prev, visible: false }));
279
+ }, duration);
280
+ };
281
+ const hide = () => {
282
+ if (hideTimeout) {
283
+ clearTimeout(hideTimeout);
284
+ hideTimeout = null;
285
+ }
286
+ setState((prev) => ({ ...prev, visible: false }));
287
+ };
288
+ const value = {
289
+ info: (message, duration = DEFAULT_DURATION) => show(message, "info", duration),
290
+ success: (message, duration = DEFAULT_DURATION) => show(message, "success", duration),
291
+ error: (message, duration = DEFAULT_DURATION) => show(message, "error", duration),
292
+ warning: (message, duration = DEFAULT_DURATION) => show(message, "warning", duration),
293
+ state,
294
+ hide
295
+ };
296
+ return solid.createComponent(ToastContext.Provider, {
297
+ value,
298
+ get children() {
299
+ return props.children;
300
+ }
301
+ });
302
+ }
303
+ function useToast() {
304
+ const context = solidJs.useContext(ToastContext);
305
+ if (!context) {
306
+ throw new Error("useToast must be used within a ToastProvider");
307
+ }
308
+ return context;
309
+ }
310
+ function createTuiApp(App, config = {}) {
311
+ const { theme = "dark", shortcuts = [], onDestroy } = config;
312
+ return solid.render(
313
+ () => solid.createComponent(ExitGuard, {
314
+ get children() {
315
+ return solid.createComponent(ThemeProvider, {
316
+ get initial() {
317
+ return theme;
318
+ },
319
+ get children() {
320
+ return solid.createComponent(ShortcutProvider, {
321
+ get bindings() {
322
+ return shortcuts;
323
+ },
324
+ get children() {
325
+ return solid.createComponent(ToastProvider, {
326
+ get children() {
327
+ return solid.createComponent(App, {});
328
+ }
329
+ });
330
+ }
331
+ });
332
+ }
333
+ });
334
+ }
335
+ }),
336
+ { onDestroy }
337
+ );
338
+ }
339
+ const TuiContext = solidJs.createContext();
340
+ function useTui() {
341
+ const context = solidJs.useContext(TuiContext);
342
+ if (!context) {
343
+ throw new Error("useTui must be used within a TuiProvider");
344
+ }
345
+ return context;
346
+ }
347
+ function TuiProvider(props) {
348
+ const renderer = solid.useRenderer();
349
+ const reservedRows = props.reservedRows ?? 8;
350
+ const scrollSpeed = props.scrollSpeed ?? 3;
351
+ const [viewportHeight, setViewportHeight] = solidJs.createSignal(
352
+ Math.max(10, renderer.height - reservedRows)
353
+ );
354
+ const [viewportWidth, setViewportWidth] = solidJs.createSignal(renderer.width);
355
+ const exit = (code = 0) => {
356
+ process.exitCode = code;
357
+ renderer.destroy();
358
+ };
359
+ const handleResize = (width, height) => {
360
+ setViewportHeight(Math.max(10, height - reservedRows));
361
+ setViewportWidth(width);
362
+ };
363
+ const handleMouseScroll = (event) => {
364
+ if (!props.onScroll || !event.scroll) {
365
+ return;
366
+ }
367
+ const sign = event.scroll.direction === "up" ? -1 : event.scroll.direction === "down" ? 1 : 0;
368
+ if (sign === 0) {
369
+ return;
370
+ }
371
+ const delta = event.scroll.delta || 1;
372
+ props.onScroll(sign * delta * scrollSpeed);
373
+ };
374
+ solidJs.onMount(() => {
375
+ renderer.on("resize", handleResize);
376
+ });
377
+ solidJs.onCleanup(() => {
378
+ renderer.off("resize", handleResize);
379
+ });
380
+ const contextValue = {
381
+ viewportHeight,
382
+ viewportWidth,
383
+ exit
384
+ };
385
+ const themeName = typeof props.theme === "string" ? props.theme : props.theme?.name ?? "dark";
386
+ return solid.createComponent(ExitGuard, {
387
+ get children() {
388
+ return solid.createComponent(TuiContext.Provider, {
389
+ value: contextValue,
390
+ get children() {
391
+ return solid.createComponent(ThemeProvider, {
392
+ initial: themeName,
393
+ get children() {
394
+ return solid.createComponent(ShortcutProvider, {
395
+ get bindings() {
396
+ return props.shortcuts ?? [];
397
+ },
398
+ get children() {
399
+ return solid.createComponent(ToastProvider, {
400
+ get children() {
401
+ return /* @__PURE__ */ React.createElement(
402
+ "box",
403
+ {
404
+ width: "100%",
405
+ height: "100%",
406
+ onMouseScroll: handleMouseScroll
407
+ },
408
+ props.children
409
+ );
410
+ }
411
+ });
412
+ }
413
+ });
414
+ }
415
+ });
416
+ }
417
+ });
418
+ }
419
+ });
420
+ }
421
+ function Breadcrumb(props) {
422
+ const { current: theme } = useTheme();
423
+ const separator = props.separator ?? "›";
424
+ const accentColor = () => props.accentColor ?? theme().colors.accent;
425
+ const mutedColor = () => props.mutedColor ?? theme().colors.muted;
426
+ return /* @__PURE__ */ React.createElement("box", { height: 1, paddingLeft: 2 }, /* @__PURE__ */ React.createElement(solidJs.For, { each: props.segments }, (segment, idx) => /* @__PURE__ */ React.createElement(React.Fragment, null, idx() > 0 && /* @__PURE__ */ React.createElement("text", { color: mutedColor() }, " ", separator, " "), /* @__PURE__ */ React.createElement("text", { color: accentColor(), bold: true }, segment))));
427
+ }
428
+ function unwrap(value) {
429
+ return typeof value === "function" ? value() : value;
430
+ }
431
+ function VirtualList(props) {
432
+ const { current: theme } = useTheme();
433
+ const [scrollOffset, setScrollOffset] = solidJs.createSignal(0);
434
+ const items = () => unwrap(props.items);
435
+ const selectedIndex = () => unwrap(props.selectedIndex);
436
+ const viewportHeight = () => unwrap(props.viewportHeight ?? 20);
437
+ const showIndicator = props.showIndicator ?? true;
438
+ const visibleItems = solidJs.createMemo(() => {
439
+ const allItems = items();
440
+ const vh = viewportHeight();
441
+ const start = scrollOffset();
442
+ const end = Math.min(start + vh, allItems.length);
443
+ return allItems.slice(start, end).map((item, localIdx) => ({
444
+ item,
445
+ globalIndex: start + localIdx
446
+ }));
447
+ });
448
+ const defaultRenderItem = (item, index, selected) => {
449
+ const label = props.getLabel ? props.getLabel(item) : String(item);
450
+ const t = theme();
451
+ return /* @__PURE__ */ React.createElement(
452
+ "box",
453
+ {
454
+ height: 1,
455
+ backgroundColor: selected ? t.colors.selection : void 0
456
+ },
457
+ /* @__PURE__ */ React.createElement("text", { color: selected ? t.colors.background : t.colors.text }, showIndicator ? selected ? "› " : " " : "", label)
458
+ );
459
+ };
460
+ const renderItem = props.renderItem ?? defaultRenderItem;
461
+ return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement(solidJs.Show, { when: props.title }, /* @__PURE__ */ React.createElement("text", { bold: true, color: theme().colors.text, marginBottom: 1 }, props.title, " (", selectedIndex() + 1, "/", items().length, ")")), /* @__PURE__ */ React.createElement(solidJs.For, { each: visibleItems() }, ({ item, globalIndex }) => renderItem(item, globalIndex, globalIndex === selectedIndex())));
462
+ }
463
+ function createVirtualListController(items, selectedIndex, setSelectedIndex, viewportHeight) {
464
+ const [scrollOffset, setScrollOffset] = solidJs.createSignal(0);
465
+ const adjustScroll = (newIdx) => {
466
+ const vh = viewportHeight();
467
+ const currentOffset = scrollOffset();
468
+ if (newIdx < currentOffset) {
469
+ setScrollOffset(newIdx);
470
+ } else if (newIdx >= currentOffset + vh) {
471
+ setScrollOffset(newIdx - vh + 1);
472
+ }
473
+ };
474
+ const scrollBy = (delta) => {
475
+ const maxOffset = Math.max(0, items().length - viewportHeight());
476
+ setScrollOffset((o) => Math.max(0, Math.min(maxOffset, o + delta)));
477
+ };
478
+ const selectPrevious = () => {
479
+ const newIdx = Math.max(0, selectedIndex() - 1);
480
+ setSelectedIndex(newIdx);
481
+ adjustScroll(newIdx);
482
+ };
483
+ const selectNext = () => {
484
+ const newIdx = Math.min(items().length - 1, selectedIndex() + 1);
485
+ setSelectedIndex(newIdx);
486
+ adjustScroll(newIdx);
487
+ };
488
+ return { scrollOffset, adjustScroll, scrollBy, selectPrevious, selectNext };
489
+ }
490
+ function MasterDetail(props) {
491
+ const { current: theme } = useTheme();
492
+ const masterWidth = props.masterWidth ?? "35%";
493
+ return /* @__PURE__ */ React.createElement(
494
+ "box",
495
+ {
496
+ width: "100%",
497
+ height: "100%",
498
+ flexDirection: "column",
499
+ backgroundColor: theme().colors.background
500
+ },
501
+ /* @__PURE__ */ React.createElement(
502
+ "box",
503
+ {
504
+ height: 3,
505
+ borderStyle: "single",
506
+ borderColor: theme().colors.accent,
507
+ justifyContent: "center",
508
+ alignItems: "center"
509
+ },
510
+ /* @__PURE__ */ React.createElement("text", { bold: true, color: theme().colors.accent }, props.headerIcon ? ` ${props.headerIcon} ` : " ", props.header, " ")
511
+ ),
512
+ /* @__PURE__ */ React.createElement(solidJs.Show, { when: props.breadcrumb && props.breadcrumb.length > 0 }, /* @__PURE__ */ React.createElement(Breadcrumb, { segments: props.breadcrumb })),
513
+ /* @__PURE__ */ React.createElement("box", { flexGrow: 1, flexDirection: "row" }, /* @__PURE__ */ React.createElement(
514
+ "box",
515
+ {
516
+ width: masterWidth,
517
+ borderStyle: "single",
518
+ borderColor: theme().colors.border,
519
+ flexDirection: "column",
520
+ padding: 1
521
+ },
522
+ props.master
523
+ ), /* @__PURE__ */ React.createElement(
524
+ "box",
525
+ {
526
+ flexGrow: 1,
527
+ borderStyle: "single",
528
+ borderColor: theme().colors.border,
529
+ flexDirection: "column",
530
+ padding: 2
531
+ },
532
+ props.detail
533
+ )),
534
+ /* @__PURE__ */ React.createElement(solidJs.Show, { when: props.footer }, /* @__PURE__ */ React.createElement("box", { height: 1, backgroundColor: theme().colors.background }, /* @__PURE__ */ React.createElement("text", { color: theme().colors.muted }, " ", props.footer)))
535
+ );
536
+ }
537
+ function parseWidth(width) {
538
+ if (width === void 0) return "30%";
539
+ if (typeof width === "number") return width;
540
+ if (width.endsWith("%")) {
541
+ const percent = parseInt(width.replace("%", ""), 10);
542
+ return `${percent}%`;
543
+ }
544
+ return width;
545
+ }
546
+ function MasterDetailLayout$1(props) {
547
+ const masterWidth = parseWidth(props.masterWidth);
548
+ const gap = props.gap ?? 1;
549
+ const showDivider = props.showDivider ?? true;
550
+ return {
551
+ type: "box",
552
+ props: {
553
+ flexDirection: "row",
554
+ width: "100%",
555
+ height: "100%"
556
+ },
557
+ children: [
558
+ // Master panel
559
+ {
560
+ type: "box",
561
+ props: {
562
+ flexBasis: masterWidth,
563
+ flexShrink: 0,
564
+ overflow: "scroll"
565
+ },
566
+ children: props.master
567
+ },
568
+ // Divider (optional)
569
+ ...showDivider ? [
570
+ {
571
+ type: "box",
572
+ props: {
573
+ width: 1,
574
+ marginLeft: Math.floor(gap / 2),
575
+ marginRight: Math.ceil(gap / 2)
576
+ },
577
+ children: {
578
+ type: "text",
579
+ props: {
580
+ style: { fg: "#444444" }
581
+ },
582
+ children: "│".repeat(100)
583
+ // Repeating for height
584
+ }
585
+ }
586
+ ] : [],
587
+ // Detail panel
588
+ {
589
+ type: "box",
590
+ props: {
591
+ flexGrow: 1,
592
+ overflow: "scroll"
593
+ },
594
+ children: props.detail
595
+ }
596
+ ]
597
+ };
598
+ }
599
+ function DrillDownNavigator(props) {
600
+ const [stack, setStack] = solidJs.createSignal([]);
601
+ const push = (view) => {
602
+ setStack((prev) => [...prev, view]);
603
+ props.onNavigate?.(stack().length + 1);
604
+ };
605
+ const pop = () => {
606
+ if (stack().length > 0) {
607
+ setStack((prev) => prev.slice(0, -1));
608
+ props.onNavigate?.(stack().length - 1);
609
+ }
610
+ };
611
+ const replace = (view) => {
612
+ if (stack().length > 0) {
613
+ setStack((prev) => [...prev.slice(0, -1), view]);
614
+ } else {
615
+ push(view);
616
+ }
617
+ };
618
+ const reset = () => {
619
+ setStack([]);
620
+ props.onNavigate?.(0);
621
+ };
622
+ const depth = () => stack().length;
623
+ const canGoBack = () => stack().length > 0;
624
+ const api = {
625
+ push,
626
+ pop,
627
+ replace,
628
+ reset,
629
+ depth,
630
+ canGoBack
631
+ };
632
+ const currentView = () => {
633
+ const currentStack = stack();
634
+ if (currentStack.length > 0) {
635
+ return currentStack[currentStack.length - 1]();
636
+ }
637
+ return props.children(api);
638
+ };
639
+ return {
640
+ type: "box",
641
+ props: {
642
+ width: "100%",
643
+ height: "100%",
644
+ onKeyDown: (event) => {
645
+ if (event.defaultPrevented) return;
646
+ if ((event.key === "Escape" || event.key === "ArrowLeft") && canGoBack()) {
647
+ pop();
648
+ }
649
+ }
650
+ },
651
+ children: currentView()
652
+ };
653
+ }
654
+ function Card(props) {
655
+ const borderStyle = props.borderStyle ?? "single";
656
+ const padding = props.padding ?? 1;
657
+ let borderColor = props.borderColor;
658
+ if (!borderColor) {
659
+ try {
660
+ const { current } = useTheme();
661
+ borderColor = current().colors.border;
662
+ } catch {
663
+ borderColor = "#444444";
664
+ }
665
+ }
666
+ return {
667
+ type: "box",
668
+ props: {
669
+ flexDirection: "column",
670
+ width: props.width,
671
+ height: props.height,
672
+ ...props.onClick && { onMouseDown: props.onClick },
673
+ style: {
674
+ border: borderStyle !== "none" ? borderStyle : void 0,
675
+ borderColor,
676
+ padding
677
+ }
678
+ },
679
+ children: [
680
+ // Title row (if provided)
681
+ ...props.title ? [
682
+ {
683
+ type: "text",
684
+ props: {
685
+ style: { bold: true }
686
+ },
687
+ children: ` ${props.title} `
688
+ }
689
+ ] : [],
690
+ // Content
691
+ props.children
692
+ ]
693
+ };
694
+ }
695
+ function formatValue(value, format, currency) {
696
+ if (typeof value === "string") return value;
697
+ switch (format) {
698
+ case "compact":
699
+ if (value >= 1e9)
700
+ return `${(value / 1e9).toFixed(1)}B`;
701
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
702
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
703
+ return value.toString();
704
+ case "percent":
705
+ return `${(value * 100).toFixed(1)}%`;
706
+ case "currency":
707
+ return new Intl.NumberFormat("en-US", {
708
+ style: "currency",
709
+ currency: currency ?? "USD",
710
+ minimumFractionDigits: 0,
711
+ maximumFractionDigits: 2
712
+ }).format(value);
713
+ case "number":
714
+ default:
715
+ return new Intl.NumberFormat("en-US").format(value);
716
+ }
717
+ }
718
+ function getTrendIndicator(trend) {
719
+ switch (trend) {
720
+ case "up":
721
+ return { symbol: "▲", color: "#00ff88" };
722
+ case "down":
723
+ return { symbol: "▼", color: "#ff4444" };
724
+ case "neutral":
725
+ default:
726
+ return { symbol: "─", color: "#888888" };
727
+ }
728
+ }
729
+ function StatCard(props) {
730
+ const formattedValue = formatValue(
731
+ props.value,
732
+ props.format ?? "number",
733
+ props.currency
734
+ );
735
+ const trendIndicator = props.trend ? getTrendIndicator(props.trend) : null;
736
+ return {
737
+ type: "box",
738
+ props: {
739
+ flexDirection: "column",
740
+ width: props.width,
741
+ padding: 1,
742
+ style: {
743
+ border: "single"
744
+ },
745
+ ...props.onClick && { onMouseDown: props.onClick }
746
+ },
747
+ children: [
748
+ // Label
749
+ {
750
+ type: "text",
751
+ props: {
752
+ style: { fg: "#888888" }
753
+ },
754
+ children: props.label
755
+ },
756
+ // Value with optional trend
757
+ {
758
+ type: "box",
759
+ props: {
760
+ flexDirection: "row",
761
+ gap: 1
762
+ },
763
+ children: [
764
+ {
765
+ type: "text",
766
+ props: {
767
+ style: { bold: true, fg: "#ffffff" }
768
+ },
769
+ children: formattedValue
770
+ },
771
+ ...trendIndicator ? [
772
+ {
773
+ type: "text",
774
+ props: {
775
+ style: { fg: trendIndicator.color }
776
+ },
777
+ children: trendIndicator.symbol
778
+ }
779
+ ] : []
780
+ ]
781
+ }
782
+ ]
783
+ };
784
+ }
785
+ function MarkdownBlock(props) {
786
+ const { current } = useTheme();
787
+ return {
788
+ type: "box",
789
+ props: {
790
+ flexDirection: "column",
791
+ width: props.width ?? "100%",
792
+ height: props.height,
793
+ padding: props.padding ?? 0,
794
+ overflow: "scroll"
795
+ },
796
+ children: [
797
+ {
798
+ type: "text",
799
+ props: {
800
+ style: {
801
+ fg: current().colors.text
802
+ },
803
+ text: props.content
804
+ }
805
+ }
806
+ ]
807
+ };
808
+ }
809
+ function Button(props) {
810
+ const { current } = useTheme();
811
+ const [isHovered, setIsHovered] = solidJs.createSignal(false);
812
+ const [isPressed, setIsPressed] = solidJs.createSignal(false);
813
+ const getBackgroundColor = () => {
814
+ if (props.disabled) return current().colors.muted;
815
+ switch (props.variant) {
816
+ case "danger":
817
+ return isPressed() ? "#bd2c00" : isHovered() ? "#c82829" : current().colors.error;
818
+ case "primary":
819
+ default:
820
+ return isPressed() ? current().colors.selection : isHovered() ? current().colors.accent : current().colors.accent;
821
+ }
822
+ };
823
+ const getTextColor = () => {
824
+ if (props.disabled) return current().colors.background;
825
+ return props.variant === "primary" || props.variant === "danger" ? "#ffffff" : current().colors.text;
826
+ };
827
+ return {
828
+ type: "box",
829
+ props: {
830
+ width: props.width,
831
+ height: 3,
832
+ // Standard button height with border
833
+ flexDirection: "row",
834
+ alignItems: "center",
835
+ justifyContent: "center",
836
+ style: {
837
+ border: "single",
838
+ borderColor: isHovered() && !props.disabled ? current().colors.text : current().colors.border,
839
+ bg: getBackgroundColor()
840
+ },
841
+ // Event handlers
842
+ onMouseOver: () => !props.disabled && setIsHovered(true),
843
+ onMouseOut: () => {
844
+ setIsHovered(false);
845
+ setIsPressed(false);
846
+ },
847
+ onMouseDown: () => !props.disabled && setIsPressed(true),
848
+ onMouseUp: () => {
849
+ if (!props.disabled && isPressed()) {
850
+ setIsPressed(false);
851
+ props.onClick?.();
852
+ }
853
+ }
854
+ },
855
+ children: [
856
+ {
857
+ type: "text",
858
+ props: {
859
+ style: {
860
+ fg: getTextColor(),
861
+ bold: true
862
+ },
863
+ text: ` ${props.label} `
864
+ }
865
+ }
866
+ ]
867
+ };
868
+ }
869
+ const LAYOUT_THEMES = {
870
+ dark: {
871
+ bg: "#0d0d0d",
872
+ fg: "#f5f5f5",
873
+ accent: "#00d4ff",
874
+ muted: "#999999",
875
+ error: "#ff4444",
876
+ success: "#44ff44",
877
+ border: "#444444",
878
+ selection: "#00d4ff",
879
+ selectionFg: "#000000"
880
+ },
881
+ light: {
882
+ bg: "#e8e8e8",
883
+ fg: "#000000",
884
+ accent: "#0044aa",
885
+ muted: "#333333",
886
+ error: "#880000",
887
+ success: "#005500",
888
+ border: "#888888",
889
+ selection: "#0044aa",
890
+ selectionFg: "#ffffff"
891
+ },
892
+ monokai: {
893
+ bg: "#272822",
894
+ fg: "#f8f8f2",
895
+ accent: "#a6e22e",
896
+ muted: "#75715e",
897
+ error: "#f92672",
898
+ success: "#a6e22e",
899
+ border: "#49483e",
900
+ selection: "#a6e22e",
901
+ selectionFg: "#272822"
902
+ }
903
+ };
904
+ function resolveTheme(theme) {
905
+ if (!theme) return LAYOUT_THEMES.dark;
906
+ if (typeof theme === "string")
907
+ return LAYOUT_THEMES[theme] ?? LAYOUT_THEMES.dark;
908
+ return theme;
909
+ }
910
+ function MasterDetailLayout(props) {
911
+ const t = resolveTheme(props.theme);
912
+ props.masterWidth ?? "35%";
913
+ return /* @__PURE__ */ React.createElement(
914
+ "box",
915
+ {
916
+ width: "100%",
917
+ height: "100%",
918
+ flexDirection: "column",
919
+ backgroundColor: t.bg
920
+ },
921
+ /* @__PURE__ */ React.createElement(
922
+ "box",
923
+ {
924
+ height: 3,
925
+ borderStyle: "single",
926
+ borderColor: t.accent,
927
+ justifyContent: "center",
928
+ alignItems: "center"
929
+ },
930
+ /* @__PURE__ */ React.createElement("text", { bold: true, color: t.accent }, " ", props.header, " ")
931
+ ),
932
+ props.breadcrumb && props.breadcrumb.length > 0 && /* @__PURE__ */ React.createElement("box", { height: 1, paddingLeft: 2, backgroundColor: t.bg }, props.breadcrumb.map((segment, idx) => /* @__PURE__ */ React.createElement(React.Fragment, null, idx > 0 && /* @__PURE__ */ React.createElement("text", { color: t.muted }, " › "), /* @__PURE__ */ React.createElement("text", { color: t.accent, bold: true }, segment)))),
933
+ /* @__PURE__ */ React.createElement("box", { flexGrow: 1, flexDirection: "row" }, props.children),
934
+ props.footer && /* @__PURE__ */ React.createElement("box", { height: 1, backgroundColor: t.bg }, /* @__PURE__ */ React.createElement("text", { color: t.muted }, " ", props.footer))
935
+ );
936
+ }
937
+ function MasterPanel(props) {
938
+ const t = resolveTheme(props.theme);
939
+ return /* @__PURE__ */ React.createElement(
940
+ "box",
941
+ {
942
+ width: props.width ?? "35%",
943
+ borderStyle: "single",
944
+ borderColor: t.border,
945
+ flexDirection: "column",
946
+ padding: 1
947
+ },
948
+ props.children
949
+ );
950
+ }
951
+ function DetailPanel(props) {
952
+ const t = resolveTheme(props.theme);
953
+ return /* @__PURE__ */ React.createElement(
954
+ "box",
955
+ {
956
+ flexGrow: 1,
957
+ borderStyle: "single",
958
+ borderColor: t.border,
959
+ flexDirection: "column",
960
+ padding: 2
961
+ },
962
+ props.children
963
+ );
964
+ }
965
+ function ListItem(props) {
966
+ const t = resolveTheme(props.theme);
967
+ return /* @__PURE__ */ React.createElement("box", { height: 1, backgroundColor: props.selected ? t.selection : void 0 }, /* @__PURE__ */ React.createElement("text", { color: props.selected ? t.selectionFg : t.fg }, props.selected ? "› " : " ", props.label));
968
+ }
969
+ function useVirtualScroll(items, _selectedIdx, viewportHeight) {
970
+ const [scrollOffset, setScrollOffset] = solidJs.createSignal(0);
971
+ const adjustScroll = (newIdx) => {
972
+ const vh = viewportHeight();
973
+ const currentOffset = scrollOffset();
974
+ if (newIdx < currentOffset) {
975
+ setScrollOffset(newIdx);
976
+ } else if (newIdx >= currentOffset + vh) {
977
+ setScrollOffset(newIdx - vh + 1);
978
+ }
979
+ };
980
+ const visibleItems = solidJs.createMemo(() => {
981
+ const allItems = items();
982
+ const vh = viewportHeight();
983
+ const start = scrollOffset();
984
+ const end = Math.min(start + vh, allItems.length);
985
+ return allItems.slice(start, end).map((item, localIdx) => ({
986
+ item,
987
+ globalIndex: start + localIdx
988
+ }));
989
+ });
990
+ const scrollBy = (delta) => {
991
+ const maxOffset = Math.max(0, items().length - viewportHeight());
992
+ setScrollOffset((o) => Math.max(0, Math.min(maxOffset, o + delta)));
993
+ };
994
+ return { visibleItems, adjustScroll, scrollOffset, scrollBy };
995
+ }
996
+ function getViewportHeight(reservedRows = 8, minHeight = 10) {
997
+ return Math.max(minHeight, (process.stdout.rows || 24) - reservedRows);
998
+ }
999
+ function enableMouseReporting() {
1000
+ process.stdout.write("\x1B[?1000h");
1001
+ process.stdout.write("\x1B[?1006h");
1002
+ }
1003
+ function disableMouseReporting() {
1004
+ process.stdout.write("\x1B[?1000l");
1005
+ process.stdout.write("\x1B[?1002l");
1006
+ process.stdout.write("\x1B[?1003l");
1007
+ process.stdout.write("\x1B[?1006l");
1008
+ process.stdout.write("\x1B[?1015l");
1009
+ }
1010
+ function clearScreen() {
1011
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
1012
+ }
1013
+ function resetAttributes() {
1014
+ process.stdout.write("\x1B[0m");
1015
+ }
1016
+ function restoreStdin() {
1017
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
1018
+ try {
1019
+ process.stdin.setRawMode(false);
1020
+ } catch {
1021
+ }
1022
+ }
1023
+ }
1024
+ function switchToMainScreen() {
1025
+ process.stdout.write("\x1B[?1049l");
1026
+ }
1027
+ function cleanupTerminal() {
1028
+ switchToMainScreen();
1029
+ disableMouseReporting();
1030
+ clearScreen();
1031
+ resetAttributes();
1032
+ restoreStdin();
1033
+ }
1034
+ function parseMouseScroll(data) {
1035
+ const str = data.toString();
1036
+ const sgrMatch = str.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
1037
+ if (sgrMatch) {
1038
+ const button = parseInt(sgrMatch[1], 10);
1039
+ if (button === 64) return -1;
1040
+ if (button === 65) return 1;
1041
+ }
1042
+ return 0;
1043
+ }
1044
+ function useMouse(options = {}) {
1045
+ const { onScroll, scrollSpeed = 3 } = options;
1046
+ const handleInput = (data) => {
1047
+ const scrollDir = parseMouseScroll(data);
1048
+ if (scrollDir !== 0 && onScroll) {
1049
+ onScroll(scrollDir * scrollSpeed);
1050
+ }
1051
+ };
1052
+ solidJs.onMount(() => {
1053
+ enableMouseReporting();
1054
+ if (process.stdin.isTTY) {
1055
+ process.stdin.setRawMode(true);
1056
+ }
1057
+ process.stdin.on("data", handleInput);
1058
+ });
1059
+ solidJs.onCleanup(() => {
1060
+ disableMouseReporting();
1061
+ process.stdin.off("data", handleInput);
1062
+ });
1063
+ }
1064
+ Object.defineProperty(exports, "createComponent", {
1065
+ enumerable: true,
1066
+ get: () => solid.createComponent
1067
+ });
1068
+ Object.defineProperty(exports, "effect", {
1069
+ enumerable: true,
1070
+ get: () => solid.effect
1071
+ });
1072
+ Object.defineProperty(exports, "insert", {
1073
+ enumerable: true,
1074
+ get: () => solid.insert
1075
+ });
1076
+ Object.defineProperty(exports, "memo", {
1077
+ enumerable: true,
1078
+ get: () => solid.memo
1079
+ });
1080
+ Object.defineProperty(exports, "mergeProps", {
1081
+ enumerable: true,
1082
+ get: () => solid.mergeProps
1083
+ });
1084
+ Object.defineProperty(exports, "render", {
1085
+ enumerable: true,
1086
+ get: () => solid.render
1087
+ });
1088
+ Object.defineProperty(exports, "spread", {
1089
+ enumerable: true,
1090
+ get: () => solid.spread
1091
+ });
1092
+ Object.defineProperty(exports, "useKeyboard", {
1093
+ enumerable: true,
1094
+ get: () => solid.useKeyboard
1095
+ });
1096
+ Object.defineProperty(exports, "useRenderer", {
1097
+ enumerable: true,
1098
+ get: () => solid.useRenderer
1099
+ });
1100
+ exports.Breadcrumb = Breadcrumb;
1101
+ exports.Button = Button;
1102
+ exports.Card = Card;
1103
+ exports.DetailPanel = DetailPanel;
1104
+ exports.DrillDownNavigator = DrillDownNavigator;
1105
+ exports.LAYOUT_THEMES = LAYOUT_THEMES;
1106
+ exports.ListItem = ListItem;
1107
+ exports.MarkdownBlock = MarkdownBlock;
1108
+ exports.MasterDetail = MasterDetail;
1109
+ exports.MasterDetailLayout = MasterDetailLayout$1;
1110
+ exports.MasterDetailTemplate = MasterDetailLayout;
1111
+ exports.MasterPanel = MasterPanel;
1112
+ exports.ShortcutProvider = ShortcutProvider;
1113
+ exports.StatCard = StatCard;
1114
+ exports.THEMES = THEMES;
1115
+ exports.Theme = Theme;
1116
+ exports.ThemeProvider = ThemeProvider;
1117
+ exports.ToastProvider = ToastProvider;
1118
+ exports.TuiProvider = TuiProvider;
1119
+ exports.TuiThemes = TuiThemes;
1120
+ exports.VirtualList = VirtualList;
1121
+ exports.cleanupTerminal = cleanupTerminal;
1122
+ exports.clearScreen = clearScreen;
1123
+ exports.createTuiApp = createTuiApp;
1124
+ exports.createVirtualListController = createVirtualListController;
1125
+ exports.disableMouseReporting = disableMouseReporting;
1126
+ exports.enableMouseReporting = enableMouseReporting;
1127
+ exports.getViewportHeight = getViewportHeight;
1128
+ exports.parseMouseScroll = parseMouseScroll;
1129
+ exports.resetAttributes = resetAttributes;
1130
+ exports.restoreStdin = restoreStdin;
1131
+ exports.useMouse = useMouse;
1132
+ exports.useShortcuts = useShortcuts;
1133
+ exports.useTheme = useTheme;
1134
+ exports.useToast = useToast;
1135
+ exports.useTui = useTui;
1136
+ exports.useVirtualScroll = useVirtualScroll;
1137
+ //# sourceMappingURL=tui.cjs.map