1ch 0.1.0 → 0.3.0

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/dist/index.js CHANGED
@@ -14,6 +14,23 @@ function preserveTableTag(source, target) {
14
14
  if (toTaggedLine(source)[TABLE_TAG]) toTaggedLine(target)[TABLE_TAG] = true;
15
15
  }
16
16
 
17
+ // src/termui/scroll-tag.ts
18
+ var SCROLL_KEY = /* @__PURE__ */ Symbol("scrollRegion");
19
+ function toTagged(line) {
20
+ return line;
21
+ }
22
+ function markScrollRegion(line, meta) {
23
+ toTagged(line)[SCROLL_KEY] = meta;
24
+ return line;
25
+ }
26
+ function getScrollRegionMeta(line) {
27
+ return toTagged(line)[SCROLL_KEY];
28
+ }
29
+ function preserveScrollTag(source, target) {
30
+ const meta = toTagged(source)[SCROLL_KEY];
31
+ if (meta) toTagged(target)[SCROLL_KEY] = meta;
32
+ }
33
+
17
34
  // src/termui/base.ts
18
35
  function normalizedWidth(width) {
19
36
  if (!Number.isFinite(width)) return 0;
@@ -90,6 +107,11 @@ function padLine(line, w) {
90
107
  if (used === w) return line;
91
108
  return [...line, { text: " ".repeat(w - used), noSelect: true }];
92
109
  }
110
+ function truncate(text, width, ellipsis = "\u2026") {
111
+ if (text.length <= width) return text;
112
+ if (width <= ellipsis.length) return ellipsis.slice(0, width);
113
+ return text.slice(0, width - ellipsis.length) + ellipsis;
114
+ }
93
115
  function wrapText(text, w) {
94
116
  if (w <= 0) return [""];
95
117
  if (text.length <= w) return [text];
@@ -113,20 +135,27 @@ function wrapText(text, w) {
113
135
  }
114
136
 
115
137
  // src/termui/layout.ts
116
- function borderTop(w, title, color) {
138
+ function borderTop(w, opts) {
117
139
  const width = normalizedWidth(w);
118
140
  if (width <= 0) return [];
119
- if (width === 1) return [{ text: "\u250C", color, noSelect: true }];
141
+ if (width === 1) return [{ text: "\u250C", color: opts?.color, noSelect: true }];
142
+ const { title, color, action } = opts ?? {};
143
+ const innerWidth = width - 2;
144
+ const titleBudget = innerWidth - (action ? action.label.length + 2 : 0) - 4;
145
+ let titleStr = "";
120
146
  if (title) {
121
- const maxTitle = Math.max(0, width - 6);
122
- const safeTitle = title.length > maxTitle ? title.slice(0, maxTitle) : title;
123
- const titleStr = "\u2500 " + safeTitle + " ";
124
- const remaining = Math.max(0, width - 2 - titleStr.length);
125
- return [
126
- { text: "\u250C" + titleStr + hl(remaining) + "\u2510", color, noSelect: true }
127
- ];
147
+ const safeTitle = title.length > titleBudget ? title.slice(0, Math.max(0, titleBudget)) : title;
148
+ titleStr = safeTitle.length > 0 ? "\u2500 " + safeTitle + " " : "";
128
149
  }
129
- return [{ text: "\u250C" + hl(width - 2) + "\u2510", color, noSelect: true }];
150
+ const fillWidth = Math.max(0, innerWidth - titleStr.length - (action ? action.label.length + 2 : 0));
151
+ if (!action) {
152
+ return [{ text: "\u250C" + titleStr + hl(fillWidth) + "\u2510", color, noSelect: true }];
153
+ }
154
+ return [
155
+ { text: "\u250C" + titleStr + hl(fillWidth), color, noSelect: true },
156
+ { text: " " + action.label + " ", color: action.color ?? color, onClick: action.onClick, noSelect: true },
157
+ { text: "\u2510", color, noSelect: true }
158
+ ];
130
159
  }
131
160
  function borderBottom(w, color) {
132
161
  const width = normalizedWidth(w);
@@ -148,7 +177,7 @@ function box(content, opts) {
148
177
  const inner = width - 2;
149
178
  const contentBlock = content(inner);
150
179
  const result = [];
151
- result.push(borderTop(width, opts?.title, bc));
180
+ result.push(borderTop(width, { title: opts?.title, color: bc, action: opts?.action }));
152
181
  for (const line of contentBlock) {
153
182
  result.push([
154
183
  { text: "\u2502", color: bc, noSelect: true },
@@ -205,11 +234,128 @@ function vstack(...children) {
205
234
  return result;
206
235
  };
207
236
  }
208
- function separator(color) {
209
- return (w) => [[{ text: hl(w), color, noSelect: true }]];
237
+ function separator(color, char) {
238
+ return (w) => {
239
+ const c = char ?? "\u2500";
240
+ return [[{ text: c.repeat(normalizedWidth(w)), color, noSelect: true }]];
241
+ };
242
+ }
243
+ function blank(count = 1) {
244
+ return (w) => {
245
+ const line = [" ".repeat(normalizedWidth(w))];
246
+ const result = [];
247
+ for (let i = 0; i < count; i++) result.push(line);
248
+ return result;
249
+ };
250
+ }
251
+ function divider(label, opts) {
252
+ return (w) => {
253
+ const width = normalizedWidth(w);
254
+ const c = opts?.char ?? "\u2500";
255
+ const padded = ` ${label} `;
256
+ if (padded.length >= width) {
257
+ const lc = opts?.labelColor ?? opts?.color;
258
+ return [[lc ? { text: padded.slice(0, width), color: lc } : padded.slice(0, width)]];
259
+ }
260
+ const remaining = width - padded.length;
261
+ const leftLen = Math.floor(remaining / 2);
262
+ const rightLen = remaining - leftLen;
263
+ const lineColor = opts?.color;
264
+ const labelColor = opts?.labelColor ?? opts?.color;
265
+ const line = [
266
+ { text: c.repeat(leftLen), color: lineColor, noSelect: true },
267
+ labelColor ? { text: padded, color: labelColor } : padded,
268
+ { text: c.repeat(rightLen), color: lineColor, noSelect: true }
269
+ ];
270
+ return [line];
271
+ };
272
+ }
273
+ function grid(cells, opts) {
274
+ return (w) => {
275
+ const width = normalizedWidth(w);
276
+ const cols = Math.max(1, opts.cols);
277
+ const gap = normalizedWidth(opts.gap ?? 1);
278
+ const rowGap = normalizedWidth(opts.rowGap ?? 0);
279
+ const totalGap = gap * (cols - 1);
280
+ const cellWidth = Math.max(1, Math.floor((width - totalGap) / cols));
281
+ const gapStr = gap > 0 ? " ".repeat(gap) : "";
282
+ const result = [];
283
+ for (let rowStart = 0; rowStart < cells.length; rowStart += cols) {
284
+ if (rowStart > 0 && rowGap > 0) {
285
+ const emptyLine = [" ".repeat(width)];
286
+ for (let g = 0; g < rowGap; g++) result.push(emptyLine);
287
+ }
288
+ const rowCells = cells.slice(rowStart, rowStart + cols);
289
+ const blocks = rowCells.map((cell) => cell(cellWidth));
290
+ const maxLines = Math.max(0, ...blocks.map((b) => b.length));
291
+ for (let i = 0; i < maxLines; i++) {
292
+ const line = [];
293
+ for (let j = 0; j < cols; j++) {
294
+ if (j > 0 && gapStr) line.push(gapStr);
295
+ const block = blocks[j];
296
+ if (block && block[i]) {
297
+ line.push(...padLine(block[i], cellWidth));
298
+ } else {
299
+ line.push(" ".repeat(cellWidth));
300
+ }
301
+ }
302
+ result.push(padLine(line, width));
303
+ }
304
+ }
305
+ return result;
306
+ };
307
+ }
308
+ function scroll(content, opts) {
309
+ return (w) => {
310
+ const width = normalizedWidth(w);
311
+ const maxH = normalizedWidth(opts.maxHeight);
312
+ const contentW = Math.max(1, width - 1);
313
+ const fullBlock = content(contentW);
314
+ const totalLines = fullBlock.length;
315
+ if (totalLines <= maxH) {
316
+ return fullBlock.map((line) => padLine(line, width));
317
+ }
318
+ const maxOffset = Math.max(0, totalLines - maxH);
319
+ const offset = Math.min(maxOffset, Math.max(0, opts.offset ?? 0));
320
+ const visible = fullBlock.slice(offset, offset + maxH);
321
+ const thumbHeight = Math.max(1, Math.round(maxH / totalLines * maxH));
322
+ const thumbStart = Math.round(offset / maxOffset * (maxH - thumbHeight));
323
+ const result = [];
324
+ for (let i = 0; i < visible.length; i++) {
325
+ const inThumb = i >= thumbStart && i < thumbStart + thumbHeight;
326
+ const scrollChar = inThumb ? "\u2588" : "\u2591";
327
+ const line = [
328
+ ...padLine(visible[i], contentW),
329
+ { text: scrollChar, color: opts.scrollbarColor, noSelect: true }
330
+ ];
331
+ if (opts.onScroll) {
332
+ markScrollRegion(line, { onScroll: opts.onScroll, totalLines, maxHeight: maxH, offset });
333
+ }
334
+ result.push(line);
335
+ }
336
+ return result;
337
+ };
210
338
  }
211
- function blank() {
212
- return (w) => [[" ".repeat(normalizedWidth(w))]];
339
+ function padding(content, opts) {
340
+ return (w) => {
341
+ const width = normalizedWidth(w);
342
+ const t = normalizedWidth(opts?.top ?? opts?.y ?? 0);
343
+ const b = normalizedWidth(opts?.bottom ?? opts?.y ?? 0);
344
+ const l = normalizedWidth(opts?.left ?? opts?.x ?? 0);
345
+ const r = normalizedWidth(opts?.right ?? opts?.x ?? 0);
346
+ const innerW = Math.max(1, width - l - r);
347
+ const block = content(innerW);
348
+ const result = [];
349
+ const emptyLine = [" ".repeat(width)];
350
+ for (let i = 0; i < t; i++) result.push(emptyLine);
351
+ const leftPad = l > 0 ? " ".repeat(l) : "";
352
+ for (const line of block) {
353
+ const padded = leftPad ? [leftPad, ...line] : line;
354
+ result.push(padLine(padded, width));
355
+ }
356
+ for (let i = 0; i < b; i++) result.push(emptyLine);
357
+ return result;
358
+ };
213
359
  }
214
360
 
215
361
  // src/termui/table.ts
@@ -311,7 +457,8 @@ function table(columns, data, opts) {
311
457
  const text = padStr + pad(cellLine, innerW, col.align === "right") + padStr;
312
458
  const color = typeof col.color === "function" ? col.color(row[col.key], row) : col.color;
313
459
  const dim = typeof col.dim === "function" ? col.dim(row[col.key], row) : col.dim;
314
- dataLine.push({ text, color: color || void 0, dim: dim || void 0, cell: i });
460
+ const cellClick = typeof col.onClick === "function" ? () => col.onClick(row[col.key], row) : void 0;
461
+ dataLine.push({ text, color: color || void 0, dim: dim || void 0, onClick: cellClick, cell: i });
315
462
  dataLine.push({ text: "\u2502", color: bc, noSelect: true });
316
463
  }
317
464
  result.push(dataLine);
@@ -384,6 +531,92 @@ function badge(label, opts) {
384
531
  onClick: opts?.onClick
385
532
  };
386
533
  }
534
+ function kv(pairs, opts) {
535
+ return (w) => {
536
+ const width = normalizedWidth(w);
537
+ const sep = opts?.separator ?? " ";
538
+ const maxKey = opts?.keyWidth ?? Math.max(0, ...pairs.map((p) => p.key.length));
539
+ const result = [];
540
+ for (const pair of pairs) {
541
+ const keyText = pad(pair.key, maxKey);
542
+ const keyColor = pair.keyColor ?? opts?.keyColor;
543
+ const keySeg = keyColor ? { text: keyText, color: keyColor } : keyText;
544
+ if (typeof pair.value === "string") {
545
+ const valColor = pair.valueColor ?? opts?.valueColor;
546
+ const valSeg = valColor || pair.onClick ? { text: pair.value, color: valColor, onClick: pair.onClick } : pair.value;
547
+ result.push(padLine([keySeg, sep, valSeg], width));
548
+ } else {
549
+ result.push(padLine([keySeg, sep, ...pair.value], width));
550
+ }
551
+ }
552
+ return result;
553
+ };
554
+ }
555
+ function callout(content, opts) {
556
+ return (w) => {
557
+ const width = normalizedWidth(w);
558
+ const accent = opts?.accent ?? opts?.color;
559
+ const innerWidth = Math.max(1, width - 2);
560
+ let block;
561
+ if (typeof content === "string") {
562
+ const wrapped = wrapText(content, innerWidth);
563
+ block = wrapped.map((line) => {
564
+ const seg = opts?.color ? [{ text: line, color: opts.color }] : [line];
565
+ return padLine(seg, innerWidth);
566
+ });
567
+ } else {
568
+ block = content(innerWidth);
569
+ }
570
+ const result = [];
571
+ if (opts?.label) {
572
+ const labelLine = [
573
+ { text: "\u2590", color: accent, noSelect: true },
574
+ " ",
575
+ { text: opts.label, color: accent, bold: true }
576
+ ];
577
+ result.push(padLine(labelLine, width));
578
+ }
579
+ for (const line of block) {
580
+ result.push([
581
+ { text: "\u2590", color: accent, noSelect: true },
582
+ " ",
583
+ ...padLine(line, innerWidth)
584
+ ]);
585
+ }
586
+ return result;
587
+ };
588
+ }
589
+ function checkbox(checked, label, opts) {
590
+ return (w) => {
591
+ const width = normalizedWidth(w);
592
+ const check = checked ? "\u2713" : " ";
593
+ const boxSeg = {
594
+ text: `[${check}]`,
595
+ color: opts?.checkColor ?? opts?.color,
596
+ onClick: opts?.onClick
597
+ };
598
+ const labelSeg = opts?.onClick ? { text: ` ${label}`, color: opts.color, onClick: opts.onClick } : opts?.color ? { text: ` ${label}`, color: opts.color } : ` ${label}`;
599
+ return [padLine([boxSeg, labelSeg], width)];
600
+ };
601
+ }
602
+ function inputField(value, opts) {
603
+ return (w) => {
604
+ const totalWidth = normalizedWidth(opts?.width ?? w);
605
+ const innerWidth = Math.max(1, totalWidth - 2);
606
+ const bc = opts?.borderColor;
607
+ const focused = opts?.focused ?? false;
608
+ const display = value || opts?.placeholder || "";
609
+ const isPlaceholder = !value && opts?.placeholder;
610
+ const textTruncated = display.length > innerWidth ? display.slice(display.length - innerWidth) : display;
611
+ const padded = pad(textTruncated + (focused ? "\u2588" : ""), innerWidth);
612
+ const line = [
613
+ { text: focused ? "\u2502" : "[", color: bc, noSelect: true },
614
+ isPlaceholder ? { text: padded, dim: true, color: opts?.color, onClick: opts?.onClick } : opts?.color || opts?.onClick ? { text: padded, color: opts?.color, onClick: opts?.onClick } : padded,
615
+ { text: focused ? "\u2502" : "]", color: bc, noSelect: true }
616
+ ];
617
+ return [padLine(line, w)];
618
+ };
619
+ }
387
620
 
388
621
  // src/termui/json.ts
389
622
  function fromJsonNode(node) {
@@ -447,7 +680,7 @@ function tree(data, opts) {
447
680
  const line = [];
448
681
  if (prefix) line.push({ text: prefix, color: opts?.branchColor ?? opts?.color, noSelect: true });
449
682
  line.push({ text: connector, color: opts?.branchColor ?? opts?.color, noSelect: true });
450
- line.push({ text: node.label, color: hasKids ? opts?.color : opts?.leafColor ?? opts?.color });
683
+ line.push({ text: node.label, color: hasKids ? opts?.color : opts?.leafColor ?? opts?.color, onClick: node.onClick });
451
684
  result.push(padLine(line, w));
452
685
  if (hasKids) {
453
686
  walk(node.children, prefix + (last ? " " : "\u2502 "));
@@ -464,9 +697,13 @@ function list(items, opts) {
464
697
  const ordered = opts?.ordered ?? false;
465
698
  const digits = String(items.length).length;
466
699
  for (let i = 0; i < items.length; i++) {
700
+ const item = items[i];
701
+ const text = typeof item === "string" ? item : item.text;
702
+ const itemColor = typeof item === "string" ? opts?.color : item.color ?? opts?.color;
703
+ const itemClick = typeof item === "string" ? void 0 : item.onClick;
467
704
  const prefix = ordered ? ` ${String(i + 1).padStart(digits, " ")}. ` : " * ";
468
705
  const indent = " ".repeat(prefix.length);
469
- const wrapped = wrapText(items[i], w - prefix.length);
706
+ const wrapped = wrapText(text, w - prefix.length);
470
707
  for (let j = 0; j < wrapped.length; j++) {
471
708
  const line = [];
472
709
  if (j === 0) {
@@ -474,7 +711,7 @@ function list(items, opts) {
474
711
  } else {
475
712
  line.push(indent);
476
713
  }
477
- line.push(opts?.color ? { text: wrapped[j], color: opts.color } : wrapped[j]);
714
+ line.push(itemColor || itemClick ? { text: wrapped[j], color: itemColor, onClick: itemClick } : wrapped[j]);
478
715
  result.push(padLine(line, w));
479
716
  }
480
717
  }
@@ -482,6 +719,9 @@ function list(items, opts) {
482
719
  };
483
720
  }
484
721
 
722
+ // src/termui/code-diff.ts
723
+ import { createPatch } from "diff";
724
+
485
725
  // src/tokenize.ts
486
726
  function rules(pairs) {
487
727
  return pairs.map(([pattern, type]) => ({
@@ -1023,6 +1263,7 @@ function tokenColor(type, theme) {
1023
1263
  function code(source, opts) {
1024
1264
  const t = opts?.theme ?? dark;
1025
1265
  const tokens = tokenize(source, opts?.language);
1266
+ const copyAction = opts?.copyable !== false ? { label: "cp", onClick: () => navigator.clipboard.writeText(source), color: t.semantic.frameMuted } : void 0;
1026
1267
  return box(
1027
1268
  lines(
1028
1269
  tokens.length > 0 ? tokens.map(
@@ -1031,11 +1272,12 @@ function code(source, opts) {
1031
1272
  ),
1032
1273
  {
1033
1274
  title: opts?.title ?? (opts?.language ? `code:${opts.language}` : "code"),
1034
- borderColor: opts?.borderColor ?? t.markdown.codeBorder
1275
+ borderColor: opts?.borderColor ?? t.markdown.codeBorder,
1276
+ action: opts?.action ?? copyAction
1035
1277
  }
1036
1278
  );
1037
1279
  }
1038
- function diff(value, opts) {
1280
+ function unifiedDiff(value, opts) {
1039
1281
  const t = opts?.theme ?? dark;
1040
1282
  const addC = opts?.addColor ?? t.semantic.success;
1041
1283
  const remC = opts?.removeColor ?? t.semantic.danger;
@@ -1049,6 +1291,72 @@ function diff(value, opts) {
1049
1291
  });
1050
1292
  });
1051
1293
  }
1294
+ function computeDiff(before, after, opts) {
1295
+ const patch = createPatch("", before, after, "", "", { context: opts?.context ?? 3 });
1296
+ const hunks = patch.replace(/\r\n/g, "\n").split("\n").filter(
1297
+ (l) => !l.startsWith("Index:") && !l.startsWith("====") && !l.startsWith("---") && !l.startsWith("+++")
1298
+ ).join("\n").trim();
1299
+ return unifiedDiff(hunks, opts);
1300
+ }
1301
+
1302
+ // src/termui/chart.ts
1303
+ function fmtNum(n) {
1304
+ if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(1) + "k";
1305
+ if (Number.isInteger(n)) return String(n);
1306
+ return n.toFixed(1);
1307
+ }
1308
+ function chart(data, opts) {
1309
+ return (w) => {
1310
+ const width = normalizedWidth(w);
1311
+ const height = opts?.height ?? 8;
1312
+ const color = opts?.color;
1313
+ const axisColor = opts?.axisColor;
1314
+ if (data.length === 0 || width < 4 || height < 2) return [padLine([], width)];
1315
+ const min = Math.min(...data);
1316
+ const max = Math.max(...data);
1317
+ const range = max - min || 1;
1318
+ const minLabel = fmtNum(min);
1319
+ const maxLabel = fmtNum(max);
1320
+ const labelW = Math.max(minLabel.length, maxLabel.length);
1321
+ const axisW = labelW + 2;
1322
+ const plotW = Math.max(1, width - axisW);
1323
+ const points = [];
1324
+ if (data.length === 1) {
1325
+ for (let i = 0; i < plotW; i++) points.push(data[0]);
1326
+ } else {
1327
+ for (let i = 0; i < plotW; i++) {
1328
+ const idx = i / (plotW - 1) * (data.length - 1);
1329
+ const lo = Math.floor(idx);
1330
+ const hi = Math.min(lo + 1, data.length - 1);
1331
+ const t = idx - lo;
1332
+ points.push(data[lo] * (1 - t) + data[hi] * t);
1333
+ }
1334
+ }
1335
+ const rows = points.map((v) => Math.round((v - min) / range * (height - 1)));
1336
+ const result = [];
1337
+ for (let row = height - 1; row >= 0; row--) {
1338
+ const line = [];
1339
+ let label = "";
1340
+ if (row === height - 1) label = maxLabel;
1341
+ else if (row === 0) label = minLabel;
1342
+ line.push({ text: label.padStart(labelW) + " \u2502", color: axisColor, noSelect: true });
1343
+ let text = "";
1344
+ for (let col = 0; col < plotW; col++) {
1345
+ const val = rows[col];
1346
+ if (val === row) text += "\u2584";
1347
+ else if (opts?.fill && val > row) text += "\u2588";
1348
+ else text += " ";
1349
+ }
1350
+ line.push(color ? { text, color } : text);
1351
+ result.push(padLine(line, width));
1352
+ }
1353
+ const xAxis = [
1354
+ { text: " ".repeat(labelW) + " \u2514" + "\u2500".repeat(plotW), color: axisColor, noSelect: true }
1355
+ ];
1356
+ result.push(padLine(xAxis, width));
1357
+ return result;
1358
+ };
1359
+ }
1052
1360
 
1053
1361
  // src/document/common.ts
1054
1362
  function headingLevelKey(level) {
@@ -1386,16 +1694,23 @@ function renderMarkdownBlock(block, theme, overrides) {
1386
1694
  };
1387
1695
  case "separator":
1388
1696
  return separator(overrides?.separator ?? md.separator);
1389
- case "code":
1697
+ case "code": {
1698
+ const codeSource = block.lines.join("\n");
1390
1699
  return box(
1391
1700
  lines(
1392
1701
  block.lines.length > 0 ? block.lines.map((line) => [{ text: line || " ", color: overrides?.codeText ?? md.codeText }]) : [[{ text: " ", color: overrides?.codeEmpty ?? md.codeEmpty, dim: true }]]
1393
1702
  ),
1394
1703
  {
1395
1704
  title: block.language ? `code:${block.language}` : "code",
1396
- borderColor: overrides?.codeBorder ?? md.codeBorder
1705
+ borderColor: overrides?.codeBorder ?? md.codeBorder,
1706
+ action: {
1707
+ label: "cp",
1708
+ onClick: () => navigator.clipboard.writeText(codeSource),
1709
+ color: theme.semantic.frameMuted
1710
+ }
1397
1711
  }
1398
1712
  );
1713
+ }
1399
1714
  }
1400
1715
  }
1401
1716
  function fromMarkdown(markdown, opts) {
@@ -2048,9 +2363,6 @@ function asNumber(value) {
2048
2363
  function asBoolean(value) {
2049
2364
  return typeof value === "boolean" ? value : void 0;
2050
2365
  }
2051
- function asStringArray(value) {
2052
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
2053
- }
2054
2366
  function asNumberArray(value) {
2055
2367
  if (!Array.isArray(value)) return void 0;
2056
2368
  const out = [];
@@ -2068,6 +2380,13 @@ function asRecordArray(value) {
2068
2380
  }
2069
2381
  return out;
2070
2382
  }
2383
+ function asBoxAction(value) {
2384
+ if (!isRecord4(value)) return void 0;
2385
+ const label = asString(value.label);
2386
+ const onClick = typeof value.onClick === "function" ? value.onClick : void 0;
2387
+ if (!label || !onClick) return void 0;
2388
+ return { label, onClick, color: asString(value.color) };
2389
+ }
2071
2390
  function isMarkdownOverrides(value) {
2072
2391
  if (!isRecord4(value)) return false;
2073
2392
  if (value.headingMarker !== void 0 && typeof value.headingMarker !== "string") return false;
@@ -2309,40 +2628,51 @@ function compileRaw(p, children) {
2309
2628
  ])
2310
2629
  );
2311
2630
  }
2312
- function compileLine(children) {
2631
+ function compileSpanSegments(children, inherited) {
2313
2632
  const segments = [];
2314
2633
  for (const child of children) {
2315
2634
  if (child.type === "t-text") {
2316
- segments.push(child.text ?? "");
2635
+ const text = child.text ?? "";
2636
+ if (inherited && (inherited.color || inherited.bg || inherited.bold !== void 0 || inherited.dim !== void 0 || inherited.inverted !== void 0 || inherited.blink !== void 0 || inherited.onClick)) {
2637
+ segments.push({ text, ...inherited });
2638
+ } else {
2639
+ segments.push(text);
2640
+ }
2317
2641
  continue;
2318
2642
  }
2319
2643
  if (child.type === "t-span") {
2320
2644
  const cp = child.props;
2321
- const text = asString(cp.text) ?? extractTermText(child.children);
2322
- const color = asString(cp.color);
2323
- const bg = asString(cp.bg);
2324
- const bold = asBoolean(cp.bold);
2325
- const dim = asBoolean(cp.dim);
2326
- const inverted = asBoolean(cp.inverted);
2327
- const blink = asBoolean(cp.blink);
2328
- if (color || bg || bold !== void 0 || dim !== void 0 || inverted !== void 0 || blink !== void 0) {
2329
- segments.push({
2330
- text,
2331
- color,
2332
- bg,
2333
- bold,
2334
- dim,
2335
- inverted,
2336
- blink
2337
- });
2645
+ const color = asString(cp.color) ?? inherited?.color;
2646
+ const bg = asString(cp.bg) ?? inherited?.bg;
2647
+ const bold = asBoolean(cp.bold) ?? inherited?.bold;
2648
+ const dim = asBoolean(cp.dim) ?? inherited?.dim;
2649
+ const inverted = asBoolean(cp.inverted) ?? inherited?.inverted;
2650
+ const blink = asBoolean(cp.blink) ?? inherited?.blink;
2651
+ const onClick = (typeof cp.onClick === "function" ? cp.onClick : void 0) ?? inherited?.onClick;
2652
+ const style = { color, bg, bold, dim, inverted, blink, onClick };
2653
+ if (cp.text) {
2654
+ const text = asString(cp.text);
2655
+ if (color || bg || bold !== void 0 || dim !== void 0 || inverted !== void 0 || blink !== void 0 || onClick) {
2656
+ segments.push({ text, ...style });
2657
+ } else {
2658
+ segments.push(text);
2659
+ }
2660
+ } else if (child.children.length > 0) {
2661
+ segments.push(...compileSpanSegments(child.children, style));
2662
+ }
2663
+ } else {
2664
+ const text = extractTermText(child.children);
2665
+ if (inherited && (inherited.color || inherited.bg || inherited.bold !== void 0 || inherited.dim !== void 0 || inherited.inverted !== void 0 || inherited.blink !== void 0 || inherited.onClick)) {
2666
+ segments.push({ text, ...inherited });
2338
2667
  } else {
2339
2668
  segments.push(text);
2340
2669
  }
2341
- } else {
2342
- segments.push(extractTermText(child.children));
2343
2670
  }
2344
2671
  }
2345
- return lines([segments]);
2672
+ return segments;
2673
+ }
2674
+ function compileLine(children) {
2675
+ return lines([compileSpanSegments(children)]);
2346
2676
  }
2347
2677
  function compileTermNode(node, theme, mdOverrides) {
2348
2678
  if (node.type === "t-text") {
@@ -2368,13 +2698,25 @@ function compileTermNode(node, theme, mdOverrides) {
2368
2698
  const gap = asNumber(p.gap) ?? 0;
2369
2699
  return box(stackWithGap(ch, gap), {
2370
2700
  title: asString(p.title),
2371
- borderColor: asString(p.borderColor) ?? theme.semantic.border
2701
+ borderColor: asString(p.borderColor) ?? theme.semantic.border,
2702
+ action: asBoxAction(p.action)
2703
+ });
2704
+ }
2705
+ case "t-pad": {
2706
+ const ch = compileChildLayouts(node.children, theme, mdOverrides);
2707
+ return padding(ch.length > 0 ? stackWithGap(ch, 0) : blank(), {
2708
+ x: asNumber(p.x),
2709
+ y: asNumber(p.y),
2710
+ top: asNumber(p.top),
2711
+ bottom: asNumber(p.bottom),
2712
+ left: asNumber(p.left),
2713
+ right: asNumber(p.right)
2372
2714
  });
2373
2715
  }
2374
2716
  case "t-separator":
2375
- return separator(asString(p.color) ?? theme.semantic.border);
2717
+ return separator(asString(p.color) ?? theme.semantic.border, asString(p.char));
2376
2718
  case "t-blank":
2377
- return blank();
2719
+ return blank(asNumber(p.lines) ?? 1);
2378
2720
  case "t-raw":
2379
2721
  return compileRaw(p, node.children);
2380
2722
  case "t-line":
@@ -2447,10 +2789,20 @@ function compileTermNode(node, theme, mdOverrides) {
2447
2789
  language: asString(p.language),
2448
2790
  title: asString(p.title),
2449
2791
  borderColor: asString(p.borderColor) ?? theme.markdown.codeBorder,
2450
- theme
2792
+ theme,
2793
+ copyable: asBoolean(p.copyable),
2794
+ action: asBoxAction(p.action)
2451
2795
  });
2452
2796
  case "t-diff":
2453
- return diff(asString(p.value) ?? "", {
2797
+ return unifiedDiff(asString(p.value) ?? "", {
2798
+ addColor: asString(p.addColor),
2799
+ removeColor: asString(p.removeColor),
2800
+ metaColor: asString(p.metaColor),
2801
+ theme
2802
+ });
2803
+ case "t-diff-compute":
2804
+ return computeDiff(asString(p.before) ?? "", asString(p.after) ?? "", {
2805
+ context: asNumber(p.context),
2454
2806
  addColor: asString(p.addColor),
2455
2807
  removeColor: asString(p.removeColor),
2456
2808
  metaColor: asString(p.metaColor),
@@ -2490,7 +2842,7 @@ function compileTermNode(node, theme, mdOverrides) {
2490
2842
  };
2491
2843
  }
2492
2844
  case "t-list":
2493
- return list(asStringArray(p.items), {
2845
+ return list(Array.isArray(p.items) ? p.items : [], {
2494
2846
  ordered: asBoolean(p.ordered),
2495
2847
  bulletColor: asString(p.bulletColor),
2496
2848
  color: asString(p.color)
@@ -2499,6 +2851,8 @@ function compileTermNode(node, theme, mdOverrides) {
2499
2851
  const label = asString(p.label) ?? "";
2500
2852
  const btnColor = asString(p.color);
2501
2853
  const btnBg = asString(p.bg);
2854
+ const btnBold = asBoolean(p.bold);
2855
+ const btnDim = asBoolean(p.dim);
2502
2856
  const onClick = typeof p.onClick === "function" ? p.onClick : void 0;
2503
2857
  const resolvedBtnColor = btnColor ?? theme.components.button.fg;
2504
2858
  const resolvedBtnBg = btnBg ?? theme.components.button.bg;
@@ -2508,11 +2862,90 @@ function compileTermNode(node, theme, mdOverrides) {
2508
2862
  const rp = Math.max(0, w - lp - decorated.length);
2509
2863
  const segments = [];
2510
2864
  if (lp > 0) segments.push(" ".repeat(lp));
2511
- segments.push({ text: decorated, color: resolvedBtnColor, bg: resolvedBtnBg, onClick });
2865
+ segments.push({ text: decorated, color: resolvedBtnColor, bg: resolvedBtnBg, bold: btnBold, dim: btnDim, onClick });
2512
2866
  if (rp > 0) segments.push(" ".repeat(rp));
2513
2867
  return [segments];
2514
2868
  };
2515
2869
  }
2870
+ case "t-badge": {
2871
+ const label = asString(p.label) ?? "";
2872
+ const badgeColor = asString(p.color) ?? theme.components.badge.fg;
2873
+ const badgeBg = asString(p.bg) ?? theme.components.badge.bg;
2874
+ const onClick = typeof p.onClick === "function" ? p.onClick : void 0;
2875
+ return lines([[{ text: ` ${label} `, color: badgeColor, bg: badgeBg, onClick }]]);
2876
+ }
2877
+ case "t-divider":
2878
+ return divider(asString(p.label) ?? "", {
2879
+ color: asString(p.color) ?? theme.semantic.border,
2880
+ char: asString(p.char),
2881
+ labelColor: asString(p.labelColor)
2882
+ });
2883
+ case "t-kv":
2884
+ return kv(Array.isArray(p.pairs) ? p.pairs : [], {
2885
+ separator: asString(p.separator),
2886
+ keyColor: asString(p.keyColor),
2887
+ valueColor: asString(p.valueColor),
2888
+ keyWidth: asNumber(p.keyWidth)
2889
+ });
2890
+ case "t-callout": {
2891
+ const ch = compileChildLayouts(node.children, theme, mdOverrides);
2892
+ const text = asString(p.text);
2893
+ const content = text ?? (ch.length > 0 ? stackWithGap(ch, 0) : blank());
2894
+ return callout(content, {
2895
+ color: asString(p.color),
2896
+ accent: asString(p.accent),
2897
+ label: asString(p.label)
2898
+ });
2899
+ }
2900
+ case "t-chart":
2901
+ return chart(
2902
+ Array.isArray(p.data) ? p.data.filter((v) => typeof v === "number") : [],
2903
+ {
2904
+ height: asNumber(p.height),
2905
+ color: asString(p.color),
2906
+ axisColor: asString(p.axisColor) ?? theme.semantic.border,
2907
+ fill: asBoolean(p.fill)
2908
+ }
2909
+ );
2910
+ case "t-scroll": {
2911
+ const ch = compileChildLayouts(node.children, theme, mdOverrides);
2912
+ const content = ch.length > 0 ? stackWithGap(ch, 0) : blank();
2913
+ const onScroll = typeof p.onScroll === "function" ? p.onScroll : void 0;
2914
+ return scroll(content, {
2915
+ maxHeight: asNumber(p.maxHeight) ?? 10,
2916
+ offset: asNumber(p.offset),
2917
+ scrollbarColor: asString(p.scrollbarColor) ?? theme.semantic.border,
2918
+ onScroll
2919
+ });
2920
+ }
2921
+ case "t-grid": {
2922
+ const ch = compileChildLayouts(node.children, theme, mdOverrides);
2923
+ return grid(ch, {
2924
+ cols: asNumber(p.cols) ?? 2,
2925
+ gap: asNumber(p.gap),
2926
+ rowGap: asNumber(p.rowGap)
2927
+ });
2928
+ }
2929
+ case "t-checkbox": {
2930
+ const checked = asBoolean(p.checked) ?? false;
2931
+ const label = asString(p.label) ?? "";
2932
+ const onChange = typeof p.onChange === "function" ? p.onChange : void 0;
2933
+ const onClick = typeof p.onClick === "function" ? p.onClick : onChange ? () => onChange(!checked) : void 0;
2934
+ return checkbox(checked, label, {
2935
+ color: asString(p.color),
2936
+ checkColor: asString(p.checkColor),
2937
+ onClick
2938
+ });
2939
+ }
2940
+ case "t-input":
2941
+ return inputField(asString(p.value) ?? "", {
2942
+ placeholder: asString(p.placeholder),
2943
+ width: asNumber(p.width),
2944
+ focused: asBoolean(p.focused),
2945
+ color: asString(p.color),
2946
+ borderColor: asString(p.borderColor) ?? theme.semantic.border,
2947
+ onClick: typeof p.onClick === "function" ? p.onClick : void 0
2948
+ });
2516
2949
  default: {
2517
2950
  const ch = compileChildLayouts(node.children, theme, mdOverrides);
2518
2951
  return ch.length > 0 ? stackWithGap(ch, 0) : null;
@@ -2745,6 +3178,7 @@ function reconcileLine(prevLine, nextLine) {
2745
3178
  reconciled[i] = prevSeg !== void 0 && sameSegment(prevSeg, nextSeg) ? prevSeg : nextSeg;
2746
3179
  }
2747
3180
  preserveTableTag(nextLine, reconciled);
3181
+ preserveScrollTag(nextLine, reconciled);
2748
3182
  return reconciled;
2749
3183
  }
2750
3184
  function reconcileBlock(prevBlock, nextBlock) {
@@ -2869,31 +3303,71 @@ var TermTable = memo(
2869
3303
  return true;
2870
3304
  }
2871
3305
  );
3306
+ var TermScrollRegion = memo(function TermScrollRegion2({ lines: lines2, meta, theme }) {
3307
+ return /* @__PURE__ */ jsx2(
3308
+ "div",
3309
+ {
3310
+ className: "termui-scroll-region",
3311
+ onWheel: (e) => {
3312
+ e.preventDefault();
3313
+ const step = e.deltaY > 0 ? 1 : -1;
3314
+ const maxOffset = Math.max(0, meta.totalLines - meta.maxHeight);
3315
+ const newOffset = Math.min(maxOffset, Math.max(0, meta.offset + step));
3316
+ if (newOffset !== meta.offset) meta.onScroll(newOffset);
3317
+ },
3318
+ children: lines2.map((line, i) => /* @__PURE__ */ jsx2(TermLine, { line, theme }, i))
3319
+ }
3320
+ );
3321
+ });
2872
3322
  var TermBlock = memo(
2873
3323
  function TermBlock2({ block, theme }) {
2874
3324
  const elements = [];
2875
3325
  let tableLines = null;
2876
3326
  let tableStart = 0;
3327
+ let scrollLines = null;
3328
+ let scrollStart = 0;
3329
+ let scrollMeta = null;
2877
3330
  const flushTable = () => {
2878
3331
  if (tableLines) {
2879
3332
  elements.push(/* @__PURE__ */ jsx2(TermTable, { lines: tableLines, theme }, `t${tableStart}`));
2880
3333
  tableLines = null;
2881
3334
  }
2882
3335
  };
3336
+ const flushScroll = () => {
3337
+ if (scrollLines && scrollMeta) {
3338
+ elements.push(
3339
+ /* @__PURE__ */ jsx2(TermScrollRegion, { lines: scrollLines, meta: scrollMeta, theme }, `s${scrollStart}`)
3340
+ );
3341
+ scrollLines = null;
3342
+ scrollMeta = null;
3343
+ }
3344
+ };
2883
3345
  for (let i = 0; i < block.length; i++) {
2884
3346
  const line = block[i];
3347
+ const sm = getScrollRegionMeta(line);
2885
3348
  if (isTableLine(line)) {
3349
+ flushScroll();
2886
3350
  if (!tableLines) {
2887
3351
  tableLines = [];
2888
3352
  tableStart = i;
2889
3353
  }
2890
3354
  tableLines.push(line);
3355
+ } else if (sm) {
3356
+ flushTable();
3357
+ if (!scrollLines) {
3358
+ scrollLines = [];
3359
+ scrollStart = i;
3360
+ scrollMeta = sm;
3361
+ }
3362
+ scrollLines.push(line);
2891
3363
  } else {
2892
3364
  flushTable();
3365
+ flushScroll();
2893
3366
  elements.push(/* @__PURE__ */ jsx2(TermLine, { line, theme }, i));
2894
3367
  }
2895
3368
  }
2896
3369
  flushTable();
3370
+ flushScroll();
2897
3371
  return /* @__PURE__ */ jsx2(Fragment, { children: elements });
2898
3372
  },
2899
3373
  (prev, next) => prev.block === next.block && prev.theme === next.theme
@@ -3014,21 +3488,24 @@ function TermDocument({
3014
3488
  }
3015
3489
 
3016
3490
  // src/term-react.tsx
3017
- import { createElement } from "react";
3491
+ import { createElement, useCallback, useEffect as useEffect3, useState as useState2 } from "react";
3018
3492
  function TVStack({ children, gap }) {
3019
3493
  return createElement("t-vstack", { gap }, children);
3020
3494
  }
3021
3495
  function THStack({ children, gap, widths }) {
3022
3496
  return createElement("t-hstack", { gap, widths }, children);
3023
3497
  }
3024
- function TBox({ children, title, borderColor, gap }) {
3025
- return createElement("t-box", { title, borderColor, gap }, children);
3498
+ function TBox({ children, title, borderColor, gap, action }) {
3499
+ return createElement("t-box", { title, borderColor, gap, action }, children);
3500
+ }
3501
+ function TSeparator({ color, char }) {
3502
+ return createElement("t-separator", { color, char });
3026
3503
  }
3027
- function TSeparator({ color }) {
3028
- return createElement("t-separator", { color });
3504
+ function TBlank({ lines: lines2 } = {}) {
3505
+ return createElement("t-blank", { lines: lines2 });
3029
3506
  }
3030
- function TBlank() {
3031
- return createElement("t-blank", {});
3507
+ function TBadge({ label, color, bg, onClick }) {
3508
+ return createElement("t-badge", { label, color, bg, onClick });
3032
3509
  }
3033
3510
  function TRaw({ children, text, color, bg, bold, dim, blink, inverted, onClick }) {
3034
3511
  return createElement(
@@ -3090,25 +3567,31 @@ function THtml({ value, themeOverrides }) {
3090
3567
  function TLine({ children }) {
3091
3568
  return createElement("t-line", {}, children);
3092
3569
  }
3093
- function TSpan({ children, text, color, bg, bold, dim, inverted, blink }) {
3570
+ function TSpan({ children, text, color, bg, bold, dim, inverted, blink, onClick }) {
3094
3571
  return createElement(
3095
3572
  "t-span",
3096
- { text, color, bg, bold, dim, inverted, blink },
3573
+ { text, color, bg, bold, dim, inverted, blink, onClick },
3097
3574
  children
3098
3575
  );
3099
3576
  }
3577
+ function TPad({ children, x, y, top, bottom, left, right }) {
3578
+ return createElement("t-pad", { x, y, top, bottom, left, right }, children);
3579
+ }
3100
3580
  function TLayout({ layout }) {
3101
3581
  return createElement("t-layout", { layout });
3102
3582
  }
3103
3583
  function TTree({ data, color, branchColor, leafColor }) {
3104
3584
  return createElement("t-tree", { data, color, branchColor, leafColor });
3105
3585
  }
3106
- function TCode({ code: code2, language, title, borderColor }) {
3107
- return createElement("t-code", { code: code2, language, title, borderColor });
3586
+ function TCode({ code: code2, language, title, borderColor, action, copyable }) {
3587
+ return createElement("t-code", { code: code2, language, title, borderColor, action, copyable });
3108
3588
  }
3109
- function TDiff({ value, addColor, removeColor, metaColor }) {
3589
+ function TUnifiedDiff({ value, addColor, removeColor, metaColor }) {
3110
3590
  return createElement("t-diff", { value, addColor, removeColor, metaColor });
3111
3591
  }
3592
+ function TDiffCompute({ before, after, context, addColor, removeColor, metaColor }) {
3593
+ return createElement("t-diff-compute", { before, after, context, addColor, removeColor, metaColor });
3594
+ }
3112
3595
  function TBar({ label, value, max, color }) {
3113
3596
  return createElement("t-bar", { label, value, max, color });
3114
3597
  }
@@ -3118,8 +3601,70 @@ function TSpark({ data, color }) {
3118
3601
  function TList({ items, ordered, bulletColor, color }) {
3119
3602
  return createElement("t-list", { items, ordered, bulletColor, color });
3120
3603
  }
3121
- function TButton({ label, onClick, color, bg }) {
3122
- return createElement("t-button", { label, onClick, color, bg });
3604
+ function TButton({ label, onClick, color, bg, bold, dim }) {
3605
+ return createElement("t-button", { label, onClick, color, bg, bold, dim });
3606
+ }
3607
+ function TDivider({ label, color, char, labelColor }) {
3608
+ return createElement("t-divider", { label, color, char, labelColor });
3609
+ }
3610
+ function TKV({ pairs, separator: separator2, keyColor, valueColor, keyWidth }) {
3611
+ return createElement("t-kv", { pairs, separator: separator2, keyColor, valueColor, keyWidth });
3612
+ }
3613
+ function TCallout({ children, text, color, accent, label }) {
3614
+ return createElement("t-callout", { text, color, accent, label }, children);
3615
+ }
3616
+ function TChart({ data, height, color, axisColor, fill }) {
3617
+ return createElement("t-chart", { data, height, color, axisColor, fill });
3618
+ }
3619
+ function TScroll({ children, maxHeight, offset: controlledOffset, onScroll: externalOnScroll, scrollbarColor }) {
3620
+ const [internalOffset, setInternalOffset] = useState2(0);
3621
+ const isControlled = controlledOffset !== void 0;
3622
+ const offset = isControlled ? controlledOffset : internalOffset;
3623
+ const handleScroll = useCallback((newOffset) => {
3624
+ if (!isControlled) setInternalOffset(newOffset);
3625
+ externalOnScroll?.(newOffset);
3626
+ }, [isControlled, externalOnScroll]);
3627
+ return createElement("t-scroll", { maxHeight, offset, scrollbarColor, onScroll: handleScroll }, children);
3628
+ }
3629
+ function TGrid({ children, cols, gap, rowGap }) {
3630
+ return createElement("t-grid", { cols, gap, rowGap }, children);
3631
+ }
3632
+ function TCheckbox({ checked, label, color, checkColor, onChange, onClick }) {
3633
+ return createElement("t-checkbox", { checked, label, color, checkColor, onChange, onClick });
3634
+ }
3635
+ function TInput({ value, onChange, onSubmit, placeholder, width, focused: controlledFocused, color, borderColor, onClick }) {
3636
+ const [internalFocused, setInternalFocused] = useState2(false);
3637
+ const focused = controlledFocused ?? internalFocused;
3638
+ useEffect3(() => {
3639
+ if (!focused || !onChange) return;
3640
+ const handler = (e) => {
3641
+ if (e.key === "Escape") {
3642
+ setInternalFocused(false);
3643
+ return;
3644
+ }
3645
+ if (e.key === "Enter") {
3646
+ setInternalFocused(false);
3647
+ onSubmit?.(value);
3648
+ return;
3649
+ }
3650
+ if (e.key === "Backspace") {
3651
+ e.preventDefault();
3652
+ onChange(value.slice(0, -1));
3653
+ return;
3654
+ }
3655
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
3656
+ e.preventDefault();
3657
+ onChange(value + e.key);
3658
+ }
3659
+ };
3660
+ document.addEventListener("keydown", handler);
3661
+ return () => document.removeEventListener("keydown", handler);
3662
+ }, [focused, value, onChange, onSubmit]);
3663
+ const handleClick = () => {
3664
+ setInternalFocused(true);
3665
+ onClick?.();
3666
+ };
3667
+ return createElement("t-input", { value, placeholder, width, focused, color, borderColor, onClick: handleClick });
3123
3668
  }
3124
3669
  function renderTermReact(tree2, opts) {
3125
3670
  const resolvedTheme = opts?.theme ?? resolveTheme(opts?.themeSpec ?? defaultThemeSpec, opts?.mode ?? "dark");
@@ -3134,27 +3679,27 @@ function renderTermReact(tree2, opts) {
3134
3679
  }
3135
3680
 
3136
3681
  // src/hooks.ts
3137
- import { useState as useState2, useEffect as useEffect3, useRef as useRef2 } from "react";
3682
+ import { useState as useState3, useEffect as useEffect4, useRef as useRef2 } from "react";
3138
3683
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3139
3684
  function useSpinner(ms = 80) {
3140
- const [frame, setFrame] = useState2(0);
3141
- useEffect3(() => {
3685
+ const [frame, setFrame] = useState3(0);
3686
+ useEffect4(() => {
3142
3687
  const id = setInterval(() => setFrame((v) => (v + 1) % FRAMES.length), ms);
3143
3688
  return () => clearInterval(id);
3144
3689
  }, [ms]);
3145
3690
  return FRAMES[frame % FRAMES.length];
3146
3691
  }
3147
3692
  function useTick(ms = 500) {
3148
- const [tick, setTick] = useState2(0);
3149
- useEffect3(() => {
3693
+ const [tick, setTick] = useState3(0);
3694
+ useEffect4(() => {
3150
3695
  const id = setInterval(() => setTick((v) => v + 1), ms);
3151
3696
  return () => clearInterval(id);
3152
3697
  }, [ms]);
3153
3698
  return tick;
3154
3699
  }
3155
3700
  function useTermWidth(ref, fallback = 80) {
3156
- const [width, setWidth] = useState2(fallback);
3157
- useEffect3(() => {
3701
+ const [width, setWidth] = useState3(fallback);
3702
+ useEffect4(() => {
3158
3703
  const el = ref.current;
3159
3704
  if (!el) return;
3160
3705
  const m = document.createElement("span");
@@ -3175,12 +3720,12 @@ function useTermWidth(ref, fallback = 80) {
3175
3720
  function useStreamingText(text, lerp = 0.08) {
3176
3721
  const visibleRef = useRef2(0);
3177
3722
  const prevTextRef = useRef2("");
3178
- const [sliceEnd, setSliceEnd] = useState2(0);
3723
+ const [sliceEnd, setSliceEnd] = useState3(0);
3179
3724
  if (!text.startsWith(prevTextRef.current)) {
3180
3725
  visibleRef.current = text.length;
3181
3726
  }
3182
3727
  prevTextRef.current = text;
3183
- useEffect3(() => {
3728
+ useEffect4(() => {
3184
3729
  const target = text.length;
3185
3730
  if (visibleRef.current >= target) {
3186
3731
  setSliceEnd(target);
@@ -3192,13 +3737,13 @@ function useStreamingText(text, lerp = 0.08) {
3192
3737
  const dt = (now - lastTime) / 16.67;
3193
3738
  lastTime = now;
3194
3739
  const current = visibleRef.current;
3195
- const diff2 = target - current;
3196
- if (diff2 < 0.5) {
3740
+ const diff = target - current;
3741
+ if (diff < 0.5) {
3197
3742
  visibleRef.current = target;
3198
3743
  setSliceEnd(target);
3199
3744
  return;
3200
3745
  }
3201
- const step = Math.max(1, diff2 * lerp * dt);
3746
+ const step = Math.max(1, diff * lerp * dt);
3202
3747
  visibleRef.current = Math.min(current + step, target);
3203
3748
  const next = Math.floor(visibleRef.current);
3204
3749
  setSliceEnd((prev) => prev !== next ? next : prev);
@@ -3210,20 +3755,30 @@ function useStreamingText(text, lerp = 0.08) {
3210
3755
  return text.slice(0, sliceEnd);
3211
3756
  }
3212
3757
  export {
3758
+ TBadge,
3213
3759
  TBar,
3214
3760
  TBlank,
3215
3761
  TBox,
3216
3762
  TButton,
3763
+ TCallout,
3764
+ TChart,
3765
+ TCheckbox,
3217
3766
  TCode,
3218
- TDiff,
3767
+ TDiffCompute,
3768
+ TDivider,
3769
+ TGrid,
3219
3770
  THStack,
3220
3771
  THtml,
3772
+ TInput,
3221
3773
  TJson,
3774
+ TKV,
3222
3775
  TLayout,
3223
3776
  TLine,
3224
3777
  TList,
3225
3778
  TMarkdown,
3779
+ TPad,
3226
3780
  TRaw,
3781
+ TScroll,
3227
3782
  TSeparator,
3228
3783
  TSpan,
3229
3784
  TSpark,
@@ -3232,6 +3787,7 @@ export {
3232
3787
  TTable,
3233
3788
  TTabs,
3234
3789
  TTree,
3790
+ TUnifiedDiff,
3235
3791
  TVStack,
3236
3792
  TermDocument,
3237
3793
  TermThemeProvider,
@@ -3241,28 +3797,37 @@ export {
3241
3797
  blank,
3242
3798
  box,
3243
3799
  builtinThemes,
3800
+ callout,
3801
+ chart,
3802
+ checkbox,
3244
3803
  code,
3804
+ computeDiff,
3245
3805
  dark,
3246
3806
  defaultThemeSpec,
3247
- diff,
3807
+ divider,
3248
3808
  fromDocument,
3249
3809
  fromHtml,
3250
3810
  fromJson,
3251
3811
  fromJsonNode,
3252
3812
  fromMarkdown,
3813
+ grid,
3253
3814
  hl,
3254
3815
  hstack,
3816
+ inputField,
3255
3817
  isTableLine,
3818
+ kv,
3256
3819
  light,
3257
3820
  lines,
3258
3821
  list,
3259
3822
  pad,
3260
3823
  padLine,
3824
+ padding,
3261
3825
  parseThemeSpec,
3262
3826
  preserveTableTag,
3263
3827
  renderTermReact,
3264
3828
  resolveTheme,
3265
3829
  resolveThemeMode,
3830
+ scroll,
3266
3831
  separator,
3267
3832
  spark,
3268
3833
  statusbar,
@@ -3272,6 +3837,8 @@ export {
3272
3837
  tokenColor,
3273
3838
  tokenize,
3274
3839
  tree,
3840
+ truncate,
3841
+ unifiedDiff,
3275
3842
  useSpinner,
3276
3843
  useStreamingText,
3277
3844
  useTermTheme,