@opentui/core 0.0.0-20250908-4906ddad → 0.0.0-20250915-f5db043a

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-6esrcarp.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,232 @@ 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
+ get fg() {
1630
+ return this._fg;
1631
+ }
1632
+ set fg(fg2) {
1633
+ if (!fg2) {
1634
+ this._fg = undefined;
1635
+ this.requestRender();
1636
+ return;
1637
+ }
1638
+ this._fg = parseColor(fg2);
1639
+ this.requestRender();
1640
+ }
1641
+ set bg(bg2) {
1642
+ if (!bg2) {
1643
+ this._bg = undefined;
1644
+ this.requestRender();
1645
+ return;
1646
+ }
1647
+ this._bg = parseColor(bg2);
1648
+ this.requestRender();
1649
+ }
1650
+ get bg() {
1651
+ return this._bg;
1652
+ }
1653
+ set attributes(attributes) {
1654
+ this._attributes = attributes;
1655
+ this.requestRender();
1656
+ }
1657
+ get attributes() {
1658
+ return this._attributes;
1659
+ }
1660
+ }
1661
+
1662
+ class RootTextNodeRenderable extends TextNodeRenderable {
1663
+ ctx;
1664
+ textParent;
1665
+ constructor(ctx, options, textParent) {
1666
+ super(options);
1667
+ this.ctx = ctx;
1668
+ this.textParent = textParent;
1669
+ }
1670
+ requestRender() {
1671
+ this.markDirty();
1672
+ this.ctx.requestRender();
1673
+ }
1674
+ }
1675
+
1446
1676
  // src/renderables/Text.ts
1447
1677
  class TextRenderable extends Renderable {
1448
1678
  selectable = true;
@@ -1455,6 +1685,7 @@ class TextRenderable extends Renderable {
1455
1685
  lastLocalSelection = null;
1456
1686
  textBuffer;
1457
1687
  _lineInfo = { lineStarts: [], lineWidths: [] };
1688
+ rootTextNode;
1458
1689
  _defaultOptions = {
1459
1690
  content: "",
1460
1691
  fg: RGBA.fromValues(1, 1, 1, 1),
@@ -1480,6 +1711,12 @@ class TextRenderable extends Renderable {
1480
1711
  this.textBuffer.setDefaultBg(this._defaultBg);
1481
1712
  this.textBuffer.setDefaultAttributes(this._defaultAttributes);
1482
1713
  this.setupMeasureFunc();
1714
+ this.rootTextNode = new RootTextNodeRenderable(ctx, {
1715
+ id: `${this.id}-root`,
1716
+ fg: this._defaultFg,
1717
+ bg: this._defaultBg,
1718
+ attributes: this._defaultAttributes
1719
+ }, this);
1483
1720
  this.updateTextBuffer(styledText);
1484
1721
  this._text.mount(this);
1485
1722
  this.updateTextInfo();
@@ -1488,12 +1725,7 @@ class TextRenderable extends Renderable {
1488
1725
  this.textBuffer.setStyledText(styledText);
1489
1726
  this.clearChunks(styledText);
1490
1727
  }
1491
- clearChunks(styledText) {
1492
- styledText.chunks.forEach((chunk) => {
1493
- chunk.text = undefined;
1494
- chunk.plainText = undefined;
1495
- });
1496
- }
1728
+ clearChunks(styledText) {}
1497
1729
  get content() {
1498
1730
  return this._text;
1499
1731
  }
@@ -1506,6 +1738,9 @@ class TextRenderable extends Renderable {
1506
1738
  get chunks() {
1507
1739
  return this._text.chunks;
1508
1740
  }
1741
+ get textNode() {
1742
+ return this.rootTextNode;
1743
+ }
1509
1744
  set content(value) {
1510
1745
  const styledText = typeof value === "string" ? stringToStyledText(value) : value;
1511
1746
  if (this._text !== styledText) {
@@ -1520,9 +1755,11 @@ class TextRenderable extends Renderable {
1520
1755
  }
1521
1756
  set fg(value) {
1522
1757
  const newColor = parseColor(value ?? this._defaultOptions.fg);
1758
+ this.rootTextNode.fg = newColor;
1523
1759
  if (this._defaultFg !== newColor) {
1524
1760
  this._defaultFg = newColor;
1525
1761
  this.textBuffer.setDefaultFg(this._defaultFg);
1762
+ this.rootTextNode.fg = newColor;
1526
1763
  this.requestRender();
1527
1764
  }
1528
1765
  }
@@ -1557,9 +1794,11 @@ class TextRenderable extends Renderable {
1557
1794
  }
1558
1795
  set bg(value) {
1559
1796
  const newColor = parseColor(value ?? this._defaultOptions.bg);
1797
+ this.rootTextNode.bg = newColor;
1560
1798
  if (this._defaultBg !== newColor) {
1561
1799
  this._defaultBg = newColor;
1562
1800
  this.textBuffer.setDefaultBg(this._defaultBg);
1801
+ this.rootTextNode.bg = newColor;
1563
1802
  this.requestRender();
1564
1803
  }
1565
1804
  }
@@ -1570,6 +1809,7 @@ class TextRenderable extends Renderable {
1570
1809
  if (this._defaultAttributes !== value) {
1571
1810
  this._defaultAttributes = value;
1572
1811
  this.textBuffer.setDefaultAttributes(this._defaultAttributes);
1812
+ this.rootTextNode.attributes = value;
1573
1813
  this.requestRender();
1574
1814
  }
1575
1815
  }
@@ -1625,7 +1865,7 @@ class TextRenderable extends Renderable {
1625
1865
  this.layoutNode.yogaNode.setMeasureFunc(measureFunc);
1626
1866
  }
1627
1867
  insertChunk(chunk, index) {
1628
- this.textBuffer.insertEncodedChunkGroup(index ?? this.textBuffer.chunkGroupCount, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1868
+ this.textBuffer.insertChunkGroup(index ?? this.textBuffer.chunkGroupCount, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1629
1869
  this.updateTextInfo();
1630
1870
  this.clearChunks(this._text);
1631
1871
  }
@@ -1641,10 +1881,46 @@ class TextRenderable extends Renderable {
1641
1881
  const index = this._text.chunks.indexOf(oldChunk);
1642
1882
  if (index === -1)
1643
1883
  return;
1644
- this.textBuffer.replaceEncodedChunkGroup(index, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1884
+ this.textBuffer.replaceChunkGroup(index, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
1645
1885
  this.updateTextInfo();
1646
1886
  this.clearChunks(this._text);
1647
1887
  }
1888
+ updateTextFromNodes() {
1889
+ if (this.rootTextNode.isDirty) {
1890
+ const chunks = this.rootTextNode.gatherWithInheritedStyle({
1891
+ fg: this._defaultFg,
1892
+ bg: this._defaultBg,
1893
+ attributes: this._defaultAttributes
1894
+ });
1895
+ this.textBuffer.setStyledText(new StyledText(chunks));
1896
+ this.updateTextInfo();
1897
+ }
1898
+ }
1899
+ add(obj, index) {
1900
+ return this.rootTextNode.add(obj, index);
1901
+ }
1902
+ remove(id) {
1903
+ const child = this.rootTextNode.getRenderable(id);
1904
+ if (child && isTextNodeRenderable(child)) {
1905
+ this.rootTextNode.remove(child);
1906
+ }
1907
+ }
1908
+ insertBefore(obj, anchor) {
1909
+ this.rootTextNode.insertBefore(obj, anchor);
1910
+ return this.rootTextNode.children.indexOf(obj);
1911
+ }
1912
+ getTextChildren() {
1913
+ return this.rootTextNode.getChildren();
1914
+ }
1915
+ clear() {
1916
+ this.rootTextNode.clear();
1917
+ const emptyStyledText = stringToStyledText("");
1918
+ this._text = emptyStyledText;
1919
+ emptyStyledText.mount(this);
1920
+ this.updateTextBuffer(emptyStyledText);
1921
+ this.updateTextInfo();
1922
+ this.requestRender();
1923
+ }
1648
1924
  shouldStartSelection(x, y) {
1649
1925
  if (!this.selectable)
1650
1926
  return false;
@@ -1670,6 +1946,16 @@ class TextRenderable extends Renderable {
1670
1946
  getSelection() {
1671
1947
  return this.textBuffer.getSelection();
1672
1948
  }
1949
+ onLifecyclePass = () => {
1950
+ this.updateTextFromNodes();
1951
+ };
1952
+ render(buffer, deltaTime) {
1953
+ if (!this.visible)
1954
+ return;
1955
+ this.markClean();
1956
+ this._ctx.addToHitGrid(this.x, this.y, this.width, this.height, this.num);
1957
+ this.renderSelf(buffer);
1958
+ }
1673
1959
  renderSelf(buffer) {
1674
1960
  if (this.textBuffer.ptr) {
1675
1961
  const clipRect = {
@@ -1683,6 +1969,7 @@ class TextRenderable extends Renderable {
1683
1969
  }
1684
1970
  destroy() {
1685
1971
  this.textBuffer.destroy();
1972
+ this.rootTextNode.children.length = 0;
1686
1973
  super.destroy();
1687
1974
  }
1688
1975
  }
@@ -1795,6 +2082,8 @@ class ASCIIFontRenderable extends FrameBufferRenderable {
1795
2082
  this.renderFontToBuffer();
1796
2083
  }
1797
2084
  renderFontToBuffer() {
2085
+ if (this.isDestroyed)
2086
+ return;
1798
2087
  this.frameBuffer.clear(this._bg);
1799
2088
  renderFontToFrameBuffer(this.frameBuffer, {
1800
2089
  text: this._text,
@@ -1842,7 +2131,7 @@ var InputRenderableEvents;
1842
2131
  })(InputRenderableEvents ||= {});
1843
2132
 
1844
2133
  class InputRenderable extends Renderable {
1845
- focusable = true;
2134
+ _focusable = true;
1846
2135
  _value = "";
1847
2136
  _cursorPosition = 0;
1848
2137
  _placeholder;
@@ -2115,7 +2404,7 @@ var SelectRenderableEvents;
2115
2404
  })(SelectRenderableEvents ||= {});
2116
2405
 
2117
2406
  class SelectRenderable extends Renderable {
2118
- focusable = true;
2407
+ _focusable = true;
2119
2408
  _options = [];
2120
2409
  selectedIndex = 0;
2121
2410
  scrollOffset = 0;
@@ -2451,7 +2740,7 @@ function calculateDynamicHeight(showUnderline, showDescription) {
2451
2740
  }
2452
2741
 
2453
2742
  class TabSelectRenderable extends Renderable {
2454
- focusable = true;
2743
+ _focusable = true;
2455
2744
  _options = [];
2456
2745
  selectedIndex = 0;
2457
2746
  scrollOffset = 0;
@@ -2741,43 +3030,71 @@ var defaultTrackBackgroundColor = RGBA.fromHex("#252527");
2741
3030
 
2742
3031
  class SliderRenderable extends Renderable {
2743
3032
  orientation;
2744
- _thumbSize;
2745
- _thumbPosition;
3033
+ _value;
3034
+ _min;
3035
+ _max;
3036
+ _viewPortSize;
2746
3037
  _backgroundColor;
2747
3038
  _foregroundColor;
2748
3039
  _onChange;
2749
3040
  constructor(ctx, options) {
2750
3041
  super(ctx, options);
2751
3042
  this.orientation = options.orientation;
2752
- this._thumbSize = options.thumbSize ?? 1;
2753
- this._thumbPosition = options.thumbPosition ?? 0;
3043
+ this._min = options.min ?? 0;
3044
+ this._max = options.max ?? 100;
3045
+ this._value = options.value ?? this._min;
3046
+ this._viewPortSize = options.viewPortSize ?? Math.max(1, (this._max - this._min) * 0.1);
2754
3047
  this._onChange = options.onChange;
2755
3048
  this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : defaultTrackBackgroundColor;
2756
3049
  this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : defaultThumbBackgroundColor;
2757
3050
  this.setupMouseHandling();
2758
3051
  }
2759
- get thumbSize() {
2760
- return this._thumbSize;
3052
+ get value() {
3053
+ return this._value;
2761
3054
  }
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;
3055
+ set value(newValue) {
3056
+ const clamped = Math.max(this._min, Math.min(this._max, newValue));
3057
+ if (clamped !== this._value) {
3058
+ this._value = clamped;
3059
+ this._onChange?.(clamped);
3060
+ this.emit("change", { value: clamped });
2766
3061
  this.requestRender();
2767
3062
  }
2768
3063
  }
2769
- get thumbPosition() {
2770
- return this._thumbPosition;
3064
+ get min() {
3065
+ return this._min;
2771
3066
  }
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 });
3067
+ set min(newMin) {
3068
+ if (newMin !== this._min) {
3069
+ this._min = newMin;
3070
+ if (this._value < newMin) {
3071
+ this.value = newMin;
3072
+ }
3073
+ this.requestRender();
3074
+ }
3075
+ }
3076
+ get max() {
3077
+ return this._max;
3078
+ }
3079
+ set max(newMax) {
3080
+ if (newMax !== this._max) {
3081
+ this._max = newMax;
3082
+ if (this._value > newMax) {
3083
+ this.value = newMax;
3084
+ }
2778
3085
  this.requestRender();
2779
3086
  }
2780
3087
  }
3088
+ set viewPortSize(size) {
3089
+ const clampedSize = Math.max(0.01, Math.min(size, this._max - this._min));
3090
+ if (clampedSize !== this._viewPortSize) {
3091
+ this._viewPortSize = clampedSize;
3092
+ this.requestRender();
3093
+ }
3094
+ }
3095
+ get viewPortSize() {
3096
+ return this._viewPortSize;
3097
+ }
2781
3098
  get backgroundColor() {
2782
3099
  return this._backgroundColor;
2783
3100
  }
@@ -2792,69 +3109,183 @@ class SliderRenderable extends Renderable {
2792
3109
  this._foregroundColor = parseColor(value);
2793
3110
  this.requestRender();
2794
3111
  }
3112
+ calculateDragOffsetVirtual(event) {
3113
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
3114
+ const mousePos = (this.orientation === "vertical" ? event.y : event.x) - trackStart;
3115
+ const virtualMousePos = Math.max(0, Math.min((this.orientation === "vertical" ? this.height : this.width) * 2, mousePos * 2));
3116
+ const virtualThumbStart = this.getVirtualThumbStart();
3117
+ const virtualThumbSize = this.getVirtualThumbSize();
3118
+ return Math.max(0, Math.min(virtualThumbSize, virtualMousePos - virtualThumbStart));
3119
+ }
2795
3120
  setupMouseHandling() {
2796
3121
  let isDragging = false;
2797
- let relativeStartPos = 0;
3122
+ let dragOffsetVirtual = 0;
2798
3123
  this.onMouseDown = (event) => {
2799
3124
  event.stopPropagation();
2800
3125
  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;
3126
+ const thumb = this.getThumbRect();
3127
+ const inThumb = event.x >= thumb.x && event.x < thumb.x + thumb.width && event.y >= thumb.y && event.y < thumb.y + thumb.height;
3128
+ if (inThumb) {
3129
+ isDragging = true;
3130
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
2806
3131
  } else {
2807
- relativeStartPos = this.orientation === "vertical" ? thumbRect.height / 2 : thumbRect.width / 2;
3132
+ this.updateValueFromMouseDirect(event);
3133
+ isDragging = true;
3134
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
2808
3135
  }
2809
- this.updatePositionFromMouse(event, relativeStartPos);
2810
3136
  };
2811
3137
  this.onMouseDrag = (event) => {
2812
3138
  if (!isDragging)
2813
3139
  return;
2814
3140
  event.stopPropagation();
2815
- this.updatePositionFromMouse(event, relativeStartPos);
3141
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
2816
3142
  };
2817
- this.onMouseUp = () => {
3143
+ this.onMouseUp = (event) => {
3144
+ if (isDragging) {
3145
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
3146
+ }
2818
3147
  isDragging = false;
2819
3148
  };
2820
3149
  }
2821
- updatePositionFromMouse(event, relativeStartPos) {
3150
+ updateValueFromMouseDirect(event) {
2822
3151
  const trackStart = this.orientation === "vertical" ? this.y : this.x;
2823
3152
  const trackSize = this.orientation === "vertical" ? this.height : this.width;
2824
3153
  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() {
3154
+ const relativeMousePos = mousePos - trackStart;
3155
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
3156
+ const ratio = trackSize === 0 ? 0 : clampedMousePos / trackSize;
3157
+ const range = this._max - this._min;
3158
+ const newValue = this._min + ratio * range;
3159
+ this.value = newValue;
3160
+ }
3161
+ updateValueFromMouseWithOffset(event, offsetVirtual) {
3162
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
2832
3163
  const trackSize = this.orientation === "vertical" ? this.height : this.width;
2833
- const maxPos = trackSize - this._thumbSize;
2834
- return Math.round(this._thumbPosition * maxPos);
3164
+ const mousePos = this.orientation === "vertical" ? event.y : event.x;
3165
+ const virtualTrackSize = trackSize * 2;
3166
+ const relativeMousePos = mousePos - trackStart;
3167
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
3168
+ const virtualMousePos = clampedMousePos * 2;
3169
+ const virtualThumbSize = this.getVirtualThumbSize();
3170
+ const maxThumbStart = Math.max(0, virtualTrackSize - virtualThumbSize);
3171
+ let desiredThumbStart = virtualMousePos - offsetVirtual;
3172
+ desiredThumbStart = Math.max(0, Math.min(maxThumbStart, desiredThumbStart));
3173
+ const ratio = maxThumbStart === 0 ? 0 : desiredThumbStart / maxThumbStart;
3174
+ const range = this._max - this._min;
3175
+ const newValue = this._min + ratio * range;
3176
+ this.value = newValue;
2835
3177
  }
2836
3178
  getThumbRect() {
2837
- const thumbPos = this.getThumbPosition();
3179
+ const virtualThumbSize = this.getVirtualThumbSize();
3180
+ const virtualThumbStart = this.getVirtualThumbStart();
3181
+ const realThumbStart = Math.floor(virtualThumbStart / 2);
3182
+ const realThumbSize = Math.ceil((virtualThumbStart + virtualThumbSize) / 2) - realThumbStart;
2838
3183
  if (this.orientation === "vertical") {
2839
3184
  return {
2840
3185
  x: this.x,
2841
- y: this.y + thumbPos,
3186
+ y: this.y + realThumbStart,
2842
3187
  width: this.width,
2843
- height: this._thumbSize
3188
+ height: Math.max(1, realThumbSize)
2844
3189
  };
2845
3190
  } else {
2846
3191
  return {
2847
- x: this.x + thumbPos,
3192
+ x: this.x + realThumbStart,
2848
3193
  y: this.y,
2849
- width: this._thumbSize,
3194
+ width: Math.max(1, realThumbSize),
2850
3195
  height: this.height
2851
3196
  };
2852
3197
  }
2853
3198
  }
2854
3199
  renderSelf(buffer) {
3200
+ if (this.orientation === "horizontal") {
3201
+ this.renderHorizontal(buffer);
3202
+ } else {
3203
+ this.renderVertical(buffer);
3204
+ }
3205
+ }
3206
+ renderHorizontal(buffer) {
3207
+ const virtualThumbSize = this.getVirtualThumbSize();
3208
+ const virtualThumbStart = this.getVirtualThumbStart();
3209
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
3210
+ buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
3211
+ const realStartCell = Math.floor(virtualThumbStart / 2);
3212
+ const realEndCell = Math.floor((virtualThumbEnd - 1) / 2);
3213
+ const startX = Math.max(0, realStartCell);
3214
+ const endX = Math.min(this.width - 1, realEndCell);
3215
+ for (let realX = startX;realX <= endX; realX++) {
3216
+ const virtualCellStart = realX * 2;
3217
+ const virtualCellEnd = virtualCellStart + 2;
3218
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
3219
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
3220
+ const coverage = thumbEndInCell - thumbStartInCell;
3221
+ let char = " ";
3222
+ if (coverage >= 2) {
3223
+ char = "\u2588";
3224
+ } else {
3225
+ const isLeftHalf = thumbStartInCell === virtualCellStart;
3226
+ if (isLeftHalf) {
3227
+ char = "\u258C";
3228
+ } else {
3229
+ char = "\u2590";
3230
+ }
3231
+ }
3232
+ for (let y = 0;y < this.height; y++) {
3233
+ buffer.setCellWithAlphaBlending(this.x + realX, this.y + y, char, this._foregroundColor, this._backgroundColor);
3234
+ }
3235
+ }
3236
+ }
3237
+ renderVertical(buffer) {
3238
+ const virtualThumbSize = this.getVirtualThumbSize();
3239
+ const virtualThumbStart = this.getVirtualThumbStart();
3240
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
2855
3241
  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);
3242
+ const realStartCell = Math.floor(virtualThumbStart / 2);
3243
+ const realEndCell = Math.floor((virtualThumbEnd - 1) / 2);
3244
+ const startY = Math.max(0, realStartCell);
3245
+ const endY = Math.min(this.height - 1, realEndCell);
3246
+ for (let realY = startY;realY <= endY; realY++) {
3247
+ const virtualCellStart = realY * 2;
3248
+ const virtualCellEnd = virtualCellStart + 2;
3249
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
3250
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
3251
+ const coverage = thumbEndInCell - thumbStartInCell;
3252
+ let char = " ";
3253
+ if (coverage >= 2) {
3254
+ char = "\u2588";
3255
+ } else if (coverage > 0) {
3256
+ const virtualPositionInCell = thumbStartInCell - virtualCellStart;
3257
+ if (virtualPositionInCell === 0) {
3258
+ char = "\u2580";
3259
+ } else {
3260
+ char = "\u2584";
3261
+ }
3262
+ }
3263
+ for (let x = 0;x < this.width; x++) {
3264
+ buffer.setCellWithAlphaBlending(this.x + x, this.y + realY, char, this._foregroundColor, this._backgroundColor);
3265
+ }
3266
+ }
3267
+ }
3268
+ getVirtualThumbSize() {
3269
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
3270
+ const range = this._max - this._min;
3271
+ if (range === 0)
3272
+ return virtualTrackSize;
3273
+ const viewportSize = Math.max(1, this._viewPortSize);
3274
+ const contentSize = range + viewportSize;
3275
+ if (contentSize <= viewportSize)
3276
+ return virtualTrackSize;
3277
+ const thumbRatio = viewportSize / contentSize;
3278
+ const calculatedSize = Math.floor(virtualTrackSize * thumbRatio);
3279
+ return Math.max(1, Math.min(calculatedSize, virtualTrackSize));
3280
+ }
3281
+ getVirtualThumbStart() {
3282
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
3283
+ const range = this._max - this._min;
3284
+ if (range === 0)
3285
+ return 0;
3286
+ const valueRatio = (this._value - this._min) / range;
3287
+ const virtualThumbSize = this.getVirtualThumbSize();
3288
+ return Math.round(valueRatio * (virtualTrackSize - virtualThumbSize));
2858
3289
  }
2859
3290
  }
2860
3291
 
@@ -2864,7 +3295,7 @@ class ScrollBarRenderable extends Renderable {
2864
3295
  startArrow;
2865
3296
  endArrow;
2866
3297
  orientation;
2867
- focusable = true;
3298
+ _focusable = true;
2868
3299
  _scrollSize = 0;
2869
3300
  _scrollPosition = 0;
2870
3301
  _viewportSize = 0;
@@ -2897,6 +3328,7 @@ class ScrollBarRenderable extends Renderable {
2897
3328
  return;
2898
3329
  this._scrollSize = value;
2899
3330
  this.recalculateVisibility();
3331
+ this.updateSliderFromScrollState();
2900
3332
  this.scrollPosition = this.scrollPosition;
2901
3333
  }
2902
3334
  set scrollPosition(value) {
@@ -2904,15 +3336,15 @@ class ScrollBarRenderable extends Renderable {
2904
3336
  if (newPosition !== this._scrollPosition) {
2905
3337
  this._scrollPosition = newPosition;
2906
3338
  this.updateSliderFromScrollState();
2907
- this._onChange?.(newPosition);
2908
- this.emit("change", { position: newPosition });
2909
3339
  }
2910
3340
  }
2911
3341
  set viewportSize(value) {
2912
3342
  if (value === this.viewportSize)
2913
3343
  return;
2914
3344
  this._viewportSize = value;
3345
+ this.slider.viewPortSize = Math.max(1, this._viewportSize);
2915
3346
  this.recalculateVisibility();
3347
+ this.updateSliderFromScrollState();
2916
3348
  this.scrollPosition = this.scrollPosition;
2917
3349
  }
2918
3350
  get showArrows() {
@@ -2935,16 +3367,22 @@ class ScrollBarRenderable extends Renderable {
2935
3367
  this._onChange = options.onChange;
2936
3368
  this.orientation = orientation;
2937
3369
  this._showArrows = showArrows;
3370
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
3371
+ const defaultStepSize = Math.max(1, this._viewportSize);
3372
+ const stepSize = trackOptions?.viewPortSize ?? defaultStepSize;
2938
3373
  this.slider = new SliderRenderable(ctx, {
2939
3374
  orientation,
2940
- onChange: (position) => {
2941
- const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
2942
- this._scrollPosition = Math.round(position * scrollRange);
3375
+ min: 0,
3376
+ max: scrollRange,
3377
+ value: this._scrollPosition,
3378
+ viewPortSize: stepSize,
3379
+ onChange: (value) => {
3380
+ this._scrollPosition = Math.round(value);
2943
3381
  this._onChange?.(this._scrollPosition);
2944
3382
  this.emit("change", { position: this._scrollPosition });
2945
3383
  },
2946
3384
  ...orientation === "vertical" ? {
2947
- width: 2,
3385
+ width: Math.max(1, Math.min(2, this.width)),
2948
3386
  height: "100%",
2949
3387
  marginLeft: "auto"
2950
3388
  } : {
@@ -3023,17 +3461,10 @@ class ScrollBarRenderable extends Renderable {
3023
3461
  this.requestRender();
3024
3462
  }
3025
3463
  updateSliderFromScrollState() {
3026
- const trackSize = this.orientation === "vertical" ? this.slider.height : this.slider.width;
3027
3464
  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
- }
3465
+ this.slider.min = 0;
3466
+ this.slider.max = scrollRange;
3467
+ this.slider.value = Math.min(this._scrollPosition, scrollRange);
3037
3468
  }
3038
3469
  scrollBy(delta, unit = "absolute") {
3039
3470
  const multiplier = unit === "viewport" ? this.viewportSize : unit === "content" ? this.scrollSize : unit === "step" ? this.scrollStep ?? 1 : 1;
@@ -3103,10 +3534,10 @@ class ArrowRenderable extends Renderable {
3103
3534
  this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : RGBA.fromValues(0, 0, 0, 0);
3104
3535
  this._attributes = options.attributes ?? 0;
3105
3536
  this._arrowChars = {
3106
- up: "\u25E2\u25E3",
3107
- down: "\u25E5\u25E4",
3108
- left: " \u25C0 ",
3109
- right: " \u25B6 ",
3537
+ up: "\u25B2",
3538
+ down: "\u25BC",
3539
+ left: "\u25C0",
3540
+ right: "\u25B6",
3110
3541
  ...options.arrowChars
3111
3542
  };
3112
3543
  if (!options.width) {
@@ -3184,7 +3615,7 @@ class ContentRenderable extends BoxRenderable {
3184
3615
  this.viewport = viewport;
3185
3616
  }
3186
3617
  _getChildren() {
3187
- return this.getChildrenInViewport(this.viewport);
3618
+ return getObjectsInViewport(this.viewport, this.getChildrenSortedByPrimaryAxis(), this.primaryAxis);
3188
3619
  }
3189
3620
  }
3190
3621
 
@@ -3196,7 +3627,7 @@ class ScrollBoxRenderable extends BoxRenderable {
3196
3627
  content;
3197
3628
  horizontalScrollBar;
3198
3629
  verticalScrollBar;
3199
- focusable = true;
3630
+ _focusable = true;
3200
3631
  selectionListener;
3201
3632
  autoScrollMouseX = 0;
3202
3633
  autoScrollMouseY = 0;
@@ -3209,17 +3640,42 @@ class ScrollBoxRenderable extends BoxRenderable {
3209
3640
  cachedAutoScrollSpeed = 3;
3210
3641
  autoScrollAccumulatorX = 0;
3211
3642
  autoScrollAccumulatorY = 0;
3643
+ _stickyScroll;
3644
+ _stickyScrollTop = false;
3645
+ _stickyScrollBottom = false;
3646
+ _stickyScrollLeft = false;
3647
+ _stickyScrollRight = false;
3648
+ _stickyStart;
3649
+ _hasManualScroll = false;
3650
+ get stickyScroll() {
3651
+ return this._stickyScroll;
3652
+ }
3653
+ set stickyScroll(value) {
3654
+ this._stickyScroll = value;
3655
+ this.updateStickyState();
3656
+ }
3657
+ get stickyStart() {
3658
+ return this._stickyStart;
3659
+ }
3660
+ set stickyStart(value) {
3661
+ this._stickyStart = value;
3662
+ this.updateStickyState();
3663
+ }
3212
3664
  get scrollTop() {
3213
3665
  return this.verticalScrollBar.scrollPosition;
3214
3666
  }
3215
3667
  set scrollTop(value) {
3216
3668
  this.verticalScrollBar.scrollPosition = value;
3669
+ this._hasManualScroll = true;
3670
+ this.updateStickyState();
3217
3671
  }
3218
3672
  get scrollLeft() {
3219
3673
  return this.horizontalScrollBar.scrollPosition;
3220
3674
  }
3221
3675
  set scrollLeft(value) {
3222
3676
  this.horizontalScrollBar.scrollPosition = value;
3677
+ this._hasManualScroll = true;
3678
+ this.updateStickyState();
3223
3679
  }
3224
3680
  get scrollWidth() {
3225
3681
  return this.horizontalScrollBar.scrollSize;
@@ -3227,6 +3683,56 @@ class ScrollBoxRenderable extends BoxRenderable {
3227
3683
  get scrollHeight() {
3228
3684
  return this.verticalScrollBar.scrollSize;
3229
3685
  }
3686
+ updateStickyState() {
3687
+ if (!this._stickyScroll)
3688
+ return;
3689
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
3690
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
3691
+ if (this.scrollTop <= 0) {
3692
+ this._stickyScrollTop = true;
3693
+ this._stickyScrollBottom = false;
3694
+ } else if (this.scrollTop >= maxScrollTop) {
3695
+ this._stickyScrollTop = false;
3696
+ this._stickyScrollBottom = true;
3697
+ } else {
3698
+ this._stickyScrollTop = false;
3699
+ this._stickyScrollBottom = false;
3700
+ }
3701
+ if (this.scrollLeft <= 0) {
3702
+ this._stickyScrollLeft = true;
3703
+ this._stickyScrollRight = false;
3704
+ } else if (this.scrollLeft >= maxScrollLeft) {
3705
+ this._stickyScrollLeft = false;
3706
+ this._stickyScrollRight = true;
3707
+ } else {
3708
+ this._stickyScrollLeft = false;
3709
+ this._stickyScrollRight = false;
3710
+ }
3711
+ }
3712
+ applyStickyStart(stickyStart) {
3713
+ switch (stickyStart) {
3714
+ case "top":
3715
+ this._stickyScrollTop = true;
3716
+ this._stickyScrollBottom = false;
3717
+ this.verticalScrollBar.scrollPosition = 0;
3718
+ break;
3719
+ case "bottom":
3720
+ this._stickyScrollTop = false;
3721
+ this._stickyScrollBottom = true;
3722
+ this.verticalScrollBar.scrollPosition = Math.max(0, this.scrollHeight - this.viewport.height);
3723
+ break;
3724
+ case "left":
3725
+ this._stickyScrollLeft = true;
3726
+ this._stickyScrollRight = false;
3727
+ this.horizontalScrollBar.scrollPosition = 0;
3728
+ break;
3729
+ case "right":
3730
+ this._stickyScrollLeft = false;
3731
+ this._stickyScrollRight = true;
3732
+ this.horizontalScrollBar.scrollPosition = Math.max(0, this.scrollWidth - this.viewport.width);
3733
+ break;
3734
+ }
3735
+ }
3230
3736
  constructor(ctx, {
3231
3737
  wrapperOptions,
3232
3738
  viewportOptions,
@@ -3235,6 +3741,8 @@ class ScrollBoxRenderable extends BoxRenderable {
3235
3741
  scrollbarOptions,
3236
3742
  verticalScrollbarOptions,
3237
3743
  horizontalScrollbarOptions,
3744
+ stickyScroll = false,
3745
+ stickyStart,
3238
3746
  ...options
3239
3747
  }) {
3240
3748
  super(ctx, {
@@ -3247,6 +3755,8 @@ class ScrollBoxRenderable extends BoxRenderable {
3247
3755
  ...rootOptions
3248
3756
  });
3249
3757
  this.internalId = ScrollBoxRenderable.idCounter++;
3758
+ this._stickyScroll = stickyScroll;
3759
+ this._stickyStart = stickyStart;
3250
3760
  this.wrapper = new BoxRenderable(ctx, {
3251
3761
  flexDirection: "column",
3252
3762
  flexGrow: 1,
@@ -3295,6 +3805,8 @@ class ScrollBoxRenderable extends BoxRenderable {
3295
3805
  orientation: "vertical",
3296
3806
  onChange: (position) => {
3297
3807
  this.content.translateY = -position;
3808
+ this._hasManualScroll = true;
3809
+ this.updateStickyState();
3298
3810
  }
3299
3811
  });
3300
3812
  super.add(this.verticalScrollBar);
@@ -3309,10 +3821,15 @@ class ScrollBoxRenderable extends BoxRenderable {
3309
3821
  orientation: "horizontal",
3310
3822
  onChange: (position) => {
3311
3823
  this.content.translateX = -position;
3824
+ this._hasManualScroll = true;
3825
+ this.updateStickyState();
3312
3826
  }
3313
3827
  });
3314
3828
  this.wrapper.add(this.horizontalScrollBar);
3315
3829
  this.recalculateBarProps();
3830
+ if (stickyStart && stickyScroll) {
3831
+ this.applyStickyStart(stickyStart);
3832
+ }
3316
3833
  this.selectionListener = () => {
3317
3834
  const selection = this._ctx.getSelection();
3318
3835
  if (!selection || !selection.isSelecting) {
@@ -3331,6 +3848,7 @@ class ScrollBoxRenderable extends BoxRenderable {
3331
3848
  this.verticalScrollBar.scrollBy(delta.y, unit);
3332
3849
  this.horizontalScrollBar.scrollBy(delta.x, unit);
3333
3850
  }
3851
+ this._hasManualScroll = true;
3334
3852
  }
3335
3853
  scrollTo(position) {
3336
3854
  if (typeof position === "number") {
@@ -3362,6 +3880,7 @@ class ScrollBoxRenderable extends BoxRenderable {
3362
3880
  this.scrollLeft -= event.scroll?.delta ?? 0;
3363
3881
  else if (dir === "right")
3364
3882
  this.scrollLeft += event.scroll?.delta ?? 0;
3883
+ this._hasManualScroll = true;
3365
3884
  }
3366
3885
  if (event.type === "drag" && event.isSelecting) {
3367
3886
  this.updateAutoScroll(event.x, event.y);
@@ -3370,10 +3889,14 @@ class ScrollBoxRenderable extends BoxRenderable {
3370
3889
  }
3371
3890
  }
3372
3891
  handleKeyPress(key) {
3373
- if (this.verticalScrollBar.handleKeyPress(key))
3892
+ if (this.verticalScrollBar.handleKeyPress(key)) {
3893
+ this._hasManualScroll = true;
3374
3894
  return true;
3375
- if (this.horizontalScrollBar.handleKeyPress(key))
3895
+ }
3896
+ if (this.horizontalScrollBar.handleKeyPress(key)) {
3897
+ this._hasManualScroll = true;
3376
3898
  return true;
3899
+ }
3377
3900
  return false;
3378
3901
  }
3379
3902
  startAutoScroll(mouseX, mouseY) {
@@ -3487,6 +4010,27 @@ class ScrollBoxRenderable extends BoxRenderable {
3487
4010
  this.verticalScrollBar.viewportSize = this.viewport.height;
3488
4011
  this.horizontalScrollBar.scrollSize = this.content.width;
3489
4012
  this.horizontalScrollBar.viewportSize = this.viewport.width;
4013
+ if (this._stickyScroll) {
4014
+ const newMaxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
4015
+ const newMaxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
4016
+ if (this._stickyStart && !this._hasManualScroll) {
4017
+ this.applyStickyStart(this._stickyStart);
4018
+ } else {
4019
+ if (this._stickyScrollTop) {
4020
+ this.scrollTop = 0;
4021
+ } else if (this._stickyScrollBottom && newMaxScrollTop > 0) {
4022
+ this.scrollTop = newMaxScrollTop;
4023
+ }
4024
+ if (this._stickyScrollLeft) {
4025
+ this.scrollLeft = 0;
4026
+ } else if (this._stickyScrollRight && newMaxScrollLeft > 0) {
4027
+ this.scrollLeft = newMaxScrollLeft;
4028
+ }
4029
+ }
4030
+ }
4031
+ process.nextTick(() => {
4032
+ this.requestRender();
4033
+ });
3490
4034
  }
3491
4035
  set rootOptions(options) {
3492
4036
  Object.assign(this, options);
@@ -3550,6 +4094,37 @@ function TabSelect(props, ...children) {
3550
4094
  function FrameBuffer(props, ...children) {
3551
4095
  return h(FrameBufferRenderable, props, ...children);
3552
4096
  }
4097
+ function StyledText2(props, ...children) {
4098
+ const styledProps = props;
4099
+ const textNodeOptions = {
4100
+ ...styledProps,
4101
+ attributes: styledProps?.attributes ?? 0
4102
+ };
4103
+ const textNode = new TextNodeRenderable(textNodeOptions);
4104
+ for (const child of children) {
4105
+ textNode.add(child);
4106
+ }
4107
+ return textNode;
4108
+ }
4109
+ var vstyles = {
4110
+ bold: (...children) => StyledText2({ attributes: TextAttributes.BOLD }, ...children),
4111
+ italic: (...children) => StyledText2({ attributes: TextAttributes.ITALIC }, ...children),
4112
+ underline: (...children) => StyledText2({ attributes: TextAttributes.UNDERLINE }, ...children),
4113
+ dim: (...children) => StyledText2({ attributes: TextAttributes.DIM }, ...children),
4114
+ blink: (...children) => StyledText2({ attributes: TextAttributes.BLINK }, ...children),
4115
+ inverse: (...children) => StyledText2({ attributes: TextAttributes.INVERSE }, ...children),
4116
+ hidden: (...children) => StyledText2({ attributes: TextAttributes.HIDDEN }, ...children),
4117
+ strikethrough: (...children) => StyledText2({ attributes: TextAttributes.STRIKETHROUGH }, ...children),
4118
+ boldItalic: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC }, ...children),
4119
+ boldUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.UNDERLINE }, ...children),
4120
+ italicUnderline: (...children) => StyledText2({ attributes: TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
4121
+ boldItalicUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
4122
+ color: (color, ...children) => StyledText2({ fg: color }, ...children),
4123
+ bgColor: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
4124
+ fg: (color, ...children) => StyledText2({ fg: color }, ...children),
4125
+ bg: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
4126
+ styled: (attributes = 0, ...children) => StyledText2({ attributes }, ...children)
4127
+ };
3553
4128
  // src/renderables/composition/VRenderable.ts
3554
4129
  class VRenderable extends Renderable {
3555
4130
  options;
@@ -3567,6 +4142,8 @@ export {
3567
4142
  yellow,
3568
4143
  wrapWithDelegates,
3569
4144
  white,
4145
+ vstyles,
4146
+ visualizeRenderableTree,
3570
4147
  underline,
3571
4148
  t,
3572
4149
  stringToStyledText,
@@ -3601,6 +4178,8 @@ export {
3601
4178
  italic,
3602
4179
  isValidPercentage,
3603
4180
  isVNode,
4181
+ isTextNodeRenderable,
4182
+ isStyledText,
3604
4183
  isSizeType,
3605
4184
  isRenderable,
3606
4185
  isPositionTypeType,
@@ -3667,6 +4246,7 @@ export {
3667
4246
  TrackedNode,
3668
4247
  Timeline,
3669
4248
  TextRenderable,
4249
+ TextNodeRenderable,
3670
4250
  TextBuffer,
3671
4251
  TextAttributes,
3672
4252
  Text,
@@ -3682,6 +4262,7 @@ export {
3682
4262
  Select,
3683
4263
  ScrollBoxRenderable,
3684
4264
  ScrollBarRenderable,
4265
+ RootTextNodeRenderable,
3685
4266
  RootRenderable,
3686
4267
  RenderableEvents,
3687
4268
  Renderable,
@@ -3711,11 +4292,12 @@ export {
3711
4292
  BorderCharArrays,
3712
4293
  BlurEffect,
3713
4294
  BloomEffect,
4295
+ BaseRenderable,
3714
4296
  ArrowRenderable,
3715
4297
  ASCIIFontSelectionHelper,
3716
4298
  ASCIIFontRenderable,
3717
4299
  ASCIIFont
3718
4300
  };
3719
4301
 
3720
- //# debugId=D81404410A6C6E2564756E2164756E21
4302
+ //# debugId=E23A05456FB5EA0464756E2164756E21
3721
4303
  //# sourceMappingURL=index.js.map