@zakmandhro/bunti 0.1.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.
@@ -0,0 +1,124 @@
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
+ }
@@ -0,0 +1,4 @@
1
+ export * from './Button';
2
+ export * from './Card';
3
+ export * from './Header';
4
+ export * from './Input';
@@ -0,0 +1,30 @@
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 ADDED
@@ -0,0 +1,60 @@
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
+ }