{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"retail-format","title":"Retail format","description":"Money, units, percent, price index and weeks of cover, formatted the way a category manager reads them (en-AU by default).","files":[{"path":"src/registry/catman/lib/retail-format.ts","content":"/**\n * Number formatting in the dialect a category manager reads: money, units,\n * percentages, a price index, and weeks of cover. en-AU and AUD by default,\n * because that is who reads it first; pass a locale and currency otherwise.\n *\n * Part of the twelfth catman registry (`@twelfth/retail-format`).\n */\n\nexport type RetailLocale = { locale?: string; currency?: string }\n\nconst DEFAULT_LOCALE = \"en-AU\"\nconst DEFAULT_CURRENCY = \"AUD\"\n\nexport function formatMoney(value: number, { locale = DEFAULT_LOCALE, currency = DEFAULT_CURRENCY, whole = false }: RetailLocale & { whole?: boolean } = {}): string {\n  return new Intl.NumberFormat(locale, {\n    style: \"currency\",\n    currency,\n    currencyDisplay: \"narrowSymbol\",\n    maximumFractionDigits: whole ? 0 : 2,\n    minimumFractionDigits: whole ? 0 : 2,\n  }).format(value)\n}\n\nexport function formatUnits(value: number, { locale = DEFAULT_LOCALE }: RetailLocale = {}): string {\n  return new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(value)\n}\n\n/** A percentage already in percent units: `12.4` reads `12%`. */\nexport function formatPercent(value: number, { locale = DEFAULT_LOCALE, digits = 0 }: RetailLocale & { digits?: number } = {}): string {\n  return `${new Intl.NumberFormat(locale, { maximumFractionDigits: digits }).format(value)}%`\n}\n\n/** Price index from a position: ours ÷ theirs × 100, so a 12% gap above is 112. */\nexport function formatIndex(positionPct: number, { locale = DEFAULT_LOCALE }: RetailLocale = {}): string {\n  return new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(100 + positionPct)\n}\n\n/** A price position in words: \"12% above\", \"4% below\", \"In line\"; \"—\" when unknown. */\nexport function formatPosition(positionPct: number | null): string {\n  if (positionPct === null || !Number.isFinite(positionPct)) return \"—\"\n  const n = Math.round(Math.abs(positionPct))\n  if (n === 0) return \"In line\"\n  return `${n}% ${positionPct > 0 ? \"above\" : \"below\"}`\n}\n\n/** The same against the market: \"12% above market\", \"In line with market\". */\nexport function formatMarketPosition(positionPct: number | null): string {\n  const words = formatPosition(positionPct)\n  if (words === \"—\") return words\n  return words === \"In line\" ? \"In line with market\" : `${words} market`\n}\n\nexport function formatWeeks(weeks: number, { locale = DEFAULT_LOCALE }: RetailLocale = {}): string {\n  const figure = new Intl.NumberFormat(locale, { maximumFractionDigits: weeks < 10 ? 1 : 0 }).format(weeks)\n  return `${figure} ${weeks === 1 ? \"wk\" : \"wks\"}`\n}\n","type":"registry:lib"}],"type":"registry:lib"}