@zakmandhro/bunti 0.1.4 → 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 (203) hide show
  1. package/AGENTS.md +65 -0
  2. package/README.md +145 -34
  3. package/dist/cli.d.ts +44 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +147 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/colors.d.ts +144 -0
  8. package/dist/colors.d.ts.map +1 -0
  9. package/dist/colors.js +426 -0
  10. package/dist/colors.js.map +1 -0
  11. package/dist/components/Box.d.ts +11 -0
  12. package/dist/components/Box.d.ts.map +1 -0
  13. package/dist/components/Box.js +9 -0
  14. package/dist/components/Box.js.map +1 -0
  15. package/dist/components/Button.d.ts +24 -0
  16. package/dist/components/Button.d.ts.map +1 -0
  17. package/dist/components/Button.js +102 -0
  18. package/dist/components/Button.js.map +1 -0
  19. package/dist/components/Card.d.ts +33 -0
  20. package/dist/components/Card.d.ts.map +1 -0
  21. package/dist/components/Card.js +79 -0
  22. package/dist/components/Card.js.map +1 -0
  23. package/dist/components/Header.d.ts +23 -0
  24. package/dist/components/Header.d.ts.map +1 -0
  25. package/dist/components/Header.js +58 -0
  26. package/dist/components/Header.js.map +1 -0
  27. package/dist/components/Input.d.ts +25 -0
  28. package/dist/components/Input.d.ts.map +1 -0
  29. package/dist/components/Input.js +232 -0
  30. package/dist/components/Input.js.map +1 -0
  31. package/dist/components/Link.d.ts +21 -0
  32. package/dist/components/Link.d.ts.map +1 -0
  33. package/dist/components/Link.js +29 -0
  34. package/dist/components/Link.js.map +1 -0
  35. package/dist/components/Modal.d.ts +38 -0
  36. package/dist/components/Modal.d.ts.map +1 -0
  37. package/dist/components/Modal.js +87 -0
  38. package/dist/components/Modal.js.map +1 -0
  39. package/dist/components/Progress.d.ts +27 -0
  40. package/dist/components/Progress.d.ts.map +1 -0
  41. package/dist/components/Progress.js +39 -0
  42. package/dist/components/Progress.js.map +1 -0
  43. package/dist/components/Spinner.d.ts +19 -0
  44. package/dist/components/Spinner.d.ts.map +1 -0
  45. package/dist/components/Spinner.js +27 -0
  46. package/dist/components/Spinner.js.map +1 -0
  47. package/dist/components/index.d.ts +10 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/components/index.js +10 -0
  50. package/dist/components/index.js.map +1 -0
  51. package/dist/data/nf-glyphs.d.ts +11 -0
  52. package/dist/data/nf-glyphs.d.ts.map +1 -0
  53. package/dist/data/nf-glyphs.js +535 -0
  54. package/dist/data/nf-glyphs.js.map +1 -0
  55. package/dist/data/nf-names.d.ts +8 -0
  56. package/dist/data/nf-names.d.ts.map +1 -0
  57. package/dist/data/nf-names.js +2 -0
  58. package/dist/data/nf-names.js.map +1 -0
  59. package/dist/demo-registry.d.ts +27 -0
  60. package/dist/demo-registry.d.ts.map +1 -0
  61. package/dist/demo-registry.js +57 -0
  62. package/dist/demo-registry.js.map +1 -0
  63. package/dist/demos/2048.ts +743 -0
  64. package/dist/demos/animation.ts +246 -0
  65. package/dist/demos/demo-layout.ts +124 -0
  66. package/dist/demos/engine.ts +374 -0
  67. package/dist/demos/interaction.ts +91 -0
  68. package/dist/demos/login.ts +203 -0
  69. package/dist/demos/mission-control.ts +558 -0
  70. package/dist/demos/showcase.ts +224 -0
  71. package/dist/detect.d.ts +104 -0
  72. package/dist/detect.d.ts.map +1 -0
  73. package/dist/detect.js +227 -0
  74. package/dist/detect.js.map +1 -0
  75. package/dist/diagnostics.d.ts +73 -0
  76. package/dist/diagnostics.d.ts.map +1 -0
  77. package/dist/diagnostics.js +225 -0
  78. package/dist/diagnostics.js.map +1 -0
  79. package/dist/dsl/context.d.ts +11 -0
  80. package/dist/dsl/context.d.ts.map +1 -0
  81. package/dist/dsl/context.js +563 -0
  82. package/dist/dsl/context.js.map +1 -0
  83. package/dist/dsl/hooks.d.ts +12 -0
  84. package/dist/dsl/hooks.d.ts.map +1 -0
  85. package/dist/dsl/hooks.js +148 -0
  86. package/dist/dsl/hooks.js.map +1 -0
  87. package/dist/dsl/interaction.d.ts +16 -0
  88. package/dist/dsl/interaction.d.ts.map +1 -0
  89. package/dist/dsl/interaction.js +101 -0
  90. package/dist/dsl/interaction.js.map +1 -0
  91. package/dist/dsl/layers.d.ts +14 -0
  92. package/dist/dsl/layers.d.ts.map +1 -0
  93. package/dist/dsl/layers.js +87 -0
  94. package/dist/dsl/layers.js.map +1 -0
  95. package/dist/dsl/motion.d.ts +10 -0
  96. package/dist/dsl/motion.d.ts.map +1 -0
  97. package/dist/dsl/motion.js +150 -0
  98. package/dist/dsl/motion.js.map +1 -0
  99. package/dist/dsl/render.d.ts +22 -0
  100. package/dist/dsl/render.d.ts.map +1 -0
  101. package/dist/dsl/render.js +69 -0
  102. package/dist/dsl/render.js.map +1 -0
  103. package/dist/dsl/types.d.ts +510 -0
  104. package/dist/dsl/types.d.ts.map +1 -0
  105. package/dist/dsl/types.js +33 -0
  106. package/dist/dsl/types.js.map +1 -0
  107. package/dist/dsl.d.ts +18 -0
  108. package/dist/dsl.d.ts.map +1 -0
  109. package/dist/dsl.js +17 -0
  110. package/dist/dsl.js.map +1 -0
  111. package/dist/easing.d.ts +45 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +121 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/geometry.d.ts +77 -0
  116. package/dist/geometry.d.ts.map +1 -0
  117. package/dist/geometry.js +130 -0
  118. package/dist/geometry.js.map +1 -0
  119. package/dist/icons-full.d.ts +5 -0
  120. package/dist/icons-full.d.ts.map +1 -0
  121. package/dist/icons-full.js +31 -0
  122. package/dist/icons-full.js.map +1 -0
  123. package/dist/icons.d.ts +442 -0
  124. package/dist/icons.d.ts.map +1 -0
  125. package/dist/icons.js +314 -0
  126. package/dist/icons.js.map +1 -0
  127. package/dist/index.d.ts +228 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +46 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/input.d.ts +177 -0
  132. package/dist/input.d.ts.map +1 -0
  133. package/dist/input.js +533 -0
  134. package/dist/input.js.map +1 -0
  135. package/dist/layout.d.ts +194 -0
  136. package/dist/layout.d.ts.map +1 -0
  137. package/dist/layout.js +673 -0
  138. package/dist/layout.js.map +1 -0
  139. package/dist/render.d.ts +49 -0
  140. package/dist/render.d.ts.map +1 -0
  141. package/dist/render.js +650 -0
  142. package/dist/render.js.map +1 -0
  143. package/dist/state.d.ts +226 -0
  144. package/dist/state.d.ts.map +1 -0
  145. package/dist/state.js +130 -0
  146. package/dist/state.js.map +1 -0
  147. package/dist/theme.d.ts +129 -0
  148. package/dist/theme.d.ts.map +1 -0
  149. package/dist/theme.js +167 -0
  150. package/dist/theme.js.map +1 -0
  151. package/dist/themes/catppuccin-mocha.d.ts +19 -0
  152. package/dist/themes/catppuccin-mocha.d.ts.map +1 -0
  153. package/dist/themes/catppuccin-mocha.js +36 -0
  154. package/dist/themes/catppuccin-mocha.js.map +1 -0
  155. package/dist/themes/dracula.d.ts +21 -0
  156. package/dist/themes/dracula.d.ts.map +1 -0
  157. package/dist/themes/dracula.js +37 -0
  158. package/dist/themes/dracula.js.map +1 -0
  159. package/dist/themes/github-light.d.ts +21 -0
  160. package/dist/themes/github-light.d.ts.map +1 -0
  161. package/dist/themes/github-light.js +36 -0
  162. package/dist/themes/github-light.js.map +1 -0
  163. package/dist/themes/index.d.ts +19 -0
  164. package/dist/themes/index.d.ts.map +1 -0
  165. package/dist/themes/index.js +17 -0
  166. package/dist/themes/index.js.map +1 -0
  167. package/dist/themes/nord.d.ts +23 -0
  168. package/dist/themes/nord.d.ts.map +1 -0
  169. package/dist/themes/nord.js +38 -0
  170. package/dist/themes/nord.js.map +1 -0
  171. package/dist/themes/one-dark-pro.d.ts +21 -0
  172. package/dist/themes/one-dark-pro.d.ts.map +1 -0
  173. package/dist/themes/one-dark-pro.js +36 -0
  174. package/dist/themes/one-dark-pro.js.map +1 -0
  175. package/dist/themes/tokyo-night.d.ts +20 -0
  176. package/dist/themes/tokyo-night.d.ts.map +1 -0
  177. package/dist/themes/tokyo-night.js +37 -0
  178. package/dist/themes/tokyo-night.js.map +1 -0
  179. package/dist/utils.d.ts +28 -0
  180. package/dist/utils.d.ts.map +1 -0
  181. package/dist/utils.js +158 -0
  182. package/dist/utils.js.map +1 -0
  183. package/dist/vendor/colors.d.ts +45 -0
  184. package/dist/vendor/colors.d.ts.map +1 -0
  185. package/dist/vendor/colors.js +72 -0
  186. package/dist/vendor/colors.js.map +1 -0
  187. package/llms.txt +79 -0
  188. package/package.json +52 -11
  189. package/src/colors.ts +0 -255
  190. package/src/components/Button.ts +0 -104
  191. package/src/components/Card.ts +0 -53
  192. package/src/components/Header.ts +0 -65
  193. package/src/components/Input.ts +0 -124
  194. package/src/components/index.ts +0 -4
  195. package/src/data/glyphs.ts +0 -30
  196. package/src/detect.ts +0 -60
  197. package/src/dsl.ts +0 -661
  198. package/src/icons.ts +0 -186
  199. package/src/index.ts +0 -72
  200. package/src/layout.ts +0 -640
  201. package/src/render.ts +0 -302
  202. package/src/state.ts +0 -148
  203. package/src/utils.ts +0 -165
package/src/colors.ts DELETED
@@ -1,255 +0,0 @@
1
- /**
2
- * Bunti Semantic & Palette Color System
3
- */
4
-
5
- import type { RGB } from './state';
6
-
7
- export interface Gradient {
8
- colors: RGB[];
9
- direction: 'vertical' | 'horizontal';
10
- steps: number;
11
- }
12
-
13
- export const PALETTE = {
14
- // Greyscale
15
- slate: '235',
16
- ash: '240',
17
- gray: '244',
18
- silver: '247',
19
- white: '255',
20
- black: '0',
21
-
22
- // Deep Blues & Space
23
- midnight: '17',
24
- ocean: '24',
25
- sky: '33',
26
- 'bunti-blue': '38',
27
- 'deep-navy': '17',
28
- nebula: '61',
29
- plasma: '165',
30
-
31
- // Semantic
32
- success: '40',
33
- warning: '214',
34
- error: '196',
35
- info: '39',
36
-
37
- // Accents
38
- gold: '220',
39
- rose: '211',
40
- mint: '121',
41
- } as const;
42
-
43
- const RGB_REGISTRY: Record<string, RGB> = {
44
- black: { r: 0, g: 0, b: 0 },
45
- red: { r: 200, g: 50, b: 50 },
46
- green: { r: 50, g: 200, b: 50 },
47
- yellow: { r: 200, g: 200, b: 50 },
48
- blue: { r: 50, g: 50, b: 200 },
49
- magenta: { r: 200, g: 50, b: 200 },
50
- cyan: { r: 50, g: 200, b: 200 },
51
- white: { r: 255, g: 255, b: 255 },
52
- gray: { r: 128, g: 128, b: 128 },
53
- // Greyscale
54
- slate: { r: 40, g: 44, b: 52 },
55
- ash: { r: 75, g: 82, b: 99 },
56
- silver: { r: 188, g: 192, b: 204 },
57
- // Deep Blues & Space
58
- midnight: { r: 15, g: 15, b: 35 },
59
- ocean: { r: 20, g: 40, b: 80 },
60
- sky: { r: 0, g: 135, b: 255 },
61
- 'bunti-blue': { r: 59, g: 188, b: 225 },
62
- 'deep-navy': { r: 0, g: 51, b: 102 },
63
- nebula: { r: 95, g: 95, b: 175 },
64
- plasma: { r: 201, g: 0, b: 255 },
65
- // Semantic
66
- success: { r: 0, g: 215, b: 0 },
67
- warning: { r: 255, g: 175, b: 0 },
68
- error: { r: 215, g: 0, b: 0 },
69
- info: { r: 0, g: 175, b: 255 },
70
- // Accents
71
- gold: { r: 255, g: 215, b: 0 },
72
- rose: { r: 255, g: 175, b: 175 },
73
- mint: { r: 175, g: 255, b: 175 },
74
- };
75
-
76
- // Map standard names to ANSI-256 for basic compat
77
- const NAME_TO_ANSI: Record<string, string> = {
78
- red: '160',
79
- green: '40',
80
- yellow: '220',
81
- blue: '33',
82
- magenta: '165',
83
- cyan: '39',
84
- white: '255',
85
- black: '0',
86
- gray: '244',
87
- };
88
-
89
- export type PaletteColor = keyof typeof PALETTE;
90
-
91
- /**
92
- * Parses a hex color string (#RRGGBB or #RGB) to an RGB object.
93
- */
94
- export function hexToRGB(hex: string): RGB {
95
- const h = hex.replace('#', '');
96
- if (h.length === 3) {
97
- return {
98
- r: parseInt(h[0]! + h[0]!, 16),
99
- g: parseInt(h[1]! + h[1]!, 16),
100
- b: parseInt(h[2]! + h[2]!, 16),
101
- };
102
- }
103
- return {
104
- r: parseInt(h.substring(0, 2), 16),
105
- g: parseInt(h.substring(2, 4), 16),
106
- b: parseInt(h.substring(4, 6), 16),
107
- };
108
- }
109
-
110
- /**
111
- * Resolves a palette name, color code, or RGB object to a string for ANSI.
112
- */
113
- export function resolveColor(
114
- color: PaletteColor | string | number | RGB,
115
- ): string | number {
116
- if (typeof color === 'object' && 'r' in color) {
117
- return `2;${color.r};${color.g};${color.b}`;
118
- }
119
- if (typeof color === 'string') {
120
- if (color.startsWith('#')) {
121
- const rgb = hexToRGB(color);
122
- return `2;${rgb.r};${rgb.g};${rgb.b}`;
123
- }
124
- return (PALETTE as any)[color] || NAME_TO_ANSI[color] || color;
125
- }
126
- return color;
127
- }
128
-
129
- /**
130
- * Creates a multi-stop gradient (array of RGB objects) between multiple colors.
131
- */
132
- export function createGradient(
133
- arg1: (string | number | RGB)[] | string | number | RGB,
134
- arg2?: string | number | RGB | number,
135
- arg3?: number,
136
- ): RGB[] {
137
- let colors: (string | number | RGB)[] = [];
138
- let steps = 5;
139
-
140
- if (Array.isArray(arg1)) {
141
- colors = arg1;
142
- steps = typeof arg2 === 'number' ? arg2 : 5;
143
- } else if (arg1 && arg2) {
144
- colors = [arg1, arg2 as any];
145
- steps = typeof arg3 === 'number' ? arg3 : 5;
146
- }
147
-
148
- if (colors.length === 0) return [];
149
- if (colors.length === 1)
150
- return Array(steps).fill(resolveColorToRGB(colors[0]));
151
- if (steps <= 1) return [resolveColorToRGB(colors[0])];
152
-
153
- const result: RGB[] = [];
154
- const rgbColors = colors.map(resolveColorToRGB);
155
-
156
- for (let i = 0; i < steps; i++) {
157
- const globalT = i / (steps - 1);
158
- const segmentCount = rgbColors.length - 1;
159
- const segmentIdx = Math.min(
160
- Math.floor(globalT * segmentCount),
161
- segmentCount - 1,
162
- );
163
-
164
- const start = rgbColors[segmentIdx];
165
- const end = rgbColors[segmentIdx + 1];
166
- const t = globalT * segmentCount - segmentIdx;
167
-
168
- result.push({
169
- r: Math.round(start!.r + (end!.r - start!.r) * t),
170
- g: Math.round(start!.g + (end!.g - start!.g) * t),
171
- b: Math.round(start!.b + (end!.b - start!.b) * t),
172
- });
173
- }
174
-
175
- return result;
176
- }
177
-
178
- /**
179
- * Helper to resolve any color type to an RGB object.
180
- */
181
- function resolveColorToRGB(color: any): RGB {
182
- if (typeof color === 'object' && 'r' in color) return color;
183
- if (typeof color === 'string' && color.startsWith('#'))
184
- return hexToRGB(color);
185
-
186
- // 1. Check registry
187
- if (typeof color === 'string' && RGB_REGISTRY[color])
188
- return RGB_REGISTRY[color];
189
-
190
- // 2. Check Palette
191
- const paletteValue = (PALETTE as any)[color];
192
- if (paletteValue) {
193
- // If palette value is a name, look it up in registry
194
- const registered = RGB_REGISTRY[color];
195
- if (registered) return registered;
196
- }
197
-
198
- // Fallback
199
- return { r: 128, g: 128, b: 128 };
200
- }
201
-
202
- /**
203
- * Adjusts the brightness of a color.
204
- */
205
- export function adjustBrightness(color: any, amount: number): RGB {
206
- const rgb = resolveColorToRGB(color);
207
- const factor = 1 + amount / 100;
208
- return {
209
- r: Math.max(0, Math.min(255, Math.round(rgb.r * factor))),
210
- g: Math.max(0, Math.min(255, Math.round(rgb.g * factor))),
211
- b: Math.max(0, Math.min(255, Math.round(rgb.b * factor))),
212
- };
213
- }
214
-
215
- /**
216
- * Returns a function that darkens a color and can be used as a style wrapper.
217
- */
218
- export function darken(color: any, amount: number = 20) {
219
- const adjusted = adjustBrightness(color, -amount);
220
- return (text: string) => fg(adjusted, text);
221
- }
222
-
223
- /**
224
- * Returns a function that lightens a color and can be used as a style wrapper.
225
- */
226
- export function lighten(color: any, amount: number = 20) {
227
- const adjusted = adjustBrightness(color, amount);
228
- return (text: string) => fg(adjusted, text);
229
- }
230
-
231
- /**
232
- * Returns an RGB object for TrueColor rendering.
233
- */
234
- export function rgb(r: number, g: number, b: number) {
235
- return { r, g, b };
236
- }
237
-
238
- /**
239
- * Returns an ANSI escape sequence for a color.
240
- */
241
- export function fg(color: any, text: string): string {
242
- const code = resolveColor(color);
243
- const codeStr = String(code);
244
- const prefix =
245
- typeof color === 'object' || codeStr.startsWith('2;') ? '38' : '38;5';
246
- return `\x1b[${prefix};${codeStr}m${text}\x1b[0m`;
247
- }
248
-
249
- export function bg(color: any, text: string): string {
250
- const code = resolveColor(color);
251
- const codeStr = String(code);
252
- const prefix =
253
- typeof color === 'object' || codeStr.startsWith('2;') ? '48' : '48;5';
254
- return `\x1b[${prefix};${codeStr}m${text}\x1b[0m`;
255
- }
@@ -1,104 +0,0 @@
1
- import type { BuntiContext } from '../dsl';
2
- import type { StyleOptions } from '../layout';
3
-
4
- export interface ButtonProps extends StyleOptions {
5
- id: string;
6
- label: string;
7
- icon?: string;
8
- variant?: 'primary' | 'secondary' | 'danger';
9
- onClick?: () => void;
10
- }
11
-
12
- /**
13
- * Tactical Button Component
14
- * Fully reactive to keyboard focus and action events.
15
- */
16
- export function Button(ctx: BuntiContext, props: ButtonProps) {
17
- const {
18
- box,
19
- color,
20
- focusable,
21
- state,
22
- offsetX,
23
- offsetY,
24
- mouseX,
25
- mouseY,
26
- isMouseDown,
27
- } = ctx;
28
-
29
- // 1. Register in the global focus loop (for TAB navigation)
30
- const isSelected = focusable(props.id);
31
-
32
- // 2. Resolve final dimensions to perform Mouse Hit-Testing
33
- const finalLabel = props.icon ? `${props.icon} ${props.label}` : props.label;
34
- const w = props.width || Math.max(12, finalLabel.length + 4);
35
- const h = props.height || 3;
36
-
37
- const resolvedW = typeof w === 'number' ? w : ctx.width;
38
- const absX =
39
- props.width === '100%'
40
- ? offsetX
41
- : offsetX + Math.max(0, Math.floor((ctx.width - resolvedW) / 2));
42
- const absY = offsetY + ctx.cursorY;
43
-
44
- const isHovered =
45
- mouseX >= absX &&
46
- mouseX < absX + resolvedW &&
47
- mouseY >= absY &&
48
- mouseY < absY + (h as number);
49
-
50
- // 3. Resolve Theme & State
51
- let bg: any = 254; // Near white
52
- let fg: any = 'black';
53
- let borderCol: any = { r: 217, g: 216, b: 213 };
54
-
55
- if (props.variant === 'primary') {
56
- bg = 'black';
57
- fg = 'white';
58
- borderCol = 'black';
59
- } else if (props.variant === 'danger') {
60
- bg = 'error';
61
- borderCol = color.lighten('error', 20);
62
- fg = 'white';
63
- }
64
-
65
- // Active Interactive State
66
- const isActive = isSelected || isHovered;
67
-
68
- if (isActive) {
69
- if (props.variant === 'primary') {
70
- bg = 'ash'; // Hover state for primary
71
- } else {
72
- borderCol = 'black';
73
- if (isMouseDown && isHovered) {
74
- bg = color.darken(bg, 5); // Pressed state
75
- }
76
- }
77
- }
78
-
79
- // 4. Handle Interaction
80
- const wasClicked = isHovered && isMouseDown && state.mouseButton === 0;
81
- const wasActivated =
82
- isSelected && (state.lastKey === 'enter' || state.lastKey === ' ');
83
-
84
- if (wasClicked || wasActivated) {
85
- if (props.onClick) props.onClick();
86
- }
87
-
88
- // 5. Render the Primitive
89
- return box(
90
- {
91
- width: w,
92
- height: h,
93
- border: props.variant === 'primary' ? 'none' : props.border || 'rounded',
94
- borderColor: borderCol,
95
- bgColor: bg,
96
- align: props.align || 'center',
97
- valign: props.valign || 'middle',
98
- padding: props.padding || [0, 1],
99
- },
100
- ({ text }) => {
101
- text(color.fg(fg, isActive ? color.bold(finalLabel) : finalLabel));
102
- },
103
- );
104
- }
@@ -1,53 +0,0 @@
1
- import type { BuntiContext, DSLBoxOptions } from '../dsl';
2
-
3
- export interface CardProps extends DSLBoxOptions {
4
- title?: string;
5
- theme?: 'default' | 'accent' | 'danger';
6
- }
7
-
8
- /**
9
- * Tactical Card Component
10
- * A structural container with an integrated header.
11
- */
12
- export function Card(
13
- ctx: BuntiContext,
14
- props: CardProps,
15
- callback: (sub: BuntiContext) => void,
16
- ) {
17
- const { box, color } = ctx;
18
-
19
- const neutralGray = { r: 217, g: 216, b: 213 };
20
- let borderColor: any = neutralGray;
21
- let titleColor: any = neutralGray;
22
-
23
- if (props.theme === 'accent') {
24
- titleColor = 'black';
25
- // Border remains neutral gray
26
- } else if (props.theme === 'danger') {
27
- borderColor = 'error';
28
- titleColor = 'error';
29
- }
30
-
31
- return box(
32
- {
33
- x: props.x,
34
- y: props.y,
35
- anchor: props.anchor,
36
- width: props.width || '100%',
37
- height: props.height,
38
- minHeight: props.minHeight,
39
- border: props.border || 'frame',
40
- borderColor: props.borderColor || borderColor,
41
- padding: props.padding || [1, 2],
42
- bgColor: props.bgColor,
43
- },
44
- (sub) => {
45
- if (props.title) {
46
- sub.text(
47
- color.fg(titleColor, color.bold(`${props.title.toUpperCase()}\n\n`)),
48
- );
49
- }
50
- callback(sub);
51
- },
52
- );
53
- }
@@ -1,65 +0,0 @@
1
- import type { BuntiContext } from '../dsl';
2
- import { box as engineBox } from '../layout';
3
-
4
- export interface HeaderProps {
5
- title: string;
6
- leftIcon?: string;
7
- rightLabel?: string;
8
- theme?: 'light' | 'dark';
9
- }
10
-
11
- /**
12
- * Enterprise Tactical Header
13
- * A full-width, high-signal branding bar.
14
- */
15
- export function Header(ctx: BuntiContext, props: HeaderProps) {
16
- const { box, color, width, joinHorizontal } = ctx;
17
-
18
- // Resolve Theme Colors
19
- const isLight = props.theme !== 'dark';
20
- const bg = isLight ? 'white' : 'midnight';
21
- const fg = isLight ? 236 : 'silver';
22
-
23
- // Master Background Box
24
- box(
25
- {
26
- anchor: 'top',
27
- width: '100%',
28
- height: 1,
29
- bgColor: bg,
30
- border: 'none',
31
- padding: [0, 2],
32
- },
33
- ({ text }) => {
34
- // 1. Left: Branding
35
- const leftStr = props.leftIcon ? color.fg('plasma', props.leftIcon) : '';
36
- const leftNode = engineBox(leftStr, {
37
- width: 12,
38
- align: 'left',
39
- border: 'none',
40
- padding: [0, 0],
41
- });
42
-
43
- // 2. Center: Title
44
- const midStr = color.fg(fg, color.bold(props.title));
45
- const midNode = engineBox(midStr, {
46
- width: width - 28,
47
- align: 'center',
48
- border: 'none',
49
- padding: [0, 0],
50
- });
51
-
52
- // 3. Right: Telemetry / Exit
53
- const rightStr = props.rightLabel ? color.dim(props.rightLabel) : '';
54
- const rightNode = engineBox(rightStr, {
55
- width: 12,
56
- align: 'right',
57
- border: 'none',
58
- padding: [0, 0],
59
- });
60
-
61
- // Join and push to render pipeline
62
- text(joinHorizontal(leftNode, midNode, rightNode));
63
- },
64
- );
65
- }
@@ -1,124 +0,0 @@
1
- import type { BuntiContext } from '../dsl';
2
- import type { StyleOptions } from '../layout';
3
-
4
- export interface InputProps extends StyleOptions {
5
- id: string;
6
- label?: string;
7
- placeholder?: string;
8
- value?: string;
9
- type?: 'text' | 'password';
10
- onChange?: (value: string) => void;
11
- }
12
-
13
- /**
14
- * Tactical Input Component
15
- * Managed state HOC with cursor simulation, keyboard interception, and mouse focus.
16
- */
17
- export function Input(ctx: BuntiContext, props: InputProps) {
18
- const {
19
- box,
20
- color,
21
- focusable,
22
- state,
23
- useState,
24
- offsetX,
25
- offsetY,
26
- mouseX,
27
- mouseY,
28
- isMouseDown,
29
- } = ctx;
30
-
31
- // 1. Mouse Hit-Testing
32
- const _finalLabelLen = props.label ? props.label.length + 1 : 0;
33
- const w = props.width || 40;
34
- const h = props.height || 3;
35
-
36
- // Calculate absolute coordinates based on parent offsets and current flow cursor
37
- // Assuming 100% width, x offset is just parent's offsetX.
38
- const absX = offsetX;
39
- const absY = offsetY + ctx.cursorY;
40
-
41
- const isHovered =
42
- mouseX >= absX &&
43
- mouseX < absX + (w as number) &&
44
- mouseY >= absY &&
45
- mouseY < absY + (h as number);
46
-
47
- // If clicked, force focus state
48
- if (isHovered && isMouseDown && state.mouseButton === 0) {
49
- state.focusedId = props.id;
50
- }
51
-
52
- // 2. Register in the global focus loop
53
- const isSelected = focusable(props.id);
54
-
55
- // 3. Manage internal state
56
- const [value, setValue] = useState(props.id, props.value || '');
57
-
58
- // 4. Handle Keyboard Interaction (only when focused)
59
- if (isSelected && state.lastKey) {
60
- const key = state.lastKey;
61
-
62
- if (key === 'backspace') {
63
- if (value.length > 0) {
64
- const newValue = value.slice(0, -1);
65
- setValue(newValue);
66
- if (props.onChange) props.onChange(newValue);
67
- }
68
- } else if (
69
- key === 'enter' ||
70
- key === 'tab' ||
71
- key === 'escape' ||
72
- key === 'up' ||
73
- key === 'down' ||
74
- key === 'left' ||
75
- key === 'right'
76
- ) {
77
- // System keys: ignore
78
- } else if (key.length === 1) {
79
- // Standard character input
80
- const newValue = value + key;
81
- setValue(newValue);
82
- if (props.onChange) props.onChange(newValue);
83
- }
84
- }
85
-
86
- // 5. Resolve Theme
87
- const neutralGray = { r: 217, g: 216, b: 213 };
88
- const borderCol = isSelected ? 'black' : isHovered ? 'ash' : neutralGray;
89
- const _bgColor = { r: 255, g: 255, b: 255 };
90
- const textColor = 'black';
91
-
92
- // 6. Render
93
- return box(
94
- {
95
- width: w,
96
- height: h,
97
- border: 'rounded',
98
- borderColor: borderCol,
99
- padding: [0, 1],
100
- align: 'left',
101
- valign: 'middle',
102
- },
103
- ({ text }) => {
104
- // Label
105
- if (props.label) {
106
- text(color.dim(`${props.label} `));
107
- }
108
-
109
- // Value Display with simulated cursor
110
- if (value.length === 0 && props.placeholder) {
111
- text(color.dim(props.placeholder));
112
- } else {
113
- const displayValue =
114
- props.type === 'password' ? '*'.repeat(value.length) : value;
115
- text(color.fg(textColor, displayValue));
116
- }
117
-
118
- // Cursor (blinking)
119
- if (isSelected && ctx.flicker(0.8)) {
120
- text(color.black('█'));
121
- }
122
- },
123
- );
124
- }
@@ -1,4 +0,0 @@
1
- export * from './Button';
2
- export * from './Card';
3
- export * from './Header';
4
- export * from './Input';
@@ -1,30 +0,0 @@
1
- /**
2
- * Bunti Curated Glyph Registry (Nerd Font v3)
3
- */
4
- export const GLYPHS: Record<string, string> = {
5
- // Languages & Tech
6
- js: '\u{E781}', // nf-dev-javascript
7
- ts: '\u{E628}', // nf-dev-typescript
8
- python: '\u{E73C}', // nf-dev-python
9
- rust: '\u{E7A8}', // nf-dev-rust
10
- go: '\u{E627}', // nf-dev-go
11
- node: '\u{E718}', // nf-dev-nodejs
12
- bun: '\u{E22F}', // nf-seti-terminal (best proxy)
13
-
14
- // Git & Source
15
- git: '\u{F02A2}', // nf-md-git
16
- branch: '\u{E725}', // nf-oct-git_branch
17
- commit: '\u{E729}', // nf-oct-git_commit
18
- pull: '\u{E728}', // nf-oct-git_pull_request
19
- merge: '\u{E727}', // nf-oct-git_merge
20
-
21
- // System & UI
22
- folder: '\u{F07B}', // nf-fa-folder
23
- file: '\u{F15B}', // nf-fa-file
24
- lock: '\u{F023}', // nf-fa-lock
25
- search: '\u{F002}', // nf-fa-search
26
- settings: '\u{F013}', // nf-fa-cog
27
- terminal: '\u{F489}', // nf-oct-terminal
28
- heart: '\u{F004}', // nf-fa-heart
29
- star: '\u{F005}', // nf-fa-star
30
- };
package/src/detect.ts DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Bunti Terminal Capability Detection
3
- */
4
-
5
- export interface TerminalCapabilities {
6
- nerdFont: boolean;
7
- glyphProtocol: boolean;
8
- unicode: boolean;
9
- color: boolean;
10
- }
11
-
12
- /**
13
- * Detects terminal capabilities using environment variables and
14
- * modern protocol handshakes.
15
- */
16
- export async function detectCapabilities(): Promise<TerminalCapabilities> {
17
- const caps: TerminalCapabilities = {
18
- nerdFont: false,
19
- glyphProtocol: false,
20
- unicode: true,
21
- color: true,
22
- };
23
-
24
- // 1. Environment Variable Heuristics
25
- const term = process.env.TERM_PROGRAM || '';
26
- const termEmulator = process.env.TERMINAL_EMULATOR || '';
27
- const nerdEnv =
28
- process.env.NERD_FONTS || process.env.NERD_FONT || process.env.BUNTI_NF;
29
-
30
- // Optimistic list of terminals known to support modern fonts
31
- const modernTerms = [
32
- 'Ghostty',
33
- 'WezTerm',
34
- 'iTerm.app',
35
- 'WarpTerminal',
36
- 'Apple_Terminal',
37
- 'vscode',
38
- 'Hyper',
39
- 'Rio',
40
- 'Term7',
41
- ];
42
-
43
- if (nerdEnv === '1' || nerdEnv === 'true' || nerdEnv === 'yes') {
44
- caps.nerdFont = true;
45
- } else if (modernTerms.includes(term) || modernTerms.includes(termEmulator)) {
46
- caps.nerdFont = true;
47
- } else if (process.env.LC_TERMINAL === 'iTerm2') {
48
- caps.nerdFont = true;
49
- }
50
-
51
- // 2. Glyph Protocol Handshake (Ghostty 1.3+, Rio, WezTerm)
52
- // We send the Support Query and wait briefly for a response.
53
- // Note: This is an optimistic check for now, can be expanded to
54
- // a full async TTY listener if needed.
55
- if (term === 'Ghostty') {
56
- caps.glyphProtocol = true;
57
- }
58
-
59
- return caps;
60
- }