@outfitter/tui 0.2.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.
Files changed (235) hide show
  1. package/README.md +250 -0
  2. package/dist/borders/index.d.ts +3 -0
  3. package/dist/borders/index.js +13 -0
  4. package/dist/box/index.d.ts +4 -0
  5. package/dist/box/index.js +10 -0
  6. package/dist/confirm.d.ts +37 -0
  7. package/dist/confirm.js +36 -0
  8. package/dist/demo/index.d.ts +77 -0
  9. package/dist/demo/index.js +142 -0
  10. package/dist/demo/registry.d.ts +6 -0
  11. package/dist/demo/registry.js +28 -0
  12. package/dist/demo/renderers/borders.d.ts +7 -0
  13. package/dist/demo/renderers/borders.js +14 -0
  14. package/dist/demo/renderers/box.d.ts +7 -0
  15. package/dist/demo/renderers/box.js +15 -0
  16. package/dist/demo/renderers/colors.d.ts +7 -0
  17. package/dist/demo/renderers/colors.js +15 -0
  18. package/dist/demo/renderers/indicators.d.ts +7 -0
  19. package/dist/demo/renderers/indicators.js +14 -0
  20. package/dist/demo/renderers/list.d.ts +7 -0
  21. package/dist/demo/renderers/list.js +16 -0
  22. package/dist/demo/renderers/markdown.d.ts +7 -0
  23. package/dist/demo/renderers/markdown.js +15 -0
  24. package/dist/demo/renderers/progress.d.ts +7 -0
  25. package/dist/demo/renderers/progress.js +14 -0
  26. package/dist/demo/renderers/spinner.d.ts +7 -0
  27. package/dist/demo/renderers/spinner.js +16 -0
  28. package/dist/demo/renderers/table.d.ts +7 -0
  29. package/dist/demo/renderers/table.js +16 -0
  30. package/dist/demo/renderers/text.d.ts +7 -0
  31. package/dist/demo/renderers/text.js +13 -0
  32. package/dist/demo/renderers/tree.d.ts +7 -0
  33. package/dist/demo/renderers/tree.js +15 -0
  34. package/dist/demo/section.d.ts +4 -0
  35. package/dist/demo/section.js +20 -0
  36. package/dist/demo/templates.d.ts +3 -0
  37. package/dist/demo/templates.js +10 -0
  38. package/dist/demo/types.d.ts +2 -0
  39. package/dist/demo/types.js +8 -0
  40. package/dist/index.d.ts +31 -0
  41. package/dist/index.js +77 -0
  42. package/dist/list/index.d.ts +3 -0
  43. package/dist/list/index.js +9 -0
  44. package/dist/preset/full.d.ts +12 -0
  45. package/dist/preset/full.js +37 -0
  46. package/dist/preset/standard.d.ts +9 -0
  47. package/dist/preset/standard.js +26 -0
  48. package/dist/prompt/confirm.d.ts +4 -0
  49. package/dist/prompt/confirm.js +9 -0
  50. package/dist/prompt/group.d.ts +4 -0
  51. package/dist/prompt/group.js +9 -0
  52. package/dist/prompt/index.d.ts +7 -0
  53. package/dist/prompt/index.js +32 -0
  54. package/dist/prompt/select.d.ts +4 -0
  55. package/dist/prompt/select.js +11 -0
  56. package/dist/prompt/text.d.ts +4 -0
  57. package/dist/prompt/text.js +11 -0
  58. package/dist/prompt/types.d.ts +3 -0
  59. package/dist/prompt/types.js +8 -0
  60. package/dist/prompt/validators.d.ts +2 -0
  61. package/dist/prompt/validators.js +8 -0
  62. package/dist/render/borders.d.ts +2 -0
  63. package/dist/render/borders.js +15 -0
  64. package/dist/render/box.d.ts +3 -0
  65. package/dist/render/box.js +20 -0
  66. package/dist/render/date.d.ts +2 -0
  67. package/dist/render/date.js +12 -0
  68. package/dist/render/format-relative.d.ts +2 -0
  69. package/dist/render/format-relative.js +8 -0
  70. package/dist/render/format.d.ts +2 -0
  71. package/dist/render/format.js +10 -0
  72. package/dist/render/heading.d.ts +3 -0
  73. package/dist/render/heading.js +11 -0
  74. package/dist/render/index.d.ts +31 -0
  75. package/dist/render/index.js +222 -0
  76. package/dist/render/indicators.d.ts +2 -0
  77. package/dist/render/indicators.js +16 -0
  78. package/dist/render/json.d.ts +2 -0
  79. package/dist/render/json.js +10 -0
  80. package/dist/render/layout.d.ts +5 -0
  81. package/dist/render/layout.js +22 -0
  82. package/dist/render/list.d.ts +2 -0
  83. package/dist/render/list.js +8 -0
  84. package/dist/render/markdown.d.ts +2 -0
  85. package/dist/render/markdown.js +8 -0
  86. package/dist/render/progress.d.ts +2 -0
  87. package/dist/render/progress.js +8 -0
  88. package/dist/render/separator.d.ts +3 -0
  89. package/dist/render/separator.js +11 -0
  90. package/dist/render/shapes.d.ts +2 -0
  91. package/dist/render/shapes.js +32 -0
  92. package/dist/render/spinner.d.ts +2 -0
  93. package/dist/render/spinner.js +12 -0
  94. package/dist/render/stack.d.ts +3 -0
  95. package/dist/render/stack.js +35 -0
  96. package/dist/render/table.d.ts +3 -0
  97. package/dist/render/table.js +9 -0
  98. package/dist/render/tree.d.ts +2 -0
  99. package/dist/render/tree.js +10 -0
  100. package/dist/render/types.d.ts +2 -0
  101. package/dist/render/types.js +1 -0
  102. package/dist/shared/@outfitter/tui-011579t8.d.ts +24 -0
  103. package/dist/shared/@outfitter/tui-06dntkse.js +146 -0
  104. package/dist/shared/@outfitter/tui-0awf316b.js +71 -0
  105. package/dist/shared/@outfitter/tui-0b85rht7.js +1 -0
  106. package/dist/shared/@outfitter/tui-0mpqnyc1.js +55 -0
  107. package/dist/shared/@outfitter/tui-1qh888th.d.ts +106 -0
  108. package/dist/shared/@outfitter/tui-1tk0kxa6.js +94 -0
  109. package/dist/shared/@outfitter/tui-1wggw2zj.d.ts +56 -0
  110. package/dist/shared/@outfitter/tui-2pwhzg55.js +123 -0
  111. package/dist/shared/@outfitter/tui-2tkva96b.d.ts +20 -0
  112. package/dist/shared/@outfitter/tui-2wfat6jb.js +22 -0
  113. package/dist/shared/@outfitter/tui-34q2aenf.d.ts +24 -0
  114. package/dist/shared/@outfitter/tui-37hjcqhb.d.ts +3 -0
  115. package/dist/shared/@outfitter/tui-3dyxg62j.d.ts +97 -0
  116. package/dist/shared/@outfitter/tui-3kza6p9k.d.ts +132 -0
  117. package/dist/shared/@outfitter/tui-43bnfxv9.js +30 -0
  118. package/dist/shared/@outfitter/tui-4yz7jcyq.js +214 -0
  119. package/dist/shared/@outfitter/tui-52ndmswq.js +23 -0
  120. package/dist/shared/@outfitter/tui-53ms1ba4.d.ts +41 -0
  121. package/dist/shared/@outfitter/tui-5dsn6d6d.js +86 -0
  122. package/dist/shared/@outfitter/tui-5pb72vf9.js +51 -0
  123. package/dist/shared/@outfitter/tui-6605wa2j.js +127 -0
  124. package/dist/shared/@outfitter/tui-6ptks7jj.js +19 -0
  125. package/dist/shared/@outfitter/tui-6svngg69.js +126 -0
  126. package/dist/shared/@outfitter/tui-733asbd8.js +37 -0
  127. package/dist/shared/@outfitter/tui-75bztyfp.d.ts +50 -0
  128. package/dist/shared/@outfitter/tui-83zaah9b.d.ts +17 -0
  129. package/dist/shared/@outfitter/tui-8ejx8gq5.d.ts +71 -0
  130. package/dist/shared/@outfitter/tui-8j1gbehy.d.ts +164 -0
  131. package/dist/shared/@outfitter/tui-8mypsnva.d.ts +74 -0
  132. package/dist/shared/@outfitter/tui-997aapz0.js +61 -0
  133. package/dist/shared/@outfitter/tui-9dbykt4g.d.ts +42 -0
  134. package/dist/shared/@outfitter/tui-9h1kdd98.d.ts +119 -0
  135. package/dist/shared/@outfitter/tui-9rj9yesd.js +118 -0
  136. package/dist/shared/@outfitter/tui-a65efhsk.d.ts +23 -0
  137. package/dist/shared/@outfitter/tui-a8nkrbds.js +1 -0
  138. package/dist/shared/@outfitter/tui-ajp1153q.d.ts +59 -0
  139. package/dist/shared/@outfitter/tui-ay1fv41j.d.ts +30 -0
  140. package/dist/shared/@outfitter/tui-azh1w4ak.js +67 -0
  141. package/dist/shared/@outfitter/tui-b14ry1j1.d.ts +53 -0
  142. package/dist/shared/@outfitter/tui-c56sxcm8.d.ts +87 -0
  143. package/dist/shared/@outfitter/tui-c6ft5w6q.d.ts +91 -0
  144. package/dist/shared/@outfitter/tui-cq7za0cz.js +62 -0
  145. package/dist/shared/@outfitter/tui-dh15zwg0.js +95 -0
  146. package/dist/shared/@outfitter/tui-esc46z2v.js +20 -0
  147. package/dist/shared/@outfitter/tui-f1mj6h6p.d.ts +2 -0
  148. package/dist/shared/@outfitter/tui-gcpz529w.js +122 -0
  149. package/dist/shared/@outfitter/tui-gqsdhmk9.d.ts +42 -0
  150. package/dist/shared/@outfitter/tui-hescagw2.js +32 -0
  151. package/dist/shared/@outfitter/tui-j2kd7eej.js +126 -0
  152. package/dist/shared/@outfitter/tui-j3bkjt2g.js +7 -0
  153. package/dist/shared/@outfitter/tui-jnn9d8cd.js +8 -0
  154. package/dist/shared/@outfitter/tui-jz5nws55.d.ts +51 -0
  155. package/dist/shared/@outfitter/tui-k3mby2kk.js +70 -0
  156. package/dist/shared/@outfitter/tui-kc4nxak0.js +20 -0
  157. package/dist/shared/@outfitter/tui-kcxv8txp.js +7 -0
  158. package/dist/shared/@outfitter/tui-kydbggmj.js +39 -0
  159. package/dist/shared/@outfitter/tui-mch672g9.js +30 -0
  160. package/dist/shared/@outfitter/tui-n9kxkdrh.js +82 -0
  161. package/dist/shared/@outfitter/tui-na4dnjpw.js +348 -0
  162. package/dist/shared/@outfitter/tui-ncaatp4j.js +25 -0
  163. package/dist/shared/@outfitter/tui-nce7fgtf.js +48 -0
  164. package/dist/shared/@outfitter/tui-ngz2fbdw.js +144 -0
  165. package/dist/shared/@outfitter/tui-nprd7g0d.js +52 -0
  166. package/dist/shared/@outfitter/tui-nr580mbv.js +272 -0
  167. package/dist/shared/@outfitter/tui-nr93tf31.d.ts +64 -0
  168. package/dist/shared/@outfitter/tui-pdwbbzwr.js +179 -0
  169. package/dist/shared/@outfitter/tui-qb07rtct.js +19 -0
  170. package/dist/shared/@outfitter/tui-qkyazctw.d.ts +36 -0
  171. package/dist/shared/@outfitter/tui-qn1rgz9v.d.ts +93 -0
  172. package/dist/shared/@outfitter/tui-qs3fhwp8.js +43 -0
  173. package/dist/shared/@outfitter/tui-r8hywf9m.d.ts +48 -0
  174. package/dist/shared/@outfitter/tui-rbbcc034.js +20 -0
  175. package/dist/shared/@outfitter/tui-rgkerz72.js +85 -0
  176. package/dist/shared/@outfitter/tui-rh52sycm.d.ts +45 -0
  177. package/dist/shared/@outfitter/tui-rr924x4z.d.ts +59 -0
  178. package/dist/shared/@outfitter/tui-rwpdjrt3.js +20 -0
  179. package/dist/shared/@outfitter/tui-rx9xq9s7.d.ts +26 -0
  180. package/dist/shared/@outfitter/tui-sk05ye6g.js +1 -0
  181. package/dist/shared/@outfitter/tui-sv2xmh3w.d.ts +26 -0
  182. package/dist/shared/@outfitter/tui-t1a9xgbd.js +111 -0
  183. package/dist/shared/@outfitter/tui-t9vd88jr.js +273 -0
  184. package/dist/shared/@outfitter/tui-tq1z78x0.js +11 -0
  185. package/dist/shared/@outfitter/tui-ts1f957s.d.ts +128 -0
  186. package/dist/shared/@outfitter/tui-ve0083wa.d.ts +61 -0
  187. package/dist/shared/@outfitter/tui-vt0wg6c4.js +54 -0
  188. package/dist/shared/@outfitter/tui-wfnnq0zq.d.ts +328 -0
  189. package/dist/shared/@outfitter/tui-x3yg0887.d.ts +223 -0
  190. package/dist/shared/@outfitter/tui-x9vvtfkk.js +20 -0
  191. package/dist/shared/@outfitter/tui-xbvz707j.js +20 -0
  192. package/dist/shared/@outfitter/tui-xbx6d4t7.js +1 -0
  193. package/dist/shared/@outfitter/tui-xph95b7h.js +67 -0
  194. package/dist/shared/@outfitter/tui-xqjx7d1f.js +135 -0
  195. package/dist/shared/@outfitter/tui-xrgrp99k.d.ts +112 -0
  196. package/dist/shared/@outfitter/tui-xzjv96ps.d.ts +300 -0
  197. package/dist/shared/@outfitter/tui-ykzs6v4v.d.ts +66 -0
  198. package/dist/shared/@outfitter/tui-yw9n5h6q.d.ts +54 -0
  199. package/dist/shared/@outfitter/tui-ywmakc6b.js +30 -0
  200. package/dist/shared/@outfitter/tui-z6nf9b9h.js +7 -0
  201. package/dist/shared/@outfitter/tui-zcspg6z6.d.ts +190 -0
  202. package/dist/shared/@outfitter/tui-zhbsnj7w.js +1 -0
  203. package/dist/streaming/ansi.d.ts +2 -0
  204. package/dist/streaming/ansi.js +8 -0
  205. package/dist/streaming/index.d.ts +4 -0
  206. package/dist/streaming/index.js +17 -0
  207. package/dist/streaming/spinner.d.ts +3 -0
  208. package/dist/streaming/spinner.js +10 -0
  209. package/dist/streaming/writer.d.ts +2 -0
  210. package/dist/streaming/writer.js +9 -0
  211. package/dist/table/index.d.ts +4 -0
  212. package/dist/table/index.js +10 -0
  213. package/dist/theme/context.d.ts +9 -0
  214. package/dist/theme/context.js +12 -0
  215. package/dist/theme/create.d.ts +8 -0
  216. package/dist/theme/create.js +10 -0
  217. package/dist/theme/index.d.ts +17 -0
  218. package/dist/theme/index.js +40 -0
  219. package/dist/theme/presets/bold.d.ts +8 -0
  220. package/dist/theme/presets/bold.js +10 -0
  221. package/dist/theme/presets/default.d.ts +8 -0
  222. package/dist/theme/presets/default.js +9 -0
  223. package/dist/theme/presets/index.d.ts +12 -0
  224. package/dist/theme/presets/index.js +22 -0
  225. package/dist/theme/presets/minimal.d.ts +8 -0
  226. package/dist/theme/presets/minimal.js +10 -0
  227. package/dist/theme/presets/rounded.d.ts +8 -0
  228. package/dist/theme/presets/rounded.js +10 -0
  229. package/dist/theme/resolve.d.ts +8 -0
  230. package/dist/theme/resolve.js +11 -0
  231. package/dist/theme/types.d.ts +7 -0
  232. package/dist/theme/types.js +1 -0
  233. package/dist/tree/index.d.ts +3 -0
  234. package/dist/tree/index.js +11 -0
  235. package/package.json +263 -0
@@ -0,0 +1,51 @@
1
+ // @bun
2
+ // packages/tui/src/render/spinner.ts
3
+ var SPINNERS = {
4
+ dots: {
5
+ frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
6
+ interval: 80
7
+ },
8
+ line: {
9
+ frames: ["-", "\\", "|", "/"],
10
+ interval: 100
11
+ },
12
+ arc: {
13
+ frames: ["\u25DC", "\u25DD", "\u25DE", "\u25DF"],
14
+ interval: 100
15
+ },
16
+ circle: {
17
+ frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"],
18
+ interval: 100
19
+ },
20
+ bounce: {
21
+ frames: ["\u2801", "\u2802", "\u2804", "\u2802"],
22
+ interval: 120
23
+ },
24
+ ping: {
25
+ frames: [
26
+ "(\u25CF )",
27
+ "( \u25CF )",
28
+ "( \u25CF )",
29
+ "( \u25CF )",
30
+ "( \u25CF)",
31
+ "( \u25CF )",
32
+ "( \u25CF )",
33
+ "( \u25CF )"
34
+ ],
35
+ interval: 80
36
+ }
37
+ };
38
+ function getSpinnerFrame(style, elapsed) {
39
+ const spinner = SPINNERS[style];
40
+ const frameIndex = Math.floor(elapsed / spinner.interval) % spinner.frames.length;
41
+ return spinner.frames[frameIndex] ?? spinner.frames[0] ?? "...";
42
+ }
43
+ function renderSpinner(style, message) {
44
+ const frame = getSpinnerFrame(style, 0);
45
+ if (message && message.length > 0) {
46
+ return `${frame} ${message}`;
47
+ }
48
+ return frame;
49
+ }
50
+
51
+ export { SPINNERS, getSpinnerFrame, renderSpinner };
@@ -0,0 +1,127 @@
1
+ // @bun
2
+ import {
3
+ getExample
4
+ } from "./tui-nprd7g0d.js";
5
+ import {
6
+ demoSection
7
+ } from "./tui-733asbd8.js";
8
+ import {
9
+ getThemeMethodsByCategory
10
+ } from "./tui-nr580mbv.js";
11
+
12
+ // packages/tui/src/demo/renderers/colors.ts
13
+ import {
14
+ ANSI,
15
+ applyColor,
16
+ createTokens,
17
+ resolveTokenColorEnabled
18
+ } from "@outfitter/cli/colors";
19
+ import { hasNoColorEnv, resolveForceColorEnv } from "@outfitter/cli/terminal";
20
+ function renderColorsDemo(config, theme) {
21
+ const showCode = config.showCode ?? true;
22
+ const tokens = createTokens();
23
+ const colorEnabled = resolveTokenColorEnabled();
24
+ const lines = [];
25
+ lines.push(demoSection("Theme Colors (createTheme)", { case: "none" }));
26
+ lines.push("");
27
+ if (showCode) {
28
+ lines.push('import { createTheme } from "@outfitter/tui/render";');
29
+ lines.push("const theme = createTheme();");
30
+ lines.push("");
31
+ }
32
+ const { semantic } = getThemeMethodsByCategory();
33
+ for (const method of semantic) {
34
+ const text = getExample(method, config.examples);
35
+ const fn = theme[method];
36
+ const output = fn(text);
37
+ const code = `theme.${method}("${text}")`;
38
+ lines.push(`${code.padEnd(42)} \u2192 ${output}`);
39
+ }
40
+ lines.push("");
41
+ lines.push(demoSection("Utility Methods"));
42
+ lines.push("");
43
+ const { utility } = getThemeMethodsByCategory();
44
+ for (const method of utility) {
45
+ const text = getExample(method, config.examples);
46
+ const fn = theme[method];
47
+ const output = fn(text);
48
+ const code = `theme.${method}("${text}")`;
49
+ lines.push(`${code.padEnd(42)} \u2192 ${output}`);
50
+ }
51
+ lines.push("");
52
+ lines.push(demoSection("Direct Colors (applyColor)", { case: "none" }));
53
+ lines.push("");
54
+ if (showCode) {
55
+ lines.push('import { applyColor } from "@outfitter/tui/render";');
56
+ lines.push("");
57
+ }
58
+ const colors = [
59
+ "green",
60
+ "yellow",
61
+ "red",
62
+ "blue",
63
+ "cyan",
64
+ "magenta",
65
+ "gray"
66
+ ];
67
+ for (const color of colors) {
68
+ const code = `applyColor("text", "${color}")`;
69
+ const output = applyColor("text", color);
70
+ lines.push(`${code.padEnd(32)} \u2192 ${output}`);
71
+ }
72
+ lines.push("");
73
+ lines.push(demoSection("Raw Tokens (createTokens)", { case: "none" }));
74
+ lines.push("");
75
+ if (showCode) {
76
+ lines.push('import { createTokens, ANSI } from "@outfitter/tui/render";');
77
+ lines.push("const t = createTokens();");
78
+ lines.push("");
79
+ }
80
+ const d = "$";
81
+ const tokenExamples = [
82
+ {
83
+ code: `\`${d}{t.success}Done${d}{ANSI.reset}\``,
84
+ token: tokens.success,
85
+ text: "Done"
86
+ },
87
+ {
88
+ code: `\`${d}{t.error}Fail${d}{ANSI.reset}\``,
89
+ token: tokens.error,
90
+ text: "Fail"
91
+ },
92
+ {
93
+ code: `\`${d}{t.warning}Warn${d}{ANSI.reset}\``,
94
+ token: tokens.warning,
95
+ text: "Warn"
96
+ },
97
+ {
98
+ code: `\`${d}{t.info}Info${d}{ANSI.reset}\``,
99
+ token: tokens.info,
100
+ text: "Info"
101
+ }
102
+ ];
103
+ for (const example of tokenExamples) {
104
+ const reset = example.token ? ANSI.reset : "";
105
+ const output = `${example.token}${example.text}${reset}`;
106
+ lines.push(`${example.code.padEnd(36)} \u2192 ${output}`);
107
+ }
108
+ lines.push("");
109
+ lines.push(demoSection("Environment"));
110
+ const noColor = hasNoColorEnv();
111
+ const forceColor = resolveForceColorEnv();
112
+ let forceColorDisplay;
113
+ if (forceColor === true) {
114
+ forceColorDisplay = theme.success("enabled");
115
+ } else if (forceColor === false) {
116
+ forceColorDisplay = theme.error("disabled");
117
+ } else {
118
+ forceColorDisplay = theme.muted("not set");
119
+ }
120
+ lines.push(`Colors: ${colorEnabled ? theme.success("enabled") : theme.muted("disabled")} ${theme.muted(colorEnabled ? "(TTY detected)" : "(non-TTY or NO_COLOR set)")}`);
121
+ lines.push(`NO_COLOR: ${noColor ? theme.warning("set") : theme.muted("not set")}`);
122
+ lines.push(`FORCE_COLOR: ${forceColorDisplay}`);
123
+ return lines.join(`
124
+ `);
125
+ }
126
+
127
+ export { renderColorsDemo };
@@ -0,0 +1,19 @@
1
+ // @bun
2
+ // packages/tui/src/prompt/validators.ts
3
+ var validators = {
4
+ required: (message = "Required") => (value) => value.length > 0 ? undefined : message,
5
+ minLength: (length, message) => (value) => value.length >= length ? undefined : message ?? `Minimum ${length} characters`,
6
+ maxLength: (length, message) => (value) => value.length <= length ? undefined : message ?? `Maximum ${length} characters`,
7
+ pattern: (regex, message) => (value) => regex.test(value) ? undefined : message,
8
+ email: (message = "Invalid email") => validators.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, message),
9
+ compose: (...fns) => (value) => {
10
+ for (const fn of fns) {
11
+ const error = fn(value);
12
+ if (error)
13
+ return error;
14
+ }
15
+ return;
16
+ }
17
+ };
18
+
19
+ export { validators };
@@ -0,0 +1,126 @@
1
+ // @bun
2
+ import {
3
+ demoSection
4
+ } from "./tui-733asbd8.js";
5
+
6
+ // packages/tui/src/demo/renderers/text.ts
7
+ import {
8
+ getStringWidth,
9
+ padText,
10
+ pluralize,
11
+ slugify,
12
+ truncateText,
13
+ wrapText
14
+ } from "@outfitter/cli/text";
15
+ function renderTextDemo(config, theme) {
16
+ const showCode = config.showCode ?? true;
17
+ const lines = [];
18
+ lines.push(demoSection("String Width"));
19
+ lines.push("");
20
+ if (showCode) {
21
+ lines.push('import { getStringWidth } from "@outfitter/tui/render";');
22
+ lines.push("");
23
+ }
24
+ const widthExamples = [
25
+ { text: "Hello", expected: 5 },
26
+ { text: "\u4F60\u597D", expected: 4 },
27
+ { text: "\uD83C\uDF89", expected: 2 },
28
+ { text: "\x1B[32mGreen\x1B[0m", expected: 5 }
29
+ ];
30
+ for (const ex of widthExamples) {
31
+ const width = getStringWidth(ex.text);
32
+ const display = ex.text.includes("\x1B") ? '"\\x1b[32mGreen\\x1b[0m"' : `"${ex.text}"`;
33
+ lines.push(`getStringWidth(${display.padEnd(24)}) \u2192 ${width}`);
34
+ }
35
+ lines.push("");
36
+ lines.push(theme.muted("Correctly handles CJK, emoji, and ANSI codes."));
37
+ lines.push("");
38
+ lines.push(demoSection("Text Wrapping"));
39
+ lines.push("");
40
+ if (showCode) {
41
+ lines.push('import { wrapText } from "@outfitter/tui/render";');
42
+ lines.push("");
43
+ }
44
+ const longText = "This is a long sentence that should be wrapped at the specified width.";
45
+ if (showCode) {
46
+ lines.push(`wrapText("${longText}", 30)`);
47
+ lines.push("");
48
+ }
49
+ lines.push("Result:");
50
+ const wrapped = wrapText(longText, 30);
51
+ for (const line of wrapped.split(`
52
+ `)) {
53
+ lines.push(` \u2502${line}\u2502`);
54
+ }
55
+ lines.push("");
56
+ lines.push(demoSection("Truncation"));
57
+ lines.push("");
58
+ if (showCode) {
59
+ lines.push('import { truncateText } from "@outfitter/tui/render";');
60
+ lines.push("");
61
+ }
62
+ const truncExamples = [
63
+ { text: "Hello World", width: 8 },
64
+ { text: "Short", width: 10 },
65
+ { text: "Very long text that will be truncated", width: 15 }
66
+ ];
67
+ for (const ex of truncExamples) {
68
+ const result = truncateText(ex.text, ex.width);
69
+ lines.push(`truncateText("${ex.text}", ${ex.width})`);
70
+ lines.push(` \u2192 "${result}"`);
71
+ lines.push("");
72
+ }
73
+ lines.push(demoSection("Padding"));
74
+ lines.push("");
75
+ if (showCode) {
76
+ lines.push('import { padText } from "@outfitter/tui/render";');
77
+ lines.push("");
78
+ }
79
+ const padExamples = ["Hi", "Hello", "Greetings"];
80
+ for (const text of padExamples) {
81
+ const padded = padText(text, 15);
82
+ lines.push(`padText("${text}", 15) \u2192 "${padded}"`);
83
+ }
84
+ lines.push("");
85
+ lines.push(demoSection("Pluralize"));
86
+ lines.push("");
87
+ if (showCode) {
88
+ lines.push('import { pluralize } from "@outfitter/tui/render";');
89
+ lines.push("");
90
+ }
91
+ const pluralExamples = [
92
+ { count: 0, word: "item" },
93
+ { count: 1, word: "item" },
94
+ { count: 5, word: "item" },
95
+ { count: 1, word: "child", plural: "children" },
96
+ { count: 3, word: "child", plural: "children" }
97
+ ];
98
+ for (const ex of pluralExamples) {
99
+ const result = pluralize(ex.count, ex.word, ex.plural);
100
+ const code = ex.plural ? `pluralize(${ex.count}, "${ex.word}", "${ex.plural}")` : `pluralize(${ex.count}, "${ex.word}")`;
101
+ lines.push(`${code.padEnd(40)} \u2192 "${result}"`);
102
+ }
103
+ lines.push("");
104
+ lines.push(demoSection("Slugify"));
105
+ lines.push("");
106
+ if (showCode) {
107
+ lines.push('import { slugify } from "@outfitter/tui/render";');
108
+ lines.push("");
109
+ }
110
+ const slugExamples = [
111
+ "Hello World!",
112
+ "This & That",
113
+ " Multiple Spaces ",
114
+ "CamelCase"
115
+ ];
116
+ for (const text of slugExamples) {
117
+ const result = slugify(text);
118
+ lines.push(`slugify("${text}")`);
119
+ lines.push(` \u2192 "${result}"`);
120
+ lines.push("");
121
+ }
122
+ return lines.join(`
123
+ `);
124
+ }
125
+
126
+ export { renderTextDemo };
@@ -0,0 +1,37 @@
1
+ // @bun
2
+ import {
3
+ renderHeading
4
+ } from "./tui-n9kxkdrh.js";
5
+ import {
6
+ joinVertical
7
+ } from "./tui-06dntkse.js";
8
+
9
+ // packages/tui/src/demo/section.ts
10
+ function demoSection(title, options) {
11
+ return renderHeading(title, {
12
+ separator: options?.separator ?? "\u2500",
13
+ case: options?.case ?? "title"
14
+ });
15
+ }
16
+ function demoSubsection(title, options) {
17
+ return renderHeading(title, {
18
+ separator: options?.separator ?? "\u2500",
19
+ case: options?.case ?? "title"
20
+ });
21
+ }
22
+ function codeBlock(lines, show = true) {
23
+ if (!show)
24
+ return [];
25
+ return lines;
26
+ }
27
+ function description(text, theme, show = true) {
28
+ if (!show)
29
+ return [];
30
+ return [theme.muted(text)];
31
+ }
32
+ function demoContent(blocks, gap = 1) {
33
+ const filtered = blocks.filter((b) => b !== "");
34
+ return joinVertical(filtered, { gap });
35
+ }
36
+
37
+ export { demoSection, demoSubsection, codeBlock, description, demoContent };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Shared types for render utilities.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ /**
7
+ * Width mode for layout calculations.
8
+ *
9
+ * - `"text"` - Fit to text content (returns 0 to indicate no width constraint)
10
+ * - `"full"` - Full terminal width
11
+ * - `"container"` - Available container width (requires LayoutContext)
12
+ * - `number` - Fixed character width
13
+ * - `"${number}%"` - Percentage of container/terminal width
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { resolveWidth } from "@outfitter/tui/render";
18
+ *
19
+ * resolveWidth("full"); // → terminal width (e.g., 120)
20
+ * resolveWidth(50); // → 50
21
+ * resolveWidth("50%"); // → half of terminal width
22
+ * resolveWidth("container", ctx); // → context width
23
+ * ```
24
+ */
25
+ type WidthMode = "text" | "full" | "container" | number | `${number}%`;
26
+ /**
27
+ * Layout context for container-aware width calculations.
28
+ *
29
+ * Provides width information to nested components so they can size
30
+ * themselves relative to their container. Create contexts with
31
+ * `createLayoutContext()` and pass them through component hierarchies.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { createLayoutContext, resolveWidth } from "@outfitter/tui/render";
36
+ *
37
+ * const outerCtx = createLayoutContext({ width: 80, padding: 1 });
38
+ * // outerCtx.width = 76 (80 - 4 overhead)
39
+ *
40
+ * const innerWidth = resolveWidth("50%", outerCtx);
41
+ * // innerWidth = 38 (50% of 76)
42
+ * ```
43
+ */
44
+ interface LayoutContext {
45
+ /** Available content width in characters */
46
+ readonly width: number;
47
+ /** Parent context for chained calculations */
48
+ readonly parent?: LayoutContext;
49
+ }
50
+ export { WidthMode, LayoutContext };
@@ -0,0 +1,17 @@
1
+ import { ExampleTexts } from "./tui-xrgrp99k";
2
+ /**
3
+ * Default example texts used when no custom examples are provided.
4
+ *
5
+ * These defaults are generic enough to work for any CLI while still
6
+ * being meaningful and demonstrative.
7
+ */
8
+ declare const DEFAULT_EXAMPLES: ExampleTexts;
9
+ /**
10
+ * Resolves example text with custom overrides.
11
+ *
12
+ * @param key - The example text key
13
+ * @param custom - Optional custom examples
14
+ * @returns The example text (custom if provided, default otherwise)
15
+ */
16
+ declare function getExample<K extends keyof ExampleTexts>(key: K, custom?: Partial<ExampleTexts>): ExampleTexts[K];
17
+ export { DEFAULT_EXAMPLES, getExample };
@@ -0,0 +1,71 @@
1
+ import { BorderStyle } from "./tui-1qh888th";
2
+ /**
3
+ * Configuration options for {@link renderTable}.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const options: TableOptions = {
8
+ * headers: { id: "ID", name: "Name" },
9
+ * columnWidths: { description: 20 },
10
+ * border: "rounded",
11
+ * };
12
+ * ```
13
+ */
14
+ interface TableOptions {
15
+ /**
16
+ * Fixed column widths by key.
17
+ * If not specified, column width is calculated from content.
18
+ */
19
+ columnWidths?: Record<string, number>;
20
+ /**
21
+ * Custom header labels by key.
22
+ * If not specified, the object key is used as the header.
23
+ */
24
+ headers?: Record<string, string>;
25
+ /**
26
+ * Border style for the table.
27
+ * @default "single"
28
+ */
29
+ border?: BorderStyle;
30
+ /**
31
+ * Compact mode removes all borders and uses space separators.
32
+ * When true, overrides the border option.
33
+ * @default false
34
+ */
35
+ compact?: boolean;
36
+ }
37
+ /**
38
+ * Renders an array of objects as a table with Unicode box-drawing borders.
39
+ *
40
+ * Automatically calculates column widths based on content unless
41
+ * overridden in options. Supports custom header labels, border styles,
42
+ * and truncates cell content that exceeds column width.
43
+ *
44
+ * @param data - Array of objects to render as rows
45
+ * @param options - Table rendering options
46
+ * @returns Formatted table string with borders
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const table = renderTable(
51
+ * [
52
+ * { id: 1, name: "Alice", status: "Active" },
53
+ * { id: 2, name: "Bob", status: "Inactive" },
54
+ * ],
55
+ * {
56
+ * headers: { id: "ID", name: "Name" },
57
+ * columnWidths: { status: 10 },
58
+ * }
59
+ * );
60
+ *
61
+ * console.log(table);
62
+ * // ┌────┬───────┬──────────┐
63
+ * // │ ID │ Name │ status │
64
+ * // ├────┼───────┼──────────┤
65
+ * // │ 1 │ Alice │ Active │
66
+ * // │ 2 │ Bob │ Inactive │
67
+ * // └────┴───────┴──────────┘
68
+ * ```
69
+ */
70
+ declare function renderTable(data: Record<string, unknown>[], options?: TableOptions): string;
71
+ export { TableOptions, renderTable };
@@ -0,0 +1,164 @@
1
+ import { DelimiterName } from "./tui-wfnnq0zq";
2
+ import { TreeGuideStyle } from "./tui-3dyxg62j";
3
+ import { IndicatorCategory } from "./tui-9h1kdd98";
4
+ import { SpinnerStyle } from "./tui-c6ft5w6q";
5
+ import { BorderCharacters, BorderStyle } from "./tui-1qh888th";
6
+ /**
7
+ * A glyph with unicode and ASCII fallback representations.
8
+ *
9
+ * Used for characters that may not be available in all terminals.
10
+ * The appropriate character is selected at runtime based on terminal capability.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const bullet: GlyphPair = { unicode: "•", fallback: "*" };
15
+ * const checkbox: GlyphPair = { unicode: "☑", fallback: "[x]" };
16
+ * ```
17
+ */
18
+ interface GlyphPair {
19
+ /** Unicode character for modern terminals */
20
+ unicode: string;
21
+ /** ASCII fallback for limited terminals */
22
+ fallback: string;
23
+ }
24
+ /**
25
+ * Semantic states that can be mapped to visual markers.
26
+ *
27
+ * These represent the logical state of an item, which themes translate
28
+ * to specific visual representations.
29
+ */
30
+ type SemanticState = "default" | "current" | "focused" | "checked" | "disabled" | "success" | "warning" | "error" | "info";
31
+ /**
32
+ * Specification for how to render a semantic state marker.
33
+ *
34
+ * Can either reference an existing indicator from the INDICATORS registry,
35
+ * or provide a custom glyph pair.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // Reference existing indicator
40
+ * const successMarker: MarkerSpec = {
41
+ * type: "indicator",
42
+ * category: "status",
43
+ * name: "success"
44
+ * };
45
+ *
46
+ * // Custom glyph
47
+ * const starMarker: MarkerSpec = {
48
+ * type: "custom",
49
+ * glyph: { unicode: "★", fallback: "*" }
50
+ * };
51
+ * ```
52
+ */
53
+ type MarkerSpec = {
54
+ type: "indicator";
55
+ category: IndicatorCategory;
56
+ name: string;
57
+ } | {
58
+ type: "custom";
59
+ glyph: GlyphPair;
60
+ };
61
+ /**
62
+ * Semantic color tokens as ANSI escape codes.
63
+ *
64
+ * Colors are stored as raw ANSI codes and can be empty strings
65
+ * when colors are disabled.
66
+ */
67
+ interface ThemeColors {
68
+ /** Green - success messages, completed items */
69
+ success: string;
70
+ /** Yellow - warnings, caution */
71
+ warning: string;
72
+ /** Red - errors, failures */
73
+ error: string;
74
+ /** Blue - informational messages */
75
+ info: string;
76
+ /** Default text color (typically empty string) */
77
+ primary: string;
78
+ /** Gray - secondary text */
79
+ secondary: string;
80
+ /** Dim - de-emphasized text */
81
+ muted: string;
82
+ /** Cyan - interactive elements, highlights */
83
+ accent: string;
84
+ /** Bold - strong emphasis */
85
+ highlight: string;
86
+ /** Cyan + underline - URLs, clickable references */
87
+ link: string;
88
+ /** Bright red - dangerous actions */
89
+ destructive: string;
90
+ /** Dim gray - less prominent than muted */
91
+ subtle: string;
92
+ }
93
+ /**
94
+ * Default spacing values for various components.
95
+ */
96
+ interface ThemeSpacing {
97
+ /** Default padding inside boxes (characters) */
98
+ boxPadding: number;
99
+ /** Indentation for nested list items (characters) */
100
+ listIndent: number;
101
+ /** Gap between items in vertical stacks (lines) */
102
+ stackGap: number;
103
+ /** Gap between items in horizontal stacks (characters) */
104
+ horizontalGap: number;
105
+ }
106
+ /**
107
+ * Complete visual theme configuration.
108
+ *
109
+ * Consolidates all visual primitives (borders, delimiters, markers, guides,
110
+ * colors) into a cohesive design system. Themes can be used as-is (presets)
111
+ * or customized via {@link createVisualTheme}.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * import { defaultTheme, roundedTheme, createVisualTheme } from "@outfitter/tui/theme";
116
+ *
117
+ * // Use a preset
118
+ * const box = renderBox("Hello", { theme: roundedTheme });
119
+ *
120
+ * // Create a custom theme
121
+ * const brandTheme = createVisualTheme({
122
+ * extends: roundedTheme,
123
+ * overrides: {
124
+ * colors: { accent: "\x1b[38;5;39m" },
125
+ * spacing: { boxPadding: 2 }
126
+ * }
127
+ * });
128
+ * ```
129
+ */
130
+ interface VisualTheme {
131
+ /** Theme identifier */
132
+ name: string;
133
+ /** Border style preset name */
134
+ border: BorderStyle;
135
+ /** Derived border characters for the border style */
136
+ borderChars: BorderCharacters;
137
+ /** Tree guide style for hierarchical displays */
138
+ treeGuide: TreeGuideStyle;
139
+ /** Default delimiter for inline separators */
140
+ delimiter: DelimiterName;
141
+ /** Map of semantic states to marker specifications */
142
+ markers: Record<SemanticState, MarkerSpec>;
143
+ /** Default list bullet glyph */
144
+ listBullet: GlyphPair;
145
+ /** Checkbox glyphs for checked/unchecked states */
146
+ checkbox: {
147
+ checked: GlyphPair;
148
+ unchecked: GlyphPair;
149
+ };
150
+ /** Semantic color tokens */
151
+ colors: ThemeColors;
152
+ /** Default spacing values */
153
+ spacing: ThemeSpacing;
154
+ /** Default spinner animation style */
155
+ spinner: SpinnerStyle;
156
+ }
157
+ /**
158
+ * Partial theme for overriding specific properties.
159
+ *
160
+ * Used by {@link createVisualTheme} to allow partial customization
161
+ * while inheriting defaults.
162
+ */
163
+ type PartialVisualTheme = { [K in keyof VisualTheme]? : K extends "colors" ? Partial<ThemeColors> : K extends "spacing" ? Partial<ThemeSpacing> : K extends "markers" ? Partial<Record<SemanticState, MarkerSpec>> : K extends "checkbox" ? Partial<VisualTheme["checkbox"]> : VisualTheme[K] };
164
+ export { GlyphPair, SemanticState, MarkerSpec, ThemeColors, ThemeSpacing, VisualTheme, PartialVisualTheme };