@termuijs/widgets 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,22 +20,49 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ Banner: () => Banner,
23
24
  BarChart: () => BarChart,
25
+ BigText: () => BigText,
24
26
  Box: () => Box,
27
+ Card: () => Card,
28
+ Center: () => Center,
29
+ ChatMessage: () => ChatMessage,
30
+ Columns: () => Columns,
31
+ CommandPalette: () => CommandPalette,
32
+ Definition: () => Definition,
33
+ DiffView: () => DiffView,
25
34
  Gauge: () => Gauge,
35
+ Gradient: () => Gradient,
36
+ Grid: () => Grid,
37
+ HeatMap: () => HeatMap,
38
+ JSONView: () => JSONView,
39
+ KeyValue: () => KeyValue,
40
+ LineChart: () => LineChart,
26
41
  List: () => List,
27
42
  LogView: () => LogView,
43
+ MultiProgress: () => MultiProgress,
28
44
  ProgressBar: () => ProgressBar,
29
45
  SPINNER_FRAMES: () => SPINNER_FRAMES,
46
+ ScrollView: () => ScrollView,
30
47
  Scrollbar: () => Scrollbar,
48
+ Sidebar: () => Sidebar,
49
+ Skeleton: () => Skeleton,
31
50
  Sparkline: () => Sparkline,
32
51
  Spinner: () => Spinner,
33
52
  StatusIndicator: () => StatusIndicator,
53
+ StatusMessage: () => StatusMessage,
54
+ StreamingText: () => StreamingText,
34
55
  Table: () => Table,
35
56
  Text: () => Text,
36
57
  TextInput: () => TextInput,
58
+ ToolApproval: () => ToolApproval,
59
+ ToolCall: () => ToolCall,
60
+ Tree: () => Tree,
37
61
  VirtualList: () => VirtualList,
38
- Widget: () => Widget
62
+ Widget: () => Widget,
63
+ computeRange: () => computeRange,
64
+ computeVariableRange: () => computeVariableRange,
65
+ jsonToTree: () => jsonToTree
39
66
  });
40
67
  module.exports = __toCommonJS(index_exports);
41
68
 
@@ -261,6 +288,7 @@ var Widget = class {
261
288
  child.unmount();
262
289
  }
263
290
  this.events.emit("unmount", void 0);
291
+ this.events.removeAll();
264
292
  }
265
293
  };
266
294
 
@@ -426,8 +454,806 @@ var LogView = class extends Widget {
426
454
  }
427
455
  };
428
456
 
429
- // src/input/List.ts
457
+ // src/display/Tree.ts
430
458
  var import_core5 = require("@termuijs/core");
459
+ var Tree = class extends Widget {
460
+ _nodes;
461
+ _onSelect;
462
+ _indent;
463
+ _selectedIndex = 0;
464
+ _scrollOffset = 0;
465
+ _visibleNodes = [];
466
+ constructor(options, style = {}) {
467
+ super(style);
468
+ this._nodes = options.nodes;
469
+ this._onSelect = options.onSelect;
470
+ this._indent = options.indent ?? 2;
471
+ this.focusable = true;
472
+ this._buildVisibleNodes();
473
+ }
474
+ // ── Public API ─────────────────────────────────────
475
+ get selectedIndex() {
476
+ return this._selectedIndex;
477
+ }
478
+ get selectedNode() {
479
+ return this._visibleNodes[this._selectedIndex]?.node;
480
+ }
481
+ setNodes(nodes) {
482
+ this._nodes = nodes;
483
+ this._selectedIndex = 0;
484
+ this._scrollOffset = 0;
485
+ this._buildVisibleNodes();
486
+ this.markDirty();
487
+ }
488
+ /** Move cursor up one visible row */
489
+ movePrev() {
490
+ if (this._selectedIndex > 0) {
491
+ this._selectedIndex--;
492
+ this._clampScroll();
493
+ this.markDirty();
494
+ }
495
+ }
496
+ /** Move cursor down one visible row */
497
+ moveNext() {
498
+ if (this._selectedIndex < this._visibleNodes.length - 1) {
499
+ this._selectedIndex++;
500
+ this._clampScroll();
501
+ this.markDirty();
502
+ }
503
+ }
504
+ /** Go to first visible node */
505
+ moveFirst() {
506
+ this._selectedIndex = 0;
507
+ this._clampScroll();
508
+ this.markDirty();
509
+ }
510
+ /** Go to last visible node */
511
+ moveLast() {
512
+ if (this._visibleNodes.length > 0) {
513
+ this._selectedIndex = this._visibleNodes.length - 1;
514
+ this._clampScroll();
515
+ this.markDirty();
516
+ }
517
+ }
518
+ /** Expand the selected node (if it's a collapsed parent) */
519
+ expand() {
520
+ const entry = this._visibleNodes[this._selectedIndex];
521
+ if (!entry) return;
522
+ const node = entry.node;
523
+ if (_isParent(node) && !node.expanded) {
524
+ node.expanded = true;
525
+ this._buildVisibleNodes();
526
+ this.markDirty();
527
+ }
528
+ }
529
+ /** Collapse the selected node, or move to parent if already collapsed/leaf */
530
+ collapse() {
531
+ const entry = this._visibleNodes[this._selectedIndex];
532
+ if (!entry) return;
533
+ const node = entry.node;
534
+ if (_isParent(node) && node.expanded) {
535
+ node.expanded = false;
536
+ this._buildVisibleNodes();
537
+ this._clampScroll();
538
+ this.markDirty();
539
+ } else if (entry.depth > 0) {
540
+ const parentPath = entry.path.slice(0, -1);
541
+ const parentIdx = this._visibleNodes.findIndex(
542
+ (e) => _pathsEqual(e.path, parentPath)
543
+ );
544
+ if (parentIdx >= 0) {
545
+ this._selectedIndex = parentIdx;
546
+ this._clampScroll();
547
+ this.markDirty();
548
+ }
549
+ }
550
+ }
551
+ /** Toggle expand/collapse (parent) or call onSelect (leaf) */
552
+ toggle() {
553
+ const entry = this._visibleNodes[this._selectedIndex];
554
+ if (!entry) return;
555
+ const node = entry.node;
556
+ if (_isParent(node)) {
557
+ node.expanded = !node.expanded;
558
+ this._buildVisibleNodes();
559
+ this._clampScroll();
560
+ this.markDirty();
561
+ } else {
562
+ this._onSelect?.(node, entry.path);
563
+ }
564
+ }
565
+ /**
566
+ * Handle a key event. Call this from your app's key-routing logic
567
+ * when this widget is focused.
568
+ */
569
+ handleKey(key) {
570
+ switch (key) {
571
+ case "ArrowUp":
572
+ case "k":
573
+ this.movePrev();
574
+ break;
575
+ case "ArrowDown":
576
+ case "j":
577
+ this.moveNext();
578
+ break;
579
+ case "Enter":
580
+ case " ":
581
+ this.toggle();
582
+ break;
583
+ case "ArrowLeft":
584
+ case "h":
585
+ this.collapse();
586
+ break;
587
+ case "ArrowRight":
588
+ case "l":
589
+ this.expand();
590
+ break;
591
+ case "Home":
592
+ this.moveFirst();
593
+ break;
594
+ case "End":
595
+ this.moveLast();
596
+ break;
597
+ }
598
+ }
599
+ // ── Rendering ──────────────────────────────────────
600
+ _renderSelf(screen) {
601
+ const rect = this._getContentRect();
602
+ const { x, y, width, height } = rect;
603
+ if (width <= 0 || height <= 0) return;
604
+ const attrs = (0, import_core5.styleToCellAttrs)(this._style);
605
+ const useUnicode = import_core5.caps.unicode;
606
+ const collapsedChevron = useUnicode ? "\u25B6 " : "> ";
607
+ const expandedChevron = useUnicode ? "\u25BC " : "v ";
608
+ const leafPrefix = useUnicode ? "\u2022 " : "* ";
609
+ const visibleCount = Math.min(
610
+ this._visibleNodes.length - this._scrollOffset,
611
+ height
612
+ );
613
+ for (let i = 0; i < visibleCount; i++) {
614
+ const entryIdx = this._scrollOffset + i;
615
+ const entry = this._visibleNodes[entryIdx];
616
+ const { node, depth } = entry;
617
+ const isSelected = entryIdx === this._selectedIndex;
618
+ const indentStr = " ".repeat(this._indent * depth);
619
+ let chevron;
620
+ if (_isParent(node)) {
621
+ chevron = node.expanded ? expandedChevron : collapsedChevron;
622
+ } else {
623
+ chevron = leafPrefix;
624
+ }
625
+ let line = indentStr + chevron + node.label;
626
+ line = (0, import_core5.truncate)(line, width);
627
+ const cellStyle = isSelected && this.isFocused ? {
628
+ ...attrs,
629
+ bg: { type: "named", name: "blue" },
630
+ bold: true
631
+ } : isSelected ? { ...attrs, bold: true } : attrs;
632
+ screen.writeString(x, y + i, line, cellStyle);
633
+ if (isSelected && this.isFocused) {
634
+ const lineWidth = (0, import_core5.stringWidth)(line);
635
+ const remaining = width - lineWidth;
636
+ for (let c = 0; c < remaining; c++) {
637
+ screen.setCell(x + lineWidth + c, y + i, {
638
+ char: " ",
639
+ ...cellStyle
640
+ });
641
+ }
642
+ }
643
+ }
644
+ }
645
+ // ── Private helpers ────────────────────────────────
646
+ /** Rebuild the flat visible-node list from the tree */
647
+ _buildVisibleNodes() {
648
+ this._visibleNodes = [];
649
+ _collectVisible(this._nodes, 0, [], this._visibleNodes);
650
+ }
651
+ /** Ensure scroll keeps the selected index in view */
652
+ _clampScroll() {
653
+ const rect = this._getContentRect();
654
+ const visibleHeight = rect.height > 0 ? rect.height : 24;
655
+ if (this._selectedIndex < this._scrollOffset) {
656
+ this._scrollOffset = this._selectedIndex;
657
+ }
658
+ if (this._selectedIndex >= this._scrollOffset + visibleHeight) {
659
+ this._scrollOffset = this._selectedIndex - visibleHeight + 1;
660
+ }
661
+ this._scrollOffset = Math.max(0, this._scrollOffset);
662
+ }
663
+ };
664
+ function _isParent(node) {
665
+ return Array.isArray(node.children) && node.children.length > 0;
666
+ }
667
+ function _pathsEqual(a, b) {
668
+ if (a.length !== b.length) return false;
669
+ for (let i = 0; i < a.length; i++) {
670
+ if (a[i] !== b[i]) return false;
671
+ }
672
+ return true;
673
+ }
674
+ function _collectVisible(nodes, depth, parentPath, out) {
675
+ for (let i = 0; i < nodes.length; i++) {
676
+ const node = nodes[i];
677
+ const path = [...parentPath, i];
678
+ out.push({ node, depth, path });
679
+ if (_isParent(node) && node.expanded) {
680
+ _collectVisible(node.children, depth + 1, path, out);
681
+ }
682
+ }
683
+ }
684
+
685
+ // src/display/JSONView.ts
686
+ var import_core6 = require("@termuijs/core");
687
+ function jsonToTree(value, key) {
688
+ const prefix = key !== void 0 ? `${key}: ` : "";
689
+ if (value === null) {
690
+ return {
691
+ label: `${prefix}null`,
692
+ data: { type: "null", key }
693
+ };
694
+ }
695
+ if (typeof value === "boolean") {
696
+ return {
697
+ label: `${prefix}${value}`,
698
+ data: { type: "boolean", key, value }
699
+ };
700
+ }
701
+ if (typeof value === "number") {
702
+ return {
703
+ label: `${prefix}${value}`,
704
+ data: { type: "number", key, value }
705
+ };
706
+ }
707
+ if (typeof value === "string") {
708
+ return {
709
+ label: `${prefix}"${value}"`,
710
+ data: { type: "string", key, value }
711
+ };
712
+ }
713
+ if (Array.isArray(value)) {
714
+ const children = value.map((v, i) => jsonToTree(v, String(i)));
715
+ return {
716
+ label: `${prefix}[${children.length}]`,
717
+ children,
718
+ expanded: false,
719
+ data: { type: "array", key }
720
+ };
721
+ }
722
+ if (typeof value === "object") {
723
+ const obj = value;
724
+ const children = Object.keys(obj).map((k) => jsonToTree(obj[k], k));
725
+ return {
726
+ label: `${prefix}{${children.length}}`,
727
+ children,
728
+ expanded: false,
729
+ data: { type: "object", key }
730
+ };
731
+ }
732
+ return {
733
+ label: `${prefix}${String(value)}`,
734
+ data: { type: "unknown", key }
735
+ };
736
+ }
737
+ function _valueColor(type) {
738
+ switch (type) {
739
+ case "string":
740
+ return { type: "named", name: "green" };
741
+ case "number":
742
+ return { type: "named", name: "yellow" };
743
+ case "boolean":
744
+ return { type: "named", name: "magenta" };
745
+ case "null":
746
+ return { type: "named", name: "magenta" };
747
+ default:
748
+ return { type: "named", name: "white" };
749
+ }
750
+ }
751
+ var KEY_COLOR = { type: "named", name: "cyan" };
752
+ function _splitLabel(label, nodeData) {
753
+ if (nodeData.key !== void 0) {
754
+ const sep = `${nodeData.key}: `;
755
+ if (label.startsWith(sep)) {
756
+ return { keyPart: sep, valuePart: label.slice(sep.length) };
757
+ }
758
+ }
759
+ return { keyPart: "", valuePart: label };
760
+ }
761
+ var JSONView = class extends Tree {
762
+ constructor(options, style = {}) {
763
+ const root = jsonToTree(options.data);
764
+ const nodes = root.children && root.children.length > 0 ? root.children : [root];
765
+ super({ nodes, onSelect: options.onSelect, indent: options.indent }, style);
766
+ }
767
+ // ── Override rendering ─────────────────────────────
768
+ /**
769
+ * Replicate Tree's _renderSelf but colorize key/value segments.
770
+ * We access the private state via `(this as any)` since Tree doesn't
771
+ * expose those fields publicly — the tradeoff of extending vs composing.
772
+ */
773
+ _renderSelf(screen) {
774
+ const rect = this._getContentRect();
775
+ const { x, y, width, height } = rect;
776
+ if (width <= 0 || height <= 0) return;
777
+ const attrs = (0, import_core6.styleToCellAttrs)(this._style);
778
+ const useUnicode = import_core6.caps.unicode;
779
+ const collapsedChevron = useUnicode ? "\u25B6 " : "> ";
780
+ const expandedChevron = useUnicode ? "\u25BC " : "v ";
781
+ const leafPrefix = useUnicode ? "\u2022 " : "* ";
782
+ const visibleNodes = this._visibleNodes;
783
+ const scrollOffset = this._scrollOffset;
784
+ const selectedIndex = this._selectedIndex;
785
+ const indent = this._indent;
786
+ const visibleCount = Math.min(
787
+ visibleNodes.length - scrollOffset,
788
+ height
789
+ );
790
+ for (let i = 0; i < visibleCount; i++) {
791
+ const entryIdx = scrollOffset + i;
792
+ const entry = visibleNodes[entryIdx];
793
+ const { node, depth } = entry;
794
+ const isSelected = entryIdx === selectedIndex;
795
+ const nodeData = node.data ?? { type: "unknown" };
796
+ const indentStr = " ".repeat(indent * depth);
797
+ const isParent = Array.isArray(node.children) && node.children.length > 0;
798
+ let chevron;
799
+ if (isParent) {
800
+ chevron = node.expanded ? expandedChevron : collapsedChevron;
801
+ } else {
802
+ chevron = leafPrefix;
803
+ }
804
+ const linePrefix = indentStr + chevron;
805
+ const { keyPart, valuePart } = _splitLabel(node.label, nodeData);
806
+ const baseCellStyle = isSelected && this.isFocused ? {
807
+ ...attrs,
808
+ bg: { type: "named", name: "blue" },
809
+ bold: true
810
+ } : isSelected ? { ...attrs, bold: true } : attrs;
811
+ let cursorX = x;
812
+ const prefixTrunc = (0, import_core6.truncate)(linePrefix, width);
813
+ if (prefixTrunc.length > 0) {
814
+ screen.writeString(cursorX, y + i, prefixTrunc, baseCellStyle);
815
+ cursorX += (0, import_core6.stringWidth)(prefixTrunc);
816
+ }
817
+ const remaining = width - (cursorX - x);
818
+ if (remaining <= 0) {
819
+ _fillSelection(screen, x, y + i, width, cursorX - x, isSelected, this.isFocused, baseCellStyle);
820
+ continue;
821
+ }
822
+ if (keyPart.length > 0) {
823
+ const keyTrunc = (0, import_core6.truncate)(keyPart, remaining);
824
+ const keyStyle = { ...baseCellStyle, fg: KEY_COLOR };
825
+ screen.writeString(cursorX, y + i, keyTrunc, keyStyle);
826
+ cursorX += (0, import_core6.stringWidth)(keyTrunc);
827
+ }
828
+ const remaining2 = width - (cursorX - x);
829
+ if (remaining2 <= 0) {
830
+ _fillSelection(screen, x, y + i, width, cursorX - x, isSelected, this.isFocused, baseCellStyle);
831
+ continue;
832
+ }
833
+ if (valuePart.length > 0) {
834
+ const valTrunc = (0, import_core6.truncate)(valuePart, remaining2);
835
+ const valColor = _valueColor(nodeData.type);
836
+ const valStyle = { ...baseCellStyle, fg: valColor };
837
+ screen.writeString(cursorX, y + i, valTrunc, valStyle);
838
+ cursorX += (0, import_core6.stringWidth)(valTrunc);
839
+ }
840
+ _fillSelection(screen, x, y + i, width, cursorX - x, isSelected, this.isFocused, baseCellStyle);
841
+ }
842
+ }
843
+ };
844
+ function _fillSelection(screen, rowX, rowY, width, writtenWidth, isSelected, isFocused, cellStyle) {
845
+ if (!isSelected || !isFocused) return;
846
+ const remaining = width - writtenWidth;
847
+ for (let c = 0; c < remaining; c++) {
848
+ screen.setCell(rowX + writtenWidth + c, rowY, {
849
+ char: " ",
850
+ ...cellStyle
851
+ });
852
+ }
853
+ }
854
+
855
+ // src/display/DiffView.ts
856
+ var import_core7 = require("@termuijs/core");
857
+ var DiffView = class extends Widget {
858
+ _lines;
859
+ _scrollOffset = 0;
860
+ _showLineNumbers;
861
+ _gutterWidth;
862
+ constructor(options, style = {}) {
863
+ super(style);
864
+ this._lines = options.lines;
865
+ this._showLineNumbers = options.showLineNumbers ?? true;
866
+ this._gutterWidth = options.gutterWidth ?? 5;
867
+ this.focusable = true;
868
+ }
869
+ setLines(lines) {
870
+ this._lines = lines;
871
+ this._scrollOffset = 0;
872
+ this.markDirty();
873
+ }
874
+ _renderSelf(screen) {
875
+ const rect = this._getContentRect();
876
+ const { x, y, width, height } = rect;
877
+ if (width <= 0 || height <= 0) return;
878
+ const baseAttrs = (0, import_core7.styleToCellAttrs)(this._style);
879
+ const visibleLines = this._lines.slice(
880
+ this._scrollOffset,
881
+ this._scrollOffset + height
882
+ );
883
+ for (let i = 0; i < visibleLines.length; i++) {
884
+ const diffLine = visibleLines[i];
885
+ let col = x;
886
+ const row = y + i;
887
+ const isAdd = diffLine.type === "add";
888
+ const isRemove = diffLine.type === "remove";
889
+ const isContext = diffLine.type === "context";
890
+ const fg = isAdd ? { type: "named", name: "green" } : isRemove ? { type: "named", name: "red" } : void 0;
891
+ const lineAttrs = {
892
+ ...baseAttrs,
893
+ ...fg ? { fg } : {},
894
+ dim: isContext
895
+ };
896
+ if (this._showLineNumbers) {
897
+ const gutterStr = diffLine.lineNo !== void 0 ? String(diffLine.lineNo).padStart(this._gutterWidth - 1, " ") + " " : " ".repeat(this._gutterWidth);
898
+ const gutterAttrs = { ...baseAttrs, dim: true };
899
+ screen.writeString(col, row, gutterStr.slice(0, this._gutterWidth), gutterAttrs);
900
+ col += this._gutterWidth;
901
+ }
902
+ const prefix = isAdd ? "+" : isRemove ? "-" : " ";
903
+ const remainingWidth = width - (col - x);
904
+ if (remainingWidth <= 0) continue;
905
+ screen.writeString(col, row, prefix, lineAttrs);
906
+ col += 1;
907
+ const contentWidth = width - (col - x);
908
+ if (contentWidth <= 0) continue;
909
+ const content = (0, import_core7.truncate)(diffLine.content, contentWidth);
910
+ screen.writeString(col, row, content, lineAttrs);
911
+ }
912
+ }
913
+ handleKey(key) {
914
+ const rect = this._getContentRect();
915
+ const visibleHeight = Math.max(1, rect.height);
916
+ const maxOffset = Math.max(0, this._lines.length - visibleHeight);
917
+ switch (key) {
918
+ case "ArrowUp":
919
+ case "k":
920
+ this._scrollOffset = Math.max(0, this._scrollOffset - 1);
921
+ this.markDirty();
922
+ break;
923
+ case "ArrowDown":
924
+ case "j":
925
+ this._scrollOffset = Math.min(maxOffset, this._scrollOffset + 1);
926
+ this.markDirty();
927
+ break;
928
+ case "PageUp":
929
+ this._scrollOffset = Math.max(0, this._scrollOffset - visibleHeight);
930
+ this.markDirty();
931
+ break;
932
+ case "PageDown":
933
+ this._scrollOffset = Math.min(maxOffset, this._scrollOffset + visibleHeight);
934
+ this.markDirty();
935
+ break;
936
+ case "Home":
937
+ this._scrollOffset = 0;
938
+ this.markDirty();
939
+ break;
940
+ case "End":
941
+ this._scrollOffset = maxOffset;
942
+ this.markDirty();
943
+ break;
944
+ }
945
+ }
946
+ };
947
+
948
+ // src/display/StreamingText.ts
949
+ var import_core8 = require("@termuijs/core");
950
+ var import_motion = require("@termuijs/motion");
951
+ var StreamingText = class extends Widget {
952
+ _text;
953
+ _cursor;
954
+ _speed;
955
+ _blinkInterval;
956
+ /** Number of characters currently revealed (used when speed > 0) */
957
+ _revealed;
958
+ _cursorVisible;
959
+ _blinkUnsub;
960
+ constructor(options, style = {}) {
961
+ super(style);
962
+ this._text = options.text;
963
+ this._cursor = options.cursor ?? (import_core8.caps.unicode ? "\u258B" : "_");
964
+ this._speed = options.speed ?? 0;
965
+ this._blinkInterval = options.blinkInterval ?? 530;
966
+ this._revealed = 0;
967
+ this._cursorVisible = true;
968
+ }
969
+ /** Replace text content and reset the revealed counter to 0. */
970
+ setText(text) {
971
+ this._text = text;
972
+ this._revealed = 0;
973
+ this.markDirty();
974
+ }
975
+ /**
976
+ * Advance `_revealed` by `speed` characters.
977
+ * Call this from an external tick/render loop when `speed > 0`.
978
+ */
979
+ tick() {
980
+ if (this._speed <= 0 || this.isComplete()) return;
981
+ this._revealed = Math.min(this._revealed + this._speed, this._text.length);
982
+ this.markDirty();
983
+ }
984
+ /** Returns true when all text has been revealed. */
985
+ isComplete() {
986
+ if (this._speed === 0) return true;
987
+ return this._revealed >= this._text.length;
988
+ }
989
+ /** Lifecycle: start the blink timer (only when motion is enabled). */
990
+ mount() {
991
+ super.mount();
992
+ if (!import_core8.caps.motion) {
993
+ this._cursorVisible = false;
994
+ return;
995
+ }
996
+ this._blinkUnsub = (0, import_motion.timerPoolSubscribe)(this._blinkInterval, () => {
997
+ this._cursorVisible = !this._cursorVisible;
998
+ this.markDirty();
999
+ });
1000
+ }
1001
+ /** Lifecycle: stop the blink timer. */
1002
+ unmount() {
1003
+ this._blinkUnsub?.();
1004
+ this._blinkUnsub = void 0;
1005
+ super.unmount();
1006
+ }
1007
+ _renderSelf(screen) {
1008
+ const rect = this._getContentRect();
1009
+ const { x, y, width, height } = rect;
1010
+ if (width <= 0 || height <= 0) return;
1011
+ const attrs = (0, import_core8.styleToCellAttrs)(this._style);
1012
+ const displayText = this._speed > 0 ? this._text.slice(0, this._revealed) : this._text;
1013
+ const fullText = this._cursorVisible ? displayText + this._cursor : displayText;
1014
+ const wrapped = (0, import_core8.wordWrap)(fullText, width);
1015
+ const lines = wrapped.split("\n");
1016
+ const limit = Math.min(lines.length, height);
1017
+ for (let i = 0; i < limit; i++) {
1018
+ const line = lines[i];
1019
+ if (line === void 0) continue;
1020
+ screen.writeString(x, y + i, line, attrs);
1021
+ }
1022
+ }
1023
+ };
1024
+
1025
+ // src/display/ChatMessage.ts
1026
+ var import_core9 = require("@termuijs/core");
1027
+ var ROLE_CONFIG = {
1028
+ user: { badge: "[User]", colorName: "cyan" },
1029
+ assistant: { badge: "[Assistant]", colorName: "green" },
1030
+ system: { badge: "[System]", colorName: "yellow" },
1031
+ tool: { badge: "[Tool]", colorName: "magenta" }
1032
+ };
1033
+ var ChatMessage = class extends Widget {
1034
+ _role;
1035
+ _content;
1036
+ _timestamp;
1037
+ constructor(options, style = {}) {
1038
+ super(style);
1039
+ this._role = options.role;
1040
+ this._content = options.content;
1041
+ this._timestamp = options.timestamp;
1042
+ this.focusable = false;
1043
+ }
1044
+ /** Update the message content and mark dirty. */
1045
+ setContent(content) {
1046
+ this._content = content;
1047
+ this.markDirty();
1048
+ }
1049
+ /** Update the message role and mark dirty. */
1050
+ setRole(role) {
1051
+ this._role = role;
1052
+ this.markDirty();
1053
+ }
1054
+ _renderSelf(screen) {
1055
+ const rect = this._getContentRect();
1056
+ const { x, y, width, height } = rect;
1057
+ if (width <= 0 || height <= 0) return;
1058
+ const config = ROLE_CONFIG[this._role];
1059
+ const baseAttrs = (0, import_core9.styleToCellAttrs)(this._style);
1060
+ const badgeAttrs = {
1061
+ ...baseAttrs,
1062
+ fg: { type: "named", name: config.colorName }
1063
+ };
1064
+ screen.writeString(x, y, config.badge, badgeAttrs);
1065
+ if (this._timestamp) {
1066
+ const ts = this._timestamp.toLocaleTimeString("en-GB", {
1067
+ hour: "2-digit",
1068
+ minute: "2-digit",
1069
+ second: "2-digit",
1070
+ hour12: false
1071
+ });
1072
+ const tsWidth = (0, import_core9.stringWidth)(ts);
1073
+ const tsX = x + width - tsWidth;
1074
+ if (tsX > x + (0, import_core9.stringWidth)(config.badge)) {
1075
+ const dimAttrs = { ...baseAttrs, dim: true };
1076
+ screen.writeString(tsX, y, ts, dimAttrs);
1077
+ }
1078
+ }
1079
+ if (height <= 1) return;
1080
+ const indent = " ";
1081
+ const contentWidth = Math.max(0, width - indent.length);
1082
+ const lines = contentWidth > 0 ? (0, import_core9.wordWrap)(this._content, contentWidth).split("\n") : [];
1083
+ const maxContentRows = height - 1;
1084
+ for (let i = 0; i < Math.min(lines.length, maxContentRows); i++) {
1085
+ const line = lines[i];
1086
+ if (line === void 0) continue;
1087
+ const displayLine = (0, import_core9.truncate)(indent + line, width);
1088
+ screen.writeString(x, y + 1 + i, displayLine, baseAttrs);
1089
+ }
1090
+ }
1091
+ };
1092
+
1093
+ // src/display/ToolCall.ts
1094
+ var import_core10 = require("@termuijs/core");
1095
+ var STATUS_CONFIG = {
1096
+ pending: { unicodeSymbol: "\u25CC", asciiSymbol: "?", colorName: "white", dim: true },
1097
+ running: { unicodeSymbol: "\u25CE", asciiSymbol: "~", colorName: "yellow" },
1098
+ done: { unicodeSymbol: "\u2713", asciiSymbol: "+", colorName: "green" },
1099
+ error: { unicodeSymbol: "\u2717", asciiSymbol: "!", colorName: "red" }
1100
+ };
1101
+ var ToolCall = class extends Widget {
1102
+ _name;
1103
+ _args;
1104
+ _result;
1105
+ _status;
1106
+ _collapsed;
1107
+ constructor(options, style = {}) {
1108
+ super(style);
1109
+ this._name = options.name;
1110
+ this._args = options.args;
1111
+ this._result = options.result;
1112
+ this._status = options.status;
1113
+ this._collapsed = options.collapsed ?? true;
1114
+ this.focusable = true;
1115
+ }
1116
+ // ── Public API ─────────────────────────────────────
1117
+ setStatus(status) {
1118
+ this._status = status;
1119
+ this.markDirty();
1120
+ }
1121
+ setResult(result) {
1122
+ this._result = result;
1123
+ this.markDirty();
1124
+ }
1125
+ collapse() {
1126
+ this._collapsed = true;
1127
+ this.markDirty();
1128
+ }
1129
+ expand() {
1130
+ this._collapsed = false;
1131
+ this.markDirty();
1132
+ }
1133
+ handleKey(key) {
1134
+ if (key === " " || key === "Enter") {
1135
+ this._collapsed = !this._collapsed;
1136
+ this.markDirty();
1137
+ }
1138
+ }
1139
+ // ── Rendering ──────────────────────────────────────
1140
+ _renderSelf(screen) {
1141
+ const rect = this._getContentRect();
1142
+ const { x, y, width, height } = rect;
1143
+ if (width <= 0 || height <= 0) return;
1144
+ const useUnicode = import_core10.caps.unicode;
1145
+ const baseAttrs = (0, import_core10.styleToCellAttrs)(this._style);
1146
+ const config = STATUS_CONFIG[this._status];
1147
+ const collapsedChevron = useUnicode ? "\u25B6" : ">";
1148
+ const expandedChevron = useUnicode ? "\u25BC" : "v";
1149
+ const chevron = this._collapsed ? collapsedChevron : expandedChevron;
1150
+ const symbol = useUnicode ? config.unicodeSymbol : config.asciiSymbol;
1151
+ const statusAttrs = {
1152
+ ...baseAttrs,
1153
+ fg: { type: "named", name: config.colorName },
1154
+ bold: config.bold ?? false,
1155
+ dim: config.dim ?? false
1156
+ };
1157
+ const headerText = `${chevron} ${this._name} ${symbol} [${this._status}]`;
1158
+ screen.writeString(x, y, (0, import_core10.truncate)(headerText, width), baseAttrs);
1159
+ const symbolOffset = chevron.length + 1 + this._name.length + 1;
1160
+ if (symbolOffset < width) {
1161
+ screen.writeString(x + symbolOffset, y, symbol, statusAttrs);
1162
+ }
1163
+ if (this._collapsed) return;
1164
+ let row = 1;
1165
+ const argEntries = Object.entries(this._args);
1166
+ for (const [key, value] of argEntries) {
1167
+ if (row >= height) break;
1168
+ const argLine = ` ${key}: ${JSON.stringify(value)}`;
1169
+ screen.writeString(x, y + row, (0, import_core10.truncate)(argLine, width), baseAttrs);
1170
+ row++;
1171
+ }
1172
+ if (row < height && this._result !== void 0 && (this._status === "done" || this._status === "error")) {
1173
+ const resultStr = typeof this._result === "string" ? this._result : JSON.stringify(this._result);
1174
+ const resultLine = ` result: ${resultStr}`;
1175
+ screen.writeString(x, y + row, (0, import_core10.truncate)(resultLine, width), baseAttrs);
1176
+ }
1177
+ }
1178
+ };
1179
+ var ToolApproval = class extends ToolCall {
1180
+ _onApprove;
1181
+ _onDeny;
1182
+ constructor(options, style = {}) {
1183
+ super(options, style);
1184
+ this._onApprove = options.onApprove;
1185
+ this._onDeny = options.onDeny;
1186
+ }
1187
+ _renderSelf(screen) {
1188
+ super._renderSelf(screen);
1189
+ const rect = this._getContentRect();
1190
+ const { x, y, width, height } = rect;
1191
+ if (width <= 0 || height <= 0) return;
1192
+ let rowsUsed = 1;
1193
+ if (!this._collapsed) {
1194
+ rowsUsed += Object.keys(this._args).length;
1195
+ if (this._result !== void 0 && (this._status === "done" || this._status === "error")) {
1196
+ rowsUsed += 1;
1197
+ }
1198
+ }
1199
+ const approvalRow = y + rowsUsed;
1200
+ if (approvalRow >= y + height) return;
1201
+ const baseAttrs = (0, import_core10.styleToCellAttrs)(this._style);
1202
+ const approveAttrs = {
1203
+ ...baseAttrs,
1204
+ fg: { type: "named", name: "green" },
1205
+ bold: true
1206
+ };
1207
+ const denyAttrs = {
1208
+ ...baseAttrs,
1209
+ fg: { type: "named", name: "red" },
1210
+ bold: true
1211
+ };
1212
+ const approveText = "[y] Approve";
1213
+ const denyText = "[n] Deny";
1214
+ screen.writeString(x, approvalRow, approveText, approveAttrs);
1215
+ const denyX = x + approveText.length + 2;
1216
+ if (denyX + denyText.length <= x + width) {
1217
+ screen.writeString(denyX, approvalRow, denyText, denyAttrs);
1218
+ }
1219
+ }
1220
+ handleKey(key) {
1221
+ if (key === "y" || key === "Enter") {
1222
+ this._onApprove?.();
1223
+ } else if (key === "n" || key === "Escape") {
1224
+ this._onDeny?.();
1225
+ } else {
1226
+ super.handleKey(key);
1227
+ }
1228
+ }
1229
+ };
1230
+
1231
+ // src/input/virtual-scroll.ts
1232
+ function computeRange(scrollOffset, viewportItems, itemCount, overscan = 2) {
1233
+ const start = Math.max(0, scrollOffset - overscan);
1234
+ const end = Math.min(itemCount, scrollOffset + viewportItems + overscan);
1235
+ return { start, end, offsetPx: start };
1236
+ }
1237
+ function computeVariableRange(scrollPx, viewportPx, sizes, overscan = 2) {
1238
+ const cumulative = [];
1239
+ let sum = 0;
1240
+ for (const s of sizes) {
1241
+ cumulative.push(sum);
1242
+ sum += s;
1243
+ }
1244
+ cumulative.push(sum);
1245
+ let startIdx = cumulative.findIndex((c, i) => i < sizes.length && c + sizes[i] > scrollPx);
1246
+ if (startIdx < 0) startIdx = sizes.length;
1247
+ startIdx = Math.max(0, startIdx - overscan);
1248
+ const viewportEnd = scrollPx + viewportPx;
1249
+ let endIdx = cumulative.findIndex((c) => c >= viewportEnd);
1250
+ if (endIdx < 0) endIdx = sizes.length;
1251
+ endIdx = Math.min(sizes.length, endIdx + overscan);
1252
+ return { start: startIdx, end: endIdx, offsetPx: cumulative[startIdx] };
1253
+ }
1254
+
1255
+ // src/input/List.ts
1256
+ var import_core11 = require("@termuijs/core");
431
1257
  var List = class extends Widget {
432
1258
  _items;
433
1259
  _selectedIndex = 0;
@@ -482,15 +1308,15 @@ var List = class extends Widget {
482
1308
  const rect = this._getContentRect();
483
1309
  const { x, y, width, height } = rect;
484
1310
  if (width <= 0 || height <= 0) return;
485
- const attrs = (0, import_core5.styleToCellAttrs)(this._style);
1311
+ const attrs = (0, import_core11.styleToCellAttrs)(this._style);
486
1312
  const visibleCount = Math.min(this._items.length - this._scrollOffset, height);
487
1313
  for (let i = 0; i < visibleCount; i++) {
488
1314
  const itemIdx = this._scrollOffset + i;
489
1315
  const item = this._items[itemIdx];
490
1316
  const isSelected = itemIdx === this._selectedIndex;
491
- const prefix = isSelected ? "\u25B8 " : " ";
1317
+ const prefix = isSelected ? import_core11.caps.unicode ? "\u25B8 " : "> " : " ";
492
1318
  let line = prefix + item.label;
493
- line = (0, import_core5.truncate)(line, width);
1319
+ line = (0, import_core11.truncate)(line, width);
494
1320
  const cellStyle = {
495
1321
  ...attrs,
496
1322
  bold: isSelected,
@@ -499,9 +1325,9 @@ var List = class extends Widget {
499
1325
  };
500
1326
  screen.writeString(x, y + i, line, cellStyle);
501
1327
  if (isSelected && this.isFocused) {
502
- const remaining = width - (0, import_core5.stringWidth)(line);
1328
+ const remaining = width - (0, import_core11.stringWidth)(line);
503
1329
  for (let c = 0; c < remaining; c++) {
504
- screen.setCell(x + (0, import_core5.stringWidth)(line) + c, y + i, { char: " ", ...cellStyle });
1330
+ screen.setCell(x + (0, import_core11.stringWidth)(line) + c, y + i, { char: " ", ...cellStyle });
505
1331
  }
506
1332
  }
507
1333
  }
@@ -509,7 +1335,7 @@ var List = class extends Widget {
509
1335
  const scrollRatio = this._scrollOffset / (this._items.length - height);
510
1336
  const scrollPos = Math.floor(scrollRatio * (height - 1));
511
1337
  for (let r = 0; r < height; r++) {
512
- const scrollChar = r === scrollPos ? "\u2588" : "\u2591";
1338
+ const scrollChar = r === scrollPos ? import_core11.caps.unicode ? "\u2588" : "#" : import_core11.caps.unicode ? "\u2591" : "-";
513
1339
  screen.setCell(x + width - 1, y + r, { char: scrollChar, ...attrs, dim: true });
514
1340
  }
515
1341
  }
@@ -531,7 +1357,7 @@ var List = class extends Widget {
531
1357
  };
532
1358
 
533
1359
  // src/input/TextInput.ts
534
- var import_core6 = require("@termuijs/core");
1360
+ var import_core12 = require("@termuijs/core");
535
1361
  var TextInput = class extends Widget {
536
1362
  _value = "";
537
1363
  _cursorPos = 0;
@@ -608,9 +1434,9 @@ var TextInput = class extends Widget {
608
1434
  const rect = this._getContentRect();
609
1435
  const { x, y, width, height } = rect;
610
1436
  if (width <= 0 || height <= 0) return;
611
- const attrs = (0, import_core6.styleToCellAttrs)(this._style);
1437
+ const attrs = (0, import_core12.styleToCellAttrs)(this._style);
612
1438
  if (this._value.length === 0 && !this.isFocused) {
613
- screen.writeString(x, y, (0, import_core6.truncate)(this._placeholder, width), { ...attrs, dim: true });
1439
+ screen.writeString(x, y, (0, import_core12.truncate)(this._placeholder, width), { ...attrs, dim: true });
614
1440
  return;
615
1441
  }
616
1442
  const displayValue = this._mask ? this._mask.repeat(this._value.length) : this._value;
@@ -636,7 +1462,7 @@ var TextInput = class extends Widget {
636
1462
  };
637
1463
 
638
1464
  // src/input/VirtualList.ts
639
- var import_core7 = require("@termuijs/core");
1465
+ var import_core13 = require("@termuijs/core");
640
1466
  var VirtualList = class extends Widget {
641
1467
  _totalItems;
642
1468
  _itemHeight;
@@ -740,10 +1566,14 @@ var VirtualList = class extends Widget {
740
1566
  const rect = this._getContentRect();
741
1567
  const { x, y, width, height } = rect;
742
1568
  if (width <= 0 || height <= 0 || this._totalItems === 0) return;
743
- const attrs = (0, import_core7.styleToCellAttrs)(this._style);
1569
+ const attrs = (0, import_core13.styleToCellAttrs)(this._style);
744
1570
  const visibleItemCount = Math.floor(height / this._itemHeight);
745
- const startIdx = Math.max(0, this._scrollOffset - this._overscan);
746
- const endIdx = Math.min(this._totalItems, this._scrollOffset + visibleItemCount + this._overscan);
1571
+ const { start: startIdx, end: endIdx } = computeRange(
1572
+ this._scrollOffset,
1573
+ visibleItemCount,
1574
+ this._totalItems,
1575
+ this._overscan
1576
+ );
747
1577
  const contentWidth = this._showScrollbar && this._totalItems > visibleItemCount ? width - 1 : width;
748
1578
  for (let idx = startIdx; idx < endIdx; idx++) {
749
1579
  const rowY = y + (idx - this._scrollOffset) * this._itemHeight;
@@ -755,9 +1585,9 @@ var VirtualList = class extends Widget {
755
1585
  } catch {
756
1586
  content = `[Error: item ${idx}]`;
757
1587
  }
758
- const prefix = isSelected ? "\u25B8 " : " ";
1588
+ const prefix = isSelected ? import_core13.caps.unicode ? "\u25B8 " : "> " : " ";
759
1589
  let line = prefix + content;
760
- line = (0, import_core7.truncate)(line, contentWidth);
1590
+ line = (0, import_core13.truncate)(line, contentWidth);
761
1591
  const cellStyle = {
762
1592
  ...attrs,
763
1593
  bold: isSelected,
@@ -765,9 +1595,9 @@ var VirtualList = class extends Widget {
765
1595
  };
766
1596
  screen.writeString(x, rowY, line, cellStyle);
767
1597
  if (isSelected && this.isFocused) {
768
- const remaining = contentWidth - (0, import_core7.stringWidth)(line);
1598
+ const remaining = contentWidth - (0, import_core13.stringWidth)(line);
769
1599
  for (let c = 0; c < remaining; c++) {
770
- screen.setCell(x + (0, import_core7.stringWidth)(line) + c, rowY, { char: " ", ...cellStyle });
1600
+ screen.setCell(x + (0, import_core13.stringWidth)(line) + c, rowY, { char: " ", ...cellStyle });
771
1601
  }
772
1602
  }
773
1603
  }
@@ -776,8 +1606,10 @@ var VirtualList = class extends Widget {
776
1606
  const totalPages = this._totalItems - visibleItemCount;
777
1607
  const scrollRatio = totalPages > 0 ? this._scrollOffset / totalPages : 0;
778
1608
  const thumbPos = Math.floor(scrollRatio * (height - 1));
1609
+ const thumbChar = import_core13.caps.unicode ? "\u2588" : "#";
1610
+ const trackChar = import_core13.caps.unicode ? "\u2591" : "|";
779
1611
  for (let r = 0; r < height; r++) {
780
- const scrollChar = r === thumbPos ? "\u2588" : "\u2591";
1612
+ const scrollChar = r === thumbPos ? thumbChar : trackChar;
781
1613
  screen.setCell(scrollbarX, y + r, { char: scrollChar, ...attrs, dim: r !== thumbPos });
782
1614
  }
783
1615
  }
@@ -800,8 +1632,173 @@ var VirtualList = class extends Widget {
800
1632
  }
801
1633
  };
802
1634
 
1635
+ // src/input/CommandPalette.ts
1636
+ var import_core14 = require("@termuijs/core");
1637
+ var CommandPalette = class extends Widget {
1638
+ _commands;
1639
+ _filtered;
1640
+ _query = "";
1641
+ _selectedIndex = 0;
1642
+ _options;
1643
+ constructor(options, style = {}) {
1644
+ super({ border: "single", ...style });
1645
+ this._options = {
1646
+ placeholder: "Type to search...",
1647
+ maxVisible: 8,
1648
+ ...options
1649
+ };
1650
+ this._commands = options.commands;
1651
+ this._filtered = [...this._commands];
1652
+ this.focusable = true;
1653
+ }
1654
+ /** Update the full command list and re-filter. */
1655
+ setCommands(commands) {
1656
+ this._commands = commands;
1657
+ this._filter();
1658
+ this.markDirty();
1659
+ }
1660
+ /**
1661
+ * Reset query, re-filter all commands, reset selection.
1662
+ * Call this when opening the palette.
1663
+ */
1664
+ open() {
1665
+ this._query = "";
1666
+ this._selectedIndex = 0;
1667
+ this._filtered = [...this._commands];
1668
+ this.markDirty();
1669
+ }
1670
+ /** Current query string. */
1671
+ getQuery() {
1672
+ return this._query;
1673
+ }
1674
+ // ─── Private helpers ────────────────────────────────
1675
+ /** Filter commands based on current query (case-insensitive substring). */
1676
+ _filter() {
1677
+ const q = this._query.toLowerCase();
1678
+ if (q === "") {
1679
+ this._filtered = [...this._commands];
1680
+ } else {
1681
+ this._filtered = this._commands.filter(
1682
+ (cmd) => cmd.label.toLowerCase().includes(q) || cmd.id.toLowerCase().includes(q)
1683
+ );
1684
+ }
1685
+ const max = Math.max(0, this._filtered.length - 1);
1686
+ this._selectedIndex = Math.min(this._selectedIndex, max);
1687
+ }
1688
+ _executeSelected() {
1689
+ const cmd = this._filtered[this._selectedIndex];
1690
+ if (cmd) {
1691
+ cmd.action();
1692
+ }
1693
+ }
1694
+ _moveUp() {
1695
+ if (this._selectedIndex > 0) {
1696
+ this._selectedIndex--;
1697
+ }
1698
+ }
1699
+ _moveDown() {
1700
+ const maxVisible = this._options.maxVisible ?? 8;
1701
+ const visibleCount = Math.min(this._filtered.length, maxVisible);
1702
+ if (this._selectedIndex < visibleCount - 1) {
1703
+ this._selectedIndex++;
1704
+ }
1705
+ }
1706
+ // ─── Key handling ────────────────────────────────────
1707
+ handleKey(key) {
1708
+ switch (key) {
1709
+ case "Escape":
1710
+ this._options.onClose?.();
1711
+ break;
1712
+ case "Enter":
1713
+ this._executeSelected();
1714
+ break;
1715
+ case "ArrowUp":
1716
+ case "k":
1717
+ this._moveUp();
1718
+ break;
1719
+ case "ArrowDown":
1720
+ case "j":
1721
+ this._moveDown();
1722
+ break;
1723
+ case "Backspace":
1724
+ this._query = this._query.slice(0, -1);
1725
+ this._filter();
1726
+ break;
1727
+ default:
1728
+ if (key.length === 1) {
1729
+ this._query += key;
1730
+ this._filter();
1731
+ }
1732
+ break;
1733
+ }
1734
+ this.markDirty();
1735
+ }
1736
+ // ─── Rendering ───────────────────────────────────────
1737
+ _renderSelf(screen) {
1738
+ const rect = this._getContentRect();
1739
+ const { x, y, width, height } = rect;
1740
+ if (width <= 0 || height <= 0) return;
1741
+ const attrs = (0, import_core14.styleToCellAttrs)(this._style);
1742
+ const maxVisible = this._options.maxVisible ?? 8;
1743
+ const placeholder = this._options.placeholder ?? "Type to search...";
1744
+ const prefix = "> ";
1745
+ const queryDisplay = this._query.length > 0 ? this._query : placeholder;
1746
+ const queryDim = this._query.length === 0;
1747
+ const queryLine = (0, import_core14.truncate)(prefix + queryDisplay, width);
1748
+ screen.writeString(x, y, queryLine, { ...attrs, dim: queryDim });
1749
+ const queryLineWidth = (0, import_core14.stringWidth)(queryLine);
1750
+ for (let c = queryLineWidth; c < width; c++) {
1751
+ screen.setCell(x + c, y, { char: " ", ...attrs });
1752
+ }
1753
+ const listStartRow = 1;
1754
+ const listHeight = height - listStartRow;
1755
+ const visibleCount = Math.min(this._filtered.length, maxVisible, listHeight);
1756
+ for (let i = 0; i < visibleCount; i++) {
1757
+ const cmd = this._filtered[i];
1758
+ const isSelected = i === this._selectedIndex;
1759
+ const rowY = y + listStartRow + i;
1760
+ const rowPrefix = isSelected ? "\u25B8 " : " ";
1761
+ const labelPart = rowPrefix + cmd.label;
1762
+ const desc = cmd.description ?? "";
1763
+ const descWidth = (0, import_core14.stringWidth)(desc);
1764
+ const gap = 1;
1765
+ const labelMaxWidth = desc ? Math.max(0, width - descWidth - gap) : width;
1766
+ const labelTruncated = (0, import_core14.truncate)(labelPart, labelMaxWidth);
1767
+ const labelWidth = (0, import_core14.stringWidth)(labelTruncated);
1768
+ const cellStyle = {
1769
+ ...attrs,
1770
+ bold: isSelected,
1771
+ inverse: isSelected
1772
+ };
1773
+ const dimStyle = {
1774
+ ...attrs,
1775
+ dim: true,
1776
+ inverse: isSelected
1777
+ };
1778
+ screen.writeString(x, rowY, labelTruncated, cellStyle);
1779
+ if (desc) {
1780
+ const descX = x + width - descWidth;
1781
+ for (let c = x + labelWidth; c < descX; c++) {
1782
+ screen.setCell(c, rowY, { char: " ", ...cellStyle });
1783
+ }
1784
+ screen.writeString(descX, rowY, desc, dimStyle);
1785
+ } else {
1786
+ for (let c = x + labelWidth; c < x + width; c++) {
1787
+ screen.setCell(c, rowY, { char: " ", ...cellStyle });
1788
+ }
1789
+ }
1790
+ }
1791
+ for (let i = visibleCount; i < listHeight; i++) {
1792
+ const rowY = y + listStartRow + i;
1793
+ for (let c = 0; c < width; c++) {
1794
+ screen.setCell(x + c, rowY, { char: " ", ...attrs });
1795
+ }
1796
+ }
1797
+ }
1798
+ };
1799
+
803
1800
  // src/data/Table.ts
804
- var import_core8 = require("@termuijs/core");
1801
+ var import_core15 = require("@termuijs/core");
805
1802
  var Table = class extends Widget {
806
1803
  _columns;
807
1804
  _rows;
@@ -828,8 +1825,8 @@ var Table = class extends Widget {
828
1825
  const rect = this._getContentRect();
829
1826
  const { x, y, width, height } = rect;
830
1827
  if (width <= 0 || height <= 0) return;
831
- const attrs = (0, import_core8.styleToCellAttrs)(this._style);
832
- const sepWidth = (0, import_core8.stringWidth)(this._separator);
1828
+ const attrs = (0, import_core15.styleToCellAttrs)(this._style);
1829
+ const sepWidth = (0, import_core15.stringWidth)(this._separator);
833
1830
  const colWidths = this._computeColumnWidths(
834
1831
  width - (this._columns.length - 1) * sepWidth
835
1832
  );
@@ -896,8 +1893,8 @@ var Table = class extends Widget {
896
1893
  return this._columns.map((c) => c.width ?? flexWidth);
897
1894
  }
898
1895
  _alignText(text, width, align) {
899
- const truncated = (0, import_core8.truncate)(text, width);
900
- const textWidth = (0, import_core8.stringWidth)(truncated);
1896
+ const truncated = (0, import_core15.truncate)(text, width);
1897
+ const textWidth = (0, import_core15.stringWidth)(truncated);
901
1898
  const pad = Math.max(0, width - textWidth);
902
1899
  switch (align) {
903
1900
  case "right":
@@ -915,7 +1912,7 @@ var Table = class extends Widget {
915
1912
  };
916
1913
 
917
1914
  // src/data/Gauge.ts
918
- var import_core9 = require("@termuijs/core");
1915
+ var import_core16 = require("@termuijs/core");
919
1916
  var Gauge = class extends Widget {
920
1917
  _label;
921
1918
  _value = 0;
@@ -942,17 +1939,17 @@ var Gauge = class extends Widget {
942
1939
  const rect = this._getContentRect();
943
1940
  const { x, y, width, height } = rect;
944
1941
  if (width <= 0 || height <= 0) return;
945
- const attrs = (0, import_core9.styleToCellAttrs)(this._style);
1942
+ const attrs = (0, import_core16.styleToCellAttrs)(this._style);
946
1943
  const labelStr = this._label + " ";
947
1944
  const percentStr = this._showLabel ? ` ${Math.round(this._value * 100)}%` : "";
948
- const labelWidth = (0, import_core9.stringWidth)(labelStr);
949
- const percentWidth = (0, import_core9.stringWidth)(percentStr);
1945
+ const labelWidth = (0, import_core16.stringWidth)(labelStr);
1946
+ const percentWidth = (0, import_core16.stringWidth)(percentStr);
950
1947
  const barWidth = Math.max(0, width - labelWidth - percentWidth);
951
1948
  screen.writeString(x, y, labelStr, { ...attrs, bold: true });
952
1949
  const filled = Math.round(barWidth * this._value);
953
1950
  const barX = x + labelWidth;
954
1951
  for (let i = 0; i < barWidth; i++) {
955
- const char = i < filled ? "\u2588" : "\u2591";
1952
+ const char = i < filled ? import_core16.caps.unicode ? "\u2588" : "#" : import_core16.caps.unicode ? "\u2591" : "-";
956
1953
  screen.setCell(barX + i, y, {
957
1954
  char,
958
1955
  fg: i < filled ? this._color : { type: "named", name: "brightBlack" }
@@ -968,8 +1965,9 @@ var Gauge = class extends Widget {
968
1965
  };
969
1966
 
970
1967
  // src/data/Sparkline.ts
971
- var import_core10 = require("@termuijs/core");
972
- var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
1968
+ var import_core17 = require("@termuijs/core");
1969
+ var SPARK_CHARS_UNICODE = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
1970
+ var SPARK_CHARS_ASCII = ["1", "2", "3", "4", "5", "6", "7", "8"];
973
1971
  var Sparkline = class extends Widget {
974
1972
  _label;
975
1973
  _data = [];
@@ -993,7 +1991,7 @@ var Sparkline = class extends Widget {
993
1991
  const rect = this._getContentRect();
994
1992
  const { x, y, width, height } = rect;
995
1993
  if (width <= 0 || height <= 0) return;
996
- const attrs = (0, import_core10.styleToCellAttrs)(this._style);
1994
+ const attrs = (0, import_core17.styleToCellAttrs)(this._style);
997
1995
  const labelStr = this._label + " ";
998
1996
  const labelWidth = labelStr.length;
999
1997
  screen.writeString(x, y, labelStr, { ...attrs, bold: true });
@@ -1003,11 +2001,12 @@ var Sparkline = class extends Widget {
1003
2001
  const min = Math.min(...data);
1004
2002
  const max = Math.max(...data);
1005
2003
  const range = max - min || 1;
2004
+ const sparkChars = import_core17.caps.unicode ? SPARK_CHARS_UNICODE : SPARK_CHARS_ASCII;
1006
2005
  for (let i = 0; i < data.length; i++) {
1007
2006
  const normalized = (data[i] - min) / range;
1008
2007
  const charIdx = Math.min(7, Math.floor(normalized * 8));
1009
2008
  screen.setCell(x + labelWidth + i, y, {
1010
- char: SPARK_CHARS[charIdx],
2009
+ char: sparkChars[charIdx],
1011
2010
  fg: this._color
1012
2011
  });
1013
2012
  }
@@ -1022,7 +2021,7 @@ var Sparkline = class extends Widget {
1022
2021
  };
1023
2022
 
1024
2023
  // src/data/StatusIndicator.ts
1025
- var import_core11 = require("@termuijs/core");
2024
+ var import_core18 = require("@termuijs/core");
1026
2025
  var StatusIndicator = class extends Widget {
1027
2026
  _label;
1028
2027
  _isUp;
@@ -1048,7 +2047,7 @@ var StatusIndicator = class extends Widget {
1048
2047
  const rect = this._getContentRect();
1049
2048
  const { x, y, width, height } = rect;
1050
2049
  if (width <= 0 || height <= 0) return;
1051
- const attrs = (0, import_core11.styleToCellAttrs)(this._style);
2050
+ const attrs = (0, import_core18.styleToCellAttrs)(this._style);
1052
2051
  const dot = this._isUp ? "\u25CF" : "\u25CB";
1053
2052
  const statusText = this._isUp ? "Online" : "Offline";
1054
2053
  const color = this._isUp ? this._upColor : this._downColor;
@@ -1061,7 +2060,7 @@ var StatusIndicator = class extends Widget {
1061
2060
  };
1062
2061
 
1063
2062
  // src/data/BarChart.ts
1064
- var import_core12 = require("@termuijs/core");
2063
+ var import_core19 = require("@termuijs/core");
1065
2064
  var BarChart = class extends Widget {
1066
2065
  _data = [];
1067
2066
  _direction;
@@ -1141,7 +2140,7 @@ var BarChart = class extends Widget {
1141
2140
  for (let row = barAreaHeight - 1; row >= 0; row--) {
1142
2141
  if (remaining <= 0) break;
1143
2142
  const level = Math.min(remaining, 8);
1144
- const symbol = import_core12.VERTICAL_BAR_SYMBOLS[level] ?? " ";
2143
+ const symbol = import_core19.VERTICAL_BAR_SYMBOLS[level] ?? " ";
1145
2144
  const cellY = oy + row;
1146
2145
  for (let col = 0; col < this._barWidth; col++) {
1147
2146
  const cellX = cx + col;
@@ -1152,7 +2151,7 @@ var BarChart = class extends Widget {
1152
2151
  remaining -= 8;
1153
2152
  }
1154
2153
  const valStr = Math.round(bar.value).toString();
1155
- const valX = cx + Math.floor((this._barWidth - (0, import_core12.stringWidth)(valStr)) / 2);
2154
+ const valX = cx + Math.floor((this._barWidth - (0, import_core19.stringWidth)(valStr)) / 2);
1156
2155
  screen.writeString(
1157
2156
  Math.max(cx, valX),
1158
2157
  oy + barAreaHeight,
@@ -1161,7 +2160,7 @@ var BarChart = class extends Widget {
1161
2160
  );
1162
2161
  if (hasLabels && bar.label) {
1163
2162
  const label = bar.label.slice(0, this._barWidth);
1164
- const labelX = cx + Math.floor((this._barWidth - (0, import_core12.stringWidth)(label)) / 2);
2163
+ const labelX = cx + Math.floor((this._barWidth - (0, import_core19.stringWidth)(label)) / 2);
1165
2164
  screen.writeString(
1166
2165
  Math.max(cx, labelX),
1167
2166
  oy + barAreaHeight + valueRows,
@@ -1175,7 +2174,7 @@ var BarChart = class extends Widget {
1175
2174
  if (hasGroupLabels && group.label) {
1176
2175
  const groupWidth = cx - groupStartX;
1177
2176
  const label = group.label.slice(0, groupWidth);
1178
- const labelX = groupStartX + Math.floor((groupWidth - (0, import_core12.stringWidth)(label)) / 2);
2177
+ const labelX = groupStartX + Math.floor((groupWidth - (0, import_core19.stringWidth)(label)) / 2);
1179
2178
  screen.writeString(
1180
2179
  Math.max(groupStartX, labelX),
1181
2180
  oy + height - 1,
@@ -1193,7 +2192,7 @@ var BarChart = class extends Widget {
1193
2192
  for (const group of this._data) {
1194
2193
  for (const bar of group.bars) {
1195
2194
  if (bar.label) {
1196
- const w = (0, import_core12.stringWidth)(bar.label);
2195
+ const w = (0, import_core19.stringWidth)(bar.label);
1197
2196
  if (w > maxLabelWidth) maxLabelWidth = w;
1198
2197
  }
1199
2198
  const vw = Math.round(bar.value).toString().length;
@@ -1226,7 +2225,7 @@ var BarChart = class extends Widget {
1226
2225
  for (let col = 0; col < barAreaWidth; col++) {
1227
2226
  if (remaining <= 0) break;
1228
2227
  const level = Math.min(remaining, 8);
1229
- const symbol = import_core12.HORIZONTAL_BAR_SYMBOLS[level] ?? " ";
2228
+ const symbol = import_core19.HORIZONTAL_BAR_SYMBOLS[level] ?? " ";
1230
2229
  screen.setCell(barStartX + col, cellY, { char: symbol, fg: color });
1231
2230
  remaining -= 8;
1232
2231
  }
@@ -1248,8 +2247,270 @@ var BarChart = class extends Widget {
1248
2247
  }
1249
2248
  };
1250
2249
 
2250
+ // src/layout/Grid.ts
2251
+ var Grid = class extends Widget {
2252
+ _columns;
2253
+ _colGap;
2254
+ _rowGap;
2255
+ _rows = [];
2256
+ _itemCount = 0;
2257
+ constructor(style, options) {
2258
+ super({ flexDirection: "column", ...style });
2259
+ this._columns = Math.max(1, options.columns);
2260
+ const gap = options.gap ?? 1;
2261
+ this._colGap = options.colGap ?? gap;
2262
+ this._rowGap = options.rowGap ?? gap;
2263
+ }
2264
+ _renderSelf(_screen) {
2265
+ }
2266
+ /**
2267
+ * Add a widget to the grid. Items fill left-to-right,
2268
+ * wrapping to a new row automatically every `columns` items.
2269
+ * Overrides Widget.addChild so the reconciler generic loop works unchanged.
2270
+ */
2271
+ addChild(widget) {
2272
+ const rowIndex = Math.floor(this._itemCount / this._columns);
2273
+ if (rowIndex >= this._rows.length) {
2274
+ const rowStyle = { flexDirection: "row" };
2275
+ if (this._colGap > 0) rowStyle.gap = this._colGap;
2276
+ if (this._rows.length > 0 && this._rowGap > 0) {
2277
+ rowStyle.margin = this._rowGap;
2278
+ }
2279
+ const row = new Box(rowStyle);
2280
+ this._rows.push(row);
2281
+ super.addChild(row);
2282
+ }
2283
+ const currentRow = this._rows[rowIndex];
2284
+ widget.setStyle({ flexGrow: 1 });
2285
+ currentRow.addChild(widget);
2286
+ this._itemCount++;
2287
+ }
2288
+ /** Add an item explicitly (alias for addChild) */
2289
+ addItem(widget) {
2290
+ this.addChild(widget);
2291
+ }
2292
+ /** Remove all items and reset the grid */
2293
+ clearItems() {
2294
+ for (const row of this._rows) {
2295
+ row.unmount();
2296
+ row.parent = null;
2297
+ }
2298
+ this._children = [];
2299
+ this._rows = [];
2300
+ this._itemCount = 0;
2301
+ }
2302
+ };
2303
+
2304
+ // src/layout/ScrollView.ts
2305
+ var import_core20 = require("@termuijs/core");
2306
+ var ScrollView = class extends Widget {
2307
+ _scrollOffset = 0;
2308
+ _contentHeight;
2309
+ _showScrollbar;
2310
+ constructor(style = {}, opts = {}) {
2311
+ super({ overflow: "hidden", ...style });
2312
+ this._contentHeight = opts.contentHeight ?? 0;
2313
+ this._showScrollbar = opts.showScrollbar ?? true;
2314
+ this.focusable = true;
2315
+ }
2316
+ /** Set the total content height (in rows) */
2317
+ setContentHeight(h) {
2318
+ this._contentHeight = h;
2319
+ this._clampOffset();
2320
+ this.markDirty();
2321
+ }
2322
+ /** Get current scroll offset */
2323
+ get scrollOffset() {
2324
+ return this._scrollOffset;
2325
+ }
2326
+ /** Scroll by delta rows */
2327
+ scrollBy(delta) {
2328
+ this._scrollOffset += delta;
2329
+ this._clampOffset();
2330
+ this.markDirty();
2331
+ }
2332
+ /** Scroll to absolute offset */
2333
+ scrollTo(offset) {
2334
+ this._scrollOffset = offset;
2335
+ this._clampOffset();
2336
+ this.markDirty();
2337
+ }
2338
+ _clampOffset() {
2339
+ const viewHeight = this._rect.height;
2340
+ const maxOffset = Math.max(0, this._contentHeight - viewHeight);
2341
+ this._scrollOffset = Math.max(0, Math.min(this._scrollOffset, maxOffset));
2342
+ }
2343
+ /** Handle keyboard navigation */
2344
+ onKey(event) {
2345
+ switch (event.key) {
2346
+ case "ArrowUp":
2347
+ this.scrollBy(-1);
2348
+ break;
2349
+ case "ArrowDown":
2350
+ this.scrollBy(1);
2351
+ break;
2352
+ case "PageUp":
2353
+ this.scrollBy(-Math.max(1, this._rect.height - 1));
2354
+ break;
2355
+ case "PageDown":
2356
+ this.scrollBy(Math.max(1, this._rect.height - 1));
2357
+ break;
2358
+ }
2359
+ }
2360
+ render(screen) {
2361
+ if (this._style.visible === false) return;
2362
+ const shouldClip = true;
2363
+ if (shouldClip) screen.pushClip(this._rect);
2364
+ this._renderSelf(screen);
2365
+ this._renderBorder(screen);
2366
+ const rect = this._getContentRect();
2367
+ for (const child of this._children) {
2368
+ const origRect = { ...child.rect };
2369
+ child._rect = {
2370
+ x: origRect.x,
2371
+ y: origRect.y - this._scrollOffset,
2372
+ width: origRect.width,
2373
+ height: origRect.height
2374
+ };
2375
+ try {
2376
+ child.render(screen);
2377
+ } finally {
2378
+ child._rect = origRect;
2379
+ }
2380
+ }
2381
+ if (shouldClip) screen.popClip();
2382
+ if (this._showScrollbar && this._contentHeight > this._rect.height) {
2383
+ this._renderScrollbar(screen, rect);
2384
+ }
2385
+ }
2386
+ _renderScrollbar(screen, contentRect) {
2387
+ const { y, width, height } = contentRect;
2388
+ const scrollX = contentRect.x + width;
2389
+ if (scrollX >= this._rect.x + this._rect.width) return;
2390
+ const trackHeight = height;
2391
+ const thumbSize = Math.max(1, Math.round(height / this._contentHeight * trackHeight));
2392
+ const maxOffset = Math.max(1, this._contentHeight - height);
2393
+ const thumbPos = Math.round(this._scrollOffset / maxOffset * (trackHeight - thumbSize));
2394
+ const attrs = (0, import_core20.styleToCellAttrs)(this._style);
2395
+ const thumbChar = "\u2588";
2396
+ const trackChar = "\u2591";
2397
+ for (let i = 0; i < trackHeight; i++) {
2398
+ const isThumb = i >= thumbPos && i < thumbPos + thumbSize;
2399
+ screen.setCell(scrollX, y + i, {
2400
+ char: isThumb ? thumbChar : trackChar,
2401
+ ...attrs,
2402
+ dim: !isThumb
2403
+ });
2404
+ }
2405
+ }
2406
+ _renderSelf(_screen) {
2407
+ }
2408
+ };
2409
+
2410
+ // src/layout/Center.ts
2411
+ var Center = class extends Widget {
2412
+ _horizontal;
2413
+ _vertical;
2414
+ constructor(style = {}, opts = {}) {
2415
+ super(style);
2416
+ this._horizontal = opts.horizontal ?? true;
2417
+ this._vertical = opts.vertical ?? true;
2418
+ }
2419
+ _renderSelf(_screen) {
2420
+ }
2421
+ render(screen) {
2422
+ if (this._style.visible === false) return;
2423
+ const shouldClip = this._style.overflow !== "visible";
2424
+ if (shouldClip) screen.pushClip(this._rect);
2425
+ this._renderSelf(screen);
2426
+ this._renderBorder(screen);
2427
+ const content = this._getContentRect();
2428
+ for (const child of this._children) {
2429
+ const childRect = child.rect;
2430
+ const origRect = { ...childRect };
2431
+ let offsetX = content.x;
2432
+ let offsetY = content.y;
2433
+ if (this._horizontal) {
2434
+ offsetX = content.x + Math.max(0, Math.floor((content.width - childRect.width) / 2));
2435
+ }
2436
+ if (this._vertical) {
2437
+ offsetY = content.y + Math.max(0, Math.floor((content.height - childRect.height) / 2));
2438
+ }
2439
+ child._rect = {
2440
+ x: offsetX,
2441
+ y: offsetY,
2442
+ width: childRect.width,
2443
+ height: childRect.height
2444
+ };
2445
+ child.render(screen);
2446
+ child._rect = origRect;
2447
+ }
2448
+ if (shouldClip) screen.popClip();
2449
+ }
2450
+ };
2451
+
2452
+ // src/layout/Card.ts
2453
+ var import_core21 = require("@termuijs/core");
2454
+ var Card = class extends Widget {
2455
+ _title;
2456
+ _borderColor;
2457
+ constructor(style = {}, opts = {}) {
2458
+ super({
2459
+ border: "single",
2460
+ padding: 1,
2461
+ ...style
2462
+ });
2463
+ this._title = opts.title ?? "";
2464
+ this._borderColor = opts.borderColor;
2465
+ }
2466
+ setTitle(title) {
2467
+ this._title = title;
2468
+ this.markDirty();
2469
+ }
2470
+ _renderSelf(screen) {
2471
+ if (!this._title) return;
2472
+ const { x, y, width } = this._rect;
2473
+ if (width < 4) return;
2474
+ const attrs = (0, import_core21.styleToCellAttrs)(this._style);
2475
+ const fg = this._borderColor ?? attrs.fg;
2476
+ const titleText = ` ${this._title} `;
2477
+ const titleWidth = (0, import_core21.stringWidth)(titleText);
2478
+ const innerWidth = width - 2;
2479
+ if (titleWidth > innerWidth) return;
2480
+ const titleX = x + 1 + Math.floor((innerWidth - titleWidth) / 2);
2481
+ screen.writeString(titleX, y, titleText, { fg, bold: true });
2482
+ }
2483
+ };
2484
+
2485
+ // src/layout/Columns.ts
2486
+ var Columns = class extends Widget {
2487
+ _inner;
2488
+ constructor(style = {}, opts = {}) {
2489
+ super(style);
2490
+ this._inner = new Box({
2491
+ flexDirection: "row",
2492
+ gap: opts.gap ?? 1,
2493
+ width: "100%",
2494
+ height: "100%"
2495
+ });
2496
+ super.addChild(this._inner);
2497
+ }
2498
+ addChild(widget) {
2499
+ widget.setStyle({ flexGrow: 1 });
2500
+ this._inner.addChild(widget);
2501
+ }
2502
+ removeChild(widget) {
2503
+ this._inner.removeChild(widget);
2504
+ }
2505
+ clearChildren() {
2506
+ this._inner.clearChildren();
2507
+ }
2508
+ _renderSelf(_screen) {
2509
+ }
2510
+ };
2511
+
1251
2512
  // src/feedback/ProgressBar.ts
1252
- var import_core13 = require("@termuijs/core");
2513
+ var import_core22 = require("@termuijs/core");
1253
2514
  var ProgressBar = class extends Widget {
1254
2515
  _value;
1255
2516
  _fillChar;
@@ -1261,8 +2522,8 @@ var ProgressBar = class extends Widget {
1261
2522
  constructor(style = {}, options = {}) {
1262
2523
  super({ height: 1, ...style });
1263
2524
  this._value = Math.max(0, Math.min(1, options.value ?? 0));
1264
- this._fillChar = options.fillChar ?? "\u2588";
1265
- this._emptyChar = options.emptyChar ?? "\u2591";
2525
+ this._fillChar = options.fillChar ?? (import_core22.caps.unicode ? "\u2588" : "#");
2526
+ this._emptyChar = options.emptyChar ?? (import_core22.caps.unicode ? "\u2591" : "-");
1266
2527
  this._fillColor = options.fillColor ?? { type: "named", name: "green" };
1267
2528
  this._showLabel = options.showLabel ?? true;
1268
2529
  this._labelFormat = options.labelFormat ?? "percent";
@@ -1280,7 +2541,7 @@ var ProgressBar = class extends Widget {
1280
2541
  const rect = this._getContentRect();
1281
2542
  const { x, y, width } = rect;
1282
2543
  if (width <= 0) return;
1283
- const attrs = (0, import_core13.styleToCellAttrs)(this._style);
2544
+ const attrs = (0, import_core22.styleToCellAttrs)(this._style);
1284
2545
  let label = "";
1285
2546
  if (this._showLabel) {
1286
2547
  if (this._labelFormat === "percent") {
@@ -1304,8 +2565,84 @@ var ProgressBar = class extends Widget {
1304
2565
  }
1305
2566
  };
1306
2567
 
2568
+ // src/feedback/MultiProgress.ts
2569
+ var import_core23 = require("@termuijs/core");
2570
+ var MultiProgress = class extends Widget {
2571
+ _items;
2572
+ _labelWidth;
2573
+ _showValues;
2574
+ constructor(options, style = {}) {
2575
+ const height = options.items.length;
2576
+ super({ height, ...style });
2577
+ this._items = options.items.map((item) => ({
2578
+ ...item,
2579
+ value: Math.max(0, Math.min(1, item.value))
2580
+ }));
2581
+ this._labelWidth = options.labelWidth ?? 12;
2582
+ this._showValues = options.showValues ?? true;
2583
+ }
2584
+ /**
2585
+ * Replace all items and mark dirty
2586
+ */
2587
+ setItems(items) {
2588
+ this._items = items.map((item) => ({
2589
+ ...item,
2590
+ value: Math.max(0, Math.min(1, item.value))
2591
+ }));
2592
+ this.markDirty();
2593
+ }
2594
+ /**
2595
+ * Update a single item's value
2596
+ */
2597
+ updateItem(index, value) {
2598
+ if (index >= 0 && index < this._items.length) {
2599
+ this._items[index].value = Math.max(0, Math.min(1, value));
2600
+ this.markDirty();
2601
+ }
2602
+ }
2603
+ _renderSelf(screen) {
2604
+ const rect = this._getContentRect();
2605
+ const { x, y, width } = rect;
2606
+ if (width <= 0) return;
2607
+ const attrs = (0, import_core23.styleToCellAttrs)(this._style);
2608
+ const fillChar = import_core23.caps.unicode ? "\u2588" : import_core23.BLOCK.full;
2609
+ const emptyChar = import_core23.caps.unicode ? "\u2591" : import_core23.BLOCK.empty;
2610
+ for (let i = 0; i < this._items.length; i++) {
2611
+ const item = this._items[i];
2612
+ const rowY = y + i;
2613
+ let colX = x;
2614
+ const label = item.label.length > this._labelWidth ? item.label.substring(0, this._labelWidth) : item.label.padEnd(this._labelWidth);
2615
+ screen.writeString(colX, rowY, label, attrs);
2616
+ colX += this._labelWidth;
2617
+ if (colX < x + width) {
2618
+ screen.setCell(colX, rowY, { char: " ", ...attrs });
2619
+ colX++;
2620
+ }
2621
+ let percentLabel = "";
2622
+ if (this._showValues) {
2623
+ percentLabel = ` ${Math.round(item.value * 100)}%`;
2624
+ }
2625
+ const barWidth = Math.max(0, x + width - colX - percentLabel.length);
2626
+ const filled = Math.round(barWidth * item.value);
2627
+ const empty = barWidth - filled;
2628
+ const fillColor = item.color ?? { type: "named", name: "green" };
2629
+ for (let j = 0; j < filled; j++) {
2630
+ screen.setCell(colX + j, rowY, { char: fillChar, ...attrs, fg: fillColor });
2631
+ }
2632
+ for (let j = 0; j < empty; j++) {
2633
+ screen.setCell(colX + filled + j, rowY, { char: emptyChar, ...attrs, dim: true });
2634
+ }
2635
+ if (percentLabel) {
2636
+ const labelX = colX + barWidth;
2637
+ screen.writeString(labelX, rowY, percentLabel, { ...attrs, bold: true });
2638
+ }
2639
+ }
2640
+ }
2641
+ };
2642
+
1307
2643
  // src/feedback/Spinner.ts
1308
- var import_core14 = require("@termuijs/core");
2644
+ var import_core24 = require("@termuijs/core");
2645
+ var import_motion2 = require("@termuijs/motion");
1309
2646
  var SPINNER_FRAMES = {
1310
2647
  dots: {
1311
2648
  frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
@@ -1348,6 +2685,7 @@ var Spinner = class extends Widget {
1348
2685
  _color;
1349
2686
  _lastTick = 0;
1350
2687
  _elapsed = 0;
2688
+ _timerUnsub;
1351
2689
  constructor(style = {}, options = {}) {
1352
2690
  super({ height: 1, ...style });
1353
2691
  const spinnerDef = typeof options.spinner === "string" ? SPINNER_FRAMES[options.spinner] ?? SPINNER_FRAMES.dots : options.spinner ?? SPINNER_FRAMES.dots;
@@ -1355,6 +2693,10 @@ var Spinner = class extends Widget {
1355
2693
  this._interval = spinnerDef.interval;
1356
2694
  this._label = options.label ?? "";
1357
2695
  this._color = options.color ?? { type: "named", name: "cyan" };
2696
+ if (!import_core24.caps.unicode && this._frames.some((f) => f.codePointAt(0) > 127)) {
2697
+ this._frames = Array.from(import_core24.BRAILLE_SPIN);
2698
+ this._interval = 130;
2699
+ }
1358
2700
  }
1359
2701
  /** Update the spinner label */
1360
2702
  setLabel(label) {
@@ -1371,11 +2713,26 @@ var Spinner = class extends Widget {
1371
2713
  this._elapsed = 0;
1372
2714
  }
1373
2715
  }
2716
+ /** Lifecycle: start the frame-advance timer (only when motion is enabled). */
2717
+ mount() {
2718
+ super.mount();
2719
+ if (!import_core24.caps.motion) return;
2720
+ this._timerUnsub = (0, import_motion2.timerPoolSubscribe)(this._interval, () => {
2721
+ this._frameIndex = (this._frameIndex + 1) % this._frames.length;
2722
+ this.markDirty();
2723
+ });
2724
+ }
2725
+ /** Lifecycle: stop the frame-advance timer. */
2726
+ unmount() {
2727
+ this._timerUnsub?.();
2728
+ this._timerUnsub = void 0;
2729
+ super.unmount();
2730
+ }
1374
2731
  _renderSelf(screen) {
1375
2732
  const rect = this._getContentRect();
1376
2733
  const { x, y, width } = rect;
1377
2734
  if (width <= 0) return;
1378
- const attrs = (0, import_core14.styleToCellAttrs)(this._style);
2735
+ const attrs = (0, import_core24.styleToCellAttrs)(this._style);
1379
2736
  const frame = this._frames[this._frameIndex];
1380
2737
  screen.writeString(x, y, frame, { ...attrs, fg: this._color });
1381
2738
  if (this._label) {
@@ -1385,7 +2742,7 @@ var Spinner = class extends Widget {
1385
2742
  };
1386
2743
 
1387
2744
  // src/feedback/Scrollbar.ts
1388
- var import_core15 = require("@termuijs/core");
2745
+ var import_core25 = require("@termuijs/core");
1389
2746
  var Scrollbar = class extends Widget {
1390
2747
  _contentLength;
1391
2748
  _viewportLength;
@@ -1422,7 +2779,7 @@ var Scrollbar = class extends Widget {
1422
2779
  if (width <= 0 || height <= 0 || this._contentLength <= 0) return;
1423
2780
  if (this._contentLength <= this._viewportLength) return;
1424
2781
  const vertical = this._orientation === "verticalRight" || this._orientation === "verticalLeft";
1425
- const symbols = vertical ? import_core15.ScrollbarSets.VERTICAL : import_core15.ScrollbarSets.HORIZONTAL;
2782
+ const symbols = vertical ? import_core25.ScrollbarSets.VERTICAL : import_core25.ScrollbarSets.HORIZONTAL;
1426
2783
  const trackX = this._orientation === "verticalLeft" ? x : this._orientation === "verticalRight" ? x + width - 1 : x;
1427
2784
  const trackY = this._orientation === "horizontalTop" ? y : this._orientation === "horizontalBottom" ? y + height - 1 : y;
1428
2785
  const totalLength = vertical ? height : width;
@@ -1466,23 +2823,755 @@ var Scrollbar = class extends Widget {
1466
2823
  }
1467
2824
  }
1468
2825
  };
2826
+
2827
+ // src/feedback/Skeleton.ts
2828
+ var import_core26 = require("@termuijs/core");
2829
+ var import_motion3 = require("@termuijs/motion");
2830
+ var Skeleton = class extends Widget {
2831
+ _frame = 0;
2832
+ _shimmerPos = 0;
2833
+ _unsub;
2834
+ _chars;
2835
+ _variant;
2836
+ _intervalMs;
2837
+ constructor(style = {}, options = {}) {
2838
+ super(style);
2839
+ this._variant = options.variant ?? "pulse";
2840
+ this._intervalMs = options.intervalMs ?? 600;
2841
+ const defaultChars = import_core26.caps.unicode ? ["\u2591", "\u2592"] : ["-", "#"];
2842
+ this._chars = options.chars ?? defaultChars;
2843
+ if (import_core26.caps.motion) {
2844
+ this._unsub = (0, import_motion3.timerPoolSubscribe)(this._intervalMs, () => {
2845
+ this._frame = 1 - this._frame;
2846
+ if (this._variant === "shimmer") {
2847
+ this._shimmerPos++;
2848
+ }
2849
+ this.markDirty();
2850
+ });
2851
+ }
2852
+ }
2853
+ unmount() {
2854
+ this._unsub?.();
2855
+ this._unsub = void 0;
2856
+ super.unmount();
2857
+ }
2858
+ _renderSelf(screen) {
2859
+ const rect = this._getContentRect();
2860
+ const { x, y, width, height } = rect;
2861
+ if (width <= 0 || height <= 0) return;
2862
+ if (this._variant === "pulse") {
2863
+ const char = this._chars[this._frame];
2864
+ const dim = this._frame === 0;
2865
+ for (let row = y; row < y + height; row++) {
2866
+ for (let col = x; col < x + width; col++) {
2867
+ screen.setCell(col, row, { char, dim, bold: false });
2868
+ }
2869
+ }
2870
+ } else {
2871
+ const bandWidth = Math.max(1, Math.floor(width * 0.2));
2872
+ const totalPositions = width + bandWidth;
2873
+ const bandStart = this._shimmerPos % totalPositions;
2874
+ for (let row = y; row < y + height; row++) {
2875
+ for (let colOffset = 0; colOffset < width; colOffset++) {
2876
+ const col = x + colOffset;
2877
+ const inBand = colOffset >= bandStart && colOffset < bandStart + bandWidth;
2878
+ const char = inBand ? this._chars[1] : this._chars[0];
2879
+ screen.setCell(col, row, { char, dim: !inBand, bold: false });
2880
+ }
2881
+ }
2882
+ }
2883
+ }
2884
+ };
2885
+
2886
+ // src/feedback/StatusMessage.ts
2887
+ var import_core27 = require("@termuijs/core");
2888
+ var ICONS_UNICODE = {
2889
+ success: "\u2713",
2890
+ error: "\u2717",
2891
+ warning: "\u26A0",
2892
+ info: "\u2139"
2893
+ };
2894
+ var ICONS_ASCII = {
2895
+ success: "+",
2896
+ error: "x",
2897
+ warning: "!",
2898
+ info: "i"
2899
+ };
2900
+ var COLORS = {
2901
+ success: { type: "named", name: "green" },
2902
+ error: { type: "named", name: "red" },
2903
+ warning: { type: "named", name: "yellow" },
2904
+ info: { type: "named", name: "cyan" }
2905
+ };
2906
+ var StatusMessage = class extends Widget {
2907
+ _message;
2908
+ _variant;
2909
+ _icon;
2910
+ constructor(message, style = {}, opts = {}) {
2911
+ super({ height: 1, ...style });
2912
+ this._message = message;
2913
+ this._variant = opts.variant ?? "info";
2914
+ this._icon = opts.icon;
2915
+ }
2916
+ setMessage(message) {
2917
+ this._message = message;
2918
+ this.markDirty();
2919
+ }
2920
+ setVariant(variant) {
2921
+ this._variant = variant;
2922
+ this.markDirty();
2923
+ }
2924
+ _renderSelf(screen) {
2925
+ const rect = this._getContentRect();
2926
+ const { x, y, width } = rect;
2927
+ if (width <= 0) return;
2928
+ const attrs = (0, import_core27.styleToCellAttrs)(this._style);
2929
+ const color = COLORS[this._variant];
2930
+ const iconMap = import_core27.caps.unicode ? ICONS_UNICODE : ICONS_ASCII;
2931
+ const icon = this._icon ?? iconMap[this._variant];
2932
+ screen.writeString(x, y, icon, { ...attrs, fg: color, bold: true });
2933
+ const msgX = x + icon.length + 1;
2934
+ const remaining = width - icon.length - 1;
2935
+ if (remaining > 0) {
2936
+ screen.writeString(msgX, y, this._message.slice(0, remaining), { ...attrs, fg: color });
2937
+ }
2938
+ }
2939
+ };
2940
+
2941
+ // src/feedback/Banner.ts
2942
+ var import_core28 = require("@termuijs/core");
2943
+ var VARIANT_COLORS = {
2944
+ success: { type: "named", name: "green" },
2945
+ error: { type: "named", name: "red" },
2946
+ warning: { type: "named", name: "yellow" },
2947
+ info: { type: "named", name: "cyan" }
2948
+ };
2949
+ var Banner = class extends Widget {
2950
+ _variant;
2951
+ _title;
2952
+ _body;
2953
+ constructor(style = {}, opts = {}) {
2954
+ super({
2955
+ width: "100%",
2956
+ padding: 1,
2957
+ ...style
2958
+ });
2959
+ this._variant = opts.variant ?? "info";
2960
+ this._title = opts.title ?? "";
2961
+ this._body = opts.body ?? "";
2962
+ }
2963
+ setTitle(title) {
2964
+ this._title = title;
2965
+ this.markDirty();
2966
+ }
2967
+ setBody(body) {
2968
+ this._body = body;
2969
+ this.markDirty();
2970
+ }
2971
+ setVariant(variant) {
2972
+ this._variant = variant;
2973
+ this.markDirty();
2974
+ }
2975
+ _renderSelf(screen) {
2976
+ const { x, y, width, height } = this._rect;
2977
+ if (width < 2 || height < 2) return;
2978
+ const attrs = (0, import_core28.styleToCellAttrs)(this._style);
2979
+ const color = VARIANT_COLORS[this._variant];
2980
+ const fg = color;
2981
+ const borderChars = (0, import_core28.getBorderChars)("single");
2982
+ if (borderChars) {
2983
+ screen.setCell(x, y, { char: borderChars.topLeft, fg });
2984
+ for (let c = 1; c < width - 1; c++) {
2985
+ screen.setCell(x + c, y, { char: borderChars.top, fg });
2986
+ }
2987
+ screen.setCell(x + width - 1, y, { char: borderChars.topRight, fg });
2988
+ screen.setCell(x, y + height - 1, { char: borderChars.bottomLeft, fg });
2989
+ for (let c = 1; c < width - 1; c++) {
2990
+ screen.setCell(x + c, y + height - 1, { char: borderChars.bottom, fg });
2991
+ }
2992
+ screen.setCell(x + width - 1, y + height - 1, { char: borderChars.bottomRight, fg });
2993
+ for (let r = 1; r < height - 1; r++) {
2994
+ screen.setCell(x, y + r, { char: borderChars.left, fg });
2995
+ screen.setCell(x + width - 1, y + r, { char: borderChars.right, fg });
2996
+ }
2997
+ }
2998
+ const cx = x + 2;
2999
+ const cy = y + 2;
3000
+ const contentWidth = Math.max(0, width - 4);
3001
+ const contentHeight = Math.max(0, height - 4);
3002
+ let row = 0;
3003
+ if (this._title && row < contentHeight) {
3004
+ screen.writeString(cx, cy + row, this._title.slice(0, contentWidth), {
3005
+ ...attrs,
3006
+ fg: color,
3007
+ bold: true
3008
+ });
3009
+ row++;
3010
+ }
3011
+ if (this._body) {
3012
+ const lines = this._body.split("\n");
3013
+ for (const line of lines) {
3014
+ if (row >= contentHeight) break;
3015
+ screen.writeString(cx, cy + row, line.slice(0, contentWidth), {
3016
+ ...attrs,
3017
+ fg: color
3018
+ });
3019
+ row++;
3020
+ }
3021
+ }
3022
+ }
3023
+ };
3024
+
3025
+ // src/data/KeyValue.ts
3026
+ var import_core29 = require("@termuijs/core");
3027
+ var KeyValue = class extends Widget {
3028
+ _pairs;
3029
+ _separator;
3030
+ _keyColor;
3031
+ _valueColor;
3032
+ constructor(pairs, style = {}, opts = {}) {
3033
+ super(style);
3034
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([key, value]) => ({ key, value }));
3035
+ this._separator = opts.separator ?? ": ";
3036
+ this._keyColor = opts.keyColor;
3037
+ this._valueColor = opts.valueColor;
3038
+ }
3039
+ setPairs(pairs) {
3040
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([key, value]) => ({ key, value }));
3041
+ this.markDirty();
3042
+ }
3043
+ _renderSelf(screen) {
3044
+ const rect = this._getContentRect();
3045
+ const { x, y, width, height } = rect;
3046
+ if (width <= 0 || height <= 0 || this._pairs.length === 0) return;
3047
+ const attrs = (0, import_core29.styleToCellAttrs)(this._style);
3048
+ let maxKeyWidth = 0;
3049
+ for (const pair of this._pairs) {
3050
+ const w = (0, import_core29.stringWidth)(pair.key);
3051
+ if (w > maxKeyWidth) maxKeyWidth = w;
3052
+ }
3053
+ const sepWidth = (0, import_core29.stringWidth)(this._separator);
3054
+ for (let i = 0; i < this._pairs.length && i < height; i++) {
3055
+ const pair = this._pairs[i];
3056
+ if (!pair) continue;
3057
+ const keyWidth = (0, import_core29.stringWidth)(pair.key);
3058
+ const keyX = x + (maxKeyWidth - keyWidth);
3059
+ const sepX = x + maxKeyWidth;
3060
+ const valX = sepX + sepWidth;
3061
+ const valWidth = Math.max(0, width - maxKeyWidth - sepWidth);
3062
+ screen.writeString(keyX, y + i, pair.key, {
3063
+ ...attrs,
3064
+ fg: this._keyColor ?? attrs.fg,
3065
+ bold: true
3066
+ });
3067
+ screen.writeString(sepX, y + i, this._separator, { ...attrs, dim: true });
3068
+ if (valWidth > 0) {
3069
+ screen.writeString(valX, y + i, pair.value.slice(0, valWidth), {
3070
+ ...attrs,
3071
+ fg: this._valueColor ?? attrs.fg
3072
+ });
3073
+ }
3074
+ }
3075
+ }
3076
+ };
3077
+
3078
+ // src/data/Sidebar.ts
3079
+ var import_core30 = require("@termuijs/core");
3080
+ var Sidebar = class extends Widget {
3081
+ _items;
3082
+ _collapsed;
3083
+ _collapsedWidth;
3084
+ _activeColor;
3085
+ _badgeColor;
3086
+ constructor(items, style = {}, opts = {}) {
3087
+ super(style);
3088
+ this._items = items;
3089
+ this._collapsed = opts.collapsed ?? false;
3090
+ this._collapsedWidth = opts.collapsedWidth ?? 3;
3091
+ this._activeColor = opts.activeColor ?? { type: "named", name: "cyan" };
3092
+ this._badgeColor = opts.badgeColor ?? { type: "named", name: "yellow" };
3093
+ }
3094
+ setItems(items) {
3095
+ this._items = items;
3096
+ this.markDirty();
3097
+ }
3098
+ setCollapsed(collapsed) {
3099
+ this._collapsed = collapsed;
3100
+ this.markDirty();
3101
+ }
3102
+ toggle() {
3103
+ this._collapsed = !this._collapsed;
3104
+ this.markDirty();
3105
+ }
3106
+ get isCollapsed() {
3107
+ return this._collapsed;
3108
+ }
3109
+ _renderSelf(screen) {
3110
+ const rect = this._getContentRect();
3111
+ const { x, y, width, height } = rect;
3112
+ if (width <= 0 || height <= 0) return;
3113
+ const attrs = (0, import_core30.styleToCellAttrs)(this._style);
3114
+ for (let i = 0; i < this._items.length && i < height; i++) {
3115
+ const item = this._items[i];
3116
+ if (!item) continue;
3117
+ const isActive = item.active ?? false;
3118
+ const fg = isActive ? this._activeColor : attrs.fg;
3119
+ if (this._collapsed) {
3120
+ const char = item.label.charAt(0) || " ";
3121
+ screen.writeString(x, y + i, char, { ...attrs, fg, bold: isActive });
3122
+ } else {
3123
+ const prefix = isActive ? "\u25B6 " : " ";
3124
+ const prefixWidth = (0, import_core30.stringWidth)(prefix);
3125
+ screen.writeString(x, y + i, prefix, { ...attrs, fg });
3126
+ const badgeText = item.badge ? ` [${item.badge}]` : "";
3127
+ const badgeWidth = (0, import_core30.stringWidth)(badgeText);
3128
+ const labelWidth = Math.max(0, width - prefixWidth - badgeWidth);
3129
+ const label = item.label.slice(0, labelWidth);
3130
+ screen.writeString(x + prefixWidth, y + i, label, {
3131
+ ...attrs,
3132
+ fg,
3133
+ bold: isActive
3134
+ });
3135
+ if (item.badge && badgeWidth > 0) {
3136
+ const badgeX = x + width - badgeWidth;
3137
+ screen.writeString(badgeX, y + i, badgeText, {
3138
+ ...attrs,
3139
+ fg: this._badgeColor
3140
+ });
3141
+ }
3142
+ }
3143
+ }
3144
+ }
3145
+ };
3146
+
3147
+ // src/data/LineChart.ts
3148
+ var import_core31 = require("@termuijs/core");
3149
+ var POINT_CHAR_UNICODE = "\u25CF";
3150
+ var POINT_CHAR_ASCII = "*";
3151
+ var LineChart = class extends Widget {
3152
+ _data;
3153
+ _color;
3154
+ _showYAxis;
3155
+ _showXAxis;
3156
+ _yLabel;
3157
+ _max;
3158
+ _min;
3159
+ constructor(data, style = {}, opts = {}) {
3160
+ super(style);
3161
+ this._data = data;
3162
+ this._color = opts.color ?? { type: "named", name: "cyan" };
3163
+ this._showYAxis = opts.showYAxis ?? false;
3164
+ this._showXAxis = opts.showXAxis ?? false;
3165
+ this._yLabel = opts.yLabel ?? "";
3166
+ this._max = opts.max;
3167
+ this._min = opts.min;
3168
+ }
3169
+ setData(data) {
3170
+ this._data = data;
3171
+ this.markDirty();
3172
+ }
3173
+ pushValue(value) {
3174
+ this._data.push(value);
3175
+ this.markDirty();
3176
+ }
3177
+ _renderSelf(screen) {
3178
+ const rect = this._getContentRect();
3179
+ let { x, y, width, height } = rect;
3180
+ if (width <= 0 || height <= 0 || this._data.length === 0) return;
3181
+ const attrs = (0, import_core31.styleToCellAttrs)(this._style);
3182
+ const plotHeight = this._showXAxis ? Math.max(1, height - 1) : height;
3183
+ const yAxisWidth = this._showYAxis ? 5 : 0;
3184
+ const plotWidth = Math.max(1, width - yAxisWidth);
3185
+ const plotX = x + yAxisWidth;
3186
+ const min = this._min ?? Math.min(...this._data);
3187
+ const max = this._max ?? Math.max(...this._data);
3188
+ const range = max - min || 1;
3189
+ const samples = [];
3190
+ for (let col = 0; col < plotWidth; col++) {
3191
+ const idx = Math.floor(col / plotWidth * this._data.length);
3192
+ const val = this._data[Math.min(idx, this._data.length - 1)];
3193
+ samples.push(val ?? min);
3194
+ }
3195
+ const toRow = (v) => {
3196
+ const norm = (v - min) / range;
3197
+ return Math.max(0, Math.min(plotHeight - 1, Math.round((1 - norm) * (plotHeight - 1))));
3198
+ };
3199
+ if (this._showYAxis && yAxisWidth > 0) {
3200
+ for (let row = 0; row < plotHeight; row++) {
3201
+ const v = plotHeight > 1 ? max - row / (plotHeight - 1) * range : max;
3202
+ if (row === 0 || row === plotHeight - 1) {
3203
+ const label = v.toFixed(0).padStart(yAxisWidth - 1, " ");
3204
+ screen.writeString(x, y + row, label + "\u2524", { ...attrs, dim: true });
3205
+ } else {
3206
+ screen.writeString(x + yAxisWidth - 1, y + row, "\u2502", { ...attrs, dim: true });
3207
+ }
3208
+ }
3209
+ }
3210
+ const pointChar = import_core31.caps.unicode ? POINT_CHAR_UNICODE : POINT_CHAR_ASCII;
3211
+ let prevRow = null;
3212
+ for (let col = 0; col < samples.length; col++) {
3213
+ const val = samples[col];
3214
+ if (val === void 0) continue;
3215
+ const row = toRow(val);
3216
+ if (prevRow !== null && Math.abs(row - prevRow) > 1) {
3217
+ const top = Math.min(prevRow, row) + 1;
3218
+ const bottom = Math.max(prevRow, row);
3219
+ for (let r = top; r < bottom; r++) {
3220
+ screen.setCell(plotX + col, y + r, { char: "\u2502", fg: this._color, dim: true });
3221
+ }
3222
+ }
3223
+ screen.setCell(plotX + col, y + row, { char: pointChar, fg: this._color });
3224
+ prevRow = row;
3225
+ }
3226
+ if (this._showXAxis) {
3227
+ const axisY = y + height - 1;
3228
+ for (let col = 0; col < plotWidth; col++) {
3229
+ screen.setCell(plotX + col, axisY, { char: "\u2500", ...attrs, dim: true });
3230
+ }
3231
+ if (yAxisWidth > 0) {
3232
+ screen.setCell(plotX - 1, axisY, { char: "\u2514", ...attrs, dim: true });
3233
+ }
3234
+ }
3235
+ }
3236
+ };
3237
+
3238
+ // src/data/HeatMap.ts
3239
+ var import_core32 = require("@termuijs/core");
3240
+ var SHADE_CHARS_UNICODE = ["\u2591", "\u2592", "\u2593", "\u2588"];
3241
+ var SHADE_CHARS_ASCII = [".", ":", "+", "#"];
3242
+ var HeatMap = class extends Widget {
3243
+ _matrix;
3244
+ _highColor;
3245
+ _lowColor;
3246
+ _rowLabels;
3247
+ _colLabels;
3248
+ constructor(matrix, style = {}, opts = {}) {
3249
+ super(style);
3250
+ this._matrix = matrix;
3251
+ this._highColor = opts.highColor ?? { type: "named", name: "red" };
3252
+ this._lowColor = opts.lowColor ?? { type: "named", name: "brightBlack" };
3253
+ this._rowLabels = opts.rowLabels ?? [];
3254
+ this._colLabels = opts.colLabels ?? [];
3255
+ }
3256
+ setMatrix(matrix) {
3257
+ this._matrix = matrix;
3258
+ this.markDirty();
3259
+ }
3260
+ _renderSelf(screen) {
3261
+ const rect = this._getContentRect();
3262
+ const { x, y, width, height } = rect;
3263
+ if (width <= 0 || height <= 0 || this._matrix.length === 0) return;
3264
+ const attrs = (0, import_core32.styleToCellAttrs)(this._style);
3265
+ const shadeChars = import_core32.caps.unicode ? SHADE_CHARS_UNICODE : SHADE_CHARS_ASCII;
3266
+ const labelWidth = this._rowLabels.length > 0 ? Math.max(...this._rowLabels.map((l) => l.length)) + 1 : 0;
3267
+ let globalMin = Infinity;
3268
+ let globalMax = -Infinity;
3269
+ for (const row of this._matrix) {
3270
+ for (const val of row) {
3271
+ if (val < globalMin) globalMin = val;
3272
+ if (val > globalMax) globalMax = val;
3273
+ }
3274
+ }
3275
+ const range = globalMax - globalMin || 1;
3276
+ let startRow = 0;
3277
+ if (this._colLabels.length > 0) {
3278
+ for (let col = 0; col < this._colLabels.length; col++) {
3279
+ const cx = x + labelWidth + col;
3280
+ if (cx >= x + width) break;
3281
+ const label = (this._colLabels[col] ?? "").charAt(0);
3282
+ screen.setCell(cx, y, { char: label, ...attrs, dim: true });
3283
+ }
3284
+ startRow = 1;
3285
+ }
3286
+ for (let r = 0; r < this._matrix.length; r++) {
3287
+ const rowY = y + startRow + r;
3288
+ if (rowY >= y + height) break;
3289
+ if (this._rowLabels[r]) {
3290
+ const label = this._rowLabels[r].slice(0, labelWidth - 1).padEnd(labelWidth - 1, " ");
3291
+ screen.writeString(x, rowY, label + " ", { ...attrs, dim: true });
3292
+ }
3293
+ const row = this._matrix[r];
3294
+ if (!row) continue;
3295
+ for (let col = 0; col < row.length; col++) {
3296
+ const cx = x + labelWidth + col;
3297
+ if (cx >= x + width) break;
3298
+ const val = row[col] ?? 0;
3299
+ const norm = (val - globalMin) / range;
3300
+ const level = Math.min(3, Math.floor(norm * 4));
3301
+ const char = shadeChars[level] ?? shadeChars[0];
3302
+ const fg = norm >= 0.75 ? this._highColor : this._lowColor;
3303
+ screen.setCell(cx, rowY, { char, fg });
3304
+ }
3305
+ }
3306
+ }
3307
+ };
3308
+
3309
+ // src/data/Definition.ts
3310
+ var import_core33 = require("@termuijs/core");
3311
+ var Definition = class extends Widget {
3312
+ _pairs;
3313
+ _indent;
3314
+ _spacing;
3315
+ _termColor;
3316
+ _definitionColor;
3317
+ constructor(pairs, style = {}, opts = {}) {
3318
+ super(style);
3319
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([term, definition]) => ({ term, definition }));
3320
+ this._indent = opts.indent ?? 2;
3321
+ this._spacing = opts.spacing ?? true;
3322
+ this._termColor = opts.termColor;
3323
+ this._definitionColor = opts.definitionColor;
3324
+ }
3325
+ setPairs(pairs) {
3326
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([term, definition]) => ({ term, definition }));
3327
+ this.markDirty();
3328
+ }
3329
+ _renderSelf(screen) {
3330
+ const rect = this._getContentRect();
3331
+ const { x, y, width, height } = rect;
3332
+ if (width <= 0 || height <= 0 || this._pairs.length === 0) return;
3333
+ const attrs = (0, import_core33.styleToCellAttrs)(this._style);
3334
+ const indent = Math.min(this._indent, width - 1);
3335
+ let row = 0;
3336
+ for (const pair of this._pairs) {
3337
+ if (row >= height) break;
3338
+ screen.writeString(x, y + row, pair.term.slice(0, width), {
3339
+ ...attrs,
3340
+ fg: this._termColor ?? attrs.fg,
3341
+ bold: true
3342
+ });
3343
+ row++;
3344
+ if (row >= height) break;
3345
+ const defWidth = Math.max(1, width - indent);
3346
+ const words = pair.definition.split(" ");
3347
+ let line = "";
3348
+ for (const word of words) {
3349
+ if (line.length === 0) {
3350
+ line = word;
3351
+ } else if (line.length + 1 + word.length <= defWidth) {
3352
+ line += " " + word;
3353
+ } else {
3354
+ if (row >= height) break;
3355
+ screen.writeString(x + indent, y + row, line, {
3356
+ ...attrs,
3357
+ fg: this._definitionColor ?? attrs.fg
3358
+ });
3359
+ row++;
3360
+ line = word;
3361
+ }
3362
+ }
3363
+ if (line && row < height) {
3364
+ screen.writeString(x + indent, y + row, line, {
3365
+ ...attrs,
3366
+ fg: this._definitionColor ?? attrs.fg
3367
+ });
3368
+ row++;
3369
+ }
3370
+ if (this._spacing && row < height) {
3371
+ row++;
3372
+ }
3373
+ }
3374
+ }
3375
+ };
3376
+
3377
+ // src/display/BigText.ts
3378
+ var import_core34 = require("@termuijs/core");
3379
+ var CHAR_MAP = {
3380
+ "A": [" # ", "# #", "###", "# #", "# #"],
3381
+ "B": ["## ", "# #", "## ", "# #", "## "],
3382
+ "C": [" ##", "# ", "# ", "# ", " ##"],
3383
+ "D": ["## ", "# #", "# #", "# #", "## "],
3384
+ "E": ["###", "# ", "## ", "# ", "###"],
3385
+ "F": ["###", "# ", "## ", "# ", "# "],
3386
+ "G": [" ##", "# ", "# #", "# #", " ##"],
3387
+ "H": ["# #", "# #", "###", "# #", "# #"],
3388
+ "I": ["###", " # ", " # ", " # ", "###"],
3389
+ "J": ["###", " #", " #", "# #", " # "],
3390
+ "K": ["# #", "## ", "# ", "## ", "# #"],
3391
+ "L": ["# ", "# ", "# ", "# ", "###"],
3392
+ "M": ["# #", "###", "# #", "# #", "# #"],
3393
+ "N": ["# #", "## ", "# #", "# #", "# #"],
3394
+ "O": [" # ", "# #", "# #", "# #", " # "],
3395
+ "P": ["## ", "# #", "## ", "# ", "# "],
3396
+ "Q": [" # ", "# #", "# #", "##:", " ##"],
3397
+ "R": ["## ", "# #", "## ", "## ", "# #"],
3398
+ "S": [" ##", "# ", " # ", " #", "## "],
3399
+ "T": ["###", " # ", " # ", " # ", " # "],
3400
+ "U": ["# #", "# #", "# #", "# #", "###"],
3401
+ "V": ["# #", "# #", "# #", "# #", " # "],
3402
+ "W": ["# #", "# #", "# #", "###", "# #"],
3403
+ "X": ["# #", "# #", " # ", "# #", "# #"],
3404
+ "Y": ["# #", "# #", " # ", " # ", " # "],
3405
+ "Z": ["###", " #", " # ", "# ", "###"],
3406
+ "0": [" # ", "# #", "# #", "# #", " # "],
3407
+ "1": [" # ", "## ", " # ", " # ", "###"],
3408
+ "2": [" # ", "# #", " # ", "# ", "###"],
3409
+ "3": ["## ", " #", " ##", " #", "## "],
3410
+ "4": ["# #", "# #", "###", " #", " #"],
3411
+ "5": ["###", "# ", "## ", " #", "## "],
3412
+ "6": [" # ", "# ", "## ", "# #", " # "],
3413
+ "7": ["###", " #", " # ", "# ", "# "],
3414
+ "8": [" # ", "# #", " # ", "# #", " # "],
3415
+ "9": [" # ", "# #", " ##", " #", " # "],
3416
+ " ": [" ", " ", " ", " ", " "],
3417
+ "!": [" # ", " # ", " # ", " ", " # "],
3418
+ ".": [" ", " ", " ", " ", " # "],
3419
+ "-": [" ", " ", "###", " ", " "],
3420
+ ":": [" ", " # ", " ", " # ", " "]
3421
+ };
3422
+ var CHAR_HEIGHT = 5;
3423
+ var CHAR_WIDTH = 3;
3424
+ var BigText = class extends Widget {
3425
+ _text;
3426
+ _color;
3427
+ constructor(text, style = {}, opts = {}) {
3428
+ super(style);
3429
+ this._text = text.toUpperCase();
3430
+ this._color = opts.color ?? { type: "named", name: "white" };
3431
+ }
3432
+ setText(text) {
3433
+ this._text = text.toUpperCase();
3434
+ this.markDirty();
3435
+ }
3436
+ _renderSelf(screen) {
3437
+ const rect = this._getContentRect();
3438
+ const { x, y, width, height } = rect;
3439
+ if (width <= 0 || height <= 0) return;
3440
+ const attrs = (0, import_core34.styleToCellAttrs)(this._style);
3441
+ const fg = this._color;
3442
+ let curX = x;
3443
+ for (const ch of this._text) {
3444
+ const glyph = CHAR_MAP[ch] ?? ["# #", "# #", "# #", "# #", "# #"];
3445
+ const glyphWidth = glyph[0]?.length ?? CHAR_WIDTH;
3446
+ if (curX + glyphWidth > x + width) break;
3447
+ for (let row = 0; row < CHAR_HEIGHT && row < height; row++) {
3448
+ const rowStr = glyph[row] ?? "";
3449
+ for (let col = 0; col < rowStr.length; col++) {
3450
+ if (rowStr[col] !== " ") {
3451
+ screen.setCell(curX + col, y + row, { char: "\u2588", ...attrs, fg });
3452
+ }
3453
+ }
3454
+ }
3455
+ curX += glyphWidth + 1;
3456
+ }
3457
+ }
3458
+ };
3459
+
3460
+ // src/display/Gradient.ts
3461
+ var import_core35 = require("@termuijs/core");
3462
+ function hexToRgb(hex) {
3463
+ const clean = hex.replace("#", "");
3464
+ if (clean.length !== 6) return null;
3465
+ const r = parseInt(clean.slice(0, 2), 16);
3466
+ const g = parseInt(clean.slice(2, 4), 16);
3467
+ const b = parseInt(clean.slice(4, 6), 16);
3468
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
3469
+ return [r, g, b];
3470
+ }
3471
+ function lerpRgb(a, b, t) {
3472
+ return [
3473
+ Math.round(a[0] + (b[0] - a[0]) * t),
3474
+ Math.round(a[1] + (b[1] - a[1]) * t),
3475
+ Math.round(a[2] + (b[2] - a[2]) * t)
3476
+ ];
3477
+ }
3478
+ var Gradient = class extends Widget {
3479
+ _text;
3480
+ _startColor;
3481
+ _endColor;
3482
+ _align;
3483
+ constructor(text, style = {}, opts = {}) {
3484
+ super({ height: 1, ...style });
3485
+ this._text = text;
3486
+ this._startColor = opts.startColor ?? "#ff0000";
3487
+ this._endColor = opts.endColor ?? "#0000ff";
3488
+ this._align = opts.align ?? "left";
3489
+ }
3490
+ setText(text) {
3491
+ this._text = text;
3492
+ this.markDirty();
3493
+ }
3494
+ setColors(start, end) {
3495
+ this._startColor = start;
3496
+ this._endColor = end;
3497
+ this.markDirty();
3498
+ }
3499
+ _renderSelf(screen) {
3500
+ const rect = this._getContentRect();
3501
+ const { x, y, width } = rect;
3502
+ if (width <= 0 || !this._text) return;
3503
+ const attrs = (0, import_core35.styleToCellAttrs)(this._style);
3504
+ if (!import_core35.caps.color) {
3505
+ screen.writeString(x, y, this._text.slice(0, width), attrs);
3506
+ return;
3507
+ }
3508
+ const startRgb = hexToRgb(this._startColor);
3509
+ const endRgb = hexToRgb(this._endColor);
3510
+ const chars = Array.from(this._text).slice(0, width);
3511
+ const len = chars.length;
3512
+ let offsetX = 0;
3513
+ if (this._align === "center") offsetX = Math.floor((width - len) / 2);
3514
+ else if (this._align === "right") offsetX = width - len;
3515
+ offsetX = Math.max(0, offsetX);
3516
+ for (let i = 0; i < chars.length; i++) {
3517
+ const t = len > 1 ? i / (len - 1) : 0;
3518
+ let fg;
3519
+ if (startRgb && endRgb) {
3520
+ const [r, g, b] = lerpRgb(startRgb, endRgb, t);
3521
+ fg = { type: "rgb", r, g, b };
3522
+ } else if (startRgb) {
3523
+ fg = (0, import_core35.parseColor)(this._startColor) ?? attrs.fg;
3524
+ } else {
3525
+ fg = attrs.fg;
3526
+ }
3527
+ screen.setCell(x + offsetX + i, y, { char: chars[i] ?? " ", ...attrs, fg });
3528
+ }
3529
+ }
3530
+ };
1469
3531
  // Annotate the CommonJS export names for ESM import in node:
1470
3532
  0 && (module.exports = {
3533
+ Banner,
1471
3534
  BarChart,
3535
+ BigText,
1472
3536
  Box,
3537
+ Card,
3538
+ Center,
3539
+ ChatMessage,
3540
+ Columns,
3541
+ CommandPalette,
3542
+ Definition,
3543
+ DiffView,
1473
3544
  Gauge,
3545
+ Gradient,
3546
+ Grid,
3547
+ HeatMap,
3548
+ JSONView,
3549
+ KeyValue,
3550
+ LineChart,
1474
3551
  List,
1475
3552
  LogView,
3553
+ MultiProgress,
1476
3554
  ProgressBar,
1477
3555
  SPINNER_FRAMES,
3556
+ ScrollView,
1478
3557
  Scrollbar,
3558
+ Sidebar,
3559
+ Skeleton,
1479
3560
  Sparkline,
1480
3561
  Spinner,
1481
3562
  StatusIndicator,
3563
+ StatusMessage,
3564
+ StreamingText,
1482
3565
  Table,
1483
3566
  Text,
1484
3567
  TextInput,
3568
+ ToolApproval,
3569
+ ToolCall,
3570
+ Tree,
1485
3571
  VirtualList,
1486
- Widget
3572
+ Widget,
3573
+ computeRange,
3574
+ computeVariableRange,
3575
+ jsonToTree
1487
3576
  });
1488
3577
  //# sourceMappingURL=index.cjs.map