UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

172 lines (171 loc) 7.17 kB
import { isArray } from "./array.js"; import { requireCurrencyCode } from "./currency.js"; import { isDate, requireDate } from "./date.js"; import { getPercent } from "./number.js"; import { isObject } from "./object.js"; import { isURI, requireURI } from "./uri.js"; import { requireURL } from "./url.js"; /** Format a boolean as "Yes" or "No". */ export function formatBoolean(value) { return value ? "Yes" : "No"; } /** Format a number (based on the user's browser language settings). */ export function formatNumber(num, options) { return Intl.NumberFormat(options?.locale, options).format(num); } /** Format a number range (based on the user's browser language settings). */ export function formatRange(from, to, options) { return Intl.NumberFormat(options?.locale, options).formatRange(from, to); } /** * Format a quantity of a given unit. * * - Javascript has built-in support for formatting a number of different units. * - Unfortunately the list of supported units changes in different browsers. * - Ideally we want to format units using the built-in formatting so things like translation and internationalisation are covered. * - But we want provide fallback formatting for unsupported units, and do something _good enough_ job in most cases. */ export function formatUnit(num, unit, options) { // Check if the unit is supported by the browser. if (Intl.supportedValuesOf("unit").includes(unit)) return Intl.NumberFormat(options?.locale, { ...options, style: "unit", unit }).format(num); // Otherwise, use the default number format. const str = Intl.NumberFormat(options?.locale, { ...options, style: "decimal" }).format(num); const { unitDisplay, abbr = unit, one = unit, many = `${one}s` } = options ?? {}; if (unitDisplay === "long") return `${str} ${str === "1" ? one : many}`; return `${str}${unitDisplay === "narrow" ? "" : " "}${abbr}`; // "short" is the default. } /** Format a currency amount (based on the user's browser language settings). */ export function formatCurrency(amount, currency, options, caller = formatCurrency) { return Intl.NumberFormat(options?.locale, { style: "currency", ...options, currency: requireCurrencyCode(currency, caller), }).format(amount); } /** * Format a percentage (combines `getPercent()` and `formatUnit()` for convenience). * - Defaults to showing no decimal places. * - Defaults to rounding closer to zero (so that 99.99% is shown as 99%). * - Javascript's built-in percent formatting works on the `0` zero to `1` range. This uses `getPercent()` which works on `0` to `100` for convenience. * * @param numerator Number representing the amount of progress (e.g. `50`). * @param denumerator The number representing the whole amount (defaults to 100). */ export function formatPercent(numerator, denumerator, options) { return Intl.NumberFormat(options?.locale, { style: "percent", maximumFractionDigits: 0, roundingMode: "floor", ...options, }).format(getPercent(numerator, denumerator) / 100); } /** * Format an unknown object as a string. * - Use the custom `.toString()` function if it exists (don't use built in `Object.prototype.toString` because it's useless. * - Use `.title` or `.name` or `.id` if they exist and are strings. * - Use `Object` otherwise. */ export function formatObject(obj) { if (typeof obj.toString === "function" && obj.toString !== Object.prototype.toString) return obj.toString(); const name = obj.name; if (typeof name === "string") return name; const title = obj.title; if (typeof title === "string") return title; const id = obj.id; if (typeof id === "string") return id; return "Object"; } /** Format an unknown array as a string. */ export function formatArray(arr, options, caller = formatArray) { return new Intl.ListFormat(undefined, { style: "long", type: "unit", ...options }).format(formatValues(arr, options, caller)); } /** Format a date in the browser locale. */ export function formatDate(date, options, caller = formatDate) { return requireDate(date, caller).toLocaleDateString(options?.locale, options); } /** Format a time in the browser locale (no seconds by default). */ export function formatTime(time, options, caller = formatTime) { return requireDate(time, caller).toLocaleTimeString(options?.locale, { hour: "2-digit", minute: "2-digit", second: undefined, // No seconds by default. ...options, }); } /** Format a datetime in the browser locale (no seconds by default). */ export function formatDateTime(date, options, caller = formatDateTime) { return requireDate(date, caller).toLocaleString(options?.locale, { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit", // No seconds by default. ...options, }); } /** * Format a URI as a user-friendly string * e.g. `mailto:dave@shax.com` → `dave@shax.com` * e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */ export function formatURI(url, caller = formatURI) { return _formatURI(requireURI(url, caller)); } function _formatURI({ host, pathname }) { return `${host}${pathname.endsWith("/") ? pathname.slice(0, -1) : pathname}`; } /** * Format a URI as a string. * e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */ export function formatURL(url, base, caller = formatURL) { return _formatURI(requireURL(url, base, caller)); } /** * Convert any unknown value into a friendly string for user-facing use. * - Strings return the string. * - Booleans return `"Yes"` or `"No"` * - Numbers return formatted number with commas etc (e.g. `formatNumber()`). * - Dates return formatted datetime (e.g. `formatDateTime()`). * - Arrays return the array items converted to string (with `toTitle()`), and joined with a comma. * - Objects return... * 1. `object.name` if it exists, or * 2. `object.title` if it exists. * - Falsy values like `null` and `undefined` return `"None"` * - Everything else returns `"Unknown"` */ export function formatValue(value, options, caller = formatValue) { if (value === null || value === undefined) return "None"; if (typeof value === "boolean") return formatBoolean(value); if (typeof value === "string") return value || "None"; if (typeof value === "number") return formatNumber(value, options); if (typeof value === "symbol") return value.description || "Symbol"; if (typeof value === "function") return "Function"; if (isDate(value)) return formatDateTime(value, options, caller); if (isArray(value)) return formatArray(value, options, caller); if (isObject(value)) return formatObject(value); if (isURI(value)) return formatURI(value, caller); return "Unknown"; } /** Format a sequence of values. */ export function* formatValues(values, options, caller = formatValues) { for (const v of values) yield formatValue(v, options, caller); }