@opentui/core 0.0.0-20250908-4906ddad → 0.0.0-20250912-12c969f4

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
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  ASCIIFontSelectionHelper,
4
+ BaseRenderable,
4
5
  BorderCharArrays,
5
6
  BorderChars,
6
7
  CliRenderEvents,
@@ -65,6 +66,7 @@ import {
65
66
  getBorderSides,
66
67
  getCharacterPositions,
67
68
  getKeyHandler,
69
+ getObjectsInViewport,
68
70
  green,
69
71
  h,
70
72
  hastToStyledText,
@@ -80,6 +82,7 @@ import {
80
82
  isPositionTypeType,
81
83
  isRenderable,
82
84
  isSizeType,
85
+ isStyledText,
83
86
  isVNode,
84
87
  isValidPercentage,
85
88
  italic,
@@ -114,10 +117,11 @@ import {
114
117
  stringToStyledText,
115
118
  t,
116
119
  underline,
120
+ visualizeRenderableTree,
117
121
  white,
118
122
  wrapWithDelegates,
119
123
  yellow
120
- } from "./index-d6kwx5pm.js";
124
+ } from "./index-mh94hn7d.js";
121
125
  // src/post/filters.ts
122
126
  function applyScanlines(buffer, strength = 0.8, step = 2) {
123
127
  const width = buffer.width;
@@ -1434,7 +1438,7 @@ class FrameBufferRenderable extends Renderable {
1434
1438
  this.requestRender();
1435
1439
  }
1436
1440
  renderSelf(buffer) {
1437
- if (!this.visible)
1441
+ if (!this.visible || this.isDestroyed)
1438
1442
  return;
1439
1443
  buffer.drawFrameBuffer(this.x, this.y, this.frameBuffer);
1440
1444
  }
@@ -1443,6 +1447,213 @@ class FrameBufferRenderable extends Renderable {
1443
1447
  super.destroySelf();
1444
1448
  }
1445
1449
  }
1450
+ // src/renderables/TextNode.ts
1451
+ var BrandedTextNodeRenderable = Symbol.for("@opentui/core/TextNodeRenderable");
1452
+ function isTextNodeRenderable(obj) {
1453
+ return !!obj?.[BrandedTextNodeRenderable];
1454
+ }
1455
+ function styledTextToTextNodes(styledText) {
1456
+ return styledText.chunks.map((chunk) => {
1457
+ const node = new TextNodeRenderable({
1458
+ fg: chunk.fg,
1459
+ bg: chunk.bg,
1460
+ attributes: chunk.attributes
1461
+ });
1462
+ node.add(chunk.text);
1463
+ return node;
1464
+ });
1465
+ }
1466
+
1467
+ class TextNodeRenderable extends BaseRenderable {
1468
+ [BrandedTextNodeRenderable] = true;
1469
+ _fg;
1470
+ _bg;
1471
+ _attributes;
1472
+ _children = [];
1473
+ parent = null;
1474
+ constructor(options) {
1475
+ super(options);
1476
+ this._fg = options.fg ? parseColor(options.fg) : undefined;
1477
+ this._bg = options.bg ? parseColor(options.bg) : undefined;
1478
+ this._attributes = options.attributes ?? 0;
1479
+ }
1480
+ get children() {
1481
+ return this._children;
1482
+ }
1483
+ set children(children) {
1484
+ this._children = children;
1485
+ this.requestRender();
1486
+ }
1487
+ requestRender() {
1488
+ this.markDirty();
1489
+ this.parent?.requestRender();
1490
+ }
1491
+ add(obj, index) {
1492
+ if (typeof obj === "string") {
1493
+ if (index !== undefined) {
1494
+ this._children.splice(index, 0, obj);
1495
+ this.requestRender();
1496
+ return index;
1497
+ }
1498
+ const insertIndex = this._children.length;
1499
+ this._children.push(obj);
1500
+ this.requestRender();
1501
+ return insertIndex;
1502
+ }
1503
+ if (isTextNodeRenderable(obj)) {
1504
+ if (index !== undefined) {
1505
+ this._children.splice(index, 0, obj);
1506
+ obj.parent = this;
1507
+ this.requestRender();
1508
+ return index;
1509
+ }
1510
+ const insertIndex = this._children.length;
1511
+ this._children.push(obj);
1512
+ obj.parent = this;
1513
+ this.requestRender();
1514
+ return insertIndex;
1515
+ }
1516
+ if (isStyledText(obj)) {
1517
+ const textNodes = styledTextToTextNodes(obj);
1518
+ if (index !== undefined) {
1519
+ this._children.splice(index, 0, ...textNodes);
1520
+ textNodes.forEach((node) => node.parent = this);
1521
+ this.requestRender();
1522
+ return index;
1523
+ }
1524
+ const insertIndex = this._children.length;
1525
+ this._children.push(...textNodes);
1526
+ textNodes.forEach((node) => node.parent = this);
1527
+ this.requestRender();
1528
+ return insertIndex;
1529
+ }
1530
+ throw new Error("TextNodeRenderable only accepts strings, TextNodeRenderable instances, or StyledText instances");
1531
+ }
1532
+ replace(obj, index) {
1533
+ this._children[index] = obj;
1534
+ if (typeof obj !== "string") {
1535
+ obj.parent = this;
1536
+ }
1537
+ this.requestRender();
1538
+ }
1539
+ insertBefore(child, anchorNode) {
1540
+ if (!anchorNode || !isTextNodeRenderable(anchorNode)) {
1541
+ throw new Error("Anchor must be a TextNodeRenderable");
1542
+ }
1543
+ const anchorIndex = this._children.indexOf(anchorNode);
1544
+ if (anchorIndex === -1) {
1545
+ throw new Error("Anchor node not found in children");
1546
+ }
1547
+ if (typeof child === "string") {
1548
+ this._children.splice(anchorIndex, 0, child);
1549
+ } else if (isTextNodeRenderable(child)) {
1550
+ this._children.splice(anchorIndex, 0, child);
1551
+ child.parent = this;
1552
+ } else if (child instanceof StyledText) {
1553
+ const textNodes = styledTextToTextNodes(child);
1554
+ this._children.splice(anchorIndex, 0, ...textNodes);
1555
+ textNodes.forEach((node) => node.parent = this);
1556
+ } else {
1557
+ throw new Error("Child must be a string, TextNodeRenderable, or StyledText instance");
1558
+ }
1559
+ this.requestRender();
1560
+ return this;
1561
+ }
1562
+ remove(child) {
1563
+ const childIndex = this._children.indexOf(child);
1564
+ if (childIndex === -1) {
1565
+ throw new Error("Child not found in children");
1566
+ }
1567
+ this._children.splice(childIndex, 1);
1568
+ if (typeof child !== "string") {
1569
+ child.parent = null;
1570
+ }
1571
+ this.requestRender();
1572
+ return this;
1573
+ }
1574
+ clear() {
1575
+ this._children = [];
1576
+ this.requestRender();
1577
+ }
1578
+ mergeStyles(parentStyle) {
1579
+ return {
1580
+ fg: this._fg ?? parentStyle.fg,
1581
+ bg: this._bg ?? parentStyle.bg,
1582
+ attributes: this._attributes | parentStyle.attributes
1583
+ };
1584
+ }
1585
+ gatherWithInheritedStyle(parentStyle = { fg: undefined, bg: undefined, attributes: 0 }) {
1586
+ const currentStyle = this.mergeStyles(parentStyle);
1587
+ const chunks = [];
1588
+ for (const child of this._children) {
1589
+ if (typeof child === "string") {
1590
+ chunks.push({
1591
+ __isChunk: true,
1592
+ text: child,
1593
+ fg: currentStyle.fg,
1594
+ bg: currentStyle.bg,
1595
+ attributes: currentStyle.attributes
1596
+ });
1597
+ } else {
1598
+ const childChunks = child.gatherWithInheritedStyle(currentStyle);
1599
+ chunks.push(...childChunks);
1600
+ }
1601
+ }
1602
+ this.markClean();
1603
+ return chunks;
1604
+ }
1605
+ static fromString(text, options = {}) {
1606
+ const node = new TextNodeRenderable(options);
1607
+ node.add(text);
1608
+ return node;
1609
+ }
1610
+ static fromNodes(nodes, options = {}) {
1611
+ const node = new TextNodeRenderable(options);
1612
+ for (const childNode of nodes) {
1613
+ node.add(childNode);
1614
+ }
1615
+ return node;
1616
+ }
1617
+ toChunks(parentStyle = { fg: undefined, bg: undefined, attributes: 0 }) {
1618
+ return this.gatherWithInheritedStyle(parentStyle);
1619
+ }
1620
+ getChildren() {
1621
+ return this._children.filter((child) => typeof child !== "string");
1622
+ }
1623
+ getChildrenCount() {
1624
+ return this._children.length;
1625
+ }
1626
+ getRenderable(id) {
1627
+ return this._children.find((child) => typeof child !== "string" && child.id === id);
1628
+ }
1629
+ set fg(fg2) {
1630
+ this._fg = parseColor(fg2);
1631
+ this.requestRender();
1632
+ }
1633
+ set bg(bg2) {
1634
+ this._bg = parseColor(bg2);
1635
+ this.requestRender();
1636
+ }
1637
+ set attributes(attributes) {
1638
+ this._attributes = attributes;
1639
+ this.requestRender();
1640
+ }
1641
+ }
1642
+
1643
+ class RootTextNodeRenderable extends TextNodeRenderable {
1644
+ ctx;
1645
+ textParent;
1646
+ constructor(ctx, options, textParent) {
1647
+ super(options);
1648
+ this.ctx = ctx;
1649
+ this.textParent = textParent;
1650
+ }
1651
+ requestRender() {
1652
+ this.markDirty();
1653
+ this.ctx.requestRender();
1654
+ }
1655
+ }
1656
+
1446
1657
  // src/renderables/Text.ts
1447
1658
  class TextRenderable extends Renderable {
1448
1659
  selectable = true;
@@ -1455,6 +1666,7 @@ class TextRenderable extends Renderable {
1455
1666
  lastLocalSelection = null;
1456
1667
  textBuffer;
1457
1668
  _lineInfo = { lineStarts: [], lineWidths: [] };
1669
+ rootTextNode;
1458
1670
  _defaultOptions = {
1459
1671
  content: "",
1460
1672
  fg: RGBA.fromValues(1, 1, 1, 1),
@@ -1480,6 +1692,12 @@ class TextRenderable extends Renderable {
1480
1692
  this.textBuffer.setDefaultBg(this._defaultBg);
1481
1693
  this.textBuffer.setDefaultAttributes(this._defaultAttributes);
1482
1694
  this.setupMeasureFunc();
1695
+ this.rootTextNode = new RootTextNodeRenderable(ctx, {
1696
+ id: `${this.id}-root`,
1697
+ fg: this._defaultFg,
1698
+ bg: this._defaultBg,
1699
+ attributes: this._defaultAttributes
1700
+ }, this);
1483
1701
  this.updateTextBuffer(styledText);
1484
1702
  this._text.mount(this);
1485
1703
  this.updateTextInfo();
@@ -1488,12 +1706,7 @@ class TextRenderable extends Renderable {
1488
1706
  this.textBuffer.setStyledText(styledText);
1489
1707
  this.clearChunks(styledText);
1490
1708
  }
1491
- clearChunks(styledText) {
1492
- styledText.chunks.forEach((chunk) => {
1493
- chunk.text = undefined;
1494
- chunk.plainText = undefined;
1495
- });
1496
- }
1709
+ clearChunks(styledText) {}
1497
1710
  get content() {
1498
1711
  return this._text;
1499
1712
  }
@@ -1506,6 +1719,9 @@ class TextRenderable extends Renderable {
1506
1719
  get chunks() {
1507
1720
  return this._text.chunks;
1508
1721
  }
1722
+ get textNode() {
1723
+ return this.rootTextNode;
1724
+ }
1509
1725
  set content(value) {
1510
1726
  const styledText = typeof value === "string" ? stringToStyledText(value) : value;
1511
1727
  if (this._text !== styledText) {
@@ -1520,9 +1736,11 @@ class TextRenderable extends Renderable {
1520
1736
  }
1521
1737
  set fg(value) {
1522
1738
  const newColor = parseColor(value ?? this._defaultOptions.fg);
1739
+ this.rootTextNode.fg = newColor;
1523
1740
  if (this._defaultFg !== newColor) {
1524
1741
  this._defaultFg = newColor;
1525
1742
  this.textBuffer.setDefaultFg(this._defaultFg);
1743
+ this.rootTextNode.fg = newColor;
1526
1744
  this.requestRender();
1527
1745
  }
1528
1746
  }
@@ -1557,9 +1775,11 @@ class TextRenderable extends Renderable {
1557
1775
  }
1558
1776
  set bg(value) {
1559
1777
  const newColor = parseColor(value ?? this._defaultOptions.bg);
1778
+ this.rootTextNode.bg = newColor;
1560
1779
  if (this._defaultBg !== newColor) {
1561
1780
  this._defaultBg = newColor;
1562
1781
  this.textBuffer.setDefaultBg(this._defaultBg);
1782
+ this.rootTextNode.bg = newColor;
1563
1783
  this.requestRender();
1564
1784
  }
1565
1785
  }
@@ -1570,6 +1790,7 @@ class TextRenderable extends Renderable {
1570
1790
  if (this._defaultAttributes !== value) {
1571
1791
  this._defaultAttributes = value;
1572
1792
  this.textBuffer.setDefaultAttributes(this._defaultAttributes);
1793
+ this.rootTextNode.attributes = value;
1573
1794
  this.requestRender();
1574
1795
  }
1575
1796
  }
@@ -1625,7 +1846,7 @@ class TextRenderable extends Renderable {
1625
1846
  this.layoutNode.yogaNode.setMeasureFunc(measureFunc);
1626
1847
  }
1627
1848
  insertChunk(chunk, index) {
1628
- this.textBuffer.insertEncodedChunkGroup(index ?? this.textBuffer.chunkGroupCount, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1849
+ this.textBuffer.insertChunkGroup(index ?? this.textBuffer.chunkGroupCount, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1629
1850
  this.updateTextInfo();
1630
1851
  this.clearChunks(this._text);
1631
1852
  }
@@ -1641,10 +1862,46 @@ class TextRenderable extends Renderable {
1641
1862
  const index = this._text.chunks.indexOf(oldChunk);
1642
1863
  if (index === -1)
1643
1864
  return;
1644
- this.textBuffer.replaceEncodedChunkGroup(index, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1865
+ this.textBuffer.replaceChunkGroup(index, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1645
1866
  this.updateTextInfo();
1646
1867
  this.clearChunks(this._text);
1647
1868
  }
1869
+ updateTextFromNodes() {
1870
+ if (this.rootTextNode.isDirty) {
1871
+ const chunks = this.rootTextNode.gatherWithInheritedStyle({
1872
+ fg: this._defaultFg,
1873
+ bg: this._defaultBg,
1874
+ attributes: this._defaultAttributes
1875
+ });
1876
+ this.textBuffer.setStyledText(new StyledText(chunks));
1877
+ this.updateTextInfo();
1878
+ }
1879
+ }
1880
+ add(obj, index) {
1881
+ return this.rootTextNode.add(obj, index);
1882
+ }
1883
+ remove(id) {
1884
+ const child = this.rootTextNode.getRenderable(id);
1885
+ if (child && isTextNodeRenderable(child)) {
1886
+ this.rootTextNode.remove(child);
1887
+ }
1888
+ }
1889
+ insertBefore(obj, anchor) {
1890
+ this.rootTextNode.insertBefore(obj, anchor);
1891
+ return this.rootTextNode.children.indexOf(obj);
1892
+ }
1893
+ getTextChildren() {
1894
+ return this.rootTextNode.getChildren();
1895
+ }
1896
+ clear() {
1897
+ this.rootTextNode.clear();
1898
+ const emptyStyledText = stringToStyledText("");
1899
+ this._text = emptyStyledText;
1900
+ emptyStyledText.mount(this);
1901
+ this.updateTextBuffer(emptyStyledText);
1902
+ this.updateTextInfo();
1903
+ this.requestRender();
1904
+ }
1648
1905
  shouldStartSelection(x, y) {
1649
1906
  if (!this.selectable)
1650
1907
  return false;
@@ -1670,6 +1927,16 @@ class TextRenderable extends Renderable {
1670
1927
  getSelection() {
1671
1928
  return this.textBuffer.getSelection();
1672
1929
  }
1930
+ onLifecyclePass = () => {
1931
+ this.updateTextFromNodes();
1932
+ };
1933
+ render(buffer, deltaTime) {
1934
+ if (!this.visible)
1935
+ return;
1936
+ this.markClean();
1937
+ this._ctx.addToHitGrid(this.x, this.y, this.width, this.height, this.num);
1938
+ this.renderSelf(buffer);
1939
+ }
1673
1940
  renderSelf(buffer) {
1674
1941
  if (this.textBuffer.ptr) {
1675
1942
  const clipRect = {
@@ -1683,6 +1950,7 @@ class TextRenderable extends Renderable {
1683
1950
  }
1684
1951
  destroy() {
1685
1952
  this.textBuffer.destroy();
1953
+ this.rootTextNode.children.length = 0;
1686
1954
  super.destroy();
1687
1955
  }
1688
1956
  }
@@ -1795,6 +2063,8 @@ class ASCIIFontRenderable extends FrameBufferRenderable {
1795
2063
  this.renderFontToBuffer();
1796
2064
  }
1797
2065
  renderFontToBuffer() {
2066
+ if (this.isDestroyed)
2067
+ return;
1798
2068
  this.frameBuffer.clear(this._bg);
1799
2069
  renderFontToFrameBuffer(this.frameBuffer, {
1800
2070
  text: this._text,
@@ -1842,7 +2112,7 @@ var InputRenderableEvents;
1842
2112
  })(InputRenderableEvents ||= {});
1843
2113
 
1844
2114
  class InputRenderable extends Renderable {
1845
- focusable = true;
2115
+ _focusable = true;
1846
2116
  _value = "";
1847
2117
  _cursorPosition = 0;
1848
2118
  _placeholder;
@@ -2115,7 +2385,7 @@ var SelectRenderableEvents;
2115
2385
  })(SelectRenderableEvents ||= {});
2116
2386
 
2117
2387
  class SelectRenderable extends Renderable {
2118
- focusable = true;
2388
+ _focusable = true;
2119
2389
  _options = [];
2120
2390
  selectedIndex = 0;
2121
2391
  scrollOffset = 0;
@@ -2451,7 +2721,7 @@ function calculateDynamicHeight(showUnderline, showDescription) {
2451
2721
  }
2452
2722
 
2453
2723
  class TabSelectRenderable extends Renderable {
2454
- focusable = true;
2724
+ _focusable = true;
2455
2725
  _options = [];
2456
2726
  selectedIndex = 0;
2457
2727
  scrollOffset = 0;
@@ -2741,43 +3011,71 @@ var defaultTrackBackgroundColor = RGBA.fromHex("#252527");
2741
3011
 
2742
3012
  class SliderRenderable extends Renderable {
2743
3013
  orientation;
2744
- _thumbSize;
2745
- _thumbPosition;
3014
+ _value;
3015
+ _min;
3016
+ _max;
3017
+ _viewPortSize;
2746
3018
  _backgroundColor;
2747
3019
  _foregroundColor;
2748
3020
  _onChange;
2749
3021
  constructor(ctx, options) {
2750
3022
  super(ctx, options);
2751
3023
  this.orientation = options.orientation;
2752
- this._thumbSize = options.thumbSize ?? 1;
2753
- this._thumbPosition = options.thumbPosition ?? 0;
3024
+ this._min = options.min ?? 0;
3025
+ this._max = options.max ?? 100;
3026
+ this._value = options.value ?? this._min;
3027
+ this._viewPortSize = options.viewPortSize ?? Math.max(1, (this._max - this._min) * 0.1);
2754
3028
  this._onChange = options.onChange;
2755
3029
  this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : defaultTrackBackgroundColor;
2756
3030
  this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : defaultThumbBackgroundColor;
2757
3031
  this.setupMouseHandling();
2758
3032
  }
2759
- get thumbSize() {
2760
- return this._thumbSize;
3033
+ get value() {
3034
+ return this._value;
2761
3035
  }
2762
- set thumbSize(value) {
2763
- const clamped = Math.max(1, Math.min(value, this.orientation === "vertical" ? this.height : this.width));
2764
- if (clamped !== this._thumbSize) {
2765
- this._thumbSize = clamped;
3036
+ set value(newValue) {
3037
+ const clamped = Math.max(this._min, Math.min(this._max, newValue));
3038
+ if (clamped !== this._value) {
3039
+ this._value = clamped;
3040
+ this._onChange?.(clamped);
3041
+ this.emit("change", { value: clamped });
2766
3042
  this.requestRender();
2767
3043
  }
2768
3044
  }
2769
- get thumbPosition() {
2770
- return this._thumbPosition;
3045
+ get min() {
3046
+ return this._min;
2771
3047
  }
2772
- set thumbPosition(value) {
2773
- const clamped = Math.max(0, Math.min(1, value));
2774
- if (clamped !== this._thumbPosition) {
2775
- this._thumbPosition = clamped;
2776
- this._onChange?.(clamped);
2777
- this.emit("change", { position: clamped });
3048
+ set min(newMin) {
3049
+ if (newMin !== this._min) {
3050
+ this._min = newMin;
3051
+ if (this._value < newMin) {
3052
+ this.value = newMin;
3053
+ }
3054
+ this.requestRender();
3055
+ }
3056
+ }
3057
+ get max() {
3058
+ return this._max;
3059
+ }
3060
+ set max(newMax) {
3061
+ if (newMax !== this._max) {
3062
+ this._max = newMax;
3063
+ if (this._value > newMax) {
3064
+ this.value = newMax;
3065
+ }
3066
+ this.requestRender();
3067
+ }
3068
+ }
3069
+ set viewPortSize(size) {
3070
+ const clampedSize = Math.max(0.01, Math.min(size, this._max - this._min));
3071
+ if (clampedSize !== this._viewPortSize) {
3072
+ this._viewPortSize = clampedSize;
2778
3073
  this.requestRender();
2779
3074
  }
2780
3075
  }
3076
+ get viewPortSize() {
3077
+ return this._viewPortSize;
3078
+ }
2781
3079
  get backgroundColor() {
2782
3080
  return this._backgroundColor;
2783
3081
  }
@@ -2792,69 +3090,183 @@ class SliderRenderable extends Renderable {
2792
3090
  this._foregroundColor = parseColor(value);
2793
3091
  this.requestRender();
2794
3092
  }
3093
+ calculateDragOffsetVirtual(event) {
3094
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
3095
+ const mousePos = (this.orientation === "vertical" ? event.y : event.x) - trackStart;
3096
+ const virtualMousePos = Math.max(0, Math.min((this.orientation === "vertical" ? this.height : this.width) * 2, mousePos * 2));
3097
+ const virtualThumbStart = this.getVirtualThumbStart();
3098
+ const virtualThumbSize = this.getVirtualThumbSize();
3099
+ return Math.max(0, Math.min(virtualThumbSize, virtualMousePos - virtualThumbStart));
3100
+ }
2795
3101
  setupMouseHandling() {
2796
3102
  let isDragging = false;
2797
- let relativeStartPos = 0;
3103
+ let dragOffsetVirtual = 0;
2798
3104
  this.onMouseDown = (event) => {
2799
3105
  event.stopPropagation();
2800
3106
  event.preventDefault();
2801
- isDragging = true;
2802
- const thumbRect = this.getThumbRect();
2803
- const isOnThumb = event.x >= thumbRect.x && event.x < thumbRect.x + thumbRect.width && event.y >= thumbRect.y && event.y < thumbRect.y + thumbRect.height;
2804
- if (isOnThumb) {
2805
- relativeStartPos = this.orientation === "vertical" ? event.y - thumbRect.y : event.x - thumbRect.x;
3107
+ const thumb = this.getThumbRect();
3108
+ const inThumb = event.x >= thumb.x && event.x < thumb.x + thumb.width && event.y >= thumb.y && event.y < thumb.y + thumb.height;
3109
+ if (inThumb) {
3110
+ isDragging = true;
3111
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
2806
3112
  } else {
2807
- relativeStartPos = this.orientation === "vertical" ? thumbRect.height / 2 : thumbRect.width / 2;
3113
+ this.updateValueFromMouseDirect(event);
3114
+ isDragging = true;
3115
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
2808
3116
  }
2809
- this.updatePositionFromMouse(event, relativeStartPos);
2810
3117
  };
2811
3118
  this.onMouseDrag = (event) => {
2812
3119
  if (!isDragging)
2813
3120
  return;
2814
3121
  event.stopPropagation();
2815
- this.updatePositionFromMouse(event, relativeStartPos);
3122
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
2816
3123
  };
2817
- this.onMouseUp = () => {
3124
+ this.onMouseUp = (event) => {
3125
+ if (isDragging) {
3126
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
3127
+ }
2818
3128
  isDragging = false;
2819
3129
  };
2820
3130
  }
2821
- updatePositionFromMouse(event, relativeStartPos) {
3131
+ updateValueFromMouseDirect(event) {
2822
3132
  const trackStart = this.orientation === "vertical" ? this.y : this.x;
2823
3133
  const trackSize = this.orientation === "vertical" ? this.height : this.width;
2824
3134
  const mousePos = this.orientation === "vertical" ? event.y : event.x;
2825
- const thumbStartPos = mousePos - trackStart - relativeStartPos;
2826
- const maxThumbStartPos = trackSize - this._thumbSize;
2827
- const clampedThumbStartPos = Math.max(0, Math.min(maxThumbStartPos, thumbStartPos));
2828
- const newPosition = maxThumbStartPos > 0 ? clampedThumbStartPos / maxThumbStartPos : 0;
2829
- this.thumbPosition = newPosition;
2830
- }
2831
- getThumbPosition() {
3135
+ const relativeMousePos = mousePos - trackStart;
3136
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
3137
+ const ratio = trackSize === 0 ? 0 : clampedMousePos / trackSize;
3138
+ const range = this._max - this._min;
3139
+ const newValue = this._min + ratio * range;
3140
+ this.value = newValue;
3141
+ }
3142
+ updateValueFromMouseWithOffset(event, offsetVirtual) {
3143
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
2832
3144
  const trackSize = this.orientation === "vertical" ? this.height : this.width;
2833
- const maxPos = trackSize - this._thumbSize;
2834
- return Math.round(this._thumbPosition * maxPos);
3145
+ const mousePos = this.orientation === "vertical" ? event.y : event.x;
3146
+ const virtualTrackSize = trackSize * 2;
3147
+ const relativeMousePos = mousePos - trackStart;
3148
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
3149
+ const virtualMousePos = clampedMousePos * 2;
3150
+ const virtualThumbSize = this.getVirtualThumbSize();
3151
+ const maxThumbStart = Math.max(0, virtualTrackSize - virtualThumbSize);
3152
+ let desiredThumbStart = virtualMousePos - offsetVirtual;
3153
+ desiredThumbStart = Math.max(0, Math.min(maxThumbStart, desiredThumbStart));
3154
+ const ratio = maxThumbStart === 0 ? 0 : desiredThumbStart / maxThumbStart;
3155
+ const range = this._max - this._min;
3156
+ const newValue = this._min + ratio * range;
3157
+ this.value = newValue;
2835
3158
  }
2836
3159
  getThumbRect() {
2837
- const thumbPos = this.getThumbPosition();
3160
+ const virtualThumbSize = this.getVirtualThumbSize();
3161
+ const virtualThumbStart = this.getVirtualThumbStart();
3162
+ const realThumbStart = Math.floor(virtualThumbStart / 2);
3163
+ const realThumbSize = Math.ceil((virtualThumbStart + virtualThumbSize) / 2) - realThumbStart;
2838
3164
  if (this.orientation === "vertical") {
2839
3165
  return {
2840
3166
  x: this.x,
2841
- y: this.y + thumbPos,
3167
+ y: this.y + realThumbStart,
2842
3168
  width: this.width,
2843
- height: this._thumbSize
3169
+ height: Math.max(1, realThumbSize)
2844
3170
  };
2845
3171
  } else {
2846
3172
  return {
2847
- x: this.x + thumbPos,
3173
+ x: this.x + realThumbStart,
2848
3174
  y: this.y,
2849
- width: this._thumbSize,
3175
+ width: Math.max(1, realThumbSize),
2850
3176
  height: this.height
2851
3177
  };
2852
3178
  }
2853
3179
  }
2854
3180
  renderSelf(buffer) {
3181
+ if (this.orientation === "horizontal") {
3182
+ this.renderHorizontal(buffer);
3183
+ } else {
3184
+ this.renderVertical(buffer);
3185
+ }
3186
+ }
3187
+ renderHorizontal(buffer) {
3188
+ const virtualThumbSize = this.getVirtualThumbSize();
3189
+ const virtualThumbStart = this.getVirtualThumbStart();
3190
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
2855
3191
  buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
2856
- const thumbRect = this.getThumbRect();
2857
- buffer.fillRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, this._foregroundColor);
3192
+ const realStartCell = Math.floor(virtualThumbStart / 2);
3193
+ const realEndCell = Math.floor((virtualThumbEnd - 1) / 2);
3194
+ const startX = Math.max(0, realStartCell);
3195
+ const endX = Math.min(this.width - 1, realEndCell);
3196
+ for (let realX = startX;realX <= endX; realX++) {
3197
+ const virtualCellStart = realX * 2;
3198
+ const virtualCellEnd = virtualCellStart + 2;
3199
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
3200
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
3201
+ const coverage = thumbEndInCell - thumbStartInCell;
3202
+ let char = " ";
3203
+ if (coverage >= 2) {
3204
+ char = "\u2588";
3205
+ } else {
3206
+ const isLeftHalf = thumbStartInCell === virtualCellStart;
3207
+ if (isLeftHalf) {
3208
+ char = "\u258C";
3209
+ } else {
3210
+ char = "\u2590";
3211
+ }
3212
+ }
3213
+ for (let y = 0;y < this.height; y++) {
3214
+ buffer.setCellWithAlphaBlending(this.x + realX, this.y + y, char, this._foregroundColor, this._backgroundColor);
3215
+ }
3216
+ }
3217
+ }
3218
+ renderVertical(buffer) {
3219
+ const virtualThumbSize = this.getVirtualThumbSize();
3220
+ const virtualThumbStart = this.getVirtualThumbStart();
3221
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
3222
+ buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
3223
+ const realStartCell = Math.floor(virtualThumbStart / 2);
3224
+ const realEndCell = Math.floor((virtualThumbEnd - 1) / 2);
3225
+ const startY = Math.max(0, realStartCell);
3226
+ const endY = Math.min(this.height - 1, realEndCell);
3227
+ for (let realY = startY;realY <= endY; realY++) {
3228
+ const virtualCellStart = realY * 2;
3229
+ const virtualCellEnd = virtualCellStart + 2;
3230
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
3231
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
3232
+ const coverage = thumbEndInCell - thumbStartInCell;
3233
+ let char = " ";
3234
+ if (coverage >= 2) {
3235
+ char = "\u2588";
3236
+ } else if (coverage > 0) {
3237
+ const virtualPositionInCell = thumbStartInCell - virtualCellStart;
3238
+ if (virtualPositionInCell === 0) {
3239
+ char = "\u2580";
3240
+ } else {
3241
+ char = "\u2584";
3242
+ }
3243
+ }
3244
+ for (let x = 0;x < this.width; x++) {
3245
+ buffer.setCellWithAlphaBlending(this.x + x, this.y + realY, char, this._foregroundColor, this._backgroundColor);
3246
+ }
3247
+ }
3248
+ }
3249
+ getVirtualThumbSize() {
3250
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
3251
+ const range = this._max - this._min;
3252
+ if (range === 0)
3253
+ return virtualTrackSize;
3254
+ const viewportSize = Math.max(1, this._viewPortSize);
3255
+ const contentSize = range + viewportSize;
3256
+ if (contentSize <= viewportSize)
3257
+ return virtualTrackSize;
3258
+ const thumbRatio = viewportSize / contentSize;
3259
+ const calculatedSize = Math.floor(virtualTrackSize * thumbRatio);
3260
+ return Math.max(1, Math.min(calculatedSize, virtualTrackSize));
3261
+ }
3262
+ getVirtualThumbStart() {
3263
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
3264
+ const range = this._max - this._min;
3265
+ if (range === 0)
3266
+ return 0;
3267
+ const valueRatio = (this._value - this._min) / range;
3268
+ const virtualThumbSize = this.getVirtualThumbSize();
3269
+ return Math.round(valueRatio * (virtualTrackSize - virtualThumbSize));
2858
3270
  }
2859
3271
  }
2860
3272
 
@@ -2864,7 +3276,7 @@ class ScrollBarRenderable extends Renderable {
2864
3276
  startArrow;
2865
3277
  endArrow;
2866
3278
  orientation;
2867
- focusable = true;
3279
+ _focusable = true;
2868
3280
  _scrollSize = 0;
2869
3281
  _scrollPosition = 0;
2870
3282
  _viewportSize = 0;
@@ -2897,6 +3309,7 @@ class ScrollBarRenderable extends Renderable {
2897
3309
  return;
2898
3310
  this._scrollSize = value;
2899
3311
  this.recalculateVisibility();
3312
+ this.updateSliderFromScrollState();
2900
3313
  this.scrollPosition = this.scrollPosition;
2901
3314
  }
2902
3315
  set scrollPosition(value) {
@@ -2904,15 +3317,15 @@ class ScrollBarRenderable extends Renderable {
2904
3317
  if (newPosition !== this._scrollPosition) {
2905
3318
  this._scrollPosition = newPosition;
2906
3319
  this.updateSliderFromScrollState();
2907
- this._onChange?.(newPosition);
2908
- this.emit("change", { position: newPosition });
2909
3320
  }
2910
3321
  }
2911
3322
  set viewportSize(value) {
2912
3323
  if (value === this.viewportSize)
2913
3324
  return;
2914
3325
  this._viewportSize = value;
3326
+ this.slider.viewPortSize = Math.max(1, this._viewportSize);
2915
3327
  this.recalculateVisibility();
3328
+ this.updateSliderFromScrollState();
2916
3329
  this.scrollPosition = this.scrollPosition;
2917
3330
  }
2918
3331
  get showArrows() {
@@ -2935,16 +3348,22 @@ class ScrollBarRenderable extends Renderable {
2935
3348
  this._onChange = options.onChange;
2936
3349
  this.orientation = orientation;
2937
3350
  this._showArrows = showArrows;
3351
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
3352
+ const defaultStepSize = Math.max(1, this._viewportSize);
3353
+ const stepSize = trackOptions?.viewPortSize ?? defaultStepSize;
2938
3354
  this.slider = new SliderRenderable(ctx, {
2939
3355
  orientation,
2940
- onChange: (position) => {
2941
- const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
2942
- this._scrollPosition = Math.round(position * scrollRange);
3356
+ min: 0,
3357
+ max: scrollRange,
3358
+ value: this._scrollPosition,
3359
+ viewPortSize: stepSize,
3360
+ onChange: (value) => {
3361
+ this._scrollPosition = Math.round(value);
2943
3362
  this._onChange?.(this._scrollPosition);
2944
3363
  this.emit("change", { position: this._scrollPosition });
2945
3364
  },
2946
3365
  ...orientation === "vertical" ? {
2947
- width: 2,
3366
+ width: Math.max(1, Math.min(2, this.width)),
2948
3367
  height: "100%",
2949
3368
  marginLeft: "auto"
2950
3369
  } : {
@@ -3023,17 +3442,10 @@ class ScrollBarRenderable extends Renderable {
3023
3442
  this.requestRender();
3024
3443
  }
3025
3444
  updateSliderFromScrollState() {
3026
- const trackSize = this.orientation === "vertical" ? this.slider.height : this.slider.width;
3027
3445
  const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
3028
- if (scrollRange === 0) {
3029
- this.slider.thumbSize = trackSize;
3030
- this.slider.thumbPosition = 0;
3031
- } else {
3032
- const sizeRatio = this._viewportSize / this._scrollSize;
3033
- this.slider.thumbSize = Math.max(1, Math.round(sizeRatio * trackSize));
3034
- const positionRatio = this._scrollPosition / scrollRange;
3035
- this.slider.thumbPosition = Math.max(0, Math.min(1, positionRatio));
3036
- }
3446
+ this.slider.min = 0;
3447
+ this.slider.max = scrollRange;
3448
+ this.slider.value = Math.min(this._scrollPosition, scrollRange);
3037
3449
  }
3038
3450
  scrollBy(delta, unit = "absolute") {
3039
3451
  const multiplier = unit === "viewport" ? this.viewportSize : unit === "content" ? this.scrollSize : unit === "step" ? this.scrollStep ?? 1 : 1;
@@ -3103,10 +3515,10 @@ class ArrowRenderable extends Renderable {
3103
3515
  this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : RGBA.fromValues(0, 0, 0, 0);
3104
3516
  this._attributes = options.attributes ?? 0;
3105
3517
  this._arrowChars = {
3106
- up: "\u25E2\u25E3",
3107
- down: "\u25E5\u25E4",
3108
- left: " \u25C0 ",
3109
- right: " \u25B6 ",
3518
+ up: "\u25B2",
3519
+ down: "\u25BC",
3520
+ left: "\u25C0",
3521
+ right: "\u25B6",
3110
3522
  ...options.arrowChars
3111
3523
  };
3112
3524
  if (!options.width) {
@@ -3184,7 +3596,7 @@ class ContentRenderable extends BoxRenderable {
3184
3596
  this.viewport = viewport;
3185
3597
  }
3186
3598
  _getChildren() {
3187
- return this.getChildrenInViewport(this.viewport);
3599
+ return getObjectsInViewport(this.viewport, this.getChildrenSortedByPrimaryAxis(), this.primaryAxis);
3188
3600
  }
3189
3601
  }
3190
3602
 
@@ -3196,7 +3608,7 @@ class ScrollBoxRenderable extends BoxRenderable {
3196
3608
  content;
3197
3609
  horizontalScrollBar;
3198
3610
  verticalScrollBar;
3199
- focusable = true;
3611
+ _focusable = true;
3200
3612
  selectionListener;
3201
3613
  autoScrollMouseX = 0;
3202
3614
  autoScrollMouseY = 0;
@@ -3209,17 +3621,42 @@ class ScrollBoxRenderable extends BoxRenderable {
3209
3621
  cachedAutoScrollSpeed = 3;
3210
3622
  autoScrollAccumulatorX = 0;
3211
3623
  autoScrollAccumulatorY = 0;
3624
+ _stickyScroll;
3625
+ _stickyScrollTop = false;
3626
+ _stickyScrollBottom = false;
3627
+ _stickyScrollLeft = false;
3628
+ _stickyScrollRight = false;
3629
+ _stickyStart;
3630
+ _hasManualScroll = false;
3631
+ get stickyScroll() {
3632
+ return this._stickyScroll;
3633
+ }
3634
+ set stickyScroll(value) {
3635
+ this._stickyScroll = value;
3636
+ this.updateStickyState();
3637
+ }
3638
+ get stickyStart() {
3639
+ return this._stickyStart;
3640
+ }
3641
+ set stickyStart(value) {
3642
+ this._stickyStart = value;
3643
+ this.updateStickyState();
3644
+ }
3212
3645
  get scrollTop() {
3213
3646
  return this.verticalScrollBar.scrollPosition;
3214
3647
  }
3215
3648
  set scrollTop(value) {
3216
3649
  this.verticalScrollBar.scrollPosition = value;
3650
+ this._hasManualScroll = true;
3651
+ this.updateStickyState();
3217
3652
  }
3218
3653
  get scrollLeft() {
3219
3654
  return this.horizontalScrollBar.scrollPosition;
3220
3655
  }
3221
3656
  set scrollLeft(value) {
3222
3657
  this.horizontalScrollBar.scrollPosition = value;
3658
+ this._hasManualScroll = true;
3659
+ this.updateStickyState();
3223
3660
  }
3224
3661
  get scrollWidth() {
3225
3662
  return this.horizontalScrollBar.scrollSize;
@@ -3227,6 +3664,56 @@ class ScrollBoxRenderable extends BoxRenderable {
3227
3664
  get scrollHeight() {
3228
3665
  return this.verticalScrollBar.scrollSize;
3229
3666
  }
3667
+ updateStickyState() {
3668
+ if (!this._stickyScroll)
3669
+ return;
3670
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
3671
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
3672
+ if (this.scrollTop <= 0) {
3673
+ this._stickyScrollTop = true;
3674
+ this._stickyScrollBottom = false;
3675
+ } else if (this.scrollTop >= maxScrollTop) {
3676
+ this._stickyScrollTop = false;
3677
+ this._stickyScrollBottom = true;
3678
+ } else {
3679
+ this._stickyScrollTop = false;
3680
+ this._stickyScrollBottom = false;
3681
+ }
3682
+ if (this.scrollLeft <= 0) {
3683
+ this._stickyScrollLeft = true;
3684
+ this._stickyScrollRight = false;
3685
+ } else if (this.scrollLeft >= maxScrollLeft) {
3686
+ this._stickyScrollLeft = false;
3687
+ this._stickyScrollRight = true;
3688
+ } else {
3689
+ this._stickyScrollLeft = false;
3690
+ this._stickyScrollRight = false;
3691
+ }
3692
+ }
3693
+ applyStickyStart(stickyStart) {
3694
+ switch (stickyStart) {
3695
+ case "top":
3696
+ this._stickyScrollTop = true;
3697
+ this._stickyScrollBottom = false;
3698
+ this.verticalScrollBar.scrollPosition = 0;
3699
+ break;
3700
+ case "bottom":
3701
+ this._stickyScrollTop = false;
3702
+ this._stickyScrollBottom = true;
3703
+ this.verticalScrollBar.scrollPosition = Math.max(0, this.scrollHeight - this.viewport.height);
3704
+ break;
3705
+ case "left":
3706
+ this._stickyScrollLeft = true;
3707
+ this._stickyScrollRight = false;
3708
+ this.horizontalScrollBar.scrollPosition = 0;
3709
+ break;
3710
+ case "right":
3711
+ this._stickyScrollLeft = false;
3712
+ this._stickyScrollRight = true;
3713
+ this.horizontalScrollBar.scrollPosition = Math.max(0, this.scrollWidth - this.viewport.width);
3714
+ break;
3715
+ }
3716
+ }
3230
3717
  constructor(ctx, {
3231
3718
  wrapperOptions,
3232
3719
  viewportOptions,
@@ -3235,6 +3722,8 @@ class ScrollBoxRenderable extends BoxRenderable {
3235
3722
  scrollbarOptions,
3236
3723
  verticalScrollbarOptions,
3237
3724
  horizontalScrollbarOptions,
3725
+ stickyScroll = false,
3726
+ stickyStart,
3238
3727
  ...options
3239
3728
  }) {
3240
3729
  super(ctx, {
@@ -3247,6 +3736,8 @@ class ScrollBoxRenderable extends BoxRenderable {
3247
3736
  ...rootOptions
3248
3737
  });
3249
3738
  this.internalId = ScrollBoxRenderable.idCounter++;
3739
+ this._stickyScroll = stickyScroll;
3740
+ this._stickyStart = stickyStart;
3250
3741
  this.wrapper = new BoxRenderable(ctx, {
3251
3742
  flexDirection: "column",
3252
3743
  flexGrow: 1,
@@ -3295,6 +3786,8 @@ class ScrollBoxRenderable extends BoxRenderable {
3295
3786
  orientation: "vertical",
3296
3787
  onChange: (position) => {
3297
3788
  this.content.translateY = -position;
3789
+ this._hasManualScroll = true;
3790
+ this.updateStickyState();
3298
3791
  }
3299
3792
  });
3300
3793
  super.add(this.verticalScrollBar);
@@ -3309,10 +3802,15 @@ class ScrollBoxRenderable extends BoxRenderable {
3309
3802
  orientation: "horizontal",
3310
3803
  onChange: (position) => {
3311
3804
  this.content.translateX = -position;
3805
+ this._hasManualScroll = true;
3806
+ this.updateStickyState();
3312
3807
  }
3313
3808
  });
3314
3809
  this.wrapper.add(this.horizontalScrollBar);
3315
3810
  this.recalculateBarProps();
3811
+ if (stickyStart && stickyScroll) {
3812
+ this.applyStickyStart(stickyStart);
3813
+ }
3316
3814
  this.selectionListener = () => {
3317
3815
  const selection = this._ctx.getSelection();
3318
3816
  if (!selection || !selection.isSelecting) {
@@ -3331,6 +3829,7 @@ class ScrollBoxRenderable extends BoxRenderable {
3331
3829
  this.verticalScrollBar.scrollBy(delta.y, unit);
3332
3830
  this.horizontalScrollBar.scrollBy(delta.x, unit);
3333
3831
  }
3832
+ this._hasManualScroll = true;
3334
3833
  }
3335
3834
  scrollTo(position) {
3336
3835
  if (typeof position === "number") {
@@ -3362,6 +3861,7 @@ class ScrollBoxRenderable extends BoxRenderable {
3362
3861
  this.scrollLeft -= event.scroll?.delta ?? 0;
3363
3862
  else if (dir === "right")
3364
3863
  this.scrollLeft += event.scroll?.delta ?? 0;
3864
+ this._hasManualScroll = true;
3365
3865
  }
3366
3866
  if (event.type === "drag" && event.isSelecting) {
3367
3867
  this.updateAutoScroll(event.x, event.y);
@@ -3370,10 +3870,14 @@ class ScrollBoxRenderable extends BoxRenderable {
3370
3870
  }
3371
3871
  }
3372
3872
  handleKeyPress(key) {
3373
- if (this.verticalScrollBar.handleKeyPress(key))
3873
+ if (this.verticalScrollBar.handleKeyPress(key)) {
3874
+ this._hasManualScroll = true;
3374
3875
  return true;
3375
- if (this.horizontalScrollBar.handleKeyPress(key))
3876
+ }
3877
+ if (this.horizontalScrollBar.handleKeyPress(key)) {
3878
+ this._hasManualScroll = true;
3376
3879
  return true;
3880
+ }
3377
3881
  return false;
3378
3882
  }
3379
3883
  startAutoScroll(mouseX, mouseY) {
@@ -3487,6 +3991,27 @@ class ScrollBoxRenderable extends BoxRenderable {
3487
3991
  this.verticalScrollBar.viewportSize = this.viewport.height;
3488
3992
  this.horizontalScrollBar.scrollSize = this.content.width;
3489
3993
  this.horizontalScrollBar.viewportSize = this.viewport.width;
3994
+ if (this._stickyScroll) {
3995
+ const newMaxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
3996
+ const newMaxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
3997
+ if (this._stickyStart && !this._hasManualScroll) {
3998
+ this.applyStickyStart(this._stickyStart);
3999
+ } else {
4000
+ if (this._stickyScrollTop) {
4001
+ this.scrollTop = 0;
4002
+ } else if (this._stickyScrollBottom && newMaxScrollTop > 0) {
4003
+ this.scrollTop = newMaxScrollTop;
4004
+ }
4005
+ if (this._stickyScrollLeft) {
4006
+ this.scrollLeft = 0;
4007
+ } else if (this._stickyScrollRight && newMaxScrollLeft > 0) {
4008
+ this.scrollLeft = newMaxScrollLeft;
4009
+ }
4010
+ }
4011
+ }
4012
+ process.nextTick(() => {
4013
+ this.requestRender();
4014
+ });
3490
4015
  }
3491
4016
  set rootOptions(options) {
3492
4017
  Object.assign(this, options);
@@ -3550,6 +4075,37 @@ function TabSelect(props, ...children) {
3550
4075
  function FrameBuffer(props, ...children) {
3551
4076
  return h(FrameBufferRenderable, props, ...children);
3552
4077
  }
4078
+ function StyledText2(props, ...children) {
4079
+ const styledProps = props;
4080
+ const textNodeOptions = {
4081
+ ...styledProps,
4082
+ attributes: styledProps?.attributes ?? 0
4083
+ };
4084
+ const textNode = new TextNodeRenderable(textNodeOptions);
4085
+ for (const child of children) {
4086
+ textNode.add(child);
4087
+ }
4088
+ return textNode;
4089
+ }
4090
+ var vstyles = {
4091
+ bold: (...children) => StyledText2({ attributes: TextAttributes.BOLD }, ...children),
4092
+ italic: (...children) => StyledText2({ attributes: TextAttributes.ITALIC }, ...children),
4093
+ underline: (...children) => StyledText2({ attributes: TextAttributes.UNDERLINE }, ...children),
4094
+ dim: (...children) => StyledText2({ attributes: TextAttributes.DIM }, ...children),
4095
+ blink: (...children) => StyledText2({ attributes: TextAttributes.BLINK }, ...children),
4096
+ inverse: (...children) => StyledText2({ attributes: TextAttributes.INVERSE }, ...children),
4097
+ hidden: (...children) => StyledText2({ attributes: TextAttributes.HIDDEN }, ...children),
4098
+ strikethrough: (...children) => StyledText2({ attributes: TextAttributes.STRIKETHROUGH }, ...children),
4099
+ boldItalic: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC }, ...children),
4100
+ boldUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.UNDERLINE }, ...children),
4101
+ italicUnderline: (...children) => StyledText2({ attributes: TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
4102
+ boldItalicUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
4103
+ color: (color, ...children) => StyledText2({ fg: color }, ...children),
4104
+ bgColor: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
4105
+ fg: (color, ...children) => StyledText2({ fg: color }, ...children),
4106
+ bg: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
4107
+ styled: (attributes = 0, ...children) => StyledText2({ attributes }, ...children)
4108
+ };
3553
4109
  // src/renderables/composition/VRenderable.ts
3554
4110
  class VRenderable extends Renderable {
3555
4111
  options;
@@ -3567,6 +4123,8 @@ export {
3567
4123
  yellow,
3568
4124
  wrapWithDelegates,
3569
4125
  white,
4126
+ vstyles,
4127
+ visualizeRenderableTree,
3570
4128
  underline,
3571
4129
  t,
3572
4130
  stringToStyledText,
@@ -3601,6 +4159,8 @@ export {
3601
4159
  italic,
3602
4160
  isValidPercentage,
3603
4161
  isVNode,
4162
+ isTextNodeRenderable,
4163
+ isStyledText,
3604
4164
  isSizeType,
3605
4165
  isRenderable,
3606
4166
  isPositionTypeType,
@@ -3667,6 +4227,7 @@ export {
3667
4227
  TrackedNode,
3668
4228
  Timeline,
3669
4229
  TextRenderable,
4230
+ TextNodeRenderable,
3670
4231
  TextBuffer,
3671
4232
  TextAttributes,
3672
4233
  Text,
@@ -3682,6 +4243,7 @@ export {
3682
4243
  Select,
3683
4244
  ScrollBoxRenderable,
3684
4245
  ScrollBarRenderable,
4246
+ RootTextNodeRenderable,
3685
4247
  RootRenderable,
3686
4248
  RenderableEvents,
3687
4249
  Renderable,
@@ -3711,11 +4273,12 @@ export {
3711
4273
  BorderCharArrays,
3712
4274
  BlurEffect,
3713
4275
  BloomEffect,
4276
+ BaseRenderable,
3714
4277
  ArrowRenderable,
3715
4278
  ASCIIFontSelectionHelper,
3716
4279
  ASCIIFontRenderable,
3717
4280
  ASCIIFont
3718
4281
  };
3719
4282
 
3720
- //# debugId=D81404410A6C6E2564756E2164756E21
4283
+ //# debugId=4D8A7644AF667D7864756E2164756E21
3721
4284
  //# sourceMappingURL=index.js.map