ansimax 1.4.1 → 1.4.2

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,89 @@
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.4.2] — Internal consolidation v3
7
+
8
+ Patch release continuing the DRY work from v1.3.7. Three more helper
9
+ patterns that were duplicated across modules are now consolidated into
10
+ `utils/helpers` and exported. **Zero behavior changes** — the
11
+ consolidated implementations are byte-identical to the inline copies
12
+ they replace.
13
+
14
+ ### Added — Three new exported helpers
15
+
16
+ **`ensureString(value)`** — coerce any value to a string, with `null`
17
+ and `undefined` becoming `''` (not `'null'`/`'undefined'`):
18
+
19
+ ```js
20
+ import { ensureString } from 'ansimax';
21
+
22
+ ensureString('hello') // → 'hello'
23
+ ensureString(42) // → '42'
24
+ ensureString(null) // → ''
25
+ ensureString(undefined) // → ''
26
+ ensureString({}) // → '[object Object]'
27
+ ```
28
+
29
+ **`clampNonNeg(value, fallback?)`** — coerce to a non-negative integer
30
+ (≥ 0), with floor and fallback for non-finite input:
31
+
32
+ ```js
33
+ import { clampNonNeg } from 'ansimax';
34
+
35
+ clampNonNeg(5.7) // → 5
36
+ clampNonNeg(-3) // → 0
37
+ clampNonNeg(NaN, 10) // → 10
38
+ clampNonNeg('abc', 5) // → 5
39
+ ```
40
+
41
+ **`clampPositiveInt(value, fallback?)`** — coerce to a positive integer
42
+ (≥ 1), with floor and fallback. Default fallback is `1`:
43
+
44
+ ```js
45
+ import { clampPositiveInt } from 'ansimax';
46
+
47
+ clampPositiveInt(5.7) // → 5
48
+ clampPositiveInt(0) // → 1 (clamped up)
49
+ clampPositiveInt(NaN, 10) // → 10
50
+ ```
51
+
52
+ ### Improved — Internal consolidation
53
+
54
+ Removed **10 duplicate inline implementations** across modules:
55
+
56
+ | Helper | Was duplicated in | Now imported from |
57
+ |---|---|---|
58
+ | `ensureString` | components, frames, loaders, trees (×4 identical) | `utils/helpers` |
59
+ | `clampNonNeg` | components, trees (×2 identical) | `utils/helpers` |
60
+ | `clampPositive` / `clampPositiveInt` | components, loaders (×3 identical) | `utils/helpers` |
61
+
62
+ What's left intentionally:
63
+ - `utils/ansi.ts` keeps its own private versions (architectural choice
64
+ from v1.3.7 — `ansi` and `helpers` stay at the bottom of the
65
+ dependency graph with no cross-imports)
66
+ - `ascii/index.ts` keeps a distinct `ensureString(value, paramName)`
67
+ variant that throws with parameter info (different semantics, not a
68
+ duplicate)
69
+
70
+ ### Improved — Tests
71
+
72
+ - `+5` tests for `ensureString` (string/number/boolean/null/object)
73
+ - `+6` tests for `clampNonNeg` (range, floor, fallback, default, clamp)
74
+ - `+6` tests for `clampPositiveInt` (clamp-up, range, floor, fallback)
75
+ - `+1` test for barrel re-exports
76
+
77
+ Total: **+18 tests**.
78
+
79
+ ### Notes
80
+
81
+ - **Zero behavior changes** — drop-in replacement for `1.4.1`
82
+ - **Zero API changes** — only additions (3 new exports)
83
+ - All call sites use explicit `fallback` arguments → compatible with
84
+ default-arg signatures of the exported helpers
85
+ - ~30 lines of duplicated source removed across 4 modules
86
+
87
+ ---
88
+
6
89
  ## [1.4.1] — Grid v2 + markdown internal refactor
7
90
 
8
91
  Patch release with two improvements: **grid** gains CSS Grid-style
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.4.1-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.4.2-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-2000%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -478,7 +478,7 @@ console.log(components.table([
478
478
  ['loaders', color.green('● listo'), '100%'],
479
479
  ], { borderStyle: 'rounded' }));
480
480
 
481
- console.log(components.badge('VERSION', 'v1.4.1'));
481
+ console.log(components.badge('VERSION', 'v1.4.2'));
482
482
  console.log(components.badge('BUILD', 'passing'));
483
483
  ```
484
484
 
@@ -1065,6 +1065,27 @@ ansimax/
1065
1065
 
1066
1066
  ## 📝 Changelog
1067
1067
 
1068
+ ### v1.4.2 — Consolidación interna v3
1069
+
1070
+ Release patch continuando el trabajo DRY de v1.3.7. Cero cambios de comportamiento:
1071
+
1072
+ - ➕ **`ensureString(value)`** — coerce a string (null/undefined → '')
1073
+ - ➕ **`clampNonNeg(n, fallback?)`** — entero no-negativo con fallback seguro
1074
+ - ➕ **`clampPositiveInt(n, fallback?)`** — entero positivo (≥ 1) con fallback seguro
1075
+ - 🧹 Removidas **10 implementaciones inline duplicadas** entre components, frames, loaders, trees
1076
+ - 🧪 **+18 tests** para los helpers consolidados
1077
+
1078
+ ```js
1079
+ import { ensureString, clampNonNeg, clampPositiveInt } from 'ansimax';
1080
+
1081
+ ensureString(null) // → ''
1082
+ clampNonNeg(-3) // → 0
1083
+ clampPositiveInt(0) // → 1 (clamped up)
1084
+ clampPositiveInt(NaN, 10) // → 10 (fallback)
1085
+ ```
1086
+
1087
+ Drop-in replacement para `1.4.1`.
1088
+
1068
1089
  ### v1.4.1 — Grid v2 + refactor markdown
1069
1090
 
1070
1091
  Release patch. Cero breaking changes:
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.4.1-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.4.2-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-2000%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -478,7 +478,7 @@ console.log(components.table([
478
478
  ['loaders', color.green('● ready'), '100%'],
479
479
  ], { borderStyle: 'rounded' }));
480
480
 
481
- console.log(components.badge('VERSION', 'v1.4.1'));
481
+ console.log(components.badge('VERSION', 'v1.4.2'));
482
482
  console.log(components.badge('BUILD', 'passing'));
483
483
  ```
484
484
 
@@ -1065,6 +1065,27 @@ ansimax/
1065
1065
 
1066
1066
  ## 📝 Changelog
1067
1067
 
1068
+ ### v1.4.2 — Internal consolidation v3
1069
+
1070
+ Patch release continuing v1.3.7's DRY work. Zero behavior changes:
1071
+
1072
+ - ➕ **`ensureString(value)`** — coerce to string (null/undefined → '')
1073
+ - ➕ **`clampNonNeg(n, fallback?)`** — non-negative integer with safe fallback
1074
+ - ➕ **`clampPositiveInt(n, fallback?)`** — positive integer (≥ 1) with safe fallback
1075
+ - 🧹 Removed **10 duplicate inline implementations** across components, frames, loaders, trees
1076
+ - 🧪 **+18 tests** for the consolidated helpers
1077
+
1078
+ ```js
1079
+ import { ensureString, clampNonNeg, clampPositiveInt } from 'ansimax';
1080
+
1081
+ ensureString(null) // → ''
1082
+ clampNonNeg(-3) // → 0
1083
+ clampPositiveInt(0) // → 1 (clamped up)
1084
+ clampPositiveInt(NaN, 10) // → 10 (fallback)
1085
+ ```
1086
+
1087
+ Drop-in replacement for `1.4.1`.
1088
+
1068
1089
  ### v1.4.1 — Grid v2 + markdown refactor
1069
1090
 
1070
1091
  Patch release. Zero breaking changes:
package/dist/index.d.mts CHANGED
@@ -277,6 +277,60 @@ declare const clampPercent: (p: unknown) => number;
277
277
  * @since 1.3.7
278
278
  */
279
279
  declare const clampInt: (value: unknown, min: number, max: number, fallback?: number) => number;
280
+ /**
281
+ * Coerce any value to a string. Non-strings go through `String()`. `null`
282
+ * and `undefined` become `''` (not `'null'`/`'undefined'`). Consolidates
283
+ * the `typeof v === 'string' ? v : String(v ?? '')` pattern previously
284
+ * duplicated across 5 modules (components, frames, loaders, trees, ansi).
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * ensureString('hello') // → 'hello'
289
+ * ensureString(42) // → '42'
290
+ * ensureString(null) // → ''
291
+ * ensureString(undefined) // → ''
292
+ * ensureString({}) // → '[object Object]'
293
+ * ```
294
+ *
295
+ * @since 1.4.2
296
+ */
297
+ declare const ensureString: (v: unknown) => string;
298
+ /**
299
+ * Coerce to a non-negative integer (≥ 0). Falls back to `fallback` for
300
+ * non-finite input. Floors fractional values.
301
+ *
302
+ * Consolidates the `Math.max(0, Math.floor(n))` pattern previously
303
+ * duplicated as a private `clampNonNeg` in components + trees.
304
+ *
305
+ * @example
306
+ * ```ts
307
+ * clampNonNeg(5.7) // → 5
308
+ * clampNonNeg(-3) // → 0
309
+ * clampNonNeg(NaN, 10) // → 10
310
+ * clampNonNeg('abc', 5) // → 5
311
+ * ```
312
+ *
313
+ * @since 1.4.2
314
+ */
315
+ declare const clampNonNeg: (n: unknown, fallback?: number) => number;
316
+ /**
317
+ * Coerce to a positive integer (≥ 1). Falls back to `fallback` for
318
+ * non-finite input. Floors fractional values.
319
+ *
320
+ * Consolidates the `Math.max(1, Math.floor(n))` pattern previously
321
+ * duplicated as private `clampPositive` / `clampPositiveInt` in
322
+ * components + loaders.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * clampPositiveInt(5.7) // → 5
327
+ * clampPositiveInt(0) // → 1 (clamped up)
328
+ * clampPositiveInt(NaN, 10) // → 10
329
+ * ```
330
+ *
331
+ * @since 1.4.2
332
+ */
333
+ declare const clampPositiveInt: (n: unknown, fallback?: number) => number;
280
334
  /** Returns true when a string is a valid 3- or 6-digit hex color. */
281
335
  declare const isHexColor: (hex: string) => boolean;
282
336
  /**
@@ -3278,4 +3332,4 @@ declare const ansimax: {
3278
3332
  };
3279
3333
  };
3280
3334
 
3281
- export { ASCII_RAMPS, type Alignment, type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, type AsciiRamp, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type CenterOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSpace, 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 EasingFunction, type EasingLibraryName, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FigletFont, type FigletOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type FrameOptions, type FromImageOptions, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type GridOptions, type HSL, type HsplitOptions, type PrettyOptions as JsonPrettyOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MarkdownOptions, type MarkdownTheme, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type Oklab, 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$1 as RenderOptions, type ResizeListener, type ReusableGradient, 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 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type VsplitOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center$1 as center, center as centerBlock, chain, charWidth, clamp, clampByte, clampInt, clampPercent, clearAnsiCache, clearColorCache, clearLine, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createGradient, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, easings, escapeForRegex, escapeRegex, fg256, fgRgb, figletText, filterTree, findInTree, flipHorizontal, flipVertical, frame, frames, fromImage, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, gradientStops, graphemes, grid, hasFont, hexToRgb, hideCursor, hslToRgb, hsplit, hyperlink, images, isFiniteNumber, isHexColor, isNoColor, json, pretty as jsonPretty, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, markdown, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, parseBlocks as parseMarkdownBlocks, parseInline as parseMarkdownInline, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, render as renderMarkdown, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resolveEasingByName, resumeListeners, reverseGradient, rgbTo256, rgbToHex, rgbToHsl, rgbToOklab, rotate90, safeInt, safeJson, screen, setConfigValue, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$2 as stripAnsi, stripAnsi$1 as stripAnsiCodes, stripAnsi as stripAnsiColors, subscribeConfig, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, vsplit, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
3335
+ export { ASCII_RAMPS, type Alignment, type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, type AsciiRamp, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type CenterOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSpace, 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 EasingFunction, type EasingLibraryName, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FigletFont, type FigletOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type FrameOptions, type FromImageOptions, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type GridOptions, type HSL, type HsplitOptions, type PrettyOptions as JsonPrettyOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MarkdownOptions, type MarkdownTheme, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type Oklab, 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$1 as RenderOptions, type ResizeListener, type ReusableGradient, 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 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type VsplitOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center$1 as center, center as centerBlock, chain, charWidth, clamp, clampByte, clampInt, clampNonNeg, clampPercent, clampPositiveInt, clearAnsiCache, clearColorCache, clearLine, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createGradient, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, easings, ensureString, escapeForRegex, escapeRegex, fg256, fgRgb, figletText, filterTree, findInTree, flipHorizontal, flipVertical, frame, frames, fromImage, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, gradientStops, graphemes, grid, hasFont, hexToRgb, hideCursor, hslToRgb, hsplit, hyperlink, images, isFiniteNumber, isHexColor, isNoColor, json, pretty as jsonPretty, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, markdown, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, parseBlocks as parseMarkdownBlocks, parseInline as parseMarkdownInline, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, render as renderMarkdown, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resolveEasingByName, resumeListeners, reverseGradient, rgbTo256, rgbToHex, rgbToHsl, rgbToOklab, rotate90, safeInt, safeJson, screen, setConfigValue, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$2 as stripAnsi, stripAnsi$1 as stripAnsiCodes, stripAnsi as stripAnsiColors, subscribeConfig, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, vsplit, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
package/dist/index.d.ts CHANGED
@@ -277,6 +277,60 @@ declare const clampPercent: (p: unknown) => number;
277
277
  * @since 1.3.7
278
278
  */
279
279
  declare const clampInt: (value: unknown, min: number, max: number, fallback?: number) => number;
280
+ /**
281
+ * Coerce any value to a string. Non-strings go through `String()`. `null`
282
+ * and `undefined` become `''` (not `'null'`/`'undefined'`). Consolidates
283
+ * the `typeof v === 'string' ? v : String(v ?? '')` pattern previously
284
+ * duplicated across 5 modules (components, frames, loaders, trees, ansi).
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * ensureString('hello') // → 'hello'
289
+ * ensureString(42) // → '42'
290
+ * ensureString(null) // → ''
291
+ * ensureString(undefined) // → ''
292
+ * ensureString({}) // → '[object Object]'
293
+ * ```
294
+ *
295
+ * @since 1.4.2
296
+ */
297
+ declare const ensureString: (v: unknown) => string;
298
+ /**
299
+ * Coerce to a non-negative integer (≥ 0). Falls back to `fallback` for
300
+ * non-finite input. Floors fractional values.
301
+ *
302
+ * Consolidates the `Math.max(0, Math.floor(n))` pattern previously
303
+ * duplicated as a private `clampNonNeg` in components + trees.
304
+ *
305
+ * @example
306
+ * ```ts
307
+ * clampNonNeg(5.7) // → 5
308
+ * clampNonNeg(-3) // → 0
309
+ * clampNonNeg(NaN, 10) // → 10
310
+ * clampNonNeg('abc', 5) // → 5
311
+ * ```
312
+ *
313
+ * @since 1.4.2
314
+ */
315
+ declare const clampNonNeg: (n: unknown, fallback?: number) => number;
316
+ /**
317
+ * Coerce to a positive integer (≥ 1). Falls back to `fallback` for
318
+ * non-finite input. Floors fractional values.
319
+ *
320
+ * Consolidates the `Math.max(1, Math.floor(n))` pattern previously
321
+ * duplicated as private `clampPositive` / `clampPositiveInt` in
322
+ * components + loaders.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * clampPositiveInt(5.7) // → 5
327
+ * clampPositiveInt(0) // → 1 (clamped up)
328
+ * clampPositiveInt(NaN, 10) // → 10
329
+ * ```
330
+ *
331
+ * @since 1.4.2
332
+ */
333
+ declare const clampPositiveInt: (n: unknown, fallback?: number) => number;
280
334
  /** Returns true when a string is a valid 3- or 6-digit hex color. */
281
335
  declare const isHexColor: (hex: string) => boolean;
282
336
  /**
@@ -3278,4 +3332,4 @@ declare const ansimax: {
3278
3332
  };
3279
3333
  };
3280
3334
 
3281
- export { ASCII_RAMPS, type Alignment, type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, type AsciiRamp, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type CenterOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSpace, 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 EasingFunction, type EasingLibraryName, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FigletFont, type FigletOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type FrameOptions, type FromImageOptions, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type GridOptions, type HSL, type HsplitOptions, type PrettyOptions as JsonPrettyOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MarkdownOptions, type MarkdownTheme, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type Oklab, 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$1 as RenderOptions, type ResizeListener, type ReusableGradient, 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 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type VsplitOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center$1 as center, center as centerBlock, chain, charWidth, clamp, clampByte, clampInt, clampPercent, clearAnsiCache, clearColorCache, clearLine, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createGradient, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, easings, escapeForRegex, escapeRegex, fg256, fgRgb, figletText, filterTree, findInTree, flipHorizontal, flipVertical, frame, frames, fromImage, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, gradientStops, graphemes, grid, hasFont, hexToRgb, hideCursor, hslToRgb, hsplit, hyperlink, images, isFiniteNumber, isHexColor, isNoColor, json, pretty as jsonPretty, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, markdown, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, parseBlocks as parseMarkdownBlocks, parseInline as parseMarkdownInline, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, render as renderMarkdown, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resolveEasingByName, resumeListeners, reverseGradient, rgbTo256, rgbToHex, rgbToHsl, rgbToOklab, rotate90, safeInt, safeJson, screen, setConfigValue, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$2 as stripAnsi, stripAnsi$1 as stripAnsiCodes, stripAnsi as stripAnsiColors, subscribeConfig, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, vsplit, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
3335
+ export { ASCII_RAMPS, type Alignment, type AnimateGradientController, type AnimateGradientOptions, type AnimationHooks, type AnimationSpeed, type AnsiCode, type AnsimaxConfig, type AsciiRamp, BEL, BG, type BadgeOptions, type BallOptions, type BannerOptions, type BoxOptions, type BoxStyle, type BreatheOptions, DEFAULTS as CONFIG_DEFAULTS, CSI, type Canvas, type CanvasRenderOptions, type CenterOptions, type ColorChain, type ColorFn, type ColorLevel, type ColorMode, type ColorSpace, 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 EasingFunction, type EasingLibraryName, type EasingName, type EraseMode, FG, FRAME_MS, type FadeOptions, type FigletFont, type FigletOptions, type FontMap, type FontName, type FrameCallback, type FrameHandle, type FrameOptions, type FromImageOptions, type GlitchOptions, type Glyph, type GradientOptions, type GradientRectOptions, type GridOptions, type HSL, type HsplitOptions, type PrettyOptions as JsonPrettyOptions, type LineDiff, type LiveController, type LiveOptions, type LoadingBarOptions, type LogoOptions, MENU_CANCELLED, type MarkdownOptions, type MarkdownTheme, type MemoizeOptions, type MenuInput, type MenuOptions, type MenuOutput, type MenuResult, type MultiLoader, type MultiLoaderItem, OSC, type Oklab, 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$1 as RenderOptions, type ResizeListener, type ReusableGradient, 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 as TreeRenderOptions, type TreeStyle, type TypeDeleteOptions, type TypewriterOptions, type VsplitOptions, type WalkVisitor, type WaveOptions, type WriteAsyncOptions, animate, animateGradient, ascii, bell, bg256, bgRgb, box, canAnimate, cancelTerminalFrame, center$1 as center, center as centerBlock, chain, charWidth, clamp, clampByte, clampInt, clampNonNeg, clampPercent, clampPositiveInt, clearAnsiCache, clearColorCache, clearLine, clearRenderCache, clearThemeColorCache, color, colorLevel, presets as colorPresets, components, compose, configure, countNodes, createCanvas, createGradient, createOutputBuffer, createTheme, cursor, debounce, ansimax as default, diffLines, easings, ensureString, escapeForRegex, escapeRegex, fg256, fgRgb, figletText, filterTree, findInTree, flipHorizontal, flipVertical, frame, frames, fromImage, getConfig, getConfigValue, getRenderCacheSize, getSpeedMultiplier, getTerminalHeight, getTerminalWidth, gradient, gradientColor, gradientRect, gradientStops, graphemes, grid, hasFont, hexToRgb, hideCursor, hslToRgb, hsplit, hyperlink, images, isFiniteNumber, isHexColor, isNoColor, json, pretty as jsonPretty, lerp, lerpColor, link, listFonts, listPresets, loader, mapTree, markdown, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, parseBlocks as parseMarkdownBlocks, parseInline as parseMarkdownInline, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, render as renderMarkdown, renderPixelArt, renderTree, renderTreeStream, repeatVisible, requestTerminalFrame, reset, resetColorSupportCache, resetConfig, resetCursorRefCount, resetFramesCursorCount, resetLoaderCursorCount, resetNoColor, resolveEasingByName, resumeListeners, reverseGradient, rgbTo256, rgbToHex, rgbToHsl, rgbToOklab, rotate90, safeInt, safeJson, screen, setConfigValue, setNoColor, setTitle, sgr, showCursor, sleep, sleepFrame, sliceAnsi, stripAnsi$2 as stripAnsi, stripAnsi$1 as stripAnsiCodes, stripAnsi as stripAnsiColors, subscribeConfig, supportsColor, supportsColorLevel, termSize, themes, throttle, tree, trees, truncateAnsi, visibleLen, vsplit, walkTree, withConfig, wordWrap, wrapAnsi, write, writeAsync, writeErr, writeln, writelnErr };
package/dist/index.js CHANGED
@@ -62,7 +62,9 @@ __export(index_exports, {
62
62
  clamp: () => clamp,
63
63
  clampByte: () => clampByte2,
64
64
  clampInt: () => clampInt,
65
+ clampNonNeg: () => clampNonNeg,
65
66
  clampPercent: () => clampPercent,
67
+ clampPositiveInt: () => clampPositiveInt,
66
68
  clearAnsiCache: () => clearAnsiCache,
67
69
  clearColorCache: () => clearColorCache,
68
70
  clearLine: () => clearLine,
@@ -84,6 +86,7 @@ __export(index_exports, {
84
86
  default: () => index_default,
85
87
  diffLines: () => diffLines,
86
88
  easings: () => easings,
89
+ ensureString: () => ensureString2,
87
90
  escapeForRegex: () => escapeForRegex,
88
91
  escapeRegex: () => escapeRegex,
89
92
  fg256: () => fg256,
@@ -624,6 +627,15 @@ var clampInt = (value, min, max, fallback = 0) => {
624
627
  }
625
628
  return Math.max(min, Math.min(max, Math.floor(value)));
626
629
  };
630
+ var ensureString2 = (v) => typeof v === "string" ? v : String(v ?? "");
631
+ var clampNonNeg = (n, fallback = 0) => {
632
+ if (!isFiniteNumber2(n)) return Math.max(0, Math.floor(fallback));
633
+ return Math.max(0, Math.floor(n));
634
+ };
635
+ var clampPositiveInt = (n, fallback = 1) => {
636
+ if (!isFiniteNumber2(n)) return Math.max(1, Math.floor(fallback));
637
+ return Math.max(1, Math.floor(n));
638
+ };
627
639
  var HEX_RE = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
628
640
  var isHexColor = (hex) => typeof hex === "string" && HEX_RE.test(hex.trim());
629
641
  var hexToRgb = (hex) => {
@@ -2576,7 +2588,7 @@ var clearRenderCache = () => {
2576
2588
  _renderCache.clear();
2577
2589
  };
2578
2590
  var getRenderCacheSize = () => _renderCache.size;
2579
- var ensureString2 = (value, paramName) => {
2591
+ var ensureString3 = (value, paramName) => {
2580
2592
  if (typeof value !== "string") {
2581
2593
  throw new TypeError(
2582
2594
  `ascii: ${paramName} must be a string, got ${typeof value}`
@@ -2715,15 +2727,15 @@ var BOX_STYLES = {
2715
2727
  ascii: { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" }
2716
2728
  };
2717
2729
  var big = (text) => {
2718
- const safe = ensureString2(text, "big(text)");
2730
+ const safe = ensureString3(text, "big(text)");
2719
2731
  return renderFont(safe, BLOCK, { _fontKey: "big" });
2720
2732
  };
2721
2733
  var small = (text) => {
2722
- const safe = ensureString2(text, "small(text)");
2734
+ const safe = ensureString3(text, "small(text)");
2723
2735
  return renderFont(safe, SMALL, { _fontKey: "small" });
2724
2736
  };
2725
2737
  var figlet = (text, opts = {}) => {
2726
- const safe = ensureString2(text, "figlet(text)");
2738
+ const safe = ensureString3(text, "figlet(text)");
2727
2739
  const fontName = opts.font ?? "big";
2728
2740
  const fontMap = FONTS[fontName] ?? BLOCK;
2729
2741
  return renderFont(safe, fontMap, {
@@ -2733,7 +2745,7 @@ var figlet = (text, opts = {}) => {
2733
2745
  });
2734
2746
  };
2735
2747
  var stageRender = (text, font, letterSpacing) => {
2736
- const safe = ensureString2(text, "stageRender(text)");
2748
+ const safe = ensureString3(text, "stageRender(text)");
2737
2749
  const fontMap = FONTS[font] ?? BLOCK;
2738
2750
  const renderOpts = { _fontKey: typeof font === "string" ? font : "big" };
2739
2751
  if (letterSpacing !== void 0) renderOpts.letterSpacing = letterSpacing;
@@ -2775,7 +2787,7 @@ var stageColorize = (rendered, colorFn, perCharColor) => {
2775
2787
  return rendered.split("\n").map(colorFn).join("\n");
2776
2788
  };
2777
2789
  var banner = (text, opts = {}) => {
2778
- const safe = ensureString2(text, "banner(text)");
2790
+ const safe = ensureString3(text, "banner(text)");
2779
2791
  const {
2780
2792
  font = "big",
2781
2793
  colorFn = null,
@@ -2789,7 +2801,7 @@ var banner = (text, opts = {}) => {
2789
2801
  return result;
2790
2802
  };
2791
2803
  var box = (text, opts = {}) => {
2792
- const safe = ensureString2(text, "box(text)");
2804
+ const safe = ensureString3(text, "box(text)");
2793
2805
  const {
2794
2806
  padding = 1,
2795
2807
  borderStyle = "rounded",
@@ -2870,7 +2882,7 @@ var divider = (opts = {}) => {
2870
2882
  return fill.repeat(w);
2871
2883
  };
2872
2884
  var logo = (text, opts = {}) => {
2873
- const safe = ensureString2(text, "logo(text)");
2885
+ const safe = ensureString3(text, "logo(text)");
2874
2886
  const { gradient: gfn = null, boxStyle = "double", centered = true } = opts;
2875
2887
  if (!safe.length) return box("", { borderStyle: boxStyle, padding: 1 });
2876
2888
  const art = big(safe);
@@ -2884,7 +2896,7 @@ var logo = (text, opts = {}) => {
2884
2896
  return box(lines.join("\n"), { borderStyle: boxStyle, padding: 1 });
2885
2897
  };
2886
2898
  var measure = (text, font = "big", letterSpacing) => {
2887
- const safe = ensureString2(text, "measure(text)");
2899
+ const safe = ensureString3(text, "measure(text)");
2888
2900
  if (!safe.length) return { width: 0, height: 0 };
2889
2901
  const rendered = stageRender(safe, font, letterSpacing);
2890
2902
  const lines = rendered.split("\n");
@@ -2892,7 +2904,7 @@ var measure = (text, font = "big", letterSpacing) => {
2892
2904
  return { width, height: lines.length };
2893
2905
  };
2894
2906
  var stream = async function* (text, opts = {}) {
2895
- const safe = ensureString2(text, "stream(text)");
2907
+ const safe = ensureString3(text, "stream(text)");
2896
2908
  const { font = "big", letterSpacing, granularity = "row", signal } = opts;
2897
2909
  if (!safe.length) return;
2898
2910
  if (signal?.aborted) return;
@@ -3258,11 +3270,6 @@ var ascii = {
3258
3270
 
3259
3271
  // src/loaders/index.ts
3260
3272
  var canAnimate2 = () => Boolean(process.stdout?.isTTY) && supportsColor() !== "none";
3261
- var ensureString3 = (v) => typeof v === "string" ? v : String(v ?? "");
3262
- var clampPositiveInt = (n, fallback) => {
3263
- if (!isFiniteNumber2(n)) return fallback;
3264
- return Math.max(1, Math.floor(n));
3265
- };
3266
3273
  var isUnicodeCapable = () => {
3267
3274
  const env = process.env;
3268
3275
  if (env["CI"]) return true;
@@ -3379,9 +3386,9 @@ var spin = (text = "Loading", opts = {}) => {
3379
3386
  signal,
3380
3387
  reducedMotion = false
3381
3388
  } = opts;
3382
- const safeText = ensureString3(text);
3383
- const safePrefix = ensureString3(prefix);
3384
- const safeSuffix = ensureString3(suffix);
3389
+ const safeText = ensureString2(text);
3390
+ const safePrefix = ensureString2(prefix);
3391
+ const safeSuffix = ensureString2(suffix);
3385
3392
  const safeInterval = clampPositiveInt(interval, 80);
3386
3393
  registerCrashHandlers2();
3387
3394
  const frames2 = resolveSpinnerFrames(type);
@@ -3393,7 +3400,7 @@ var spin = (text = "Loading", opts = {}) => {
3393
3400
  return (finalText, success) => {
3394
3401
  if (finalText !== void 0) {
3395
3402
  const icon = success === false ? "\u2717" : success === true ? "\u2713" : "";
3396
- write(`${safePrefix}${icon ? icon + " " : ""}${ensureString3(finalText)}${safeSuffix}`);
3403
+ write(`${safePrefix}${icon ? icon + " " : ""}${ensureString2(finalText)}${safeSuffix}`);
3397
3404
  if (persist) writeln();
3398
3405
  }
3399
3406
  };
@@ -3434,7 +3441,7 @@ var spin = (text = "Loading", opts = {}) => {
3434
3441
  if (finalText !== void 0) {
3435
3442
  const icon = success === false ? sgr(FG.red) + "\u2717" + reset() : success === true ? sgr(FG.green) + "\u2713" + reset() : "";
3436
3443
  const line = padToTerminalWidth(
3437
- `${safePrefix}${icon ? icon + " " : ""}${ensureString3(finalText)}${safeSuffix}`
3444
+ `${safePrefix}${icon ? icon + " " : ""}${ensureString2(finalText)}${safeSuffix}`
3438
3445
  );
3439
3446
  write(line);
3440
3447
  if (persist) writeln();
@@ -3463,7 +3470,7 @@ var progress = (percent, label = "", opts = {}) => {
3463
3470
  const safeWidth = clampPositiveInt(width, 30);
3464
3471
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
3465
3472
  const safeEmpty = typeof emptyChar === "string" && emptyChar.length > 0 ? emptyChar : "\u2591";
3466
- const safeLabel = ensureString3(label);
3473
+ const safeLabel = ensureString2(label);
3467
3474
  const clamped = clampPercent(percent);
3468
3475
  const filled = Math.floor(clamped / 100 * safeWidth);
3469
3476
  const empty = Math.max(0, safeWidth - filled);
@@ -3653,7 +3660,7 @@ var custom = (frames2, text = "", opts = {}) => {
3653
3660
  var countdown = async (seconds, opts = {}) => {
3654
3661
  const { label = "Starting in", color: hex, signal } = opts;
3655
3662
  const safeSeconds = isFiniteNumber2(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
3656
- const safeLabel = ensureString3(label);
3663
+ const safeLabel = ensureString2(label);
3657
3664
  const colorToUse = safeColor(hex) ? hex : "#ffd700";
3658
3665
  if (!canAnimate2() || signal?.aborted) {
3659
3666
  const final = applyColor(String(safeSeconds), colorToUse);
@@ -3860,7 +3867,6 @@ var loader = {
3860
3867
  };
3861
3868
 
3862
3869
  // src/frames/index.ts
3863
- var ensureString4 = (v) => typeof v === "string" ? v : String(v ?? "");
3864
3870
  var MAX_FPS = 60;
3865
3871
  var clampFps = (fps, fallback) => {
3866
3872
  if (!isFiniteNumber2(fps)) return fallback;
@@ -4001,7 +4007,7 @@ var play = (frames2, opts = {}) => {
4001
4007
  } catch {
4002
4008
  rendered = frame2;
4003
4009
  }
4004
- const safe = ensureString4(rendered);
4010
+ const safe = ensureString2(rendered);
4005
4011
  try {
4006
4012
  write(safe);
4007
4013
  } catch {
@@ -4111,7 +4117,7 @@ var generate = (count, fn) => {
4111
4117
  if (typeof fn !== "function") return Array(safeCount).fill("");
4112
4118
  return Array.from({ length: safeCount }, (_, i) => {
4113
4119
  try {
4114
- return ensureString4(fn(i, safeCount));
4120
+ return ensureString2(fn(i, safeCount));
4115
4121
  } catch {
4116
4122
  return "";
4117
4123
  }
@@ -4169,15 +4175,15 @@ var live = (opts = {}) => {
4169
4175
  if (wasRunning) showCursorSafe2();
4170
4176
  };
4171
4177
  const update = (newFrame) => {
4172
- currentFrame = ensureString4(newFrame);
4178
+ currentFrame = ensureString2(newFrame);
4173
4179
  if (running) render2();
4174
4180
  };
4175
4181
  if (autoStart) start();
4176
4182
  return { start, stop, update, isRunning: () => running };
4177
4183
  };
4178
4184
  var morph = (frameA, frameB, steps = 8, charset = "\u2591\u2592\u2593\u2588\u2593\u2592\u2591") => {
4179
- const a0 = ensureString4(frameA);
4180
- const b0 = ensureString4(frameB);
4185
+ const a0 = ensureString2(frameA);
4186
+ const b0 = ensureString2(frameB);
4181
4187
  if (!a0 && !b0) return [""];
4182
4188
  const n = Math.max(2, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
4183
4189
  const len = Math.max(a0.length, b0.length);
@@ -4204,7 +4210,7 @@ var presets2 = {
4204
4210
  const safeWidth = Math.max(0, isFiniteNumber2(width) ? Math.floor(width) : 20);
4205
4211
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
4206
4212
  const safeEmpty = typeof empty === "string" && empty.length > 0 ? empty : "\u2591";
4207
- const safeLabel = ensureString4(label);
4213
+ const safeLabel = ensureString2(label);
4208
4214
  return generate(safeWidth + 1, (i) => {
4209
4215
  const filled = safeChar.repeat(i);
4210
4216
  const rest = safeEmpty.repeat(safeWidth - i);
@@ -4224,7 +4230,7 @@ var presets2 = {
4224
4230
  /* istanbul ignore next — default opts {} */
4225
4231
  breathe: (text, opts = {}) => {
4226
4232
  const { steps = 8 } = opts;
4227
- const safeText = ensureString4(text);
4233
+ const safeText = ensureString2(text);
4228
4234
  const safeSteps2 = Math.max(1, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
4229
4235
  const shades = ["\u2591", "\u2592", "\u2593", "\u2588"];
4230
4236
  return generate(safeSteps2 * 2, (i) => {
@@ -4236,7 +4242,7 @@ var presets2 = {
4236
4242
  /* istanbul ignore next — default opts {} */
4237
4243
  typeDelete: (text, opts = {}) => {
4238
4244
  const { cursor: cur = "\u258C" } = opts;
4239
- const safeText = ensureString4(text);
4245
+ const safeText = ensureString2(text);
4240
4246
  const safeCur = typeof cur === "string" ? cur : "\u258C";
4241
4247
  const typed = generate(safeText.length + 1, (i) => safeText.slice(0, i) + safeCur);
4242
4248
  const deleted = generate(safeText.length + 1, (i) => safeText.slice(0, safeText.length - i) + safeCur);
@@ -4246,15 +4252,6 @@ var presets2 = {
4246
4252
  var frames = { play, generate, live, morph, presets: presets2 };
4247
4253
 
4248
4254
  // src/components/index.ts
4249
- var ensureString5 = (v) => typeof v === "string" ? v : String(v ?? "");
4250
- var clampNonNeg = (n, fallback) => {
4251
- if (!isFiniteNumber2(n)) return fallback;
4252
- return Math.max(0, Math.floor(n));
4253
- };
4254
- var clampPositive2 = (n, fallback) => {
4255
- if (!isFiniteNumber2(n)) return fallback;
4256
- return Math.max(1, Math.floor(n));
4257
- };
4258
4255
  var safeSgrCode = (code, fallback) => {
4259
4256
  if (!isFiniteNumber2(code)) return fallback;
4260
4257
  return Math.max(0, Math.min(255, Math.floor(code)));
@@ -4301,7 +4298,7 @@ var table = (rows, opts = {}) => {
4301
4298
  if (cols === 0) return "";
4302
4299
  const processedRows = validRows.map(
4303
4300
  (row) => Array.from({ length: cols }, (_, ci) => {
4304
- const raw = ensureString5(row[ci]);
4301
+ const raw = ensureString2(row[ci]);
4305
4302
  const lines2 = raw.split("\n");
4306
4303
  return safeMaxCol !== null ? lines2.map((l) => truncateVisible(l, safeMaxCol)) : lines2;
4307
4304
  })
@@ -4352,8 +4349,8 @@ var badge = (label, value, opts = {}) => {
4352
4349
  padding = 1,
4353
4350
  border = false
4354
4351
  } = opts;
4355
- const safeLabel = ensureString5(label);
4356
- const safeValue = ensureString5(value);
4352
+ const safeLabel = ensureString2(label);
4353
+ const safeValue = ensureString2(value);
4357
4354
  const safePadding = clampNonNeg(padding, 1);
4358
4355
  const pad = " ".repeat(safePadding);
4359
4356
  const lhs = sgr(safeSgrCode(labelBg, BG.blue), safeSgrCode(labelFg, FG.white)) + pad + safeLabel + pad + reset();
@@ -4377,10 +4374,10 @@ var progressBar = (percent, opts = {}) => {
4377
4374
  color: color2 = null,
4378
4375
  gradient: gradientStops2 = null
4379
4376
  } = opts;
4380
- const safeWidth = clampPositive2(width, 30);
4377
+ const safeWidth = clampPositiveInt(width, 30);
4381
4378
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
4382
4379
  const safeEmpty = typeof emptyChar === "string" && emptyChar.length > 0 ? emptyChar : "\u2591";
4383
- const safeLabel = ensureString5(label);
4380
+ const safeLabel = ensureString2(label);
4384
4381
  const clamped = clampPercent(percent);
4385
4382
  const filled = Math.floor(clamped / 100 * safeWidth);
4386
4383
  const empty = Math.max(0, safeWidth - filled);
@@ -4407,7 +4404,7 @@ var status = (type, message, opts = {}) => {
4407
4404
  const def = STATUS_MAP[type] ?? STATUS_MAP.info;
4408
4405
  const colorCode = opts.color !== void 0 ? safeSgrCode(opts.color, def.color) : def.color;
4409
4406
  const useIcon = opts.icon === void 0 ? def.icon : opts.icon ?? "";
4410
- const safeMsg = ensureString5(message);
4407
+ const safeMsg = ensureString2(message);
4411
4408
  const iconPart = useIcon ? sgr(colorCode) + useIcon + reset() + " " : "";
4412
4409
  if (safeMsg.includes("\n")) {
4413
4410
  const indent = " ".repeat(useIcon ? visibleLen(useIcon) + 1 : 0);
@@ -4419,7 +4416,7 @@ var status = (type, message, opts = {}) => {
4419
4416
  var section = (title, opts = {}) => {
4420
4417
  const { char = "\u2500", width = null, color: colorCode = FG.cyan } = opts;
4421
4418
  const { cols } = termSize();
4422
- const safeTitle = ensureString5(title);
4419
+ const safeTitle = ensureString2(title);
4423
4420
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2500";
4424
4421
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4425
4422
  const titleLen = visibleLen(safeTitle);
@@ -4434,7 +4431,7 @@ var section = (title, opts = {}) => {
4434
4431
  var columns = (items, opts = {}) => {
4435
4432
  if (!Array.isArray(items) || items.length === 0) return "";
4436
4433
  const { cols: numCols = 2, gap = 2, width = null, overflow = "truncate" } = opts;
4437
- const safeCols = clampPositive2(numCols, 2);
4434
+ const safeCols = clampPositiveInt(numCols, 2);
4438
4435
  const safeGap = clampNonNeg(gap, 2);
4439
4436
  const { cols: termCols } = termSize();
4440
4437
  const totalW = width !== null && isFiniteNumber2(width) ? Math.max(safeCols, Math.floor(width)) : termCols;
@@ -4444,7 +4441,7 @@ var columns = (items, opts = {}) => {
4444
4441
  for (let i = 0; i < items.length; i += safeCols) {
4445
4442
  const chunk = items.slice(i, i + safeCols);
4446
4443
  if (overflow === "wrap") {
4447
- const cellLines = chunk.map((it) => wrapVisible(ensureString5(it), colW));
4444
+ const cellLines = chunk.map((it) => wrapVisible(ensureString2(it), colW));
4448
4445
  const rowHeight = Math.max(...cellLines.map((c) => c.length), 1);
4449
4446
  for (let line = 0; line < rowHeight; line++) {
4450
4447
  rows.push(
@@ -4454,7 +4451,7 @@ var columns = (items, opts = {}) => {
4454
4451
  } else {
4455
4452
  rows.push(
4456
4453
  chunk.map((item) => {
4457
- const t = truncateVisible(ensureString5(item), colW);
4454
+ const t = truncateVisible(ensureString2(item), colW);
4458
4455
  return padEnd(t, colW);
4459
4456
  }).join(gapStr)
4460
4457
  );
@@ -4477,15 +4474,15 @@ var timeline = (events, opts = {}) => {
4477
4474
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4478
4475
  const safeDoneColor = safeSgrCode(doneColor, FG.green);
4479
4476
  const safePendingColor = safeSgrCode(pendingColor, FG.brightBlack);
4480
- const computedTimeWidth = timeColumnWidth !== null && isFiniteNumber2(timeColumnWidth) ? Math.max(0, Math.floor(timeColumnWidth)) : Math.max(0, ...events.map((e) => e.time ? visibleLen(ensureString5(e.time)) : 0));
4477
+ const computedTimeWidth = timeColumnWidth !== null && isFiniteNumber2(timeColumnWidth) ? Math.max(0, Math.floor(timeColumnWidth)) : Math.max(0, ...events.map((e) => e.time ? visibleLen(ensureString2(e.time)) : 0));
4481
4478
  const lines = [];
4482
4479
  events.forEach((ev, i) => {
4483
4480
  const isLast = i === events.length - 1;
4484
- const evLabel = ensureString5(ev.label);
4481
+ const evLabel = ensureString2(ev.label);
4485
4482
  const clr = ev.done ? safeDoneColor : i === 0 ? safeColor2 : safePendingColor;
4486
4483
  const nodeStr = sgr(clr) + safeNode + reset();
4487
4484
  const textStr = ev.done ? sgr(STYLE.bold) + evLabel + reset() : sgr(safePendingColor) + evLabel + reset();
4488
- const timePart = ev.time && computedTimeWidth > 0 ? " " + sgr(FG.brightBlack) + padEnd(ensureString5(ev.time), computedTimeWidth) + reset() : "";
4485
+ const timePart = ev.time && computedTimeWidth > 0 ? " " + sgr(FG.brightBlack) + padEnd(ensureString2(ev.time), computedTimeWidth) + reset() : "";
4489
4486
  lines.push(`${nodeStr} ${textStr}${timePart}`);
4490
4487
  if (!isLast) lines.push(sgr(safePendingColor) + safeConnector + reset());
4491
4488
  });
@@ -4507,8 +4504,8 @@ var menu = (items, opts = {}) => {
4507
4504
  if (!inp.isTTY) return Promise.resolve(multiSelect ? [] : 0);
4508
4505
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4509
4506
  const safePointer = typeof pointer === "string" && pointer.length > 0 ? pointer : "\u25B6";
4510
- const safeTitle = title === null ? null : ensureString5(title);
4511
- const safeItems = items.map(ensureString5);
4507
+ const safeTitle = title === null ? null : ensureString2(title);
4508
+ const safeItems = items.map(ensureString2);
4512
4509
  let cursorPos = 0;
4513
4510
  let lastRenderedLines = 0;
4514
4511
  let cursorHidden = false;
@@ -4669,19 +4666,14 @@ var STYLES = {
4669
4666
  heavy: { branch: "\u2523\u2501\u2501 ", last: "\u2517\u2501\u2501 ", vert: "\u2503 ", space: " " },
4670
4667
  ascii: { branch: "+-- ", last: "`-- ", vert: "| ", space: " " }
4671
4668
  };
4672
- var ensureString6 = (v) => typeof v === "string" ? v : String(v ?? "");
4673
- var clampNonNeg2 = (n, fallback) => {
4674
- if (!isFiniteNumber2(n)) return fallback;
4675
- return Math.max(0, Math.floor(n));
4676
- };
4677
4669
  var normalizeNewlines = (s) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
4678
4670
  var toTreeData = (child) => {
4679
4671
  if (typeof child === "string") return { label: child, children: [] };
4680
4672
  if (typeof child !== "object" || child === null || Array.isArray(child)) {
4681
- return { label: ensureString6(child), children: [] };
4673
+ return { label: ensureString2(child), children: [] };
4682
4674
  }
4683
4675
  return {
4684
- label: ensureString6(child.label ?? ""),
4676
+ label: ensureString2(child.label ?? ""),
4685
4677
  children: [],
4686
4678
  ...child
4687
4679
  };
@@ -4717,8 +4709,8 @@ var paletteAt = (palette, depth) => {
4717
4709
  };
4718
4710
  var formatNode = (node, depth, palette) => {
4719
4711
  const colorFn = node.color ?? paletteAt(palette, depth);
4720
- const safeLabel = normalizeNewlines(ensureString6(node.label));
4721
- const iconPrefix = node.icon ? ensureString6(node.icon) + " " : "";
4712
+ const safeLabel = normalizeNewlines(ensureString2(node.label));
4713
+ const iconPrefix = node.icon ? ensureString2(node.icon) + " " : "";
4722
4714
  const labelLines = safeLabel.split("\n");
4723
4715
  return labelLines.map((line, i) => {
4724
4716
  const text = i === 0 ? iconPrefix + line : line;
@@ -4812,7 +4804,7 @@ var renderTree = (root, opts = {}) => {
4812
4804
  indent = 0
4813
4805
  } = opts;
4814
4806
  const safeStyle = style && STYLES[style] ? style : "normal";
4815
- const safeIndent = clampNonNeg2(indent, 0);
4807
+ const safeIndent = clampNonNeg(indent, 0);
4816
4808
  const safeMaxDepth = isFiniteNumber2(maxDepth) ? Math.max(0, Math.floor(maxDepth)) : Infinity;
4817
4809
  const indentStr = " ".repeat(safeIndent);
4818
4810
  const lines = [];
@@ -7121,7 +7113,9 @@ var index_default = ansimax;
7121
7113
  clamp,
7122
7114
  clampByte,
7123
7115
  clampInt,
7116
+ clampNonNeg,
7124
7117
  clampPercent,
7118
+ clampPositiveInt,
7125
7119
  clearAnsiCache,
7126
7120
  clearColorCache,
7127
7121
  clearLine,
@@ -7142,6 +7136,7 @@ var index_default = ansimax;
7142
7136
  debounce,
7143
7137
  diffLines,
7144
7138
  easings,
7139
+ ensureString,
7145
7140
  escapeForRegex,
7146
7141
  escapeRegex,
7147
7142
  fg256,
package/dist/index.mjs CHANGED
@@ -412,6 +412,15 @@ var clampInt = (value, min, max, fallback = 0) => {
412
412
  }
413
413
  return Math.max(min, Math.min(max, Math.floor(value)));
414
414
  };
415
+ var ensureString2 = (v) => typeof v === "string" ? v : String(v ?? "");
416
+ var clampNonNeg = (n, fallback = 0) => {
417
+ if (!isFiniteNumber2(n)) return Math.max(0, Math.floor(fallback));
418
+ return Math.max(0, Math.floor(n));
419
+ };
420
+ var clampPositiveInt = (n, fallback = 1) => {
421
+ if (!isFiniteNumber2(n)) return Math.max(1, Math.floor(fallback));
422
+ return Math.max(1, Math.floor(n));
423
+ };
415
424
  var HEX_RE = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
416
425
  var isHexColor = (hex) => typeof hex === "string" && HEX_RE.test(hex.trim());
417
426
  var hexToRgb = (hex) => {
@@ -2364,7 +2373,7 @@ var clearRenderCache = () => {
2364
2373
  _renderCache.clear();
2365
2374
  };
2366
2375
  var getRenderCacheSize = () => _renderCache.size;
2367
- var ensureString2 = (value, paramName) => {
2376
+ var ensureString3 = (value, paramName) => {
2368
2377
  if (typeof value !== "string") {
2369
2378
  throw new TypeError(
2370
2379
  `ascii: ${paramName} must be a string, got ${typeof value}`
@@ -2503,15 +2512,15 @@ var BOX_STYLES = {
2503
2512
  ascii: { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" }
2504
2513
  };
2505
2514
  var big = (text) => {
2506
- const safe = ensureString2(text, "big(text)");
2515
+ const safe = ensureString3(text, "big(text)");
2507
2516
  return renderFont(safe, BLOCK, { _fontKey: "big" });
2508
2517
  };
2509
2518
  var small = (text) => {
2510
- const safe = ensureString2(text, "small(text)");
2519
+ const safe = ensureString3(text, "small(text)");
2511
2520
  return renderFont(safe, SMALL, { _fontKey: "small" });
2512
2521
  };
2513
2522
  var figlet = (text, opts = {}) => {
2514
- const safe = ensureString2(text, "figlet(text)");
2523
+ const safe = ensureString3(text, "figlet(text)");
2515
2524
  const fontName = opts.font ?? "big";
2516
2525
  const fontMap = FONTS[fontName] ?? BLOCK;
2517
2526
  return renderFont(safe, fontMap, {
@@ -2521,7 +2530,7 @@ var figlet = (text, opts = {}) => {
2521
2530
  });
2522
2531
  };
2523
2532
  var stageRender = (text, font, letterSpacing) => {
2524
- const safe = ensureString2(text, "stageRender(text)");
2533
+ const safe = ensureString3(text, "stageRender(text)");
2525
2534
  const fontMap = FONTS[font] ?? BLOCK;
2526
2535
  const renderOpts = { _fontKey: typeof font === "string" ? font : "big" };
2527
2536
  if (letterSpacing !== void 0) renderOpts.letterSpacing = letterSpacing;
@@ -2563,7 +2572,7 @@ var stageColorize = (rendered, colorFn, perCharColor) => {
2563
2572
  return rendered.split("\n").map(colorFn).join("\n");
2564
2573
  };
2565
2574
  var banner = (text, opts = {}) => {
2566
- const safe = ensureString2(text, "banner(text)");
2575
+ const safe = ensureString3(text, "banner(text)");
2567
2576
  const {
2568
2577
  font = "big",
2569
2578
  colorFn = null,
@@ -2577,7 +2586,7 @@ var banner = (text, opts = {}) => {
2577
2586
  return result;
2578
2587
  };
2579
2588
  var box = (text, opts = {}) => {
2580
- const safe = ensureString2(text, "box(text)");
2589
+ const safe = ensureString3(text, "box(text)");
2581
2590
  const {
2582
2591
  padding = 1,
2583
2592
  borderStyle = "rounded",
@@ -2658,7 +2667,7 @@ var divider = (opts = {}) => {
2658
2667
  return fill.repeat(w);
2659
2668
  };
2660
2669
  var logo = (text, opts = {}) => {
2661
- const safe = ensureString2(text, "logo(text)");
2670
+ const safe = ensureString3(text, "logo(text)");
2662
2671
  const { gradient: gfn = null, boxStyle = "double", centered = true } = opts;
2663
2672
  if (!safe.length) return box("", { borderStyle: boxStyle, padding: 1 });
2664
2673
  const art = big(safe);
@@ -2672,7 +2681,7 @@ var logo = (text, opts = {}) => {
2672
2681
  return box(lines.join("\n"), { borderStyle: boxStyle, padding: 1 });
2673
2682
  };
2674
2683
  var measure = (text, font = "big", letterSpacing) => {
2675
- const safe = ensureString2(text, "measure(text)");
2684
+ const safe = ensureString3(text, "measure(text)");
2676
2685
  if (!safe.length) return { width: 0, height: 0 };
2677
2686
  const rendered = stageRender(safe, font, letterSpacing);
2678
2687
  const lines = rendered.split("\n");
@@ -2680,7 +2689,7 @@ var measure = (text, font = "big", letterSpacing) => {
2680
2689
  return { width, height: lines.length };
2681
2690
  };
2682
2691
  var stream = async function* (text, opts = {}) {
2683
- const safe = ensureString2(text, "stream(text)");
2692
+ const safe = ensureString3(text, "stream(text)");
2684
2693
  const { font = "big", letterSpacing, granularity = "row", signal } = opts;
2685
2694
  if (!safe.length) return;
2686
2695
  if (signal?.aborted) return;
@@ -3046,11 +3055,6 @@ var ascii = {
3046
3055
 
3047
3056
  // src/loaders/index.ts
3048
3057
  var canAnimate2 = () => Boolean(process.stdout?.isTTY) && supportsColor() !== "none";
3049
- var ensureString3 = (v) => typeof v === "string" ? v : String(v ?? "");
3050
- var clampPositiveInt = (n, fallback) => {
3051
- if (!isFiniteNumber2(n)) return fallback;
3052
- return Math.max(1, Math.floor(n));
3053
- };
3054
3058
  var isUnicodeCapable = () => {
3055
3059
  const env = process.env;
3056
3060
  if (env["CI"]) return true;
@@ -3167,9 +3171,9 @@ var spin = (text = "Loading", opts = {}) => {
3167
3171
  signal,
3168
3172
  reducedMotion = false
3169
3173
  } = opts;
3170
- const safeText = ensureString3(text);
3171
- const safePrefix = ensureString3(prefix);
3172
- const safeSuffix = ensureString3(suffix);
3174
+ const safeText = ensureString2(text);
3175
+ const safePrefix = ensureString2(prefix);
3176
+ const safeSuffix = ensureString2(suffix);
3173
3177
  const safeInterval = clampPositiveInt(interval, 80);
3174
3178
  registerCrashHandlers2();
3175
3179
  const frames2 = resolveSpinnerFrames(type);
@@ -3181,7 +3185,7 @@ var spin = (text = "Loading", opts = {}) => {
3181
3185
  return (finalText, success) => {
3182
3186
  if (finalText !== void 0) {
3183
3187
  const icon = success === false ? "\u2717" : success === true ? "\u2713" : "";
3184
- write(`${safePrefix}${icon ? icon + " " : ""}${ensureString3(finalText)}${safeSuffix}`);
3188
+ write(`${safePrefix}${icon ? icon + " " : ""}${ensureString2(finalText)}${safeSuffix}`);
3185
3189
  if (persist) writeln();
3186
3190
  }
3187
3191
  };
@@ -3222,7 +3226,7 @@ var spin = (text = "Loading", opts = {}) => {
3222
3226
  if (finalText !== void 0) {
3223
3227
  const icon = success === false ? sgr(FG.red) + "\u2717" + reset() : success === true ? sgr(FG.green) + "\u2713" + reset() : "";
3224
3228
  const line = padToTerminalWidth(
3225
- `${safePrefix}${icon ? icon + " " : ""}${ensureString3(finalText)}${safeSuffix}`
3229
+ `${safePrefix}${icon ? icon + " " : ""}${ensureString2(finalText)}${safeSuffix}`
3226
3230
  );
3227
3231
  write(line);
3228
3232
  if (persist) writeln();
@@ -3251,7 +3255,7 @@ var progress = (percent, label = "", opts = {}) => {
3251
3255
  const safeWidth = clampPositiveInt(width, 30);
3252
3256
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
3253
3257
  const safeEmpty = typeof emptyChar === "string" && emptyChar.length > 0 ? emptyChar : "\u2591";
3254
- const safeLabel = ensureString3(label);
3258
+ const safeLabel = ensureString2(label);
3255
3259
  const clamped = clampPercent(percent);
3256
3260
  const filled = Math.floor(clamped / 100 * safeWidth);
3257
3261
  const empty = Math.max(0, safeWidth - filled);
@@ -3441,7 +3445,7 @@ var custom = (frames2, text = "", opts = {}) => {
3441
3445
  var countdown = async (seconds, opts = {}) => {
3442
3446
  const { label = "Starting in", color: hex, signal } = opts;
3443
3447
  const safeSeconds = isFiniteNumber2(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
3444
- const safeLabel = ensureString3(label);
3448
+ const safeLabel = ensureString2(label);
3445
3449
  const colorToUse = safeColor(hex) ? hex : "#ffd700";
3446
3450
  if (!canAnimate2() || signal?.aborted) {
3447
3451
  const final = applyColor(String(safeSeconds), colorToUse);
@@ -3648,7 +3652,6 @@ var loader = {
3648
3652
  };
3649
3653
 
3650
3654
  // src/frames/index.ts
3651
- var ensureString4 = (v) => typeof v === "string" ? v : String(v ?? "");
3652
3655
  var MAX_FPS = 60;
3653
3656
  var clampFps = (fps, fallback) => {
3654
3657
  if (!isFiniteNumber2(fps)) return fallback;
@@ -3789,7 +3792,7 @@ var play = (frames2, opts = {}) => {
3789
3792
  } catch {
3790
3793
  rendered = frame2;
3791
3794
  }
3792
- const safe = ensureString4(rendered);
3795
+ const safe = ensureString2(rendered);
3793
3796
  try {
3794
3797
  write(safe);
3795
3798
  } catch {
@@ -3899,7 +3902,7 @@ var generate = (count, fn) => {
3899
3902
  if (typeof fn !== "function") return Array(safeCount).fill("");
3900
3903
  return Array.from({ length: safeCount }, (_, i) => {
3901
3904
  try {
3902
- return ensureString4(fn(i, safeCount));
3905
+ return ensureString2(fn(i, safeCount));
3903
3906
  } catch {
3904
3907
  return "";
3905
3908
  }
@@ -3957,15 +3960,15 @@ var live = (opts = {}) => {
3957
3960
  if (wasRunning) showCursorSafe2();
3958
3961
  };
3959
3962
  const update = (newFrame) => {
3960
- currentFrame = ensureString4(newFrame);
3963
+ currentFrame = ensureString2(newFrame);
3961
3964
  if (running) render2();
3962
3965
  };
3963
3966
  if (autoStart) start();
3964
3967
  return { start, stop, update, isRunning: () => running };
3965
3968
  };
3966
3969
  var morph = (frameA, frameB, steps = 8, charset = "\u2591\u2592\u2593\u2588\u2593\u2592\u2591") => {
3967
- const a0 = ensureString4(frameA);
3968
- const b0 = ensureString4(frameB);
3970
+ const a0 = ensureString2(frameA);
3971
+ const b0 = ensureString2(frameB);
3969
3972
  if (!a0 && !b0) return [""];
3970
3973
  const n = Math.max(2, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
3971
3974
  const len = Math.max(a0.length, b0.length);
@@ -3992,7 +3995,7 @@ var presets2 = {
3992
3995
  const safeWidth = Math.max(0, isFiniteNumber2(width) ? Math.floor(width) : 20);
3993
3996
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
3994
3997
  const safeEmpty = typeof empty === "string" && empty.length > 0 ? empty : "\u2591";
3995
- const safeLabel = ensureString4(label);
3998
+ const safeLabel = ensureString2(label);
3996
3999
  return generate(safeWidth + 1, (i) => {
3997
4000
  const filled = safeChar.repeat(i);
3998
4001
  const rest = safeEmpty.repeat(safeWidth - i);
@@ -4012,7 +4015,7 @@ var presets2 = {
4012
4015
  /* istanbul ignore next — default opts {} */
4013
4016
  breathe: (text, opts = {}) => {
4014
4017
  const { steps = 8 } = opts;
4015
- const safeText = ensureString4(text);
4018
+ const safeText = ensureString2(text);
4016
4019
  const safeSteps2 = Math.max(1, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
4017
4020
  const shades = ["\u2591", "\u2592", "\u2593", "\u2588"];
4018
4021
  return generate(safeSteps2 * 2, (i) => {
@@ -4024,7 +4027,7 @@ var presets2 = {
4024
4027
  /* istanbul ignore next — default opts {} */
4025
4028
  typeDelete: (text, opts = {}) => {
4026
4029
  const { cursor: cur = "\u258C" } = opts;
4027
- const safeText = ensureString4(text);
4030
+ const safeText = ensureString2(text);
4028
4031
  const safeCur = typeof cur === "string" ? cur : "\u258C";
4029
4032
  const typed = generate(safeText.length + 1, (i) => safeText.slice(0, i) + safeCur);
4030
4033
  const deleted = generate(safeText.length + 1, (i) => safeText.slice(0, safeText.length - i) + safeCur);
@@ -4034,15 +4037,6 @@ var presets2 = {
4034
4037
  var frames = { play, generate, live, morph, presets: presets2 };
4035
4038
 
4036
4039
  // src/components/index.ts
4037
- var ensureString5 = (v) => typeof v === "string" ? v : String(v ?? "");
4038
- var clampNonNeg = (n, fallback) => {
4039
- if (!isFiniteNumber2(n)) return fallback;
4040
- return Math.max(0, Math.floor(n));
4041
- };
4042
- var clampPositive2 = (n, fallback) => {
4043
- if (!isFiniteNumber2(n)) return fallback;
4044
- return Math.max(1, Math.floor(n));
4045
- };
4046
4040
  var safeSgrCode = (code, fallback) => {
4047
4041
  if (!isFiniteNumber2(code)) return fallback;
4048
4042
  return Math.max(0, Math.min(255, Math.floor(code)));
@@ -4089,7 +4083,7 @@ var table = (rows, opts = {}) => {
4089
4083
  if (cols === 0) return "";
4090
4084
  const processedRows = validRows.map(
4091
4085
  (row) => Array.from({ length: cols }, (_, ci) => {
4092
- const raw = ensureString5(row[ci]);
4086
+ const raw = ensureString2(row[ci]);
4093
4087
  const lines2 = raw.split("\n");
4094
4088
  return safeMaxCol !== null ? lines2.map((l) => truncateVisible(l, safeMaxCol)) : lines2;
4095
4089
  })
@@ -4140,8 +4134,8 @@ var badge = (label, value, opts = {}) => {
4140
4134
  padding = 1,
4141
4135
  border = false
4142
4136
  } = opts;
4143
- const safeLabel = ensureString5(label);
4144
- const safeValue = ensureString5(value);
4137
+ const safeLabel = ensureString2(label);
4138
+ const safeValue = ensureString2(value);
4145
4139
  const safePadding = clampNonNeg(padding, 1);
4146
4140
  const pad = " ".repeat(safePadding);
4147
4141
  const lhs = sgr(safeSgrCode(labelBg, BG.blue), safeSgrCode(labelFg, FG.white)) + pad + safeLabel + pad + reset();
@@ -4165,10 +4159,10 @@ var progressBar = (percent, opts = {}) => {
4165
4159
  color: color2 = null,
4166
4160
  gradient: gradientStops2 = null
4167
4161
  } = opts;
4168
- const safeWidth = clampPositive2(width, 30);
4162
+ const safeWidth = clampPositiveInt(width, 30);
4169
4163
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
4170
4164
  const safeEmpty = typeof emptyChar === "string" && emptyChar.length > 0 ? emptyChar : "\u2591";
4171
- const safeLabel = ensureString5(label);
4165
+ const safeLabel = ensureString2(label);
4172
4166
  const clamped = clampPercent(percent);
4173
4167
  const filled = Math.floor(clamped / 100 * safeWidth);
4174
4168
  const empty = Math.max(0, safeWidth - filled);
@@ -4195,7 +4189,7 @@ var status = (type, message, opts = {}) => {
4195
4189
  const def = STATUS_MAP[type] ?? STATUS_MAP.info;
4196
4190
  const colorCode = opts.color !== void 0 ? safeSgrCode(opts.color, def.color) : def.color;
4197
4191
  const useIcon = opts.icon === void 0 ? def.icon : opts.icon ?? "";
4198
- const safeMsg = ensureString5(message);
4192
+ const safeMsg = ensureString2(message);
4199
4193
  const iconPart = useIcon ? sgr(colorCode) + useIcon + reset() + " " : "";
4200
4194
  if (safeMsg.includes("\n")) {
4201
4195
  const indent = " ".repeat(useIcon ? visibleLen(useIcon) + 1 : 0);
@@ -4207,7 +4201,7 @@ var status = (type, message, opts = {}) => {
4207
4201
  var section = (title, opts = {}) => {
4208
4202
  const { char = "\u2500", width = null, color: colorCode = FG.cyan } = opts;
4209
4203
  const { cols } = termSize();
4210
- const safeTitle = ensureString5(title);
4204
+ const safeTitle = ensureString2(title);
4211
4205
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2500";
4212
4206
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4213
4207
  const titleLen = visibleLen(safeTitle);
@@ -4222,7 +4216,7 @@ var section = (title, opts = {}) => {
4222
4216
  var columns = (items, opts = {}) => {
4223
4217
  if (!Array.isArray(items) || items.length === 0) return "";
4224
4218
  const { cols: numCols = 2, gap = 2, width = null, overflow = "truncate" } = opts;
4225
- const safeCols = clampPositive2(numCols, 2);
4219
+ const safeCols = clampPositiveInt(numCols, 2);
4226
4220
  const safeGap = clampNonNeg(gap, 2);
4227
4221
  const { cols: termCols } = termSize();
4228
4222
  const totalW = width !== null && isFiniteNumber2(width) ? Math.max(safeCols, Math.floor(width)) : termCols;
@@ -4232,7 +4226,7 @@ var columns = (items, opts = {}) => {
4232
4226
  for (let i = 0; i < items.length; i += safeCols) {
4233
4227
  const chunk = items.slice(i, i + safeCols);
4234
4228
  if (overflow === "wrap") {
4235
- const cellLines = chunk.map((it) => wrapVisible(ensureString5(it), colW));
4229
+ const cellLines = chunk.map((it) => wrapVisible(ensureString2(it), colW));
4236
4230
  const rowHeight = Math.max(...cellLines.map((c) => c.length), 1);
4237
4231
  for (let line = 0; line < rowHeight; line++) {
4238
4232
  rows.push(
@@ -4242,7 +4236,7 @@ var columns = (items, opts = {}) => {
4242
4236
  } else {
4243
4237
  rows.push(
4244
4238
  chunk.map((item) => {
4245
- const t = truncateVisible(ensureString5(item), colW);
4239
+ const t = truncateVisible(ensureString2(item), colW);
4246
4240
  return padEnd(t, colW);
4247
4241
  }).join(gapStr)
4248
4242
  );
@@ -4265,15 +4259,15 @@ var timeline = (events, opts = {}) => {
4265
4259
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4266
4260
  const safeDoneColor = safeSgrCode(doneColor, FG.green);
4267
4261
  const safePendingColor = safeSgrCode(pendingColor, FG.brightBlack);
4268
- const computedTimeWidth = timeColumnWidth !== null && isFiniteNumber2(timeColumnWidth) ? Math.max(0, Math.floor(timeColumnWidth)) : Math.max(0, ...events.map((e) => e.time ? visibleLen(ensureString5(e.time)) : 0));
4262
+ const computedTimeWidth = timeColumnWidth !== null && isFiniteNumber2(timeColumnWidth) ? Math.max(0, Math.floor(timeColumnWidth)) : Math.max(0, ...events.map((e) => e.time ? visibleLen(ensureString2(e.time)) : 0));
4269
4263
  const lines = [];
4270
4264
  events.forEach((ev, i) => {
4271
4265
  const isLast = i === events.length - 1;
4272
- const evLabel = ensureString5(ev.label);
4266
+ const evLabel = ensureString2(ev.label);
4273
4267
  const clr = ev.done ? safeDoneColor : i === 0 ? safeColor2 : safePendingColor;
4274
4268
  const nodeStr = sgr(clr) + safeNode + reset();
4275
4269
  const textStr = ev.done ? sgr(STYLE.bold) + evLabel + reset() : sgr(safePendingColor) + evLabel + reset();
4276
- const timePart = ev.time && computedTimeWidth > 0 ? " " + sgr(FG.brightBlack) + padEnd(ensureString5(ev.time), computedTimeWidth) + reset() : "";
4270
+ const timePart = ev.time && computedTimeWidth > 0 ? " " + sgr(FG.brightBlack) + padEnd(ensureString2(ev.time), computedTimeWidth) + reset() : "";
4277
4271
  lines.push(`${nodeStr} ${textStr}${timePart}`);
4278
4272
  if (!isLast) lines.push(sgr(safePendingColor) + safeConnector + reset());
4279
4273
  });
@@ -4295,8 +4289,8 @@ var menu = (items, opts = {}) => {
4295
4289
  if (!inp.isTTY) return Promise.resolve(multiSelect ? [] : 0);
4296
4290
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4297
4291
  const safePointer = typeof pointer === "string" && pointer.length > 0 ? pointer : "\u25B6";
4298
- const safeTitle = title === null ? null : ensureString5(title);
4299
- const safeItems = items.map(ensureString5);
4292
+ const safeTitle = title === null ? null : ensureString2(title);
4293
+ const safeItems = items.map(ensureString2);
4300
4294
  let cursorPos = 0;
4301
4295
  let lastRenderedLines = 0;
4302
4296
  let cursorHidden = false;
@@ -4457,19 +4451,14 @@ var STYLES = {
4457
4451
  heavy: { branch: "\u2523\u2501\u2501 ", last: "\u2517\u2501\u2501 ", vert: "\u2503 ", space: " " },
4458
4452
  ascii: { branch: "+-- ", last: "`-- ", vert: "| ", space: " " }
4459
4453
  };
4460
- var ensureString6 = (v) => typeof v === "string" ? v : String(v ?? "");
4461
- var clampNonNeg2 = (n, fallback) => {
4462
- if (!isFiniteNumber2(n)) return fallback;
4463
- return Math.max(0, Math.floor(n));
4464
- };
4465
4454
  var normalizeNewlines = (s) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
4466
4455
  var toTreeData = (child) => {
4467
4456
  if (typeof child === "string") return { label: child, children: [] };
4468
4457
  if (typeof child !== "object" || child === null || Array.isArray(child)) {
4469
- return { label: ensureString6(child), children: [] };
4458
+ return { label: ensureString2(child), children: [] };
4470
4459
  }
4471
4460
  return {
4472
- label: ensureString6(child.label ?? ""),
4461
+ label: ensureString2(child.label ?? ""),
4473
4462
  children: [],
4474
4463
  ...child
4475
4464
  };
@@ -4505,8 +4494,8 @@ var paletteAt = (palette, depth) => {
4505
4494
  };
4506
4495
  var formatNode = (node, depth, palette) => {
4507
4496
  const colorFn = node.color ?? paletteAt(palette, depth);
4508
- const safeLabel = normalizeNewlines(ensureString6(node.label));
4509
- const iconPrefix = node.icon ? ensureString6(node.icon) + " " : "";
4497
+ const safeLabel = normalizeNewlines(ensureString2(node.label));
4498
+ const iconPrefix = node.icon ? ensureString2(node.icon) + " " : "";
4510
4499
  const labelLines = safeLabel.split("\n");
4511
4500
  return labelLines.map((line, i) => {
4512
4501
  const text = i === 0 ? iconPrefix + line : line;
@@ -4600,7 +4589,7 @@ var renderTree = (root, opts = {}) => {
4600
4589
  indent = 0
4601
4590
  } = opts;
4602
4591
  const safeStyle = style && STYLES[style] ? style : "normal";
4603
- const safeIndent = clampNonNeg2(indent, 0);
4592
+ const safeIndent = clampNonNeg(indent, 0);
4604
4593
  const safeMaxDepth = isFiniteNumber2(maxDepth) ? Math.max(0, Math.floor(maxDepth)) : Infinity;
4605
4594
  const indentStr = " ".repeat(safeIndent);
4606
4595
  const lines = [];
@@ -6908,7 +6897,9 @@ export {
6908
6897
  clamp,
6909
6898
  clampByte2 as clampByte,
6910
6899
  clampInt,
6900
+ clampNonNeg,
6911
6901
  clampPercent,
6902
+ clampPositiveInt,
6912
6903
  clearAnsiCache,
6913
6904
  clearColorCache,
6914
6905
  clearLine,
@@ -6930,6 +6921,7 @@ export {
6930
6921
  index_default as default,
6931
6922
  diffLines,
6932
6923
  easings,
6924
+ ensureString2 as ensureString,
6933
6925
  escapeForRegex,
6934
6926
  escapeRegex,
6935
6927
  fg256,
@@ -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.4.1'),
121
+ components.badge('VERSION', 'v1.4.2'),
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.4.1'),
120
+ components.badge('VERSION', 'v1.4.2'),
121
121
  components.badge('BUILD', 'passing'),
122
122
  components.badge('LICENSE', 'Apache 2.0'));
123
123
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ansimax",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
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",