ansimax 1.3.5 → 1.3.7

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,123 @@
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.3.7] — Internal consolidation + new clamp helpers
7
+
8
+ Maintenance release focused on code cleanup. Zero behavior changes —
9
+ internal duplications consolidated into the existing `utils/helpers`
10
+ module, plus two new exported helpers that codify the most common
11
+ defensive patterns.
12
+
13
+ ### Added — New exported helpers
14
+
15
+ **`clampPercent(value)`** — clamp + coerce to 0–100 range, with 0 fallback
16
+ for non-finite input. Previously duplicated as a private function in
17
+ `components/index.ts` and `loaders/index.ts`:
18
+
19
+ ```js
20
+ import { clampPercent } from 'ansimax';
21
+
22
+ clampPercent(50) // → 50
23
+ clampPercent(150) // → 100
24
+ clampPercent(-5) // → 0
25
+ clampPercent(NaN) // → 0
26
+ clampPercent('abc') // → 0
27
+ ```
28
+
29
+ **`clampInt(value, min, max, fallback?)`** — coerce to integer clamped
30
+ between `[min, max]`. Falls back (and clamps) when input is non-finite:
31
+
32
+ ```js
33
+ import { clampInt } from 'ansimax';
34
+
35
+ clampInt(50.7, 0, 100) // → 50 (floored)
36
+ clampInt(150, 0, 100) // → 100 (clamped)
37
+ clampInt(NaN, 0, 100, 25) // → 25 (fallback, also clamped)
38
+ clampInt(NaN, 10, 20, 999) // → 20 (fallback clamped to range)
39
+ ```
40
+
41
+ ### Improved — Internal consolidation (DRY)
42
+
43
+ Removed 5 copies of the inline `isFiniteNumber` definition that were
44
+ duplicated across modules. They now all import the canonical version
45
+ from `utils/helpers` (exported since v1.3.5):
46
+
47
+ - `components/index.ts` — was 1 inline copy + 1 inline `clampPercent`
48
+ - `frames/index.ts` — was 1 inline copy
49
+ - `images/index.ts` — was 1 inline copy + 1 inline `clampInt`
50
+ - `loaders/index.ts` — was 1 inline copy + 1 inline `clampPercent`
51
+ - `trees/index.ts` — was 1 inline copy
52
+
53
+ `utils/ansi.ts` intentionally keeps its private copy to avoid creating a
54
+ dependency on `utils/helpers` (the two modules are deliberately
55
+ independent at the bottom of the dependency graph).
56
+
57
+ ### Improved — Tests
58
+
59
+ - `+6` tests for `clampPercent` (range, fallback, fractional values, non-numeric input)
60
+ - `+6` tests for `clampInt` (clamping, fallback behavior, edge cases)
61
+ - `+1` test for barrel re-exports
62
+
63
+ Total: **+13 tests**.
64
+
65
+ ### Notes
66
+
67
+ - **No behavior changes** — the consolidated helpers are byte-identical
68
+ to the inline copies they replaced
69
+ - **No API changes** — only additions (`clampPercent`, `clampInt`)
70
+ - **No breaking changes** — drop-in replacement for `1.3.6`
71
+ - Smaller bundle output expected as TypeScript can dedupe the
72
+ consolidated implementations
73
+
74
+ ---
75
+
76
+ ## [1.3.6] — Branch coverage improvements
77
+
78
+ Maintenance release. Zero behavior changes — only adds tests covering
79
+ defensive branches and adjusts `istanbul ignore` comments where branches
80
+ are genuinely unreachable in normal usage.
81
+
82
+ ### Improved — Tests
83
+
84
+ **panels (`+7` tests):**
85
+ - `vsplit` default `gap = 1` when option omitted
86
+ - `vsplit` handles columns of unequal heights (triggers `block[r] ?? ''` fallback)
87
+ - `centerBlock` default `align = 'center'` with `height` (vertical centering branch)
88
+ - `centerBlock` with explicit `align='start'` (alternative branch)
89
+ - `frame` fallback `'─'` when `topChar=''` or non-string
90
+ - `grid` default `columns = 1` when option omitted/undefined
91
+
92
+ **frames (`+7` tests):**
93
+ - `ensureString` handles `null`/`undefined` via `?? ''` fallback
94
+ - `presets.loadingBar` fallback `'░'` for invalid `empty` (`''` or non-string)
95
+ - `presets.ball` fallback `width = 20` for `NaN`/non-number input
96
+ - `presets.breathe` fallback `steps = 8` for `NaN`/non-number input
97
+
98
+ **loaders (`+5` tests):**
99
+ - `loader.bar` fallback `'░'` for invalid `emptyChar`
100
+ - `loader.spin` finalText with all three success states (`undefined`, `true`, `false`)
101
+ — exercises icon ternary chain branches
102
+
103
+ ### Improved — `istanbul ignore` comments
104
+
105
+ Some `??` and conditional-spread branches are unreachable in real usage
106
+ (e.g. `frames[frame] ?? ''` after `frame = i % frames.length` with a non-empty
107
+ array). Marked with explanatory `istanbul ignore next` comments instead of
108
+ chasing coverage with synthetic tests:
109
+
110
+ - `loaders:376` — `frames[frame] ?? ''` (frame index always in bounds)
111
+ - `loaders:997` — second instance of `addOpts.color !== undefined` spread
112
+
113
+ Total: **+19 tests** added across panels, frames, loaders.
114
+
115
+ ### Notes
116
+
117
+ - No runtime changes — drop-in replacement for `1.3.5`
118
+ - No new exports, no API surface changes
119
+ - Same `dist/` output as `1.3.5` would have produced
120
+
121
+ ---
122
+
6
123
  ## [1.3.5] — Mathematical color science + cleanup
7
124
 
8
125
  Patch release focused on mathematical depth: perceptually-uniform color
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.3.5-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.3.7-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.3.5'));
481
+ console.log(components.badge('VERSION', 'v1.3.7'));
482
482
  console.log(components.badge('BUILD', 'passing'));
483
483
  ```
484
484
 
@@ -1065,6 +1065,36 @@ ansimax/
1065
1065
 
1066
1066
  ## 📝 Changelog
1067
1067
 
1068
+ ### v1.3.7 — Consolidación interna + helpers clamp
1069
+
1070
+ Release de mantenimiento enfocado en limpieza de código. Cero cambios de comportamiento:
1071
+
1072
+ - ➕ **`clampPercent(value)`** — clamp + coerce a rango 0–100 (NaN/no-numérico → 0)
1073
+ - ➕ **`clampInt(value, min, max, fallback?)`** — clamp de enteros con fallback seguro
1074
+ - 🧹 Removidas 5 copias duplicadas de `isFiniteNumber` entre módulos (ahora importadas desde `utils/helpers`)
1075
+ - 🧹 Removidas 2 copias duplicadas de `clampPercent` (components + loaders)
1076
+ - 🧹 Removida 1 copia duplicada de `clampInt` en images
1077
+ - 🧪 **+13 tests** para los helpers consolidados
1078
+
1079
+ ```js
1080
+ import { clampPercent, clampInt } from 'ansimax';
1081
+
1082
+ clampPercent(150) // → 100 (clamped)
1083
+ clampPercent(NaN) // → 0 (fallback seguro)
1084
+ clampInt(50.7, 0, 100) // → 50 (floored + clamped)
1085
+ clampInt(NaN, 0, 100, 25) // → 25 (fallback con inválidos)
1086
+ ```
1087
+
1088
+ Drop-in replacement para `1.3.6`.
1089
+
1090
+ ### v1.3.6 — Mejoras de branch coverage
1091
+
1092
+ Release de mantenimiento. Cero cambios de comportamiento:
1093
+
1094
+ - 🧪 **+19 tests** cubriendo branches defensivos en panels, frames, loaders
1095
+ - 🧹 Mejorados comentarios `istanbul ignore` para branches genuinamente inalcanzables
1096
+ - ✅ Drop-in replacement para `1.3.5` — mismo `dist/` output
1097
+
1068
1098
  ### v1.3.5 — Color science matemático + limpieza
1069
1099
 
1070
1100
  Release patch enfocado en profundidad matemática y limpieza de código. 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.3.5-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.3.7-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.3.5'));
481
+ console.log(components.badge('VERSION', 'v1.3.7'));
482
482
  console.log(components.badge('BUILD', 'passing'));
483
483
  ```
484
484
 
@@ -1065,6 +1065,36 @@ ansimax/
1065
1065
 
1066
1066
  ## 📝 Changelog
1067
1067
 
1068
+ ### v1.3.7 — Internal consolidation + clamp helpers
1069
+
1070
+ Maintenance release focused on code cleanup. Zero behavior changes:
1071
+
1072
+ - ➕ **`clampPercent(value)`** — clamp + coerce to 0–100 range (NaN/non-numeric → 0)
1073
+ - ➕ **`clampInt(value, min, max, fallback?)`** — integer clamp with safe fallback
1074
+ - 🧹 Removed 5 duplicate copies of `isFiniteNumber` across modules (now imported from `utils/helpers`)
1075
+ - 🧹 Removed 2 duplicate copies of `clampPercent` (components + loaders)
1076
+ - 🧹 Removed 1 duplicate `clampInt` from images
1077
+ - 🧪 **+13 tests** for the consolidated helpers
1078
+
1079
+ ```js
1080
+ import { clampPercent, clampInt } from 'ansimax';
1081
+
1082
+ clampPercent(150) // → 100 (clamped)
1083
+ clampPercent(NaN) // → 0 (safe fallback)
1084
+ clampInt(50.7, 0, 100) // → 50 (floored + clamped)
1085
+ clampInt(NaN, 0, 100, 25) // → 25 (fallback when invalid)
1086
+ ```
1087
+
1088
+ Drop-in replacement for `1.3.6`.
1089
+
1090
+ ### v1.3.6 — Branch coverage improvements
1091
+
1092
+ Maintenance release. Zero behavior changes:
1093
+
1094
+ - 🧪 **+19 tests** covering defensive branches in panels, frames, loaders
1095
+ - 🧹 Improved `istanbul ignore` comments for genuinely unreachable branches
1096
+ - ✅ Drop-in replacement for `1.3.5` — same `dist/` output
1097
+
1068
1098
  ### v1.3.5 — Mathematical color science + cleanup
1069
1099
 
1070
1100
  Patch release focused on math depth and code cleanliness. Zero breaking changes:
package/dist/index.d.mts CHANGED
@@ -168,6 +168,42 @@ declare const lerp: (a: number, b: number, t: number) => number;
168
168
  * @since 1.3.5
169
169
  */
170
170
  declare const clampByte: (v: number) => number;
171
+ /**
172
+ * Clamp + coerce a number to the 0–100 percentage range. Returns `0` for
173
+ * non-finite input. Consolidates duplicate `clampPercent` definitions
174
+ * previously living in `components/index.ts` and `loaders/index.ts`.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * clampPercent(50) // → 50
179
+ * clampPercent(150) // → 100
180
+ * clampPercent(-5) // → 0
181
+ * clampPercent(NaN) // → 0
182
+ * clampPercent('abc') // → 0
183
+ * ```
184
+ *
185
+ * @since 1.3.7
186
+ */
187
+ declare const clampPercent: (p: unknown) => number;
188
+ /**
189
+ * Coerce any value to an integer clamped between `[min, max]`. Returns
190
+ * `fallback` (which is also clamped) when input is non-finite.
191
+ *
192
+ * More flexible than `safeInt` for the common pattern
193
+ * `Math.max(min, Math.min(max, Math.floor(n)))` that appears 30+ times
194
+ * across the codebase.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * clampInt(50, 0, 100) // → 50
199
+ * clampInt(150, 0, 100) // → 100
200
+ * clampInt(NaN, 0, 100, 25) // → 25 (fallback)
201
+ * clampInt('abc', 0, 100, 25) // → 25 (fallback)
202
+ * ```
203
+ *
204
+ * @since 1.3.7
205
+ */
206
+ declare const clampInt: (value: unknown, min: number, max: number, fallback?: number) => number;
171
207
  /** Returns true when a string is a valid 3- or 6-digit hex color. */
172
208
  declare const isHexColor: (hex: string) => boolean;
173
209
  /**
@@ -3054,4 +3090,4 @@ declare const ansimax: {
3054
3090
  configure: (opts?: AnsimaxConfig, meta?: ConfigureOptions) => void;
3055
3091
  };
3056
3092
 
3057
- 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 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, 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, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, 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 };
3093
+ 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 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, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, 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
@@ -168,6 +168,42 @@ declare const lerp: (a: number, b: number, t: number) => number;
168
168
  * @since 1.3.5
169
169
  */
170
170
  declare const clampByte: (v: number) => number;
171
+ /**
172
+ * Clamp + coerce a number to the 0–100 percentage range. Returns `0` for
173
+ * non-finite input. Consolidates duplicate `clampPercent` definitions
174
+ * previously living in `components/index.ts` and `loaders/index.ts`.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * clampPercent(50) // → 50
179
+ * clampPercent(150) // → 100
180
+ * clampPercent(-5) // → 0
181
+ * clampPercent(NaN) // → 0
182
+ * clampPercent('abc') // → 0
183
+ * ```
184
+ *
185
+ * @since 1.3.7
186
+ */
187
+ declare const clampPercent: (p: unknown) => number;
188
+ /**
189
+ * Coerce any value to an integer clamped between `[min, max]`. Returns
190
+ * `fallback` (which is also clamped) when input is non-finite.
191
+ *
192
+ * More flexible than `safeInt` for the common pattern
193
+ * `Math.max(min, Math.min(max, Math.floor(n)))` that appears 30+ times
194
+ * across the codebase.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * clampInt(50, 0, 100) // → 50
199
+ * clampInt(150, 0, 100) // → 100
200
+ * clampInt(NaN, 0, 100, 25) // → 25 (fallback)
201
+ * clampInt('abc', 0, 100, 25) // → 25 (fallback)
202
+ * ```
203
+ *
204
+ * @since 1.3.7
205
+ */
206
+ declare const clampInt: (value: unknown, min: number, max: number, fallback?: number) => number;
171
207
  /** Returns true when a string is a valid 3- or 6-digit hex color. */
172
208
  declare const isHexColor: (hex: string) => boolean;
173
209
  /**
@@ -3054,4 +3090,4 @@ declare const ansimax: {
3054
3090
  configure: (opts?: AnsimaxConfig, meta?: ConfigureOptions) => void;
3055
3091
  };
3056
3092
 
3057
- 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 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, 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, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, 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 };
3093
+ 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 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, measureBlock, measureTree, memoize, mixColors, nextTick, oklabToRgb, onConfigChange, onConfigKeyChange, onResize, once, padBoth, padEnd, padStart, panels, parseFiglet, pauseListeners, presetNames, presets, quantizeColor, rainbow, registerFont, registerPreset, 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
@@ -61,6 +61,8 @@ __export(index_exports, {
61
61
  charWidth: () => charWidth,
62
62
  clamp: () => clamp,
63
63
  clampByte: () => clampByte2,
64
+ clampInt: () => clampInt,
65
+ clampPercent: () => clampPercent,
64
66
  clearAnsiCache: () => clearAnsiCache,
65
67
  clearColorCache: () => clearColorCache,
66
68
  clearLine: () => clearLine,
@@ -608,6 +610,16 @@ var safeInt = (value, fallback = 0, min = -Infinity, max = Infinity) => {
608
610
  var clamp = (n, min, max) => Math.min(Math.max(n, min), max);
609
611
  var lerp = (a, b, t) => a + (b - a) * t;
610
612
  var clampByte2 = (v) => clamp(Math.round(v), 0, 255);
613
+ var clampPercent = (p) => {
614
+ if (!isFiniteNumber2(p)) return 0;
615
+ return Math.max(0, Math.min(100, p));
616
+ };
617
+ var clampInt = (value, min, max, fallback = 0) => {
618
+ if (!isFiniteNumber2(value)) {
619
+ return Math.max(min, Math.min(max, Math.floor(fallback)));
620
+ }
621
+ return Math.max(min, Math.min(max, Math.floor(value)));
622
+ };
611
623
  var HEX_RE = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
612
624
  var isHexColor = (hex) => typeof hex === "string" && HEX_RE.test(hex.trim());
613
625
  var hexToRgb = (hex) => {
@@ -3242,16 +3254,11 @@ var ascii = {
3242
3254
 
3243
3255
  // src/loaders/index.ts
3244
3256
  var canAnimate2 = () => Boolean(process.stdout?.isTTY) && supportsColor() !== "none";
3245
- var isFiniteNumber3 = (n) => typeof n === "number" && Number.isFinite(n);
3246
3257
  var ensureString3 = (v) => typeof v === "string" ? v : String(v ?? "");
3247
3258
  var clampPositiveInt = (n, fallback) => {
3248
- if (!isFiniteNumber3(n)) return fallback;
3259
+ if (!isFiniteNumber2(n)) return fallback;
3249
3260
  return Math.max(1, Math.floor(n));
3250
3261
  };
3251
- var clampPercent = (p) => {
3252
- if (!isFiniteNumber3(p)) return 0;
3253
- return Math.max(0, Math.min(100, p));
3254
- };
3255
3262
  var isUnicodeCapable = () => {
3256
3263
  const env = process.env;
3257
3264
  if (env["CI"]) return true;
@@ -3641,7 +3648,7 @@ var custom = (frames2, text = "", opts = {}) => {
3641
3648
  };
3642
3649
  var countdown = async (seconds, opts = {}) => {
3643
3650
  const { label = "Starting in", color: hex, signal } = opts;
3644
- const safeSeconds = isFiniteNumber3(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
3651
+ const safeSeconds = isFiniteNumber2(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
3645
3652
  const safeLabel = ensureString3(label);
3646
3653
  const colorToUse = safeColor(hex) ? hex : "#ffd700";
3647
3654
  if (!canAnimate2() || signal?.aborted) {
@@ -3764,6 +3771,7 @@ var multi = (opts = {}) => {
3764
3771
  const item = {
3765
3772
  id,
3766
3773
  text,
3774
+ /* istanbul ignore next — conditional spread + default */
3767
3775
  ...addOpts.color !== void 0 && { color: addOpts.color },
3768
3776
  type: addOpts.type ?? "dots",
3769
3777
  state: "spinning"
@@ -3848,11 +3856,10 @@ var loader = {
3848
3856
  };
3849
3857
 
3850
3858
  // src/frames/index.ts
3851
- var isFiniteNumber4 = (n) => typeof n === "number" && Number.isFinite(n);
3852
3859
  var ensureString4 = (v) => typeof v === "string" ? v : String(v ?? "");
3853
3860
  var MAX_FPS = 60;
3854
3861
  var clampFps = (fps, fallback) => {
3855
- if (!isFiniteNumber4(fps)) return fallback;
3862
+ if (!isFiniteNumber2(fps)) return fallback;
3856
3863
  return Math.max(1, Math.min(MAX_FPS, Math.floor(fps)));
3857
3864
  };
3858
3865
  var _cursorHiddenCount2 = 0;
@@ -3951,14 +3958,14 @@ var play = (frames2, opts = {}) => {
3951
3958
  if (fps !== void 0) {
3952
3959
  const safeFps = clampFps(fps, 60);
3953
3960
  tickMs = Math.max(FRAME_MS, Math.floor(1e3 / safeFps));
3954
- } else if (isFiniteNumber4(interval)) {
3961
+ } else if (isFiniteNumber2(interval)) {
3955
3962
  tickMs = Math.max(FRAME_MS, Math.floor(interval));
3956
3963
  } else {
3957
3964
  tickMs = 100;
3958
3965
  }
3959
3966
  let safeRepeat;
3960
3967
  let infinite;
3961
- if (!isFiniteNumber4(repeat)) {
3968
+ if (!isFiniteNumber2(repeat)) {
3962
3969
  safeRepeat = 1;
3963
3970
  infinite = false;
3964
3971
  } else if (repeat === 0) {
@@ -4083,7 +4090,7 @@ var play = (frames2, opts = {}) => {
4083
4090
  paused = false;
4084
4091
  },
4085
4092
  seek: (idx) => {
4086
- if (!isFiniteNumber4(idx)) return;
4093
+ if (!isFiniteNumber2(idx)) return;
4087
4094
  const safe = Math.max(0, Math.floor(idx));
4088
4095
  frameIdx = frames2.length > 0 ? safe % frames2.length : 0;
4089
4096
  },
@@ -4096,7 +4103,7 @@ var play = (frames2, opts = {}) => {
4096
4103
  };
4097
4104
  };
4098
4105
  var generate = (count, fn) => {
4099
- const safeCount = isFiniteNumber4(count) ? Math.max(0, Math.floor(count)) : 0;
4106
+ const safeCount = isFiniteNumber2(count) ? Math.max(0, Math.floor(count)) : 0;
4100
4107
  if (typeof fn !== "function") return Array(safeCount).fill("");
4101
4108
  return Array.from({ length: safeCount }, (_, i) => {
4102
4109
  try {
@@ -4168,7 +4175,7 @@ var morph = (frameA, frameB, steps = 8, charset = "\u2591\u2592\u2593\u2588\u259
4168
4175
  const a0 = ensureString4(frameA);
4169
4176
  const b0 = ensureString4(frameB);
4170
4177
  if (!a0 && !b0) return [""];
4171
- const n = Math.max(2, isFiniteNumber4(steps) ? Math.floor(steps) : 8);
4178
+ const n = Math.max(2, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
4172
4179
  const len = Math.max(a0.length, b0.length);
4173
4180
  const a = a0.padEnd(len);
4174
4181
  const b = b0.padEnd(len);
@@ -4190,7 +4197,7 @@ var morph = (frameA, frameB, steps = 8, charset = "\u2591\u2592\u2593\u2588\u259
4190
4197
  var presets2 = {
4191
4198
  loadingBar: (opts = {}) => {
4192
4199
  const { width = 20, char = "\u2588", empty = "\u2591", label = "Loading" } = opts;
4193
- const safeWidth = Math.max(0, isFiniteNumber4(width) ? Math.floor(width) : 20);
4200
+ const safeWidth = Math.max(0, isFiniteNumber2(width) ? Math.floor(width) : 20);
4194
4201
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
4195
4202
  const safeEmpty = typeof empty === "string" && empty.length > 0 ? empty : "\u2591";
4196
4203
  const safeLabel = ensureString4(label);
@@ -4204,7 +4211,7 @@ var presets2 = {
4204
4211
  /* istanbul ignore next — default opts {} */
4205
4212
  ball: (opts = {}) => {
4206
4213
  const { width = 20, char = "\u25CF" } = opts;
4207
- const safeWidth = Math.max(1, isFiniteNumber4(width) ? Math.floor(width) : 20);
4214
+ const safeWidth = Math.max(1, isFiniteNumber2(width) ? Math.floor(width) : 20);
4208
4215
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u25CF";
4209
4216
  const forward = generate(safeWidth, (i) => " ".repeat(i) + safeChar);
4210
4217
  const backward = generate(safeWidth, (i) => " ".repeat(safeWidth - i - 1) + safeChar);
@@ -4214,7 +4221,7 @@ var presets2 = {
4214
4221
  breathe: (text, opts = {}) => {
4215
4222
  const { steps = 8 } = opts;
4216
4223
  const safeText = ensureString4(text);
4217
- const safeSteps2 = Math.max(1, isFiniteNumber4(steps) ? Math.floor(steps) : 8);
4224
+ const safeSteps2 = Math.max(1, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
4218
4225
  const shades = ["\u2591", "\u2592", "\u2593", "\u2588"];
4219
4226
  return generate(safeSteps2 * 2, (i) => {
4220
4227
  const t = i < safeSteps2 ? i / safeSteps2 : 1 - (i - safeSteps2) / safeSteps2;
@@ -4235,22 +4242,17 @@ var presets2 = {
4235
4242
  var frames = { play, generate, live, morph, presets: presets2 };
4236
4243
 
4237
4244
  // src/components/index.ts
4238
- var isFiniteNumber5 = (n) => typeof n === "number" && Number.isFinite(n);
4239
4245
  var ensureString5 = (v) => typeof v === "string" ? v : String(v ?? "");
4240
- var clampPercent2 = (p) => {
4241
- if (!isFiniteNumber5(p)) return 0;
4242
- return Math.max(0, Math.min(100, p));
4243
- };
4244
4246
  var clampNonNeg = (n, fallback) => {
4245
- if (!isFiniteNumber5(n)) return fallback;
4247
+ if (!isFiniteNumber2(n)) return fallback;
4246
4248
  return Math.max(0, Math.floor(n));
4247
4249
  };
4248
4250
  var clampPositive2 = (n, fallback) => {
4249
- if (!isFiniteNumber5(n)) return fallback;
4251
+ if (!isFiniteNumber2(n)) return fallback;
4250
4252
  return Math.max(1, Math.floor(n));
4251
4253
  };
4252
4254
  var safeSgrCode = (code, fallback) => {
4253
- if (!isFiniteNumber5(code)) return fallback;
4255
+ if (!isFiniteNumber2(code)) return fallback;
4254
4256
  return Math.max(0, Math.min(255, Math.floor(code)));
4255
4257
  };
4256
4258
  var truncateVisible = (str, maxWidth, ellipsis = "\u2026") => {
@@ -4288,7 +4290,7 @@ var table = (rows, opts = {}) => {
4288
4290
  const b = TABLE_BORDERS[borderStyle] ?? TABLE_BORDERS.rounded;
4289
4291
  const safePadding = clampNonNeg(padding, 1);
4290
4292
  const pad = " ".repeat(safePadding);
4291
- const safeMaxCol = maxColWidth !== null && isFiniteNumber5(maxColWidth) ? Math.max(1, Math.floor(maxColWidth)) : null;
4293
+ const safeMaxCol = maxColWidth !== null && isFiniteNumber2(maxColWidth) ? Math.max(1, Math.floor(maxColWidth)) : null;
4292
4294
  const validRows = rows.filter((r) => Array.isArray(r));
4293
4295
  if (validRows.length === 0) return "";
4294
4296
  const cols = Math.max(...validRows.map((r) => r.length), 0);
@@ -4375,13 +4377,13 @@ var progressBar = (percent, opts = {}) => {
4375
4377
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
4376
4378
  const safeEmpty = typeof emptyChar === "string" && emptyChar.length > 0 ? emptyChar : "\u2591";
4377
4379
  const safeLabel = ensureString5(label);
4378
- const clamped = clampPercent2(percent);
4380
+ const clamped = clampPercent(percent);
4379
4381
  const filled = Math.floor(clamped / 100 * safeWidth);
4380
4382
  const empty = Math.max(0, safeWidth - filled);
4381
4383
  let filledStr = safeChar.repeat(filled);
4382
4384
  if (Array.isArray(gradientStops2) && gradientStops2.length >= 1 && filled > 0) {
4383
4385
  filledStr = gradient(filledStr, gradientStops2);
4384
- } else if (color2 !== null && isFiniteNumber5(color2)) {
4386
+ } else if (color2 !== null && isFiniteNumber2(color2)) {
4385
4387
  filledStr = sgr(safeSgrCode(color2, FG.white)) + filledStr + reset();
4386
4388
  }
4387
4389
  const emptyStr = safeEmpty.repeat(empty);
@@ -4417,7 +4419,7 @@ var section = (title, opts = {}) => {
4417
4419
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2500";
4418
4420
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4419
4421
  const titleLen = visibleLen(safeTitle);
4420
- const requestedWidth = width !== null && isFiniteNumber5(width) ? Math.max(1, Math.floor(width)) : cols;
4422
+ const requestedWidth = width !== null && isFiniteNumber2(width) ? Math.max(1, Math.floor(width)) : cols;
4421
4423
  const w = Math.max(requestedWidth, titleLen + 2);
4422
4424
  const side = Math.floor((w - titleLen - 2) / 2);
4423
4425
  const dividerL = sgr(safeColor2) + safeChar.repeat(Math.max(0, side)) + reset();
@@ -4431,7 +4433,7 @@ var columns = (items, opts = {}) => {
4431
4433
  const safeCols = clampPositive2(numCols, 2);
4432
4434
  const safeGap = clampNonNeg(gap, 2);
4433
4435
  const { cols: termCols } = termSize();
4434
- const totalW = width !== null && isFiniteNumber5(width) ? Math.max(safeCols, Math.floor(width)) : termCols;
4436
+ const totalW = width !== null && isFiniteNumber2(width) ? Math.max(safeCols, Math.floor(width)) : termCols;
4435
4437
  const colW = Math.max(1, Math.floor((totalW - safeGap * (safeCols - 1)) / safeCols));
4436
4438
  const gapStr = " ".repeat(safeGap);
4437
4439
  const rows = [];
@@ -4471,7 +4473,7 @@ var timeline = (events, opts = {}) => {
4471
4473
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4472
4474
  const safeDoneColor = safeSgrCode(doneColor, FG.green);
4473
4475
  const safePendingColor = safeSgrCode(pendingColor, FG.brightBlack);
4474
- const computedTimeWidth = timeColumnWidth !== null && isFiniteNumber5(timeColumnWidth) ? Math.max(0, Math.floor(timeColumnWidth)) : Math.max(0, ...events.map((e) => e.time ? visibleLen(ensureString5(e.time)) : 0));
4476
+ 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));
4475
4477
  const lines = [];
4476
4478
  events.forEach((ev, i) => {
4477
4479
  const isLast = i === events.length - 1;
@@ -4663,10 +4665,9 @@ var STYLES = {
4663
4665
  heavy: { branch: "\u2523\u2501\u2501 ", last: "\u2517\u2501\u2501 ", vert: "\u2503 ", space: " " },
4664
4666
  ascii: { branch: "+-- ", last: "`-- ", vert: "| ", space: " " }
4665
4667
  };
4666
- var isFiniteNumber6 = (n) => typeof n === "number" && Number.isFinite(n);
4667
4668
  var ensureString6 = (v) => typeof v === "string" ? v : String(v ?? "");
4668
4669
  var clampNonNeg2 = (n, fallback) => {
4669
- if (!isFiniteNumber6(n)) return fallback;
4670
+ if (!isFiniteNumber2(n)) return fallback;
4670
4671
  return Math.max(0, Math.floor(n));
4671
4672
  };
4672
4673
  var normalizeNewlines = (s) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -4738,7 +4739,7 @@ var renderChildren = (children, parentContinuation, lines, indentStr, defaultSty
4738
4739
  if (!Array.isArray(children)) return;
4739
4740
  let visible = children;
4740
4741
  let collapsedCount = 0;
4741
- const safeCollapse = isFiniteNumber6(collapse) ? Math.floor(collapse) : 0;
4742
+ const safeCollapse = isFiniteNumber2(collapse) ? Math.floor(collapse) : 0;
4742
4743
  if (safeCollapse > 0 && safeCollapse < children.length) {
4743
4744
  visible = children.slice(safeCollapse);
4744
4745
  collapsedCount = safeCollapse;
@@ -4808,7 +4809,7 @@ var renderTree = (root, opts = {}) => {
4808
4809
  } = opts;
4809
4810
  const safeStyle = style && STYLES[style] ? style : "normal";
4810
4811
  const safeIndent = clampNonNeg2(indent, 0);
4811
- const safeMaxDepth = isFiniteNumber6(maxDepth) ? Math.max(0, Math.floor(maxDepth)) : Infinity;
4812
+ const safeMaxDepth = isFiniteNumber2(maxDepth) ? Math.max(0, Math.floor(maxDepth)) : Infinity;
4812
4813
  const indentStr = " ".repeat(safeIndent);
4813
4814
  const lines = [];
4814
4815
  const rootLabel = formatNode(root, 0, palette);
@@ -5427,13 +5428,8 @@ var themes = {
5427
5428
  var FULL_BLOCK = "\u2588";
5428
5429
  var UPPER_HALF = "\u2580";
5429
5430
  var LOWER_HALF = "\u2584";
5430
- var isFiniteNumber7 = (n) => typeof n === "number" && Number.isFinite(n);
5431
- var clampInt = (n, min, max, fallback) => {
5432
- if (!isFiniteNumber7(n)) return fallback;
5433
- return Math.max(min, Math.min(max, Math.floor(n)));
5434
- };
5435
5431
  var clampByte3 = (n) => {
5436
- if (!isFiniteNumber7(n)) return 0;
5432
+ if (!isFiniteNumber2(n)) return 0;
5437
5433
  return Math.max(0, Math.min(255, Math.round(n)));
5438
5434
  };
5439
5435
  var MAX_DIMENSION = 1e4;
@@ -5738,7 +5734,7 @@ var gradientRect = (opts = {}) => {
5738
5734
  const safeW = clampInt(width, 2, MAX_DIMENSION, 40);
5739
5735
  const safeH = clampInt(height, 2, MAX_DIMENSION, 10);
5740
5736
  let cosA = 1, sinA = 0;
5741
- if (isFiniteNumber7(angle)) {
5737
+ if (isFiniteNumber2(angle)) {
5742
5738
  const rad = angle * Math.PI / 180;
5743
5739
  cosA = Math.cos(rad);
5744
5740
  sinA = Math.sin(rad);
@@ -5748,7 +5744,7 @@ var gradientRect = (opts = {}) => {
5748
5744
  const line = [];
5749
5745
  for (let col = 0; col < safeW; col++) {
5750
5746
  let t;
5751
- if (isFiniteNumber7(angle)) {
5747
+ if (isFiniteNumber2(angle)) {
5752
5748
  const projection = col / (safeW - 1) * cosA + row / (safeH - 1) * sinA;
5753
5749
  t = clamp((projection + 1) / 2, 0, 1);
5754
5750
  } else if (style === "horizontal") t = col / (safeW - 1);
@@ -5805,7 +5801,7 @@ var createCanvas = (width, height, fillColor = null) => {
5805
5801
  if (y > dirtyMaxY) dirtyMaxY = y;
5806
5802
  };
5807
5803
  const setInternal = (x, y, color2) => {
5808
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y)) return;
5804
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y)) return;
5809
5805
  const ix = Math.floor(x);
5810
5806
  const iy = Math.floor(y);
5811
5807
  if (iy < 0 || iy >= h || ix < 0 || ix >= w) return;
@@ -5820,7 +5816,7 @@ var createCanvas = (width, height, fillColor = null) => {
5820
5816
  },
5821
5817
  set: setInternal,
5822
5818
  get(x, y) {
5823
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y)) return null;
5819
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y)) return null;
5824
5820
  const ix = Math.floor(x), iy = Math.floor(y);
5825
5821
  return cloneColor(pixels[iy]?.[ix] ?? null);
5826
5822
  },
@@ -5836,7 +5832,7 @@ var createCanvas = (width, height, fillColor = null) => {
5836
5832
  dirtyMaxY = h - 1;
5837
5833
  },
5838
5834
  drawRect(x, y, rw, rh, color2, fill = false) {
5839
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y) || !isFiniteNumber7(rw) || !isFiniteNumber7(rh)) return;
5835
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y) || !isFiniteNumber2(rw) || !isFiniteNumber2(rh)) return;
5840
5836
  if (rw <= 0 || rh <= 0) return;
5841
5837
  const ix = Math.floor(x);
5842
5838
  const iy = Math.floor(y);
@@ -5858,7 +5854,7 @@ var createCanvas = (width, height, fillColor = null) => {
5858
5854
  }
5859
5855
  },
5860
5856
  drawCircle(cx, cy, radius, color2, fill = false) {
5861
- if (!isFiniteNumber7(cx) || !isFiniteNumber7(cy) || !isFiniteNumber7(radius)) return;
5857
+ if (!isFiniteNumber2(cx) || !isFiniteNumber2(cy) || !isFiniteNumber2(radius)) return;
5862
5858
  if (radius <= 0) return;
5863
5859
  const x0 = Math.max(0, Math.floor(cx - radius - 1));
5864
5860
  const y0 = Math.max(0, Math.floor(cy - radius - 1));
@@ -5881,7 +5877,7 @@ var createCanvas = (width, height, fillColor = null) => {
5881
5877
  }
5882
5878
  },
5883
5879
  drawSprite(x, y, sprite) {
5884
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y)) return;
5880
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y)) return;
5885
5881
  if (!Array.isArray(sprite)) return;
5886
5882
  const sx = Math.floor(x);
5887
5883
  const sy = Math.floor(y);
@@ -6768,6 +6764,8 @@ var index_default = ansimax;
6768
6764
  charWidth,
6769
6765
  clamp,
6770
6766
  clampByte,
6767
+ clampInt,
6768
+ clampPercent,
6771
6769
  clearAnsiCache,
6772
6770
  clearColorCache,
6773
6771
  clearLine,
package/dist/index.mjs CHANGED
@@ -402,6 +402,16 @@ var safeInt = (value, fallback = 0, min = -Infinity, max = Infinity) => {
402
402
  var clamp = (n, min, max) => Math.min(Math.max(n, min), max);
403
403
  var lerp = (a, b, t) => a + (b - a) * t;
404
404
  var clampByte2 = (v) => clamp(Math.round(v), 0, 255);
405
+ var clampPercent = (p) => {
406
+ if (!isFiniteNumber2(p)) return 0;
407
+ return Math.max(0, Math.min(100, p));
408
+ };
409
+ var clampInt = (value, min, max, fallback = 0) => {
410
+ if (!isFiniteNumber2(value)) {
411
+ return Math.max(min, Math.min(max, Math.floor(fallback)));
412
+ }
413
+ return Math.max(min, Math.min(max, Math.floor(value)));
414
+ };
405
415
  var HEX_RE = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
406
416
  var isHexColor = (hex) => typeof hex === "string" && HEX_RE.test(hex.trim());
407
417
  var hexToRgb = (hex) => {
@@ -3036,16 +3046,11 @@ var ascii = {
3036
3046
 
3037
3047
  // src/loaders/index.ts
3038
3048
  var canAnimate2 = () => Boolean(process.stdout?.isTTY) && supportsColor() !== "none";
3039
- var isFiniteNumber3 = (n) => typeof n === "number" && Number.isFinite(n);
3040
3049
  var ensureString3 = (v) => typeof v === "string" ? v : String(v ?? "");
3041
3050
  var clampPositiveInt = (n, fallback) => {
3042
- if (!isFiniteNumber3(n)) return fallback;
3051
+ if (!isFiniteNumber2(n)) return fallback;
3043
3052
  return Math.max(1, Math.floor(n));
3044
3053
  };
3045
- var clampPercent = (p) => {
3046
- if (!isFiniteNumber3(p)) return 0;
3047
- return Math.max(0, Math.min(100, p));
3048
- };
3049
3054
  var isUnicodeCapable = () => {
3050
3055
  const env = process.env;
3051
3056
  if (env["CI"]) return true;
@@ -3435,7 +3440,7 @@ var custom = (frames2, text = "", opts = {}) => {
3435
3440
  };
3436
3441
  var countdown = async (seconds, opts = {}) => {
3437
3442
  const { label = "Starting in", color: hex, signal } = opts;
3438
- const safeSeconds = isFiniteNumber3(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
3443
+ const safeSeconds = isFiniteNumber2(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
3439
3444
  const safeLabel = ensureString3(label);
3440
3445
  const colorToUse = safeColor(hex) ? hex : "#ffd700";
3441
3446
  if (!canAnimate2() || signal?.aborted) {
@@ -3558,6 +3563,7 @@ var multi = (opts = {}) => {
3558
3563
  const item = {
3559
3564
  id,
3560
3565
  text,
3566
+ /* istanbul ignore next — conditional spread + default */
3561
3567
  ...addOpts.color !== void 0 && { color: addOpts.color },
3562
3568
  type: addOpts.type ?? "dots",
3563
3569
  state: "spinning"
@@ -3642,11 +3648,10 @@ var loader = {
3642
3648
  };
3643
3649
 
3644
3650
  // src/frames/index.ts
3645
- var isFiniteNumber4 = (n) => typeof n === "number" && Number.isFinite(n);
3646
3651
  var ensureString4 = (v) => typeof v === "string" ? v : String(v ?? "");
3647
3652
  var MAX_FPS = 60;
3648
3653
  var clampFps = (fps, fallback) => {
3649
- if (!isFiniteNumber4(fps)) return fallback;
3654
+ if (!isFiniteNumber2(fps)) return fallback;
3650
3655
  return Math.max(1, Math.min(MAX_FPS, Math.floor(fps)));
3651
3656
  };
3652
3657
  var _cursorHiddenCount2 = 0;
@@ -3745,14 +3750,14 @@ var play = (frames2, opts = {}) => {
3745
3750
  if (fps !== void 0) {
3746
3751
  const safeFps = clampFps(fps, 60);
3747
3752
  tickMs = Math.max(FRAME_MS, Math.floor(1e3 / safeFps));
3748
- } else if (isFiniteNumber4(interval)) {
3753
+ } else if (isFiniteNumber2(interval)) {
3749
3754
  tickMs = Math.max(FRAME_MS, Math.floor(interval));
3750
3755
  } else {
3751
3756
  tickMs = 100;
3752
3757
  }
3753
3758
  let safeRepeat;
3754
3759
  let infinite;
3755
- if (!isFiniteNumber4(repeat)) {
3760
+ if (!isFiniteNumber2(repeat)) {
3756
3761
  safeRepeat = 1;
3757
3762
  infinite = false;
3758
3763
  } else if (repeat === 0) {
@@ -3877,7 +3882,7 @@ var play = (frames2, opts = {}) => {
3877
3882
  paused = false;
3878
3883
  },
3879
3884
  seek: (idx) => {
3880
- if (!isFiniteNumber4(idx)) return;
3885
+ if (!isFiniteNumber2(idx)) return;
3881
3886
  const safe = Math.max(0, Math.floor(idx));
3882
3887
  frameIdx = frames2.length > 0 ? safe % frames2.length : 0;
3883
3888
  },
@@ -3890,7 +3895,7 @@ var play = (frames2, opts = {}) => {
3890
3895
  };
3891
3896
  };
3892
3897
  var generate = (count, fn) => {
3893
- const safeCount = isFiniteNumber4(count) ? Math.max(0, Math.floor(count)) : 0;
3898
+ const safeCount = isFiniteNumber2(count) ? Math.max(0, Math.floor(count)) : 0;
3894
3899
  if (typeof fn !== "function") return Array(safeCount).fill("");
3895
3900
  return Array.from({ length: safeCount }, (_, i) => {
3896
3901
  try {
@@ -3962,7 +3967,7 @@ var morph = (frameA, frameB, steps = 8, charset = "\u2591\u2592\u2593\u2588\u259
3962
3967
  const a0 = ensureString4(frameA);
3963
3968
  const b0 = ensureString4(frameB);
3964
3969
  if (!a0 && !b0) return [""];
3965
- const n = Math.max(2, isFiniteNumber4(steps) ? Math.floor(steps) : 8);
3970
+ const n = Math.max(2, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
3966
3971
  const len = Math.max(a0.length, b0.length);
3967
3972
  const a = a0.padEnd(len);
3968
3973
  const b = b0.padEnd(len);
@@ -3984,7 +3989,7 @@ var morph = (frameA, frameB, steps = 8, charset = "\u2591\u2592\u2593\u2588\u259
3984
3989
  var presets2 = {
3985
3990
  loadingBar: (opts = {}) => {
3986
3991
  const { width = 20, char = "\u2588", empty = "\u2591", label = "Loading" } = opts;
3987
- const safeWidth = Math.max(0, isFiniteNumber4(width) ? Math.floor(width) : 20);
3992
+ const safeWidth = Math.max(0, isFiniteNumber2(width) ? Math.floor(width) : 20);
3988
3993
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
3989
3994
  const safeEmpty = typeof empty === "string" && empty.length > 0 ? empty : "\u2591";
3990
3995
  const safeLabel = ensureString4(label);
@@ -3998,7 +4003,7 @@ var presets2 = {
3998
4003
  /* istanbul ignore next — default opts {} */
3999
4004
  ball: (opts = {}) => {
4000
4005
  const { width = 20, char = "\u25CF" } = opts;
4001
- const safeWidth = Math.max(1, isFiniteNumber4(width) ? Math.floor(width) : 20);
4006
+ const safeWidth = Math.max(1, isFiniteNumber2(width) ? Math.floor(width) : 20);
4002
4007
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u25CF";
4003
4008
  const forward = generate(safeWidth, (i) => " ".repeat(i) + safeChar);
4004
4009
  const backward = generate(safeWidth, (i) => " ".repeat(safeWidth - i - 1) + safeChar);
@@ -4008,7 +4013,7 @@ var presets2 = {
4008
4013
  breathe: (text, opts = {}) => {
4009
4014
  const { steps = 8 } = opts;
4010
4015
  const safeText = ensureString4(text);
4011
- const safeSteps2 = Math.max(1, isFiniteNumber4(steps) ? Math.floor(steps) : 8);
4016
+ const safeSteps2 = Math.max(1, isFiniteNumber2(steps) ? Math.floor(steps) : 8);
4012
4017
  const shades = ["\u2591", "\u2592", "\u2593", "\u2588"];
4013
4018
  return generate(safeSteps2 * 2, (i) => {
4014
4019
  const t = i < safeSteps2 ? i / safeSteps2 : 1 - (i - safeSteps2) / safeSteps2;
@@ -4029,22 +4034,17 @@ var presets2 = {
4029
4034
  var frames = { play, generate, live, morph, presets: presets2 };
4030
4035
 
4031
4036
  // src/components/index.ts
4032
- var isFiniteNumber5 = (n) => typeof n === "number" && Number.isFinite(n);
4033
4037
  var ensureString5 = (v) => typeof v === "string" ? v : String(v ?? "");
4034
- var clampPercent2 = (p) => {
4035
- if (!isFiniteNumber5(p)) return 0;
4036
- return Math.max(0, Math.min(100, p));
4037
- };
4038
4038
  var clampNonNeg = (n, fallback) => {
4039
- if (!isFiniteNumber5(n)) return fallback;
4039
+ if (!isFiniteNumber2(n)) return fallback;
4040
4040
  return Math.max(0, Math.floor(n));
4041
4041
  };
4042
4042
  var clampPositive2 = (n, fallback) => {
4043
- if (!isFiniteNumber5(n)) return fallback;
4043
+ if (!isFiniteNumber2(n)) return fallback;
4044
4044
  return Math.max(1, Math.floor(n));
4045
4045
  };
4046
4046
  var safeSgrCode = (code, fallback) => {
4047
- if (!isFiniteNumber5(code)) return fallback;
4047
+ if (!isFiniteNumber2(code)) return fallback;
4048
4048
  return Math.max(0, Math.min(255, Math.floor(code)));
4049
4049
  };
4050
4050
  var truncateVisible = (str, maxWidth, ellipsis = "\u2026") => {
@@ -4082,7 +4082,7 @@ var table = (rows, opts = {}) => {
4082
4082
  const b = TABLE_BORDERS[borderStyle] ?? TABLE_BORDERS.rounded;
4083
4083
  const safePadding = clampNonNeg(padding, 1);
4084
4084
  const pad = " ".repeat(safePadding);
4085
- const safeMaxCol = maxColWidth !== null && isFiniteNumber5(maxColWidth) ? Math.max(1, Math.floor(maxColWidth)) : null;
4085
+ const safeMaxCol = maxColWidth !== null && isFiniteNumber2(maxColWidth) ? Math.max(1, Math.floor(maxColWidth)) : null;
4086
4086
  const validRows = rows.filter((r) => Array.isArray(r));
4087
4087
  if (validRows.length === 0) return "";
4088
4088
  const cols = Math.max(...validRows.map((r) => r.length), 0);
@@ -4169,13 +4169,13 @@ var progressBar = (percent, opts = {}) => {
4169
4169
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2588";
4170
4170
  const safeEmpty = typeof emptyChar === "string" && emptyChar.length > 0 ? emptyChar : "\u2591";
4171
4171
  const safeLabel = ensureString5(label);
4172
- const clamped = clampPercent2(percent);
4172
+ const clamped = clampPercent(percent);
4173
4173
  const filled = Math.floor(clamped / 100 * safeWidth);
4174
4174
  const empty = Math.max(0, safeWidth - filled);
4175
4175
  let filledStr = safeChar.repeat(filled);
4176
4176
  if (Array.isArray(gradientStops2) && gradientStops2.length >= 1 && filled > 0) {
4177
4177
  filledStr = gradient(filledStr, gradientStops2);
4178
- } else if (color2 !== null && isFiniteNumber5(color2)) {
4178
+ } else if (color2 !== null && isFiniteNumber2(color2)) {
4179
4179
  filledStr = sgr(safeSgrCode(color2, FG.white)) + filledStr + reset();
4180
4180
  }
4181
4181
  const emptyStr = safeEmpty.repeat(empty);
@@ -4211,7 +4211,7 @@ var section = (title, opts = {}) => {
4211
4211
  const safeChar = typeof char === "string" && char.length > 0 ? char : "\u2500";
4212
4212
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4213
4213
  const titleLen = visibleLen(safeTitle);
4214
- const requestedWidth = width !== null && isFiniteNumber5(width) ? Math.max(1, Math.floor(width)) : cols;
4214
+ const requestedWidth = width !== null && isFiniteNumber2(width) ? Math.max(1, Math.floor(width)) : cols;
4215
4215
  const w = Math.max(requestedWidth, titleLen + 2);
4216
4216
  const side = Math.floor((w - titleLen - 2) / 2);
4217
4217
  const dividerL = sgr(safeColor2) + safeChar.repeat(Math.max(0, side)) + reset();
@@ -4225,7 +4225,7 @@ var columns = (items, opts = {}) => {
4225
4225
  const safeCols = clampPositive2(numCols, 2);
4226
4226
  const safeGap = clampNonNeg(gap, 2);
4227
4227
  const { cols: termCols } = termSize();
4228
- const totalW = width !== null && isFiniteNumber5(width) ? Math.max(safeCols, Math.floor(width)) : termCols;
4228
+ const totalW = width !== null && isFiniteNumber2(width) ? Math.max(safeCols, Math.floor(width)) : termCols;
4229
4229
  const colW = Math.max(1, Math.floor((totalW - safeGap * (safeCols - 1)) / safeCols));
4230
4230
  const gapStr = " ".repeat(safeGap);
4231
4231
  const rows = [];
@@ -4265,7 +4265,7 @@ var timeline = (events, opts = {}) => {
4265
4265
  const safeColor2 = safeSgrCode(colorCode, FG.cyan);
4266
4266
  const safeDoneColor = safeSgrCode(doneColor, FG.green);
4267
4267
  const safePendingColor = safeSgrCode(pendingColor, FG.brightBlack);
4268
- const computedTimeWidth = timeColumnWidth !== null && isFiniteNumber5(timeColumnWidth) ? Math.max(0, Math.floor(timeColumnWidth)) : Math.max(0, ...events.map((e) => e.time ? visibleLen(ensureString5(e.time)) : 0));
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));
4269
4269
  const lines = [];
4270
4270
  events.forEach((ev, i) => {
4271
4271
  const isLast = i === events.length - 1;
@@ -4457,10 +4457,9 @@ var STYLES = {
4457
4457
  heavy: { branch: "\u2523\u2501\u2501 ", last: "\u2517\u2501\u2501 ", vert: "\u2503 ", space: " " },
4458
4458
  ascii: { branch: "+-- ", last: "`-- ", vert: "| ", space: " " }
4459
4459
  };
4460
- var isFiniteNumber6 = (n) => typeof n === "number" && Number.isFinite(n);
4461
4460
  var ensureString6 = (v) => typeof v === "string" ? v : String(v ?? "");
4462
4461
  var clampNonNeg2 = (n, fallback) => {
4463
- if (!isFiniteNumber6(n)) return fallback;
4462
+ if (!isFiniteNumber2(n)) return fallback;
4464
4463
  return Math.max(0, Math.floor(n));
4465
4464
  };
4466
4465
  var normalizeNewlines = (s) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -4532,7 +4531,7 @@ var renderChildren = (children, parentContinuation, lines, indentStr, defaultSty
4532
4531
  if (!Array.isArray(children)) return;
4533
4532
  let visible = children;
4534
4533
  let collapsedCount = 0;
4535
- const safeCollapse = isFiniteNumber6(collapse) ? Math.floor(collapse) : 0;
4534
+ const safeCollapse = isFiniteNumber2(collapse) ? Math.floor(collapse) : 0;
4536
4535
  if (safeCollapse > 0 && safeCollapse < children.length) {
4537
4536
  visible = children.slice(safeCollapse);
4538
4537
  collapsedCount = safeCollapse;
@@ -4602,7 +4601,7 @@ var renderTree = (root, opts = {}) => {
4602
4601
  } = opts;
4603
4602
  const safeStyle = style && STYLES[style] ? style : "normal";
4604
4603
  const safeIndent = clampNonNeg2(indent, 0);
4605
- const safeMaxDepth = isFiniteNumber6(maxDepth) ? Math.max(0, Math.floor(maxDepth)) : Infinity;
4604
+ const safeMaxDepth = isFiniteNumber2(maxDepth) ? Math.max(0, Math.floor(maxDepth)) : Infinity;
4606
4605
  const indentStr = " ".repeat(safeIndent);
4607
4606
  const lines = [];
4608
4607
  const rootLabel = formatNode(root, 0, palette);
@@ -5221,13 +5220,8 @@ var themes = {
5221
5220
  var FULL_BLOCK = "\u2588";
5222
5221
  var UPPER_HALF = "\u2580";
5223
5222
  var LOWER_HALF = "\u2584";
5224
- var isFiniteNumber7 = (n) => typeof n === "number" && Number.isFinite(n);
5225
- var clampInt = (n, min, max, fallback) => {
5226
- if (!isFiniteNumber7(n)) return fallback;
5227
- return Math.max(min, Math.min(max, Math.floor(n)));
5228
- };
5229
5223
  var clampByte3 = (n) => {
5230
- if (!isFiniteNumber7(n)) return 0;
5224
+ if (!isFiniteNumber2(n)) return 0;
5231
5225
  return Math.max(0, Math.min(255, Math.round(n)));
5232
5226
  };
5233
5227
  var MAX_DIMENSION = 1e4;
@@ -5532,7 +5526,7 @@ var gradientRect = (opts = {}) => {
5532
5526
  const safeW = clampInt(width, 2, MAX_DIMENSION, 40);
5533
5527
  const safeH = clampInt(height, 2, MAX_DIMENSION, 10);
5534
5528
  let cosA = 1, sinA = 0;
5535
- if (isFiniteNumber7(angle)) {
5529
+ if (isFiniteNumber2(angle)) {
5536
5530
  const rad = angle * Math.PI / 180;
5537
5531
  cosA = Math.cos(rad);
5538
5532
  sinA = Math.sin(rad);
@@ -5542,7 +5536,7 @@ var gradientRect = (opts = {}) => {
5542
5536
  const line = [];
5543
5537
  for (let col = 0; col < safeW; col++) {
5544
5538
  let t;
5545
- if (isFiniteNumber7(angle)) {
5539
+ if (isFiniteNumber2(angle)) {
5546
5540
  const projection = col / (safeW - 1) * cosA + row / (safeH - 1) * sinA;
5547
5541
  t = clamp((projection + 1) / 2, 0, 1);
5548
5542
  } else if (style === "horizontal") t = col / (safeW - 1);
@@ -5599,7 +5593,7 @@ var createCanvas = (width, height, fillColor = null) => {
5599
5593
  if (y > dirtyMaxY) dirtyMaxY = y;
5600
5594
  };
5601
5595
  const setInternal = (x, y, color2) => {
5602
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y)) return;
5596
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y)) return;
5603
5597
  const ix = Math.floor(x);
5604
5598
  const iy = Math.floor(y);
5605
5599
  if (iy < 0 || iy >= h || ix < 0 || ix >= w) return;
@@ -5614,7 +5608,7 @@ var createCanvas = (width, height, fillColor = null) => {
5614
5608
  },
5615
5609
  set: setInternal,
5616
5610
  get(x, y) {
5617
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y)) return null;
5611
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y)) return null;
5618
5612
  const ix = Math.floor(x), iy = Math.floor(y);
5619
5613
  return cloneColor(pixels[iy]?.[ix] ?? null);
5620
5614
  },
@@ -5630,7 +5624,7 @@ var createCanvas = (width, height, fillColor = null) => {
5630
5624
  dirtyMaxY = h - 1;
5631
5625
  },
5632
5626
  drawRect(x, y, rw, rh, color2, fill = false) {
5633
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y) || !isFiniteNumber7(rw) || !isFiniteNumber7(rh)) return;
5627
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y) || !isFiniteNumber2(rw) || !isFiniteNumber2(rh)) return;
5634
5628
  if (rw <= 0 || rh <= 0) return;
5635
5629
  const ix = Math.floor(x);
5636
5630
  const iy = Math.floor(y);
@@ -5652,7 +5646,7 @@ var createCanvas = (width, height, fillColor = null) => {
5652
5646
  }
5653
5647
  },
5654
5648
  drawCircle(cx, cy, radius, color2, fill = false) {
5655
- if (!isFiniteNumber7(cx) || !isFiniteNumber7(cy) || !isFiniteNumber7(radius)) return;
5649
+ if (!isFiniteNumber2(cx) || !isFiniteNumber2(cy) || !isFiniteNumber2(radius)) return;
5656
5650
  if (radius <= 0) return;
5657
5651
  const x0 = Math.max(0, Math.floor(cx - radius - 1));
5658
5652
  const y0 = Math.max(0, Math.floor(cy - radius - 1));
@@ -5675,7 +5669,7 @@ var createCanvas = (width, height, fillColor = null) => {
5675
5669
  }
5676
5670
  },
5677
5671
  drawSprite(x, y, sprite) {
5678
- if (!isFiniteNumber7(x) || !isFiniteNumber7(y)) return;
5672
+ if (!isFiniteNumber2(x) || !isFiniteNumber2(y)) return;
5679
5673
  if (!Array.isArray(sprite)) return;
5680
5674
  const sx = Math.floor(x);
5681
5675
  const sy = Math.floor(y);
@@ -6561,6 +6555,8 @@ export {
6561
6555
  charWidth,
6562
6556
  clamp,
6563
6557
  clampByte2 as clampByte,
6558
+ clampInt,
6559
+ clampPercent,
6564
6560
  clearAnsiCache,
6565
6561
  clearColorCache,
6566
6562
  clearLine,
@@ -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.3.5'),
121
+ components.badge('VERSION', 'v1.3.7'),
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.3.5'),
120
+ components.badge('VERSION', 'v1.3.7'),
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.3.5",
3
+ "version": "1.3.7",
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",