@zakmandhro/bunti 0.1.5 → 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 (192) hide show
  1. package/AGENTS.md +65 -0
  2. package/README.md +81 -11
  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 +77 -7
  8. package/dist/colors.d.ts.map +1 -1
  9. package/dist/colors.js +238 -39
  10. package/dist/colors.js.map +1 -1
  11. package/dist/components/Box.d.ts +6 -0
  12. package/dist/components/Box.d.ts.map +1 -1
  13. package/dist/components/Box.js +5 -0
  14. package/dist/components/Box.js.map +1 -1
  15. package/dist/components/Button.d.ts +12 -2
  16. package/dist/components/Button.d.ts.map +1 -1
  17. package/dist/components/Button.js +35 -24
  18. package/dist/components/Button.js.map +1 -1
  19. package/dist/components/Card.d.ts +24 -2
  20. package/dist/components/Card.d.ts.map +1 -1
  21. package/dist/components/Card.js +56 -14
  22. package/dist/components/Card.js.map +1 -1
  23. package/dist/components/Header.d.ts +11 -1
  24. package/dist/components/Header.d.ts.map +1 -1
  25. package/dist/components/Header.js +12 -8
  26. package/dist/components/Header.js.map +1 -1
  27. package/dist/components/Input.d.ts +10 -1
  28. package/dist/components/Input.d.ts.map +1 -1
  29. package/dist/components/Input.js +172 -41
  30. package/dist/components/Input.js.map +1 -1
  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 +25 -0
  36. package/dist/components/Modal.d.ts.map +1 -1
  37. package/dist/components/Modal.js +83 -24
  38. package/dist/components/Modal.js.map +1 -1
  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 +3 -0
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/components/index.js +3 -0
  50. package/dist/components/index.js.map +1 -1
  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 +92 -3
  72. package/dist/detect.d.ts.map +1 -1
  73. package/dist/detect.js +218 -40
  74. package/dist/detect.js.map +1 -1
  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 +13 -154
  108. package/dist/dsl.d.ts.map +1 -1
  109. package/dist/dsl.js +12 -658
  110. package/dist/dsl.js.map +1 -1
  111. package/dist/easing.d.ts +41 -1
  112. package/dist/easing.d.ts.map +1 -1
  113. package/dist/easing.js +105 -2
  114. package/dist/easing.js.map +1 -1
  115. package/dist/geometry.d.ts +39 -0
  116. package/dist/geometry.d.ts.map +1 -1
  117. package/dist/geometry.js +20 -0
  118. package/dist/geometry.js.map +1 -1
  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 +426 -5
  124. package/dist/icons.d.ts.map +1 -1
  125. package/dist/icons.js +144 -15
  126. package/dist/icons.js.map +1 -1
  127. package/dist/index.d.ts +93 -22
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +17 -7
  130. package/dist/index.js.map +1 -1
  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 +111 -6
  136. package/dist/layout.d.ts.map +1 -1
  137. package/dist/layout.js +163 -23
  138. package/dist/layout.js.map +1 -1
  139. package/dist/render.d.ts +33 -1
  140. package/dist/render.d.ts.map +1 -1
  141. package/dist/render.js +431 -116
  142. package/dist/render.js.map +1 -1
  143. package/dist/state.d.ts +137 -3
  144. package/dist/state.d.ts.map +1 -1
  145. package/dist/state.js +25 -0
  146. package/dist/state.js.map +1 -1
  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 +1 -0
  180. package/dist/utils.d.ts.map +1 -1
  181. package/dist/utils.js +1 -0
  182. package/dist/utils.js.map +1 -1
  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 +23 -7
  189. package/dist/data/glyphs.d.ts +0 -5
  190. package/dist/data/glyphs.d.ts.map +0 -1
  191. package/dist/data/glyphs.js +0 -29
  192. package/dist/data/glyphs.js.map +0 -1
package/dist/layout.d.ts CHANGED
@@ -2,39 +2,104 @@
2
2
  * Bunti Functional Layout & Drawing
3
3
  * Strictly functional primitives for buffer manipulation and layout generation.
4
4
  */
5
+ import type { Rect } from './geometry';
5
6
  import type { Cell, RGB, ScreenState } from './state';
7
+ import type { ThemeColor } from './theme';
8
+ /**
9
+ * Writes one cell into the back buffer (bounds-checked). Wide glyphs mark
10
+ * their tail cells as skip so the differ keeps columns aligned.
11
+ */
6
12
  export declare function setCell(state: ScreenState, x: number, y: number, cell: Partial<Cell>): void;
13
+ /**
14
+ * Repaints existing back-buffer cells inside `area` at reduced luminance.
15
+ * Each cell's current fg/bg is resolved to exact RGB (numeric ANSI-256 codes
16
+ * via the xterm table) and scaled toward black by `amount` (0 = unchanged,
17
+ * 1 = fully black). Cells with an undefined bg keep their transparent
18
+ * background (fg-only dim); undefined fg is left alone (there is no color to
19
+ * scale). Wide-glyph tail cells (skip) are dimmed too so they stay in sync
20
+ * with their head cell. No-op on the mono tier — there is no color to dim.
21
+ */
22
+ export declare function dimRect(state: ScreenState, area: Rect, amount: number): void;
7
23
  import type { Gradient } from './colors';
24
+ /** Fill style for rect(). */
8
25
  export interface RectOptions {
26
+ /** Fill glyph; omitted = pure background fill that keeps existing chars. */
9
27
  char?: string;
10
- fg?: string | number | RGB;
11
- bg?: string | number | RGB | Gradient;
28
+ /** Foreground color for the fill glyphs. */
29
+ fg?: string | number | RGB | ThemeColor;
30
+ /** Background color (Gradients resolve per-cell across the rect). */
31
+ bg?: string | number | RGB | Gradient | ThemeColor;
12
32
  }
33
+ /**
34
+ * Fills a w×h cell rectangle at absolute coordinates — bars, panels,
35
+ * charts, backgrounds. Pure-background fills (no `char`) preserve the
36
+ * characters already painted underneath.
37
+ * @example rect(state, 0, 0, state.width, 3, { bg: 'midnight' });
38
+ */
13
39
  export declare function rect(state: ScreenState, x: number, y: number, w: number, h: number, style: RectOptions): void;
40
+ /**
41
+ * Paints a multi-line, ANSI-styled string into the back buffer at absolute
42
+ * cell coordinates, parsing SGR codes into per-cell attributes. Grapheme-
43
+ * aware: emoji and wide glyphs occupy their real column width.
44
+ * @example blit(state, 2, 1, fg('gold', 'FPS 60'));
45
+ */
14
46
  export declare function blit(state: ScreenState, startX: number, startY: number, content: string, style?: Partial<Cell>): void;
47
+ /**
48
+ * Border glyph sets. Wireframe: 'default' | 'rounded' | 'double' |
49
+ * 'dashed' | 'dotted'. Block: 'frame' | 'thick-frame'. ASCII: 'classic'.
50
+ * 'none' keeps the box purely structural.
51
+ */
15
52
  export type BorderStyle = 'default' | 'rounded' | 'double' | 'dashed' | 'dotted' | 'frame' | 'thick-frame' | 'classic' | 'none';
53
+ /** Per-side border colors; missing sides inherit top (or left/right). */
16
54
  export interface SideColors {
17
55
  top?: any;
18
56
  bottom?: any;
19
57
  left?: any;
20
58
  right?: any;
21
59
  }
60
+ /**
61
+ * A dimension: absolute cells (20), percent of parent ('50%'), or fill
62
+ * fraction ('1fr'). undefined/'auto' sizes to content.
63
+ */
22
64
  export type SizeUnit = number | string;
65
+ /**
66
+ * Box styling shared by box()/table()/components.
67
+ */
23
68
  export interface StyleOptions {
69
+ /** Outer width: cells, '50%', '1fr', or 'auto' (content-sized). */
24
70
  width?: SizeUnit;
71
+ /** Lower bound on the outer width in cells. */
25
72
  minWidth?: number;
73
+ /** Upper bound on the outer width in cells. */
26
74
  maxWidth?: number;
75
+ /** Outer height: rows, '50%', '1fr', or 'auto' (content-sized). */
27
76
  height?: SizeUnit;
77
+ /** Lower bound on the outer height in rows. */
28
78
  minHeight?: number;
79
+ /** Upper bound on the outer height in rows. */
29
80
  maxHeight?: number;
81
+ /**
82
+ * [vertical, horizontal] padding INSIDE the border, in cells —
83
+ * [1, 2] = 1 blank row above/below, 2 blank columns left/right.
84
+ * Note the order: rows first (a 2:1 h:v ratio usually looks balanced
85
+ * because terminal cells are taller than wide).
86
+ */
30
87
  padding?: [number, number];
88
+ /** Border glyph set (default 'none'). */
31
89
  border?: BorderStyle;
32
- borderColor?: string | number | RGB | ((s: string) => string) | SideColors;
33
- bgColor?: string | number | RGB | Gradient;
90
+ /** Border color: color value, styler function, or per-side SideColors. */
91
+ borderColor?: string | number | RGB | ThemeColor | ((s: string) => string) | SideColors;
92
+ /** Background fill (Gradient backgrounds apply in direct/rect mode). */
93
+ bgColor?: string | number | RGB | Gradient | ThemeColor;
94
+ /** Horizontal content alignment (default 'left'). */
34
95
  align?: 'left' | 'center' | 'right';
96
+ /** Vertical content alignment (default 'top'). */
35
97
  valign?: 'top' | 'middle' | 'bottom';
98
+ /** Word-wraps content to the inner width instead of truncating. */
36
99
  wrap?: boolean;
100
+ /** Title rendered into the top border. */
37
101
  title?: string;
102
+ /** Styler applied to the rendered title. */
38
103
  titleStyle?: (s: string) => string;
39
104
  }
40
105
  /**
@@ -42,47 +107,87 @@ export interface StyleOptions {
42
107
  */
43
108
  export declare function resolveSize(unit: SizeUnit | undefined, parentDim: number, contentDim: number): number;
44
109
  /**
45
- * Generates a styled box string with perfect alignment and optional wrapping.
110
+ * Generates a styled box string with perfect alignment and optional
111
+ * wrapping. This is the pure string-building form; ctx.box() adds
112
+ * placement and painting on top of it.
113
+ * @example box('hello', { width: 20, border: 'rounded', padding: [0, 1] });
46
114
  */
47
115
  export declare function box(content: string, options?: StyleOptions, parentW?: number, parentH?: number): string;
116
+ /** Joins rendered blocks side-by-side, padding each to its own width. */
48
117
  export declare function joinHorizontal(...blocks: string[]): string;
118
+ /** Stacks rendered blocks vertically (skips empty strings). */
49
119
  export declare function joinVertical(...blocks: string[]): string;
120
+ /** Returns a box() partially applied with default StyleOptions. */
50
121
  export declare function createStyle(defaults: StyleOptions): (content: string, overrides?: StyleOptions) => string;
122
+ /** Uppercased ` TEXT ` chip, wrapped by the given styler. */
51
123
  export declare function badge(text: string, colorFn?: (s: string) => string): string;
124
+ /** Options for table() / ctx.table(). */
52
125
  export interface TableOptions {
126
+ /** Overall table width: cells, '50%', or '100%' (default 80 cells). */
53
127
  width?: SizeUnit;
128
+ /** Per-column width/alignment overrides, by column index. */
54
129
  columns?: {
55
130
  width?: SizeUnit;
56
131
  align?: 'left' | 'center' | 'right';
57
132
  }[];
133
+ /** Outer border glyph set (default 'default'). */
58
134
  border?: BorderStyle;
135
+ /** [vertical, horizontal] cell padding (default [0, 1]). */
59
136
  padding?: [number, number];
60
137
  }
61
138
  /**
62
139
  * Renders a data table with perfectly aligned columns and shared borders.
63
140
  */
64
141
  export declare function table(rows: string[][], options?: TableOptions, parentW?: number): string;
142
+ /**
143
+ * Computes the scroll window that keeps `selectedIndex` visible in
144
+ * `maxVisible` rows (used by list windowing).
145
+ */
65
146
  export declare function getWindow<T>(items: T[], selectedIndex: number, maxVisible: number): {
66
147
  visible: T[];
67
148
  start: number;
68
149
  hasMoreAbove: boolean;
69
150
  hasMoreBelow: boolean;
70
151
  };
152
+ /** Options for list() / ctx.list(). */
71
153
  export interface ListOptions {
154
+ /** Prefix rendered before every item (e.g. '• '). */
72
155
  bullet?: string;
156
+ /** Leading spaces before each row. */
73
157
  indent?: number;
158
+ /** Row rendered as selected (ctx.list manages this from state). */
74
159
  focusedIndex?: number;
160
+ /** Styler for the selected row's text (default theme.focus). */
75
161
  focusStyle?: (s: string) => string;
76
- selectedBg?: string | number | RGB;
162
+ /** Background wash behind the selected row (default theme.selection). */
163
+ selectedBg?: string | number | RGB | ThemeColor;
164
+ /** Mouse-hovered row (styled with hoverBg; selection wins over hover). */
165
+ hoveredIndex?: number;
166
+ /** Background wash for the hovered row. */
167
+ hoverBg?: string | number | RGB | ThemeColor;
168
+ /** Row width for background washes: cells, '50%', or '100%'. */
77
169
  width?: SizeUnit;
170
+ /** Windows the list to N rows, scrolled to keep the selection visible. */
78
171
  maxVisible?: number;
172
+ /** Set false to disable ctx.list's built-in keyboard/mouse handling. */
79
173
  interactive?: boolean;
80
174
  }
175
+ /**
176
+ * Renders items as list rows with selection/hover washes and optional
177
+ * maxVisible windowing. Pure string form; ctx.list() adds the keyboard
178
+ * and mouse behavior on top.
179
+ */
81
180
  export declare function list(items: string[], options?: ListOptions, parentW?: number): string;
181
+ /**
182
+ * Crops a multi-line string to a width×height window starting at
183
+ * `scrollY` — the primitive for scrollable content.
184
+ */
82
185
  export declare function viewport(content: string, width: number, height: number, scrollY?: number): string;
186
+ /** Fills the whole screen background with a solid color. */
83
187
  export declare function wallpaper(state: any, options: {
84
188
  bg: any;
85
189
  }): void;
190
+ /** Fills the whole screen background with a multi-stop gradient. */
86
191
  export declare function gradient(state: any, colors: any[], options?: {
87
192
  direction?: 'vertical' | 'horizontal';
88
193
  }): void;
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAKtD,wBAAgB,OAAO,CACrB,KAAK,EAAE,WAAW,EAClB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,QAwCpB;AAED,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC;IAC3B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC;CACvC;AAED,wBAAgB,IAAI,CAClB,KAAK,EAAE,WAAW,EAClB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,WAAW,QA2CnB;AAED,wBAAgB,IAAI,CAClB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,OAAO,CAAC,IAAI,CAAM,QA8E1B;AAID,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,aAAa,GACb,SAAS,GACT,MAAM,CAAC;AAEX,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,GAAG,CAAC;CACb;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,UAAU,CAAC;IAC3E,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACpC;AAsBD;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,CAaR;AAED;;GAEG;AACH,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,YAAiB,EAC1B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CA8LR;AAED,wBAAgB,cAAc,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAsB1D;AAED,wBAAgB,YAAY,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAExD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,IACxC,SAAS,MAAM,EAAE,YAAW,YAAiB,YAGtD;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAiB,GACxC,MAAM,CAER;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,QAAQ,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;KAAE,EAAE,CAAC;IACtE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EAAE,EAAE,EAChB,OAAO,GAAE,YAAiB,EAC1B,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAmDR;AAED,wBAAgB,SAAS,CAAC,CAAC,EACzB,KAAK,EAAE,CAAC,EAAE,EACV,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM;;;;;EAgBnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC;IACnC,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,IAAI,CAClB,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,WAAgB,EACzB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAgDR;AAED,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAAU,GAClB,MAAM,CASR;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,QAGzD;AAED,wBAAgB,QAAQ,CACtB,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,GAAG,EAAE,EACb,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,UAAU,GAAG,YAAY,CAAA;CAAO,QAWxD"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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"}
package/dist/layout.js CHANGED
@@ -2,10 +2,15 @@
2
2
  * Bunti Functional Layout & Drawing
3
3
  * Strictly functional primitives for buffer manipulation and layout generation.
4
4
  */
5
- import { resolveColor } from './colors';
5
+ import { fg as fgColor, isThemeColor, resolveColor, resolveColorToRGB, } from './colors';
6
+ import { colorTier } from './detect';
6
7
  import { replaceEmojis } from './icons';
7
8
  import { charWidth, truncate, visibleWidth, wrapText } from './utils';
8
9
  // --- Functional Primitives (Buffer Manipulation) ---
10
+ /**
11
+ * Writes one cell into the back buffer (bounds-checked). Wide glyphs mark
12
+ * their tail cells as skip so the differ keeps columns aligned.
13
+ */
9
14
  export function setCell(state, x, y, cell) {
10
15
  if (x >= 0 && x < state.width && y >= 0 && y < state.height) {
11
16
  const target = state.backBuffer[y * state.width + x];
@@ -20,6 +25,10 @@ export function setCell(state, x, y, cell) {
20
25
  target.fg = undefined;
21
26
  target.fgCode = undefined;
22
27
  target.bold = false;
28
+ target.italic = false;
29
+ target.underline = false;
30
+ target.dim = false;
31
+ target.strike = false;
23
32
  }
24
33
  if (cell.fg !== undefined) {
25
34
  target.fg = cell.fg;
@@ -31,6 +40,14 @@ export function setCell(state, x, y, cell) {
31
40
  }
32
41
  if (cell.bold !== undefined)
33
42
  target.bold = cell.bold;
43
+ if (cell.italic !== undefined)
44
+ target.italic = cell.italic;
45
+ if (cell.underline !== undefined)
46
+ target.underline = cell.underline;
47
+ if (cell.dim !== undefined)
48
+ target.dim = cell.dim;
49
+ if (cell.strike !== undefined)
50
+ target.strike = cell.strike;
34
51
  target.skip = cell.skip ?? false;
35
52
  for (let dx = 1; dx < displayWidth; dx++) {
36
53
  const skipX = x + dx;
@@ -43,10 +60,67 @@ export function setCell(state, x, y, cell) {
43
60
  skip.fgCode = target.fgCode;
44
61
  skip.bgCode = target.bgCode;
45
62
  skip.bold = target.bold;
63
+ skip.italic = target.italic;
64
+ skip.underline = target.underline;
65
+ skip.dim = target.dim;
66
+ skip.strike = target.strike;
46
67
  skip.skip = true;
47
68
  }
48
69
  }
49
70
  }
71
+ /**
72
+ * Repaints existing back-buffer cells inside `area` at reduced luminance.
73
+ * Each cell's current fg/bg is resolved to exact RGB (numeric ANSI-256 codes
74
+ * via the xterm table) and scaled toward black by `amount` (0 = unchanged,
75
+ * 1 = fully black). Cells with an undefined bg keep their transparent
76
+ * background (fg-only dim); undefined fg is left alone (there is no color to
77
+ * scale). Wide-glyph tail cells (skip) are dimmed too so they stay in sync
78
+ * with their head cell. No-op on the mono tier — there is no color to dim.
79
+ */
80
+ export function dimRect(state, area, amount) {
81
+ if (colorTier() === 'mono')
82
+ return;
83
+ const t = Math.max(0, Math.min(1, amount));
84
+ if (t === 0)
85
+ return;
86
+ const keep = 1 - t;
87
+ const x0 = Math.max(0, area.x);
88
+ const y0 = Math.max(0, area.y);
89
+ const x1 = Math.min(state.width, area.x + area.width);
90
+ const y1 = Math.min(state.height, area.y + area.height);
91
+ for (let y = y0; y < y1; y++) {
92
+ const rowOffset = y * state.width;
93
+ for (let x = x0; x < x1; x++) {
94
+ const cell = state.backBuffer[rowOffset + x];
95
+ if (cell.fg !== undefined) {
96
+ const rgb = resolveColorToRGB(cell.fg);
97
+ const dimmed = {
98
+ r: Math.round(rgb.r * keep),
99
+ g: Math.round(rgb.g * keep),
100
+ b: Math.round(rgb.b * keep),
101
+ };
102
+ cell.fg = dimmed;
103
+ cell.fgCode = resolveColor(dimmed);
104
+ }
105
+ if (cell.bg !== undefined) {
106
+ const rgb = resolveColorToRGB(cell.bg);
107
+ const dimmed = {
108
+ r: Math.round(rgb.r * keep),
109
+ g: Math.round(rgb.g * keep),
110
+ b: Math.round(rgb.b * keep),
111
+ };
112
+ cell.bg = dimmed;
113
+ cell.bgCode = resolveColor(dimmed);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * Fills a w×h cell rectangle at absolute coordinates — bars, panels,
120
+ * charts, backgrounds. Pure-background fills (no `char`) preserve the
121
+ * characters already painted underneath.
122
+ * @example rect(state, 0, 0, state.width, 3, { bg: 'midnight' });
123
+ */
50
124
  export function rect(state, x, y, w, h, style) {
51
125
  const isGradient = style.bg && typeof style.bg === 'object' && 'colors' in style.bg;
52
126
  for (let dy = 0; dy < h; dy++) {
@@ -88,6 +162,12 @@ export function rect(state, x, y, w, h, style) {
88
162
  }
89
163
  }
90
164
  }
165
+ /**
166
+ * Paints a multi-line, ANSI-styled string into the back buffer at absolute
167
+ * cell coordinates, parsing SGR codes into per-cell attributes. Grapheme-
168
+ * aware: emoji and wide glyphs occupy their real column width.
169
+ * @example blit(state, 2, 1, fg('gold', 'FPS 60'));
170
+ */
91
171
  export function blit(state, startX, startY, content, style = {}) {
92
172
  const lines = content.split('\n');
93
173
  for (let row = 0; row < lines.length; row++) {
@@ -95,6 +175,10 @@ export function blit(state, startX, startY, content, style = {}) {
95
175
  let x = startX;
96
176
  let currentFg, currentBg;
97
177
  let currentBold = false;
178
+ let currentItalic = false;
179
+ let currentUnderline = false;
180
+ let currentDim = false;
181
+ let currentStrike = false;
98
182
  const regex = /\x1B\[([0-9;]*)m|([^\x1B]+)/g;
99
183
  let match = null;
100
184
  while ((match = regex.exec(line)) !== null) {
@@ -106,11 +190,32 @@ export function blit(state, startX, startY, content, style = {}) {
106
190
  currentFg = undefined;
107
191
  currentBg = undefined;
108
192
  currentBold = false;
193
+ currentItalic = false;
194
+ currentUnderline = false;
195
+ currentDim = false;
196
+ currentStrike = false;
109
197
  }
110
198
  else if (code === 1)
111
199
  currentBold = true;
112
- else if (code === 22)
200
+ else if (code === 2)
201
+ currentDim = true;
202
+ else if (code === 3)
203
+ currentItalic = true;
204
+ else if (code === 4)
205
+ currentUnderline = true;
206
+ else if (code === 9)
207
+ currentStrike = true;
208
+ else if (code === 22) {
209
+ // SGR 22 resets intensity: clears BOTH bold and dim.
113
210
  currentBold = false;
211
+ currentDim = false;
212
+ }
213
+ else if (code === 23)
214
+ currentItalic = false;
215
+ else if (code === 24)
216
+ currentUnderline = false;
217
+ else if (code === 29)
218
+ currentStrike = false;
114
219
  else if (code >= 30 && code <= 37)
115
220
  currentFg = (code - 30).toString();
116
221
  else if (code >= 40 && code <= 47)
@@ -172,6 +277,14 @@ export function blit(state, startX, startY, content, style = {}) {
172
277
  cell.bg = style.bg;
173
278
  if (currentBold)
174
279
  cell.bold = true;
280
+ if (currentItalic)
281
+ cell.italic = true;
282
+ if (currentUnderline)
283
+ cell.underline = true;
284
+ if (currentDim)
285
+ cell.dim = true;
286
+ if (currentStrike)
287
+ cell.strike = true;
175
288
  setCell(state, x, startY + row, cell);
176
289
  x += w;
177
290
  }
@@ -218,7 +331,10 @@ export function resolveSize(unit, parentDim, contentDim) {
218
331
  return contentDim;
219
332
  }
220
333
  /**
221
- * Generates a styled box string with perfect alignment and optional wrapping.
334
+ * Generates a styled box string with perfect alignment and optional
335
+ * wrapping. This is the pure string-building form; ctx.box() adds
336
+ * placement and painting on top of it.
337
+ * @example box('hello', { width: 20, border: 'rounded', padding: [0, 1] });
222
338
  */
223
339
  export function box(content, options = {}, parentW, parentH) {
224
340
  // Ensure we operate on the swapped glyphs for all layout math
@@ -257,8 +373,13 @@ export function box(content, options = {}, parentW, parentH) {
257
373
  ? null
258
374
  : BORDERS[borderStyle] || BORDERS.default;
259
375
  // Color Resolution
260
- const { fg } = require('./colors');
261
- const resolveSide = (color) => typeof color === 'function' ? color : (s) => fg(color, s);
376
+ // ThemeColors are callable, so they must be detected BEFORE the
377
+ // function-style border-wrapper path (brand check via isThemeColor).
378
+ const resolveSide = (color) => isThemeColor(color)
379
+ ? (s) => fgColor(color.rgb, s)
380
+ : typeof color === 'function'
381
+ ? color
382
+ : (s) => fgColor(color, s);
262
383
  const wrapBg = (s) => {
263
384
  if (!options.bgColor)
264
385
  return s;
@@ -267,12 +388,12 @@ export function box(content, options = {}, parentW, parentH) {
267
388
  // In string-building mode, we must ignore gradients. They are handled in absolute rect mode.
268
389
  return s;
269
390
  }
270
- const { resolveColor } = require('./colors');
271
391
  const code = resolveColor(options.bgColor);
272
- const prefix = typeof options.bgColor === 'object' || String(code).startsWith('2;')
273
- ? '48'
274
- : '48;5';
275
- const bgOn = `\x1b[${prefix};${code}m`;
392
+ if (code === undefined)
393
+ return s; // mono tier: no color output
394
+ const codeStr = String(code);
395
+ const prefix = codeStr.startsWith('2;') ? '48' : '48;5';
396
+ const bgOn = `\x1b[${prefix};${codeStr}m`;
276
397
  // Apply bg, and if the string contains resets, re-apply the bg right after the reset.
277
398
  // We add a final reset at the very end to clean up.
278
399
  return `${bgOn + s.replace(/\x1b\[0m/g, `\x1b[0m${bgOn}`)}\x1b[0m`;
@@ -374,6 +495,7 @@ export function box(content, options = {}, parentW, parentH) {
374
495
  }
375
496
  return out.join('\n');
376
497
  }
498
+ /** Joins rendered blocks side-by-side, padding each to its own width. */
377
499
  export function joinHorizontal(...blocks) {
378
500
  const parsed = blocks.map((b) => b.split('\n'));
379
501
  const maxH = Math.max(...parsed.map((p) => p.length));
@@ -397,14 +519,17 @@ export function joinHorizontal(...blocks) {
397
519
  }
398
520
  return out.join('\n');
399
521
  }
522
+ /** Stacks rendered blocks vertically (skips empty strings). */
400
523
  export function joinVertical(...blocks) {
401
524
  return blocks.filter(Boolean).join('\n');
402
525
  }
526
+ /** Returns a box() partially applied with default StyleOptions. */
403
527
  export function createStyle(defaults) {
404
528
  return (content, overrides = {}) => {
405
529
  return box(content, { ...defaults, ...overrides });
406
530
  };
407
531
  }
532
+ /** Uppercased ` TEXT ` chip, wrapped by the given styler. */
408
533
  export function badge(text, colorFn = (s) => s) {
409
534
  return colorFn(` ${text.toUpperCase()} `);
410
535
  }
@@ -448,6 +573,10 @@ export function table(rows, options = {}, parentW) {
448
573
  align: 'left',
449
574
  });
450
575
  }
576
+ /**
577
+ * Computes the scroll window that keeps `selectedIndex` visible in
578
+ * `maxVisible` rows (used by list windowing).
579
+ */
451
580
  export function getWindow(items, selectedIndex, maxVisible) {
452
581
  const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), items.length - maxVisible));
453
582
  const visible = items.slice(start, start + maxVisible);
@@ -458,22 +587,27 @@ export function getWindow(items, selectedIndex, maxVisible) {
458
587
  hasMoreBelow: start + maxVisible < items.length,
459
588
  };
460
589
  }
590
+ /**
591
+ * Renders items as list rows with selection/hover washes and optional
592
+ * maxVisible windowing. Pure string form; ctx.list() adds the keyboard
593
+ * and mouse behavior on top.
594
+ */
461
595
  export function list(items, options = {}, parentW) {
462
596
  const bullet = options.bullet || '';
463
597
  const indent = ' '.repeat(options.indent || 0);
464
598
  const resolvedW = resolveSize(options.width, parentW || 0, 0);
465
- const { resolveColor } = require('./colors');
466
- const wrapSelectedBg = (line) => {
467
- if (!options.selectedBg)
468
- return line;
469
- const code = resolveColor(options.selectedBg);
599
+ const wrapLineBg = (line, bg) => {
600
+ const code = resolveColor(bg);
601
+ if (code === undefined)
602
+ return line; // mono tier: no color output
470
603
  const codeStr = String(code);
471
- const prefix = typeof options.selectedBg === 'object' || codeStr.startsWith('2;')
472
- ? '48'
473
- : '48;5';
604
+ const prefix = codeStr.startsWith('2;') ? '48' : '48;5';
474
605
  const bgOn = `\x1b[${prefix};${codeStr}m`;
475
606
  return `${bgOn + line.replace(/\x1b\[0m/g, `\x1b[0m${bgOn}`)}\x1b[0m`;
476
607
  };
608
+ const padToWidth = (line) => resolvedW > 0
609
+ ? line + ' '.repeat(Math.max(0, resolvedW - visibleWidth(line)))
610
+ : line;
477
611
  let targetItems = items, offset = 0, hasMoreAbove = false, hasMoreBelow = false;
478
612
  if (options.maxVisible && items.length > options.maxVisible) {
479
613
  const win = getWindow(items, options.focusedIndex || 0, options.maxVisible);
@@ -490,11 +624,11 @@ export function list(items, options = {}, parentW) {
490
624
  const styledLine = options.focusStyle ? options.focusStyle(line) : line;
491
625
  if (!options.selectedBg)
492
626
  return styledLine;
493
- const paddedLine = resolvedW > 0
494
- ? styledLine +
495
- ' '.repeat(Math.max(0, resolvedW - visibleWidth(styledLine)))
496
- : styledLine;
497
- return wrapSelectedBg(paddedLine);
627
+ return wrapLineBg(padToWidth(styledLine), options.selectedBg);
628
+ }
629
+ // Hover wash (selection wins: checked above).
630
+ if (options.hoveredIndex === actualIdx && options.hoverBg) {
631
+ return wrapLineBg(padToWidth(line), options.hoverBg);
498
632
  }
499
633
  return line;
500
634
  })
@@ -506,6 +640,10 @@ export function list(items, options = {}, parentW) {
506
640
  out = `${out}\n${indent} ↓ more…`;
507
641
  return out;
508
642
  }
643
+ /**
644
+ * Crops a multi-line string to a width×height window starting at
645
+ * `scrollY` — the primitive for scrollable content.
646
+ */
509
647
  export function viewport(content, width, height, scrollY = 0) {
510
648
  const lines = content.split('\n');
511
649
  const visibleLines = lines.slice(scrollY, scrollY + height);
@@ -515,10 +653,12 @@ export function viewport(content, width, height, scrollY = 0) {
515
653
  })
516
654
  .join('\n');
517
655
  }
656
+ /** Fills the whole screen background with a solid color. */
518
657
  export function wallpaper(state, options) {
519
658
  const { bg } = options;
520
659
  rect(state, 0, 0, state.width, state.height, { char: ' ', bg });
521
660
  }
661
+ /** Fills the whole screen background with a multi-stop gradient. */
522
662
  export function gradient(state, colors, options = {}) {
523
663
  const { direction = 'vertical' } = options;
524
664
  rect(state, 0, 0, state.width, state.height, {