@pyreon/document 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/confluence-Bd3ua1Ut.js.map +1 -1
  3. package/lib/csv-COrS4qdy.js.map +1 -1
  4. package/lib/discord-BLUnkEh9.js.map +1 -1
  5. package/lib/{dist-BsqdI2nY.js → dist-CYL41kqQ.js} +2 -2
  6. package/lib/dist-CYL41kqQ.js.map +1 -0
  7. package/lib/{docx-BEBOihjl.js → docx-uNAel545.js} +7 -2
  8. package/lib/docx-uNAel545.js.map +1 -0
  9. package/lib/email-D0bbfWq4.js.map +1 -1
  10. package/lib/{exceljs-BoIDUUaw.js → exceljs-BYETsesT.js} +314 -314
  11. package/lib/exceljs-BYETsesT.js.map +1 -0
  12. package/lib/google-chat-CkKCBUWC.js.map +1 -1
  13. package/lib/html-B5biprN2.js.map +1 -1
  14. package/lib/index.js +17 -8
  15. package/lib/index.js.map +1 -1
  16. package/lib/markdown-CdtlFGC0.js.map +1 -1
  17. package/lib/notion-iG2C5bEY.js.map +1 -1
  18. package/lib/{pdf-DIUQUEdj.js → pdf-IuBgTb3T.js} +9 -3
  19. package/lib/pdf-IuBgTb3T.js.map +1 -0
  20. package/lib/{pdfmake-DnmLxK4Q.js → pdfmake-CKMX5URW.js} +2 -4
  21. package/lib/pdfmake-CKMX5URW.js.map +1 -0
  22. package/lib/{pptx-Dd33oL3_.js → pptx-DXiMiYFM.js} +7 -2
  23. package/lib/pptx-DXiMiYFM.js.map +1 -0
  24. package/lib/{pptxgen.es-COcgXsyx.js → pptxgen.es-FsqHs8mD.js} +3 -6
  25. package/lib/pptxgen.es-FsqHs8mD.js.map +1 -0
  26. package/lib/sanitize-O_3j1mNJ.js.map +1 -1
  27. package/lib/slack-BI3EQwYm.js.map +1 -1
  28. package/lib/svg-BKxumy-p.js.map +1 -1
  29. package/lib/teams-Cwz9lce0.js.map +1 -1
  30. package/lib/telegram-gYFqyMXb.js.map +1 -1
  31. package/lib/text-l1XNXBOC.js.map +1 -1
  32. package/lib/types/index.d.ts +43 -39
  33. package/lib/types/index.d.ts.map +1 -1
  34. package/lib/{vfs_fonts-Df1kkZ4Y.js → vfs_fonts-Cap07Jg3.js} +2 -2
  35. package/lib/vfs_fonts-Cap07Jg3.js.map +1 -0
  36. package/lib/whatsapp-CjSGoOKx.js.map +1 -1
  37. package/lib/{xlsx-Bb5TWyXQ.js → xlsx-Cvu4LBNy.js} +8 -2
  38. package/lib/xlsx-Cvu4LBNy.js.map +1 -0
  39. package/package.json +19 -7
  40. package/src/builder.ts +53 -44
  41. package/src/download.ts +32 -36
  42. package/src/env.d.ts +3 -17
  43. package/src/index.ts +6 -8
  44. package/src/nodes.ts +45 -45
  45. package/src/render.ts +45 -118
  46. package/src/renderers/confluence.ts +64 -80
  47. package/src/renderers/csv.ts +11 -18
  48. package/src/renderers/discord.ts +38 -50
  49. package/src/renderers/docx.ts +78 -120
  50. package/src/renderers/email.ts +73 -92
  51. package/src/renderers/google-chat.ts +35 -47
  52. package/src/renderers/html.ts +78 -101
  53. package/src/renderers/markdown.ts +43 -53
  54. package/src/renderers/notion.ts +63 -85
  55. package/src/renderers/pdf.ts +92 -115
  56. package/src/renderers/pptx.ts +60 -66
  57. package/src/renderers/slack.ts +49 -61
  58. package/src/renderers/svg.ts +49 -63
  59. package/src/renderers/teams.ts +68 -80
  60. package/src/renderers/telegram.ts +40 -54
  61. package/src/renderers/text.ts +44 -66
  62. package/src/renderers/whatsapp.ts +34 -48
  63. package/src/renderers/xlsx.ts +47 -61
  64. package/src/sanitize.ts +21 -25
  65. package/src/tests/document.test.ts +1337 -1385
  66. package/src/tests/stress.test.ts +111 -111
  67. package/src/types.ts +66 -65
  68. package/lib/dist-BsqdI2nY.js.map +0 -1
  69. package/lib/docx-BEBOihjl.js.map +0 -1
  70. package/lib/exceljs-BoIDUUaw.js.map +0 -1
  71. package/lib/pdf-DIUQUEdj.js.map +0 -1
  72. package/lib/pdfmake-DnmLxK4Q.js.map +0 -1
  73. package/lib/pptx-Dd33oL3_.js.map +0 -1
  74. package/lib/pptxgen.es-COcgXsyx.js.map +0 -1
  75. package/lib/vfs_fonts-Df1kkZ4Y.js.map +0 -1
  76. package/lib/xlsx-Bb5TWyXQ.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"whatsapp-CjSGoOKx.js","names":[],"sources":["../src/renderers/whatsapp.ts"],"sourcesContent":["import { sanitizeHref } from '../sanitize'\nimport type {\n DocChild,\n DocNode,\n DocumentRenderer,\n RenderOptions,\n TableColumn,\n} from '../types'\n\n/**\n * WhatsApp renderer — outputs formatted text using WhatsApp's markup.\n * WhatsApp supports: *bold*, _italic_, ~strikethrough~, ```code```, > quote\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === 'string' ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) =>\n typeof c === 'string' ? c : getTextContent((c as DocNode).children),\n )\n .join('')\n}\n\nfunction renderNode(node: DocNode): string {\n const p = node.props\n\n switch (node.type) {\n case 'document':\n case 'page':\n case 'section':\n case 'row':\n case 'column':\n return node.children\n .map((c) => (typeof c === 'string' ? c : renderNode(c)))\n .join('')\n\n case 'heading': {\n const text = getTextContent(node.children)\n return `*${text}*\\n\\n`\n }\n\n case 'text': {\n let text = getTextContent(node.children)\n if (p.bold) text = `*${text}*`\n if (p.italic) text = `_${text}_`\n if (p.strikethrough) text = `~${text}~`\n return `${text}\\n\\n`\n }\n\n case 'link': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n return `${text}: ${href}\\n\\n`\n }\n\n case 'image':\n // WhatsApp doesn't support inline images in text\n return ''\n\n case 'table': {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(\n resolveColumn,\n )\n const rows = (p.rows ?? []) as (string | number)[][]\n\n const header = columns.map((c) => `*${c.header}*`).join(' | ')\n const body = rows\n .map((row) => row.map((c) => String(c ?? '')).join(' | '))\n .join('\\n')\n\n let result = `${header}\\n${body}\\n\\n`\n if (p.caption) result = `_${p.caption}_\\n${result}`\n return result\n }\n\n case 'list': {\n const ordered = p.ordered as boolean | undefined\n return `${node.children\n .filter((c): c is DocNode => typeof c !== 'string')\n .map((item, i) => {\n const prefix = ordered ? `${i + 1}.` : '•'\n return `${prefix} ${getTextContent(item.children)}`\n })\n .join('\\n')}\\n\\n`\n }\n\n case 'code': {\n const text = getTextContent(node.children)\n return `\\`\\`\\`${text}\\`\\`\\`\\n\\n`\n }\n\n case 'divider':\n case 'page-break':\n return '───────────\\n\\n'\n\n case 'spacer':\n return '\\n'\n\n case 'button': {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n return `*${text}*: ${href}\\n\\n`\n }\n\n case 'quote': {\n const text = getTextContent(node.children)\n return `> ${text}\\n\\n`\n }\n\n default:\n return ''\n }\n}\n\nexport const whatsappRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n return renderNode(node).trim()\n },\n}\n"],"mappings":";;;;;;;AAcA,SAAS,cAAc,KAAwC;AAC7D,QAAO,OAAO,QAAQ,WAAW,EAAE,QAAQ,KAAK,GAAG;;AAGrD,SAAS,eAAe,UAA8B;AACpD,QAAO,SACJ,KAAK,MACJ,OAAO,MAAM,WAAW,IAAI,eAAgB,EAAc,SAAS,CACpE,CACA,KAAK,GAAG;;AAGb,SAAS,WAAW,MAAuB;CACzC,MAAM,IAAI,KAAK;AAEf,SAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,SACH,QAAO,KAAK,SACT,KAAK,MAAO,OAAO,MAAM,WAAW,IAAI,WAAW,EAAE,CAAE,CACvD,KAAK,GAAG;EAEb,KAAK,UAEH,QAAO,IADM,eAAe,KAAK,SAAS,CAC1B;EAGlB,KAAK,QAAQ;GACX,IAAI,OAAO,eAAe,KAAK,SAAS;AACxC,OAAI,EAAE,KAAM,QAAO,IAAI,KAAK;AAC5B,OAAI,EAAE,OAAQ,QAAO,IAAI,KAAK;AAC9B,OAAI,EAAE,cAAe,QAAO,IAAI,KAAK;AACrC,UAAO,GAAG,KAAK;;EAGjB,KAAK,QAAQ;GACX,MAAM,OAAO,aAAa,EAAE,KAAe;AAE3C,UAAO,GADM,eAAe,KAAK,SAAS,CAC3B,IAAI,KAAK;;EAG1B,KAAK,QAEH,QAAO;EAET,KAAK,SAAS;GACZ,MAAM,WAAY,EAAE,WAAW,EAAE,EAA+B,IAC9D,cACD;GACD,MAAM,OAAQ,EAAE,QAAQ,EAAE;GAO1B,IAAI,SAAS,GALE,QAAQ,KAAK,MAAM,IAAI,EAAE,OAAO,GAAG,CAAC,KAAK,MAAM,CAKvC,IAJV,KACV,KAAK,QAAQ,IAAI,KAAK,MAAM,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,CACzD,KAAK,KAAK,CAEmB;AAChC,OAAI,EAAE,QAAS,UAAS,IAAI,EAAE,QAAQ,KAAK;AAC3C,UAAO;;EAGT,KAAK,QAAQ;GACX,MAAM,UAAU,EAAE;AAClB,UAAO,GAAG,KAAK,SACZ,QAAQ,MAAoB,OAAO,MAAM,SAAS,CAClD,KAAK,MAAM,MAAM;AAEhB,WAAO,GADQ,UAAU,GAAG,IAAI,EAAE,KAAK,IACtB,GAAG,eAAe,KAAK,SAAS;KACjD,CACD,KAAK,KAAK,CAAC;;EAGhB,KAAK,OAEH,QAAO,SADM,eAAe,KAAK,SAAS,CACrB;EAGvB,KAAK;EACL,KAAK,aACH,QAAO;EAET,KAAK,SACH,QAAO;EAET,KAAK,UAAU;GACb,MAAM,OAAO,aAAa,EAAE,KAAe;AAE3C,UAAO,IADM,eAAe,KAAK,SAAS,CAC1B,KAAK,KAAK;;EAG5B,KAAK,QAEH,QAAO,KADM,eAAe,KAAK,SAAS,CACzB;EAGnB,QACE,QAAO;;;AAIb,MAAa,mBAAqC,EAChD,MAAM,OAAO,MAAe,UAA2C;AACrE,QAAO,WAAW,KAAK,CAAC,MAAM;GAEjC"}
1
+ {"version":3,"file":"whatsapp-CjSGoOKx.js","names":[],"sources":["../src/renderers/whatsapp.ts"],"sourcesContent":["import { sanitizeHref } from \"../sanitize\"\nimport type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from \"../types\"\n\n/**\n * WhatsApp renderer — outputs formatted text using WhatsApp's markup.\n * WhatsApp supports: *bold*, _italic_, ~strikethrough~, ```code```, > quote\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === \"string\" ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === \"string\" ? c : getTextContent((c as DocNode).children)))\n .join(\"\")\n}\n\nfunction renderNode(node: DocNode): string {\n const p = node.props\n\n switch (node.type) {\n case \"document\":\n case \"page\":\n case \"section\":\n case \"row\":\n case \"column\":\n return node.children.map((c) => (typeof c === \"string\" ? c : renderNode(c))).join(\"\")\n\n case \"heading\": {\n const text = getTextContent(node.children)\n return `*${text}*\\n\\n`\n }\n\n case \"text\": {\n let text = getTextContent(node.children)\n if (p.bold) text = `*${text}*`\n if (p.italic) text = `_${text}_`\n if (p.strikethrough) text = `~${text}~`\n return `${text}\\n\\n`\n }\n\n case \"link\": {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n return `${text}: ${href}\\n\\n`\n }\n\n case \"image\":\n // WhatsApp doesn't support inline images in text\n return \"\"\n\n case \"table\": {\n const columns = ((p.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (p.rows ?? []) as (string | number)[][]\n\n const header = columns.map((c) => `*${c.header}*`).join(\" | \")\n const body = rows.map((row) => row.map((c) => String(c ?? \"\")).join(\" | \")).join(\"\\n\")\n\n let result = `${header}\\n${body}\\n\\n`\n if (p.caption) result = `_${p.caption}_\\n${result}`\n return result\n }\n\n case \"list\": {\n const ordered = p.ordered as boolean | undefined\n return `${node.children\n .filter((c): c is DocNode => typeof c !== \"string\")\n .map((item, i) => {\n const prefix = ordered ? `${i + 1}.` : \"•\"\n return `${prefix} ${getTextContent(item.children)}`\n })\n .join(\"\\n\")}\\n\\n`\n }\n\n case \"code\": {\n const text = getTextContent(node.children)\n return `\\`\\`\\`${text}\\`\\`\\`\\n\\n`\n }\n\n case \"divider\":\n case \"page-break\":\n return \"───────────\\n\\n\"\n\n case \"spacer\":\n return \"\\n\"\n\n case \"button\": {\n const href = sanitizeHref(p.href as string)\n const text = getTextContent(node.children)\n return `*${text}*: ${href}\\n\\n`\n }\n\n case \"quote\": {\n const text = getTextContent(node.children)\n return `> ${text}\\n\\n`\n }\n\n default:\n return \"\"\n }\n}\n\nexport const whatsappRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<string> {\n return renderNode(node).trim()\n },\n}\n"],"mappings":";;;;;;;AAQA,SAAS,cAAc,KAAwC;AAC7D,QAAO,OAAO,QAAQ,WAAW,EAAE,QAAQ,KAAK,GAAG;;AAGrD,SAAS,eAAe,UAA8B;AACpD,QAAO,SACJ,KAAK,MAAO,OAAO,MAAM,WAAW,IAAI,eAAgB,EAAc,SAAS,CAAE,CACjF,KAAK,GAAG;;AAGb,SAAS,WAAW,MAAuB;CACzC,MAAM,IAAI,KAAK;AAEf,SAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,SACH,QAAO,KAAK,SAAS,KAAK,MAAO,OAAO,MAAM,WAAW,IAAI,WAAW,EAAE,CAAE,CAAC,KAAK,GAAG;EAEvF,KAAK,UAEH,QAAO,IADM,eAAe,KAAK,SAAS,CAC1B;EAGlB,KAAK,QAAQ;GACX,IAAI,OAAO,eAAe,KAAK,SAAS;AACxC,OAAI,EAAE,KAAM,QAAO,IAAI,KAAK;AAC5B,OAAI,EAAE,OAAQ,QAAO,IAAI,KAAK;AAC9B,OAAI,EAAE,cAAe,QAAO,IAAI,KAAK;AACrC,UAAO,GAAG,KAAK;;EAGjB,KAAK,QAAQ;GACX,MAAM,OAAO,aAAa,EAAE,KAAe;AAE3C,UAAO,GADM,eAAe,KAAK,SAAS,CAC3B,IAAI,KAAK;;EAG1B,KAAK,QAEH,QAAO;EAET,KAAK,SAAS;GACZ,MAAM,WAAY,EAAE,WAAW,EAAE,EAA+B,IAAI,cAAc;GAClF,MAAM,OAAQ,EAAE,QAAQ,EAAE;GAK1B,IAAI,SAAS,GAHE,QAAQ,KAAK,MAAM,IAAI,EAAE,OAAO,GAAG,CAAC,KAAK,MAAM,CAGvC,IAFV,KAAK,KAAK,QAAQ,IAAI,KAAK,MAAM,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,KAAK,KAAK,CAEtD;AAChC,OAAI,EAAE,QAAS,UAAS,IAAI,EAAE,QAAQ,KAAK;AAC3C,UAAO;;EAGT,KAAK,QAAQ;GACX,MAAM,UAAU,EAAE;AAClB,UAAO,GAAG,KAAK,SACZ,QAAQ,MAAoB,OAAO,MAAM,SAAS,CAClD,KAAK,MAAM,MAAM;AAEhB,WAAO,GADQ,UAAU,GAAG,IAAI,EAAE,KAAK,IACtB,GAAG,eAAe,KAAK,SAAS;KACjD,CACD,KAAK,KAAK,CAAC;;EAGhB,KAAK,OAEH,QAAO,SADM,eAAe,KAAK,SAAS,CACrB;EAGvB,KAAK;EACL,KAAK,aACH,QAAO;EAET,KAAK,SACH,QAAO;EAET,KAAK,UAAU;GACb,MAAM,OAAO,aAAa,EAAE,KAAe;AAE3C,UAAO,IADM,eAAe,KAAK,SAAS,CAC1B,KAAK,KAAK;;EAG5B,KAAK,QAEH,QAAO,KADM,eAAe,KAAK,SAAS,CACzB;EAGnB,QACE,QAAO;;;AAIb,MAAa,mBAAqC,EAChD,MAAM,OAAO,MAAe,UAA2C;AACrE,QAAO,WAAW,KAAK,CAAC,MAAM;GAEjC"}
@@ -169,7 +169,13 @@ function autoFitColumns(ws) {
169
169
  }
170
170
  }
171
171
  const xlsxRenderer = { async render(node, _options) {
172
- const workbook = new (await (import("./exceljs-BoIDUUaw.js"))).default.Workbook();
172
+ let ExcelJS;
173
+ try {
174
+ ExcelJS = await import("./exceljs-BYETsesT.js");
175
+ } catch {
176
+ throw new Error("[@pyreon/document] XLSX renderer requires \"exceljs\" package. Install it: bun add exceljs");
177
+ }
178
+ const workbook = new ExcelJS.default.Workbook();
173
179
  workbook.creator = node.props.author ?? "";
174
180
  workbook.title = node.props.title ?? "";
175
181
  const sheets = extractSheets(node);
@@ -196,4 +202,4 @@ const xlsxRenderer = { async render(node, _options) {
196
202
 
197
203
  //#endregion
198
204
  export { xlsxRenderer };
199
- //# sourceMappingURL=xlsx-Bb5TWyXQ.js.map
205
+ //# sourceMappingURL=xlsx-Cvu4LBNy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xlsx-Cvu4LBNy.js","names":[],"sources":["../src/renderers/xlsx.ts"],"sourcesContent":["import type { DocChild, DocNode, DocumentRenderer, RenderOptions, TableColumn } from \"../types\"\n\n/**\n * XLSX renderer — lazy-loads ExcelJS on first use.\n * Extracts tables from the document and renders each as a worksheet.\n * Non-table content (headings, text) becomes header rows.\n */\n\nfunction resolveColumn(col: string | TableColumn): TableColumn {\n return typeof col === \"string\" ? { header: col } : col\n}\n\nfunction getTextContent(children: DocChild[]): string {\n return children\n .map((c) => (typeof c === \"string\" ? c : getTextContent((c as DocNode).children)))\n .join(\"\")\n}\n\ninterface ExtractedSheet {\n name: string\n headings: string[]\n tables: DocNode[]\n}\n\n/** Walk the tree and group content into sheets (one per page, or one global). */\nfunction extractSheets(node: DocNode): ExtractedSheet[] {\n const sheets: ExtractedSheet[] = []\n let currentSheet: ExtractedSheet = {\n name: \"Sheet 1\",\n headings: [],\n tables: [],\n }\n\n function walk(n: DocNode): void {\n switch (n.type) {\n case \"document\":\n walkChildren(n)\n break\n\n case \"page\":\n pushCurrentSheet()\n currentSheet = {\n name: `Sheet ${sheets.length + 1}`,\n headings: [],\n tables: [],\n }\n walkChildren(n)\n break\n\n case \"heading\":\n addHeading(n)\n break\n\n case \"table\":\n currentSheet.tables.push(n)\n break\n\n default:\n walkChildren(n)\n }\n }\n\n function walkChildren(n: DocNode): void {\n for (const child of n.children) {\n if (typeof child !== \"string\") walk(child)\n }\n }\n\n function pushCurrentSheet(): void {\n if (currentSheet.tables.length > 0 || currentSheet.headings.length > 0) {\n sheets.push(currentSheet)\n }\n }\n\n function addHeading(n: DocNode): void {\n const text = getTextContent(n.children)\n currentSheet.headings.push(text)\n if (currentSheet.headings.length === 1) {\n currentSheet.name = text.slice(0, 31) // Excel sheet name max 31 chars\n }\n }\n\n walk(node)\n pushCurrentSheet()\n\n return sheets\n}\n\n/** Parse a cell value, handling currencies, percentages, and plain numbers. */\nfunction parseCellValue(value: string | number | undefined): string | number {\n if (value == null) return \"\"\n if (typeof value === \"number\") return value\n\n const trimmed = value.trim()\n\n // Percentage: \"45%\" or \"12.5%\"\n if (/^-?\\d+(\\.\\d+)?%$/.test(trimmed)) {\n return Number.parseFloat(trimmed) / 100\n }\n\n // Currency: \"$1,234.56\", \"$1234\", \"-$500\"\n const currencyMatch = trimmed.match(/^-?\\$[\\d,]+(\\.\\d+)?$/)\n if (currencyMatch) {\n return Number.parseFloat(trimmed.replace(/[$,]/g, \"\"))\n }\n\n // Plain number: \"1,234.56\", \"1234\", \"-500.5\"\n const plainNum = Number(trimmed.replace(/,/g, \"\"))\n if (!Number.isNaN(plainNum) && /^-?[\\d,]+(\\.\\d+)?$/.test(trimmed)) {\n return plainNum\n }\n\n return value\n}\n\n/** Get ExcelJS number format string for a value. */\nfunction getCellFormat(originalValue: string | number | undefined): string | undefined {\n if (typeof originalValue !== \"string\") return undefined\n const trimmed = originalValue.trim()\n\n if (/^-?\\d+(\\.\\d+)?%$/.test(trimmed)) return \"0.00%\"\n if (/^-?\\$/.test(trimmed)) return \"$#,##0.00\"\n return undefined\n}\n\n/** Map alignment string to ExcelJS horizontal alignment. */\nfunction mapAlignment(align?: string): \"left\" | \"center\" | \"right\" | undefined {\n if (align === \"left\" || align === \"center\" || align === \"right\") return align\n return undefined\n}\n\n/** Thin border style for ExcelJS. */\nfunction thinBorder(): { style: \"thin\"; color: { argb: string } } {\n return { style: \"thin\", color: { argb: \"FFDDDDDD\" } }\n}\n\n/** Apply header styling to a cell. */\nfunction styleHeaderCell(\n cell: {\n font: unknown\n fill: unknown\n alignment: unknown\n border: unknown\n value: unknown\n },\n col: TableColumn,\n hs: { background?: string; color?: string } | undefined,\n bordered: boolean,\n): void {\n cell.value = col.header\n cell.font = {\n bold: true,\n color: { argb: hs?.color?.replace(\"#\", \"FF\") ?? \"FF000000\" },\n }\n if (hs?.background) {\n cell.fill = {\n type: \"pattern\",\n pattern: \"solid\",\n fgColor: { argb: hs.background.replace(\"#\", \"FF\") },\n }\n }\n cell.alignment = { horizontal: mapAlignment(col.align) ?? \"left\" }\n if (bordered) {\n cell.border = {\n top: thinBorder(),\n bottom: thinBorder(),\n left: thinBorder(),\n right: thinBorder(),\n }\n }\n}\n\n/** Apply data cell value and styling. */\nfunction styleDataCell(\n cell: {\n value: unknown\n numFmt: unknown\n alignment: unknown\n fill: unknown\n border: unknown\n },\n rawValue: string | number | undefined,\n col: TableColumn,\n striped: boolean,\n isOddRow: boolean,\n bordered: boolean,\n): void {\n cell.value = parseCellValue(rawValue)\n const fmt = getCellFormat(rawValue)\n if (fmt) cell.numFmt = fmt\n cell.alignment = { horizontal: mapAlignment(col.align) ?? \"left\" }\n if (striped && isOddRow) {\n cell.fill = {\n type: \"pattern\",\n pattern: \"solid\",\n fgColor: { argb: \"FFF9F9F9\" },\n }\n }\n if (bordered) {\n cell.border = {\n top: thinBorder(),\n bottom: thinBorder(),\n left: thinBorder(),\n right: thinBorder(),\n }\n }\n}\n\n/** Render a single table node into the worksheet starting at the given row. Returns the next row number. */\nfunction renderTable(\n ws: {\n getRow: (n: number) => { getCell: (n: number) => Record<string, unknown> }\n columns: unknown[]\n },\n tableNode: DocNode,\n startRow: number,\n): number {\n let rowNum = startRow\n const columns = ((tableNode.props.columns ?? []) as (string | TableColumn)[]).map(resolveColumn)\n const rows = (tableNode.props.rows ?? []) as (string | number)[][]\n const hs = tableNode.props.headerStyle as { background?: string; color?: string } | undefined\n const bordered = (tableNode.props.bordered as boolean) ?? false\n\n // Caption\n if (tableNode.props.caption) {\n const captionRow = ws.getRow(rowNum)\n const captionCell = captionRow.getCell(1)\n captionCell.value = tableNode.props.caption as string\n captionCell.font = { italic: true, size: 10 }\n rowNum++\n }\n\n // Header row\n const headerRow = ws.getRow(rowNum)\n for (let i = 0; i < columns.length; i++) {\n const col = columns[i]\n if (!col) continue\n styleHeaderCell(headerRow.getCell(i + 1) as any, col, hs, bordered)\n }\n rowNum++\n\n // Data rows\n for (let r = 0; r < rows.length; r++) {\n const dataRow = ws.getRow(rowNum)\n for (let c = 0; c < columns.length; c++) {\n const col = columns[c]\n if (!col) continue\n styleDataCell(\n dataRow.getCell(c + 1) as any,\n rows[r]?.[c],\n col,\n (tableNode.props.striped as boolean) ?? false,\n r % 2 === 1,\n bordered,\n )\n }\n rowNum++\n }\n\n return rowNum + 1 // gap after table\n}\n\n/** Auto-fit column widths based on content. */\nfunction autoFitColumns(ws: {\n columns: {\n width: number\n eachCell?: (opts: { includeEmpty: boolean }, cb: (cell: { value: unknown }) => void) => void\n }[]\n}): void {\n for (const col of ws.columns) {\n let maxLen = 10\n col.eachCell?.({ includeEmpty: false }, (cell) => {\n const len = String(cell.value ?? \"\").length\n if (len > maxLen) maxLen = len\n })\n col.width = Math.min(maxLen + 2, 50)\n }\n}\n\nexport const xlsxRenderer: DocumentRenderer = {\n async render(node: DocNode, _options?: RenderOptions): Promise<Uint8Array> {\n let ExcelJS: any\n try {\n ExcelJS = await import(\"exceljs\")\n } catch {\n throw new Error(\n '[@pyreon/document] XLSX renderer requires \"exceljs\" package. Install it: bun add exceljs',\n )\n }\n const workbook = new ExcelJS.default.Workbook()\n\n workbook.creator = (node.props.author as string) ?? \"\"\n workbook.title = (node.props.title as string) ?? \"\"\n\n const sheets = extractSheets(node)\n\n if (sheets.length === 0) {\n workbook.addWorksheet(\"Sheet 1\")\n }\n\n for (const sheet of sheets) {\n const ws = workbook.addWorksheet(sheet.name)\n\n let rowNum = 1\n\n // Add headings as title rows\n for (const heading of sheet.headings) {\n const row = ws.getRow(rowNum)\n row.getCell(1).value = heading\n row.getCell(1).font = { bold: true, size: 14 }\n rowNum++\n }\n\n if (sheet.headings.length > 0) rowNum++ // gap after headings\n\n // Add tables\n for (const tableNode of sheet.tables) {\n rowNum = renderTable(ws as unknown as Parameters<typeof renderTable>[0], tableNode, rowNum)\n }\n\n // Auto-fit columns (approximate)\n autoFitColumns(ws as unknown as Parameters<typeof autoFitColumns>[0])\n }\n\n const buffer = await workbook.xlsx.writeBuffer()\n return new Uint8Array(buffer as ArrayBuffer)\n },\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@pyreon/document",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Universal document rendering for Pyreon — one template, every output format (HTML, PDF, DOCX, email, XLSX, Markdown, and more)",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/pyreon/fundamentals.git",
9
- "directory": "packages/document"
8
+ "url": "https://github.com/pyreon/pyreon.git",
9
+ "directory": "packages/fundamentals/document"
10
10
  },
11
11
  "homepage": "https://github.com/pyreon/fundamentals/tree/main/packages/document#readme",
12
12
  "bugs": {
13
- "url": "https://github.com/pyreon/fundamentals/issues"
13
+ "url": "https://github.com/pyreon/pyreon/issues"
14
14
  },
15
15
  "publishConfig": {
16
16
  "access": "public"
@@ -37,16 +37,28 @@
37
37
  "build": "vl_rolldown_build",
38
38
  "dev": "vl_rolldown_build-watch",
39
39
  "test": "vitest run",
40
- "typecheck": "tsc --noEmit"
40
+ "typecheck": "tsc --noEmit",
41
+ "lint": "biome check ."
41
42
  },
42
43
  "peerDependencies": {
43
- "@pyreon/core": ">=0.7.0 <0.8.0",
44
- "@pyreon/reactivity": ">=0.7.0 <0.8.0"
44
+ "@pyreon/core": "^0.11.1",
45
+ "@pyreon/reactivity": "^0.11.1"
45
46
  },
46
47
  "optionalDependencies": {
47
48
  "docx": "^9.6.0",
48
49
  "exceljs": "^4.4.0",
49
50
  "pdfmake": "^0.3.7",
50
51
  "pptxgenjs": "^4.0.1"
52
+ },
53
+ "devDependencies": {
54
+ "@happy-dom/global-registrator": "^20.8.3",
55
+ "@pyreon/core": "^0.11.1",
56
+ "@pyreon/reactivity": "^0.11.1",
57
+ "@types/pdfmake": "^0.3.2",
58
+ "@vitus-labs/tools-lint": "^1.11.0",
59
+ "docx": "^9.6.0",
60
+ "exceljs": "^4.4.0",
61
+ "pdfmake": "^0.3.7",
62
+ "pptxgenjs": "^4.0.1"
51
63
  }
52
64
  }
package/src/builder.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { download } from './download'
1
+ import { download } from "./download"
2
2
  import {
3
3
  Button,
4
4
  Code,
@@ -12,11 +12,12 @@ import {
12
12
  Page,
13
13
  PageBreak,
14
14
  Quote,
15
+ Section,
15
16
  Spacer,
16
17
  Table,
17
18
  Text,
18
- } from './nodes'
19
- import { render } from './render'
19
+ } from "./nodes"
20
+ import { render } from "./render"
20
21
  import type {
21
22
  ButtonProps,
22
23
  CodeProps,
@@ -32,7 +33,7 @@ import type {
32
33
  RenderOptions,
33
34
  TableProps,
34
35
  TextProps,
35
- } from './types'
36
+ } from "./types"
36
37
 
37
38
  /**
38
39
  * Create a document using the builder pattern — no JSX needed.
@@ -56,21 +57,21 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
56
57
  }
57
58
 
58
59
  const builder: DocumentBuilder = {
59
- heading(text: string, p?: Omit<HeadingProps, 'children'>) {
60
+ heading(text: string, p?: Omit<HeadingProps, "children">) {
60
61
  sections.push(Heading({ ...p, children: text }))
61
62
  return builder
62
63
  },
63
64
 
64
- text(text: string, p?: Omit<TextProps, 'children'>) {
65
+ text(text: string, p?: Omit<TextProps, "children">) {
65
66
  sections.push(Text({ ...p, children: text }))
66
67
  return builder
67
68
  },
68
69
 
69
- paragraph(text: string, p?: Omit<TextProps, 'children'>) {
70
+ paragraph(text: string, p?: Omit<TextProps, "children">) {
70
71
  return builder.text(text, p)
71
72
  },
72
73
 
73
- image(src: string, p?: Omit<ImageProps, 'src'>) {
74
+ image(src: string, p?: Omit<ImageProps, "src">) {
74
75
  sections.push(Image({ src, ...p }))
75
76
  return builder
76
77
  },
@@ -80,7 +81,7 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
80
81
  return builder
81
82
  },
82
83
 
83
- list(items: string[], p?: Omit<ListProps, 'children'>) {
84
+ list(items: string[], p?: Omit<ListProps, "children">) {
84
85
  sections.push(
85
86
  List({
86
87
  ...p,
@@ -90,7 +91,7 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
90
91
  return builder
91
92
  },
92
93
 
93
- code(text: string, p?: Omit<CodeProps, 'children'>) {
94
+ code(text: string, p?: Omit<CodeProps, "children">) {
94
95
  sections.push(Code({ ...p, children: text }))
95
96
  return builder
96
97
  },
@@ -105,17 +106,17 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
105
106
  return builder
106
107
  },
107
108
 
108
- quote(text: string, p?: Omit<QuoteProps, 'children'>) {
109
+ quote(text: string, p?: Omit<QuoteProps, "children">) {
109
110
  sections.push(Quote({ ...p, children: text }))
110
111
  return builder
111
112
  },
112
113
 
113
- button(text: string, p: Omit<ButtonProps, 'children'>) {
114
+ button(text: string, p: Omit<ButtonProps, "children">) {
114
115
  sections.push(Button({ ...p, children: text }))
115
116
  return builder
116
117
  },
117
118
 
118
- link(text: string, p: Omit<LinkProps, 'children'>) {
119
+ link(text: string, p: Omit<LinkProps, "children">) {
119
120
  sections.push(Link({ ...p, children: text }))
120
121
  return builder
121
122
  },
@@ -125,14 +126,25 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
125
126
  return builder
126
127
  },
127
128
 
128
- chart(
129
- instance: unknown,
130
- p?: { width?: number; height?: number; caption?: string },
131
- ) {
129
+ add(node) {
130
+ if (Array.isArray(node)) {
131
+ sections.push(...node)
132
+ } else {
133
+ sections.push(node)
134
+ }
135
+ return builder
136
+ },
137
+
138
+ section(children) {
139
+ sections.push(Section({ children }))
140
+ return builder
141
+ },
142
+
143
+ chart(instance: unknown, p?: { width?: number; height?: number; caption?: string }) {
132
144
  // Try to get data URL from chart instance
133
145
  const inst = instance as { getDataURL?: (opts: unknown) => string }
134
146
  if (inst?.getDataURL) {
135
- const dataUrl = inst.getDataURL({ type: 'png', pixelRatio: 2 })
147
+ const dataUrl = inst.getDataURL({ type: "png", pixelRatio: 2 })
136
148
  sections.push(
137
149
  Image({
138
150
  src: dataUrl,
@@ -144,19 +156,16 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
144
156
  } else {
145
157
  sections.push(
146
158
  Text({
147
- children: '[Chart]',
159
+ children: "[Chart]",
148
160
  italic: true,
149
- color: '#999',
161
+ color: "#999",
150
162
  } as TextProps & { children: string }),
151
163
  )
152
164
  }
153
165
  return builder
154
166
  },
155
167
 
156
- flow(
157
- instance: unknown,
158
- p?: { width?: number; height?: number; caption?: string },
159
- ) {
168
+ flow(instance: unknown, p?: { width?: number; height?: number; caption?: string }) {
160
169
  // Try to get SVG from flow instance
161
170
  const inst = instance as { toSVG?: () => string }
162
171
  if (inst?.toSVG) {
@@ -172,9 +181,9 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
172
181
  } else {
173
182
  sections.push(
174
183
  Text({
175
- children: '[Flow Diagram]',
184
+ children: "[Flow Diagram]",
176
185
  italic: true,
177
- color: '#999',
186
+ color: "#999",
178
187
  } as TextProps & { children: string }),
179
188
  )
180
189
  }
@@ -186,75 +195,75 @@ export function createDocument(props: DocumentProps = {}): DocumentBuilder {
186
195
  },
187
196
 
188
197
  async toHtml(options?: RenderOptions) {
189
- return render(getNode(), 'html', options) as Promise<string>
198
+ return render(getNode(), "html", options) as Promise<string>
190
199
  },
191
200
 
192
201
  async toPdf(options?: RenderOptions) {
193
- return render(getNode(), 'pdf', options) as Promise<Uint8Array>
202
+ return render(getNode(), "pdf", options) as Promise<Uint8Array>
194
203
  },
195
204
 
196
205
  async toDocx(options?: RenderOptions) {
197
- return render(getNode(), 'docx', options) as Promise<Uint8Array>
206
+ return render(getNode(), "docx", options) as Promise<Uint8Array>
198
207
  },
199
208
 
200
209
  async toEmail(options?: RenderOptions) {
201
- return render(getNode(), 'email', options) as Promise<string>
210
+ return render(getNode(), "email", options) as Promise<string>
202
211
  },
203
212
 
204
213
  async toPptx(options?: RenderOptions) {
205
- return render(getNode(), 'pptx', options) as Promise<Uint8Array>
214
+ return render(getNode(), "pptx", options) as Promise<Uint8Array>
206
215
  },
207
216
 
208
217
  async toXlsx(options?: RenderOptions) {
209
- return render(getNode(), 'xlsx', options) as Promise<Uint8Array>
218
+ return render(getNode(), "xlsx", options) as Promise<Uint8Array>
210
219
  },
211
220
 
212
221
  async toMarkdown(options?: RenderOptions) {
213
- return render(getNode(), 'md', options) as Promise<string>
222
+ return render(getNode(), "md", options) as Promise<string>
214
223
  },
215
224
 
216
225
  async toText(options?: RenderOptions) {
217
- return render(getNode(), 'text', options) as Promise<string>
226
+ return render(getNode(), "text", options) as Promise<string>
218
227
  },
219
228
 
220
229
  async toCsv(options?: RenderOptions) {
221
- return render(getNode(), 'csv', options) as Promise<string>
230
+ return render(getNode(), "csv", options) as Promise<string>
222
231
  },
223
232
 
224
233
  async toSlack(options?: RenderOptions) {
225
- return render(getNode(), 'slack', options) as Promise<string>
234
+ return render(getNode(), "slack", options) as Promise<string>
226
235
  },
227
236
 
228
237
  async toSvg(options?: RenderOptions) {
229
- return render(getNode(), 'svg', options) as Promise<string>
238
+ return render(getNode(), "svg", options) as Promise<string>
230
239
  },
231
240
 
232
241
  async toTeams(options?: RenderOptions) {
233
- return render(getNode(), 'teams', options) as Promise<string>
242
+ return render(getNode(), "teams", options) as Promise<string>
234
243
  },
235
244
 
236
245
  async toDiscord(options?: RenderOptions) {
237
- return render(getNode(), 'discord', options) as Promise<string>
246
+ return render(getNode(), "discord", options) as Promise<string>
238
247
  },
239
248
 
240
249
  async toTelegram(options?: RenderOptions) {
241
- return render(getNode(), 'telegram', options) as Promise<string>
250
+ return render(getNode(), "telegram", options) as Promise<string>
242
251
  },
243
252
 
244
253
  async toNotion(options?: RenderOptions) {
245
- return render(getNode(), 'notion', options) as Promise<string>
254
+ return render(getNode(), "notion", options) as Promise<string>
246
255
  },
247
256
 
248
257
  async toConfluence(options?: RenderOptions) {
249
- return render(getNode(), 'confluence', options) as Promise<string>
258
+ return render(getNode(), "confluence", options) as Promise<string>
250
259
  },
251
260
 
252
261
  async toWhatsApp(options?: RenderOptions) {
253
- return render(getNode(), 'whatsapp', options) as Promise<string>
262
+ return render(getNode(), "whatsapp", options) as Promise<string>
254
263
  },
255
264
 
256
265
  async toGoogleChat(options?: RenderOptions) {
257
- return render(getNode(), 'google-chat', options) as Promise<string>
266
+ return render(getNode(), "google-chat", options) as Promise<string>
258
267
  },
259
268
 
260
269
  async download(filename: string, options?: RenderOptions) {
package/src/download.ts CHANGED
@@ -1,33 +1,33 @@
1
- import { render } from './render'
2
- import type { DocNode, RenderOptions } from './types'
1
+ import { render } from "./render"
2
+ import type { DocNode, RenderOptions } from "./types"
3
3
 
4
4
  const FORMAT_MAP: Record<string, string> = {
5
- html: 'html',
6
- htm: 'html',
7
- pdf: 'pdf',
8
- docx: 'docx',
9
- doc: 'docx',
10
- xlsx: 'xlsx',
11
- xls: 'xlsx',
12
- pptx: 'pptx',
13
- ppt: 'pptx',
14
- md: 'md',
15
- txt: 'text',
16
- csv: 'csv',
17
- svg: 'svg',
5
+ html: "html",
6
+ htm: "html",
7
+ pdf: "pdf",
8
+ docx: "docx",
9
+ doc: "docx",
10
+ xlsx: "xlsx",
11
+ xls: "xlsx",
12
+ pptx: "pptx",
13
+ ppt: "pptx",
14
+ md: "md",
15
+ txt: "text",
16
+ csv: "csv",
17
+ svg: "svg",
18
18
  }
19
19
 
20
20
  const MIME_TYPES: Record<string, string> = {
21
- html: 'text/html',
22
- pdf: 'application/pdf',
23
- docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
24
- xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
25
- pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
26
- email: 'text/html',
27
- md: 'text/markdown',
28
- text: 'text/plain',
29
- csv: 'text/csv',
30
- svg: 'image/svg+xml',
21
+ html: "text/html",
22
+ pdf: "application/pdf",
23
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
24
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
25
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
26
+ email: "text/html",
27
+ md: "text/markdown",
28
+ text: "text/plain",
29
+ csv: "text/csv",
30
+ svg: "image/svg+xml",
31
31
  }
32
32
 
33
33
  /**
@@ -44,17 +44,15 @@ export async function download(
44
44
  filename: string,
45
45
  options?: RenderOptions,
46
46
  ): Promise<void> {
47
- const ext = filename.split('.').pop()?.toLowerCase()
47
+ const ext = filename.split(".").pop()?.toLowerCase()
48
48
  if (!ext) {
49
- throw new Error(
50
- '[@pyreon/document] Filename must have an extension (e.g., report.pdf).',
51
- )
49
+ throw new Error("[@pyreon/document] Filename must have an extension (e.g., report.pdf).")
52
50
  }
53
51
 
54
52
  const format = FORMAT_MAP[ext]
55
53
  if (!format) {
56
54
  throw new Error(
57
- `[@pyreon/document] Unknown file extension '.${ext}'. Supported: ${Object.keys(FORMAT_MAP).join(', ')}`,
55
+ `[@pyreon/document] Unknown file extension '.${ext}'. Supported: ${Object.keys(FORMAT_MAP).join(", ")}`,
58
56
  )
59
57
  }
60
58
 
@@ -64,17 +62,15 @@ export async function download(
64
62
  result instanceof Uint8Array
65
63
  ? new Blob([result as BlobPart])
66
64
  : new Blob([result], {
67
- type: MIME_TYPES[format] ?? 'application/octet-stream',
65
+ type: MIME_TYPES[format] ?? "application/octet-stream",
68
66
  })
69
67
 
70
- if (typeof document === 'undefined') {
71
- throw new Error(
72
- '[@pyreon/document] download() requires a browser environment.',
73
- )
68
+ if (typeof document === "undefined") {
69
+ throw new Error("[@pyreon/document] download() requires a browser environment.")
74
70
  }
75
71
 
76
72
  const url = URL.createObjectURL(blob)
77
- const a = document.createElement('a')
73
+ const a = document.createElement("a")
78
74
  a.href = url
79
75
  a.download = filename
80
76
  a.click()
package/src/env.d.ts CHANGED
@@ -1,17 +1,3 @@
1
- declare module 'pdfmake/build/pdfmake' {
2
- const pdfMake: {
3
- vfs: Record<string, string>
4
- createPdf: (docDefinition: Record<string, unknown>) => {
5
- getBuffer: (callback: (buffer: ArrayBuffer) => void) => void
6
- }
7
- }
8
- export default pdfMake
9
- }
10
-
11
- declare module 'pdfmake/build/vfs_fonts' {
12
- const fonts: {
13
- pdfMake?: { vfs: Record<string, string> }
14
- vfs?: Record<string, string>
15
- }
16
- export default fonts
17
- }
1
+ // Type declarations for optional dependencies are provided by:
2
+ // - @types/pdfmake (pdfmake/build/pdfmake, pdfmake/build/vfs_fonts)
3
+ // - docx, exceljs, pptxgenjs ship their own types
package/src/index.ts CHANGED
@@ -28,8 +28,9 @@
28
28
  */
29
29
 
30
30
  // Builder
31
- export { createDocument } from './builder'
32
-
31
+ export { createDocument } from "./builder"
32
+ // Download (browser)
33
+ export { download } from "./download"
33
34
  // Primitives
34
35
  export {
35
36
  Button,
@@ -51,10 +52,7 @@ export {
51
52
  Spacer,
52
53
  Table,
53
54
  Text,
54
- } from './nodes'
55
-
56
- // Download (browser)
57
- export { download } from './download'
55
+ } from "./nodes"
58
56
 
59
57
  // Render
60
58
  export {
@@ -62,7 +60,7 @@ export {
62
60
  registerRenderer,
63
61
  render,
64
62
  unregisterRenderer,
65
- } from './render'
63
+ } from "./render"
66
64
 
67
65
  // Types
68
66
  export type {
@@ -95,4 +93,4 @@ export type {
95
93
  TableColumn,
96
94
  TableProps,
97
95
  TextProps,
98
- } from './types'
96
+ } from "./types"