{"version":3,"file":"tin-spa-excelRenderer-DAREFsYF.mjs","sources":["../../../projects/tin-spa/src/lib/components/document-viewer/renderers/excelRenderer.ts"],"sourcesContent":["// The Excel (.xlsx) renderer, a PLAIN ASYNC FUNCTION for the same reason wordRenderer.ts is one: anything\n// listed in tin-spa.module.ts `declarations` is a static import and lands in every consumer's INITIAL\n// bundle. Reached only through `await import('./renderers/excelRenderer')`, this file and its library are\n// split into their own chunk and cost a user who never opens a spreadsheet nothing.\n//\n// 🔴 WHY exceljs AND NOT SheetJS/xlsx (Arbiter ruling, 2026-08-19). `xlsx` on npm is frozen at 0.18.5 —\n// SheetJS left the registry in 2023 and every patched release since lives only on cdn.sheetjs.com. 0.18.5\n// carries two HIGH advisories with NO FIX AVAILABLE (GHSA-4r6h-8v6p-xvw6 prototype pollution CVSS 7.8, and\n// a ReDoS), and the bytes this parser eats are USER-UPLOADED files. Pointing `dependencies` at the SheetJS\n// CDN tarball instead would reproduce the 2026-08-14/16 delivery failure — resolves on this machine, ENOENT\n// (or a network failure) on every cold cache and in CI. exceljs is on the ordinary registry, is maintained,\n// and its only advisory is a MODERATE one inherited from `uuid`, which sits in its write path, not this\n// read path. Do not \"simplify\" this back to xlsx.\n//\n// This file converts bytes to a plain data model only. The viewer owns the DOM and the design — nothing here\n// produces HTML, so there is no sanitisation question on this path at all (unlike Word).\n\nexport interface ExcelCell {\n  text: string;\n\n  // Right-aligns in the grid, the way a spreadsheet does. Decided from the SOURCE value's type, never by\n  // re-parsing the formatted text — \"1-2\" would otherwise read as a number in some locales.\n  numeric: boolean;\n}\n\nexport interface ExcelSheet {\n  name: string;\n  rows: ExcelCell[][];\n\n  // The widest row, so every row can be padded to a rectangle. A ragged grid renders as a broken table.\n  columns: number;\n\n  // True when the sheet was cut off at the row/column caps below. Surfaced in the UI — a silently truncated\n  // spreadsheet is the silent-failure shape this codebase keeps producing.\n  truncated: boolean;\n}\n\nexport interface ExcelRenderResult {\n  sheets: ExcelSheet[];\n  hasContent: boolean;\n}\n\n// A viewer, not an editor. Beyond a few hundred rows the browser is laying out tens of thousands of cells\n// for a person who is scrolling to check one figure, and the honest answer is \"download it\".\nconst MAX_ROWS = 500;\nconst MAX_COLUMNS = 60;\n\nexport async function renderExcel(blob: Blob): Promise<ExcelRenderResult> {\n  // 🔴 THE BROWSER BUNDLE BY PATH, for the same measured reason wordRenderer.ts imports mammoth by path:\n  // exceljs's `index.d.ts` is typed against Node and imports the `stream` module, which fails the tin-spa\n  // library build with TS2307 (measured 2026-08-19). `dist/exceljs.min.js` is exactly what the package's own\n  // `browser` field points at, has no declaration file, and keeps the cost inside this one file rather than\n  // forcing `@types/node` or `skipLibCheck` on every project in ng-space. The UMD shape means the namespace\n  // may arrive under `default`.\n  const module: any = await import('exceljs/dist/exceljs.min.js');\n  const ExcelJS = module?.Workbook ? module : (module?.default ?? module);\n\n  const workbook = new ExcelJS.Workbook();\n  await workbook.xlsx.load(await blob.arrayBuffer());\n\n  const sheets: ExcelSheet[] = [];\n\n  workbook.eachSheet((worksheet: any) => {\n    const rows: ExcelCell[][] = [];\n    let columns = 0;\n    let truncated = false;\n\n    // includeEmpty keeps a blank row's PLACE — collapsing them silently shifts every figure below it up a\n    // line, which is a wrong spreadsheet rendered as if it were right.\n    worksheet.eachRow({ includeEmpty: true }, (row: any) => {\n      if (rows.length >= MAX_ROWS) { truncated = true; return; }\n\n      const cells: ExcelCell[] = [];\n      const width = Math.min(row.cellCount ?? 0, MAX_COLUMNS);\n      if ((row.cellCount ?? 0) > MAX_COLUMNS) truncated = true;\n\n      for (let column = 1; column <= width; column++) {\n        const value = row.getCell(column)?.value;\n        cells.push({ text: cellText(value), numeric: isNumeric(value) });\n      }\n\n      columns = Math.max(columns, cells.length);\n      rows.push(cells);\n    });\n\n    // Pad every row out to the widest, so the grid is a true rectangle.\n    rows.forEach(cells => { while (cells.length < columns) cells.push({ text: '', numeric: false }); });\n\n    // Trim trailing all-blank rows, which xlsx files accumulate in quantity.\n    while (rows.length > 0 && rows[rows.length - 1].every(cell => cell.text === '')) rows.pop();\n\n    sheets.push({ name: worksheet.name ?? 'Sheet', rows: rows, columns: columns, truncated: truncated });\n  });\n\n  return { sheets: sheets, hasContent: sheets.some(sheet => sheet.rows.length > 0) };\n}\n\n// exceljs cell values are a union, not a string: a Date, a { formula, result }, a { richText: [] }, a\n// { text, hyperlink }, or an { error }. Exported so a spec can pin every branch — this is the one function\n// here that can quietly render \"[object Object]\" across a whole column.\nexport function cellText(value: any): string {\n  if (value === null || value === undefined) return '';\n  if (value instanceof Date) return value.toLocaleDateString();\n  if (typeof value === 'object') {\n    if (Array.isArray(value.richText)) return value.richText.map((run: any) => run?.text ?? '').join('');\n    // A formula cell shows its RESULT, which is what a spreadsheet shows. Falling back to the formula text\n    // is better than a blank when the file carries no cached result.\n    if ('result' in value) return value.result === null || value.result === undefined ? '' : cellText(value.result);\n    if ('text' in value) return String(value.text);\n    if ('error' in value) return String(value.error);\n    if ('hyperlink' in value) return String(value.hyperlink);\n    return '';\n  }\n  return String(value);\n}\n\n// Exported alongside cellText for the same reason.\nexport function isNumeric(value: any): boolean {\n  if (typeof value === 'number') return true;\n  if (value && typeof value === 'object' && 'result' in value) return typeof value.result === 'number';\n  return false;\n}\n"],"names":[],"mappings":"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"}