Spreadsheet-UX op een echte relationele engine

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

2.5 kB Ruw bestand
/**
 * The currency a workspace writes money in.
 *
 * One setting for the whole workspace rather than one per column: a business
 * keeps its books in a currency, not per field, and a workspace where the
 * invoice total is in euros and the line price in dollars is a bug however it
 * got that way. Columns stay `currency`-typed and simply read in whatever the
 * workspace is settled on.
 *
 * The code is stored, never the symbol — `EUR` outlives whatever glyph a
 * browser decides to render it with, and it is what `Intl` wants anyway.
 */
export const DEFAULT_CURRENCY = 'EUR';

export interface CurrencyOption {
  code: string;
  name: string;
}

/**
 * The list the settings dialog offers. Not exhaustive, and deliberately not:
 * anything ISO 4217 accepts is storable, so a workspace that needs a currency
 * missing here is never stuck — it is just not one click away.
 */
export const CURRENCIES: CurrencyOption[] = [
  { code: 'EUR', name: 'Euro' },
  { code: 'USD', name: 'US dollar' },
  { code: 'GBP', name: 'Pound sterling' },
  { code: 'CHF', name: 'Swiss franc' },
  { code: 'SEK', name: 'Swedish krona' },
  { code: 'NOK', name: 'Norwegian krone' },
  { code: 'DKK', name: 'Danish krone' },
  { code: 'PLN', name: 'Polish złoty' },
  { code: 'CZK', name: 'Czech koruna' },
  { code: 'CAD', name: 'Canadian dollar' },
  { code: 'AUD', name: 'Australian dollar' },
  { code: 'JPY', name: 'Japanese yen' },
  { code: 'INR', name: 'Indian rupee' },
  { code: 'BRL', name: 'Brazilian real' },
];

/** Whether `value` is shaped like an ISO 4217 code, which is all `Intl` asks. */
export function isCurrencyCode(value: string): boolean {
  return /^[A-Za-z]{3}$/.test(value);
}

const symbols = new Map<string, string>();

/**
 * The short sign for a currency — `€`, `$`, `kr` — for places too small to
 * hold a formatted amount, like the type marker on a column header.
 *
 * Falls back to the code itself, which is what `Intl` does for currencies with
 * no symbol in the first place.
 */
export function currencySymbol(code: string): string {
  const cached = symbols.get(code);
  if (cached !== undefined) return cached;

  let symbol = code;
  try {
    const parts = new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: code,
      currencyDisplay: 'narrowSymbol',
    }).formatToParts(0);
    symbol = parts.find((p) => p.type === 'currency')?.value ?? code;
  } catch {
    // An unusable code renders as itself rather than taking the page down.
  }
  symbols.set(code, symbol);
  return symbol;
}