@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/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