UNPKG

to-inches

Version:

Convert metric to imperial measurements with fractions

318 lines (314 loc) 10.7 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/to-inches.ts var to_inches_exports = {}; __export(to_inches_exports, { Inch: () => Inch, default: () => to_inches_default, toInches: () => toInches }); module.exports = __toCommonJS(to_inches_exports); // src/constants.ts var Order = ["miles", "yards", "feet", "inches"]; var InputMultiplayer = { mm: 1, cm: 10, m: 1e3, km: 1e6, in: 25.4, ft: 304.8, yd: 914.4, mi: 1609344 }; var RawDividers = { miles: 63360, yards: 36, feet: 12, inches: 1 }; var DefaultOptions = { denominator: 16, input: "mm", fractionClass: "fraction", miles: true, milesFraction: false, milesTitle: "mi", milesClass: "mi", yards: true, yardsFraction: false, yardsTitle: "yd", yardsClass: "yd", feet: true, feetFraction: false, feetTitle: "ft", feetClass: "ft", inches: true, inchesFraction: "fraction", inchesTitle: "in", inchesClass: "in", templates: { string: { itemTemplate: "{{value}}{{fraction?{{value?\xA0:}}{{fraction}}:}}{{title?\xA0{{title}}:}}", minus: "-", joiner: " " }, parts: { itemTemplate: ["{{value}}", "{{fraction}}", "{{title}}"], minus: "-" }, html: { itemTemplate: '<span{{class? class="{{class}}":}}>{{value}}{{fraction?{{value?\xA0:}}{{fraction}}:}}{{title?\xA0{{title}}:}}</span>', fractionTemplate: `<span{{fractionClass? class="{{fractionClass}}":}}><sup>{{numerator}}</sup>&frasl;<sub>{{denominator}}</sub></span>`, minus: "&minus;", joiner: " " } } }; // src/to-inches.ts var simpleRe = /{{(\w+)}}/g; var conditionalRe = /{{(\w+)(?:\?((?:\\:|[^:{])*?):([^{]*?))?}}/gm; var Inch = class _Inch { #options = { ...DefaultOptions }; #sign; #mm; #calculated; minus; miles; yards; feet; inches; fraction; constructor(mm, options) { this.#options = { ...this.#options, ...options, templates: { string: { ...this.#options.templates.string, ...options?.templates?.string }, parts: { ...this.#options.templates.parts, ...options?.templates?.parts }, html: { ...this.#options.templates.html, ...options?.templates?.html } } }; mm ??= 0; mm *= InputMultiplayer[this.#options.input]; this.minus = mm < 0; this.#sign = this.minus ? "-" : "+"; this.#mm = mm; this.#calculated = this.#calculate(mm); this.fraction = this.#calculated.fraction; Object.assign(this, this.#calculated.accurate); } /** * Rounds a number to a specified precision. * If precision greater than 1 or less than 0, it will be treated as a number of decimal places. * If precision is between 0 and 1, the decimals will be treated as a multiplier. * Pay attention: to round to 1/4, you should pass 0.4, not 0.25. * If precision is 0, the number will be rounded down. * If precision is not specified, the number will be rounded. * Negative precision can help with rounding integers. For example: round(123456789, -3) returns 123457000. */ static round(value, precision) { if (precision === 0) return Math.floor(value); if (!precision) return Math.round(value); const multiplier = precision >= 1 || precision < 0 ? 10 ** precision : Number(String(Math.abs(precision)).slice(2)); return Math.round(value * multiplier) / multiplier; } /** * Returns the greatest common divisor of two numbers. */ static gcd(a, b) { return b ? _Inch.gcd(b, a % b) : a; } #calculate(mm) { let raw = Math.abs(mm) / 25.4; const sizes = this.#getSizes(); const precise = { miles: mm / 1609344, yards: mm / 914.4, feet: mm / 304.8, inches: mm / 25.4 }; const accurate = { miles: 0, yards: 0, feet: 0, inches: 0 }; const reminders = { miles: 0, yards: 0, feet: 0, inches: 0 }; for (const { key, value: size } of sizes) { if (raw === 0) break; const value = raw / size; const fractionOption = this.#options[`${key}Fraction`]; const int = !fractionOption ? Math.floor(value) : fractionOption === true ? value : _Inch.round(value, fractionOption === "fraction" ? 0 : fractionOption); accurate[key] = int; reminders[key] = value - int; raw -= int * size; if (raw < 1e-15) raw = 0; } let fraction = ""; const minimal = this.#getMinimal(sizes); if (this.#options[minimal.key] && minimal.fraction === "fraction") fraction = this.#calculateFraction(reminders[minimal.key]); sizes.forEach(({ key }, index) => { sizes[index].value = accurate[key]; }); return { precise, accurate, reminders, fraction, sizes }; } #getSizes() { const sizes = []; let hasSense = true; const order = [...Order]; while (hasSense && order.length) { const key = order.shift(); if (this.#options[key]) sizes.push({ key, value: RawDividers[key] }); if (this.#options[key + "Fraction"]) hasSense = false; } return sizes; } #getInfo(key, sizes = this.#calculated.sizes) { const index = sizes.findIndex((info) => info.key === key); if (index < 0) return null; const isLast = sizes.length - 1 === index; const fraction = this.#options[`${key}Fraction`]; return { key, index, isLast, hasFraction: Boolean(fraction && isLast && this.fraction), value: this[key], title: this.#options[`${key}Title`], class: this.#options[`${key}Class`], fraction }; } #getMinimal(sizes = this.#calculated.sizes) { const key = sizes.at(-1)?.key ?? Order.at(-1); const info = this.#getInfo(key, sizes); if (info === null) throw new Error("No minimal value found"); return info; } #calculateFraction(fractionPart) { const denominator = this.#options.denominator; let numerator = Math.round(fractionPart * denominator); const commonDivisor = _Inch.gcd(numerator, denominator); numerator /= commonDivisor; const reducedDenominator = denominator / commonDivisor; if (numerator === 0) return ""; if (numerator === reducedDenominator) return "1"; return `${numerator}/${reducedDenominator}`; } get mm() { return this.#mm; } get precise() { return this.#calculated.precise; } get reminders() { return this.#calculated.reminders; } #processTemplate(template, context) { do { template = template.replace(simpleRe, (_, key) => String(context[key] ?? "")).replace(conditionalRe, (_, key, truthy, falsy) => context[key] ? truthy : falsy); } while (simpleRe.test(template) || conditionalRe.test(template)); return template; } #render(info, itemTemplate, fractionTemplate) { const isArray = Array.isArray(itemTemplate); if (!info) return isArray ? [] : ""; if (!info.value && !(info.hasFraction && this.fraction) && !info.required) return isArray ? [] : ""; let fraction = ""; const [numerator, denominator] = this.fraction.split("/"); let context = { minus: this.minus ? "\u2212" : "", value: info.required ? info.value : info.value || "", title: info.title ?? "", class: info.class ?? "", fractionClass: this.#options.fractionClass ?? "", numerator, denominator, fraction }; if (info.hasFraction) { context.fraction = fractionTemplate ? this.#processTemplate(fractionTemplate, { ...context, numerator, denominator, fraction: this.fraction }) : this.fraction; } return isArray ? itemTemplate.map((template) => this.#processTemplate(template, context)) : this.#processTemplate(itemTemplate, context); } #renderSizes(type) { const { itemTemplate, fractionTemplate, minus, joiner } = this.#options.templates[type]; let values = this.#calculated.sizes.map(({ key }) => this.#render(this.#getInfo(key), itemTemplate, fractionTemplate)).filter(Boolean).join(joiner ?? " "); if (!values) { values = this.#render({ ...this.#getMinimal(), required: true }, itemTemplate, fractionTemplate); } return (this.minus ? minus ?? "-" : "") + values; } toString() { return this.#renderSizes("string"); } toJSON() { return { minus: this.minus, miles: this.miles, yards: this.yards, feet: this.feet, inches: this.inches, fraction: this.fraction }; } parts() { const { itemTemplate, minus } = this.#options.templates.parts; const clean = (array) => array.filter(Boolean).map((value) => isNaN(Number(value)) ? value : Number(value)); const result = []; if (this.minus) result.push(minus ?? "-"); this.#calculated.sizes.forEach(({ key }) => { const values = clean(this.#render(this.#getInfo(key), itemTemplate)); result.push(...values); }); if (!result.length) { const values = clean(this.#render({ ...this.#getMinimal(), required: true }, itemTemplate)); result.push(...values); } return result; } items() { const items = this.#calculated.sizes.map(({ key }) => this.#getInfo(key)).filter((info) => info != null).filter((info) => info.value || info.hasFraction && this.fraction).map((info) => { const data = { type: info.title ?? info.key, value: info.value }; if (info.hasFraction) data.fraction = this.fraction; return data; }); if (!items.length) { const minimal = this.#getMinimal(); items.unshift({ type: minimal.title ?? minimal.key, value: 0 }); } if (this.minus) items.unshift({ type: "sign", value: "-" }); return items; } html() { return this.#renderSizes("html"); } }; function toInches(mm, options) { if (typeof mm === "object") { const options2 = mm; return { format: (mm2) => new Inch(mm2, options2) }; } return new Inch(mm, options); } var to_inches_default = toInches; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Inch, toInches });