@termuijs/widgets 0.1.1 → 0.1.2
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/LICENSE +21 -0
- package/README.md +57 -29
- package/dist/index.cjs +511 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +157 -2
- package/dist/index.d.ts +157 -2
- package/dist/index.js +514 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -257,29 +257,63 @@ var Text = class extends Widget {
|
|
|
257
257
|
_content;
|
|
258
258
|
_wrap;
|
|
259
259
|
_align;
|
|
260
|
+
_scrollY;
|
|
261
|
+
_scrollX;
|
|
260
262
|
constructor(content, style = {}, props = {}) {
|
|
261
263
|
super(style);
|
|
262
264
|
this._content = content;
|
|
263
265
|
this._wrap = props.wrap ?? true;
|
|
264
266
|
this._align = props.align ?? "left";
|
|
267
|
+
this._scrollY = props.scrollY ?? 0;
|
|
268
|
+
this._scrollX = props.scrollX ?? 0;
|
|
265
269
|
}
|
|
266
270
|
/** Update the text content */
|
|
267
271
|
setContent(content) {
|
|
268
272
|
this._content = content;
|
|
273
|
+
this.markDirty();
|
|
269
274
|
}
|
|
270
275
|
/** Get current text content */
|
|
271
276
|
getContent() {
|
|
272
277
|
return this._content;
|
|
273
278
|
}
|
|
279
|
+
/** Set vertical scroll offset (lines to skip). */
|
|
280
|
+
setScrollY(offset) {
|
|
281
|
+
this._scrollY = Math.max(0, offset);
|
|
282
|
+
this.markDirty();
|
|
283
|
+
}
|
|
284
|
+
/** Set horizontal scroll offset (columns to skip). */
|
|
285
|
+
setScrollX(offset) {
|
|
286
|
+
this._scrollX = Math.max(0, offset);
|
|
287
|
+
this.markDirty();
|
|
288
|
+
}
|
|
289
|
+
/** Get the total number of lines after wrapping. */
|
|
290
|
+
getLineCount() {
|
|
291
|
+
const contentRect = this._getContentRect();
|
|
292
|
+
const text = this._wrap ? wordWrap(this._content, contentRect.width) : this._content;
|
|
293
|
+
return text.split("\n").length;
|
|
294
|
+
}
|
|
274
295
|
_renderSelf(screen) {
|
|
275
296
|
const contentRect = this._getContentRect();
|
|
276
297
|
const { x, y, width, height } = contentRect;
|
|
277
298
|
if (width <= 0 || height <= 0) return;
|
|
278
299
|
const attrs = styleToCellAttrs3(this._style);
|
|
279
300
|
let text = this._wrap ? wordWrap(this._content, width) : this._content;
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
301
|
+
const allLines = text.split("\n");
|
|
302
|
+
const startLine = Math.min(this._scrollY, allLines.length);
|
|
303
|
+
const visibleLines = allLines.slice(startLine, startLine + height);
|
|
304
|
+
for (let i = 0; i < Math.min(visibleLines.length, height); i++) {
|
|
305
|
+
let line = visibleLines[i];
|
|
306
|
+
if (line === void 0) continue;
|
|
307
|
+
if (this._scrollX > 0) {
|
|
308
|
+
let skipped = 0;
|
|
309
|
+
let charIndex = 0;
|
|
310
|
+
for (const ch of line) {
|
|
311
|
+
if (skipped >= this._scrollX) break;
|
|
312
|
+
skipped++;
|
|
313
|
+
charIndex += ch.length;
|
|
314
|
+
}
|
|
315
|
+
line = line.slice(charIndex);
|
|
316
|
+
}
|
|
283
317
|
const lineWidth = stringWidth(line);
|
|
284
318
|
let offsetX = 0;
|
|
285
319
|
if (this._align === "center") {
|
|
@@ -314,12 +348,14 @@ var LogView = class extends Widget {
|
|
|
314
348
|
if (this._autoScroll) {
|
|
315
349
|
this._scrollToBottom();
|
|
316
350
|
}
|
|
351
|
+
this.markDirty();
|
|
317
352
|
}
|
|
318
353
|
appendLine(line) {
|
|
319
354
|
this._lines.push(line);
|
|
320
355
|
if (this._autoScroll) {
|
|
321
356
|
this._scrollToBottom();
|
|
322
357
|
}
|
|
358
|
+
this.markDirty();
|
|
323
359
|
}
|
|
324
360
|
scrollUp(n = 1) {
|
|
325
361
|
this._scrollOffset = Math.max(0, this._scrollOffset - n);
|
|
@@ -381,6 +417,7 @@ var List = class extends Widget {
|
|
|
381
417
|
this._items = items;
|
|
382
418
|
this._selectedIndex = Math.min(this._selectedIndex, items.length - 1);
|
|
383
419
|
this._clampScroll();
|
|
420
|
+
this.markDirty();
|
|
384
421
|
}
|
|
385
422
|
/** Move selection up */
|
|
386
423
|
selectPrev() {
|
|
@@ -389,6 +426,7 @@ var List = class extends Widget {
|
|
|
389
426
|
if (next >= 0) {
|
|
390
427
|
this._selectedIndex = next;
|
|
391
428
|
this._clampScroll();
|
|
429
|
+
this.markDirty();
|
|
392
430
|
}
|
|
393
431
|
}
|
|
394
432
|
/** Move selection down */
|
|
@@ -398,6 +436,7 @@ var List = class extends Widget {
|
|
|
398
436
|
if (next < this._items.length) {
|
|
399
437
|
this._selectedIndex = next;
|
|
400
438
|
this._clampScroll();
|
|
439
|
+
this.markDirty();
|
|
401
440
|
}
|
|
402
441
|
}
|
|
403
442
|
/** Confirm the current selection */
|
|
@@ -446,7 +485,10 @@ var List = class extends Widget {
|
|
|
446
485
|
_clampScroll() {
|
|
447
486
|
const rect = this._getContentRect();
|
|
448
487
|
const visibleHeight = rect.height;
|
|
449
|
-
if (visibleHeight <= 0)
|
|
488
|
+
if (visibleHeight <= 0) {
|
|
489
|
+
this._scrollOffset = 0;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
450
492
|
if (this._selectedIndex < this._scrollOffset) {
|
|
451
493
|
this._scrollOffset = this._selectedIndex;
|
|
452
494
|
}
|
|
@@ -561,8 +603,173 @@ var TextInput = class extends Widget {
|
|
|
561
603
|
}
|
|
562
604
|
};
|
|
563
605
|
|
|
606
|
+
// src/input/VirtualList.ts
|
|
607
|
+
import { styleToCellAttrs as styleToCellAttrs7, truncate as truncate4, stringWidth as stringWidth4 } from "@termuijs/core";
|
|
608
|
+
var VirtualList = class extends Widget {
|
|
609
|
+
_totalItems;
|
|
610
|
+
_itemHeight;
|
|
611
|
+
_renderItem;
|
|
612
|
+
_onSelect;
|
|
613
|
+
_selectedIndex = 0;
|
|
614
|
+
_scrollOffset = 0;
|
|
615
|
+
_overscan;
|
|
616
|
+
_showScrollbar;
|
|
617
|
+
constructor(options) {
|
|
618
|
+
super({ border: "single", ...options.style });
|
|
619
|
+
this._totalItems = options.totalItems;
|
|
620
|
+
this._itemHeight = options.itemHeight ?? 1;
|
|
621
|
+
this._renderItem = options.renderItem;
|
|
622
|
+
this._onSelect = options.onSelect;
|
|
623
|
+
this._overscan = options.overscan ?? 2;
|
|
624
|
+
this._showScrollbar = options.showScrollbar ?? true;
|
|
625
|
+
this.focusable = true;
|
|
626
|
+
}
|
|
627
|
+
// ── Getters ──
|
|
628
|
+
get totalItems() {
|
|
629
|
+
return this._totalItems;
|
|
630
|
+
}
|
|
631
|
+
get selectedIndex() {
|
|
632
|
+
return this._selectedIndex;
|
|
633
|
+
}
|
|
634
|
+
get scrollOffset() {
|
|
635
|
+
return this._scrollOffset;
|
|
636
|
+
}
|
|
637
|
+
// ── Public API ──
|
|
638
|
+
/** Update the total item count (e.g., after data refresh) */
|
|
639
|
+
setTotalItems(count) {
|
|
640
|
+
this._totalItems = count;
|
|
641
|
+
this._selectedIndex = Math.min(this._selectedIndex, Math.max(0, count - 1));
|
|
642
|
+
this._clampScroll();
|
|
643
|
+
this.markDirty();
|
|
644
|
+
}
|
|
645
|
+
/** Update the render function (e.g., when data changes) */
|
|
646
|
+
setRenderItem(fn) {
|
|
647
|
+
this._renderItem = fn;
|
|
648
|
+
this.markDirty();
|
|
649
|
+
}
|
|
650
|
+
/** Move selection up by one */
|
|
651
|
+
selectPrev() {
|
|
652
|
+
if (this._selectedIndex > 0) {
|
|
653
|
+
this._selectedIndex--;
|
|
654
|
+
this._clampScroll();
|
|
655
|
+
this.markDirty();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/** Move selection down by one */
|
|
659
|
+
selectNext() {
|
|
660
|
+
if (this._selectedIndex < this._totalItems - 1) {
|
|
661
|
+
this._selectedIndex++;
|
|
662
|
+
this._clampScroll();
|
|
663
|
+
this.markDirty();
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/** Jump to the first item */
|
|
667
|
+
selectFirst() {
|
|
668
|
+
this._selectedIndex = 0;
|
|
669
|
+
this._clampScroll();
|
|
670
|
+
this.markDirty();
|
|
671
|
+
}
|
|
672
|
+
/** Jump to the last item */
|
|
673
|
+
selectLast() {
|
|
674
|
+
this._selectedIndex = Math.max(0, this._totalItems - 1);
|
|
675
|
+
this._clampScroll();
|
|
676
|
+
this.markDirty();
|
|
677
|
+
}
|
|
678
|
+
/** Page up — move by viewport height */
|
|
679
|
+
pageUp() {
|
|
680
|
+
const rect = this._getContentRect();
|
|
681
|
+
const pageSize = Math.floor(rect.height / this._itemHeight);
|
|
682
|
+
this._selectedIndex = Math.max(0, this._selectedIndex - pageSize);
|
|
683
|
+
this._clampScroll();
|
|
684
|
+
this.markDirty();
|
|
685
|
+
}
|
|
686
|
+
/** Page down — move by viewport height */
|
|
687
|
+
pageDown() {
|
|
688
|
+
const rect = this._getContentRect();
|
|
689
|
+
const pageSize = Math.floor(rect.height / this._itemHeight);
|
|
690
|
+
this._selectedIndex = Math.min(this._totalItems - 1, this._selectedIndex + pageSize);
|
|
691
|
+
this._clampScroll();
|
|
692
|
+
this.markDirty();
|
|
693
|
+
}
|
|
694
|
+
/** Scroll to a specific index */
|
|
695
|
+
scrollTo(index) {
|
|
696
|
+
this._selectedIndex = Math.max(0, Math.min(index, this._totalItems - 1));
|
|
697
|
+
this._clampScroll();
|
|
698
|
+
this.markDirty();
|
|
699
|
+
}
|
|
700
|
+
/** Confirm the current selection */
|
|
701
|
+
confirm() {
|
|
702
|
+
if (this._totalItems > 0) {
|
|
703
|
+
this._onSelect?.(this._selectedIndex);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// ── Rendering ──
|
|
707
|
+
_renderSelf(screen) {
|
|
708
|
+
const rect = this._getContentRect();
|
|
709
|
+
const { x, y, width, height } = rect;
|
|
710
|
+
if (width <= 0 || height <= 0 || this._totalItems === 0) return;
|
|
711
|
+
const attrs = styleToCellAttrs7(this._style);
|
|
712
|
+
const visibleItemCount = Math.floor(height / this._itemHeight);
|
|
713
|
+
const startIdx = Math.max(0, this._scrollOffset - this._overscan);
|
|
714
|
+
const endIdx = Math.min(this._totalItems, this._scrollOffset + visibleItemCount + this._overscan);
|
|
715
|
+
const contentWidth = this._showScrollbar && this._totalItems > visibleItemCount ? width - 1 : width;
|
|
716
|
+
for (let idx = startIdx; idx < endIdx; idx++) {
|
|
717
|
+
const rowY = y + (idx - this._scrollOffset) * this._itemHeight;
|
|
718
|
+
if (rowY < y || rowY >= y + height) continue;
|
|
719
|
+
const isSelected = idx === this._selectedIndex;
|
|
720
|
+
let content;
|
|
721
|
+
try {
|
|
722
|
+
content = this._renderItem(idx);
|
|
723
|
+
} catch {
|
|
724
|
+
content = `[Error: item ${idx}]`;
|
|
725
|
+
}
|
|
726
|
+
const prefix = isSelected ? "\u25B8 " : " ";
|
|
727
|
+
let line = prefix + content;
|
|
728
|
+
line = truncate4(line, contentWidth);
|
|
729
|
+
const cellStyle = {
|
|
730
|
+
...attrs,
|
|
731
|
+
bold: isSelected,
|
|
732
|
+
inverse: isSelected && this.isFocused
|
|
733
|
+
};
|
|
734
|
+
screen.writeString(x, rowY, line, cellStyle);
|
|
735
|
+
if (isSelected && this.isFocused) {
|
|
736
|
+
const remaining = contentWidth - stringWidth4(line);
|
|
737
|
+
for (let c = 0; c < remaining; c++) {
|
|
738
|
+
screen.setCell(x + stringWidth4(line) + c, rowY, { char: " ", ...cellStyle });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (this._showScrollbar && this._totalItems > visibleItemCount) {
|
|
743
|
+
const scrollbarX = x + width - 1;
|
|
744
|
+
const totalPages = this._totalItems - visibleItemCount;
|
|
745
|
+
const scrollRatio = totalPages > 0 ? this._scrollOffset / totalPages : 0;
|
|
746
|
+
const thumbPos = Math.floor(scrollRatio * (height - 1));
|
|
747
|
+
for (let r = 0; r < height; r++) {
|
|
748
|
+
const scrollChar = r === thumbPos ? "\u2588" : "\u2591";
|
|
749
|
+
screen.setCell(scrollbarX, y + r, { char: scrollChar, ...attrs, dim: r !== thumbPos });
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// ── Internal ──
|
|
754
|
+
_clampScroll() {
|
|
755
|
+
const rect = this._getContentRect();
|
|
756
|
+
const visibleHeight = Math.floor(rect.height / this._itemHeight);
|
|
757
|
+
if (visibleHeight <= 0) {
|
|
758
|
+
this._scrollOffset = 0;
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (this._selectedIndex < this._scrollOffset) {
|
|
762
|
+
this._scrollOffset = this._selectedIndex;
|
|
763
|
+
}
|
|
764
|
+
if (this._selectedIndex >= this._scrollOffset + visibleHeight) {
|
|
765
|
+
this._scrollOffset = this._selectedIndex - visibleHeight + 1;
|
|
766
|
+
}
|
|
767
|
+
this._scrollOffset = Math.max(0, Math.min(this._scrollOffset, this._totalItems - visibleHeight));
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
564
771
|
// src/data/Table.ts
|
|
565
|
-
import { styleToCellAttrs as
|
|
772
|
+
import { styleToCellAttrs as styleToCellAttrs8, stringWidth as stringWidth5, truncate as truncate5 } from "@termuijs/core";
|
|
566
773
|
var Table = class extends Widget {
|
|
567
774
|
_columns;
|
|
568
775
|
_rows;
|
|
@@ -583,13 +790,14 @@ var Table = class extends Widget {
|
|
|
583
790
|
}
|
|
584
791
|
setRows(rows) {
|
|
585
792
|
this._rows = rows;
|
|
793
|
+
this.markDirty();
|
|
586
794
|
}
|
|
587
795
|
_renderSelf(screen) {
|
|
588
796
|
const rect = this._getContentRect();
|
|
589
797
|
const { x, y, width, height } = rect;
|
|
590
798
|
if (width <= 0 || height <= 0) return;
|
|
591
|
-
const attrs =
|
|
592
|
-
const sepWidth =
|
|
799
|
+
const attrs = styleToCellAttrs8(this._style);
|
|
800
|
+
const sepWidth = stringWidth5(this._separator);
|
|
593
801
|
const colWidths = this._computeColumnWidths(
|
|
594
802
|
width - (this._columns.length - 1) * sepWidth
|
|
595
803
|
);
|
|
@@ -656,8 +864,8 @@ var Table = class extends Widget {
|
|
|
656
864
|
return this._columns.map((c) => c.width ?? flexWidth);
|
|
657
865
|
}
|
|
658
866
|
_alignText(text, width, align) {
|
|
659
|
-
const truncated =
|
|
660
|
-
const textWidth =
|
|
867
|
+
const truncated = truncate5(text, width);
|
|
868
|
+
const textWidth = stringWidth5(truncated);
|
|
661
869
|
const pad = Math.max(0, width - textWidth);
|
|
662
870
|
switch (align) {
|
|
663
871
|
case "right":
|
|
@@ -675,7 +883,7 @@ var Table = class extends Widget {
|
|
|
675
883
|
};
|
|
676
884
|
|
|
677
885
|
// src/data/Gauge.ts
|
|
678
|
-
import { styleToCellAttrs as
|
|
886
|
+
import { styleToCellAttrs as styleToCellAttrs9, stringWidth as stringWidth6 } from "@termuijs/core";
|
|
679
887
|
var Gauge = class extends Widget {
|
|
680
888
|
_label;
|
|
681
889
|
_value = 0;
|
|
@@ -689,22 +897,24 @@ var Gauge = class extends Widget {
|
|
|
689
897
|
}
|
|
690
898
|
setValue(value) {
|
|
691
899
|
this._value = Math.max(0, Math.min(1, value));
|
|
900
|
+
this.markDirty();
|
|
692
901
|
}
|
|
693
902
|
getValue() {
|
|
694
903
|
return this._value;
|
|
695
904
|
}
|
|
696
905
|
setLabel(label) {
|
|
697
906
|
this._label = label;
|
|
907
|
+
this.markDirty();
|
|
698
908
|
}
|
|
699
909
|
_renderSelf(screen) {
|
|
700
910
|
const rect = this._getContentRect();
|
|
701
911
|
const { x, y, width, height } = rect;
|
|
702
912
|
if (width <= 0 || height <= 0) return;
|
|
703
|
-
const attrs =
|
|
913
|
+
const attrs = styleToCellAttrs9(this._style);
|
|
704
914
|
const labelStr = this._label + " ";
|
|
705
915
|
const percentStr = this._showLabel ? ` ${Math.round(this._value * 100)}%` : "";
|
|
706
|
-
const labelWidth =
|
|
707
|
-
const percentWidth =
|
|
916
|
+
const labelWidth = stringWidth6(labelStr);
|
|
917
|
+
const percentWidth = stringWidth6(percentStr);
|
|
708
918
|
const barWidth = Math.max(0, width - labelWidth - percentWidth);
|
|
709
919
|
screen.writeString(x, y, labelStr, { ...attrs, bold: true });
|
|
710
920
|
const filled = Math.round(barWidth * this._value);
|
|
@@ -726,7 +936,7 @@ var Gauge = class extends Widget {
|
|
|
726
936
|
};
|
|
727
937
|
|
|
728
938
|
// src/data/Sparkline.ts
|
|
729
|
-
import { styleToCellAttrs as
|
|
939
|
+
import { styleToCellAttrs as styleToCellAttrs10 } from "@termuijs/core";
|
|
730
940
|
var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
731
941
|
var Sparkline = class extends Widget {
|
|
732
942
|
_label;
|
|
@@ -741,15 +951,17 @@ var Sparkline = class extends Widget {
|
|
|
741
951
|
}
|
|
742
952
|
setData(data) {
|
|
743
953
|
this._data = data;
|
|
954
|
+
this.markDirty();
|
|
744
955
|
}
|
|
745
956
|
pushValue(value) {
|
|
746
957
|
this._data.push(value);
|
|
958
|
+
this.markDirty();
|
|
747
959
|
}
|
|
748
960
|
_renderSelf(screen) {
|
|
749
961
|
const rect = this._getContentRect();
|
|
750
962
|
const { x, y, width, height } = rect;
|
|
751
963
|
if (width <= 0 || height <= 0) return;
|
|
752
|
-
const attrs =
|
|
964
|
+
const attrs = styleToCellAttrs10(this._style);
|
|
753
965
|
const labelStr = this._label + " ";
|
|
754
966
|
const labelWidth = labelStr.length;
|
|
755
967
|
screen.writeString(x, y, labelStr, { ...attrs, bold: true });
|
|
@@ -778,7 +990,7 @@ var Sparkline = class extends Widget {
|
|
|
778
990
|
};
|
|
779
991
|
|
|
780
992
|
// src/data/StatusIndicator.ts
|
|
781
|
-
import { styleToCellAttrs as
|
|
993
|
+
import { styleToCellAttrs as styleToCellAttrs11 } from "@termuijs/core";
|
|
782
994
|
var StatusIndicator = class extends Widget {
|
|
783
995
|
_label;
|
|
784
996
|
_isUp;
|
|
@@ -804,7 +1016,7 @@ var StatusIndicator = class extends Widget {
|
|
|
804
1016
|
const rect = this._getContentRect();
|
|
805
1017
|
const { x, y, width, height } = rect;
|
|
806
1018
|
if (width <= 0 || height <= 0) return;
|
|
807
|
-
const attrs =
|
|
1019
|
+
const attrs = styleToCellAttrs11(this._style);
|
|
808
1020
|
const dot = this._isUp ? "\u25CF" : "\u25CB";
|
|
809
1021
|
const statusText = this._isUp ? "Online" : "Offline";
|
|
810
1022
|
const color = this._isUp ? this._upColor : this._downColor;
|
|
@@ -816,8 +1028,200 @@ var StatusIndicator = class extends Widget {
|
|
|
816
1028
|
}
|
|
817
1029
|
};
|
|
818
1030
|
|
|
1031
|
+
// src/data/BarChart.ts
|
|
1032
|
+
import {
|
|
1033
|
+
stringWidth as stringWidth7,
|
|
1034
|
+
VERTICAL_BAR_SYMBOLS,
|
|
1035
|
+
HORIZONTAL_BAR_SYMBOLS
|
|
1036
|
+
} from "@termuijs/core";
|
|
1037
|
+
var BarChart = class extends Widget {
|
|
1038
|
+
_data = [];
|
|
1039
|
+
_direction;
|
|
1040
|
+
_barWidth;
|
|
1041
|
+
_barGap;
|
|
1042
|
+
_groupGap;
|
|
1043
|
+
_max;
|
|
1044
|
+
_barColor;
|
|
1045
|
+
_valueColor;
|
|
1046
|
+
_labelColor;
|
|
1047
|
+
constructor(data, style = {}, opts = {}) {
|
|
1048
|
+
super(style);
|
|
1049
|
+
this._data = data;
|
|
1050
|
+
this._direction = opts.direction ?? "vertical";
|
|
1051
|
+
this._barWidth = opts.barWidth ?? 1;
|
|
1052
|
+
this._barGap = opts.barGap ?? 1;
|
|
1053
|
+
this._groupGap = opts.groupGap ?? 2;
|
|
1054
|
+
this._max = opts.max;
|
|
1055
|
+
this._barColor = opts.barColor ?? { type: "named", name: "cyan" };
|
|
1056
|
+
this._valueColor = opts.valueColor ?? { type: "named", name: "white" };
|
|
1057
|
+
this._labelColor = opts.labelColor ?? { type: "named", name: "brightBlack" };
|
|
1058
|
+
}
|
|
1059
|
+
setData(data) {
|
|
1060
|
+
this._data = data;
|
|
1061
|
+
this.markDirty();
|
|
1062
|
+
}
|
|
1063
|
+
setMax(max) {
|
|
1064
|
+
this._max = max;
|
|
1065
|
+
this.markDirty();
|
|
1066
|
+
}
|
|
1067
|
+
_renderSelf(screen) {
|
|
1068
|
+
const rect = this._getContentRect();
|
|
1069
|
+
const { x, y, width, height } = rect;
|
|
1070
|
+
if (width <= 0 || height <= 0 || this._data.length === 0) return;
|
|
1071
|
+
const maxVal = this._computeMax();
|
|
1072
|
+
if (maxVal === 0) return;
|
|
1073
|
+
if (this._direction === "vertical") {
|
|
1074
|
+
this._renderVertical(screen, x, y, width, height, maxVal);
|
|
1075
|
+
} else {
|
|
1076
|
+
this._renderHorizontal(screen, x, y, width, height, maxVal);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
_computeMax() {
|
|
1080
|
+
if (this._max !== void 0) return this._max;
|
|
1081
|
+
let max = 0;
|
|
1082
|
+
for (const group of this._data) {
|
|
1083
|
+
for (const bar of group.bars) {
|
|
1084
|
+
if (bar.value > max) max = bar.value;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return max;
|
|
1088
|
+
}
|
|
1089
|
+
// ── Vertical Rendering ───────────────────────────
|
|
1090
|
+
_renderVertical(screen, ox, oy, width, height, maxVal) {
|
|
1091
|
+
const valueRows = 1;
|
|
1092
|
+
const hasLabels = this._data.some(
|
|
1093
|
+
(g) => g.bars.some((b) => b.label !== void 0)
|
|
1094
|
+
);
|
|
1095
|
+
const labelRows = hasLabels ? 1 : 0;
|
|
1096
|
+
const hasGroupLabels = this._data.some((g) => g.label !== void 0);
|
|
1097
|
+
const groupLabelRows = hasGroupLabels ? 1 : 0;
|
|
1098
|
+
const reservedRows = valueRows + labelRows + groupLabelRows;
|
|
1099
|
+
if (height <= reservedRows) return;
|
|
1100
|
+
const barAreaHeight = height - reservedRows;
|
|
1101
|
+
let cx = ox;
|
|
1102
|
+
for (let gi = 0; gi < this._data.length; gi++) {
|
|
1103
|
+
const group = this._data[gi];
|
|
1104
|
+
if (!group) continue;
|
|
1105
|
+
const groupStartX = cx;
|
|
1106
|
+
for (let bi = 0; bi < group.bars.length; bi++) {
|
|
1107
|
+
const bar = group.bars[bi];
|
|
1108
|
+
if (!bar) continue;
|
|
1109
|
+
if (cx + this._barWidth > ox + width) break;
|
|
1110
|
+
const color = bar.color ?? this._barColor;
|
|
1111
|
+
const scaledHeight = bar.value / maxVal * (barAreaHeight * 8);
|
|
1112
|
+
let remaining = Math.round(scaledHeight);
|
|
1113
|
+
for (let row = barAreaHeight - 1; row >= 0; row--) {
|
|
1114
|
+
if (remaining <= 0) break;
|
|
1115
|
+
const level = Math.min(remaining, 8);
|
|
1116
|
+
const symbol = VERTICAL_BAR_SYMBOLS[level] ?? " ";
|
|
1117
|
+
const cellY = oy + row;
|
|
1118
|
+
for (let col = 0; col < this._barWidth; col++) {
|
|
1119
|
+
const cellX = cx + col;
|
|
1120
|
+
if (cellX < ox + width) {
|
|
1121
|
+
screen.setCell(cellX, cellY, { char: symbol, fg: color });
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
remaining -= 8;
|
|
1125
|
+
}
|
|
1126
|
+
const valStr = Math.round(bar.value).toString();
|
|
1127
|
+
const valX = cx + Math.floor((this._barWidth - stringWidth7(valStr)) / 2);
|
|
1128
|
+
screen.writeString(
|
|
1129
|
+
Math.max(cx, valX),
|
|
1130
|
+
oy + barAreaHeight,
|
|
1131
|
+
valStr.slice(0, this._barWidth),
|
|
1132
|
+
{ fg: this._valueColor }
|
|
1133
|
+
);
|
|
1134
|
+
if (hasLabels && bar.label) {
|
|
1135
|
+
const label = bar.label.slice(0, this._barWidth);
|
|
1136
|
+
const labelX = cx + Math.floor((this._barWidth - stringWidth7(label)) / 2);
|
|
1137
|
+
screen.writeString(
|
|
1138
|
+
Math.max(cx, labelX),
|
|
1139
|
+
oy + barAreaHeight + valueRows,
|
|
1140
|
+
label,
|
|
1141
|
+
{ fg: this._labelColor }
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
cx += this._barWidth;
|
|
1145
|
+
if (bi < group.bars.length - 1) cx += this._barGap;
|
|
1146
|
+
}
|
|
1147
|
+
if (hasGroupLabels && group.label) {
|
|
1148
|
+
const groupWidth = cx - groupStartX;
|
|
1149
|
+
const label = group.label.slice(0, groupWidth);
|
|
1150
|
+
const labelX = groupStartX + Math.floor((groupWidth - stringWidth7(label)) / 2);
|
|
1151
|
+
screen.writeString(
|
|
1152
|
+
Math.max(groupStartX, labelX),
|
|
1153
|
+
oy + height - 1,
|
|
1154
|
+
label,
|
|
1155
|
+
{ fg: this._labelColor }
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
if (gi < this._data.length - 1) cx += this._groupGap;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
// ── Horizontal Rendering ─────────────────────────
|
|
1162
|
+
_renderHorizontal(screen, ox, oy, width, height, maxVal) {
|
|
1163
|
+
let maxLabelWidth = 0;
|
|
1164
|
+
let maxValueWidth = 0;
|
|
1165
|
+
for (const group of this._data) {
|
|
1166
|
+
for (const bar of group.bars) {
|
|
1167
|
+
if (bar.label) {
|
|
1168
|
+
const w = stringWidth7(bar.label);
|
|
1169
|
+
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
1170
|
+
}
|
|
1171
|
+
const vw = Math.round(bar.value).toString().length;
|
|
1172
|
+
if (vw > maxValueWidth) maxValueWidth = vw;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
const labelColWidth = maxLabelWidth > 0 ? maxLabelWidth + 1 : 0;
|
|
1176
|
+
const valueColWidth = maxValueWidth > 0 ? maxValueWidth + 1 : 0;
|
|
1177
|
+
const barAreaWidth = width - labelColWidth - valueColWidth;
|
|
1178
|
+
if (barAreaWidth <= 0) return;
|
|
1179
|
+
let cy = oy;
|
|
1180
|
+
for (let gi = 0; gi < this._data.length; gi++) {
|
|
1181
|
+
const group = this._data[gi];
|
|
1182
|
+
if (!group) continue;
|
|
1183
|
+
for (let bi = 0; bi < group.bars.length; bi++) {
|
|
1184
|
+
const bar = group.bars[bi];
|
|
1185
|
+
if (!bar) continue;
|
|
1186
|
+
for (let row = 0; row < this._barWidth; row++) {
|
|
1187
|
+
const cellY = cy + row;
|
|
1188
|
+
if (cellY >= oy + height) break;
|
|
1189
|
+
if (row === 0 && bar.label) {
|
|
1190
|
+
const label = bar.label.slice(0, maxLabelWidth);
|
|
1191
|
+
const padded = label.padStart(maxLabelWidth);
|
|
1192
|
+
screen.writeString(ox, cellY, padded, { fg: this._labelColor });
|
|
1193
|
+
}
|
|
1194
|
+
const color = bar.color ?? this._barColor;
|
|
1195
|
+
const scaledWidth = bar.value / maxVal * (barAreaWidth * 8);
|
|
1196
|
+
let remaining = Math.round(scaledWidth);
|
|
1197
|
+
const barStartX = ox + labelColWidth;
|
|
1198
|
+
for (let col = 0; col < barAreaWidth; col++) {
|
|
1199
|
+
if (remaining <= 0) break;
|
|
1200
|
+
const level = Math.min(remaining, 8);
|
|
1201
|
+
const symbol = HORIZONTAL_BAR_SYMBOLS[level] ?? " ";
|
|
1202
|
+
screen.setCell(barStartX + col, cellY, { char: symbol, fg: color });
|
|
1203
|
+
remaining -= 8;
|
|
1204
|
+
}
|
|
1205
|
+
if (row === 0) {
|
|
1206
|
+
const valStr = Math.round(bar.value).toString();
|
|
1207
|
+
screen.writeString(
|
|
1208
|
+
ox + labelColWidth + barAreaWidth,
|
|
1209
|
+
cellY,
|
|
1210
|
+
` ${valStr}`,
|
|
1211
|
+
{ fg: this._valueColor }
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
cy += this._barWidth;
|
|
1216
|
+
if (bi < group.bars.length - 1) cy += this._barGap;
|
|
1217
|
+
}
|
|
1218
|
+
if (gi < this._data.length - 1) cy += this._groupGap;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
819
1223
|
// src/feedback/ProgressBar.ts
|
|
820
|
-
import { styleToCellAttrs as
|
|
1224
|
+
import { styleToCellAttrs as styleToCellAttrs13 } from "@termuijs/core";
|
|
821
1225
|
var ProgressBar = class extends Widget {
|
|
822
1226
|
_value;
|
|
823
1227
|
_fillChar;
|
|
@@ -839,6 +1243,7 @@ var ProgressBar = class extends Widget {
|
|
|
839
1243
|
/** Set progress value (0–1) */
|
|
840
1244
|
setValue(value) {
|
|
841
1245
|
this._value = Math.max(0, Math.min(1, value));
|
|
1246
|
+
this.markDirty();
|
|
842
1247
|
}
|
|
843
1248
|
get value() {
|
|
844
1249
|
return this._value;
|
|
@@ -847,7 +1252,7 @@ var ProgressBar = class extends Widget {
|
|
|
847
1252
|
const rect = this._getContentRect();
|
|
848
1253
|
const { x, y, width } = rect;
|
|
849
1254
|
if (width <= 0) return;
|
|
850
|
-
const attrs =
|
|
1255
|
+
const attrs = styleToCellAttrs13(this._style);
|
|
851
1256
|
let label = "";
|
|
852
1257
|
if (this._showLabel) {
|
|
853
1258
|
if (this._labelFormat === "percent") {
|
|
@@ -872,7 +1277,7 @@ var ProgressBar = class extends Widget {
|
|
|
872
1277
|
};
|
|
873
1278
|
|
|
874
1279
|
// src/feedback/Spinner.ts
|
|
875
|
-
import { styleToCellAttrs as
|
|
1280
|
+
import { styleToCellAttrs as styleToCellAttrs14 } from "@termuijs/core";
|
|
876
1281
|
var SPINNER_FRAMES = {
|
|
877
1282
|
dots: {
|
|
878
1283
|
frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
@@ -942,7 +1347,7 @@ var Spinner = class extends Widget {
|
|
|
942
1347
|
const rect = this._getContentRect();
|
|
943
1348
|
const { x, y, width } = rect;
|
|
944
1349
|
if (width <= 0) return;
|
|
945
|
-
const attrs =
|
|
1350
|
+
const attrs = styleToCellAttrs14(this._style);
|
|
946
1351
|
const frame = this._frames[this._frameIndex];
|
|
947
1352
|
screen.writeString(x, y, frame, { ...attrs, fg: this._color });
|
|
948
1353
|
if (this._label) {
|
|
@@ -950,19 +1355,107 @@ var Spinner = class extends Widget {
|
|
|
950
1355
|
}
|
|
951
1356
|
}
|
|
952
1357
|
};
|
|
1358
|
+
|
|
1359
|
+
// src/feedback/Scrollbar.ts
|
|
1360
|
+
import {
|
|
1361
|
+
ScrollbarSets
|
|
1362
|
+
} from "@termuijs/core";
|
|
1363
|
+
var Scrollbar = class extends Widget {
|
|
1364
|
+
_contentLength;
|
|
1365
|
+
_viewportLength;
|
|
1366
|
+
_position;
|
|
1367
|
+
_orientation;
|
|
1368
|
+
_thumbColor;
|
|
1369
|
+
_trackColor;
|
|
1370
|
+
_showArrows;
|
|
1371
|
+
constructor(style = {}, opts) {
|
|
1372
|
+
super(style);
|
|
1373
|
+
this._contentLength = opts.contentLength;
|
|
1374
|
+
this._viewportLength = opts.viewportLength;
|
|
1375
|
+
this._position = opts.position ?? 0;
|
|
1376
|
+
this._orientation = opts.orientation ?? "verticalRight";
|
|
1377
|
+
this._thumbColor = opts.thumbColor ?? { type: "named", name: "white" };
|
|
1378
|
+
this._trackColor = opts.trackColor ?? { type: "named", name: "brightBlack" };
|
|
1379
|
+
this._showArrows = opts.showArrows ?? true;
|
|
1380
|
+
}
|
|
1381
|
+
setPosition(position) {
|
|
1382
|
+
this._position = position;
|
|
1383
|
+
this.markDirty();
|
|
1384
|
+
}
|
|
1385
|
+
setContentLength(length) {
|
|
1386
|
+
this._contentLength = length;
|
|
1387
|
+
this.markDirty();
|
|
1388
|
+
}
|
|
1389
|
+
setViewportLength(length) {
|
|
1390
|
+
this._viewportLength = length;
|
|
1391
|
+
this.markDirty();
|
|
1392
|
+
}
|
|
1393
|
+
_renderSelf(screen) {
|
|
1394
|
+
const rect = this._getContentRect();
|
|
1395
|
+
const { x, y, width, height } = rect;
|
|
1396
|
+
if (width <= 0 || height <= 0 || this._contentLength <= 0) return;
|
|
1397
|
+
if (this._contentLength <= this._viewportLength) return;
|
|
1398
|
+
const vertical = this._orientation === "verticalRight" || this._orientation === "verticalLeft";
|
|
1399
|
+
const symbols = vertical ? ScrollbarSets.VERTICAL : ScrollbarSets.HORIZONTAL;
|
|
1400
|
+
const trackX = this._orientation === "verticalLeft" ? x : this._orientation === "verticalRight" ? x + width - 1 : x;
|
|
1401
|
+
const trackY = this._orientation === "horizontalTop" ? y : this._orientation === "horizontalBottom" ? y + height - 1 : y;
|
|
1402
|
+
const totalLength = vertical ? height : width;
|
|
1403
|
+
if (totalLength <= 0) return;
|
|
1404
|
+
let trackStart = 0;
|
|
1405
|
+
let trackLength = totalLength;
|
|
1406
|
+
if (this._showArrows && totalLength > 2) {
|
|
1407
|
+
const beginX = vertical ? trackX : x;
|
|
1408
|
+
const beginY = vertical ? y : trackY;
|
|
1409
|
+
screen.setCell(beginX, beginY, {
|
|
1410
|
+
char: symbols.begin,
|
|
1411
|
+
fg: this._trackColor
|
|
1412
|
+
});
|
|
1413
|
+
const endX = vertical ? trackX : x + totalLength - 1;
|
|
1414
|
+
const endY = vertical ? y + totalLength - 1 : trackY;
|
|
1415
|
+
screen.setCell(endX, endY, {
|
|
1416
|
+
char: symbols.end,
|
|
1417
|
+
fg: this._trackColor
|
|
1418
|
+
});
|
|
1419
|
+
trackStart = 1;
|
|
1420
|
+
trackLength -= 2;
|
|
1421
|
+
}
|
|
1422
|
+
if (trackLength <= 0) return;
|
|
1423
|
+
const thumbSize = Math.max(1, Math.floor(
|
|
1424
|
+
trackLength * this._viewportLength / this._contentLength
|
|
1425
|
+
));
|
|
1426
|
+
const maxScroll = Math.max(1, this._contentLength - this._viewportLength);
|
|
1427
|
+
const thumbOffset = Math.min(
|
|
1428
|
+
trackLength - thumbSize,
|
|
1429
|
+
Math.floor(this._position * (trackLength - thumbSize) / maxScroll)
|
|
1430
|
+
);
|
|
1431
|
+
for (let i = 0; i < trackLength; i++) {
|
|
1432
|
+
const pos = trackStart + i;
|
|
1433
|
+
const cellX = vertical ? trackX : x + pos;
|
|
1434
|
+
const cellY = vertical ? y + pos : trackY;
|
|
1435
|
+
const isThumb = i >= thumbOffset && i < thumbOffset + thumbSize;
|
|
1436
|
+
screen.setCell(cellX, cellY, {
|
|
1437
|
+
char: isThumb ? symbols.thumb : symbols.track,
|
|
1438
|
+
fg: isThumb ? this._thumbColor : this._trackColor
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
953
1443
|
export {
|
|
1444
|
+
BarChart,
|
|
954
1445
|
Box,
|
|
955
1446
|
Gauge,
|
|
956
1447
|
List,
|
|
957
1448
|
LogView,
|
|
958
1449
|
ProgressBar,
|
|
959
1450
|
SPINNER_FRAMES,
|
|
1451
|
+
Scrollbar,
|
|
960
1452
|
Sparkline,
|
|
961
1453
|
Spinner,
|
|
962
1454
|
StatusIndicator,
|
|
963
1455
|
Table,
|
|
964
1456
|
Text,
|
|
965
1457
|
TextInput,
|
|
1458
|
+
VirtualList,
|
|
966
1459
|
Widget
|
|
967
1460
|
};
|
|
968
1461
|
//# sourceMappingURL=index.js.map
|