github-search-cli 9.4.2 → 9.4.4

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/README.md +8 -3
  3. package/bin/ghfind.js +98 -89
  4. package/completions/ghfind.bash +1 -1
  5. package/completions/ghfind.fish +3 -1
  6. package/completions/ghfind.zsh +6 -0
  7. package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
  8. package/dist/cli.js +761 -342
  9. package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
  10. package/package.json +6 -3
  11. package/release-notes/9.3.0.md +32 -29
  12. package/scripts/build.ts +34 -19
  13. package/scripts/generate-svg-demo.js +218 -168
  14. package/scripts/postinstall.js +176 -176
  15. package/src/bookmarks.ts +69 -69
  16. package/src/cli.ts +17 -1
  17. package/src/compare.ts +69 -47
  18. package/src/config.ts +70 -65
  19. package/src/deepdive.ts +149 -118
  20. package/src/doctor.ts +287 -287
  21. package/src/errors.ts +62 -59
  22. package/src/explore.ts +34 -29
  23. package/src/export.ts +73 -63
  24. package/src/help.ts +157 -157
  25. package/src/history.ts +69 -55
  26. package/src/init.ts +60 -55
  27. package/src/landing.ts +106 -104
  28. package/src/login.ts +186 -0
  29. package/src/markdown-render.ts +1 -6
  30. package/src/notifications.ts +75 -59
  31. package/src/open-url.ts +16 -16
  32. package/src/org.ts +43 -15
  33. package/src/output.ts +153 -137
  34. package/src/package-browser.ts +317 -305
  35. package/src/package.ts +149 -137
  36. package/src/pipe.ts +60 -54
  37. package/src/releases.ts +182 -144
  38. package/src/saved-searches.ts +72 -50
  39. package/src/search-state.ts +283 -229
  40. package/src/search.ts +678 -653
  41. package/src/session.ts +26 -24
  42. package/src/share.ts +61 -54
  43. package/src/status.ts +68 -61
  44. package/src/storage.ts +136 -106
  45. package/src/themes.ts +259 -116
  46. package/src/tips.ts +42 -42
  47. package/src/toast.ts +70 -69
  48. package/src/trending-parser.ts +104 -0
  49. package/src/trending.ts +481 -462
  50. package/src/tui.ts +149 -153
  51. package/src/types.ts +160 -158
  52. package/src/watch.ts +95 -74
package/src/themes.ts CHANGED
@@ -1,116 +1,259 @@
1
- /**
2
- * Theme system — Premium palettes with nuanced color layers.
3
- * Each theme provides a rich color hierarchy for premium visual depth.
4
- */
5
- export interface Theme {
6
- name: string;
7
- /** Primary background — deepest layer */
8
- bg: string;
9
- /** Secondary surface — cards, panels, elevated areas */
10
- surface: string;
11
- /** Tertiary surface — subtle hover/focus states */
12
- surfaceAlt: string;
13
- /** Primary text — highest contrast */
14
- text: string;
15
- /** Secondary text — labels, metadata */
16
- muted: string;
17
- /** Accent colors for semantic meaning */
18
- blue: string;
19
- green: string;
20
- yellow: string;
21
- red: string;
22
- teal: string;
23
- purple: string;
24
- orange: string;
25
- /** Border and divider color */
26
- border: string;
27
- /** Subtle border for inner dividers */
28
- borderAlt: string;
29
- /** Darkest thin colorblock - thicker than border, acts as visual separator */
30
- separator: string;
31
- /** Selection/hover background */
32
- selectionBg: string;
33
- /** Selection/hover text */
34
- selectionText: string;
35
- /** Success state color */
36
- success: string;
37
- /** Warning state color */
38
- warning: string;
39
- /** Error state color */
40
- error: string;
41
- /** Modal/overlay panel background (darker than surface) */
42
- surfaceDim: string;
43
- /** Primary accent for titles and highlights */
44
- accent: string;
45
- /** Dimmed accent for secondary accents */
46
- accentDim: string;
47
- /** Border for floating/modal panels */
48
- borderAccent: string;
49
- }
50
-
51
- const THEMES: Record<string, Theme> = {
52
- "tokyo-night": {
53
- name: "tokyo-night",
54
- // Genuine Tokyo Night palette — cool blue-violet mood
55
- bg: "#1a1b26",
56
- surface: "#24283b",
57
- surfaceAlt: "#292e42",
58
- text: "#c0caf5",
59
- muted: "#565f89",
60
- blue: "#7aa2f7",
61
- green: "#9ece6a",
62
- yellow: "#e0af68",
63
- red: "#f7768e",
64
- teal: "#73daca",
65
- purple: "#bb9af7",
66
- orange: "#ff9e64",
67
- border: "#24283b",
68
- borderAlt: "#1f2335",
69
- separator: "#1f2335",
70
- selectionBg: "#2d3f76",
71
- selectionText: "#c0caf5",
72
- success: "#9ece6a",
73
- warning: "#e0af68",
74
- error: "#f7768e",
75
- surfaceDim: "#16161e",
76
- accent: "#7aa2f7",
77
- accentDim: "#5d7abf",
78
- borderAccent: "#1f2335",
79
- },
80
- "premium-dark": {
81
- name: "premium-dark",
82
- // Warm counter-mood to Tokyo Night — amber/rust accent
83
- bg: "#1e1e2e",
84
- surface: "#262637",
85
- surfaceAlt: "#2e2e42",
86
- text: "#cdd6f4",
87
- muted: "#6c7086",
88
- blue: "#89b4fa",
89
- green: "#a6e3a1",
90
- yellow: "#f9e2af",
91
- red: "#f38ba8",
92
- teal: "#94e2d5",
93
- purple: "#cba6f7",
94
- orange: "#fab387",
95
- border: "#313244",
96
- borderAlt: "#2a2a3d",
97
- separator: "#2a2a3d",
98
- selectionBg: "#45475a",
99
- selectionText: "#cdd6f4",
100
- success: "#a6e3a1",
101
- warning: "#f9e2af",
102
- error: "#f38ba8",
103
- surfaceDim: "#181825",
104
- accent: "#f9e2af",
105
- accentDim: "#c9a87a",
106
- borderAccent: "#313244",
107
- },
108
- };
109
-
110
- export function loadTheme(name: string): Theme {
111
- return THEMES[name] ?? THEMES["tokyo-night"];
112
- }
113
-
114
- export function listThemes(): string[] {
115
- return Object.keys(THEMES);
116
- }
1
+ /**
2
+ * Theme system — Premium palettes with nuanced color layers.
3
+ * Each theme provides a rich color hierarchy for premium visual depth.
4
+ */
5
+ export interface Theme {
6
+ name: string;
7
+ /** Primary background — deepest layer */
8
+ bg: string;
9
+ /** Secondary surface — cards, panels, elevated areas */
10
+ surface: string;
11
+ /** Tertiary surface — subtle hover/focus states */
12
+ surfaceAlt: string;
13
+ /** Primary text — highest contrast */
14
+ text: string;
15
+ /** Secondary text — labels, metadata */
16
+ muted: string;
17
+ /** Accent colors for semantic meaning */
18
+ blue: string;
19
+ green: string;
20
+ yellow: string;
21
+ red: string;
22
+ teal: string;
23
+ purple: string;
24
+ orange: string;
25
+ /** Border and divider color */
26
+ border: string;
27
+ /** Subtle border for inner dividers */
28
+ borderAlt: string;
29
+ /** Darkest thin colorblock - thicker than border, acts as visual separator */
30
+ separator: string;
31
+ /** Selection/hover background */
32
+ selectionBg: string;
33
+ /** Selection/hover text */
34
+ selectionText: string;
35
+ /** Success state color */
36
+ success: string;
37
+ /** Warning state color */
38
+ warning: string;
39
+ /** Error state color */
40
+ error: string;
41
+ /** Modal/overlay panel background (darker than surface) */
42
+ surfaceDim: string;
43
+ /** Primary accent for titles and highlights */
44
+ accent: string;
45
+ /** Dimmed accent for secondary accents */
46
+ accentDim: string;
47
+ /** Border for floating/modal panels */
48
+ borderAccent: string;
49
+ }
50
+
51
+ const THEMES: Record<string, Theme> = {
52
+ "tokyo-night": {
53
+ name: "tokyo-night",
54
+ // Genuine Tokyo Night palette — cool blue-violet mood
55
+ bg: "#1a1b26",
56
+ surface: "#24283b",
57
+ surfaceAlt: "#292e42",
58
+ text: "#c0caf5",
59
+ muted: "#565f89",
60
+ blue: "#7aa2f7",
61
+ green: "#9ece6a",
62
+ yellow: "#e0af68",
63
+ red: "#f7768e",
64
+ teal: "#73daca",
65
+ purple: "#bb9af7",
66
+ orange: "#ff9e64",
67
+ border: "#24283b",
68
+ borderAlt: "#1f2335",
69
+ separator: "#1f2335",
70
+ selectionBg: "#2d3f76",
71
+ selectionText: "#c0caf5",
72
+ success: "#9ece6a",
73
+ warning: "#e0af68",
74
+ error: "#f7768e",
75
+ surfaceDim: "#16161e",
76
+ accent: "#7aa2f7",
77
+ accentDim: "#5d7abf",
78
+ borderAccent: "#1f2335",
79
+ },
80
+ "premium-dark": {
81
+ name: "premium-dark",
82
+ // Warm counter-mood to Tokyo Night — amber/rust accent
83
+ bg: "#1e1e2e",
84
+ surface: "#262637",
85
+ surfaceAlt: "#2e2e42",
86
+ text: "#cdd6f4",
87
+ muted: "#6c7086",
88
+ blue: "#89b4fa",
89
+ green: "#a6e3a1",
90
+ yellow: "#f9e2af",
91
+ red: "#f38ba8",
92
+ teal: "#94e2d5",
93
+ purple: "#cba6f7",
94
+ orange: "#fab387",
95
+ border: "#313244",
96
+ borderAlt: "#2a2a3d",
97
+ separator: "#2a2a3d",
98
+ selectionBg: "#45475a",
99
+ selectionText: "#cdd6f4",
100
+ success: "#a6e3a1",
101
+ warning: "#f9e2af",
102
+ error: "#f38ba8",
103
+ surfaceDim: "#181825",
104
+ accent: "#f9e2af",
105
+ accentDim: "#c9a87a",
106
+ borderAccent: "#313244",
107
+ },
108
+ };
109
+
110
+ export function loadTheme(name: string): Theme {
111
+ return THEMES[name] ?? THEMES["tokyo-night"];
112
+ }
113
+
114
+ export function listThemes(): string[] {
115
+ return Object.keys(THEMES);
116
+ }
117
+
118
+ // ─── Terminal-matched surface blending ────────────────────────────────
119
+ /** Parse a hex color to [r,g,b]; expands 3-digit form (#abc → #aabbcc). */
120
+ function parseHex(hex: string): [number, number, number] | null {
121
+ let h = hex.trim().replace(/^#/, "");
122
+ if (h.length === 3)
123
+ h = h
124
+ .split("")
125
+ .map((c) => c + c)
126
+ .join("");
127
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
128
+ return [
129
+ parseInt(h.slice(0, 2), 16),
130
+ parseInt(h.slice(2, 4), 16),
131
+ parseInt(h.slice(4, 6), 16),
132
+ ];
133
+ }
134
+
135
+ const toHex2 = (n: number) => n.toString(16).padStart(2, "0");
136
+
137
+ /** Linear blend between two hex colors; t=0 → a, t=1 → b. */
138
+ export function mixHex(a: string, b: string, t: number): string {
139
+ const ca = parseHex(a);
140
+ const cb = parseHex(b);
141
+ if (!ca || !cb) return a;
142
+ const mix = (x: number, y: number) => Math.round(x + (y - x) * t);
143
+ return `#${toHex2(mix(ca[0], cb[0]))}${toHex2(mix(ca[1], cb[1]))}${toHex2(mix(ca[2], cb[2]))}`;
144
+ }
145
+
146
+ /** Perceived luminance (0–1); < 0.5 reads as a dark terminal background. */
147
+ export function isDarkHex(hex: string): boolean {
148
+ const c = parseHex(hex);
149
+ if (!c) return false;
150
+ return (0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]) / 255 < 0.5;
151
+ }
152
+
153
+ /**
154
+ * Derive card/surface/border layer colors from the terminal background so
155
+ * panels blend into the user's terminal instead of fighting it. Layers are
156
+ * the background lightened by small steps (and surfaceDim darkened, for
157
+ * sunken modals). Returns null for light backgrounds, where the dark-theme
158
+ * chrome wouldn't work — callers should keep the theme's own layers then.
159
+ */
160
+ export function deriveSurfaceLayers(
161
+ bgHex: string,
162
+ ): Record<string, string> | null {
163
+ if (!isDarkHex(bgHex)) return null;
164
+ const w = "#ffffff";
165
+ return {
166
+ surface: mixHex(bgHex, w, 0.045),
167
+ surfaceAlt: mixHex(bgHex, w, 0.07),
168
+ // Sunken modal/overlay panels (toast etc.) — darker than the backdrop
169
+ surfaceDim: mixHex(bgHex, "#000000", 0.15),
170
+ border: mixHex(bgHex, w, 0.09),
171
+ borderAlt: mixHex(bgHex, w, 0.055),
172
+ separator: mixHex(bgHex, w, 0.055),
173
+ borderAccent: mixHex(bgHex, w, 0.065),
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Parse an OSC color reply spec into #rrggbb.
179
+ * Handles `rgb:RR/GG/BB`, `rgb:RRRR/GGGG/BBBB` (values scaled to 8-bit),
180
+ * and `rgba:...` (alpha component ignored).
181
+ */
182
+ export function parseOscColor(spec: string): string | null {
183
+ const m = spec
184
+ .trim()
185
+ .match(
186
+ /^(?:rgb|rgba):([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})(?:\/[0-9a-fA-F]{1,4})?$/,
187
+ );
188
+ if (!m) return null;
189
+ // Scale the channel to 8-bit regardless of digit count (1–4 hex digits).
190
+ const chan = (h: string): number => {
191
+ const value = parseInt(h, 16);
192
+ const max = 16 ** h.length - 1;
193
+ return Math.round((value * 255) / max);
194
+ };
195
+ const hex = (n: number) => n.toString(16).padStart(2, "0");
196
+ return `#${hex(chan(m[1]))}${hex(chan(m[2]))}${hex(chan(m[3]))}`;
197
+ }
198
+
199
+ /**
200
+ * Query the terminal's actual default background color via OSC 11.
201
+ *
202
+ * Must be called BEFORE the opentui renderer takes over stdin. Enables raw
203
+ * mode, writes `ESC ] 11 ; ? BEL`, and parses the reply (`rgb:R/G/B`, ST- or
204
+ * BEL-terminated). Returns null when the terminal doesn't answer — e.g.
205
+ * piped output, Windows conhost, or terminals without OSC 11 support — in
206
+ * which case callers should fall back to the theme's bg.
207
+ *
208
+ * (Implemented directly rather than via opentui's TerminalPalette: its stdin
209
+ * subscription depends on renderer session machinery that isn't running yet
210
+ * at boot, so it never receives the reply.)
211
+ */
212
+ export async function detectTerminalBackground(
213
+ timeoutMs = 400,
214
+ ): Promise<string | null> {
215
+ const stdin = process.stdin;
216
+ const stdout = process.stdout;
217
+ const setRawMode = (
218
+ stdin as typeof stdin & { setRawMode?: (m: boolean) => unknown }
219
+ ).setRawMode;
220
+ if (!stdout.isTTY || !stdin.isTTY || typeof setRawMode !== "function")
221
+ return null;
222
+
223
+ return new Promise<string | null>((resolve) => {
224
+ const wasRaw = stdin.isRaw === true;
225
+ let settled = false;
226
+ let buffer = "";
227
+
228
+ const finish = (result: string | null) => {
229
+ if (settled) return;
230
+ settled = true;
231
+ clearTimeout(timer);
232
+ stdin.removeListener("data", onData);
233
+ try {
234
+ setRawMode.call(stdin, wasRaw);
235
+ } catch {
236
+ /* best effort restore */
237
+ }
238
+ stdin.pause();
239
+ resolve(result);
240
+ };
241
+
242
+ // Reply: ESC ] 11 ; <spec> (BEL | ESC \)
243
+ const onData = (d: Buffer) => {
244
+ buffer += d.toString("latin1");
245
+ const m = buffer.match(/\x1b\]11;([^\x07\x1b]*)(?:\x07|\x1b\\)/);
246
+ if (m) finish(parseOscColor(m[1]));
247
+ };
248
+
249
+ const timer = setTimeout(() => finish(null), timeoutMs);
250
+ try {
251
+ setRawMode.call(stdin, true);
252
+ stdin.on("data", onData);
253
+ stdin.resume();
254
+ stdout.write("\x1b]11;?\x07");
255
+ } catch {
256
+ finish(null);
257
+ }
258
+ });
259
+ }
package/src/tips.ts CHANGED
@@ -1,42 +1,42 @@
1
- /**
2
- * Startup tips — rotate through helpful hints.
3
- */
4
-
5
- /** Rotates tips without repeating the same one twice in a row. */
6
- export class TipRotator {
7
- private tips: string[];
8
- private lastIndex = -1;
9
-
10
- constructor(tips?: string[]) {
11
- this.tips = tips ?? TIPS;
12
- }
13
-
14
- next(): string {
15
- let idx: number;
16
- do {
17
- idx = Math.floor(Math.random() * this.tips.length);
18
- } while (idx === this.lastIndex && this.tips.length > 1);
19
- this.lastIndex = idx;
20
- return this.tips[idx];
21
- }
22
- }
23
-
24
- const TIPS = [
25
- "💡 Tip: Press ? to see all keyboard shortcuts",
26
- "💡 Tip: Use Ctrl+R to recall past searches",
27
- "💡 Tip: Press b to bookmark a repository",
28
- "💡 Tip: Tab auto-completes qualifiers like language: and stars:",
29
- "💡 Tip: Press Ctrl+E to export results as CSV or Markdown",
30
- "💡 Tip: Set GITHUB_TOKEN for higher API rate limits",
31
- "💡 Tip: Press c to compare up to 5 repos side-by-side",
32
- "💡 Tip: Press d for a deep-dive into any repo",
33
- "💡 Tip: Press E to browse popular GitHub topics",
34
- "💡 Tip: Press Ctrl+P to copy a repo link to clipboard",
35
- ];
36
-
37
- export const defaultRotator = new TipRotator();
38
-
39
- /** Get the next tip (never repeats the same tip twice in a row). */
40
- export function nextTip(): string {
41
- return defaultRotator.next();
42
- }
1
+ /**
2
+ * Startup tips — rotate through helpful hints.
3
+ */
4
+
5
+ /** Rotates tips without repeating the same one twice in a row. */
6
+ export class TipRotator {
7
+ private tips: string[];
8
+ private lastIndex = -1;
9
+
10
+ constructor(tips?: string[]) {
11
+ this.tips = tips ?? TIPS;
12
+ }
13
+
14
+ next(): string {
15
+ let idx: number;
16
+ do {
17
+ idx = Math.floor(Math.random() * this.tips.length);
18
+ } while (idx === this.lastIndex && this.tips.length > 1);
19
+ this.lastIndex = idx;
20
+ return this.tips[idx];
21
+ }
22
+ }
23
+
24
+ const TIPS = [
25
+ "💡 Tip: Press ? to see all keyboard shortcuts",
26
+ "💡 Tip: Use Ctrl+R to recall past searches",
27
+ "💡 Tip: Press b to bookmark a repository",
28
+ "💡 Tip: Tab auto-completes qualifiers like language: and stars:",
29
+ "💡 Tip: Press Ctrl+E to export results as CSV or Markdown",
30
+ "💡 Tip: Set GITHUB_TOKEN for higher API rate limits",
31
+ "💡 Tip: Press c to compare up to 5 repos side-by-side",
32
+ "💡 Tip: Press d for a deep-dive into any repo",
33
+ "💡 Tip: Press E to browse popular GitHub topics",
34
+ "💡 Tip: Press Ctrl+P to copy a repo link to clipboard",
35
+ ];
36
+
37
+ export const defaultRotator = new TipRotator();
38
+
39
+ /** Get the next tip (never repeats the same tip twice in a row). */
40
+ export function nextTip(): string {
41
+ return defaultRotator.next();
42
+ }
package/src/toast.ts CHANGED
@@ -1,69 +1,70 @@
1
- /** Toast popup — absolute-positioned card pinned top-right, auto-hides. */
2
- import { BoxRenderable, TextRenderable } from "@opentui/core";
3
-
4
- /** Only the color keys the toast reads (lazily, so runtime theme swaps apply). */
5
- export interface ToastColors {
6
- green: string;
7
- yellow: string;
8
- text: string;
9
- surfaceDim: string;
10
- }
11
-
12
- export function createToast(
13
- renderer: Parameters<BoxRenderable["constructor"]>[0],
14
- root: { add(child: unknown): unknown },
15
- colors: ToastColors,
16
- ) {
17
- let timer: ReturnType<typeof setTimeout> | undefined;
18
- const box = new BoxRenderable(renderer, {
19
- visible: false,
20
- position: "absolute",
21
- top: 1,
22
- right: 2,
23
- height: 3,
24
- border: true,
25
- borderStyle: "rounded",
26
- borderColor: colors.green,
27
- backgroundColor: colors.surfaceDim,
28
- zIndex: 100,
29
- });
30
- const text = new TextRenderable(renderer, {
31
- content: "",
32
- color: colors.text,
33
- backgroundColor: colors.surfaceDim,
34
- height: 1,
35
- });
36
- box.add(text);
37
- root.add(box);
38
-
39
- return {
40
- /** Show a message; accent picks the border color. Re-showing resets the timer. */
41
- show(msg: string, ms = 2000, accent?: string): void {
42
- text.content = msg;
43
- box.borderColor = accent ?? colors.green;
44
- // Message + paddingX(2) + side borders(2)
45
- box.width = msg.length + 4;
46
- box.visible = true;
47
- renderer.requestRender();
48
- clearTimeout(timer);
49
- timer = setTimeout(() => {
50
- box.visible = false;
51
- renderer.requestRender();
52
- }, ms);
53
- },
54
- hide(): void {
55
- clearTimeout(timer);
56
- if (!box.visible) return;
57
- box.visible = false;
58
- renderer.requestRender();
59
- },
60
- get visible(): boolean {
61
- return box.visible;
62
- },
63
- get borderColor(): string {
64
- return box.borderColor as string;
65
- },
66
- };
67
- }
68
-
69
- export type Toast = ReturnType<typeof createToast>;
1
+ /** Toast popup — absolute-positioned card pinned top-right, auto-hides. */
2
+ import { BoxRenderable, TextRenderable } from "@opentui/core";
3
+ import type { RenderContext } from "@opentui/core";
4
+
5
+ /** Only the color keys the toast reads (lazily, so runtime theme swaps apply). */
6
+ export interface ToastColors {
7
+ green: string;
8
+ yellow: string;
9
+ text: string;
10
+ surfaceDim: string;
11
+ }
12
+
13
+ export function createToast(
14
+ renderer: RenderContext,
15
+ root: { add(child: unknown): unknown },
16
+ colors: ToastColors,
17
+ ) {
18
+ let timer: ReturnType<typeof setTimeout> | undefined;
19
+ const box = new BoxRenderable(renderer, {
20
+ visible: false,
21
+ position: "absolute",
22
+ top: 1,
23
+ right: 2,
24
+ height: 3,
25
+ border: true,
26
+ borderStyle: "rounded",
27
+ borderColor: colors.green,
28
+ backgroundColor: colors.surfaceDim,
29
+ zIndex: 100,
30
+ });
31
+ const text = new TextRenderable(renderer, {
32
+ content: "",
33
+ fg: colors.text,
34
+ bg: colors.surfaceDim,
35
+ height: 1,
36
+ });
37
+ box.add(text);
38
+ root.add(box);
39
+
40
+ return {
41
+ /** Show a message; accent picks the border color. Re-showing resets the timer. */
42
+ show(msg: string, ms = 2000, accent?: string): void {
43
+ text.content = msg;
44
+ box.borderColor = accent ?? colors.green;
45
+ // Message + paddingX(2) + side borders(2)
46
+ box.width = msg.length + 4;
47
+ box.visible = true;
48
+ renderer.requestRender();
49
+ clearTimeout(timer);
50
+ timer = setTimeout(() => {
51
+ box.visible = false;
52
+ renderer.requestRender();
53
+ }, ms);
54
+ },
55
+ hide(): void {
56
+ clearTimeout(timer);
57
+ if (!box.visible) return;
58
+ box.visible = false;
59
+ renderer.requestRender();
60
+ },
61
+ get visible(): boolean {
62
+ return box.visible;
63
+ },
64
+ get borderColor(): string {
65
+ return String(box.borderColor);
66
+ },
67
+ };
68
+ }
69
+
70
+ export type Toast = ReturnType<typeof createToast>;