@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 +33 -0
- package/dist/index.cjs +315 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +165 -0
- package/dist/index.d.ts +165 -0
- package/dist/index.js +311 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
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":"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","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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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":";;;;AAOO,SAAS,aAAA,GAA4B;AAC1C,EAAA,MAAM,QAAA,GAAW,IAAI,KAAA,EAAM,CAAE,WAAW,SAAS,CAAA;AACjD,EAAA,MAAM,SAAA,GAAY,IAAI,KAAA,EAAM,CAAE,WAAW,SAAS,CAAA;AAClD,EAAA,MAAM,QAAA,GAAW,IAAI,KAAA,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,GAAYA,MAAY,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,GAAS,cAAA,CAAe,GAAG,KAAK,CAAA;AACtC,MAAA,MAAM,WAAA,GAAcA,MAAY,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,OAAO,cAAA,CAAe,GAAG,GAAG,CAAA;AAAA,EAC9B;AAAA,EAEQ,aAAA,CAAc,YAAoBC,OAAA,EAAkC;AAC1E,IAAA,IAAI,KAAK,KAAA,GAAQ,CAAA,IAAK,UAAA,GAAaA,OAAA,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,KAAA,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,IAAIE,KAAAA,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,KAAAA,EAAM,CAC/B,WAAW,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAA,EAAW,CAAA,CAChD,MAAA,CAAO,QAAQ,WAAW,CAAA;AAE7B,IAAA,MAAM,GAAA,GAAMC,cAAAA,CAAe,CAAA,EAAG,OAAA,EAAS,eAAe,CAAA;AACtD,IAAA,UAAA,IAAc,GAAG,GAAG;AAAA,CAAA;AAAA,EACtB;AAGA,EAAA,MAAM,SAAA,GAAY,IAAID,KAAAA,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,KAAAA,EAAM,CACd,KAAA,CAAM,KAAK,CAAA,CACX,MAAA,CAAO,MAAM,CAAA,CACb,MAAA,CAAO,YAAA,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,MAAM,QAAA,GAAW,cAAc,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,GAAsB,QAAA,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.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
|
+
}
|