@opentui/core 0.1.26 → 0.1.27

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
@@ -26,6 +26,7 @@ import {
26
26
  RGBA,
27
27
  Renderable,
28
28
  RenderableEvents,
29
+ RendererControlState,
29
30
  RootRenderable,
30
31
  Selection,
31
32
  StyledText,
@@ -132,7 +133,7 @@ import {
132
133
  white,
133
134
  wrapWithDelegates,
134
135
  yellow
135
- } from "./index-pxa2sv92.js";
136
+ } from "./index-zx1dwm33.js";
136
137
  // src/post/filters.ts
137
138
  function applyScanlines(buffer, strength = 0.8, step = 2) {
138
139
  const width = buffer.width;
@@ -1222,6 +1223,187 @@ function createTimeline(options = {}) {
1222
1223
  engine.register(timeline);
1223
1224
  return timeline;
1224
1225
  }
1226
+ // src/renderables/FrameBuffer.ts
1227
+ class FrameBufferRenderable extends Renderable {
1228
+ frameBuffer;
1229
+ respectAlpha;
1230
+ constructor(ctx, options) {
1231
+ super(ctx, options);
1232
+ this.respectAlpha = options.respectAlpha || false;
1233
+ this.frameBuffer = OptimizedBuffer.create(options.width, options.height, this._ctx.widthMethod, {
1234
+ respectAlpha: this.respectAlpha,
1235
+ id: options.id || `framebufferrenderable-${this.id}`
1236
+ });
1237
+ }
1238
+ onResize(width, height) {
1239
+ if (width <= 0 || height <= 0) {
1240
+ throw new Error(`Invalid resize dimensions for FrameBufferRenderable ${this.id}: ${width}x${height}`);
1241
+ }
1242
+ this.frameBuffer.resize(width, height);
1243
+ super.onResize(width, height);
1244
+ this.requestRender();
1245
+ }
1246
+ renderSelf(buffer) {
1247
+ if (!this.visible || this.isDestroyed)
1248
+ return;
1249
+ buffer.drawFrameBuffer(this.x, this.y, this.frameBuffer);
1250
+ }
1251
+ destroySelf() {
1252
+ this.frameBuffer?.destroy();
1253
+ super.destroySelf();
1254
+ }
1255
+ }
1256
+
1257
+ // src/renderables/ASCIIFont.ts
1258
+ class ASCIIFontRenderable extends FrameBufferRenderable {
1259
+ selectable = true;
1260
+ _text;
1261
+ _font;
1262
+ _fg;
1263
+ _bg;
1264
+ _selectionBg;
1265
+ _selectionFg;
1266
+ lastLocalSelection = null;
1267
+ selectionHelper;
1268
+ constructor(ctx, options) {
1269
+ const font = options.font || "tiny";
1270
+ const text = options.text || "";
1271
+ const measurements = measureText({ text, font });
1272
+ super(ctx, {
1273
+ flexShrink: 0,
1274
+ ...options,
1275
+ width: measurements.width || 1,
1276
+ height: measurements.height || 1,
1277
+ respectAlpha: true
1278
+ });
1279
+ this._text = text;
1280
+ this._font = font;
1281
+ this._fg = Array.isArray(options.fg) ? options.fg : [options.fg || RGBA.fromInts(255, 255, 255, 255)];
1282
+ this._bg = options.bg || RGBA.fromValues(0, 0, 0, 0);
1283
+ this._selectionBg = options.selectionBg ? parseColor(options.selectionBg) : undefined;
1284
+ this._selectionFg = options.selectionFg ? parseColor(options.selectionFg) : undefined;
1285
+ this.selectable = options.selectable ?? true;
1286
+ this.selectionHelper = new ASCIIFontSelectionHelper(() => this._text, () => this._font);
1287
+ this.renderFontToBuffer();
1288
+ }
1289
+ get text() {
1290
+ return this._text;
1291
+ }
1292
+ set text(value) {
1293
+ this._text = value;
1294
+ this.updateDimensions();
1295
+ if (this.lastLocalSelection) {
1296
+ this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
1297
+ }
1298
+ this.renderFontToBuffer();
1299
+ this.requestRender();
1300
+ }
1301
+ get font() {
1302
+ return this._font;
1303
+ }
1304
+ set font(value) {
1305
+ this._font = value;
1306
+ this.updateDimensions();
1307
+ if (this.lastLocalSelection) {
1308
+ this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
1309
+ }
1310
+ this.renderFontToBuffer();
1311
+ this.requestRender();
1312
+ }
1313
+ get fg() {
1314
+ return this._fg;
1315
+ }
1316
+ set fg(value) {
1317
+ if (Array.isArray(value)) {
1318
+ this._fg = value.map((color) => typeof color === "string" ? parseColor(color) : color);
1319
+ } else {
1320
+ this._fg = [typeof value === "string" ? parseColor(value) : value];
1321
+ }
1322
+ this.renderFontToBuffer();
1323
+ this.requestRender();
1324
+ }
1325
+ get bg() {
1326
+ return this._bg;
1327
+ }
1328
+ set bg(value) {
1329
+ this._bg = typeof value === "string" ? parseColor(value) : value;
1330
+ this.renderFontToBuffer();
1331
+ this.requestRender();
1332
+ }
1333
+ updateDimensions() {
1334
+ const measurements = measureText({ text: this._text, font: this._font });
1335
+ this.width = measurements.width;
1336
+ this.height = measurements.height;
1337
+ }
1338
+ shouldStartSelection(x, y) {
1339
+ const localX = x - this.x;
1340
+ const localY = y - this.y;
1341
+ return this.selectionHelper.shouldStartSelection(localX, localY, this.width, this.height);
1342
+ }
1343
+ onSelectionChanged(selection) {
1344
+ const localSelection = convertGlobalToLocalSelection(selection, this.x, this.y);
1345
+ this.lastLocalSelection = localSelection;
1346
+ const changed = this.selectionHelper.onLocalSelectionChanged(localSelection, this.width, this.height);
1347
+ if (changed) {
1348
+ this.renderFontToBuffer();
1349
+ this.requestRender();
1350
+ }
1351
+ return changed;
1352
+ }
1353
+ getSelectedText() {
1354
+ const selection = this.selectionHelper.getSelection();
1355
+ if (!selection)
1356
+ return "";
1357
+ return this._text.slice(selection.start, selection.end);
1358
+ }
1359
+ hasSelection() {
1360
+ return this.selectionHelper.hasSelection();
1361
+ }
1362
+ onResize(width, height) {
1363
+ super.onResize(width, height);
1364
+ this.renderFontToBuffer();
1365
+ }
1366
+ renderFontToBuffer() {
1367
+ if (this.isDestroyed)
1368
+ return;
1369
+ this.frameBuffer.clear(this._bg);
1370
+ renderFontToFrameBuffer(this.frameBuffer, {
1371
+ text: this._text,
1372
+ x: 0,
1373
+ y: 0,
1374
+ fg: this._fg,
1375
+ bg: this._bg,
1376
+ font: this._font
1377
+ });
1378
+ const selection = this.selectionHelper.getSelection();
1379
+ if (selection && (this._selectionBg || this._selectionFg)) {
1380
+ this.renderSelectionHighlight(selection);
1381
+ }
1382
+ }
1383
+ renderSelectionHighlight(selection) {
1384
+ if (!this._selectionBg && !this._selectionFg)
1385
+ return;
1386
+ const selectedText = this._text.slice(selection.start, selection.end);
1387
+ if (!selectedText)
1388
+ return;
1389
+ const positions = getCharacterPositions(this._text, this._font);
1390
+ const startX = positions[selection.start] || 0;
1391
+ const endX = selection.end < positions.length ? positions[selection.end] : measureText({ text: this._text, font: this._font }).width;
1392
+ if (this._selectionBg) {
1393
+ this.frameBuffer.fillRect(startX, 0, endX - startX, this.height, this._selectionBg);
1394
+ }
1395
+ if (this._selectionFg || this._selectionBg) {
1396
+ renderFontToFrameBuffer(this.frameBuffer, {
1397
+ text: selectedText,
1398
+ x: startX,
1399
+ y: 0,
1400
+ fg: this._selectionFg ? [this._selectionFg] : this._fg,
1401
+ bg: this._selectionBg || this._bg,
1402
+ font: this._font
1403
+ });
1404
+ }
1405
+ }
1406
+ }
1225
1407
  // src/renderables/Box.ts
1226
1408
  function isGapType(value) {
1227
1409
  if (value === undefined) {
@@ -1776,36 +1958,6 @@ class CodeRenderable extends TextBufferRenderable {
1776
1958
  return new StyledText(chunks);
1777
1959
  }
1778
1960
  }
1779
- // src/renderables/FrameBuffer.ts
1780
- class FrameBufferRenderable extends Renderable {
1781
- frameBuffer;
1782
- respectAlpha;
1783
- constructor(ctx, options) {
1784
- super(ctx, options);
1785
- this.respectAlpha = options.respectAlpha || false;
1786
- this.frameBuffer = OptimizedBuffer.create(options.width, options.height, this._ctx.widthMethod, {
1787
- respectAlpha: this.respectAlpha,
1788
- id: options.id || `framebufferrenderable-${this.id}`
1789
- });
1790
- }
1791
- onResize(width, height) {
1792
- if (width <= 0 || height <= 0) {
1793
- throw new Error(`Invalid resize dimensions for FrameBufferRenderable ${this.id}: ${width}x${height}`);
1794
- }
1795
- this.frameBuffer.resize(width, height);
1796
- super.onResize(width, height);
1797
- this.requestRender();
1798
- }
1799
- renderSelf(buffer) {
1800
- if (!this.visible || this.isDestroyed)
1801
- return;
1802
- buffer.drawFrameBuffer(this.x, this.y, this.frameBuffer);
1803
- }
1804
- destroySelf() {
1805
- this.frameBuffer?.destroy();
1806
- super.destroySelf();
1807
- }
1808
- }
1809
1961
  // src/renderables/TextNode.ts
1810
1962
  var BrandedTextNodeRenderable = Symbol.for("@opentui/core/TextNodeRenderable");
1811
1963
  function isTextNodeRenderable(obj) {
@@ -1918,15 +2070,14 @@ class TextNodeRenderable extends BaseRenderable {
1918
2070
  this.requestRender();
1919
2071
  return this;
1920
2072
  }
1921
- remove(child) {
1922
- const childIndex = this._children.indexOf(child);
2073
+ remove(id) {
2074
+ const childIndex = this.getRenderableIndex(id);
1923
2075
  if (childIndex === -1) {
1924
2076
  throw new Error("Child not found in children");
1925
2077
  }
2078
+ const child = this._children[childIndex];
1926
2079
  this._children.splice(childIndex, 1);
1927
- if (typeof child !== "string") {
1928
- child.parent = null;
1929
- }
2080
+ child.parent = null;
1930
2081
  this.requestRender();
1931
2082
  return this;
1932
2083
  }
@@ -1985,6 +2136,9 @@ class TextNodeRenderable extends BaseRenderable {
1985
2136
  getRenderable(id) {
1986
2137
  return this._children.find((child) => typeof child !== "string" && child.id === id);
1987
2138
  }
2139
+ getRenderableIndex(id) {
2140
+ return this._children.findIndex((child) => isTextNodeRenderable(child) && child.id === id);
2141
+ }
1988
2142
  get fg() {
1989
2143
  return this._fg;
1990
2144
  }
@@ -2035,2296 +2189,2148 @@ class RootTextNodeRenderable extends TextNodeRenderable {
2035
2189
  }
2036
2190
  }
2037
2191
 
2038
- // src/renderables/Text.ts
2039
- class TextRenderable extends TextBufferRenderable {
2040
- _text;
2041
- _hasManualStyledText = false;
2042
- rootTextNode;
2043
- _contentDefaultOptions = {
2044
- content: ""
2192
+ // src/renderables/composition/constructs.ts
2193
+ function Generic(props, ...children) {
2194
+ return h(VRenderable, props || {}, ...children);
2195
+ }
2196
+ function Box(props, ...children) {
2197
+ return h(BoxRenderable, props || {}, ...children);
2198
+ }
2199
+ function Text(props, ...children) {
2200
+ return h(TextRenderable, props || {}, ...children);
2201
+ }
2202
+ function ASCIIFont(props, ...children) {
2203
+ return h(ASCIIFontRenderable, props || {}, ...children);
2204
+ }
2205
+ function Input(props, ...children) {
2206
+ return h(InputRenderable, props || {}, ...children);
2207
+ }
2208
+ function Select(props, ...children) {
2209
+ return h(SelectRenderable, props || {}, ...children);
2210
+ }
2211
+ function TabSelect(props, ...children) {
2212
+ return h(TabSelectRenderable, props || {}, ...children);
2213
+ }
2214
+ function FrameBuffer(props, ...children) {
2215
+ return h(FrameBufferRenderable, props, ...children);
2216
+ }
2217
+ function StyledText2(props, ...children) {
2218
+ const styledProps = props;
2219
+ const textNodeOptions = {
2220
+ ...styledProps,
2221
+ attributes: styledProps?.attributes ?? 0
2045
2222
  };
2046
- constructor(ctx, options) {
2047
- super(ctx, options);
2048
- const content = options.content ?? this._contentDefaultOptions.content;
2049
- const styledText = typeof content === "string" ? stringToStyledText(content) : content;
2050
- this._text = styledText;
2051
- this._hasManualStyledText = options.content !== undefined && content !== "";
2052
- this.rootTextNode = new RootTextNodeRenderable(ctx, {
2053
- id: `${this.id}-root`,
2054
- fg: this._defaultFg,
2055
- bg: this._defaultBg,
2056
- attributes: this._defaultAttributes
2057
- }, this);
2058
- this.updateTextBuffer(styledText);
2059
- }
2060
- updateTextBuffer(styledText) {
2061
- this.textBuffer.setStyledText(styledText);
2062
- this.clearChunks(styledText);
2063
- }
2064
- clearChunks(styledText) {}
2065
- get content() {
2066
- return this._text;
2067
- }
2068
- get chunks() {
2069
- return this._text.chunks;
2223
+ const textNode = new TextNodeRenderable(textNodeOptions);
2224
+ for (const child of children) {
2225
+ textNode.add(child);
2070
2226
  }
2071
- get textNode() {
2072
- return this.rootTextNode;
2227
+ return textNode;
2228
+ }
2229
+ var vstyles = {
2230
+ bold: (...children) => StyledText2({ attributes: TextAttributes.BOLD }, ...children),
2231
+ italic: (...children) => StyledText2({ attributes: TextAttributes.ITALIC }, ...children),
2232
+ underline: (...children) => StyledText2({ attributes: TextAttributes.UNDERLINE }, ...children),
2233
+ dim: (...children) => StyledText2({ attributes: TextAttributes.DIM }, ...children),
2234
+ blink: (...children) => StyledText2({ attributes: TextAttributes.BLINK }, ...children),
2235
+ inverse: (...children) => StyledText2({ attributes: TextAttributes.INVERSE }, ...children),
2236
+ hidden: (...children) => StyledText2({ attributes: TextAttributes.HIDDEN }, ...children),
2237
+ strikethrough: (...children) => StyledText2({ attributes: TextAttributes.STRIKETHROUGH }, ...children),
2238
+ boldItalic: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC }, ...children),
2239
+ boldUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.UNDERLINE }, ...children),
2240
+ italicUnderline: (...children) => StyledText2({ attributes: TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
2241
+ boldItalicUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
2242
+ color: (color, ...children) => StyledText2({ fg: color }, ...children),
2243
+ bgColor: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
2244
+ fg: (color, ...children) => StyledText2({ fg: color }, ...children),
2245
+ bg: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
2246
+ styled: (attributes = 0, ...children) => StyledText2({ attributes }, ...children)
2247
+ };
2248
+ // src/renderables/composition/VRenderable.ts
2249
+ class VRenderable extends Renderable {
2250
+ options;
2251
+ constructor(ctx, options) {
2252
+ super(ctx, options);
2253
+ this.options = options;
2073
2254
  }
2074
- set content(value) {
2075
- this._hasManualStyledText = true;
2076
- const styledText = typeof value === "string" ? stringToStyledText(value) : value;
2077
- if (this._text !== styledText) {
2078
- this._text = styledText;
2079
- this.updateTextBuffer(styledText);
2080
- this.updateTextInfo();
2255
+ renderSelf(buffer, deltaTime) {
2256
+ if (this.options.render) {
2257
+ this.options.render.call(this.options, buffer, deltaTime, this);
2081
2258
  }
2082
2259
  }
2083
- insertChunk(chunk, index) {
2084
- super.insertChunk(chunk, index);
2085
- this.clearChunks(this._text);
2260
+ }
2261
+ // src/renderables/Input.ts
2262
+ var InputRenderableEvents;
2263
+ ((InputRenderableEvents2) => {
2264
+ InputRenderableEvents2["INPUT"] = "input";
2265
+ InputRenderableEvents2["CHANGE"] = "change";
2266
+ InputRenderableEvents2["ENTER"] = "enter";
2267
+ })(InputRenderableEvents ||= {});
2268
+
2269
+ class InputRenderable extends Renderable {
2270
+ _focusable = true;
2271
+ _value = "";
2272
+ _cursorPosition = 0;
2273
+ _placeholder;
2274
+ _backgroundColor;
2275
+ _textColor;
2276
+ _focusedBackgroundColor;
2277
+ _focusedTextColor;
2278
+ _placeholderColor;
2279
+ _cursorColor;
2280
+ _maxLength;
2281
+ _lastCommittedValue = "";
2282
+ _defaultOptions = {
2283
+ backgroundColor: "transparent",
2284
+ textColor: "#FFFFFF",
2285
+ focusedBackgroundColor: "#1a1a1a",
2286
+ focusedTextColor: "#FFFFFF",
2287
+ placeholder: "",
2288
+ placeholderColor: "#666666",
2289
+ cursorColor: "#FFFFFF",
2290
+ maxLength: 1000,
2291
+ value: ""
2292
+ };
2293
+ constructor(ctx, options) {
2294
+ super(ctx, { ...options, buffered: true });
2295
+ this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
2296
+ this._textColor = parseColor(options.textColor || this._defaultOptions.textColor);
2297
+ this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || options.backgroundColor || this._defaultOptions.focusedBackgroundColor);
2298
+ this._focusedTextColor = parseColor(options.focusedTextColor || options.textColor || this._defaultOptions.focusedTextColor);
2299
+ this._placeholder = options.placeholder || this._defaultOptions.placeholder;
2300
+ this._value = options.value || this._defaultOptions.value;
2301
+ this._lastCommittedValue = this._value;
2302
+ this._cursorPosition = this._value.length;
2303
+ this._maxLength = options.maxLength || this._defaultOptions.maxLength;
2304
+ this._placeholderColor = parseColor(options.placeholderColor || this._defaultOptions.placeholderColor);
2305
+ this._cursorColor = parseColor(options.cursorColor || this._defaultOptions.cursorColor);
2086
2306
  }
2087
- removeChunkByObject(chunk) {
2088
- const index = this._text.chunks.indexOf(chunk);
2089
- if (index === -1)
2307
+ updateCursorPosition() {
2308
+ if (!this._focused)
2090
2309
  return;
2091
- super.removeChunk(index);
2092
- this.clearChunks(this._text);
2310
+ const contentX = 0;
2311
+ const contentY = 0;
2312
+ const contentWidth = this.width;
2313
+ const maxVisibleChars = contentWidth - 1;
2314
+ let displayStartIndex = 0;
2315
+ if (this._cursorPosition >= maxVisibleChars) {
2316
+ displayStartIndex = this._cursorPosition - maxVisibleChars + 1;
2317
+ }
2318
+ const cursorDisplayX = this._cursorPosition - displayStartIndex;
2319
+ if (cursorDisplayX >= 0 && cursorDisplayX < contentWidth) {
2320
+ const absoluteCursorX = this.x + contentX + cursorDisplayX + 1;
2321
+ const absoluteCursorY = this.y + contentY + 1;
2322
+ this._ctx.setCursorPosition(absoluteCursorX, absoluteCursorY, true);
2323
+ this._ctx.setCursorColor(this._cursorColor);
2324
+ }
2093
2325
  }
2094
- replaceChunkByObject(chunk, oldChunk) {
2095
- const index = this._text.chunks.indexOf(oldChunk);
2096
- if (index === -1)
2097
- return;
2098
- super.replaceChunk(index, chunk);
2099
- this.clearChunks(this._text);
2326
+ focus() {
2327
+ super.focus();
2328
+ this._ctx.setCursorStyle("block", true);
2329
+ this._ctx.setCursorColor(this._cursorColor);
2330
+ this.updateCursorPosition();
2100
2331
  }
2101
- updateTextFromNodes() {
2102
- if (this.rootTextNode.isDirty && !this._hasManualStyledText) {
2103
- const chunks = this.rootTextNode.gatherWithInheritedStyle({
2104
- fg: this._defaultFg,
2105
- bg: this._defaultBg,
2106
- attributes: this._defaultAttributes
2107
- });
2108
- this.textBuffer.setStyledText(new StyledText(chunks));
2109
- this.refreshLocalSelection();
2110
- this.yogaNode.markDirty();
2332
+ blur() {
2333
+ super.blur();
2334
+ this._ctx.setCursorPosition(0, 0, false);
2335
+ if (this._value !== this._lastCommittedValue) {
2336
+ this._lastCommittedValue = this._value;
2337
+ this.emit("change" /* CHANGE */, this._value);
2111
2338
  }
2112
2339
  }
2113
- add(obj, index) {
2114
- return this.rootTextNode.add(obj, index);
2115
- }
2116
- remove(id) {
2117
- const child = this.rootTextNode.getRenderable(id);
2118
- if (child && isTextNodeRenderable(child)) {
2119
- this.rootTextNode.remove(child);
2340
+ renderSelf(buffer, deltaTime) {
2341
+ if (!this.visible || !this.frameBuffer)
2342
+ return;
2343
+ if (this.isDirty) {
2344
+ this.refreshFrameBuffer();
2120
2345
  }
2121
2346
  }
2122
- insertBefore(obj, anchor) {
2123
- this.rootTextNode.insertBefore(obj, anchor);
2124
- return this.rootTextNode.children.indexOf(obj);
2347
+ refreshFrameBuffer() {
2348
+ if (!this.frameBuffer)
2349
+ return;
2350
+ const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
2351
+ this.frameBuffer.clear(bgColor);
2352
+ const contentX = 0;
2353
+ const contentY = 0;
2354
+ const contentWidth = this.width;
2355
+ const contentHeight = this.height;
2356
+ const displayText = this._value || this._placeholder;
2357
+ const isPlaceholder = !this._value && this._placeholder;
2358
+ const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
2359
+ const textColor = isPlaceholder ? this._placeholderColor : baseTextColor;
2360
+ const maxVisibleChars = contentWidth - 1;
2361
+ let displayStartIndex = 0;
2362
+ if (this._cursorPosition >= maxVisibleChars) {
2363
+ displayStartIndex = this._cursorPosition - maxVisibleChars + 1;
2364
+ }
2365
+ const visibleText = displayText.substring(displayStartIndex, displayStartIndex + maxVisibleChars);
2366
+ if (visibleText) {
2367
+ this.frameBuffer.drawText(visibleText, contentX, contentY, textColor);
2368
+ }
2369
+ if (this._focused) {
2370
+ this.updateCursorPosition();
2371
+ }
2125
2372
  }
2126
- getTextChildren() {
2127
- return this.rootTextNode.getChildren();
2373
+ get value() {
2374
+ return this._value;
2128
2375
  }
2129
- clear() {
2130
- this.rootTextNode.clear();
2131
- const emptyStyledText = stringToStyledText("");
2132
- this._text = emptyStyledText;
2133
- this.updateTextBuffer(emptyStyledText);
2134
- this.updateTextInfo();
2135
- this.requestRender();
2376
+ set value(value) {
2377
+ const newValue = value.substring(0, this._maxLength);
2378
+ if (this._value !== newValue) {
2379
+ this._value = newValue;
2380
+ this._cursorPosition = Math.min(this._cursorPosition, this._value.length);
2381
+ this.requestRender();
2382
+ this.updateCursorPosition();
2383
+ this.emit("input" /* INPUT */, this._value);
2384
+ }
2136
2385
  }
2137
- onLifecyclePass = () => {
2138
- this.updateTextFromNodes();
2139
- };
2140
- onFgChanged(newColor) {
2141
- this.rootTextNode.fg = newColor;
2386
+ set placeholder(placeholder) {
2387
+ if (this._placeholder !== placeholder) {
2388
+ this._placeholder = placeholder;
2389
+ this.requestRender();
2390
+ }
2142
2391
  }
2143
- onBgChanged(newColor) {
2144
- this.rootTextNode.bg = newColor;
2392
+ get cursorPosition() {
2393
+ return this._cursorPosition;
2145
2394
  }
2146
- onAttributesChanged(newAttributes) {
2147
- this.rootTextNode.attributes = newAttributes;
2395
+ set cursorPosition(position) {
2396
+ const newPosition = Math.max(0, Math.min(position, this._value.length));
2397
+ if (this._cursorPosition !== newPosition) {
2398
+ this._cursorPosition = newPosition;
2399
+ this.requestRender();
2400
+ this.updateCursorPosition();
2401
+ }
2148
2402
  }
2149
- destroy() {
2150
- this.rootTextNode.children.length = 0;
2151
- super.destroy();
2403
+ insertText(text) {
2404
+ if (this._value.length + text.length > this._maxLength) {
2405
+ return;
2406
+ }
2407
+ const beforeCursor = this._value.substring(0, this._cursorPosition);
2408
+ const afterCursor = this._value.substring(this._cursorPosition);
2409
+ this._value = beforeCursor + text + afterCursor;
2410
+ this._cursorPosition += text.length;
2411
+ this.requestRender();
2412
+ this.updateCursorPosition();
2413
+ this.emit("input" /* INPUT */, this._value);
2152
2414
  }
2153
- }
2154
- // src/renderables/ASCIIFont.ts
2155
- class ASCIIFontRenderable extends FrameBufferRenderable {
2156
- selectable = true;
2157
- _text;
2158
- _font;
2159
- _fg;
2160
- _bg;
2161
- _selectionBg;
2162
- _selectionFg;
2163
- lastLocalSelection = null;
2164
- selectionHelper;
2165
- constructor(ctx, options) {
2166
- const font = options.font || "tiny";
2167
- const text = options.text || "";
2168
- const measurements = measureText({ text, font });
2169
- super(ctx, {
2170
- flexShrink: 0,
2171
- ...options,
2172
- width: measurements.width || 1,
2173
- height: measurements.height || 1,
2174
- respectAlpha: true
2175
- });
2176
- this._text = text;
2177
- this._font = font;
2178
- this._fg = Array.isArray(options.fg) ? options.fg : [options.fg || RGBA.fromInts(255, 255, 255, 255)];
2179
- this._bg = options.bg || RGBA.fromValues(0, 0, 0, 0);
2180
- this._selectionBg = options.selectionBg ? parseColor(options.selectionBg) : undefined;
2181
- this._selectionFg = options.selectionFg ? parseColor(options.selectionFg) : undefined;
2182
- this.selectable = options.selectable ?? true;
2183
- this.selectionHelper = new ASCIIFontSelectionHelper(() => this._text, () => this._font);
2184
- this.renderFontToBuffer();
2185
- }
2186
- get text() {
2187
- return this._text;
2188
- }
2189
- set text(value) {
2190
- this._text = value;
2191
- this.updateDimensions();
2192
- if (this.lastLocalSelection) {
2193
- this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
2415
+ deleteCharacter(direction) {
2416
+ if (direction === "backward" && this._cursorPosition > 0) {
2417
+ const beforeCursor = this._value.substring(0, this._cursorPosition - 1);
2418
+ const afterCursor = this._value.substring(this._cursorPosition);
2419
+ this._value = beforeCursor + afterCursor;
2420
+ this._cursorPosition--;
2421
+ this.requestRender();
2422
+ this.updateCursorPosition();
2423
+ this.emit("input" /* INPUT */, this._value);
2424
+ } else if (direction === "forward" && this._cursorPosition < this._value.length) {
2425
+ const beforeCursor = this._value.substring(0, this._cursorPosition);
2426
+ const afterCursor = this._value.substring(this._cursorPosition + 1);
2427
+ this._value = beforeCursor + afterCursor;
2428
+ this.requestRender();
2429
+ this.updateCursorPosition();
2430
+ this.emit("input" /* INPUT */, this._value);
2194
2431
  }
2195
- this.renderFontToBuffer();
2196
- this.requestRender();
2197
2432
  }
2198
- get font() {
2199
- return this._font;
2200
- }
2201
- set font(value) {
2202
- this._font = value;
2203
- this.updateDimensions();
2204
- if (this.lastLocalSelection) {
2205
- this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
2433
+ handleKeyPress(key) {
2434
+ const keyName = typeof key === "string" ? key : key.name;
2435
+ const keySequence = typeof key === "string" ? key : key.sequence;
2436
+ switch (keyName) {
2437
+ case "left":
2438
+ this.cursorPosition = this._cursorPosition - 1;
2439
+ return true;
2440
+ case "right":
2441
+ this.cursorPosition = this._cursorPosition + 1;
2442
+ return true;
2443
+ case "home":
2444
+ this.cursorPosition = 0;
2445
+ return true;
2446
+ case "end":
2447
+ this.cursorPosition = this._value.length;
2448
+ return true;
2449
+ case "backspace":
2450
+ this.deleteCharacter("backward");
2451
+ return true;
2452
+ case "delete":
2453
+ this.deleteCharacter("forward");
2454
+ return true;
2455
+ case "return":
2456
+ case "enter":
2457
+ if (this._value !== this._lastCommittedValue) {
2458
+ this._lastCommittedValue = this._value;
2459
+ this.emit("change" /* CHANGE */, this._value);
2460
+ }
2461
+ this.emit("enter" /* ENTER */, this._value);
2462
+ return true;
2463
+ default:
2464
+ if (keySequence && keySequence.length === 1 && keySequence.charCodeAt(0) >= 32 && keySequence.charCodeAt(0) <= 126) {
2465
+ this.insertText(keySequence);
2466
+ return true;
2467
+ }
2468
+ break;
2206
2469
  }
2207
- this.renderFontToBuffer();
2208
- this.requestRender();
2209
- }
2210
- get fg() {
2211
- return this._fg;
2470
+ return false;
2212
2471
  }
2213
- set fg(value) {
2214
- if (Array.isArray(value)) {
2215
- this._fg = value.map((color) => typeof color === "string" ? parseColor(color) : color);
2216
- } else {
2217
- this._fg = [typeof value === "string" ? parseColor(value) : value];
2472
+ set maxLength(maxLength) {
2473
+ this._maxLength = maxLength;
2474
+ if (this._value.length > maxLength) {
2475
+ this._value = this._value.substring(0, maxLength);
2476
+ this.requestRender();
2218
2477
  }
2219
- this.renderFontToBuffer();
2220
- this.requestRender();
2221
2478
  }
2222
- get bg() {
2223
- return this._bg;
2479
+ set backgroundColor(value) {
2480
+ const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
2481
+ if (this._backgroundColor !== newColor) {
2482
+ this._backgroundColor = newColor;
2483
+ this.requestRender();
2484
+ }
2224
2485
  }
2225
- set bg(value) {
2226
- this._bg = typeof value === "string" ? parseColor(value) : value;
2227
- this.renderFontToBuffer();
2228
- this.requestRender();
2486
+ set textColor(value) {
2487
+ const newColor = parseColor(value ?? this._defaultOptions.textColor);
2488
+ if (this._textColor !== newColor) {
2489
+ this._textColor = newColor;
2490
+ this.requestRender();
2491
+ }
2229
2492
  }
2230
- updateDimensions() {
2231
- const measurements = measureText({ text: this._text, font: this._font });
2232
- this.width = measurements.width;
2233
- this.height = measurements.height;
2493
+ set focusedBackgroundColor(value) {
2494
+ const newColor = parseColor(value ?? this._defaultOptions.focusedBackgroundColor);
2495
+ if (this._focusedBackgroundColor !== newColor) {
2496
+ this._focusedBackgroundColor = newColor;
2497
+ this.requestRender();
2498
+ }
2234
2499
  }
2235
- shouldStartSelection(x, y) {
2236
- const localX = x - this.x;
2237
- const localY = y - this.y;
2238
- return this.selectionHelper.shouldStartSelection(localX, localY, this.width, this.height);
2500
+ set focusedTextColor(value) {
2501
+ const newColor = parseColor(value ?? this._defaultOptions.focusedTextColor);
2502
+ if (this._focusedTextColor !== newColor) {
2503
+ this._focusedTextColor = newColor;
2504
+ this.requestRender();
2505
+ }
2239
2506
  }
2240
- onSelectionChanged(selection) {
2241
- const localSelection = convertGlobalToLocalSelection(selection, this.x, this.y);
2242
- this.lastLocalSelection = localSelection;
2243
- const changed = this.selectionHelper.onLocalSelectionChanged(localSelection, this.width, this.height);
2244
- if (changed) {
2245
- this.renderFontToBuffer();
2507
+ set placeholderColor(value) {
2508
+ const newColor = parseColor(value ?? this._defaultOptions.placeholderColor);
2509
+ if (this._placeholderColor !== newColor) {
2510
+ this._placeholderColor = newColor;
2246
2511
  this.requestRender();
2247
2512
  }
2248
- return changed;
2249
2513
  }
2250
- getSelectedText() {
2251
- const selection = this.selectionHelper.getSelection();
2252
- if (!selection)
2253
- return "";
2254
- return this._text.slice(selection.start, selection.end);
2514
+ set cursorColor(value) {
2515
+ const newColor = parseColor(value ?? this._defaultOptions.cursorColor);
2516
+ if (this._cursorColor !== newColor) {
2517
+ this._cursorColor = newColor;
2518
+ this.requestRender();
2519
+ }
2255
2520
  }
2256
- hasSelection() {
2257
- return this.selectionHelper.hasSelection();
2521
+ updateFromLayout() {
2522
+ super.updateFromLayout();
2523
+ this.updateCursorPosition();
2258
2524
  }
2259
2525
  onResize(width, height) {
2260
2526
  super.onResize(width, height);
2261
- this.renderFontToBuffer();
2262
- }
2263
- renderFontToBuffer() {
2264
- if (this.isDestroyed)
2265
- return;
2266
- this.frameBuffer.clear(this._bg);
2267
- renderFontToFrameBuffer(this.frameBuffer, {
2268
- text: this._text,
2269
- x: 0,
2270
- y: 0,
2271
- fg: this._fg,
2272
- bg: this._bg,
2273
- font: this._font
2274
- });
2275
- const selection = this.selectionHelper.getSelection();
2276
- if (selection && (this._selectionBg || this._selectionFg)) {
2277
- this.renderSelectionHighlight(selection);
2278
- }
2527
+ this.updateCursorPosition();
2279
2528
  }
2280
- renderSelectionHighlight(selection) {
2281
- if (!this._selectionBg && !this._selectionFg)
2282
- return;
2283
- const selectedText = this._text.slice(selection.start, selection.end);
2284
- if (!selectedText)
2285
- return;
2286
- const positions = getCharacterPositions(this._text, this._font);
2287
- const startX = positions[selection.start] || 0;
2288
- const endX = selection.end < positions.length ? positions[selection.end] : measureText({ text: this._text, font: this._font }).width;
2289
- if (this._selectionBg) {
2290
- this.frameBuffer.fillRect(startX, 0, endX - startX, this.height, this._selectionBg);
2291
- }
2292
- if (this._selectionFg || this._selectionBg) {
2293
- renderFontToFrameBuffer(this.frameBuffer, {
2294
- text: selectedText,
2295
- x: startX,
2296
- y: 0,
2297
- fg: this._selectionFg ? [this._selectionFg] : this._fg,
2298
- bg: this._selectionBg || this._bg,
2299
- font: this._font
2300
- });
2529
+ onRemove() {
2530
+ if (this._focused) {
2531
+ this._ctx.setCursorPosition(0, 0, false);
2301
2532
  }
2302
2533
  }
2303
2534
  }
2304
- // src/renderables/Input.ts
2305
- var InputRenderableEvents;
2306
- ((InputRenderableEvents2) => {
2307
- InputRenderableEvents2["INPUT"] = "input";
2308
- InputRenderableEvents2["CHANGE"] = "change";
2309
- InputRenderableEvents2["ENTER"] = "enter";
2310
- })(InputRenderableEvents ||= {});
2535
+ // src/renderables/Slider.ts
2536
+ var defaultThumbBackgroundColor = RGBA.fromHex("#9a9ea3");
2537
+ var defaultTrackBackgroundColor = RGBA.fromHex("#252527");
2311
2538
 
2312
- class InputRenderable extends Renderable {
2313
- _focusable = true;
2314
- _value = "";
2315
- _cursorPosition = 0;
2316
- _placeholder;
2539
+ class SliderRenderable extends Renderable {
2540
+ orientation;
2541
+ _value;
2542
+ _min;
2543
+ _max;
2544
+ _viewPortSize;
2317
2545
  _backgroundColor;
2318
- _textColor;
2319
- _focusedBackgroundColor;
2320
- _focusedTextColor;
2321
- _placeholderColor;
2322
- _cursorColor;
2323
- _maxLength;
2324
- _lastCommittedValue = "";
2325
- _defaultOptions = {
2326
- backgroundColor: "transparent",
2327
- textColor: "#FFFFFF",
2328
- focusedBackgroundColor: "#1a1a1a",
2329
- focusedTextColor: "#FFFFFF",
2330
- placeholder: "",
2331
- placeholderColor: "#666666",
2332
- cursorColor: "#FFFFFF",
2333
- maxLength: 1000,
2334
- value: ""
2335
- };
2546
+ _foregroundColor;
2547
+ _onChange;
2336
2548
  constructor(ctx, options) {
2337
- super(ctx, { ...options, buffered: true });
2338
- this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
2339
- this._textColor = parseColor(options.textColor || this._defaultOptions.textColor);
2340
- this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || options.backgroundColor || this._defaultOptions.focusedBackgroundColor);
2341
- this._focusedTextColor = parseColor(options.focusedTextColor || options.textColor || this._defaultOptions.focusedTextColor);
2342
- this._placeholder = options.placeholder || this._defaultOptions.placeholder;
2343
- this._value = options.value || this._defaultOptions.value;
2344
- this._lastCommittedValue = this._value;
2345
- this._cursorPosition = this._value.length;
2346
- this._maxLength = options.maxLength || this._defaultOptions.maxLength;
2347
- this._placeholderColor = parseColor(options.placeholderColor || this._defaultOptions.placeholderColor);
2348
- this._cursorColor = parseColor(options.cursorColor || this._defaultOptions.cursorColor);
2349
- }
2350
- updateCursorPosition() {
2351
- if (!this._focused)
2352
- return;
2353
- const contentX = 0;
2354
- const contentY = 0;
2355
- const contentWidth = this.width;
2356
- const maxVisibleChars = contentWidth - 1;
2357
- let displayStartIndex = 0;
2358
- if (this._cursorPosition >= maxVisibleChars) {
2359
- displayStartIndex = this._cursorPosition - maxVisibleChars + 1;
2360
- }
2361
- const cursorDisplayX = this._cursorPosition - displayStartIndex;
2362
- if (cursorDisplayX >= 0 && cursorDisplayX < contentWidth) {
2363
- const absoluteCursorX = this.x + contentX + cursorDisplayX + 1;
2364
- const absoluteCursorY = this.y + contentY + 1;
2365
- this._ctx.setCursorPosition(absoluteCursorX, absoluteCursorY, true);
2366
- this._ctx.setCursorColor(this._cursorColor);
2367
- }
2368
- }
2369
- focus() {
2370
- super.focus();
2371
- this._ctx.setCursorStyle("block", true);
2372
- this._ctx.setCursorColor(this._cursorColor);
2373
- this.updateCursorPosition();
2374
- }
2375
- blur() {
2376
- super.blur();
2377
- this._ctx.setCursorPosition(0, 0, false);
2378
- if (this._value !== this._lastCommittedValue) {
2379
- this._lastCommittedValue = this._value;
2380
- this.emit("change" /* CHANGE */, this._value);
2381
- }
2382
- }
2383
- renderSelf(buffer, deltaTime) {
2384
- if (!this.visible || !this.frameBuffer)
2385
- return;
2386
- if (this.isDirty) {
2387
- this.refreshFrameBuffer();
2388
- }
2389
- }
2390
- refreshFrameBuffer() {
2391
- if (!this.frameBuffer)
2392
- return;
2393
- const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
2394
- this.frameBuffer.clear(bgColor);
2395
- const contentX = 0;
2396
- const contentY = 0;
2397
- const contentWidth = this.width;
2398
- const contentHeight = this.height;
2399
- const displayText = this._value || this._placeholder;
2400
- const isPlaceholder = !this._value && this._placeholder;
2401
- const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
2402
- const textColor = isPlaceholder ? this._placeholderColor : baseTextColor;
2403
- const maxVisibleChars = contentWidth - 1;
2404
- let displayStartIndex = 0;
2405
- if (this._cursorPosition >= maxVisibleChars) {
2406
- displayStartIndex = this._cursorPosition - maxVisibleChars + 1;
2407
- }
2408
- const visibleText = displayText.substring(displayStartIndex, displayStartIndex + maxVisibleChars);
2409
- if (visibleText) {
2410
- this.frameBuffer.drawText(visibleText, contentX, contentY, textColor);
2411
- }
2412
- if (this._focused) {
2413
- this.updateCursorPosition();
2414
- }
2549
+ super(ctx, { flexShrink: 0, ...options });
2550
+ this.orientation = options.orientation;
2551
+ this._min = options.min ?? 0;
2552
+ this._max = options.max ?? 100;
2553
+ this._value = options.value ?? this._min;
2554
+ this._viewPortSize = options.viewPortSize ?? Math.max(1, (this._max - this._min) * 0.1);
2555
+ this._onChange = options.onChange;
2556
+ this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : defaultTrackBackgroundColor;
2557
+ this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : defaultThumbBackgroundColor;
2558
+ this.setupMouseHandling();
2415
2559
  }
2416
2560
  get value() {
2417
2561
  return this._value;
2418
2562
  }
2419
- set value(value) {
2420
- const newValue = value.substring(0, this._maxLength);
2421
- if (this._value !== newValue) {
2422
- this._value = newValue;
2423
- this._cursorPosition = Math.min(this._cursorPosition, this._value.length);
2563
+ set value(newValue) {
2564
+ const clamped = Math.max(this._min, Math.min(this._max, newValue));
2565
+ if (clamped !== this._value) {
2566
+ this._value = clamped;
2567
+ this._onChange?.(clamped);
2568
+ this.emit("change", { value: clamped });
2424
2569
  this.requestRender();
2425
- this.updateCursorPosition();
2426
- this.emit("input" /* INPUT */, this._value);
2427
2570
  }
2428
2571
  }
2429
- set placeholder(placeholder) {
2430
- if (this._placeholder !== placeholder) {
2431
- this._placeholder = placeholder;
2572
+ get min() {
2573
+ return this._min;
2574
+ }
2575
+ set min(newMin) {
2576
+ if (newMin !== this._min) {
2577
+ this._min = newMin;
2578
+ if (this._value < newMin) {
2579
+ this.value = newMin;
2580
+ }
2432
2581
  this.requestRender();
2433
2582
  }
2434
2583
  }
2435
- get cursorPosition() {
2436
- return this._cursorPosition;
2584
+ get max() {
2585
+ return this._max;
2437
2586
  }
2438
- set cursorPosition(position) {
2439
- const newPosition = Math.max(0, Math.min(position, this._value.length));
2440
- if (this._cursorPosition !== newPosition) {
2441
- this._cursorPosition = newPosition;
2587
+ set max(newMax) {
2588
+ if (newMax !== this._max) {
2589
+ this._max = newMax;
2590
+ if (this._value > newMax) {
2591
+ this.value = newMax;
2592
+ }
2442
2593
  this.requestRender();
2443
- this.updateCursorPosition();
2444
- }
2445
- }
2446
- insertText(text) {
2447
- if (this._value.length + text.length > this._maxLength) {
2448
- return;
2449
2594
  }
2450
- const beforeCursor = this._value.substring(0, this._cursorPosition);
2451
- const afterCursor = this._value.substring(this._cursorPosition);
2452
- this._value = beforeCursor + text + afterCursor;
2453
- this._cursorPosition += text.length;
2454
- this.requestRender();
2455
- this.updateCursorPosition();
2456
- this.emit("input" /* INPUT */, this._value);
2457
2595
  }
2458
- deleteCharacter(direction) {
2459
- if (direction === "backward" && this._cursorPosition > 0) {
2460
- const beforeCursor = this._value.substring(0, this._cursorPosition - 1);
2461
- const afterCursor = this._value.substring(this._cursorPosition);
2462
- this._value = beforeCursor + afterCursor;
2463
- this._cursorPosition--;
2464
- this.requestRender();
2465
- this.updateCursorPosition();
2466
- this.emit("input" /* INPUT */, this._value);
2467
- } else if (direction === "forward" && this._cursorPosition < this._value.length) {
2468
- const beforeCursor = this._value.substring(0, this._cursorPosition);
2469
- const afterCursor = this._value.substring(this._cursorPosition + 1);
2470
- this._value = beforeCursor + afterCursor;
2596
+ set viewPortSize(size) {
2597
+ const clampedSize = Math.max(0.01, Math.min(size, this._max - this._min));
2598
+ if (clampedSize !== this._viewPortSize) {
2599
+ this._viewPortSize = clampedSize;
2471
2600
  this.requestRender();
2472
- this.updateCursorPosition();
2473
- this.emit("input" /* INPUT */, this._value);
2474
2601
  }
2475
2602
  }
2476
- handleKeyPress(key) {
2477
- const keyName = typeof key === "string" ? key : key.name;
2478
- const keySequence = typeof key === "string" ? key : key.sequence;
2479
- switch (keyName) {
2480
- case "left":
2481
- this.cursorPosition = this._cursorPosition - 1;
2482
- return true;
2483
- case "right":
2484
- this.cursorPosition = this._cursorPosition + 1;
2485
- return true;
2486
- case "home":
2487
- this.cursorPosition = 0;
2488
- return true;
2489
- case "end":
2490
- this.cursorPosition = this._value.length;
2491
- return true;
2492
- case "backspace":
2493
- this.deleteCharacter("backward");
2494
- return true;
2495
- case "delete":
2496
- this.deleteCharacter("forward");
2497
- return true;
2498
- case "return":
2499
- case "enter":
2500
- if (this._value !== this._lastCommittedValue) {
2501
- this._lastCommittedValue = this._value;
2502
- this.emit("change" /* CHANGE */, this._value);
2503
- }
2504
- this.emit("enter" /* ENTER */, this._value);
2505
- return true;
2506
- default:
2507
- if (keySequence && keySequence.length === 1 && keySequence.charCodeAt(0) >= 32 && keySequence.charCodeAt(0) <= 126) {
2508
- this.insertText(keySequence);
2509
- return true;
2510
- }
2511
- break;
2512
- }
2513
- return false;
2603
+ get viewPortSize() {
2604
+ return this._viewPortSize;
2514
2605
  }
2515
- set maxLength(maxLength) {
2516
- this._maxLength = maxLength;
2517
- if (this._value.length > maxLength) {
2518
- this._value = this._value.substring(0, maxLength);
2519
- this.requestRender();
2520
- }
2606
+ get backgroundColor() {
2607
+ return this._backgroundColor;
2521
2608
  }
2522
2609
  set backgroundColor(value) {
2523
- const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
2524
- if (this._backgroundColor !== newColor) {
2525
- this._backgroundColor = newColor;
2526
- this.requestRender();
2527
- }
2610
+ this._backgroundColor = parseColor(value);
2611
+ this.requestRender();
2528
2612
  }
2529
- set textColor(value) {
2530
- const newColor = parseColor(value ?? this._defaultOptions.textColor);
2531
- if (this._textColor !== newColor) {
2532
- this._textColor = newColor;
2533
- this.requestRender();
2534
- }
2613
+ get foregroundColor() {
2614
+ return this._foregroundColor;
2535
2615
  }
2536
- set focusedBackgroundColor(value) {
2537
- const newColor = parseColor(value ?? this._defaultOptions.focusedBackgroundColor);
2538
- if (this._focusedBackgroundColor !== newColor) {
2539
- this._focusedBackgroundColor = newColor;
2540
- this.requestRender();
2541
- }
2616
+ set foregroundColor(value) {
2617
+ this._foregroundColor = parseColor(value);
2618
+ this.requestRender();
2542
2619
  }
2543
- set focusedTextColor(value) {
2544
- const newColor = parseColor(value ?? this._defaultOptions.focusedTextColor);
2545
- if (this._focusedTextColor !== newColor) {
2546
- this._focusedTextColor = newColor;
2547
- this.requestRender();
2548
- }
2620
+ calculateDragOffsetVirtual(event) {
2621
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
2622
+ const mousePos = (this.orientation === "vertical" ? event.y : event.x) - trackStart;
2623
+ const virtualMousePos = Math.max(0, Math.min((this.orientation === "vertical" ? this.height : this.width) * 2, mousePos * 2));
2624
+ const virtualThumbStart = this.getVirtualThumbStart();
2625
+ const virtualThumbSize = this.getVirtualThumbSize();
2626
+ return Math.max(0, Math.min(virtualThumbSize, virtualMousePos - virtualThumbStart));
2549
2627
  }
2550
- set placeholderColor(value) {
2551
- const newColor = parseColor(value ?? this._defaultOptions.placeholderColor);
2552
- if (this._placeholderColor !== newColor) {
2553
- this._placeholderColor = newColor;
2554
- this.requestRender();
2555
- }
2556
- }
2557
- set cursorColor(value) {
2558
- const newColor = parseColor(value ?? this._defaultOptions.cursorColor);
2559
- if (this._cursorColor !== newColor) {
2560
- this._cursorColor = newColor;
2561
- this.requestRender();
2562
- }
2628
+ setupMouseHandling() {
2629
+ let isDragging = false;
2630
+ let dragOffsetVirtual = 0;
2631
+ this.onMouseDown = (event) => {
2632
+ event.stopPropagation();
2633
+ event.preventDefault();
2634
+ const thumb = this.getThumbRect();
2635
+ const inThumb = event.x >= thumb.x && event.x < thumb.x + thumb.width && event.y >= thumb.y && event.y < thumb.y + thumb.height;
2636
+ if (inThumb) {
2637
+ isDragging = true;
2638
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
2639
+ } else {
2640
+ this.updateValueFromMouseDirect(event);
2641
+ isDragging = true;
2642
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
2643
+ }
2644
+ };
2645
+ this.onMouseDrag = (event) => {
2646
+ if (!isDragging)
2647
+ return;
2648
+ event.stopPropagation();
2649
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
2650
+ };
2651
+ this.onMouseUp = (event) => {
2652
+ if (isDragging) {
2653
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
2654
+ }
2655
+ isDragging = false;
2656
+ };
2563
2657
  }
2564
- updateFromLayout() {
2565
- super.updateFromLayout();
2566
- this.updateCursorPosition();
2658
+ updateValueFromMouseDirect(event) {
2659
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
2660
+ const trackSize = this.orientation === "vertical" ? this.height : this.width;
2661
+ const mousePos = this.orientation === "vertical" ? event.y : event.x;
2662
+ const relativeMousePos = mousePos - trackStart;
2663
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
2664
+ const ratio = trackSize === 0 ? 0 : clampedMousePos / trackSize;
2665
+ const range = this._max - this._min;
2666
+ const newValue = this._min + ratio * range;
2667
+ this.value = newValue;
2567
2668
  }
2568
- onResize(width, height) {
2569
- super.onResize(width, height);
2570
- this.updateCursorPosition();
2669
+ updateValueFromMouseWithOffset(event, offsetVirtual) {
2670
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
2671
+ const trackSize = this.orientation === "vertical" ? this.height : this.width;
2672
+ const mousePos = this.orientation === "vertical" ? event.y : event.x;
2673
+ const virtualTrackSize = trackSize * 2;
2674
+ const relativeMousePos = mousePos - trackStart;
2675
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
2676
+ const virtualMousePos = clampedMousePos * 2;
2677
+ const virtualThumbSize = this.getVirtualThumbSize();
2678
+ const maxThumbStart = Math.max(0, virtualTrackSize - virtualThumbSize);
2679
+ let desiredThumbStart = virtualMousePos - offsetVirtual;
2680
+ desiredThumbStart = Math.max(0, Math.min(maxThumbStart, desiredThumbStart));
2681
+ const ratio = maxThumbStart === 0 ? 0 : desiredThumbStart / maxThumbStart;
2682
+ const range = this._max - this._min;
2683
+ const newValue = this._min + ratio * range;
2684
+ this.value = newValue;
2571
2685
  }
2572
- onRemove() {
2573
- if (this._focused) {
2574
- this._ctx.setCursorPosition(0, 0, false);
2686
+ getThumbRect() {
2687
+ const virtualThumbSize = this.getVirtualThumbSize();
2688
+ const virtualThumbStart = this.getVirtualThumbStart();
2689
+ const realThumbStart = Math.floor(virtualThumbStart / 2);
2690
+ const realThumbSize = Math.ceil((virtualThumbStart + virtualThumbSize) / 2) - realThumbStart;
2691
+ if (this.orientation === "vertical") {
2692
+ return {
2693
+ x: this.x,
2694
+ y: this.y + realThumbStart,
2695
+ width: this.width,
2696
+ height: Math.max(1, realThumbSize)
2697
+ };
2698
+ } else {
2699
+ return {
2700
+ x: this.x + realThumbStart,
2701
+ y: this.y,
2702
+ width: Math.max(1, realThumbSize),
2703
+ height: this.height
2704
+ };
2575
2705
  }
2576
2706
  }
2577
- }
2578
- // src/renderables/Select.ts
2579
- var SelectRenderableEvents;
2580
- ((SelectRenderableEvents2) => {
2581
- SelectRenderableEvents2["SELECTION_CHANGED"] = "selectionChanged";
2582
- SelectRenderableEvents2["ITEM_SELECTED"] = "itemSelected";
2583
- })(SelectRenderableEvents ||= {});
2584
-
2585
- class SelectRenderable extends Renderable {
2586
- _focusable = true;
2587
- _options = [];
2588
- selectedIndex = 0;
2589
- scrollOffset = 0;
2590
- maxVisibleItems;
2591
- _backgroundColor;
2592
- _textColor;
2593
- _focusedBackgroundColor;
2594
- _focusedTextColor;
2595
- _selectedBackgroundColor;
2596
- _selectedTextColor;
2597
- _descriptionColor;
2598
- _selectedDescriptionColor;
2599
- _showScrollIndicator;
2600
- _wrapSelection;
2601
- _showDescription;
2602
- _font;
2603
- _itemSpacing;
2604
- linesPerItem;
2605
- fontHeight;
2606
- _fastScrollStep;
2607
- _defaultOptions = {
2608
- backgroundColor: "transparent",
2609
- textColor: "#FFFFFF",
2610
- focusedBackgroundColor: "#1a1a1a",
2611
- focusedTextColor: "#FFFFFF",
2612
- selectedBackgroundColor: "#334455",
2613
- selectedTextColor: "#FFFF00",
2614
- descriptionColor: "#888888",
2615
- selectedDescriptionColor: "#CCCCCC",
2616
- showScrollIndicator: false,
2617
- wrapSelection: false,
2618
- showDescription: true,
2619
- itemSpacing: 0,
2620
- fastScrollStep: 5
2621
- };
2622
- constructor(ctx, options) {
2623
- super(ctx, { ...options, buffered: true });
2624
- this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
2625
- this._textColor = parseColor(options.textColor || this._defaultOptions.textColor);
2626
- this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || this._defaultOptions.focusedBackgroundColor);
2627
- this._focusedTextColor = parseColor(options.focusedTextColor || this._defaultOptions.focusedTextColor);
2628
- this._options = options.options || [];
2629
- this._showScrollIndicator = options.showScrollIndicator ?? this._defaultOptions.showScrollIndicator;
2630
- this._wrapSelection = options.wrapSelection ?? this._defaultOptions.wrapSelection;
2631
- this._showDescription = options.showDescription ?? this._defaultOptions.showDescription;
2632
- this._font = options.font;
2633
- this._itemSpacing = options.itemSpacing || this._defaultOptions.itemSpacing;
2634
- this.fontHeight = this._font ? measureText({ text: "A", font: this._font }).height : 1;
2635
- this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
2636
- this.linesPerItem += this._itemSpacing;
2637
- this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
2638
- this._selectedBackgroundColor = parseColor(options.selectedBackgroundColor || this._defaultOptions.selectedBackgroundColor);
2639
- this._selectedTextColor = parseColor(options.selectedTextColor || this._defaultOptions.selectedTextColor);
2640
- this._descriptionColor = parseColor(options.descriptionColor || this._defaultOptions.descriptionColor);
2641
- this._selectedDescriptionColor = parseColor(options.selectedDescriptionColor || this._defaultOptions.selectedDescriptionColor);
2642
- this._fastScrollStep = options.fastScrollStep || this._defaultOptions.fastScrollStep;
2643
- this.requestRender();
2644
- }
2645
- renderSelf(buffer, deltaTime) {
2646
- if (!this.visible || !this.frameBuffer)
2647
- return;
2648
- if (this.isDirty) {
2649
- this.refreshFrameBuffer();
2707
+ renderSelf(buffer) {
2708
+ if (this.orientation === "horizontal") {
2709
+ this.renderHorizontal(buffer);
2710
+ } else {
2711
+ this.renderVertical(buffer);
2650
2712
  }
2651
2713
  }
2652
- refreshFrameBuffer() {
2653
- if (!this.frameBuffer || this._options.length === 0)
2654
- return;
2655
- const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
2656
- this.frameBuffer.clear(bgColor);
2657
- const contentX = 0;
2658
- const contentY = 0;
2659
- const contentWidth = this.width;
2660
- const contentHeight = this.height;
2661
- const visibleOptions = this._options.slice(this.scrollOffset, this.scrollOffset + this.maxVisibleItems);
2662
- for (let i = 0;i < visibleOptions.length; i++) {
2663
- const actualIndex = this.scrollOffset + i;
2664
- const option = visibleOptions[i];
2665
- const isSelected = actualIndex === this.selectedIndex;
2666
- const itemY = contentY + i * this.linesPerItem;
2667
- if (itemY + this.linesPerItem - 1 >= contentY + contentHeight)
2668
- break;
2669
- if (isSelected) {
2670
- const contentHeight2 = this.linesPerItem - this._itemSpacing;
2671
- this.frameBuffer.fillRect(contentX, itemY, contentWidth, contentHeight2, this._selectedBackgroundColor);
2672
- }
2673
- const nameContent = `${isSelected ? "\u25B6 " : " "}${option.name}`;
2674
- const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
2675
- const nameColor = isSelected ? this._selectedTextColor : baseTextColor;
2676
- let descX = contentX + 3;
2677
- if (this._font) {
2678
- const indicator = isSelected ? "\u25B6 " : " ";
2679
- this.frameBuffer.drawText(indicator, contentX + 1, itemY, nameColor);
2680
- const indicatorWidth = 2;
2681
- renderFontToFrameBuffer(this.frameBuffer, {
2682
- text: option.name,
2683
- x: contentX + 1 + indicatorWidth,
2684
- y: itemY,
2685
- fg: nameColor,
2686
- bg: isSelected ? this._selectedBackgroundColor : bgColor,
2687
- font: this._font
2688
- });
2689
- descX = contentX + 1 + indicatorWidth;
2714
+ renderHorizontal(buffer) {
2715
+ const virtualThumbSize = this.getVirtualThumbSize();
2716
+ const virtualThumbStart = this.getVirtualThumbStart();
2717
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
2718
+ buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
2719
+ const realStartCell = Math.floor(virtualThumbStart / 2);
2720
+ const realEndCell = Math.ceil(virtualThumbEnd / 2) - 1;
2721
+ const startX = Math.max(0, realStartCell);
2722
+ const endX = Math.min(this.width - 1, realEndCell);
2723
+ for (let realX = startX;realX <= endX; realX++) {
2724
+ const virtualCellStart = realX * 2;
2725
+ const virtualCellEnd = virtualCellStart + 2;
2726
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
2727
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
2728
+ const coverage = thumbEndInCell - thumbStartInCell;
2729
+ let char = " ";
2730
+ if (coverage >= 2) {
2731
+ char = "\u2588";
2690
2732
  } else {
2691
- this.frameBuffer.drawText(nameContent, contentX + 1, itemY, nameColor);
2733
+ const isLeftHalf = thumbStartInCell === virtualCellStart;
2734
+ if (isLeftHalf) {
2735
+ char = "\u258C";
2736
+ } else {
2737
+ char = "\u2590";
2738
+ }
2692
2739
  }
2693
- if (this._showDescription && itemY + this.fontHeight < contentY + contentHeight) {
2694
- const descColor = isSelected ? this._selectedDescriptionColor : this._descriptionColor;
2695
- const descBg = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
2696
- this.frameBuffer.drawText(option.description, descX, itemY + this.fontHeight, descColor);
2740
+ for (let y = 0;y < this.height; y++) {
2741
+ buffer.setCellWithAlphaBlending(this.x + realX, this.y + y, char, this._foregroundColor, this._backgroundColor);
2697
2742
  }
2698
2743
  }
2699
- if (this._showScrollIndicator && this._options.length > this.maxVisibleItems) {
2700
- this.renderScrollIndicatorToFrameBuffer(contentX, contentY, contentWidth, contentHeight);
2701
- }
2702
- }
2703
- renderScrollIndicatorToFrameBuffer(contentX, contentY, contentWidth, contentHeight) {
2704
- if (!this.frameBuffer)
2705
- return;
2706
- const scrollPercent = this.selectedIndex / Math.max(1, this._options.length - 1);
2707
- const indicatorHeight = Math.max(1, contentHeight - 2);
2708
- const indicatorY = contentY + 1 + Math.floor(scrollPercent * indicatorHeight);
2709
- const indicatorX = contentX + contentWidth - 1;
2710
- this.frameBuffer.drawText("\u2588", indicatorX, indicatorY, parseColor("#666666"));
2711
2744
  }
2712
- get options() {
2713
- return this._options;
2714
- }
2715
- set options(options) {
2716
- this._options = options;
2717
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
2718
- this.updateScrollOffset();
2719
- this.requestRender();
2720
- }
2721
- getSelectedOption() {
2722
- return this._options[this.selectedIndex] || null;
2723
- }
2724
- getSelectedIndex() {
2725
- return this.selectedIndex;
2726
- }
2727
- moveUp(steps = 1) {
2728
- const newIndex = this.selectedIndex - steps;
2729
- if (newIndex >= 0) {
2730
- this.selectedIndex = newIndex;
2731
- } else if (this._wrapSelection && this._options.length > 0) {
2732
- this.selectedIndex = this._options.length - 1;
2733
- } else {
2734
- this.selectedIndex = 0;
2735
- }
2736
- this.updateScrollOffset();
2737
- this.requestRender();
2738
- this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
2739
- }
2740
- moveDown(steps = 1) {
2741
- const newIndex = this.selectedIndex + steps;
2742
- if (newIndex < this._options.length) {
2743
- this.selectedIndex = newIndex;
2744
- } else if (this._wrapSelection && this._options.length > 0) {
2745
- this.selectedIndex = 0;
2746
- } else {
2747
- this.selectedIndex = this._options.length - 1;
2748
- }
2749
- this.updateScrollOffset();
2750
- this.requestRender();
2751
- this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
2752
- }
2753
- selectCurrent() {
2754
- const selected = this.getSelectedOption();
2755
- if (selected) {
2756
- this.emit("itemSelected" /* ITEM_SELECTED */, this.selectedIndex, selected);
2757
- }
2758
- }
2759
- setSelectedIndex(index) {
2760
- if (index >= 0 && index < this._options.length) {
2761
- this.selectedIndex = index;
2762
- this.updateScrollOffset();
2763
- this.requestRender();
2764
- this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
2765
- }
2766
- }
2767
- updateScrollOffset() {
2768
- if (!this._options)
2769
- return;
2770
- const halfVisible = Math.floor(this.maxVisibleItems / 2);
2771
- const newScrollOffset = Math.max(0, Math.min(this.selectedIndex - halfVisible, this._options.length - this.maxVisibleItems));
2772
- if (newScrollOffset !== this.scrollOffset) {
2773
- this.scrollOffset = newScrollOffset;
2774
- this.requestRender();
2775
- }
2776
- }
2777
- onResize(width, height) {
2778
- this.maxVisibleItems = Math.max(1, Math.floor(height / this.linesPerItem));
2779
- this.updateScrollOffset();
2780
- this.requestRender();
2781
- }
2782
- handleKeyPress(key) {
2783
- const keyName = typeof key === "string" ? key : key.name;
2784
- const isShift = typeof key !== "string" && key.shift;
2785
- switch (keyName) {
2786
- case "up":
2787
- case "k":
2788
- this.moveUp(isShift ? this._fastScrollStep : 1);
2789
- return true;
2790
- case "down":
2791
- case "j":
2792
- this.moveDown(isShift ? this._fastScrollStep : 1);
2793
- return true;
2794
- case "return":
2795
- case "enter":
2796
- this.selectCurrent();
2797
- return true;
2745
+ renderVertical(buffer) {
2746
+ const virtualThumbSize = this.getVirtualThumbSize();
2747
+ const virtualThumbStart = this.getVirtualThumbStart();
2748
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
2749
+ buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
2750
+ const realStartCell = Math.floor(virtualThumbStart / 2);
2751
+ const realEndCell = Math.ceil(virtualThumbEnd / 2) - 1;
2752
+ const startY = Math.max(0, realStartCell);
2753
+ const endY = Math.min(this.height - 1, realEndCell);
2754
+ for (let realY = startY;realY <= endY; realY++) {
2755
+ const virtualCellStart = realY * 2;
2756
+ const virtualCellEnd = virtualCellStart + 2;
2757
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
2758
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
2759
+ const coverage = thumbEndInCell - thumbStartInCell;
2760
+ let char = " ";
2761
+ if (coverage >= 2) {
2762
+ char = "\u2588";
2763
+ } else if (coverage > 0) {
2764
+ const virtualPositionInCell = thumbStartInCell - virtualCellStart;
2765
+ if (virtualPositionInCell === 0) {
2766
+ char = "\u2580";
2767
+ } else {
2768
+ char = "\u2584";
2769
+ }
2770
+ }
2771
+ for (let x = 0;x < this.width; x++) {
2772
+ buffer.setCellWithAlphaBlending(this.x + x, this.y + realY, char, this._foregroundColor, this._backgroundColor);
2773
+ }
2798
2774
  }
2799
- return false;
2800
2775
  }
2801
- get showScrollIndicator() {
2802
- return this._showScrollIndicator;
2776
+ getVirtualThumbSize() {
2777
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
2778
+ const range = this._max - this._min;
2779
+ if (range === 0)
2780
+ return virtualTrackSize;
2781
+ const viewportSize = Math.max(1, this._viewPortSize);
2782
+ const contentSize = range + viewportSize;
2783
+ if (contentSize <= viewportSize)
2784
+ return virtualTrackSize;
2785
+ const thumbRatio = viewportSize / contentSize;
2786
+ const calculatedSize = Math.floor(virtualTrackSize * thumbRatio);
2787
+ return Math.max(1, Math.min(calculatedSize, virtualTrackSize));
2803
2788
  }
2804
- set showScrollIndicator(show) {
2805
- this._showScrollIndicator = show;
2806
- this.requestRender();
2789
+ getVirtualThumbStart() {
2790
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
2791
+ const range = this._max - this._min;
2792
+ if (range === 0)
2793
+ return 0;
2794
+ const valueRatio = (this._value - this._min) / range;
2795
+ const virtualThumbSize = this.getVirtualThumbSize();
2796
+ return Math.round(valueRatio * (virtualTrackSize - virtualThumbSize));
2807
2797
  }
2808
- get showDescription() {
2809
- return this._showDescription;
2798
+ }
2799
+
2800
+ // src/renderables/ScrollBar.ts
2801
+ class ScrollBarRenderable extends Renderable {
2802
+ slider;
2803
+ startArrow;
2804
+ endArrow;
2805
+ orientation;
2806
+ _focusable = true;
2807
+ _scrollSize = 0;
2808
+ _scrollPosition = 0;
2809
+ _viewportSize = 0;
2810
+ _showArrows = false;
2811
+ _manualVisibility = false;
2812
+ _onChange;
2813
+ scrollStep = null;
2814
+ get visible() {
2815
+ return super.visible;
2810
2816
  }
2811
- set showDescription(show) {
2812
- if (this._showDescription !== show) {
2813
- this._showDescription = show;
2814
- this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
2815
- this.linesPerItem += this._itemSpacing;
2816
- this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
2817
- this.updateScrollOffset();
2818
- this.requestRender();
2819
- }
2817
+ set visible(value) {
2818
+ this._manualVisibility = true;
2819
+ super.visible = value;
2820
2820
  }
2821
- get wrapSelection() {
2822
- return this._wrapSelection;
2821
+ resetVisibilityControl() {
2822
+ this._manualVisibility = false;
2823
+ this.recalculateVisibility();
2823
2824
  }
2824
- set wrapSelection(wrap) {
2825
- this._wrapSelection = wrap;
2825
+ get scrollSize() {
2826
+ return this._scrollSize;
2826
2827
  }
2827
- set backgroundColor(value) {
2828
- const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
2829
- if (this._backgroundColor !== newColor) {
2830
- this._backgroundColor = newColor;
2831
- this.requestRender();
2832
- }
2828
+ get scrollPosition() {
2829
+ return this._scrollPosition;
2833
2830
  }
2834
- set textColor(value) {
2835
- const newColor = parseColor(value ?? this._defaultOptions.textColor);
2836
- if (this._textColor !== newColor) {
2837
- this._textColor = newColor;
2838
- this.requestRender();
2839
- }
2831
+ get viewportSize() {
2832
+ return this._viewportSize;
2840
2833
  }
2841
- set focusedBackgroundColor(value) {
2842
- const newColor = parseColor(value ?? this._defaultOptions.focusedBackgroundColor);
2843
- if (this._focusedBackgroundColor !== newColor) {
2844
- this._focusedBackgroundColor = newColor;
2845
- this.requestRender();
2846
- }
2834
+ set scrollSize(value) {
2835
+ if (value === this.scrollSize)
2836
+ return;
2837
+ this._scrollSize = value;
2838
+ this.recalculateVisibility();
2839
+ this.updateSliderFromScrollState();
2840
+ this.scrollPosition = this.scrollPosition;
2847
2841
  }
2848
- set focusedTextColor(value) {
2849
- const newColor = parseColor(value ?? this._defaultOptions.focusedTextColor);
2850
- if (this._focusedTextColor !== newColor) {
2851
- this._focusedTextColor = newColor;
2852
- this.requestRender();
2842
+ set scrollPosition(value) {
2843
+ const newPosition = Math.round(Math.min(Math.max(0, value), this.scrollSize - this.viewportSize));
2844
+ if (newPosition !== this._scrollPosition) {
2845
+ this._scrollPosition = newPosition;
2846
+ this.updateSliderFromScrollState();
2853
2847
  }
2854
2848
  }
2855
- set selectedBackgroundColor(value) {
2856
- const newColor = parseColor(value ?? this._defaultOptions.selectedBackgroundColor);
2857
- if (this._selectedBackgroundColor !== newColor) {
2858
- this._selectedBackgroundColor = newColor;
2859
- this.requestRender();
2860
- }
2849
+ set viewportSize(value) {
2850
+ if (value === this.viewportSize)
2851
+ return;
2852
+ this._viewportSize = value;
2853
+ this.slider.viewPortSize = Math.max(1, this._viewportSize);
2854
+ this.recalculateVisibility();
2855
+ this.updateSliderFromScrollState();
2856
+ this.scrollPosition = this.scrollPosition;
2861
2857
  }
2862
- set selectedTextColor(value) {
2863
- const newColor = parseColor(value ?? this._defaultOptions.selectedTextColor);
2864
- if (this._selectedTextColor !== newColor) {
2865
- this._selectedTextColor = newColor;
2866
- this.requestRender();
2867
- }
2858
+ get showArrows() {
2859
+ return this._showArrows;
2868
2860
  }
2869
- set descriptionColor(value) {
2870
- const newColor = parseColor(value ?? this._defaultOptions.descriptionColor);
2871
- if (this._descriptionColor !== newColor) {
2872
- this._descriptionColor = newColor;
2873
- this.requestRender();
2874
- }
2861
+ set showArrows(value) {
2862
+ if (value === this._showArrows)
2863
+ return;
2864
+ this._showArrows = value;
2865
+ this.startArrow.visible = value;
2866
+ this.endArrow.visible = value;
2875
2867
  }
2876
- set selectedDescriptionColor(value) {
2877
- const newColor = parseColor(value ?? this._defaultOptions.selectedDescriptionColor);
2878
- if (this._selectedDescriptionColor !== newColor) {
2879
- this._selectedDescriptionColor = newColor;
2880
- this.requestRender();
2881
- }
2868
+ constructor(ctx, { trackOptions, arrowOptions, orientation, showArrows = false, ...options }) {
2869
+ super(ctx, {
2870
+ flexDirection: orientation === "vertical" ? "column" : "row",
2871
+ alignSelf: "stretch",
2872
+ alignItems: "stretch",
2873
+ ...options
2874
+ });
2875
+ this._onChange = options.onChange;
2876
+ this.orientation = orientation;
2877
+ this._showArrows = showArrows;
2878
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
2879
+ const defaultStepSize = Math.max(1, this._viewportSize);
2880
+ const stepSize = trackOptions?.viewPortSize ?? defaultStepSize;
2881
+ this.slider = new SliderRenderable(ctx, {
2882
+ orientation,
2883
+ min: 0,
2884
+ max: scrollRange,
2885
+ value: this._scrollPosition,
2886
+ viewPortSize: stepSize,
2887
+ onChange: (value) => {
2888
+ this._scrollPosition = Math.round(value);
2889
+ this._onChange?.(this._scrollPosition);
2890
+ this.emit("change", { position: this._scrollPosition });
2891
+ },
2892
+ ...orientation === "vertical" ? {
2893
+ width: Math.max(1, Math.min(2, this.width)),
2894
+ height: "100%",
2895
+ marginLeft: "auto"
2896
+ } : {
2897
+ width: "100%",
2898
+ height: 1,
2899
+ marginTop: "auto"
2900
+ },
2901
+ flexGrow: 1,
2902
+ flexShrink: 1,
2903
+ ...trackOptions
2904
+ });
2905
+ this.updateSliderFromScrollState();
2906
+ const arrowOpts = arrowOptions ? {
2907
+ foregroundColor: arrowOptions.backgroundColor,
2908
+ backgroundColor: arrowOptions.backgroundColor,
2909
+ attributes: arrowOptions.attributes,
2910
+ ...arrowOptions
2911
+ } : {};
2912
+ this.startArrow = new ArrowRenderable(ctx, {
2913
+ alignSelf: "center",
2914
+ visible: this.showArrows,
2915
+ direction: this.orientation === "vertical" ? "up" : "left",
2916
+ height: this.orientation === "vertical" ? 1 : 1,
2917
+ ...arrowOpts
2918
+ });
2919
+ this.endArrow = new ArrowRenderable(ctx, {
2920
+ alignSelf: "center",
2921
+ visible: this.showArrows,
2922
+ direction: this.orientation === "vertical" ? "down" : "right",
2923
+ height: this.orientation === "vertical" ? 1 : 1,
2924
+ ...arrowOpts
2925
+ });
2926
+ this.add(this.startArrow);
2927
+ this.add(this.slider);
2928
+ this.add(this.endArrow);
2929
+ let startArrowMouseTimeout = undefined;
2930
+ let endArrowMouseTimeout = undefined;
2931
+ this.startArrow.onMouseDown = (event) => {
2932
+ event.stopPropagation();
2933
+ event.preventDefault();
2934
+ this.scrollBy(-0.5, "viewport");
2935
+ startArrowMouseTimeout = setTimeout(() => {
2936
+ this.scrollBy(-0.5, "viewport");
2937
+ startArrowMouseTimeout = setInterval(() => {
2938
+ this.scrollBy(-0.2, "viewport");
2939
+ }, 200);
2940
+ }, 500);
2941
+ };
2942
+ this.startArrow.onMouseUp = (event) => {
2943
+ event.stopPropagation();
2944
+ clearInterval(startArrowMouseTimeout);
2945
+ };
2946
+ this.endArrow.onMouseDown = (event) => {
2947
+ event.stopPropagation();
2948
+ event.preventDefault();
2949
+ this.scrollBy(0.5, "viewport");
2950
+ endArrowMouseTimeout = setTimeout(() => {
2951
+ this.scrollBy(0.5, "viewport");
2952
+ endArrowMouseTimeout = setInterval(() => {
2953
+ this.scrollBy(0.2, "viewport");
2954
+ }, 200);
2955
+ }, 500);
2956
+ };
2957
+ this.endArrow.onMouseUp = (event) => {
2958
+ event.stopPropagation();
2959
+ clearInterval(endArrowMouseTimeout);
2960
+ };
2882
2961
  }
2883
- set font(font) {
2884
- this._font = font;
2885
- this.fontHeight = measureText({ text: "A", font: this._font }).height;
2886
- this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
2887
- this.linesPerItem += this._itemSpacing;
2888
- this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
2889
- this.updateScrollOffset();
2962
+ set arrowOptions(options) {
2963
+ Object.assign(this.startArrow, options);
2964
+ Object.assign(this.endArrow, options);
2890
2965
  this.requestRender();
2891
2966
  }
2892
- set itemSpacing(spacing) {
2893
- this._itemSpacing = spacing;
2894
- this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
2895
- this.linesPerItem += this._itemSpacing;
2896
- this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
2897
- this.updateScrollOffset();
2967
+ set trackOptions(options) {
2968
+ Object.assign(this.slider, options);
2898
2969
  this.requestRender();
2899
2970
  }
2900
- set fastScrollStep(step) {
2901
- this._fastScrollStep = step;
2971
+ updateSliderFromScrollState() {
2972
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
2973
+ this.slider.min = 0;
2974
+ this.slider.max = scrollRange;
2975
+ this.slider.value = Math.min(this._scrollPosition, scrollRange);
2902
2976
  }
2903
- }
2904
- // src/renderables/TabSelect.ts
2905
- var TabSelectRenderableEvents;
2906
- ((TabSelectRenderableEvents2) => {
2907
- TabSelectRenderableEvents2["SELECTION_CHANGED"] = "selectionChanged";
2908
- TabSelectRenderableEvents2["ITEM_SELECTED"] = "itemSelected";
2909
- })(TabSelectRenderableEvents ||= {});
2910
- function calculateDynamicHeight(showUnderline, showDescription) {
2911
- let height = 1;
2912
- if (showUnderline) {
2913
- height += 1;
2977
+ scrollBy(delta, unit = "absolute") {
2978
+ const multiplier = unit === "viewport" ? this.viewportSize : unit === "content" ? this.scrollSize : unit === "step" ? this.scrollStep ?? 1 : 1;
2979
+ const resolvedDelta = multiplier * delta;
2980
+ this.scrollPosition += resolvedDelta;
2914
2981
  }
2915
- if (showDescription) {
2916
- height += 1;
2982
+ recalculateVisibility() {
2983
+ if (!this._manualVisibility) {
2984
+ const sizeRatio = this.scrollSize <= this.viewportSize ? 1 : this.viewportSize / this.scrollSize;
2985
+ super.visible = sizeRatio < 1;
2986
+ }
2987
+ }
2988
+ handleKeyPress(key) {
2989
+ const keyName = typeof key === "string" ? key : key.name;
2990
+ switch (keyName) {
2991
+ case "left":
2992
+ case "h":
2993
+ if (this.orientation !== "horizontal")
2994
+ return false;
2995
+ this.scrollBy(-1 / 5, "viewport");
2996
+ return true;
2997
+ case "right":
2998
+ case "l":
2999
+ if (this.orientation !== "horizontal")
3000
+ return false;
3001
+ this.scrollBy(1 / 5, "viewport");
3002
+ return true;
3003
+ case "up":
3004
+ case "k":
3005
+ if (this.orientation !== "vertical")
3006
+ return false;
3007
+ this.scrollBy(-1 / 5, "viewport");
3008
+ return true;
3009
+ case "down":
3010
+ case "j":
3011
+ if (this.orientation !== "vertical")
3012
+ return false;
3013
+ this.scrollBy(1 / 5, "viewport");
3014
+ return true;
3015
+ case "pageup":
3016
+ this.scrollBy(-1 / 2, "viewport");
3017
+ return true;
3018
+ case "pagedown":
3019
+ this.scrollBy(1 / 2, "viewport");
3020
+ return true;
3021
+ case "home":
3022
+ this.scrollBy(-1, "content");
3023
+ return true;
3024
+ case "end":
3025
+ this.scrollBy(1, "content");
3026
+ return true;
3027
+ }
3028
+ return false;
2917
3029
  }
2918
- return height;
2919
3030
  }
2920
3031
 
2921
- class TabSelectRenderable extends Renderable {
2922
- _focusable = true;
2923
- _options = [];
2924
- selectedIndex = 0;
2925
- scrollOffset = 0;
2926
- _tabWidth;
2927
- maxVisibleTabs;
3032
+ class ArrowRenderable extends Renderable {
3033
+ _direction;
3034
+ _foregroundColor;
2928
3035
  _backgroundColor;
2929
- _textColor;
2930
- _focusedBackgroundColor;
2931
- _focusedTextColor;
2932
- _selectedBackgroundColor;
2933
- _selectedTextColor;
2934
- _selectedDescriptionColor;
2935
- _showScrollArrows;
2936
- _showDescription;
2937
- _showUnderline;
2938
- _wrapSelection;
3036
+ _attributes;
3037
+ _arrowChars;
2939
3038
  constructor(ctx, options) {
2940
- const calculatedHeight = calculateDynamicHeight(options.showUnderline ?? true, options.showDescription ?? true);
2941
- super(ctx, { ...options, height: calculatedHeight, buffered: true });
2942
- this._backgroundColor = parseColor(options.backgroundColor || "transparent");
2943
- this._textColor = parseColor(options.textColor || "#FFFFFF");
2944
- this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || options.backgroundColor || "#1a1a1a");
2945
- this._focusedTextColor = parseColor(options.focusedTextColor || options.textColor || "#FFFFFF");
2946
- this._options = options.options || [];
2947
- this._tabWidth = options.tabWidth || 20;
2948
- this._showDescription = options.showDescription ?? true;
2949
- this._showUnderline = options.showUnderline ?? true;
2950
- this._showScrollArrows = options.showScrollArrows ?? true;
2951
- this._wrapSelection = options.wrapSelection ?? false;
2952
- this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
2953
- this._selectedBackgroundColor = parseColor(options.selectedBackgroundColor || "#334455");
2954
- this._selectedTextColor = parseColor(options.selectedTextColor || "#FFFF00");
2955
- this._selectedDescriptionColor = parseColor(options.selectedDescriptionColor || "#CCCCCC");
3039
+ super(ctx, options);
3040
+ this._direction = options.direction;
3041
+ this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : RGBA.fromValues(1, 1, 1, 1);
3042
+ this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : RGBA.fromValues(0, 0, 0, 0);
3043
+ this._attributes = options.attributes ?? 0;
3044
+ this._arrowChars = {
3045
+ up: "\u25B2",
3046
+ down: "\u25BC",
3047
+ left: "\u25C0",
3048
+ right: "\u25B6",
3049
+ ...options.arrowChars
3050
+ };
3051
+ if (!options.width) {
3052
+ this.width = Bun.stringWidth(this.getArrowChar());
3053
+ }
2956
3054
  }
2957
- calculateDynamicHeight() {
2958
- return calculateDynamicHeight(this._showUnderline, this._showDescription);
3055
+ get direction() {
3056
+ return this._direction;
2959
3057
  }
2960
- renderSelf(buffer, deltaTime) {
2961
- if (!this.visible || !this.frameBuffer)
2962
- return;
2963
- if (this.isDirty) {
2964
- this.refreshFrameBuffer();
3058
+ set direction(value) {
3059
+ if (this._direction !== value) {
3060
+ this._direction = value;
3061
+ this.requestRender();
2965
3062
  }
2966
3063
  }
2967
- refreshFrameBuffer() {
2968
- if (!this.frameBuffer || this._options.length === 0)
2969
- return;
2970
- const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
2971
- this.frameBuffer.clear(bgColor);
2972
- const contentX = 0;
2973
- const contentY = 0;
2974
- const contentWidth = this.width;
2975
- const contentHeight = this.height;
2976
- const visibleOptions = this._options.slice(this.scrollOffset, this.scrollOffset + this.maxVisibleTabs);
2977
- for (let i = 0;i < visibleOptions.length; i++) {
2978
- const actualIndex = this.scrollOffset + i;
2979
- const option = visibleOptions[i];
2980
- const isSelected = actualIndex === this.selectedIndex;
2981
- const tabX = contentX + i * this._tabWidth;
2982
- if (tabX >= contentX + contentWidth)
2983
- break;
2984
- const actualTabWidth = Math.min(this._tabWidth, contentWidth - i * this._tabWidth);
2985
- if (isSelected) {
2986
- this.frameBuffer.fillRect(tabX, contentY, actualTabWidth, 1, this._selectedBackgroundColor);
2987
- }
2988
- const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
2989
- const nameColor = isSelected ? this._selectedTextColor : baseTextColor;
2990
- const nameContent = this.truncateText(option.name, actualTabWidth - 2);
2991
- this.frameBuffer.drawText(nameContent, tabX + 1, contentY, nameColor);
2992
- if (isSelected && this._showUnderline && contentHeight >= 2) {
2993
- const underlineY = contentY + 1;
2994
- const underlineBg = isSelected ? this._selectedBackgroundColor : bgColor;
2995
- this.frameBuffer.drawText("\u25AC".repeat(actualTabWidth), tabX, underlineY, nameColor, underlineBg);
2996
- }
2997
- }
2998
- if (this._showDescription && contentHeight >= (this._showUnderline ? 3 : 2)) {
2999
- const selectedOption = this.getSelectedOption();
3000
- if (selectedOption) {
3001
- const descriptionY = contentY + (this._showUnderline ? 2 : 1);
3002
- const descColor = this._selectedDescriptionColor;
3003
- const descContent = this.truncateText(selectedOption.description, contentWidth - 2);
3004
- this.frameBuffer.drawText(descContent, contentX + 1, descriptionY, descColor);
3005
- }
3006
- }
3007
- if (this._showScrollArrows && this._options.length > this.maxVisibleTabs) {
3008
- this.renderScrollArrowsToFrameBuffer(contentX, contentY, contentWidth, contentHeight);
3009
- }
3010
- }
3011
- truncateText(text, maxWidth) {
3012
- if (text.length <= maxWidth)
3013
- return text;
3014
- return text.substring(0, Math.max(0, maxWidth - 1)) + "\u2026";
3015
- }
3016
- renderScrollArrowsToFrameBuffer(contentX, contentY, contentWidth, contentHeight) {
3017
- if (!this.frameBuffer)
3018
- return;
3019
- const hasMoreLeft = this.scrollOffset > 0;
3020
- const hasMoreRight = this.scrollOffset + this.maxVisibleTabs < this._options.length;
3021
- if (hasMoreLeft) {
3022
- this.frameBuffer.drawText("\u2039", contentX, contentY, parseColor("#AAAAAA"));
3023
- }
3024
- if (hasMoreRight) {
3025
- this.frameBuffer.drawText("\u203A", contentX + contentWidth - 1, contentY, parseColor("#AAAAAA"));
3026
- }
3027
- }
3028
- setOptions(options) {
3029
- this._options = options;
3030
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
3031
- this.updateScrollOffset();
3032
- this.requestRender();
3033
- }
3034
- getSelectedOption() {
3035
- return this._options[this.selectedIndex] || null;
3036
- }
3037
- getSelectedIndex() {
3038
- return this.selectedIndex;
3039
- }
3040
- moveLeft() {
3041
- if (this.selectedIndex > 0) {
3042
- this.selectedIndex--;
3043
- } else if (this._wrapSelection && this._options.length > 0) {
3044
- this.selectedIndex = this._options.length - 1;
3045
- } else {
3046
- return;
3047
- }
3048
- this.updateScrollOffset();
3049
- this.requestRender();
3050
- this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
3064
+ get foregroundColor() {
3065
+ return this._foregroundColor;
3051
3066
  }
3052
- moveRight() {
3053
- if (this.selectedIndex < this._options.length - 1) {
3054
- this.selectedIndex++;
3055
- } else if (this._wrapSelection && this._options.length > 0) {
3056
- this.selectedIndex = 0;
3057
- } else {
3058
- return;
3067
+ set foregroundColor(value) {
3068
+ if (this._foregroundColor !== value) {
3069
+ this._foregroundColor = parseColor(value);
3070
+ this.requestRender();
3059
3071
  }
3060
- this.updateScrollOffset();
3061
- this.requestRender();
3062
- this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
3063
3072
  }
3064
- selectCurrent() {
3065
- const selected = this.getSelectedOption();
3066
- if (selected) {
3067
- this.emit("itemSelected" /* ITEM_SELECTED */, this.selectedIndex, selected);
3068
- }
3073
+ get backgroundColor() {
3074
+ return this._backgroundColor;
3069
3075
  }
3070
- setSelectedIndex(index) {
3071
- if (index >= 0 && index < this._options.length) {
3072
- this.selectedIndex = index;
3073
- this.updateScrollOffset();
3076
+ set backgroundColor(value) {
3077
+ if (this._backgroundColor !== value) {
3078
+ this._backgroundColor = parseColor(value);
3074
3079
  this.requestRender();
3075
- this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
3076
3080
  }
3077
3081
  }
3078
- updateScrollOffset() {
3079
- const halfVisible = Math.floor(this.maxVisibleTabs / 2);
3080
- const newScrollOffset = Math.max(0, Math.min(this.selectedIndex - halfVisible, this._options.length - this.maxVisibleTabs));
3081
- if (newScrollOffset !== this.scrollOffset) {
3082
- this.scrollOffset = newScrollOffset;
3082
+ get attributes() {
3083
+ return this._attributes;
3084
+ }
3085
+ set attributes(value) {
3086
+ if (this._attributes !== value) {
3087
+ this._attributes = value;
3083
3088
  this.requestRender();
3084
3089
  }
3085
3090
  }
3086
- onResize(width, height) {
3087
- this.maxVisibleTabs = Math.max(1, Math.floor(width / this._tabWidth));
3088
- this.updateScrollOffset();
3089
- this.requestRender();
3090
- }
3091
- setTabWidth(tabWidth) {
3092
- if (this._tabWidth === tabWidth)
3093
- return;
3094
- this._tabWidth = tabWidth;
3095
- this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
3096
- this.updateScrollOffset();
3091
+ set arrowChars(value) {
3092
+ this._arrowChars = {
3093
+ ...this._arrowChars,
3094
+ ...value
3095
+ };
3097
3096
  this.requestRender();
3098
3097
  }
3099
- getTabWidth() {
3100
- return this._tabWidth;
3098
+ renderSelf(buffer) {
3099
+ const char = this.getArrowChar();
3100
+ buffer.drawText(char, this.x, this.y, this._foregroundColor, this._backgroundColor, this._attributes);
3101
3101
  }
3102
- handleKeyPress(key) {
3103
- const keyName = typeof key === "string" ? key : key.name;
3104
- switch (keyName) {
3102
+ getArrowChar() {
3103
+ switch (this._direction) {
3104
+ case "up":
3105
+ return this._arrowChars.up;
3106
+ case "down":
3107
+ return this._arrowChars.down;
3105
3108
  case "left":
3106
- case "[":
3107
- this.moveLeft();
3108
- return true;
3109
+ return this._arrowChars.left;
3109
3110
  case "right":
3110
- case "]":
3111
- this.moveRight();
3112
- return true;
3113
- case "return":
3114
- case "enter":
3115
- this.selectCurrent();
3116
- return true;
3111
+ return this._arrowChars.right;
3112
+ default:
3113
+ return "?";
3117
3114
  }
3118
- return false;
3119
- }
3120
- get options() {
3121
- return this._options;
3122
3115
  }
3123
- set options(options) {
3124
- this._options = options;
3125
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
3126
- this.updateScrollOffset();
3127
- this.requestRender();
3116
+ }
3117
+ // src/renderables/ScrollBox.ts
3118
+ class ContentRenderable extends BoxRenderable {
3119
+ viewport;
3120
+ constructor(ctx, viewport, options) {
3121
+ super(ctx, options);
3122
+ this.viewport = viewport;
3128
3123
  }
3129
- set backgroundColor(color) {
3130
- this._backgroundColor = parseColor(color);
3131
- this.requestRender();
3124
+ _getChildren() {
3125
+ return getObjectsInViewport(this.viewport, this.getChildrenSortedByPrimaryAxis(), this.primaryAxis);
3132
3126
  }
3133
- set textColor(color) {
3134
- this._textColor = parseColor(color);
3135
- this.requestRender();
3127
+ }
3128
+
3129
+ class ScrollBoxRenderable extends BoxRenderable {
3130
+ static idCounter = 0;
3131
+ internalId = 0;
3132
+ wrapper;
3133
+ viewport;
3134
+ content;
3135
+ horizontalScrollBar;
3136
+ verticalScrollBar;
3137
+ _focusable = true;
3138
+ selectionListener;
3139
+ autoScrollMouseX = 0;
3140
+ autoScrollMouseY = 0;
3141
+ autoScrollThresholdVertical = 3;
3142
+ autoScrollThresholdHorizontal = 3;
3143
+ autoScrollSpeedSlow = 6;
3144
+ autoScrollSpeedMedium = 36;
3145
+ autoScrollSpeedFast = 72;
3146
+ isAutoScrolling = false;
3147
+ cachedAutoScrollSpeed = 3;
3148
+ autoScrollAccumulatorX = 0;
3149
+ autoScrollAccumulatorY = 0;
3150
+ _stickyScroll;
3151
+ _stickyScrollTop = false;
3152
+ _stickyScrollBottom = false;
3153
+ _stickyScrollLeft = false;
3154
+ _stickyScrollRight = false;
3155
+ _stickyStart;
3156
+ _hasManualScroll = false;
3157
+ scrollAccel;
3158
+ get stickyScroll() {
3159
+ return this._stickyScroll;
3136
3160
  }
3137
- set focusedBackgroundColor(color) {
3138
- this._focusedBackgroundColor = parseColor(color);
3139
- this.requestRender();
3161
+ set stickyScroll(value) {
3162
+ this._stickyScroll = value;
3163
+ this.updateStickyState();
3140
3164
  }
3141
- set focusedTextColor(color) {
3142
- this._focusedTextColor = parseColor(color);
3143
- this.requestRender();
3165
+ get stickyStart() {
3166
+ return this._stickyStart;
3144
3167
  }
3145
- set selectedBackgroundColor(color) {
3146
- this._selectedBackgroundColor = parseColor(color);
3147
- this.requestRender();
3168
+ set stickyStart(value) {
3169
+ this._stickyStart = value;
3170
+ this.updateStickyState();
3148
3171
  }
3149
- set selectedTextColor(color) {
3150
- this._selectedTextColor = parseColor(color);
3151
- this.requestRender();
3172
+ get scrollTop() {
3173
+ return this.verticalScrollBar.scrollPosition;
3152
3174
  }
3153
- set selectedDescriptionColor(color) {
3154
- this._selectedDescriptionColor = parseColor(color);
3155
- this.requestRender();
3175
+ set scrollTop(value) {
3176
+ this.verticalScrollBar.scrollPosition = value;
3177
+ this._hasManualScroll = true;
3178
+ this.updateStickyState();
3156
3179
  }
3157
- get showDescription() {
3158
- return this._showDescription;
3180
+ get scrollLeft() {
3181
+ return this.horizontalScrollBar.scrollPosition;
3159
3182
  }
3160
- set showDescription(show) {
3161
- if (this._showDescription !== show) {
3162
- this._showDescription = show;
3163
- const newHeight = this.calculateDynamicHeight();
3164
- this.height = newHeight;
3165
- this.requestRender();
3166
- }
3183
+ set scrollLeft(value) {
3184
+ this.horizontalScrollBar.scrollPosition = value;
3185
+ this._hasManualScroll = true;
3186
+ this.updateStickyState();
3167
3187
  }
3168
- get showUnderline() {
3169
- return this._showUnderline;
3188
+ get scrollWidth() {
3189
+ return this.horizontalScrollBar.scrollSize;
3170
3190
  }
3171
- set showUnderline(show) {
3172
- if (this._showUnderline !== show) {
3173
- this._showUnderline = show;
3174
- const newHeight = this.calculateDynamicHeight();
3175
- this.height = newHeight;
3176
- this.requestRender();
3177
- }
3191
+ get scrollHeight() {
3192
+ return this.verticalScrollBar.scrollSize;
3178
3193
  }
3179
- get showScrollArrows() {
3180
- return this._showScrollArrows;
3194
+ updateStickyState() {
3195
+ if (!this._stickyScroll)
3196
+ return;
3197
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
3198
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
3199
+ if (this.scrollTop <= 0) {
3200
+ this._stickyScrollTop = true;
3201
+ this._stickyScrollBottom = false;
3202
+ } else if (this.scrollTop >= maxScrollTop) {
3203
+ this._stickyScrollTop = false;
3204
+ this._stickyScrollBottom = true;
3205
+ } else {
3206
+ this._stickyScrollTop = false;
3207
+ this._stickyScrollBottom = false;
3208
+ }
3209
+ if (this.scrollLeft <= 0) {
3210
+ this._stickyScrollLeft = true;
3211
+ this._stickyScrollRight = false;
3212
+ } else if (this.scrollLeft >= maxScrollLeft) {
3213
+ this._stickyScrollLeft = false;
3214
+ this._stickyScrollRight = true;
3215
+ } else {
3216
+ this._stickyScrollLeft = false;
3217
+ this._stickyScrollRight = false;
3218
+ }
3181
3219
  }
3182
- set showScrollArrows(show) {
3183
- if (this._showScrollArrows !== show) {
3184
- this._showScrollArrows = show;
3185
- this.requestRender();
3220
+ applyStickyStart(stickyStart) {
3221
+ switch (stickyStart) {
3222
+ case "top":
3223
+ this._stickyScrollTop = true;
3224
+ this._stickyScrollBottom = false;
3225
+ this.verticalScrollBar.scrollPosition = 0;
3226
+ break;
3227
+ case "bottom":
3228
+ this._stickyScrollTop = false;
3229
+ this._stickyScrollBottom = true;
3230
+ this.verticalScrollBar.scrollPosition = Math.max(0, this.scrollHeight - this.viewport.height);
3231
+ break;
3232
+ case "left":
3233
+ this._stickyScrollLeft = true;
3234
+ this._stickyScrollRight = false;
3235
+ this.horizontalScrollBar.scrollPosition = 0;
3236
+ break;
3237
+ case "right":
3238
+ this._stickyScrollLeft = false;
3239
+ this._stickyScrollRight = true;
3240
+ this.horizontalScrollBar.scrollPosition = Math.max(0, this.scrollWidth - this.viewport.width);
3241
+ break;
3186
3242
  }
3187
3243
  }
3188
- get wrapSelection() {
3189
- return this._wrapSelection;
3244
+ constructor(ctx, {
3245
+ wrapperOptions,
3246
+ viewportOptions,
3247
+ contentOptions,
3248
+ rootOptions,
3249
+ scrollbarOptions,
3250
+ verticalScrollbarOptions,
3251
+ horizontalScrollbarOptions,
3252
+ stickyScroll = false,
3253
+ stickyStart,
3254
+ scrollX = false,
3255
+ scrollY = true,
3256
+ scrollAcceleration,
3257
+ ...options
3258
+ }) {
3259
+ super(ctx, {
3260
+ flexDirection: "row",
3261
+ alignItems: "stretch",
3262
+ ...options,
3263
+ ...rootOptions
3264
+ });
3265
+ this.internalId = ScrollBoxRenderable.idCounter++;
3266
+ this._stickyScroll = stickyScroll;
3267
+ this._stickyStart = stickyStart;
3268
+ if (scrollAcceleration) {
3269
+ this.scrollAccel = scrollAcceleration;
3270
+ } else if (process.platform === "darwin") {
3271
+ this.scrollAccel = new MacOSScrollAccel;
3272
+ }
3273
+ this.scrollAccel ??= new LinearScrollAccel;
3274
+ this.wrapper = new BoxRenderable(ctx, {
3275
+ flexDirection: "column",
3276
+ flexGrow: 1,
3277
+ ...wrapperOptions,
3278
+ id: `scroll-box-wrapper-${this.internalId}`
3279
+ });
3280
+ super.add(this.wrapper);
3281
+ this.viewport = new BoxRenderable(ctx, {
3282
+ flexDirection: "column",
3283
+ flexGrow: 1,
3284
+ overflow: "hidden",
3285
+ onSizeChange: () => {
3286
+ this.recalculateBarProps();
3287
+ },
3288
+ ...viewportOptions,
3289
+ id: `scroll-box-viewport-${this.internalId}`
3290
+ });
3291
+ this.wrapper.add(this.viewport);
3292
+ this.content = new ContentRenderable(ctx, this.viewport, {
3293
+ alignSelf: "flex-start",
3294
+ flexShrink: 0,
3295
+ ...scrollX ? { minWidth: "100%" } : { minWidth: "100%", maxWidth: "100%" },
3296
+ ...scrollY ? { minHeight: "100%" } : { minHeight: "100%", maxHeight: "100%" },
3297
+ onSizeChange: () => {
3298
+ this.recalculateBarProps();
3299
+ },
3300
+ ...contentOptions,
3301
+ id: `scroll-box-content-${this.internalId}`
3302
+ });
3303
+ this.viewport.add(this.content);
3304
+ this.verticalScrollBar = new ScrollBarRenderable(ctx, {
3305
+ ...scrollbarOptions,
3306
+ ...verticalScrollbarOptions,
3307
+ arrowOptions: {
3308
+ ...scrollbarOptions?.arrowOptions,
3309
+ ...verticalScrollbarOptions?.arrowOptions
3310
+ },
3311
+ id: `scroll-box-vertical-scrollbar-${this.internalId}`,
3312
+ orientation: "vertical",
3313
+ onChange: (position) => {
3314
+ this.content.translateY = -position;
3315
+ this._hasManualScroll = true;
3316
+ this.updateStickyState();
3317
+ }
3318
+ });
3319
+ super.add(this.verticalScrollBar);
3320
+ this.horizontalScrollBar = new ScrollBarRenderable(ctx, {
3321
+ ...scrollbarOptions,
3322
+ ...horizontalScrollbarOptions,
3323
+ arrowOptions: {
3324
+ ...scrollbarOptions?.arrowOptions,
3325
+ ...horizontalScrollbarOptions?.arrowOptions
3326
+ },
3327
+ id: `scroll-box-horizontal-scrollbar-${this.internalId}`,
3328
+ orientation: "horizontal",
3329
+ onChange: (position) => {
3330
+ this.content.translateX = -position;
3331
+ this._hasManualScroll = true;
3332
+ this.updateStickyState();
3333
+ }
3334
+ });
3335
+ this.wrapper.add(this.horizontalScrollBar);
3336
+ this.recalculateBarProps();
3337
+ if (stickyStart && stickyScroll) {
3338
+ this.applyStickyStart(stickyStart);
3339
+ }
3340
+ this.selectionListener = () => {
3341
+ const selection = this._ctx.getSelection();
3342
+ if (!selection || !selection.isSelecting) {
3343
+ this.stopAutoScroll();
3344
+ }
3345
+ };
3346
+ this._ctx.on("selection", this.selectionListener);
3190
3347
  }
3191
- set wrapSelection(wrap) {
3192
- this._wrapSelection = wrap;
3348
+ onUpdate(deltaTime) {
3349
+ this.handleAutoScroll(deltaTime);
3193
3350
  }
3194
- get tabWidth() {
3195
- return this._tabWidth;
3351
+ scrollBy(delta, unit = "absolute") {
3352
+ if (typeof delta === "number") {
3353
+ this.verticalScrollBar.scrollBy(delta, unit);
3354
+ } else {
3355
+ this.verticalScrollBar.scrollBy(delta.y, unit);
3356
+ this.horizontalScrollBar.scrollBy(delta.x, unit);
3357
+ }
3358
+ this._hasManualScroll = true;
3196
3359
  }
3197
- set tabWidth(tabWidth) {
3198
- if (this._tabWidth === tabWidth)
3199
- return;
3200
- this._tabWidth = tabWidth;
3201
- this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
3202
- this.updateScrollOffset();
3203
- this.requestRender();
3360
+ scrollTo(position) {
3361
+ if (typeof position === "number") {
3362
+ this.scrollTop = position;
3363
+ } else {
3364
+ this.scrollTop = position.y;
3365
+ this.scrollLeft = position.x;
3366
+ }
3204
3367
  }
3205
- }
3206
- // src/renderables/Slider.ts
3207
- var defaultThumbBackgroundColor = RGBA.fromHex("#9a9ea3");
3208
- var defaultTrackBackgroundColor = RGBA.fromHex("#252527");
3209
-
3210
- class SliderRenderable extends Renderable {
3211
- orientation;
3212
- _value;
3213
- _min;
3214
- _max;
3215
- _viewPortSize;
3216
- _backgroundColor;
3217
- _foregroundColor;
3218
- _onChange;
3219
- constructor(ctx, options) {
3220
- super(ctx, { flexShrink: 0, ...options });
3221
- this.orientation = options.orientation;
3222
- this._min = options.min ?? 0;
3223
- this._max = options.max ?? 100;
3224
- this._value = options.value ?? this._min;
3225
- this._viewPortSize = options.viewPortSize ?? Math.max(1, (this._max - this._min) * 0.1);
3226
- this._onChange = options.onChange;
3227
- this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : defaultTrackBackgroundColor;
3228
- this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : defaultThumbBackgroundColor;
3229
- this.setupMouseHandling();
3368
+ add(obj, index) {
3369
+ return this.content.add(obj, index);
3230
3370
  }
3231
- get value() {
3232
- return this._value;
3371
+ insertBefore(obj, anchor) {
3372
+ return this.content.insertBefore(obj, anchor);
3233
3373
  }
3234
- set value(newValue) {
3235
- const clamped = Math.max(this._min, Math.min(this._max, newValue));
3236
- if (clamped !== this._value) {
3237
- this._value = clamped;
3238
- this._onChange?.(clamped);
3239
- this.emit("change", { value: clamped });
3240
- this.requestRender();
3241
- }
3374
+ remove(id) {
3375
+ this.content.remove(id);
3242
3376
  }
3243
- get min() {
3244
- return this._min;
3377
+ getChildren() {
3378
+ return this.content.getChildren();
3245
3379
  }
3246
- set min(newMin) {
3247
- if (newMin !== this._min) {
3248
- this._min = newMin;
3249
- if (this._value < newMin) {
3250
- this.value = newMin;
3380
+ onMouseEvent(event) {
3381
+ if (event.type === "scroll") {
3382
+ let dir = event.scroll?.direction;
3383
+ if (event.modifiers.shift)
3384
+ dir = dir === "up" ? "left" : dir === "down" ? "right" : dir === "right" ? "down" : "up";
3385
+ const baseDelta = event.scroll?.delta ?? 0;
3386
+ const now = Date.now();
3387
+ const multiplier = this.scrollAccel.tick(now);
3388
+ if (dir === "up") {
3389
+ this.scrollTop -= baseDelta * multiplier;
3390
+ } else if (dir === "down") {
3391
+ this.scrollTop += baseDelta * multiplier;
3392
+ } else if (dir === "left") {
3393
+ this.scrollLeft -= baseDelta * multiplier;
3394
+ } else if (dir === "right") {
3395
+ this.scrollLeft += baseDelta * multiplier;
3251
3396
  }
3252
- this.requestRender();
3397
+ this._hasManualScroll = true;
3253
3398
  }
3254
- }
3255
- get max() {
3256
- return this._max;
3257
- }
3258
- set max(newMax) {
3259
- if (newMax !== this._max) {
3260
- this._max = newMax;
3261
- if (this._value > newMax) {
3262
- this.value = newMax;
3263
- }
3264
- this.requestRender();
3399
+ if (event.type === "drag" && event.isSelecting) {
3400
+ this.updateAutoScroll(event.x, event.y);
3401
+ } else if (event.type === "up") {
3402
+ this.stopAutoScroll();
3265
3403
  }
3266
3404
  }
3267
- set viewPortSize(size) {
3268
- const clampedSize = Math.max(0.01, Math.min(size, this._max - this._min));
3269
- if (clampedSize !== this._viewPortSize) {
3270
- this._viewPortSize = clampedSize;
3271
- this.requestRender();
3405
+ handleKeyPress(key) {
3406
+ if (this.verticalScrollBar.handleKeyPress(key)) {
3407
+ this._hasManualScroll = true;
3408
+ this.scrollAccel.reset();
3409
+ return true;
3272
3410
  }
3411
+ if (this.horizontalScrollBar.handleKeyPress(key)) {
3412
+ this._hasManualScroll = true;
3413
+ this.scrollAccel.reset();
3414
+ return true;
3415
+ }
3416
+ return false;
3273
3417
  }
3274
- get viewPortSize() {
3275
- return this._viewPortSize;
3276
- }
3277
- get backgroundColor() {
3278
- return this._backgroundColor;
3279
- }
3280
- set backgroundColor(value) {
3281
- this._backgroundColor = parseColor(value);
3282
- this.requestRender();
3418
+ startAutoScroll(mouseX, mouseY) {
3419
+ this.stopAutoScroll();
3420
+ this.autoScrollMouseX = mouseX;
3421
+ this.autoScrollMouseY = mouseY;
3422
+ this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
3423
+ this.isAutoScrolling = true;
3424
+ if (!this.live) {
3425
+ this.live = true;
3426
+ }
3283
3427
  }
3284
- get foregroundColor() {
3285
- return this._foregroundColor;
3428
+ updateAutoScroll(mouseX, mouseY) {
3429
+ this.autoScrollMouseX = mouseX;
3430
+ this.autoScrollMouseY = mouseY;
3431
+ this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
3432
+ const scrollX = this.getAutoScrollDirectionX(mouseX);
3433
+ const scrollY = this.getAutoScrollDirectionY(mouseY);
3434
+ if (scrollX === 0 && scrollY === 0) {
3435
+ this.stopAutoScroll();
3436
+ } else if (!this.isAutoScrolling) {
3437
+ this.startAutoScroll(mouseX, mouseY);
3438
+ }
3286
3439
  }
3287
- set foregroundColor(value) {
3288
- this._foregroundColor = parseColor(value);
3289
- this.requestRender();
3440
+ stopAutoScroll() {
3441
+ const wasAutoScrolling = this.isAutoScrolling;
3442
+ this.isAutoScrolling = false;
3443
+ this.autoScrollAccumulatorX = 0;
3444
+ this.autoScrollAccumulatorY = 0;
3445
+ if (wasAutoScrolling && !this.hasOtherLiveReasons()) {
3446
+ this.live = false;
3447
+ }
3290
3448
  }
3291
- calculateDragOffsetVirtual(event) {
3292
- const trackStart = this.orientation === "vertical" ? this.y : this.x;
3293
- const mousePos = (this.orientation === "vertical" ? event.y : event.x) - trackStart;
3294
- const virtualMousePos = Math.max(0, Math.min((this.orientation === "vertical" ? this.height : this.width) * 2, mousePos * 2));
3295
- const virtualThumbStart = this.getVirtualThumbStart();
3296
- const virtualThumbSize = this.getVirtualThumbSize();
3297
- return Math.max(0, Math.min(virtualThumbSize, virtualMousePos - virtualThumbStart));
3449
+ hasOtherLiveReasons() {
3450
+ return false;
3298
3451
  }
3299
- setupMouseHandling() {
3300
- let isDragging = false;
3301
- let dragOffsetVirtual = 0;
3302
- this.onMouseDown = (event) => {
3303
- event.stopPropagation();
3304
- event.preventDefault();
3305
- const thumb = this.getThumbRect();
3306
- const inThumb = event.x >= thumb.x && event.x < thumb.x + thumb.width && event.y >= thumb.y && event.y < thumb.y + thumb.height;
3307
- if (inThumb) {
3308
- isDragging = true;
3309
- dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
3310
- } else {
3311
- this.updateValueFromMouseDirect(event);
3312
- isDragging = true;
3313
- dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
3452
+ handleAutoScroll(deltaTime) {
3453
+ if (!this.isAutoScrolling)
3454
+ return;
3455
+ const scrollX = this.getAutoScrollDirectionX(this.autoScrollMouseX);
3456
+ const scrollY = this.getAutoScrollDirectionY(this.autoScrollMouseY);
3457
+ const scrollAmount = this.cachedAutoScrollSpeed * (deltaTime / 1000);
3458
+ let scrolled = false;
3459
+ if (scrollX !== 0) {
3460
+ this.autoScrollAccumulatorX += scrollX * scrollAmount;
3461
+ const integerScrollX = Math.trunc(this.autoScrollAccumulatorX);
3462
+ if (integerScrollX !== 0) {
3463
+ this.scrollLeft += integerScrollX;
3464
+ this.autoScrollAccumulatorX -= integerScrollX;
3465
+ scrolled = true;
3314
3466
  }
3315
- };
3316
- this.onMouseDrag = (event) => {
3317
- if (!isDragging)
3318
- return;
3319
- event.stopPropagation();
3320
- this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
3321
- };
3322
- this.onMouseUp = (event) => {
3323
- if (isDragging) {
3324
- this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
3467
+ }
3468
+ if (scrollY !== 0) {
3469
+ this.autoScrollAccumulatorY += scrollY * scrollAmount;
3470
+ const integerScrollY = Math.trunc(this.autoScrollAccumulatorY);
3471
+ if (integerScrollY !== 0) {
3472
+ this.scrollTop += integerScrollY;
3473
+ this.autoScrollAccumulatorY -= integerScrollY;
3474
+ scrolled = true;
3325
3475
  }
3326
- isDragging = false;
3327
- };
3328
- }
3329
- updateValueFromMouseDirect(event) {
3330
- const trackStart = this.orientation === "vertical" ? this.y : this.x;
3331
- const trackSize = this.orientation === "vertical" ? this.height : this.width;
3332
- const mousePos = this.orientation === "vertical" ? event.y : event.x;
3333
- const relativeMousePos = mousePos - trackStart;
3334
- const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
3335
- const ratio = trackSize === 0 ? 0 : clampedMousePos / trackSize;
3336
- const range = this._max - this._min;
3337
- const newValue = this._min + ratio * range;
3338
- this.value = newValue;
3339
- }
3340
- updateValueFromMouseWithOffset(event, offsetVirtual) {
3341
- const trackStart = this.orientation === "vertical" ? this.y : this.x;
3342
- const trackSize = this.orientation === "vertical" ? this.height : this.width;
3343
- const mousePos = this.orientation === "vertical" ? event.y : event.x;
3344
- const virtualTrackSize = trackSize * 2;
3345
- const relativeMousePos = mousePos - trackStart;
3346
- const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
3347
- const virtualMousePos = clampedMousePos * 2;
3348
- const virtualThumbSize = this.getVirtualThumbSize();
3349
- const maxThumbStart = Math.max(0, virtualTrackSize - virtualThumbSize);
3350
- let desiredThumbStart = virtualMousePos - offsetVirtual;
3351
- desiredThumbStart = Math.max(0, Math.min(maxThumbStart, desiredThumbStart));
3352
- const ratio = maxThumbStart === 0 ? 0 : desiredThumbStart / maxThumbStart;
3353
- const range = this._max - this._min;
3354
- const newValue = this._min + ratio * range;
3355
- this.value = newValue;
3356
- }
3357
- getThumbRect() {
3358
- const virtualThumbSize = this.getVirtualThumbSize();
3359
- const virtualThumbStart = this.getVirtualThumbStart();
3360
- const realThumbStart = Math.floor(virtualThumbStart / 2);
3361
- const realThumbSize = Math.ceil((virtualThumbStart + virtualThumbSize) / 2) - realThumbStart;
3362
- if (this.orientation === "vertical") {
3363
- return {
3364
- x: this.x,
3365
- y: this.y + realThumbStart,
3366
- width: this.width,
3367
- height: Math.max(1, realThumbSize)
3368
- };
3369
- } else {
3370
- return {
3371
- x: this.x + realThumbStart,
3372
- y: this.y,
3373
- width: Math.max(1, realThumbSize),
3374
- height: this.height
3375
- };
3376
3476
  }
3377
- }
3378
- renderSelf(buffer) {
3379
- if (this.orientation === "horizontal") {
3380
- this.renderHorizontal(buffer);
3381
- } else {
3382
- this.renderVertical(buffer);
3477
+ if (scrolled) {
3478
+ this._ctx.requestSelectionUpdate();
3383
3479
  }
3384
- }
3385
- renderHorizontal(buffer) {
3386
- const virtualThumbSize = this.getVirtualThumbSize();
3387
- const virtualThumbStart = this.getVirtualThumbStart();
3388
- const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
3389
- buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
3390
- const realStartCell = Math.floor(virtualThumbStart / 2);
3391
- const realEndCell = Math.ceil(virtualThumbEnd / 2) - 1;
3392
- const startX = Math.max(0, realStartCell);
3393
- const endX = Math.min(this.width - 1, realEndCell);
3394
- for (let realX = startX;realX <= endX; realX++) {
3395
- const virtualCellStart = realX * 2;
3396
- const virtualCellEnd = virtualCellStart + 2;
3397
- const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
3398
- const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
3399
- const coverage = thumbEndInCell - thumbStartInCell;
3400
- let char = " ";
3401
- if (coverage >= 2) {
3402
- char = "\u2588";
3403
- } else {
3404
- const isLeftHalf = thumbStartInCell === virtualCellStart;
3405
- if (isLeftHalf) {
3406
- char = "\u258C";
3407
- } else {
3408
- char = "\u2590";
3409
- }
3410
- }
3411
- for (let y = 0;y < this.height; y++) {
3412
- buffer.setCellWithAlphaBlending(this.x + realX, this.y + y, char, this._foregroundColor, this._backgroundColor);
3413
- }
3480
+ if (scrollX === 0 && scrollY === 0) {
3481
+ this.stopAutoScroll();
3414
3482
  }
3415
3483
  }
3416
- renderVertical(buffer) {
3417
- const virtualThumbSize = this.getVirtualThumbSize();
3418
- const virtualThumbStart = this.getVirtualThumbStart();
3419
- const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
3420
- buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
3421
- const realStartCell = Math.floor(virtualThumbStart / 2);
3422
- const realEndCell = Math.ceil(virtualThumbEnd / 2) - 1;
3423
- const startY = Math.max(0, realStartCell);
3424
- const endY = Math.min(this.height - 1, realEndCell);
3425
- for (let realY = startY;realY <= endY; realY++) {
3426
- const virtualCellStart = realY * 2;
3427
- const virtualCellEnd = virtualCellStart + 2;
3428
- const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
3429
- const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
3430
- const coverage = thumbEndInCell - thumbStartInCell;
3431
- let char = " ";
3432
- if (coverage >= 2) {
3433
- char = "\u2588";
3434
- } else if (coverage > 0) {
3435
- const virtualPositionInCell = thumbStartInCell - virtualCellStart;
3436
- if (virtualPositionInCell === 0) {
3437
- char = "\u2580";
3438
- } else {
3439
- char = "\u2584";
3440
- }
3441
- }
3442
- for (let x = 0;x < this.width; x++) {
3443
- buffer.setCellWithAlphaBlending(this.x + x, this.y + realY, char, this._foregroundColor, this._backgroundColor);
3444
- }
3484
+ getAutoScrollDirectionX(mouseX) {
3485
+ const relativeX = mouseX - this.x;
3486
+ const distToLeft = relativeX;
3487
+ const distToRight = this.width - relativeX;
3488
+ if (distToLeft <= this.autoScrollThresholdHorizontal) {
3489
+ return this.scrollLeft > 0 ? -1 : 0;
3490
+ } else if (distToRight <= this.autoScrollThresholdHorizontal) {
3491
+ const maxScrollLeft = this.scrollWidth - this.viewport.width;
3492
+ return this.scrollLeft < maxScrollLeft ? 1 : 0;
3445
3493
  }
3494
+ return 0;
3446
3495
  }
3447
- getVirtualThumbSize() {
3448
- const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
3449
- const range = this._max - this._min;
3450
- if (range === 0)
3451
- return virtualTrackSize;
3452
- const viewportSize = Math.max(1, this._viewPortSize);
3453
- const contentSize = range + viewportSize;
3454
- if (contentSize <= viewportSize)
3455
- return virtualTrackSize;
3456
- const thumbRatio = viewportSize / contentSize;
3457
- const calculatedSize = Math.floor(virtualTrackSize * thumbRatio);
3458
- return Math.max(1, Math.min(calculatedSize, virtualTrackSize));
3459
- }
3460
- getVirtualThumbStart() {
3461
- const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
3462
- const range = this._max - this._min;
3463
- if (range === 0)
3464
- return 0;
3465
- const valueRatio = (this._value - this._min) / range;
3466
- const virtualThumbSize = this.getVirtualThumbSize();
3467
- return Math.round(valueRatio * (virtualTrackSize - virtualThumbSize));
3468
- }
3469
- }
3470
-
3471
- // src/renderables/ScrollBar.ts
3472
- class ScrollBarRenderable extends Renderable {
3473
- slider;
3474
- startArrow;
3475
- endArrow;
3476
- orientation;
3477
- _focusable = true;
3478
- _scrollSize = 0;
3479
- _scrollPosition = 0;
3480
- _viewportSize = 0;
3481
- _showArrows = false;
3482
- _manualVisibility = false;
3483
- _onChange;
3484
- scrollStep = null;
3485
- get visible() {
3486
- return super.visible;
3487
- }
3488
- set visible(value) {
3489
- this._manualVisibility = true;
3490
- super.visible = value;
3491
- }
3492
- resetVisibilityControl() {
3493
- this._manualVisibility = false;
3494
- this.recalculateVisibility();
3495
- }
3496
- get scrollSize() {
3497
- return this._scrollSize;
3498
- }
3499
- get scrollPosition() {
3500
- return this._scrollPosition;
3501
- }
3502
- get viewportSize() {
3503
- return this._viewportSize;
3504
- }
3505
- set scrollSize(value) {
3506
- if (value === this.scrollSize)
3507
- return;
3508
- this._scrollSize = value;
3509
- this.recalculateVisibility();
3510
- this.updateSliderFromScrollState();
3511
- this.scrollPosition = this.scrollPosition;
3512
- }
3513
- set scrollPosition(value) {
3514
- const newPosition = Math.round(Math.min(Math.max(0, value), this.scrollSize - this.viewportSize));
3515
- if (newPosition !== this._scrollPosition) {
3516
- this._scrollPosition = newPosition;
3517
- this.updateSliderFromScrollState();
3496
+ getAutoScrollDirectionY(mouseY) {
3497
+ const relativeY = mouseY - this.y;
3498
+ const distToTop = relativeY;
3499
+ const distToBottom = this.height - relativeY;
3500
+ if (distToTop <= this.autoScrollThresholdVertical) {
3501
+ return this.scrollTop > 0 ? -1 : 0;
3502
+ } else if (distToBottom <= this.autoScrollThresholdVertical) {
3503
+ const maxScrollTop = this.scrollHeight - this.viewport.height;
3504
+ return this.scrollTop < maxScrollTop ? 1 : 0;
3518
3505
  }
3506
+ return 0;
3519
3507
  }
3520
- set viewportSize(value) {
3521
- if (value === this.viewportSize)
3522
- return;
3523
- this._viewportSize = value;
3524
- this.slider.viewPortSize = Math.max(1, this._viewportSize);
3525
- this.recalculateVisibility();
3526
- this.updateSliderFromScrollState();
3527
- this.scrollPosition = this.scrollPosition;
3528
- }
3529
- get showArrows() {
3530
- return this._showArrows;
3531
- }
3532
- set showArrows(value) {
3533
- if (value === this._showArrows)
3534
- return;
3535
- this._showArrows = value;
3536
- this.startArrow.visible = value;
3537
- this.endArrow.visible = value;
3508
+ getAutoScrollSpeed(mouseX, mouseY) {
3509
+ const relativeX = mouseX - this.x;
3510
+ const relativeY = mouseY - this.y;
3511
+ const distToLeft = relativeX;
3512
+ const distToRight = this.width - relativeX;
3513
+ const distToTop = relativeY;
3514
+ const distToBottom = this.height - relativeY;
3515
+ const minDistance = Math.min(distToLeft, distToRight, distToTop, distToBottom);
3516
+ if (minDistance <= 1) {
3517
+ return this.autoScrollSpeedFast;
3518
+ } else if (minDistance <= 2) {
3519
+ return this.autoScrollSpeedMedium;
3520
+ } else {
3521
+ return this.autoScrollSpeedSlow;
3522
+ }
3538
3523
  }
3539
- constructor(ctx, { trackOptions, arrowOptions, orientation, showArrows = false, ...options }) {
3540
- super(ctx, {
3541
- flexDirection: orientation === "vertical" ? "column" : "row",
3542
- alignSelf: "stretch",
3543
- alignItems: "stretch",
3544
- ...options
3545
- });
3546
- this._onChange = options.onChange;
3547
- this.orientation = orientation;
3548
- this._showArrows = showArrows;
3549
- const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
3550
- const defaultStepSize = Math.max(1, this._viewportSize);
3551
- const stepSize = trackOptions?.viewPortSize ?? defaultStepSize;
3552
- this.slider = new SliderRenderable(ctx, {
3553
- orientation,
3554
- min: 0,
3555
- max: scrollRange,
3556
- value: this._scrollPosition,
3557
- viewPortSize: stepSize,
3558
- onChange: (value) => {
3559
- this._scrollPosition = Math.round(value);
3560
- this._onChange?.(this._scrollPosition);
3561
- this.emit("change", { position: this._scrollPosition });
3562
- },
3563
- ...orientation === "vertical" ? {
3564
- width: Math.max(1, Math.min(2, this.width)),
3565
- height: "100%",
3566
- marginLeft: "auto"
3567
- } : {
3568
- width: "100%",
3569
- height: 1,
3570
- marginTop: "auto"
3571
- },
3572
- flexGrow: 1,
3573
- flexShrink: 1,
3574
- ...trackOptions
3575
- });
3576
- this.updateSliderFromScrollState();
3577
- const arrowOpts = arrowOptions ? {
3578
- foregroundColor: arrowOptions.backgroundColor,
3579
- backgroundColor: arrowOptions.backgroundColor,
3580
- attributes: arrowOptions.attributes,
3581
- ...arrowOptions
3582
- } : {};
3583
- this.startArrow = new ArrowRenderable(ctx, {
3584
- alignSelf: "center",
3585
- visible: this.showArrows,
3586
- direction: this.orientation === "vertical" ? "up" : "left",
3587
- height: this.orientation === "vertical" ? 1 : 1,
3588
- ...arrowOpts
3589
- });
3590
- this.endArrow = new ArrowRenderable(ctx, {
3591
- alignSelf: "center",
3592
- visible: this.showArrows,
3593
- direction: this.orientation === "vertical" ? "down" : "right",
3594
- height: this.orientation === "vertical" ? 1 : 1,
3595
- ...arrowOpts
3596
- });
3597
- this.add(this.startArrow);
3598
- this.add(this.slider);
3599
- this.add(this.endArrow);
3600
- let startArrowMouseTimeout = undefined;
3601
- let endArrowMouseTimeout = undefined;
3602
- this.startArrow.onMouseDown = (event) => {
3603
- event.stopPropagation();
3604
- event.preventDefault();
3605
- this.scrollBy(-0.5, "viewport");
3606
- startArrowMouseTimeout = setTimeout(() => {
3607
- this.scrollBy(-0.5, "viewport");
3608
- startArrowMouseTimeout = setInterval(() => {
3609
- this.scrollBy(-0.2, "viewport");
3610
- }, 200);
3611
- }, 500);
3612
- };
3613
- this.startArrow.onMouseUp = (event) => {
3614
- event.stopPropagation();
3615
- clearInterval(startArrowMouseTimeout);
3616
- };
3617
- this.endArrow.onMouseDown = (event) => {
3618
- event.stopPropagation();
3619
- event.preventDefault();
3620
- this.scrollBy(0.5, "viewport");
3621
- endArrowMouseTimeout = setTimeout(() => {
3622
- this.scrollBy(0.5, "viewport");
3623
- endArrowMouseTimeout = setInterval(() => {
3624
- this.scrollBy(0.2, "viewport");
3625
- }, 200);
3626
- }, 500);
3627
- };
3628
- this.endArrow.onMouseUp = (event) => {
3629
- event.stopPropagation();
3630
- clearInterval(endArrowMouseTimeout);
3631
- };
3524
+ recalculateBarProps() {
3525
+ this.verticalScrollBar.scrollSize = this.content.height;
3526
+ this.verticalScrollBar.viewportSize = this.viewport.height;
3527
+ this.horizontalScrollBar.scrollSize = this.content.width;
3528
+ this.horizontalScrollBar.viewportSize = this.viewport.width;
3529
+ if (this._stickyScroll) {
3530
+ const newMaxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
3531
+ const newMaxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
3532
+ if (this._stickyStart && !this._hasManualScroll) {
3533
+ this.applyStickyStart(this._stickyStart);
3534
+ } else {
3535
+ if (this._stickyScrollTop) {
3536
+ this.scrollTop = 0;
3537
+ } else if (this._stickyScrollBottom && newMaxScrollTop > 0) {
3538
+ this.scrollTop = newMaxScrollTop;
3539
+ }
3540
+ if (this._stickyScrollLeft) {
3541
+ this.scrollLeft = 0;
3542
+ } else if (this._stickyScrollRight && newMaxScrollLeft > 0) {
3543
+ this.scrollLeft = newMaxScrollLeft;
3544
+ }
3545
+ }
3546
+ }
3547
+ process.nextTick(() => {
3548
+ this.requestRender();
3549
+ });
3632
3550
  }
3633
- set arrowOptions(options) {
3634
- Object.assign(this.startArrow, options);
3635
- Object.assign(this.endArrow, options);
3551
+ set rootOptions(options) {
3552
+ Object.assign(this, options);
3636
3553
  this.requestRender();
3637
3554
  }
3638
- set trackOptions(options) {
3639
- Object.assign(this.slider, options);
3555
+ set wrapperOptions(options) {
3556
+ Object.assign(this.wrapper, options);
3640
3557
  this.requestRender();
3641
3558
  }
3642
- updateSliderFromScrollState() {
3643
- const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
3644
- this.slider.min = 0;
3645
- this.slider.max = scrollRange;
3646
- this.slider.value = Math.min(this._scrollPosition, scrollRange);
3559
+ set viewportOptions(options) {
3560
+ Object.assign(this.viewport, options);
3561
+ this.requestRender();
3647
3562
  }
3648
- scrollBy(delta, unit = "absolute") {
3649
- const multiplier = unit === "viewport" ? this.viewportSize : unit === "content" ? this.scrollSize : unit === "step" ? this.scrollStep ?? 1 : 1;
3650
- const resolvedDelta = multiplier * delta;
3651
- this.scrollPosition += resolvedDelta;
3563
+ set contentOptions(options) {
3564
+ Object.assign(this.content, options);
3565
+ this.requestRender();
3652
3566
  }
3653
- recalculateVisibility() {
3654
- if (!this._manualVisibility) {
3655
- const sizeRatio = this.scrollSize <= this.viewportSize ? 1 : this.viewportSize / this.scrollSize;
3656
- super.visible = sizeRatio < 1;
3657
- }
3567
+ set scrollbarOptions(options) {
3568
+ Object.assign(this.verticalScrollBar, options);
3569
+ Object.assign(this.horizontalScrollBar, options);
3570
+ this.requestRender();
3658
3571
  }
3659
- handleKeyPress(key) {
3660
- const keyName = typeof key === "string" ? key : key.name;
3661
- switch (keyName) {
3662
- case "left":
3663
- case "h":
3664
- if (this.orientation !== "horizontal")
3665
- return false;
3666
- this.scrollBy(-1 / 5, "viewport");
3667
- return true;
3668
- case "right":
3669
- case "l":
3670
- if (this.orientation !== "horizontal")
3671
- return false;
3672
- this.scrollBy(1 / 5, "viewport");
3673
- return true;
3674
- case "up":
3675
- case "k":
3676
- if (this.orientation !== "vertical")
3677
- return false;
3678
- this.scrollBy(-1 / 5, "viewport");
3679
- return true;
3680
- case "down":
3681
- case "j":
3682
- if (this.orientation !== "vertical")
3683
- return false;
3684
- this.scrollBy(1 / 5, "viewport");
3685
- return true;
3686
- case "pageup":
3687
- this.scrollBy(-1 / 2, "viewport");
3688
- return true;
3689
- case "pagedown":
3690
- this.scrollBy(1 / 2, "viewport");
3691
- return true;
3692
- case "home":
3693
- this.scrollBy(-1, "content");
3694
- return true;
3695
- case "end":
3696
- this.scrollBy(1, "content");
3697
- return true;
3572
+ set verticalScrollbarOptions(options) {
3573
+ Object.assign(this.verticalScrollBar, options);
3574
+ this.requestRender();
3575
+ }
3576
+ set horizontalScrollbarOptions(options) {
3577
+ Object.assign(this.horizontalScrollBar, options);
3578
+ this.requestRender();
3579
+ }
3580
+ get scrollAcceleration() {
3581
+ return this.scrollAccel;
3582
+ }
3583
+ set scrollAcceleration(value) {
3584
+ this.scrollAccel = value;
3585
+ }
3586
+ destroySelf() {
3587
+ if (this.selectionListener) {
3588
+ this._ctx.off("selection", this.selectionListener);
3589
+ this.selectionListener = undefined;
3698
3590
  }
3699
- return false;
3591
+ super.destroySelf();
3700
3592
  }
3701
3593
  }
3594
+ // src/renderables/Select.ts
3595
+ var SelectRenderableEvents;
3596
+ ((SelectRenderableEvents2) => {
3597
+ SelectRenderableEvents2["SELECTION_CHANGED"] = "selectionChanged";
3598
+ SelectRenderableEvents2["ITEM_SELECTED"] = "itemSelected";
3599
+ })(SelectRenderableEvents ||= {});
3702
3600
 
3703
- class ArrowRenderable extends Renderable {
3704
- _direction;
3705
- _foregroundColor;
3601
+ class SelectRenderable extends Renderable {
3602
+ _focusable = true;
3603
+ _options = [];
3604
+ selectedIndex = 0;
3605
+ scrollOffset = 0;
3606
+ maxVisibleItems;
3706
3607
  _backgroundColor;
3707
- _attributes;
3708
- _arrowChars;
3608
+ _textColor;
3609
+ _focusedBackgroundColor;
3610
+ _focusedTextColor;
3611
+ _selectedBackgroundColor;
3612
+ _selectedTextColor;
3613
+ _descriptionColor;
3614
+ _selectedDescriptionColor;
3615
+ _showScrollIndicator;
3616
+ _wrapSelection;
3617
+ _showDescription;
3618
+ _font;
3619
+ _itemSpacing;
3620
+ linesPerItem;
3621
+ fontHeight;
3622
+ _fastScrollStep;
3623
+ _defaultOptions = {
3624
+ backgroundColor: "transparent",
3625
+ textColor: "#FFFFFF",
3626
+ focusedBackgroundColor: "#1a1a1a",
3627
+ focusedTextColor: "#FFFFFF",
3628
+ selectedBackgroundColor: "#334455",
3629
+ selectedTextColor: "#FFFF00",
3630
+ descriptionColor: "#888888",
3631
+ selectedDescriptionColor: "#CCCCCC",
3632
+ showScrollIndicator: false,
3633
+ wrapSelection: false,
3634
+ showDescription: true,
3635
+ itemSpacing: 0,
3636
+ fastScrollStep: 5
3637
+ };
3709
3638
  constructor(ctx, options) {
3710
- super(ctx, options);
3711
- this._direction = options.direction;
3712
- this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : RGBA.fromValues(1, 1, 1, 1);
3713
- this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : RGBA.fromValues(0, 0, 0, 0);
3714
- this._attributes = options.attributes ?? 0;
3715
- this._arrowChars = {
3716
- up: "\u25B2",
3717
- down: "\u25BC",
3718
- left: "\u25C0",
3719
- right: "\u25B6",
3720
- ...options.arrowChars
3721
- };
3722
- if (!options.width) {
3723
- this.width = Bun.stringWidth(this.getArrowChar());
3639
+ super(ctx, { ...options, buffered: true });
3640
+ this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
3641
+ this._textColor = parseColor(options.textColor || this._defaultOptions.textColor);
3642
+ this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || this._defaultOptions.focusedBackgroundColor);
3643
+ this._focusedTextColor = parseColor(options.focusedTextColor || this._defaultOptions.focusedTextColor);
3644
+ this._options = options.options || [];
3645
+ this._showScrollIndicator = options.showScrollIndicator ?? this._defaultOptions.showScrollIndicator;
3646
+ this._wrapSelection = options.wrapSelection ?? this._defaultOptions.wrapSelection;
3647
+ this._showDescription = options.showDescription ?? this._defaultOptions.showDescription;
3648
+ this._font = options.font;
3649
+ this._itemSpacing = options.itemSpacing || this._defaultOptions.itemSpacing;
3650
+ this.fontHeight = this._font ? measureText({ text: "A", font: this._font }).height : 1;
3651
+ this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
3652
+ this.linesPerItem += this._itemSpacing;
3653
+ this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
3654
+ this._selectedBackgroundColor = parseColor(options.selectedBackgroundColor || this._defaultOptions.selectedBackgroundColor);
3655
+ this._selectedTextColor = parseColor(options.selectedTextColor || this._defaultOptions.selectedTextColor);
3656
+ this._descriptionColor = parseColor(options.descriptionColor || this._defaultOptions.descriptionColor);
3657
+ this._selectedDescriptionColor = parseColor(options.selectedDescriptionColor || this._defaultOptions.selectedDescriptionColor);
3658
+ this._fastScrollStep = options.fastScrollStep || this._defaultOptions.fastScrollStep;
3659
+ this.requestRender();
3660
+ }
3661
+ renderSelf(buffer, deltaTime) {
3662
+ if (!this.visible || !this.frameBuffer)
3663
+ return;
3664
+ if (this.isDirty) {
3665
+ this.refreshFrameBuffer();
3666
+ }
3667
+ }
3668
+ refreshFrameBuffer() {
3669
+ if (!this.frameBuffer || this._options.length === 0)
3670
+ return;
3671
+ const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
3672
+ this.frameBuffer.clear(bgColor);
3673
+ const contentX = 0;
3674
+ const contentY = 0;
3675
+ const contentWidth = this.width;
3676
+ const contentHeight = this.height;
3677
+ const visibleOptions = this._options.slice(this.scrollOffset, this.scrollOffset + this.maxVisibleItems);
3678
+ for (let i = 0;i < visibleOptions.length; i++) {
3679
+ const actualIndex = this.scrollOffset + i;
3680
+ const option = visibleOptions[i];
3681
+ const isSelected = actualIndex === this.selectedIndex;
3682
+ const itemY = contentY + i * this.linesPerItem;
3683
+ if (itemY + this.linesPerItem - 1 >= contentY + contentHeight)
3684
+ break;
3685
+ if (isSelected) {
3686
+ const contentHeight2 = this.linesPerItem - this._itemSpacing;
3687
+ this.frameBuffer.fillRect(contentX, itemY, contentWidth, contentHeight2, this._selectedBackgroundColor);
3688
+ }
3689
+ const nameContent = `${isSelected ? "\u25B6 " : " "}${option.name}`;
3690
+ const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
3691
+ const nameColor = isSelected ? this._selectedTextColor : baseTextColor;
3692
+ let descX = contentX + 3;
3693
+ if (this._font) {
3694
+ const indicator = isSelected ? "\u25B6 " : " ";
3695
+ this.frameBuffer.drawText(indicator, contentX + 1, itemY, nameColor);
3696
+ const indicatorWidth = 2;
3697
+ renderFontToFrameBuffer(this.frameBuffer, {
3698
+ text: option.name,
3699
+ x: contentX + 1 + indicatorWidth,
3700
+ y: itemY,
3701
+ fg: nameColor,
3702
+ bg: isSelected ? this._selectedBackgroundColor : bgColor,
3703
+ font: this._font
3704
+ });
3705
+ descX = contentX + 1 + indicatorWidth;
3706
+ } else {
3707
+ this.frameBuffer.drawText(nameContent, contentX + 1, itemY, nameColor);
3708
+ }
3709
+ if (this._showDescription && itemY + this.fontHeight < contentY + contentHeight) {
3710
+ const descColor = isSelected ? this._selectedDescriptionColor : this._descriptionColor;
3711
+ const descBg = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
3712
+ this.frameBuffer.drawText(option.description, descX, itemY + this.fontHeight, descColor);
3713
+ }
3714
+ }
3715
+ if (this._showScrollIndicator && this._options.length > this.maxVisibleItems) {
3716
+ this.renderScrollIndicatorToFrameBuffer(contentX, contentY, contentWidth, contentHeight);
3724
3717
  }
3725
3718
  }
3726
- get direction() {
3727
- return this._direction;
3719
+ renderScrollIndicatorToFrameBuffer(contentX, contentY, contentWidth, contentHeight) {
3720
+ if (!this.frameBuffer)
3721
+ return;
3722
+ const scrollPercent = this.selectedIndex / Math.max(1, this._options.length - 1);
3723
+ const indicatorHeight = Math.max(1, contentHeight - 2);
3724
+ const indicatorY = contentY + 1 + Math.floor(scrollPercent * indicatorHeight);
3725
+ const indicatorX = contentX + contentWidth - 1;
3726
+ this.frameBuffer.drawText("\u2588", indicatorX, indicatorY, parseColor("#666666"));
3727
+ }
3728
+ get options() {
3729
+ return this._options;
3730
+ }
3731
+ set options(options) {
3732
+ this._options = options;
3733
+ this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
3734
+ this.updateScrollOffset();
3735
+ this.requestRender();
3736
+ }
3737
+ getSelectedOption() {
3738
+ return this._options[this.selectedIndex] || null;
3739
+ }
3740
+ getSelectedIndex() {
3741
+ return this.selectedIndex;
3728
3742
  }
3729
- set direction(value) {
3730
- if (this._direction !== value) {
3731
- this._direction = value;
3732
- this.requestRender();
3743
+ moveUp(steps = 1) {
3744
+ const newIndex = this.selectedIndex - steps;
3745
+ if (newIndex >= 0) {
3746
+ this.selectedIndex = newIndex;
3747
+ } else if (this._wrapSelection && this._options.length > 0) {
3748
+ this.selectedIndex = this._options.length - 1;
3749
+ } else {
3750
+ this.selectedIndex = 0;
3733
3751
  }
3752
+ this.updateScrollOffset();
3753
+ this.requestRender();
3754
+ this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
3734
3755
  }
3735
- get foregroundColor() {
3736
- return this._foregroundColor;
3737
- }
3738
- set foregroundColor(value) {
3739
- if (this._foregroundColor !== value) {
3740
- this._foregroundColor = parseColor(value);
3741
- this.requestRender();
3756
+ moveDown(steps = 1) {
3757
+ const newIndex = this.selectedIndex + steps;
3758
+ if (newIndex < this._options.length) {
3759
+ this.selectedIndex = newIndex;
3760
+ } else if (this._wrapSelection && this._options.length > 0) {
3761
+ this.selectedIndex = 0;
3762
+ } else {
3763
+ this.selectedIndex = this._options.length - 1;
3742
3764
  }
3765
+ this.updateScrollOffset();
3766
+ this.requestRender();
3767
+ this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
3743
3768
  }
3744
- get backgroundColor() {
3745
- return this._backgroundColor;
3769
+ selectCurrent() {
3770
+ const selected = this.getSelectedOption();
3771
+ if (selected) {
3772
+ this.emit("itemSelected" /* ITEM_SELECTED */, this.selectedIndex, selected);
3773
+ }
3746
3774
  }
3747
- set backgroundColor(value) {
3748
- if (this._backgroundColor !== value) {
3749
- this._backgroundColor = parseColor(value);
3775
+ setSelectedIndex(index) {
3776
+ if (index >= 0 && index < this._options.length) {
3777
+ this.selectedIndex = index;
3778
+ this.updateScrollOffset();
3750
3779
  this.requestRender();
3780
+ this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
3751
3781
  }
3752
3782
  }
3753
- get attributes() {
3754
- return this._attributes;
3755
- }
3756
- set attributes(value) {
3757
- if (this._attributes !== value) {
3758
- this._attributes = value;
3783
+ updateScrollOffset() {
3784
+ if (!this._options)
3785
+ return;
3786
+ const halfVisible = Math.floor(this.maxVisibleItems / 2);
3787
+ const newScrollOffset = Math.max(0, Math.min(this.selectedIndex - halfVisible, this._options.length - this.maxVisibleItems));
3788
+ if (newScrollOffset !== this.scrollOffset) {
3789
+ this.scrollOffset = newScrollOffset;
3759
3790
  this.requestRender();
3760
3791
  }
3761
3792
  }
3762
- set arrowChars(value) {
3763
- this._arrowChars = {
3764
- ...this._arrowChars,
3765
- ...value
3766
- };
3793
+ onResize(width, height) {
3794
+ this.maxVisibleItems = Math.max(1, Math.floor(height / this.linesPerItem));
3795
+ this.updateScrollOffset();
3767
3796
  this.requestRender();
3768
3797
  }
3769
- renderSelf(buffer) {
3770
- const char = this.getArrowChar();
3771
- buffer.drawText(char, this.x, this.y, this._foregroundColor, this._backgroundColor, this._attributes);
3772
- }
3773
- getArrowChar() {
3774
- switch (this._direction) {
3798
+ handleKeyPress(key) {
3799
+ const keyName = typeof key === "string" ? key : key.name;
3800
+ const isShift = typeof key !== "string" && key.shift;
3801
+ switch (keyName) {
3775
3802
  case "up":
3776
- return this._arrowChars.up;
3803
+ case "k":
3804
+ this.moveUp(isShift ? this._fastScrollStep : 1);
3805
+ return true;
3777
3806
  case "down":
3778
- return this._arrowChars.down;
3779
- case "left":
3780
- return this._arrowChars.left;
3781
- case "right":
3782
- return this._arrowChars.right;
3783
- default:
3784
- return "?";
3807
+ case "j":
3808
+ this.moveDown(isShift ? this._fastScrollStep : 1);
3809
+ return true;
3810
+ case "return":
3811
+ case "enter":
3812
+ this.selectCurrent();
3813
+ return true;
3785
3814
  }
3815
+ return false;
3786
3816
  }
3787
- }
3788
-
3789
- // src/renderables/ScrollBox.ts
3790
- class ContentRenderable extends BoxRenderable {
3791
- viewport;
3792
- constructor(ctx, viewport, options) {
3793
- super(ctx, options);
3794
- this.viewport = viewport;
3795
- }
3796
- _getChildren() {
3797
- return getObjectsInViewport(this.viewport, this.getChildrenSortedByPrimaryAxis(), this.primaryAxis);
3798
- }
3799
- }
3800
-
3801
- class ScrollBoxRenderable extends BoxRenderable {
3802
- static idCounter = 0;
3803
- internalId = 0;
3804
- wrapper;
3805
- viewport;
3806
- content;
3807
- horizontalScrollBar;
3808
- verticalScrollBar;
3809
- _focusable = true;
3810
- selectionListener;
3811
- autoScrollMouseX = 0;
3812
- autoScrollMouseY = 0;
3813
- autoScrollThresholdVertical = 3;
3814
- autoScrollThresholdHorizontal = 3;
3815
- autoScrollSpeedSlow = 6;
3816
- autoScrollSpeedMedium = 36;
3817
- autoScrollSpeedFast = 72;
3818
- isAutoScrolling = false;
3819
- cachedAutoScrollSpeed = 3;
3820
- autoScrollAccumulatorX = 0;
3821
- autoScrollAccumulatorY = 0;
3822
- _stickyScroll;
3823
- _stickyScrollTop = false;
3824
- _stickyScrollBottom = false;
3825
- _stickyScrollLeft = false;
3826
- _stickyScrollRight = false;
3827
- _stickyStart;
3828
- _hasManualScroll = false;
3829
- scrollAccel;
3830
- get stickyScroll() {
3831
- return this._stickyScroll;
3832
- }
3833
- set stickyScroll(value) {
3834
- this._stickyScroll = value;
3835
- this.updateStickyState();
3836
- }
3837
- get stickyStart() {
3838
- return this._stickyStart;
3839
- }
3840
- set stickyStart(value) {
3841
- this._stickyStart = value;
3842
- this.updateStickyState();
3843
- }
3844
- get scrollTop() {
3845
- return this.verticalScrollBar.scrollPosition;
3817
+ get showScrollIndicator() {
3818
+ return this._showScrollIndicator;
3846
3819
  }
3847
- set scrollTop(value) {
3848
- this.verticalScrollBar.scrollPosition = value;
3849
- this._hasManualScroll = true;
3850
- this.updateStickyState();
3820
+ set showScrollIndicator(show) {
3821
+ this._showScrollIndicator = show;
3822
+ this.requestRender();
3851
3823
  }
3852
- get scrollLeft() {
3853
- return this.horizontalScrollBar.scrollPosition;
3824
+ get showDescription() {
3825
+ return this._showDescription;
3854
3826
  }
3855
- set scrollLeft(value) {
3856
- this.horizontalScrollBar.scrollPosition = value;
3857
- this._hasManualScroll = true;
3858
- this.updateStickyState();
3827
+ set showDescription(show) {
3828
+ if (this._showDescription !== show) {
3829
+ this._showDescription = show;
3830
+ this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
3831
+ this.linesPerItem += this._itemSpacing;
3832
+ this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
3833
+ this.updateScrollOffset();
3834
+ this.requestRender();
3835
+ }
3859
3836
  }
3860
- get scrollWidth() {
3861
- return this.horizontalScrollBar.scrollSize;
3837
+ get wrapSelection() {
3838
+ return this._wrapSelection;
3862
3839
  }
3863
- get scrollHeight() {
3864
- return this.verticalScrollBar.scrollSize;
3840
+ set wrapSelection(wrap) {
3841
+ this._wrapSelection = wrap;
3865
3842
  }
3866
- updateStickyState() {
3867
- if (!this._stickyScroll)
3868
- return;
3869
- const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
3870
- const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
3871
- if (this.scrollTop <= 0) {
3872
- this._stickyScrollTop = true;
3873
- this._stickyScrollBottom = false;
3874
- } else if (this.scrollTop >= maxScrollTop) {
3875
- this._stickyScrollTop = false;
3876
- this._stickyScrollBottom = true;
3877
- } else {
3878
- this._stickyScrollTop = false;
3879
- this._stickyScrollBottom = false;
3843
+ set backgroundColor(value) {
3844
+ const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
3845
+ if (this._backgroundColor !== newColor) {
3846
+ this._backgroundColor = newColor;
3847
+ this.requestRender();
3880
3848
  }
3881
- if (this.scrollLeft <= 0) {
3882
- this._stickyScrollLeft = true;
3883
- this._stickyScrollRight = false;
3884
- } else if (this.scrollLeft >= maxScrollLeft) {
3885
- this._stickyScrollLeft = false;
3886
- this._stickyScrollRight = true;
3887
- } else {
3888
- this._stickyScrollLeft = false;
3889
- this._stickyScrollRight = false;
3849
+ }
3850
+ set textColor(value) {
3851
+ const newColor = parseColor(value ?? this._defaultOptions.textColor);
3852
+ if (this._textColor !== newColor) {
3853
+ this._textColor = newColor;
3854
+ this.requestRender();
3890
3855
  }
3891
3856
  }
3892
- applyStickyStart(stickyStart) {
3893
- switch (stickyStart) {
3894
- case "top":
3895
- this._stickyScrollTop = true;
3896
- this._stickyScrollBottom = false;
3897
- this.verticalScrollBar.scrollPosition = 0;
3898
- break;
3899
- case "bottom":
3900
- this._stickyScrollTop = false;
3901
- this._stickyScrollBottom = true;
3902
- this.verticalScrollBar.scrollPosition = Math.max(0, this.scrollHeight - this.viewport.height);
3903
- break;
3904
- case "left":
3905
- this._stickyScrollLeft = true;
3906
- this._stickyScrollRight = false;
3907
- this.horizontalScrollBar.scrollPosition = 0;
3908
- break;
3909
- case "right":
3910
- this._stickyScrollLeft = false;
3911
- this._stickyScrollRight = true;
3912
- this.horizontalScrollBar.scrollPosition = Math.max(0, this.scrollWidth - this.viewport.width);
3913
- break;
3857
+ set focusedBackgroundColor(value) {
3858
+ const newColor = parseColor(value ?? this._defaultOptions.focusedBackgroundColor);
3859
+ if (this._focusedBackgroundColor !== newColor) {
3860
+ this._focusedBackgroundColor = newColor;
3861
+ this.requestRender();
3914
3862
  }
3915
3863
  }
3916
- constructor(ctx, {
3917
- wrapperOptions,
3918
- viewportOptions,
3919
- contentOptions,
3920
- rootOptions,
3921
- scrollbarOptions,
3922
- verticalScrollbarOptions,
3923
- horizontalScrollbarOptions,
3924
- stickyScroll = false,
3925
- stickyStart,
3926
- scrollX = false,
3927
- scrollY = true,
3928
- scrollAcceleration,
3929
- ...options
3930
- }) {
3931
- super(ctx, {
3932
- flexDirection: "row",
3933
- alignItems: "stretch",
3934
- ...options,
3935
- ...rootOptions
3936
- });
3937
- this.internalId = ScrollBoxRenderable.idCounter++;
3938
- this._stickyScroll = stickyScroll;
3939
- this._stickyStart = stickyStart;
3940
- if (scrollAcceleration) {
3941
- this.scrollAccel = scrollAcceleration;
3942
- } else if (process.platform === "darwin") {
3943
- this.scrollAccel = new MacOSScrollAccel;
3864
+ set focusedTextColor(value) {
3865
+ const newColor = parseColor(value ?? this._defaultOptions.focusedTextColor);
3866
+ if (this._focusedTextColor !== newColor) {
3867
+ this._focusedTextColor = newColor;
3868
+ this.requestRender();
3944
3869
  }
3945
- this.scrollAccel ??= new LinearScrollAccel;
3946
- this.wrapper = new BoxRenderable(ctx, {
3947
- flexDirection: "column",
3948
- flexGrow: 1,
3949
- ...wrapperOptions,
3950
- id: `scroll-box-wrapper-${this.internalId}`
3951
- });
3952
- super.add(this.wrapper);
3953
- this.viewport = new BoxRenderable(ctx, {
3954
- flexDirection: "column",
3955
- flexGrow: 1,
3956
- overflow: "hidden",
3957
- onSizeChange: () => {
3958
- this.recalculateBarProps();
3959
- },
3960
- ...viewportOptions,
3961
- id: `scroll-box-viewport-${this.internalId}`
3962
- });
3963
- this.wrapper.add(this.viewport);
3964
- this.content = new ContentRenderable(ctx, this.viewport, {
3965
- alignSelf: "flex-start",
3966
- flexShrink: 0,
3967
- ...scrollX ? { minWidth: "100%" } : { minWidth: "100%", maxWidth: "100%" },
3968
- ...scrollY ? { minHeight: "100%" } : { minHeight: "100%", maxHeight: "100%" },
3969
- onSizeChange: () => {
3970
- this.recalculateBarProps();
3971
- },
3972
- ...contentOptions,
3973
- id: `scroll-box-content-${this.internalId}`
3974
- });
3975
- this.viewport.add(this.content);
3976
- this.verticalScrollBar = new ScrollBarRenderable(ctx, {
3977
- ...scrollbarOptions,
3978
- ...verticalScrollbarOptions,
3979
- arrowOptions: {
3980
- ...scrollbarOptions?.arrowOptions,
3981
- ...verticalScrollbarOptions?.arrowOptions
3982
- },
3983
- id: `scroll-box-vertical-scrollbar-${this.internalId}`,
3984
- orientation: "vertical",
3985
- onChange: (position) => {
3986
- this.content.translateY = -position;
3987
- this._hasManualScroll = true;
3988
- this.updateStickyState();
3989
- }
3990
- });
3991
- super.add(this.verticalScrollBar);
3992
- this.horizontalScrollBar = new ScrollBarRenderable(ctx, {
3993
- ...scrollbarOptions,
3994
- ...horizontalScrollbarOptions,
3995
- arrowOptions: {
3996
- ...scrollbarOptions?.arrowOptions,
3997
- ...horizontalScrollbarOptions?.arrowOptions
3998
- },
3999
- id: `scroll-box-horizontal-scrollbar-${this.internalId}`,
4000
- orientation: "horizontal",
4001
- onChange: (position) => {
4002
- this.content.translateX = -position;
4003
- this._hasManualScroll = true;
4004
- this.updateStickyState();
4005
- }
4006
- });
4007
- this.wrapper.add(this.horizontalScrollBar);
4008
- this.recalculateBarProps();
4009
- if (stickyStart && stickyScroll) {
4010
- this.applyStickyStart(stickyStart);
3870
+ }
3871
+ set selectedBackgroundColor(value) {
3872
+ const newColor = parseColor(value ?? this._defaultOptions.selectedBackgroundColor);
3873
+ if (this._selectedBackgroundColor !== newColor) {
3874
+ this._selectedBackgroundColor = newColor;
3875
+ this.requestRender();
4011
3876
  }
4012
- this.selectionListener = () => {
4013
- const selection = this._ctx.getSelection();
4014
- if (!selection || !selection.isSelecting) {
4015
- this.stopAutoScroll();
4016
- }
4017
- };
4018
- this._ctx.on("selection", this.selectionListener);
4019
3877
  }
4020
- onUpdate(deltaTime) {
4021
- this.handleAutoScroll(deltaTime);
3878
+ set selectedTextColor(value) {
3879
+ const newColor = parseColor(value ?? this._defaultOptions.selectedTextColor);
3880
+ if (this._selectedTextColor !== newColor) {
3881
+ this._selectedTextColor = newColor;
3882
+ this.requestRender();
3883
+ }
4022
3884
  }
4023
- scrollBy(delta, unit = "absolute") {
4024
- if (typeof delta === "number") {
4025
- this.verticalScrollBar.scrollBy(delta, unit);
4026
- } else {
4027
- this.verticalScrollBar.scrollBy(delta.y, unit);
4028
- this.horizontalScrollBar.scrollBy(delta.x, unit);
3885
+ set descriptionColor(value) {
3886
+ const newColor = parseColor(value ?? this._defaultOptions.descriptionColor);
3887
+ if (this._descriptionColor !== newColor) {
3888
+ this._descriptionColor = newColor;
3889
+ this.requestRender();
4029
3890
  }
4030
- this._hasManualScroll = true;
4031
3891
  }
4032
- scrollTo(position) {
4033
- if (typeof position === "number") {
4034
- this.scrollTop = position;
4035
- } else {
4036
- this.scrollTop = position.y;
4037
- this.scrollLeft = position.x;
3892
+ set selectedDescriptionColor(value) {
3893
+ const newColor = parseColor(value ?? this._defaultOptions.selectedDescriptionColor);
3894
+ if (this._selectedDescriptionColor !== newColor) {
3895
+ this._selectedDescriptionColor = newColor;
3896
+ this.requestRender();
4038
3897
  }
4039
3898
  }
4040
- add(obj, index) {
4041
- return this.content.add(obj, index);
3899
+ set font(font) {
3900
+ this._font = font;
3901
+ this.fontHeight = measureText({ text: "A", font: this._font }).height;
3902
+ this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
3903
+ this.linesPerItem += this._itemSpacing;
3904
+ this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
3905
+ this.updateScrollOffset();
3906
+ this.requestRender();
4042
3907
  }
4043
- insertBefore(obj, anchor) {
4044
- return this.content.insertBefore(obj, anchor);
3908
+ set itemSpacing(spacing) {
3909
+ this._itemSpacing = spacing;
3910
+ this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
3911
+ this.linesPerItem += this._itemSpacing;
3912
+ this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
3913
+ this.updateScrollOffset();
3914
+ this.requestRender();
4045
3915
  }
4046
- remove(id) {
4047
- this.content.remove(id);
3916
+ set fastScrollStep(step) {
3917
+ this._fastScrollStep = step;
4048
3918
  }
4049
- getChildren() {
4050
- return this.content.getChildren();
3919
+ }
3920
+ // src/renderables/TabSelect.ts
3921
+ var TabSelectRenderableEvents;
3922
+ ((TabSelectRenderableEvents2) => {
3923
+ TabSelectRenderableEvents2["SELECTION_CHANGED"] = "selectionChanged";
3924
+ TabSelectRenderableEvents2["ITEM_SELECTED"] = "itemSelected";
3925
+ })(TabSelectRenderableEvents ||= {});
3926
+ function calculateDynamicHeight(showUnderline, showDescription) {
3927
+ let height = 1;
3928
+ if (showUnderline) {
3929
+ height += 1;
4051
3930
  }
4052
- onMouseEvent(event) {
4053
- if (event.type === "scroll") {
4054
- let dir = event.scroll?.direction;
4055
- if (event.modifiers.shift)
4056
- dir = dir === "up" ? "left" : dir === "down" ? "right" : dir === "right" ? "down" : "up";
4057
- const baseDelta = event.scroll?.delta ?? 0;
4058
- const now = Date.now();
4059
- const multiplier = this.scrollAccel.tick(now);
4060
- if (dir === "up") {
4061
- this.scrollTop -= baseDelta * multiplier;
4062
- } else if (dir === "down") {
4063
- this.scrollTop += baseDelta * multiplier;
4064
- } else if (dir === "left") {
4065
- this.scrollLeft -= baseDelta * multiplier;
4066
- } else if (dir === "right") {
4067
- this.scrollLeft += baseDelta * multiplier;
3931
+ if (showDescription) {
3932
+ height += 1;
3933
+ }
3934
+ return height;
3935
+ }
3936
+
3937
+ class TabSelectRenderable extends Renderable {
3938
+ _focusable = true;
3939
+ _options = [];
3940
+ selectedIndex = 0;
3941
+ scrollOffset = 0;
3942
+ _tabWidth;
3943
+ maxVisibleTabs;
3944
+ _backgroundColor;
3945
+ _textColor;
3946
+ _focusedBackgroundColor;
3947
+ _focusedTextColor;
3948
+ _selectedBackgroundColor;
3949
+ _selectedTextColor;
3950
+ _selectedDescriptionColor;
3951
+ _showScrollArrows;
3952
+ _showDescription;
3953
+ _showUnderline;
3954
+ _wrapSelection;
3955
+ constructor(ctx, options) {
3956
+ const calculatedHeight = calculateDynamicHeight(options.showUnderline ?? true, options.showDescription ?? true);
3957
+ super(ctx, { ...options, height: calculatedHeight, buffered: true });
3958
+ this._backgroundColor = parseColor(options.backgroundColor || "transparent");
3959
+ this._textColor = parseColor(options.textColor || "#FFFFFF");
3960
+ this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || options.backgroundColor || "#1a1a1a");
3961
+ this._focusedTextColor = parseColor(options.focusedTextColor || options.textColor || "#FFFFFF");
3962
+ this._options = options.options || [];
3963
+ this._tabWidth = options.tabWidth || 20;
3964
+ this._showDescription = options.showDescription ?? true;
3965
+ this._showUnderline = options.showUnderline ?? true;
3966
+ this._showScrollArrows = options.showScrollArrows ?? true;
3967
+ this._wrapSelection = options.wrapSelection ?? false;
3968
+ this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
3969
+ this._selectedBackgroundColor = parseColor(options.selectedBackgroundColor || "#334455");
3970
+ this._selectedTextColor = parseColor(options.selectedTextColor || "#FFFF00");
3971
+ this._selectedDescriptionColor = parseColor(options.selectedDescriptionColor || "#CCCCCC");
3972
+ }
3973
+ calculateDynamicHeight() {
3974
+ return calculateDynamicHeight(this._showUnderline, this._showDescription);
3975
+ }
3976
+ renderSelf(buffer, deltaTime) {
3977
+ if (!this.visible || !this.frameBuffer)
3978
+ return;
3979
+ if (this.isDirty) {
3980
+ this.refreshFrameBuffer();
3981
+ }
3982
+ }
3983
+ refreshFrameBuffer() {
3984
+ if (!this.frameBuffer || this._options.length === 0)
3985
+ return;
3986
+ const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
3987
+ this.frameBuffer.clear(bgColor);
3988
+ const contentX = 0;
3989
+ const contentY = 0;
3990
+ const contentWidth = this.width;
3991
+ const contentHeight = this.height;
3992
+ const visibleOptions = this._options.slice(this.scrollOffset, this.scrollOffset + this.maxVisibleTabs);
3993
+ for (let i = 0;i < visibleOptions.length; i++) {
3994
+ const actualIndex = this.scrollOffset + i;
3995
+ const option = visibleOptions[i];
3996
+ const isSelected = actualIndex === this.selectedIndex;
3997
+ const tabX = contentX + i * this._tabWidth;
3998
+ if (tabX >= contentX + contentWidth)
3999
+ break;
4000
+ const actualTabWidth = Math.min(this._tabWidth, contentWidth - i * this._tabWidth);
4001
+ if (isSelected) {
4002
+ this.frameBuffer.fillRect(tabX, contentY, actualTabWidth, 1, this._selectedBackgroundColor);
4003
+ }
4004
+ const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
4005
+ const nameColor = isSelected ? this._selectedTextColor : baseTextColor;
4006
+ const nameContent = this.truncateText(option.name, actualTabWidth - 2);
4007
+ this.frameBuffer.drawText(nameContent, tabX + 1, contentY, nameColor);
4008
+ if (isSelected && this._showUnderline && contentHeight >= 2) {
4009
+ const underlineY = contentY + 1;
4010
+ const underlineBg = isSelected ? this._selectedBackgroundColor : bgColor;
4011
+ this.frameBuffer.drawText("\u25AC".repeat(actualTabWidth), tabX, underlineY, nameColor, underlineBg);
4068
4012
  }
4069
- this._hasManualScroll = true;
4070
- }
4071
- if (event.type === "drag" && event.isSelecting) {
4072
- this.updateAutoScroll(event.x, event.y);
4073
- } else if (event.type === "up") {
4074
- this.stopAutoScroll();
4075
4013
  }
4076
- }
4077
- handleKeyPress(key) {
4078
- if (this.verticalScrollBar.handleKeyPress(key)) {
4079
- this._hasManualScroll = true;
4080
- this.scrollAccel.reset();
4081
- return true;
4014
+ if (this._showDescription && contentHeight >= (this._showUnderline ? 3 : 2)) {
4015
+ const selectedOption = this.getSelectedOption();
4016
+ if (selectedOption) {
4017
+ const descriptionY = contentY + (this._showUnderline ? 2 : 1);
4018
+ const descColor = this._selectedDescriptionColor;
4019
+ const descContent = this.truncateText(selectedOption.description, contentWidth - 2);
4020
+ this.frameBuffer.drawText(descContent, contentX + 1, descriptionY, descColor);
4021
+ }
4082
4022
  }
4083
- if (this.horizontalScrollBar.handleKeyPress(key)) {
4084
- this._hasManualScroll = true;
4085
- this.scrollAccel.reset();
4086
- return true;
4023
+ if (this._showScrollArrows && this._options.length > this.maxVisibleTabs) {
4024
+ this.renderScrollArrowsToFrameBuffer(contentX, contentY, contentWidth, contentHeight);
4087
4025
  }
4088
- return false;
4089
4026
  }
4090
- startAutoScroll(mouseX, mouseY) {
4091
- this.stopAutoScroll();
4092
- this.autoScrollMouseX = mouseX;
4093
- this.autoScrollMouseY = mouseY;
4094
- this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
4095
- this.isAutoScrolling = true;
4096
- if (!this.live) {
4097
- this.live = true;
4098
- }
4027
+ truncateText(text, maxWidth) {
4028
+ if (text.length <= maxWidth)
4029
+ return text;
4030
+ return text.substring(0, Math.max(0, maxWidth - 1)) + "\u2026";
4099
4031
  }
4100
- updateAutoScroll(mouseX, mouseY) {
4101
- this.autoScrollMouseX = mouseX;
4102
- this.autoScrollMouseY = mouseY;
4103
- this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
4104
- const scrollX = this.getAutoScrollDirectionX(mouseX);
4105
- const scrollY = this.getAutoScrollDirectionY(mouseY);
4106
- if (scrollX === 0 && scrollY === 0) {
4107
- this.stopAutoScroll();
4108
- } else if (!this.isAutoScrolling) {
4109
- this.startAutoScroll(mouseX, mouseY);
4032
+ renderScrollArrowsToFrameBuffer(contentX, contentY, contentWidth, contentHeight) {
4033
+ if (!this.frameBuffer)
4034
+ return;
4035
+ const hasMoreLeft = this.scrollOffset > 0;
4036
+ const hasMoreRight = this.scrollOffset + this.maxVisibleTabs < this._options.length;
4037
+ if (hasMoreLeft) {
4038
+ this.frameBuffer.drawText("\u2039", contentX, contentY, parseColor("#AAAAAA"));
4110
4039
  }
4111
- }
4112
- stopAutoScroll() {
4113
- const wasAutoScrolling = this.isAutoScrolling;
4114
- this.isAutoScrolling = false;
4115
- this.autoScrollAccumulatorX = 0;
4116
- this.autoScrollAccumulatorY = 0;
4117
- if (wasAutoScrolling && !this.hasOtherLiveReasons()) {
4118
- this.live = false;
4040
+ if (hasMoreRight) {
4041
+ this.frameBuffer.drawText("\u203A", contentX + contentWidth - 1, contentY, parseColor("#AAAAAA"));
4119
4042
  }
4120
4043
  }
4121
- hasOtherLiveReasons() {
4122
- return false;
4044
+ setOptions(options) {
4045
+ this._options = options;
4046
+ this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
4047
+ this.updateScrollOffset();
4048
+ this.requestRender();
4123
4049
  }
4124
- handleAutoScroll(deltaTime) {
4125
- if (!this.isAutoScrolling)
4050
+ getSelectedOption() {
4051
+ return this._options[this.selectedIndex] || null;
4052
+ }
4053
+ getSelectedIndex() {
4054
+ return this.selectedIndex;
4055
+ }
4056
+ moveLeft() {
4057
+ if (this.selectedIndex > 0) {
4058
+ this.selectedIndex--;
4059
+ } else if (this._wrapSelection && this._options.length > 0) {
4060
+ this.selectedIndex = this._options.length - 1;
4061
+ } else {
4126
4062
  return;
4127
- const scrollX = this.getAutoScrollDirectionX(this.autoScrollMouseX);
4128
- const scrollY = this.getAutoScrollDirectionY(this.autoScrollMouseY);
4129
- const scrollAmount = this.cachedAutoScrollSpeed * (deltaTime / 1000);
4130
- let scrolled = false;
4131
- if (scrollX !== 0) {
4132
- this.autoScrollAccumulatorX += scrollX * scrollAmount;
4133
- const integerScrollX = Math.trunc(this.autoScrollAccumulatorX);
4134
- if (integerScrollX !== 0) {
4135
- this.scrollLeft += integerScrollX;
4136
- this.autoScrollAccumulatorX -= integerScrollX;
4137
- scrolled = true;
4138
- }
4139
- }
4140
- if (scrollY !== 0) {
4141
- this.autoScrollAccumulatorY += scrollY * scrollAmount;
4142
- const integerScrollY = Math.trunc(this.autoScrollAccumulatorY);
4143
- if (integerScrollY !== 0) {
4144
- this.scrollTop += integerScrollY;
4145
- this.autoScrollAccumulatorY -= integerScrollY;
4146
- scrolled = true;
4147
- }
4148
- }
4149
- if (scrolled) {
4150
- this._ctx.requestSelectionUpdate();
4151
4063
  }
4152
- if (scrollX === 0 && scrollY === 0) {
4153
- this.stopAutoScroll();
4064
+ this.updateScrollOffset();
4065
+ this.requestRender();
4066
+ this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
4067
+ }
4068
+ moveRight() {
4069
+ if (this.selectedIndex < this._options.length - 1) {
4070
+ this.selectedIndex++;
4071
+ } else if (this._wrapSelection && this._options.length > 0) {
4072
+ this.selectedIndex = 0;
4073
+ } else {
4074
+ return;
4154
4075
  }
4076
+ this.updateScrollOffset();
4077
+ this.requestRender();
4078
+ this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
4155
4079
  }
4156
- getAutoScrollDirectionX(mouseX) {
4157
- const relativeX = mouseX - this.x;
4158
- const distToLeft = relativeX;
4159
- const distToRight = this.width - relativeX;
4160
- if (distToLeft <= this.autoScrollThresholdHorizontal) {
4161
- return this.scrollLeft > 0 ? -1 : 0;
4162
- } else if (distToRight <= this.autoScrollThresholdHorizontal) {
4163
- const maxScrollLeft = this.scrollWidth - this.viewport.width;
4164
- return this.scrollLeft < maxScrollLeft ? 1 : 0;
4080
+ selectCurrent() {
4081
+ const selected = this.getSelectedOption();
4082
+ if (selected) {
4083
+ this.emit("itemSelected" /* ITEM_SELECTED */, this.selectedIndex, selected);
4165
4084
  }
4166
- return 0;
4167
4085
  }
4168
- getAutoScrollDirectionY(mouseY) {
4169
- const relativeY = mouseY - this.y;
4170
- const distToTop = relativeY;
4171
- const distToBottom = this.height - relativeY;
4172
- if (distToTop <= this.autoScrollThresholdVertical) {
4173
- return this.scrollTop > 0 ? -1 : 0;
4174
- } else if (distToBottom <= this.autoScrollThresholdVertical) {
4175
- const maxScrollTop = this.scrollHeight - this.viewport.height;
4176
- return this.scrollTop < maxScrollTop ? 1 : 0;
4086
+ setSelectedIndex(index) {
4087
+ if (index >= 0 && index < this._options.length) {
4088
+ this.selectedIndex = index;
4089
+ this.updateScrollOffset();
4090
+ this.requestRender();
4091
+ this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
4177
4092
  }
4178
- return 0;
4179
4093
  }
4180
- getAutoScrollSpeed(mouseX, mouseY) {
4181
- const relativeX = mouseX - this.x;
4182
- const relativeY = mouseY - this.y;
4183
- const distToLeft = relativeX;
4184
- const distToRight = this.width - relativeX;
4185
- const distToTop = relativeY;
4186
- const distToBottom = this.height - relativeY;
4187
- const minDistance = Math.min(distToLeft, distToRight, distToTop, distToBottom);
4188
- if (minDistance <= 1) {
4189
- return this.autoScrollSpeedFast;
4190
- } else if (minDistance <= 2) {
4191
- return this.autoScrollSpeedMedium;
4192
- } else {
4193
- return this.autoScrollSpeedSlow;
4094
+ updateScrollOffset() {
4095
+ const halfVisible = Math.floor(this.maxVisibleTabs / 2);
4096
+ const newScrollOffset = Math.max(0, Math.min(this.selectedIndex - halfVisible, this._options.length - this.maxVisibleTabs));
4097
+ if (newScrollOffset !== this.scrollOffset) {
4098
+ this.scrollOffset = newScrollOffset;
4099
+ this.requestRender();
4194
4100
  }
4195
4101
  }
4196
- recalculateBarProps() {
4197
- this.verticalScrollBar.scrollSize = this.content.height;
4198
- this.verticalScrollBar.viewportSize = this.viewport.height;
4199
- this.horizontalScrollBar.scrollSize = this.content.width;
4200
- this.horizontalScrollBar.viewportSize = this.viewport.width;
4201
- if (this._stickyScroll) {
4202
- const newMaxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
4203
- const newMaxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
4204
- if (this._stickyStart && !this._hasManualScroll) {
4205
- this.applyStickyStart(this._stickyStart);
4206
- } else {
4207
- if (this._stickyScrollTop) {
4208
- this.scrollTop = 0;
4209
- } else if (this._stickyScrollBottom && newMaxScrollTop > 0) {
4210
- this.scrollTop = newMaxScrollTop;
4211
- }
4212
- if (this._stickyScrollLeft) {
4213
- this.scrollLeft = 0;
4214
- } else if (this._stickyScrollRight && newMaxScrollLeft > 0) {
4215
- this.scrollLeft = newMaxScrollLeft;
4216
- }
4217
- }
4102
+ onResize(width, height) {
4103
+ this.maxVisibleTabs = Math.max(1, Math.floor(width / this._tabWidth));
4104
+ this.updateScrollOffset();
4105
+ this.requestRender();
4106
+ }
4107
+ setTabWidth(tabWidth) {
4108
+ if (this._tabWidth === tabWidth)
4109
+ return;
4110
+ this._tabWidth = tabWidth;
4111
+ this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
4112
+ this.updateScrollOffset();
4113
+ this.requestRender();
4114
+ }
4115
+ getTabWidth() {
4116
+ return this._tabWidth;
4117
+ }
4118
+ handleKeyPress(key) {
4119
+ const keyName = typeof key === "string" ? key : key.name;
4120
+ switch (keyName) {
4121
+ case "left":
4122
+ case "[":
4123
+ this.moveLeft();
4124
+ return true;
4125
+ case "right":
4126
+ case "]":
4127
+ this.moveRight();
4128
+ return true;
4129
+ case "return":
4130
+ case "enter":
4131
+ this.selectCurrent();
4132
+ return true;
4218
4133
  }
4219
- process.nextTick(() => {
4220
- this.requestRender();
4221
- });
4134
+ return false;
4222
4135
  }
4223
- set rootOptions(options) {
4224
- Object.assign(this, options);
4136
+ get options() {
4137
+ return this._options;
4138
+ }
4139
+ set options(options) {
4140
+ this._options = options;
4141
+ this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
4142
+ this.updateScrollOffset();
4225
4143
  this.requestRender();
4226
4144
  }
4227
- set wrapperOptions(options) {
4228
- Object.assign(this.wrapper, options);
4145
+ set backgroundColor(color) {
4146
+ this._backgroundColor = parseColor(color);
4229
4147
  this.requestRender();
4230
4148
  }
4231
- set viewportOptions(options) {
4232
- Object.assign(this.viewport, options);
4149
+ set textColor(color) {
4150
+ this._textColor = parseColor(color);
4233
4151
  this.requestRender();
4234
4152
  }
4235
- set contentOptions(options) {
4236
- Object.assign(this.content, options);
4153
+ set focusedBackgroundColor(color) {
4154
+ this._focusedBackgroundColor = parseColor(color);
4237
4155
  this.requestRender();
4238
4156
  }
4239
- set scrollbarOptions(options) {
4240
- Object.assign(this.verticalScrollBar, options);
4241
- Object.assign(this.horizontalScrollBar, options);
4157
+ set focusedTextColor(color) {
4158
+ this._focusedTextColor = parseColor(color);
4242
4159
  this.requestRender();
4243
4160
  }
4244
- set verticalScrollbarOptions(options) {
4245
- Object.assign(this.verticalScrollBar, options);
4161
+ set selectedBackgroundColor(color) {
4162
+ this._selectedBackgroundColor = parseColor(color);
4246
4163
  this.requestRender();
4247
4164
  }
4248
- set horizontalScrollbarOptions(options) {
4249
- Object.assign(this.horizontalScrollBar, options);
4165
+ set selectedTextColor(color) {
4166
+ this._selectedTextColor = parseColor(color);
4250
4167
  this.requestRender();
4251
4168
  }
4252
- destroySelf() {
4253
- if (this.selectionListener) {
4254
- this._ctx.off("selection", this.selectionListener);
4255
- this.selectionListener = undefined;
4169
+ set selectedDescriptionColor(color) {
4170
+ this._selectedDescriptionColor = parseColor(color);
4171
+ this.requestRender();
4172
+ }
4173
+ get showDescription() {
4174
+ return this._showDescription;
4175
+ }
4176
+ set showDescription(show) {
4177
+ if (this._showDescription !== show) {
4178
+ this._showDescription = show;
4179
+ const newHeight = this.calculateDynamicHeight();
4180
+ this.height = newHeight;
4181
+ this.requestRender();
4256
4182
  }
4257
- super.destroySelf();
4258
4183
  }
4259
- }
4260
- // src/renderables/composition/constructs.ts
4261
- function Generic(props, ...children) {
4262
- return h(VRenderable, props || {}, ...children);
4263
- }
4264
- function Box(props, ...children) {
4265
- return h(BoxRenderable, props || {}, ...children);
4266
- }
4267
- function Text(props, ...children) {
4268
- return h(TextRenderable, props || {}, ...children);
4269
- }
4270
- function ASCIIFont(props, ...children) {
4271
- return h(ASCIIFontRenderable, props || {}, ...children);
4272
- }
4273
- function Input(props, ...children) {
4274
- return h(InputRenderable, props || {}, ...children);
4275
- }
4276
- function Select(props, ...children) {
4277
- return h(SelectRenderable, props || {}, ...children);
4278
- }
4279
- function TabSelect(props, ...children) {
4280
- return h(TabSelectRenderable, props || {}, ...children);
4281
- }
4282
- function FrameBuffer(props, ...children) {
4283
- return h(FrameBufferRenderable, props, ...children);
4284
- }
4285
- function StyledText2(props, ...children) {
4286
- const styledProps = props;
4287
- const textNodeOptions = {
4288
- ...styledProps,
4289
- attributes: styledProps?.attributes ?? 0
4290
- };
4291
- const textNode = new TextNodeRenderable(textNodeOptions);
4292
- for (const child of children) {
4293
- textNode.add(child);
4184
+ get showUnderline() {
4185
+ return this._showUnderline;
4186
+ }
4187
+ set showUnderline(show) {
4188
+ if (this._showUnderline !== show) {
4189
+ this._showUnderline = show;
4190
+ const newHeight = this.calculateDynamicHeight();
4191
+ this.height = newHeight;
4192
+ this.requestRender();
4193
+ }
4194
+ }
4195
+ get showScrollArrows() {
4196
+ return this._showScrollArrows;
4197
+ }
4198
+ set showScrollArrows(show) {
4199
+ if (this._showScrollArrows !== show) {
4200
+ this._showScrollArrows = show;
4201
+ this.requestRender();
4202
+ }
4203
+ }
4204
+ get wrapSelection() {
4205
+ return this._wrapSelection;
4206
+ }
4207
+ set wrapSelection(wrap) {
4208
+ this._wrapSelection = wrap;
4209
+ }
4210
+ get tabWidth() {
4211
+ return this._tabWidth;
4212
+ }
4213
+ set tabWidth(tabWidth) {
4214
+ if (this._tabWidth === tabWidth)
4215
+ return;
4216
+ this._tabWidth = tabWidth;
4217
+ this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
4218
+ this.updateScrollOffset();
4219
+ this.requestRender();
4294
4220
  }
4295
- return textNode;
4296
4221
  }
4297
- var vstyles = {
4298
- bold: (...children) => StyledText2({ attributes: TextAttributes.BOLD }, ...children),
4299
- italic: (...children) => StyledText2({ attributes: TextAttributes.ITALIC }, ...children),
4300
- underline: (...children) => StyledText2({ attributes: TextAttributes.UNDERLINE }, ...children),
4301
- dim: (...children) => StyledText2({ attributes: TextAttributes.DIM }, ...children),
4302
- blink: (...children) => StyledText2({ attributes: TextAttributes.BLINK }, ...children),
4303
- inverse: (...children) => StyledText2({ attributes: TextAttributes.INVERSE }, ...children),
4304
- hidden: (...children) => StyledText2({ attributes: TextAttributes.HIDDEN }, ...children),
4305
- strikethrough: (...children) => StyledText2({ attributes: TextAttributes.STRIKETHROUGH }, ...children),
4306
- boldItalic: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC }, ...children),
4307
- boldUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.UNDERLINE }, ...children),
4308
- italicUnderline: (...children) => StyledText2({ attributes: TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
4309
- boldItalicUnderline: (...children) => StyledText2({ attributes: TextAttributes.BOLD | TextAttributes.ITALIC | TextAttributes.UNDERLINE }, ...children),
4310
- color: (color, ...children) => StyledText2({ fg: color }, ...children),
4311
- bgColor: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
4312
- fg: (color, ...children) => StyledText2({ fg: color }, ...children),
4313
- bg: (bgColor, ...children) => StyledText2({ bg: bgColor }, ...children),
4314
- styled: (attributes = 0, ...children) => StyledText2({ attributes }, ...children)
4315
- };
4316
- // src/renderables/composition/VRenderable.ts
4317
- class VRenderable extends Renderable {
4318
- options;
4222
+ // src/renderables/Text.ts
4223
+ class TextRenderable extends TextBufferRenderable {
4224
+ _text;
4225
+ _hasManualStyledText = false;
4226
+ rootTextNode;
4227
+ _contentDefaultOptions = {
4228
+ content: ""
4229
+ };
4319
4230
  constructor(ctx, options) {
4320
4231
  super(ctx, options);
4321
- this.options = options;
4232
+ const content = options.content ?? this._contentDefaultOptions.content;
4233
+ const styledText = typeof content === "string" ? stringToStyledText(content) : content;
4234
+ this._text = styledText;
4235
+ this._hasManualStyledText = options.content !== undefined && content !== "";
4236
+ this.rootTextNode = new RootTextNodeRenderable(ctx, {
4237
+ id: `${this.id}-root`,
4238
+ fg: this._defaultFg,
4239
+ bg: this._defaultBg,
4240
+ attributes: this._defaultAttributes
4241
+ }, this);
4242
+ this.updateTextBuffer(styledText);
4322
4243
  }
4323
- renderSelf(buffer, deltaTime) {
4324
- if (this.options.render) {
4325
- this.options.render.call(this.options, buffer, deltaTime, this);
4244
+ updateTextBuffer(styledText) {
4245
+ this.textBuffer.setStyledText(styledText);
4246
+ this.clearChunks(styledText);
4247
+ }
4248
+ clearChunks(styledText) {}
4249
+ get content() {
4250
+ return this._text;
4251
+ }
4252
+ get chunks() {
4253
+ return this._text.chunks;
4254
+ }
4255
+ get textNode() {
4256
+ return this.rootTextNode;
4257
+ }
4258
+ set content(value) {
4259
+ this._hasManualStyledText = true;
4260
+ const styledText = typeof value === "string" ? stringToStyledText(value) : value;
4261
+ if (this._text !== styledText) {
4262
+ this._text = styledText;
4263
+ this.updateTextBuffer(styledText);
4264
+ this.updateTextInfo();
4265
+ }
4266
+ }
4267
+ insertChunk(chunk, index) {
4268
+ super.insertChunk(chunk, index);
4269
+ this.clearChunks(this._text);
4270
+ }
4271
+ removeChunkByObject(chunk) {
4272
+ const index = this._text.chunks.indexOf(chunk);
4273
+ if (index === -1)
4274
+ return;
4275
+ super.removeChunk(index);
4276
+ this.clearChunks(this._text);
4277
+ }
4278
+ replaceChunkByObject(chunk, oldChunk) {
4279
+ const index = this._text.chunks.indexOf(oldChunk);
4280
+ if (index === -1)
4281
+ return;
4282
+ super.replaceChunk(index, chunk);
4283
+ this.clearChunks(this._text);
4284
+ }
4285
+ updateTextFromNodes() {
4286
+ if (this.rootTextNode.isDirty && !this._hasManualStyledText) {
4287
+ const chunks = this.rootTextNode.gatherWithInheritedStyle({
4288
+ fg: this._defaultFg,
4289
+ bg: this._defaultBg,
4290
+ attributes: this._defaultAttributes
4291
+ });
4292
+ this.textBuffer.setStyledText(new StyledText(chunks));
4293
+ this.refreshLocalSelection();
4294
+ this.yogaNode.markDirty();
4326
4295
  }
4327
4296
  }
4297
+ add(obj, index) {
4298
+ return this.rootTextNode.add(obj, index);
4299
+ }
4300
+ remove(id) {
4301
+ this.rootTextNode.remove(id);
4302
+ }
4303
+ insertBefore(obj, anchor) {
4304
+ this.rootTextNode.insertBefore(obj, anchor);
4305
+ return this.rootTextNode.children.indexOf(obj);
4306
+ }
4307
+ getTextChildren() {
4308
+ return this.rootTextNode.getChildren();
4309
+ }
4310
+ clear() {
4311
+ this.rootTextNode.clear();
4312
+ const emptyStyledText = stringToStyledText("");
4313
+ this._text = emptyStyledText;
4314
+ this.updateTextBuffer(emptyStyledText);
4315
+ this.updateTextInfo();
4316
+ this.requestRender();
4317
+ }
4318
+ onLifecyclePass = () => {
4319
+ this.updateTextFromNodes();
4320
+ };
4321
+ onFgChanged(newColor) {
4322
+ this.rootTextNode.fg = newColor;
4323
+ }
4324
+ onBgChanged(newColor) {
4325
+ this.rootTextNode.bg = newColor;
4326
+ }
4327
+ onAttributesChanged(newAttributes) {
4328
+ this.rootTextNode.attributes = newAttributes;
4329
+ }
4330
+ destroy() {
4331
+ this.rootTextNode.children.length = 0;
4332
+ super.destroy();
4333
+ }
4328
4334
  }
4329
4335
  export {
4330
4336
  yellow,
@@ -4450,6 +4456,7 @@ export {
4450
4456
  TabSelect,
4451
4457
  SyntaxStyle,
4452
4458
  StyledText,
4459
+ SliderRenderable,
4453
4460
  Selection,
4454
4461
  SelectRenderableEvents,
4455
4462
  SelectRenderable,
@@ -4458,6 +4465,7 @@ export {
4458
4465
  ScrollBarRenderable,
4459
4466
  RootTextNodeRenderable,
4460
4467
  RootRenderable,
4468
+ RendererControlState,
4461
4469
  RenderableEvents,
4462
4470
  Renderable,
4463
4471
  RGBA,
@@ -4500,5 +4508,5 @@ export {
4500
4508
  ASCIIFont
4501
4509
  };
4502
4510
 
4503
- //# debugId=F1FAFC796A264ABA64756E2164756E21
4511
+ //# debugId=6F0864CECE58138064756E2164756E21
4504
4512
  //# sourceMappingURL=index.js.map