@henryavila/blink-tui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +367 -0
- package/dist/index.d.ts +2429 -0
- package/dist/index.js +1901 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1901 @@
|
|
|
1
|
+
import { createContext, useState, useMemo, useContext, useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { readFileSync, existsSync } from 'fs';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import stringWidth from 'string-width';
|
|
7
|
+
import { useStdout, Box, Text, measureElement, Spacer } from 'ink';
|
|
8
|
+
|
|
9
|
+
// src/theme/palette.ts
|
|
10
|
+
var PALETTE_SLOTS = [
|
|
11
|
+
"crust",
|
|
12
|
+
"mantle",
|
|
13
|
+
"base",
|
|
14
|
+
"surface0",
|
|
15
|
+
"surface1",
|
|
16
|
+
"surface2",
|
|
17
|
+
"overlay0",
|
|
18
|
+
"overlay1",
|
|
19
|
+
"overlay2",
|
|
20
|
+
"subtext0",
|
|
21
|
+
"subtext1",
|
|
22
|
+
"text",
|
|
23
|
+
"rosewater",
|
|
24
|
+
"flamingo",
|
|
25
|
+
"pink",
|
|
26
|
+
"mauve",
|
|
27
|
+
"red",
|
|
28
|
+
"maroon",
|
|
29
|
+
"peach",
|
|
30
|
+
"yellow",
|
|
31
|
+
"green",
|
|
32
|
+
"teal",
|
|
33
|
+
"sky",
|
|
34
|
+
"sapphire",
|
|
35
|
+
"blue",
|
|
36
|
+
"lavender"
|
|
37
|
+
];
|
|
38
|
+
var neutral = {
|
|
39
|
+
crust: "#11111b",
|
|
40
|
+
mantle: "#181825",
|
|
41
|
+
base: "#1e1e2e",
|
|
42
|
+
surface0: "#313244",
|
|
43
|
+
surface1: "#45475a",
|
|
44
|
+
surface2: "#585b70",
|
|
45
|
+
overlay0: "#6c7086",
|
|
46
|
+
overlay1: "#7f849c",
|
|
47
|
+
overlay2: "#9399b2",
|
|
48
|
+
subtext0: "#a6adc8",
|
|
49
|
+
subtext1: "#bac2de",
|
|
50
|
+
text: "#cdd6f4",
|
|
51
|
+
rosewater: "#f5e0dc",
|
|
52
|
+
flamingo: "#f2cdcd",
|
|
53
|
+
pink: "#f5c2e7",
|
|
54
|
+
mauve: "#cba6f7",
|
|
55
|
+
red: "#f38ba8",
|
|
56
|
+
maroon: "#eba0ac",
|
|
57
|
+
peach: "#fab387",
|
|
58
|
+
yellow: "#f9e2af",
|
|
59
|
+
green: "#a6e3a1",
|
|
60
|
+
teal: "#94e2d5",
|
|
61
|
+
sky: "#89dceb",
|
|
62
|
+
sapphire: "#74c7ec",
|
|
63
|
+
blue: "#89b4fa",
|
|
64
|
+
lavender: "#b4befe"
|
|
65
|
+
};
|
|
66
|
+
var nord = {
|
|
67
|
+
crust: "#242933",
|
|
68
|
+
mantle: "#2b303b",
|
|
69
|
+
base: "#2e3440",
|
|
70
|
+
surface0: "#3b4252",
|
|
71
|
+
surface1: "#434c5e",
|
|
72
|
+
surface2: "#4c566a",
|
|
73
|
+
overlay0: "#545e72",
|
|
74
|
+
overlay1: "#616e88",
|
|
75
|
+
overlay2: "#707e9b",
|
|
76
|
+
subtext0: "#aeb6c6",
|
|
77
|
+
subtext1: "#d8dee9",
|
|
78
|
+
text: "#eceff4",
|
|
79
|
+
rosewater: "#ecd3c8",
|
|
80
|
+
flamingo: "#e3c0bb",
|
|
81
|
+
pink: "#c895b5",
|
|
82
|
+
mauve: "#b48ead",
|
|
83
|
+
red: "#bf616a",
|
|
84
|
+
maroon: "#cf7782",
|
|
85
|
+
peach: "#d08770",
|
|
86
|
+
yellow: "#ebcb8b",
|
|
87
|
+
green: "#a3be8c",
|
|
88
|
+
teal: "#8fbcbb",
|
|
89
|
+
sky: "#88c0d0",
|
|
90
|
+
sapphire: "#81a1c1",
|
|
91
|
+
blue: "#5e81ac",
|
|
92
|
+
lavender: "#88c0d0"
|
|
93
|
+
};
|
|
94
|
+
var gruvbox = {
|
|
95
|
+
crust: "#1d2021",
|
|
96
|
+
mantle: "#232829",
|
|
97
|
+
base: "#282828",
|
|
98
|
+
surface0: "#3c3836",
|
|
99
|
+
surface1: "#504945",
|
|
100
|
+
surface2: "#665c54",
|
|
101
|
+
overlay0: "#7c6f64",
|
|
102
|
+
overlay1: "#928374",
|
|
103
|
+
overlay2: "#a89984",
|
|
104
|
+
subtext0: "#bdae93",
|
|
105
|
+
subtext1: "#d5c4a1",
|
|
106
|
+
text: "#ebdbb2",
|
|
107
|
+
rosewater: "#f9f5d7",
|
|
108
|
+
flamingo: "#f2e5bc",
|
|
109
|
+
pink: "#d3869b",
|
|
110
|
+
mauve: "#b16286",
|
|
111
|
+
red: "#fb4934",
|
|
112
|
+
maroon: "#cc241d",
|
|
113
|
+
peach: "#fe8019",
|
|
114
|
+
yellow: "#fabd2f",
|
|
115
|
+
green: "#b8bb26",
|
|
116
|
+
teal: "#689d6a",
|
|
117
|
+
sky: "#8ec07c",
|
|
118
|
+
sapphire: "#458588",
|
|
119
|
+
blue: "#83a598",
|
|
120
|
+
lavender: "#d3869b"
|
|
121
|
+
};
|
|
122
|
+
var tokyonight = {
|
|
123
|
+
crust: "#0b0c12",
|
|
124
|
+
mantle: "#101119",
|
|
125
|
+
base: "#15161f",
|
|
126
|
+
surface0: "#1d1f2e",
|
|
127
|
+
surface1: "#262b3d",
|
|
128
|
+
surface2: "#383d57",
|
|
129
|
+
overlay0: "#565f89",
|
|
130
|
+
overlay1: "#737aa2",
|
|
131
|
+
overlay2: "#828bb8",
|
|
132
|
+
subtext0: "#9099bd",
|
|
133
|
+
subtext1: "#a9b1d6",
|
|
134
|
+
text: "#c0caf5",
|
|
135
|
+
rosewater: "#f7c8d4",
|
|
136
|
+
flamingo: "#ff9e9e",
|
|
137
|
+
pink: "#ff75a0",
|
|
138
|
+
mauve: "#bb9af7",
|
|
139
|
+
red: "#f7768e",
|
|
140
|
+
maroon: "#db4b4b",
|
|
141
|
+
peach: "#ff9e64",
|
|
142
|
+
yellow: "#e0af68",
|
|
143
|
+
green: "#9ece6a",
|
|
144
|
+
teal: "#73daca",
|
|
145
|
+
sky: "#7dcfff",
|
|
146
|
+
sapphire: "#2ac3de",
|
|
147
|
+
blue: "#7aa2f7",
|
|
148
|
+
lavender: "#bb9af7"
|
|
149
|
+
};
|
|
150
|
+
var latte = {
|
|
151
|
+
crust: "#dce0e8",
|
|
152
|
+
mantle: "#e6e9ef",
|
|
153
|
+
base: "#eff1f5",
|
|
154
|
+
surface0: "#ccd0da",
|
|
155
|
+
surface1: "#bcc0cc",
|
|
156
|
+
surface2: "#acb0be",
|
|
157
|
+
overlay0: "#9ca0b0",
|
|
158
|
+
overlay1: "#8c8fa1",
|
|
159
|
+
overlay2: "#7c7f93",
|
|
160
|
+
subtext0: "#6c6f85",
|
|
161
|
+
subtext1: "#5c5f77",
|
|
162
|
+
text: "#4c4f69",
|
|
163
|
+
rosewater: "#dc8a78",
|
|
164
|
+
flamingo: "#dd7878",
|
|
165
|
+
pink: "#ea76cb",
|
|
166
|
+
mauve: "#8839ef",
|
|
167
|
+
red: "#d20f39",
|
|
168
|
+
maroon: "#e64553",
|
|
169
|
+
peach: "#fe640b",
|
|
170
|
+
yellow: "#df8e1d",
|
|
171
|
+
green: "#40a02b",
|
|
172
|
+
teal: "#179299",
|
|
173
|
+
sky: "#04a5e5",
|
|
174
|
+
sapphire: "#209fb5",
|
|
175
|
+
blue: "#1e66f5",
|
|
176
|
+
lavender: "#7287fd"
|
|
177
|
+
};
|
|
178
|
+
var palettes = { neutral, nord, gruvbox, tokyonight, latte };
|
|
179
|
+
var catppuccinMocha = neutral;
|
|
180
|
+
|
|
181
|
+
// src/theme/tokens.ts
|
|
182
|
+
function buildTokens(p) {
|
|
183
|
+
return {
|
|
184
|
+
bg: p.base,
|
|
185
|
+
bgElevated: p.mantle,
|
|
186
|
+
bgSunken: p.crust,
|
|
187
|
+
bgPanel: p.base,
|
|
188
|
+
bgSelected: p.surface0,
|
|
189
|
+
bgFocused: p.surface1,
|
|
190
|
+
bgInverse: p.text,
|
|
191
|
+
fg: p.text,
|
|
192
|
+
fgMuted: p.subtext1,
|
|
193
|
+
fgDim: p.subtext0,
|
|
194
|
+
fgFaint: p.overlay1,
|
|
195
|
+
fgDisabled: p.overlay0,
|
|
196
|
+
fgInverse: p.base,
|
|
197
|
+
border: p.surface1,
|
|
198
|
+
borderFocus: p.lavender,
|
|
199
|
+
borderStrong: p.overlay0,
|
|
200
|
+
accent: p.lavender,
|
|
201
|
+
accentAlt: p.mauve,
|
|
202
|
+
link: p.blue,
|
|
203
|
+
highlight: p.yellow,
|
|
204
|
+
stateOk: p.green,
|
|
205
|
+
stateErr: p.red,
|
|
206
|
+
stateWarn: p.yellow,
|
|
207
|
+
statePending: p.overlay1,
|
|
208
|
+
stateDrift: p.peach,
|
|
209
|
+
stateInfo: p.sky,
|
|
210
|
+
domainBlue: p.blue,
|
|
211
|
+
domainAzure: p.sapphire,
|
|
212
|
+
domainCyan: p.sky,
|
|
213
|
+
domainGreen: p.green,
|
|
214
|
+
domainRed: p.red,
|
|
215
|
+
domainAmber: p.peach,
|
|
216
|
+
domainYellow: p.yellow,
|
|
217
|
+
domainViolet: p.mauve,
|
|
218
|
+
domainNeutral: p.subtext1
|
|
219
|
+
// == --fg-muted
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
var mochaTokens = buildTokens(neutral);
|
|
223
|
+
|
|
224
|
+
// src/theme/colorMix.ts
|
|
225
|
+
function hexToRgb(hex) {
|
|
226
|
+
const h = hex.replace("#", "");
|
|
227
|
+
return [
|
|
228
|
+
parseInt(h.slice(0, 2), 16) / 255,
|
|
229
|
+
parseInt(h.slice(2, 4), 16) / 255,
|
|
230
|
+
parseInt(h.slice(4, 6), 16) / 255
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
function rgbToHex([r, g, b]) {
|
|
234
|
+
const to = (v) => {
|
|
235
|
+
const n = Math.max(0, Math.min(255, Math.round(v * 255)));
|
|
236
|
+
return n.toString(16).padStart(2, "0");
|
|
237
|
+
};
|
|
238
|
+
return `#${to(r)}${to(g)}${to(b)}`;
|
|
239
|
+
}
|
|
240
|
+
function toLinear(c) {
|
|
241
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
242
|
+
}
|
|
243
|
+
function toSrgb(c) {
|
|
244
|
+
return c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
245
|
+
}
|
|
246
|
+
function linearToOklab([r, g, b]) {
|
|
247
|
+
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
248
|
+
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
249
|
+
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
250
|
+
const l_ = Math.cbrt(l);
|
|
251
|
+
const m_ = Math.cbrt(m);
|
|
252
|
+
const s_ = Math.cbrt(s);
|
|
253
|
+
return [
|
|
254
|
+
0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
255
|
+
1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
|
|
256
|
+
0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
function oklabToLinear([L, a, b]) {
|
|
260
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
261
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
262
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
263
|
+
const l = l_ * l_ * l_;
|
|
264
|
+
const m = m_ * m_ * m_;
|
|
265
|
+
const s = s_ * s_ * s_;
|
|
266
|
+
return [
|
|
267
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
268
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
269
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
function mixOklch(colorA, colorB, weightA) {
|
|
273
|
+
const wA = Math.max(0, Math.min(1, weightA));
|
|
274
|
+
const wB = 1 - wA;
|
|
275
|
+
const labA = linearToOklab(hexToRgb(colorA).map(toLinear));
|
|
276
|
+
const labB = linearToOklab(hexToRgb(colorB).map(toLinear));
|
|
277
|
+
const lab = [
|
|
278
|
+
labA[0] * wA + labB[0] * wB,
|
|
279
|
+
labA[1] * wA + labB[1] * wB,
|
|
280
|
+
labA[2] * wA + labB[2] * wB
|
|
281
|
+
];
|
|
282
|
+
return rgbToHex(oklabToLinear(lab).map(toSrgb));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/theme/theme.ts
|
|
286
|
+
function makeTheme(id, label, mode, blurb, palette, intentOverrides = {}) {
|
|
287
|
+
return {
|
|
288
|
+
id,
|
|
289
|
+
name: id,
|
|
290
|
+
label,
|
|
291
|
+
mode,
|
|
292
|
+
blurb,
|
|
293
|
+
palette,
|
|
294
|
+
tokens: { ...buildTokens(palette), ...intentOverrides }
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
var mocha = makeTheme(
|
|
298
|
+
"neutral",
|
|
299
|
+
"neutral",
|
|
300
|
+
"dark",
|
|
301
|
+
"Catppuccin Mocha \xB7 calm default",
|
|
302
|
+
neutral
|
|
303
|
+
);
|
|
304
|
+
var contrast = makeTheme(
|
|
305
|
+
"contrast",
|
|
306
|
+
"neutral+",
|
|
307
|
+
"dark",
|
|
308
|
+
"neutral \xB7 more contrast",
|
|
309
|
+
neutral,
|
|
310
|
+
{
|
|
311
|
+
bg: neutral.crust,
|
|
312
|
+
// deeper canvas → text & glyphs pop
|
|
313
|
+
bgPanel: neutral.crust,
|
|
314
|
+
bgElevated: neutral.base,
|
|
315
|
+
bgSunken: neutral.mantle,
|
|
316
|
+
// status bars lift off the canvas
|
|
317
|
+
bgSelected: neutral.surface1,
|
|
318
|
+
// brighter selection fill
|
|
319
|
+
bgFocused: neutral.surface2,
|
|
320
|
+
// brightest focus fill
|
|
321
|
+
border: neutral.overlay1,
|
|
322
|
+
// clearly visible borders
|
|
323
|
+
borderStrong: neutral.overlay2,
|
|
324
|
+
fgFaint: neutral.overlay2,
|
|
325
|
+
// brighter labels / captions
|
|
326
|
+
fgDim: neutral.subtext1
|
|
327
|
+
// lift the dimmest text tier
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
var vivid = makeTheme(
|
|
331
|
+
"vivid",
|
|
332
|
+
"vivid",
|
|
333
|
+
"dark",
|
|
334
|
+
"accent on selection & featured",
|
|
335
|
+
neutral,
|
|
336
|
+
{
|
|
337
|
+
bgSelected: mixOklch(neutral.lavender, neutral.crust, 0.2),
|
|
338
|
+
bgFocused: mixOklch(neutral.lavender, neutral.crust, 0.38),
|
|
339
|
+
statePending: neutral.sky
|
|
340
|
+
// the last grey state goes chromatic
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
var nordTheme = makeTheme("nord", "nord", "dark", "frost & polar night \xB7 cool", nord);
|
|
344
|
+
var gruvboxTheme = makeTheme("gruvbox", "gruvbox", "dark", "retro \xB7 warm & earthy", gruvbox);
|
|
345
|
+
var tokyonightTheme = makeTheme("tokyonight", "tokyo night", "dark", "saturated indigo night", tokyonight);
|
|
346
|
+
var latteTheme = makeTheme("latte", "latte", "light", "Catppuccin Latte \xB7 light", latte);
|
|
347
|
+
var _registry = /* @__PURE__ */ new Map();
|
|
348
|
+
var _order = [];
|
|
349
|
+
function put(theme) {
|
|
350
|
+
if (!_registry.has(theme.id)) _order.push(theme.id);
|
|
351
|
+
_registry.set(theme.id, theme);
|
|
352
|
+
}
|
|
353
|
+
[mocha, contrast, vivid, nordTheme, gruvboxTheme, tokyonightTheme, latteTheme].forEach(put);
|
|
354
|
+
var defaultTheme = tokyonightTheme;
|
|
355
|
+
function getTheme(id) {
|
|
356
|
+
return _registry.get(id) ?? defaultTheme;
|
|
357
|
+
}
|
|
358
|
+
function hasTheme(id) {
|
|
359
|
+
return _registry.has(id);
|
|
360
|
+
}
|
|
361
|
+
function allThemes() {
|
|
362
|
+
return _order.map((id) => _registry.get(id));
|
|
363
|
+
}
|
|
364
|
+
function listThemes() {
|
|
365
|
+
return allThemes().map(({ id, label, mode, blurb }) => ({ id, label, mode, blurb }));
|
|
366
|
+
}
|
|
367
|
+
function registerTheme(def) {
|
|
368
|
+
const base = getTheme(def.extends ?? "neutral");
|
|
369
|
+
const palette = { ...base.palette, ...def.palette };
|
|
370
|
+
const theme = makeTheme(
|
|
371
|
+
def.id,
|
|
372
|
+
def.label ?? def.id,
|
|
373
|
+
def.mode ?? "dark",
|
|
374
|
+
def.blurb ?? "",
|
|
375
|
+
palette,
|
|
376
|
+
def.intent ?? {}
|
|
377
|
+
);
|
|
378
|
+
put(theme);
|
|
379
|
+
return theme;
|
|
380
|
+
}
|
|
381
|
+
var BlinkContext = createContext({
|
|
382
|
+
theme: defaultTheme,
|
|
383
|
+
iconSet: "unicode",
|
|
384
|
+
setTheme: () => {
|
|
385
|
+
},
|
|
386
|
+
themes: listThemes()
|
|
387
|
+
});
|
|
388
|
+
function resolveTheme(t) {
|
|
389
|
+
if (t == null) return defaultTheme;
|
|
390
|
+
return typeof t === "string" ? getTheme(t) : t;
|
|
391
|
+
}
|
|
392
|
+
function ThemeProvider({
|
|
393
|
+
theme,
|
|
394
|
+
initialTheme,
|
|
395
|
+
iconSet = "unicode",
|
|
396
|
+
children
|
|
397
|
+
}) {
|
|
398
|
+
const [active, setActive] = useState(() => resolveTheme(theme ?? initialTheme));
|
|
399
|
+
const value = useMemo(
|
|
400
|
+
() => ({
|
|
401
|
+
theme: active,
|
|
402
|
+
iconSet,
|
|
403
|
+
setTheme: (t) => setActive(resolveTheme(t)),
|
|
404
|
+
themes: listThemes()
|
|
405
|
+
}),
|
|
406
|
+
[active, iconSet]
|
|
407
|
+
);
|
|
408
|
+
return /* @__PURE__ */ jsx(BlinkContext.Provider, { value, children });
|
|
409
|
+
}
|
|
410
|
+
function useBlink() {
|
|
411
|
+
return useContext(BlinkContext);
|
|
412
|
+
}
|
|
413
|
+
function useTheme() {
|
|
414
|
+
return useContext(BlinkContext).theme;
|
|
415
|
+
}
|
|
416
|
+
function useTokens() {
|
|
417
|
+
return useContext(BlinkContext).theme.tokens;
|
|
418
|
+
}
|
|
419
|
+
function useIconSet() {
|
|
420
|
+
return useContext(BlinkContext).iconSet;
|
|
421
|
+
}
|
|
422
|
+
function useThemeControls() {
|
|
423
|
+
const { theme, setTheme, themes } = useContext(BlinkContext);
|
|
424
|
+
return { theme, themeId: theme.id, setTheme, themes };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/glyphs/nerdIndex.ts
|
|
428
|
+
var NERD_INDEX = {
|
|
429
|
+
// ── Font Awesome (fa-) · classic, stable codepoints ───────────────────────
|
|
430
|
+
"fa-github": "f09b",
|
|
431
|
+
"fa-gitlab": "f296",
|
|
432
|
+
"fa-bitbucket": "f171",
|
|
433
|
+
"fa-google": "f1a0",
|
|
434
|
+
"fa-slack": "f198",
|
|
435
|
+
"fa-microsoft": "f3ca",
|
|
436
|
+
"fa-windows": "f17a",
|
|
437
|
+
"fa-apple": "f179",
|
|
438
|
+
"fa-linux": "f17c",
|
|
439
|
+
"fa-android": "f17b",
|
|
440
|
+
"fa-aws": "f375",
|
|
441
|
+
"fa-database": "f1c0",
|
|
442
|
+
"fa-server": "f233",
|
|
443
|
+
"fa-hdd": "f0a0",
|
|
444
|
+
"fa-cloud": "f0c2",
|
|
445
|
+
"fa-cloud_upload": "f0ee",
|
|
446
|
+
"fa-cloud_download": "f0ed",
|
|
447
|
+
"fa-terminal": "f120",
|
|
448
|
+
"fa-code": "f121",
|
|
449
|
+
"fa-cog": "f013",
|
|
450
|
+
"fa-cogs": "f085",
|
|
451
|
+
"fa-wrench": "f0ad",
|
|
452
|
+
"fa-bug": "f188",
|
|
453
|
+
"fa-rocket": "f135",
|
|
454
|
+
"fa-bolt": "f0e7",
|
|
455
|
+
"fa-lock": "f023",
|
|
456
|
+
"fa-unlock": "f09c",
|
|
457
|
+
"fa-key": "f084",
|
|
458
|
+
"fa-shield": "f132",
|
|
459
|
+
"fa-folder": "f07b",
|
|
460
|
+
"fa-folder_open": "f07c",
|
|
461
|
+
"fa-file": "f15b",
|
|
462
|
+
"fa-file_text": "f0f6",
|
|
463
|
+
"fa-home": "f015",
|
|
464
|
+
"fa-user": "f007",
|
|
465
|
+
"fa-users": "f0c0",
|
|
466
|
+
"fa-globe": "f0ac",
|
|
467
|
+
"fa-envelope": "f0e0",
|
|
468
|
+
"fa-bell": "f0f3",
|
|
469
|
+
"fa-star": "f005",
|
|
470
|
+
"fa-heart": "f004",
|
|
471
|
+
"fa-search": "f002",
|
|
472
|
+
"fa-tag": "f02b",
|
|
473
|
+
"fa-tags": "f02c",
|
|
474
|
+
"fa-clock": "f017",
|
|
475
|
+
"fa-calendar": "f073",
|
|
476
|
+
"fa-download": "f019",
|
|
477
|
+
"fa-upload": "f093",
|
|
478
|
+
"fa-refresh": "f021",
|
|
479
|
+
"fa-trash": "f1f8",
|
|
480
|
+
"fa-check": "f00c",
|
|
481
|
+
"fa-times": "f00d",
|
|
482
|
+
"fa-plus": "f067",
|
|
483
|
+
"fa-minus": "f068",
|
|
484
|
+
"fa-info": "f129",
|
|
485
|
+
"fa-question": "f128",
|
|
486
|
+
"fa-exclamation": "f12a",
|
|
487
|
+
"fa-warning": "f071",
|
|
488
|
+
"fa-power_off": "f011",
|
|
489
|
+
"fa-play": "f04b",
|
|
490
|
+
"fa-pause": "f04c",
|
|
491
|
+
"fa-stop": "f04d",
|
|
492
|
+
"fa-link": "f0c1",
|
|
493
|
+
"fa-eye": "f06e",
|
|
494
|
+
"fa-filter": "f0b0",
|
|
495
|
+
"fa-flag": "f024",
|
|
496
|
+
"fa-bookmark": "f02e",
|
|
497
|
+
"fa-comment": "f075",
|
|
498
|
+
"fa-microchip": "f2db",
|
|
499
|
+
"fa-wifi": "f1eb",
|
|
500
|
+
"fa-bluetooth": "f293",
|
|
501
|
+
"fa-battery": "f240",
|
|
502
|
+
"fa-fire": "f06d",
|
|
503
|
+
"fa-leaf": "f06c",
|
|
504
|
+
"fa-music": "f001",
|
|
505
|
+
"fa-camera": "f030",
|
|
506
|
+
"fa-image": "f03e",
|
|
507
|
+
"fa-video": "f03d",
|
|
508
|
+
"fa-map": "f279",
|
|
509
|
+
"fa-location": "f041",
|
|
510
|
+
"fa-phone": "f095",
|
|
511
|
+
"fa-desktop": "f108",
|
|
512
|
+
"fa-laptop": "f109",
|
|
513
|
+
"fa-mobile": "f10b",
|
|
514
|
+
"fa-tablet": "f10a",
|
|
515
|
+
"fa-print": "f02f",
|
|
516
|
+
"fa-save": "f0c7",
|
|
517
|
+
"fa-edit": "f044",
|
|
518
|
+
"fa-copy": "f0c5",
|
|
519
|
+
"fa-cut": "f0c4",
|
|
520
|
+
"fa-paste": "f0ea",
|
|
521
|
+
"fa-file_o": "f016",
|
|
522
|
+
"fa-file_pdf": "f1c1",
|
|
523
|
+
"fa-file_image": "f1c5",
|
|
524
|
+
"fa-file_archive": "f1c6",
|
|
525
|
+
"fa-beer": "f0fc",
|
|
526
|
+
"fa-twitter": "f099",
|
|
527
|
+
"fa-facebook": "f09a",
|
|
528
|
+
"fa-youtube": "f167",
|
|
529
|
+
"fa-linkedin": "f08c",
|
|
530
|
+
"fa-reddit": "f1a1",
|
|
531
|
+
"fa-instagram": "f16d",
|
|
532
|
+
"fa-whatsapp": "f232",
|
|
533
|
+
"fa-discord": "f392",
|
|
534
|
+
"fa-telegram": "f2c6",
|
|
535
|
+
"fa-mastodon": "f4f6",
|
|
536
|
+
// ── Devicons (dev-) · language & tool logos ───────────────────────────────
|
|
537
|
+
"dev-git": "e702",
|
|
538
|
+
"dev-git_branch": "e725",
|
|
539
|
+
"dev-python": "e73c",
|
|
540
|
+
"dev-php": "e73d",
|
|
541
|
+
"dev-laravel": "e73f",
|
|
542
|
+
"dev-rust": "e7a8",
|
|
543
|
+
"dev-react": "e7ba",
|
|
544
|
+
"dev-nodejs_small": "e718",
|
|
545
|
+
"dev-npm": "e71e",
|
|
546
|
+
"dev-java": "e738",
|
|
547
|
+
"dev-ruby": "e739",
|
|
548
|
+
"dev-go": "e724",
|
|
549
|
+
"dev-mysql": "e704",
|
|
550
|
+
"dev-postgresql": "e76e",
|
|
551
|
+
"dev-redis": "e76d",
|
|
552
|
+
"dev-mongodb": "e7a4",
|
|
553
|
+
"dev-sqllite": "e7c4",
|
|
554
|
+
"dev-vim": "e7c5",
|
|
555
|
+
"dev-sublime": "e7aa",
|
|
556
|
+
"dev-visualstudio": "e70c",
|
|
557
|
+
"dev-html5": "e736",
|
|
558
|
+
"dev-css3": "e749",
|
|
559
|
+
"dev-angular": "e753",
|
|
560
|
+
"dev-django": "e71d",
|
|
561
|
+
"dev-rails": "e73b",
|
|
562
|
+
"dev-dotnet": "e77f",
|
|
563
|
+
"dev-bootstrap": "e747",
|
|
564
|
+
"dev-jquery": "e750",
|
|
565
|
+
"dev-svelte": "e697",
|
|
566
|
+
"dev-yarn": "e6a7",
|
|
567
|
+
"dev-composer": "e683",
|
|
568
|
+
// ── Seti-UI (seti-) · file-type icons ─────────────────────────────────────
|
|
569
|
+
"seti-typescript": "e628",
|
|
570
|
+
"seti-javascript": "e60c",
|
|
571
|
+
"seti-json": "e60b",
|
|
572
|
+
"seti-html": "e60e",
|
|
573
|
+
"seti-css": "e614",
|
|
574
|
+
"seti-markdown": "e609",
|
|
575
|
+
"seti-go": "e627",
|
|
576
|
+
"seti-c": "e61e",
|
|
577
|
+
"seti-cpp": "e61d",
|
|
578
|
+
"seti-vue": "e6a0",
|
|
579
|
+
"seti-config": "e615",
|
|
580
|
+
// ── Powerline (pl-) · prompt separators & symbols ─────────────────────────
|
|
581
|
+
"pl-left_hard_divider": "e0b0",
|
|
582
|
+
"pl-left_soft_divider": "e0b1",
|
|
583
|
+
"pl-right_hard_divider": "e0b2",
|
|
584
|
+
"pl-right_soft_divider": "e0b3",
|
|
585
|
+
"pl-branch": "e0a0",
|
|
586
|
+
"pl-line_number": "e0a1",
|
|
587
|
+
"pl-readonly": "e0a2"
|
|
588
|
+
};
|
|
589
|
+
var NERD_INDEX_SOURCES = {
|
|
590
|
+
fa: "Font Awesome",
|
|
591
|
+
dev: "Devicons",
|
|
592
|
+
seti: "Seti-UI",
|
|
593
|
+
pl: "Powerline",
|
|
594
|
+
cod: "Codicons",
|
|
595
|
+
oct: "Octicons",
|
|
596
|
+
md: "Material Design",
|
|
597
|
+
weather: "Weather"
|
|
598
|
+
};
|
|
599
|
+
function registerNerdIndex(full) {
|
|
600
|
+
for (const k in full) {
|
|
601
|
+
if (k[0] === "_") continue;
|
|
602
|
+
if (!(k in NERD_INDEX)) NERD_INDEX[k] = full[k];
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function nfChar(nameOrHex) {
|
|
606
|
+
if (nameOrHex == null) return "";
|
|
607
|
+
let hex = NERD_INDEX[nameOrHex];
|
|
608
|
+
if (hex == null && /^[0-9a-fA-F]{2,6}$/.test(nameOrHex)) hex = nameOrHex;
|
|
609
|
+
if (hex == null) return "";
|
|
610
|
+
try {
|
|
611
|
+
return String.fromCodePoint(parseInt(hex, 16));
|
|
612
|
+
} catch {
|
|
613
|
+
return "";
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function nf(name) {
|
|
617
|
+
return nfChar(name);
|
|
618
|
+
}
|
|
619
|
+
function nfHas(name) {
|
|
620
|
+
return name in NERD_INDEX;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/glyphs/packs.ts
|
|
624
|
+
var cp = (hex) => String.fromCodePoint(parseInt(hex, 16));
|
|
625
|
+
var COMMON_DOMAINS = {
|
|
626
|
+
database: { nerd: cp("f1c0"), unicode: "\u25A4", ascii: "db", color: "domainNeutral" },
|
|
627
|
+
mysql: { nerd: cp("e704"), unicode: "\u25A4", ascii: "my", color: "domainAzure" },
|
|
628
|
+
postgresql: { nerd: cp("e76e"), unicode: "\u25A4", ascii: "pg", color: "domainBlue" },
|
|
629
|
+
redis: { nerd: cp("e76d"), unicode: "\u25C6", ascii: "rd", color: "domainRed" },
|
|
630
|
+
docker: { nerd: cp("f308"), unicode: "\u25A6", ascii: "dk", color: "domainCyan" },
|
|
631
|
+
github: { nerd: cp("f09b"), unicode: "\u25C9", ascii: "gh", color: "domainNeutral" },
|
|
632
|
+
git: { nerd: cp("e702"), unicode: "\u25C8", ascii: "gt", color: "domainAmber" },
|
|
633
|
+
ssh: { nerd: cp("f015"), unicode: "\u2302", ascii: "sh", color: "domainYellow" },
|
|
634
|
+
nodejs: { nerd: cp("e718"), unicode: "\u2756", ascii: "nd", color: "domainGreen" },
|
|
635
|
+
php: { nerd: cp("e73d"), unicode: "\u276E", ascii: "ph", color: "domainViolet" },
|
|
636
|
+
python: { nerd: cp("e73c"), unicode: "\u276F", ascii: "py", color: "domainYellow" },
|
|
637
|
+
vim: { nerd: cp("e7c5"), unicode: "\u2731", ascii: "vi", color: "domainGreen" },
|
|
638
|
+
apple: { nerd: cp("f179"), unicode: "\u25C7", ascii: "mac", color: "domainNeutral" },
|
|
639
|
+
linux: { nerd: cp("f17c"), unicode: "\u25B3", ascii: "lx", color: "domainYellow" },
|
|
640
|
+
ubuntu: { nerd: cp("f31b"), unicode: "\u25CB", ascii: "ub", color: "domainAmber" },
|
|
641
|
+
font: { nerd: cp("f031"), unicode: "\u0192", ascii: "ft", color: "domainNeutral" },
|
|
642
|
+
ai: { nerd: cp("f2db"), unicode: "\u259A", ascii: "ai", color: "accent" },
|
|
643
|
+
// ↯ (single-cell) over the design system's ⚡, which is emoji-presentation and
|
|
644
|
+
// double-wide — it would break the character cell in unicode mode.
|
|
645
|
+
bolt: { nerd: cp("f0e7"), unicode: "\u21AF", ascii: "!", color: "domainAmber" }
|
|
646
|
+
};
|
|
647
|
+
var LANGUAGES = {
|
|
648
|
+
javascript: { nerd: cp("e60c"), unicode: "\u25C6", ascii: "js", color: "domainYellow" },
|
|
649
|
+
typescript: { nerd: cp("e628"), unicode: "\u25C6", ascii: "ts", color: "domainBlue" },
|
|
650
|
+
python: { nerd: cp("e73c"), unicode: "\u276F", ascii: "py", color: "domainYellow" },
|
|
651
|
+
php: { nerd: cp("e73d"), unicode: "\u276E", ascii: "ph", color: "domainViolet" },
|
|
652
|
+
ruby: { nerd: cp("e739"), unicode: "\u25C6", ascii: "rb", color: "domainRed" },
|
|
653
|
+
rust: { nerd: cp("e7a8"), unicode: "\u25C6", ascii: "rs", color: "domainAmber" },
|
|
654
|
+
go: { nerd: cp("e627"), unicode: "\u25C6", ascii: "go", color: "domainCyan" },
|
|
655
|
+
java: { nerd: cp("e738"), unicode: "\u25C6", ascii: "jv", color: "domainAmber" },
|
|
656
|
+
nodejs: { nerd: cp("e718"), unicode: "\u2756", ascii: "nd", color: "domainGreen" },
|
|
657
|
+
cpp: { nerd: cp("e61d"), unicode: "\u25C6", ascii: "c+", color: "domainBlue" },
|
|
658
|
+
c: { nerd: cp("e61e"), unicode: "\u25C6", ascii: "c", color: "domainBlue" },
|
|
659
|
+
csharp: { nerd: cp("e648"), unicode: "\u25C6", ascii: "c#", color: "domainViolet" },
|
|
660
|
+
html: { nerd: cp("e60e"), unicode: "\u25C6", ascii: "ht", color: "domainAmber" },
|
|
661
|
+
css: { nerd: cp("e614"), unicode: "\u25C6", ascii: "cs", color: "domainBlue" },
|
|
662
|
+
react: { nerd: cp("e7ba"), unicode: "\u25C6", ascii: "rx", color: "domainCyan" },
|
|
663
|
+
vue: { nerd: cp("e6a0"), unicode: "\u25C6", ascii: "vu", color: "domainGreen" }
|
|
664
|
+
};
|
|
665
|
+
var DATABASES = {
|
|
666
|
+
database: { nerd: cp("f1c0"), unicode: "\u25A4", ascii: "db", color: "domainNeutral" },
|
|
667
|
+
postgresql: { nerd: cp("e76e"), unicode: "\u25A4", ascii: "pg", color: "domainBlue" },
|
|
668
|
+
mysql: { nerd: cp("e704"), unicode: "\u25A4", ascii: "my", color: "domainAzure" },
|
|
669
|
+
mariadb: { nerd: cp("f1c0"), unicode: "\u25A4", ascii: "mb", color: "domainAzure" },
|
|
670
|
+
redis: { nerd: cp("e76d"), unicode: "\u25C6", ascii: "rd", color: "domainRed" },
|
|
671
|
+
mongodb: { nerd: cp("e7a4"), unicode: "\u25C6", ascii: "mg", color: "domainGreen" },
|
|
672
|
+
sqlite: { nerd: cp("e7c4"), unicode: "\u25A4", ascii: "sq", color: "domainBlue" }
|
|
673
|
+
};
|
|
674
|
+
var CLOUD = {
|
|
675
|
+
aws: { nerd: cp("f375"), unicode: "\u25C6", ascii: "aws", color: "domainAmber" },
|
|
676
|
+
cloud: { nerd: cp("f0c2"), unicode: "\u25CC", ascii: "cl", color: "domainCyan" },
|
|
677
|
+
// ◌ over the double-wide ☁ (same width-1 rule as bolt/ngrok/mailpit)
|
|
678
|
+
server: { nerd: cp("f233"), unicode: "\u25A4", ascii: "srv", color: "domainNeutral" },
|
|
679
|
+
docker: { nerd: cp("f308"), unicode: "\u25A6", ascii: "dk", color: "domainCyan" },
|
|
680
|
+
kubernetes: { nerd: cp("e81d"), unicode: "\u25C6", ascii: "k8", color: "domainBlue" },
|
|
681
|
+
nginx: { nerd: cp("e776"), unicode: "\u25C6", ascii: "ng", color: "domainGreen" }
|
|
682
|
+
};
|
|
683
|
+
var EDITORS = {
|
|
684
|
+
vim: { nerd: cp("e7c5"), unicode: "\u2731", ascii: "vi", color: "domainGreen" },
|
|
685
|
+
neovim: { nerd: cp("e7c5"), unicode: "\u2731", ascii: "nv", color: "domainGreen" },
|
|
686
|
+
vscode: { nerd: cp("e70c"), unicode: "\u25C6", ascii: "vc", color: "domainBlue" },
|
|
687
|
+
sublime: { nerd: cp("e7aa"), unicode: "\u25C6", ascii: "su", color: "domainAmber" },
|
|
688
|
+
emacs: { nerd: cp("e632"), unicode: "\u25C6", ascii: "em", color: "domainViolet" }
|
|
689
|
+
};
|
|
690
|
+
var OS = {
|
|
691
|
+
apple: { nerd: cp("f179"), unicode: "\u25C7", ascii: "mac", color: "domainNeutral" },
|
|
692
|
+
linux: { nerd: cp("f17c"), unicode: "\u25B3", ascii: "lx", color: "domainYellow" },
|
|
693
|
+
ubuntu: { nerd: cp("f31b"), unicode: "\u25CB", ascii: "ub", color: "domainAmber" },
|
|
694
|
+
debian: { nerd: cp("f306"), unicode: "\u25C6", ascii: "dn", color: "domainRed" },
|
|
695
|
+
arch: { nerd: cp("f303"), unicode: "\u25B3", ascii: "ar", color: "domainBlue" },
|
|
696
|
+
fedora: { nerd: cp("f30a"), unicode: "\u25C6", ascii: "fd", color: "domainBlue" },
|
|
697
|
+
windows: { nerd: cp("f17a"), unicode: "\u25A6", ascii: "win", color: "domainCyan" },
|
|
698
|
+
android: { nerd: cp("f17b"), unicode: "\u25C6", ascii: "an", color: "domainGreen" }
|
|
699
|
+
};
|
|
700
|
+
var COMPANIES = {
|
|
701
|
+
github: { nerd: cp("f09b"), unicode: "\u25C9", ascii: "gh", color: "domainNeutral" },
|
|
702
|
+
gitlab: { nerd: cp("f296"), unicode: "\u25C6", ascii: "gl", color: "domainAmber" },
|
|
703
|
+
bitbucket: { nerd: cp("f171"), unicode: "\u25C6", ascii: "bb", color: "domainBlue" },
|
|
704
|
+
google: { nerd: cp("f1a0"), unicode: "\u25C9", ascii: "go", color: "domainBlue" },
|
|
705
|
+
microsoft: { nerd: cp("f3ca"), unicode: "\u25A6", ascii: "ms", color: "domainCyan" },
|
|
706
|
+
apple: { nerd: cp("f179"), unicode: "\u25C7", ascii: "ap", color: "domainNeutral" },
|
|
707
|
+
slack: { nerd: cp("f198"), unicode: "\u25C6", ascii: "sl", color: "domainViolet" },
|
|
708
|
+
npm: { nerd: cp("e71e"), unicode: "\u25C6", ascii: "np", color: "domainRed" },
|
|
709
|
+
git: { nerd: cp("e702"), unicode: "\u25C8", ascii: "gt", color: "domainAmber" },
|
|
710
|
+
// Anthropic / Claude — no verified Nerd Font codepoint, so `nerd` is left empty
|
|
711
|
+
// and the glyph degrades to ✶ (a width-1 sunburst, evoking the Claude mark);
|
|
712
|
+
// accent = the brand lavender. Same empty-nerd→unicode pattern as DEVINFRA's
|
|
713
|
+
// tailscale/syncthing/mosh. A curator can fill `nerd` if a glyph is verified.
|
|
714
|
+
claude: { nerd: "", unicode: "\u2736", ascii: "cl", color: "accent" }
|
|
715
|
+
};
|
|
716
|
+
var FRAMEWORKS = {
|
|
717
|
+
react: { nerd: cp("e7ba"), unicode: "\u25C6", ascii: "rx", color: "domainCyan" },
|
|
718
|
+
vue: { nerd: cp("e6a0"), unicode: "\u25C6", ascii: "vu", color: "domainGreen" },
|
|
719
|
+
angular: { nerd: cp("e753"), unicode: "\u25C6", ascii: "ng", color: "domainRed" },
|
|
720
|
+
svelte: { nerd: cp("e697"), unicode: "\u25C6", ascii: "sv", color: "domainAmber" },
|
|
721
|
+
laravel: { nerd: cp("e73f"), unicode: "\u25C6", ascii: "lv", color: "domainRed" },
|
|
722
|
+
django: { nerd: cp("e71d"), unicode: "\u25C6", ascii: "dj", color: "domainGreen" },
|
|
723
|
+
rails: { nerd: cp("e73b"), unicode: "\u25C6", ascii: "rr", color: "domainRed" },
|
|
724
|
+
dotnet: { nerd: cp("e77f"), unicode: "\u25C6", ascii: "net", color: "domainViolet" },
|
|
725
|
+
bootstrap: { nerd: cp("e747"), unicode: "\u25C6", ascii: "bs", color: "domainViolet" },
|
|
726
|
+
jquery: { nerd: cp("e750"), unicode: "\u25C6", ascii: "jq", color: "domainBlue" }
|
|
727
|
+
};
|
|
728
|
+
var FILES = {
|
|
729
|
+
file: { nerd: cp("f15b"), unicode: "\u25A2", ascii: "fl", color: "domainNeutral" },
|
|
730
|
+
folder: { nerd: cp("f07b"), unicode: "\u25A4", ascii: "dir", color: "domainBlue" },
|
|
731
|
+
folder_open: { nerd: cp("f07c"), unicode: "\u25A4", ascii: "dir", color: "domainBlue" },
|
|
732
|
+
json: { nerd: cp("e60b"), unicode: "\u25C6", ascii: "js", color: "domainYellow" },
|
|
733
|
+
yaml: { nerd: cp("e615"), unicode: "\u25C6", ascii: "yml", color: "domainAmber" },
|
|
734
|
+
markdown: { nerd: cp("e609"), unicode: "\u25C6", ascii: "md", color: "domainNeutral" },
|
|
735
|
+
pdf: { nerd: cp("f1c1"), unicode: "\u25A2", ascii: "pdf", color: "domainRed" },
|
|
736
|
+
image: { nerd: cp("f1c5"), unicode: "\u25A2", ascii: "img", color: "domainGreen" },
|
|
737
|
+
archive: { nerd: cp("f1c6"), unicode: "\u25A2", ascii: "zip", color: "domainAmber" },
|
|
738
|
+
lock: { nerd: cp("f023"), unicode: "\u2302", ascii: "lk", color: "domainYellow" }
|
|
739
|
+
};
|
|
740
|
+
var SOCIAL = {
|
|
741
|
+
slack: { nerd: cp("f198"), unicode: "\u25C6", ascii: "sl", color: "domainViolet" },
|
|
742
|
+
discord: { nerd: cp("f392"), unicode: "\u25C6", ascii: "dc", color: "domainBlue" },
|
|
743
|
+
telegram: { nerd: cp("f2c6"), unicode: "\u25C6", ascii: "tg", color: "domainCyan" },
|
|
744
|
+
twitter: { nerd: cp("f099"), unicode: "\u25C6", ascii: "tw", color: "domainCyan" },
|
|
745
|
+
youtube: { nerd: cp("f167"), unicode: "\u25C6", ascii: "yt", color: "domainRed" },
|
|
746
|
+
linkedin: { nerd: cp("f08c"), unicode: "\u25C6", ascii: "in", color: "domainBlue" },
|
|
747
|
+
reddit: { nerd: cp("f1a1"), unicode: "\u25C6", ascii: "rd", color: "domainAmber" },
|
|
748
|
+
mastodon: { nerd: cp("f4f6"), unicode: "\u25C6", ascii: "md", color: "domainViolet" }
|
|
749
|
+
};
|
|
750
|
+
var ACTIONS = {
|
|
751
|
+
search: { nerd: cp("f002"), unicode: "\u25CE", ascii: "?", color: "domainNeutral" },
|
|
752
|
+
filter: { nerd: cp("f0b0"), unicode: "\u25BD", ascii: "fi", color: "domainNeutral" },
|
|
753
|
+
settings: { nerd: cp("f013"), unicode: "\u2726", ascii: "cf", color: "domainNeutral" },
|
|
754
|
+
trash: { nerd: cp("f1f8"), unicode: "\u2717", ascii: "rm", color: "stateErr" },
|
|
755
|
+
download: { nerd: cp("f019"), unicode: "\u25BC", ascii: "dl", color: "domainBlue" },
|
|
756
|
+
upload: { nerd: cp("f093"), unicode: "\u25B2", ascii: "ul", color: "domainBlue" },
|
|
757
|
+
refresh: { nerd: cp("f021"), unicode: "\u21BB", ascii: "rf", color: "stateInfo" },
|
|
758
|
+
edit: { nerd: cp("f044"), unicode: "\u270E", ascii: "ed", color: "domainNeutral" },
|
|
759
|
+
bell: { nerd: cp("f0f3"), unicode: "\u25D4", ascii: "nt", color: "domainYellow" },
|
|
760
|
+
link: { nerd: cp("f0c1"), unicode: "\u221E", ascii: "ln", color: "link" }
|
|
761
|
+
};
|
|
762
|
+
var SYSTEM = {
|
|
763
|
+
terminal: { nerd: cp("f120"), unicode: "\u276F", ascii: "tm", color: "domainGreen" },
|
|
764
|
+
// fa-terminal
|
|
765
|
+
code: { nerd: cp("f121"), unicode: "\u25C7", ascii: "cd", color: "domainBlue" },
|
|
766
|
+
// fa-code
|
|
767
|
+
globe: { nerd: cp("f0ac"), unicode: "\u25CD", ascii: "wb", color: "domainCyan" },
|
|
768
|
+
// fa-globe
|
|
769
|
+
home: { nerd: cp("f015"), unicode: "\u2302", ascii: "hm", color: "domainNeutral" },
|
|
770
|
+
// fa-home
|
|
771
|
+
key: { nerd: cp("f084"), unicode: "\u26BF", ascii: "ky", color: "domainYellow" },
|
|
772
|
+
// fa-key
|
|
773
|
+
mail: { nerd: cp("f0e0"), unicode: "\u22A0", ascii: "ml", color: "domainBlue" },
|
|
774
|
+
// fa-envelope — ⊠ over the double-wide ✉
|
|
775
|
+
phone: { nerd: cp("f095"), unicode: "\u260F", ascii: "tel", color: "domainGreen" },
|
|
776
|
+
// fa-phone — ☏ over the double-wide ☎
|
|
777
|
+
package: { nerd: cp("f1b2"), unicode: "\u25A6", ascii: "pkg", color: "domainAmber" },
|
|
778
|
+
// fa-cube
|
|
779
|
+
sync: { nerd: cp("f021"), unicode: "\u21BB", ascii: "sy", color: "domainCyan" },
|
|
780
|
+
// fa-refresh
|
|
781
|
+
text: { nerd: cp("f0f6"), unicode: "\u25A2", ascii: "tx", color: "domainNeutral" },
|
|
782
|
+
// fa-file-text
|
|
783
|
+
tools: { nerd: cp("f0ad"), unicode: "\u2726", ascii: "tl", color: "domainNeutral" }
|
|
784
|
+
// fa-wrench
|
|
785
|
+
};
|
|
786
|
+
var PACKAGES = {
|
|
787
|
+
npm: { nerd: cp("e71e"), unicode: "\u25C6", ascii: "np", color: "domainRed" },
|
|
788
|
+
yarn: { nerd: cp("e6a7"), unicode: "\u25C6", ascii: "yn", color: "domainBlue" },
|
|
789
|
+
cargo: { nerd: cp("e7a8"), unicode: "\u25C6", ascii: "cg", color: "domainAmber" },
|
|
790
|
+
pip: { nerd: cp("e73c"), unicode: "\u25C6", ascii: "pp", color: "domainYellow" },
|
|
791
|
+
composer: { nerd: cp("e683"), unicode: "\u25C6", ascii: "co", color: "domainYellow" },
|
|
792
|
+
homebrew: { nerd: cp("f0fc"), unicode: "\u25C6", ascii: "br", color: "domainYellow" },
|
|
793
|
+
apt: { nerd: cp("f306"), unicode: "\u25C6", ascii: "ap", color: "domainRed" }
|
|
794
|
+
};
|
|
795
|
+
var DEVINFRA = {
|
|
796
|
+
laravel: { nerd: cp("e73f"), unicode: "\u25C6", ascii: "lv", color: "domainRed" },
|
|
797
|
+
// promoted from FRAMEWORKS
|
|
798
|
+
valet: { nerd: cp("e73f"), unicode: "\u25C6", ascii: "va", color: "domainRed" },
|
|
799
|
+
// laravel valet (alias glyph)
|
|
800
|
+
"code-server": { nerd: cp("e70c"), unicode: "\u25C6", ascii: "cs", color: "domainBlue" },
|
|
801
|
+
// vscode family (EDITORS)
|
|
802
|
+
ngrok: { nerd: cp("f0e7"), unicode: "\u21AF", ascii: "ng", color: "domainAmber" },
|
|
803
|
+
// fa-bolt — ↯ over the DS's double-wide ⚡
|
|
804
|
+
mailpit: { nerd: cp("f0e0"), unicode: "\u22A0", ascii: "mp", color: "domainAmber" },
|
|
805
|
+
// fa-envelope — ⊠ over the DS's double-wide ✉
|
|
806
|
+
tailscale: { nerd: "", unicode: "\u25C6", ascii: "ts", color: "domainCyan" },
|
|
807
|
+
// nerd: verify → degrades to ◆
|
|
808
|
+
syncthing: { nerd: "", unicode: "\u21BB", ascii: "st", color: "domainViolet" },
|
|
809
|
+
// nerd: verify → degrades to ↻
|
|
810
|
+
mosh: { nerd: "", unicode: "\u25C6", ascii: "mo", color: "domainAmber" }
|
|
811
|
+
// nerd: verify → degrades to ◆
|
|
812
|
+
};
|
|
813
|
+
var GLYPH_PACKS = {
|
|
814
|
+
languages: LANGUAGES,
|
|
815
|
+
databases: DATABASES,
|
|
816
|
+
cloud: CLOUD,
|
|
817
|
+
editors: EDITORS,
|
|
818
|
+
os: OS,
|
|
819
|
+
companies: COMPANIES,
|
|
820
|
+
frameworks: FRAMEWORKS,
|
|
821
|
+
files: FILES,
|
|
822
|
+
social: SOCIAL,
|
|
823
|
+
actions: ACTIONS,
|
|
824
|
+
system: SYSTEM,
|
|
825
|
+
packages: PACKAGES,
|
|
826
|
+
devinfra: DEVINFRA
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
// src/glyphs/glyphs.ts
|
|
830
|
+
var stateGlyphs = {
|
|
831
|
+
check: { nerd: "\u2713", unicode: "\u2713", ascii: "[x]" },
|
|
832
|
+
cross: { nerd: "\u2717", unicode: "\u2717", ascii: "[!]" },
|
|
833
|
+
circle: { nerd: "\u25EF", unicode: "\u25EF", ascii: "[ ]" },
|
|
834
|
+
half: { nerd: "\u25D0", unicode: "\u25D0", ascii: "[~]" },
|
|
835
|
+
// ⚠ WIDTH-1 — do NOT swap to ☑ (U+2611). string-width measures ☑ as TWO
|
|
836
|
+
// cells, but ambiguous-width terminals (the non-CJK default) paint it as ONE
|
|
837
|
+
// → List/Form reserve 2 columns, the terminal fills 1, and the selection
|
|
838
|
+
// background tears a phantom hole right after the checkbox (audited 2026-06-02
|
|
839
|
+
// on the mesh picker). ■ (U+25A0) is the width-1 "on" box; it pairs with the
|
|
840
|
+
// width-1 ☐ (off) and ▣ (locked) so the whole checkbox column holds the grid.
|
|
841
|
+
checkboxOn: { nerd: "\u25A0", unicode: "\u25A0", ascii: "[*]" },
|
|
842
|
+
checkboxOff: { nerd: "\u2610", unicode: "\u2610", ascii: "[ ]" },
|
|
843
|
+
// selected + locked (a required item — no toggle).
|
|
844
|
+
checkboxLock: { nerd: "\u25A3", unicode: "\u25A3", ascii: "[#]" },
|
|
845
|
+
// WIDTH-1 — ⚠ (U+26A0) measures 2 in string-width but paints 1 in
|
|
846
|
+
// ambiguous-width terminals (same grid tear as the old ☑ checkbox). nerd uses
|
|
847
|
+
// the Nerd Font warning triangle (fa-warning, U+F071, width-1); unicode
|
|
848
|
+
// degrades to △ (width-1). Drives stateIntents.warn + Banner's warn tone.
|
|
849
|
+
warn: { nerd: "\uF071", unicode: "\u25B3", ascii: "[!]" },
|
|
850
|
+
rerun: { nerd: "\u21BB", unicode: "\u21BB", ascii: "(*)" }
|
|
851
|
+
};
|
|
852
|
+
var navGlyphs = {
|
|
853
|
+
// ⚠ LOCKED — DO NOT SWAP THIS GLYPH (flagged in a prior audit).
|
|
854
|
+
// The focus caret MUST stay WIDTH-1. `►` (U+25BA, solid right pointer) is the
|
|
855
|
+
// one-cell sibling of the design's `▶` (U+25B6). `string-width` measures `▶`
|
|
856
|
+
// as TWO cells, so changing `►` back to `▶` — or to any wide glyph — is a
|
|
857
|
+
// rendering BUG, not a style choice: the focused row's highlight band gets a
|
|
858
|
+
// hole behind the wide glyph's phantom cell and the List columns drift (see
|
|
859
|
+
// the band logic in components/List.tsx). `►` looks identical (a filled
|
|
860
|
+
// triangle) at one cell. Keep it as-is.
|
|
861
|
+
focus: { nerd: "\u25BA", unicode: "\u25BA", ascii: ">" },
|
|
862
|
+
collapsed: { nerd: "\u25B8", unicode: "\u25B8", ascii: ">" },
|
|
863
|
+
expanded: { nerd: "\u25BE", unicode: "\u25BE", ascii: "v" },
|
|
864
|
+
depends: { nerd: "\u21B3", unicode: "\u21B3", ascii: "\\>" },
|
|
865
|
+
flow: { nerd: "\u2192", unicode: "\u2192", ascii: "->" },
|
|
866
|
+
// ◄ (U+25C4) is the WIDTH-1 left sibling of the focus caret ► (U+25BA) — the
|
|
867
|
+
// same one-cell rule as the focus glyph above. ◀ (U+25C0) measures 2, so it
|
|
868
|
+
// would tear any grid that uses it (latent: currently unused).
|
|
869
|
+
back: { nerd: "\u25C4", unicode: "\u25C4", ascii: "<" },
|
|
870
|
+
// Overflow markers for a windowed List / LogView — "there is more, off-screen".
|
|
871
|
+
moreAbove: { nerd: "\u25B4", unicode: "\u25B4", ascii: "^" },
|
|
872
|
+
moreBelow: { nerd: "\u25BE", unicode: "\u25BE", ascii: "v" }
|
|
873
|
+
};
|
|
874
|
+
var DEFAULT_GLYPH_COLOR = "fgMuted";
|
|
875
|
+
var DEFAULT_UNICODE = "\u25C6";
|
|
876
|
+
var registry = /* @__PURE__ */ new Map();
|
|
877
|
+
for (const table of [stateGlyphs, navGlyphs]) {
|
|
878
|
+
for (const [name, variants] of Object.entries(table)) registry.set(name, variants);
|
|
879
|
+
}
|
|
880
|
+
function deriveAscii(name) {
|
|
881
|
+
const s = String(name || "").replace(/[^a-z0-9]/gi, "").slice(0, 2).toLowerCase();
|
|
882
|
+
return "[" + (s || "?") + "]";
|
|
883
|
+
}
|
|
884
|
+
function normalizeEntry(name, e) {
|
|
885
|
+
const input = typeof e === "string" ? { nerd: e } : e;
|
|
886
|
+
let nerd = input.nerd;
|
|
887
|
+
if (nerd == null && input.cp != null) {
|
|
888
|
+
try {
|
|
889
|
+
nerd = String.fromCodePoint(parseInt(input.cp, 16));
|
|
890
|
+
} catch {
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (nerd == null && input.nf != null) nerd = nfChar(input.nf);
|
|
894
|
+
return {
|
|
895
|
+
nerd: nerd ?? "",
|
|
896
|
+
unicode: input.unicode ?? DEFAULT_UNICODE,
|
|
897
|
+
ascii: input.ascii ?? deriveAscii(name),
|
|
898
|
+
color: input.color ?? DEFAULT_GLYPH_COLOR
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function registerGlyphs(...maps) {
|
|
902
|
+
for (const map of maps) {
|
|
903
|
+
if (!map) continue;
|
|
904
|
+
for (const [name, entry] of Object.entries(map)) registry.set(name, normalizeEntry(name, entry));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function registerGlyph(name, entry) {
|
|
908
|
+
registerGlyphs({ [name]: entry });
|
|
909
|
+
}
|
|
910
|
+
function hasGlyph(name) {
|
|
911
|
+
return registry.has(name);
|
|
912
|
+
}
|
|
913
|
+
function registeredNames() {
|
|
914
|
+
return [...registry.keys()].sort();
|
|
915
|
+
}
|
|
916
|
+
function glyph(name, set) {
|
|
917
|
+
const v = registry.get(name);
|
|
918
|
+
if (!v) return name;
|
|
919
|
+
if (set === "ascii") return v.ascii || v.unicode || v.nerd || name;
|
|
920
|
+
if (set === "unicode") return v.unicode || v.ascii || v.nerd || name;
|
|
921
|
+
return v.nerd || v.unicode || v.ascii || name;
|
|
922
|
+
}
|
|
923
|
+
function glyphColor(name) {
|
|
924
|
+
return registry.get(name)?.color;
|
|
925
|
+
}
|
|
926
|
+
var stateIntents = {
|
|
927
|
+
installed: { glyph: "check", token: "stateOk" },
|
|
928
|
+
ok: { glyph: "check", token: "stateOk" },
|
|
929
|
+
done: { glyph: "check", token: "stateOk" },
|
|
930
|
+
missing: { glyph: "cross", token: "stateErr" },
|
|
931
|
+
error: { glyph: "cross", token: "stateErr" },
|
|
932
|
+
err: { glyph: "cross", token: "stateErr" },
|
|
933
|
+
failed: { glyph: "cross", token: "stateErr" },
|
|
934
|
+
drift: { glyph: "half", token: "stateDrift" },
|
|
935
|
+
partial: { glyph: "half", token: "stateDrift" },
|
|
936
|
+
warn: { glyph: "warn", token: "stateWarn" },
|
|
937
|
+
idempotent: { glyph: "rerun", token: "stateInfo" },
|
|
938
|
+
pending: { glyph: "circle", token: "statePending" },
|
|
939
|
+
info: { glyph: "depends", token: "stateInfo" }
|
|
940
|
+
};
|
|
941
|
+
function stateGlyph(name) {
|
|
942
|
+
return stateIntents[name] ?? null;
|
|
943
|
+
}
|
|
944
|
+
var selectionIntents = {
|
|
945
|
+
selected: { glyph: "checkboxOn", token: "accent" },
|
|
946
|
+
unselected: { glyph: "checkboxOff", token: "fgDim" },
|
|
947
|
+
locked: { glyph: "checkboxLock", token: "fgMuted" }
|
|
948
|
+
};
|
|
949
|
+
var boxStyles = {
|
|
950
|
+
single: { tl: "\u250C", tr: "\u2510", bl: "\u2514", br: "\u2518", h: "\u2500", v: "\u2502" },
|
|
951
|
+
rounded: { tl: "\u256D", tr: "\u256E", bl: "\u2570", br: "\u256F", h: "\u2500", v: "\u2502" },
|
|
952
|
+
ascii: { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" }
|
|
953
|
+
};
|
|
954
|
+
function boxChars(style, set) {
|
|
955
|
+
if (set === "ascii") return boxStyles.ascii;
|
|
956
|
+
return boxStyles[style];
|
|
957
|
+
}
|
|
958
|
+
var spinnerFrames = {
|
|
959
|
+
braille: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
960
|
+
ascii: ["|", "/", "-", "\\"]
|
|
961
|
+
};
|
|
962
|
+
function spinnerFor(set) {
|
|
963
|
+
return set === "ascii" ? spinnerFrames.ascii : spinnerFrames.braille;
|
|
964
|
+
}
|
|
965
|
+
var blocks = {
|
|
966
|
+
full: "\u2588",
|
|
967
|
+
dark: "\u2593",
|
|
968
|
+
medium: "\u2592",
|
|
969
|
+
light: "\u2591",
|
|
970
|
+
cursor: "\u258E"
|
|
971
|
+
};
|
|
972
|
+
var blocksH = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
|
|
973
|
+
|
|
974
|
+
// src/glyphs/useGlyph.ts
|
|
975
|
+
function useGlyph() {
|
|
976
|
+
const set = useIconSet();
|
|
977
|
+
return (name) => glyph(name, set);
|
|
978
|
+
}
|
|
979
|
+
async function detectIconSet(opts = {}) {
|
|
980
|
+
const env = opts.env ?? process.env;
|
|
981
|
+
const explicit = normalizeSet(env.BLINK_ICON_SET);
|
|
982
|
+
if (explicit) return explicit;
|
|
983
|
+
if (env.BLINK_NERD_FONT === "1") return "nerd";
|
|
984
|
+
if (env.BLINK_NERD_FONT === "0") return "unicode";
|
|
985
|
+
if (env.BLINK_ASCII === "1") return "ascii";
|
|
986
|
+
const cfg = opts.configPath ?? join(homedir(), ".config", "blink", "preferences.json");
|
|
987
|
+
const pref = readPreference(cfg);
|
|
988
|
+
if (pref) return pref;
|
|
989
|
+
if (opts.markerFiles?.some((p) => safeExists(p))) return "nerd";
|
|
990
|
+
if (env.WT_SESSION) return "nerd";
|
|
991
|
+
if (env.TERM_PROGRAM === "WezTerm") return "nerd";
|
|
992
|
+
if (env.TERM_PROGRAM === "ghostty") return "nerd";
|
|
993
|
+
if (/nerd|cask|powerline/i.test(env.ITERM_PROFILE ?? "")) return "nerd";
|
|
994
|
+
if (env.CI) return "ascii";
|
|
995
|
+
if (env.TERM === "dumb" || !env.TERM) return "ascii";
|
|
996
|
+
return "unicode";
|
|
997
|
+
}
|
|
998
|
+
function normalizeSet(value) {
|
|
999
|
+
if (value === "nerd" || value === "unicode" || value === "ascii") return value;
|
|
1000
|
+
return void 0;
|
|
1001
|
+
}
|
|
1002
|
+
function readPreference(path) {
|
|
1003
|
+
if (!safeExists(path)) return void 0;
|
|
1004
|
+
try {
|
|
1005
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1006
|
+
return normalizeSet(typeof parsed.iconSet === "string" ? parsed.iconSet : void 0);
|
|
1007
|
+
} catch {
|
|
1008
|
+
return void 0;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function safeExists(path) {
|
|
1012
|
+
try {
|
|
1013
|
+
return existsSync(path);
|
|
1014
|
+
} catch {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
function cellWidth(str) {
|
|
1019
|
+
return stringWidth(str);
|
|
1020
|
+
}
|
|
1021
|
+
function useStdoutDimensions() {
|
|
1022
|
+
const { stdout } = useStdout();
|
|
1023
|
+
const read = () => ({
|
|
1024
|
+
columns: stdout?.columns ?? 80,
|
|
1025
|
+
rows: stdout?.rows ?? 24
|
|
1026
|
+
});
|
|
1027
|
+
const [dimensions, setDimensions] = useState(read);
|
|
1028
|
+
useEffect(() => {
|
|
1029
|
+
if (!stdout) return;
|
|
1030
|
+
const onResize = () => setDimensions({ columns: stdout.columns, rows: stdout.rows });
|
|
1031
|
+
stdout.on("resize", onResize);
|
|
1032
|
+
onResize();
|
|
1033
|
+
return () => {
|
|
1034
|
+
stdout.off("resize", onResize);
|
|
1035
|
+
};
|
|
1036
|
+
}, [stdout]);
|
|
1037
|
+
return dimensions;
|
|
1038
|
+
}
|
|
1039
|
+
function useBlink2(active = true, hz = 1) {
|
|
1040
|
+
const [on, setOn] = useState(true);
|
|
1041
|
+
useEffect(() => {
|
|
1042
|
+
if (!active) {
|
|
1043
|
+
setOn(true);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const halfPeriod = Math.max(1, Math.round(1e3 / hz / 2));
|
|
1047
|
+
const timer = setInterval(() => setOn((v) => !v), halfPeriod);
|
|
1048
|
+
return () => clearInterval(timer);
|
|
1049
|
+
}, [active, hz]);
|
|
1050
|
+
return active ? on : true;
|
|
1051
|
+
}
|
|
1052
|
+
function useSpinnerFrame(opts = {}) {
|
|
1053
|
+
const { active = true, intervalMs = 80 } = opts;
|
|
1054
|
+
const [frame, setFrame] = useState(0);
|
|
1055
|
+
useEffect(() => {
|
|
1056
|
+
if (!active) {
|
|
1057
|
+
setFrame(0);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const timer = setInterval(() => setFrame((f) => f + 1), intervalMs);
|
|
1061
|
+
return () => clearInterval(timer);
|
|
1062
|
+
}, [active, intervalMs]);
|
|
1063
|
+
return frame;
|
|
1064
|
+
}
|
|
1065
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
1066
|
+
function computeWindow(prevStart, rowCount, focusedIndex, height, scrolloff = 0, overflowMarkers = true) {
|
|
1067
|
+
const n = rowCount;
|
|
1068
|
+
const H = height;
|
|
1069
|
+
if (H <= 0) return { start: 0, end: 0, aboveCount: 0, belowCount: 0 };
|
|
1070
|
+
if (n <= H) return { start: 0, end: n, aboveCount: 0, belowCount: 0 };
|
|
1071
|
+
const i = focusedIndex == null || focusedIndex < 0 ? -1 : Math.min(focusedIndex, n - 1);
|
|
1072
|
+
let start = clamp(prevStart, 0, n - 1);
|
|
1073
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
1074
|
+
const showAbove2 = overflowMarkers && start > 0;
|
|
1075
|
+
const cAbove2 = H - (showAbove2 ? 1 : 0);
|
|
1076
|
+
const showBelow2 = overflowMarkers && start + cAbove2 < n;
|
|
1077
|
+
const content2 = Math.max(1, cAbove2 - (showBelow2 ? 1 : 0));
|
|
1078
|
+
if (i >= 0) {
|
|
1079
|
+
const m = Math.min(scrolloff, Math.floor((content2 - 1) / 2));
|
|
1080
|
+
if (i < start + m) start = i - m;
|
|
1081
|
+
else if (i >= start + content2 - m) start = i - content2 + 1 + m;
|
|
1082
|
+
}
|
|
1083
|
+
start = clamp(start, 0, n - content2);
|
|
1084
|
+
}
|
|
1085
|
+
const showAbove = overflowMarkers && start > 0;
|
|
1086
|
+
const cAbove = H - (showAbove ? 1 : 0);
|
|
1087
|
+
const showBelow = overflowMarkers && start + cAbove < n;
|
|
1088
|
+
const content = Math.max(1, cAbove - (showBelow ? 1 : 0));
|
|
1089
|
+
start = clamp(start, 0, n - content);
|
|
1090
|
+
const end = start + content;
|
|
1091
|
+
return { start, end, aboveCount: start, belowCount: n - end };
|
|
1092
|
+
}
|
|
1093
|
+
function useListWindow(opts) {
|
|
1094
|
+
const { rowCount, focusedIndex, height, scrolloff = 0, overflowMarkers = true } = opts;
|
|
1095
|
+
const startRef = useRef(0);
|
|
1096
|
+
const win = computeWindow(startRef.current, rowCount, focusedIndex, height, scrolloff, overflowMarkers);
|
|
1097
|
+
startRef.current = win.start;
|
|
1098
|
+
return win;
|
|
1099
|
+
}
|
|
1100
|
+
function useListNavigation(opts) {
|
|
1101
|
+
const { ids, focusedId: controlled, onFocusChange, wrap = false } = opts;
|
|
1102
|
+
const isControlled = controlled !== void 0;
|
|
1103
|
+
const [internal, setInternal] = useState(() => ids[0] ?? null);
|
|
1104
|
+
const focusedId = isControlled ? controlled : internal;
|
|
1105
|
+
const focusedIndex = focusedId == null ? -1 : ids.indexOf(focusedId);
|
|
1106
|
+
const setFocus = useCallback(
|
|
1107
|
+
(id) => {
|
|
1108
|
+
if (id == null) return;
|
|
1109
|
+
if (!isControlled) setInternal(id);
|
|
1110
|
+
onFocusChange?.(id);
|
|
1111
|
+
},
|
|
1112
|
+
[isControlled, onFocusChange]
|
|
1113
|
+
);
|
|
1114
|
+
const move = useCallback(
|
|
1115
|
+
(delta) => {
|
|
1116
|
+
if (ids.length === 0) return;
|
|
1117
|
+
const cur = focusedIndex < 0 ? 0 : focusedIndex;
|
|
1118
|
+
let next = cur + delta;
|
|
1119
|
+
if (wrap) next = (next % ids.length + ids.length) % ids.length;
|
|
1120
|
+
else next = Math.max(0, Math.min(ids.length - 1, next));
|
|
1121
|
+
setFocus(ids[next] ?? null);
|
|
1122
|
+
},
|
|
1123
|
+
[ids, focusedIndex, wrap, setFocus]
|
|
1124
|
+
);
|
|
1125
|
+
return {
|
|
1126
|
+
focusedId,
|
|
1127
|
+
focusedIndex,
|
|
1128
|
+
focusNext: () => move(1),
|
|
1129
|
+
focusPrev: () => move(-1),
|
|
1130
|
+
focusFirst: () => setFocus(ids[0] ?? null),
|
|
1131
|
+
focusLast: () => setFocus(ids[ids.length - 1] ?? null),
|
|
1132
|
+
focusTo: (id) => setFocus(id)
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function useListSelection(opts) {
|
|
1136
|
+
const { mode, min, max, initial, onChange } = opts;
|
|
1137
|
+
const [selected, setSelected] = useState(() => new Set(initial ?? []));
|
|
1138
|
+
const [blocked, setBlocked] = useState(false);
|
|
1139
|
+
const commit = useCallback(
|
|
1140
|
+
(next) => {
|
|
1141
|
+
setBlocked(false);
|
|
1142
|
+
setSelected(next);
|
|
1143
|
+
onChange?.(next);
|
|
1144
|
+
},
|
|
1145
|
+
[onChange]
|
|
1146
|
+
);
|
|
1147
|
+
const block = useCallback(() => {
|
|
1148
|
+
setBlocked(true);
|
|
1149
|
+
return false;
|
|
1150
|
+
}, []);
|
|
1151
|
+
const toggle = useCallback(
|
|
1152
|
+
(id) => {
|
|
1153
|
+
const has = selected.has(id);
|
|
1154
|
+
if (mode === "single") {
|
|
1155
|
+
if (has) {
|
|
1156
|
+
if (min !== void 0 && min > 0) return block();
|
|
1157
|
+
commit(/* @__PURE__ */ new Set());
|
|
1158
|
+
return true;
|
|
1159
|
+
}
|
|
1160
|
+
commit(/* @__PURE__ */ new Set([id]));
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
if (has) {
|
|
1164
|
+
if (min !== void 0 && selected.size <= min) return block();
|
|
1165
|
+
const next2 = new Set(selected);
|
|
1166
|
+
next2.delete(id);
|
|
1167
|
+
commit(next2);
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
if (max !== void 0 && selected.size >= max) return block();
|
|
1171
|
+
const next = new Set(selected);
|
|
1172
|
+
next.add(id);
|
|
1173
|
+
commit(next);
|
|
1174
|
+
return true;
|
|
1175
|
+
},
|
|
1176
|
+
[selected, mode, min, max, commit, block]
|
|
1177
|
+
);
|
|
1178
|
+
const selectOnly = useCallback((id) => commit(/* @__PURE__ */ new Set([id])), [commit]);
|
|
1179
|
+
const clear = useCallback(() => {
|
|
1180
|
+
if (min !== void 0 && min > 0) {
|
|
1181
|
+
block();
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
commit(/* @__PURE__ */ new Set());
|
|
1185
|
+
}, [min, commit, block]);
|
|
1186
|
+
const isSelected = useCallback((id) => selected.has(id), [selected]);
|
|
1187
|
+
return { selectedIds: selected, isSelected, toggle, selectOnly, clear, blocked };
|
|
1188
|
+
}
|
|
1189
|
+
function inkBorderStyle(style, ascii) {
|
|
1190
|
+
if (ascii) return "classic";
|
|
1191
|
+
return style === "rounded" ? "round" : "single";
|
|
1192
|
+
}
|
|
1193
|
+
function Pane({
|
|
1194
|
+
title,
|
|
1195
|
+
tone,
|
|
1196
|
+
focused = false,
|
|
1197
|
+
variant,
|
|
1198
|
+
flexGrow = 1,
|
|
1199
|
+
flexBasis,
|
|
1200
|
+
width,
|
|
1201
|
+
height,
|
|
1202
|
+
minHeight,
|
|
1203
|
+
children
|
|
1204
|
+
}) {
|
|
1205
|
+
const tokens = useTokens();
|
|
1206
|
+
const iconSet = useIconSet();
|
|
1207
|
+
const ascii = iconSet === "ascii";
|
|
1208
|
+
const t = tone ?? (variant === "error" ? "error" : focused ? "focus" : "resting");
|
|
1209
|
+
const style = variant === "square" ? "single" : "rounded";
|
|
1210
|
+
const chars = boxChars(style, iconSet);
|
|
1211
|
+
const borderColor = t === "error" ? tokens.stateErr : t === "focus" ? tokens.borderFocus : tokens.border;
|
|
1212
|
+
const titleColor = t === "error" ? tokens.stateErr : t === "focus" ? tokens.accent : tokens.fgMuted;
|
|
1213
|
+
return /* @__PURE__ */ jsxs(
|
|
1214
|
+
Box,
|
|
1215
|
+
{
|
|
1216
|
+
flexDirection: "column",
|
|
1217
|
+
flexGrow,
|
|
1218
|
+
flexBasis,
|
|
1219
|
+
width,
|
|
1220
|
+
height,
|
|
1221
|
+
minHeight,
|
|
1222
|
+
minWidth: 0,
|
|
1223
|
+
children: [
|
|
1224
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", height: 1, children: [
|
|
1225
|
+
/* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: borderColor, children: chars.tl + chars.h }) }),
|
|
1226
|
+
title ? /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: titleColor, wrap: "truncate", children: " " + title + " " }) }) : null,
|
|
1227
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1, height: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(Text, { color: borderColor, children: chars.h.repeat(200) }) }),
|
|
1228
|
+
/* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: borderColor, children: chars.tr }) })
|
|
1229
|
+
] }),
|
|
1230
|
+
/* @__PURE__ */ jsx(
|
|
1231
|
+
Box,
|
|
1232
|
+
{
|
|
1233
|
+
flexGrow: 1,
|
|
1234
|
+
minHeight: 0,
|
|
1235
|
+
flexDirection: "column",
|
|
1236
|
+
borderStyle: inkBorderStyle(style, ascii),
|
|
1237
|
+
borderColor,
|
|
1238
|
+
borderTop: false,
|
|
1239
|
+
paddingX: 1,
|
|
1240
|
+
children
|
|
1241
|
+
}
|
|
1242
|
+
)
|
|
1243
|
+
]
|
|
1244
|
+
}
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
function hasSelectionIntent(row) {
|
|
1248
|
+
return row.locked === true || "selected" in row;
|
|
1249
|
+
}
|
|
1250
|
+
function rowSelection(row) {
|
|
1251
|
+
if (row.locked) return selectionIntents.locked;
|
|
1252
|
+
if (!("selected" in row)) return null;
|
|
1253
|
+
return row.selected ? selectionIntents.selected : selectionIntents.unselected;
|
|
1254
|
+
}
|
|
1255
|
+
function OverflowMarker({ glyph: glyph2, count }) {
|
|
1256
|
+
const tokens = useTokens();
|
|
1257
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1258
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1259
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgFaint, children: `${glyph2} ${count} more` })
|
|
1260
|
+
] });
|
|
1261
|
+
}
|
|
1262
|
+
function ListRow({
|
|
1263
|
+
row,
|
|
1264
|
+
focused = false,
|
|
1265
|
+
selected = false,
|
|
1266
|
+
showCheckbox,
|
|
1267
|
+
showState,
|
|
1268
|
+
showDomain,
|
|
1269
|
+
caretWidth,
|
|
1270
|
+
checkboxWidth,
|
|
1271
|
+
stateWidth,
|
|
1272
|
+
domainWidth
|
|
1273
|
+
}) {
|
|
1274
|
+
const tokens = useTokens();
|
|
1275
|
+
const g = useGlyph();
|
|
1276
|
+
const backgroundColor = focused ? tokens.bgFocused : selected ? tokens.bgSelected : void 0;
|
|
1277
|
+
const sel = rowSelection(row);
|
|
1278
|
+
const checkStr = sel ? g(sel.glyph) : "";
|
|
1279
|
+
const checkColor = row.muted ? tokens.fgDim : sel ? tokens[sel.token] : tokens.fgDim;
|
|
1280
|
+
const st = row.state ? stateGlyph(row.state) : null;
|
|
1281
|
+
const stateStr = st ? g(st.glyph) : "";
|
|
1282
|
+
const stateColor = row.muted ? tokens.fgDim : st ? tokens[st.token] : tokens.fg;
|
|
1283
|
+
const domainStr = row.domain ? g(row.domain) : "";
|
|
1284
|
+
const domainToken = row.domain ? glyphColor(row.domain) : void 0;
|
|
1285
|
+
const domainColor = row.muted ? tokens.fgDim : domainToken ? tokens[domainToken] : tokens.fgMuted;
|
|
1286
|
+
const labelColor = row.muted ? tokens.fgDim : tokens.fg;
|
|
1287
|
+
const wantCheckbox = showCheckbox ?? hasSelectionIntent(row);
|
|
1288
|
+
const wantState = showState ?? row.state != null;
|
|
1289
|
+
const wantDomain = showDomain ?? row.domain != null;
|
|
1290
|
+
const caretW = caretWidth ?? Math.max(1, cellWidth(g("focus")));
|
|
1291
|
+
const checkW = checkboxWidth ?? Math.max(1, cellWidth(checkStr));
|
|
1292
|
+
const stateW = stateWidth ?? Math.max(1, cellWidth(stateStr));
|
|
1293
|
+
const domainW = domainWidth ?? Math.max(1, cellWidth(domainStr));
|
|
1294
|
+
const cell2 = (s, w) => s + " ".repeat(Math.max(0, w - cellWidth(s)));
|
|
1295
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1296
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor, children: " " }),
|
|
1297
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.accent, backgroundColor, wrap: "truncate", children: cell2(focused ? g("focus") : "", caretW) }),
|
|
1298
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor, children: " " }),
|
|
1299
|
+
wantCheckbox ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1300
|
+
/* @__PURE__ */ jsx(Text, { color: checkColor, backgroundColor, wrap: "truncate", children: cell2(checkStr, checkW) }),
|
|
1301
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor, children: " " })
|
|
1302
|
+
] }) : null,
|
|
1303
|
+
wantState ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1304
|
+
/* @__PURE__ */ jsx(Text, { color: stateColor, backgroundColor, wrap: "truncate", children: cell2(stateStr, stateW) }),
|
|
1305
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor, children: " " })
|
|
1306
|
+
] }) : null,
|
|
1307
|
+
wantDomain ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1308
|
+
/* @__PURE__ */ jsx(Text, { color: domainColor, backgroundColor, wrap: "truncate", children: cell2(domainStr, domainW) }),
|
|
1309
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor, children: " " })
|
|
1310
|
+
] }) : null,
|
|
1311
|
+
/* @__PURE__ */ jsx(Text, { color: labelColor, backgroundColor, wrap: "truncate", children: row.label }),
|
|
1312
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1, flexBasis: 0, minWidth: 0, height: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(Text, { backgroundColor, children: " ".repeat(200) }) }),
|
|
1313
|
+
row.meta ? /* @__PURE__ */ jsx(Text, { color: tokens.fgDim, backgroundColor, wrap: "truncate", children: row.meta }) : null,
|
|
1314
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor, children: " " })
|
|
1315
|
+
] });
|
|
1316
|
+
}
|
|
1317
|
+
function List({
|
|
1318
|
+
rows,
|
|
1319
|
+
focusedId,
|
|
1320
|
+
selectedIds,
|
|
1321
|
+
height,
|
|
1322
|
+
scrolloff = 0,
|
|
1323
|
+
overflowMarkers = true
|
|
1324
|
+
}) {
|
|
1325
|
+
const g = useGlyph();
|
|
1326
|
+
const sel = selectedIds ?? /* @__PURE__ */ new Set();
|
|
1327
|
+
const showCheckbox = rows.some(hasSelectionIntent);
|
|
1328
|
+
const showState = rows.some((r) => r.state != null);
|
|
1329
|
+
const showDomain = rows.some((r) => r.domain != null);
|
|
1330
|
+
const focusedIndex = focusedId == null ? -1 : rows.findIndex((r) => r.id === focusedId);
|
|
1331
|
+
const { start, end, aboveCount, belowCount } = useListWindow({
|
|
1332
|
+
rowCount: rows.length,
|
|
1333
|
+
focusedIndex,
|
|
1334
|
+
height: height ?? rows.length,
|
|
1335
|
+
scrolloff,
|
|
1336
|
+
overflowMarkers
|
|
1337
|
+
});
|
|
1338
|
+
const visible = rows.slice(start, end);
|
|
1339
|
+
const caretWidth = Math.max(1, cellWidth(g("focus")));
|
|
1340
|
+
const checkboxWidth = showCheckbox ? rows.reduce((m, r) => {
|
|
1341
|
+
const s = rowSelection(r);
|
|
1342
|
+
return Math.max(m, s ? cellWidth(g(s.glyph)) : 1);
|
|
1343
|
+
}, 1) : 0;
|
|
1344
|
+
const stateWidth = showState ? rows.reduce((m, r) => {
|
|
1345
|
+
const st = r.state ? stateGlyph(r.state) : null;
|
|
1346
|
+
return Math.max(m, st ? cellWidth(g(st.glyph)) : 1);
|
|
1347
|
+
}, 1) : 0;
|
|
1348
|
+
const domainWidth = showDomain ? rows.reduce((m, r) => Math.max(m, r.domain ? cellWidth(g(r.domain)) : 1), 1) : 0;
|
|
1349
|
+
const showAbove = overflowMarkers && aboveCount > 0;
|
|
1350
|
+
const showBelow = overflowMarkers && belowCount > 0;
|
|
1351
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1352
|
+
showAbove ? /* @__PURE__ */ jsx(OverflowMarker, { glyph: g("moreAbove"), count: aboveCount }) : null,
|
|
1353
|
+
visible.map((row) => /* @__PURE__ */ jsx(
|
|
1354
|
+
ListRow,
|
|
1355
|
+
{
|
|
1356
|
+
row,
|
|
1357
|
+
focused: row.id === focusedId,
|
|
1358
|
+
selected: sel.has(row.id),
|
|
1359
|
+
showCheckbox,
|
|
1360
|
+
showState,
|
|
1361
|
+
showDomain,
|
|
1362
|
+
caretWidth,
|
|
1363
|
+
checkboxWidth,
|
|
1364
|
+
stateWidth,
|
|
1365
|
+
domainWidth
|
|
1366
|
+
},
|
|
1367
|
+
row.id
|
|
1368
|
+
)),
|
|
1369
|
+
showBelow ? /* @__PURE__ */ jsx(OverflowMarker, { glyph: g("moreBelow"), count: belowCount }) : null
|
|
1370
|
+
] });
|
|
1371
|
+
}
|
|
1372
|
+
function Header({
|
|
1373
|
+
mark = blocks.cursor,
|
|
1374
|
+
title,
|
|
1375
|
+
subtitle,
|
|
1376
|
+
right
|
|
1377
|
+
}) {
|
|
1378
|
+
const tokens = useTokens();
|
|
1379
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, children: [
|
|
1380
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexShrink: 1, overflow: "hidden", children: [
|
|
1381
|
+
mark != null && mark !== "" ? /* @__PURE__ */ jsx(Text, { color: tokens.accent, children: mark }) : null,
|
|
1382
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fg, wrap: "truncate", children: (mark != null && mark !== "" ? " " : "") + title }),
|
|
1383
|
+
subtitle ? /* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, wrap: "truncate", children: " \xB7 " + subtitle }) : null
|
|
1384
|
+
] }),
|
|
1385
|
+
right != null ? /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: typeof right === "string" ? /* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, children: right }) : right }) : null
|
|
1386
|
+
] });
|
|
1387
|
+
}
|
|
1388
|
+
function DescriptionList({
|
|
1389
|
+
items = [],
|
|
1390
|
+
gutter = 10
|
|
1391
|
+
}) {
|
|
1392
|
+
const tokens = useTokens();
|
|
1393
|
+
const g = useGlyph();
|
|
1394
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((it, i) => {
|
|
1395
|
+
const st = it.state ? stateGlyph(it.state) : null;
|
|
1396
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1397
|
+
it.term != null ? /* @__PURE__ */ jsx(Box, { width: gutter, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: tokens.fgDim, wrap: "truncate", children: it.term }) }) : null,
|
|
1398
|
+
st ? /* @__PURE__ */ jsx(Box, { width: 1, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: tokens[st.token], children: g(st.glyph) }) }) : null,
|
|
1399
|
+
/* @__PURE__ */ jsx(Text, { color: it.muted ? tokens.fgMuted : tokens.fg, wrap: "truncate", children: it.value })
|
|
1400
|
+
] }, i);
|
|
1401
|
+
}) });
|
|
1402
|
+
}
|
|
1403
|
+
var clamp2 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
1404
|
+
function wrapLine(line, w) {
|
|
1405
|
+
if (line.length === 0) return [""];
|
|
1406
|
+
const out = [];
|
|
1407
|
+
let cur = "";
|
|
1408
|
+
let curW = 0;
|
|
1409
|
+
for (const ch of Array.from(line)) {
|
|
1410
|
+
const cw = cellWidth(ch);
|
|
1411
|
+
if (curW + cw > w && cur.length > 0) {
|
|
1412
|
+
out.push(cur);
|
|
1413
|
+
cur = "";
|
|
1414
|
+
curW = 0;
|
|
1415
|
+
}
|
|
1416
|
+
cur += ch;
|
|
1417
|
+
curW += cw;
|
|
1418
|
+
}
|
|
1419
|
+
out.push(cur);
|
|
1420
|
+
return out;
|
|
1421
|
+
}
|
|
1422
|
+
function truncateLine(line, w) {
|
|
1423
|
+
let cur = "";
|
|
1424
|
+
let curW = 0;
|
|
1425
|
+
for (const ch of Array.from(line)) {
|
|
1426
|
+
const cw = cellWidth(ch);
|
|
1427
|
+
if (curW + cw > w) break;
|
|
1428
|
+
cur += ch;
|
|
1429
|
+
curW += cw;
|
|
1430
|
+
}
|
|
1431
|
+
return cur;
|
|
1432
|
+
}
|
|
1433
|
+
function LogView({
|
|
1434
|
+
lines,
|
|
1435
|
+
height,
|
|
1436
|
+
follow = true,
|
|
1437
|
+
wrap = true,
|
|
1438
|
+
width
|
|
1439
|
+
}) {
|
|
1440
|
+
const tokens = useTokens();
|
|
1441
|
+
const g = useGlyph();
|
|
1442
|
+
const ref = useRef(null);
|
|
1443
|
+
const [measured, setMeasured] = useState(0);
|
|
1444
|
+
useEffect(() => {
|
|
1445
|
+
if (width != null || !ref.current) return;
|
|
1446
|
+
const m = measureElement(ref.current);
|
|
1447
|
+
if (m.width && m.width !== measured) setMeasured(m.width);
|
|
1448
|
+
});
|
|
1449
|
+
const w = Math.max(1, width ?? measured ?? 0) || 80;
|
|
1450
|
+
const visual = [];
|
|
1451
|
+
for (const line of lines) {
|
|
1452
|
+
if (wrap) for (const vr of wrapLine(line, w)) visual.push(vr);
|
|
1453
|
+
else visual.push(truncateLine(line, w));
|
|
1454
|
+
}
|
|
1455
|
+
const total = visual.length;
|
|
1456
|
+
const bottomOffsetRef = useRef(0);
|
|
1457
|
+
const prevTotalRef = useRef(total);
|
|
1458
|
+
if (follow) {
|
|
1459
|
+
bottomOffsetRef.current = 0;
|
|
1460
|
+
} else {
|
|
1461
|
+
const delta = total - prevTotalRef.current;
|
|
1462
|
+
bottomOffsetRef.current = clamp2(bottomOffsetRef.current + Math.max(0, delta), 0, Math.max(0, total - 1));
|
|
1463
|
+
}
|
|
1464
|
+
prevTotalRef.current = total;
|
|
1465
|
+
const bottomOffset = bottomOffsetRef.current;
|
|
1466
|
+
const end = total - bottomOffset;
|
|
1467
|
+
let start = Math.max(0, end - height);
|
|
1468
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
1469
|
+
const showAbove2 = start > 0;
|
|
1470
|
+
const contentH = height - (showAbove2 ? 1 : 0);
|
|
1471
|
+
start = Math.max(0, end - contentH);
|
|
1472
|
+
}
|
|
1473
|
+
const aboveCount = start;
|
|
1474
|
+
const showAbove = aboveCount > 0;
|
|
1475
|
+
const window = visual.slice(start, end);
|
|
1476
|
+
return /* @__PURE__ */ jsxs(Box, { ref, flexDirection: "column", children: [
|
|
1477
|
+
showAbove ? /* @__PURE__ */ jsx(Text, { color: tokens.fgFaint, children: `${g("moreAbove")} ${aboveCount} more` }) : null,
|
|
1478
|
+
window.map((row, idx) => /* @__PURE__ */ jsx(Text, { color: tokens.fg, wrap: "truncate", children: row }, start + idx))
|
|
1479
|
+
] });
|
|
1480
|
+
}
|
|
1481
|
+
function Hotkey({ k, desc }) {
|
|
1482
|
+
const tokens = useTokens();
|
|
1483
|
+
return /* @__PURE__ */ jsxs(Box, { flexShrink: 0, children: [
|
|
1484
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgInverse, backgroundColor: tokens.bgInverse, wrap: "truncate", children: " " + k + " " }),
|
|
1485
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, backgroundColor: tokens.bgSunken, wrap: "truncate", children: " " + desc })
|
|
1486
|
+
] });
|
|
1487
|
+
}
|
|
1488
|
+
function chipWidth(h) {
|
|
1489
|
+
return cellWidth(h.k) + 2 + cellWidth(h.desc) + 1;
|
|
1490
|
+
}
|
|
1491
|
+
function Footer({ keys = [], right, marginTop = 1 }) {
|
|
1492
|
+
const tokens = useTokens();
|
|
1493
|
+
const { columns } = useStdoutDimensions();
|
|
1494
|
+
const rightStr = typeof right === "string" ? right : null;
|
|
1495
|
+
const rightWidth = rightStr != null ? cellWidth(rightStr) : 0;
|
|
1496
|
+
const budget = Math.max(0, columns - 2 - (rightWidth > 0 ? rightWidth + 1 : 0));
|
|
1497
|
+
const shown = [];
|
|
1498
|
+
let used = 0;
|
|
1499
|
+
for (const h of keys) {
|
|
1500
|
+
const w = (shown.length > 0 ? 3 : 0) + chipWidth(h);
|
|
1501
|
+
if (used + w > budget) break;
|
|
1502
|
+
used += w;
|
|
1503
|
+
shown.push(h);
|
|
1504
|
+
}
|
|
1505
|
+
if (right != null && rightStr == null) {
|
|
1506
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", paddingX: 1, justifyContent: "space-between", marginTop, flexShrink: 0, children: [
|
|
1507
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 3, flexShrink: 1, overflow: "hidden", children: shown.map((h, i) => /* @__PURE__ */ jsx(Hotkey, { k: h.k, desc: h.desc }, i)) }),
|
|
1508
|
+
/* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: tokens.fgFaint, backgroundColor: tokens.bgSunken, wrap: "truncate", children: right }) })
|
|
1509
|
+
] });
|
|
1510
|
+
}
|
|
1511
|
+
const fill = " ".repeat(Math.max(0, columns - 1 - used - rightWidth - 1));
|
|
1512
|
+
return /* @__PURE__ */ jsx(Box, { marginTop, flexShrink: 0, children: /* @__PURE__ */ jsxs(Text, { backgroundColor: tokens.bgSunken, wrap: "truncate", children: [
|
|
1513
|
+
" ",
|
|
1514
|
+
shown.map((h, i) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
1515
|
+
i > 0 ? " " : "",
|
|
1516
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgInverse, backgroundColor: tokens.bgInverse, children: " " + h.k + " " }),
|
|
1517
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, children: " " + h.desc })
|
|
1518
|
+
] }, i)),
|
|
1519
|
+
fill,
|
|
1520
|
+
rightStr != null ? /* @__PURE__ */ jsx(Text, { color: tokens.fgFaint, children: rightStr }) : null,
|
|
1521
|
+
" "
|
|
1522
|
+
] }) });
|
|
1523
|
+
}
|
|
1524
|
+
function Cursor({ active = true, color }) {
|
|
1525
|
+
const tokens = useTokens();
|
|
1526
|
+
const on = useBlink2(active);
|
|
1527
|
+
return /* @__PURE__ */ jsx(Text, { color: color ?? tokens.fg, children: on ? blocks.cursor : " " });
|
|
1528
|
+
}
|
|
1529
|
+
function Input({
|
|
1530
|
+
title,
|
|
1531
|
+
value = "",
|
|
1532
|
+
placeholder = "",
|
|
1533
|
+
focused = false,
|
|
1534
|
+
error
|
|
1535
|
+
}) {
|
|
1536
|
+
const tokens = useTokens();
|
|
1537
|
+
const g = useGlyph();
|
|
1538
|
+
const tone = error ? "error" : focused ? "focus" : "resting";
|
|
1539
|
+
const empty = value.length === 0;
|
|
1540
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1541
|
+
/* @__PURE__ */ jsx(Pane, { title, tone, flexGrow: 0, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1542
|
+
empty ? /* @__PURE__ */ jsx(Text, { color: tokens.fgDisabled, children: placeholder }) : /* @__PURE__ */ jsx(Text, { color: tokens.fg, children: value }),
|
|
1543
|
+
focused ? /* @__PURE__ */ jsx(Cursor, { active: true }) : null
|
|
1544
|
+
] }) }),
|
|
1545
|
+
error ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", paddingLeft: 1, children: [
|
|
1546
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.stateErr, children: g("cross") }),
|
|
1547
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, children: " " + error })
|
|
1548
|
+
] }) : null
|
|
1549
|
+
] });
|
|
1550
|
+
}
|
|
1551
|
+
function Dialog({
|
|
1552
|
+
title,
|
|
1553
|
+
tone = "default",
|
|
1554
|
+
lines = [],
|
|
1555
|
+
children,
|
|
1556
|
+
actions = [],
|
|
1557
|
+
width = 44
|
|
1558
|
+
}) {
|
|
1559
|
+
const tokens = useTokens();
|
|
1560
|
+
return /* @__PURE__ */ jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx(Box, { width, flexShrink: 0, children: /* @__PURE__ */ jsx(Pane, { title, tone: tone === "error" ? "error" : "focus", flexGrow: 0, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1561
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1562
|
+
children ?? lines.map((line, i) => /* @__PURE__ */ jsx(Text, { color: tokens.fg, children: line }, i)),
|
|
1563
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1564
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
|
|
1565
|
+
actions.map((action, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1566
|
+
action.primary ? /* @__PURE__ */ jsx(Text, { color: tokens.fgInverse, backgroundColor: tokens.accent, children: " " + action.key + " " }) : /* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, children: " " + action.key + " " }),
|
|
1567
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, children: action.label })
|
|
1568
|
+
] }, i)),
|
|
1569
|
+
/* @__PURE__ */ jsx(Spacer, {})
|
|
1570
|
+
] }),
|
|
1571
|
+
/* @__PURE__ */ jsx(Text, { children: " " })
|
|
1572
|
+
] }) }) }) });
|
|
1573
|
+
}
|
|
1574
|
+
var TONES = {
|
|
1575
|
+
info: { glyph: "depends", token: "stateInfo" },
|
|
1576
|
+
success: { glyph: "check", token: "stateOk" },
|
|
1577
|
+
warn: { glyph: "warn", token: "stateWarn" }
|
|
1578
|
+
};
|
|
1579
|
+
function Banner({ children, text, tone = "info" }) {
|
|
1580
|
+
const tokens = useTokens();
|
|
1581
|
+
const g = useGlyph();
|
|
1582
|
+
const t = TONES[tone] ?? TONES.info;
|
|
1583
|
+
const body = children ?? text ?? "";
|
|
1584
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1585
|
+
/* @__PURE__ */ jsx(Text, { color: tokens[t.token], children: g(t.glyph) + " " }),
|
|
1586
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, children: body })
|
|
1587
|
+
] });
|
|
1588
|
+
}
|
|
1589
|
+
function Spinner({ active = true, color, intervalMs = 80 }) {
|
|
1590
|
+
const tokens = useTokens();
|
|
1591
|
+
const iconSet = useIconSet();
|
|
1592
|
+
const frames = spinnerFor(iconSet);
|
|
1593
|
+
const frame = useSpinnerFrame({ active, intervalMs });
|
|
1594
|
+
return /* @__PURE__ */ jsx(Text, { color: color ?? tokens.stateInfo, children: frames[frame % frames.length] });
|
|
1595
|
+
}
|
|
1596
|
+
function ProgressBar({ value, width, color, trackColor, showPercent = true }) {
|
|
1597
|
+
const tokens = useTokens();
|
|
1598
|
+
const iconSet = useIconSet();
|
|
1599
|
+
const v = Math.max(0, Math.min(1, value));
|
|
1600
|
+
const fillColor = color ?? tokens.accent;
|
|
1601
|
+
const track = trackColor ?? tokens.border;
|
|
1602
|
+
const pct = showPercent ? /* @__PURE__ */ jsx(Text, { color: tokens.fgDim, children: ` ${Math.round(v * 100)}%` }) : null;
|
|
1603
|
+
if (iconSet === "ascii") {
|
|
1604
|
+
const full = Math.round(v * width);
|
|
1605
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1606
|
+
/* @__PURE__ */ jsx(Text, { color: fillColor, children: "#".repeat(full) }),
|
|
1607
|
+
/* @__PURE__ */ jsx(Text, { color: track, children: " ".repeat(Math.max(0, width - full)) }),
|
|
1608
|
+
pct
|
|
1609
|
+
] });
|
|
1610
|
+
}
|
|
1611
|
+
const totalEighths = Math.round(v * width * 8);
|
|
1612
|
+
const fullCells = Math.floor(totalEighths / 8);
|
|
1613
|
+
const rem = totalEighths % 8;
|
|
1614
|
+
const filled = blocksH[8].repeat(fullCells) + (rem > 0 ? blocksH[rem] ?? "" : "");
|
|
1615
|
+
const usedCells = fullCells + (rem > 0 ? 1 : 0);
|
|
1616
|
+
const empty = blocks.light.repeat(Math.max(0, width - usedCells));
|
|
1617
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1618
|
+
/* @__PURE__ */ jsx(Text, { color: fillColor, children: filled }),
|
|
1619
|
+
/* @__PURE__ */ jsx(Text, { color: track, children: empty }),
|
|
1620
|
+
pct
|
|
1621
|
+
] });
|
|
1622
|
+
}
|
|
1623
|
+
var PROGRESS_STATES = {
|
|
1624
|
+
pending: { glyph: "circle", token: "statePending" },
|
|
1625
|
+
ok: { glyph: "check", token: "stateOk" },
|
|
1626
|
+
done: { glyph: "check", token: "stateOk" },
|
|
1627
|
+
failed: { glyph: "cross", token: "stateErr" },
|
|
1628
|
+
error: { glyph: "cross", token: "stateErr" },
|
|
1629
|
+
waiting: { glyph: "half", token: "stateWarn" }
|
|
1630
|
+
};
|
|
1631
|
+
var SKIPPED = { nerd: "\u25CC", unicode: "\u25CC", ascii: "-" };
|
|
1632
|
+
function literalFor(set, v) {
|
|
1633
|
+
return set === "ascii" ? v.ascii : set === "unicode" ? v.unicode : v.nerd;
|
|
1634
|
+
}
|
|
1635
|
+
function labelToken(state) {
|
|
1636
|
+
if (state === "skipped") return "fgDisabled";
|
|
1637
|
+
if (state === "pending") return "fgMuted";
|
|
1638
|
+
return "fg";
|
|
1639
|
+
}
|
|
1640
|
+
function cell(s, w) {
|
|
1641
|
+
return s + " ".repeat(Math.max(0, w - cellWidth(s)));
|
|
1642
|
+
}
|
|
1643
|
+
function ProgressRow({
|
|
1644
|
+
item,
|
|
1645
|
+
active,
|
|
1646
|
+
metrics,
|
|
1647
|
+
animate
|
|
1648
|
+
}) {
|
|
1649
|
+
const tokens = useTokens();
|
|
1650
|
+
const iconSet = useIconSet();
|
|
1651
|
+
const g = useGlyph();
|
|
1652
|
+
const bg = active ? tokens.bgFocused : void 0;
|
|
1653
|
+
let statusCell;
|
|
1654
|
+
if (item.state === "running") {
|
|
1655
|
+
statusCell = /* @__PURE__ */ jsxs(Text, { backgroundColor: bg, children: [
|
|
1656
|
+
/* @__PURE__ */ jsx(Spinner, { active: animate }),
|
|
1657
|
+
" ".repeat(Math.max(0, metrics.glyphW - 1))
|
|
1658
|
+
] });
|
|
1659
|
+
} else if (item.state === "skipped") {
|
|
1660
|
+
statusCell = /* @__PURE__ */ jsx(Text, { color: tokens.fgDisabled, backgroundColor: bg, wrap: "truncate", children: cell(literalFor(iconSet, SKIPPED), metrics.glyphW) });
|
|
1661
|
+
} else {
|
|
1662
|
+
const st = PROGRESS_STATES[item.state] ?? null;
|
|
1663
|
+
statusCell = /* @__PURE__ */ jsx(Text, { color: st ? tokens[st.token] : tokens.fg, backgroundColor: bg, wrap: "truncate", children: cell(st ? g(st.glyph) : "", metrics.glyphW) });
|
|
1664
|
+
}
|
|
1665
|
+
const domainToken = item.domain ? glyphColor(item.domain) : void 0;
|
|
1666
|
+
const domainColor = domainToken ? tokens[domainToken] : tokens.fgMuted;
|
|
1667
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1668
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " " }),
|
|
1669
|
+
statusCell,
|
|
1670
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " " }),
|
|
1671
|
+
metrics.showDomain ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1672
|
+
/* @__PURE__ */ jsx(Text, { color: item.domain ? domainColor : tokens.fg, backgroundColor: bg, wrap: "truncate", children: cell(item.domain ? g(item.domain) : "", metrics.domainW) }),
|
|
1673
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " " })
|
|
1674
|
+
] }) : null,
|
|
1675
|
+
/* @__PURE__ */ jsx(Text, { color: tokens[labelToken(item.state)], backgroundColor: bg, wrap: "truncate", children: item.label }),
|
|
1676
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1, flexBasis: 0, minWidth: 0, height: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " ".repeat(200) }) }),
|
|
1677
|
+
item.meta ? /* @__PURE__ */ jsx(Text, { color: tokens.fgDim, backgroundColor: bg, wrap: "truncate", children: item.meta }) : null,
|
|
1678
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " " })
|
|
1679
|
+
] });
|
|
1680
|
+
}
|
|
1681
|
+
function OverflowMarker2({ glyph: glyph2, count }) {
|
|
1682
|
+
const tokens = useTokens();
|
|
1683
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1684
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1685
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgFaint, children: `${glyph2} ${count} more` })
|
|
1686
|
+
] });
|
|
1687
|
+
}
|
|
1688
|
+
function ProgressList({
|
|
1689
|
+
items,
|
|
1690
|
+
activeId,
|
|
1691
|
+
height,
|
|
1692
|
+
overflowMarkers = true,
|
|
1693
|
+
animate = true
|
|
1694
|
+
}) {
|
|
1695
|
+
const iconSet = useIconSet();
|
|
1696
|
+
const g = useGlyph();
|
|
1697
|
+
const showDomain = items.some((it) => it.domain != null);
|
|
1698
|
+
const glyphW = items.reduce((m, it) => {
|
|
1699
|
+
if (it.state === "running") return Math.max(m, 1);
|
|
1700
|
+
if (it.state === "skipped") return Math.max(m, cellWidth(literalFor(iconSet, SKIPPED)));
|
|
1701
|
+
const st = PROGRESS_STATES[it.state] ?? null;
|
|
1702
|
+
return Math.max(m, st ? cellWidth(g(st.glyph)) : 1);
|
|
1703
|
+
}, 1);
|
|
1704
|
+
const domainW = showDomain ? items.reduce((m, it) => Math.max(m, it.domain ? cellWidth(g(it.domain)) : 1), 1) : 0;
|
|
1705
|
+
const metrics = { glyphW, domainW, showDomain };
|
|
1706
|
+
const activeIndex = activeId == null ? -1 : items.findIndex((it) => it.id === activeId);
|
|
1707
|
+
const { start, end, aboveCount, belowCount } = useListWindow({
|
|
1708
|
+
rowCount: items.length,
|
|
1709
|
+
focusedIndex: activeIndex,
|
|
1710
|
+
height: height ?? items.length,
|
|
1711
|
+
overflowMarkers
|
|
1712
|
+
});
|
|
1713
|
+
const visible = items.slice(start, end);
|
|
1714
|
+
const showAbove = overflowMarkers && aboveCount > 0;
|
|
1715
|
+
const showBelow = overflowMarkers && belowCount > 0;
|
|
1716
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1717
|
+
showAbove ? /* @__PURE__ */ jsx(OverflowMarker2, { glyph: g("moreAbove"), count: aboveCount }) : null,
|
|
1718
|
+
visible.map((it) => /* @__PURE__ */ jsx(
|
|
1719
|
+
ProgressRow,
|
|
1720
|
+
{
|
|
1721
|
+
item: it,
|
|
1722
|
+
active: it.id === activeId,
|
|
1723
|
+
metrics,
|
|
1724
|
+
animate
|
|
1725
|
+
},
|
|
1726
|
+
it.id
|
|
1727
|
+
)),
|
|
1728
|
+
showBelow ? /* @__PURE__ */ jsx(OverflowMarker2, { glyph: g("moreBelow"), count: belowCount }) : null
|
|
1729
|
+
] });
|
|
1730
|
+
}
|
|
1731
|
+
function resolveChoices(field, values = {}) {
|
|
1732
|
+
if (field.optionsFrom) {
|
|
1733
|
+
const src = values[field.optionsFrom];
|
|
1734
|
+
const ids = Array.isArray(src) ? src : src != null && src !== "" ? [String(src)] : [];
|
|
1735
|
+
return ids.map((id) => ({ id: String(id), label: String(id) }));
|
|
1736
|
+
}
|
|
1737
|
+
return (field.choices ?? []).map(
|
|
1738
|
+
(c) => typeof c === "string" ? { id: c, label: c } : { id: c.id, label: c.label != null ? c.label : String(c.id) }
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
function buildStops(fields, values = {}) {
|
|
1742
|
+
const stops = [];
|
|
1743
|
+
for (const f of fields) {
|
|
1744
|
+
if (f.kind === "select" || f.kind === "multiselect") {
|
|
1745
|
+
for (const c of resolveChoices(f, values)) {
|
|
1746
|
+
stops.push({ id: `${f.name}::${c.id}`, name: f.name, kind: f.kind, choiceId: c.id });
|
|
1747
|
+
}
|
|
1748
|
+
} else {
|
|
1749
|
+
stops.push({ id: f.name, name: f.name, kind: f.kind, choiceId: null });
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return stops;
|
|
1753
|
+
}
|
|
1754
|
+
function isEmpty(v) {
|
|
1755
|
+
return v == null || v === "" || Array.isArray(v) && v.length === 0;
|
|
1756
|
+
}
|
|
1757
|
+
function validateForm(fields, values = {}) {
|
|
1758
|
+
const errors = {};
|
|
1759
|
+
for (const f of fields) {
|
|
1760
|
+
const v = values[f.name];
|
|
1761
|
+
if (f.required && isEmpty(v)) errors[f.name] = "required";
|
|
1762
|
+
if (f.kind === "multiselect" && f.min != null) {
|
|
1763
|
+
const n = Array.isArray(v) ? v.length : 0;
|
|
1764
|
+
if (n < f.min) errors[f.name] = `select at least ${f.min}`;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return { ok: Object.keys(errors).length === 0, errors };
|
|
1768
|
+
}
|
|
1769
|
+
function useFormNavigation({
|
|
1770
|
+
fields,
|
|
1771
|
+
values = {},
|
|
1772
|
+
onChange
|
|
1773
|
+
}) {
|
|
1774
|
+
const stops = buildStops(fields, values);
|
|
1775
|
+
const [idx, setIdx] = useState(0);
|
|
1776
|
+
const safe = Math.min(idx, Math.max(0, stops.length - 1));
|
|
1777
|
+
const stop = stops[safe] ?? null;
|
|
1778
|
+
const emit = (next) => onChange?.(next);
|
|
1779
|
+
return {
|
|
1780
|
+
focusId: stop ? stop.id : null,
|
|
1781
|
+
focusStop: stop,
|
|
1782
|
+
stops,
|
|
1783
|
+
next: () => setIdx((i) => Math.min(i + 1, stops.length - 1)),
|
|
1784
|
+
prev: () => setIdx((i) => Math.max(i - 1, 0)),
|
|
1785
|
+
focusField: (name) => {
|
|
1786
|
+
const j = stops.findIndex((s) => s.name === name);
|
|
1787
|
+
if (j >= 0) setIdx(j);
|
|
1788
|
+
},
|
|
1789
|
+
toggle: () => {
|
|
1790
|
+
if (!stop) return;
|
|
1791
|
+
const f = fields.find((ff) => ff.name === stop.name);
|
|
1792
|
+
if (!f) return;
|
|
1793
|
+
const v = values[f.name];
|
|
1794
|
+
if (f.kind === "toggle") emit({ ...values, [f.name]: !v });
|
|
1795
|
+
else if (f.kind === "select") emit({ ...values, [f.name]: stop.choiceId ?? "" });
|
|
1796
|
+
else if (f.kind === "multiselect") {
|
|
1797
|
+
const cur = Array.isArray(v) ? v : [];
|
|
1798
|
+
const id = stop.choiceId ?? "";
|
|
1799
|
+
if (cur.includes(id)) {
|
|
1800
|
+
if (f.min != null && cur.length <= f.min) return;
|
|
1801
|
+
emit({ ...values, [f.name]: cur.filter((x) => x !== id) });
|
|
1802
|
+
} else {
|
|
1803
|
+
if (f.max != null && cur.length >= f.max) return;
|
|
1804
|
+
emit({ ...values, [f.name]: cur.concat(id) });
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
},
|
|
1808
|
+
setText: (name, str) => emit({ ...values, [name]: str }),
|
|
1809
|
+
commit: () => validateForm(fields, values)
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
function FieldLabel({ label, required }) {
|
|
1813
|
+
const tokens = useTokens();
|
|
1814
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1815
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fg, children: label }),
|
|
1816
|
+
required ? /* @__PURE__ */ jsx(Text, { color: tokens.stateWarn, children: " *" }) : null
|
|
1817
|
+
] });
|
|
1818
|
+
}
|
|
1819
|
+
function Choice({
|
|
1820
|
+
selected,
|
|
1821
|
+
locked,
|
|
1822
|
+
label,
|
|
1823
|
+
focused
|
|
1824
|
+
}) {
|
|
1825
|
+
const tokens = useTokens();
|
|
1826
|
+
const g = useGlyph();
|
|
1827
|
+
const sel = locked ? selectionIntents.locked : selected ? selectionIntents.selected : selectionIntents.unselected;
|
|
1828
|
+
const bg = focused ? tokens.bgFocused : void 0;
|
|
1829
|
+
const glyphStr = g(sel.glyph);
|
|
1830
|
+
const labelColor = selected || locked ? tokens.fg : tokens.fgMuted;
|
|
1831
|
+
const pad = glyphStr + " ".repeat(Math.max(0, 2 - cellWidth(glyphStr)));
|
|
1832
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", marginRight: 1, children: [
|
|
1833
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " " }),
|
|
1834
|
+
/* @__PURE__ */ jsx(Text, { color: tokens[sel.token], backgroundColor: bg, wrap: "truncate", children: pad }),
|
|
1835
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " " }),
|
|
1836
|
+
/* @__PURE__ */ jsx(Text, { color: labelColor, backgroundColor: bg, wrap: "truncate", children: label }),
|
|
1837
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: bg, children: " " })
|
|
1838
|
+
] });
|
|
1839
|
+
}
|
|
1840
|
+
function ErrorLine({ msg }) {
|
|
1841
|
+
const tokens = useTokens();
|
|
1842
|
+
const g = useGlyph();
|
|
1843
|
+
if (!msg) return null;
|
|
1844
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", paddingLeft: 1, children: [
|
|
1845
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.stateErr, children: g("cross") }),
|
|
1846
|
+
/* @__PURE__ */ jsx(Text, { color: tokens.fgMuted, children: " " + msg })
|
|
1847
|
+
] });
|
|
1848
|
+
}
|
|
1849
|
+
function FieldControl({
|
|
1850
|
+
field,
|
|
1851
|
+
value,
|
|
1852
|
+
values,
|
|
1853
|
+
focusStop,
|
|
1854
|
+
error
|
|
1855
|
+
}) {
|
|
1856
|
+
const tokens = useTokens();
|
|
1857
|
+
const focusedHere = !!focusStop && focusStop.name === field.name;
|
|
1858
|
+
if (field.kind === "text" || field.kind === "secret") {
|
|
1859
|
+
const isSecret = field.kind === "secret";
|
|
1860
|
+
const str = value == null ? "" : String(value);
|
|
1861
|
+
const display = isSecret ? "\u2022".repeat(str.length) : str;
|
|
1862
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1863
|
+
/* @__PURE__ */ jsx(Input, { value: display, placeholder: field.placeholder ?? "", focused: focusedHere, error }),
|
|
1864
|
+
isSecret && focusedHere ? /* @__PURE__ */ jsx(Box, { paddingLeft: 1, children: /* @__PURE__ */ jsx(Text, { color: tokens.fgFaint, children: "\u2423 reveal" }) }) : null
|
|
1865
|
+
] });
|
|
1866
|
+
}
|
|
1867
|
+
if (field.kind === "toggle") {
|
|
1868
|
+
const on = !!value;
|
|
1869
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Choice, { selected: on, label: on ? "enabled" : "disabled", focused: focusedHere }) });
|
|
1870
|
+
}
|
|
1871
|
+
const choices = resolveChoices(field, values);
|
|
1872
|
+
const selectedSet = field.kind === "multiselect" ? new Set(Array.isArray(value) ? value : []) : new Set(value != null && value !== "" ? [String(value)] : []);
|
|
1873
|
+
if (choices.length === 0) {
|
|
1874
|
+
return /* @__PURE__ */ jsx(Box, { paddingLeft: 1, children: /* @__PURE__ */ jsx(Text, { color: tokens.fgDim, children: "\u2014" }) });
|
|
1875
|
+
}
|
|
1876
|
+
return /* @__PURE__ */ jsx(Box, { flexWrap: "wrap", flexDirection: "row", children: choices.map((c) => /* @__PURE__ */ jsx(
|
|
1877
|
+
Choice,
|
|
1878
|
+
{
|
|
1879
|
+
selected: selectedSet.has(c.id),
|
|
1880
|
+
label: c.label,
|
|
1881
|
+
focused: !!focusStop && focusStop.name === field.name && focusStop.choiceId === c.id
|
|
1882
|
+
},
|
|
1883
|
+
c.id
|
|
1884
|
+
)) });
|
|
1885
|
+
}
|
|
1886
|
+
function Form({ fields, values = {}, focusId, errors = {} }) {
|
|
1887
|
+
const stops = buildStops(fields, values);
|
|
1888
|
+
const focusStop = stops.find((s) => s.id === focusId) ?? null;
|
|
1889
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: fields.map((f) => {
|
|
1890
|
+
const textual = f.kind === "text" || f.kind === "secret";
|
|
1891
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1892
|
+
/* @__PURE__ */ jsx(FieldLabel, { label: f.label, required: f.required }),
|
|
1893
|
+
/* @__PURE__ */ jsx(FieldControl, { field: f, value: values[f.name], values, focusStop, error: errors[f.name] }),
|
|
1894
|
+
textual ? null : /* @__PURE__ */ jsx(ErrorLine, { msg: errors[f.name] })
|
|
1895
|
+
] }, f.name);
|
|
1896
|
+
}) });
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
export { ACTIONS, Banner, CLOUD, COMMON_DOMAINS, COMPANIES, Cursor, DATABASES, DEFAULT_GLYPH_COLOR, DEFAULT_UNICODE, DEVINFRA, DescriptionList, Dialog, EDITORS, FILES, FRAMEWORKS, Footer, Form, GLYPH_PACKS, Header, Input, LANGUAGES, List, ListRow, LogView, NERD_INDEX, NERD_INDEX_SOURCES, OS, PACKAGES, PALETTE_SLOTS, Pane, ProgressBar, ProgressList, SOCIAL, SYSTEM, Spinner, ThemeProvider, allThemes, blocks, blocksH, boxChars, boxStyles, buildStops, buildTokens, catppuccinMocha, cellWidth, computeWindow, defaultTheme, deriveAscii, detectIconSet, getTheme, glyph, glyphColor, gruvbox, hasGlyph, hasTheme, latte, listThemes, mocha, mochaTokens, navGlyphs, neutral, nf, nfChar, nfHas, nord, palettes, registerGlyph, registerGlyphs, registerNerdIndex, registerTheme, registeredNames, resolveChoices, selectionIntents, spinnerFor, spinnerFrames, stateGlyph, stateGlyphs, stateIntents, tokyonight, useBlink2 as useBlink, useBlink as useBlinkContext, useFormNavigation, useGlyph, useIconSet, useListNavigation, useListSelection, useListWindow, useSpinnerFrame, useStdoutDimensions, useTheme, useThemeControls, useTokens, validateForm };
|
|
1900
|
+
//# sourceMappingURL=index.js.map
|
|
1901
|
+
//# sourceMappingURL=index.js.map
|