@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/README.md +88 -39
- package/dist/index.cjs +2141 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +800 -1
- package/dist/index.d.ts +800 -1
- package/dist/index.js +2131 -47
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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
|
|
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 =
|
|
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 =
|
|
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 -
|
|
1291
|
+
const remaining = width - stringWidth6(line);
|
|
471
1292
|
for (let c = 0; c < remaining; c++) {
|
|
472
|
-
screen.setCell(x +
|
|
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
|
|
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 =
|
|
1400
|
+
const attrs = styleToCellAttrs12(this._style);
|
|
580
1401
|
if (this._value.length === 0 && !this.isFocused) {
|
|
581
|
-
screen.writeString(x, y,
|
|
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
|
|
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 =
|
|
1532
|
+
const attrs = styleToCellAttrs13(this._style);
|
|
712
1533
|
const visibleItemCount = Math.floor(height / this._itemHeight);
|
|
713
|
-
const
|
|
714
|
-
|
|
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 =
|
|
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 -
|
|
1561
|
+
const remaining = contentWidth - stringWidth8(line);
|
|
737
1562
|
for (let c = 0; c < remaining; c++) {
|
|
738
|
-
screen.setCell(x +
|
|
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 ?
|
|
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
|
|
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 =
|
|
800
|
-
const sepWidth =
|
|
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 =
|
|
868
|
-
const textWidth =
|
|
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
|
|
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 =
|
|
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 =
|
|
917
|
-
const percentWidth =
|
|
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
|
|
940
|
-
var
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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 -
|
|
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 -
|
|
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 -
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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) {
|
|
@@ -1343,11 +2680,26 @@ var Spinner = class extends Widget {
|
|
|
1343
2680
|
this._elapsed = 0;
|
|
1344
2681
|
}
|
|
1345
2682
|
}
|
|
2683
|
+
/** Lifecycle: start the frame-advance timer (only when motion is enabled). */
|
|
2684
|
+
mount() {
|
|
2685
|
+
super.mount();
|
|
2686
|
+
if (!caps12.motion) return;
|
|
2687
|
+
this._timerUnsub = timerPoolSubscribe2(this._interval, () => {
|
|
2688
|
+
this._frameIndex = (this._frameIndex + 1) % this._frames.length;
|
|
2689
|
+
this.markDirty();
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
/** Lifecycle: stop the frame-advance timer. */
|
|
2693
|
+
unmount() {
|
|
2694
|
+
this._timerUnsub?.();
|
|
2695
|
+
this._timerUnsub = void 0;
|
|
2696
|
+
super.unmount();
|
|
2697
|
+
}
|
|
1346
2698
|
_renderSelf(screen) {
|
|
1347
2699
|
const rect = this._getContentRect();
|
|
1348
2700
|
const { x, y, width } = rect;
|
|
1349
2701
|
if (width <= 0) return;
|
|
1350
|
-
const attrs =
|
|
2702
|
+
const attrs = styleToCellAttrs24(this._style);
|
|
1351
2703
|
const frame = this._frames[this._frameIndex];
|
|
1352
2704
|
screen.writeString(x, y, frame, { ...attrs, fg: this._color });
|
|
1353
2705
|
if (this._label) {
|
|
@@ -1440,22 +2792,754 @@ var Scrollbar = class extends Widget {
|
|
|
1440
2792
|
}
|
|
1441
2793
|
}
|
|
1442
2794
|
};
|
|
2795
|
+
|
|
2796
|
+
// src/feedback/Skeleton.ts
|
|
2797
|
+
import { caps as caps13 } from "@termuijs/core";
|
|
2798
|
+
import { timerPoolSubscribe as timerPoolSubscribe3 } from "@termuijs/motion";
|
|
2799
|
+
var Skeleton = class extends Widget {
|
|
2800
|
+
_frame = 0;
|
|
2801
|
+
_shimmerPos = 0;
|
|
2802
|
+
_unsub;
|
|
2803
|
+
_chars;
|
|
2804
|
+
_variant;
|
|
2805
|
+
_intervalMs;
|
|
2806
|
+
constructor(style = {}, options = {}) {
|
|
2807
|
+
super(style);
|
|
2808
|
+
this._variant = options.variant ?? "pulse";
|
|
2809
|
+
this._intervalMs = options.intervalMs ?? 600;
|
|
2810
|
+
const defaultChars = caps13.unicode ? ["\u2591", "\u2592"] : ["-", "#"];
|
|
2811
|
+
this._chars = options.chars ?? defaultChars;
|
|
2812
|
+
if (caps13.motion) {
|
|
2813
|
+
this._unsub = timerPoolSubscribe3(this._intervalMs, () => {
|
|
2814
|
+
this._frame = 1 - this._frame;
|
|
2815
|
+
if (this._variant === "shimmer") {
|
|
2816
|
+
this._shimmerPos++;
|
|
2817
|
+
}
|
|
2818
|
+
this.markDirty();
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
unmount() {
|
|
2823
|
+
this._unsub?.();
|
|
2824
|
+
this._unsub = void 0;
|
|
2825
|
+
super.unmount();
|
|
2826
|
+
}
|
|
2827
|
+
_renderSelf(screen) {
|
|
2828
|
+
const rect = this._getContentRect();
|
|
2829
|
+
const { x, y, width, height } = rect;
|
|
2830
|
+
if (width <= 0 || height <= 0) return;
|
|
2831
|
+
if (this._variant === "pulse") {
|
|
2832
|
+
const char = this._chars[this._frame];
|
|
2833
|
+
const dim = this._frame === 0;
|
|
2834
|
+
for (let row = y; row < y + height; row++) {
|
|
2835
|
+
for (let col = x; col < x + width; col++) {
|
|
2836
|
+
screen.setCell(col, row, { char, dim, bold: false });
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
} else {
|
|
2840
|
+
const bandWidth = Math.max(1, Math.floor(width * 0.2));
|
|
2841
|
+
const totalPositions = width + bandWidth;
|
|
2842
|
+
const bandStart = this._shimmerPos % totalPositions;
|
|
2843
|
+
for (let row = y; row < y + height; row++) {
|
|
2844
|
+
for (let colOffset = 0; colOffset < width; colOffset++) {
|
|
2845
|
+
const col = x + colOffset;
|
|
2846
|
+
const inBand = colOffset >= bandStart && colOffset < bandStart + bandWidth;
|
|
2847
|
+
const char = inBand ? this._chars[1] : this._chars[0];
|
|
2848
|
+
screen.setCell(col, row, { char, dim: !inBand, bold: false });
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
|
|
2855
|
+
// src/feedback/StatusMessage.ts
|
|
2856
|
+
import { styleToCellAttrs as styleToCellAttrs25, caps as caps14 } from "@termuijs/core";
|
|
2857
|
+
var ICONS_UNICODE = {
|
|
2858
|
+
success: "\u2713",
|
|
2859
|
+
error: "\u2717",
|
|
2860
|
+
warning: "\u26A0",
|
|
2861
|
+
info: "\u2139"
|
|
2862
|
+
};
|
|
2863
|
+
var ICONS_ASCII = {
|
|
2864
|
+
success: "+",
|
|
2865
|
+
error: "x",
|
|
2866
|
+
warning: "!",
|
|
2867
|
+
info: "i"
|
|
2868
|
+
};
|
|
2869
|
+
var COLORS = {
|
|
2870
|
+
success: { type: "named", name: "green" },
|
|
2871
|
+
error: { type: "named", name: "red" },
|
|
2872
|
+
warning: { type: "named", name: "yellow" },
|
|
2873
|
+
info: { type: "named", name: "cyan" }
|
|
2874
|
+
};
|
|
2875
|
+
var StatusMessage = class extends Widget {
|
|
2876
|
+
_message;
|
|
2877
|
+
_variant;
|
|
2878
|
+
_icon;
|
|
2879
|
+
constructor(message, style = {}, opts = {}) {
|
|
2880
|
+
super({ height: 1, ...style });
|
|
2881
|
+
this._message = message;
|
|
2882
|
+
this._variant = opts.variant ?? "info";
|
|
2883
|
+
this._icon = opts.icon;
|
|
2884
|
+
}
|
|
2885
|
+
setMessage(message) {
|
|
2886
|
+
this._message = message;
|
|
2887
|
+
this.markDirty();
|
|
2888
|
+
}
|
|
2889
|
+
setVariant(variant) {
|
|
2890
|
+
this._variant = variant;
|
|
2891
|
+
this.markDirty();
|
|
2892
|
+
}
|
|
2893
|
+
_renderSelf(screen) {
|
|
2894
|
+
const rect = this._getContentRect();
|
|
2895
|
+
const { x, y, width } = rect;
|
|
2896
|
+
if (width <= 0) return;
|
|
2897
|
+
const attrs = styleToCellAttrs25(this._style);
|
|
2898
|
+
const color = COLORS[this._variant];
|
|
2899
|
+
const iconMap = caps14.unicode ? ICONS_UNICODE : ICONS_ASCII;
|
|
2900
|
+
const icon = this._icon ?? iconMap[this._variant];
|
|
2901
|
+
screen.writeString(x, y, icon, { ...attrs, fg: color, bold: true });
|
|
2902
|
+
const msgX = x + icon.length + 1;
|
|
2903
|
+
const remaining = width - icon.length - 1;
|
|
2904
|
+
if (remaining > 0) {
|
|
2905
|
+
screen.writeString(msgX, y, this._message.slice(0, remaining), { ...attrs, fg: color });
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
|
|
2910
|
+
// src/feedback/Banner.ts
|
|
2911
|
+
import { styleToCellAttrs as styleToCellAttrs26, getBorderChars as getBorderChars2 } from "@termuijs/core";
|
|
2912
|
+
var VARIANT_COLORS = {
|
|
2913
|
+
success: { type: "named", name: "green" },
|
|
2914
|
+
error: { type: "named", name: "red" },
|
|
2915
|
+
warning: { type: "named", name: "yellow" },
|
|
2916
|
+
info: { type: "named", name: "cyan" }
|
|
2917
|
+
};
|
|
2918
|
+
var Banner = class extends Widget {
|
|
2919
|
+
_variant;
|
|
2920
|
+
_title;
|
|
2921
|
+
_body;
|
|
2922
|
+
constructor(style = {}, opts = {}) {
|
|
2923
|
+
super({
|
|
2924
|
+
width: "100%",
|
|
2925
|
+
padding: 1,
|
|
2926
|
+
...style
|
|
2927
|
+
});
|
|
2928
|
+
this._variant = opts.variant ?? "info";
|
|
2929
|
+
this._title = opts.title ?? "";
|
|
2930
|
+
this._body = opts.body ?? "";
|
|
2931
|
+
}
|
|
2932
|
+
setTitle(title) {
|
|
2933
|
+
this._title = title;
|
|
2934
|
+
this.markDirty();
|
|
2935
|
+
}
|
|
2936
|
+
setBody(body) {
|
|
2937
|
+
this._body = body;
|
|
2938
|
+
this.markDirty();
|
|
2939
|
+
}
|
|
2940
|
+
setVariant(variant) {
|
|
2941
|
+
this._variant = variant;
|
|
2942
|
+
this.markDirty();
|
|
2943
|
+
}
|
|
2944
|
+
_renderSelf(screen) {
|
|
2945
|
+
const { x, y, width, height } = this._rect;
|
|
2946
|
+
if (width < 2 || height < 2) return;
|
|
2947
|
+
const attrs = styleToCellAttrs26(this._style);
|
|
2948
|
+
const color = VARIANT_COLORS[this._variant];
|
|
2949
|
+
const fg = color;
|
|
2950
|
+
const borderChars = getBorderChars2("single");
|
|
2951
|
+
if (borderChars) {
|
|
2952
|
+
screen.setCell(x, y, { char: borderChars.topLeft, fg });
|
|
2953
|
+
for (let c = 1; c < width - 1; c++) {
|
|
2954
|
+
screen.setCell(x + c, y, { char: borderChars.top, fg });
|
|
2955
|
+
}
|
|
2956
|
+
screen.setCell(x + width - 1, y, { char: borderChars.topRight, fg });
|
|
2957
|
+
screen.setCell(x, y + height - 1, { char: borderChars.bottomLeft, fg });
|
|
2958
|
+
for (let c = 1; c < width - 1; c++) {
|
|
2959
|
+
screen.setCell(x + c, y + height - 1, { char: borderChars.bottom, fg });
|
|
2960
|
+
}
|
|
2961
|
+
screen.setCell(x + width - 1, y + height - 1, { char: borderChars.bottomRight, fg });
|
|
2962
|
+
for (let r = 1; r < height - 1; r++) {
|
|
2963
|
+
screen.setCell(x, y + r, { char: borderChars.left, fg });
|
|
2964
|
+
screen.setCell(x + width - 1, y + r, { char: borderChars.right, fg });
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
const cx = x + 2;
|
|
2968
|
+
const cy = y + 2;
|
|
2969
|
+
const contentWidth = Math.max(0, width - 4);
|
|
2970
|
+
const contentHeight = Math.max(0, height - 4);
|
|
2971
|
+
let row = 0;
|
|
2972
|
+
if (this._title && row < contentHeight) {
|
|
2973
|
+
screen.writeString(cx, cy + row, this._title.slice(0, contentWidth), {
|
|
2974
|
+
...attrs,
|
|
2975
|
+
fg: color,
|
|
2976
|
+
bold: true
|
|
2977
|
+
});
|
|
2978
|
+
row++;
|
|
2979
|
+
}
|
|
2980
|
+
if (this._body) {
|
|
2981
|
+
const lines = this._body.split("\n");
|
|
2982
|
+
for (const line of lines) {
|
|
2983
|
+
if (row >= contentHeight) break;
|
|
2984
|
+
screen.writeString(cx, cy + row, line.slice(0, contentWidth), {
|
|
2985
|
+
...attrs,
|
|
2986
|
+
fg: color
|
|
2987
|
+
});
|
|
2988
|
+
row++;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
|
|
2994
|
+
// src/data/KeyValue.ts
|
|
2995
|
+
import { styleToCellAttrs as styleToCellAttrs27, stringWidth as stringWidth14 } from "@termuijs/core";
|
|
2996
|
+
var KeyValue = class extends Widget {
|
|
2997
|
+
_pairs;
|
|
2998
|
+
_separator;
|
|
2999
|
+
_keyColor;
|
|
3000
|
+
_valueColor;
|
|
3001
|
+
constructor(pairs, style = {}, opts = {}) {
|
|
3002
|
+
super(style);
|
|
3003
|
+
this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([key, value]) => ({ key, value }));
|
|
3004
|
+
this._separator = opts.separator ?? ": ";
|
|
3005
|
+
this._keyColor = opts.keyColor;
|
|
3006
|
+
this._valueColor = opts.valueColor;
|
|
3007
|
+
}
|
|
3008
|
+
setPairs(pairs) {
|
|
3009
|
+
this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([key, value]) => ({ key, value }));
|
|
3010
|
+
this.markDirty();
|
|
3011
|
+
}
|
|
3012
|
+
_renderSelf(screen) {
|
|
3013
|
+
const rect = this._getContentRect();
|
|
3014
|
+
const { x, y, width, height } = rect;
|
|
3015
|
+
if (width <= 0 || height <= 0 || this._pairs.length === 0) return;
|
|
3016
|
+
const attrs = styleToCellAttrs27(this._style);
|
|
3017
|
+
let maxKeyWidth = 0;
|
|
3018
|
+
for (const pair of this._pairs) {
|
|
3019
|
+
const w = stringWidth14(pair.key);
|
|
3020
|
+
if (w > maxKeyWidth) maxKeyWidth = w;
|
|
3021
|
+
}
|
|
3022
|
+
const sepWidth = stringWidth14(this._separator);
|
|
3023
|
+
for (let i = 0; i < this._pairs.length && i < height; i++) {
|
|
3024
|
+
const pair = this._pairs[i];
|
|
3025
|
+
if (!pair) continue;
|
|
3026
|
+
const keyWidth = stringWidth14(pair.key);
|
|
3027
|
+
const keyX = x + (maxKeyWidth - keyWidth);
|
|
3028
|
+
const sepX = x + maxKeyWidth;
|
|
3029
|
+
const valX = sepX + sepWidth;
|
|
3030
|
+
const valWidth = Math.max(0, width - maxKeyWidth - sepWidth);
|
|
3031
|
+
screen.writeString(keyX, y + i, pair.key, {
|
|
3032
|
+
...attrs,
|
|
3033
|
+
fg: this._keyColor ?? attrs.fg,
|
|
3034
|
+
bold: true
|
|
3035
|
+
});
|
|
3036
|
+
screen.writeString(sepX, y + i, this._separator, { ...attrs, dim: true });
|
|
3037
|
+
if (valWidth > 0) {
|
|
3038
|
+
screen.writeString(valX, y + i, pair.value.slice(0, valWidth), {
|
|
3039
|
+
...attrs,
|
|
3040
|
+
fg: this._valueColor ?? attrs.fg
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
|
|
3047
|
+
// src/data/Sidebar.ts
|
|
3048
|
+
import { styleToCellAttrs as styleToCellAttrs28, stringWidth as stringWidth15 } from "@termuijs/core";
|
|
3049
|
+
var Sidebar = class extends Widget {
|
|
3050
|
+
_items;
|
|
3051
|
+
_collapsed;
|
|
3052
|
+
_collapsedWidth;
|
|
3053
|
+
_activeColor;
|
|
3054
|
+
_badgeColor;
|
|
3055
|
+
constructor(items, style = {}, opts = {}) {
|
|
3056
|
+
super(style);
|
|
3057
|
+
this._items = items;
|
|
3058
|
+
this._collapsed = opts.collapsed ?? false;
|
|
3059
|
+
this._collapsedWidth = opts.collapsedWidth ?? 3;
|
|
3060
|
+
this._activeColor = opts.activeColor ?? { type: "named", name: "cyan" };
|
|
3061
|
+
this._badgeColor = opts.badgeColor ?? { type: "named", name: "yellow" };
|
|
3062
|
+
}
|
|
3063
|
+
setItems(items) {
|
|
3064
|
+
this._items = items;
|
|
3065
|
+
this.markDirty();
|
|
3066
|
+
}
|
|
3067
|
+
setCollapsed(collapsed) {
|
|
3068
|
+
this._collapsed = collapsed;
|
|
3069
|
+
this.markDirty();
|
|
3070
|
+
}
|
|
3071
|
+
toggle() {
|
|
3072
|
+
this._collapsed = !this._collapsed;
|
|
3073
|
+
this.markDirty();
|
|
3074
|
+
}
|
|
3075
|
+
get isCollapsed() {
|
|
3076
|
+
return this._collapsed;
|
|
3077
|
+
}
|
|
3078
|
+
_renderSelf(screen) {
|
|
3079
|
+
const rect = this._getContentRect();
|
|
3080
|
+
const { x, y, width, height } = rect;
|
|
3081
|
+
if (width <= 0 || height <= 0) return;
|
|
3082
|
+
const attrs = styleToCellAttrs28(this._style);
|
|
3083
|
+
for (let i = 0; i < this._items.length && i < height; i++) {
|
|
3084
|
+
const item = this._items[i];
|
|
3085
|
+
if (!item) continue;
|
|
3086
|
+
const isActive = item.active ?? false;
|
|
3087
|
+
const fg = isActive ? this._activeColor : attrs.fg;
|
|
3088
|
+
if (this._collapsed) {
|
|
3089
|
+
const char = item.label.charAt(0) || " ";
|
|
3090
|
+
screen.writeString(x, y + i, char, { ...attrs, fg, bold: isActive });
|
|
3091
|
+
} else {
|
|
3092
|
+
const prefix = isActive ? "\u25B6 " : " ";
|
|
3093
|
+
const prefixWidth = stringWidth15(prefix);
|
|
3094
|
+
screen.writeString(x, y + i, prefix, { ...attrs, fg });
|
|
3095
|
+
const badgeText = item.badge ? ` [${item.badge}]` : "";
|
|
3096
|
+
const badgeWidth = stringWidth15(badgeText);
|
|
3097
|
+
const labelWidth = Math.max(0, width - prefixWidth - badgeWidth);
|
|
3098
|
+
const label = item.label.slice(0, labelWidth);
|
|
3099
|
+
screen.writeString(x + prefixWidth, y + i, label, {
|
|
3100
|
+
...attrs,
|
|
3101
|
+
fg,
|
|
3102
|
+
bold: isActive
|
|
3103
|
+
});
|
|
3104
|
+
if (item.badge && badgeWidth > 0) {
|
|
3105
|
+
const badgeX = x + width - badgeWidth;
|
|
3106
|
+
screen.writeString(badgeX, y + i, badgeText, {
|
|
3107
|
+
...attrs,
|
|
3108
|
+
fg: this._badgeColor
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3116
|
+
// src/data/LineChart.ts
|
|
3117
|
+
import { styleToCellAttrs as styleToCellAttrs29, caps as caps15 } from "@termuijs/core";
|
|
3118
|
+
var POINT_CHAR_UNICODE = "\u25CF";
|
|
3119
|
+
var POINT_CHAR_ASCII = "*";
|
|
3120
|
+
var LineChart = class extends Widget {
|
|
3121
|
+
_data;
|
|
3122
|
+
_color;
|
|
3123
|
+
_showYAxis;
|
|
3124
|
+
_showXAxis;
|
|
3125
|
+
_yLabel;
|
|
3126
|
+
_max;
|
|
3127
|
+
_min;
|
|
3128
|
+
constructor(data, style = {}, opts = {}) {
|
|
3129
|
+
super(style);
|
|
3130
|
+
this._data = data;
|
|
3131
|
+
this._color = opts.color ?? { type: "named", name: "cyan" };
|
|
3132
|
+
this._showYAxis = opts.showYAxis ?? false;
|
|
3133
|
+
this._showXAxis = opts.showXAxis ?? false;
|
|
3134
|
+
this._yLabel = opts.yLabel ?? "";
|
|
3135
|
+
this._max = opts.max;
|
|
3136
|
+
this._min = opts.min;
|
|
3137
|
+
}
|
|
3138
|
+
setData(data) {
|
|
3139
|
+
this._data = data;
|
|
3140
|
+
this.markDirty();
|
|
3141
|
+
}
|
|
3142
|
+
pushValue(value) {
|
|
3143
|
+
this._data.push(value);
|
|
3144
|
+
this.markDirty();
|
|
3145
|
+
}
|
|
3146
|
+
_renderSelf(screen) {
|
|
3147
|
+
const rect = this._getContentRect();
|
|
3148
|
+
let { x, y, width, height } = rect;
|
|
3149
|
+
if (width <= 0 || height <= 0 || this._data.length === 0) return;
|
|
3150
|
+
const attrs = styleToCellAttrs29(this._style);
|
|
3151
|
+
const plotHeight = this._showXAxis ? Math.max(1, height - 1) : height;
|
|
3152
|
+
const yAxisWidth = this._showYAxis ? 5 : 0;
|
|
3153
|
+
const plotWidth = Math.max(1, width - yAxisWidth);
|
|
3154
|
+
const plotX = x + yAxisWidth;
|
|
3155
|
+
const min = this._min ?? Math.min(...this._data);
|
|
3156
|
+
const max = this._max ?? Math.max(...this._data);
|
|
3157
|
+
const range = max - min || 1;
|
|
3158
|
+
const samples = [];
|
|
3159
|
+
for (let col = 0; col < plotWidth; col++) {
|
|
3160
|
+
const idx = Math.floor(col / plotWidth * this._data.length);
|
|
3161
|
+
const val = this._data[Math.min(idx, this._data.length - 1)];
|
|
3162
|
+
samples.push(val ?? min);
|
|
3163
|
+
}
|
|
3164
|
+
const toRow = (v) => {
|
|
3165
|
+
const norm = (v - min) / range;
|
|
3166
|
+
return Math.max(0, Math.min(plotHeight - 1, Math.round((1 - norm) * (plotHeight - 1))));
|
|
3167
|
+
};
|
|
3168
|
+
if (this._showYAxis && yAxisWidth > 0) {
|
|
3169
|
+
for (let row = 0; row < plotHeight; row++) {
|
|
3170
|
+
const v = plotHeight > 1 ? max - row / (plotHeight - 1) * range : max;
|
|
3171
|
+
if (row === 0 || row === plotHeight - 1) {
|
|
3172
|
+
const label = v.toFixed(0).padStart(yAxisWidth - 1, " ");
|
|
3173
|
+
screen.writeString(x, y + row, label + "\u2524", { ...attrs, dim: true });
|
|
3174
|
+
} else {
|
|
3175
|
+
screen.writeString(x + yAxisWidth - 1, y + row, "\u2502", { ...attrs, dim: true });
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
const pointChar = caps15.unicode ? POINT_CHAR_UNICODE : POINT_CHAR_ASCII;
|
|
3180
|
+
let prevRow = null;
|
|
3181
|
+
for (let col = 0; col < samples.length; col++) {
|
|
3182
|
+
const val = samples[col];
|
|
3183
|
+
if (val === void 0) continue;
|
|
3184
|
+
const row = toRow(val);
|
|
3185
|
+
if (prevRow !== null && Math.abs(row - prevRow) > 1) {
|
|
3186
|
+
const top = Math.min(prevRow, row) + 1;
|
|
3187
|
+
const bottom = Math.max(prevRow, row);
|
|
3188
|
+
for (let r = top; r < bottom; r++) {
|
|
3189
|
+
screen.setCell(plotX + col, y + r, { char: "\u2502", fg: this._color, dim: true });
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
screen.setCell(plotX + col, y + row, { char: pointChar, fg: this._color });
|
|
3193
|
+
prevRow = row;
|
|
3194
|
+
}
|
|
3195
|
+
if (this._showXAxis) {
|
|
3196
|
+
const axisY = y + height - 1;
|
|
3197
|
+
for (let col = 0; col < plotWidth; col++) {
|
|
3198
|
+
screen.setCell(plotX + col, axisY, { char: "\u2500", ...attrs, dim: true });
|
|
3199
|
+
}
|
|
3200
|
+
if (yAxisWidth > 0) {
|
|
3201
|
+
screen.setCell(plotX - 1, axisY, { char: "\u2514", ...attrs, dim: true });
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
};
|
|
3206
|
+
|
|
3207
|
+
// src/data/HeatMap.ts
|
|
3208
|
+
import { styleToCellAttrs as styleToCellAttrs30, caps as caps16 } from "@termuijs/core";
|
|
3209
|
+
var SHADE_CHARS_UNICODE = ["\u2591", "\u2592", "\u2593", "\u2588"];
|
|
3210
|
+
var SHADE_CHARS_ASCII = [".", ":", "+", "#"];
|
|
3211
|
+
var HeatMap = class extends Widget {
|
|
3212
|
+
_matrix;
|
|
3213
|
+
_highColor;
|
|
3214
|
+
_lowColor;
|
|
3215
|
+
_rowLabels;
|
|
3216
|
+
_colLabels;
|
|
3217
|
+
constructor(matrix, style = {}, opts = {}) {
|
|
3218
|
+
super(style);
|
|
3219
|
+
this._matrix = matrix;
|
|
3220
|
+
this._highColor = opts.highColor ?? { type: "named", name: "red" };
|
|
3221
|
+
this._lowColor = opts.lowColor ?? { type: "named", name: "brightBlack" };
|
|
3222
|
+
this._rowLabels = opts.rowLabels ?? [];
|
|
3223
|
+
this._colLabels = opts.colLabels ?? [];
|
|
3224
|
+
}
|
|
3225
|
+
setMatrix(matrix) {
|
|
3226
|
+
this._matrix = matrix;
|
|
3227
|
+
this.markDirty();
|
|
3228
|
+
}
|
|
3229
|
+
_renderSelf(screen) {
|
|
3230
|
+
const rect = this._getContentRect();
|
|
3231
|
+
const { x, y, width, height } = rect;
|
|
3232
|
+
if (width <= 0 || height <= 0 || this._matrix.length === 0) return;
|
|
3233
|
+
const attrs = styleToCellAttrs30(this._style);
|
|
3234
|
+
const shadeChars = caps16.unicode ? SHADE_CHARS_UNICODE : SHADE_CHARS_ASCII;
|
|
3235
|
+
const labelWidth = this._rowLabels.length > 0 ? Math.max(...this._rowLabels.map((l) => l.length)) + 1 : 0;
|
|
3236
|
+
let globalMin = Infinity;
|
|
3237
|
+
let globalMax = -Infinity;
|
|
3238
|
+
for (const row of this._matrix) {
|
|
3239
|
+
for (const val of row) {
|
|
3240
|
+
if (val < globalMin) globalMin = val;
|
|
3241
|
+
if (val > globalMax) globalMax = val;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
const range = globalMax - globalMin || 1;
|
|
3245
|
+
let startRow = 0;
|
|
3246
|
+
if (this._colLabels.length > 0) {
|
|
3247
|
+
for (let col = 0; col < this._colLabels.length; col++) {
|
|
3248
|
+
const cx = x + labelWidth + col;
|
|
3249
|
+
if (cx >= x + width) break;
|
|
3250
|
+
const label = (this._colLabels[col] ?? "").charAt(0);
|
|
3251
|
+
screen.setCell(cx, y, { char: label, ...attrs, dim: true });
|
|
3252
|
+
}
|
|
3253
|
+
startRow = 1;
|
|
3254
|
+
}
|
|
3255
|
+
for (let r = 0; r < this._matrix.length; r++) {
|
|
3256
|
+
const rowY = y + startRow + r;
|
|
3257
|
+
if (rowY >= y + height) break;
|
|
3258
|
+
if (this._rowLabels[r]) {
|
|
3259
|
+
const label = this._rowLabels[r].slice(0, labelWidth - 1).padEnd(labelWidth - 1, " ");
|
|
3260
|
+
screen.writeString(x, rowY, label + " ", { ...attrs, dim: true });
|
|
3261
|
+
}
|
|
3262
|
+
const row = this._matrix[r];
|
|
3263
|
+
if (!row) continue;
|
|
3264
|
+
for (let col = 0; col < row.length; col++) {
|
|
3265
|
+
const cx = x + labelWidth + col;
|
|
3266
|
+
if (cx >= x + width) break;
|
|
3267
|
+
const val = row[col] ?? 0;
|
|
3268
|
+
const norm = (val - globalMin) / range;
|
|
3269
|
+
const level = Math.min(3, Math.floor(norm * 4));
|
|
3270
|
+
const char = shadeChars[level] ?? shadeChars[0];
|
|
3271
|
+
const fg = norm >= 0.75 ? this._highColor : this._lowColor;
|
|
3272
|
+
screen.setCell(cx, rowY, { char, fg });
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
// src/data/Definition.ts
|
|
3279
|
+
import { styleToCellAttrs as styleToCellAttrs31 } from "@termuijs/core";
|
|
3280
|
+
var Definition = class extends Widget {
|
|
3281
|
+
_pairs;
|
|
3282
|
+
_indent;
|
|
3283
|
+
_spacing;
|
|
3284
|
+
_termColor;
|
|
3285
|
+
_definitionColor;
|
|
3286
|
+
constructor(pairs, style = {}, opts = {}) {
|
|
3287
|
+
super(style);
|
|
3288
|
+
this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([term, definition]) => ({ term, definition }));
|
|
3289
|
+
this._indent = opts.indent ?? 2;
|
|
3290
|
+
this._spacing = opts.spacing ?? true;
|
|
3291
|
+
this._termColor = opts.termColor;
|
|
3292
|
+
this._definitionColor = opts.definitionColor;
|
|
3293
|
+
}
|
|
3294
|
+
setPairs(pairs) {
|
|
3295
|
+
this._pairs = Array.isArray(pairs) ? pairs : Object.entries(pairs).map(([term, definition]) => ({ term, definition }));
|
|
3296
|
+
this.markDirty();
|
|
3297
|
+
}
|
|
3298
|
+
_renderSelf(screen) {
|
|
3299
|
+
const rect = this._getContentRect();
|
|
3300
|
+
const { x, y, width, height } = rect;
|
|
3301
|
+
if (width <= 0 || height <= 0 || this._pairs.length === 0) return;
|
|
3302
|
+
const attrs = styleToCellAttrs31(this._style);
|
|
3303
|
+
const indent = Math.min(this._indent, width - 1);
|
|
3304
|
+
let row = 0;
|
|
3305
|
+
for (const pair of this._pairs) {
|
|
3306
|
+
if (row >= height) break;
|
|
3307
|
+
screen.writeString(x, y + row, pair.term.slice(0, width), {
|
|
3308
|
+
...attrs,
|
|
3309
|
+
fg: this._termColor ?? attrs.fg,
|
|
3310
|
+
bold: true
|
|
3311
|
+
});
|
|
3312
|
+
row++;
|
|
3313
|
+
if (row >= height) break;
|
|
3314
|
+
const defWidth = Math.max(1, width - indent);
|
|
3315
|
+
const words = pair.definition.split(" ");
|
|
3316
|
+
let line = "";
|
|
3317
|
+
for (const word of words) {
|
|
3318
|
+
if (line.length === 0) {
|
|
3319
|
+
line = word;
|
|
3320
|
+
} else if (line.length + 1 + word.length <= defWidth) {
|
|
3321
|
+
line += " " + word;
|
|
3322
|
+
} else {
|
|
3323
|
+
if (row >= height) break;
|
|
3324
|
+
screen.writeString(x + indent, y + row, line, {
|
|
3325
|
+
...attrs,
|
|
3326
|
+
fg: this._definitionColor ?? attrs.fg
|
|
3327
|
+
});
|
|
3328
|
+
row++;
|
|
3329
|
+
line = word;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
if (line && row < height) {
|
|
3333
|
+
screen.writeString(x + indent, y + row, line, {
|
|
3334
|
+
...attrs,
|
|
3335
|
+
fg: this._definitionColor ?? attrs.fg
|
|
3336
|
+
});
|
|
3337
|
+
row++;
|
|
3338
|
+
}
|
|
3339
|
+
if (this._spacing && row < height) {
|
|
3340
|
+
row++;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
3345
|
+
|
|
3346
|
+
// src/display/BigText.ts
|
|
3347
|
+
import { styleToCellAttrs as styleToCellAttrs32 } from "@termuijs/core";
|
|
3348
|
+
var CHAR_MAP = {
|
|
3349
|
+
"A": [" # ", "# #", "###", "# #", "# #"],
|
|
3350
|
+
"B": ["## ", "# #", "## ", "# #", "## "],
|
|
3351
|
+
"C": [" ##", "# ", "# ", "# ", " ##"],
|
|
3352
|
+
"D": ["## ", "# #", "# #", "# #", "## "],
|
|
3353
|
+
"E": ["###", "# ", "## ", "# ", "###"],
|
|
3354
|
+
"F": ["###", "# ", "## ", "# ", "# "],
|
|
3355
|
+
"G": [" ##", "# ", "# #", "# #", " ##"],
|
|
3356
|
+
"H": ["# #", "# #", "###", "# #", "# #"],
|
|
3357
|
+
"I": ["###", " # ", " # ", " # ", "###"],
|
|
3358
|
+
"J": ["###", " #", " #", "# #", " # "],
|
|
3359
|
+
"K": ["# #", "## ", "# ", "## ", "# #"],
|
|
3360
|
+
"L": ["# ", "# ", "# ", "# ", "###"],
|
|
3361
|
+
"M": ["# #", "###", "# #", "# #", "# #"],
|
|
3362
|
+
"N": ["# #", "## ", "# #", "# #", "# #"],
|
|
3363
|
+
"O": [" # ", "# #", "# #", "# #", " # "],
|
|
3364
|
+
"P": ["## ", "# #", "## ", "# ", "# "],
|
|
3365
|
+
"Q": [" # ", "# #", "# #", "##:", " ##"],
|
|
3366
|
+
"R": ["## ", "# #", "## ", "## ", "# #"],
|
|
3367
|
+
"S": [" ##", "# ", " # ", " #", "## "],
|
|
3368
|
+
"T": ["###", " # ", " # ", " # ", " # "],
|
|
3369
|
+
"U": ["# #", "# #", "# #", "# #", "###"],
|
|
3370
|
+
"V": ["# #", "# #", "# #", "# #", " # "],
|
|
3371
|
+
"W": ["# #", "# #", "# #", "###", "# #"],
|
|
3372
|
+
"X": ["# #", "# #", " # ", "# #", "# #"],
|
|
3373
|
+
"Y": ["# #", "# #", " # ", " # ", " # "],
|
|
3374
|
+
"Z": ["###", " #", " # ", "# ", "###"],
|
|
3375
|
+
"0": [" # ", "# #", "# #", "# #", " # "],
|
|
3376
|
+
"1": [" # ", "## ", " # ", " # ", "###"],
|
|
3377
|
+
"2": [" # ", "# #", " # ", "# ", "###"],
|
|
3378
|
+
"3": ["## ", " #", " ##", " #", "## "],
|
|
3379
|
+
"4": ["# #", "# #", "###", " #", " #"],
|
|
3380
|
+
"5": ["###", "# ", "## ", " #", "## "],
|
|
3381
|
+
"6": [" # ", "# ", "## ", "# #", " # "],
|
|
3382
|
+
"7": ["###", " #", " # ", "# ", "# "],
|
|
3383
|
+
"8": [" # ", "# #", " # ", "# #", " # "],
|
|
3384
|
+
"9": [" # ", "# #", " ##", " #", " # "],
|
|
3385
|
+
" ": [" ", " ", " ", " ", " "],
|
|
3386
|
+
"!": [" # ", " # ", " # ", " ", " # "],
|
|
3387
|
+
".": [" ", " ", " ", " ", " # "],
|
|
3388
|
+
"-": [" ", " ", "###", " ", " "],
|
|
3389
|
+
":": [" ", " # ", " ", " # ", " "]
|
|
3390
|
+
};
|
|
3391
|
+
var CHAR_HEIGHT = 5;
|
|
3392
|
+
var CHAR_WIDTH = 3;
|
|
3393
|
+
var BigText = class extends Widget {
|
|
3394
|
+
_text;
|
|
3395
|
+
_color;
|
|
3396
|
+
constructor(text, style = {}, opts = {}) {
|
|
3397
|
+
super(style);
|
|
3398
|
+
this._text = text.toUpperCase();
|
|
3399
|
+
this._color = opts.color ?? { type: "named", name: "white" };
|
|
3400
|
+
}
|
|
3401
|
+
setText(text) {
|
|
3402
|
+
this._text = text.toUpperCase();
|
|
3403
|
+
this.markDirty();
|
|
3404
|
+
}
|
|
3405
|
+
_renderSelf(screen) {
|
|
3406
|
+
const rect = this._getContentRect();
|
|
3407
|
+
const { x, y, width, height } = rect;
|
|
3408
|
+
if (width <= 0 || height <= 0) return;
|
|
3409
|
+
const attrs = styleToCellAttrs32(this._style);
|
|
3410
|
+
const fg = this._color;
|
|
3411
|
+
let curX = x;
|
|
3412
|
+
for (const ch of this._text) {
|
|
3413
|
+
const glyph = CHAR_MAP[ch] ?? ["# #", "# #", "# #", "# #", "# #"];
|
|
3414
|
+
const glyphWidth = glyph[0]?.length ?? CHAR_WIDTH;
|
|
3415
|
+
if (curX + glyphWidth > x + width) break;
|
|
3416
|
+
for (let row = 0; row < CHAR_HEIGHT && row < height; row++) {
|
|
3417
|
+
const rowStr = glyph[row] ?? "";
|
|
3418
|
+
for (let col = 0; col < rowStr.length; col++) {
|
|
3419
|
+
if (rowStr[col] !== " ") {
|
|
3420
|
+
screen.setCell(curX + col, y + row, { char: "\u2588", ...attrs, fg });
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
curX += glyphWidth + 1;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3428
|
+
|
|
3429
|
+
// src/display/Gradient.ts
|
|
3430
|
+
import { styleToCellAttrs as styleToCellAttrs33, caps as caps17, parseColor } from "@termuijs/core";
|
|
3431
|
+
function hexToRgb(hex) {
|
|
3432
|
+
const clean = hex.replace("#", "");
|
|
3433
|
+
if (clean.length !== 6) return null;
|
|
3434
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
3435
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
3436
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
3437
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
3438
|
+
return [r, g, b];
|
|
3439
|
+
}
|
|
3440
|
+
function lerpRgb(a, b, t) {
|
|
3441
|
+
return [
|
|
3442
|
+
Math.round(a[0] + (b[0] - a[0]) * t),
|
|
3443
|
+
Math.round(a[1] + (b[1] - a[1]) * t),
|
|
3444
|
+
Math.round(a[2] + (b[2] - a[2]) * t)
|
|
3445
|
+
];
|
|
3446
|
+
}
|
|
3447
|
+
var Gradient = class extends Widget {
|
|
3448
|
+
_text;
|
|
3449
|
+
_startColor;
|
|
3450
|
+
_endColor;
|
|
3451
|
+
_align;
|
|
3452
|
+
constructor(text, style = {}, opts = {}) {
|
|
3453
|
+
super({ height: 1, ...style });
|
|
3454
|
+
this._text = text;
|
|
3455
|
+
this._startColor = opts.startColor ?? "#ff0000";
|
|
3456
|
+
this._endColor = opts.endColor ?? "#0000ff";
|
|
3457
|
+
this._align = opts.align ?? "left";
|
|
3458
|
+
}
|
|
3459
|
+
setText(text) {
|
|
3460
|
+
this._text = text;
|
|
3461
|
+
this.markDirty();
|
|
3462
|
+
}
|
|
3463
|
+
setColors(start, end) {
|
|
3464
|
+
this._startColor = start;
|
|
3465
|
+
this._endColor = end;
|
|
3466
|
+
this.markDirty();
|
|
3467
|
+
}
|
|
3468
|
+
_renderSelf(screen) {
|
|
3469
|
+
const rect = this._getContentRect();
|
|
3470
|
+
const { x, y, width } = rect;
|
|
3471
|
+
if (width <= 0 || !this._text) return;
|
|
3472
|
+
const attrs = styleToCellAttrs33(this._style);
|
|
3473
|
+
if (!caps17.color) {
|
|
3474
|
+
screen.writeString(x, y, this._text.slice(0, width), attrs);
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
const startRgb = hexToRgb(this._startColor);
|
|
3478
|
+
const endRgb = hexToRgb(this._endColor);
|
|
3479
|
+
const chars = Array.from(this._text).slice(0, width);
|
|
3480
|
+
const len = chars.length;
|
|
3481
|
+
let offsetX = 0;
|
|
3482
|
+
if (this._align === "center") offsetX = Math.floor((width - len) / 2);
|
|
3483
|
+
else if (this._align === "right") offsetX = width - len;
|
|
3484
|
+
offsetX = Math.max(0, offsetX);
|
|
3485
|
+
for (let i = 0; i < chars.length; i++) {
|
|
3486
|
+
const t = len > 1 ? i / (len - 1) : 0;
|
|
3487
|
+
let fg;
|
|
3488
|
+
if (startRgb && endRgb) {
|
|
3489
|
+
const [r, g, b] = lerpRgb(startRgb, endRgb, t);
|
|
3490
|
+
fg = { type: "rgb", r, g, b };
|
|
3491
|
+
} else if (startRgb) {
|
|
3492
|
+
fg = parseColor(this._startColor) ?? attrs.fg;
|
|
3493
|
+
} else {
|
|
3494
|
+
fg = attrs.fg;
|
|
3495
|
+
}
|
|
3496
|
+
screen.setCell(x + offsetX + i, y, { char: chars[i] ?? " ", ...attrs, fg });
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
1443
3500
|
export {
|
|
3501
|
+
Banner,
|
|
1444
3502
|
BarChart,
|
|
3503
|
+
BigText,
|
|
1445
3504
|
Box,
|
|
3505
|
+
Card,
|
|
3506
|
+
Center,
|
|
3507
|
+
ChatMessage,
|
|
3508
|
+
Columns,
|
|
3509
|
+
CommandPalette,
|
|
3510
|
+
Definition,
|
|
3511
|
+
DiffView,
|
|
1446
3512
|
Gauge,
|
|
3513
|
+
Gradient,
|
|
3514
|
+
Grid,
|
|
3515
|
+
HeatMap,
|
|
3516
|
+
JSONView,
|
|
3517
|
+
KeyValue,
|
|
3518
|
+
LineChart,
|
|
1447
3519
|
List,
|
|
1448
3520
|
LogView,
|
|
3521
|
+
MultiProgress,
|
|
1449
3522
|
ProgressBar,
|
|
1450
3523
|
SPINNER_FRAMES,
|
|
3524
|
+
ScrollView,
|
|
1451
3525
|
Scrollbar,
|
|
3526
|
+
Sidebar,
|
|
3527
|
+
Skeleton,
|
|
1452
3528
|
Sparkline,
|
|
1453
3529
|
Spinner,
|
|
1454
3530
|
StatusIndicator,
|
|
3531
|
+
StatusMessage,
|
|
3532
|
+
StreamingText,
|
|
1455
3533
|
Table,
|
|
1456
3534
|
Text,
|
|
1457
3535
|
TextInput,
|
|
3536
|
+
ToolApproval,
|
|
3537
|
+
ToolCall,
|
|
3538
|
+
Tree,
|
|
1458
3539
|
VirtualList,
|
|
1459
|
-
Widget
|
|
3540
|
+
Widget,
|
|
3541
|
+
computeRange,
|
|
3542
|
+
computeVariableRange,
|
|
3543
|
+
jsonToTree
|
|
1460
3544
|
};
|
|
1461
3545
|
//# sourceMappingURL=index.js.map
|