ansimax 1.2.1 → 1.2.3

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/CHANGELOG.md CHANGED
@@ -3,6 +3,116 @@
3
3
  All notable changes to **ansimax** are documented in this file.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.2.3] — Gradient factory + performance
7
+
8
+ Patch release adding a new performance-oriented API and refinements. No
9
+ breaking changes — every 1.2.x program runs identically.
10
+
11
+ ### Added — `createGradient()` factory
12
+
13
+ A pre-resolved gradient that can be applied repeatedly to different
14
+ strings without re-parsing hex stops on every call. Significantly
15
+ faster for animation loops, frame-based rendering, and bulk colorizing:
16
+
17
+ ```ts
18
+ import { createGradient } from 'ansimax';
19
+
20
+ const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
21
+
22
+ // Reuse — hex stops are pre-resolved
23
+ console.log(fire('first line'));
24
+ console.log(fire('second line'));
25
+
26
+ // Use as colorFn for ascii.banner (matches the ColorFn signature)
27
+ console.log(ascii.banner('FIRE', { colorFn: fire }));
28
+
29
+ // Per-call options still work (especially useful for animation)
30
+ for (let p = 0; p < 1; p += 0.05) {
31
+ process.stdout.write('\r' + fire('flowing', { phase: p }));
32
+ }
33
+ ```
34
+
35
+ **Performance**: hex→RGB conversion happens once at factory time. For
36
+ loops calling `gradient()` hundreds of times per frame, this can cut
37
+ gradient overhead by ~40–60% (depending on stop count).
38
+
39
+ **API surface**:
40
+ - `createGradient(stops, defaultOpts?)` returns `(text, opts?) => string`
41
+ - The returned function matches the `ColorFn` shape (compatible with
42
+ `ascii.banner({ colorFn })`, `themes.gradient`, etc.)
43
+ - Per-call `opts` override defaults; useful for varying `phase` per frame
44
+ while keeping the same colors/easing
45
+
46
+ ### Improved
47
+
48
+ - **More JSDoc on `createGradient`** with three runnable `@example` blocks.
49
+ - All 1880 + 12 new tests pass.
50
+ - No new runtime dependencies — still zero.
51
+
52
+ ---
53
+
54
+ ## [1.2.2] — Quality polish
55
+
56
+ Patch release focused on API ergonomics and robustness refinements. No
57
+ breaking changes — every 1.2.x program runs identically.
58
+
59
+ ### Improved
60
+
61
+ - **`animateGradient` controller is now thenable** — you can `await` it
62
+ directly instead of `await ctrl.done`:
63
+
64
+ ```ts
65
+ // Before (v1.2.0)
66
+ const ctrl = animateGradient(text, stops, { infinite: false, cycles: 1 });
67
+ await ctrl.done;
68
+
69
+ // After (v1.2.2) — both still work
70
+ await animateGradient(text, stops, { infinite: false, cycles: 1 });
71
+
72
+ // Also supports .then() / .catch() / .finally()
73
+ animateGradient(text, stops, opts).finally(() => cleanup());
74
+ ```
75
+
76
+ The `.done` property remains for backwards compatibility.
77
+
78
+ - **Stable error codes for programmatic catch.** Errors thrown by the
79
+ theme system now carry a `.code` property with the `ANSIMAX_*` prefix
80
+ for stable programmatic filtering:
81
+
82
+ ```ts
83
+ try {
84
+ themes.use('not-a-real-theme');
85
+ } catch (e) {
86
+ if ((e as { code?: string }).code === 'ANSIMAX_UNKNOWN_THEME') {
87
+ // handle gracefully
88
+ }
89
+ }
90
+ ```
91
+
92
+ New codes: `ANSIMAX_INVALID_THEME`, `ANSIMAX_INVALID_THEME_NAME`,
93
+ `ANSIMAX_UNKNOWN_THEME`. Error messages and types
94
+ (`TypeError` / `RangeError`) are unchanged.
95
+
96
+ - **`animateGradient` is safer in sandboxed environments.** The default
97
+ stdout-write path now checks `process?.stdout?.write` before calling
98
+ it, so the function no longer crashes in workers, Edge runtimes, or
99
+ embedded sandboxes that lack a writable stdout.
100
+
101
+ - **Better JSDoc on the `gradient()` function.** IntelliSense now shows
102
+ parameter descriptions, return semantics, and three runnable
103
+ `@example` blocks (basic, with easing, with phase animation).
104
+
105
+ - **README fix: `Easing Curves` snippet.** The v1.2.0 snippet was missing
106
+ the `stops` variable declaration. Now copy-paste runnable.
107
+
108
+ ### Notes
109
+
110
+ - 1880 + 7 new tests pass.
111
+ - No new dependencies — still zero runtime deps.
112
+ - All API additions are non-breaking and side-effect-free for existing code.
113
+
114
+ ---
115
+
6
116
  ## [1.2.0] — Phase 2 complete: animated, eased & conic gradients
7
117
 
8
118
  Minor release closing the **gradient engine roadmap (Phase 2)** with three
package/README.es.md CHANGED
@@ -7,7 +7,7 @@
7
7
  _Colores • Gradientes • Animaciones • ASCII Art • Pixel Art • Árboles • Componentes • Temas_
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.2.0-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.2.3-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
13
  [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -216,10 +216,17 @@ const ctrl = animateGradient('Cargando...', ['#ff79c6', '#bd93f9', '#8be9fd'], {
216
216
 
217
217
  await sleep(3000);
218
218
  ctrl.stop();
219
+
220
+ // v1.2.2: await directo (no se necesita .done)
221
+ await animateGradient('¡Listo!', ['#50fa7b', '#bd93f9'], {
222
+ infinite: false, cycles: 2, duration: 800,
223
+ });
219
224
  ```
220
225
 
221
226
  ### Curvas de interpolación (v1.2.0)
222
227
 
228
+ <img src="media/easing_curves.png" alt="Colors and gradients" />
229
+
223
230
  ```ts
224
231
  import { gradient } from 'ansimax';
225
232
 
@@ -238,6 +245,8 @@ console.log(gradient('hola mundo', stops, { easing: (t) => t * t * t }));
238
245
 
239
246
  ### Gradientes cónicos (v1.2.0)
240
247
 
248
+ <img src="media/conic_gradients.png" alt="Colors and gradients" />
249
+
241
250
  ```ts
242
251
  import { gradientRect } from 'ansimax';
243
252
 
@@ -251,6 +260,28 @@ console.log(gradientRect({
251
260
  }));
252
261
  ```
253
262
 
263
+ ### Gradientes reusables (v1.2.3)
264
+
265
+ ```ts
266
+ import { createGradient, ascii } from 'ansimax';
267
+
268
+ // Pre-resuelve los stops de hex una vez — significativamente más rápido para uso repetido
269
+ const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
270
+
271
+ console.log(fire('Primera línea'));
272
+ console.log(fire('Segunda línea'));
273
+ console.log(fire('Tercera línea'));
274
+
275
+ // Úsalo como colorFn para banners — misma firma de ColorFn
276
+ console.log(ascii.banner('FIRE', { colorFn: fire }));
277
+
278
+ // Las opciones por-llamada aún funcionan — perfecto para animación
279
+ for (let p = 0; p < 1; p += 0.05) {
280
+ process.stdout.write('\r' + fire('fluyendo', { phase: p }));
281
+ await new Promise((r) => setTimeout(r, 50));
282
+ }
283
+ ```
284
+
254
285
  ### ASCII Art
255
286
 
256
287
  <img src="media/ascii_art.png" alt="ASCII art" />
@@ -326,7 +357,7 @@ console.log(components.table([
326
357
  ['loaders', color.green('● listo'), '100%'],
327
358
  ], { borderStyle: 'rounded' }));
328
359
 
329
- console.log(components.badge('VERSION', 'v1.2.0'));
360
+ console.log(components.badge('VERSION', 'v1.2.3'));
330
361
  console.log(components.badge('BUILD', 'passing'));
331
362
  ```
332
363
 
@@ -728,6 +759,36 @@ ansimax/
728
759
 
729
760
  ## 📝 Changelog
730
761
 
762
+ ### v1.2.3 — Factory de gradientes + performance
763
+
764
+ Release patch añadiendo una API orientada a performance:
765
+
766
+ - ⚡ **Factory `createGradient()`** — pre-resuelve los stops de hex una vez para reuso, ~40-60% más rápido en loops de animación y colorizado en bulk
767
+ - 📖 Más JSDoc con ejemplos ejecutables
768
+ - 🎯 Coincide con la firma `ColorFn` — funciona como `colorFn` en `ascii.banner`, themes, etc.
769
+
770
+ ```ts
771
+ import { createGradient } from 'ansimax';
772
+
773
+ const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
774
+ console.log(fire('¡Colores reusados!'));
775
+ console.log(ascii.banner('FIRE', { colorFn: fire }));
776
+ ```
777
+
778
+ Drop-in replacement para `1.2.2`.
779
+
780
+ ### v1.2.2 — Pulido de calidad
781
+
782
+ Release patch enfocado en ergonomía de API y refinamientos de robustez.
783
+
784
+ - ✨ **`animateGradient` ahora es thenable** — `await animateGradient(...)` directo sin `.done`
785
+ - 🎯 **Códigos de error estables** — los errores de tema llevan propiedad `.code` (`ANSIMAX_UNKNOWN_THEME`, etc.) para `catch` programático
786
+ - 🛡️ **Seguridad en sandboxes** — `animateGradient` ya no crashea cuando `process.stdout` no está disponible (workers, edge runtimes)
787
+ - 📖 **Mejor JSDoc** — `gradient()` ahora muestra IntelliSense completo con ejemplos
788
+ - 🐛 **Fix de README** — snippet de Easing Curves ahora es ejecutable copy-paste
789
+
790
+ Drop-in replacement para `1.2.0`.
791
+
731
792
  ### v1.2.0 — Fase 2 completa: gradientes animados, easing y cónicos
732
793
 
733
794
  Release minor que cierra el roadmap del motor de gradientes con tres features potentes:
@@ -862,4 +923,4 @@ Ansimax está licenciada bajo **Apache License, Version 2.0** — una licencia p
862
923
 
863
924
  Si Ansimax te ayuda a hacer mejores CLIs, ¡dale ⭐ en [GitHub](https://github.com/Brashkie/ansimax)!
864
925
 
865
- </div>
926
+ </div>
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  _Colors • Gradients • Animations • ASCII Art • Pixel Art • Trees • Components • Themes_
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.2.0-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.2.3-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
13
  [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -216,6 +216,11 @@ const ctrl = animateGradient('Loading...', ['#ff79c6', '#bd93f9', '#8be9fd'], {
216
216
 
217
217
  await sleep(3000);
218
218
  ctrl.stop();
219
+
220
+ // v1.2.2: await directly (no .done needed)
221
+ await animateGradient('Done!', ['#50fa7b', '#bd93f9'], {
222
+ infinite: false, cycles: 2, duration: 800,
223
+ });
219
224
  ```
220
225
 
221
226
  ### Easing Curves (v1.2.0)
@@ -255,6 +260,28 @@ console.log(gradientRect({
255
260
  }));
256
261
  ```
257
262
 
263
+ ### Reusable Gradients (v1.2.3)
264
+
265
+ ```ts
266
+ import { createGradient, ascii } from 'ansimax';
267
+
268
+ // Pre-resolve hex stops once — significantly faster for repeated use
269
+ const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
270
+
271
+ console.log(fire('First line'));
272
+ console.log(fire('Second line'));
273
+ console.log(fire('Third line'));
274
+
275
+ // Use as a colorFn for banners — same ColorFn signature
276
+ console.log(ascii.banner('FIRE', { colorFn: fire }));
277
+
278
+ // Per-call options still work — perfect for animation
279
+ for (let p = 0; p < 1; p += 0.05) {
280
+ process.stdout.write('\r' + fire('flowing', { phase: p }));
281
+ await new Promise((r) => setTimeout(r, 50));
282
+ }
283
+ ```
284
+
258
285
  ### ASCII Art
259
286
 
260
287
  <img src="media/ascii_art.png" alt="ASCII art" />
@@ -330,7 +357,7 @@ console.log(components.table([
330
357
  ['loaders', color.green('● ready'), '100%'],
331
358
  ], { borderStyle: 'rounded' }));
332
359
 
333
- console.log(components.badge('VERSION', 'v1.2.0'));
360
+ console.log(components.badge('VERSION', 'v1.2.3'));
334
361
  console.log(components.badge('BUILD', 'passing'));
335
362
  ```
336
363
 
@@ -732,6 +759,36 @@ ansimax/
732
759
 
733
760
  ## 📝 Changelog
734
761
 
762
+ ### v1.2.3 — Gradient factory + performance
763
+
764
+ Patch release adding a performance-oriented API:
765
+
766
+ - ⚡ **`createGradient()` factory** — pre-resolves hex stops once for reuse, ~40-60% faster for animation loops and bulk colorizing
767
+ - 📖 More JSDoc with runnable examples
768
+ - 🎯 Matches the `ColorFn` signature — works as `colorFn` in `ascii.banner`, themes, etc.
769
+
770
+ ```ts
771
+ import { createGradient } from 'ansimax';
772
+
773
+ const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
774
+ console.log(fire('Reused colors!'));
775
+ console.log(ascii.banner('FIRE', { colorFn: fire }));
776
+ ```
777
+
778
+ Drop-in replacement for `1.2.2`.
779
+
780
+ ### v1.2.2 — Quality polish
781
+
782
+ Patch release focused on API ergonomics and robustness refinements.
783
+
784
+ - ✨ **`animateGradient` is now thenable** — `await animateGradient(...)` directly without `.done`
785
+ - 🎯 **Stable error codes** — theme errors carry `.code` properties (`ANSIMAX_UNKNOWN_THEME`, etc.) for programmatic catch
786
+ - 🛡️ **Sandbox safety** — `animateGradient` no longer crashes when `process.stdout` is unavailable (workers, edge runtimes)
787
+ - 📖 **Better JSDoc** — `gradient()` now shows full IntelliSense with examples
788
+ - 🐛 **README fix** — Easing Curves snippet now copy-paste runnable
789
+
790
+ Drop-in replacement for `1.2.0`.
791
+
735
792
  ### v1.2.0 — Phase 2 complete: animated, eased & conic gradients
736
793
 
737
794
  Minor release closing the gradient engine roadmap with three powerful features:
@@ -866,4 +923,4 @@ Ansimax is licensed under the **Apache License, Version 2.0** — a permissive l
866
923
 
867
924
  If Ansimax helps you ship better CLIs, give it a ⭐ on [GitHub](https://github.com/Brashkie/ansimax)!
868
925
 
869
- </div>
926
+ </div>
package/dist/index.d.mts CHANGED
@@ -565,7 +565,64 @@ interface GradientOptions {
565
565
  */
566
566
  phase?: number;
567
567
  }
568
+ /**
569
+ * Apply a multi-stop color gradient across the visible characters of `text`.
570
+ * ANSI escapes are skipped, and Unicode wide characters / graphemes are
571
+ * counted correctly.
572
+ *
573
+ * @param text - The text to colorize. Non-string inputs are coerced via `String(text)`.
574
+ * @param stops - Array of hex colors (`#rgb` or `#rrggbb`). At least 1 required.
575
+ * Invalid stops are silently dropped; an empty array returns input as-is.
576
+ * @param opts - Optional configuration. See `GradientOptions`.
577
+ *
578
+ * @returns ANSI-colorized string. Returns input unchanged if `NO_COLOR` is set
579
+ * or if no valid stops are provided.
580
+ *
581
+ * @example
582
+ * ```ts
583
+ * gradient('Hello world!', ['#ff0000', '#00ff00', '#0000ff']);
584
+ * // → ANSI-colored "Hello world!" with smooth color transition
585
+ * ```
586
+ *
587
+ * @example with easing curve
588
+ * ```ts
589
+ * gradient('Smooth text', ['#ff79c6', '#8be9fd'], { easing: 'ease-in-out' });
590
+ * ```
591
+ *
592
+ * @example animated phase shift
593
+ * ```ts
594
+ * setInterval(() => {
595
+ * const phase = (Date.now() / 1000) % 1;
596
+ * process.stdout.write('\r' + gradient('flowing', ['#f00', '#00f'], { phase }));
597
+ * }, 33);
598
+ * ```
599
+ */
568
600
  declare const gradient: (text: unknown, stops: string[] | null | undefined, opts?: GradientOptions) => string;
601
+ /**
602
+ * A pre-resolved gradient that can be applied repeatedly to different
603
+ * strings without re-parsing hex stops. Useful for hot loops, animation
604
+ * frames, or any case where the same color palette colorizes many texts.
605
+ *
606
+ * The returned function accepts the same per-call options as `gradient()`
607
+ * (e.g. you can still override `phase` per call for animation).
608
+ *
609
+ * @example
610
+ * ```ts
611
+ * const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
612
+ *
613
+ * console.log(fire('first line'));
614
+ * console.log(fire('second line'));
615
+ *
616
+ * // Use as colorFn for ascii.banner
617
+ * console.log(ascii.banner('FIRE', { colorFn: fire }));
618
+ *
619
+ * // Animate by overriding phase per frame
620
+ * for (let p = 0; p < 1; p += 0.05) {
621
+ * console.log(fire('flowing', { phase: p }));
622
+ * }
623
+ * ```
624
+ */
625
+ declare const createGradient: (stops: string[] | null | undefined, defaultOpts?: Omit<GradientOptions, "phase">) => ((text: unknown, opts?: GradientOptions) => string);
569
626
  declare const rainbow: ColorFn;
570
627
  interface AnimateGradientOptions {
571
628
  /** Total animation duration in ms. Default `2000`. */
@@ -614,6 +671,15 @@ interface AnimateGradientController {
614
671
  stop: () => void;
615
672
  /** Promise that resolves when animation finishes (stop / abort / cycle limit). */
616
673
  done: Promise<void>;
674
+ /**
675
+ * Thenable hook — lets you `await animateGradient(...)` directly.
676
+ * Equivalent to `await ctrl.done`.
677
+ */
678
+ then: Promise<void>['then'];
679
+ /** Equivalent to `ctrl.done.catch(...)`. */
680
+ catch: Promise<void>['catch'];
681
+ /** Equivalent to `ctrl.done.finally(...)`. */
682
+ finally: Promise<void>['finally'];
617
683
  }
618
684
  declare const animateGradient: (text: string, stops: string[], opts?: AnimateGradientOptions) => AnimateGradientController;
619
685
  declare const PRESET_DEFS: {
@@ -1701,4 +1767,4 @@ declare const ansimax: {
1701
1767
  configure: (opts?: AnsimaxConfig, meta?: ConfigureOptions) => void;
1702
1768
  };
1703
1769
 
1704
- export { type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSupport, type ColumnsOptions, type ConfigChangeListener, type ConfigKey, type ConfigKeyListener, type ConfigureOptions, type CountdownOptions, type CustomOptions, DEFAULT_TERM_COLS, DEFAULT_TERM_ROWS, type DebounceOptions, type DiffType, type Dimensions, type DividerOptions, type DotsOptions, ESC, type EasingFn, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type OnResizeOptions, type OutputBuffer, type ParallelOptions, type ParallelStep, type Pixel, type PixelGrid, type PlayController, type PlayOptions, type PresetName, type ProgressAnimateOptions, type ProgressBarOptions, type ProgressOptions, type PulseOptions, type RGB, type RGBA, type RegisterFontOptions, type RenderOptions, type ResizeListener, type RevealOptions, SPINNERS, SPRITES, ST, STYLE, type SectionOptions, type SleepOptions, type SlideOptions, type SpinOptions, type SpinnerType, type StatusOptions, type StatusType, type StopFn, type StreamOptions, type TableBorderStyle, type TableOptions, type Task, type TaskResult, type TasksOptions, type Theme, type BannerOpts as ThemeBannerOpts, type ThemeChangeListener, type ThemeInstance, type ThemeStyleName, type TimelineEvent, type TimelineOptions, type TreeData, type TreeDimensions, type TreeNode, type RenderOptions$1 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center, chain, charWidth, clamp, clearAnsiCache, clearColorCache, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, escapeRegex, fg256, fgRgb, filterTree, findInTree, flipHorizontal, flipVertical, frames, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, graphemes, hasFont, hexToRgb, hideCursor, images, isHexColor, isNoColor, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, measureTree, memoize, nextTick, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, pauseListeners, presetNames, rainbow, registerFont, registerPreset, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resumeListeners, rgbTo256, rgbToHex, rotate90, safeJson, screen, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$1 as stripAnsi, stripAnsi$2 as stripAnsiCodes, stripAnsi as stripAnsiColors, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
1770
+ export { type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSupport, type ColumnsOptions, type ConfigChangeListener, type ConfigKey, type ConfigKeyListener, type ConfigureOptions, type CountdownOptions, type CustomOptions, DEFAULT_TERM_COLS, DEFAULT_TERM_ROWS, type DebounceOptions, type DiffType, type Dimensions, type DividerOptions, type DotsOptions, ESC, type EasingFn, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type OnResizeOptions, type OutputBuffer, type ParallelOptions, type ParallelStep, type Pixel, type PixelGrid, type PlayController, type PlayOptions, type PresetName, type ProgressAnimateOptions, type ProgressBarOptions, type ProgressOptions, type PulseOptions, type RGB, type RGBA, type RegisterFontOptions, type RenderOptions, type ResizeListener, type RevealOptions, SPINNERS, SPRITES, ST, STYLE, type SectionOptions, type SleepOptions, type SlideOptions, type SpinOptions, type SpinnerType, type StatusOptions, type StatusType, type StopFn, type StreamOptions, type TableBorderStyle, type TableOptions, type Task, type TaskResult, type TasksOptions, type Theme, type BannerOpts as ThemeBannerOpts, type ThemeChangeListener, type ThemeInstance, type ThemeStyleName, type TimelineEvent, type TimelineOptions, type TreeData, type TreeDimensions, type TreeNode, type RenderOptions$1 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center, chain, charWidth, clamp, clearAnsiCache, clearColorCache, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createGradient, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, escapeRegex, fg256, fgRgb, filterTree, findInTree, flipHorizontal, flipVertical, frames, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, graphemes, hasFont, hexToRgb, hideCursor, images, isHexColor, isNoColor, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, measureTree, memoize, nextTick, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, pauseListeners, presetNames, rainbow, registerFont, registerPreset, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resumeListeners, rgbTo256, rgbToHex, rotate90, safeJson, screen, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$1 as stripAnsi, stripAnsi$2 as stripAnsiCodes, stripAnsi as stripAnsiColors, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
package/dist/index.d.ts CHANGED
@@ -565,7 +565,64 @@ interface GradientOptions {
565
565
  */
566
566
  phase?: number;
567
567
  }
568
+ /**
569
+ * Apply a multi-stop color gradient across the visible characters of `text`.
570
+ * ANSI escapes are skipped, and Unicode wide characters / graphemes are
571
+ * counted correctly.
572
+ *
573
+ * @param text - The text to colorize. Non-string inputs are coerced via `String(text)`.
574
+ * @param stops - Array of hex colors (`#rgb` or `#rrggbb`). At least 1 required.
575
+ * Invalid stops are silently dropped; an empty array returns input as-is.
576
+ * @param opts - Optional configuration. See `GradientOptions`.
577
+ *
578
+ * @returns ANSI-colorized string. Returns input unchanged if `NO_COLOR` is set
579
+ * or if no valid stops are provided.
580
+ *
581
+ * @example
582
+ * ```ts
583
+ * gradient('Hello world!', ['#ff0000', '#00ff00', '#0000ff']);
584
+ * // → ANSI-colored "Hello world!" with smooth color transition
585
+ * ```
586
+ *
587
+ * @example with easing curve
588
+ * ```ts
589
+ * gradient('Smooth text', ['#ff79c6', '#8be9fd'], { easing: 'ease-in-out' });
590
+ * ```
591
+ *
592
+ * @example animated phase shift
593
+ * ```ts
594
+ * setInterval(() => {
595
+ * const phase = (Date.now() / 1000) % 1;
596
+ * process.stdout.write('\r' + gradient('flowing', ['#f00', '#00f'], { phase }));
597
+ * }, 33);
598
+ * ```
599
+ */
568
600
  declare const gradient: (text: unknown, stops: string[] | null | undefined, opts?: GradientOptions) => string;
601
+ /**
602
+ * A pre-resolved gradient that can be applied repeatedly to different
603
+ * strings without re-parsing hex stops. Useful for hot loops, animation
604
+ * frames, or any case where the same color palette colorizes many texts.
605
+ *
606
+ * The returned function accepts the same per-call options as `gradient()`
607
+ * (e.g. you can still override `phase` per call for animation).
608
+ *
609
+ * @example
610
+ * ```ts
611
+ * const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
612
+ *
613
+ * console.log(fire('first line'));
614
+ * console.log(fire('second line'));
615
+ *
616
+ * // Use as colorFn for ascii.banner
617
+ * console.log(ascii.banner('FIRE', { colorFn: fire }));
618
+ *
619
+ * // Animate by overriding phase per frame
620
+ * for (let p = 0; p < 1; p += 0.05) {
621
+ * console.log(fire('flowing', { phase: p }));
622
+ * }
623
+ * ```
624
+ */
625
+ declare const createGradient: (stops: string[] | null | undefined, defaultOpts?: Omit<GradientOptions, "phase">) => ((text: unknown, opts?: GradientOptions) => string);
569
626
  declare const rainbow: ColorFn;
570
627
  interface AnimateGradientOptions {
571
628
  /** Total animation duration in ms. Default `2000`. */
@@ -614,6 +671,15 @@ interface AnimateGradientController {
614
671
  stop: () => void;
615
672
  /** Promise that resolves when animation finishes (stop / abort / cycle limit). */
616
673
  done: Promise<void>;
674
+ /**
675
+ * Thenable hook — lets you `await animateGradient(...)` directly.
676
+ * Equivalent to `await ctrl.done`.
677
+ */
678
+ then: Promise<void>['then'];
679
+ /** Equivalent to `ctrl.done.catch(...)`. */
680
+ catch: Promise<void>['catch'];
681
+ /** Equivalent to `ctrl.done.finally(...)`. */
682
+ finally: Promise<void>['finally'];
617
683
  }
618
684
  declare const animateGradient: (text: string, stops: string[], opts?: AnimateGradientOptions) => AnimateGradientController;
619
685
  declare const PRESET_DEFS: {
@@ -1701,4 +1767,4 @@ declare const ansimax: {
1701
1767
  configure: (opts?: AnsimaxConfig, meta?: ConfigureOptions) => void;
1702
1768
  };
1703
1769
 
1704
- export { type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSupport, type ColumnsOptions, type ConfigChangeListener, type ConfigKey, type ConfigKeyListener, type ConfigureOptions, type CountdownOptions, type CustomOptions, DEFAULT_TERM_COLS, DEFAULT_TERM_ROWS, type DebounceOptions, type DiffType, type Dimensions, type DividerOptions, type DotsOptions, ESC, type EasingFn, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type OnResizeOptions, type OutputBuffer, type ParallelOptions, type ParallelStep, type Pixel, type PixelGrid, type PlayController, type PlayOptions, type PresetName, type ProgressAnimateOptions, type ProgressBarOptions, type ProgressOptions, type PulseOptions, type RGB, type RGBA, type RegisterFontOptions, type RenderOptions, type ResizeListener, type RevealOptions, SPINNERS, SPRITES, ST, STYLE, type SectionOptions, type SleepOptions, type SlideOptions, type SpinOptions, type SpinnerType, type StatusOptions, type StatusType, type StopFn, type StreamOptions, type TableBorderStyle, type TableOptions, type Task, type TaskResult, type TasksOptions, type Theme, type BannerOpts as ThemeBannerOpts, type ThemeChangeListener, type ThemeInstance, type ThemeStyleName, type TimelineEvent, type TimelineOptions, type TreeData, type TreeDimensions, type TreeNode, type RenderOptions$1 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center, chain, charWidth, clamp, clearAnsiCache, clearColorCache, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, escapeRegex, fg256, fgRgb, filterTree, findInTree, flipHorizontal, flipVertical, frames, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, graphemes, hasFont, hexToRgb, hideCursor, images, isHexColor, isNoColor, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, measureTree, memoize, nextTick, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, pauseListeners, presetNames, rainbow, registerFont, registerPreset, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resumeListeners, rgbTo256, rgbToHex, rotate90, safeJson, screen, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$1 as stripAnsi, stripAnsi$2 as stripAnsiCodes, stripAnsi as stripAnsiColors, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
1770
+ export { type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSupport, type ColumnsOptions, type ConfigChangeListener, type ConfigKey, type ConfigKeyListener, type ConfigureOptions, type CountdownOptions, type CustomOptions, DEFAULT_TERM_COLS, DEFAULT_TERM_ROWS, type DebounceOptions, type DiffType, type Dimensions, type DividerOptions, type DotsOptions, ESC, type EasingFn, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type OnResizeOptions, type OutputBuffer, type ParallelOptions, type ParallelStep, type Pixel, type PixelGrid, type PlayController, type PlayOptions, type PresetName, type ProgressAnimateOptions, type ProgressBarOptions, type ProgressOptions, type PulseOptions, type RGB, type RGBA, type RegisterFontOptions, type RenderOptions, type ResizeListener, type RevealOptions, SPINNERS, SPRITES, ST, STYLE, type SectionOptions, type SleepOptions, type SlideOptions, type SpinOptions, type SpinnerType, type StatusOptions, type StatusType, type StopFn, type StreamOptions, type TableBorderStyle, type TableOptions, type Task, type TaskResult, type TasksOptions, type Theme, type BannerOpts as ThemeBannerOpts, type ThemeChangeListener, type ThemeInstance, type ThemeStyleName, type TimelineEvent, type TimelineOptions, type TreeData, type TreeDimensions, type TreeNode, type RenderOptions$1 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center, chain, charWidth, clamp, clearAnsiCache, clearColorCache, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createGradient, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, escapeRegex, fg256, fgRgb, filterTree, findInTree, flipHorizontal, flipVertical, frames, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, graphemes, hasFont, hexToRgb, hideCursor, images, isHexColor, isNoColor, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, measureTree, memoize, nextTick, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, pauseListeners, presetNames, rainbow, registerFont, registerPreset, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resumeListeners, rgbTo256, rgbToHex, rotate90, safeJson, screen, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$1 as stripAnsi, stripAnsi$2 as stripAnsiCodes, stripAnsi as stripAnsiColors, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
package/dist/index.js CHANGED
@@ -70,6 +70,7 @@ __export(index_exports, {
70
70
  configure: () => configure,
71
71
  countNodes: () => countNodes,
72
72
  createCanvas: () => createCanvas,
73
+ createGradient: () => createGradient,
73
74
  createOutputBuffer: () => createOutputBuffer,
74
75
  createTheme: () => createTheme,
75
76
  cursor: () => cursor,
@@ -1165,6 +1166,28 @@ var gradient = (text, stops, opts = {}) => {
1165
1166
  }
1166
1167
  return _gradientAnsiAware(s, colors, easingFn, phaseN);
1167
1168
  };
1169
+ var createGradient = (stops, defaultOpts = {}) => {
1170
+ const colors = Array.isArray(stops) ? stops.map(safeHex).filter((c) => c !== null) : [];
1171
+ const defaultEasingFn = resolveEasing(defaultOpts.easing);
1172
+ const defaultPreserveAnsi = defaultOpts.preserveAnsi ?? false;
1173
+ return (text, opts = {}) => {
1174
+ const s = coerceText(text);
1175
+ if (!s || isNoColor()) return s;
1176
+ if (colors.length === 0) return s;
1177
+ if (colors.length === 1) {
1178
+ const c = colors[0];
1179
+ return adaptiveFg(c.r, c.g, c.b) + s + reset();
1180
+ }
1181
+ const easingFn = opts.easing !== void 0 ? resolveEasing(opts.easing) : defaultEasingFn;
1182
+ const preserveAnsi = opts.preserveAnsi ?? defaultPreserveAnsi;
1183
+ const phase = opts.phase ?? 0;
1184
+ const phaseN = Number.isFinite(phase) ? (phase % 1 + 1) % 1 : 0;
1185
+ if (!preserveAnsi || !s.includes("\x1B")) {
1186
+ return _gradientPlain(s, colors, easingFn, phaseN);
1187
+ }
1188
+ return _gradientAnsiAware(s, colors, easingFn, phaseN);
1189
+ };
1190
+ };
1168
1191
  var _gradientPlain = (text, colors, easingFn, phase) => {
1169
1192
  const chars = [...text];
1170
1193
  const visible = chars.filter((c) => c !== " ").length;
@@ -1273,7 +1296,13 @@ var animateGradient = (text, stops, opts = {}) => {
1273
1296
  if (signal) {
1274
1297
  if (signal.aborted) {
1275
1298
  stop();
1276
- return { stop, done };
1299
+ return {
1300
+ stop,
1301
+ done,
1302
+ then: done.then.bind(done),
1303
+ catch: done.catch.bind(done),
1304
+ finally: done.finally.bind(done)
1305
+ };
1277
1306
  }
1278
1307
  signal.addEventListener("abort", stop, { once: true });
1279
1308
  }
@@ -1288,7 +1317,9 @@ var animateGradient = (text, stops, opts = {}) => {
1288
1317
  onFrame(frame, phase);
1289
1318
  } else {
1290
1319
  try {
1291
- process.stdout.write("\r" + frame);
1320
+ if (process?.stdout?.write) {
1321
+ process.stdout.write("\r" + frame);
1322
+ }
1292
1323
  } catch {
1293
1324
  }
1294
1325
  }
@@ -1303,7 +1334,14 @@ var animateGradient = (text, stops, opts = {}) => {
1303
1334
  if (!stopped) {
1304
1335
  timer = setInterval(renderFrame, frameInterval2);
1305
1336
  }
1306
- return { stop, done };
1337
+ return {
1338
+ stop,
1339
+ done,
1340
+ // Thenable hooks — bind to done so `await ctrl` works directly
1341
+ then: done.then.bind(done),
1342
+ catch: done.catch.bind(done),
1343
+ finally: done.finally.bind(done)
1344
+ };
1307
1345
  };
1308
1346
  var PRESET_DEFS = {
1309
1347
  sunset: ["#ff6b6b", "#feca57", "#48dbfb"],
@@ -4222,13 +4260,18 @@ var STYLE_NAMES = [
4222
4260
  "muted",
4223
4261
  "text"
4224
4262
  ];
4263
+ var _themeError = (Ctor, code, message) => {
4264
+ const err = new Ctor(message);
4265
+ err.code = code;
4266
+ return err;
4267
+ };
4225
4268
  var validateTheme = (t) => {
4226
4269
  if (typeof t !== "object" || t === null || Array.isArray(t)) {
4227
- throw new TypeError("Theme must be a non-null object.");
4270
+ throw _themeError(TypeError, "ANSIMAX_INVALID_THEME", "Theme must be a non-null object.");
4228
4271
  }
4229
4272
  const obj = t;
4230
4273
  if (typeof obj.name !== "string" || obj.name.length === 0) {
4231
- throw new TypeError('Theme must have a non-empty "name" string.');
4274
+ throw _themeError(TypeError, "ANSIMAX_INVALID_THEME_NAME", 'Theme must have a non-empty "name" string.');
4232
4275
  }
4233
4276
  for (const key of REQUIRED_COLOR_KEYS) {
4234
4277
  const value = obj[key];
@@ -4567,7 +4610,9 @@ var themes = {
4567
4610
  use(name) {
4568
4611
  const t = _globalRegistry.get(name);
4569
4612
  if (!t) {
4570
- throw new RangeError(
4613
+ throw _themeError(
4614
+ RangeError,
4615
+ "ANSIMAX_UNKNOWN_THEME",
4571
4616
  `Theme "${name}" not found. Available themes: ${[..._globalRegistry.keys()].join(", ")}`
4572
4617
  );
4573
4618
  }
@@ -5466,6 +5511,7 @@ var index_default = ansimax;
5466
5511
  configure,
5467
5512
  countNodes,
5468
5513
  createCanvas,
5514
+ createGradient,
5469
5515
  createOutputBuffer,
5470
5516
  createTheme,
5471
5517
  cursor,
package/dist/index.mjs CHANGED
@@ -992,6 +992,28 @@ var gradient = (text, stops, opts = {}) => {
992
992
  }
993
993
  return _gradientAnsiAware(s, colors, easingFn, phaseN);
994
994
  };
995
+ var createGradient = (stops, defaultOpts = {}) => {
996
+ const colors = Array.isArray(stops) ? stops.map(safeHex).filter((c) => c !== null) : [];
997
+ const defaultEasingFn = resolveEasing(defaultOpts.easing);
998
+ const defaultPreserveAnsi = defaultOpts.preserveAnsi ?? false;
999
+ return (text, opts = {}) => {
1000
+ const s = coerceText(text);
1001
+ if (!s || isNoColor()) return s;
1002
+ if (colors.length === 0) return s;
1003
+ if (colors.length === 1) {
1004
+ const c = colors[0];
1005
+ return adaptiveFg(c.r, c.g, c.b) + s + reset();
1006
+ }
1007
+ const easingFn = opts.easing !== void 0 ? resolveEasing(opts.easing) : defaultEasingFn;
1008
+ const preserveAnsi = opts.preserveAnsi ?? defaultPreserveAnsi;
1009
+ const phase = opts.phase ?? 0;
1010
+ const phaseN = Number.isFinite(phase) ? (phase % 1 + 1) % 1 : 0;
1011
+ if (!preserveAnsi || !s.includes("\x1B")) {
1012
+ return _gradientPlain(s, colors, easingFn, phaseN);
1013
+ }
1014
+ return _gradientAnsiAware(s, colors, easingFn, phaseN);
1015
+ };
1016
+ };
995
1017
  var _gradientPlain = (text, colors, easingFn, phase) => {
996
1018
  const chars = [...text];
997
1019
  const visible = chars.filter((c) => c !== " ").length;
@@ -1100,7 +1122,13 @@ var animateGradient = (text, stops, opts = {}) => {
1100
1122
  if (signal) {
1101
1123
  if (signal.aborted) {
1102
1124
  stop();
1103
- return { stop, done };
1125
+ return {
1126
+ stop,
1127
+ done,
1128
+ then: done.then.bind(done),
1129
+ catch: done.catch.bind(done),
1130
+ finally: done.finally.bind(done)
1131
+ };
1104
1132
  }
1105
1133
  signal.addEventListener("abort", stop, { once: true });
1106
1134
  }
@@ -1115,7 +1143,9 @@ var animateGradient = (text, stops, opts = {}) => {
1115
1143
  onFrame(frame, phase);
1116
1144
  } else {
1117
1145
  try {
1118
- process.stdout.write("\r" + frame);
1146
+ if (process?.stdout?.write) {
1147
+ process.stdout.write("\r" + frame);
1148
+ }
1119
1149
  } catch {
1120
1150
  }
1121
1151
  }
@@ -1130,7 +1160,14 @@ var animateGradient = (text, stops, opts = {}) => {
1130
1160
  if (!stopped) {
1131
1161
  timer = setInterval(renderFrame, frameInterval2);
1132
1162
  }
1133
- return { stop, done };
1163
+ return {
1164
+ stop,
1165
+ done,
1166
+ // Thenable hooks — bind to done so `await ctrl` works directly
1167
+ then: done.then.bind(done),
1168
+ catch: done.catch.bind(done),
1169
+ finally: done.finally.bind(done)
1170
+ };
1134
1171
  };
1135
1172
  var PRESET_DEFS = {
1136
1173
  sunset: ["#ff6b6b", "#feca57", "#48dbfb"],
@@ -4049,13 +4086,18 @@ var STYLE_NAMES = [
4049
4086
  "muted",
4050
4087
  "text"
4051
4088
  ];
4089
+ var _themeError = (Ctor, code, message) => {
4090
+ const err = new Ctor(message);
4091
+ err.code = code;
4092
+ return err;
4093
+ };
4052
4094
  var validateTheme = (t) => {
4053
4095
  if (typeof t !== "object" || t === null || Array.isArray(t)) {
4054
- throw new TypeError("Theme must be a non-null object.");
4096
+ throw _themeError(TypeError, "ANSIMAX_INVALID_THEME", "Theme must be a non-null object.");
4055
4097
  }
4056
4098
  const obj = t;
4057
4099
  if (typeof obj.name !== "string" || obj.name.length === 0) {
4058
- throw new TypeError('Theme must have a non-empty "name" string.');
4100
+ throw _themeError(TypeError, "ANSIMAX_INVALID_THEME_NAME", 'Theme must have a non-empty "name" string.');
4059
4101
  }
4060
4102
  for (const key of REQUIRED_COLOR_KEYS) {
4061
4103
  const value = obj[key];
@@ -4394,7 +4436,9 @@ var themes = {
4394
4436
  use(name) {
4395
4437
  const t = _globalRegistry.get(name);
4396
4438
  if (!t) {
4397
- throw new RangeError(
4439
+ throw _themeError(
4440
+ RangeError,
4441
+ "ANSIMAX_UNKNOWN_THEME",
4398
4442
  `Theme "${name}" not found. Available themes: ${[..._globalRegistry.keys()].join(", ")}`
4399
4443
  );
4400
4444
  }
@@ -5292,6 +5336,7 @@ export {
5292
5336
  configure,
5293
5337
  countNodes,
5294
5338
  createCanvas,
5339
+ createGradient,
5295
5340
  createOutputBuffer,
5296
5341
  createTheme,
5297
5342
  cursor,
@@ -118,7 +118,7 @@ async function main() {
118
118
  console.log(components.section('🏷️ Badges & Status', { width: 60 }));
119
119
  console.log();
120
120
  console.log(' ',
121
- components.badge('VERSION', 'v1.2.0'),
121
+ components.badge('VERSION', 'v1.2.3'),
122
122
  components.badge('BUILD', 'passing'),
123
123
  components.badge('LICENSE', 'Apache 2.0'));
124
124
  console.log();
@@ -117,7 +117,7 @@ console.log();
117
117
  console.log(components.section('🏷️ Badges & Status', { width: 60 }));
118
118
  console.log();
119
119
  console.log(' ',
120
- components.badge('VERSION', 'v1.2.0'),
120
+ components.badge('VERSION', 'v1.2.3'),
121
121
  components.badge('BUILD', 'passing'),
122
122
  components.badge('LICENSE', 'Apache 2.0'));
123
123
  console.log();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ansimax",
3
- "version": "1.2.1",
4
- "description": "Zero-dependency CLI rendering library: colors, gradients, animations, ASCII art, pixel art, components, and themes all in TypeScript.",
3
+ "version": "1.2.3",
4
+ "description": "Zero-dependency CLI rendering library: colors, gradients, animations, ASCII art, pixel art, components, and themes \u2014 all in TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",