@vizejs/fresco 0.48.0 → 0.57.0

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