@opentui/core 0.1.14 → 0.1.15

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/index.js CHANGED
@@ -27,7 +27,6 @@ import {
27
27
  TerminalConsole,
28
28
  TextAttributes,
29
29
  TextBuffer,
30
- TextSelectionHelper,
31
30
  TrackedNode,
32
31
  bg,
33
32
  bgBlack,
@@ -52,6 +51,7 @@ import {
52
51
  brightWhite,
53
52
  brightYellow,
54
53
  capture,
54
+ convertGlobalToLocalSelection,
55
55
  coordinateToCharacterIndex,
56
56
  createCliRenderer,
57
57
  createTextAttributes,
@@ -79,6 +79,7 @@ import {
79
79
  isPaddingType,
80
80
  isPositionType,
81
81
  isPositionTypeType,
82
+ isRenderable,
82
83
  isSizeType,
83
84
  isVNode,
84
85
  isValidPercentage,
@@ -117,7 +118,7 @@ import {
117
118
  white,
118
119
  wrapWithDelegates,
119
120
  yellow
120
- } from "./index-rv93tneq.js";
121
+ } from "./index-sw194bbj.js";
121
122
  // src/post/filters.ts
122
123
  function applyScanlines(buffer, strength = 0.8, step = 2) {
123
124
  const width = buffer.width;
@@ -1446,15 +1447,14 @@ class FrameBufferRenderable extends Renderable {
1446
1447
  // src/renderables/Text.ts
1447
1448
  class TextRenderable extends Renderable {
1448
1449
  selectable = true;
1449
- _text = stringToStyledText("");
1450
+ _text;
1450
1451
  _defaultFg;
1451
1452
  _defaultBg;
1452
1453
  _defaultAttributes;
1453
1454
  _selectionBg;
1454
1455
  _selectionFg;
1455
- selectionHelper;
1456
+ lastLocalSelection = null;
1456
1457
  textBuffer;
1457
- _plainText = "";
1458
1458
  _lineInfo = { lineStarts: [], lineWidths: [] };
1459
1459
  _defaultOptions = {
1460
1460
  content: "",
@@ -1467,9 +1467,9 @@ class TextRenderable extends Renderable {
1467
1467
  };
1468
1468
  constructor(ctx, options) {
1469
1469
  super(ctx, options);
1470
- this.selectionHelper = new TextSelectionHelper(() => this.x, () => this.y, () => this._plainText.length, () => this._lineInfo);
1471
1470
  const content = options.content ?? this._defaultOptions.content;
1472
- this._text = typeof content === "string" ? stringToStyledText(content) : content;
1471
+ const styledText = typeof content === "string" ? stringToStyledText(content) : content;
1472
+ this._text = styledText;
1473
1473
  this._defaultFg = parseColor(options.fg ?? this._defaultOptions.fg);
1474
1474
  this._defaultBg = parseColor(options.bg ?? this._defaultOptions.bg);
1475
1475
  this._defaultAttributes = options.attributes ?? this._defaultOptions.attributes;
@@ -1481,14 +1481,15 @@ class TextRenderable extends Renderable {
1481
1481
  this.textBuffer.setDefaultBg(this._defaultBg);
1482
1482
  this.textBuffer.setDefaultAttributes(this._defaultAttributes);
1483
1483
  this.setupMeasureFunc();
1484
- this.updateTextInfo();
1484
+ this.updateTextInfo(styledText);
1485
1485
  }
1486
1486
  get content() {
1487
1487
  return this._text;
1488
1488
  }
1489
1489
  set content(value) {
1490
- this._text = typeof value === "string" ? stringToStyledText(value) : value;
1491
- this.updateTextInfo();
1490
+ const styledText = typeof value === "string" ? stringToStyledText(value) : value;
1491
+ this._text = styledText;
1492
+ this.updateTextInfo(styledText);
1492
1493
  }
1493
1494
  get fg() {
1494
1495
  return this._defaultFg;
@@ -1508,7 +1509,9 @@ class TextRenderable extends Renderable {
1508
1509
  const newColor = value ? parseColor(value) : this._defaultOptions.selectionBg;
1509
1510
  if (this._selectionBg !== newColor) {
1510
1511
  this._selectionBg = newColor;
1511
- this.syncSelectionToTextBuffer();
1512
+ if (this.lastLocalSelection) {
1513
+ this.updateLocalSelection(this.lastLocalSelection);
1514
+ }
1512
1515
  this.requestRender();
1513
1516
  }
1514
1517
  }
@@ -1519,7 +1522,9 @@ class TextRenderable extends Renderable {
1519
1522
  const newColor = value ? parseColor(value) : this._defaultOptions.selectionFg;
1520
1523
  if (this._selectionFg !== newColor) {
1521
1524
  this._selectionFg = newColor;
1522
- this.syncSelectionToTextBuffer();
1525
+ if (this.lastLocalSelection) {
1526
+ this.updateLocalSelection(this.lastLocalSelection);
1527
+ }
1523
1528
  this.requestRender();
1524
1529
  }
1525
1530
  }
@@ -1545,29 +1550,30 @@ class TextRenderable extends Renderable {
1545
1550
  }
1546
1551
  }
1547
1552
  onResize(width, height) {
1548
- const changed = this.selectionHelper.reevaluateSelection(width, height);
1549
- if (changed) {
1550
- this.syncSelectionToTextBuffer();
1551
- this.requestRender();
1553
+ if (this.lastLocalSelection) {
1554
+ const changed = this.updateLocalSelection(this.lastLocalSelection);
1555
+ if (changed) {
1556
+ this.requestRender();
1557
+ }
1552
1558
  }
1553
1559
  }
1554
- syncSelectionToTextBuffer() {
1555
- const selection = this.selectionHelper.getSelection();
1556
- if (selection) {
1557
- this.textBuffer.setSelection(selection.start, selection.end, this._selectionBg, this._selectionFg);
1558
- } else {
1559
- this.textBuffer.resetSelection();
1560
+ updateLocalSelection(localSelection) {
1561
+ if (!localSelection?.isActive) {
1562
+ this.textBuffer.resetLocalSelection();
1563
+ return true;
1560
1564
  }
1565
+ return this.textBuffer.setLocalSelection(localSelection.anchorX, localSelection.anchorY, localSelection.focusX, localSelection.focusY, this._selectionBg, this._selectionFg);
1561
1566
  }
1562
- updateTextInfo() {
1563
- this._plainText = this._text.toString();
1564
- this.updateTextBuffer();
1567
+ updateTextInfo(styledText) {
1568
+ this.updateTextBuffer(styledText);
1565
1569
  const lineInfo = this.textBuffer.lineInfo;
1566
1570
  this._lineInfo.lineStarts = lineInfo.lineStarts;
1567
1571
  this._lineInfo.lineWidths = lineInfo.lineWidths;
1568
- const changed = this.selectionHelper.reevaluateSelection(this.width, this.height);
1569
- if (changed) {
1570
- this.syncSelectionToTextBuffer();
1572
+ if (this.lastLocalSelection) {
1573
+ const changed = this.updateLocalSelection(this.lastLocalSelection);
1574
+ if (changed) {
1575
+ this.requestRender();
1576
+ }
1571
1577
  }
1572
1578
  this.layoutNode.yogaNode.markDirty();
1573
1579
  this.requestRender();
@@ -1596,27 +1602,32 @@ class TextRenderable extends Renderable {
1596
1602
  this.layoutNode.yogaNode.setMeasureFunc(measureFunc);
1597
1603
  }
1598
1604
  shouldStartSelection(x, y) {
1599
- return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height);
1605
+ if (!this.selectable)
1606
+ return false;
1607
+ const localX = x - this.x;
1608
+ const localY = y - this.y;
1609
+ return localX >= 0 && localX < this.width && localY >= 0 && localY < this.height;
1600
1610
  }
1601
1611
  onSelectionChanged(selection) {
1602
- const changed = this.selectionHelper.onSelectionChanged(selection, this.width, this.height);
1612
+ const localSelection = convertGlobalToLocalSelection(selection, this.x, this.y);
1613
+ this.lastLocalSelection = localSelection;
1614
+ const changed = this.updateLocalSelection(localSelection);
1603
1615
  if (changed) {
1604
- this.syncSelectionToTextBuffer();
1605
1616
  this.requestRender();
1606
1617
  }
1607
- return this.selectionHelper.hasSelection();
1618
+ return this.hasSelection();
1608
1619
  }
1609
1620
  getSelectedText() {
1610
- const selection = this.selectionHelper.getSelection();
1611
- if (!selection)
1612
- return "";
1613
- return this._plainText.slice(selection.start, selection.end);
1621
+ return this.textBuffer.getSelectedText();
1614
1622
  }
1615
1623
  hasSelection() {
1616
- return this.selectionHelper.hasSelection();
1624
+ return this.textBuffer.hasSelection();
1617
1625
  }
1618
- updateTextBuffer() {
1619
- this.textBuffer.setStyledText(this._text);
1626
+ getSelection() {
1627
+ return this.textBuffer.getSelection();
1628
+ }
1629
+ updateTextBuffer(styledText) {
1630
+ this.textBuffer.setStyledText(styledText);
1620
1631
  }
1621
1632
  renderSelf(buffer) {
1622
1633
  if (this.textBuffer.ptr) {
@@ -1643,6 +1654,7 @@ class ASCIIFontRenderable extends FrameBufferRenderable {
1643
1654
  _bg;
1644
1655
  _selectionBg;
1645
1656
  _selectionFg;
1657
+ lastLocalSelection = null;
1646
1658
  selectionHelper;
1647
1659
  constructor(ctx, options) {
1648
1660
  const font = options.font || "tiny";
@@ -1661,7 +1673,7 @@ class ASCIIFontRenderable extends FrameBufferRenderable {
1661
1673
  this._selectionBg = options.selectionBg ? parseColor(options.selectionBg) : undefined;
1662
1674
  this._selectionFg = options.selectionFg ? parseColor(options.selectionFg) : undefined;
1663
1675
  this.selectable = options.selectable ?? true;
1664
- this.selectionHelper = new ASCIIFontSelectionHelper(() => this.x, () => this.y, () => this._text, () => this._font);
1676
+ this.selectionHelper = new ASCIIFontSelectionHelper(() => this._text, () => this._font);
1665
1677
  this.renderFontToBuffer();
1666
1678
  }
1667
1679
  get text() {
@@ -1670,7 +1682,9 @@ class ASCIIFontRenderable extends FrameBufferRenderable {
1670
1682
  set text(value) {
1671
1683
  this._text = value;
1672
1684
  this.updateDimensions();
1673
- this.selectionHelper.reevaluateSelection(this.width, this.height);
1685
+ if (this.lastLocalSelection) {
1686
+ this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
1687
+ }
1674
1688
  this.renderFontToBuffer();
1675
1689
  this.requestRender();
1676
1690
  }
@@ -1680,7 +1694,9 @@ class ASCIIFontRenderable extends FrameBufferRenderable {
1680
1694
  set font(value) {
1681
1695
  this._font = value;
1682
1696
  this.updateDimensions();
1683
- this.selectionHelper.reevaluateSelection(this.width, this.height);
1697
+ if (this.lastLocalSelection) {
1698
+ this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
1699
+ }
1684
1700
  this.renderFontToBuffer();
1685
1701
  this.requestRender();
1686
1702
  }
@@ -1710,15 +1726,19 @@ class ASCIIFontRenderable extends FrameBufferRenderable {
1710
1726
  this.height = measurements.height;
1711
1727
  }
1712
1728
  shouldStartSelection(x, y) {
1713
- return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height);
1729
+ const localX = x - this.x;
1730
+ const localY = y - this.y;
1731
+ return this.selectionHelper.shouldStartSelection(localX, localY, this.width, this.height);
1714
1732
  }
1715
1733
  onSelectionChanged(selection) {
1716
- const changed = this.selectionHelper.onSelectionChanged(selection, this.width, this.height);
1734
+ const localSelection = convertGlobalToLocalSelection(selection, this.x, this.y);
1735
+ this.lastLocalSelection = localSelection;
1736
+ const changed = this.selectionHelper.onLocalSelectionChanged(localSelection, this.width, this.height);
1717
1737
  if (changed) {
1718
1738
  this.renderFontToBuffer();
1719
1739
  this.requestRender();
1720
1740
  }
1721
- return this.selectionHelper.hasSelection();
1741
+ return changed;
1722
1742
  }
1723
1743
  getSelectedText() {
1724
1744
  const selection = this.selectionHelper.getSelection();
@@ -2674,6 +2694,791 @@ class TabSelectRenderable extends Renderable {
2674
2694
  this.requestRender();
2675
2695
  }
2676
2696
  }
2697
+ // src/renderables/Slider.ts
2698
+ var defaultThumbBackgroundColor = RGBA.fromHex("#9a9ea3");
2699
+ var defaultTrackBackgroundColor = RGBA.fromHex("#252527");
2700
+
2701
+ class SliderRenderable extends Renderable {
2702
+ orientation;
2703
+ _thumbSize;
2704
+ _thumbPosition;
2705
+ _backgroundColor;
2706
+ _foregroundColor;
2707
+ _onChange;
2708
+ constructor(ctx, options) {
2709
+ super(ctx, options);
2710
+ this.orientation = options.orientation;
2711
+ this._thumbSize = options.thumbSize ?? 1;
2712
+ this._thumbPosition = options.thumbPosition ?? 0;
2713
+ this._onChange = options.onChange;
2714
+ this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : defaultTrackBackgroundColor;
2715
+ this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : defaultThumbBackgroundColor;
2716
+ this.setupMouseHandling();
2717
+ }
2718
+ get thumbSize() {
2719
+ return this._thumbSize;
2720
+ }
2721
+ set thumbSize(value) {
2722
+ const clamped = Math.max(1, Math.min(value, this.orientation === "vertical" ? this.height : this.width));
2723
+ if (clamped !== this._thumbSize) {
2724
+ this._thumbSize = clamped;
2725
+ this.requestRender();
2726
+ }
2727
+ }
2728
+ get thumbPosition() {
2729
+ return this._thumbPosition;
2730
+ }
2731
+ set thumbPosition(value) {
2732
+ const clamped = Math.max(0, Math.min(1, value));
2733
+ if (clamped !== this._thumbPosition) {
2734
+ this._thumbPosition = clamped;
2735
+ this._onChange?.(clamped);
2736
+ this.emit("change", { position: clamped });
2737
+ this.requestRender();
2738
+ }
2739
+ }
2740
+ get backgroundColor() {
2741
+ return this._backgroundColor;
2742
+ }
2743
+ set backgroundColor(value) {
2744
+ this._backgroundColor = parseColor(value);
2745
+ this.requestRender();
2746
+ }
2747
+ get foregroundColor() {
2748
+ return this._foregroundColor;
2749
+ }
2750
+ set foregroundColor(value) {
2751
+ this._foregroundColor = parseColor(value);
2752
+ this.requestRender();
2753
+ }
2754
+ setupMouseHandling() {
2755
+ let isDragging = false;
2756
+ let relativeStartPos = 0;
2757
+ this.onMouseDown = (event) => {
2758
+ event.stopPropagation();
2759
+ isDragging = true;
2760
+ const thumbRect = this.getThumbRect();
2761
+ const isOnThumb = event.x >= thumbRect.x && event.x < thumbRect.x + thumbRect.width && event.y >= thumbRect.y && event.y < thumbRect.y + thumbRect.height;
2762
+ if (isOnThumb) {
2763
+ relativeStartPos = this.orientation === "vertical" ? event.y - thumbRect.y : event.x - thumbRect.x;
2764
+ } else {
2765
+ relativeStartPos = this.orientation === "vertical" ? thumbRect.height / 2 : thumbRect.width / 2;
2766
+ }
2767
+ this.updatePositionFromMouse(event, relativeStartPos);
2768
+ };
2769
+ this.onMouseDrag = (event) => {
2770
+ if (!isDragging)
2771
+ return;
2772
+ event.stopPropagation();
2773
+ this.updatePositionFromMouse(event, relativeStartPos);
2774
+ };
2775
+ this.onMouseUp = () => {
2776
+ isDragging = false;
2777
+ };
2778
+ }
2779
+ updatePositionFromMouse(event, relativeStartPos) {
2780
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
2781
+ const trackSize = this.orientation === "vertical" ? this.height : this.width;
2782
+ const mousePos = this.orientation === "vertical" ? event.y : event.x;
2783
+ const thumbStartPos = mousePos - trackStart - relativeStartPos;
2784
+ const maxThumbStartPos = trackSize - this._thumbSize;
2785
+ const clampedThumbStartPos = Math.max(0, Math.min(maxThumbStartPos, thumbStartPos));
2786
+ const newPosition = maxThumbStartPos > 0 ? clampedThumbStartPos / maxThumbStartPos : 0;
2787
+ this.thumbPosition = newPosition;
2788
+ }
2789
+ getThumbPosition() {
2790
+ const trackSize = this.orientation === "vertical" ? this.height : this.width;
2791
+ const maxPos = trackSize - this._thumbSize;
2792
+ return Math.round(this._thumbPosition * maxPos);
2793
+ }
2794
+ getThumbRect() {
2795
+ const thumbPos = this.getThumbPosition();
2796
+ if (this.orientation === "vertical") {
2797
+ return {
2798
+ x: this.x,
2799
+ y: this.y + thumbPos,
2800
+ width: this.width,
2801
+ height: this._thumbSize
2802
+ };
2803
+ } else {
2804
+ return {
2805
+ x: this.x + thumbPos,
2806
+ y: this.y,
2807
+ width: this._thumbSize,
2808
+ height: this.height
2809
+ };
2810
+ }
2811
+ }
2812
+ renderSelf(buffer) {
2813
+ buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
2814
+ const thumbRect = this.getThumbRect();
2815
+ buffer.fillRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, this._foregroundColor);
2816
+ }
2817
+ }
2818
+
2819
+ // src/renderables/ScrollBar.ts
2820
+ class ScrollBarRenderable extends Renderable {
2821
+ slider;
2822
+ startArrow;
2823
+ endArrow;
2824
+ orientation;
2825
+ focusable = true;
2826
+ _scrollSize = 0;
2827
+ _scrollPosition = 0;
2828
+ _viewportSize = 0;
2829
+ _showArrows = false;
2830
+ _manualVisibility = false;
2831
+ _onChange;
2832
+ scrollStep = null;
2833
+ get visible() {
2834
+ return super.visible;
2835
+ }
2836
+ set visible(value) {
2837
+ this._manualVisibility = true;
2838
+ super.visible = value;
2839
+ }
2840
+ resetVisibilityControl() {
2841
+ this._manualVisibility = false;
2842
+ this.recalculateVisibility();
2843
+ }
2844
+ get scrollSize() {
2845
+ return this._scrollSize;
2846
+ }
2847
+ get scrollPosition() {
2848
+ return this._scrollPosition;
2849
+ }
2850
+ get viewportSize() {
2851
+ return this._viewportSize;
2852
+ }
2853
+ set scrollSize(value) {
2854
+ if (value === this.scrollSize)
2855
+ return;
2856
+ this._scrollSize = value;
2857
+ this.recalculateVisibility();
2858
+ this.scrollPosition = this.scrollPosition;
2859
+ }
2860
+ set scrollPosition(value) {
2861
+ const newPosition = Math.round(Math.min(Math.max(0, value), this.scrollSize - this.viewportSize));
2862
+ if (newPosition !== this._scrollPosition) {
2863
+ this._scrollPosition = newPosition;
2864
+ this.updateSliderFromScrollState();
2865
+ this._onChange?.(newPosition);
2866
+ this.emit("change", { position: newPosition });
2867
+ }
2868
+ }
2869
+ set viewportSize(value) {
2870
+ if (value === this.viewportSize)
2871
+ return;
2872
+ this._viewportSize = value;
2873
+ this.recalculateVisibility();
2874
+ this.scrollPosition = this.scrollPosition;
2875
+ }
2876
+ get showArrows() {
2877
+ return this._showArrows;
2878
+ }
2879
+ set showArrows(value) {
2880
+ if (value === this._showArrows)
2881
+ return;
2882
+ this._showArrows = value;
2883
+ this.startArrow.visible = value;
2884
+ this.endArrow.visible = value;
2885
+ }
2886
+ constructor(ctx, { trackOptions, arrowOptions, orientation, showArrows = false, ...options }) {
2887
+ super(ctx, {
2888
+ flexDirection: orientation === "vertical" ? "column" : "row",
2889
+ alignSelf: "stretch",
2890
+ alignItems: "stretch",
2891
+ ...options
2892
+ });
2893
+ this._onChange = options.onChange;
2894
+ this.orientation = orientation;
2895
+ this._showArrows = showArrows;
2896
+ this.slider = new SliderRenderable(ctx, {
2897
+ orientation,
2898
+ onChange: (position) => {
2899
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
2900
+ this._scrollPosition = Math.round(position * scrollRange);
2901
+ this._onChange?.(this._scrollPosition);
2902
+ this.emit("change", { position: this._scrollPosition });
2903
+ },
2904
+ ...orientation === "vertical" ? {
2905
+ width: 2,
2906
+ height: "100%",
2907
+ marginLeft: "auto"
2908
+ } : {
2909
+ width: "100%",
2910
+ height: 1,
2911
+ marginTop: "auto"
2912
+ },
2913
+ flexGrow: 1,
2914
+ flexShrink: 1,
2915
+ ...trackOptions
2916
+ });
2917
+ this.updateSliderFromScrollState();
2918
+ const arrowOpts = arrowOptions ? {
2919
+ foregroundColor: arrowOptions.backgroundColor,
2920
+ backgroundColor: arrowOptions.backgroundColor,
2921
+ attributes: arrowOptions.attributes,
2922
+ ...arrowOptions
2923
+ } : {};
2924
+ this.startArrow = new ArrowRenderable(ctx, {
2925
+ alignSelf: "center",
2926
+ visible: this.showArrows,
2927
+ direction: this.orientation === "vertical" ? "up" : "left",
2928
+ height: this.orientation === "vertical" ? 1 : 1,
2929
+ ...arrowOpts
2930
+ });
2931
+ this.endArrow = new ArrowRenderable(ctx, {
2932
+ alignSelf: "center",
2933
+ visible: this.showArrows,
2934
+ direction: this.orientation === "vertical" ? "down" : "right",
2935
+ height: this.orientation === "vertical" ? 1 : 1,
2936
+ ...arrowOpts
2937
+ });
2938
+ this.add(this.startArrow);
2939
+ this.add(this.slider);
2940
+ this.add(this.endArrow);
2941
+ let startArrowMouseTimeout = undefined;
2942
+ let endArrowMouseTimeout = undefined;
2943
+ this.startArrow.onMouseDown = (event) => {
2944
+ event.stopPropagation();
2945
+ this.scrollBy(-0.5, "viewport");
2946
+ startArrowMouseTimeout = setTimeout(() => {
2947
+ this.scrollBy(-0.5, "viewport");
2948
+ startArrowMouseTimeout = setInterval(() => {
2949
+ this.scrollBy(-0.2, "viewport");
2950
+ }, 200);
2951
+ }, 500);
2952
+ };
2953
+ this.startArrow.onMouseUp = (event) => {
2954
+ event.stopPropagation();
2955
+ clearInterval(startArrowMouseTimeout);
2956
+ };
2957
+ this.endArrow.onMouseDown = (event) => {
2958
+ event.stopPropagation();
2959
+ this.scrollBy(0.5, "viewport");
2960
+ endArrowMouseTimeout = setTimeout(() => {
2961
+ this.scrollBy(0.5, "viewport");
2962
+ endArrowMouseTimeout = setInterval(() => {
2963
+ this.scrollBy(0.2, "viewport");
2964
+ }, 200);
2965
+ }, 500);
2966
+ };
2967
+ this.endArrow.onMouseUp = (event) => {
2968
+ event.stopPropagation();
2969
+ clearInterval(endArrowMouseTimeout);
2970
+ };
2971
+ }
2972
+ set arrowOptions(options) {
2973
+ Object.assign(this.startArrow, options);
2974
+ Object.assign(this.endArrow, options);
2975
+ this.requestRender();
2976
+ }
2977
+ set trackOptions(options) {
2978
+ Object.assign(this.slider, options);
2979
+ this.requestRender();
2980
+ }
2981
+ updateSliderFromScrollState() {
2982
+ const trackSize = this.orientation === "vertical" ? this.slider.height : this.slider.width;
2983
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
2984
+ if (scrollRange === 0) {
2985
+ this.slider.thumbSize = trackSize;
2986
+ this.slider.thumbPosition = 0;
2987
+ } else {
2988
+ const sizeRatio = this._viewportSize / this._scrollSize;
2989
+ this.slider.thumbSize = Math.max(1, Math.round(sizeRatio * trackSize));
2990
+ const positionRatio = this._scrollPosition / scrollRange;
2991
+ this.slider.thumbPosition = Math.max(0, Math.min(1, positionRatio));
2992
+ }
2993
+ }
2994
+ scrollBy(delta, unit = "absolute") {
2995
+ const multiplier = unit === "viewport" ? this.viewportSize : unit === "content" ? this.scrollSize : unit === "step" ? this.scrollStep ?? 1 : 1;
2996
+ const resolvedDelta = multiplier * delta;
2997
+ this.scrollPosition += resolvedDelta;
2998
+ }
2999
+ recalculateVisibility() {
3000
+ if (!this._manualVisibility) {
3001
+ const sizeRatio = this.scrollSize <= this.viewportSize ? 1 : this.viewportSize / this.scrollSize;
3002
+ super.visible = sizeRatio < 1;
3003
+ }
3004
+ }
3005
+ handleKeyPress(key) {
3006
+ const keyName = typeof key === "string" ? key : key.name;
3007
+ switch (keyName) {
3008
+ case "left":
3009
+ case "h":
3010
+ if (this.orientation !== "horizontal")
3011
+ return false;
3012
+ this.scrollBy(-1 / 5, "viewport");
3013
+ return true;
3014
+ case "right":
3015
+ case "l":
3016
+ if (this.orientation !== "horizontal")
3017
+ return false;
3018
+ this.scrollBy(1 / 5, "viewport");
3019
+ return true;
3020
+ case "up":
3021
+ case "k":
3022
+ if (this.orientation !== "vertical")
3023
+ return false;
3024
+ this.scrollBy(-1 / 5, "viewport");
3025
+ return true;
3026
+ case "down":
3027
+ case "j":
3028
+ if (this.orientation !== "vertical")
3029
+ return false;
3030
+ this.scrollBy(1 / 5, "viewport");
3031
+ return true;
3032
+ case "pageup":
3033
+ this.scrollBy(-1 / 2, "viewport");
3034
+ return true;
3035
+ case "pagedown":
3036
+ this.scrollBy(1 / 2, "viewport");
3037
+ return true;
3038
+ case "home":
3039
+ this.scrollBy(-1, "content");
3040
+ return true;
3041
+ case "end":
3042
+ this.scrollBy(1, "content");
3043
+ return true;
3044
+ }
3045
+ return false;
3046
+ }
3047
+ }
3048
+
3049
+ class ArrowRenderable extends Renderable {
3050
+ _direction;
3051
+ _foregroundColor;
3052
+ _backgroundColor;
3053
+ _attributes;
3054
+ _arrowChars;
3055
+ constructor(ctx, options) {
3056
+ super(ctx, options);
3057
+ this._direction = options.direction;
3058
+ this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : RGBA.fromValues(1, 1, 1, 1);
3059
+ this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : RGBA.fromValues(0, 0, 0, 0);
3060
+ this._attributes = options.attributes ?? 0;
3061
+ this._arrowChars = {
3062
+ up: "\u25E2\u25E3",
3063
+ down: "\u25E5\u25E4",
3064
+ left: " \u25C0 ",
3065
+ right: " \u25B6 ",
3066
+ ...options.arrowChars
3067
+ };
3068
+ if (!options.width) {
3069
+ this.width = Bun.stringWidth(this.getArrowChar());
3070
+ }
3071
+ }
3072
+ get direction() {
3073
+ return this._direction;
3074
+ }
3075
+ set direction(value) {
3076
+ if (this._direction !== value) {
3077
+ this._direction = value;
3078
+ this.requestRender();
3079
+ }
3080
+ }
3081
+ get foregroundColor() {
3082
+ return this._foregroundColor;
3083
+ }
3084
+ set foregroundColor(value) {
3085
+ if (this._foregroundColor !== value) {
3086
+ this._foregroundColor = parseColor(value);
3087
+ this.requestRender();
3088
+ }
3089
+ }
3090
+ get backgroundColor() {
3091
+ return this._backgroundColor;
3092
+ }
3093
+ set backgroundColor(value) {
3094
+ if (this._backgroundColor !== value) {
3095
+ this._backgroundColor = parseColor(value);
3096
+ this.requestRender();
3097
+ }
3098
+ }
3099
+ get attributes() {
3100
+ return this._attributes;
3101
+ }
3102
+ set attributes(value) {
3103
+ if (this._attributes !== value) {
3104
+ this._attributes = value;
3105
+ this.requestRender();
3106
+ }
3107
+ }
3108
+ set arrowChars(value) {
3109
+ this._arrowChars = {
3110
+ ...this._arrowChars,
3111
+ ...value
3112
+ };
3113
+ this.requestRender();
3114
+ }
3115
+ renderSelf(buffer) {
3116
+ const char = this.getArrowChar();
3117
+ buffer.drawText(char, this.x, this.y, this._foregroundColor, this._backgroundColor, this._attributes);
3118
+ }
3119
+ getArrowChar() {
3120
+ switch (this._direction) {
3121
+ case "up":
3122
+ return this._arrowChars.up;
3123
+ case "down":
3124
+ return this._arrowChars.down;
3125
+ case "left":
3126
+ return this._arrowChars.left;
3127
+ case "right":
3128
+ return this._arrowChars.right;
3129
+ default:
3130
+ return "?";
3131
+ }
3132
+ }
3133
+ }
3134
+
3135
+ // src/renderables/ScrollBox.ts
3136
+ class ContentRenderable extends BoxRenderable {
3137
+ viewport;
3138
+ constructor(ctx, viewport, options) {
3139
+ super(ctx, options);
3140
+ this.viewport = viewport;
3141
+ }
3142
+ _getChildren() {
3143
+ return this.getChildrenInViewport(this.viewport);
3144
+ }
3145
+ }
3146
+
3147
+ class ScrollBoxRenderable extends BoxRenderable {
3148
+ static idCounter = 0;
3149
+ internalId = 0;
3150
+ wrapper;
3151
+ viewport;
3152
+ content;
3153
+ horizontalScrollBar;
3154
+ verticalScrollBar;
3155
+ focusable = true;
3156
+ selectionListener;
3157
+ autoScrollMouseX = 0;
3158
+ autoScrollMouseY = 0;
3159
+ autoScrollThresholdVertical = 3;
3160
+ autoScrollThresholdHorizontal = 3;
3161
+ autoScrollSpeedSlow = 6;
3162
+ autoScrollSpeedMedium = 36;
3163
+ autoScrollSpeedFast = 72;
3164
+ isAutoScrolling = false;
3165
+ cachedAutoScrollSpeed = 3;
3166
+ autoScrollAccumulatorX = 0;
3167
+ autoScrollAccumulatorY = 0;
3168
+ get scrollTop() {
3169
+ return this.verticalScrollBar.scrollPosition;
3170
+ }
3171
+ set scrollTop(value) {
3172
+ this.verticalScrollBar.scrollPosition = value;
3173
+ }
3174
+ get scrollLeft() {
3175
+ return this.horizontalScrollBar.scrollPosition;
3176
+ }
3177
+ set scrollLeft(value) {
3178
+ this.horizontalScrollBar.scrollPosition = value;
3179
+ }
3180
+ get scrollWidth() {
3181
+ return this.horizontalScrollBar.scrollSize;
3182
+ }
3183
+ get scrollHeight() {
3184
+ return this.verticalScrollBar.scrollSize;
3185
+ }
3186
+ constructor(ctx, {
3187
+ wrapperOptions,
3188
+ viewportOptions,
3189
+ contentOptions,
3190
+ rootOptions,
3191
+ scrollbarOptions,
3192
+ verticalScrollbarOptions,
3193
+ horizontalScrollbarOptions,
3194
+ ...options
3195
+ }) {
3196
+ super(ctx, {
3197
+ flexShrink: 1,
3198
+ flexGrow: 1,
3199
+ flexDirection: "row",
3200
+ flexWrap: "wrap",
3201
+ alignItems: "stretch",
3202
+ ...options,
3203
+ ...rootOptions
3204
+ });
3205
+ this.internalId = ScrollBoxRenderable.idCounter++;
3206
+ this.wrapper = new BoxRenderable(ctx, {
3207
+ flexDirection: "column",
3208
+ flexGrow: 1,
3209
+ flexShrink: 1,
3210
+ flexBasis: "auto",
3211
+ maxHeight: "100%",
3212
+ maxWidth: "100%",
3213
+ ...wrapperOptions,
3214
+ id: `scroll-box-wrapper-${this.internalId}`
3215
+ });
3216
+ super.add(this.wrapper);
3217
+ this.viewport = new BoxRenderable(ctx, {
3218
+ flexDirection: "column",
3219
+ flexGrow: 1,
3220
+ flexShrink: 1,
3221
+ flexBasis: "auto",
3222
+ maxHeight: "100%",
3223
+ maxWidth: "100%",
3224
+ overflow: "scroll",
3225
+ onSizeChange: () => {
3226
+ this.recalculateBarProps();
3227
+ },
3228
+ ...viewportOptions,
3229
+ id: `scroll-box-viewport-${this.internalId}`
3230
+ });
3231
+ this.wrapper.add(this.viewport);
3232
+ this.content = new ContentRenderable(ctx, this.viewport, {
3233
+ alignSelf: "flex-start",
3234
+ onSizeChange: () => {
3235
+ this.recalculateBarProps();
3236
+ },
3237
+ ...contentOptions,
3238
+ id: `scroll-box-content-${this.internalId}`
3239
+ });
3240
+ this.viewport.add(this.content);
3241
+ this.verticalScrollBar = new ScrollBarRenderable(ctx, {
3242
+ ...scrollbarOptions,
3243
+ ...verticalScrollbarOptions,
3244
+ arrowOptions: {
3245
+ ...scrollbarOptions?.arrowOptions,
3246
+ ...verticalScrollbarOptions?.arrowOptions
3247
+ },
3248
+ id: `scroll-box-vertical-scrollbar-${this.internalId}`,
3249
+ orientation: "vertical",
3250
+ onChange: (position) => {
3251
+ this.content.translateY = -position;
3252
+ }
3253
+ });
3254
+ super.add(this.verticalScrollBar);
3255
+ this.horizontalScrollBar = new ScrollBarRenderable(ctx, {
3256
+ ...scrollbarOptions,
3257
+ ...horizontalScrollbarOptions,
3258
+ arrowOptions: {
3259
+ ...scrollbarOptions?.arrowOptions,
3260
+ ...horizontalScrollbarOptions?.arrowOptions
3261
+ },
3262
+ id: `scroll-box-horizontal-scrollbar-${this.internalId}`,
3263
+ orientation: "horizontal",
3264
+ onChange: (position) => {
3265
+ this.content.translateX = -position;
3266
+ }
3267
+ });
3268
+ this.wrapper.add(this.horizontalScrollBar);
3269
+ this.recalculateBarProps();
3270
+ this.selectionListener = () => {
3271
+ const selection = this._ctx.getSelection();
3272
+ if (!selection || !selection.isSelecting) {
3273
+ this.stopAutoScroll();
3274
+ }
3275
+ };
3276
+ this._ctx.on("selection", this.selectionListener);
3277
+ }
3278
+ onUpdate(deltaTime) {
3279
+ this.handleAutoScroll(deltaTime);
3280
+ }
3281
+ scrollBy(delta, unit = "absolute") {
3282
+ if (typeof delta === "number") {
3283
+ this.verticalScrollBar.scrollBy(delta, unit);
3284
+ } else {
3285
+ this.verticalScrollBar.scrollBy(delta.y, unit);
3286
+ this.horizontalScrollBar.scrollBy(delta.x, unit);
3287
+ }
3288
+ }
3289
+ scrollTo(position) {
3290
+ if (typeof position === "number") {
3291
+ this.scrollTop = position;
3292
+ } else {
3293
+ this.scrollTop = position.y;
3294
+ this.scrollLeft = position.x;
3295
+ }
3296
+ }
3297
+ add(obj, index) {
3298
+ return this.content.add(obj, index);
3299
+ }
3300
+ remove(id) {
3301
+ this.content.remove(id);
3302
+ }
3303
+ getChildren() {
3304
+ return this.content.getChildren();
3305
+ }
3306
+ onMouseEvent(event) {
3307
+ if (event.type === "scroll") {
3308
+ let dir = event.scroll?.direction;
3309
+ if (event.modifiers.shift)
3310
+ dir = dir === "up" ? "left" : dir === "down" ? "right" : dir === "right" ? "down" : "up";
3311
+ if (dir === "up")
3312
+ this.scrollTop -= event.scroll?.delta ?? 0;
3313
+ else if (dir === "down")
3314
+ this.scrollTop += event.scroll?.delta ?? 0;
3315
+ else if (dir === "left")
3316
+ this.scrollLeft -= event.scroll?.delta ?? 0;
3317
+ else if (dir === "right")
3318
+ this.scrollLeft += event.scroll?.delta ?? 0;
3319
+ }
3320
+ if (event.type === "drag" && event.isSelecting) {
3321
+ this.updateAutoScroll(event.x, event.y);
3322
+ } else if (event.type === "up") {
3323
+ this.stopAutoScroll();
3324
+ }
3325
+ }
3326
+ handleKeyPress(key) {
3327
+ if (this.verticalScrollBar.handleKeyPress(key))
3328
+ return true;
3329
+ if (this.horizontalScrollBar.handleKeyPress(key))
3330
+ return true;
3331
+ return false;
3332
+ }
3333
+ startAutoScroll(mouseX, mouseY) {
3334
+ this.stopAutoScroll();
3335
+ this.autoScrollMouseX = mouseX;
3336
+ this.autoScrollMouseY = mouseY;
3337
+ this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
3338
+ this.isAutoScrolling = true;
3339
+ if (!this.live) {
3340
+ this.live = true;
3341
+ }
3342
+ }
3343
+ updateAutoScroll(mouseX, mouseY) {
3344
+ this.autoScrollMouseX = mouseX;
3345
+ this.autoScrollMouseY = mouseY;
3346
+ this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
3347
+ const scrollX = this.getAutoScrollDirectionX(mouseX);
3348
+ const scrollY = this.getAutoScrollDirectionY(mouseY);
3349
+ if (scrollX === 0 && scrollY === 0) {
3350
+ this.stopAutoScroll();
3351
+ } else if (!this.isAutoScrolling) {
3352
+ this.startAutoScroll(mouseX, mouseY);
3353
+ }
3354
+ }
3355
+ stopAutoScroll() {
3356
+ const wasAutoScrolling = this.isAutoScrolling;
3357
+ this.isAutoScrolling = false;
3358
+ this.autoScrollAccumulatorX = 0;
3359
+ this.autoScrollAccumulatorY = 0;
3360
+ if (wasAutoScrolling && !this.hasOtherLiveReasons()) {
3361
+ this.live = false;
3362
+ }
3363
+ }
3364
+ hasOtherLiveReasons() {
3365
+ return false;
3366
+ }
3367
+ handleAutoScroll(deltaTime) {
3368
+ if (!this.isAutoScrolling)
3369
+ return;
3370
+ const scrollX = this.getAutoScrollDirectionX(this.autoScrollMouseX);
3371
+ const scrollY = this.getAutoScrollDirectionY(this.autoScrollMouseY);
3372
+ const scrollAmount = this.cachedAutoScrollSpeed * (deltaTime / 1000);
3373
+ let scrolled = false;
3374
+ if (scrollX !== 0) {
3375
+ this.autoScrollAccumulatorX += scrollX * scrollAmount;
3376
+ const integerScrollX = Math.trunc(this.autoScrollAccumulatorX);
3377
+ if (integerScrollX !== 0) {
3378
+ this.scrollLeft += integerScrollX;
3379
+ this.autoScrollAccumulatorX -= integerScrollX;
3380
+ scrolled = true;
3381
+ }
3382
+ }
3383
+ if (scrollY !== 0) {
3384
+ this.autoScrollAccumulatorY += scrollY * scrollAmount;
3385
+ const integerScrollY = Math.trunc(this.autoScrollAccumulatorY);
3386
+ if (integerScrollY !== 0) {
3387
+ this.scrollTop += integerScrollY;
3388
+ this.autoScrollAccumulatorY -= integerScrollY;
3389
+ scrolled = true;
3390
+ }
3391
+ }
3392
+ if (scrolled) {
3393
+ this._ctx.requestSelectionUpdate();
3394
+ }
3395
+ if (scrollX === 0 && scrollY === 0) {
3396
+ this.stopAutoScroll();
3397
+ }
3398
+ }
3399
+ getAutoScrollDirectionX(mouseX) {
3400
+ const relativeX = mouseX - this.x;
3401
+ const distToLeft = relativeX;
3402
+ const distToRight = this.width - relativeX;
3403
+ if (distToLeft <= this.autoScrollThresholdHorizontal) {
3404
+ return this.scrollLeft > 0 ? -1 : 0;
3405
+ } else if (distToRight <= this.autoScrollThresholdHorizontal) {
3406
+ const maxScrollLeft = this.scrollWidth - this.viewport.width;
3407
+ return this.scrollLeft < maxScrollLeft ? 1 : 0;
3408
+ }
3409
+ return 0;
3410
+ }
3411
+ getAutoScrollDirectionY(mouseY) {
3412
+ const relativeY = mouseY - this.y;
3413
+ const distToTop = relativeY;
3414
+ const distToBottom = this.height - relativeY;
3415
+ if (distToTop <= this.autoScrollThresholdVertical) {
3416
+ return this.scrollTop > 0 ? -1 : 0;
3417
+ } else if (distToBottom <= this.autoScrollThresholdVertical) {
3418
+ const maxScrollTop = this.scrollHeight - this.viewport.height;
3419
+ return this.scrollTop < maxScrollTop ? 1 : 0;
3420
+ }
3421
+ return 0;
3422
+ }
3423
+ getAutoScrollSpeed(mouseX, mouseY) {
3424
+ const relativeX = mouseX - this.x;
3425
+ const relativeY = mouseY - this.y;
3426
+ const distToLeft = relativeX;
3427
+ const distToRight = this.width - relativeX;
3428
+ const distToTop = relativeY;
3429
+ const distToBottom = this.height - relativeY;
3430
+ const minDistance = Math.min(distToLeft, distToRight, distToTop, distToBottom);
3431
+ if (minDistance <= 1) {
3432
+ return this.autoScrollSpeedFast;
3433
+ } else if (minDistance <= 2) {
3434
+ return this.autoScrollSpeedMedium;
3435
+ } else {
3436
+ return this.autoScrollSpeedSlow;
3437
+ }
3438
+ }
3439
+ recalculateBarProps() {
3440
+ this.verticalScrollBar.scrollSize = this.content.height;
3441
+ this.verticalScrollBar.viewportSize = this.viewport.height;
3442
+ this.horizontalScrollBar.scrollSize = this.content.width;
3443
+ this.horizontalScrollBar.viewportSize = this.viewport.width;
3444
+ }
3445
+ set rootOptions(options) {
3446
+ Object.assign(this, options);
3447
+ this.requestRender();
3448
+ }
3449
+ set wrapperOptions(options) {
3450
+ Object.assign(this.wrapper, options);
3451
+ this.requestRender();
3452
+ }
3453
+ set viewportOptions(options) {
3454
+ Object.assign(this.viewport, options);
3455
+ this.requestRender();
3456
+ }
3457
+ set contentOptions(options) {
3458
+ Object.assign(this.content, options);
3459
+ this.requestRender();
3460
+ }
3461
+ set scrollbarOptions(options) {
3462
+ Object.assign(this.verticalScrollBar, options);
3463
+ Object.assign(this.horizontalScrollBar, options);
3464
+ this.requestRender();
3465
+ }
3466
+ set verticalScrollbarOptions(options) {
3467
+ Object.assign(this.verticalScrollBar, options);
3468
+ this.requestRender();
3469
+ }
3470
+ set horizontalScrollbarOptions(options) {
3471
+ Object.assign(this.horizontalScrollBar, options);
3472
+ this.requestRender();
3473
+ }
3474
+ destroySelf() {
3475
+ if (this.selectionListener) {
3476
+ this._ctx.off("selection", this.selectionListener);
3477
+ this.selectionListener = undefined;
3478
+ }
3479
+ super.destroySelf();
3480
+ }
3481
+ }
2677
3482
  // src/renderables/composition/constructs.ts
2678
3483
  function Generic(props, ...children) {
2679
3484
  return h(VRenderable, props || {}, ...children);
@@ -2751,6 +3556,7 @@ export {
2751
3556
  isValidPercentage,
2752
3557
  isVNode,
2753
3558
  isSizeType,
3559
+ isRenderable,
2754
3560
  isPositionTypeType,
2755
3561
  isPositionType,
2756
3562
  isPaddingType,
@@ -2780,6 +3586,7 @@ export {
2780
3586
  createTextAttributes,
2781
3587
  createCliRenderer,
2782
3588
  coordinateToCharacterIndex,
3589
+ convertGlobalToLocalSelection,
2783
3590
  capture,
2784
3591
  brightYellow,
2785
3592
  brightWhite,
@@ -2814,7 +3621,6 @@ export {
2814
3621
  VRenderable,
2815
3622
  TrackedNode,
2816
3623
  Timeline,
2817
- TextSelectionHelper,
2818
3624
  TextRenderable,
2819
3625
  TextBuffer,
2820
3626
  TextAttributes,
@@ -2829,6 +3635,8 @@ export {
2829
3635
  SelectRenderableEvents,
2830
3636
  SelectRenderable,
2831
3637
  Select,
3638
+ ScrollBoxRenderable,
3639
+ ScrollBarRenderable,
2832
3640
  RootRenderable,
2833
3641
  RenderableEvents,
2834
3642
  Renderable,
@@ -2858,10 +3666,11 @@ export {
2858
3666
  BorderCharArrays,
2859
3667
  BlurEffect,
2860
3668
  BloomEffect,
3669
+ ArrowRenderable,
2861
3670
  ASCIIFontSelectionHelper,
2862
3671
  ASCIIFontRenderable,
2863
3672
  ASCIIFont
2864
3673
  };
2865
3674
 
2866
- //# debugId=673E2078AE4B8BC264756E2164756E21
3675
+ //# debugId=B627209D76BECE1A64756E2164756E21
2867
3676
  //# sourceMappingURL=index.js.map