@nghyane/arcane-tui 0.1.10 → 0.1.12

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
1
  # Changelog
2
2
 
3
3
  ## [Unreleased]
4
+
5
+ ## [0.1.11] - 2026-03-02
6
+
7
+ ### Added
8
+
9
+ - `LeftBorderBox` component: renders children with a colored left border accent ([#51](https://github.com/nghyane/arcane/issues/51))
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@nghyane/arcane-tui",
4
- "version": "0.1.10",
4
+ "version": "0.1.12",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://github.com/nghyane/arcane",
7
7
  "author": "Can Bölük",
@@ -36,8 +36,8 @@
36
36
  "test": "bun test test/*.test.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@nghyane/arcane-natives": "^0.1.7",
40
- "@nghyane/arcane-utils": "^0.1.6",
39
+ "@nghyane/arcane-natives": "^0.1.8",
40
+ "@nghyane/arcane-utils": "^0.1.7",
41
41
  "@types/mime-types": "^3.0.1",
42
42
  "chalk": "^5.6.2",
43
43
  "marked": "^17.0.2",
@@ -0,0 +1,93 @@
1
+ import type { Component } from "../tui";
2
+ import { applyBackgroundToLine, padding } from "../utils";
3
+
4
+ /**
5
+ * LeftBorderBox - a container that renders children with a colored left border accent
6
+ * and an optional full-width background.
7
+ *
8
+ * Used as a lighter alternative to full-background Box for tool outputs.
9
+ * The border character is colored via borderFn to indicate status.
10
+ */
11
+ export class LeftBorderBox implements Component {
12
+ children: Component[] = [];
13
+ #borderFn: (char: string) => string;
14
+ #bgFn?: (text: string) => string;
15
+ #paddingLeft: number;
16
+ #paddingY: number;
17
+
18
+ constructor(paddingLeft = 1, paddingY = 0, borderFn?: (char: string) => string, bgFn?: (text: string) => string) {
19
+ this.#paddingLeft = paddingLeft;
20
+ this.#paddingY = paddingY;
21
+ this.#borderFn = borderFn ?? (s => s);
22
+ this.#bgFn = bgFn;
23
+ }
24
+
25
+ addChild(component: Component): void {
26
+ this.children.push(component);
27
+ }
28
+
29
+ removeChild(component: Component): void {
30
+ const index = this.children.indexOf(component);
31
+ if (index !== -1) {
32
+ this.children.splice(index, 1);
33
+ }
34
+ }
35
+
36
+ clear(): void {
37
+ this.children = [];
38
+ }
39
+
40
+ setBorderFn(borderFn: (char: string) => string): void {
41
+ this.#borderFn = borderFn;
42
+ }
43
+
44
+ setBgFn(bgFn?: (text: string) => string): void {
45
+ this.#bgFn = bgFn;
46
+ }
47
+
48
+ invalidate(): void {
49
+ for (const child of this.children) {
50
+ child.invalidate?.();
51
+ }
52
+ }
53
+
54
+ render(width: number): string[] {
55
+ if (this.children.length === 0) return [];
56
+
57
+ const border = this.#borderFn("┃");
58
+ const leftPad = padding(this.#paddingLeft);
59
+ // Border char takes 1 visible column + paddingLeft
60
+ const contentWidth = Math.max(1, width - 1 - this.#paddingLeft);
61
+
62
+ const childLines: string[] = [];
63
+ for (const child of this.children) {
64
+ childLines.push(...child.render(contentWidth));
65
+ }
66
+
67
+ if (childLines.length === 0) return [];
68
+
69
+ const result: string[] = [];
70
+
71
+ // Top padding
72
+ for (let i = 0; i < this.#paddingY; i++) {
73
+ result.push(this.#applyBg(border, width));
74
+ }
75
+
76
+ // Content
77
+ for (const line of childLines) {
78
+ result.push(this.#applyBg(`${border}${leftPad}${line}`, width));
79
+ }
80
+
81
+ // Bottom padding
82
+ for (let i = 0; i < this.#paddingY; i++) {
83
+ result.push(this.#applyBg(border, width));
84
+ }
85
+
86
+ return result;
87
+ }
88
+
89
+ #applyBg(line: string, width: number): string {
90
+ if (!this.#bgFn) return line;
91
+ return applyBackgroundToLine(line, width, this.#bgFn);
92
+ }
93
+ }
@@ -463,11 +463,15 @@ export class Markdown implements Component {
463
463
  // For mailto: links, strip the prefix before comparing (autolinked emails have
464
464
  // text="foo@bar.com" but href="mailto:foo@bar.com")
465
465
  const hrefForComparison = token.href.startsWith("mailto:") ? token.href.slice(7) : token.href;
466
+ const osc8Open = TERMINAL.hyperlinks ? `\x1b]8;;${token.href}\x07` : "";
467
+ const osc8Close = TERMINAL.hyperlinks ? "\x1b]8;;\x07" : "";
466
468
  if (token.text === token.href || token.text === hrefForComparison) {
467
- result += this.#theme.link(this.#theme.underline(linkText)) + stylePrefix;
469
+ result += osc8Open + this.#theme.link(this.#theme.underline(linkText)) + osc8Close + stylePrefix;
468
470
  } else {
469
471
  result +=
472
+ osc8Open +
470
473
  this.#theme.link(this.#theme.underline(linkText)) +
474
+ osc8Close +
471
475
  this.#theme.linkUrl(` (${token.href})`) +
472
476
  stylePrefix;
473
477
  }
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export { CancellableLoader } from "./components/cancellable-loader";
15
15
  export { Editor, type EditorTheme, type EditorTopBorder } from "./components/editor";
16
16
  export { Image, type ImageOptions, type ImageTheme } from "./components/image";
17
17
  export { Input } from "./components/input";
18
+ export { LeftBorderBox } from "./components/left-border-box";
18
19
  export { Loader } from "./components/loader";
19
20
  export { type DefaultTextStyle, Markdown, type MarkdownTheme } from "./components/markdown";
20
21
  export { type SelectItem, SelectList, type SelectListTheme } from "./components/select-list";