import { asParser, type Parser } from '../types' /* === Internal Functions === */ /** * Parse a string value using the given parse function, returning `undefined` * if the value is nullish or the result is not finite. * * @param {(v: string) => number} parseFn - Parsing function (e.g. `parseFloat`, `parseInt`) * @param {string | null | undefined} value - Raw attribute string to parse * @returns {number | undefined} Parsed finite number, or `undefined` if unparseable */ const parseNumber = ( parseFn: (v: string) => number, value: string | null | undefined, ): number | undefined => { if (value == null) return const parsed = parseFn(value) return Number.isFinite(parsed) ? parsed : undefined } /* === Exported Functions === */ /** * Parse a string as a number forced to integer with a fallback * * Supports hexadecimal and scientific notation * * @since 0.11.0 * @param {number} [fallback=0] - Fallback value * @returns {Parser} Parser function */ const asInteger = (fallback: number = 0): Parser => asParser((value: string | null | undefined) => { if (value == null) return fallback // Handle hexadecimal notation const trimmed = value.trim() if (trimmed.toLowerCase().startsWith('0x')) return parseNumber(v => parseInt(v, 16), trimmed) ?? fallback // Handle other formats (including scientific notation) const parsed = parseNumber(parseFloat, value) return parsed != null ? Math.trunc(parsed) : fallback }) /** * Parse a string as a number with a fallback * * @since 0.11.0 * @param {number} [fallback=0] - Fallback value * @returns {Parser} Parser function */ const asNumber = (fallback: number = 0): Parser => asParser( (value: string | null | undefined) => parseNumber(parseFloat, value) ?? fallback, ) /** * Parse a string as a clamped integer (>= min, <= max) with fallbacks * * @since 2.0 * @param {number} [min=0] - Minimum value * @param {number} [max=Number.MAX_SAFE_INTEGER] - Maximum value * @returns {Parser} Parser function */ const asClampedInteger = ( min: number = 0, max: number = Number.MAX_SAFE_INTEGER, ): Parser => asParser((value: string | null | undefined) => { if (value == null) return min const trimmed = value.trim() const raw = trimmed.toLowerCase().startsWith('0x') ? parseNumber(v => parseInt(v, 16), trimmed) : parseNumber(parseFloat, value) const parsed = raw != null ? Math.trunc(raw) : min return Math.max(min, Math.min(parsed, max)) }) export { asClampedInteger, asInteger, asNumber }