@boba-cli/help 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @boba-cli/help
2
+
3
+ Render short or full help text from your key bindings. Ported from the Charm `bubbles/help` component.
4
+
5
+ <img src="../../examples/help-demo.gif" width="950" alt="Help component demo" />
6
+
7
+ ```ts
8
+ import { HelpModel } from '@boba-cli/help'
9
+
10
+ const help = HelpModel.new({ width: 80 })
11
+ const text = help.view(keyMap)
12
+ ```
13
+
14
+ ## HelpBubble
15
+
16
+ A self-contained help bubble component with a scrollable viewport for displaying keyboard shortcuts.
17
+
18
+ <img src="../../examples/help-bubble-demo.gif" width="950" alt="HelpBubble component demo" />
19
+
20
+ ```ts
21
+ import { HelpBubble } from '@boba-cli/help'
22
+
23
+ const bubble = HelpBubble.new(
24
+ true,
25
+ 'Keyboard Shortcuts',
26
+ { foreground: '#f8f8f2', background: '#6272a4' },
27
+ [
28
+ { key: '↑/↓', description: 'Navigate' },
29
+ { key: 'enter', description: 'Select' },
30
+ { key: 'q', description: 'Quit' },
31
+ ],
32
+ )
33
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,315 @@
1
+ 'use strict';
2
+
3
+ var chapstick = require('@boba-cli/chapstick');
4
+ var viewport = require('@boba-cli/viewport');
5
+
6
+ // src/model.ts
7
+ function defaultStyles() {
8
+ const keyStyle = new chapstick.Style().foreground("#626262");
9
+ const descStyle = new chapstick.Style().foreground("#4A4A4A");
10
+ const sepStyle = new chapstick.Style().foreground("#3C3C3C");
11
+ return {
12
+ shortKey: keyStyle,
13
+ shortDesc: descStyle,
14
+ shortSeparator: sepStyle,
15
+ ellipsis: sepStyle,
16
+ fullKey: keyStyle,
17
+ fullDesc: descStyle,
18
+ fullSeparator: sepStyle
19
+ };
20
+ }
21
+
22
+ // src/model.ts
23
+ var HelpModel = class _HelpModel {
24
+ width;
25
+ showAll;
26
+ shortSeparator;
27
+ fullSeparator;
28
+ ellipsis;
29
+ styles;
30
+ constructor(options = {}) {
31
+ this.width = options.width ?? 0;
32
+ this.showAll = options.showAll ?? false;
33
+ this.shortSeparator = options.shortSeparator ?? " \u2022 ";
34
+ this.fullSeparator = options.fullSeparator ?? " ";
35
+ this.ellipsis = options.ellipsis ?? "\u2026";
36
+ const defaults = defaultStyles();
37
+ this.styles = { ...defaults, ...options.styles };
38
+ }
39
+ /** Create a new help model with defaults applied. */
40
+ static new(options = {}) {
41
+ return new _HelpModel(options);
42
+ }
43
+ /** Return a new model with updated width. */
44
+ withWidth(width) {
45
+ return this.with({ width });
46
+ }
47
+ /** Return a new model with showAll toggled. */
48
+ withShowAll(showAll) {
49
+ return this.with({ showAll });
50
+ }
51
+ /** Tea update: no-op (view-only component). */
52
+ update(_msg) {
53
+ return [this, null];
54
+ }
55
+ /** Render help text from the provided key map. */
56
+ view(keyMap) {
57
+ if (this.showAll) {
58
+ return this.fullHelpView(keyMap.fullHelp());
59
+ }
60
+ return this.shortHelpView(keyMap.shortHelp());
61
+ }
62
+ /** Render single-line help. */
63
+ shortHelpView(bindings) {
64
+ if (!bindings || bindings.length === 0) {
65
+ return "";
66
+ }
67
+ let result = "";
68
+ let totalWidth = 0;
69
+ const separator = this.styles.shortSeparator.inline(true).render(this.shortSeparator);
70
+ for (const [i, kb] of bindings.entries()) {
71
+ if (!kb.enabled()) continue;
72
+ const sep = totalWidth > 0 && i < bindings.length ? separator : "";
73
+ const help = kb.help();
74
+ const item = sep + this.styles.shortKey.inline(true).render(help.key) + " " + this.styles.shortDesc.inline(true).render(help.desc);
75
+ const itemWidth = chapstick.width(item);
76
+ const [tail, ok] = this.shouldAddItem(totalWidth, itemWidth);
77
+ if (!ok) {
78
+ if (tail) {
79
+ result += tail;
80
+ }
81
+ break;
82
+ }
83
+ totalWidth += itemWidth;
84
+ result += item;
85
+ }
86
+ return result;
87
+ }
88
+ /** Render multi-column help. */
89
+ fullHelpView(groups) {
90
+ if (!groups || groups.length === 0) {
91
+ return "";
92
+ }
93
+ const out = [];
94
+ let totalWidth = 0;
95
+ const separator = this.styles.fullSeparator.inline(true).render(this.fullSeparator);
96
+ for (const [i, group] of groups.entries()) {
97
+ if (!group || !shouldRenderColumn(group)) {
98
+ continue;
99
+ }
100
+ const keys = [];
101
+ const descriptions = [];
102
+ for (const kb of group) {
103
+ if (!kb.enabled()) continue;
104
+ const help = kb.help();
105
+ keys.push(this.styles.fullKey.render(help.key));
106
+ descriptions.push(this.styles.fullDesc.render(help.desc));
107
+ }
108
+ const sep = totalWidth > 0 && i < groups.length ? separator : "";
109
+ const parts = sep ? [sep, keys.join("\n"), " ", descriptions.join("\n")] : [keys.join("\n"), " ", descriptions.join("\n")];
110
+ const column = chapstick.joinHorizontal(...parts);
111
+ const columnWidth = chapstick.width(column);
112
+ const [tail, ok] = this.shouldAddItem(totalWidth, columnWidth);
113
+ if (!ok) {
114
+ if (tail) {
115
+ out.push(tail);
116
+ }
117
+ break;
118
+ }
119
+ totalWidth += columnWidth;
120
+ out.push(column);
121
+ }
122
+ return chapstick.joinHorizontal(...out);
123
+ }
124
+ shouldAddItem(totalWidth, width) {
125
+ if (this.width > 0 && totalWidth + width > this.width) {
126
+ const tail = " " + this.styles.ellipsis.inline(true).render(this.ellipsis);
127
+ if (totalWidth + chapstick.width(tail) < this.width) {
128
+ return [tail, false];
129
+ }
130
+ }
131
+ return ["", true];
132
+ }
133
+ with(patch) {
134
+ return new _HelpModel({
135
+ width: this.width,
136
+ showAll: this.showAll,
137
+ shortSeparator: this.shortSeparator,
138
+ fullSeparator: this.fullSeparator,
139
+ ellipsis: this.ellipsis,
140
+ styles: this.styles,
141
+ ...patch
142
+ });
143
+ }
144
+ };
145
+ function shouldRenderColumn(bindings) {
146
+ return bindings.some((b) => b.enabled());
147
+ }
148
+ var KEY_WIDTH = 12;
149
+ function generateHelpScreen(title, titleColor, entries, width, height) {
150
+ let helpScreen = "";
151
+ for (const content of entries) {
152
+ const paddedKey = content.key.padEnd(KEY_WIDTH);
153
+ const keyText = new chapstick.Style().bold(true).foreground({ dark: "#ffffff", light: "#000000" }).render(paddedKey);
154
+ const descriptionText = new chapstick.Style().foreground({ dark: "#ffffff", light: "#000000" }).render(content.description);
155
+ const row = chapstick.joinHorizontal(2, keyText, descriptionText);
156
+ helpScreen += `${row}
157
+ `;
158
+ }
159
+ const titleText = new chapstick.Style().bold(true).background(titleColor.background).foreground(titleColor.foreground).padding(0, 1).italic(true).render(title);
160
+ return new chapstick.Style().width(width).height(height).render(chapstick.joinVertical(titleText, helpScreen));
161
+ }
162
+ var HelpBubble = class _HelpBubble {
163
+ /**
164
+ * The viewport model that handles scrolling and content display.
165
+ */
166
+ viewport;
167
+ /**
168
+ * The array of help entries displayed in the bubble.
169
+ */
170
+ entries;
171
+ /**
172
+ * The title text shown at the top of the help screen.
173
+ */
174
+ title;
175
+ /**
176
+ * The color configuration for the title bar.
177
+ */
178
+ titleColor;
179
+ /**
180
+ * Whether the help bubble is active and receiving input.
181
+ */
182
+ active;
183
+ constructor(viewport, entries, title, titleColor, active) {
184
+ this.viewport = viewport;
185
+ this.entries = entries;
186
+ this.title = title;
187
+ this.titleColor = titleColor;
188
+ this.active = active;
189
+ }
190
+ /**
191
+ * Create a new help bubble.
192
+ * @param active - Whether the component receives input
193
+ * @param title - Title text for the help screen
194
+ * @param titleColor - Color configuration for the title
195
+ * @param entries - Array of help entries to display
196
+ */
197
+ static new(active, title, titleColor, entries) {
198
+ const viewport$1 = viewport.ViewportModel.new({ width: 0, height: 0 });
199
+ const content = generateHelpScreen(title, titleColor, entries, 0, 0);
200
+ const initializedViewport = viewport$1.setContent(content);
201
+ return new _HelpBubble(
202
+ initializedViewport,
203
+ entries,
204
+ title,
205
+ titleColor,
206
+ active
207
+ );
208
+ }
209
+ /**
210
+ * Set the size of the help bubble and regenerate content.
211
+ * @param width - Width in characters
212
+ * @param height - Height in lines
213
+ */
214
+ setSize(width, height) {
215
+ const content = generateHelpScreen(
216
+ this.title,
217
+ this.titleColor,
218
+ this.entries,
219
+ width,
220
+ height
221
+ );
222
+ const updatedViewport = this.viewport.setWidth(width).setHeight(height).setContent(content);
223
+ return new _HelpBubble(
224
+ updatedViewport,
225
+ this.entries,
226
+ this.title,
227
+ this.titleColor,
228
+ this.active
229
+ );
230
+ }
231
+ /**
232
+ * Set whether the component is active (receives input).
233
+ * @param active - Active state
234
+ */
235
+ setIsActive(active) {
236
+ if (this.active === active) return this;
237
+ return new _HelpBubble(
238
+ this.viewport,
239
+ this.entries,
240
+ this.title,
241
+ this.titleColor,
242
+ active
243
+ );
244
+ }
245
+ /**
246
+ * Set the title color and regenerate content.
247
+ * @param color - New title color configuration
248
+ */
249
+ setTitleColor(color) {
250
+ const content = generateHelpScreen(
251
+ this.title,
252
+ color,
253
+ this.entries,
254
+ this.viewport.width,
255
+ this.viewport.height
256
+ );
257
+ const updatedViewport = this.viewport.setContent(content);
258
+ return new _HelpBubble(
259
+ updatedViewport,
260
+ this.entries,
261
+ this.title,
262
+ color,
263
+ this.active
264
+ );
265
+ }
266
+ /**
267
+ * Scroll to the top of the viewport.
268
+ */
269
+ gotoTop() {
270
+ const updatedViewport = this.viewport.scrollToTop();
271
+ if (updatedViewport === this.viewport) return this;
272
+ return new _HelpBubble(
273
+ updatedViewport,
274
+ this.entries,
275
+ this.title,
276
+ this.titleColor,
277
+ this.active
278
+ );
279
+ }
280
+ /**
281
+ * Handle Tea update messages (viewport scrolling when active).
282
+ * @param msg - Tea message
283
+ */
284
+ update(msg) {
285
+ if (!this.active) {
286
+ return [this, null];
287
+ }
288
+ const [updatedViewport, cmd] = this.viewport.update(msg);
289
+ if (updatedViewport === this.viewport) {
290
+ return [this, cmd];
291
+ }
292
+ return [
293
+ new _HelpBubble(
294
+ updatedViewport,
295
+ this.entries,
296
+ this.title,
297
+ this.titleColor,
298
+ this.active
299
+ ),
300
+ cmd
301
+ ];
302
+ }
303
+ /**
304
+ * Render the help screen.
305
+ */
306
+ view() {
307
+ return this.viewport.view();
308
+ }
309
+ };
310
+
311
+ exports.HelpBubble = HelpBubble;
312
+ exports.HelpModel = HelpModel;
313
+ exports.defaultStyles = defaultStyles;
314
+ //# sourceMappingURL=index.cjs.map
315
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/styles.ts","../src/model.ts","../src/bubble.ts"],"names":["Style","stringWidth","joinHorizontal","joinVertical","viewport","ViewportModel"],"mappings":";;;;;;AAOO,SAAS,aAAA,GAA4B;AAC1C,EAAA,MAAM,QAAA,GAAW,IAAIA,eAAA,EAAM,CAAE,WAAW,SAAS,CAAA;AACjD,EAAA,MAAM,SAAA,GAAY,IAAIA,eAAA,EAAM,CAAE,WAAW,SAAS,CAAA;AAClD,EAAA,MAAM,QAAA,GAAW,IAAIA,eAAA,EAAM,CAAE,WAAW,SAAS,CAAA;AAEjD,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,QAAA;AAAA,IACV,SAAA,EAAW,SAAA;AAAA,IACX,cAAA,EAAgB,QAAA;AAAA,IAChB,QAAA,EAAU,QAAA;AAAA,IACV,OAAA,EAAS,QAAA;AAAA,IACT,QAAA,EAAU,SAAA;AAAA,IACV,aAAA,EAAe;AAAA,GACjB;AACF;;;ACEO,IAAM,SAAA,GAAN,MAAM,UAAA,CAAU;AAAA,EACZ,KAAA;AAAA,EACA,OAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EAED,WAAA,CAAY,OAAA,GAAuB,EAAC,EAAG;AAC7C,IAAA,IAAA,CAAK,KAAA,GAAQ,QAAQ,KAAA,IAAS,CAAA;AAC9B,IAAA,IAAA,CAAK,OAAA,GAAU,QAAQ,OAAA,IAAW,KAAA;AAClC,IAAA,IAAA,CAAK,cAAA,GAAiB,QAAQ,cAAA,IAAkB,UAAA;AAChD,IAAA,IAAA,CAAK,aAAA,GAAgB,QAAQ,aAAA,IAAiB,MAAA;AAC9C,IAAA,IAAA,CAAK,QAAA,GAAW,QAAQ,QAAA,IAAY,QAAA;AACpC,IAAA,MAAM,WAAW,aAAA,EAAc;AAC/B,IAAA,IAAA,CAAK,SAAS,EAAE,GAAG,QAAA,EAAU,GAAG,QAAQ,MAAA,EAAO;AAAA,EACjD;AAAA;AAAA,EAGA,OAAO,GAAA,CAAI,OAAA,GAAuB,EAAC,EAAc;AAC/C,IAAA,OAAO,IAAI,WAAU,OAAO,CAAA;AAAA,EAC9B;AAAA;AAAA,EAGA,UAAU,KAAA,EAA0B;AAClC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA;AAAA,EAC5B;AAAA;AAAA,EAGA,YAAY,OAAA,EAA6B;AACvC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,EAAE,OAAA,EAAS,CAAA;AAAA,EAC9B;AAAA;AAAA,EAGA,OAAO,IAAA,EAAkC;AACvC,IAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,EACpB;AAAA;AAAA,EAGA,KAAK,MAAA,EAAwB;AAC3B,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,OAAO,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,QAAA,EAAU,CAAA;AAAA,IAC5C;AACA,IAAA,OAAO,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,SAAA,EAAW,CAAA;AAAA,EAC9C;AAAA;AAAA,EAGA,cAAc,QAAA,EAA6B;AACzC,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AACtC,MAAA,OAAO,EAAA;AAAA,IACT;AAEA,IAAA,IAAI,MAAA,GAAS,EAAA;AACb,IAAA,IAAI,UAAA,GAAa,CAAA;AACjB,IAAA,MAAM,SAAA,GAAY,KAAK,MAAA,CAAO,cAAA,CAC3B,OAAO,IAAI,CAAA,CACX,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA;AAE7B,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,EAAE,CAAA,IAAK,QAAA,CAAS,SAAQ,EAAG;AACxC,MAAA,IAAI,CAAC,EAAA,CAAG,OAAA,EAAQ,EAAG;AAEnB,MAAA,MAAM,MAAM,UAAA,GAAa,CAAA,IAAK,CAAA,GAAI,QAAA,CAAS,SAAS,SAAA,GAAY,EAAA;AAChE,MAAA,MAAM,IAAA,GAAO,GAAG,IAAA,EAAK;AACrB,MAAA,MAAM,IAAA,GACJ,MACA,IAAA,CAAK,MAAA,CAAO,SAAS,MAAA,CAAO,IAAI,EAAE,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,GACjD,GAAA,GACA,KAAK,MAAA,CAAO,SAAA,CAAU,OAAO,IAAI,CAAA,CAAE,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AACrD,MAAA,MAAM,SAAA,GAAYC,gBAAY,IAAI,CAAA;AAElC,MAAA,MAAM,CAAC,IAAA,EAAM,EAAE,IAAI,IAAA,CAAK,aAAA,CAAc,YAAY,SAAS,CAAA;AAC3D,MAAA,IAAI,CAAC,EAAA,EAAI;AACP,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,MAAA,IAAU,IAAA;AAAA,QACZ;AACA,QAAA;AAAA,MACF;AAEA,MAAA,UAAA,IAAc,SAAA;AACd,MAAA,MAAA,IAAU,IAAA;AAAA,IACZ;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA,EAGA,aAAa,MAAA,EAA6B;AACxC,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG;AAClC,MAAA,OAAO,EAAA;AAAA,IACT;AAEA,IAAA,MAAM,MAAgB,EAAC;AACvB,IAAA,IAAI,UAAA,GAAa,CAAA;AACjB,IAAA,MAAM,SAAA,GAAY,KAAK,MAAA,CAAO,aAAA,CAC3B,OAAO,IAAI,CAAA,CACX,MAAA,CAAO,IAAA,CAAK,aAAa,CAAA;AAE5B,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,KAAK,CAAA,IAAK,MAAA,CAAO,SAAQ,EAAG;AACzC,MAAA,IAAI,CAAC,KAAA,IAAS,CAAC,kBAAA,CAAmB,KAAK,CAAA,EAAG;AACxC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAiB,EAAC;AACxB,MAAA,MAAM,eAAyB,EAAC;AAChC,MAAA,KAAA,MAAW,MAAM,KAAA,EAAO;AACtB,QAAA,IAAI,CAAC,EAAA,CAAG,OAAA,EAAQ,EAAG;AACnB,QAAA,MAAM,IAAA,GAAO,GAAG,IAAA,EAAK;AACrB,QAAA,IAAA,CAAK,KAAK,IAAA,CAAK,MAAA,CAAO,QAAQ,MAAA,CAAO,IAAA,CAAK,GAAG,CAAC,CAAA;AAC9C,QAAA,YAAA,CAAa,KAAK,IAAA,CAAK,MAAA,CAAO,SAAS,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,MAC1D;AAEA,MAAA,MAAM,MAAM,UAAA,GAAa,CAAA,IAAK,CAAA,GAAI,MAAA,CAAO,SAAS,SAAA,GAAY,EAAA;AAC9D,MAAA,MAAM,KAAA,GAAQ,MACV,CAAC,GAAA,EAAK,KAAK,IAAA,CAAK,IAAI,CAAA,EAAG,GAAA,EAAK,YAAA,CAAa,IAAA,CAAK,IAAI,CAAC,CAAA,GACnD,CAAC,IAAA,CAAK,IAAA,CAAK,IAAI,GAAG,GAAA,EAAK,YAAA,CAAa,IAAA,CAAK,IAAI,CAAC,CAAA;AAClD,MAAA,MAAM,MAAA,GAASC,wBAAA,CAAe,GAAG,KAAK,CAAA;AACtC,MAAA,MAAM,WAAA,GAAcD,gBAAY,MAAM,CAAA;AAEtC,MAAA,MAAM,CAAC,IAAA,EAAM,EAAE,IAAI,IAAA,CAAK,aAAA,CAAc,YAAY,WAAW,CAAA;AAC7D,MAAA,IAAI,CAAC,EAAA,EAAI;AACP,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AAAA,QACf;AACA,QAAA;AAAA,MACF;AAEA,MAAA,UAAA,IAAc,WAAA;AACd,MAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,IACjB;AAEA,IAAA,OAAOC,wBAAA,CAAe,GAAG,GAAG,CAAA;AAAA,EAC9B;AAAA,EAEQ,aAAA,CAAc,YAAoB,KAAA,EAAkC;AAC1E,IAAA,IAAI,KAAK,KAAA,GAAQ,CAAA,IAAK,UAAA,GAAa,KAAA,GAAQ,KAAK,KAAA,EAAO;AACrD,MAAA,MAAM,IAAA,GAAO,GAAA,GAAM,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAO,IAAI,CAAA,CAAE,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA;AACzE,MAAA,IAAI,UAAA,GAAaD,eAAA,CAAY,IAAI,CAAA,GAAI,KAAK,KAAA,EAAO;AAC/C,QAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AAAA,MACrB;AAAA,IACF;AACA,IAAA,OAAO,CAAC,IAAI,IAAI,CAAA;AAAA,EAClB;AAAA,EAEQ,KAAK,KAAA,EAAwC;AACnD,IAAA,OAAO,IAAI,UAAA,CAAU;AAAA,MACnB,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,gBAAgB,IAAA,CAAK,cAAA;AAAA,MACrB,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,GAAG;AAAA,KACJ,CAAA;AAAA,EACH;AACF;AAEA,SAAS,mBAAmB,QAAA,EAA8B;AACxD,EAAA,OAAO,SAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,CAAA;AACzC;AChLA,IAAM,SAAA,GAAY,EAAA;AAMlB,SAAS,kBAAA,CACP,KAAA,EACA,UAAA,EACA,OAAA,EACA,OACA,MAAA,EACQ;AACR,EAAA,IAAI,UAAA,GAAa,EAAA;AAGjB,EAAA,KAAA,MAAW,WAAW,OAAA,EAAS;AAC7B,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,MAAA,CAAO,SAAS,CAAA;AAC9C,IAAA,MAAM,UAAU,IAAID,eAAAA,EAAM,CACvB,IAAA,CAAK,IAAI,CAAA,CACT,UAAA,CAAW,EAAE,IAAA,EAAM,WAAW,KAAA,EAAO,SAAA,EAAW,CAAA,CAChD,OAAO,SAAS,CAAA;AAEnB,IAAA,MAAM,eAAA,GAAkB,IAAIA,eAAAA,EAAM,CAC/B,WAAW,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAA,EAAW,CAAA,CAChD,MAAA,CAAO,QAAQ,WAAW,CAAA;AAE7B,IAAA,MAAM,GAAA,GAAME,wBAAAA,CAAe,CAAA,EAAG,OAAA,EAAS,eAAe,CAAA;AACtD,IAAA,UAAA,IAAc,GAAG,GAAG;AAAA,CAAA;AAAA,EACtB;AAGA,EAAA,MAAM,SAAA,GAAY,IAAIF,eAAAA,EAAM,CACzB,KAAK,IAAI,CAAA,CACT,UAAA,CAAW,UAAA,CAAW,UAAU,CAAA,CAChC,WAAW,UAAA,CAAW,UAAU,CAAA,CAChC,OAAA,CAAQ,CAAA,EAAG,CAAC,EACZ,MAAA,CAAO,IAAI,CAAA,CACX,MAAA,CAAO,KAAK,CAAA;AAGf,EAAA,OAAO,IAAIA,eAAAA,EAAM,CACd,KAAA,CAAM,KAAK,CAAA,CACX,MAAA,CAAO,MAAM,CAAA,CACb,MAAA,CAAOG,sBAAA,CAAa,SAAA,EAAW,UAAU,CAAC,CAAA;AAC/C;AAOO,IAAM,UAAA,GAAN,MAAM,WAAA,CAAW;AAAA;AAAA;AAAA;AAAA,EAIb,QAAA;AAAA;AAAA;AAAA;AAAA,EAIA,OAAA;AAAA;AAAA;AAAA;AAAA,EAIA,KAAA;AAAA;AAAA;AAAA;AAAA,EAIA,UAAA;AAAA;AAAA;AAAA;AAAA,EAIA,MAAA;AAAA,EAED,WAAA,CACN,QAAA,EACA,OAAA,EACA,KAAA,EACA,YACA,MAAA,EACA;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,GAAA,CACL,MAAA,EACA,KAAA,EACA,YACA,OAAA,EACY;AACZ,IAAA,MAAMC,UAAA,GAAWC,uBAAc,GAAA,CAAI,EAAE,OAAO,CAAA,EAAG,MAAA,EAAQ,GAAG,CAAA;AAC1D,IAAA,MAAM,UAAU,kBAAA,CAAmB,KAAA,EAAO,UAAA,EAAY,OAAA,EAAS,GAAG,CAAC,CAAA;AACnE,IAAA,MAAM,mBAAA,GAAsBD,UAAA,CAAS,UAAA,CAAW,OAAO,CAAA;AAEvD,IAAA,OAAO,IAAI,WAAA;AAAA,MACT,mBAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAA,CAAQ,OAAe,MAAA,EAA4B;AACjD,IAAA,MAAM,OAAA,GAAU,kBAAA;AAAA,MACd,IAAA,CAAK,KAAA;AAAA,MACL,IAAA,CAAK,UAAA;AAAA,MACL,IAAA,CAAK,OAAA;AAAA,MACL,KAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,QAAA,CAC1B,QAAA,CAAS,KAAK,EACd,SAAA,CAAU,MAAM,CAAA,CAChB,UAAA,CAAW,OAAO,CAAA;AAErB,IAAA,OAAO,IAAI,WAAA;AAAA,MACT,eAAA;AAAA,MACA,IAAA,CAAK,OAAA;AAAA,MACL,IAAA,CAAK,KAAA;AAAA,MACL,IAAA,CAAK,UAAA;AAAA,MACL,IAAA,CAAK;AAAA,KACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,MAAA,EAA6B;AACvC,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,MAAA,EAAQ,OAAO,IAAA;AACnC,IAAA,OAAO,IAAI,WAAA;AAAA,MACT,IAAA,CAAK,QAAA;AAAA,MACL,IAAA,CAAK,OAAA;AAAA,MACL,IAAA,CAAK,KAAA;AAAA,MACL,IAAA,CAAK,UAAA;AAAA,MACL;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,KAAA,EAA+B;AAC3C,IAAA,MAAM,OAAA,GAAU,kBAAA;AAAA,MACd,IAAA,CAAK,KAAA;AAAA,MACL,KAAA;AAAA,MACA,IAAA,CAAK,OAAA;AAAA,MACL,KAAK,QAAA,CAAS,KAAA;AAAA,MACd,KAAK,QAAA,CAAS;AAAA,KAChB;AACA,IAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,QAAA,CAAS,UAAA,CAAW,OAAO,CAAA;AAExD,IAAA,OAAO,IAAI,WAAA;AAAA,MACT,eAAA;AAAA,MACA,IAAA,CAAK,OAAA;AAAA,MACL,IAAA,CAAK,KAAA;AAAA,MACL,KAAA;AAAA,MACA,IAAA,CAAK;AAAA,KACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,GAAsB;AACpB,IAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,QAAA,CAAS,WAAA,EAAY;AAClD,IAAA,IAAI,eAAA,KAAoB,IAAA,CAAK,QAAA,EAAU,OAAO,IAAA;AAE9C,IAAA,OAAO,IAAI,WAAA;AAAA,MACT,eAAA;AAAA,MACA,IAAA,CAAK,OAAA;AAAA,MACL,IAAA,CAAK,KAAA;AAAA,MACL,IAAA,CAAK,UAAA;AAAA,MACL,IAAA,CAAK;AAAA,KACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,GAAA,EAAkC;AACvC,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAChB,MAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,IACpB;AAEA,IAAA,MAAM,CAAC,eAAA,EAAiB,GAAG,IAAI,IAAA,CAAK,QAAA,CAAS,OAAO,GAAG,CAAA;AAEvD,IAAA,IAAI,eAAA,KAAoB,KAAK,QAAA,EAAU;AACrC,MAAA,OAAO,CAAC,MAAM,GAAG,CAAA;AAAA,IACnB;AAEA,IAAA,OAAO;AAAA,MACL,IAAI,WAAA;AAAA,QACF,eAAA;AAAA,QACA,IAAA,CAAK,OAAA;AAAA,QACL,IAAA,CAAK,KAAA;AAAA,QACL,IAAA,CAAK,UAAA;AAAA,QACL,IAAA,CAAK;AAAA,OACP;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,SAAS,IAAA,EAAK;AAAA,EAC5B;AACF","file":"index.cjs","sourcesContent":["import { Style } from '@boba-cli/chapstick'\nimport type { HelpStyles } from './types.js'\n\n/**\n * Default styling for help output.\n * @public\n */\nexport function defaultStyles(): HelpStyles {\n const keyStyle = new Style().foreground('#626262')\n const descStyle = new Style().foreground('#4A4A4A')\n const sepStyle = new Style().foreground('#3C3C3C')\n\n return {\n shortKey: keyStyle,\n shortDesc: descStyle,\n shortSeparator: sepStyle,\n ellipsis: sepStyle,\n fullKey: keyStyle,\n fullDesc: descStyle,\n fullSeparator: sepStyle,\n }\n}\n","import { joinHorizontal, width as stringWidth } from '@boba-cli/chapstick'\nimport { type Cmd, type Msg } from '@boba-cli/tea'\nimport type { Binding } from '@boba-cli/key'\nimport { defaultStyles } from './styles.js'\nimport type { HelpStyles, KeyMap } from './types.js'\n\n/**\n * Options for configuring the help component.\n * @public\n */\nexport interface HelpOptions {\n width?: number\n showAll?: boolean\n shortSeparator?: string\n fullSeparator?: string\n ellipsis?: string\n styles?: Partial<HelpStyles>\n}\n\n/**\n * Help view renderer.\n * @public\n */\nexport class HelpModel {\n readonly width: number\n readonly showAll: boolean\n readonly shortSeparator: string\n readonly fullSeparator: string\n readonly ellipsis: string\n readonly styles: HelpStyles\n\n private constructor(options: HelpOptions = {}) {\n this.width = options.width ?? 0\n this.showAll = options.showAll ?? false\n this.shortSeparator = options.shortSeparator ?? ' • '\n this.fullSeparator = options.fullSeparator ?? ' '\n this.ellipsis = options.ellipsis ?? '…'\n const defaults = defaultStyles()\n this.styles = { ...defaults, ...options.styles }\n }\n\n /** Create a new help model with defaults applied. */\n static new(options: HelpOptions = {}): HelpModel {\n return new HelpModel(options)\n }\n\n /** Return a new model with updated width. */\n withWidth(width: number): HelpModel {\n return this.with({ width })\n }\n\n /** Return a new model with showAll toggled. */\n withShowAll(showAll: boolean): HelpModel {\n return this.with({ showAll })\n }\n\n /** Tea update: no-op (view-only component). */\n update(_msg: Msg): [HelpModel, Cmd<Msg>] {\n return [this, null]\n }\n\n /** Render help text from the provided key map. */\n view(keyMap: KeyMap): string {\n if (this.showAll) {\n return this.fullHelpView(keyMap.fullHelp())\n }\n return this.shortHelpView(keyMap.shortHelp())\n }\n\n /** Render single-line help. */\n shortHelpView(bindings: Binding[]): string {\n if (!bindings || bindings.length === 0) {\n return ''\n }\n\n let result = ''\n let totalWidth = 0\n const separator = this.styles.shortSeparator\n .inline(true)\n .render(this.shortSeparator)\n\n for (const [i, kb] of bindings.entries()) {\n if (!kb.enabled()) continue\n\n const sep = totalWidth > 0 && i < bindings.length ? separator : ''\n const help = kb.help()\n const item =\n sep +\n this.styles.shortKey.inline(true).render(help.key) +\n ' ' +\n this.styles.shortDesc.inline(true).render(help.desc)\n const itemWidth = stringWidth(item)\n\n const [tail, ok] = this.shouldAddItem(totalWidth, itemWidth)\n if (!ok) {\n if (tail) {\n result += tail\n }\n break\n }\n\n totalWidth += itemWidth\n result += item\n }\n\n return result\n }\n\n /** Render multi-column help. */\n fullHelpView(groups: Binding[][]): string {\n if (!groups || groups.length === 0) {\n return ''\n }\n\n const out: string[] = []\n let totalWidth = 0\n const separator = this.styles.fullSeparator\n .inline(true)\n .render(this.fullSeparator)\n\n for (const [i, group] of groups.entries()) {\n if (!group || !shouldRenderColumn(group)) {\n continue\n }\n\n const keys: string[] = []\n const descriptions: string[] = []\n for (const kb of group) {\n if (!kb.enabled()) continue\n const help = kb.help()\n keys.push(this.styles.fullKey.render(help.key))\n descriptions.push(this.styles.fullDesc.render(help.desc))\n }\n\n const sep = totalWidth > 0 && i < groups.length ? separator : ''\n const parts = sep\n ? [sep, keys.join('\\n'), ' ', descriptions.join('\\n')]\n : [keys.join('\\n'), ' ', descriptions.join('\\n')]\n const column = joinHorizontal(...parts)\n const columnWidth = stringWidth(column)\n\n const [tail, ok] = this.shouldAddItem(totalWidth, columnWidth)\n if (!ok) {\n if (tail) {\n out.push(tail)\n }\n break\n }\n\n totalWidth += columnWidth\n out.push(column)\n }\n\n return joinHorizontal(...out)\n }\n\n private shouldAddItem(totalWidth: number, width: number): [string, boolean] {\n if (this.width > 0 && totalWidth + width > this.width) {\n const tail = ' ' + this.styles.ellipsis.inline(true).render(this.ellipsis)\n if (totalWidth + stringWidth(tail) < this.width) {\n return [tail, false]\n }\n }\n return ['', true]\n }\n\n private with(patch: Partial<HelpOptions>): HelpModel {\n return new HelpModel({\n width: this.width,\n showAll: this.showAll,\n shortSeparator: this.shortSeparator,\n fullSeparator: this.fullSeparator,\n ellipsis: this.ellipsis,\n styles: this.styles,\n ...patch,\n })\n }\n}\n\nfunction shouldRenderColumn(bindings: Binding[]): boolean {\n return bindings.some((b) => b.enabled())\n}\n","import { Style, joinVertical, joinHorizontal } from '@boba-cli/chapstick'\nimport { ViewportModel } from '@boba-cli/viewport'\nimport { type Cmd, type Msg } from '@boba-cli/tea'\nimport type { Entry, TitleColor } from './bubble-types.js'\n\nconst KEY_WIDTH = 12\n\n/**\n * Generates the help screen content with styled title and entries.\n * @internal\n */\nfunction generateHelpScreen(\n title: string,\n titleColor: TitleColor,\n entries: Entry[],\n width: number,\n height: number,\n): string {\n let helpScreen = ''\n\n // Build each entry row with two-column layout\n for (const content of entries) {\n const paddedKey = content.key.padEnd(KEY_WIDTH)\n const keyText = new Style()\n .bold(true)\n .foreground({ dark: '#ffffff', light: '#000000' })\n .render(paddedKey)\n\n const descriptionText = new Style()\n .foreground({ dark: '#ffffff', light: '#000000' })\n .render(content.description)\n\n const row = joinHorizontal(2, keyText, descriptionText)\n helpScreen += `${row}\\n`\n }\n\n // Create styled title\n const titleText = new Style()\n .bold(true)\n .background(titleColor.background)\n .foreground(titleColor.foreground)\n .padding(0, 1)\n .italic(true)\n .render(title)\n\n // Combine title and entries, apply width/height constraints\n return new Style()\n .width(width)\n .height(height)\n .render(joinVertical(titleText, helpScreen))\n}\n\n/**\n * Model representing a scrollable help bubble.\n * Displays a styled title and a list of key binding entries in a scrollable viewport.\n * @public\n */\nexport class HelpBubble {\n /**\n * The viewport model that handles scrolling and content display.\n */\n readonly viewport: ViewportModel\n /**\n * The array of help entries displayed in the bubble.\n */\n readonly entries: Entry[]\n /**\n * The title text shown at the top of the help screen.\n */\n readonly title: string\n /**\n * The color configuration for the title bar.\n */\n readonly titleColor: TitleColor\n /**\n * Whether the help bubble is active and receiving input.\n */\n readonly active: boolean\n\n private constructor(\n viewport: ViewportModel,\n entries: Entry[],\n title: string,\n titleColor: TitleColor,\n active: boolean,\n ) {\n this.viewport = viewport\n this.entries = entries\n this.title = title\n this.titleColor = titleColor\n this.active = active\n }\n\n /**\n * Create a new help bubble.\n * @param active - Whether the component receives input\n * @param title - Title text for the help screen\n * @param titleColor - Color configuration for the title\n * @param entries - Array of help entries to display\n */\n static new(\n active: boolean,\n title: string,\n titleColor: TitleColor,\n entries: Entry[],\n ): HelpBubble {\n const viewport = ViewportModel.new({ width: 0, height: 0 })\n const content = generateHelpScreen(title, titleColor, entries, 0, 0)\n const initializedViewport = viewport.setContent(content)\n\n return new HelpBubble(\n initializedViewport,\n entries,\n title,\n titleColor,\n active,\n )\n }\n\n /**\n * Set the size of the help bubble and regenerate content.\n * @param width - Width in characters\n * @param height - Height in lines\n */\n setSize(width: number, height: number): HelpBubble {\n const content = generateHelpScreen(\n this.title,\n this.titleColor,\n this.entries,\n width,\n height,\n )\n const updatedViewport = this.viewport\n .setWidth(width)\n .setHeight(height)\n .setContent(content)\n\n return new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n this.titleColor,\n this.active,\n )\n }\n\n /**\n * Set whether the component is active (receives input).\n * @param active - Active state\n */\n setIsActive(active: boolean): HelpBubble {\n if (this.active === active) return this\n return new HelpBubble(\n this.viewport,\n this.entries,\n this.title,\n this.titleColor,\n active,\n )\n }\n\n /**\n * Set the title color and regenerate content.\n * @param color - New title color configuration\n */\n setTitleColor(color: TitleColor): HelpBubble {\n const content = generateHelpScreen(\n this.title,\n color,\n this.entries,\n this.viewport.width,\n this.viewport.height,\n )\n const updatedViewport = this.viewport.setContent(content)\n\n return new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n color,\n this.active,\n )\n }\n\n /**\n * Scroll to the top of the viewport.\n */\n gotoTop(): HelpBubble {\n const updatedViewport = this.viewport.scrollToTop()\n if (updatedViewport === this.viewport) return this\n\n return new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n this.titleColor,\n this.active,\n )\n }\n\n /**\n * Handle Tea update messages (viewport scrolling when active).\n * @param msg - Tea message\n */\n update(msg: Msg): [HelpBubble, Cmd<Msg>] {\n if (!this.active) {\n return [this, null]\n }\n\n const [updatedViewport, cmd] = this.viewport.update(msg)\n\n if (updatedViewport === this.viewport) {\n return [this, cmd]\n }\n\n return [\n new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n this.titleColor,\n this.active,\n ),\n cmd,\n ]\n }\n\n /**\n * Render the help screen.\n */\n view(): string {\n return this.viewport.view()\n }\n}\n"]}
@@ -0,0 +1,165 @@
1
+ import { Msg, Cmd } from '@boba-cli/tea';
2
+ import { Binding } from '@boba-cli/key';
3
+ import { Style, ColorInput } from '@boba-cli/chapstick';
4
+ import { ViewportModel } from '@boba-cli/viewport';
5
+
6
+ /**
7
+ * Interface for components that provide help bindings.
8
+ * @public
9
+ */
10
+ interface KeyMap {
11
+ /** Bindings for short (single-line) help. */
12
+ shortHelp(): Binding[];
13
+ /** Bindings grouped into columns for full help. */
14
+ fullHelp(): Binding[][];
15
+ }
16
+ /**
17
+ * Style configuration for help rendering.
18
+ * @public
19
+ */
20
+ interface HelpStyles {
21
+ ellipsis: Style;
22
+ shortKey: Style;
23
+ shortDesc: Style;
24
+ shortSeparator: Style;
25
+ fullKey: Style;
26
+ fullDesc: Style;
27
+ fullSeparator: Style;
28
+ }
29
+
30
+ /**
31
+ * Options for configuring the help component.
32
+ * @public
33
+ */
34
+ interface HelpOptions {
35
+ width?: number;
36
+ showAll?: boolean;
37
+ shortSeparator?: string;
38
+ fullSeparator?: string;
39
+ ellipsis?: string;
40
+ styles?: Partial<HelpStyles>;
41
+ }
42
+ /**
43
+ * Help view renderer.
44
+ * @public
45
+ */
46
+ declare class HelpModel {
47
+ readonly width: number;
48
+ readonly showAll: boolean;
49
+ readonly shortSeparator: string;
50
+ readonly fullSeparator: string;
51
+ readonly ellipsis: string;
52
+ readonly styles: HelpStyles;
53
+ private constructor();
54
+ /** Create a new help model with defaults applied. */
55
+ static new(options?: HelpOptions): HelpModel;
56
+ /** Return a new model with updated width. */
57
+ withWidth(width: number): HelpModel;
58
+ /** Return a new model with showAll toggled. */
59
+ withShowAll(showAll: boolean): HelpModel;
60
+ /** Tea update: no-op (view-only component). */
61
+ update(_msg: Msg): [HelpModel, Cmd<Msg>];
62
+ /** Render help text from the provided key map. */
63
+ view(keyMap: KeyMap): string;
64
+ /** Render single-line help. */
65
+ shortHelpView(bindings: Binding[]): string;
66
+ /** Render multi-column help. */
67
+ fullHelpView(groups: Binding[][]): string;
68
+ private shouldAddItem;
69
+ private with;
70
+ }
71
+
72
+ /**
73
+ * Default styling for help output.
74
+ * @public
75
+ */
76
+ declare function defaultStyles(): HelpStyles;
77
+
78
+ /**
79
+ * Color configuration for the help bubble title (adaptive for light/dark terminals).
80
+ * @public
81
+ */
82
+ interface TitleColor {
83
+ /** Background color (adaptive) */
84
+ background: ColorInput;
85
+ /** Foreground color (adaptive) */
86
+ foreground: ColorInput;
87
+ }
88
+ /**
89
+ * A single entry in the help bubble screen.
90
+ * @public
91
+ */
92
+ interface Entry {
93
+ /** Key binding (e.g., "ctrl+c", "j/up") */
94
+ key: string;
95
+ /** Description of what the key does */
96
+ description: string;
97
+ }
98
+
99
+ /**
100
+ * Model representing a scrollable help bubble.
101
+ * Displays a styled title and a list of key binding entries in a scrollable viewport.
102
+ * @public
103
+ */
104
+ declare class HelpBubble {
105
+ /**
106
+ * The viewport model that handles scrolling and content display.
107
+ */
108
+ readonly viewport: ViewportModel;
109
+ /**
110
+ * The array of help entries displayed in the bubble.
111
+ */
112
+ readonly entries: Entry[];
113
+ /**
114
+ * The title text shown at the top of the help screen.
115
+ */
116
+ readonly title: string;
117
+ /**
118
+ * The color configuration for the title bar.
119
+ */
120
+ readonly titleColor: TitleColor;
121
+ /**
122
+ * Whether the help bubble is active and receiving input.
123
+ */
124
+ readonly active: boolean;
125
+ private constructor();
126
+ /**
127
+ * Create a new help bubble.
128
+ * @param active - Whether the component receives input
129
+ * @param title - Title text for the help screen
130
+ * @param titleColor - Color configuration for the title
131
+ * @param entries - Array of help entries to display
132
+ */
133
+ static new(active: boolean, title: string, titleColor: TitleColor, entries: Entry[]): HelpBubble;
134
+ /**
135
+ * Set the size of the help bubble and regenerate content.
136
+ * @param width - Width in characters
137
+ * @param height - Height in lines
138
+ */
139
+ setSize(width: number, height: number): HelpBubble;
140
+ /**
141
+ * Set whether the component is active (receives input).
142
+ * @param active - Active state
143
+ */
144
+ setIsActive(active: boolean): HelpBubble;
145
+ /**
146
+ * Set the title color and regenerate content.
147
+ * @param color - New title color configuration
148
+ */
149
+ setTitleColor(color: TitleColor): HelpBubble;
150
+ /**
151
+ * Scroll to the top of the viewport.
152
+ */
153
+ gotoTop(): HelpBubble;
154
+ /**
155
+ * Handle Tea update messages (viewport scrolling when active).
156
+ * @param msg - Tea message
157
+ */
158
+ update(msg: Msg): [HelpBubble, Cmd<Msg>];
159
+ /**
160
+ * Render the help screen.
161
+ */
162
+ view(): string;
163
+ }
164
+
165
+ export { type Entry, HelpBubble, HelpModel, type HelpOptions, type HelpStyles, type KeyMap, type TitleColor, defaultStyles };
@@ -0,0 +1,165 @@
1
+ import { Msg, Cmd } from '@boba-cli/tea';
2
+ import { Binding } from '@boba-cli/key';
3
+ import { Style, ColorInput } from '@boba-cli/chapstick';
4
+ import { ViewportModel } from '@boba-cli/viewport';
5
+
6
+ /**
7
+ * Interface for components that provide help bindings.
8
+ * @public
9
+ */
10
+ interface KeyMap {
11
+ /** Bindings for short (single-line) help. */
12
+ shortHelp(): Binding[];
13
+ /** Bindings grouped into columns for full help. */
14
+ fullHelp(): Binding[][];
15
+ }
16
+ /**
17
+ * Style configuration for help rendering.
18
+ * @public
19
+ */
20
+ interface HelpStyles {
21
+ ellipsis: Style;
22
+ shortKey: Style;
23
+ shortDesc: Style;
24
+ shortSeparator: Style;
25
+ fullKey: Style;
26
+ fullDesc: Style;
27
+ fullSeparator: Style;
28
+ }
29
+
30
+ /**
31
+ * Options for configuring the help component.
32
+ * @public
33
+ */
34
+ interface HelpOptions {
35
+ width?: number;
36
+ showAll?: boolean;
37
+ shortSeparator?: string;
38
+ fullSeparator?: string;
39
+ ellipsis?: string;
40
+ styles?: Partial<HelpStyles>;
41
+ }
42
+ /**
43
+ * Help view renderer.
44
+ * @public
45
+ */
46
+ declare class HelpModel {
47
+ readonly width: number;
48
+ readonly showAll: boolean;
49
+ readonly shortSeparator: string;
50
+ readonly fullSeparator: string;
51
+ readonly ellipsis: string;
52
+ readonly styles: HelpStyles;
53
+ private constructor();
54
+ /** Create a new help model with defaults applied. */
55
+ static new(options?: HelpOptions): HelpModel;
56
+ /** Return a new model with updated width. */
57
+ withWidth(width: number): HelpModel;
58
+ /** Return a new model with showAll toggled. */
59
+ withShowAll(showAll: boolean): HelpModel;
60
+ /** Tea update: no-op (view-only component). */
61
+ update(_msg: Msg): [HelpModel, Cmd<Msg>];
62
+ /** Render help text from the provided key map. */
63
+ view(keyMap: KeyMap): string;
64
+ /** Render single-line help. */
65
+ shortHelpView(bindings: Binding[]): string;
66
+ /** Render multi-column help. */
67
+ fullHelpView(groups: Binding[][]): string;
68
+ private shouldAddItem;
69
+ private with;
70
+ }
71
+
72
+ /**
73
+ * Default styling for help output.
74
+ * @public
75
+ */
76
+ declare function defaultStyles(): HelpStyles;
77
+
78
+ /**
79
+ * Color configuration for the help bubble title (adaptive for light/dark terminals).
80
+ * @public
81
+ */
82
+ interface TitleColor {
83
+ /** Background color (adaptive) */
84
+ background: ColorInput;
85
+ /** Foreground color (adaptive) */
86
+ foreground: ColorInput;
87
+ }
88
+ /**
89
+ * A single entry in the help bubble screen.
90
+ * @public
91
+ */
92
+ interface Entry {
93
+ /** Key binding (e.g., "ctrl+c", "j/up") */
94
+ key: string;
95
+ /** Description of what the key does */
96
+ description: string;
97
+ }
98
+
99
+ /**
100
+ * Model representing a scrollable help bubble.
101
+ * Displays a styled title and a list of key binding entries in a scrollable viewport.
102
+ * @public
103
+ */
104
+ declare class HelpBubble {
105
+ /**
106
+ * The viewport model that handles scrolling and content display.
107
+ */
108
+ readonly viewport: ViewportModel;
109
+ /**
110
+ * The array of help entries displayed in the bubble.
111
+ */
112
+ readonly entries: Entry[];
113
+ /**
114
+ * The title text shown at the top of the help screen.
115
+ */
116
+ readonly title: string;
117
+ /**
118
+ * The color configuration for the title bar.
119
+ */
120
+ readonly titleColor: TitleColor;
121
+ /**
122
+ * Whether the help bubble is active and receiving input.
123
+ */
124
+ readonly active: boolean;
125
+ private constructor();
126
+ /**
127
+ * Create a new help bubble.
128
+ * @param active - Whether the component receives input
129
+ * @param title - Title text for the help screen
130
+ * @param titleColor - Color configuration for the title
131
+ * @param entries - Array of help entries to display
132
+ */
133
+ static new(active: boolean, title: string, titleColor: TitleColor, entries: Entry[]): HelpBubble;
134
+ /**
135
+ * Set the size of the help bubble and regenerate content.
136
+ * @param width - Width in characters
137
+ * @param height - Height in lines
138
+ */
139
+ setSize(width: number, height: number): HelpBubble;
140
+ /**
141
+ * Set whether the component is active (receives input).
142
+ * @param active - Active state
143
+ */
144
+ setIsActive(active: boolean): HelpBubble;
145
+ /**
146
+ * Set the title color and regenerate content.
147
+ * @param color - New title color configuration
148
+ */
149
+ setTitleColor(color: TitleColor): HelpBubble;
150
+ /**
151
+ * Scroll to the top of the viewport.
152
+ */
153
+ gotoTop(): HelpBubble;
154
+ /**
155
+ * Handle Tea update messages (viewport scrolling when active).
156
+ * @param msg - Tea message
157
+ */
158
+ update(msg: Msg): [HelpBubble, Cmd<Msg>];
159
+ /**
160
+ * Render the help screen.
161
+ */
162
+ view(): string;
163
+ }
164
+
165
+ export { type Entry, HelpBubble, HelpModel, type HelpOptions, type HelpStyles, type KeyMap, type TitleColor, defaultStyles };
package/dist/index.js ADDED
@@ -0,0 +1,311 @@
1
+ import { Style, width, joinHorizontal, joinVertical } from '@boba-cli/chapstick';
2
+ import { ViewportModel } from '@boba-cli/viewport';
3
+
4
+ // src/model.ts
5
+ function defaultStyles() {
6
+ const keyStyle = new Style().foreground("#626262");
7
+ const descStyle = new Style().foreground("#4A4A4A");
8
+ const sepStyle = new Style().foreground("#3C3C3C");
9
+ return {
10
+ shortKey: keyStyle,
11
+ shortDesc: descStyle,
12
+ shortSeparator: sepStyle,
13
+ ellipsis: sepStyle,
14
+ fullKey: keyStyle,
15
+ fullDesc: descStyle,
16
+ fullSeparator: sepStyle
17
+ };
18
+ }
19
+
20
+ // src/model.ts
21
+ var HelpModel = class _HelpModel {
22
+ width;
23
+ showAll;
24
+ shortSeparator;
25
+ fullSeparator;
26
+ ellipsis;
27
+ styles;
28
+ constructor(options = {}) {
29
+ this.width = options.width ?? 0;
30
+ this.showAll = options.showAll ?? false;
31
+ this.shortSeparator = options.shortSeparator ?? " \u2022 ";
32
+ this.fullSeparator = options.fullSeparator ?? " ";
33
+ this.ellipsis = options.ellipsis ?? "\u2026";
34
+ const defaults = defaultStyles();
35
+ this.styles = { ...defaults, ...options.styles };
36
+ }
37
+ /** Create a new help model with defaults applied. */
38
+ static new(options = {}) {
39
+ return new _HelpModel(options);
40
+ }
41
+ /** Return a new model with updated width. */
42
+ withWidth(width) {
43
+ return this.with({ width });
44
+ }
45
+ /** Return a new model with showAll toggled. */
46
+ withShowAll(showAll) {
47
+ return this.with({ showAll });
48
+ }
49
+ /** Tea update: no-op (view-only component). */
50
+ update(_msg) {
51
+ return [this, null];
52
+ }
53
+ /** Render help text from the provided key map. */
54
+ view(keyMap) {
55
+ if (this.showAll) {
56
+ return this.fullHelpView(keyMap.fullHelp());
57
+ }
58
+ return this.shortHelpView(keyMap.shortHelp());
59
+ }
60
+ /** Render single-line help. */
61
+ shortHelpView(bindings) {
62
+ if (!bindings || bindings.length === 0) {
63
+ return "";
64
+ }
65
+ let result = "";
66
+ let totalWidth = 0;
67
+ const separator = this.styles.shortSeparator.inline(true).render(this.shortSeparator);
68
+ for (const [i, kb] of bindings.entries()) {
69
+ if (!kb.enabled()) continue;
70
+ const sep = totalWidth > 0 && i < bindings.length ? separator : "";
71
+ const help = kb.help();
72
+ const item = sep + this.styles.shortKey.inline(true).render(help.key) + " " + this.styles.shortDesc.inline(true).render(help.desc);
73
+ const itemWidth = width(item);
74
+ const [tail, ok] = this.shouldAddItem(totalWidth, itemWidth);
75
+ if (!ok) {
76
+ if (tail) {
77
+ result += tail;
78
+ }
79
+ break;
80
+ }
81
+ totalWidth += itemWidth;
82
+ result += item;
83
+ }
84
+ return result;
85
+ }
86
+ /** Render multi-column help. */
87
+ fullHelpView(groups) {
88
+ if (!groups || groups.length === 0) {
89
+ return "";
90
+ }
91
+ const out = [];
92
+ let totalWidth = 0;
93
+ const separator = this.styles.fullSeparator.inline(true).render(this.fullSeparator);
94
+ for (const [i, group] of groups.entries()) {
95
+ if (!group || !shouldRenderColumn(group)) {
96
+ continue;
97
+ }
98
+ const keys = [];
99
+ const descriptions = [];
100
+ for (const kb of group) {
101
+ if (!kb.enabled()) continue;
102
+ const help = kb.help();
103
+ keys.push(this.styles.fullKey.render(help.key));
104
+ descriptions.push(this.styles.fullDesc.render(help.desc));
105
+ }
106
+ const sep = totalWidth > 0 && i < groups.length ? separator : "";
107
+ const parts = sep ? [sep, keys.join("\n"), " ", descriptions.join("\n")] : [keys.join("\n"), " ", descriptions.join("\n")];
108
+ const column = joinHorizontal(...parts);
109
+ const columnWidth = width(column);
110
+ const [tail, ok] = this.shouldAddItem(totalWidth, columnWidth);
111
+ if (!ok) {
112
+ if (tail) {
113
+ out.push(tail);
114
+ }
115
+ break;
116
+ }
117
+ totalWidth += columnWidth;
118
+ out.push(column);
119
+ }
120
+ return joinHorizontal(...out);
121
+ }
122
+ shouldAddItem(totalWidth, width$1) {
123
+ if (this.width > 0 && totalWidth + width$1 > this.width) {
124
+ const tail = " " + this.styles.ellipsis.inline(true).render(this.ellipsis);
125
+ if (totalWidth + width(tail) < this.width) {
126
+ return [tail, false];
127
+ }
128
+ }
129
+ return ["", true];
130
+ }
131
+ with(patch) {
132
+ return new _HelpModel({
133
+ width: this.width,
134
+ showAll: this.showAll,
135
+ shortSeparator: this.shortSeparator,
136
+ fullSeparator: this.fullSeparator,
137
+ ellipsis: this.ellipsis,
138
+ styles: this.styles,
139
+ ...patch
140
+ });
141
+ }
142
+ };
143
+ function shouldRenderColumn(bindings) {
144
+ return bindings.some((b) => b.enabled());
145
+ }
146
+ var KEY_WIDTH = 12;
147
+ function generateHelpScreen(title, titleColor, entries, width, height) {
148
+ let helpScreen = "";
149
+ for (const content of entries) {
150
+ const paddedKey = content.key.padEnd(KEY_WIDTH);
151
+ const keyText = new Style().bold(true).foreground({ dark: "#ffffff", light: "#000000" }).render(paddedKey);
152
+ const descriptionText = new Style().foreground({ dark: "#ffffff", light: "#000000" }).render(content.description);
153
+ const row = joinHorizontal(2, keyText, descriptionText);
154
+ helpScreen += `${row}
155
+ `;
156
+ }
157
+ const titleText = new Style().bold(true).background(titleColor.background).foreground(titleColor.foreground).padding(0, 1).italic(true).render(title);
158
+ return new Style().width(width).height(height).render(joinVertical(titleText, helpScreen));
159
+ }
160
+ var HelpBubble = class _HelpBubble {
161
+ /**
162
+ * The viewport model that handles scrolling and content display.
163
+ */
164
+ viewport;
165
+ /**
166
+ * The array of help entries displayed in the bubble.
167
+ */
168
+ entries;
169
+ /**
170
+ * The title text shown at the top of the help screen.
171
+ */
172
+ title;
173
+ /**
174
+ * The color configuration for the title bar.
175
+ */
176
+ titleColor;
177
+ /**
178
+ * Whether the help bubble is active and receiving input.
179
+ */
180
+ active;
181
+ constructor(viewport, entries, title, titleColor, active) {
182
+ this.viewport = viewport;
183
+ this.entries = entries;
184
+ this.title = title;
185
+ this.titleColor = titleColor;
186
+ this.active = active;
187
+ }
188
+ /**
189
+ * Create a new help bubble.
190
+ * @param active - Whether the component receives input
191
+ * @param title - Title text for the help screen
192
+ * @param titleColor - Color configuration for the title
193
+ * @param entries - Array of help entries to display
194
+ */
195
+ static new(active, title, titleColor, entries) {
196
+ const viewport = ViewportModel.new({ width: 0, height: 0 });
197
+ const content = generateHelpScreen(title, titleColor, entries, 0, 0);
198
+ const initializedViewport = viewport.setContent(content);
199
+ return new _HelpBubble(
200
+ initializedViewport,
201
+ entries,
202
+ title,
203
+ titleColor,
204
+ active
205
+ );
206
+ }
207
+ /**
208
+ * Set the size of the help bubble and regenerate content.
209
+ * @param width - Width in characters
210
+ * @param height - Height in lines
211
+ */
212
+ setSize(width, height) {
213
+ const content = generateHelpScreen(
214
+ this.title,
215
+ this.titleColor,
216
+ this.entries,
217
+ width,
218
+ height
219
+ );
220
+ const updatedViewport = this.viewport.setWidth(width).setHeight(height).setContent(content);
221
+ return new _HelpBubble(
222
+ updatedViewport,
223
+ this.entries,
224
+ this.title,
225
+ this.titleColor,
226
+ this.active
227
+ );
228
+ }
229
+ /**
230
+ * Set whether the component is active (receives input).
231
+ * @param active - Active state
232
+ */
233
+ setIsActive(active) {
234
+ if (this.active === active) return this;
235
+ return new _HelpBubble(
236
+ this.viewport,
237
+ this.entries,
238
+ this.title,
239
+ this.titleColor,
240
+ active
241
+ );
242
+ }
243
+ /**
244
+ * Set the title color and regenerate content.
245
+ * @param color - New title color configuration
246
+ */
247
+ setTitleColor(color) {
248
+ const content = generateHelpScreen(
249
+ this.title,
250
+ color,
251
+ this.entries,
252
+ this.viewport.width,
253
+ this.viewport.height
254
+ );
255
+ const updatedViewport = this.viewport.setContent(content);
256
+ return new _HelpBubble(
257
+ updatedViewport,
258
+ this.entries,
259
+ this.title,
260
+ color,
261
+ this.active
262
+ );
263
+ }
264
+ /**
265
+ * Scroll to the top of the viewport.
266
+ */
267
+ gotoTop() {
268
+ const updatedViewport = this.viewport.scrollToTop();
269
+ if (updatedViewport === this.viewport) return this;
270
+ return new _HelpBubble(
271
+ updatedViewport,
272
+ this.entries,
273
+ this.title,
274
+ this.titleColor,
275
+ this.active
276
+ );
277
+ }
278
+ /**
279
+ * Handle Tea update messages (viewport scrolling when active).
280
+ * @param msg - Tea message
281
+ */
282
+ update(msg) {
283
+ if (!this.active) {
284
+ return [this, null];
285
+ }
286
+ const [updatedViewport, cmd] = this.viewport.update(msg);
287
+ if (updatedViewport === this.viewport) {
288
+ return [this, cmd];
289
+ }
290
+ return [
291
+ new _HelpBubble(
292
+ updatedViewport,
293
+ this.entries,
294
+ this.title,
295
+ this.titleColor,
296
+ this.active
297
+ ),
298
+ cmd
299
+ ];
300
+ }
301
+ /**
302
+ * Render the help screen.
303
+ */
304
+ view() {
305
+ return this.viewport.view();
306
+ }
307
+ };
308
+
309
+ export { HelpBubble, HelpModel, defaultStyles };
310
+ //# sourceMappingURL=index.js.map
311
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/styles.ts","../src/model.ts","../src/bubble.ts"],"names":["stringWidth","width","Style","joinHorizontal"],"mappings":"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","file":"index.js","sourcesContent":["import { Style } from '@boba-cli/chapstick'\nimport type { HelpStyles } from './types.js'\n\n/**\n * Default styling for help output.\n * @public\n */\nexport function defaultStyles(): HelpStyles {\n const keyStyle = new Style().foreground('#626262')\n const descStyle = new Style().foreground('#4A4A4A')\n const sepStyle = new Style().foreground('#3C3C3C')\n\n return {\n shortKey: keyStyle,\n shortDesc: descStyle,\n shortSeparator: sepStyle,\n ellipsis: sepStyle,\n fullKey: keyStyle,\n fullDesc: descStyle,\n fullSeparator: sepStyle,\n }\n}\n","import { joinHorizontal, width as stringWidth } from '@boba-cli/chapstick'\nimport { type Cmd, type Msg } from '@boba-cli/tea'\nimport type { Binding } from '@boba-cli/key'\nimport { defaultStyles } from './styles.js'\nimport type { HelpStyles, KeyMap } from './types.js'\n\n/**\n * Options for configuring the help component.\n * @public\n */\nexport interface HelpOptions {\n width?: number\n showAll?: boolean\n shortSeparator?: string\n fullSeparator?: string\n ellipsis?: string\n styles?: Partial<HelpStyles>\n}\n\n/**\n * Help view renderer.\n * @public\n */\nexport class HelpModel {\n readonly width: number\n readonly showAll: boolean\n readonly shortSeparator: string\n readonly fullSeparator: string\n readonly ellipsis: string\n readonly styles: HelpStyles\n\n private constructor(options: HelpOptions = {}) {\n this.width = options.width ?? 0\n this.showAll = options.showAll ?? false\n this.shortSeparator = options.shortSeparator ?? ' • '\n this.fullSeparator = options.fullSeparator ?? ' '\n this.ellipsis = options.ellipsis ?? '…'\n const defaults = defaultStyles()\n this.styles = { ...defaults, ...options.styles }\n }\n\n /** Create a new help model with defaults applied. */\n static new(options: HelpOptions = {}): HelpModel {\n return new HelpModel(options)\n }\n\n /** Return a new model with updated width. */\n withWidth(width: number): HelpModel {\n return this.with({ width })\n }\n\n /** Return a new model with showAll toggled. */\n withShowAll(showAll: boolean): HelpModel {\n return this.with({ showAll })\n }\n\n /** Tea update: no-op (view-only component). */\n update(_msg: Msg): [HelpModel, Cmd<Msg>] {\n return [this, null]\n }\n\n /** Render help text from the provided key map. */\n view(keyMap: KeyMap): string {\n if (this.showAll) {\n return this.fullHelpView(keyMap.fullHelp())\n }\n return this.shortHelpView(keyMap.shortHelp())\n }\n\n /** Render single-line help. */\n shortHelpView(bindings: Binding[]): string {\n if (!bindings || bindings.length === 0) {\n return ''\n }\n\n let result = ''\n let totalWidth = 0\n const separator = this.styles.shortSeparator\n .inline(true)\n .render(this.shortSeparator)\n\n for (const [i, kb] of bindings.entries()) {\n if (!kb.enabled()) continue\n\n const sep = totalWidth > 0 && i < bindings.length ? separator : ''\n const help = kb.help()\n const item =\n sep +\n this.styles.shortKey.inline(true).render(help.key) +\n ' ' +\n this.styles.shortDesc.inline(true).render(help.desc)\n const itemWidth = stringWidth(item)\n\n const [tail, ok] = this.shouldAddItem(totalWidth, itemWidth)\n if (!ok) {\n if (tail) {\n result += tail\n }\n break\n }\n\n totalWidth += itemWidth\n result += item\n }\n\n return result\n }\n\n /** Render multi-column help. */\n fullHelpView(groups: Binding[][]): string {\n if (!groups || groups.length === 0) {\n return ''\n }\n\n const out: string[] = []\n let totalWidth = 0\n const separator = this.styles.fullSeparator\n .inline(true)\n .render(this.fullSeparator)\n\n for (const [i, group] of groups.entries()) {\n if (!group || !shouldRenderColumn(group)) {\n continue\n }\n\n const keys: string[] = []\n const descriptions: string[] = []\n for (const kb of group) {\n if (!kb.enabled()) continue\n const help = kb.help()\n keys.push(this.styles.fullKey.render(help.key))\n descriptions.push(this.styles.fullDesc.render(help.desc))\n }\n\n const sep = totalWidth > 0 && i < groups.length ? separator : ''\n const parts = sep\n ? [sep, keys.join('\\n'), ' ', descriptions.join('\\n')]\n : [keys.join('\\n'), ' ', descriptions.join('\\n')]\n const column = joinHorizontal(...parts)\n const columnWidth = stringWidth(column)\n\n const [tail, ok] = this.shouldAddItem(totalWidth, columnWidth)\n if (!ok) {\n if (tail) {\n out.push(tail)\n }\n break\n }\n\n totalWidth += columnWidth\n out.push(column)\n }\n\n return joinHorizontal(...out)\n }\n\n private shouldAddItem(totalWidth: number, width: number): [string, boolean] {\n if (this.width > 0 && totalWidth + width > this.width) {\n const tail = ' ' + this.styles.ellipsis.inline(true).render(this.ellipsis)\n if (totalWidth + stringWidth(tail) < this.width) {\n return [tail, false]\n }\n }\n return ['', true]\n }\n\n private with(patch: Partial<HelpOptions>): HelpModel {\n return new HelpModel({\n width: this.width,\n showAll: this.showAll,\n shortSeparator: this.shortSeparator,\n fullSeparator: this.fullSeparator,\n ellipsis: this.ellipsis,\n styles: this.styles,\n ...patch,\n })\n }\n}\n\nfunction shouldRenderColumn(bindings: Binding[]): boolean {\n return bindings.some((b) => b.enabled())\n}\n","import { Style, joinVertical, joinHorizontal } from '@boba-cli/chapstick'\nimport { ViewportModel } from '@boba-cli/viewport'\nimport { type Cmd, type Msg } from '@boba-cli/tea'\nimport type { Entry, TitleColor } from './bubble-types.js'\n\nconst KEY_WIDTH = 12\n\n/**\n * Generates the help screen content with styled title and entries.\n * @internal\n */\nfunction generateHelpScreen(\n title: string,\n titleColor: TitleColor,\n entries: Entry[],\n width: number,\n height: number,\n): string {\n let helpScreen = ''\n\n // Build each entry row with two-column layout\n for (const content of entries) {\n const paddedKey = content.key.padEnd(KEY_WIDTH)\n const keyText = new Style()\n .bold(true)\n .foreground({ dark: '#ffffff', light: '#000000' })\n .render(paddedKey)\n\n const descriptionText = new Style()\n .foreground({ dark: '#ffffff', light: '#000000' })\n .render(content.description)\n\n const row = joinHorizontal(2, keyText, descriptionText)\n helpScreen += `${row}\\n`\n }\n\n // Create styled title\n const titleText = new Style()\n .bold(true)\n .background(titleColor.background)\n .foreground(titleColor.foreground)\n .padding(0, 1)\n .italic(true)\n .render(title)\n\n // Combine title and entries, apply width/height constraints\n return new Style()\n .width(width)\n .height(height)\n .render(joinVertical(titleText, helpScreen))\n}\n\n/**\n * Model representing a scrollable help bubble.\n * Displays a styled title and a list of key binding entries in a scrollable viewport.\n * @public\n */\nexport class HelpBubble {\n /**\n * The viewport model that handles scrolling and content display.\n */\n readonly viewport: ViewportModel\n /**\n * The array of help entries displayed in the bubble.\n */\n readonly entries: Entry[]\n /**\n * The title text shown at the top of the help screen.\n */\n readonly title: string\n /**\n * The color configuration for the title bar.\n */\n readonly titleColor: TitleColor\n /**\n * Whether the help bubble is active and receiving input.\n */\n readonly active: boolean\n\n private constructor(\n viewport: ViewportModel,\n entries: Entry[],\n title: string,\n titleColor: TitleColor,\n active: boolean,\n ) {\n this.viewport = viewport\n this.entries = entries\n this.title = title\n this.titleColor = titleColor\n this.active = active\n }\n\n /**\n * Create a new help bubble.\n * @param active - Whether the component receives input\n * @param title - Title text for the help screen\n * @param titleColor - Color configuration for the title\n * @param entries - Array of help entries to display\n */\n static new(\n active: boolean,\n title: string,\n titleColor: TitleColor,\n entries: Entry[],\n ): HelpBubble {\n const viewport = ViewportModel.new({ width: 0, height: 0 })\n const content = generateHelpScreen(title, titleColor, entries, 0, 0)\n const initializedViewport = viewport.setContent(content)\n\n return new HelpBubble(\n initializedViewport,\n entries,\n title,\n titleColor,\n active,\n )\n }\n\n /**\n * Set the size of the help bubble and regenerate content.\n * @param width - Width in characters\n * @param height - Height in lines\n */\n setSize(width: number, height: number): HelpBubble {\n const content = generateHelpScreen(\n this.title,\n this.titleColor,\n this.entries,\n width,\n height,\n )\n const updatedViewport = this.viewport\n .setWidth(width)\n .setHeight(height)\n .setContent(content)\n\n return new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n this.titleColor,\n this.active,\n )\n }\n\n /**\n * Set whether the component is active (receives input).\n * @param active - Active state\n */\n setIsActive(active: boolean): HelpBubble {\n if (this.active === active) return this\n return new HelpBubble(\n this.viewport,\n this.entries,\n this.title,\n this.titleColor,\n active,\n )\n }\n\n /**\n * Set the title color and regenerate content.\n * @param color - New title color configuration\n */\n setTitleColor(color: TitleColor): HelpBubble {\n const content = generateHelpScreen(\n this.title,\n color,\n this.entries,\n this.viewport.width,\n this.viewport.height,\n )\n const updatedViewport = this.viewport.setContent(content)\n\n return new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n color,\n this.active,\n )\n }\n\n /**\n * Scroll to the top of the viewport.\n */\n gotoTop(): HelpBubble {\n const updatedViewport = this.viewport.scrollToTop()\n if (updatedViewport === this.viewport) return this\n\n return new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n this.titleColor,\n this.active,\n )\n }\n\n /**\n * Handle Tea update messages (viewport scrolling when active).\n * @param msg - Tea message\n */\n update(msg: Msg): [HelpBubble, Cmd<Msg>] {\n if (!this.active) {\n return [this, null]\n }\n\n const [updatedViewport, cmd] = this.viewport.update(msg)\n\n if (updatedViewport === this.viewport) {\n return [this, cmd]\n }\n\n return [\n new HelpBubble(\n updatedViewport,\n this.entries,\n this.title,\n this.titleColor,\n this.active,\n ),\n cmd,\n ]\n }\n\n /**\n * Render the help screen.\n */\n view(): string {\n return this.viewport.view()\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@boba-cli/help",
3
+ "description": "Help components for Boba terminal UIs: key binding renderer and scrollable help bubble",
4
+ "version": "0.1.0-alpha.2",
5
+ "dependencies": {
6
+ "@boba-cli/chapstick": "0.1.0-alpha.2",
7
+ "@boba-cli/key": "0.1.0-alpha.1",
8
+ "@boba-cli/tea": "0.1.0-alpha.1",
9
+ "@boba-cli/viewport": "0.1.0-alpha.2"
10
+ },
11
+ "devDependencies": {
12
+ "typescript": "5.8.2",
13
+ "vitest": "^4.0.16"
14
+ },
15
+ "engines": {
16
+ "node": ">=20.0.0"
17
+ },
18
+ "exports": {
19
+ ".": {
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.js",
36
+ "type": "module",
37
+ "types": "./dist/index.d.ts",
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "check:api-report": "pnpm run generate:api-report",
41
+ "check:eslint": "pnpm run lint",
42
+ "generate:api-report": "api-extractor run --local",
43
+ "lint": "eslint \"{src,test}/**/*.{ts,tsx}\"",
44
+ "test": "vitest run"
45
+ }
46
+ }