@termuijs/widgets 0.1.3 → 0.1.5

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.js CHANGED
@@ -229,6 +229,7 @@ var Widget = class {
229
229
  child.unmount();
230
230
  }
231
231
  this.events.emit("unmount", void 0);
232
+ this.events.removeAll();
232
233
  }
233
234
  };
234
235
 
@@ -394,8 +395,828 @@ var LogView = class extends Widget {
394
395
  }
395
396
  };
396
397
 
398
+ // src/display/Tree.ts
399
+ import {
400
+ styleToCellAttrs as styleToCellAttrs5,
401
+ truncate as truncate2,
402
+ stringWidth as stringWidth2,
403
+ caps
404
+ } from "@termuijs/core";
405
+ var Tree = class extends Widget {
406
+ _nodes;
407
+ _onSelect;
408
+ _indent;
409
+ _selectedIndex = 0;
410
+ _scrollOffset = 0;
411
+ _visibleNodes = [];
412
+ constructor(options, style = {}) {
413
+ super(style);
414
+ this._nodes = options.nodes;
415
+ this._onSelect = options.onSelect;
416
+ this._indent = options.indent ?? 2;
417
+ this.focusable = true;
418
+ this._buildVisibleNodes();
419
+ }
420
+ // ── Public API ─────────────────────────────────────
421
+ get selectedIndex() {
422
+ return this._selectedIndex;
423
+ }
424
+ get selectedNode() {
425
+ return this._visibleNodes[this._selectedIndex]?.node;
426
+ }
427
+ setNodes(nodes) {
428
+ this._nodes = nodes;
429
+ this._selectedIndex = 0;
430
+ this._scrollOffset = 0;
431
+ this._buildVisibleNodes();
432
+ this.markDirty();
433
+ }
434
+ /** Move cursor up one visible row */
435
+ movePrev() {
436
+ if (this._selectedIndex > 0) {
437
+ this._selectedIndex--;
438
+ this._clampScroll();
439
+ this.markDirty();
440
+ }
441
+ }
442
+ /** Move cursor down one visible row */
443
+ moveNext() {
444
+ if (this._selectedIndex < this._visibleNodes.length - 1) {
445
+ this._selectedIndex++;
446
+ this._clampScroll();
447
+ this.markDirty();
448
+ }
449
+ }
450
+ /** Go to first visible node */
451
+ moveFirst() {
452
+ this._selectedIndex = 0;
453
+ this._clampScroll();
454
+ this.markDirty();
455
+ }
456
+ /** Go to last visible node */
457
+ moveLast() {
458
+ if (this._visibleNodes.length > 0) {
459
+ this._selectedIndex = this._visibleNodes.length - 1;
460
+ this._clampScroll();
461
+ this.markDirty();
462
+ }
463
+ }
464
+ /** Expand the selected node (if it's a collapsed parent) */
465
+ expand() {
466
+ const entry = this._visibleNodes[this._selectedIndex];
467
+ if (!entry) return;
468
+ const node = entry.node;
469
+ if (_isParent(node) && !node.expanded) {
470
+ node.expanded = true;
471
+ this._buildVisibleNodes();
472
+ this.markDirty();
473
+ }
474
+ }
475
+ /** Collapse the selected node, or move to parent if already collapsed/leaf */
476
+ collapse() {
477
+ const entry = this._visibleNodes[this._selectedIndex];
478
+ if (!entry) return;
479
+ const node = entry.node;
480
+ if (_isParent(node) && node.expanded) {
481
+ node.expanded = false;
482
+ this._buildVisibleNodes();
483
+ this._clampScroll();
484
+ this.markDirty();
485
+ } else if (entry.depth > 0) {
486
+ const parentPath = entry.path.slice(0, -1);
487
+ const parentIdx = this._visibleNodes.findIndex(
488
+ (e) => _pathsEqual(e.path, parentPath)
489
+ );
490
+ if (parentIdx >= 0) {
491
+ this._selectedIndex = parentIdx;
492
+ this._clampScroll();
493
+ this.markDirty();
494
+ }
495
+ }
496
+ }
497
+ /** Toggle expand/collapse (parent) or call onSelect (leaf) */
498
+ toggle() {
499
+ const entry = this._visibleNodes[this._selectedIndex];
500
+ if (!entry) return;
501
+ const node = entry.node;
502
+ if (_isParent(node)) {
503
+ node.expanded = !node.expanded;
504
+ this._buildVisibleNodes();
505
+ this._clampScroll();
506
+ this.markDirty();
507
+ } else {
508
+ this._onSelect?.(node, entry.path);
509
+ }
510
+ }
511
+ /**
512
+ * Handle a key event. Call this from your app's key-routing logic
513
+ * when this widget is focused.
514
+ */
515
+ handleKey(key) {
516
+ switch (key) {
517
+ case "ArrowUp":
518
+ case "k":
519
+ this.movePrev();
520
+ break;
521
+ case "ArrowDown":
522
+ case "j":
523
+ this.moveNext();
524
+ break;
525
+ case "Enter":
526
+ case " ":
527
+ this.toggle();
528
+ break;
529
+ case "ArrowLeft":
530
+ case "h":
531
+ this.collapse();
532
+ break;
533
+ case "ArrowRight":
534
+ case "l":
535
+ this.expand();
536
+ break;
537
+ case "Home":
538
+ this.moveFirst();
539
+ break;
540
+ case "End":
541
+ this.moveLast();
542
+ break;
543
+ }
544
+ }
545
+ // ── Rendering ──────────────────────────────────────
546
+ _renderSelf(screen) {
547
+ const rect = this._getContentRect();
548
+ const { x, y, width, height } = rect;
549
+ if (width <= 0 || height <= 0) return;
550
+ const attrs = styleToCellAttrs5(this._style);
551
+ const useUnicode = caps.unicode;
552
+ const collapsedChevron = useUnicode ? "\u25B6 " : "> ";
553
+ const expandedChevron = useUnicode ? "\u25BC " : "v ";
554
+ const leafPrefix = useUnicode ? "\u2022 " : "* ";
555
+ const visibleCount = Math.min(
556
+ this._visibleNodes.length - this._scrollOffset,
557
+ height
558
+ );
559
+ for (let i = 0; i < visibleCount; i++) {
560
+ const entryIdx = this._scrollOffset + i;
561
+ const entry = this._visibleNodes[entryIdx];
562
+ const { node, depth } = entry;
563
+ const isSelected = entryIdx === this._selectedIndex;
564
+ const indentStr = " ".repeat(this._indent * depth);
565
+ let chevron;
566
+ if (_isParent(node)) {
567
+ chevron = node.expanded ? expandedChevron : collapsedChevron;
568
+ } else {
569
+ chevron = leafPrefix;
570
+ }
571
+ let line = indentStr + chevron + node.label;
572
+ line = truncate2(line, width);
573
+ const cellStyle = isSelected && this.isFocused ? {
574
+ ...attrs,
575
+ bg: { type: "named", name: "blue" },
576
+ bold: true
577
+ } : isSelected ? { ...attrs, bold: true } : attrs;
578
+ screen.writeString(x, y + i, line, cellStyle);
579
+ if (isSelected && this.isFocused) {
580
+ const lineWidth = stringWidth2(line);
581
+ const remaining = width - lineWidth;
582
+ for (let c = 0; c < remaining; c++) {
583
+ screen.setCell(x + lineWidth + c, y + i, {
584
+ char: " ",
585
+ ...cellStyle
586
+ });
587
+ }
588
+ }
589
+ }
590
+ }
591
+ // ── Private helpers ────────────────────────────────
592
+ /** Rebuild the flat visible-node list from the tree */
593
+ _buildVisibleNodes() {
594
+ this._visibleNodes = [];
595
+ _collectVisible(this._nodes, 0, [], this._visibleNodes);
596
+ }
597
+ /** Ensure scroll keeps the selected index in view */
598
+ _clampScroll() {
599
+ const rect = this._getContentRect();
600
+ const visibleHeight = rect.height > 0 ? rect.height : 24;
601
+ if (this._selectedIndex < this._scrollOffset) {
602
+ this._scrollOffset = this._selectedIndex;
603
+ }
604
+ if (this._selectedIndex >= this._scrollOffset + visibleHeight) {
605
+ this._scrollOffset = this._selectedIndex - visibleHeight + 1;
606
+ }
607
+ this._scrollOffset = Math.max(0, this._scrollOffset);
608
+ }
609
+ };
610
+ function _isParent(node) {
611
+ return Array.isArray(node.children) && node.children.length > 0;
612
+ }
613
+ function _pathsEqual(a, b) {
614
+ if (a.length !== b.length) return false;
615
+ for (let i = 0; i < a.length; i++) {
616
+ if (a[i] !== b[i]) return false;
617
+ }
618
+ return true;
619
+ }
620
+ function _collectVisible(nodes, depth, parentPath, out) {
621
+ for (let i = 0; i < nodes.length; i++) {
622
+ const node = nodes[i];
623
+ const path = [...parentPath, i];
624
+ out.push({ node, depth, path });
625
+ if (_isParent(node) && node.expanded) {
626
+ _collectVisible(node.children, depth + 1, path, out);
627
+ }
628
+ }
629
+ }
630
+
631
+ // src/display/JSONView.ts
632
+ import {
633
+ styleToCellAttrs as styleToCellAttrs6,
634
+ truncate as truncate3,
635
+ stringWidth as stringWidth3,
636
+ caps as caps2
637
+ } from "@termuijs/core";
638
+ function jsonToTree(value, key) {
639
+ const prefix = key !== void 0 ? `${key}: ` : "";
640
+ if (value === null) {
641
+ return {
642
+ label: `${prefix}null`,
643
+ data: { type: "null", key }
644
+ };
645
+ }
646
+ if (typeof value === "boolean") {
647
+ return {
648
+ label: `${prefix}${value}`,
649
+ data: { type: "boolean", key, value }
650
+ };
651
+ }
652
+ if (typeof value === "number") {
653
+ return {
654
+ label: `${prefix}${value}`,
655
+ data: { type: "number", key, value }
656
+ };
657
+ }
658
+ if (typeof value === "string") {
659
+ return {
660
+ label: `${prefix}"${value}"`,
661
+ data: { type: "string", key, value }
662
+ };
663
+ }
664
+ if (Array.isArray(value)) {
665
+ const children = value.map((v, i) => jsonToTree(v, String(i)));
666
+ return {
667
+ label: `${prefix}[${children.length}]`,
668
+ children,
669
+ expanded: false,
670
+ data: { type: "array", key }
671
+ };
672
+ }
673
+ if (typeof value === "object") {
674
+ const obj = value;
675
+ const children = Object.keys(obj).map((k) => jsonToTree(obj[k], k));
676
+ return {
677
+ label: `${prefix}{${children.length}}`,
678
+ children,
679
+ expanded: false,
680
+ data: { type: "object", key }
681
+ };
682
+ }
683
+ return {
684
+ label: `${prefix}${String(value)}`,
685
+ data: { type: "unknown", key }
686
+ };
687
+ }
688
+ function _valueColor(type) {
689
+ switch (type) {
690
+ case "string":
691
+ return { type: "named", name: "green" };
692
+ case "number":
693
+ return { type: "named", name: "yellow" };
694
+ case "boolean":
695
+ return { type: "named", name: "magenta" };
696
+ case "null":
697
+ return { type: "named", name: "magenta" };
698
+ default:
699
+ return { type: "named", name: "white" };
700
+ }
701
+ }
702
+ var KEY_COLOR = { type: "named", name: "cyan" };
703
+ function _splitLabel(label, nodeData) {
704
+ if (nodeData.key !== void 0) {
705
+ const sep = `${nodeData.key}: `;
706
+ if (label.startsWith(sep)) {
707
+ return { keyPart: sep, valuePart: label.slice(sep.length) };
708
+ }
709
+ }
710
+ return { keyPart: "", valuePart: label };
711
+ }
712
+ var JSONView = class extends Tree {
713
+ constructor(options, style = {}) {
714
+ const root = jsonToTree(options.data);
715
+ const nodes = root.children && root.children.length > 0 ? root.children : [root];
716
+ super({ nodes, onSelect: options.onSelect, indent: options.indent }, style);
717
+ }
718
+ // ── Override rendering ─────────────────────────────
719
+ /**
720
+ * Replicate Tree's _renderSelf but colorize key/value segments.
721
+ * We access the private state via `(this as any)` since Tree doesn't
722
+ * expose those fields publicly — the tradeoff of extending vs composing.
723
+ */
724
+ _renderSelf(screen) {
725
+ const rect = this._getContentRect();
726
+ const { x, y, width, height } = rect;
727
+ if (width <= 0 || height <= 0) return;
728
+ const attrs = styleToCellAttrs6(this._style);
729
+ const useUnicode = caps2.unicode;
730
+ const collapsedChevron = useUnicode ? "\u25B6 " : "> ";
731
+ const expandedChevron = useUnicode ? "\u25BC " : "v ";
732
+ const leafPrefix = useUnicode ? "\u2022 " : "* ";
733
+ const visibleNodes = this._visibleNodes;
734
+ const scrollOffset = this._scrollOffset;
735
+ const selectedIndex = this._selectedIndex;
736
+ const indent = this._indent;
737
+ const visibleCount = Math.min(
738
+ visibleNodes.length - scrollOffset,
739
+ height
740
+ );
741
+ for (let i = 0; i < visibleCount; i++) {
742
+ const entryIdx = scrollOffset + i;
743
+ const entry = visibleNodes[entryIdx];
744
+ const { node, depth } = entry;
745
+ const isSelected = entryIdx === selectedIndex;
746
+ const nodeData = node.data ?? { type: "unknown" };
747
+ const indentStr = " ".repeat(indent * depth);
748
+ const isParent = Array.isArray(node.children) && node.children.length > 0;
749
+ let chevron;
750
+ if (isParent) {
751
+ chevron = node.expanded ? expandedChevron : collapsedChevron;
752
+ } else {
753
+ chevron = leafPrefix;
754
+ }
755
+ const linePrefix = indentStr + chevron;
756
+ const { keyPart, valuePart } = _splitLabel(node.label, nodeData);
757
+ const baseCellStyle = isSelected && this.isFocused ? {
758
+ ...attrs,
759
+ bg: { type: "named", name: "blue" },
760
+ bold: true
761
+ } : isSelected ? { ...attrs, bold: true } : attrs;
762
+ let cursorX = x;
763
+ const prefixTrunc = truncate3(linePrefix, width);
764
+ if (prefixTrunc.length > 0) {
765
+ screen.writeString(cursorX, y + i, prefixTrunc, baseCellStyle);
766
+ cursorX += stringWidth3(prefixTrunc);
767
+ }
768
+ const remaining = width - (cursorX - x);
769
+ if (remaining <= 0) {
770
+ _fillSelection(screen, x, y + i, width, cursorX - x, isSelected, this.isFocused, baseCellStyle);
771
+ continue;
772
+ }
773
+ if (keyPart.length > 0) {
774
+ const keyTrunc = truncate3(keyPart, remaining);
775
+ const keyStyle = { ...baseCellStyle, fg: KEY_COLOR };
776
+ screen.writeString(cursorX, y + i, keyTrunc, keyStyle);
777
+ cursorX += stringWidth3(keyTrunc);
778
+ }
779
+ const remaining2 = width - (cursorX - x);
780
+ if (remaining2 <= 0) {
781
+ _fillSelection(screen, x, y + i, width, cursorX - x, isSelected, this.isFocused, baseCellStyle);
782
+ continue;
783
+ }
784
+ if (valuePart.length > 0) {
785
+ const valTrunc = truncate3(valuePart, remaining2);
786
+ const valColor = _valueColor(nodeData.type);
787
+ const valStyle = { ...baseCellStyle, fg: valColor };
788
+ screen.writeString(cursorX, y + i, valTrunc, valStyle);
789
+ cursorX += stringWidth3(valTrunc);
790
+ }
791
+ _fillSelection(screen, x, y + i, width, cursorX - x, isSelected, this.isFocused, baseCellStyle);
792
+ }
793
+ }
794
+ };
795
+ function _fillSelection(screen, rowX, rowY, width, writtenWidth, isSelected, isFocused, cellStyle) {
796
+ if (!isSelected || !isFocused) return;
797
+ const remaining = width - writtenWidth;
798
+ for (let c = 0; c < remaining; c++) {
799
+ screen.setCell(rowX + writtenWidth + c, rowY, {
800
+ char: " ",
801
+ ...cellStyle
802
+ });
803
+ }
804
+ }
805
+
806
+ // src/display/DiffView.ts
807
+ import {
808
+ styleToCellAttrs as styleToCellAttrs7,
809
+ truncate as truncate4
810
+ } from "@termuijs/core";
811
+ var DiffView = class extends Widget {
812
+ _lines;
813
+ _scrollOffset = 0;
814
+ _showLineNumbers;
815
+ _gutterWidth;
816
+ constructor(options, style = {}) {
817
+ super(style);
818
+ this._lines = options.lines;
819
+ this._showLineNumbers = options.showLineNumbers ?? true;
820
+ this._gutterWidth = options.gutterWidth ?? 5;
821
+ this.focusable = true;
822
+ }
823
+ setLines(lines) {
824
+ this._lines = lines;
825
+ this._scrollOffset = 0;
826
+ this.markDirty();
827
+ }
828
+ _renderSelf(screen) {
829
+ const rect = this._getContentRect();
830
+ const { x, y, width, height } = rect;
831
+ if (width <= 0 || height <= 0) return;
832
+ const baseAttrs = styleToCellAttrs7(this._style);
833
+ const visibleLines = this._lines.slice(
834
+ this._scrollOffset,
835
+ this._scrollOffset + height
836
+ );
837
+ for (let i = 0; i < visibleLines.length; i++) {
838
+ const diffLine = visibleLines[i];
839
+ let col = x;
840
+ const row = y + i;
841
+ const isAdd = diffLine.type === "add";
842
+ const isRemove = diffLine.type === "remove";
843
+ const isContext = diffLine.type === "context";
844
+ const fg = isAdd ? { type: "named", name: "green" } : isRemove ? { type: "named", name: "red" } : void 0;
845
+ const lineAttrs = {
846
+ ...baseAttrs,
847
+ ...fg ? { fg } : {},
848
+ dim: isContext
849
+ };
850
+ if (this._showLineNumbers) {
851
+ const gutterStr = diffLine.lineNo !== void 0 ? String(diffLine.lineNo).padStart(this._gutterWidth - 1, " ") + " " : " ".repeat(this._gutterWidth);
852
+ const gutterAttrs = { ...baseAttrs, dim: true };
853
+ screen.writeString(col, row, gutterStr.slice(0, this._gutterWidth), gutterAttrs);
854
+ col += this._gutterWidth;
855
+ }
856
+ const prefix = isAdd ? "+" : isRemove ? "-" : " ";
857
+ const remainingWidth = width - (col - x);
858
+ if (remainingWidth <= 0) continue;
859
+ screen.writeString(col, row, prefix, lineAttrs);
860
+ col += 1;
861
+ const contentWidth = width - (col - x);
862
+ if (contentWidth <= 0) continue;
863
+ const content = truncate4(diffLine.content, contentWidth);
864
+ screen.writeString(col, row, content, lineAttrs);
865
+ }
866
+ }
867
+ handleKey(key) {
868
+ const rect = this._getContentRect();
869
+ const visibleHeight = Math.max(1, rect.height);
870
+ const maxOffset = Math.max(0, this._lines.length - visibleHeight);
871
+ switch (key) {
872
+ case "ArrowUp":
873
+ case "k":
874
+ this._scrollOffset = Math.max(0, this._scrollOffset - 1);
875
+ this.markDirty();
876
+ break;
877
+ case "ArrowDown":
878
+ case "j":
879
+ this._scrollOffset = Math.min(maxOffset, this._scrollOffset + 1);
880
+ this.markDirty();
881
+ break;
882
+ case "PageUp":
883
+ this._scrollOffset = Math.max(0, this._scrollOffset - visibleHeight);
884
+ this.markDirty();
885
+ break;
886
+ case "PageDown":
887
+ this._scrollOffset = Math.min(maxOffset, this._scrollOffset + visibleHeight);
888
+ this.markDirty();
889
+ break;
890
+ case "Home":
891
+ this._scrollOffset = 0;
892
+ this.markDirty();
893
+ break;
894
+ case "End":
895
+ this._scrollOffset = maxOffset;
896
+ this.markDirty();
897
+ break;
898
+ }
899
+ }
900
+ };
901
+
902
+ // src/display/StreamingText.ts
903
+ import { styleToCellAttrs as styleToCellAttrs8, wordWrap as wordWrap2, caps as caps3 } from "@termuijs/core";
904
+ import { timerPoolSubscribe } from "@termuijs/motion";
905
+ var StreamingText = class extends Widget {
906
+ _text;
907
+ _cursor;
908
+ _speed;
909
+ _blinkInterval;
910
+ /** Number of characters currently revealed (used when speed > 0) */
911
+ _revealed;
912
+ _cursorVisible;
913
+ _blinkUnsub;
914
+ constructor(options, style = {}) {
915
+ super(style);
916
+ this._text = options.text;
917
+ this._cursor = options.cursor ?? (caps3.unicode ? "\u258B" : "_");
918
+ this._speed = options.speed ?? 0;
919
+ this._blinkInterval = options.blinkInterval ?? 530;
920
+ this._revealed = 0;
921
+ this._cursorVisible = true;
922
+ }
923
+ /** Replace text content and reset the revealed counter to 0. */
924
+ setText(text) {
925
+ this._text = text;
926
+ this._revealed = 0;
927
+ this.markDirty();
928
+ }
929
+ /**
930
+ * Advance `_revealed` by `speed` characters.
931
+ * Call this from an external tick/render loop when `speed > 0`.
932
+ */
933
+ tick() {
934
+ if (this._speed <= 0 || this.isComplete()) return;
935
+ this._revealed = Math.min(this._revealed + this._speed, this._text.length);
936
+ this.markDirty();
937
+ }
938
+ /** Returns true when all text has been revealed. */
939
+ isComplete() {
940
+ if (this._speed === 0) return true;
941
+ return this._revealed >= this._text.length;
942
+ }
943
+ /** Lifecycle: start the blink timer (only when motion is enabled). */
944
+ mount() {
945
+ super.mount();
946
+ if (!caps3.motion) {
947
+ this._cursorVisible = false;
948
+ return;
949
+ }
950
+ this._blinkUnsub = timerPoolSubscribe(this._blinkInterval, () => {
951
+ this._cursorVisible = !this._cursorVisible;
952
+ this.markDirty();
953
+ });
954
+ }
955
+ /** Lifecycle: stop the blink timer. */
956
+ unmount() {
957
+ this._blinkUnsub?.();
958
+ this._blinkUnsub = void 0;
959
+ super.unmount();
960
+ }
961
+ _renderSelf(screen) {
962
+ const rect = this._getContentRect();
963
+ const { x, y, width, height } = rect;
964
+ if (width <= 0 || height <= 0) return;
965
+ const attrs = styleToCellAttrs8(this._style);
966
+ const displayText = this._speed > 0 ? this._text.slice(0, this._revealed) : this._text;
967
+ const fullText = this._cursorVisible ? displayText + this._cursor : displayText;
968
+ const wrapped = wordWrap2(fullText, width);
969
+ const lines = wrapped.split("\n");
970
+ const limit = Math.min(lines.length, height);
971
+ for (let i = 0; i < limit; i++) {
972
+ const line = lines[i];
973
+ if (line === void 0) continue;
974
+ screen.writeString(x, y + i, line, attrs);
975
+ }
976
+ }
977
+ };
978
+
979
+ // src/display/ChatMessage.ts
980
+ import {
981
+ styleToCellAttrs as styleToCellAttrs9,
982
+ stringWidth as stringWidth5,
983
+ truncate as truncate5,
984
+ wordWrap as wordWrap3
985
+ } from "@termuijs/core";
986
+ var ROLE_CONFIG = {
987
+ user: { badge: "[User]", colorName: "cyan" },
988
+ assistant: { badge: "[Assistant]", colorName: "green" },
989
+ system: { badge: "[System]", colorName: "yellow" },
990
+ tool: { badge: "[Tool]", colorName: "magenta" }
991
+ };
992
+ var ChatMessage = class extends Widget {
993
+ _role;
994
+ _content;
995
+ _timestamp;
996
+ constructor(options, style = {}) {
997
+ super(style);
998
+ this._role = options.role;
999
+ this._content = options.content;
1000
+ this._timestamp = options.timestamp;
1001
+ this.focusable = false;
1002
+ }
1003
+ /** Update the message content and mark dirty. */
1004
+ setContent(content) {
1005
+ this._content = content;
1006
+ this.markDirty();
1007
+ }
1008
+ /** Update the message role and mark dirty. */
1009
+ setRole(role) {
1010
+ this._role = role;
1011
+ this.markDirty();
1012
+ }
1013
+ _renderSelf(screen) {
1014
+ const rect = this._getContentRect();
1015
+ const { x, y, width, height } = rect;
1016
+ if (width <= 0 || height <= 0) return;
1017
+ const config = ROLE_CONFIG[this._role];
1018
+ const baseAttrs = styleToCellAttrs9(this._style);
1019
+ const badgeAttrs = {
1020
+ ...baseAttrs,
1021
+ fg: { type: "named", name: config.colorName }
1022
+ };
1023
+ screen.writeString(x, y, config.badge, badgeAttrs);
1024
+ if (this._timestamp) {
1025
+ const ts = this._timestamp.toLocaleTimeString("en-GB", {
1026
+ hour: "2-digit",
1027
+ minute: "2-digit",
1028
+ second: "2-digit",
1029
+ hour12: false
1030
+ });
1031
+ const tsWidth = stringWidth5(ts);
1032
+ const tsX = x + width - tsWidth;
1033
+ if (tsX > x + stringWidth5(config.badge)) {
1034
+ const dimAttrs = { ...baseAttrs, dim: true };
1035
+ screen.writeString(tsX, y, ts, dimAttrs);
1036
+ }
1037
+ }
1038
+ if (height <= 1) return;
1039
+ const indent = " ";
1040
+ const contentWidth = Math.max(0, width - indent.length);
1041
+ const lines = contentWidth > 0 ? wordWrap3(this._content, contentWidth).split("\n") : [];
1042
+ const maxContentRows = height - 1;
1043
+ for (let i = 0; i < Math.min(lines.length, maxContentRows); i++) {
1044
+ const line = lines[i];
1045
+ if (line === void 0) continue;
1046
+ const displayLine = truncate5(indent + line, width);
1047
+ screen.writeString(x, y + 1 + i, displayLine, baseAttrs);
1048
+ }
1049
+ }
1050
+ };
1051
+
1052
+ // src/display/ToolCall.ts
1053
+ import {
1054
+ styleToCellAttrs as styleToCellAttrs10,
1055
+ truncate as truncate6,
1056
+ caps as caps4
1057
+ } from "@termuijs/core";
1058
+ var STATUS_CONFIG = {
1059
+ pending: { unicodeSymbol: "\u25CC", asciiSymbol: "?", colorName: "white", dim: true },
1060
+ running: { unicodeSymbol: "\u25CE", asciiSymbol: "~", colorName: "yellow" },
1061
+ done: { unicodeSymbol: "\u2713", asciiSymbol: "+", colorName: "green" },
1062
+ error: { unicodeSymbol: "\u2717", asciiSymbol: "!", colorName: "red" }
1063
+ };
1064
+ var ToolCall = class extends Widget {
1065
+ _name;
1066
+ _args;
1067
+ _result;
1068
+ _status;
1069
+ _collapsed;
1070
+ constructor(options, style = {}) {
1071
+ super(style);
1072
+ this._name = options.name;
1073
+ this._args = options.args;
1074
+ this._result = options.result;
1075
+ this._status = options.status;
1076
+ this._collapsed = options.collapsed ?? true;
1077
+ this.focusable = true;
1078
+ }
1079
+ // ── Public API ─────────────────────────────────────
1080
+ setStatus(status) {
1081
+ this._status = status;
1082
+ this.markDirty();
1083
+ }
1084
+ setResult(result) {
1085
+ this._result = result;
1086
+ this.markDirty();
1087
+ }
1088
+ collapse() {
1089
+ this._collapsed = true;
1090
+ this.markDirty();
1091
+ }
1092
+ expand() {
1093
+ this._collapsed = false;
1094
+ this.markDirty();
1095
+ }
1096
+ handleKey(key) {
1097
+ if (key === " " || key === "Enter") {
1098
+ this._collapsed = !this._collapsed;
1099
+ this.markDirty();
1100
+ }
1101
+ }
1102
+ // ── Rendering ──────────────────────────────────────
1103
+ _renderSelf(screen) {
1104
+ const rect = this._getContentRect();
1105
+ const { x, y, width, height } = rect;
1106
+ if (width <= 0 || height <= 0) return;
1107
+ const useUnicode = caps4.unicode;
1108
+ const baseAttrs = styleToCellAttrs10(this._style);
1109
+ const config = STATUS_CONFIG[this._status];
1110
+ const collapsedChevron = useUnicode ? "\u25B6" : ">";
1111
+ const expandedChevron = useUnicode ? "\u25BC" : "v";
1112
+ const chevron = this._collapsed ? collapsedChevron : expandedChevron;
1113
+ const symbol = useUnicode ? config.unicodeSymbol : config.asciiSymbol;
1114
+ const statusAttrs = {
1115
+ ...baseAttrs,
1116
+ fg: { type: "named", name: config.colorName },
1117
+ bold: config.bold ?? false,
1118
+ dim: config.dim ?? false
1119
+ };
1120
+ const headerText = `${chevron} ${this._name} ${symbol} [${this._status}]`;
1121
+ screen.writeString(x, y, truncate6(headerText, width), baseAttrs);
1122
+ const symbolOffset = chevron.length + 1 + this._name.length + 1;
1123
+ if (symbolOffset < width) {
1124
+ screen.writeString(x + symbolOffset, y, symbol, statusAttrs);
1125
+ }
1126
+ if (this._collapsed) return;
1127
+ let row = 1;
1128
+ const argEntries = Object.entries(this._args);
1129
+ for (const [key, value] of argEntries) {
1130
+ if (row >= height) break;
1131
+ const argLine = ` ${key}: ${JSON.stringify(value)}`;
1132
+ screen.writeString(x, y + row, truncate6(argLine, width), baseAttrs);
1133
+ row++;
1134
+ }
1135
+ if (row < height && this._result !== void 0 && (this._status === "done" || this._status === "error")) {
1136
+ const resultStr = typeof this._result === "string" ? this._result : JSON.stringify(this._result);
1137
+ const resultLine = ` result: ${resultStr}`;
1138
+ screen.writeString(x, y + row, truncate6(resultLine, width), baseAttrs);
1139
+ }
1140
+ }
1141
+ };
1142
+ var ToolApproval = class extends ToolCall {
1143
+ _onApprove;
1144
+ _onDeny;
1145
+ constructor(options, style = {}) {
1146
+ super(options, style);
1147
+ this._onApprove = options.onApprove;
1148
+ this._onDeny = options.onDeny;
1149
+ }
1150
+ _renderSelf(screen) {
1151
+ super._renderSelf(screen);
1152
+ const rect = this._getContentRect();
1153
+ const { x, y, width, height } = rect;
1154
+ if (width <= 0 || height <= 0) return;
1155
+ let rowsUsed = 1;
1156
+ if (!this._collapsed) {
1157
+ rowsUsed += Object.keys(this._args).length;
1158
+ if (this._result !== void 0 && (this._status === "done" || this._status === "error")) {
1159
+ rowsUsed += 1;
1160
+ }
1161
+ }
1162
+ const approvalRow = y + rowsUsed;
1163
+ if (approvalRow >= y + height) return;
1164
+ const baseAttrs = styleToCellAttrs10(this._style);
1165
+ const approveAttrs = {
1166
+ ...baseAttrs,
1167
+ fg: { type: "named", name: "green" },
1168
+ bold: true
1169
+ };
1170
+ const denyAttrs = {
1171
+ ...baseAttrs,
1172
+ fg: { type: "named", name: "red" },
1173
+ bold: true
1174
+ };
1175
+ const approveText = "[y] Approve";
1176
+ const denyText = "[n] Deny";
1177
+ screen.writeString(x, approvalRow, approveText, approveAttrs);
1178
+ const denyX = x + approveText.length + 2;
1179
+ if (denyX + denyText.length <= x + width) {
1180
+ screen.writeString(denyX, approvalRow, denyText, denyAttrs);
1181
+ }
1182
+ }
1183
+ handleKey(key) {
1184
+ if (key === "y" || key === "Enter") {
1185
+ this._onApprove?.();
1186
+ } else if (key === "n" || key === "Escape") {
1187
+ this._onDeny?.();
1188
+ } else {
1189
+ super.handleKey(key);
1190
+ }
1191
+ }
1192
+ };
1193
+
1194
+ // src/input/virtual-scroll.ts
1195
+ function computeRange(scrollOffset, viewportItems, itemCount, overscan = 2) {
1196
+ const start = Math.max(0, scrollOffset - overscan);
1197
+ const end = Math.min(itemCount, scrollOffset + viewportItems + overscan);
1198
+ return { start, end, offsetPx: start };
1199
+ }
1200
+ function computeVariableRange(scrollPx, viewportPx, sizes, overscan = 2) {
1201
+ const cumulative = [];
1202
+ let sum = 0;
1203
+ for (const s of sizes) {
1204
+ cumulative.push(sum);
1205
+ sum += s;
1206
+ }
1207
+ cumulative.push(sum);
1208
+ let startIdx = cumulative.findIndex((c, i) => i < sizes.length && c + sizes[i] > scrollPx);
1209
+ if (startIdx < 0) startIdx = sizes.length;
1210
+ startIdx = Math.max(0, startIdx - overscan);
1211
+ const viewportEnd = scrollPx + viewportPx;
1212
+ let endIdx = cumulative.findIndex((c) => c >= viewportEnd);
1213
+ if (endIdx < 0) endIdx = sizes.length;
1214
+ endIdx = Math.min(sizes.length, endIdx + overscan);
1215
+ return { start: startIdx, end: endIdx, offsetPx: cumulative[startIdx] };
1216
+ }
1217
+
397
1218
  // src/input/List.ts
398
- import { styleToCellAttrs as styleToCellAttrs5, stringWidth as stringWidth2, truncate as truncate2 } from "@termuijs/core";
1219
+ import { styleToCellAttrs as styleToCellAttrs11, stringWidth as stringWidth6, truncate as truncate7, caps as caps5 } from "@termuijs/core";
399
1220
  var List = class extends Widget {
400
1221
  _items;
401
1222
  _selectedIndex = 0;
@@ -450,15 +1271,15 @@ var List = class extends Widget {
450
1271
  const rect = this._getContentRect();
451
1272
  const { x, y, width, height } = rect;
452
1273
  if (width <= 0 || height <= 0) return;
453
- const attrs = styleToCellAttrs5(this._style);
1274
+ const attrs = styleToCellAttrs11(this._style);
454
1275
  const visibleCount = Math.min(this._items.length - this._scrollOffset, height);
455
1276
  for (let i = 0; i < visibleCount; i++) {
456
1277
  const itemIdx = this._scrollOffset + i;
457
1278
  const item = this._items[itemIdx];
458
1279
  const isSelected = itemIdx === this._selectedIndex;
459
- const prefix = isSelected ? "\u25B8 " : " ";
1280
+ const prefix = isSelected ? caps5.unicode ? "\u25B8 " : "> " : " ";
460
1281
  let line = prefix + item.label;
461
- line = truncate2(line, width);
1282
+ line = truncate7(line, width);
462
1283
  const cellStyle = {
463
1284
  ...attrs,
464
1285
  bold: isSelected,
@@ -467,9 +1288,9 @@ var List = class extends Widget {
467
1288
  };
468
1289
  screen.writeString(x, y + i, line, cellStyle);
469
1290
  if (isSelected && this.isFocused) {
470
- const remaining = width - stringWidth2(line);
1291
+ const remaining = width - stringWidth6(line);
471
1292
  for (let c = 0; c < remaining; c++) {
472
- screen.setCell(x + stringWidth2(line) + c, y + i, { char: " ", ...cellStyle });
1293
+ screen.setCell(x + stringWidth6(line) + c, y + i, { char: " ", ...cellStyle });
473
1294
  }
474
1295
  }
475
1296
  }
@@ -477,7 +1298,7 @@ var List = class extends Widget {
477
1298
  const scrollRatio = this._scrollOffset / (this._items.length - height);
478
1299
  const scrollPos = Math.floor(scrollRatio * (height - 1));
479
1300
  for (let r = 0; r < height; r++) {
480
- const scrollChar = r === scrollPos ? "\u2588" : "\u2591";
1301
+ const scrollChar = r === scrollPos ? caps5.unicode ? "\u2588" : "#" : caps5.unicode ? "\u2591" : "-";
481
1302
  screen.setCell(x + width - 1, y + r, { char: scrollChar, ...attrs, dim: true });
482
1303
  }
483
1304
  }
@@ -499,7 +1320,7 @@ var List = class extends Widget {
499
1320
  };
500
1321
 
501
1322
  // src/input/TextInput.ts
502
- import { styleToCellAttrs as styleToCellAttrs6, truncate as truncate3 } from "@termuijs/core";
1323
+ import { styleToCellAttrs as styleToCellAttrs12, truncate as truncate8 } from "@termuijs/core";
503
1324
  var TextInput = class extends Widget {
504
1325
  _value = "";
505
1326
  _cursorPos = 0;
@@ -576,9 +1397,9 @@ var TextInput = class extends Widget {
576
1397
  const rect = this._getContentRect();
577
1398
  const { x, y, width, height } = rect;
578
1399
  if (width <= 0 || height <= 0) return;
579
- const attrs = styleToCellAttrs6(this._style);
1400
+ const attrs = styleToCellAttrs12(this._style);
580
1401
  if (this._value.length === 0 && !this.isFocused) {
581
- screen.writeString(x, y, truncate3(this._placeholder, width), { ...attrs, dim: true });
1402
+ screen.writeString(x, y, truncate8(this._placeholder, width), { ...attrs, dim: true });
582
1403
  return;
583
1404
  }
584
1405
  const displayValue = this._mask ? this._mask.repeat(this._value.length) : this._value;
@@ -604,7 +1425,7 @@ var TextInput = class extends Widget {
604
1425
  };
605
1426
 
606
1427
  // src/input/VirtualList.ts
607
- import { styleToCellAttrs as styleToCellAttrs7, truncate as truncate4, stringWidth as stringWidth4 } from "@termuijs/core";
1428
+ import { styleToCellAttrs as styleToCellAttrs13, truncate as truncate9, stringWidth as stringWidth8, caps as caps6 } from "@termuijs/core";
608
1429
  var VirtualList = class extends Widget {
609
1430
  _totalItems;
610
1431
  _itemHeight;
@@ -708,10 +1529,14 @@ var VirtualList = class extends Widget {
708
1529
  const rect = this._getContentRect();
709
1530
  const { x, y, width, height } = rect;
710
1531
  if (width <= 0 || height <= 0 || this._totalItems === 0) return;
711
- const attrs = styleToCellAttrs7(this._style);
1532
+ const attrs = styleToCellAttrs13(this._style);
712
1533
  const visibleItemCount = Math.floor(height / this._itemHeight);
713
- const startIdx = Math.max(0, this._scrollOffset - this._overscan);
714
- const endIdx = Math.min(this._totalItems, this._scrollOffset + visibleItemCount + this._overscan);
1534
+ const { start: startIdx, end: endIdx } = computeRange(
1535
+ this._scrollOffset,
1536
+ visibleItemCount,
1537
+ this._totalItems,
1538
+ this._overscan
1539
+ );
715
1540
  const contentWidth = this._showScrollbar && this._totalItems > visibleItemCount ? width - 1 : width;
716
1541
  for (let idx = startIdx; idx < endIdx; idx++) {
717
1542
  const rowY = y + (idx - this._scrollOffset) * this._itemHeight;
@@ -723,9 +1548,9 @@ var VirtualList = class extends Widget {
723
1548
  } catch {
724
1549
  content = `[Error: item ${idx}]`;
725
1550
  }
726
- const prefix = isSelected ? "\u25B8 " : " ";
1551
+ const prefix = isSelected ? caps6.unicode ? "\u25B8 " : "> " : " ";
727
1552
  let line = prefix + content;
728
- line = truncate4(line, contentWidth);
1553
+ line = truncate9(line, contentWidth);
729
1554
  const cellStyle = {
730
1555
  ...attrs,
731
1556
  bold: isSelected,
@@ -733,9 +1558,9 @@ var VirtualList = class extends Widget {
733
1558
  };
734
1559
  screen.writeString(x, rowY, line, cellStyle);
735
1560
  if (isSelected && this.isFocused) {
736
- const remaining = contentWidth - stringWidth4(line);
1561
+ const remaining = contentWidth - stringWidth8(line);
737
1562
  for (let c = 0; c < remaining; c++) {
738
- screen.setCell(x + stringWidth4(line) + c, rowY, { char: " ", ...cellStyle });
1563
+ screen.setCell(x + stringWidth8(line) + c, rowY, { char: " ", ...cellStyle });
739
1564
  }
740
1565
  }
741
1566
  }
@@ -744,8 +1569,10 @@ var VirtualList = class extends Widget {
744
1569
  const totalPages = this._totalItems - visibleItemCount;
745
1570
  const scrollRatio = totalPages > 0 ? this._scrollOffset / totalPages : 0;
746
1571
  const thumbPos = Math.floor(scrollRatio * (height - 1));
1572
+ const thumbChar = caps6.unicode ? "\u2588" : "#";
1573
+ const trackChar = caps6.unicode ? "\u2591" : "|";
747
1574
  for (let r = 0; r < height; r++) {
748
- const scrollChar = r === thumbPos ? "\u2588" : "\u2591";
1575
+ const scrollChar = r === thumbPos ? thumbChar : trackChar;
749
1576
  screen.setCell(scrollbarX, y + r, { char: scrollChar, ...attrs, dim: r !== thumbPos });
750
1577
  }
751
1578
  }
@@ -768,8 +1595,173 @@ var VirtualList = class extends Widget {
768
1595
  }
769
1596
  };
770
1597
 
1598
+ // src/input/CommandPalette.ts
1599
+ import { styleToCellAttrs as styleToCellAttrs14, stringWidth as stringWidth9, truncate as truncate10 } from "@termuijs/core";
1600
+ var CommandPalette = class extends Widget {
1601
+ _commands;
1602
+ _filtered;
1603
+ _query = "";
1604
+ _selectedIndex = 0;
1605
+ _options;
1606
+ constructor(options, style = {}) {
1607
+ super({ border: "single", ...style });
1608
+ this._options = {
1609
+ placeholder: "Type to search...",
1610
+ maxVisible: 8,
1611
+ ...options
1612
+ };
1613
+ this._commands = options.commands;
1614
+ this._filtered = [...this._commands];
1615
+ this.focusable = true;
1616
+ }
1617
+ /** Update the full command list and re-filter. */
1618
+ setCommands(commands) {
1619
+ this._commands = commands;
1620
+ this._filter();
1621
+ this.markDirty();
1622
+ }
1623
+ /**
1624
+ * Reset query, re-filter all commands, reset selection.
1625
+ * Call this when opening the palette.
1626
+ */
1627
+ open() {
1628
+ this._query = "";
1629
+ this._selectedIndex = 0;
1630
+ this._filtered = [...this._commands];
1631
+ this.markDirty();
1632
+ }
1633
+ /** Current query string. */
1634
+ getQuery() {
1635
+ return this._query;
1636
+ }
1637
+ // ─── Private helpers ────────────────────────────────
1638
+ /** Filter commands based on current query (case-insensitive substring). */
1639
+ _filter() {
1640
+ const q = this._query.toLowerCase();
1641
+ if (q === "") {
1642
+ this._filtered = [...this._commands];
1643
+ } else {
1644
+ this._filtered = this._commands.filter(
1645
+ (cmd) => cmd.label.toLowerCase().includes(q) || cmd.id.toLowerCase().includes(q)
1646
+ );
1647
+ }
1648
+ const max = Math.max(0, this._filtered.length - 1);
1649
+ this._selectedIndex = Math.min(this._selectedIndex, max);
1650
+ }
1651
+ _executeSelected() {
1652
+ const cmd = this._filtered[this._selectedIndex];
1653
+ if (cmd) {
1654
+ cmd.action();
1655
+ }
1656
+ }
1657
+ _moveUp() {
1658
+ if (this._selectedIndex > 0) {
1659
+ this._selectedIndex--;
1660
+ }
1661
+ }
1662
+ _moveDown() {
1663
+ const maxVisible = this._options.maxVisible ?? 8;
1664
+ const visibleCount = Math.min(this._filtered.length, maxVisible);
1665
+ if (this._selectedIndex < visibleCount - 1) {
1666
+ this._selectedIndex++;
1667
+ }
1668
+ }
1669
+ // ─── Key handling ────────────────────────────────────
1670
+ handleKey(key) {
1671
+ switch (key) {
1672
+ case "Escape":
1673
+ this._options.onClose?.();
1674
+ break;
1675
+ case "Enter":
1676
+ this._executeSelected();
1677
+ break;
1678
+ case "ArrowUp":
1679
+ case "k":
1680
+ this._moveUp();
1681
+ break;
1682
+ case "ArrowDown":
1683
+ case "j":
1684
+ this._moveDown();
1685
+ break;
1686
+ case "Backspace":
1687
+ this._query = this._query.slice(0, -1);
1688
+ this._filter();
1689
+ break;
1690
+ default:
1691
+ if (key.length === 1) {
1692
+ this._query += key;
1693
+ this._filter();
1694
+ }
1695
+ break;
1696
+ }
1697
+ this.markDirty();
1698
+ }
1699
+ // ─── Rendering ───────────────────────────────────────
1700
+ _renderSelf(screen) {
1701
+ const rect = this._getContentRect();
1702
+ const { x, y, width, height } = rect;
1703
+ if (width <= 0 || height <= 0) return;
1704
+ const attrs = styleToCellAttrs14(this._style);
1705
+ const maxVisible = this._options.maxVisible ?? 8;
1706
+ const placeholder = this._options.placeholder ?? "Type to search...";
1707
+ const prefix = "> ";
1708
+ const queryDisplay = this._query.length > 0 ? this._query : placeholder;
1709
+ const queryDim = this._query.length === 0;
1710
+ const queryLine = truncate10(prefix + queryDisplay, width);
1711
+ screen.writeString(x, y, queryLine, { ...attrs, dim: queryDim });
1712
+ const queryLineWidth = stringWidth9(queryLine);
1713
+ for (let c = queryLineWidth; c < width; c++) {
1714
+ screen.setCell(x + c, y, { char: " ", ...attrs });
1715
+ }
1716
+ const listStartRow = 1;
1717
+ const listHeight = height - listStartRow;
1718
+ const visibleCount = Math.min(this._filtered.length, maxVisible, listHeight);
1719
+ for (let i = 0; i < visibleCount; i++) {
1720
+ const cmd = this._filtered[i];
1721
+ const isSelected = i === this._selectedIndex;
1722
+ const rowY = y + listStartRow + i;
1723
+ const rowPrefix = isSelected ? "\u25B8 " : " ";
1724
+ const labelPart = rowPrefix + cmd.label;
1725
+ const desc = cmd.description ?? "";
1726
+ const descWidth = stringWidth9(desc);
1727
+ const gap = 1;
1728
+ const labelMaxWidth = desc ? Math.max(0, width - descWidth - gap) : width;
1729
+ const labelTruncated = truncate10(labelPart, labelMaxWidth);
1730
+ const labelWidth = stringWidth9(labelTruncated);
1731
+ const cellStyle = {
1732
+ ...attrs,
1733
+ bold: isSelected,
1734
+ inverse: isSelected
1735
+ };
1736
+ const dimStyle = {
1737
+ ...attrs,
1738
+ dim: true,
1739
+ inverse: isSelected
1740
+ };
1741
+ screen.writeString(x, rowY, labelTruncated, cellStyle);
1742
+ if (desc) {
1743
+ const descX = x + width - descWidth;
1744
+ for (let c = x + labelWidth; c < descX; c++) {
1745
+ screen.setCell(c, rowY, { char: " ", ...cellStyle });
1746
+ }
1747
+ screen.writeString(descX, rowY, desc, dimStyle);
1748
+ } else {
1749
+ for (let c = x + labelWidth; c < x + width; c++) {
1750
+ screen.setCell(c, rowY, { char: " ", ...cellStyle });
1751
+ }
1752
+ }
1753
+ }
1754
+ for (let i = visibleCount; i < listHeight; i++) {
1755
+ const rowY = y + listStartRow + i;
1756
+ for (let c = 0; c < width; c++) {
1757
+ screen.setCell(x + c, rowY, { char: " ", ...attrs });
1758
+ }
1759
+ }
1760
+ }
1761
+ };
1762
+
771
1763
  // src/data/Table.ts
772
- import { styleToCellAttrs as styleToCellAttrs8, stringWidth as stringWidth5, truncate as truncate5 } from "@termuijs/core";
1764
+ import { styleToCellAttrs as styleToCellAttrs15, stringWidth as stringWidth10, truncate as truncate11 } from "@termuijs/core";
773
1765
  var Table = class extends Widget {
774
1766
  _columns;
775
1767
  _rows;
@@ -796,8 +1788,8 @@ var Table = class extends Widget {
796
1788
  const rect = this._getContentRect();
797
1789
  const { x, y, width, height } = rect;
798
1790
  if (width <= 0 || height <= 0) return;
799
- const attrs = styleToCellAttrs8(this._style);
800
- const sepWidth = stringWidth5(this._separator);
1791
+ const attrs = styleToCellAttrs15(this._style);
1792
+ const sepWidth = stringWidth10(this._separator);
801
1793
  const colWidths = this._computeColumnWidths(
802
1794
  width - (this._columns.length - 1) * sepWidth
803
1795
  );
@@ -864,8 +1856,8 @@ var Table = class extends Widget {
864
1856
  return this._columns.map((c) => c.width ?? flexWidth);
865
1857
  }
866
1858
  _alignText(text, width, align) {
867
- const truncated = truncate5(text, width);
868
- const textWidth = stringWidth5(truncated);
1859
+ const truncated = truncate11(text, width);
1860
+ const textWidth = stringWidth10(truncated);
869
1861
  const pad = Math.max(0, width - textWidth);
870
1862
  switch (align) {
871
1863
  case "right":
@@ -883,7 +1875,7 @@ var Table = class extends Widget {
883
1875
  };
884
1876
 
885
1877
  // src/data/Gauge.ts
886
- import { styleToCellAttrs as styleToCellAttrs9, stringWidth as stringWidth6 } from "@termuijs/core";
1878
+ import { styleToCellAttrs as styleToCellAttrs16, stringWidth as stringWidth11, caps as caps7 } from "@termuijs/core";
887
1879
  var Gauge = class extends Widget {
888
1880
  _label;
889
1881
  _value = 0;
@@ -910,17 +1902,17 @@ var Gauge = class extends Widget {
910
1902
  const rect = this._getContentRect();
911
1903
  const { x, y, width, height } = rect;
912
1904
  if (width <= 0 || height <= 0) return;
913
- const attrs = styleToCellAttrs9(this._style);
1905
+ const attrs = styleToCellAttrs16(this._style);
914
1906
  const labelStr = this._label + " ";
915
1907
  const percentStr = this._showLabel ? ` ${Math.round(this._value * 100)}%` : "";
916
- const labelWidth = stringWidth6(labelStr);
917
- const percentWidth = stringWidth6(percentStr);
1908
+ const labelWidth = stringWidth11(labelStr);
1909
+ const percentWidth = stringWidth11(percentStr);
918
1910
  const barWidth = Math.max(0, width - labelWidth - percentWidth);
919
1911
  screen.writeString(x, y, labelStr, { ...attrs, bold: true });
920
1912
  const filled = Math.round(barWidth * this._value);
921
1913
  const barX = x + labelWidth;
922
1914
  for (let i = 0; i < barWidth; i++) {
923
- const char = i < filled ? "\u2588" : "\u2591";
1915
+ const char = i < filled ? caps7.unicode ? "\u2588" : "#" : caps7.unicode ? "\u2591" : "-";
924
1916
  screen.setCell(barX + i, y, {
925
1917
  char,
926
1918
  fg: i < filled ? this._color : { type: "named", name: "brightBlack" }
@@ -936,8 +1928,9 @@ var Gauge = class extends Widget {
936
1928
  };
937
1929
 
938
1930
  // src/data/Sparkline.ts
939
- import { styleToCellAttrs as styleToCellAttrs10 } from "@termuijs/core";
940
- var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
1931
+ import { styleToCellAttrs as styleToCellAttrs17, caps as caps8 } from "@termuijs/core";
1932
+ var SPARK_CHARS_UNICODE = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
1933
+ var SPARK_CHARS_ASCII = ["1", "2", "3", "4", "5", "6", "7", "8"];
941
1934
  var Sparkline = class extends Widget {
942
1935
  _label;
943
1936
  _data = [];
@@ -961,7 +1954,7 @@ var Sparkline = class extends Widget {
961
1954
  const rect = this._getContentRect();
962
1955
  const { x, y, width, height } = rect;
963
1956
  if (width <= 0 || height <= 0) return;
964
- const attrs = styleToCellAttrs10(this._style);
1957
+ const attrs = styleToCellAttrs17(this._style);
965
1958
  const labelStr = this._label + " ";
966
1959
  const labelWidth = labelStr.length;
967
1960
  screen.writeString(x, y, labelStr, { ...attrs, bold: true });
@@ -971,11 +1964,12 @@ var Sparkline = class extends Widget {
971
1964
  const min = Math.min(...data);
972
1965
  const max = Math.max(...data);
973
1966
  const range = max - min || 1;
1967
+ const sparkChars = caps8.unicode ? SPARK_CHARS_UNICODE : SPARK_CHARS_ASCII;
974
1968
  for (let i = 0; i < data.length; i++) {
975
1969
  const normalized = (data[i] - min) / range;
976
1970
  const charIdx = Math.min(7, Math.floor(normalized * 8));
977
1971
  screen.setCell(x + labelWidth + i, y, {
978
- char: SPARK_CHARS[charIdx],
1972
+ char: sparkChars[charIdx],
979
1973
  fg: this._color
980
1974
  });
981
1975
  }
@@ -990,7 +1984,7 @@ var Sparkline = class extends Widget {
990
1984
  };
991
1985
 
992
1986
  // src/data/StatusIndicator.ts
993
- import { styleToCellAttrs as styleToCellAttrs11 } from "@termuijs/core";
1987
+ import { styleToCellAttrs as styleToCellAttrs18 } from "@termuijs/core";
994
1988
  var StatusIndicator = class extends Widget {
995
1989
  _label;
996
1990
  _isUp;
@@ -1016,7 +2010,7 @@ var StatusIndicator = class extends Widget {
1016
2010
  const rect = this._getContentRect();
1017
2011
  const { x, y, width, height } = rect;
1018
2012
  if (width <= 0 || height <= 0) return;
1019
- const attrs = styleToCellAttrs11(this._style);
2013
+ const attrs = styleToCellAttrs18(this._style);
1020
2014
  const dot = this._isUp ? "\u25CF" : "\u25CB";
1021
2015
  const statusText = this._isUp ? "Online" : "Offline";
1022
2016
  const color = this._isUp ? this._upColor : this._downColor;
@@ -1030,7 +2024,7 @@ var StatusIndicator = class extends Widget {
1030
2024
 
1031
2025
  // src/data/BarChart.ts
1032
2026
  import {
1033
- stringWidth as stringWidth7,
2027
+ stringWidth as stringWidth12,
1034
2028
  VERTICAL_BAR_SYMBOLS,
1035
2029
  HORIZONTAL_BAR_SYMBOLS
1036
2030
  } from "@termuijs/core";
@@ -1124,7 +2118,7 @@ var BarChart = class extends Widget {
1124
2118
  remaining -= 8;
1125
2119
  }
1126
2120
  const valStr = Math.round(bar.value).toString();
1127
- const valX = cx + Math.floor((this._barWidth - stringWidth7(valStr)) / 2);
2121
+ const valX = cx + Math.floor((this._barWidth - stringWidth12(valStr)) / 2);
1128
2122
  screen.writeString(
1129
2123
  Math.max(cx, valX),
1130
2124
  oy + barAreaHeight,
@@ -1133,7 +2127,7 @@ var BarChart = class extends Widget {
1133
2127
  );
1134
2128
  if (hasLabels && bar.label) {
1135
2129
  const label = bar.label.slice(0, this._barWidth);
1136
- const labelX = cx + Math.floor((this._barWidth - stringWidth7(label)) / 2);
2130
+ const labelX = cx + Math.floor((this._barWidth - stringWidth12(label)) / 2);
1137
2131
  screen.writeString(
1138
2132
  Math.max(cx, labelX),
1139
2133
  oy + barAreaHeight + valueRows,
@@ -1147,7 +2141,7 @@ var BarChart = class extends Widget {
1147
2141
  if (hasGroupLabels && group.label) {
1148
2142
  const groupWidth = cx - groupStartX;
1149
2143
  const label = group.label.slice(0, groupWidth);
1150
- const labelX = groupStartX + Math.floor((groupWidth - stringWidth7(label)) / 2);
2144
+ const labelX = groupStartX + Math.floor((groupWidth - stringWidth12(label)) / 2);
1151
2145
  screen.writeString(
1152
2146
  Math.max(groupStartX, labelX),
1153
2147
  oy + height - 1,
@@ -1165,7 +2159,7 @@ var BarChart = class extends Widget {
1165
2159
  for (const group of this._data) {
1166
2160
  for (const bar of group.bars) {
1167
2161
  if (bar.label) {
1168
- const w = stringWidth7(bar.label);
2162
+ const w = stringWidth12(bar.label);
1169
2163
  if (w > maxLabelWidth) maxLabelWidth = w;
1170
2164
  }
1171
2165
  const vw = Math.round(bar.value).toString().length;
@@ -1220,8 +2214,270 @@ var BarChart = class extends Widget {
1220
2214
  }
1221
2215
  };
1222
2216
 
2217
+ // src/layout/Grid.ts
2218
+ var Grid = class extends Widget {
2219
+ _columns;
2220
+ _colGap;
2221
+ _rowGap;
2222
+ _rows = [];
2223
+ _itemCount = 0;
2224
+ constructor(style, options) {
2225
+ super({ flexDirection: "column", ...style });
2226
+ this._columns = Math.max(1, options.columns);
2227
+ const gap = options.gap ?? 1;
2228
+ this._colGap = options.colGap ?? gap;
2229
+ this._rowGap = options.rowGap ?? gap;
2230
+ }
2231
+ _renderSelf(_screen) {
2232
+ }
2233
+ /**
2234
+ * Add a widget to the grid. Items fill left-to-right,
2235
+ * wrapping to a new row automatically every `columns` items.
2236
+ * Overrides Widget.addChild so the reconciler generic loop works unchanged.
2237
+ */
2238
+ addChild(widget) {
2239
+ const rowIndex = Math.floor(this._itemCount / this._columns);
2240
+ if (rowIndex >= this._rows.length) {
2241
+ const rowStyle = { flexDirection: "row" };
2242
+ if (this._colGap > 0) rowStyle.gap = this._colGap;
2243
+ if (this._rows.length > 0 && this._rowGap > 0) {
2244
+ rowStyle.margin = this._rowGap;
2245
+ }
2246
+ const row = new Box(rowStyle);
2247
+ this._rows.push(row);
2248
+ super.addChild(row);
2249
+ }
2250
+ const currentRow = this._rows[rowIndex];
2251
+ widget.setStyle({ flexGrow: 1 });
2252
+ currentRow.addChild(widget);
2253
+ this._itemCount++;
2254
+ }
2255
+ /** Add an item explicitly (alias for addChild) */
2256
+ addItem(widget) {
2257
+ this.addChild(widget);
2258
+ }
2259
+ /** Remove all items and reset the grid */
2260
+ clearItems() {
2261
+ for (const row of this._rows) {
2262
+ row.unmount();
2263
+ row.parent = null;
2264
+ }
2265
+ this._children = [];
2266
+ this._rows = [];
2267
+ this._itemCount = 0;
2268
+ }
2269
+ };
2270
+
2271
+ // src/layout/ScrollView.ts
2272
+ import { styleToCellAttrs as styleToCellAttrs20 } from "@termuijs/core";
2273
+ var ScrollView = class extends Widget {
2274
+ _scrollOffset = 0;
2275
+ _contentHeight;
2276
+ _showScrollbar;
2277
+ constructor(style = {}, opts = {}) {
2278
+ super({ overflow: "hidden", ...style });
2279
+ this._contentHeight = opts.contentHeight ?? 0;
2280
+ this._showScrollbar = opts.showScrollbar ?? true;
2281
+ this.focusable = true;
2282
+ }
2283
+ /** Set the total content height (in rows) */
2284
+ setContentHeight(h) {
2285
+ this._contentHeight = h;
2286
+ this._clampOffset();
2287
+ this.markDirty();
2288
+ }
2289
+ /** Get current scroll offset */
2290
+ get scrollOffset() {
2291
+ return this._scrollOffset;
2292
+ }
2293
+ /** Scroll by delta rows */
2294
+ scrollBy(delta) {
2295
+ this._scrollOffset += delta;
2296
+ this._clampOffset();
2297
+ this.markDirty();
2298
+ }
2299
+ /** Scroll to absolute offset */
2300
+ scrollTo(offset) {
2301
+ this._scrollOffset = offset;
2302
+ this._clampOffset();
2303
+ this.markDirty();
2304
+ }
2305
+ _clampOffset() {
2306
+ const viewHeight = this._rect.height;
2307
+ const maxOffset = Math.max(0, this._contentHeight - viewHeight);
2308
+ this._scrollOffset = Math.max(0, Math.min(this._scrollOffset, maxOffset));
2309
+ }
2310
+ /** Handle keyboard navigation */
2311
+ onKey(event) {
2312
+ switch (event.key) {
2313
+ case "ArrowUp":
2314
+ this.scrollBy(-1);
2315
+ break;
2316
+ case "ArrowDown":
2317
+ this.scrollBy(1);
2318
+ break;
2319
+ case "PageUp":
2320
+ this.scrollBy(-Math.max(1, this._rect.height - 1));
2321
+ break;
2322
+ case "PageDown":
2323
+ this.scrollBy(Math.max(1, this._rect.height - 1));
2324
+ break;
2325
+ }
2326
+ }
2327
+ render(screen) {
2328
+ if (this._style.visible === false) return;
2329
+ const shouldClip = true;
2330
+ if (shouldClip) screen.pushClip(this._rect);
2331
+ this._renderSelf(screen);
2332
+ this._renderBorder(screen);
2333
+ const rect = this._getContentRect();
2334
+ for (const child of this._children) {
2335
+ const origRect = { ...child.rect };
2336
+ child._rect = {
2337
+ x: origRect.x,
2338
+ y: origRect.y - this._scrollOffset,
2339
+ width: origRect.width,
2340
+ height: origRect.height
2341
+ };
2342
+ try {
2343
+ child.render(screen);
2344
+ } finally {
2345
+ child._rect = origRect;
2346
+ }
2347
+ }
2348
+ if (shouldClip) screen.popClip();
2349
+ if (this._showScrollbar && this._contentHeight > this._rect.height) {
2350
+ this._renderScrollbar(screen, rect);
2351
+ }
2352
+ }
2353
+ _renderScrollbar(screen, contentRect) {
2354
+ const { y, width, height } = contentRect;
2355
+ const scrollX = contentRect.x + width;
2356
+ if (scrollX >= this._rect.x + this._rect.width) return;
2357
+ const trackHeight = height;
2358
+ const thumbSize = Math.max(1, Math.round(height / this._contentHeight * trackHeight));
2359
+ const maxOffset = Math.max(1, this._contentHeight - height);
2360
+ const thumbPos = Math.round(this._scrollOffset / maxOffset * (trackHeight - thumbSize));
2361
+ const attrs = styleToCellAttrs20(this._style);
2362
+ const thumbChar = "\u2588";
2363
+ const trackChar = "\u2591";
2364
+ for (let i = 0; i < trackHeight; i++) {
2365
+ const isThumb = i >= thumbPos && i < thumbPos + thumbSize;
2366
+ screen.setCell(scrollX, y + i, {
2367
+ char: isThumb ? thumbChar : trackChar,
2368
+ ...attrs,
2369
+ dim: !isThumb
2370
+ });
2371
+ }
2372
+ }
2373
+ _renderSelf(_screen) {
2374
+ }
2375
+ };
2376
+
2377
+ // src/layout/Center.ts
2378
+ var Center = class extends Widget {
2379
+ _horizontal;
2380
+ _vertical;
2381
+ constructor(style = {}, opts = {}) {
2382
+ super(style);
2383
+ this._horizontal = opts.horizontal ?? true;
2384
+ this._vertical = opts.vertical ?? true;
2385
+ }
2386
+ _renderSelf(_screen) {
2387
+ }
2388
+ render(screen) {
2389
+ if (this._style.visible === false) return;
2390
+ const shouldClip = this._style.overflow !== "visible";
2391
+ if (shouldClip) screen.pushClip(this._rect);
2392
+ this._renderSelf(screen);
2393
+ this._renderBorder(screen);
2394
+ const content = this._getContentRect();
2395
+ for (const child of this._children) {
2396
+ const childRect = child.rect;
2397
+ const origRect = { ...childRect };
2398
+ let offsetX = content.x;
2399
+ let offsetY = content.y;
2400
+ if (this._horizontal) {
2401
+ offsetX = content.x + Math.max(0, Math.floor((content.width - childRect.width) / 2));
2402
+ }
2403
+ if (this._vertical) {
2404
+ offsetY = content.y + Math.max(0, Math.floor((content.height - childRect.height) / 2));
2405
+ }
2406
+ child._rect = {
2407
+ x: offsetX,
2408
+ y: offsetY,
2409
+ width: childRect.width,
2410
+ height: childRect.height
2411
+ };
2412
+ child.render(screen);
2413
+ child._rect = origRect;
2414
+ }
2415
+ if (shouldClip) screen.popClip();
2416
+ }
2417
+ };
2418
+
2419
+ // src/layout/Card.ts
2420
+ import { styleToCellAttrs as styleToCellAttrs21, stringWidth as stringWidth13 } from "@termuijs/core";
2421
+ var Card = class extends Widget {
2422
+ _title;
2423
+ _borderColor;
2424
+ constructor(style = {}, opts = {}) {
2425
+ super({
2426
+ border: "single",
2427
+ padding: 1,
2428
+ ...style
2429
+ });
2430
+ this._title = opts.title ?? "";
2431
+ this._borderColor = opts.borderColor;
2432
+ }
2433
+ setTitle(title) {
2434
+ this._title = title;
2435
+ this.markDirty();
2436
+ }
2437
+ _renderSelf(screen) {
2438
+ if (!this._title) return;
2439
+ const { x, y, width } = this._rect;
2440
+ if (width < 4) return;
2441
+ const attrs = styleToCellAttrs21(this._style);
2442
+ const fg = this._borderColor ?? attrs.fg;
2443
+ const titleText = ` ${this._title} `;
2444
+ const titleWidth = stringWidth13(titleText);
2445
+ const innerWidth = width - 2;
2446
+ if (titleWidth > innerWidth) return;
2447
+ const titleX = x + 1 + Math.floor((innerWidth - titleWidth) / 2);
2448
+ screen.writeString(titleX, y, titleText, { fg, bold: true });
2449
+ }
2450
+ };
2451
+
2452
+ // src/layout/Columns.ts
2453
+ var Columns = class extends Widget {
2454
+ _inner;
2455
+ constructor(style = {}, opts = {}) {
2456
+ super(style);
2457
+ this._inner = new Box({
2458
+ flexDirection: "row",
2459
+ gap: opts.gap ?? 1,
2460
+ width: "100%",
2461
+ height: "100%"
2462
+ });
2463
+ super.addChild(this._inner);
2464
+ }
2465
+ addChild(widget) {
2466
+ widget.setStyle({ flexGrow: 1 });
2467
+ this._inner.addChild(widget);
2468
+ }
2469
+ removeChild(widget) {
2470
+ this._inner.removeChild(widget);
2471
+ }
2472
+ clearChildren() {
2473
+ this._inner.clearChildren();
2474
+ }
2475
+ _renderSelf(_screen) {
2476
+ }
2477
+ };
2478
+
1223
2479
  // src/feedback/ProgressBar.ts
1224
- import { styleToCellAttrs as styleToCellAttrs13 } from "@termuijs/core";
2480
+ import { styleToCellAttrs as styleToCellAttrs22, caps as caps10 } from "@termuijs/core";
1225
2481
  var ProgressBar = class extends Widget {
1226
2482
  _value;
1227
2483
  _fillChar;
@@ -1233,8 +2489,8 @@ var ProgressBar = class extends Widget {
1233
2489
  constructor(style = {}, options = {}) {
1234
2490
  super({ height: 1, ...style });
1235
2491
  this._value = Math.max(0, Math.min(1, options.value ?? 0));
1236
- this._fillChar = options.fillChar ?? "\u2588";
1237
- this._emptyChar = options.emptyChar ?? "\u2591";
2492
+ this._fillChar = options.fillChar ?? (caps10.unicode ? "\u2588" : "#");
2493
+ this._emptyChar = options.emptyChar ?? (caps10.unicode ? "\u2591" : "-");
1238
2494
  this._fillColor = options.fillColor ?? { type: "named", name: "green" };
1239
2495
  this._showLabel = options.showLabel ?? true;
1240
2496
  this._labelFormat = options.labelFormat ?? "percent";
@@ -1252,7 +2508,7 @@ var ProgressBar = class extends Widget {
1252
2508
  const rect = this._getContentRect();
1253
2509
  const { x, y, width } = rect;
1254
2510
  if (width <= 0) return;
1255
- const attrs = styleToCellAttrs13(this._style);
2511
+ const attrs = styleToCellAttrs22(this._style);
1256
2512
  let label = "";
1257
2513
  if (this._showLabel) {
1258
2514
  if (this._labelFormat === "percent") {
@@ -1276,8 +2532,84 @@ var ProgressBar = class extends Widget {
1276
2532
  }
1277
2533
  };
1278
2534
 
2535
+ // src/feedback/MultiProgress.ts
2536
+ import { styleToCellAttrs as styleToCellAttrs23, caps as caps11, BLOCK } from "@termuijs/core";
2537
+ var MultiProgress = class extends Widget {
2538
+ _items;
2539
+ _labelWidth;
2540
+ _showValues;
2541
+ constructor(options, style = {}) {
2542
+ const height = options.items.length;
2543
+ super({ height, ...style });
2544
+ this._items = options.items.map((item) => ({
2545
+ ...item,
2546
+ value: Math.max(0, Math.min(1, item.value))
2547
+ }));
2548
+ this._labelWidth = options.labelWidth ?? 12;
2549
+ this._showValues = options.showValues ?? true;
2550
+ }
2551
+ /**
2552
+ * Replace all items and mark dirty
2553
+ */
2554
+ setItems(items) {
2555
+ this._items = items.map((item) => ({
2556
+ ...item,
2557
+ value: Math.max(0, Math.min(1, item.value))
2558
+ }));
2559
+ this.markDirty();
2560
+ }
2561
+ /**
2562
+ * Update a single item's value
2563
+ */
2564
+ updateItem(index, value) {
2565
+ if (index >= 0 && index < this._items.length) {
2566
+ this._items[index].value = Math.max(0, Math.min(1, value));
2567
+ this.markDirty();
2568
+ }
2569
+ }
2570
+ _renderSelf(screen) {
2571
+ const rect = this._getContentRect();
2572
+ const { x, y, width } = rect;
2573
+ if (width <= 0) return;
2574
+ const attrs = styleToCellAttrs23(this._style);
2575
+ const fillChar = caps11.unicode ? "\u2588" : BLOCK.full;
2576
+ const emptyChar = caps11.unicode ? "\u2591" : BLOCK.empty;
2577
+ for (let i = 0; i < this._items.length; i++) {
2578
+ const item = this._items[i];
2579
+ const rowY = y + i;
2580
+ let colX = x;
2581
+ const label = item.label.length > this._labelWidth ? item.label.substring(0, this._labelWidth) : item.label.padEnd(this._labelWidth);
2582
+ screen.writeString(colX, rowY, label, attrs);
2583
+ colX += this._labelWidth;
2584
+ if (colX < x + width) {
2585
+ screen.setCell(colX, rowY, { char: " ", ...attrs });
2586
+ colX++;
2587
+ }
2588
+ let percentLabel = "";
2589
+ if (this._showValues) {
2590
+ percentLabel = ` ${Math.round(item.value * 100)}%`;
2591
+ }
2592
+ const barWidth = Math.max(0, x + width - colX - percentLabel.length);
2593
+ const filled = Math.round(barWidth * item.value);
2594
+ const empty = barWidth - filled;
2595
+ const fillColor = item.color ?? { type: "named", name: "green" };
2596
+ for (let j = 0; j < filled; j++) {
2597
+ screen.setCell(colX + j, rowY, { char: fillChar, ...attrs, fg: fillColor });
2598
+ }
2599
+ for (let j = 0; j < empty; j++) {
2600
+ screen.setCell(colX + filled + j, rowY, { char: emptyChar, ...attrs, dim: true });
2601
+ }
2602
+ if (percentLabel) {
2603
+ const labelX = colX + barWidth;
2604
+ screen.writeString(labelX, rowY, percentLabel, { ...attrs, bold: true });
2605
+ }
2606
+ }
2607
+ }
2608
+ };
2609
+
1279
2610
  // src/feedback/Spinner.ts
1280
- import { styleToCellAttrs as styleToCellAttrs14 } from "@termuijs/core";
2611
+ import { styleToCellAttrs as styleToCellAttrs24, caps as caps12, BRAILLE_SPIN } from "@termuijs/core";
2612
+ import { timerPoolSubscribe as timerPoolSubscribe2 } from "@termuijs/motion";
1281
2613
  var SPINNER_FRAMES = {
1282
2614
  dots: {
1283
2615
  frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
@@ -1320,6 +2652,7 @@ var Spinner = class extends Widget {
1320
2652
  _color;
1321
2653
  _lastTick = 0;
1322
2654
  _elapsed = 0;
2655
+ _timerUnsub;
1323
2656
  constructor(style = {}, options = {}) {
1324
2657
  super({ height: 1, ...style });
1325
2658
  const spinnerDef = typeof options.spinner === "string" ? SPINNER_FRAMES[options.spinner] ?? SPINNER_FRAMES.dots : options.spinner ?? SPINNER_FRAMES.dots;
@@ -1327,6 +2660,10 @@ var Spinner = class extends Widget {
1327
2660
  this._interval = spinnerDef.interval;
1328
2661
  this._label = options.label ?? "";
1329
2662
  this._color = options.color ?? { type: "named", name: "cyan" };
2663
+ if (!caps12.unicode && this._frames.some((f) => f.codePointAt(0) > 127)) {
2664
+ this._frames = Array.from(BRAILLE_SPIN);
2665
+ this._interval = 130;
2666
+ }
1330
2667
  }
1331
2668
  /** Update the spinner label */
1332
2669
  setLabel(label) {
@@ -1337,17 +2674,33 @@ var Spinner = class extends Widget {
1337
2674
  * Call this with a delta (ms) from the render loop.
1338
2675
  */
1339
2676
  tick(deltaMs) {
2677
+ if (!caps12.motion) return;
1340
2678
  this._elapsed += deltaMs;
1341
2679
  if (this._elapsed >= this._interval) {
1342
2680
  this._frameIndex = (this._frameIndex + 1) % this._frames.length;
1343
2681
  this._elapsed = 0;
1344
2682
  }
1345
2683
  }
2684
+ /** Lifecycle: start the frame-advance timer (only when motion is enabled). */
2685
+ mount() {
2686
+ super.mount();
2687
+ if (!caps12.motion) return;
2688
+ this._timerUnsub = timerPoolSubscribe2(this._interval, () => {
2689
+ this._frameIndex = (this._frameIndex + 1) % this._frames.length;
2690
+ this.markDirty();
2691
+ });
2692
+ }
2693
+ /** Lifecycle: stop the frame-advance timer. */
2694
+ unmount() {
2695
+ this._timerUnsub?.();
2696
+ this._timerUnsub = void 0;
2697
+ super.unmount();
2698
+ }
1346
2699
  _renderSelf(screen) {
1347
2700
  const rect = this._getContentRect();
1348
2701
  const { x, y, width } = rect;
1349
2702
  if (width <= 0) return;
1350
- const attrs = styleToCellAttrs14(this._style);
2703
+ const attrs = styleToCellAttrs24(this._style);
1351
2704
  const frame = this._frames[this._frameIndex];
1352
2705
  screen.writeString(x, y, frame, { ...attrs, fg: this._color });
1353
2706
  if (this._label) {
@@ -1440,22 +2793,754 @@ var Scrollbar = class extends Widget {
1440
2793
  }
1441
2794
  }
1442
2795
  };
2796
+
2797
+ // src/feedback/Skeleton.ts
2798
+ import { caps as caps13 } from "@termuijs/core";
2799
+ import { timerPoolSubscribe as timerPoolSubscribe3 } from "@termuijs/motion";
2800
+ var Skeleton = class extends Widget {
2801
+ _frame = 0;
2802
+ _shimmerPos = 0;
2803
+ _unsub;
2804
+ _chars;
2805
+ _variant;
2806
+ _intervalMs;
2807
+ constructor(style = {}, options = {}) {
2808
+ super(style);
2809
+ this._variant = options.variant ?? "pulse";
2810
+ this._intervalMs = options.intervalMs ?? 600;
2811
+ const defaultChars = caps13.unicode ? ["\u2591", "\u2592"] : ["-", "#"];
2812
+ this._chars = options.chars ?? defaultChars;
2813
+ if (caps13.motion) {
2814
+ this._unsub = timerPoolSubscribe3(this._intervalMs, () => {
2815
+ this._frame = 1 - this._frame;
2816
+ if (this._variant === "shimmer") {
2817
+ this._shimmerPos++;
2818
+ }
2819
+ this.markDirty();
2820
+ });
2821
+ }
2822
+ }
2823
+ unmount() {
2824
+ this._unsub?.();
2825
+ this._unsub = void 0;
2826
+ super.unmount();
2827
+ }
2828
+ _renderSelf(screen) {
2829
+ const rect = this._getContentRect();
2830
+ const { x, y, width, height } = rect;
2831
+ if (width <= 0 || height <= 0) return;
2832
+ if (this._variant === "pulse") {
2833
+ const char = this._chars[this._frame];
2834
+ const dim = this._frame === 0;
2835
+ for (let row = y; row < y + height; row++) {
2836
+ for (let col = x; col < x + width; col++) {
2837
+ screen.setCell(col, row, { char, dim, bold: false });
2838
+ }
2839
+ }
2840
+ } else {
2841
+ const bandWidth = Math.max(1, Math.floor(width * 0.2));
2842
+ const totalPositions = width + bandWidth;
2843
+ const bandStart = this._shimmerPos % totalPositions;
2844
+ for (let row = y; row < y + height; row++) {
2845
+ for (let colOffset = 0; colOffset < width; colOffset++) {
2846
+ const col = x + colOffset;
2847
+ const inBand = colOffset >= bandStart && colOffset < bandStart + bandWidth;
2848
+ const char = inBand ? this._chars[1] : this._chars[0];
2849
+ screen.setCell(col, row, { char, dim: !inBand, bold: false });
2850
+ }
2851
+ }
2852
+ }
2853
+ }
2854
+ };
2855
+
2856
+ // src/feedback/StatusMessage.ts
2857
+ import { styleToCellAttrs as styleToCellAttrs25, caps as caps14 } from "@termuijs/core";
2858
+ var ICONS_UNICODE = {
2859
+ success: "\u2713",
2860
+ error: "\u2717",
2861
+ warning: "\u26A0",
2862
+ info: "\u2139"
2863
+ };
2864
+ var ICONS_ASCII = {
2865
+ success: "+",
2866
+ error: "x",
2867
+ warning: "!",
2868
+ info: "i"
2869
+ };
2870
+ var COLORS = {
2871
+ success: { type: "named", name: "green" },
2872
+ error: { type: "named", name: "red" },
2873
+ warning: { type: "named", name: "yellow" },
2874
+ info: { type: "named", name: "cyan" }
2875
+ };
2876
+ var StatusMessage = class extends Widget {
2877
+ _message;
2878
+ _variant;
2879
+ _icon;
2880
+ constructor(message, style = {}, opts = {}) {
2881
+ super({ height: 1, ...style });
2882
+ this._message = message;
2883
+ this._variant = opts.variant ?? "info";
2884
+ this._icon = opts.icon;
2885
+ }
2886
+ setMessage(message) {
2887
+ this._message = message;
2888
+ this.markDirty();
2889
+ }
2890
+ setVariant(variant) {
2891
+ this._variant = variant;
2892
+ this.markDirty();
2893
+ }
2894
+ _renderSelf(screen) {
2895
+ const rect = this._getContentRect();
2896
+ const { x, y, width } = rect;
2897
+ if (width <= 0) return;
2898
+ const attrs = styleToCellAttrs25(this._style);
2899
+ const color = COLORS[this._variant];
2900
+ const iconMap = caps14.unicode ? ICONS_UNICODE : ICONS_ASCII;
2901
+ const icon = this._icon ?? iconMap[this._variant];
2902
+ screen.writeString(x, y, icon, { ...attrs, fg: color, bold: true });
2903
+ const msgX = x + icon.length + 1;
2904
+ const remaining = width - icon.length - 1;
2905
+ if (remaining > 0) {
2906
+ screen.writeString(msgX, y, this._message.slice(0, remaining), { ...attrs, fg: color });
2907
+ }
2908
+ }
2909
+ };
2910
+
2911
+ // src/feedback/Banner.ts
2912
+ import { styleToCellAttrs as styleToCellAttrs26, getBorderChars as getBorderChars2 } from "@termuijs/core";
2913
+ var VARIANT_COLORS = {
2914
+ success: { type: "named", name: "green" },
2915
+ error: { type: "named", name: "red" },
2916
+ warning: { type: "named", name: "yellow" },
2917
+ info: { type: "named", name: "cyan" }
2918
+ };
2919
+ var Banner = class extends Widget {
2920
+ _variant;
2921
+ _title;
2922
+ _body;
2923
+ constructor(style = {}, opts = {}) {
2924
+ super({
2925
+ width: "100%",
2926
+ padding: 1,
2927
+ ...style
2928
+ });
2929
+ this._variant = opts.variant ?? "info";
2930
+ this._title = opts.title ?? "";
2931
+ this._body = opts.body ?? "";
2932
+ }
2933
+ setTitle(title) {
2934
+ this._title = title;
2935
+ this.markDirty();
2936
+ }
2937
+ setBody(body) {
2938
+ this._body = body;
2939
+ this.markDirty();
2940
+ }
2941
+ setVariant(variant) {
2942
+ this._variant = variant;
2943
+ this.markDirty();
2944
+ }
2945
+ _renderSelf(screen) {
2946
+ const { x, y, width, height } = this._rect;
2947
+ if (width < 2 || height < 2) return;
2948
+ const attrs = styleToCellAttrs26(this._style);
2949
+ const color = VARIANT_COLORS[this._variant];
2950
+ const fg = color;
2951
+ const borderChars = getBorderChars2("single");
2952
+ if (borderChars) {
2953
+ screen.setCell(x, y, { char: borderChars.topLeft, fg });
2954
+ for (let c = 1; c < width - 1; c++) {
2955
+ screen.setCell(x + c, y, { char: borderChars.top, fg });
2956
+ }
2957
+ screen.setCell(x + width - 1, y, { char: borderChars.topRight, fg });
2958
+ screen.setCell(x, y + height - 1, { char: borderChars.bottomLeft, fg });
2959
+ for (let c = 1; c < width - 1; c++) {
2960
+ screen.setCell(x + c, y + height - 1, { char: borderChars.bottom, fg });
2961
+ }
2962
+ screen.setCell(x + width - 1, y + height - 1, { char: borderChars.bottomRight, fg });
2963
+ for (let r = 1; r < height - 1; r++) {
2964
+ screen.setCell(x, y + r, { char: borderChars.left, fg });
2965
+ screen.setCell(x + width - 1, y + r, { char: borderChars.right, fg });
2966
+ }
2967
+ }
2968
+ const cx = x + 2;
2969
+ const cy = y + 2;
2970
+ const contentWidth = Math.max(0, width - 4);
2971
+ const contentHeight = Math.max(0, height - 4);
2972
+ let row = 0;
2973
+ if (this._title && row < contentHeight) {
2974
+ screen.writeString(cx, cy + row, this._title.slice(0, contentWidth), {
2975
+ ...attrs,
2976
+ fg: color,
2977
+ bold: true
2978
+ });
2979
+ row++;
2980
+ }
2981
+ if (this._body) {
2982
+ const lines = this._body.split("\n");
2983
+ for (const line of lines) {
2984
+ if (row >= contentHeight) break;
2985
+ screen.writeString(cx, cy + row, line.slice(0, contentWidth), {
2986
+ ...attrs,
2987
+ fg: color
2988
+ });
2989
+ row++;
2990
+ }
2991
+ }
2992
+ }
2993
+ };
2994
+
2995
+ // src/data/KeyValue.ts
2996
+ import { styleToCellAttrs as styleToCellAttrs27, stringWidth as stringWidth14 } from "@termuijs/core";
2997
+ var KeyValue = class extends Widget {
2998
+ _pairs;
2999
+ _separator;
3000
+ _keyColor;
3001
+ _valueColor;
3002
+ constructor(pairs, style = {}, opts = {}) {
3003
+ super(style);
3004
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([key, value]) => ({ key, value }));
3005
+ this._separator = opts.separator ?? ": ";
3006
+ this._keyColor = opts.keyColor;
3007
+ this._valueColor = opts.valueColor;
3008
+ }
3009
+ setPairs(pairs) {
3010
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([key, value]) => ({ key, value }));
3011
+ this.markDirty();
3012
+ }
3013
+ _renderSelf(screen) {
3014
+ const rect = this._getContentRect();
3015
+ const { x, y, width, height } = rect;
3016
+ if (width <= 0 || height <= 0 || this._pairs.length === 0) return;
3017
+ const attrs = styleToCellAttrs27(this._style);
3018
+ let maxKeyWidth = 0;
3019
+ for (const pair of this._pairs) {
3020
+ const w = stringWidth14(pair.key);
3021
+ if (w > maxKeyWidth) maxKeyWidth = w;
3022
+ }
3023
+ const sepWidth = stringWidth14(this._separator);
3024
+ for (let i = 0; i < this._pairs.length && i < height; i++) {
3025
+ const pair = this._pairs[i];
3026
+ if (!pair) continue;
3027
+ const keyWidth = stringWidth14(pair.key);
3028
+ const keyX = x + (maxKeyWidth - keyWidth);
3029
+ const sepX = x + maxKeyWidth;
3030
+ const valX = sepX + sepWidth;
3031
+ const valWidth = Math.max(0, width - maxKeyWidth - sepWidth);
3032
+ screen.writeString(keyX, y + i, pair.key, {
3033
+ ...attrs,
3034
+ fg: this._keyColor ?? attrs.fg,
3035
+ bold: true
3036
+ });
3037
+ screen.writeString(sepX, y + i, this._separator, { ...attrs, dim: true });
3038
+ if (valWidth > 0) {
3039
+ screen.writeString(valX, y + i, pair.value.slice(0, valWidth), {
3040
+ ...attrs,
3041
+ fg: this._valueColor ?? attrs.fg
3042
+ });
3043
+ }
3044
+ }
3045
+ }
3046
+ };
3047
+
3048
+ // src/data/Sidebar.ts
3049
+ import { styleToCellAttrs as styleToCellAttrs28, stringWidth as stringWidth15 } from "@termuijs/core";
3050
+ var Sidebar = class extends Widget {
3051
+ _items;
3052
+ _collapsed;
3053
+ _collapsedWidth;
3054
+ _activeColor;
3055
+ _badgeColor;
3056
+ constructor(items, style = {}, opts = {}) {
3057
+ super(style);
3058
+ this._items = items;
3059
+ this._collapsed = opts.collapsed ?? false;
3060
+ this._collapsedWidth = opts.collapsedWidth ?? 3;
3061
+ this._activeColor = opts.activeColor ?? { type: "named", name: "cyan" };
3062
+ this._badgeColor = opts.badgeColor ?? { type: "named", name: "yellow" };
3063
+ }
3064
+ setItems(items) {
3065
+ this._items = items;
3066
+ this.markDirty();
3067
+ }
3068
+ setCollapsed(collapsed) {
3069
+ this._collapsed = collapsed;
3070
+ this.markDirty();
3071
+ }
3072
+ toggle() {
3073
+ this._collapsed = !this._collapsed;
3074
+ this.markDirty();
3075
+ }
3076
+ get isCollapsed() {
3077
+ return this._collapsed;
3078
+ }
3079
+ _renderSelf(screen) {
3080
+ const rect = this._getContentRect();
3081
+ const { x, y, width, height } = rect;
3082
+ if (width <= 0 || height <= 0) return;
3083
+ const attrs = styleToCellAttrs28(this._style);
3084
+ for (let i = 0; i < this._items.length && i < height; i++) {
3085
+ const item = this._items[i];
3086
+ if (!item) continue;
3087
+ const isActive = item.active ?? false;
3088
+ const fg = isActive ? this._activeColor : attrs.fg;
3089
+ if (this._collapsed) {
3090
+ const char = item.label.charAt(0) || " ";
3091
+ screen.writeString(x, y + i, char, { ...attrs, fg, bold: isActive });
3092
+ } else {
3093
+ const prefix = isActive ? "\u25B6 " : " ";
3094
+ const prefixWidth = stringWidth15(prefix);
3095
+ screen.writeString(x, y + i, prefix, { ...attrs, fg });
3096
+ const badgeText = item.badge ? ` [${item.badge}]` : "";
3097
+ const badgeWidth = stringWidth15(badgeText);
3098
+ const labelWidth = Math.max(0, width - prefixWidth - badgeWidth);
3099
+ const label = item.label.slice(0, labelWidth);
3100
+ screen.writeString(x + prefixWidth, y + i, label, {
3101
+ ...attrs,
3102
+ fg,
3103
+ bold: isActive
3104
+ });
3105
+ if (item.badge && badgeWidth > 0) {
3106
+ const badgeX = x + width - badgeWidth;
3107
+ screen.writeString(badgeX, y + i, badgeText, {
3108
+ ...attrs,
3109
+ fg: this._badgeColor
3110
+ });
3111
+ }
3112
+ }
3113
+ }
3114
+ }
3115
+ };
3116
+
3117
+ // src/data/LineChart.ts
3118
+ import { styleToCellAttrs as styleToCellAttrs29, caps as caps15 } from "@termuijs/core";
3119
+ var POINT_CHAR_UNICODE = "\u25CF";
3120
+ var POINT_CHAR_ASCII = "*";
3121
+ var LineChart = class extends Widget {
3122
+ _data;
3123
+ _color;
3124
+ _showYAxis;
3125
+ _showXAxis;
3126
+ _yLabel;
3127
+ _max;
3128
+ _min;
3129
+ constructor(data, style = {}, opts = {}) {
3130
+ super(style);
3131
+ this._data = data;
3132
+ this._color = opts.color ?? { type: "named", name: "cyan" };
3133
+ this._showYAxis = opts.showYAxis ?? false;
3134
+ this._showXAxis = opts.showXAxis ?? false;
3135
+ this._yLabel = opts.yLabel ?? "";
3136
+ this._max = opts.max;
3137
+ this._min = opts.min;
3138
+ }
3139
+ setData(data) {
3140
+ this._data = data;
3141
+ this.markDirty();
3142
+ }
3143
+ pushValue(value) {
3144
+ this._data.push(value);
3145
+ this.markDirty();
3146
+ }
3147
+ _renderSelf(screen) {
3148
+ const rect = this._getContentRect();
3149
+ let { x, y, width, height } = rect;
3150
+ if (width <= 0 || height <= 0 || this._data.length === 0) return;
3151
+ const attrs = styleToCellAttrs29(this._style);
3152
+ const plotHeight = this._showXAxis ? Math.max(1, height - 1) : height;
3153
+ const yAxisWidth = this._showYAxis ? 5 : 0;
3154
+ const plotWidth = Math.max(1, width - yAxisWidth);
3155
+ const plotX = x + yAxisWidth;
3156
+ const min = this._min ?? Math.min(...this._data);
3157
+ const max = this._max ?? Math.max(...this._data);
3158
+ const range = max - min || 1;
3159
+ const samples = [];
3160
+ for (let col = 0; col < plotWidth; col++) {
3161
+ const idx = Math.floor(col / plotWidth * this._data.length);
3162
+ const val = this._data[Math.min(idx, this._data.length - 1)];
3163
+ samples.push(val ?? min);
3164
+ }
3165
+ const toRow = (v) => {
3166
+ const norm = (v - min) / range;
3167
+ return Math.max(0, Math.min(plotHeight - 1, Math.round((1 - norm) * (plotHeight - 1))));
3168
+ };
3169
+ if (this._showYAxis && yAxisWidth > 0) {
3170
+ for (let row = 0; row < plotHeight; row++) {
3171
+ const v = plotHeight > 1 ? max - row / (plotHeight - 1) * range : max;
3172
+ if (row === 0 || row === plotHeight - 1) {
3173
+ const label = v.toFixed(0).padStart(yAxisWidth - 1, " ");
3174
+ screen.writeString(x, y + row, label + "\u2524", { ...attrs, dim: true });
3175
+ } else {
3176
+ screen.writeString(x + yAxisWidth - 1, y + row, "\u2502", { ...attrs, dim: true });
3177
+ }
3178
+ }
3179
+ }
3180
+ const pointChar = caps15.unicode ? POINT_CHAR_UNICODE : POINT_CHAR_ASCII;
3181
+ let prevRow = null;
3182
+ for (let col = 0; col < samples.length; col++) {
3183
+ const val = samples[col];
3184
+ if (val === void 0) continue;
3185
+ const row = toRow(val);
3186
+ if (prevRow !== null && Math.abs(row - prevRow) > 1) {
3187
+ const top = Math.min(prevRow, row) + 1;
3188
+ const bottom = Math.max(prevRow, row);
3189
+ for (let r = top; r < bottom; r++) {
3190
+ screen.setCell(plotX + col, y + r, { char: "\u2502", fg: this._color, dim: true });
3191
+ }
3192
+ }
3193
+ screen.setCell(plotX + col, y + row, { char: pointChar, fg: this._color });
3194
+ prevRow = row;
3195
+ }
3196
+ if (this._showXAxis) {
3197
+ const axisY = y + height - 1;
3198
+ for (let col = 0; col < plotWidth; col++) {
3199
+ screen.setCell(plotX + col, axisY, { char: "\u2500", ...attrs, dim: true });
3200
+ }
3201
+ if (yAxisWidth > 0) {
3202
+ screen.setCell(plotX - 1, axisY, { char: "\u2514", ...attrs, dim: true });
3203
+ }
3204
+ }
3205
+ }
3206
+ };
3207
+
3208
+ // src/data/HeatMap.ts
3209
+ import { styleToCellAttrs as styleToCellAttrs30, caps as caps16 } from "@termuijs/core";
3210
+ var SHADE_CHARS_UNICODE = ["\u2591", "\u2592", "\u2593", "\u2588"];
3211
+ var SHADE_CHARS_ASCII = [".", ":", "+", "#"];
3212
+ var HeatMap = class extends Widget {
3213
+ _matrix;
3214
+ _highColor;
3215
+ _lowColor;
3216
+ _rowLabels;
3217
+ _colLabels;
3218
+ constructor(matrix, style = {}, opts = {}) {
3219
+ super(style);
3220
+ this._matrix = matrix;
3221
+ this._highColor = opts.highColor ?? { type: "named", name: "red" };
3222
+ this._lowColor = opts.lowColor ?? { type: "named", name: "brightBlack" };
3223
+ this._rowLabels = opts.rowLabels ?? [];
3224
+ this._colLabels = opts.colLabels ?? [];
3225
+ }
3226
+ setMatrix(matrix) {
3227
+ this._matrix = matrix;
3228
+ this.markDirty();
3229
+ }
3230
+ _renderSelf(screen) {
3231
+ const rect = this._getContentRect();
3232
+ const { x, y, width, height } = rect;
3233
+ if (width <= 0 || height <= 0 || this._matrix.length === 0) return;
3234
+ const attrs = styleToCellAttrs30(this._style);
3235
+ const shadeChars = caps16.unicode ? SHADE_CHARS_UNICODE : SHADE_CHARS_ASCII;
3236
+ const labelWidth = this._rowLabels.length > 0 ? Math.max(...this._rowLabels.map((l) => l.length)) + 1 : 0;
3237
+ let globalMin = Infinity;
3238
+ let globalMax = -Infinity;
3239
+ for (const row of this._matrix) {
3240
+ for (const val of row) {
3241
+ if (val < globalMin) globalMin = val;
3242
+ if (val > globalMax) globalMax = val;
3243
+ }
3244
+ }
3245
+ const range = globalMax - globalMin || 1;
3246
+ let startRow = 0;
3247
+ if (this._colLabels.length > 0) {
3248
+ for (let col = 0; col < this._colLabels.length; col++) {
3249
+ const cx = x + labelWidth + col;
3250
+ if (cx >= x + width) break;
3251
+ const label = (this._colLabels[col] ?? "").charAt(0);
3252
+ screen.setCell(cx, y, { char: label, ...attrs, dim: true });
3253
+ }
3254
+ startRow = 1;
3255
+ }
3256
+ for (let r = 0; r < this._matrix.length; r++) {
3257
+ const rowY = y + startRow + r;
3258
+ if (rowY >= y + height) break;
3259
+ if (this._rowLabels[r]) {
3260
+ const label = this._rowLabels[r].slice(0, labelWidth - 1).padEnd(labelWidth - 1, " ");
3261
+ screen.writeString(x, rowY, label + " ", { ...attrs, dim: true });
3262
+ }
3263
+ const row = this._matrix[r];
3264
+ if (!row) continue;
3265
+ for (let col = 0; col < row.length; col++) {
3266
+ const cx = x + labelWidth + col;
3267
+ if (cx >= x + width) break;
3268
+ const val = row[col] ?? 0;
3269
+ const norm = (val - globalMin) / range;
3270
+ const level = Math.min(3, Math.floor(norm * 4));
3271
+ const char = shadeChars[level] ?? shadeChars[0];
3272
+ const fg = norm >= 0.75 ? this._highColor : this._lowColor;
3273
+ screen.setCell(cx, rowY, { char, fg });
3274
+ }
3275
+ }
3276
+ }
3277
+ };
3278
+
3279
+ // src/data/Definition.ts
3280
+ import { styleToCellAttrs as styleToCellAttrs31 } from "@termuijs/core";
3281
+ var Definition = class extends Widget {
3282
+ _pairs;
3283
+ _indent;
3284
+ _spacing;
3285
+ _termColor;
3286
+ _definitionColor;
3287
+ constructor(pairs, style = {}, opts = {}) {
3288
+ super(style);
3289
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([term, definition]) => ({ term, definition }));
3290
+ this._indent = opts.indent ?? 2;
3291
+ this._spacing = opts.spacing ?? true;
3292
+ this._termColor = opts.termColor;
3293
+ this._definitionColor = opts.definitionColor;
3294
+ }
3295
+ setPairs(pairs) {
3296
+ this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([term, definition]) => ({ term, definition }));
3297
+ this.markDirty();
3298
+ }
3299
+ _renderSelf(screen) {
3300
+ const rect = this._getContentRect();
3301
+ const { x, y, width, height } = rect;
3302
+ if (width <= 0 || height <= 0 || this._pairs.length === 0) return;
3303
+ const attrs = styleToCellAttrs31(this._style);
3304
+ const indent = Math.min(this._indent, width - 1);
3305
+ let row = 0;
3306
+ for (const pair of this._pairs) {
3307
+ if (row >= height) break;
3308
+ screen.writeString(x, y + row, pair.term.slice(0, width), {
3309
+ ...attrs,
3310
+ fg: this._termColor ?? attrs.fg,
3311
+ bold: true
3312
+ });
3313
+ row++;
3314
+ if (row >= height) break;
3315
+ const defWidth = Math.max(1, width - indent);
3316
+ const words = pair.definition.split(" ");
3317
+ let line = "";
3318
+ for (const word of words) {
3319
+ if (line.length === 0) {
3320
+ line = word;
3321
+ } else if (line.length + 1 + word.length <= defWidth) {
3322
+ line += " " + word;
3323
+ } else {
3324
+ if (row >= height) break;
3325
+ screen.writeString(x + indent, y + row, line, {
3326
+ ...attrs,
3327
+ fg: this._definitionColor ?? attrs.fg
3328
+ });
3329
+ row++;
3330
+ line = word;
3331
+ }
3332
+ }
3333
+ if (line && row < height) {
3334
+ screen.writeString(x + indent, y + row, line, {
3335
+ ...attrs,
3336
+ fg: this._definitionColor ?? attrs.fg
3337
+ });
3338
+ row++;
3339
+ }
3340
+ if (this._spacing && row < height) {
3341
+ row++;
3342
+ }
3343
+ }
3344
+ }
3345
+ };
3346
+
3347
+ // src/display/BigText.ts
3348
+ import { styleToCellAttrs as styleToCellAttrs32 } from "@termuijs/core";
3349
+ var CHAR_MAP = {
3350
+ "A": [" # ", "# #", "###", "# #", "# #"],
3351
+ "B": ["## ", "# #", "## ", "# #", "## "],
3352
+ "C": [" ##", "# ", "# ", "# ", " ##"],
3353
+ "D": ["## ", "# #", "# #", "# #", "## "],
3354
+ "E": ["###", "# ", "## ", "# ", "###"],
3355
+ "F": ["###", "# ", "## ", "# ", "# "],
3356
+ "G": [" ##", "# ", "# #", "# #", " ##"],
3357
+ "H": ["# #", "# #", "###", "# #", "# #"],
3358
+ "I": ["###", " # ", " # ", " # ", "###"],
3359
+ "J": ["###", " #", " #", "# #", " # "],
3360
+ "K": ["# #", "## ", "# ", "## ", "# #"],
3361
+ "L": ["# ", "# ", "# ", "# ", "###"],
3362
+ "M": ["# #", "###", "# #", "# #", "# #"],
3363
+ "N": ["# #", "## ", "# #", "# #", "# #"],
3364
+ "O": [" # ", "# #", "# #", "# #", " # "],
3365
+ "P": ["## ", "# #", "## ", "# ", "# "],
3366
+ "Q": [" # ", "# #", "# #", "##:", " ##"],
3367
+ "R": ["## ", "# #", "## ", "## ", "# #"],
3368
+ "S": [" ##", "# ", " # ", " #", "## "],
3369
+ "T": ["###", " # ", " # ", " # ", " # "],
3370
+ "U": ["# #", "# #", "# #", "# #", "###"],
3371
+ "V": ["# #", "# #", "# #", "# #", " # "],
3372
+ "W": ["# #", "# #", "# #", "###", "# #"],
3373
+ "X": ["# #", "# #", " # ", "# #", "# #"],
3374
+ "Y": ["# #", "# #", " # ", " # ", " # "],
3375
+ "Z": ["###", " #", " # ", "# ", "###"],
3376
+ "0": [" # ", "# #", "# #", "# #", " # "],
3377
+ "1": [" # ", "## ", " # ", " # ", "###"],
3378
+ "2": [" # ", "# #", " # ", "# ", "###"],
3379
+ "3": ["## ", " #", " ##", " #", "## "],
3380
+ "4": ["# #", "# #", "###", " #", " #"],
3381
+ "5": ["###", "# ", "## ", " #", "## "],
3382
+ "6": [" # ", "# ", "## ", "# #", " # "],
3383
+ "7": ["###", " #", " # ", "# ", "# "],
3384
+ "8": [" # ", "# #", " # ", "# #", " # "],
3385
+ "9": [" # ", "# #", " ##", " #", " # "],
3386
+ " ": [" ", " ", " ", " ", " "],
3387
+ "!": [" # ", " # ", " # ", " ", " # "],
3388
+ ".": [" ", " ", " ", " ", " # "],
3389
+ "-": [" ", " ", "###", " ", " "],
3390
+ ":": [" ", " # ", " ", " # ", " "]
3391
+ };
3392
+ var CHAR_HEIGHT = 5;
3393
+ var CHAR_WIDTH = 3;
3394
+ var BigText = class extends Widget {
3395
+ _text;
3396
+ _color;
3397
+ constructor(text, style = {}, opts = {}) {
3398
+ super(style);
3399
+ this._text = text.toUpperCase();
3400
+ this._color = opts.color ?? { type: "named", name: "white" };
3401
+ }
3402
+ setText(text) {
3403
+ this._text = text.toUpperCase();
3404
+ this.markDirty();
3405
+ }
3406
+ _renderSelf(screen) {
3407
+ const rect = this._getContentRect();
3408
+ const { x, y, width, height } = rect;
3409
+ if (width <= 0 || height <= 0) return;
3410
+ const attrs = styleToCellAttrs32(this._style);
3411
+ const fg = this._color;
3412
+ let curX = x;
3413
+ for (const ch of this._text) {
3414
+ const glyph = CHAR_MAP[ch] ?? ["# #", "# #", "# #", "# #", "# #"];
3415
+ const glyphWidth = glyph[0]?.length ?? CHAR_WIDTH;
3416
+ if (curX + glyphWidth > x + width) break;
3417
+ for (let row = 0; row < CHAR_HEIGHT && row < height; row++) {
3418
+ const rowStr = glyph[row] ?? "";
3419
+ for (let col = 0; col < rowStr.length; col++) {
3420
+ if (rowStr[col] !== " ") {
3421
+ screen.setCell(curX + col, y + row, { char: "\u2588", ...attrs, fg });
3422
+ }
3423
+ }
3424
+ }
3425
+ curX += glyphWidth + 1;
3426
+ }
3427
+ }
3428
+ };
3429
+
3430
+ // src/display/Gradient.ts
3431
+ import { styleToCellAttrs as styleToCellAttrs33, caps as caps17, parseColor } from "@termuijs/core";
3432
+ function hexToRgb(hex) {
3433
+ const clean = hex.replace("#", "");
3434
+ if (clean.length !== 6) return null;
3435
+ const r = parseInt(clean.slice(0, 2), 16);
3436
+ const g = parseInt(clean.slice(2, 4), 16);
3437
+ const b = parseInt(clean.slice(4, 6), 16);
3438
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
3439
+ return [r, g, b];
3440
+ }
3441
+ function lerpRgb(a, b, t) {
3442
+ return [
3443
+ Math.round(a[0] + (b[0] - a[0]) * t),
3444
+ Math.round(a[1] + (b[1] - a[1]) * t),
3445
+ Math.round(a[2] + (b[2] - a[2]) * t)
3446
+ ];
3447
+ }
3448
+ var Gradient = class extends Widget {
3449
+ _text;
3450
+ _startColor;
3451
+ _endColor;
3452
+ _align;
3453
+ constructor(text, style = {}, opts = {}) {
3454
+ super({ height: 1, ...style });
3455
+ this._text = text;
3456
+ this._startColor = opts.startColor ?? "#ff0000";
3457
+ this._endColor = opts.endColor ?? "#0000ff";
3458
+ this._align = opts.align ?? "left";
3459
+ }
3460
+ setText(text) {
3461
+ this._text = text;
3462
+ this.markDirty();
3463
+ }
3464
+ setColors(start, end) {
3465
+ this._startColor = start;
3466
+ this._endColor = end;
3467
+ this.markDirty();
3468
+ }
3469
+ _renderSelf(screen) {
3470
+ const rect = this._getContentRect();
3471
+ const { x, y, width } = rect;
3472
+ if (width <= 0 || !this._text) return;
3473
+ const attrs = styleToCellAttrs33(this._style);
3474
+ if (!caps17.color) {
3475
+ screen.writeString(x, y, this._text.slice(0, width), attrs);
3476
+ return;
3477
+ }
3478
+ const startRgb = hexToRgb(this._startColor);
3479
+ const endRgb = hexToRgb(this._endColor);
3480
+ const chars = Array.from(this._text).slice(0, width);
3481
+ const len = chars.length;
3482
+ let offsetX = 0;
3483
+ if (this._align === "center") offsetX = Math.floor((width - len) / 2);
3484
+ else if (this._align === "right") offsetX = width - len;
3485
+ offsetX = Math.max(0, offsetX);
3486
+ for (let i = 0; i < chars.length; i++) {
3487
+ const t = len > 1 ? i / (len - 1) : 0;
3488
+ let fg;
3489
+ if (startRgb && endRgb) {
3490
+ const [r, g, b] = lerpRgb(startRgb, endRgb, t);
3491
+ fg = { type: "rgb", r, g, b };
3492
+ } else if (startRgb) {
3493
+ fg = parseColor(this._startColor) ?? attrs.fg;
3494
+ } else {
3495
+ fg = attrs.fg;
3496
+ }
3497
+ screen.setCell(x + offsetX + i, y, { char: chars[i] ?? " ", ...attrs, fg });
3498
+ }
3499
+ }
3500
+ };
1443
3501
  export {
3502
+ Banner,
1444
3503
  BarChart,
3504
+ BigText,
1445
3505
  Box,
3506
+ Card,
3507
+ Center,
3508
+ ChatMessage,
3509
+ Columns,
3510
+ CommandPalette,
3511
+ Definition,
3512
+ DiffView,
1446
3513
  Gauge,
3514
+ Gradient,
3515
+ Grid,
3516
+ HeatMap,
3517
+ JSONView,
3518
+ KeyValue,
3519
+ LineChart,
1447
3520
  List,
1448
3521
  LogView,
3522
+ MultiProgress,
1449
3523
  ProgressBar,
1450
3524
  SPINNER_FRAMES,
3525
+ ScrollView,
1451
3526
  Scrollbar,
3527
+ Sidebar,
3528
+ Skeleton,
1452
3529
  Sparkline,
1453
3530
  Spinner,
1454
3531
  StatusIndicator,
3532
+ StatusMessage,
3533
+ StreamingText,
1455
3534
  Table,
1456
3535
  Text,
1457
3536
  TextInput,
3537
+ ToolApproval,
3538
+ ToolCall,
3539
+ Tree,
1458
3540
  VirtualList,
1459
- Widget
3541
+ Widget,
3542
+ computeRange,
3543
+ computeVariableRange,
3544
+ jsonToTree
1460
3545
  };
1461
3546
  //# sourceMappingURL=index.js.map