@rigerc/ink-ui 0.0.0-stage → 0.41.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,1920 @@
1
+ import { Box, Text, useWindowSize, useInput, measureElement, useApp, useStdout } from 'ink';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { glyph } from '@kud/glyphs';
4
+ import React8, { createContext, useContext, useState, useEffect, useRef, useLayoutEffect, Children, isValidElement, Fragment as Fragment$1 } from 'react';
5
+ import stringWidth3 from 'string-width';
6
+ import { spawn } from 'child_process';
7
+
8
+ // src/components/banner.tsx
9
+
10
+ // src/tokens.ts
11
+ var palette = {
12
+ orange: "#FF8C00",
13
+ amber: "#FFB400",
14
+ yellow: "#FFCB6B",
15
+ red: "#FF5364",
16
+ mint: "#5FD7A7",
17
+ green: "#4ADE80",
18
+ coral: "#FF6B6B",
19
+ blue: "#82AAFF",
20
+ cyan: "#89DDFF",
21
+ purple: "#C792EA",
22
+ ink: "#E4E4EC",
23
+ grey: "#999999",
24
+ slate: "#75767E",
25
+ dusk: "#5C5E73",
26
+ selection: "#30364A",
27
+ track: "#2B323D",
28
+ trackHigh: "#6B7480",
29
+ ground: "#1A1B27"
30
+ };
31
+ var colors = {
32
+ accent: palette.orange,
33
+ secondary: palette.grey,
34
+ muted: palette.slate,
35
+ success: palette.mint,
36
+ error: palette.red,
37
+ warning: palette.amber,
38
+ info: palette.cyan,
39
+ group: palette.purple,
40
+ track: palette.track,
41
+ trackHighlight: palette.trackHigh,
42
+ ticket: palette.yellow,
43
+ pr: palette.orange,
44
+ link: palette.blue,
45
+ added: palette.green,
46
+ removed: palette.coral,
47
+ pending: palette.slate,
48
+ dim: palette.dusk,
49
+ selection: palette.selection
50
+ };
51
+ var softColors = {
52
+ accent: "#8f4611",
53
+ muted: "#474a54",
54
+ success: "#2e6d4c",
55
+ error: "#7e2a2c",
56
+ warning: "#8a6a1e",
57
+ info: "#3c74a2",
58
+ group: "#524389"
59
+ };
60
+ var tonalColors = {
61
+ success: { fill: "#25393B", ink: "#5FD7A7" },
62
+ error: { fill: "#3F2431", ink: "#FF5364" },
63
+ warning: { fill: "#3F3321", ink: "#FFB400" },
64
+ info: { fill: "#2C3A4A", ink: "#89DDFF" },
65
+ accent: { fill: "#3D2A1C", ink: "#C47718" },
66
+ muted: { fill: "#262735", ink: "#7A7B85" },
67
+ group: { fill: "#362E46", ink: "#C792EA" }
68
+ };
69
+ var tonalStrongColors = {
70
+ success: { fill: "#2D504B", ink: "#5FD7A7" },
71
+ error: { fill: "#5A2B38", ink: "#FF5364" },
72
+ warning: { fill: "#5A461C", ink: "#FFB400" },
73
+ info: { fill: "#395163", ink: "#89DDFF" },
74
+ accent: { fill: "#5A3816", ink: "#E0913A" },
75
+ muted: { fill: "#33343F", ink: "#7F808A" },
76
+ group: { fill: "#4A3C5E", ink: "#C792EA" }
77
+ };
78
+ var priorityColors = {
79
+ highest: palette.ink,
80
+ high: palette.ink,
81
+ medium: palette.grey,
82
+ low: palette.slate,
83
+ lowest: palette.slate
84
+ };
85
+ var spacing = {
86
+ xs: 1,
87
+ sm: 2,
88
+ md: 3,
89
+ lg: 4
90
+ };
91
+ var symbols = {
92
+ active: "\u25CF",
93
+ nestedBullet: "\u25AA",
94
+ nestedBulletEmpty: "\u25AB",
95
+ rule: "\u254C",
96
+ caret: "\u258F",
97
+ verticalLine: "\u2502",
98
+ success: "\u2713",
99
+ error: "\u2717",
100
+ warn: "\u26A0",
101
+ info: "\u2139",
102
+ pointer: "\u276F",
103
+ checkboxChecked: "\u25FC",
104
+ checkboxUnchecked: "\u25FB",
105
+ bullet: "\u2022",
106
+ pending: "\u25CB",
107
+ ellipsis: "\u2026",
108
+ line: "\u2500",
109
+ barFull: "\u2588",
110
+ barEmpty: "\u2591"
111
+ };
112
+ var asciiSymbols = {
113
+ active: "*",
114
+ nestedBullet: "+",
115
+ nestedBulletEmpty: "-",
116
+ rule: "-",
117
+ caret: "|",
118
+ verticalLine: "|",
119
+ success: "+",
120
+ error: "x",
121
+ warn: "!",
122
+ info: "i",
123
+ pointer: ">",
124
+ checkboxChecked: "[x]",
125
+ checkboxUnchecked: "[ ]",
126
+ bullet: "*",
127
+ pending: "o",
128
+ ellipsis: "...",
129
+ line: "-",
130
+ barFull: "#",
131
+ barEmpty: "-"
132
+ };
133
+ var borders = {
134
+ base: "round",
135
+ focus: "bold",
136
+ ascii: "classic"
137
+ };
138
+ var motion = {
139
+ spinner: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
140
+ asciiSpinner: ["-", "\\", "|", "/"],
141
+ intervalMs: 80,
142
+ /** Skeleton shimmer has a faster independent rhythm. */
143
+ skeletonIntervalMs: 70
144
+ };
145
+ var Banner = ({ title, subtitle, icon = "\u25C6" }) => /* @__PURE__ */ jsxs(Box, { gap: 1, marginBottom: 1, children: [
146
+ /* @__PURE__ */ jsx(Text, { color: colors.accent, children: icon }),
147
+ /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
148
+ subtitle && /* @__PURE__ */ jsx(Text, { dimColor: true, children: subtitle })
149
+ ] });
150
+ var variantColor = {
151
+ success: colors.success,
152
+ error: colors.error,
153
+ warning: colors.warning,
154
+ info: colors.info
155
+ };
156
+ var Badge = ({ children, variant = "info" }) => /* @__PURE__ */ jsxs(Text, { color: variantColor[variant], children: [
157
+ "[",
158
+ children,
159
+ "]"
160
+ ] });
161
+ var FILL = {
162
+ success: colors.success,
163
+ error: colors.error,
164
+ warning: colors.warning,
165
+ info: colors.info,
166
+ accent: colors.accent,
167
+ muted: colors.muted,
168
+ group: colors.group
169
+ };
170
+ var INK = {
171
+ success: "black",
172
+ error: "white",
173
+ warning: "black",
174
+ info: "black",
175
+ accent: "black",
176
+ muted: "white",
177
+ group: "white"
178
+ };
179
+ var inkFor = (fill) => {
180
+ const hex = /^#([0-9a-f]{6})$/i.exec(fill)?.[1];
181
+ if (!hex) return "white";
182
+ const channel = (i) => {
183
+ const c = parseInt(hex.slice(i, i + 2), 16) / 255;
184
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
185
+ };
186
+ const luminance = 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4);
187
+ return (luminance + 0.05) / 0.05 >= 1.05 / (luminance + 0.05) ? "black" : "white";
188
+ };
189
+ var pillStyle = (variant, tone, strong = false) => {
190
+ if (tone === "tonal") {
191
+ const pair = strong ? tonalStrongColors[variant] : tonalColors[variant];
192
+ return { fill: pair.fill, ink: pair.ink, bold: strong };
193
+ }
194
+ if (tone === "soft")
195
+ return {
196
+ fill: softColors[variant],
197
+ ink: inkFor(softColors[variant]),
198
+ bold: false
199
+ };
200
+ if (tone === "outline")
201
+ return { fill: FILL[variant], ink: FILL[variant], bold: false };
202
+ return { fill: FILL[variant], ink: INK[variant], bold: false };
203
+ };
204
+ var Pill = ({
205
+ children,
206
+ variant = "muted",
207
+ tone = "solid",
208
+ strong = false,
209
+ color
210
+ }) => {
211
+ if (tone === "outline") {
212
+ const fill2 = color ?? FILL[variant];
213
+ return /* @__PURE__ */ jsxs(Text, { color: fill2, children: [
214
+ glyph("plCapLeftThin"),
215
+ children,
216
+ glyph("plCapRightThin")
217
+ ] });
218
+ }
219
+ if (tone === "tonal") {
220
+ const style = pillStyle(variant, tone, strong);
221
+ const fill2 = color ?? style.fill;
222
+ if (process.env["NO_COLOR"]) return /* @__PURE__ */ jsxs(Text, { color: fill2, children: [
223
+ "[",
224
+ children,
225
+ "]"
226
+ ] });
227
+ return /* @__PURE__ */ jsxs(Text, { children: [
228
+ /* @__PURE__ */ jsx(Text, { color: fill2, children: glyph("plCapLeft") }),
229
+ /* @__PURE__ */ jsx(Text, { backgroundColor: fill2, color: style.ink, bold: style.bold, children }),
230
+ /* @__PURE__ */ jsx(Text, { color: fill2, children: glyph("plCapRight") })
231
+ ] });
232
+ }
233
+ const fill = color ?? (tone === "soft" ? softColors[variant] : FILL[variant]);
234
+ const ink = tone === "soft" || color ? inkFor(fill) : INK[variant];
235
+ if (process.env["NO_COLOR"]) return /* @__PURE__ */ jsxs(Text, { color: fill, children: [
236
+ "[",
237
+ children,
238
+ "]"
239
+ ] });
240
+ return /* @__PURE__ */ jsxs(Text, { children: [
241
+ /* @__PURE__ */ jsx(Text, { color: fill, children: glyph("plCapLeft") }),
242
+ /* @__PURE__ */ jsx(Text, { backgroundColor: fill, color: ink, children }),
243
+ /* @__PURE__ */ jsx(Text, { color: fill, children: glyph("plCapRight") })
244
+ ] });
245
+ };
246
+ var pillWidth = (text) => text.length + 2;
247
+ var Header = ({ children, subtitle }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
248
+ /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children }),
249
+ subtitle && /* @__PURE__ */ jsx(Text, { dimColor: true, children: subtitle })
250
+ ] });
251
+ var Spinner = ({ label, ascii = false }) => {
252
+ const [frame, setFrame] = useState(0);
253
+ const frames = ascii ? motion.asciiSpinner : motion.spinner;
254
+ useEffect(() => {
255
+ const timer = setInterval(() => {
256
+ setFrame((prev) => (prev + 1) % frames.length);
257
+ }, motion.intervalMs);
258
+ return () => clearInterval(timer);
259
+ }, [ascii]);
260
+ return /* @__PURE__ */ jsxs(Box, { gap: spacing.xs, children: [
261
+ /* @__PURE__ */ jsx(Text, { color: colors.accent, children: frames[frame % frames.length] }),
262
+ label && /* @__PURE__ */ jsx(Text, { dimColor: true, children: label })
263
+ ] });
264
+ };
265
+ var knownTerminalColumns = (stdout, windowColumns) => {
266
+ if (typeof stdout !== "object" || stdout === null || !("columns" in stdout))
267
+ return void 0;
268
+ const columns = stdout.columns;
269
+ return typeof columns === "number" && Number.isFinite(columns) && columns > 0 ? windowColumns : void 0;
270
+ };
271
+ var useTerminalColumns = () => {
272
+ const { stdout } = useStdout();
273
+ const { columns } = useWindowSize();
274
+ return knownTerminalColumns(stdout, columns);
275
+ };
276
+ var DEFAULT_MIN_WIDTH = 3;
277
+ var widestLine = (value) => value.split("\n").reduce((widest, line) => Math.max(widest, stringWidth3(line)), 0);
278
+ var naturalWidth = (column, cells) => cells.reduce(
279
+ (widest, cell) => Math.max(widest, widestLine(cell)),
280
+ widestLine(column.header)
281
+ );
282
+ var floorFor = (column, width) => column.width !== void 0 ? width : Math.min(width, column.minWidth ?? DEFAULT_MIN_WIDTH);
283
+ var widestShrinkable = (widths, floors) => widths.reduce(
284
+ (best, width, index) => width > floors[index] && (best === -1 || width > widths[best]) ? index : best,
285
+ -1
286
+ );
287
+ var shrinkToFit = (widths, floors, excess) => {
288
+ const shrunk = [...widths];
289
+ let remaining = excess;
290
+ while (remaining > 0) {
291
+ const target = widestShrinkable(shrunk, floors);
292
+ if (target === -1) break;
293
+ shrunk[target] = shrunk[target] - 1;
294
+ remaining -= 1;
295
+ }
296
+ return shrunk;
297
+ };
298
+ var resolveColumnWidths = ({
299
+ columns,
300
+ rows,
301
+ gap,
302
+ maxWidth
303
+ }) => {
304
+ const widths = columns.map(
305
+ (column, index) => column.width ?? naturalWidth(
306
+ column,
307
+ rows.map((row) => row[index] ?? "")
308
+ )
309
+ );
310
+ const gaps = gap * Math.max(columns.length - 1, 0);
311
+ const excess = widths.reduce((total, width) => total + width, gaps) - maxWidth;
312
+ return excess > 0 ? shrinkToFit(
313
+ widths,
314
+ columns.map((column, index) => floorFor(column, widths[index])),
315
+ excess
316
+ ) : widths;
317
+ };
318
+ var FALLBACK_TERMINAL_WIDTH = 80;
319
+ var justification = {
320
+ left: "flex-start",
321
+ center: "center",
322
+ right: "flex-end"
323
+ };
324
+ var cellText = (value) => String(value ?? "");
325
+ var Table = ({
326
+ data,
327
+ columns,
328
+ gap = 2,
329
+ maxWidth,
330
+ headerColor = colors.muted
331
+ }) => {
332
+ const terminalColumns = useTerminalColumns();
333
+ const rows = data.map(
334
+ (row) => columns.map((column) => cellText(row[column.key]))
335
+ );
336
+ const widths = resolveColumnWidths({
337
+ columns,
338
+ rows,
339
+ gap,
340
+ maxWidth: maxWidth ?? terminalColumns ?? FALLBACK_TERMINAL_WIDTH
341
+ });
342
+ const cell = (column, index, content) => /* @__PURE__ */ jsx(
343
+ Box,
344
+ {
345
+ width: widths[index],
346
+ flexShrink: 0,
347
+ flexGrow: 0,
348
+ justifyContent: justification[column.align ?? "left"],
349
+ children: content
350
+ },
351
+ column.key
352
+ );
353
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
354
+ /* @__PURE__ */ jsx(Box, { gap, children: columns.map(
355
+ (column, index) => cell(
356
+ column,
357
+ index,
358
+ /* @__PURE__ */ jsx(Text, { bold: true, color: headerColor, wrap: "truncate-end", children: column.header })
359
+ )
360
+ ) }),
361
+ rows.map((cells, rowIndex) => /* @__PURE__ */ jsx(Box, { gap, children: columns.map(
362
+ (column, index) => cell(
363
+ column,
364
+ index,
365
+ /* @__PURE__ */ jsx(Text, { wrap: column.overflow === "wrap" ? "wrap" : "truncate-end", children: cells[index] })
366
+ )
367
+ ) }, rowIndex))
368
+ ] });
369
+ };
370
+ var tailHints = (page, help = true) => [
371
+ ...page === "nested" ? [["\u232B", "back"]] : [],
372
+ ...help ? [["?", "help"]] : [],
373
+ ["q", "quit"]
374
+ ];
375
+ var FooterHints = ({ hints, page, help = true }) => {
376
+ const all = page ? [...hints, ...tailHints(page, help)] : hints;
377
+ return /* @__PURE__ */ jsx(Box, { columnGap: spacing.sm, rowGap: 0, flexWrap: "wrap", children: all.map(([key, label]) => /* @__PURE__ */ jsxs(Box, { children: [
378
+ /* @__PURE__ */ jsx(Text, { color: "white", children: key }),
379
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + label })
380
+ ] }, key)) });
381
+ };
382
+ var STATUS_COLOR = {
383
+ news: colors.accent,
384
+ busy: colors.info,
385
+ quiet: void 0
386
+ };
387
+ var Page = ({
388
+ title,
389
+ icon,
390
+ count,
391
+ noun = "item",
392
+ user,
393
+ scope,
394
+ alert,
395
+ critical = false,
396
+ status,
397
+ facts,
398
+ tabs,
399
+ counter,
400
+ hints,
401
+ page = "root",
402
+ help = true,
403
+ gap = true,
404
+ filter,
405
+ fill = false,
406
+ width,
407
+ height,
408
+ children
409
+ }) => {
410
+ const terminal = useWindowSize();
411
+ const frameWidth = width ?? (fill ? terminal.columns : void 0);
412
+ const frameHeight = height ?? (fill ? terminal.rows : void 0);
413
+ const brand = icon ? `${icon} ${title}` : title;
414
+ const countSeg = count === void 0 ? facts ? ` ${facts} ` : " " : ` ${String(count).padStart(3)} ${noun}${count !== 1 ? "s" : ""} \xB7 `;
415
+ const userSeg = user ? `@${user} ` : "";
416
+ const scopeSeg = scope ? `${scope} ` : "";
417
+ const alertSeg = alert ? `${alert} ` : "";
418
+ const statusSeg = status ? `${status.text} ` : "";
419
+ const inner = (frameWidth ?? 80) - 4;
420
+ const used = stringWidth3(
421
+ brand + countSeg + userSeg + scopeSeg + alertSeg + statusSeg
422
+ );
423
+ const ruleWidth = Math.max(4, inner - used);
424
+ return /* @__PURE__ */ jsxs(
425
+ Box,
426
+ {
427
+ flexDirection: "column",
428
+ width: frameWidth,
429
+ height: frameHeight,
430
+ borderStyle: borders.base,
431
+ borderColor: colors.muted,
432
+ overflow: "hidden",
433
+ children: [
434
+ /* @__PURE__ */ jsxs(Box, { paddingLeft: 1, children: [
435
+ /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: brand }),
436
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: countSeg }),
437
+ userSeg ? /* @__PURE__ */ jsx(Text, { children: userSeg }) : null,
438
+ scopeSeg ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: scopeSeg }) : null,
439
+ alertSeg ? /* @__PURE__ */ jsx(Text, { bold: true, color: critical ? colors.error : colors.accent, children: alertSeg }) : null,
440
+ statusSeg && status ? /* @__PURE__ */ jsx(
441
+ Text,
442
+ {
443
+ color: STATUS_COLOR[status.tone],
444
+ dimColor: status.tone === "quiet",
445
+ bold: status.tone === "news",
446
+ children: statusSeg
447
+ }
448
+ ) : null,
449
+ /* @__PURE__ */ jsx(Text, { color: colors.info, dimColor: true, children: symbols.rule.repeat(ruleWidth) })
450
+ ] }),
451
+ filter ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: filter }) : null,
452
+ tabs ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, marginTop: filter ? 0 : 1, children: tabs }) : null,
453
+ /* @__PURE__ */ jsx(
454
+ Box,
455
+ {
456
+ flexDirection: "column",
457
+ flexGrow: 1,
458
+ marginTop: gap && !(filter && !tabs) ? 1 : 0,
459
+ children
460
+ }
461
+ ),
462
+ counter ? /* @__PURE__ */ jsx(Box, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: counter }) }) : null,
463
+ hints ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx(FooterHints, { hints, page, help }) }) : null
464
+ ]
465
+ }
466
+ );
467
+ };
468
+ var PAGE_CHROME = 4;
469
+ var isTextValue = (value) => typeof value === "string" || typeof value === "number";
470
+ var KeyValue = ({ label, value, labelWidth = 12 }) => /* @__PURE__ */ jsxs(Box, { children: [
471
+ /* @__PURE__ */ jsx(Box, { width: labelWidth, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: label }) }),
472
+ isTextValue(value) ? /* @__PURE__ */ jsx(Text, { children: value }) : value
473
+ ] });
474
+ var LoadingScreen = ({ label }) => /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Spinner, { label }) });
475
+ var Panel = ({
476
+ title,
477
+ color = colors.info,
478
+ focused = false,
479
+ width,
480
+ height,
481
+ flexGrow,
482
+ children
483
+ }) => /* @__PURE__ */ jsxs(
484
+ Box,
485
+ {
486
+ flexDirection: "column",
487
+ width,
488
+ height,
489
+ flexGrow,
490
+ borderStyle: focused ? borders.focus : borders.base,
491
+ borderColor: focused ? color : colors.muted,
492
+ overflow: "hidden",
493
+ children: [
494
+ title && /* @__PURE__ */ jsx(Text, { color: focused ? color : colors.muted, bold: true, children: focused ? `${title} ${symbols.active}` : title }),
495
+ children
496
+ ]
497
+ }
498
+ );
499
+ var Columns = ({ gap = 1, children }) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap, flexGrow: 1, children });
500
+ var isTextValue2 = (value) => typeof value === "string" || typeof value === "number";
501
+ var SelectableRow = ({
502
+ active = false,
503
+ marker = symbols.pointer,
504
+ children
505
+ }) => /* @__PURE__ */ jsxs(Box, { children: [
506
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: colors.info, children: active ? ` ${marker} ` : " " }) }),
507
+ isTextValue2(children) ? /* @__PURE__ */ jsx(Text, { bold: active, children }) : children
508
+ ] });
509
+ var windowStart = (cursor, total, count) => {
510
+ if (total <= count) return 0;
511
+ return Math.max(0, Math.min(cursor - Math.floor(count / 2), total - count));
512
+ };
513
+ var Select = ({
514
+ options,
515
+ onChange,
516
+ onSubmit,
517
+ defaultValue,
518
+ isDisabled = false,
519
+ visibleOptionCount
520
+ }) => {
521
+ const initial = options.findIndex((o) => o.value === defaultValue);
522
+ const [cursor, setCursor] = useState(initial === -1 ? 0 : initial);
523
+ const move = (delta) => {
524
+ const next = (cursor + delta + options.length) % options.length;
525
+ setCursor(next);
526
+ onChange?.(options[next].value);
527
+ };
528
+ useInput(
529
+ (_input, key) => {
530
+ if (key.upArrow) move(-1);
531
+ if (key.downArrow) move(1);
532
+ if (key.return) onSubmit?.(options[cursor].value);
533
+ },
534
+ { isActive: !isDisabled && options.length > 0 }
535
+ );
536
+ const count = visibleOptionCount ?? options.length;
537
+ const start = windowStart(cursor, options.length, count);
538
+ const visible = options.slice(start, start + count);
539
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
540
+ visible.map((option, i) => {
541
+ const active = start + i === cursor;
542
+ return /* @__PURE__ */ jsxs(SelectableRow, { active, children: [
543
+ /* @__PURE__ */ jsx(Text, { bold: active, color: active ? colors.info : void 0, children: option.label }),
544
+ active ? /* @__PURE__ */ jsx(Text, { color: colors.success, children: " " + symbols.success }) : null
545
+ ] }, option.value);
546
+ }),
547
+ options.length > count && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${symbols.ellipsis} ${options.length} total` })
548
+ ] });
549
+ };
550
+ var windowStart2 = (cursor, total, count) => {
551
+ if (total <= count) return 0;
552
+ return Math.max(0, Math.min(cursor - Math.floor(count / 2), total - count));
553
+ };
554
+ var MultiSelect = ({
555
+ options,
556
+ onChange,
557
+ onSubmit,
558
+ defaultValue = [],
559
+ isDisabled = false,
560
+ visibleOptionCount
561
+ }) => {
562
+ const [cursor, setCursor] = useState(0);
563
+ const [selected, setSelected] = useState(new Set(defaultValue));
564
+ useInput(
565
+ (input, key) => {
566
+ if (key.upArrow)
567
+ setCursor((i) => (i - 1 + options.length) % options.length);
568
+ if (key.downArrow) setCursor((i) => (i + 1) % options.length);
569
+ if (input === " ") {
570
+ const value = options[cursor].value;
571
+ const next = new Set(selected);
572
+ if (next.has(value)) next.delete(value);
573
+ else next.add(value);
574
+ setSelected(next);
575
+ onChange?.([...next]);
576
+ }
577
+ if (key.return) onSubmit?.([...selected]);
578
+ },
579
+ { isActive: !isDisabled && options.length > 0 }
580
+ );
581
+ const count = visibleOptionCount ?? options.length;
582
+ const start = windowStart2(cursor, options.length, count);
583
+ const visible = options.slice(start, start + count);
584
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
585
+ visible.map((option, i) => {
586
+ const active = start + i === cursor;
587
+ const on = selected.has(option.value);
588
+ return /* @__PURE__ */ jsxs(SelectableRow, { active, children: [
589
+ /* @__PURE__ */ jsx(Text, { bold: active, color: active ? colors.info : void 0, children: option.label }),
590
+ on ? /* @__PURE__ */ jsx(Text, { color: colors.success, children: " " + symbols.success }) : null
591
+ ] }, option.value);
592
+ }),
593
+ options.length > count && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${symbols.ellipsis} ${options.length} total` })
594
+ ] });
595
+ };
596
+ var TextInput = ({
597
+ placeholder = "",
598
+ defaultValue = "",
599
+ onChange,
600
+ onSubmit,
601
+ onCancel,
602
+ isDisabled = false
603
+ }) => {
604
+ const [value, setValue] = useState(defaultValue);
605
+ const [cursor, setCursor] = useState(defaultValue.length);
606
+ const update = (nextValue, nextCursor) => {
607
+ setValue(nextValue);
608
+ setCursor(nextCursor);
609
+ onChange?.(nextValue);
610
+ };
611
+ useInput(
612
+ (input, key) => {
613
+ if (key.return) {
614
+ onSubmit?.(value);
615
+ return;
616
+ }
617
+ if (key.escape) {
618
+ onCancel?.();
619
+ return;
620
+ }
621
+ if (key.leftArrow) {
622
+ setCursor((c) => Math.max(0, c - 1));
623
+ return;
624
+ }
625
+ if (key.rightArrow) {
626
+ setCursor((c) => Math.min(value.length, c + 1));
627
+ return;
628
+ }
629
+ if (key.backspace || key.delete) {
630
+ if (cursor === 0) return;
631
+ update(value.slice(0, cursor - 1) + value.slice(cursor), cursor - 1);
632
+ return;
633
+ }
634
+ if (input && !key.ctrl && !key.meta && !key.tab) {
635
+ update(
636
+ value.slice(0, cursor) + input + value.slice(cursor),
637
+ cursor + input.length
638
+ );
639
+ }
640
+ },
641
+ { isActive: !isDisabled }
642
+ );
643
+ if (value.length === 0) {
644
+ return /* @__PURE__ */ jsxs(Text, { children: [
645
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: " " }),
646
+ placeholder ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder }) : null
647
+ ] });
648
+ }
649
+ const before = value.slice(0, cursor);
650
+ const at = value.slice(cursor, cursor + 1) || " ";
651
+ const after = value.slice(cursor + 1);
652
+ return /* @__PURE__ */ jsxs(Text, { children: [
653
+ before,
654
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: at }),
655
+ after
656
+ ] });
657
+ };
658
+ var defaultDomains = [
659
+ "gmail.com",
660
+ "outlook.com",
661
+ "hotmail.com",
662
+ "yahoo.com",
663
+ "icloud.com",
664
+ "proton.me"
665
+ ];
666
+ var EmailInput = ({
667
+ placeholder = "",
668
+ defaultValue = "",
669
+ domains = defaultDomains,
670
+ onChange,
671
+ onSubmit,
672
+ isDisabled = false
673
+ }) => {
674
+ const [value, setValue] = useState(defaultValue);
675
+ const [cursor, setCursor] = useState(defaultValue.length);
676
+ const at = value.indexOf("@");
677
+ const domainFragment = at === -1 ? "" : value.slice(at + 1);
678
+ const match = at !== -1 && domainFragment.length > 0 ? domains.find(
679
+ (d) => d.startsWith(domainFragment) && d !== domainFragment
680
+ ) : void 0;
681
+ const suggestion = match ? match.slice(domainFragment.length) : "";
682
+ const showSuggestion = suggestion.length > 0 && cursor === value.length;
683
+ const update = (nextValue, nextCursor) => {
684
+ setValue(nextValue);
685
+ setCursor(nextCursor);
686
+ onChange?.(nextValue);
687
+ };
688
+ useInput(
689
+ (input, key) => {
690
+ if (key.return) {
691
+ onSubmit?.(value);
692
+ return;
693
+ }
694
+ if (key.tab && showSuggestion) {
695
+ update(value + suggestion, value.length + suggestion.length);
696
+ return;
697
+ }
698
+ if (key.leftArrow) {
699
+ setCursor((c) => Math.max(0, c - 1));
700
+ return;
701
+ }
702
+ if (key.rightArrow) {
703
+ setCursor((c) => Math.min(value.length, c + 1));
704
+ return;
705
+ }
706
+ if (key.backspace || key.delete) {
707
+ if (cursor === 0) return;
708
+ update(value.slice(0, cursor - 1) + value.slice(cursor), cursor - 1);
709
+ return;
710
+ }
711
+ if (input && !key.ctrl && !key.meta && !key.tab) {
712
+ update(
713
+ value.slice(0, cursor) + input + value.slice(cursor),
714
+ cursor + input.length
715
+ );
716
+ }
717
+ },
718
+ { isActive: !isDisabled }
719
+ );
720
+ if (value.length === 0) {
721
+ return /* @__PURE__ */ jsxs(Text, { children: [
722
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: " " }),
723
+ placeholder ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder }) : null
724
+ ] });
725
+ }
726
+ if (showSuggestion) {
727
+ return /* @__PURE__ */ jsxs(Text, { children: [
728
+ value,
729
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: suggestion.slice(0, 1) }),
730
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: suggestion.slice(1) })
731
+ ] });
732
+ }
733
+ const before = value.slice(0, cursor);
734
+ const atChar = value.slice(cursor, cursor + 1) || " ";
735
+ const after = value.slice(cursor + 1);
736
+ return /* @__PURE__ */ jsxs(Text, { children: [
737
+ before,
738
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: atChar }),
739
+ after
740
+ ] });
741
+ };
742
+ var PasswordInput = ({
743
+ placeholder = "",
744
+ mask = "*",
745
+ onChange,
746
+ onSubmit,
747
+ isDisabled = false
748
+ }) => {
749
+ const [value, setValue] = useState("");
750
+ const [cursor, setCursor] = useState(0);
751
+ const update = (nextValue, nextCursor) => {
752
+ setValue(nextValue);
753
+ setCursor(nextCursor);
754
+ onChange?.(nextValue);
755
+ };
756
+ useInput(
757
+ (input, key) => {
758
+ if (key.return) {
759
+ onSubmit?.(value);
760
+ return;
761
+ }
762
+ if (key.leftArrow) {
763
+ setCursor((c) => Math.max(0, c - 1));
764
+ return;
765
+ }
766
+ if (key.rightArrow) {
767
+ setCursor((c) => Math.min(value.length, c + 1));
768
+ return;
769
+ }
770
+ if (key.backspace || key.delete) {
771
+ if (cursor === 0) return;
772
+ update(value.slice(0, cursor - 1) + value.slice(cursor), cursor - 1);
773
+ return;
774
+ }
775
+ if (input && !key.ctrl && !key.meta && !key.tab) {
776
+ update(
777
+ value.slice(0, cursor) + input + value.slice(cursor),
778
+ cursor + input.length
779
+ );
780
+ }
781
+ },
782
+ { isActive: !isDisabled }
783
+ );
784
+ if (value.length === 0) {
785
+ return /* @__PURE__ */ jsxs(Text, { children: [
786
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: " " }),
787
+ placeholder ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder }) : null
788
+ ] });
789
+ }
790
+ const masked = mask.repeat(value.length);
791
+ const before = masked.slice(0, cursor);
792
+ const at = masked.slice(cursor, cursor + 1) || " ";
793
+ const after = masked.slice(cursor + 1);
794
+ return /* @__PURE__ */ jsxs(Text, { children: [
795
+ before,
796
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: at }),
797
+ after
798
+ ] });
799
+ };
800
+ var ConfirmInput = ({
801
+ defaultChoice = "confirm",
802
+ onConfirm,
803
+ onCancel,
804
+ isDisabled = false
805
+ }) => {
806
+ useInput(
807
+ (input, key) => {
808
+ if (input.toLowerCase() === "y") {
809
+ onConfirm?.();
810
+ return;
811
+ }
812
+ if (input.toLowerCase() === "n") {
813
+ onCancel?.();
814
+ return;
815
+ }
816
+ if (key.return) {
817
+ if (defaultChoice === "confirm") onConfirm?.();
818
+ else onCancel?.();
819
+ }
820
+ },
821
+ { isActive: !isDisabled }
822
+ );
823
+ const hint = defaultChoice === "confirm" ? "(Y/n)" : "(y/N)";
824
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint });
825
+ };
826
+ var markers = [symbols.active, symbols.pending, symbols.nestedBullet, symbols.nestedBulletEmpty];
827
+ var DepthContext = createContext(0);
828
+ var UnorderedListItem = ({ children }) => {
829
+ const depth = useContext(DepthContext);
830
+ const marker = markers[Math.max(0, depth - 1) % markers.length];
831
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
832
+ /* @__PURE__ */ jsxs(Text, { children: [
833
+ marker,
834
+ " "
835
+ ] }),
836
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children })
837
+ ] });
838
+ };
839
+ var UnorderedListRoot = ({ children }) => {
840
+ const depth = useContext(DepthContext);
841
+ return /* @__PURE__ */ jsx(DepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children }) });
842
+ };
843
+ var UnorderedList = Object.assign(UnorderedListRoot, {
844
+ Item: UnorderedListItem
845
+ });
846
+ var OrderedListItem = ({ children, _index = 1 }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
847
+ /* @__PURE__ */ jsxs(Text, { children: [
848
+ _index,
849
+ ". "
850
+ ] }),
851
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children })
852
+ ] });
853
+ var OrderedListRoot = ({ children }) => {
854
+ let n = 0;
855
+ const numbered = React8.Children.map(children, (child) => {
856
+ if (React8.isValidElement(child) && child.type === OrderedListItem) {
857
+ n += 1;
858
+ return React8.cloneElement(child, {
859
+ _index: n
860
+ });
861
+ }
862
+ return child;
863
+ });
864
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: numbered });
865
+ };
866
+ var OrderedList = Object.assign(OrderedListRoot, {
867
+ Item: OrderedListItem
868
+ });
869
+ var ScrollView = ({
870
+ lines,
871
+ showIndicator = true,
872
+ initialStart = 0,
873
+ isActive = true
874
+ }) => {
875
+ const ref = useRef(null);
876
+ const [height, setHeight] = useState(1);
877
+ const [start, setStart] = useState(initialStart);
878
+ useEffect(() => setStart(initialStart), [initialStart]);
879
+ useLayoutEffect(() => {
880
+ if (!ref.current) return;
881
+ const { height: h } = measureElement(ref.current);
882
+ if (h > 0 && h !== height) setHeight(h);
883
+ });
884
+ const maxStart = Math.max(0, lines.length - height);
885
+ const clamped = Math.min(start, maxStart);
886
+ useInput(
887
+ (input, key) => {
888
+ if (key.downArrow || input === "j")
889
+ setStart((s) => Math.min(maxStart, s + 1));
890
+ if (key.upArrow || input === "k") setStart((s) => Math.max(0, s - 1));
891
+ if (input === " " || key.pageDown || input === "f")
892
+ setStart((s) => Math.min(maxStart, s + height));
893
+ if (key.pageUp || input === "b") setStart((s) => Math.max(0, s - height));
894
+ if (input === "g") setStart(0);
895
+ if (input === "G") setStart(maxStart);
896
+ },
897
+ { isActive }
898
+ );
899
+ const visible = lines.slice(clamped, clamped + height);
900
+ const hasMore = lines.length > height;
901
+ const atEnd = clamped >= maxStart;
902
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
903
+ /* @__PURE__ */ jsx(Box, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visible.map(
904
+ (l, i) => l.spans ? /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
905
+ l.spans.map((s, j) => /* @__PURE__ */ jsx(
906
+ Text,
907
+ {
908
+ color: s.color,
909
+ bold: s.bold,
910
+ dimColor: s.dim,
911
+ italic: s.italic,
912
+ children: s.text
913
+ },
914
+ j
915
+ )),
916
+ l.spans.length === 0 ? " " : ""
917
+ ] }, i) : /* @__PURE__ */ jsx(
918
+ Text,
919
+ {
920
+ color: l.color,
921
+ bold: l.bold,
922
+ dimColor: l.dim,
923
+ wrap: "truncate-end",
924
+ children: l.text || " "
925
+ },
926
+ i
927
+ )
928
+ ) }),
929
+ showIndicator && hasMore && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${clamped + 1}\u2013${clamped + visible.length} / ${lines.length}` + (atEnd ? " (end)" : " \u2193 j/space") })
930
+ ] });
931
+ };
932
+ var GAP = spacing.sm;
933
+ var FOLDED_PENDING_COUNT = "\u2013";
934
+ var DIVIDER = symbols.verticalLine;
935
+ var DIVIDER_WIDTH = 1;
936
+ var tabCountStyle = (isActive) => {
937
+ const style = pillStyle(isActive ? "accent" : "muted", "tonal", isActive);
938
+ return {
939
+ bold: style.bold,
940
+ color: style.ink,
941
+ backgroundColor: style.fill
942
+ };
943
+ };
944
+ var countContentOf = (item) => {
945
+ if (item.count === void 0) return void 0;
946
+ if (item.count === null)
947
+ return process.env["NO_COLOR"] ? FOLDED_PENDING_COUNT : " \u2013";
948
+ if (item.total === void 0) return String(item.count);
949
+ return `${item.count}/${item.total}`;
950
+ };
951
+ var chipTailOf = (content) => ` ${glyph("plCapLeft")}${content}${glyph("plCapRight")}`;
952
+ var fullPartsOf = (item) => {
953
+ const head = item.icon === void 0 ? item.label : `${item.icon} ${item.label}`;
954
+ const content = countContentOf(item);
955
+ if (content === void 0) return [head, ""];
956
+ if (process.env["NO_COLOR"]) return [head, ` (${content})`];
957
+ return [head, chipTailOf(content)];
958
+ };
959
+ var shouldFoldTabs = (fullWidth, availableWidth) => availableWidth !== void 0 && fullWidth > availableWidth;
960
+ var tabLabelStyle = (isActive) => ({
961
+ bold: isActive,
962
+ color: isActive ? colors.accent : void 0,
963
+ dimColor: !isActive
964
+ });
965
+ var fullTextOf = (item) => fullPartsOf(item).join("");
966
+ var foldedPartsOf = (item) => {
967
+ const icon = item.icon ?? "";
968
+ const content = countContentOf(item);
969
+ if (content === void 0) return [icon, ""];
970
+ if (process.env["NO_COLOR"])
971
+ return [
972
+ icon,
973
+ item.count === null ? ` ${FOLDED_PENDING_COUNT}` : ` ${content}`
974
+ ];
975
+ return [icon, chipTailOf(content)];
976
+ };
977
+ var foldedTextOf = (item) => foldedPartsOf(item).join("");
978
+ var Tabs = ({
979
+ active,
980
+ items,
981
+ width
982
+ }) => {
983
+ const terminalColumns = useTerminalColumns();
984
+ const availableWidth = width ?? terminalColumns;
985
+ const cells = items.map((item) => {
986
+ const [fullLabel, fullTail] = fullPartsOf(item);
987
+ const [foldedLabel, foldedTail] = foldedPartsOf(item);
988
+ return {
989
+ key: item.value,
990
+ marker: item.marker ?? "",
991
+ markerColor: item.markerColor,
992
+ fullText: fullTextOf(item),
993
+ foldedText: foldedTextOf(item),
994
+ fullLabel,
995
+ fullTail,
996
+ foldedLabel,
997
+ foldedTail,
998
+ countContent: countContentOf(item),
999
+ hasIcon: item.icon !== void 0,
1000
+ isActive: item.value === active,
1001
+ group: item.group
1002
+ };
1003
+ });
1004
+ const renderItems = [];
1005
+ for (let i = 0; i < cells.length; i++) {
1006
+ const prevGroup = i > 0 ? cells[i - 1].group : void 0;
1007
+ const currGroup = cells[i].group;
1008
+ if (i > 0 && currGroup !== prevGroup) {
1009
+ renderItems.push({ type: "divider" });
1010
+ }
1011
+ renderItems.push({ type: "tab", cell: cells[i] });
1012
+ }
1013
+ const cellWidth = (marker, text) => stringWidth3(marker) + stringWidth3(text);
1014
+ const fullWidth = renderItems.reduce(
1015
+ (sum, item) => sum + (item.type === "divider" ? DIVIDER_WIDTH : cellWidth(item.cell.marker, item.cell.fullText)),
1016
+ 0
1017
+ ) + GAP * Math.max(0, renderItems.length - 1);
1018
+ const folded = shouldFoldTabs(fullWidth, availableWidth);
1019
+ const textOf = (cell) => folded && !cell.isActive && cell.hasIcon ? cell.foldedText : cell.fullText;
1020
+ const gutterOf = (cell) => stringWidth3(cell.marker);
1021
+ const labelOf = (cell) => stringWidth3(
1022
+ folded && !cell.isActive && cell.hasIcon ? cell.foldedLabel : cell.fullLabel
1023
+ );
1024
+ let x = 0;
1025
+ let rule = { start: 0, width: 0 };
1026
+ for (const item of renderItems) {
1027
+ if (item.type === "tab") {
1028
+ if (item.cell.isActive)
1029
+ rule = { start: x + gutterOf(item.cell), width: labelOf(item.cell) };
1030
+ x += gutterOf(item.cell) + stringWidth3(textOf(item.cell)) + GAP;
1031
+ } else {
1032
+ x += DIVIDER_WIDTH + GAP;
1033
+ }
1034
+ }
1035
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1036
+ /* @__PURE__ */ jsx(Box, { gap: GAP, children: renderItems.map((item, idx) => {
1037
+ if (item.type === "divider") {
1038
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: colors.muted, dimColor: true, children: DIVIDER }) }, `divider-${idx}`);
1039
+ }
1040
+ const cell = item.cell;
1041
+ const isFolded = folded && !cell.isActive && cell.hasIcon;
1042
+ const labelText = isFolded ? cell.foldedLabel : cell.fullLabel;
1043
+ const tailText = isFolded ? cell.foldedTail : cell.fullTail;
1044
+ return /* @__PURE__ */ jsxs(Box, { children: [
1045
+ cell.marker ? /* @__PURE__ */ jsx(Text, { color: cell.markerColor, children: cell.marker }) : null,
1046
+ /* @__PURE__ */ jsx(Text, { ...tabLabelStyle(cell.isActive), children: labelText }),
1047
+ tailText ? process.env["NO_COLOR"] ? /* @__PURE__ */ jsx(Text, { ...tabCountStyle(cell.isActive), children: tailText }) : (
1048
+ // The separating space lives inside this run, not beside
1049
+ // it: a bare string child of the row's `Box` blanks the
1050
+ // whole strip, while a string inside a `Text` is just text.
1051
+ /* @__PURE__ */ jsxs(Text, { children: [
1052
+ " ",
1053
+ /* @__PURE__ */ jsx(
1054
+ Pill,
1055
+ {
1056
+ tone: "tonal",
1057
+ variant: cell.isActive ? "accent" : "muted",
1058
+ strong: cell.isActive,
1059
+ children: cell.countContent ?? ""
1060
+ }
1061
+ )
1062
+ ] })
1063
+ ) : null
1064
+ ] }, cell.key);
1065
+ }) }),
1066
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: colors.accent, children: " ".repeat(rule.start) + symbols.line.repeat(rule.width) }) })
1067
+ ] });
1068
+ };
1069
+
1070
+ // src/icon-mode.ts
1071
+ var current = "text";
1072
+ var setIconMode = (mode) => {
1073
+ current = mode;
1074
+ };
1075
+ var getIconMode = () => current;
1076
+
1077
+ // src/components/status-variants.ts
1078
+ var UNICODE = {
1079
+ success: symbols.success,
1080
+ error: symbols.error,
1081
+ warning: symbols.warn,
1082
+ info: symbols.info
1083
+ };
1084
+ var NERD = {
1085
+ success: glyph("check"),
1086
+ error: glyph("cross"),
1087
+ warning: glyph("warning"),
1088
+ info: glyph("info")
1089
+ };
1090
+ var COLOR = {
1091
+ success: colors.success,
1092
+ error: colors.error,
1093
+ warning: colors.warning,
1094
+ info: colors.info
1095
+ };
1096
+ var statusVariant = (variant) => ({
1097
+ icon: (getIconMode() === "nerd" ? NERD : UNICODE)[variant],
1098
+ color: COLOR[variant]
1099
+ });
1100
+ var StatusMessage = ({
1101
+ variant = "info",
1102
+ children
1103
+ }) => {
1104
+ const { icon, color } = statusVariant(variant);
1105
+ return /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
1106
+ /* @__PURE__ */ jsx(Text, { color, children: icon }),
1107
+ /* @__PURE__ */ jsx(Text, { children })
1108
+ ] });
1109
+ };
1110
+ var Step = (_props) => null;
1111
+ var StepperProgress = ({
1112
+ steps,
1113
+ currentStep,
1114
+ orientation = "horizontal",
1115
+ ascii = false
1116
+ }) => {
1117
+ const markers2 = ascii ? asciiSymbols : symbols;
1118
+ return /* @__PURE__ */ jsx(
1119
+ Box,
1120
+ {
1121
+ flexDirection: orientation === "vertical" ? "column" : "row",
1122
+ flexWrap: "wrap",
1123
+ columnGap: spacing.sm,
1124
+ children: steps.map(({ id, name }, index) => {
1125
+ const complete = index < currentStep;
1126
+ const current2 = index === currentStep;
1127
+ const marker = complete ? markers2.success : current2 ? markers2.pointer : markers2.pending;
1128
+ return /* @__PURE__ */ jsxs(
1129
+ Text,
1130
+ {
1131
+ bold: current2,
1132
+ color: colors[complete ? "success" : current2 ? "accent" : "muted"],
1133
+ dimColor: !current2 && !complete,
1134
+ children: [
1135
+ marker,
1136
+ " ",
1137
+ index + 1,
1138
+ ". ",
1139
+ name
1140
+ ]
1141
+ },
1142
+ id
1143
+ );
1144
+ })
1145
+ }
1146
+ );
1147
+ };
1148
+ var StepperContext = createContext(null);
1149
+ var useStepper = () => {
1150
+ const context = useContext(StepperContext);
1151
+ if (!context) throw new Error("useStepper must be used inside Stepper");
1152
+ return context;
1153
+ };
1154
+ var StepperNavigation = () => {
1155
+ const { isFirst, isLast, isValidating, keyboardNav } = useStepper();
1156
+ if (isValidating) return /* @__PURE__ */ jsxs(Text, { color: colors.muted, children: [
1157
+ "Validating",
1158
+ symbols.ellipsis
1159
+ ] });
1160
+ const hints = [
1161
+ ...keyboardNav ? [["\u21B5", isLast ? "finish" : "next"]] : [],
1162
+ ["\u232B", isFirst ? "cancel" : "back"]
1163
+ ];
1164
+ return /* @__PURE__ */ jsx(FooterHints, { hints });
1165
+ };
1166
+ var collectSteps = (children, prefix = "") => Children.toArray(children).flatMap((child, index) => {
1167
+ if (!isValidElement(child)) {
1168
+ throw new Error("Stepper children must be Step elements or fragments");
1169
+ }
1170
+ const id = prefix + "/" + (child.key ?? index);
1171
+ if (child.type === Fragment$1) {
1172
+ return collectSteps(child.props.children, id);
1173
+ }
1174
+ if (child.type !== Step) {
1175
+ throw new Error("Stepper children must be Step elements or fragments");
1176
+ }
1177
+ return [{ id, element: child }];
1178
+ });
1179
+ var clamp = (index, length) => Math.max(0, Math.min(Number.isFinite(index) ? Math.trunc(index) : 0, length - 1));
1180
+ var Stepper = ({
1181
+ children,
1182
+ onComplete,
1183
+ onCancel,
1184
+ onStepChange,
1185
+ onExitStep,
1186
+ onEnterStep,
1187
+ onError,
1188
+ step,
1189
+ initialStep = 0,
1190
+ keyboardNav = false,
1191
+ ascii = false,
1192
+ showProgress = true,
1193
+ renderProgress
1194
+ }) => {
1195
+ const steps = collectSteps(children);
1196
+ const [position, setPosition] = useState({
1197
+ index: initialStep,
1198
+ id: null
1199
+ });
1200
+ const trackedIndex = position.id === null ? -1 : steps.findIndex((item) => item.id === position.id);
1201
+ const currentStep = clamp(
1202
+ step ?? (trackedIndex < 0 ? position.index : trackedIndex),
1203
+ steps.length
1204
+ );
1205
+ const active = steps[currentStep];
1206
+ const [isValidating, setIsValidating] = useState(false);
1207
+ const [error, setError] = useState();
1208
+ const [finished, setFinished] = useState(false);
1209
+ const busy = useRef(false);
1210
+ const ended = useRef(false);
1211
+ const mounted = useRef(true);
1212
+ const source = useRef({ id: active?.id, index: currentStep, signature: "" });
1213
+ source.current = {
1214
+ id: active?.id,
1215
+ index: currentStep,
1216
+ signature: JSON.stringify(steps.map((item) => item.id))
1217
+ };
1218
+ useEffect(() => {
1219
+ mounted.current = true;
1220
+ return () => {
1221
+ mounted.current = false;
1222
+ };
1223
+ }, []);
1224
+ useEffect(() => {
1225
+ if (step === void 0 && active) {
1226
+ setPosition(
1227
+ (previous) => previous.id === active.id && previous.index === currentStep ? previous : { id: active.id, index: currentStep }
1228
+ );
1229
+ }
1230
+ }, [active?.id, currentStep, step]);
1231
+ const cancel = () => {
1232
+ if (busy.current || ended.current || !active) return;
1233
+ ended.current = true;
1234
+ setFinished(true);
1235
+ onCancel?.();
1236
+ };
1237
+ const navigate = async (direction) => {
1238
+ if (busy.current || ended.current || !active) return;
1239
+ const target = typeof direction === "number" ? clamp(direction, steps.length) : direction === "next" ? currentStep + 1 : currentStep - 1;
1240
+ if (target === currentStep) return;
1241
+ busy.current = true;
1242
+ setIsValidating(true);
1243
+ setError(void 0);
1244
+ const origin = source.current;
1245
+ const stillCurrent = () => mounted.current && source.current.id === origin.id && source.current.index === origin.index && source.current.signature === origin.signature;
1246
+ try {
1247
+ await Promise.resolve();
1248
+ if (!stillCurrent()) return;
1249
+ if (direction === "next") {
1250
+ const guard = active.element.props.canProceed ?? true;
1251
+ const permitted = typeof guard === "function" ? await guard() : guard;
1252
+ if (!permitted || !stillCurrent()) return;
1253
+ }
1254
+ if (onExitStep && await onExitStep(currentStep) === false) return;
1255
+ if (!stillCurrent()) return;
1256
+ if (target < 0 || target >= steps.length) {
1257
+ ended.current = true;
1258
+ setFinished(true);
1259
+ if (target < 0) onCancel?.();
1260
+ else onComplete();
1261
+ } else {
1262
+ if (step === void 0) {
1263
+ setPosition({ index: target, id: steps[target].id });
1264
+ }
1265
+ onStepChange?.(target);
1266
+ onEnterStep?.(target);
1267
+ }
1268
+ } catch (cause) {
1269
+ if (stillCurrent()) {
1270
+ setError(cause instanceof Error ? cause.message : String(cause));
1271
+ onError?.(cause);
1272
+ }
1273
+ } finally {
1274
+ if (mounted.current) setIsValidating(false);
1275
+ busy.current = false;
1276
+ }
1277
+ };
1278
+ const context = {
1279
+ currentStep,
1280
+ totalSteps: steps.length,
1281
+ isFirst: currentStep === 0,
1282
+ isLast: currentStep === steps.length - 1,
1283
+ isValidating,
1284
+ keyboardNav,
1285
+ goNext: () => navigate("next"),
1286
+ goBack: () => navigate("back"),
1287
+ goTo: (index) => navigate(index),
1288
+ cancel
1289
+ };
1290
+ useInput(
1291
+ (_input, key) => {
1292
+ if (key.return) void context.goNext();
1293
+ },
1294
+ // Keep the subscription mounted while a transition is validating.
1295
+ // navigate() uses busy.current to reject overlapping Enter presses.
1296
+ { isActive: keyboardNav && !finished && !!active }
1297
+ );
1298
+ if (!active || finished) return null;
1299
+ const progress = {
1300
+ steps: steps.map((item) => ({ id: item.id, name: item.element.props.name })),
1301
+ currentStep,
1302
+ ascii
1303
+ };
1304
+ const content = active.element.props.children;
1305
+ return /* @__PURE__ */ jsx(StepperContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: spacing.xs, children: [
1306
+ showProgress && (renderProgress ? renderProgress(progress) : /* @__PURE__ */ jsx(StepperProgress, { ...progress })),
1307
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: typeof content === "function" ? content(context) : content }, active.id),
1308
+ error && /* @__PURE__ */ jsx(StatusMessage, { variant: "error", children: error })
1309
+ ] }) });
1310
+ };
1311
+ var useTabs = (items, { initial, isActive = true, arrows = true } = {}) => {
1312
+ const [active, setActive] = useState(
1313
+ initial ?? items[0]?.value
1314
+ );
1315
+ useInput(
1316
+ (_input, key) => {
1317
+ if (items.length === 0) return;
1318
+ const backward = key.tab ? key.shift : arrows && key.leftArrow;
1319
+ const forward = key.tab ? !key.shift : arrows && key.rightArrow;
1320
+ if (!backward && !forward) return;
1321
+ const current2 = items.findIndex((item) => item.value === active);
1322
+ const next = (current2 + (backward ? -1 : 1) + items.length) % items.length;
1323
+ setActive(items[next].value);
1324
+ },
1325
+ { isActive }
1326
+ );
1327
+ return { active, setActive };
1328
+ };
1329
+ var useListCursor = (length, {
1330
+ initial = 0,
1331
+ wrap = false,
1332
+ vimKeys = true,
1333
+ isActive = true
1334
+ } = {}) => {
1335
+ const [cursor, setCursor] = useState(initial);
1336
+ const moveCursor = (delta) => {
1337
+ if (length === 0) return;
1338
+ setCursor((current2) => {
1339
+ const next = current2 + delta;
1340
+ if (wrap) return (next + length) % length;
1341
+ return Math.min(length - 1, Math.max(0, next));
1342
+ });
1343
+ };
1344
+ useInput(
1345
+ (input, key) => {
1346
+ if (key.upArrow || vimKeys && input === "k") moveCursor(-1);
1347
+ if (key.downArrow || vimKeys && input === "j") moveCursor(1);
1348
+ },
1349
+ { isActive }
1350
+ );
1351
+ return { cursor, setCursor, moveCursor };
1352
+ };
1353
+ var isItemRow = (row) => "item" in row;
1354
+ var rowsOf = (items, query) => {
1355
+ const grouped = query.trim() === "" && items.some((item) => item.group);
1356
+ if (!grouped) return items.map((item, index) => ({ item, index }));
1357
+ const rows = [];
1358
+ const seen = [];
1359
+ for (const group of items.map((item) => item.group ?? "")) {
1360
+ if (!seen.includes(group)) seen.push(group);
1361
+ }
1362
+ for (const group of seen) {
1363
+ if (group) rows.push({ header: group });
1364
+ items.forEach((item, index) => {
1365
+ if ((item.group ?? "") === group) rows.push({ item, index });
1366
+ });
1367
+ }
1368
+ return rows;
1369
+ };
1370
+ var DEFAULT_HINTS = [
1371
+ ["\u2191\u2193", "move"],
1372
+ ["\u23CE", "select"],
1373
+ ["esc", "close"]
1374
+ ];
1375
+ var spaces = (count) => " ".repeat(Math.max(0, count));
1376
+ var padEndWidth = (value, width) => value + spaces(width - stringWidth3(value));
1377
+ var CommandPalette = ({
1378
+ items,
1379
+ query,
1380
+ onQueryChange,
1381
+ onSelect,
1382
+ onClose,
1383
+ message,
1384
+ emptyHint,
1385
+ placeholder = "type to search",
1386
+ width = 60,
1387
+ maxRows = 10,
1388
+ hints = DEFAULT_HINTS,
1389
+ isActive = true
1390
+ }) => {
1391
+ const { cursor, setCursor } = useListCursor(items.length, {
1392
+ vimKeys: false,
1393
+ isActive
1394
+ });
1395
+ useEffect(() => setCursor(0), [query, setCursor]);
1396
+ const at = Math.min(cursor, Math.max(0, items.length - 1));
1397
+ const rows = rowsOf(items, query);
1398
+ const cursorRow = rows.findIndex((row) => isItemRow(row) && row.index === at);
1399
+ const budget = Math.max(1, maxRows);
1400
+ const start = Math.max(
1401
+ 0,
1402
+ Math.min(cursorRow - Math.floor(budget / 2), rows.length - budget)
1403
+ );
1404
+ const visible = rows.slice(start, start + budget);
1405
+ const scrolls = rows.length > budget;
1406
+ const hasMarkers = items.some((item) => item.marker);
1407
+ const inner = width - 2;
1408
+ const headerTrail = (title) => Math.max(2, inner - 5 - stringWidth3(title));
1409
+ const note = typeof message === "string" ? { text: message } : message;
1410
+ const emptyLine = note ?? (query.trim() === "" && emptyHint ? { text: emptyHint } : void 0);
1411
+ const errorMark = emptyLine?.tone === "error" ? statusVariant("error") : void 0;
1412
+ const counter = `${at + 1} of ${items.length}`;
1413
+ const hintsWidth = hints.reduce(
1414
+ (total, [key, label]) => total + stringWidth3(key) + 1 + stringWidth3(label),
1415
+ 0
1416
+ ) + Math.max(0, hints.length - 1) * 2;
1417
+ return /* @__PURE__ */ jsxs(
1418
+ Box,
1419
+ {
1420
+ flexDirection: "column",
1421
+ borderStyle: borders.base,
1422
+ borderColor: colors.info,
1423
+ backgroundColor: colors.track,
1424
+ width,
1425
+ children: [
1426
+ /* @__PURE__ */ jsxs(Box, { width: inner, children: [
1427
+ /* @__PURE__ */ jsx(Text, { color: colors.info, children: " \u203A " }),
1428
+ /* @__PURE__ */ jsx(
1429
+ TextInput,
1430
+ {
1431
+ defaultValue: query,
1432
+ placeholder,
1433
+ onChange: onQueryChange,
1434
+ onSubmit: () => {
1435
+ const item = items[at];
1436
+ if (item) onSelect(item.id);
1437
+ else note?.onSubmit?.();
1438
+ },
1439
+ onCancel: onClose,
1440
+ isDisabled: !isActive
1441
+ }
1442
+ ),
1443
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx(Text, { children: " " }) })
1444
+ ] }),
1445
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + symbols.line.repeat(inner - 2) + " " }),
1446
+ items.length === 0 && emptyLine?.text ? errorMark ? /* @__PURE__ */ jsx(Text, { color: errorMark.color, children: " " + padEndWidth(`${errorMark.icon} ${emptyLine.text}`, inner - 2) + " " }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + padEndWidth(emptyLine.text, inner - 2) + " " }) : null,
1447
+ visible.map((row) => {
1448
+ if (!isItemRow(row)) {
1449
+ const title = row.header.toUpperCase();
1450
+ return /* @__PURE__ */ jsxs(Text, { children: [
1451
+ " ",
1452
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: symbols.line.repeat(2) + " " }),
1453
+ /* @__PURE__ */ jsx(Text, { bold: true, color: colors.group, children: title }),
1454
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + symbols.line.repeat(headerTrail(title)) })
1455
+ ] }, `header:${row.header}`);
1456
+ }
1457
+ const { item } = row;
1458
+ const active = row.index === at;
1459
+ return /* @__PURE__ */ jsxs(Box, { width: inner, children: [
1460
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: colors.info, children: active ? ` ${symbols.pointer} ` : " " }) }),
1461
+ hasMarkers ? /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { color: item.markerColor, children: (item.marker ?? " ") + " " }) }) : null,
1462
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsxs(Text, { bold: active, children: [
1463
+ item.label ?? item.title,
1464
+ item.hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + item.hint }) : null
1465
+ ] }) })
1466
+ ] }, item.id);
1467
+ }),
1468
+ scrolls ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + spaces(inner - 2 - stringWidth3(counter)) + counter + " " }) : null,
1469
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + symbols.line.repeat(inner - 2) + " " }),
1470
+ /* @__PURE__ */ jsxs(Text, { children: [
1471
+ " ",
1472
+ hints.map(([key, label], index) => /* @__PURE__ */ jsxs(React8.Fragment, { children: [
1473
+ index > 0 ? " " : null,
1474
+ /* @__PURE__ */ jsx(Text, { color: "white", children: key }),
1475
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " + label })
1476
+ ] }, key)),
1477
+ spaces(inner - 1 - hintsWidth)
1478
+ ] })
1479
+ ]
1480
+ }
1481
+ );
1482
+ };
1483
+
1484
+ // src/components/fuzzy-filter.ts
1485
+ var isSubsequence = (needle, haystack) => {
1486
+ let at = 0;
1487
+ for (const ch of haystack) {
1488
+ if (ch === needle[at]) at++;
1489
+ if (at === needle.length) return true;
1490
+ }
1491
+ return needle.length === 0;
1492
+ };
1493
+ var haystackOf = (item) => [item.title, item.group ?? "", item.hint ?? "", ...item.keywords ?? []].join(" ").toLowerCase();
1494
+ var fuzzyFilter = (items, query) => {
1495
+ const q = query.trim().toLowerCase();
1496
+ if (!q) return items;
1497
+ const substring = [];
1498
+ const subsequence = [];
1499
+ for (const item of items) {
1500
+ const hay = haystackOf(item);
1501
+ if (hay.includes(q)) substring.push(item);
1502
+ else if (isSubsequence(q, hay)) subsequence.push(item);
1503
+ }
1504
+ return [...substring, ...subsequence];
1505
+ };
1506
+ var useAppKeys = ({
1507
+ onBack,
1508
+ onQuit,
1509
+ atRoot = "ignore",
1510
+ isActive = true
1511
+ } = {}) => {
1512
+ const { exit } = useApp();
1513
+ const quit = onQuit ?? exit;
1514
+ useInput(
1515
+ (input, key) => {
1516
+ if (input === "q") return quit();
1517
+ if (key.escape || key.backspace || key.delete) {
1518
+ const handled = onBack ? onBack() !== false : false;
1519
+ if (!handled && atRoot === "quit") quit();
1520
+ }
1521
+ },
1522
+ { isActive }
1523
+ );
1524
+ };
1525
+ var useFilterMode = ({
1526
+ isActive = true,
1527
+ onChange
1528
+ } = {}) => {
1529
+ const [state, setState] = useState({
1530
+ term: null,
1531
+ caret: 0
1532
+ });
1533
+ const [typing, setTyping] = useState(false);
1534
+ const { term, caret } = state;
1535
+ const previous = useRef(term);
1536
+ useEffect(() => {
1537
+ if (previous.current === term) return;
1538
+ previous.current = term;
1539
+ onChange?.(term);
1540
+ }, [term, onChange]);
1541
+ const clear = () => {
1542
+ setState({ term: null, caret: 0 });
1543
+ setTyping(false);
1544
+ };
1545
+ useInput(
1546
+ (input, key) => {
1547
+ if (!typing) {
1548
+ if (input === "/") {
1549
+ setState((s) => {
1550
+ const next = s.term ?? "";
1551
+ return { term: next, caret: next.length };
1552
+ });
1553
+ setTyping(true);
1554
+ }
1555
+ return;
1556
+ }
1557
+ if (key.upArrow || key.downArrow) return;
1558
+ if (key.return || key.escape) {
1559
+ setState((s) => !s.term ? { term: null, caret: 0 } : s);
1560
+ return setTyping(false);
1561
+ }
1562
+ if (key.ctrl && (input === "u" || input === "U"))
1563
+ return setState({ term: "", caret: 0 });
1564
+ if (key.leftArrow)
1565
+ return setState((s) => ({ ...s, caret: Math.max(0, s.caret - 1) }));
1566
+ if (key.rightArrow)
1567
+ return setState((s) => ({
1568
+ ...s,
1569
+ caret: Math.min((s.term ?? "").length, s.caret + 1)
1570
+ }));
1571
+ if (key.ctrl && (input === "a" || input === "A"))
1572
+ return setState((s) => ({ ...s, caret: 0 }));
1573
+ if (key.ctrl && (input === "e" || input === "E"))
1574
+ return setState((s) => ({ ...s, caret: (s.term ?? "").length }));
1575
+ if (key.backspace || key.delete)
1576
+ return setState((s) => {
1577
+ const current2 = s.term ?? "";
1578
+ if (s.caret === 0) return { term: current2, caret: 0 };
1579
+ return {
1580
+ term: current2.slice(0, s.caret - 1) + current2.slice(s.caret),
1581
+ caret: s.caret - 1
1582
+ };
1583
+ });
1584
+ if (input && !key.ctrl && !key.meta && !key.tab)
1585
+ return setState((s) => {
1586
+ const current2 = s.term ?? "";
1587
+ const at = Math.min(s.caret, current2.length);
1588
+ return {
1589
+ term: current2.slice(0, at) + input + current2.slice(at),
1590
+ caret: at + input.length
1591
+ };
1592
+ });
1593
+ },
1594
+ { isActive }
1595
+ );
1596
+ const hints = typing ? [
1597
+ ["\u2191\u2193", "move"],
1598
+ ["\u21B5/esc", "done"],
1599
+ ["\u2303u", "clear"]
1600
+ ] : term !== null ? [
1601
+ ["/", "edit"],
1602
+ ["esc", "clear"]
1603
+ ] : [["/", "filter"]];
1604
+ return { term, typing, caret, active: term !== null, clear, hints };
1605
+ };
1606
+ var FilterBar = ({ term, typing, caret, matches }) => {
1607
+ if (term === null) return null;
1608
+ const at = Math.max(0, Math.min(caret ?? term.length, term.length));
1609
+ return /* @__PURE__ */ jsxs(Box, { children: [
1610
+ /* @__PURE__ */ jsx(Text, { color: colors.info, children: "/ " }),
1611
+ /* @__PURE__ */ jsx(Text, { children: term.slice(0, at) }),
1612
+ typing && at < term.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
1613
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: term[at] }),
1614
+ /* @__PURE__ */ jsx(Text, { children: term.slice(at + 1) })
1615
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1616
+ typing ? /* @__PURE__ */ jsx(Text, { color: colors.info, children: symbols.caret }) : null,
1617
+ /* @__PURE__ */ jsx(Text, { children: term.slice(at) })
1618
+ ] }),
1619
+ matches === void 0 ? null : /* @__PURE__ */ jsx(
1620
+ Text,
1621
+ {
1622
+ dimColor: true,
1623
+ children: ` ${matches} match${matches !== 1 ? "es" : ""}`
1624
+ }
1625
+ )
1626
+ ] });
1627
+ };
1628
+ var Switch = ({
1629
+ left,
1630
+ right,
1631
+ value,
1632
+ color = colors.accent
1633
+ }) => {
1634
+ const leftActive = value === "left";
1635
+ return /* @__PURE__ */ jsxs(Text, { children: [
1636
+ /* @__PURE__ */ jsx(
1637
+ Text,
1638
+ {
1639
+ color: leftActive ? color : void 0,
1640
+ bold: leftActive,
1641
+ dimColor: !leftActive,
1642
+ children: left
1643
+ }
1644
+ ),
1645
+ /* @__PURE__ */ jsx(Text, { children: " " }),
1646
+ /* @__PURE__ */ jsx(Text, { color: leftActive ? color : colors.muted, children: leftActive ? symbols.active : symbols.pending }),
1647
+ /* @__PURE__ */ jsx(Text, { color: colors.muted, children: symbols.line }),
1648
+ /* @__PURE__ */ jsx(Text, { color: !leftActive ? color : colors.muted, children: !leftActive ? symbols.active : symbols.pending }),
1649
+ /* @__PURE__ */ jsx(Text, { children: " " }),
1650
+ /* @__PURE__ */ jsx(
1651
+ Text,
1652
+ {
1653
+ color: !leftActive ? color : void 0,
1654
+ bold: !leftActive,
1655
+ dimColor: leftActive,
1656
+ children: right
1657
+ }
1658
+ )
1659
+ ] });
1660
+ };
1661
+ var Toggle = ({ label, on, color = colors.accent }) => /* @__PURE__ */ jsxs(Text, { color: on ? color : void 0, dimColor: !on, children: [
1662
+ label,
1663
+ " ",
1664
+ on ? symbols.active : symbols.pending
1665
+ ] });
1666
+ var CAP_LEFT = "\uE0B6";
1667
+ var CAP_RIGHT = "\uE0B4";
1668
+ var ToggleSwitch = ({
1669
+ on,
1670
+ label,
1671
+ onColor = colors.success,
1672
+ offColor = colors.muted
1673
+ }) => {
1674
+ const track = on ? onColor : offColor;
1675
+ return /* @__PURE__ */ jsxs(Box, { columnGap: 1, children: [
1676
+ /* @__PURE__ */ jsxs(Text, { children: [
1677
+ /* @__PURE__ */ jsx(Text, { color: track, children: CAP_LEFT }),
1678
+ /* @__PURE__ */ jsx(Text, { backgroundColor: track, color: "black", children: on ? ` ${symbols.active}` : `${symbols.active} ` }),
1679
+ /* @__PURE__ */ jsx(Text, { color: track, children: CAP_RIGHT })
1680
+ ] }),
1681
+ label !== void 0 && /* @__PURE__ */ jsx(Text, { color: on ? onColor : void 0, dimColor: !on, children: label })
1682
+ ] });
1683
+ };
1684
+ var UpdateBanner = ({
1685
+ name,
1686
+ current: current2,
1687
+ latest,
1688
+ command,
1689
+ onConfirm,
1690
+ onDecline,
1691
+ focused = true
1692
+ }) => {
1693
+ useInput(
1694
+ (input, key) => {
1695
+ if (key.return || /^y$/i.test(input)) onConfirm();
1696
+ else if (key.escape || /^n$/i.test(input)) onDecline();
1697
+ },
1698
+ { isActive: focused }
1699
+ );
1700
+ return /* @__PURE__ */ jsxs(
1701
+ Box,
1702
+ {
1703
+ flexDirection: "column",
1704
+ borderStyle: borders.base,
1705
+ borderColor: colors.accent,
1706
+ paddingX: spacing.sm,
1707
+ paddingY: spacing.xs,
1708
+ rowGap: spacing.xs,
1709
+ children: [
1710
+ /* @__PURE__ */ jsxs(Box, { columnGap: 1, children: [
1711
+ /* @__PURE__ */ jsx(Text, { bold: true, color: colors.accent, children: name }),
1712
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1713
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "update available" })
1714
+ ] }),
1715
+ /* @__PURE__ */ jsxs(Box, { columnGap: 1, children: [
1716
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: current2 }),
1717
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2192" }),
1718
+ /* @__PURE__ */ jsx(Text, { bold: true, color: colors.success, children: latest })
1719
+ ] }),
1720
+ /* @__PURE__ */ jsxs(Box, { columnGap: 1, children: [
1721
+ /* @__PURE__ */ jsx(Text, { backgroundColor: colors.accent, color: "black", bold: true, children: " Y " }),
1722
+ /* @__PURE__ */ jsx(Text, { children: "upgrade now" }),
1723
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1724
+ /* @__PURE__ */ jsx(Text, { backgroundColor: colors.track, color: "#8fa3ad", children: " N " }),
1725
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1726
+ "later \u2014 run ",
1727
+ command,
1728
+ " yourself"
1729
+ ] })
1730
+ ] })
1731
+ ]
1732
+ }
1733
+ );
1734
+ };
1735
+ var ProgressBar = ({
1736
+ value,
1737
+ width = 20,
1738
+ color = colors.accent,
1739
+ trackColor = colors.track
1740
+ }) => {
1741
+ const clamped = Math.max(0, Math.min(100, value));
1742
+ const filled = Math.round(clamped / 100 * width);
1743
+ return /* @__PURE__ */ jsxs(Text, { children: [
1744
+ /* @__PURE__ */ jsx(Text, { color, children: symbols.barFull.repeat(filled) }),
1745
+ /* @__PURE__ */ jsx(Text, { color: trackColor, children: symbols.barFull.repeat(width - filled) })
1746
+ ] });
1747
+ };
1748
+ var SHIMMER_FRAME_MS = motion.skeletonIntervalMs;
1749
+ var CREST_HALF = 3;
1750
+ var FALLOFF = 2;
1751
+ var LEAD = CREST_HALF + FALLOFF + 1;
1752
+ var PAUSE_FRAMES = 10;
1753
+ var ROW_LAG = 2;
1754
+ var toRgb = (hex) => [
1755
+ parseInt(hex.slice(1, 3), 16),
1756
+ parseInt(hex.slice(3, 5), 16),
1757
+ parseInt(hex.slice(5, 7), 16)
1758
+ ];
1759
+ var blendHex = (from, to, t) => {
1760
+ const [r1, g1, b1] = toRgb(from);
1761
+ const [r2, g2, b2] = toRgb(to);
1762
+ const mix = (a, b) => Math.round(a + (b - a) * t);
1763
+ const hex = (n) => n.toString(16).padStart(2, "0");
1764
+ return `#${hex(mix(r1, r2))}${hex(mix(g1, g2))}${hex(mix(b1, b2))}`;
1765
+ };
1766
+ var skeletonCellColor = (cell, frame, width, rowOffset = 0) => {
1767
+ const cells = Math.max(0, Math.floor(width));
1768
+ const travel = cells + LEAD * 2;
1769
+ const cycle = travel + PAUSE_FRAMES;
1770
+ const phase = ((frame - rowOffset) % cycle + cycle) % cycle;
1771
+ if (phase >= travel) return colors.track;
1772
+ const distance = Math.abs(cell - (phase - LEAD));
1773
+ if (distance <= CREST_HALF) return colors.trackHighlight;
1774
+ if (distance <= CREST_HALF + FALLOFF)
1775
+ return blendHex(
1776
+ colors.track,
1777
+ colors.trackHighlight,
1778
+ (CREST_HALF + FALLOFF + 1 - distance) / (FALLOFF + 1)
1779
+ );
1780
+ return colors.track;
1781
+ };
1782
+ var skeletonMotionEnabled = (animate) => {
1783
+ if (!animate) return false;
1784
+ if (process.env["NO_MOTION"] || process.env["REDUCE_MOTION"]) return false;
1785
+ return process.stdout?.isTTY === true;
1786
+ };
1787
+ var SkeletonBar = ({
1788
+ width,
1789
+ animate = true,
1790
+ frame,
1791
+ rowOffset = 0
1792
+ }) => {
1793
+ const cells = Math.max(0, Math.floor(width));
1794
+ const controlled = frame !== void 0;
1795
+ const active = !controlled && skeletonMotionEnabled(animate);
1796
+ const [ownFrame, setOwnFrame] = useState(0);
1797
+ useEffect(() => {
1798
+ if (!active) return;
1799
+ const timer = setInterval(
1800
+ () => setOwnFrame((f) => f + 1),
1801
+ SHIMMER_FRAME_MS
1802
+ );
1803
+ return () => clearInterval(timer);
1804
+ }, [active]);
1805
+ if (!controlled && !active)
1806
+ return /* @__PURE__ */ jsx(Text, { color: colors.track, children: symbols.barFull.repeat(cells) });
1807
+ const at = controlled ? frame : ownFrame;
1808
+ return /* @__PURE__ */ jsx(Text, { children: Array.from({ length: cells }, (_, i) => /* @__PURE__ */ jsx(Text, { color: skeletonCellColor(i, at, cells, rowOffset), children: symbols.barFull }, i)) });
1809
+ };
1810
+ var SkeletonRows = ({
1811
+ rows,
1812
+ widths,
1813
+ gapEvery,
1814
+ indent = 0,
1815
+ width,
1816
+ animate = true
1817
+ }) => {
1818
+ const ref = useRef(null);
1819
+ const [measured, setMeasured] = useState(0);
1820
+ useWindowSize();
1821
+ useLayoutEffect(() => {
1822
+ if (width !== void 0 || !ref.current) return;
1823
+ const { width: w } = measureElement(ref.current);
1824
+ if (w !== measured) setMeasured(w);
1825
+ });
1826
+ const active = skeletonMotionEnabled(animate);
1827
+ const [frame, setFrame] = useState(0);
1828
+ useEffect(() => {
1829
+ if (!active) return;
1830
+ const timer = setInterval(() => setFrame((f) => f + 1), SHIMMER_FRAME_MS);
1831
+ return () => clearInterval(timer);
1832
+ }, [active]);
1833
+ const available = Math.max(0, (width ?? measured) - indent);
1834
+ const lines = [];
1835
+ for (let i = 0; i < rows; i++) {
1836
+ const fraction = widths.length ? widths[i % widths.length] : 1;
1837
+ lines.push(Math.round(Math.max(0, Math.min(1, fraction)) * available));
1838
+ const closesGroup = gapEvery !== void 0 && gapEvery > 0 && (i + 1) % gapEvery === 0;
1839
+ if (closesGroup && i < rows - 1) lines.push(null);
1840
+ }
1841
+ let bar = 0;
1842
+ return /* @__PURE__ */ jsx(Box, { ref, flexDirection: "column", children: lines.map((cells, i) => {
1843
+ if (cells === null)
1844
+ return /* @__PURE__ */ jsx(Text, { children: " " }, i);
1845
+ const rowOffset = bar++ * ROW_LAG;
1846
+ return /* @__PURE__ */ jsxs(Text, { children: [
1847
+ " ".repeat(indent),
1848
+ /* @__PURE__ */ jsx(
1849
+ SkeletonBar,
1850
+ {
1851
+ width: cells,
1852
+ animate,
1853
+ frame: active ? frame : void 0,
1854
+ rowOffset
1855
+ }
1856
+ )
1857
+ ] }, i);
1858
+ }) });
1859
+ };
1860
+ var Alert = ({ variant = "info", title, children }) => {
1861
+ const { icon, color } = statusVariant(variant);
1862
+ return /* @__PURE__ */ jsxs(
1863
+ Box,
1864
+ {
1865
+ borderStyle: borders.base,
1866
+ borderColor: color,
1867
+ paddingX: spacing.xs,
1868
+ flexDirection: "column",
1869
+ children: [
1870
+ /* @__PURE__ */ jsxs(Box, { gap: spacing.xs, children: [
1871
+ /* @__PURE__ */ jsx(Text, { color, children: icon }),
1872
+ title ? /* @__PURE__ */ jsx(Text, { bold: true, color, children: title }) : null
1873
+ ] }),
1874
+ /* @__PURE__ */ jsx(Text, { children })
1875
+ ]
1876
+ }
1877
+ );
1878
+ };
1879
+ var Toast = ({
1880
+ message,
1881
+ variant = "info",
1882
+ duration = 2e3,
1883
+ onDone
1884
+ }) => {
1885
+ const [visible, setVisible] = useState(true);
1886
+ useEffect(() => {
1887
+ const timer = setTimeout(() => {
1888
+ setVisible(false);
1889
+ onDone?.();
1890
+ }, duration);
1891
+ return () => clearTimeout(timer);
1892
+ }, [duration]);
1893
+ if (!visible) return null;
1894
+ const { icon, color } = statusVariant(variant);
1895
+ return /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
1896
+ /* @__PURE__ */ jsx(Text, { color, children: icon }),
1897
+ /* @__PURE__ */ jsx(Text, { children: message })
1898
+ ] });
1899
+ };
1900
+ var escape = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1901
+ var buildAppleScript = (message, options = {}) => {
1902
+ const clauses = [
1903
+ options.title && `title "${escape(options.title)}"`,
1904
+ options.subtitle && `subtitle "${escape(options.subtitle)}"`,
1905
+ options.sound && `sound name "${escape(typeof options.sound === "string" ? options.sound : "default")}"`
1906
+ ].filter(Boolean);
1907
+ const suffix = clauses.length > 0 ? ` with ${clauses.join(" ")}` : "";
1908
+ return `display notification "${escape(message)}"${suffix}`;
1909
+ };
1910
+ var notify = (message, options = {}) => {
1911
+ if (process.platform !== "darwin") return;
1912
+ const child = spawn("osascript", ["-e", buildAppleScript(message, options)], {
1913
+ stdio: "ignore"
1914
+ });
1915
+ child.on("error", () => {
1916
+ });
1917
+ child.unref();
1918
+ };
1919
+
1920
+ export { Alert, Badge, Banner, Columns, CommandPalette, ConfirmInput, EmailInput, FilterBar, FooterHints, Header, KeyValue, LoadingScreen, MultiSelect, OrderedList, PAGE_CHROME, Page, Panel, PasswordInput, Pill, ProgressBar, ScrollView, Select, SelectableRow, SkeletonBar, SkeletonRows, Spinner, StatusMessage, Step, Stepper, StepperNavigation, StepperProgress, Switch, Table, Tabs, TextInput, Toast, Toggle, ToggleSwitch, UnorderedList, UpdateBanner, asciiSymbols, borders, colors, fuzzyFilter, getIconMode, motion, notify, palette, pillWidth, priorityColors, setIconMode, softColors, spacing, symbols, tailHints, tonalColors, tonalStrongColors, useAppKeys, useFilterMode, useListCursor, useStepper, useTabs };