import { DOWNLOAD_ITEM_IDS } from './constants' import { downloadToCSV } from './exports' import { CSVIcon } from './icons' import type { DownloadItem } from './types' interface BuildCsvDownloadItemBase { /** Base filename (without extension). The item appends `.csv`. */ filename: string /** Override the menu label. Default `'CSV'`. */ label?: string } /** * Args for {@link buildCsvDownloadItem}. A discriminated union: a caller must * supply exactly one content source — `getRows` (rows serialised through the * shared `toCsvString`) or `getCsv` (a pre-built CSV string). The `?: never` * arms make passing both — or neither — a compile-time error. */ export type BuildCsvDownloadItemArgs = | (BuildCsvDownloadItemBase & { /** * Builds the CSV rows at click time. Used by most widgets — rows are run * through `toCsvString` so escaping (incl. the spreadsheet * formula-injection guard) stays consistent across widgets. */ getRows: () => readonly (readonly unknown[])[] getCsv?: never }) | (BuildCsvDownloadItemBase & { /** * Returns a pre-built CSV string at click time. Escape hatch for widgets * (e.g. Table) that already serialise their own CSV with bespoke * header/cell handling. */ getCsv: () => string getRows?: never }) /** * Builds the standard CSV `DownloadItem` used by every per-widget download * config. Centralised so the menu label, icon, and `.csv` filename suffix stay * consistent across widgets — mirrors {@link buildPngDownloadItem} so neither * format can drift again. */ export function buildCsvDownloadItem( args: BuildCsvDownloadItemArgs, ): DownloadItem { return { id: DOWNLOAD_ITEM_IDS.csv, label: args.label ?? 'CSV', icon: , resolve: () => { if (args.getCsv) { const csv = args.getCsv() const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) return Promise.resolve({ url, filename: `${args.filename}.csv`, revoke: () => URL.revokeObjectURL(url), }) } const handle = downloadToCSV(args.getRows()) return Promise.resolve({ url: handle.url, filename: `${args.filename}.csv`, revoke: handle.revoke, }) }, } }