@vizejs/fresco 0.0.1-alpha.31

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.
@@ -0,0 +1,2521 @@
1
+ import { useInput$1 as useInput } from "./useInput-CvEslk0z.js";
2
+ import { computed, defineComponent, h, onMounted, onUnmounted, ref, watch } from "@vue/runtime-core";
3
+
4
+ //#region src/components/Box.ts
5
+ const Box = defineComponent({
6
+ name: "Box",
7
+ props: {
8
+ flexDirection: String,
9
+ flexWrap: String,
10
+ justifyContent: String,
11
+ alignItems: String,
12
+ alignSelf: String,
13
+ flexGrow: Number,
14
+ flexShrink: Number,
15
+ width: [Number, String],
16
+ height: [Number, String],
17
+ minWidth: [Number, String],
18
+ minHeight: [Number, String],
19
+ maxWidth: [Number, String],
20
+ maxHeight: [Number, String],
21
+ padding: Number,
22
+ paddingX: Number,
23
+ paddingY: Number,
24
+ paddingTop: Number,
25
+ paddingRight: Number,
26
+ paddingBottom: Number,
27
+ paddingLeft: Number,
28
+ margin: Number,
29
+ marginX: Number,
30
+ marginY: Number,
31
+ marginTop: Number,
32
+ marginRight: Number,
33
+ marginBottom: Number,
34
+ marginLeft: Number,
35
+ gap: Number,
36
+ border: String,
37
+ fg: String,
38
+ bg: String
39
+ },
40
+ setup(props, { slots }) {
41
+ return () => {
42
+ const style = {};
43
+ if (props.flexDirection) style.flex_direction = props.flexDirection;
44
+ if (props.flexWrap) style.flex_wrap = props.flexWrap;
45
+ if (props.justifyContent) style.justify_content = props.justifyContent;
46
+ if (props.alignItems) style.align_items = props.alignItems;
47
+ if (props.flexGrow !== void 0) style.flex_grow = props.flexGrow;
48
+ if (props.flexShrink !== void 0) style.flex_shrink = props.flexShrink;
49
+ if (props.width !== void 0) style.width = String(props.width);
50
+ if (props.height !== void 0) style.height = String(props.height);
51
+ if (props.minWidth !== void 0) style.min_width = String(props.minWidth);
52
+ if (props.minHeight !== void 0) style.min_height = String(props.minHeight);
53
+ if (props.maxWidth !== void 0) style.max_width = String(props.maxWidth);
54
+ if (props.maxHeight !== void 0) style.max_height = String(props.maxHeight);
55
+ if (props.padding !== void 0) style.padding = props.padding;
56
+ if (props.paddingTop !== void 0 || props.paddingY !== void 0) style.padding_top = props.paddingTop ?? props.paddingY ?? props.padding;
57
+ if (props.paddingRight !== void 0 || props.paddingX !== void 0) style.padding_right = props.paddingRight ?? props.paddingX ?? props.padding;
58
+ if (props.paddingBottom !== void 0 || props.paddingY !== void 0) style.padding_bottom = props.paddingBottom ?? props.paddingY ?? props.padding;
59
+ if (props.paddingLeft !== void 0 || props.paddingX !== void 0) style.padding_left = props.paddingLeft ?? props.paddingX ?? props.padding;
60
+ if (props.margin !== void 0) style.margin = props.margin;
61
+ if (props.marginTop !== void 0 || props.marginY !== void 0) style.margin_top = props.marginTop ?? props.marginY ?? props.margin;
62
+ if (props.marginRight !== void 0 || props.marginX !== void 0) style.margin_right = props.marginRight ?? props.marginX ?? props.margin;
63
+ if (props.marginBottom !== void 0 || props.marginY !== void 0) style.margin_bottom = props.marginBottom ?? props.marginY ?? props.margin;
64
+ if (props.marginLeft !== void 0 || props.marginX !== void 0) style.margin_left = props.marginLeft ?? props.marginX ?? props.margin;
65
+ if (props.gap !== void 0) style.gap = props.gap;
66
+ return h("box", {
67
+ style,
68
+ border: props.border,
69
+ fg: props.fg,
70
+ bg: props.bg
71
+ }, slots.default?.());
72
+ };
73
+ }
74
+ });
75
+
76
+ //#endregion
77
+ //#region src/components/Divider.ts
78
+ const Divider = defineComponent({
79
+ name: "Divider",
80
+ props: {
81
+ direction: {
82
+ type: String,
83
+ default: "horizontal"
84
+ },
85
+ char: String,
86
+ title: String,
87
+ fg: {
88
+ type: String,
89
+ default: "gray"
90
+ },
91
+ titleFg: String
92
+ },
93
+ setup(props) {
94
+ return () => {
95
+ const dividerChar = props.char ?? (props.direction === "horizontal" ? "─" : "│");
96
+ if (props.direction === "vertical") return h("text", { fg: props.fg }, dividerChar);
97
+ if (props.title) return h("box", { style: {
98
+ flex_direction: "row",
99
+ align_items: "center"
100
+ } }, [
101
+ h("text", { fg: props.fg }, dividerChar.repeat(3)),
102
+ h("text", {
103
+ fg: props.titleFg ?? props.fg,
104
+ bold: true
105
+ }, ` ${props.title} `),
106
+ h("text", { fg: props.fg }, dividerChar.repeat(3))
107
+ ]);
108
+ return h("text", { fg: props.fg }, dividerChar.repeat(40));
109
+ };
110
+ }
111
+ });
112
+
113
+ //#endregion
114
+ //#region src/components/Stack.ts
115
+ const ALIGN_MAP = {
116
+ start: "flex-start",
117
+ center: "center",
118
+ end: "flex-end",
119
+ stretch: "stretch"
120
+ };
121
+ const JUSTIFY_MAP = {
122
+ start: "flex-start",
123
+ center: "center",
124
+ end: "flex-end",
125
+ between: "space-between",
126
+ around: "space-around",
127
+ evenly: "space-evenly"
128
+ };
129
+ const Stack = defineComponent({
130
+ name: "Stack",
131
+ props: {
132
+ direction: {
133
+ type: String,
134
+ default: "vertical"
135
+ },
136
+ gap: {
137
+ type: Number,
138
+ default: 0
139
+ },
140
+ align: {
141
+ type: String,
142
+ default: "stretch"
143
+ },
144
+ justify: {
145
+ type: String,
146
+ default: "start"
147
+ },
148
+ wrap: {
149
+ type: Boolean,
150
+ default: false
151
+ }
152
+ },
153
+ setup(props, { slots }) {
154
+ return () => {
155
+ return h("box", { style: {
156
+ flex_direction: props.direction === "horizontal" ? "row" : "column",
157
+ gap: props.gap,
158
+ align_items: ALIGN_MAP[props.align ?? "stretch"],
159
+ justify_content: JUSTIFY_MAP[props.justify ?? "start"],
160
+ flex_wrap: props.wrap ? "wrap" : "nowrap"
161
+ } }, slots.default?.());
162
+ };
163
+ }
164
+ });
165
+ const HStack = defineComponent({
166
+ name: "HStack",
167
+ props: {
168
+ gap: {
169
+ type: Number,
170
+ default: 1
171
+ },
172
+ align: String,
173
+ justify: String
174
+ },
175
+ setup(props, { slots }) {
176
+ return () => h(Stack, {
177
+ direction: "horizontal",
178
+ ...props
179
+ }, slots.default);
180
+ }
181
+ });
182
+ const VStack = defineComponent({
183
+ name: "VStack",
184
+ props: {
185
+ gap: {
186
+ type: Number,
187
+ default: 0
188
+ },
189
+ align: String,
190
+ justify: String
191
+ },
192
+ setup(props, { slots }) {
193
+ return () => h(Stack, {
194
+ direction: "vertical",
195
+ ...props
196
+ }, slots.default);
197
+ }
198
+ });
199
+
200
+ //#endregion
201
+ //#region src/components/Grid.ts
202
+ const Grid = defineComponent({
203
+ name: "Grid",
204
+ props: {
205
+ columns: {
206
+ type: Number,
207
+ default: 2
208
+ },
209
+ gap: {
210
+ type: Number,
211
+ default: 1
212
+ },
213
+ rowGap: Number,
214
+ columnGap: Number
215
+ },
216
+ setup(props, { slots }) {
217
+ return () => {
218
+ const children = slots.default?.() ?? [];
219
+ const flatChildren = Array.isArray(children) ? children.flat() : [children];
220
+ const rows = [];
221
+ let currentRow = [];
222
+ flatChildren.forEach((child, index) => {
223
+ currentRow.push(child);
224
+ if (currentRow.length === props.columns || index === flatChildren.length - 1) {
225
+ rows.push([...currentRow]);
226
+ currentRow = [];
227
+ }
228
+ });
229
+ const rowGap = props.rowGap ?? props.gap;
230
+ const columnGap = props.columnGap ?? props.gap;
231
+ return h("box", { style: {
232
+ flex_direction: "column",
233
+ gap: rowGap
234
+ } }, rows.map((row, rowIndex) => h("box", {
235
+ key: `row-${rowIndex}`,
236
+ style: {
237
+ flex_direction: "row",
238
+ gap: columnGap
239
+ }
240
+ }, row.map((cell, cellIndex) => h("box", {
241
+ key: `cell-${rowIndex}-${cellIndex}`,
242
+ style: { flex_grow: 1 }
243
+ }, [cell])))));
244
+ };
245
+ }
246
+ });
247
+
248
+ //#endregion
249
+ //#region src/components/Card.ts
250
+ const Card = defineComponent({
251
+ name: "Card",
252
+ props: {
253
+ title: String,
254
+ subtitle: String,
255
+ footer: String,
256
+ border: {
257
+ type: String,
258
+ default: "rounded"
259
+ },
260
+ padding: {
261
+ type: Number,
262
+ default: 1
263
+ },
264
+ titleFg: {
265
+ type: String,
266
+ default: "white"
267
+ },
268
+ borderFg: String,
269
+ bg: String
270
+ },
271
+ setup(props, { slots }) {
272
+ return () => {
273
+ const children = [];
274
+ if (props.title || props.subtitle) {
275
+ const headerContent = [];
276
+ if (props.title) headerContent.push(h("text", {
277
+ fg: props.titleFg,
278
+ bold: true
279
+ }, props.title));
280
+ if (props.subtitle) headerContent.push(h("text", { dim: true }, props.title ? ` - ${props.subtitle}` : props.subtitle));
281
+ children.push(h("box", {
282
+ key: "header",
283
+ style: {
284
+ flex_direction: "row",
285
+ margin_bottom: 1
286
+ }
287
+ }, headerContent));
288
+ }
289
+ children.push(h("box", {
290
+ key: "content",
291
+ style: { flex_grow: 1 }
292
+ }, slots.default?.()));
293
+ if (props.footer || slots.footer) children.push(h("box", {
294
+ key: "footer",
295
+ style: { margin_top: 1 }
296
+ }, slots.footer?.() ?? [h("text", { dim: true }, props.footer)]));
297
+ return h("box", {
298
+ border: props.border === "none" ? void 0 : props.border,
299
+ fg: props.borderFg,
300
+ bg: props.bg,
301
+ style: {
302
+ flex_direction: "column",
303
+ padding: props.padding
304
+ }
305
+ }, children);
306
+ };
307
+ }
308
+ });
309
+
310
+ //#endregion
311
+ //#region src/components/Text.ts
312
+ const Text = defineComponent({
313
+ name: "Text",
314
+ props: {
315
+ content: String,
316
+ wrap: Boolean,
317
+ fg: String,
318
+ bg: String,
319
+ bold: Boolean,
320
+ dim: Boolean,
321
+ italic: Boolean,
322
+ underline: Boolean,
323
+ strikethrough: Boolean
324
+ },
325
+ setup(props, { slots }) {
326
+ return () => {
327
+ const text = props.content ?? slots.default?.()?.map((vnode) => {
328
+ if (typeof vnode.children === "string") return vnode.children;
329
+ return "";
330
+ }).join("") ?? "";
331
+ return h("text", {
332
+ text,
333
+ wrap: props.wrap,
334
+ fg: props.fg,
335
+ bg: props.bg,
336
+ bold: props.bold,
337
+ dim: props.dim,
338
+ italic: props.italic,
339
+ underline: props.underline,
340
+ strikethrough: props.strikethrough
341
+ });
342
+ };
343
+ }
344
+ });
345
+ /**
346
+ * Convenience components for common text styles
347
+ */
348
+ const ErrorText = defineComponent({
349
+ name: "ErrorText",
350
+ props: { content: String },
351
+ setup(props, { slots }) {
352
+ return () => h(Text, {
353
+ fg: "red",
354
+ ...props
355
+ }, slots);
356
+ }
357
+ });
358
+ const WarningText = defineComponent({
359
+ name: "WarningText",
360
+ props: { content: String },
361
+ setup(props, { slots }) {
362
+ return () => h(Text, {
363
+ fg: "yellow",
364
+ ...props
365
+ }, slots);
366
+ }
367
+ });
368
+ const SuccessText = defineComponent({
369
+ name: "SuccessText",
370
+ props: { content: String },
371
+ setup(props, { slots }) {
372
+ return () => h(Text, {
373
+ fg: "green",
374
+ ...props
375
+ }, slots);
376
+ }
377
+ });
378
+ const InfoText = defineComponent({
379
+ name: "InfoText",
380
+ props: { content: String },
381
+ setup(props, { slots }) {
382
+ return () => h(Text, {
383
+ fg: "blue",
384
+ ...props
385
+ }, slots);
386
+ }
387
+ });
388
+ const MutedText = defineComponent({
389
+ name: "MutedText",
390
+ props: { content: String },
391
+ setup(props, { slots }) {
392
+ return () => h(Text, {
393
+ dim: true,
394
+ ...props
395
+ }, slots);
396
+ }
397
+ });
398
+
399
+ //#endregion
400
+ //#region src/components/Code.ts
401
+ const Code = defineComponent({
402
+ name: "Code",
403
+ props: {
404
+ code: {
405
+ type: String,
406
+ required: true
407
+ },
408
+ language: String,
409
+ lineNumbers: {
410
+ type: Boolean,
411
+ default: true
412
+ },
413
+ startLine: {
414
+ type: Number,
415
+ default: 1
416
+ },
417
+ highlightLines: {
418
+ type: Array,
419
+ default: () => []
420
+ },
421
+ border: {
422
+ type: String,
423
+ default: "single"
424
+ },
425
+ fg: {
426
+ type: String,
427
+ default: "white"
428
+ },
429
+ lineNumberFg: {
430
+ type: String,
431
+ default: "gray"
432
+ },
433
+ highlightBg: {
434
+ type: String,
435
+ default: "blue"
436
+ }
437
+ },
438
+ setup(props) {
439
+ return () => {
440
+ const lines = props.code.split("\n");
441
+ const maxLineNum = props.startLine + lines.length - 1;
442
+ const lineNumWidth = String(maxLineNum).length;
443
+ const children = lines.map((line, index) => {
444
+ const lineNum = props.startLine + index;
445
+ const isHighlighted = props.highlightLines?.includes(lineNum);
446
+ const parts = [];
447
+ if (props.lineNumbers) parts.push(h("text", {
448
+ key: `ln-${lineNum}`,
449
+ fg: props.lineNumberFg
450
+ }, `${String(lineNum).padStart(lineNumWidth)} │ `));
451
+ parts.push(h("text", {
452
+ key: `code-${lineNum}`,
453
+ fg: props.fg,
454
+ bg: isHighlighted ? props.highlightBg : void 0
455
+ }, line || " "));
456
+ return h("box", {
457
+ key: `line-${lineNum}`,
458
+ style: { flex_direction: "row" }
459
+ }, parts);
460
+ });
461
+ if (props.language) children.unshift(h("text", {
462
+ key: "lang",
463
+ dim: true,
464
+ style: { margin_bottom: 1 }
465
+ }, `// ${props.language}`));
466
+ return h("box", {
467
+ border: props.border === "none" ? void 0 : props.border,
468
+ style: {
469
+ flex_direction: "column",
470
+ padding: props.border !== "none" ? 1 : 0
471
+ }
472
+ }, children);
473
+ };
474
+ }
475
+ });
476
+
477
+ //#endregion
478
+ //#region src/components/Link.ts
479
+ const Link = defineComponent({
480
+ name: "Link",
481
+ props: {
482
+ text: {
483
+ type: String,
484
+ required: true
485
+ },
486
+ url: String,
487
+ fg: {
488
+ type: String,
489
+ default: "blue"
490
+ },
491
+ underline: {
492
+ type: Boolean,
493
+ default: true
494
+ },
495
+ showUrl: {
496
+ type: Boolean,
497
+ default: false
498
+ }
499
+ },
500
+ emits: ["click"],
501
+ setup(props, { emit: _emit }) {
502
+ return () => {
503
+ const parts = [h("text", {
504
+ fg: props.fg,
505
+ underline: props.underline
506
+ }, props.text)];
507
+ if (props.showUrl && props.url) parts.push(h("text", { dim: true }, ` (${props.url})`));
508
+ return h("box", { style: { flex_direction: "row" } }, parts);
509
+ };
510
+ }
511
+ });
512
+
513
+ //#endregion
514
+ //#region src/components/TextInput.ts
515
+ const TextInput = defineComponent({
516
+ name: "TextInput",
517
+ props: {
518
+ modelValue: {
519
+ type: String,
520
+ default: ""
521
+ },
522
+ placeholder: {
523
+ type: String,
524
+ default: ""
525
+ },
526
+ focus: {
527
+ type: Boolean,
528
+ default: false
529
+ },
530
+ mask: {
531
+ type: Boolean,
532
+ default: false
533
+ },
534
+ maskChar: {
535
+ type: String,
536
+ default: "*"
537
+ },
538
+ width: [Number, String],
539
+ fg: String,
540
+ bg: String
541
+ },
542
+ emits: [
543
+ "update:modelValue",
544
+ "submit",
545
+ "cancel"
546
+ ],
547
+ setup(props, { emit }) {
548
+ const internalValue = ref(props.modelValue);
549
+ const cursorPos = ref(props.modelValue.length);
550
+ watch(() => props.modelValue, (newValue) => {
551
+ internalValue.value = newValue;
552
+ if (cursorPos.value > newValue.length) cursorPos.value = newValue.length;
553
+ });
554
+ const updateValue = (value) => {
555
+ internalValue.value = value;
556
+ emit("update:modelValue", value);
557
+ };
558
+ const insertText = (text) => {
559
+ const before = internalValue.value.slice(0, cursorPos.value);
560
+ const after = internalValue.value.slice(cursorPos.value);
561
+ updateValue(before + text + after);
562
+ cursorPos.value += text.length;
563
+ };
564
+ const deleteBack = () => {
565
+ if (cursorPos.value > 0) {
566
+ const before = internalValue.value.slice(0, cursorPos.value - 1);
567
+ const after = internalValue.value.slice(cursorPos.value);
568
+ updateValue(before + after);
569
+ cursorPos.value--;
570
+ }
571
+ };
572
+ const deleteForward = () => {
573
+ if (cursorPos.value < internalValue.value.length) {
574
+ const before = internalValue.value.slice(0, cursorPos.value);
575
+ const after = internalValue.value.slice(cursorPos.value + 1);
576
+ updateValue(before + after);
577
+ }
578
+ };
579
+ const moveLeft = () => {
580
+ if (cursorPos.value > 0) cursorPos.value--;
581
+ };
582
+ const moveRight = () => {
583
+ if (cursorPos.value < internalValue.value.length) cursorPos.value++;
584
+ };
585
+ const moveToStart = () => {
586
+ cursorPos.value = 0;
587
+ };
588
+ const moveToEnd = () => {
589
+ cursorPos.value = internalValue.value.length;
590
+ };
591
+ const isActive = computed(() => props.focus);
592
+ useInput({
593
+ isActive,
594
+ onChar: (char) => {
595
+ insertText(char);
596
+ },
597
+ onArrow: (direction) => {
598
+ if (direction === "left") moveLeft();
599
+ if (direction === "right") moveRight();
600
+ },
601
+ onKey: (key, modifiers) => {
602
+ if (key === "backspace") deleteBack();
603
+ else if (key === "delete") deleteForward();
604
+ else if (key === "home") moveToStart();
605
+ else if (key === "end") moveToEnd();
606
+ else if (key === "a" && modifiers.ctrl) moveToEnd();
607
+ },
608
+ onSubmit: () => {
609
+ emit("submit", internalValue.value);
610
+ },
611
+ onEscape: () => {
612
+ emit("cancel");
613
+ }
614
+ });
615
+ return () => {
616
+ const style = {};
617
+ if (props.width !== void 0) style.width = String(props.width);
618
+ return h("input", {
619
+ value: internalValue.value,
620
+ placeholder: props.placeholder,
621
+ focused: props.focus,
622
+ cursor: cursorPos.value,
623
+ mask: props.mask,
624
+ "mask-char": props.maskChar,
625
+ style,
626
+ fg: props.fg,
627
+ bg: props.bg
628
+ });
629
+ };
630
+ }
631
+ });
632
+ /**
633
+ * Password input variant
634
+ */
635
+ const PasswordInput = defineComponent({
636
+ name: "PasswordInput",
637
+ props: {
638
+ modelValue: {
639
+ type: String,
640
+ default: ""
641
+ },
642
+ placeholder: {
643
+ type: String,
644
+ default: "Enter password..."
645
+ },
646
+ focus: Boolean,
647
+ width: [Number, String],
648
+ fg: String,
649
+ bg: String
650
+ },
651
+ emits: [
652
+ "update:modelValue",
653
+ "submit",
654
+ "cancel"
655
+ ],
656
+ setup(props, { emit }) {
657
+ return () => h(TextInput, {
658
+ ...props,
659
+ mask: true,
660
+ "onUpdate:modelValue": (v) => emit("update:modelValue", v),
661
+ onSubmit: (v) => emit("submit", v),
662
+ onCancel: () => emit("cancel")
663
+ });
664
+ }
665
+ });
666
+
667
+ //#endregion
668
+ //#region src/components/TextArea.ts
669
+ const TextArea = defineComponent({
670
+ name: "TextArea",
671
+ props: {
672
+ modelValue: {
673
+ type: String,
674
+ default: ""
675
+ },
676
+ placeholder: String,
677
+ rows: {
678
+ type: Number,
679
+ default: 5
680
+ },
681
+ focused: {
682
+ type: Boolean,
683
+ default: false
684
+ },
685
+ disabled: {
686
+ type: Boolean,
687
+ default: false
688
+ },
689
+ lineNumbers: {
690
+ type: Boolean,
691
+ default: false
692
+ },
693
+ border: {
694
+ type: String,
695
+ default: "single"
696
+ },
697
+ fg: String,
698
+ placeholderFg: {
699
+ type: String,
700
+ default: "gray"
701
+ },
702
+ lineNumberFg: {
703
+ type: String,
704
+ default: "gray"
705
+ },
706
+ cursorLine: {
707
+ type: Number,
708
+ default: 0
709
+ },
710
+ cursorColumn: {
711
+ type: Number,
712
+ default: 0
713
+ }
714
+ },
715
+ emits: ["update:modelValue"],
716
+ setup(props) {
717
+ const lines = computed(() => {
718
+ const text = props.modelValue || "";
719
+ const textLines = text.split("\n");
720
+ while (textLines.length < props.rows) textLines.push("");
721
+ return textLines.slice(0, props.rows);
722
+ });
723
+ const showPlaceholder = computed(() => !props.modelValue && props.placeholder && !props.focused);
724
+ return () => {
725
+ const lineNumWidth = String(lines.value.length).length;
726
+ const children = lines.value.map((line, index) => {
727
+ const isCursorLine = props.focused && index === props.cursorLine;
728
+ const parts = [];
729
+ if (props.lineNumbers) parts.push(h("text", {
730
+ key: `ln-${index}`,
731
+ fg: props.lineNumberFg,
732
+ dim: !isCursorLine
733
+ }, `${String(index + 1).padStart(lineNumWidth)} │ `));
734
+ let content = line || (showPlaceholder.value && index === 0 ? props.placeholder : " ");
735
+ parts.push(h("text", {
736
+ key: `content-${index}`,
737
+ fg: showPlaceholder.value && index === 0 ? props.placeholderFg : props.fg,
738
+ dim: props.disabled,
739
+ bold: isCursorLine
740
+ }, content));
741
+ return h("box", {
742
+ key: `line-${index}`,
743
+ style: { flex_direction: "row" },
744
+ bg: isCursorLine ? "gray" : void 0
745
+ }, parts);
746
+ });
747
+ return h("box", {
748
+ border: props.border === "none" ? void 0 : props.border,
749
+ style: {
750
+ flex_direction: "column",
751
+ padding: props.border !== "none" ? 1 : 0,
752
+ height: String(props.rows + (props.border !== "none" ? 2 : 0))
753
+ }
754
+ }, children);
755
+ };
756
+ }
757
+ });
758
+
759
+ //#endregion
760
+ //#region src/components/Select.ts
761
+ const Select = defineComponent({
762
+ name: "Select",
763
+ props: {
764
+ options: {
765
+ type: Array,
766
+ required: true
767
+ },
768
+ modelValue: String,
769
+ placeholder: {
770
+ type: String,
771
+ default: "Select an option"
772
+ },
773
+ focused: {
774
+ type: Boolean,
775
+ default: false
776
+ },
777
+ indicator: {
778
+ type: String,
779
+ default: "> "
780
+ },
781
+ indicatorEmpty: {
782
+ type: String,
783
+ default: " "
784
+ },
785
+ fg: String,
786
+ bg: String,
787
+ selectedFg: {
788
+ type: String,
789
+ default: "cyan"
790
+ },
791
+ selectedBg: String
792
+ },
793
+ emits: ["update:modelValue", "select"],
794
+ setup(props, { emit: _emit }) {
795
+ const highlightedIndex = ref(0);
796
+ watch(() => props.modelValue, (value) => {
797
+ if (value) {
798
+ const index = props.options.findIndex((opt) => opt.value === value);
799
+ if (index !== -1) highlightedIndex.value = index;
800
+ }
801
+ }, { immediate: true });
802
+ return () => {
803
+ const children = props.options.map((option, index) => {
804
+ const isHighlighted = index === highlightedIndex.value;
805
+ const isSelected = option.value === props.modelValue;
806
+ const indicator = isHighlighted ? props.indicator : props.indicatorEmpty;
807
+ return h("box", {
808
+ key: option.value,
809
+ style: { flex_direction: "row" }
810
+ }, [h("text", {
811
+ fg: isHighlighted ? props.selectedFg : props.fg,
812
+ bg: isHighlighted ? props.selectedBg : props.bg,
813
+ dim: option.disabled
814
+ }, `${indicator}${option.label}${isSelected ? " (selected)" : ""}`)]);
815
+ });
816
+ return h("box", {
817
+ style: { flex_direction: "column" },
818
+ fg: props.fg,
819
+ bg: props.bg
820
+ }, children);
821
+ };
822
+ }
823
+ });
824
+
825
+ //#endregion
826
+ //#region src/components/Checkbox.ts
827
+ const Checkbox = defineComponent({
828
+ name: "Checkbox",
829
+ props: {
830
+ modelValue: {
831
+ type: Boolean,
832
+ default: false
833
+ },
834
+ label: String,
835
+ focused: {
836
+ type: Boolean,
837
+ default: false
838
+ },
839
+ disabled: {
840
+ type: Boolean,
841
+ default: false
842
+ },
843
+ checked: {
844
+ type: String,
845
+ default: "[x]"
846
+ },
847
+ unchecked: {
848
+ type: String,
849
+ default: "[ ]"
850
+ },
851
+ fg: String,
852
+ checkedFg: {
853
+ type: String,
854
+ default: "green"
855
+ }
856
+ },
857
+ emits: ["update:modelValue", "change"],
858
+ setup(props, { emit: _emit }) {
859
+ return () => {
860
+ const indicator = props.modelValue ? props.checked : props.unchecked;
861
+ const color = props.modelValue ? props.checkedFg : props.fg;
862
+ return h("box", { style: { flex_direction: "row" } }, [h("text", {
863
+ fg: color,
864
+ dim: props.disabled,
865
+ bold: props.focused
866
+ }, `${indicator} ${props.label ?? ""}`)]);
867
+ };
868
+ }
869
+ });
870
+
871
+ //#endregion
872
+ //#region src/components/RadioGroup.ts
873
+ const RadioGroup = defineComponent({
874
+ name: "RadioGroup",
875
+ props: {
876
+ options: {
877
+ type: Array,
878
+ required: true
879
+ },
880
+ modelValue: String,
881
+ direction: {
882
+ type: String,
883
+ default: "vertical"
884
+ },
885
+ focusedIndex: Number,
886
+ selected: {
887
+ type: String,
888
+ default: "◉"
889
+ },
890
+ unselected: {
891
+ type: String,
892
+ default: "○"
893
+ },
894
+ fg: String,
895
+ selectedFg: {
896
+ type: String,
897
+ default: "green"
898
+ }
899
+ },
900
+ emits: ["update:modelValue", "change"],
901
+ setup(props, { emit: _emit }) {
902
+ return () => {
903
+ const children = props.options.map((option, index) => {
904
+ const isSelected = option.value === props.modelValue;
905
+ const isFocused = index === props.focusedIndex;
906
+ const indicator = isSelected ? props.selected : props.unselected;
907
+ return h("text", {
908
+ key: option.value,
909
+ fg: isSelected ? props.selectedFg : props.fg,
910
+ bold: isFocused,
911
+ dim: option.disabled
912
+ }, `${indicator} ${option.label}`);
913
+ });
914
+ return h("box", { style: {
915
+ flex_direction: props.direction === "horizontal" ? "row" : "column",
916
+ gap: props.direction === "horizontal" ? 2 : 0
917
+ } }, children);
918
+ };
919
+ }
920
+ });
921
+
922
+ //#endregion
923
+ //#region src/components/Confirm.ts
924
+ const Confirm = defineComponent({
925
+ name: "Confirm",
926
+ props: {
927
+ message: {
928
+ type: String,
929
+ required: true
930
+ },
931
+ confirmText: {
932
+ type: String,
933
+ default: "Yes"
934
+ },
935
+ cancelText: {
936
+ type: String,
937
+ default: "No"
938
+ },
939
+ defaultConfirm: {
940
+ type: Boolean,
941
+ default: true
942
+ },
943
+ confirmFg: {
944
+ type: String,
945
+ default: "green"
946
+ },
947
+ cancelFg: {
948
+ type: String,
949
+ default: "red"
950
+ },
951
+ selectedFg: {
952
+ type: String,
953
+ default: "cyan"
954
+ }
955
+ },
956
+ emits: [
957
+ "confirm",
958
+ "cancel",
959
+ "select"
960
+ ],
961
+ setup(props, { emit: _emit }) {
962
+ const isConfirmSelected = ref(props.defaultConfirm);
963
+ return () => {
964
+ return h("box", { style: { flex_direction: "column" } }, [h("text", { key: "message" }, props.message), h("box", {
965
+ key: "buttons",
966
+ style: {
967
+ flex_direction: "row",
968
+ gap: 2,
969
+ margin_top: 1
970
+ }
971
+ }, [h("text", {
972
+ key: "confirm",
973
+ fg: isConfirmSelected.value ? props.selectedFg : props.confirmFg,
974
+ bold: isConfirmSelected.value
975
+ }, `[${props.confirmText}]`), h("text", {
976
+ key: "cancel",
977
+ fg: !isConfirmSelected.value ? props.selectedFg : props.cancelFg,
978
+ bold: !isConfirmSelected.value
979
+ }, `[${props.cancelText}]`)])]);
980
+ };
981
+ }
982
+ });
983
+
984
+ //#endregion
985
+ //#region src/components/Form.ts
986
+ const Form = defineComponent({
987
+ name: "Form",
988
+ props: {
989
+ fields: {
990
+ type: Array,
991
+ default: () => []
992
+ },
993
+ labelWidth: {
994
+ type: Number,
995
+ default: 15
996
+ },
997
+ gap: {
998
+ type: Number,
999
+ default: 1
1000
+ },
1001
+ labelPosition: {
1002
+ type: String,
1003
+ default: "left"
1004
+ },
1005
+ labelFg: String,
1006
+ requiredIndicator: {
1007
+ type: String,
1008
+ default: "*"
1009
+ },
1010
+ hintFg: {
1011
+ type: String,
1012
+ default: "gray"
1013
+ }
1014
+ },
1015
+ setup(props, { slots }) {
1016
+ return () => {
1017
+ const children = [];
1018
+ props.fields.forEach((field, _index) => {
1019
+ const labelContent = [h("text", { fg: props.labelFg }, field.label.padEnd(props.labelWidth))];
1020
+ if (field.required) labelContent.push(h("text", { fg: "red" }, props.requiredIndicator));
1021
+ const fieldSlot = slots[field.key]?.();
1022
+ if (props.labelPosition === "top") children.push(h("box", {
1023
+ key: field.key,
1024
+ style: {
1025
+ flex_direction: "column",
1026
+ margin_bottom: props.gap
1027
+ }
1028
+ }, [
1029
+ h("box", { style: { flex_direction: "row" } }, labelContent),
1030
+ fieldSlot ? h("box", { style: { margin_top: .5 } }, fieldSlot) : null,
1031
+ field.hint ? h("text", {
1032
+ fg: props.hintFg,
1033
+ dim: true
1034
+ }, field.hint) : null
1035
+ ].filter(Boolean)));
1036
+ else {
1037
+ children.push(h("box", {
1038
+ key: field.key,
1039
+ style: {
1040
+ flex_direction: "row",
1041
+ align_items: "center",
1042
+ margin_bottom: props.gap
1043
+ }
1044
+ }, [h("box", { style: {
1045
+ width: String(props.labelWidth),
1046
+ flex_direction: "row"
1047
+ } }, labelContent), h("box", { style: { flex_grow: 1 } }, fieldSlot)]));
1048
+ if (field.hint) children.push(h("text", {
1049
+ key: `hint-${field.key}`,
1050
+ fg: props.hintFg,
1051
+ dim: true,
1052
+ style: {
1053
+ margin_left: props.labelWidth,
1054
+ margin_bottom: props.gap
1055
+ }
1056
+ }, field.hint));
1057
+ }
1058
+ });
1059
+ return h("box", { style: { flex_direction: "column" } }, children);
1060
+ };
1061
+ }
1062
+ });
1063
+
1064
+ //#endregion
1065
+ //#region src/components/Spinner.ts
1066
+ /**
1067
+ * Spinner frame sets
1068
+ */
1069
+ const spinnerTypes = {
1070
+ dots: [
1071
+ "⠋",
1072
+ "⠙",
1073
+ "⠹",
1074
+ "⠸",
1075
+ "⠼",
1076
+ "⠴",
1077
+ "⠦",
1078
+ "⠧",
1079
+ "⠇",
1080
+ "⠏"
1081
+ ],
1082
+ dots2: [
1083
+ "⣾",
1084
+ "⣽",
1085
+ "⣻",
1086
+ "⢿",
1087
+ "⡿",
1088
+ "⣟",
1089
+ "⣯",
1090
+ "⣷"
1091
+ ],
1092
+ line: [
1093
+ "-",
1094
+ "\\",
1095
+ "|",
1096
+ "/"
1097
+ ],
1098
+ arc: [
1099
+ "◜",
1100
+ "◠",
1101
+ "◝",
1102
+ "◞",
1103
+ "◡",
1104
+ "◟"
1105
+ ],
1106
+ circle: [
1107
+ "◐",
1108
+ "◓",
1109
+ "◑",
1110
+ "◒"
1111
+ ],
1112
+ bounce: [
1113
+ "⠁",
1114
+ "⠂",
1115
+ "⠄",
1116
+ "⡀",
1117
+ "⢀",
1118
+ "⠠",
1119
+ "⠐",
1120
+ "⠈"
1121
+ ],
1122
+ box: [
1123
+ "▖",
1124
+ "▘",
1125
+ "▝",
1126
+ "▗"
1127
+ ],
1128
+ arrow: [
1129
+ "←",
1130
+ "↖",
1131
+ "↑",
1132
+ "↗",
1133
+ "→",
1134
+ "↘",
1135
+ "↓",
1136
+ "↙"
1137
+ ],
1138
+ clock: [
1139
+ "🕛",
1140
+ "🕐",
1141
+ "🕑",
1142
+ "🕒",
1143
+ "🕓",
1144
+ "🕔",
1145
+ "🕕",
1146
+ "🕖",
1147
+ "🕗",
1148
+ "🕘",
1149
+ "🕙",
1150
+ "🕚"
1151
+ ],
1152
+ moon: [
1153
+ "🌑",
1154
+ "🌒",
1155
+ "🌓",
1156
+ "🌔",
1157
+ "🌕",
1158
+ "🌖",
1159
+ "🌗",
1160
+ "🌘"
1161
+ ],
1162
+ earth: [
1163
+ "🌍",
1164
+ "🌎",
1165
+ "🌏"
1166
+ ]
1167
+ };
1168
+ const Spinner = defineComponent({
1169
+ name: "Spinner",
1170
+ props: {
1171
+ type: {
1172
+ type: String,
1173
+ default: "dots"
1174
+ },
1175
+ frames: Array,
1176
+ interval: {
1177
+ type: Number,
1178
+ default: 80
1179
+ },
1180
+ label: String,
1181
+ fg: String
1182
+ },
1183
+ setup(props) {
1184
+ const frameIndex = ref(0);
1185
+ let timer = null;
1186
+ const frames = props.frames ?? spinnerTypes[props.type] ?? spinnerTypes.dots;
1187
+ onMounted(() => {
1188
+ timer = setInterval(() => {
1189
+ frameIndex.value = (frameIndex.value + 1) % frames.length;
1190
+ }, props.interval);
1191
+ });
1192
+ onUnmounted(() => {
1193
+ if (timer) clearInterval(timer);
1194
+ });
1195
+ return () => {
1196
+ const frame = frames[frameIndex.value];
1197
+ const content = props.label ? `${frame} ${props.label}` : frame;
1198
+ return h(Text, { fg: props.fg }, () => content);
1199
+ };
1200
+ }
1201
+ });
1202
+
1203
+ //#endregion
1204
+ //#region src/components/ProgressBar.ts
1205
+ const ProgressBar = defineComponent({
1206
+ name: "ProgressBar",
1207
+ props: {
1208
+ value: {
1209
+ type: Number,
1210
+ required: true,
1211
+ validator: (v) => v >= 0 && v <= 100
1212
+ },
1213
+ width: {
1214
+ type: Number,
1215
+ default: 20
1216
+ },
1217
+ showLabel: {
1218
+ type: Boolean,
1219
+ default: true
1220
+ },
1221
+ labelPosition: {
1222
+ type: String,
1223
+ default: "right"
1224
+ },
1225
+ filledChar: {
1226
+ type: String,
1227
+ default: "█"
1228
+ },
1229
+ emptyChar: {
1230
+ type: String,
1231
+ default: "░"
1232
+ },
1233
+ leftBorder: {
1234
+ type: String,
1235
+ default: ""
1236
+ },
1237
+ rightBorder: {
1238
+ type: String,
1239
+ default: ""
1240
+ },
1241
+ filledFg: {
1242
+ type: String,
1243
+ default: "green"
1244
+ },
1245
+ emptyFg: {
1246
+ type: String,
1247
+ default: "gray"
1248
+ }
1249
+ },
1250
+ setup(props) {
1251
+ const normalizedValue = computed(() => Math.max(0, Math.min(100, props.value)));
1252
+ const filledWidth = computed(() => Math.round(normalizedValue.value / 100 * props.width));
1253
+ const emptyWidth = computed(() => props.width - filledWidth.value);
1254
+ const label = computed(() => `${Math.round(normalizedValue.value)}%`);
1255
+ return () => {
1256
+ const filled = props.filledChar.repeat(filledWidth.value);
1257
+ const empty = props.emptyChar.repeat(emptyWidth.value);
1258
+ const barContent = [
1259
+ props.leftBorder && h(Text, {}, () => props.leftBorder),
1260
+ h(Text, { fg: props.filledFg }, () => filled),
1261
+ h(Text, { fg: props.emptyFg }, () => empty),
1262
+ props.rightBorder && h(Text, {}, () => props.rightBorder)
1263
+ ].filter(Boolean);
1264
+ if (!props.showLabel) return h(Box, { flexDirection: "row" }, () => barContent);
1265
+ const labelElement = h(Text, { dim: true }, () => label.value);
1266
+ switch (props.labelPosition) {
1267
+ case "left": return h(Box, {
1268
+ flexDirection: "row",
1269
+ gap: 1
1270
+ }, () => [labelElement, ...barContent]);
1271
+ case "inside": return h(Box, {
1272
+ flexDirection: "row",
1273
+ gap: 1
1274
+ }, () => [...barContent, labelElement]);
1275
+ case "right":
1276
+ default: return h(Box, {
1277
+ flexDirection: "row",
1278
+ gap: 1
1279
+ }, () => [...barContent, labelElement]);
1280
+ }
1281
+ };
1282
+ }
1283
+ });
1284
+ /**
1285
+ * Indeterminate progress bar (animated)
1286
+ */
1287
+ const IndeterminateProgressBar = defineComponent({
1288
+ name: "IndeterminateProgressBar",
1289
+ props: {
1290
+ width: {
1291
+ type: Number,
1292
+ default: 20
1293
+ },
1294
+ fg: {
1295
+ type: String,
1296
+ default: "cyan"
1297
+ }
1298
+ },
1299
+ setup(props) {
1300
+ return () => {
1301
+ const pattern = "▓▒░░░░░░░░░░░░░░░░░░";
1302
+ const display = pattern.slice(0, props.width);
1303
+ return h(Text, { fg: props.fg }, () => display);
1304
+ };
1305
+ }
1306
+ });
1307
+
1308
+ //#endregion
1309
+ //#region src/components/Alert.ts
1310
+ const ALERT_CONFIG = {
1311
+ info: {
1312
+ icon: "ℹ",
1313
+ fg: "cyan",
1314
+ title: "Info"
1315
+ },
1316
+ success: {
1317
+ icon: "✓",
1318
+ fg: "green",
1319
+ title: "Success"
1320
+ },
1321
+ warning: {
1322
+ icon: "⚠",
1323
+ fg: "yellow",
1324
+ title: "Warning"
1325
+ },
1326
+ error: {
1327
+ icon: "✗",
1328
+ fg: "red",
1329
+ title: "Error"
1330
+ }
1331
+ };
1332
+ const Alert = defineComponent({
1333
+ name: "Alert",
1334
+ props: {
1335
+ message: {
1336
+ type: String,
1337
+ required: true
1338
+ },
1339
+ type: {
1340
+ type: String,
1341
+ default: "info"
1342
+ },
1343
+ title: String,
1344
+ showIcon: {
1345
+ type: Boolean,
1346
+ default: true
1347
+ },
1348
+ border: {
1349
+ type: String,
1350
+ default: "rounded"
1351
+ }
1352
+ },
1353
+ setup(props) {
1354
+ return () => {
1355
+ const config = ALERT_CONFIG[props.type];
1356
+ const title = props.title ?? config.title;
1357
+ const children = [];
1358
+ const headerParts = [];
1359
+ if (props.showIcon) headerParts.push(h("text", { fg: config.fg }, `${config.icon} `));
1360
+ headerParts.push(h("text", {
1361
+ fg: config.fg,
1362
+ bold: true
1363
+ }, title));
1364
+ children.push(h("box", {
1365
+ key: "header",
1366
+ style: { flex_direction: "row" }
1367
+ }, headerParts));
1368
+ children.push(h("text", {
1369
+ key: "message",
1370
+ style: { margin_top: 1 }
1371
+ }, props.message));
1372
+ return h("box", {
1373
+ border: props.border === "none" ? void 0 : props.border,
1374
+ fg: config.fg,
1375
+ style: {
1376
+ flex_direction: "column",
1377
+ padding: 1
1378
+ }
1379
+ }, children);
1380
+ };
1381
+ }
1382
+ });
1383
+
1384
+ //#endregion
1385
+ //#region src/components/Badge.ts
1386
+ const VARIANT_COLORS = {
1387
+ default: { fg: "white" },
1388
+ success: { fg: "green" },
1389
+ warning: { fg: "yellow" },
1390
+ error: { fg: "red" },
1391
+ info: { fg: "cyan" }
1392
+ };
1393
+ const Badge = defineComponent({
1394
+ name: "Badge",
1395
+ props: {
1396
+ label: {
1397
+ type: String,
1398
+ required: true
1399
+ },
1400
+ variant: {
1401
+ type: String,
1402
+ default: "default"
1403
+ },
1404
+ fg: String,
1405
+ bg: String,
1406
+ border: {
1407
+ type: Boolean,
1408
+ default: false
1409
+ }
1410
+ },
1411
+ setup(props) {
1412
+ return () => {
1413
+ const colors = VARIANT_COLORS[props.variant];
1414
+ const fg = props.fg ?? colors.fg;
1415
+ const bg = props.bg ?? colors.bg;
1416
+ if (props.border) return h("box", {
1417
+ border: "single",
1418
+ fg,
1419
+ bg,
1420
+ style: {
1421
+ padding_left: 1,
1422
+ padding_right: 1
1423
+ }
1424
+ }, [h("text", {
1425
+ fg,
1426
+ bg
1427
+ }, props.label)]);
1428
+ return h("text", {
1429
+ fg,
1430
+ bg,
1431
+ bold: true
1432
+ }, `[${props.label}]`);
1433
+ };
1434
+ }
1435
+ });
1436
+
1437
+ //#endregion
1438
+ //#region src/components/Timer.ts
1439
+ const Timer = defineComponent({
1440
+ name: "Timer",
1441
+ props: {
1442
+ mode: {
1443
+ type: String,
1444
+ default: "stopwatch"
1445
+ },
1446
+ initialSeconds: {
1447
+ type: Number,
1448
+ default: 0
1449
+ },
1450
+ autoStart: {
1451
+ type: Boolean,
1452
+ default: true
1453
+ },
1454
+ showHours: {
1455
+ type: Boolean,
1456
+ default: false
1457
+ },
1458
+ showMilliseconds: {
1459
+ type: Boolean,
1460
+ default: false
1461
+ },
1462
+ fg: {
1463
+ type: String,
1464
+ default: "white"
1465
+ },
1466
+ warningFg: {
1467
+ type: String,
1468
+ default: "yellow"
1469
+ },
1470
+ dangerFg: {
1471
+ type: String,
1472
+ default: "red"
1473
+ }
1474
+ },
1475
+ emits: ["tick", "complete"],
1476
+ setup(props, { emit, expose }) {
1477
+ const elapsed = ref(0);
1478
+ const isRunning = ref(false);
1479
+ let intervalId = null;
1480
+ const totalMs = computed(() => {
1481
+ if (props.mode === "countdown") return Math.max(0, props.initialSeconds * 1e3 - elapsed.value);
1482
+ return elapsed.value;
1483
+ });
1484
+ const formatted = computed(() => {
1485
+ const ms = totalMs.value;
1486
+ const totalSeconds = Math.floor(ms / 1e3);
1487
+ const hours = Math.floor(totalSeconds / 3600);
1488
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1489
+ const seconds = totalSeconds % 60;
1490
+ const milliseconds = Math.floor(ms % 1e3 / 10);
1491
+ let result = "";
1492
+ if (props.showHours || hours > 0) result += `${String(hours).padStart(2, "0")}:`;
1493
+ result += `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
1494
+ if (props.showMilliseconds) result += `.${String(milliseconds).padStart(2, "0")}`;
1495
+ return result;
1496
+ });
1497
+ const color = computed(() => {
1498
+ if (props.mode === "countdown") {
1499
+ const seconds = totalMs.value / 1e3;
1500
+ if (seconds <= 5) return props.dangerFg;
1501
+ if (seconds <= 10) return props.warningFg;
1502
+ }
1503
+ return props.fg;
1504
+ });
1505
+ const start = () => {
1506
+ if (isRunning.value) return;
1507
+ isRunning.value = true;
1508
+ intervalId = setInterval(() => {
1509
+ elapsed.value += 100;
1510
+ emit("tick", totalMs.value);
1511
+ if (props.mode === "countdown" && totalMs.value <= 0) {
1512
+ stop();
1513
+ emit("complete");
1514
+ }
1515
+ }, 100);
1516
+ };
1517
+ const stop = () => {
1518
+ isRunning.value = false;
1519
+ if (intervalId) {
1520
+ clearInterval(intervalId);
1521
+ intervalId = null;
1522
+ }
1523
+ };
1524
+ const reset = () => {
1525
+ elapsed.value = 0;
1526
+ };
1527
+ const toggle = () => {
1528
+ if (isRunning.value) stop();
1529
+ else start();
1530
+ };
1531
+ expose({
1532
+ start,
1533
+ stop,
1534
+ reset,
1535
+ toggle,
1536
+ isRunning
1537
+ });
1538
+ onMounted(() => {
1539
+ if (props.autoStart) start();
1540
+ });
1541
+ onUnmounted(() => {
1542
+ stop();
1543
+ });
1544
+ return () => {
1545
+ return h("text", {
1546
+ fg: color.value,
1547
+ bold: true
1548
+ }, formatted.value);
1549
+ };
1550
+ }
1551
+ });
1552
+
1553
+ //#endregion
1554
+ //#region src/components/Tooltip.ts
1555
+ const Tooltip = defineComponent({
1556
+ name: "Tooltip",
1557
+ props: {
1558
+ text: {
1559
+ type: String,
1560
+ required: true
1561
+ },
1562
+ visible: {
1563
+ type: Boolean,
1564
+ default: true
1565
+ },
1566
+ position: {
1567
+ type: String,
1568
+ default: "top"
1569
+ },
1570
+ border: {
1571
+ type: String,
1572
+ default: "rounded"
1573
+ },
1574
+ bg: {
1575
+ type: String,
1576
+ default: "white"
1577
+ },
1578
+ fg: {
1579
+ type: String,
1580
+ default: "black"
1581
+ }
1582
+ },
1583
+ setup(props, { slots }) {
1584
+ return () => {
1585
+ const content = slots.default?.();
1586
+ if (!props.visible) return h("box", {}, content);
1587
+ const tooltip = h("box", {
1588
+ key: "tooltip",
1589
+ border: props.border === "none" ? void 0 : props.border,
1590
+ bg: props.bg,
1591
+ fg: props.fg,
1592
+ style: {
1593
+ padding_left: 1,
1594
+ padding_right: 1
1595
+ }
1596
+ }, [h("text", {
1597
+ fg: props.fg,
1598
+ bg: props.bg
1599
+ }, props.text)]);
1600
+ const children = [];
1601
+ switch (props.position) {
1602
+ case "top":
1603
+ children.push(tooltip);
1604
+ children.push(h("box", { key: "content" }, content));
1605
+ break;
1606
+ case "bottom":
1607
+ children.push(h("box", { key: "content" }, content));
1608
+ children.push(tooltip);
1609
+ break;
1610
+ case "left": return h("box", { style: { flex_direction: "row" } }, [tooltip, h("box", { key: "content" }, content)]);
1611
+ case "right": return h("box", { style: { flex_direction: "row" } }, [h("box", { key: "content" }, content), tooltip]);
1612
+ }
1613
+ return h("box", { style: { flex_direction: "column" } }, children);
1614
+ };
1615
+ }
1616
+ });
1617
+
1618
+ //#endregion
1619
+ //#region src/components/List.ts
1620
+ const List = defineComponent({
1621
+ name: "List",
1622
+ props: {
1623
+ items: {
1624
+ type: Array,
1625
+ required: true
1626
+ },
1627
+ modelValue: String,
1628
+ maxHeight: Number,
1629
+ focused: {
1630
+ type: Boolean,
1631
+ default: false
1632
+ },
1633
+ indicator: {
1634
+ type: String,
1635
+ default: "> "
1636
+ },
1637
+ indicatorEmpty: {
1638
+ type: String,
1639
+ default: " "
1640
+ },
1641
+ fg: String,
1642
+ selectedFg: {
1643
+ type: String,
1644
+ default: "cyan"
1645
+ },
1646
+ selectedBg: String
1647
+ },
1648
+ emits: ["update:modelValue", "select"],
1649
+ setup(props, { emit: _emit }) {
1650
+ const scrollOffset = ref(0);
1651
+ const highlightedIndex = ref(0);
1652
+ const visibleItems = computed(() => {
1653
+ if (!props.maxHeight) return props.items;
1654
+ const start = scrollOffset.value;
1655
+ const end = start + props.maxHeight;
1656
+ return props.items.slice(start, end);
1657
+ });
1658
+ const scrollIndicator = computed(() => {
1659
+ if (!props.maxHeight || props.items.length <= props.maxHeight) return {
1660
+ showUp: false,
1661
+ showDown: false
1662
+ };
1663
+ return {
1664
+ showUp: scrollOffset.value > 0,
1665
+ showDown: scrollOffset.value + props.maxHeight < props.items.length
1666
+ };
1667
+ });
1668
+ return () => {
1669
+ const children = [];
1670
+ if (scrollIndicator.value.showUp) children.push(h("text", {
1671
+ key: "scroll-up",
1672
+ dim: true
1673
+ }, " ..."));
1674
+ visibleItems.value.forEach((item, visibleIndex) => {
1675
+ const actualIndex = scrollOffset.value + visibleIndex;
1676
+ const isHighlighted = actualIndex === highlightedIndex.value;
1677
+ const isSelected = item.key === props.modelValue;
1678
+ const indicator = isHighlighted ? props.indicator : props.indicatorEmpty;
1679
+ children.push(h("text", {
1680
+ key: item.key,
1681
+ fg: isHighlighted ? props.selectedFg : props.fg,
1682
+ bg: isHighlighted ? props.selectedBg : void 0,
1683
+ dim: item.disabled,
1684
+ bold: isSelected
1685
+ }, `${indicator}${item.label}`));
1686
+ });
1687
+ if (scrollIndicator.value.showDown) children.push(h("text", {
1688
+ key: "scroll-down",
1689
+ dim: true
1690
+ }, " ..."));
1691
+ return h("box", {
1692
+ style: { flex_direction: "column" },
1693
+ fg: props.fg
1694
+ }, children);
1695
+ };
1696
+ }
1697
+ });
1698
+
1699
+ //#endregion
1700
+ //#region src/components/Table.ts
1701
+ const Table = defineComponent({
1702
+ name: "Table",
1703
+ props: {
1704
+ columns: {
1705
+ type: Array,
1706
+ required: true
1707
+ },
1708
+ data: {
1709
+ type: Array,
1710
+ required: true
1711
+ },
1712
+ showHeader: {
1713
+ type: Boolean,
1714
+ default: true
1715
+ },
1716
+ border: {
1717
+ type: String,
1718
+ default: "single"
1719
+ },
1720
+ headerFg: {
1721
+ type: String,
1722
+ default: "white"
1723
+ },
1724
+ headerBg: String,
1725
+ rowFg: String,
1726
+ stripedBg: String,
1727
+ cellPadding: {
1728
+ type: Number,
1729
+ default: 1
1730
+ }
1731
+ },
1732
+ setup(props) {
1733
+ const formatCell = (value, width) => {
1734
+ const str = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : value == null ? "" : JSON.stringify(value);
1735
+ if (typeof width === "number" && str.length < width) return str.padEnd(width);
1736
+ return str;
1737
+ };
1738
+ return () => {
1739
+ const rows = [];
1740
+ if (props.showHeader) {
1741
+ const headerCells = props.columns.map((col) => h("text", {
1742
+ key: col.key,
1743
+ bold: true,
1744
+ fg: props.headerFg,
1745
+ bg: props.headerBg
1746
+ }, formatCell(col.header, col.width)));
1747
+ rows.push(h("box", {
1748
+ key: "header",
1749
+ style: {
1750
+ flex_direction: "row",
1751
+ gap: props.cellPadding
1752
+ }
1753
+ }, headerCells));
1754
+ if (props.border !== "none") {
1755
+ const sepChar = props.border === "double" ? "=" : "-";
1756
+ const totalWidth = props.columns.reduce((acc, col) => {
1757
+ const w = typeof col.width === "number" ? col.width : 10;
1758
+ return acc + w + (props.cellPadding ?? 1);
1759
+ }, 0);
1760
+ rows.push(h("text", {
1761
+ key: "separator",
1762
+ dim: true
1763
+ }, sepChar.repeat(totalWidth)));
1764
+ }
1765
+ }
1766
+ props.data.forEach((row, rowIndex) => {
1767
+ const cells = props.columns.map((col) => h("text", {
1768
+ key: col.key,
1769
+ fg: props.rowFg,
1770
+ bg: rowIndex % 2 === 1 ? props.stripedBg : void 0
1771
+ }, formatCell(row[col.key], col.width)));
1772
+ rows.push(h("box", {
1773
+ key: `row-${rowIndex}`,
1774
+ style: {
1775
+ flex_direction: "row",
1776
+ gap: props.cellPadding
1777
+ }
1778
+ }, cells));
1779
+ });
1780
+ return h("box", {
1781
+ style: { flex_direction: "column" },
1782
+ border: props.border
1783
+ }, rows);
1784
+ };
1785
+ }
1786
+ });
1787
+
1788
+ //#endregion
1789
+ //#region src/components/Tree.ts
1790
+ const Tree = defineComponent({
1791
+ name: "Tree",
1792
+ props: {
1793
+ data: {
1794
+ type: Array,
1795
+ required: true
1796
+ },
1797
+ expanded: {
1798
+ type: Array,
1799
+ default: () => []
1800
+ },
1801
+ selected: String,
1802
+ showLines: {
1803
+ type: Boolean,
1804
+ default: true
1805
+ },
1806
+ indent: {
1807
+ type: Number,
1808
+ default: 2
1809
+ },
1810
+ expandedIcon: {
1811
+ type: String,
1812
+ default: "▼"
1813
+ },
1814
+ collapsedIcon: {
1815
+ type: String,
1816
+ default: "▶"
1817
+ },
1818
+ leafIcon: {
1819
+ type: String,
1820
+ default: "•"
1821
+ },
1822
+ fg: String,
1823
+ selectedFg: {
1824
+ type: String,
1825
+ default: "cyan"
1826
+ }
1827
+ },
1828
+ emits: ["select", "toggle"],
1829
+ setup(props, { emit: _emit }) {
1830
+ const renderNode = (node, depth, isLast, prefix) => {
1831
+ const nodes = [];
1832
+ const hasChildren = node.children && node.children.length > 0;
1833
+ const isExpanded = props.expanded?.includes(node.key);
1834
+ const isSelected = node.key === props.selected;
1835
+ let linePrefix = prefix;
1836
+ if (props.showLines && depth > 0) linePrefix += isLast ? "└─" : "├─";
1837
+ let icon = props.leafIcon;
1838
+ if (hasChildren) icon = isExpanded ? props.expandedIcon : props.collapsedIcon;
1839
+ if (node.icon) icon = node.icon;
1840
+ nodes.push(h("text", {
1841
+ key: node.key,
1842
+ fg: isSelected ? props.selectedFg : props.fg,
1843
+ bold: isSelected,
1844
+ dim: node.disabled
1845
+ }, `${linePrefix}${icon} ${node.label}`));
1846
+ if (hasChildren && isExpanded) {
1847
+ const childPrefix = prefix + (props.showLines && depth > 0 ? isLast ? " " : "│ " : "");
1848
+ node.children.forEach((child, index) => {
1849
+ const childIsLast = index === node.children.length - 1;
1850
+ nodes.push(...renderNode(child, depth + 1, childIsLast, childPrefix + " ".repeat(props.indent)));
1851
+ });
1852
+ }
1853
+ return nodes;
1854
+ };
1855
+ return () => {
1856
+ const children = [];
1857
+ props.data.forEach((node, index) => {
1858
+ const isLast = index === props.data.length - 1;
1859
+ children.push(...renderNode(node, 0, isLast, ""));
1860
+ });
1861
+ return h("box", { style: { flex_direction: "column" } }, children);
1862
+ };
1863
+ }
1864
+ });
1865
+
1866
+ //#endregion
1867
+ //#region src/components/Menu.ts
1868
+ const Menu = defineComponent({
1869
+ name: "Menu",
1870
+ props: {
1871
+ items: {
1872
+ type: Array,
1873
+ required: true
1874
+ },
1875
+ focusedIndex: {
1876
+ type: Number,
1877
+ default: 0
1878
+ },
1879
+ border: {
1880
+ type: String,
1881
+ default: "single"
1882
+ },
1883
+ width: Number,
1884
+ fg: String,
1885
+ focusedFg: {
1886
+ type: String,
1887
+ default: "black"
1888
+ },
1889
+ focusedBg: {
1890
+ type: String,
1891
+ default: "cyan"
1892
+ },
1893
+ shortcutFg: {
1894
+ type: String,
1895
+ default: "gray"
1896
+ }
1897
+ },
1898
+ emits: ["select"],
1899
+ setup(props, { emit: _emit }) {
1900
+ return () => {
1901
+ const children = [];
1902
+ props.items.forEach((item, index) => {
1903
+ if (item.separator) {
1904
+ children.push(h("text", {
1905
+ key: `sep-${index}`,
1906
+ dim: true
1907
+ }, "─".repeat(props.width ?? 20)));
1908
+ return;
1909
+ }
1910
+ const isFocused = index === props.focusedIndex;
1911
+ const itemContent = [];
1912
+ if (item.icon) itemContent.push(h("text", { key: "icon" }, `${item.icon} `));
1913
+ itemContent.push(h("text", {
1914
+ key: "label",
1915
+ fg: isFocused ? props.focusedFg : props.fg,
1916
+ bg: isFocused ? props.focusedBg : void 0,
1917
+ dim: item.disabled,
1918
+ style: { flex_grow: 1 }
1919
+ }, item.label));
1920
+ if (item.shortcut) itemContent.push(h("text", {
1921
+ key: "shortcut",
1922
+ fg: props.shortcutFg,
1923
+ dim: true
1924
+ }, ` ${item.shortcut}`));
1925
+ children.push(h("box", {
1926
+ key: item.key,
1927
+ style: {
1928
+ flex_direction: "row",
1929
+ padding_left: 1,
1930
+ padding_right: 1
1931
+ },
1932
+ bg: isFocused ? props.focusedBg : void 0
1933
+ }, itemContent));
1934
+ });
1935
+ return h("box", {
1936
+ border: props.border === "none" ? void 0 : props.border,
1937
+ style: {
1938
+ flex_direction: "column",
1939
+ width: props.width ? String(props.width) : void 0
1940
+ }
1941
+ }, children);
1942
+ };
1943
+ }
1944
+ });
1945
+
1946
+ //#endregion
1947
+ //#region src/components/Tabs.ts
1948
+ const Tabs = defineComponent({
1949
+ name: "Tabs",
1950
+ props: {
1951
+ tabs: {
1952
+ type: Array,
1953
+ required: true
1954
+ },
1955
+ modelValue: String,
1956
+ position: {
1957
+ type: String,
1958
+ default: "top"
1959
+ },
1960
+ separator: {
1961
+ type: String,
1962
+ default: " | "
1963
+ },
1964
+ activeFg: {
1965
+ type: String,
1966
+ default: "cyan"
1967
+ },
1968
+ activeBg: String,
1969
+ inactiveFg: String,
1970
+ underline: {
1971
+ type: Boolean,
1972
+ default: true
1973
+ }
1974
+ },
1975
+ emits: ["update:modelValue", "change"],
1976
+ setup(props, { slots, emit: _emit }) {
1977
+ return () => {
1978
+ const tabItems = [];
1979
+ props.tabs.forEach((tab, index) => {
1980
+ if (index > 0) tabItems.push(h("text", {
1981
+ key: `sep-${index}`,
1982
+ dim: true
1983
+ }, props.separator));
1984
+ const isActive = tab.key === props.modelValue;
1985
+ tabItems.push(h("text", {
1986
+ key: tab.key,
1987
+ fg: isActive ? props.activeFg : props.inactiveFg,
1988
+ bg: isActive ? props.activeBg : void 0,
1989
+ bold: isActive,
1990
+ underline: isActive && props.underline,
1991
+ dim: tab.disabled
1992
+ }, tab.label));
1993
+ });
1994
+ const tabBar = h("box", {
1995
+ key: "tab-bar",
1996
+ style: {
1997
+ flex_direction: "row",
1998
+ padding_bottom: props.position === "top" ? 1 : 0,
1999
+ padding_top: props.position === "bottom" ? 1 : 0
2000
+ }
2001
+ }, tabItems);
2002
+ const content = h("box", {
2003
+ key: "content",
2004
+ style: { flex_grow: 1 }
2005
+ }, slots.default?.());
2006
+ const children = props.position === "top" ? [tabBar, content] : [content, tabBar];
2007
+ return h("box", { style: {
2008
+ flex_direction: "column",
2009
+ flex_grow: 1
2010
+ } }, children);
2011
+ };
2012
+ }
2013
+ });
2014
+
2015
+ //#endregion
2016
+ //#region src/components/Breadcrumb.ts
2017
+ const Breadcrumb = defineComponent({
2018
+ name: "Breadcrumb",
2019
+ props: {
2020
+ items: {
2021
+ type: Array,
2022
+ required: true
2023
+ },
2024
+ separator: {
2025
+ type: String,
2026
+ default: " > "
2027
+ },
2028
+ fg: {
2029
+ type: String,
2030
+ default: "gray"
2031
+ },
2032
+ activeFg: {
2033
+ type: String,
2034
+ default: "white"
2035
+ },
2036
+ separatorFg: {
2037
+ type: String,
2038
+ default: "gray"
2039
+ }
2040
+ },
2041
+ emits: ["select"],
2042
+ setup(props, { emit: _emit }) {
2043
+ return () => {
2044
+ const children = props.items.flatMap((item, index) => {
2045
+ const isLast = index === props.items.length - 1;
2046
+ const result = [];
2047
+ if (item.icon) result.push(h("text", {
2048
+ key: `icon-${item.key}`,
2049
+ fg: isLast ? props.activeFg : props.fg
2050
+ }, `${item.icon} `));
2051
+ result.push(h("text", {
2052
+ key: item.key,
2053
+ fg: isLast ? props.activeFg : props.fg,
2054
+ bold: isLast,
2055
+ underline: !isLast
2056
+ }, item.label));
2057
+ if (!isLast) result.push(h("text", {
2058
+ key: `sep-${item.key}`,
2059
+ fg: props.separatorFg
2060
+ }, props.separator));
2061
+ return result;
2062
+ });
2063
+ return h("box", { style: { flex_direction: "row" } }, children);
2064
+ };
2065
+ }
2066
+ });
2067
+
2068
+ //#endregion
2069
+ //#region src/components/Stepper.ts
2070
+ const Stepper = defineComponent({
2071
+ name: "Stepper",
2072
+ props: {
2073
+ steps: {
2074
+ type: Array,
2075
+ required: true
2076
+ },
2077
+ current: {
2078
+ type: Number,
2079
+ default: 0
2080
+ },
2081
+ completed: {
2082
+ type: Array,
2083
+ default: () => []
2084
+ },
2085
+ errors: {
2086
+ type: Array,
2087
+ default: () => []
2088
+ },
2089
+ direction: {
2090
+ type: String,
2091
+ default: "horizontal"
2092
+ },
2093
+ showNumbers: {
2094
+ type: Boolean,
2095
+ default: true
2096
+ },
2097
+ completedIcon: {
2098
+ type: String,
2099
+ default: "✓"
2100
+ },
2101
+ errorIcon: {
2102
+ type: String,
2103
+ default: "✗"
2104
+ },
2105
+ currentIcon: {
2106
+ type: String,
2107
+ default: "●"
2108
+ },
2109
+ pendingIcon: {
2110
+ type: String,
2111
+ default: "○"
2112
+ }
2113
+ },
2114
+ setup(props) {
2115
+ const getStatus = (index) => {
2116
+ if (props.errors?.includes(index)) return "error";
2117
+ if (props.completed?.includes(index)) return "completed";
2118
+ if (index === props.current) return "current";
2119
+ return "pending";
2120
+ };
2121
+ const getIcon = (index, status) => {
2122
+ if (props.showNumbers && status === "pending") return String(index + 1);
2123
+ switch (status) {
2124
+ case "completed": return props.completedIcon;
2125
+ case "error": return props.errorIcon;
2126
+ case "current": return props.currentIcon;
2127
+ default: return props.pendingIcon;
2128
+ }
2129
+ };
2130
+ const getColor = (status) => {
2131
+ switch (status) {
2132
+ case "completed": return "green";
2133
+ case "error": return "red";
2134
+ case "current": return "cyan";
2135
+ default: return "gray";
2136
+ }
2137
+ };
2138
+ return () => {
2139
+ const isHorizontal = props.direction === "horizontal";
2140
+ const connector = isHorizontal ? "───" : "│";
2141
+ const children = props.steps.flatMap((step, index) => {
2142
+ const status = getStatus(index);
2143
+ const icon = getIcon(index, status);
2144
+ const color = getColor(status);
2145
+ const isLast = index === props.steps.length - 1;
2146
+ const stepContent = [h("box", {
2147
+ key: `step-${step.key}`,
2148
+ style: {
2149
+ flex_direction: isHorizontal ? "column" : "row",
2150
+ align_items: "center"
2151
+ }
2152
+ }, [h("text", {
2153
+ fg: color,
2154
+ bold: status === "current"
2155
+ }, `[${icon}]`), h("text", {
2156
+ fg: status === "current" ? "white" : "gray",
2157
+ bold: status === "current",
2158
+ style: isHorizontal ? { margin_top: .5 } : { margin_left: 1 }
2159
+ }, step.label)])];
2160
+ if (!isLast) stepContent.push(h("text", {
2161
+ key: `connector-${index}`,
2162
+ dim: true,
2163
+ style: isHorizontal ? {
2164
+ margin_left: 1,
2165
+ margin_right: 1
2166
+ } : {
2167
+ margin_top: .5,
2168
+ margin_bottom: .5,
2169
+ margin_left: 1
2170
+ }
2171
+ }, connector));
2172
+ return stepContent;
2173
+ });
2174
+ return h("box", { style: {
2175
+ flex_direction: isHorizontal ? "row" : "column",
2176
+ align_items: isHorizontal ? "flex-start" : "stretch"
2177
+ } }, children);
2178
+ };
2179
+ }
2180
+ });
2181
+
2182
+ //#endregion
2183
+ //#region src/components/Modal.ts
2184
+ const Modal = defineComponent({
2185
+ name: "Modal",
2186
+ props: {
2187
+ visible: {
2188
+ type: Boolean,
2189
+ default: true
2190
+ },
2191
+ title: String,
2192
+ width: {
2193
+ type: [Number, String],
2194
+ default: "50%"
2195
+ },
2196
+ height: {
2197
+ type: [Number, String],
2198
+ default: "auto"
2199
+ },
2200
+ border: {
2201
+ type: String,
2202
+ default: "rounded"
2203
+ },
2204
+ titleFg: {
2205
+ type: String,
2206
+ default: "white"
2207
+ },
2208
+ borderFg: String,
2209
+ bg: String
2210
+ },
2211
+ emits: ["close"],
2212
+ setup(props, { slots, emit: _emit }) {
2213
+ return () => {
2214
+ if (!props.visible) return null;
2215
+ const children = [];
2216
+ if (props.title) children.push(h("box", {
2217
+ key: "title",
2218
+ style: {
2219
+ flex_direction: "row",
2220
+ justify_content: "center",
2221
+ padding_bottom: 1
2222
+ }
2223
+ }, [h("text", {
2224
+ bold: true,
2225
+ fg: props.titleFg
2226
+ }, props.title)]));
2227
+ children.push(h("box", {
2228
+ key: "content",
2229
+ style: { flex_grow: 1 }
2230
+ }, slots.default?.()));
2231
+ return h("box", { style: {
2232
+ justify_content: "center",
2233
+ align_items: "center",
2234
+ width: "100%",
2235
+ height: "100%"
2236
+ } }, [h("box", {
2237
+ style: {
2238
+ flex_direction: "column",
2239
+ width: String(props.width),
2240
+ height: String(props.height),
2241
+ padding: 1
2242
+ },
2243
+ border: props.border,
2244
+ fg: props.borderFg,
2245
+ bg: props.bg
2246
+ }, children)]);
2247
+ };
2248
+ }
2249
+ });
2250
+
2251
+ //#endregion
2252
+ //#region src/components/StatusBar.ts
2253
+ const StatusBar = defineComponent({
2254
+ name: "StatusBar",
2255
+ props: {
2256
+ items: {
2257
+ type: Array,
2258
+ required: true
2259
+ },
2260
+ bg: {
2261
+ type: String,
2262
+ default: "blue"
2263
+ },
2264
+ fg: {
2265
+ type: String,
2266
+ default: "white"
2267
+ },
2268
+ separator: {
2269
+ type: String,
2270
+ default: " │ "
2271
+ }
2272
+ },
2273
+ setup(props) {
2274
+ return () => {
2275
+ const leftItems = props.items.filter((item) => item.align !== "right");
2276
+ const rightItems = props.items.filter((item) => item.align === "right");
2277
+ const renderItems = (items) => {
2278
+ const result = [];
2279
+ items.forEach((item, index) => {
2280
+ if (index > 0) result.push(h("text", {
2281
+ key: `sep-${item.key}`,
2282
+ fg: props.fg,
2283
+ bg: props.bg,
2284
+ dim: true
2285
+ }, props.separator));
2286
+ result.push(h("text", {
2287
+ key: item.key,
2288
+ fg: item.fg ?? props.fg,
2289
+ bg: item.bg ?? props.bg,
2290
+ bold: item.bold
2291
+ }, item.content));
2292
+ });
2293
+ return result;
2294
+ };
2295
+ return h("box", {
2296
+ bg: props.bg,
2297
+ style: {
2298
+ flex_direction: "row",
2299
+ justify_content: "space-between",
2300
+ width: "100%",
2301
+ padding_left: 1,
2302
+ padding_right: 1
2303
+ }
2304
+ }, [h("box", {
2305
+ key: "left",
2306
+ style: { flex_direction: "row" }
2307
+ }, renderItems(leftItems)), h("box", {
2308
+ key: "right",
2309
+ style: { flex_direction: "row" }
2310
+ }, renderItems(rightItems))]);
2311
+ };
2312
+ }
2313
+ });
2314
+
2315
+ //#endregion
2316
+ //#region src/components/Header.ts
2317
+ const Header = defineComponent({
2318
+ name: "Header",
2319
+ props: {
2320
+ title: {
2321
+ type: String,
2322
+ required: true
2323
+ },
2324
+ subtitle: String,
2325
+ left: String,
2326
+ right: String,
2327
+ bg: String,
2328
+ titleFg: {
2329
+ type: String,
2330
+ default: "white"
2331
+ },
2332
+ subtitleFg: {
2333
+ type: String,
2334
+ default: "gray"
2335
+ },
2336
+ borderBottom: {
2337
+ type: Boolean,
2338
+ default: false
2339
+ }
2340
+ },
2341
+ setup(props, { slots }) {
2342
+ return () => {
2343
+ const leftContent = slots.left?.() ?? (props.left ? [h("text", {}, props.left)] : []);
2344
+ const rightContent = slots.right?.() ?? (props.right ? [h("text", {}, props.right)] : []);
2345
+ const centerContent = [h("text", {
2346
+ fg: props.titleFg,
2347
+ bold: true
2348
+ }, props.title)];
2349
+ if (props.subtitle) centerContent.push(h("text", {
2350
+ fg: props.subtitleFg,
2351
+ dim: true
2352
+ }, ` - ${props.subtitle}`));
2353
+ const children = [h("box", {
2354
+ key: "header-content",
2355
+ bg: props.bg,
2356
+ style: {
2357
+ flex_direction: "row",
2358
+ justify_content: "space-between",
2359
+ align_items: "center",
2360
+ width: "100%",
2361
+ padding: 1
2362
+ }
2363
+ }, [
2364
+ h("box", {
2365
+ key: "left",
2366
+ style: { flex_direction: "row" }
2367
+ }, leftContent),
2368
+ h("box", {
2369
+ key: "center",
2370
+ style: { flex_direction: "row" }
2371
+ }, centerContent),
2372
+ h("box", {
2373
+ key: "right",
2374
+ style: { flex_direction: "row" }
2375
+ }, rightContent)
2376
+ ])];
2377
+ if (props.borderBottom) children.push(h("text", {
2378
+ key: "border",
2379
+ dim: true
2380
+ }, "─".repeat(80)));
2381
+ return h("box", { style: { flex_direction: "column" } }, children);
2382
+ };
2383
+ }
2384
+ });
2385
+
2386
+ //#endregion
2387
+ //#region src/components/KeyHint.ts
2388
+ const KeyHint = defineComponent({
2389
+ name: "KeyHint",
2390
+ props: {
2391
+ bindings: {
2392
+ type: Array,
2393
+ required: true
2394
+ },
2395
+ direction: {
2396
+ type: String,
2397
+ default: "horizontal"
2398
+ },
2399
+ keyFg: {
2400
+ type: String,
2401
+ default: "black"
2402
+ },
2403
+ keyBg: {
2404
+ type: String,
2405
+ default: "white"
2406
+ },
2407
+ descFg: {
2408
+ type: String,
2409
+ default: "gray"
2410
+ },
2411
+ separator: {
2412
+ type: String,
2413
+ default: " "
2414
+ }
2415
+ },
2416
+ setup(props) {
2417
+ return () => {
2418
+ const children = props.bindings.map((binding, index) => {
2419
+ const keyParts = binding.keys.map((key, keyIndex) => [h("text", {
2420
+ key: `key-${keyIndex}`,
2421
+ fg: props.keyFg,
2422
+ bg: props.keyBg,
2423
+ bold: true
2424
+ }, ` ${key} `), keyIndex < binding.keys.length - 1 ? h("text", { key: `plus-${keyIndex}` }, "+") : null]).flat().filter(Boolean);
2425
+ return h("box", {
2426
+ key: `binding-${index}`,
2427
+ style: {
2428
+ flex_direction: "row",
2429
+ margin_right: props.direction === "horizontal" ? 2 : 0
2430
+ }
2431
+ }, [
2432
+ ...keyParts,
2433
+ h("text", {}, props.separator),
2434
+ h("text", { fg: props.descFg }, binding.description)
2435
+ ]);
2436
+ });
2437
+ return h("box", { style: {
2438
+ flex_direction: props.direction === "horizontal" ? "row" : "column",
2439
+ flex_wrap: "wrap"
2440
+ } }, children);
2441
+ };
2442
+ }
2443
+ });
2444
+
2445
+ //#endregion
2446
+ //#region src/components/Avatar.ts
2447
+ const STATUS_COLORS = {
2448
+ online: "green",
2449
+ offline: "gray",
2450
+ away: "yellow",
2451
+ busy: "red"
2452
+ };
2453
+ const STATUS_ICONS = {
2454
+ online: "●",
2455
+ offline: "○",
2456
+ away: "◐",
2457
+ busy: "⊘"
2458
+ };
2459
+ const Avatar = defineComponent({
2460
+ name: "Avatar",
2461
+ props: {
2462
+ name: String,
2463
+ initials: String,
2464
+ size: {
2465
+ type: String,
2466
+ default: "md"
2467
+ },
2468
+ bg: {
2469
+ type: String,
2470
+ default: "blue"
2471
+ },
2472
+ fg: {
2473
+ type: String,
2474
+ default: "white"
2475
+ },
2476
+ border: {
2477
+ type: Boolean,
2478
+ default: true
2479
+ },
2480
+ status: { type: String }
2481
+ },
2482
+ setup(props) {
2483
+ return () => {
2484
+ let displayInitials = props.initials;
2485
+ if (!displayInitials && props.name) {
2486
+ const parts = props.name.split(" ").filter(Boolean);
2487
+ if (parts.length >= 2) displayInitials = `${parts[0][0]}${parts[1][0]}`.toUpperCase();
2488
+ else if (parts.length === 1) displayInitials = parts[0].slice(0, 2).toUpperCase();
2489
+ }
2490
+ displayInitials = displayInitials || "??";
2491
+ const padding = props.size === "sm" ? 0 : props.size === "lg" ? 1 : 0;
2492
+ const children = [h("text", {
2493
+ fg: props.fg,
2494
+ bg: props.bg,
2495
+ bold: true
2496
+ }, displayInitials)];
2497
+ if (props.status) children.push(h("text", { fg: STATUS_COLORS[props.status] }, STATUS_ICONS[props.status]));
2498
+ if (props.border) return h("box", {
2499
+ border: "rounded",
2500
+ bg: props.bg,
2501
+ style: {
2502
+ flex_direction: "row",
2503
+ padding_left: padding,
2504
+ padding_right: padding
2505
+ }
2506
+ }, children);
2507
+ return h("box", {
2508
+ bg: props.bg,
2509
+ style: {
2510
+ flex_direction: "row",
2511
+ padding_left: padding,
2512
+ padding_right: padding
2513
+ }
2514
+ }, children);
2515
+ };
2516
+ }
2517
+ });
2518
+
2519
+ //#endregion
2520
+ export { Alert, Avatar, Badge, Box, Breadcrumb, Card, Checkbox, Code, Confirm, Divider, Form, Grid, HStack, Header, KeyHint, Link, List, Menu, Modal, ProgressBar, RadioGroup, Select, Spinner, Stack, StatusBar, Stepper, Table, Tabs, Text, TextArea, TextInput, Timer, Tooltip, Tree, VStack };
2521
+ //# sourceMappingURL=components-DtI-O8Cr.js.map