rowpine 0.0.0-stage → 0.1.0
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.
- package/LICENSE.md +18 -0
- package/README.md +71 -2
- package/dist/build-info.d.ts +2 -0
- package/dist/csv.d.ts +14 -0
- package/dist/download.d.ts +4 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +13 -0
- package/dist/license.d.ts +32 -0
- package/dist/pdf/doc.d.ts +357 -0
- package/dist/pdf/index.d.ts +11 -0
- package/dist/pivot/designer.d.ts +90 -0
- package/dist/pivot/extent.d.ts +7 -0
- package/dist/pivot/pivot.d.ts +106 -0
- package/dist/pivot/tanstack.d.ts +65 -0
- package/dist/table-source.d.ts +147 -0
- package/dist/xlsx/index.d.ts +17 -0
- package/dist/xlsx/ooxml.d.ts +115 -0
- package/package.json +63 -4
package/LICENSE.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
DRAFT - replace with the final license terms before the first npm release.
|
|
2
|
+
|
|
3
|
+
Rowpine commercial license
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 Rowpine. All rights reserved.
|
|
6
|
+
|
|
7
|
+
You may install Rowpine and use it without a license key to evaluate it.
|
|
8
|
+
Exports made without a key carry a notice line.
|
|
9
|
+
|
|
10
|
+
Using Rowpine in a product or service needs a license for each developer
|
|
11
|
+
who works on code that uses it. A license is a one-time payment and includes
|
|
12
|
+
one year of updates. You may keep using every version released during that
|
|
13
|
+
year without further payment.
|
|
14
|
+
|
|
15
|
+
You may not redistribute Rowpine as a standalone library or as part of a
|
|
16
|
+
component library, or share license keys outside the licensed developers.
|
|
17
|
+
|
|
18
|
+
CSV export (exportCsv, tableToCsv) may be used without a license.
|
package/README.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# rowpine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pivot tables and Excel and PDF export for TanStack Table and shadcn/ui data tables. Works with TanStack Table v8 and v9.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install rowpine
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The UI (an export menu, a pivot table and its field chooser) installs from the shadcn registry:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx shadcn add https://rowpine.com/r/export-menu.json https://rowpine.com/r/pivot.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Export
|
|
16
|
+
|
|
17
|
+
Pass the TanStack table you already render. The file matches the screen: visible columns in their order, pinned columns frozen, the current filter and sort, groups as Excel outline rows with `SUBTOTAL` formulas, and typed numbers and dates.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { exportCsv, exportPdf, exportXlsx, setLicenseKey } from 'rowpine'
|
|
21
|
+
|
|
22
|
+
setLicenseKey(import.meta.env.VITE_ROWPINE_KEY)
|
|
23
|
+
|
|
24
|
+
await exportXlsx(table, { fileName: 'orders' })
|
|
25
|
+
await exportPdf(table, { fileName: 'orders', title: 'Orders' })
|
|
26
|
+
await exportCsv(table, { fileName: 'orders' }) // free, no key needed
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`rows: 'all' | 'page' | 'selected'` picks the rows (default `'all'`: every filtered row on every page).
|
|
30
|
+
|
|
31
|
+
Per-column settings go in `meta.rowpine`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
{
|
|
35
|
+
accessorKey: 'amount',
|
|
36
|
+
header: 'Amount',
|
|
37
|
+
aggregationFn: 'sum',
|
|
38
|
+
meta: {
|
|
39
|
+
rowpine: {
|
|
40
|
+
numFmt: '"$"#,##0.00', // Excel number format
|
|
41
|
+
format: (v) => money.format(v as number), // PDF text
|
|
42
|
+
cellStyle: (v) => ((v as number) > 5000 ? { fill: '#dcfce7' } : undefined),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Also: `header`, `width`, `align`, `exclude`.
|
|
49
|
+
|
|
50
|
+
## Pivot
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createPivot, exportXlsx, pivotRowStyle } from 'rowpine'
|
|
54
|
+
|
|
55
|
+
const pivot = createPivot(orders, {
|
|
56
|
+
rows: ['region', 'country'],
|
|
57
|
+
cols: ['quarter'],
|
|
58
|
+
values: [{ field: 'amount', agg: 'sum', label: 'Amount' }],
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
pivot.data // rows nested through subRows, for useTable / useReactTable
|
|
62
|
+
pivot.columns // column definitions with accessor functions
|
|
63
|
+
await exportXlsx(pivot.table, { rowStyle: pivotRowStyle })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Aggregations: `sum`, `avg`, `min`, `max`, `count`, `countDistinct`, `first`, `last`, or a function. Subtotals and grand totals are on by default (`rowSubtotals`, `grandTotalRow`, `grandTotalCol`).
|
|
67
|
+
|
|
68
|
+
## License key
|
|
69
|
+
|
|
70
|
+
Without a key everything runs. Excel and PDF files carry one notice line, and the console logs a message once. A key covers every version released during its year of updates, for good.
|
|
71
|
+
|
|
72
|
+
Commercial license: see LICENSE.md.
|
package/dist/csv.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReadOptions, type TableLike } from './table-source';
|
|
2
|
+
export type CsvExportOptions = ReadOptions & {
|
|
3
|
+
fileName?: string;
|
|
4
|
+
/** Field separator. Default ','. */
|
|
5
|
+
delimiter?: string;
|
|
6
|
+
/** Start the file with a UTF-8 byte order mark so Excel reads accents. Default true. */
|
|
7
|
+
bom?: boolean;
|
|
8
|
+
/** Save the file in the browser. Default true. */
|
|
9
|
+
download?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** The table as CSV text. */
|
|
12
|
+
export declare function tableToCsv(table: TableLike, opts?: CsvExportOptions): string;
|
|
13
|
+
/** Export the table to a CSV file. Returns the file as a Blob. */
|
|
14
|
+
export declare function exportCsv(table: TableLike, opts?: CsvExportOptions): Promise<Blob>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Save a Blob as a file in the browser. Does nothing outside one. */
|
|
2
|
+
export declare function saveBlob(blob: Blob, fileName: string): void;
|
|
3
|
+
/** `name` with `ext` added unless it already ends with it. */
|
|
4
|
+
export declare function withExt(name: string | undefined, ext: string): string;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { exportCsv, tableToCsv, type CsvExportOptions } from './csv';
|
|
2
|
+
export { exportXlsx, tableToXlsxModel, sheetToXlsxModel, type XlsxExportOptions } from './xlsx';
|
|
3
|
+
export { buildXlsxParts, type XlsxCell, type XlsxModel } from './xlsx/ooxml';
|
|
4
|
+
export { exportPdf, sheetToPdfParams, type PdfTableExportOptions } from './pdf';
|
|
5
|
+
export type { PdfExportOptions, PdfFont, PdfPageSize } from './pdf/doc';
|
|
6
|
+
export { readTable, type CellStyle, type ColumnLike, type RowLike, type RowScope, type RowpineColumnMeta, type Sheet, type SheetColumn, type SheetRow, type TableLike, } from './table-source';
|
|
7
|
+
export { createPivotModel, filterCollapsedPivotRows, pivotAggregators, type PivotAggregator, type PivotAggregatorId, type PivotColumn, type PivotConfig, type PivotResult, type PivotRow, type PivotRowKind, type PivotValueConfig, } from './pivot/pivot';
|
|
8
|
+
export { createPivot, pivotAsTable, pivotRowStyle, pivotToTree, toTanStackColumns, type PivotColumnDef, type PivotColumnMeta, type PivotTable, type PivotTreeRow, type ToColumnsOptions, } from './pivot/tanstack';
|
|
9
|
+
export { getLicenseStatus, setLicenseKey, type LicensePayload, type LicenseStatus } from './license';
|
|
10
|
+
export { saveBlob } from './download';
|
|
11
|
+
export { ALL_AGGREGATORS, AGG_LABEL, EMPTY_LAYOUT, KIND_WELLS, WELL_LABEL, addToWell, applyLayoutFilters, clearWell, defaultLayoutFor, defaultWellFor, distinctValues, isFieldInLayout, layoutToConfig, pivotFromLayout, removeFromWell, setAggregatorAt, setFilterAllowed, toggleField, type PivotField, type PivotFilterChip, type PivotLayout, type PivotPreset, type PivotValueChip, type Well, } from './pivot/designer';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function q(e,n){if(typeof document>"u"||typeof URL>"u"||!URL.createObjectURL)return;let t=URL.createObjectURL(e),o=document.createElement("a");o.href=t,o.download=n,o.rel="noopener",o.style.display="none",document.body.appendChild(o),o.click(),o.remove(),setTimeout(()=>URL.revokeObjectURL(t),1e3)}function ne(e,n){let t=(e??"").trim()||"export";return t.toLowerCase().endsWith(`.${n}`)?t:`${t}.${n}`}function $t(e){return(e.getState?.()??e.store?.state??e.state)?.grouping??[]}function _t(e){return(e.getSortedRowModel?.()??e.getGroupedRowModel?.()??e.getFilteredRowModel?.()??e.getCoreRowModel()).rows}var Ft=new Set(["sum","mean","min","max"]);function ye(e){return e.columnDef.meta?.rowpine??{}}function Mt(e){let n=ye(e).header??(typeof e.columnDef.header=="string"?e.columnDef.header:e.id),t=[];for(let o=e.parent;o;o=o.parent){let r=typeof o.columnDef.header=="string"?o.columnDef.header:"";r&&t.unshift(r)}return[...t,n].join(" / ")}function It(e){let n=e.getStartVisibleLeafColumns??e.getLeftVisibleLeafColumns,t=e.getEndVisibleLeafColumns??e.getRightVisibleLeafColumns;if(n&&t&&e.getCenterVisibleLeafColumns){let o=n.call(e);return{columns:[...o,...e.getCenterVisibleLeafColumns(),...t.call(e)],frozen:o.length}}return{columns:e.getVisibleLeafColumns?.()??e.getAllLeafColumns(),frozen:0}}function Z(e,n={}){let t=It(e),o=t.columns.filter(l=>!ye(l).exclude),r=t.columns.slice(0,t.frozen).filter(l=>!ye(l).exclude).length,i=o.map(l=>{let h=ye(l),P=l.columnDef.aggregationFn;return{id:l.id,header:Mt(l),width:h.width??l.getSize?.()??150,align:h.align,numFmt:h.numFmt,format:h.format,cellStyle:h.cellStyle,subtotal:typeof P=="string"&&(Ft.has(P)||P==="auto")?P:void 0}}),s=new Set($t(e)),u=[],c=(l,h)=>{let P=l.getIsGrouped?.()??!1,L=i.map(k=>l.getValue(k.id)),N=i.map(k=>s.has(k.id)&&(P?k.id!==l.groupingColumnId:h>0)),x={kind:P?"group":"data",depth:h,values:L,blank:N,descendants:0,hasChildren:l.subRows.length>0,original:l.original,style:n.rowStyle?.(l.original)};if(P){let k=String(l.groupingValue??l.getValue(l.groupingColumnId??"")??""),S=i.findIndex(y=>y.id===l.groupingColumnId);S<0&&(S=0),x.labelAt=S,x.label=k,L[S]=k,L.forEach((y,R)=>{R!==S&&typeof y!="number"&&(x.blank[R]=!0)})}return x},m=(l,h)=>{let P=c(l,h);u.push(P);let L=0;for(let N of l.subRows)L+=m(N,h+1);return P.descendants=L,L+1},d=n.rows??"all";if(d==="all")for(let l of _t(e))m(l,0);else{let l=d==="page"?e.getRowModel().rows:e.getSelectedRowModel?.().rows??[];for(let h of l){if(d==="selected"&&h.getIsGrouped?.())continue;let P=c(h,d==="page"?h.depth:0);d==="selected"&&(P.blank=P.blank.map(()=>!1)),u.push(P)}}return{columns:i,rows:u,freezeColumns:r}}function Ve(e,n,t){return n==null||n===""?"":e.format?e.format(n,t):typeof n=="number"?Number.isFinite(n)?n.toLocaleString(void 0,{maximumFractionDigits:2}):"":n instanceof Date?Number.isNaN(n.getTime())?"":n.toLocaleDateString():typeof n=="boolean"?n?"Yes":"No":String(n)}function Xe(e,n,t){return e.align?e.align:typeof n.rows.find(r=>r.kind==="data"&&r.values[t]!=null)?.values[t]=="number"?"right":"left"}var de=e=>String(e).padStart(2,"0");function Dt(e){if(e==null)return"";if(e instanceof Date){if(Number.isNaN(e.getTime()))return"";let n=`${e.getFullYear()}-${de(e.getMonth()+1)}-${de(e.getDate())}`;return e.getHours()||e.getMinutes()||e.getSeconds()?`${n} ${de(e.getHours())}:${de(e.getMinutes())}:${de(e.getSeconds())}`:n}return typeof e=="number"?Number.isFinite(e)?String(e):"":String(e)}function We(e,n){let t=/^[=+\-@\t\r]/.test(e)&&!/^-?\d/.test(e)?`'${e}`:e;return t.includes(n)||/["\r\n]/.test(t)?`"${t.replace(/"/g,'""')}"`:t}function je(e,n={}){let t=Z(e,n),o=n.delimiter??",",r=[t.columns.map(i=>We(i.header,o)).join(o)];for(let i of t.rows)i.kind!=="group"&&r.push(i.values.map(s=>We(Dt(s),o)).join(o));return r.join(`\r
|
|
2
|
+
`)+`\r
|
|
3
|
+
`}async function Ot(e,n={}){let t=je(e,n),o=new Blob([n.bom??!0?"\uFEFF"+t:t],{type:"text/csv;charset=utf-8"});return(n.download??!0)&&q(o,ne(n.fileName,"csv")),o}import{verifyAsync as Nt}from"@noble/ed25519";var Ue="2026-11-13";var Et="943b6a3625ce15fa7fd62ee65897491f1d4f55fa5430cb253b6db9d0634e6cad",be="Exported with an unlicensed copy of Rowpine. https://rowpine.com/",Ke=null,Le=null,Ae=!1;function zt(e){Ke=e?.trim()||null,Le=null,Ae=!1}function Ge(e){let n=e.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-e.length%4)%4),t=atob(n),o=new Uint8Array(t.length);for(let r=0;r<t.length;r++)o[r]=t.charCodeAt(r);return o}function Bt(e){let n=new Uint8Array(e.length/2);for(let t=0;t<n.length;t++)n[t]=parseInt(e.slice(t*2,t*2+2),16);return n}async function Vt(e,n,t){if(!e)return{licensed:!1,reason:"missing",buildDate:t};let o=e.split(".");if(o.length!==3||o[0]!=="RP1")return{licensed:!1,reason:"malformed",buildDate:t};let r;try{r=JSON.parse(new TextDecoder().decode(Ge(o[1])))}catch{return{licensed:!1,reason:"malformed",buildDate:t}}let i=!1;try{i=await Nt(Ge(o[2]),new TextEncoder().encode(o[1]),Bt(n))}catch{i=!1}return!i||r.v!==1?{licensed:!1,reason:"invalid",buildDate:t}:t<=r.upd?{licensed:!0,payload:r,buildDate:t}:{licensed:!1,reason:"updates-ended",payload:r,buildDate:t}}function He(){return Le??=Vt(Ke,Et,Ue),Le}function Xt(e,n){let t="Get a key at https://rowpine.com/#pricing and pass it to setLicenseKey().";switch(e.reason){case"missing":return`Rowpine: ${n} is running without a license key. It works, and files carry a one-line notice. ${t}`;case"malformed":case"invalid":return`Rowpine: the license key passed to setLicenseKey() is not valid. ${t}`;case"updates-ended":return`Rowpine: this version was built on ${e.buildDate}, after the updates on your key ended (${e.payload?.upd}). Keep the version you have, or renew updates at https://rowpine.com/#pricing.`}}async function oe(e){let n=await He();return n.licensed?!0:(Ae||(Ae=!0,console.warn(Xt(n,e))),!1)}var Wt={"&":"&","<":"<",">":">",'"':""","'":"'"},jt=/[\x00-\x08\x0B\x0C\x0E-\x1F]/g;function j(e){return e.replace(jt,"").replace(/[&<>"']/g,n=>Wt[n])}function Y(e){let n=e,t="";do t=String.fromCharCode(65+n%26)+t,n=Math.floor(n/26)-1;while(n>=0);return t}function we(e){if(!e)return null;let n=/^#?([0-9a-f]{6})$/i.exec(e.trim());return n?"FF"+n[1].toUpperCase():null}function Ut(e){return(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds())-Date.UTC(1899,11,30))/864e5}function Gt(e){if(e instanceof Date)return e;let n=String(e).trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(n);return t?new Date(Number(t[1]),Number(t[2])-1,Number(t[3])):new Date(n)}var Ye=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
4
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/></Types>`,Kt=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
5
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`;function Ht(e){return e.replace(/[\\/?*[\]:]/g," ").trim().slice(0,31)||"Sheet1"}function Yt(e){return`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
6
|
+
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="${j(Ht(e))}" sheetId="1" r:id="rId1"/></sheets></workbook>`}var qt=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
7
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>`;function Zt(e,n){let t=new Map,o=164,r=x=>{if(!x)return 0;let k=t.get(x);if(k!=null)return k;let S=o++;return t.set(x,S),S},i=['<font><sz val="11"/><name val="Calibri"/></font>','<font><b/><sz val="11"/><name val="Calibri"/></font>'],s=new Map,u=(x,k)=>{let S=we(k);if(!x&&!S)return 0;let y=`${x?"b":""}|${S??""}`,R=s.get(y);if(R!=null)return R;let F=i.length;return i.push(`<font>${x?"<b/>":""}<sz val="11"/><name val="Calibri"/>${S?`<color rgb="${S}"/>`:""}</font>`),s.set(y,F),F},c=['<fill><patternFill patternType="none"/></fill>','<fill><patternFill patternType="gray125"/></fill>'],m=new Map,d=x=>{let k=we(x);if(!k)return 0;let S=m.get(k);if(S!=null)return S;let y=c.length;return c.push(`<fill><patternFill patternType="solid"><fgColor rgb="${k}"/><bgColor indexed="64"/></patternFill></fill>`),m.set(k,y),y},l=['<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>','<xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/>'],h=new Map,P=(x,k,S,y)=>{let R=`${x}|${k}|${S}|${y??""}`,F=h.get(R);if(F!=null)return F;let $=l.length,D=y&&y!=="left"?`<alignment horizontal="${y}"/>`:"";return l.push(`<xf numFmtId="${x}" fontId="${k}" fillId="${S}" borderId="0" xfId="0"${x?' applyNumberFormat="1"':""}${k?' applyFont="1"':""}${S?' applyFill="1"':""}${D?' applyAlignment="1"':""}>${D}</xf>`),h.set(R,$),$},L=x=>{let k=r(x.numFmt),S=u(x.bold===!0,x.color),y=d(x.fill);return k===0&&S===0&&y===0&&(!x.align||x.align==="left")?0:P(k,S,y,x.align)};for(let x of n)for(let k of x)L(k);return{styleIndexOf:L,build:()=>`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
8
|
+
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`+(t.size>0?`<numFmts count="${t.size}">${[...t.entries()].map(([k,S])=>`<numFmt numFmtId="${S}" formatCode="${j(k)}"/>`).join("")}</numFmts>`:"")+`<fonts count="${i.length}">${i.join("")}</fonts><fills count="${c.length}">${c.join("")}</fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="${l.length}">${l.join("")}</cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`}}function $e(e){let{header:n,rows:t}=e,o=Zt(n,t),r=[],s=[`<row r="1">${n.map((y,R)=>`<c r="${Y(R)}1" s="1" t="inlineStr"><is><t xml:space="preserve">${j(y)}</t></is></c>`).join("")}</row>`];for(let y=0;y<t.length;y++){let R=y+2,F=t[y],$=[];for(let E=0;E<F.length;E++){let T=F[E],M=`${Y(E)}${R}`,J=o.styleIndexOf(T),I=J?` s="${J}"`:"";if(T.link&&r.push({ref:M,url:T.link,id:`rId${r.length+1}`}),T.formula){let V=T.value==null||T.value===""?"":T.t==="n"?`<v>${Number(T.value)}</v>`:"";$.push(`<c r="${M}"${I}><f>${j(T.formula)}</f>${V}</c>`)}else if(T.value==null||T.value==="")$.push(`<c r="${M}"${I}/>`);else if(T.t==="n")$.push(`<c r="${M}"${I}><v>${Number(T.value)}</v></c>`);else if(T.t==="d"){let V=Gt(T.value);$.push(`<c r="${M}"${I}><v>${Ut(V)}</v></c>`)}else T.t==="b"?$.push(`<c r="${M}"${I} t="b"><v>${T.value?1:0}</v></c>`):$.push(`<c r="${M}"${I} t="inlineStr"><is><t xml:space="preserve">${j(String(T.value))}</t></is></c>`)}let D=Math.max(0,Math.min(7,Math.floor(e.outline?.[y]??0)));s.push(`<row r="${R}"${D?` outlineLevel="${D}"`:""}>${$.join("")}</row>`)}let u=Math.max(0,...(e.outline??[]).map(y=>Math.min(7,Math.floor(y))));e.notes?.length&&!e.table&&e.notes.forEach((y,R)=>{let F=t.length+3+R;s.push(`<row r="${F}"><c r="A${F}" t="inlineStr"><is><t xml:space="preserve">${j(y)}</t></is></c></row>`)});let c=e.widths?`<cols>${e.widths.map((y,R)=>`<col min="${R+1}" max="${R+1}" width="${Math.max(4,Math.round(y/7*100)/100)}" customWidth="1"/>`).join("")}</cols>`:"",m=Math.max(0,Math.floor(e.freezeColumns??0)),d=e.freezeHeader?1:0,l='<sheetView workbookViewId="0"/>';if(d>0||m>0){let y=`${Y(m)}${d+1}`,R=m>0&&d>0?"bottomRight":m>0?"topRight":"bottomLeft";l=`<sheetView workbookViewId="0"><pane${m>0?` xSplit="${m}"`:""}${d>0?` ySplit="${d}"`:""} topLeftCell="${y}" activePane="${R}" state="frozen"/><selection pane="${R}" activeCell="${y}" sqref="${y}"/></sheetView>`}let h=t.length>0?(e.condFormats??[]).map((y,R)=>{let F=Y(y.colIdx),$=`${F}2:${F}${t.length+1}`;if(y.kind==="dataBar"){let M=we(y.color)??"FF638EC6";return`<conditionalFormatting sqref="${$}"><cfRule type="dataBar" priority="${R+1}"><dataBar><cfvo type="min"/><cfvo type="max"/><color rgb="${M}"/></dataBar></cfRule></conditionalFormatting>`}let D=y.colors.map(M=>we(M)??"FFFFFFFF"),E=D.length>=3?'<cfvo type="min"/><cfvo type="percentile" val="50"/><cfvo type="max"/>':'<cfvo type="min"/><cfvo type="max"/>',T=D.map(M=>`<color rgb="${M}"/>`).join("");return`<conditionalFormatting sqref="${$}"><cfRule type="colorScale" priority="${R+1}"><colorScale>${E}${T}</colorScale></cfRule></conditionalFormatting>`}).join(""):"",P=r.length?`<hyperlinks>${r.map(y=>`<hyperlink ref="${y.ref}" r:id="${y.id}"/>`).join("")}</hyperlinks>`:"",L="",N="",x="";if(e.table&&n.length>0){let y=e.table,R=new Set,F=n.map((I,V)=>{let O=(I??"").trim()||`Column${V+1}`,X=O,H=1;for(;R.has(X.toLowerCase());)X=`${O}${++H}`;return R.add(X.toLowerCase()),X}),$=Y(n.length-1),D=t.length+1,E=D+1,T=y.totalsRow&&t.length>0,M=y.totalsFns.findIndex(I=>!I);if(T){let I=n.map((V,O)=>{let X=`${Y(O)}${E}`,H=y.totalsFns[O];if(H){let U=0;for(let Q of t){let ae=Q[O];ae?.t==="n"&&typeof ae.value=="number"&&(U+=ae.value)}let se=t.find(Q=>Q[O]?.t==="n")?.[O]?.numFmt,W=o.styleIndexOf({t:"n",value:U,numFmt:se,bold:!0}),G=H==="average"?101:H==="count"?103:109,Pe=H==="count"?t.length:U;return`<c r="${X}"${W?` s="${W}"`:""}><f>SUBTOTAL(${G},${y.name}[${j(F[O])}])</f><v>${Pe}</v></c>`}if(O===M){let U=o.styleIndexOf({t:"s",value:"Total",bold:!0});return`<c r="${X}"${U?` s="${U}"`:""} t="inlineStr"><is><t>Total</t></is></c>`}return`<c r="${X}"/>`});s.push(`<row r="${E}">${I.join("")}</row>`)}let J=F.map((I,V)=>{let O=T?y.totalsFns[V]:void 0,X=T?O?` totalsRowFunction="${O}"`:V===M?' totalsRowLabel="Total"':"":"";return`<tableColumn id="${V+1}" name="${j(I)}"${X}/>`}).join("");L=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
9
|
+
<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="${j(y.name)}" displayName="${j(y.name)}" ref="A1:${$}${T?E:D}"${T?' totalsRowCount="1"':' totalsRowShown="0"'}><autoFilter ref="A1:${$}${D}"/><tableColumns count="${n.length}">${J}</tableColumns><tableStyleInfo name="${j(y.style??"TableStyleMedium2")}" showFirstColumn="0" showLastColumn="0" showRowStripes="1" showColumnStripes="0"/></table>`,x=`rId${r.length+1}`,N=`<tableParts count="1"><tablePart r:id="${x}"/></tableParts>`}let k=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
10
|
+
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">`+(u>0?'<sheetPr><outlinePr summaryBelow="0"/></sheetPr>':"")+`<sheetViews>${l}</sheetViews><sheetFormatPr defaultRowHeight="15"${u>0?` outlineLevelRow="${u}"`:""}/>`+c+`<sheetData>${s.join("")}</sheetData>`+h+P+N+"</worksheet>",S={"[Content_Types].xml":L?Ye.replace("</Types>",'<Override PartName="/xl/tables/table1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"/></Types>'):Ye,"_rels/.rels":Kt,"xl/workbook.xml":Yt(e.sheetName),"xl/_rels/workbook.xml.rels":qt,"xl/styles.xml":o.build(),"xl/worksheets/sheet1.xml":k};if(L&&(S["xl/tables/table1.xml"]=L),r.length||x){let y=r.map(R=>`<Relationship Id="${R.id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="${j(R.url)}" TargetMode="External"/>`);x&&y.push(`<Relationship Id="${x}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" Target="../tables/table1.xml"/>`),S["xl/worksheets/_rels/sheet1.xml.rels"]=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
11
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">`+y.join("")+"</Relationships>"}return S}async function qe(e,n){let t=new n;for(let[o,r]of Object.entries(e))t.file(o,r);return t.generateAsync({type:"blob",mimeType:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"})}var Jt={sum:9,mean:1,min:5,max:4};function Qt(e,n){return n==null||n===""?{t:"s",value:""}:typeof n=="number"?Number.isFinite(n)?{t:"n",value:n,numFmt:e.numFmt}:{t:"s",value:""}:n instanceof Date?Number.isNaN(n.getTime())?{t:"s",value:""}:{t:"d",value:n,numFmt:e.numFmt??"yyyy-mm-dd"}:typeof n=="boolean"?{t:"b",value:n}:{t:"s",value:String(n)}}function Ze(e,n={},t){let o=n.groupFill??"#eef2f0",r=e.rows.map((i,s)=>{let u=s+2;return e.columns.map((c,m)=>{if(i.blank[m])return{t:"s",value:"",fill:i.kind==="group"?o:void 0};let d=i.values[m],l=Qt(c,d);if(i.kind==="group"){l.bold=!0,l.fill=o;let h=c.subtotal==="auto"?typeof d=="number"?"sum":void 0:c.subtotal;if(h&&m!==i.labelAt&&typeof d=="number"&&i.descendants>0){let P=Y(m);l.formula=`SUBTOTAL(${Jt[h]},${P}${u+1}:${P}${u+i.descendants})`}}else{let h=c.cellStyle?.(d,i.original);h&&Object.assign(l,{fill:h.fill,color:h.color,bold:h.bold})}return i.style&&(i.style.bold&&(l.bold=!0),i.style.fill&&(l.fill=i.style.fill),i.style.color&&(l.color=i.style.color)),i.hasChildren&&i.kind==="data"&&(l.bold=!0),c.align&&c.align!=="left"&&(l.align=c.align),l})});return{sheetName:n.sheetName??"Sheet1",header:e.columns.map(i=>i.header),rows:r,widths:e.columns.map(i=>i.width),freezeHeader:!0,freezeColumns:e.freezeColumns,outline:e.rows.map(i=>i.depth),notes:t}}function Je(e,n={},t){return Ze(Z(e,n),n,t)}async function en(e,n={}){let t=await oe("Excel export"),o=Je(e,n,t?void 0:[be]),{default:r}=await import("jszip"),i=await qe($e(o),r);return(n.download??!0)&&q(i,ne(n.fileName,"xlsx")),i}function tn(e){let n=r=>!!r&&typeof r=="object"&&Object.keys(r).some(i=>/\.(ttf|otf)$/i.test(i))&&Object.values(r).every(i=>typeof i=="string"),t=new Set,o=r=>{if(!r||typeof r!="object"||t.has(r))return null;if(t.add(r),n(r))return r;let i=r;return o(i.pdfMake?.vfs)??o(i.vfs)??o(i.default)};return o(e)}function tt(e){return new Promise((n,t)=>{let o=e.getBlob(n);o&&typeof o.then=="function"&&o.then(n,t)})}function nt(e,n){let t=tn(n);return t?(typeof e.addVirtualFileSystem=="function"&&e.addVirtualFileSystem(t),e.vfs=t,!0):!1}var nn={normal:"Roboto-Regular.ttf",bold:"Roboto-Medium.ttf",italics:"Roboto-Italic.ttf",bolditalics:"Roboto-MediumItalic.ttf"};function ot(e,n){let t={},o=m=>/^(https?:|blob:|data:|\/|\.\.?\/)/i.test(m),r=(m,d)=>{if(!d)return;if(o(d))return d;let l=`rowpine-${n.name}-${m}.ttf`;return t[l]=d,l},i=r("normal",n.normal),s=r("bold",n.bold)??i,u={normal:i,bold:s,italics:r("italics",n.italics)??i,bolditalics:r("bolditalics",n.bolditalics)??s};if(Object.keys(t).length){let m={...e.vfs??{},...t};typeof e.addVirtualFileSystem=="function"&&e.addVirtualFileSystem(m),e.vfs=m}let c={Roboto:nn,...e.fonts??{},[n.name]:u};typeof e.addFonts=="function"&&e.addFonts(c),e.fonts=c}var on={A4:[595.28,841.89],A3:[841.89,1190.55],A5:[419.53,595.28],LETTER:[612,792],LEGAL:[612,1008]},_e="#dc2626",Qe="#d97706",rn="#eab308",Fe="#16a34a",re=(e,n)=>({type:"polyline",points:e,closePath:!0,lineWidth:.1,lineColor:n,color:n}),Me=e=>[{x:e/2,y:0},{x:e,y:e*.5},{x:e*.68,y:e*.5},{x:e*.68,y:e},{x:e*.32,y:e},{x:e*.32,y:e*.5},{x:0,y:e*.5}],et={"\u2191":e=>[re(Me(e),Fe)],"\u2193":e=>[re(Me(e).map(n=>({x:n.x,y:e-n.y})),_e)],"\u2192":e=>[re(Me(e).map(n=>({x:e-n.y,y:n.x})),Qe)],"\u25B2":e=>[re([{x:0,y:e},{x:e/2,y:0},{x:e,y:e}],Fe)],"\u25BC":e=>[re([{x:0,y:0},{x:e,y:0},{x:e/2,y:e}],_e)],"\u25C6":e=>[re([{x:e/2,y:0},{x:e,y:e/2},{x:e/2,y:e},{x:0,y:e/2}],Qe)],"\u{1F534}":e=>[{type:"ellipse",x:e/2,y:e/2,r1:e/2,r2:e/2,color:_e}],"\u{1F7E1}":e=>[{type:"ellipse",x:e/2,y:e/2,r1:e/2,r2:e/2,color:rn}],"\u{1F7E2}":e=>[{type:"ellipse",x:e/2,y:e/2,r1:e/2,r2:e/2,color:Fe}],"\u2713":e=>[{type:"polyline",points:[{x:0,y:e*.55},{x:e*.38,y:e*.92},{x:e,y:e*.08}],lineWidth:Math.max(1,e*.16),lineColor:"#334155"}]};function ln(e){let{columns:n}=e,t=e.opts??{},o=e.styles??{},r=e.theme??{},i=e.now??new Date(0),s=t.fontSize??8,u=t.headerColor??o.headerRow?.backgroundColor??r.headerColor??"#334155",c=t.headerTextColor??o.headerRow?.color??r.headerTextColor??"#ffffff",m=t.zebra??!0,d=t.zebraColor??o.rowAlternate?.backgroundColor??r.zebraColor??"#f1f5f9",l=t.groupColor??r.groupColor??"#e2e8f0",h=t.groupTextColor??r.groupTextColor??"#0f172a",P=t.subtotalColor??r.subtotalColor??"#f8fafc",L=t.textColor??o.rows?.color??r.textColor,N=t.borderColor??r.borderColor??"#e2e8f0",x=t.repeatHeader??!0,k=a=>a==="bold"||typeof a=="number"&&a>=600,S=a=>{if(a===void 0)return;let f=typeof a=="number"?a:parseFloat(a);return Number.isFinite(f)&&f>0?f:void 0},y=(a,f)=>{if(!f)return;f.backgroundColor&&(a.fillColor=f.backgroundColor),f.color&&(a.color=f.color),k(f.fontWeight)?a.bold=!0:f.fontWeight==="normal"&&(a.bold=!1),f.fontStyle==="italic"&&(a.italics=!0);let p=S(f.fontSize);p&&(a.fontSize=p),f.textAlign&&(a.alignment=f.textAlign)},R=new Map;for(let[a,f]of Object.entries(o.cells??{})){let p=/^([A-Z]+)(\d+)$/i.exec(a.trim());if(!p)continue;let g=0;for(let C of p[1].toUpperCase())g=g*26+(C.charCodeAt(0)-64);R.set(`${Number(p[2])-1}:${g-1}`,f)}let F=t.pageOrientation??(n.length>(t.autoLandscapeThreshold??8)?"landscape":"portrait"),$=t.margins??[28,34,28,34],[D,E]=on[t.pageSize??"A4"],T=(F==="landscape"?E:D)-$[0]-$[2],M=(F==="landscape"?D:E)-$[1]-$[3],J=n.map((a,f)=>{let p={text:a.header,bold:!0,alignment:a.align??"left",color:c,fillColor:u};return y(p,o.headerRow?{...o.headerRow,backgroundColor:void 0,color:void 0}:void 0),y(p,R.get(`0:${f}`)),p}),I=e.body??(e.rows??[]).map(a=>({kind:"data",cells:a})),V=e.dataCellStyle,O=e.dataCellLink,X=0,H=new Set,U=new Set,se=[],W=[J];if(I.forEach((a,f)=>{let p=f+1;if(a.kind==="group"){let g={text:a.label,bold:!0,color:h,fillColor:l,colSpan:n.length,margin:[(a.level??0)*12,2,0,2]},C=Array.from({length:Math.max(0,n.length-1)},()=>({text:""}));U.add(p),W.push([g,...C])}else if(a.kind==="subtotal")W.push(n.map((g,C)=>({text:a.cells[C]??"",bold:!0,alignment:g.align??"left",fillColor:P})));else{H.add(p);let g=X++;W.push(n.map((C,v)=>{let b={text:a.cells[v]??"",alignment:C.align??"left"};L&&(b.color=L),y(b,o.rows?{...o.rows,backgroundColor:void 0}:void 0),m&&g%2===1&&o.rowAlternate?.color&&(b.color=o.rowAlternate.color),y(b,R.get(`${g+1}:${v}`));let w=V?.(g,v);w&&(w.fill&&(b.fillColor=w.fill),w.color&&(b.color=w.color),w.bold&&(b.bold=!0),w.icon&&w.iconOnly&&(b.text=""),w.icon&&et[w.icon]?se.push([p,v,w.icon]):w.icon&&(b.text=w.iconOnly?w.icon:`${w.icon} ${b.text??""}`));let A=O?.(g,v);return A&&(b.link=A,b.decoration="underline",w?.color||(b.color="#2563eb")),b}))}}),e.merges?.length&&!e.body){let a=[...H].sort((f,p)=>f-p);for(let f of e.merges){let p=Math.max(1,Math.floor(f.rowSpan??1)),g=Math.max(1,Math.floor(f.colSpan??1));if(p===1&&g===1)continue;let C=a[f.row];if(C===void 0||f.col<0||f.col>=n.length)continue;let v=W[C]?.[f.col];if(v){p>1&&(v.rowSpan=Math.min(p,W.length-C)),g>1&&(v.colSpan=Math.min(g,n.length-f.col));for(let b=0;b<(v.rowSpan??1);b+=1)for(let w=0;w<(v.colSpan??1);w+=1){if(b===0&&w===0)continue;let A=W[C+b]?.[f.col+w];A&&(A.text="")}}}}let G=n.length,Pe=10.5,Q=a=>a?.62:.56,ae=new Set(se.map(([a,f])=>`${a}:${f}`)),fe=new Array(G).fill(0),ke=new Array(G).fill(0);W.forEach((a,f)=>{U.has(f)||a.forEach((p,g)=>{if((p.colSpan??1)>1||g>=G)return;let C=Q(p.bold)*(p.fontSize??s)/s,v=ae.has(`${f}:${g}`)?2:0,b=0,w=0;for(let A of(p.text??"").split(`
|
|
12
|
+
`)){w=Math.max(w,A.length);for(let _ of A.split(/\s+/))b=Math.max(b,_.length)}fe[g]=Math.max(fe[g],(Math.min(b,28)+v)*C),ke[g]=Math.max(ke[g],(w+v)*C)})});let ue=a=>T-a*Pe-2,Ce=(a,f)=>a.reduce((p,g)=>p+Math.max(fe[g]*f,f),0),ee=t.fit??"auto",te=Array.isArray(t.columnWidths)?t.columnWidths:null,pe=n.map((a,f)=>f),z=s,me=[pe];if(ee!=="none"&&!te&&G>0&&Ce(pe,s)>ue(G)){let a=ue(G),f=a>0?s*a/Ce(pe,s):0,p=ee==="shrink"?4:Math.max(5,s*.75);if(ee!=="split"&&f>=p)z=Math.floor(f*10)/10;else if(ee==="shrink")z=p;else{let g=Math.max(0,Math.min(t.repeatColumns??1,G-1)),C=pe.slice(0,g);me=[];let v=[];for(let b=g;b<G;b++)v.length&&Ce([...C,...v,b],s)>ue(C.length+v.length+1)&&(me.push([...C,...v]),v=[]),v.push(b);v.length&&me.push([...C,...v])}}let kt=a=>{if(te){let w=te.filter(_=>typeof _=="number").reduce((_,B)=>_+B,0),A=ue(te.length);return w>A&&ee!=="none"?te.map(_=>typeof _=="number"?Math.floor(_*A/w):_):te}if(ee==="none")return a.map(()=>t.columnWidths==="auto"?"auto":"*");let f=a.map(b=>Math.max(fe[b]*z,z)),p=a.map((b,w)=>Math.max(f[w],ke[b]*z)),g=ue(a.length),C=p.reduce((b,w)=>b+w,0),v;if(t.columnWidths==="auto"&&C<=g)v=p;else if(C<=g)v=p.map(b=>b+(g-C)/a.length);else{let b=f.reduce((_,B)=>_+B,0),w=Math.max(0,g-b),A=p.reduce((_,B,Te)=>_+(B-f[Te]),0);v=f.map((_,B)=>_+(A>0?w*(p[B]-_)/A:w/a.length))}return v.map(b=>Math.floor(b*100)/100)},Ct=z*1.25,ze=(a,f,p)=>{let g=0;return f.forEach((C,v)=>{let b=a[C],w=p[v];if(!b||(b.rowSpan??1)>1||typeof w!="number")return;let A=Q(b.bold)*(b.fontSize??z),_=0;for(let B of(b.text??"").split(`
|
|
13
|
+
`))_+=Math.max(1,Math.ceil(B.length*A/Math.max(w,1)));g=Math.max(g,_*Ct*((b.fontSize??z)/z))}),g+6};for(let[a,f,p]of se){let g=W[a]?.[f];if(!g)continue;let C=z*.8,{text:v,alignment:b,color:w,bold:A,italics:_,fontSize:B,link:Te,decoration:Lt}=g;delete g.text;let ge={width:C+3,canvas:et[p](C),margin:[0,z*.2,0,0]},he={width:"*",text:""};g.columns=v?[ge,{width:"*",text:v,alignment:b,color:w,bold:A,italics:_,fontSize:B,link:Te,decoration:Lt}]:b==="center"?[he,ge,he]:b==="right"?[he,ge]:[ge,he],g.columnGap=0;for(let At of["alignment","color","bold","italics","fontSize","link","decoration"])delete g[At]}let Rt=(a,f,p)=>p.length===G&&p.every((g,C)=>g===C)?a:U.has(f)?[{...a[0],colSpan:p.length},...p.slice(1).map(()=>({text:""}))]:p.map((g,C)=>{let v=a[g],b=v.colSpan??1;if(b<=1)return v;let w=1;for(;w<b&&p[C+w]===g+w;)w+=1;return{...v,colSpan:w}}),St={kind:"table",stripes:m?[...H].filter(a=>a>0&&a%2===0):[],zebraColor:d,lineColor:N},Tt=me.map((a,f)=>{let p=kt(a),g=W.map((w,A)=>Rt(w,A,a)),C=x?ze(g[0],a.map((w,A)=>A),p):0,v=M-C,b=g.some((w,A)=>A>0&&!U.has(A)&&ze(w,a.map((_,B)=>B),p)>v*.7);return{...f>0?{pageBreak:"before"}:{},...z!==s?{fontSize:z}:{},table:{headerRows:x?1:0,dontBreakRows:!b,widths:p,body:g},rpLayout:St}}),Be=(a,f)=>(a??[]).map(p=>{if("image"in p)return{image:p.image,...p.width?{width:p.width}:{},...p.height?{height:p.height}:{},margin:f};if("text"in p){let g={text:p.text};return y(g,p.style),{...g,margin:f}}return{columns:[{text:p.left??"",alignment:"left"},{text:p.center??"",alignment:"center"},{text:p.right??"",alignment:"right"}],margin:f}}),K=[];if(K.push(...Be(e.headerLines,[0,0,0,4])),t.logo&&K.push({image:t.logo,width:t.logoWidth??90,margin:[0,0,0,8]}),t.title&&K.push({text:t.title,fontSize:s+8,bold:!0,margin:[0,0,0,2]}),t.subtitle&&K.push({text:t.subtitle,fontSize:s+1,color:"#64748b",margin:[0,0,0,8]}),t.kpis?.length){let a=t.kpiColor??"#f8fafc";K.push({table:{widths:t.kpis.map(()=>"*"),body:[t.kpis.map(f=>({fillColor:a,margin:[8,6,8,6],stack:[{text:f.label,fontSize:s,color:"#64748b"},{text:f.value,fontSize:s+10,bold:!0,margin:[0,2,0,0]},...f.delta?[{text:f.delta,fontSize:s,color:f.color??"#64748b",margin:[0,2,0,0]}]:[]]}))]},rpLayout:{kind:"kpi"},margin:[0,0,0,10]})}let Re=(e.charts??(t.charts??[]).filter(a=>"image"in a)).map(a=>({stack:[...a.title?[{text:a.title,fontSize:s+3,bold:!0,margin:[0,0,0,4]}]:[],{image:a.image,width:Math.min(a.width??T,T)},...a.caption?[{text:a.caption,fontSize:s,color:"#64748b",margin:[0,3,0,0]}]:[]],margin:[0,0,0,12],unbreakable:!0}));t.chartsPosition!=="below"&&K.push(...Re),K.push(...Tt),t.chartsPosition==="below"&&(Re.length&&K.push({text:"",margin:[0,0,0,12]}),K.push(...Re)),K.push(...Be(e.footerLines,[0,4,0,0]));let Se={pageSize:t.pageSize??"A4",pageOrientation:F,pageMargins:$,defaultStyle:t.font?{fontSize:s,font:t.font.name}:{fontSize:s},content:K,info:{...t.title?{title:t.title}:{},...t.author?{author:t.author}:{},...t.subtitle?{subject:t.subtitle}:{},creator:"Rowpine",creationDate:i}};if(t.watermark){let a=typeof t.watermark=="string"?{text:t.watermark}:t.watermark;Se.watermark={text:a.text,color:a.color??"#94a3b8",opacity:a.opacity??.15,bold:!0}}if(t.showPageNumbers??!0){let a=g=>String(g).padStart(2,"0"),f=t.labels?.date?.(i)??`${i.getFullYear()}-${a(i.getMonth()+1)}-${a(i.getDate())}`,p="Page {page} of {pages}";if(t.labels?.page){let g=t.labels.page(Ie,De);g.includes(String(Ie))&&g.includes(String(De))&&(p=g.split(String(Ie)).join("{page}").split(String(De)).join("{pages}"))}Se.rpFooter={margin:[$[0],6,$[2],0],date:f,page:p}}return Se}var Ie=86421,De=97531;function rt(e){return sn(ln(e),{page:e.opts?.labels?.page})}function sn(e,n={}){let t=i=>{if(Array.isArray(i)){i.forEach(t);return}if(!i||typeof i!="object")return;let s=i;s.rpLayout&&(s.layout=an(s.rpLayout)),s.stack&&t(s.stack)};t(e.content);let o=e,r=e.rpFooter;if(r){let i=n.page??((s,u)=>r.page.split("{page}").join(String(s)).split("{pages}").join(String(u)));o.footer=(s,u)=>({margin:r.margin,columns:[{text:r.date,alignment:"left",fontSize:7,color:"#94a3b8"},{text:i(s,u),alignment:"right",fontSize:7,color:"#94a3b8"}]})}return o}function an(e){if(e.kind==="kpi")return{hLineWidth:()=>0,vLineWidth:()=>4,vLineColor:()=>"#ffffff",paddingLeft:()=>0,paddingRight:()=>0,paddingTop:()=>0,paddingBottom:()=>0};let n=new Set(e.stripes);return{fillColor:t=>n.has(t)?e.zebraColor:null,hLineWidth:()=>.5,vLineWidth:()=>.5,hLineColor:()=>e.lineColor,vLineColor:()=>e.lineColor,paddingTop:()=>3,paddingBottom:()=>3,paddingLeft:()=>5,paddingRight:()=>5}}function lt(e,n={},t,o=new Date){let r=e.columns.map((u,c)=>({header:u.header,align:Xe(u,e,c)})),i=[],s=[];for(let u of e.rows){let c=e.columns.map((m,d)=>{if(u.blank[d])return"";let l=u.kind==="group"&&d===u.labelAt?u.label??"":Ve(m,u.values[d],u.original);return d===0&&u.depth>0?`${" ".repeat(u.depth)}${l}`:l});u.kind==="group"||u.hasChildren||u.style?.bold?i.push({kind:"subtotal",cells:c}):(i.push({kind:"data",cells:c}),s.push(m=>{let d=e.columns[m].cellStyle?.(u.values[m],u.original);return d||u.style?{...u.style,...d}:void 0}))}return{columns:r,body:i,dataCellStyle:(u,c)=>s[u]?.(c),opts:{grandTotal:!1,...n},footerLines:t?.map(u=>({text:u,style:{color:"#64748b",fontSize:7}})),now:o}}var it=null;function un(){return it??=(async()=>{let e=await import("pdfmake/build/pdfmake"),n=await import("pdfmake/build/vfs_fonts"),t=e.default??e;if(!nt(t,n))throw new Error('rowpine: "pdfmake/build/vfs_fonts" did not contain a font map; this pdfmake version is not supported.');return t})(),it}async function dn(e,n={}){let t=await oe("PDF export"),o=lt(Z(e,n),n,t?void 0:[be]),r=await un();n.font&&ot(r,n.font);let i=await tt(r.createPdf(rt(o)));return(n.download??!0)&&q(i,ne(n.fileName,"pdf")),i}function st(e,n=-1/0){let t=n;for(let o of e){if(o!==o)return NaN;o>t&&(t=o)}return t}function at(e,n=1/0){let t=n;for(let o of e){if(o!==o)return NaN;o<t&&(t=o)}return t}var ie=e=>e.map(n=>typeof n=="number"?n:Number(n)).filter(n=>!Number.isNaN(n)),ct={sum:e=>ie(e).reduce((n,t)=>n+t,0),avg:e=>{let n=ie(e);return n.length===0?null:n.reduce((t,o)=>t+o,0)/n.length},min:e=>ie(e).length===0?null:at(ie(e)),max:e=>ie(e).length===0?null:st(ie(e)),count:e=>e.length,countDistinct:e=>new Set(e).size,first:e=>e[0]??null,last:e=>e[e.length-1]??null};function ut(e){if(typeof e=="function")return e;let n=ct[e];if(!n)throw new Error(`pivot: unknown aggregator '${String(e)}'`);return n}function cn(e,n){return e===n?0:e==null?-1:n==null?1:typeof e=="number"&&typeof n=="number"?e-n:String(e).localeCompare(String(n),void 0,{numeric:!0})}function dt(e,n,t){let o={level:0,value:null,path:[],pathKey:"__root",children:new Map,rows:e};for(let r of e){let i=o;for(let s=0;s<n.length;s+=1){let u=n[s],c=r[u],m=`${s}:${c===null?"__null":String(c)}`,d=i.children.get(m);d||(d={level:s+1,value:c,path:[...i.path,c],pathKey:i.pathKey==="__root"?m:`${i.pathKey}|${m}`,children:new Map,rows:[]},i.children.set(m,d)),d.rows.push(r),i=d}}return ft(o,t),o}function ft(e,n){let t=Array.from(e.children.entries());t.sort(([,o],[,r])=>(n??cn)(o.value,r.value,o.level-1)),e.children=new Map(t);for(let o of e.children.values())ft(o,n)}function le(e,n){return`pv__${e.length===0?"all":e.map(o=>fn(o)).join("__")}__m${n}`}function fn(e){return e==null?"null":String(e).replace(/[^a-zA-Z0-9_-]/g,"_")}function pn(e,n,t,o){let r={id:"__pivotRowHeader",header:o,width:240,isRowHeader:!0};function i(d){return d.children.size===0?n.map((l,h)=>{let P=le(d.path,h),L=l.label??`${l.field} (${typeof l.agg=="function"?"custom":l.agg})`;return{id:P,header:L,measure:h,format:l.format,width:130}}):Array.from(d.children.values()).map(l=>({id:`pv_group_${l.pathKey}`,header:String(l.value??"(blank)"),columns:i(l)}))}let s=(e.children.size===0,i(e));if(!(t.grandTotalCol!==!1&&e.children.size>0))return[r,...s];let m={id:"pv_group__grand_total",header:"Total",columns:n.map((d,l)=>{let h=le(["__total"],l),P=d.label??`${d.field} (${typeof d.agg=="function"?"custom":d.agg})`;return{id:h,header:P,measure:l,format:d.format,width:140,isTotal:!0}}),isTotal:!0};return[r,...s,m]}function pt(e,n){if(e.children.size===0){n.push(e.path);return}for(let t of e.children.values())pt(t,n)}function xe(e,n,t,o,r){let i={},s=new Map,u=c=>{let m=s.get(c.length);if(!m){m=new Map;for(let l of e){let h=m;for(let N=0;N<c.length-1;N+=1){let x=l[t[N]],k=h.get(x);k||h.set(x,k=new Map),h=k}let P=l[t[c.length-1]],L=h.get(P);L?L.push(l):h.set(P,[l])}s.set(c.length,m)}let d=m;for(let l of c)if(d=d.get(l),d===void 0)return[];return d};for(let c of n){let m=c.length===0?e:u(c);for(let d=0;d<o.length;d+=1){let l=o[d],h=ut(l.agg),P=le(c,d);i[P]=m.length===0?null:h(m.map(L=>L[l.field]))}}if(r)for(let c=0;c<o.length;c+=1){let m=o[c],d=ut(m.agg),l=le(["__total"],c);i[l]=d(e.map(h=>h[m.field]))}return i}function mn(e,n,t){let o={};for(let r of e)for(let i=0;i<n.length;i+=1)o[le(r,i)]=null;if(t)for(let r=0;r<n.length;r+=1)o[le(["__total"],r)]=null;return o}function mt(e,n,t,o,r,i,s,u){let c=i.grandTotalCol!==!1,m=i.rowSubtotals!==!1;if(e.children.size===0){n.push({__pivotId:`row__${e.pathKey}`,__pivotKind:"leaf",__pivotDepth:e.level,__pivotLabel:e.value===null?"(All)":String(e.value),__pivotParentId:u,__pivotExpandable:!1,...xe(e.rows,t,o,r,c)});return}let d=e.level>0&&e.level<s.length,l=u;if(d){let h=`group__${e.pathKey}`;n.push({__pivotId:h,__pivotKind:"group",__pivotDepth:e.level,__pivotLabel:String(e.value??"(All)"),__pivotParentId:u,__pivotExpandable:!0,...m?xe(e.rows,t,o,r,c):mn(t,r,c)}),l=h}for(let h of e.children.values())mt(h,n,t,o,r,i,s,l)}function Oe(e,n){if(n.values.length===0)throw new Error("pivot: at least one value config is required");let t=n.rows,o=n.cols,r=n.values,i=dt(e,t,n.rowSort),s=dt(e,o,n.colSort),u=[];pt(s,u);let c=[];t.length===0?c.push({__pivotId:"row__all",__pivotKind:"leaf",__pivotDepth:0,__pivotLabel:"(All)",__pivotParentId:null,__pivotExpandable:!1,...xe(e,u,o,r,n.grandTotalCol!==!1)}):mt(i,c,u,o,r,n,t,null),n.grandTotalRow!==!1&&c.push({__pivotId:"row__grand_total",__pivotKind:"grandTotal",__pivotDepth:0,__pivotLabel:"Grand total",__pivotParentId:null,__pivotExpandable:!1,...xe(e,u,o,r,n.grandTotalCol!==!1)});let m=pn(s,r,n,t.length>0?t.join(" / "):"");return{rows:c,columns:m}}var gn={...ct};function hn(e,n){if(n===!0)return e.slice();let t=n instanceof Set?n:new Set(n),o=new Map;for(let i of e)o.set(i.__pivotId,i.__pivotParentId);let r=[];for(let i of e){if(i.__pivotParentId===null){r.push(i);continue}let s=i.__pivotParentId,u=!0;for(;s!==null;){if(!t.has(s)){u=!1;break}s=o.get(s)??null}u&&r.push(i)}return r}function gt(e){let n=new Map,t=[];for(let o of e){let r={...o};n.set(o.__pivotId,r);let i=o.__pivotParentId?n.get(o.__pivotParentId):void 0;i?(i.subRows??=[]).push(r):t.push(r)}return t}var yn=e=>e==null||e===""?"":typeof e=="number"?e.toLocaleString(void 0,{maximumFractionDigits:2}):String(e);function Ne(e,n={}){return e.map(t=>{let o={isRowHeader:!!t.isRowHeader,isTotal:!!t.isTotal,measure:t.measure};if(t.columns)return{id:t.id,header:t.header,columns:Ne(t.columns,n),meta:{rowpine:{},pivot:o}};if(t.isRowHeader)return{id:t.id,header:t.header,accessorFn:i=>i.__pivotLabel,size:t.width,meta:{rowpine:{header:t.header||" "},pivot:o}};let r=t.format??yn;return{id:t.id,header:t.header,accessorFn:i=>i[t.id],size:t.width,meta:{rowpine:{numFmt:n.numFmt,format:i=>r(i),align:"right"},pivot:o}}})}function bn(e){let n=e?.__pivotKind;return n==="group"||n==="subtotal"||n==="grandTotal"?{bold:!0}:void 0}function ht(e,n){let t=[],o=(d,l)=>{for(let h of d){let P={id:h.id,columnDef:h,parent:l,getSize:()=>"size"in h&&h.size||130};"columns"in h?o(h.columns,P):t.push(P)}};o(n);let r=new Map,i=d=>{for(let l of d)"columns"in l?i(l.columns):r.set(l.id,l.accessorFn)};i(n);let s=(d,l)=>({id:d.__pivotId,depth:l,original:d,subRows:(d.subRows??[]).map(h=>s(h,l+1)),getValue:h=>r.get(h)?.(d),getIsGrouped:()=>!1}),u={rows:e.map(d=>s(d,0))},c=[],m=d=>d.forEach(l=>(c.push(l),m(l.subRows)));return m(u.rows),{getAllLeafColumns:()=>t,getCoreRowModel:()=>u,getRowModel:()=>({rows:c}),getState:()=>({grouping:[]})}}function Ee(e,n,t={}){oe("Pivot");let o=Oe(e,n),r=gt(o.rows),i=Ne(o.columns,t);return{data:r,columns:i,rows:o.rows,table:ht(r,i)}}var wn={rows:"Rows",cols:"Columns",values:"Values",filters:"Filters"},xn={dimension:["rows","cols","filters"],measure:["values","filters"]},vn=["sum","avg","min","max","count","countDistinct","first","last"],yt={sum:"Sum",avg:"Average",min:"Min",max:"Max",count:"Count",countDistinct:"Distinct count",first:"First",last:"Last"},Pn={rows:[],cols:[],values:[],filters:[]},ce=(e,n)=>e.find(t=>t.field===n),ve=(e,n,t)=>`${yt[t]} of ${ce(e,n)?.label??n}`;function kn(e){let n=e.find(r=>r.kind==="dimension"),t=e.find(r=>r.kind==="measure"),o=t?.defaultAgg??"sum";return{rows:n?[n.field]:[],cols:[],values:t?[{field:t.field,agg:o,label:ve(e,t.field,o)}]:[],filters:[]}}function bt(e){return e.kind==="measure"?"values":"rows"}function wt(e,n){return e.rows.includes(n)||e.cols.includes(n)||e.values.some(t=>t.field===n)||e.filters.some(t=>t.field===n)}function xt(e,n,t,o,r=1/0,i){let s=ce(n,t);if(!s)return e;let u={...e,rows:e.rows.filter(c=>c!==t),cols:e.cols.filter(c=>c!==t),values:i!==void 0?e.values.filter((c,m)=>m!==i):e.values.slice(),filters:e.filters.filter(c=>c.field!==t)};if(o==="rows"||o==="cols")u[o].splice(Math.min(u[o].length,r),0,t);else if(o==="values"){let m=(i!==void 0?e.values[i]:void 0)?.agg??s.defaultAgg??(s.kind==="measure"?"sum":"count");if(u.values.some(d=>d.field===t&&d.agg===m))return u;u.values.splice(Math.min(u.values.length,r),0,{field:t,agg:m,label:ve(n,t,m)})}else u.filters.splice(Math.min(u.filters.length,r),0,{field:t,allowed:null});return u}function Cn(e,n,t,o){return t==="rows"?{...e,rows:e.rows.filter(r=>r!==n)}:t==="cols"?{...e,cols:e.cols.filter(r=>r!==n)}:t==="filters"?{...e,filters:e.filters.filter(r=>r.field!==n)}:{...e,values:o!==void 0?e.values.filter((r,i)=>i!==o):e.values.filter(r=>r.field!==n)}}function Rn(e,n,t){let o=ce(n,t);return o?wt(e,t)?{...e,rows:e.rows.filter(r=>r!==t),cols:e.cols.filter(r=>r!==t),values:e.values.filter(r=>r.field!==t),filters:e.filters.filter(r=>r.field!==t)}:xt(e,n,t,bt(o)):e}function Sn(e,n,t,o){let r=e.values[t];if(!r)return e;let i=e.values.some((s,u)=>u!==t&&s.field===r.field&&s.agg===o);return{...e,values:i?e.values.filter((s,u)=>u!==t):e.values.map((s,u)=>u===t?{...s,agg:o,label:ve(n,r.field,o)}:s)}}function Tn(e,n,t){return{...e,filters:e.filters.map(o=>o.field===n?{...o,allowed:t}:o)}}function Ln(e,n){return{...e,[n]:[]}}function An(e,n){let t=new Set;for(let o of e)t.add(String(o[n]));return[...t].sort((o,r)=>o.localeCompare(r,void 0,{numeric:!0}))}function vt(e,n){let t=n.filters.filter(r=>r.allowed!==null);if(t.length===0)return e.slice();let o=t.map(r=>({field:r.field,allowed:new Set(r.allowed)}));return e.filter(r=>o.every(i=>i.allowed.has(String(r[i.field]))))}function Pt(e,n){return{rows:e.rows,cols:e.cols,values:e.values.map(t=>({field:t.field,agg:t.agg,label:t.label??ve(n,t.field,t.agg),format:t.agg==="count"||t.agg==="countDistinct"?void 0:ce(n,t.field)?.format})),grandTotalRow:!e.hideGrandTotals,grandTotalCol:!e.hideGrandTotals,rowSubtotals:!e.hideSubtotals}}function $n(e,n,t,o){if(n.values.length===0)return null;let r=Ee(vt(e,n),Pt(n,t),o),i=n.rows.map(u=>ce(t,u)?.label??u).join(" / "),s=r.columns[0];return s&&s.meta.pivot.isRowHeader&&(s.header=i,s.meta.rowpine.header=i||" "),r}export{yt as AGG_LABEL,vn as ALL_AGGREGATORS,Pn as EMPTY_LAYOUT,xn as KIND_WELLS,wn as WELL_LABEL,xt as addToWell,vt as applyLayoutFilters,$e as buildXlsxParts,Ln as clearWell,Ee as createPivot,Oe as createPivotModel,kn as defaultLayoutFor,bt as defaultWellFor,An as distinctValues,Ot as exportCsv,dn as exportPdf,en as exportXlsx,hn as filterCollapsedPivotRows,He as getLicenseStatus,wt as isFieldInLayout,Pt as layoutToConfig,gn as pivotAggregators,ht as pivotAsTable,$n as pivotFromLayout,bn as pivotRowStyle,gt as pivotToTree,Z as readTable,Cn as removeFromWell,q as saveBlob,Sn as setAggregatorAt,Tn as setFilterAllowed,zt as setLicenseKey,lt as sheetToPdfParams,Ze as sheetToXlsxModel,je as tableToCsv,Je as tableToXlsxModel,Ne as toTanStackColumns,Rn as toggleField};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const UNLICENSED_NOTE = "Exported with an unlicensed copy of Rowpine. https://rowpine.com/";
|
|
2
|
+
export type LicensePayload = {
|
|
3
|
+
v: 1;
|
|
4
|
+
/** Licensee name or company. */
|
|
5
|
+
sub: string;
|
|
6
|
+
plan: 'developer' | 'team';
|
|
7
|
+
seats: number;
|
|
8
|
+
/** Last day of the update period, YYYY-MM-DD. */
|
|
9
|
+
upd: string;
|
|
10
|
+
/** Issue date, YYYY-MM-DD. */
|
|
11
|
+
iat: string;
|
|
12
|
+
/** Order or license id. */
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
export type LicenseStatus = {
|
|
16
|
+
licensed: true;
|
|
17
|
+
payload: LicensePayload;
|
|
18
|
+
buildDate: string;
|
|
19
|
+
} | {
|
|
20
|
+
licensed: false;
|
|
21
|
+
reason: 'missing' | 'malformed' | 'invalid' | 'updates-ended';
|
|
22
|
+
payload?: LicensePayload;
|
|
23
|
+
buildDate: string;
|
|
24
|
+
};
|
|
25
|
+
/** Set the license key. Call once, before the first export or pivot. */
|
|
26
|
+
export declare function setLicenseKey(value: string | null | undefined): void;
|
|
27
|
+
/** Check a key against a public key and a build date. Exported for tests. */
|
|
28
|
+
export declare function verifyLicenseKey(value: string | null, publicKeyHex: string, buildDate: string): Promise<LicenseStatus>;
|
|
29
|
+
/** The current license status. */
|
|
30
|
+
export declare function getLicenseStatus(): Promise<LicenseStatus>;
|
|
31
|
+
/** True when licensed. Otherwise logs the notice once and returns false. */
|
|
32
|
+
export declare function checkLicense(feature: string): Promise<boolean>;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF document builder on pdfmake: page size / orientation / margins, a
|
|
3
|
+
* repeated header row on every page, "Page X of Y" + date footer, per-column
|
|
4
|
+
* alignment and widths, group and subtotal rows, an optional title /
|
|
5
|
+
* subtitle / logo, and header and zebra colors.
|
|
6
|
+
*
|
|
7
|
+
* `buildPdfDocDefinition` is a pure function (no pdfmake, no DOM) returning
|
|
8
|
+
* the docDefinition object; `exportPdf` (./index) feeds it to pdfmake.
|
|
9
|
+
*/
|
|
10
|
+
export type PdfPageSize = 'A4' | 'A3' | 'A5' | 'LETTER' | 'LEGAL';
|
|
11
|
+
/** The font files pdfmake reads, by name, as base64. */
|
|
12
|
+
export type PdfVirtualFileSystem = Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* The font map inside whatever `import('pdfmake/build/vfs_fonts')` returned.
|
|
15
|
+
*
|
|
16
|
+
* The file has changed shape across pdfmake releases and bundlers wrap it
|
|
17
|
+
* once more: `{ pdfMake: { vfs } }` (0.1.x), `{ vfs }`, either of those under
|
|
18
|
+
* `default`, and since 0.2.8 the module IS the map (`module.exports = vfs`,
|
|
19
|
+
* so an ESM interop hands it over as `default` with the `.ttf` names as
|
|
20
|
+
* keys). Only the last shape ships today, and it is the one the old lookup
|
|
21
|
+
* did not recognise, which left registration to a side effect of import
|
|
22
|
+
* order (see {@link registerPdfFonts}). Returns `null` when nothing in the
|
|
23
|
+
* module looks like a font map.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolvePdfVfs(mod: unknown): PdfVirtualFileSystem | null;
|
|
26
|
+
/** The parts of pdfmake's browser API that font registration touches. */
|
|
27
|
+
export type PdfMakeLike = {
|
|
28
|
+
vfs?: PdfVirtualFileSystem;
|
|
29
|
+
addVirtualFileSystem?: (vfs: PdfVirtualFileSystem) => void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Hand the fonts to the pdfmake instance that will create the document,
|
|
33
|
+
* explicitly. `vfs_fonts` registers itself only as a side effect: at import
|
|
34
|
+
* time it looks for a global `pdfMake` with `addVirtualFileSystem` and calls
|
|
35
|
+
* it. Under a bundler that global is whichever instance happened to load
|
|
36
|
+
* first, and when a dev server re-optimises its dependencies mid-session
|
|
37
|
+
* that is a stale one, so the instance doing the export has no fonts and
|
|
38
|
+
* `createPdf` fails with "File 'Roboto-Medium.ttf' not found in virtual
|
|
39
|
+
* file system". Registering here, on the instance in hand, removes the
|
|
40
|
+
* dependence on import order: `addVirtualFileSystem` where the version has
|
|
41
|
+
* it (0.2.x, where `createPdf` reads that before the `vfs` property) and the
|
|
42
|
+
* `vfs` property as well, which older versions read. Returns whether a map
|
|
43
|
+
* was found to register.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* The PDF as a Blob from either pdfmake line. 0.2 hands it to a callback; 0.3
|
|
47
|
+
* made `getBlob` async and never calls one, so waiting on the callback alone
|
|
48
|
+
* hung every export once pnpm or yarn installed 0.3 for `pnpm add pdfmake`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function pdfBlob(doc: {
|
|
51
|
+
getBlob(cb?: (b: Blob) => void): unknown;
|
|
52
|
+
}): Promise<Blob>;
|
|
53
|
+
export declare function registerPdfFonts(pdfMake: PdfMakeLike, vfsModule: unknown): boolean;
|
|
54
|
+
export type PdfExportOptions = {
|
|
55
|
+
pageSize?: PdfPageSize;
|
|
56
|
+
pageOrientation?: 'portrait' | 'landscape';
|
|
57
|
+
/** [left, top, right, bottom] in pt. Default [28, 34, 28, 34]. */
|
|
58
|
+
margins?: [number, number, number, number];
|
|
59
|
+
/** Document title rendered above the table. */
|
|
60
|
+
title?: string;
|
|
61
|
+
/** Smaller line under the title. */
|
|
62
|
+
subtitle?: string;
|
|
63
|
+
/** Logo as a data URL, rendered top-left above the title. */
|
|
64
|
+
logo?: string;
|
|
65
|
+
/** Logo width in pt (height auto). Default 90. */
|
|
66
|
+
logoWidth?: number;
|
|
67
|
+
/** Base font size in pt. Default 8. */
|
|
68
|
+
fontSize?: number;
|
|
69
|
+
/** Header row fill color. Default '#334155'. */
|
|
70
|
+
headerColor?: string;
|
|
71
|
+
/** Header row text color. Default '#ffffff'. */
|
|
72
|
+
headerTextColor?: string;
|
|
73
|
+
/** Zebra-stripe alternate rows. Default true. */
|
|
74
|
+
zebra?: boolean;
|
|
75
|
+
/** Zebra fill color. Default '#f1f5f9'. */
|
|
76
|
+
zebraColor?: string;
|
|
77
|
+
/** Repeat the header row on every page. Default true. */
|
|
78
|
+
repeatHeader?: boolean;
|
|
79
|
+
/** Show the "Page X of Y" + date footer. Default true. */
|
|
80
|
+
showPageNumbers?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Column widths: '*' (fill, default), 'auto', or an explicit array of
|
|
83
|
+
* pt numbers / 'auto' / '*' per column.
|
|
84
|
+
*/
|
|
85
|
+
columnWidths?: '*' | 'auto' | Array<number | 'auto' | '*'>;
|
|
86
|
+
/** Switch to landscape automatically when column count exceeds this (and no
|
|
87
|
+
* explicit orientation was set). Default 8. */
|
|
88
|
+
autoLandscapeThreshold?: number;
|
|
89
|
+
/** Group header row fill color. Default '#e2e8f0'. */
|
|
90
|
+
groupColor?: string;
|
|
91
|
+
/** Group header row text color. Default '#0f172a'. */
|
|
92
|
+
groupTextColor?: string;
|
|
93
|
+
/** Subtotal row fill color. Default '#f8fafc'. */
|
|
94
|
+
subtotalColor?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Charts to print with the table, as image data URLs, each with an
|
|
97
|
+
* optional title and caption. Printed at the content width unless `width`
|
|
98
|
+
* (pt) says otherwise.
|
|
99
|
+
*/
|
|
100
|
+
charts?: ReadonlyArray<PdfChartImage>;
|
|
101
|
+
/** Where the charts go: above the table (default) or below it. */
|
|
102
|
+
chartsPosition?: 'above' | 'below';
|
|
103
|
+
/**
|
|
104
|
+
* A strip of headline numbers above the table: a label, the value, and an
|
|
105
|
+
* optional delta line in its own colour ("+4.2%" in green).
|
|
106
|
+
*/
|
|
107
|
+
kpis?: ReadonlyArray<PdfKpi>;
|
|
108
|
+
/** KPI box fill. Default '#f8fafc'. */
|
|
109
|
+
kpiColor?: string;
|
|
110
|
+
/** Body text colour. Default black. */
|
|
111
|
+
textColor?: string;
|
|
112
|
+
/** Table line colour. Default '#e2e8f0'. */
|
|
113
|
+
borderColor?: string;
|
|
114
|
+
/**
|
|
115
|
+
* What to do when the columns do not fit across the page.
|
|
116
|
+
* - `'auto'` (default): shrink the table text, down to three quarters of
|
|
117
|
+
* `fontSize`; past that, split the columns over several pages.
|
|
118
|
+
* - `'shrink'`: only shrink, down to 4 pt.
|
|
119
|
+
* - `'split'`: only split.
|
|
120
|
+
* - `'none'`: leave it to pdfmake, which lets a wide table run off the page.
|
|
121
|
+
*/
|
|
122
|
+
fit?: 'auto' | 'shrink' | 'split' | 'none';
|
|
123
|
+
/** When the columns are split over pages, how many leading columns repeat on every page. Default 1. */
|
|
124
|
+
repeatColumns?: number;
|
|
125
|
+
/** A grouped export ends with a total row over every row. Default true. */
|
|
126
|
+
grandTotal?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* The words the PDF prints itself, for another language or house style:
|
|
129
|
+
* the page footer, the footer date, a group's subtotal label, and the total
|
|
130
|
+
* row's label.
|
|
131
|
+
*/
|
|
132
|
+
labels?: {
|
|
133
|
+
page?: (page: number, pages: number) => string;
|
|
134
|
+
date?: (now: Date) => string;
|
|
135
|
+
subtotal?: (group: string) => string;
|
|
136
|
+
total?: string;
|
|
137
|
+
};
|
|
138
|
+
/** Text printed large and faint across every page: "DRAFT", "CONFIDENTIAL". */
|
|
139
|
+
watermark?: string | {
|
|
140
|
+
text: string;
|
|
141
|
+
color?: string;
|
|
142
|
+
opacity?: number;
|
|
143
|
+
};
|
|
144
|
+
/** Author recorded in the PDF's document properties. */
|
|
145
|
+
author?: string;
|
|
146
|
+
/**
|
|
147
|
+
* A font to print with instead of Roboto, which has no Chinese, Japanese,
|
|
148
|
+
* Korean, Arabic, Hebrew, Thai or Indic characters (they print as empty
|
|
149
|
+
* boxes). Each file is the font's base64 data or a URL pdfmake can fetch.
|
|
150
|
+
* It must cover Latin too: the whole document uses it.
|
|
151
|
+
*/
|
|
152
|
+
font?: PdfFont;
|
|
153
|
+
};
|
|
154
|
+
/** A TrueType / OpenType font for the PDF. Missing styles fall back to `normal`. */
|
|
155
|
+
export type PdfFont = {
|
|
156
|
+
name: string;
|
|
157
|
+
normal: string;
|
|
158
|
+
bold?: string;
|
|
159
|
+
italics?: string;
|
|
160
|
+
bolditalics?: string;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Register `font` with pdfmake next to Roboto. Base64 files go into the
|
|
164
|
+
* virtual file system, merged with what is there (`addVirtualFileSystem`
|
|
165
|
+
* replaces the map rather than adding to it); URLs are passed through for
|
|
166
|
+
* pdfmake to fetch.
|
|
167
|
+
*/
|
|
168
|
+
export declare function registerPdfFont(pdfMake: PdfMakeLike & {
|
|
169
|
+
fonts?: Record<string, unknown>;
|
|
170
|
+
addFonts?: (fonts: Record<string, unknown>) => void;
|
|
171
|
+
}, font: PdfFont): void;
|
|
172
|
+
/**
|
|
173
|
+
* Text Roboto has no glyphs for: CJK, Hangul, Arabic, Hebrew, Thai, the Indic
|
|
174
|
+
* scripts, dingbats (check marks, stars) and emoji. Exported so an export can
|
|
175
|
+
* warn that it needs `pdf.font`.
|
|
176
|
+
*/
|
|
177
|
+
export declare const PDF_ROBOTO_MISSING: RegExp;
|
|
178
|
+
/** One cell's look, the same keys `ExportCellStyle` has. */
|
|
179
|
+
export type PdfCellStyle = {
|
|
180
|
+
color?: string;
|
|
181
|
+
backgroundColor?: string;
|
|
182
|
+
fontWeight?: 'normal' | 'bold' | number;
|
|
183
|
+
fontStyle?: 'normal' | 'italic';
|
|
184
|
+
fontSize?: number | string;
|
|
185
|
+
textAlign?: 'left' | 'right' | 'center';
|
|
186
|
+
};
|
|
187
|
+
/** Blanket styles for the PDF table: what `ExportOptions.styles` carries. */
|
|
188
|
+
export type PdfStyles = {
|
|
189
|
+
headerRow?: PdfCellStyle;
|
|
190
|
+
rows?: PdfCellStyle;
|
|
191
|
+
rowAlternate?: PdfCellStyle;
|
|
192
|
+
/** Per-cell overrides by Excel-style reference: `A1` is the first header cell, `A2` the first data cell. */
|
|
193
|
+
cells?: Record<string, PdfCellStyle>;
|
|
194
|
+
};
|
|
195
|
+
/** A line above or below the table: what `ExportOptions.header` / `footer` carry. */
|
|
196
|
+
export type PdfPageLine = {
|
|
197
|
+
text: string;
|
|
198
|
+
style?: PdfCellStyle;
|
|
199
|
+
} | {
|
|
200
|
+
image: string;
|
|
201
|
+
width?: number;
|
|
202
|
+
height?: number;
|
|
203
|
+
} | {
|
|
204
|
+
left?: string;
|
|
205
|
+
center?: string;
|
|
206
|
+
right?: string;
|
|
207
|
+
};
|
|
208
|
+
/** A merged block of data cells, zero-based within the data rows. */
|
|
209
|
+
export type PdfMerge = {
|
|
210
|
+
row: number;
|
|
211
|
+
col: number;
|
|
212
|
+
rowSpan?: number;
|
|
213
|
+
colSpan?: number;
|
|
214
|
+
};
|
|
215
|
+
/** A color theme for the PDF table; explicit options and `styles` win over it. */
|
|
216
|
+
export type PdfTheme = Partial<Pick<PdfExportOptions, 'headerColor' | 'headerTextColor' | 'textColor' | 'zebraColor' | 'borderColor' | 'groupColor' | 'groupTextColor' | 'subtotalColor'>>;
|
|
217
|
+
/** `rgb(...)` / `rgba(...)` / `#hex` -> `#rrggbb`, or null for transparent and anything else. */
|
|
218
|
+
export declare function cssColorToHex(value: string | null | undefined): string | null;
|
|
219
|
+
/** Relative luminance of a `#rrggbb`, 0 (black) to 1 (white). */
|
|
220
|
+
export declare function hexLuminance(hex: string): number;
|
|
221
|
+
/** One box of the KPI strip. */
|
|
222
|
+
export type PdfKpi = {
|
|
223
|
+
label: string;
|
|
224
|
+
value: string;
|
|
225
|
+
/** A second line under the value: a change, a target, a period. */
|
|
226
|
+
delta?: string;
|
|
227
|
+
/** Colour of the delta line. Default '#64748b'. */
|
|
228
|
+
color?: string;
|
|
229
|
+
};
|
|
230
|
+
/** A chart whose image is ready: what `buildPdfDocDefinition` consumes. */
|
|
231
|
+
export type PdfChartImage = {
|
|
232
|
+
image: string;
|
|
233
|
+
title?: string;
|
|
234
|
+
caption?: string;
|
|
235
|
+
width?: number;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* A body row for the PDF table. `data` is a normal row; `group` is a bold,
|
|
239
|
+
* full-width group header (from grid grouping); `subtotal` is a per-group
|
|
240
|
+
* summary row (bold, aligned like the data).
|
|
241
|
+
*/
|
|
242
|
+
export type PdfBodyRow = {
|
|
243
|
+
kind: 'data';
|
|
244
|
+
cells: ReadonlyArray<string>; /** The row's index in the exported rows. */
|
|
245
|
+
source?: number;
|
|
246
|
+
} | {
|
|
247
|
+
kind: 'group';
|
|
248
|
+
label: string;
|
|
249
|
+
level?: number;
|
|
250
|
+
} | {
|
|
251
|
+
kind: 'subtotal';
|
|
252
|
+
cells: ReadonlyArray<string>;
|
|
253
|
+
};
|
|
254
|
+
/** A table's look, as data; {@link hydratePdfDoc} turns it into pdfmake's layout callbacks. */
|
|
255
|
+
export type PdfLayoutSpec = {
|
|
256
|
+
kind: 'table';
|
|
257
|
+
stripes: number[];
|
|
258
|
+
zebraColor: string;
|
|
259
|
+
lineColor: string;
|
|
260
|
+
} | {
|
|
261
|
+
kind: 'kpi';
|
|
262
|
+
};
|
|
263
|
+
/** The document as plain data, safe to post to a worker. */
|
|
264
|
+
export type PdfDocData = {
|
|
265
|
+
pageSize: PdfPageSize;
|
|
266
|
+
pageOrientation: 'portrait' | 'landscape';
|
|
267
|
+
pageMargins: [number, number, number, number];
|
|
268
|
+
defaultStyle: {
|
|
269
|
+
fontSize: number;
|
|
270
|
+
font?: string;
|
|
271
|
+
};
|
|
272
|
+
content: unknown[];
|
|
273
|
+
watermark?: {
|
|
274
|
+
text: string;
|
|
275
|
+
color: string;
|
|
276
|
+
opacity: number;
|
|
277
|
+
bold: boolean;
|
|
278
|
+
};
|
|
279
|
+
/** The PDF's document properties, which a viewer shows in place of the file name. */
|
|
280
|
+
info?: {
|
|
281
|
+
title?: string;
|
|
282
|
+
author?: string;
|
|
283
|
+
subject?: string;
|
|
284
|
+
creator: string;
|
|
285
|
+
creationDate: Date;
|
|
286
|
+
};
|
|
287
|
+
/** The footer, as data: margins, the date text, and a page template with `{page}` / `{pages}`. */
|
|
288
|
+
rpFooter?: {
|
|
289
|
+
margin: number[];
|
|
290
|
+
date: string;
|
|
291
|
+
page: string;
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
/** The document pdfmake renders: the data with its layouts and footer as functions. */
|
|
295
|
+
export type PdfDocDefinition = PdfDocData & {
|
|
296
|
+
footer?: (currentPage: number, pageCount: number) => unknown;
|
|
297
|
+
};
|
|
298
|
+
/** What the PDF table is built from. `now` is injected for a deterministic footer date in tests. */
|
|
299
|
+
export type PdfDocParams = {
|
|
300
|
+
columns: ReadonlyArray<{
|
|
301
|
+
header: string;
|
|
302
|
+
align?: 'left' | 'center' | 'right';
|
|
303
|
+
}>;
|
|
304
|
+
/** Data rows as pre-formatted strings, in column order (no header row).
|
|
305
|
+
* Convenience for the flat (ungrouped) case. */
|
|
306
|
+
rows?: ReadonlyArray<ReadonlyArray<string>>;
|
|
307
|
+
/** Structured body with group headers + subtotals. Takes precedence over
|
|
308
|
+
* `rows` when provided (used for grouped exports). */
|
|
309
|
+
body?: ReadonlyArray<PdfBodyRow>;
|
|
310
|
+
/** Per-data-cell visual override (conditional formatting). `dataRowIdx` is
|
|
311
|
+
* the 0-based index among data rows. */
|
|
312
|
+
dataCellStyle?: (dataRowIdx: number, colIdx: number) => {
|
|
313
|
+
fill?: string;
|
|
314
|
+
color?: string;
|
|
315
|
+
bold?: boolean;
|
|
316
|
+
icon?: string;
|
|
317
|
+
iconOnly?: boolean;
|
|
318
|
+
} | undefined;
|
|
319
|
+
/** Per-data-cell hyperlink URL. */
|
|
320
|
+
dataCellLink?: (dataRowIdx: number, colIdx: number) => string | undefined;
|
|
321
|
+
opts?: PdfExportOptions;
|
|
322
|
+
/** Chart images; take precedence over `opts.charts`. */
|
|
323
|
+
charts?: ReadonlyArray<PdfChartImage>;
|
|
324
|
+
/** Blanket header / row / zebra / per-cell styles. */
|
|
325
|
+
styles?: PdfStyles;
|
|
326
|
+
/** A color theme; `opts` and `styles` win over it. */
|
|
327
|
+
theme?: PdfTheme;
|
|
328
|
+
/** Lines printed above the table (`ExportOptions.header`). */
|
|
329
|
+
headerLines?: ReadonlyArray<PdfPageLine>;
|
|
330
|
+
/** Lines printed below the table (`ExportOptions.footer`). */
|
|
331
|
+
footerLines?: ReadonlyArray<PdfPageLine>;
|
|
332
|
+
/** Merged data cells; ignored with a structured `body`, whose rows own the layout. */
|
|
333
|
+
merges?: ReadonlyArray<PdfMerge>;
|
|
334
|
+
now?: Date;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Build the pdfmake document from resolved columns + formatted string rows,
|
|
338
|
+
* as plain data: no functions, so it can be posted to a worker. Hand it to
|
|
339
|
+
* {@link hydratePdfDoc} before pdfmake sees it, or use
|
|
340
|
+
* {@link buildPdfDocDefinition}, which does both.
|
|
341
|
+
*/
|
|
342
|
+
export declare function buildPdfDocData(params: PdfDocParams): PdfDocData;
|
|
343
|
+
/**
|
|
344
|
+
* Build the pdfmake document for a table and hand it over ready to render.
|
|
345
|
+
* Same as {@link buildPdfDocData}, with its layouts and footer turned into
|
|
346
|
+
* the functions pdfmake calls.
|
|
347
|
+
*/
|
|
348
|
+
export declare function buildPdfDocDefinition(params: PdfDocParams): PdfDocDefinition;
|
|
349
|
+
/**
|
|
350
|
+
* Give a document from {@link buildPdfDocData} the functions pdfmake calls:
|
|
351
|
+
* each table's fill and line callbacks and the page footer. Runs where the
|
|
352
|
+
* PDF is rendered, which may be a worker the functions could not be sent to.
|
|
353
|
+
* `page` overrides the footer's page template with the caller's own function.
|
|
354
|
+
*/
|
|
355
|
+
export declare function hydratePdfDoc(doc: PdfDocData, opts?: {
|
|
356
|
+
page?: (page: number, pages: number) => string;
|
|
357
|
+
}): PdfDocDefinition;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReadOptions, type Sheet, type TableLike } from '../table-source';
|
|
2
|
+
import { type PdfDocParams, type PdfExportOptions } from './doc';
|
|
3
|
+
export type PdfTableExportOptions = ReadOptions & PdfExportOptions & {
|
|
4
|
+
fileName?: string;
|
|
5
|
+
/** Save the file in the browser. Default true. */
|
|
6
|
+
download?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/** The pdfmake document parameters for a sheet. Pure. */
|
|
9
|
+
export declare function sheetToPdfParams(sheet: Sheet, opts?: PdfTableExportOptions, notes?: string[], now?: Date): PdfDocParams;
|
|
10
|
+
/** Export the table to a PDF file. Returns the file as a Blob. */
|
|
11
|
+
export declare function exportPdf(table: TableLike, opts?: PdfTableExportOptions): Promise<Blob>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pivot designer state: the four wells (rows, columns, values, filters), the
|
|
3
|
+
* rules for moving fields between them, filters, totals switches and presets.
|
|
4
|
+
*
|
|
5
|
+
* Every operation is a pure function from one layout to the next, so any UI
|
|
6
|
+
* (the shadcn `PivotDesigner`, a custom one, a saved-view store) can drive it
|
|
7
|
+
* and persist the layout as plain JSON.
|
|
8
|
+
*/
|
|
9
|
+
import { type PivotTable, type ToColumnsOptions } from './tanstack';
|
|
10
|
+
import type { PivotAggregatorId, PivotConfig } from './pivot';
|
|
11
|
+
/** One field the designer offers. Dimensions land in Rows by default; measures in Values. */
|
|
12
|
+
export type PivotField<T = Record<string, unknown>> = {
|
|
13
|
+
field: keyof T & string;
|
|
14
|
+
/** Name shown in the field list and on chips. */
|
|
15
|
+
label: string;
|
|
16
|
+
kind: 'dimension' | 'measure';
|
|
17
|
+
/** Optional group heading in the field list, e.g. 'Customer'. */
|
|
18
|
+
group?: string;
|
|
19
|
+
/** Display text for this field's values when it is a measure. */
|
|
20
|
+
format?: (value: unknown) => string;
|
|
21
|
+
/** Aggregation used when the field is added to Values. Default 'sum' for measures. */
|
|
22
|
+
defaultAgg?: PivotAggregatorId;
|
|
23
|
+
};
|
|
24
|
+
/** A chip in the Values well. */
|
|
25
|
+
export type PivotValueChip = {
|
|
26
|
+
field: string;
|
|
27
|
+
agg: PivotAggregatorId;
|
|
28
|
+
label?: string;
|
|
29
|
+
};
|
|
30
|
+
/** A chip in the Filters well. `allowed: null` lets every value through. */
|
|
31
|
+
export type PivotFilterChip = {
|
|
32
|
+
field: string;
|
|
33
|
+
allowed: string[] | null;
|
|
34
|
+
};
|
|
35
|
+
/** The whole designer state, as plain JSON. */
|
|
36
|
+
export type PivotLayout = {
|
|
37
|
+
rows: string[];
|
|
38
|
+
cols: string[];
|
|
39
|
+
values: PivotValueChip[];
|
|
40
|
+
filters: PivotFilterChip[];
|
|
41
|
+
/** Hide subtotal rows between groups. */
|
|
42
|
+
hideSubtotals?: boolean;
|
|
43
|
+
/** Hide the grand-total row and column. */
|
|
44
|
+
hideGrandTotals?: boolean;
|
|
45
|
+
};
|
|
46
|
+
/** A saved layout offered in the designer's Presets menu. */
|
|
47
|
+
export type PivotPreset = {
|
|
48
|
+
name: string;
|
|
49
|
+
layout: PivotLayout;
|
|
50
|
+
};
|
|
51
|
+
export type Well = 'rows' | 'cols' | 'values' | 'filters';
|
|
52
|
+
export declare const WELL_LABEL: Record<Well, string>;
|
|
53
|
+
/** Which wells each kind of field may go to. */
|
|
54
|
+
export declare const KIND_WELLS: Record<PivotField['kind'], Well[]>;
|
|
55
|
+
export declare const ALL_AGGREGATORS: PivotAggregatorId[];
|
|
56
|
+
export declare const AGG_LABEL: Record<PivotAggregatorId, string>;
|
|
57
|
+
export declare const EMPTY_LAYOUT: PivotLayout;
|
|
58
|
+
/** First dimension in Rows, first measure in Values. */
|
|
59
|
+
export declare function defaultLayoutFor<T>(fields: ReadonlyArray<PivotField<T>>): PivotLayout;
|
|
60
|
+
/** The well a field goes to when added without one. */
|
|
61
|
+
export declare function defaultWellFor(field: Pick<PivotField, 'kind'>): Well;
|
|
62
|
+
export declare function isFieldInLayout(layout: PivotLayout, field: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Add a field to a well at `index` (default: the end). In Rows, Columns and
|
|
65
|
+
* Filters a field appears at most once, so adding it moves it. In Values the
|
|
66
|
+
* same field may appear with different aggregations (sum and average of one
|
|
67
|
+
* amount), but not twice with the same one. `sourceIndex` is the chip's
|
|
68
|
+
* position in Values when it is being moved out of that well.
|
|
69
|
+
*/
|
|
70
|
+
export declare function addToWell<T>(layout: PivotLayout, fields: ReadonlyArray<PivotField<T>>, field: string, well: Well, index?: number, sourceIndex?: number): PivotLayout;
|
|
71
|
+
/** Remove a field from a well. In Values, `valueIndex` picks the one chip. */
|
|
72
|
+
export declare function removeFromWell(layout: PivotLayout, field: string, well: Well, valueIndex?: number): PivotLayout;
|
|
73
|
+
/** Field-list checkbox: take the field out of every well, or add it to its default well. */
|
|
74
|
+
export declare function toggleField<T>(layout: PivotLayout, fields: ReadonlyArray<PivotField<T>>, field: string): PivotLayout;
|
|
75
|
+
/** Change a value chip's aggregation. A change that would duplicate another chip removes this one. */
|
|
76
|
+
export declare function setAggregatorAt<T>(layout: PivotLayout, fields: ReadonlyArray<PivotField<T>>, index: number, agg: PivotAggregatorId): PivotLayout;
|
|
77
|
+
/** Set which values of a filter field pass. `null` lets everything through. */
|
|
78
|
+
export declare function setFilterAllowed(layout: PivotLayout, field: string, allowed: string[] | null): PivotLayout;
|
|
79
|
+
export declare function clearWell(layout: PivotLayout, well: Well): PivotLayout;
|
|
80
|
+
/** Every distinct value of a field in the full data, as text, sorted. */
|
|
81
|
+
export declare function distinctValues<T>(data: ReadonlyArray<T>, field: string): string[];
|
|
82
|
+
/** The rows that pass the layout's filters. */
|
|
83
|
+
export declare function applyLayoutFilters<T>(data: ReadonlyArray<T>, layout: PivotLayout): T[];
|
|
84
|
+
/** The engine config for a layout. */
|
|
85
|
+
export declare function layoutToConfig<T>(layout: PivotLayout, fields: ReadonlyArray<PivotField<T>>): PivotConfig<T>;
|
|
86
|
+
/**
|
|
87
|
+
* Filter, pivot and label in one step. Returns null when the layout has no
|
|
88
|
+
* values yet, which a UI shows as an empty state.
|
|
89
|
+
*/
|
|
90
|
+
export declare function pivotFromLayout<T>(data: ReadonlyArray<T>, layout: PivotLayout, fields: ReadonlyArray<PivotField<T>>, opts?: ToColumnsOptions): PivotTable | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The smallest and largest of a list of any length. `Math.max(...values)` passes
|
|
3
|
+
* each value as an argument and throws a RangeError past about 120,000 of them.
|
|
4
|
+
* Like `Math.max`: an empty list gives the start value, and a NaN gives NaN.
|
|
5
|
+
*/
|
|
6
|
+
export declare function maxOf(values: Iterable<number>, start?: number): number;
|
|
7
|
+
export declare function minOf(values: Iterable<number>, start?: number): number;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** Built-in pivot measures, named in {@link PivotValueConfig}. */
|
|
2
|
+
export type PivotAggregatorId = 'sum' | 'avg' | 'min' | 'max' | 'count' | 'countDistinct' | 'first' | 'last';
|
|
3
|
+
/** A custom measure: reduce the values under one cell to the number to display. */
|
|
4
|
+
export type PivotAggregator = (values: ReadonlyArray<unknown>) => unknown;
|
|
5
|
+
/** One measure: the field to aggregate and how to aggregate it. */
|
|
6
|
+
export type PivotValueConfig<TData> = {
|
|
7
|
+
/** Row field whose values get aggregated. */
|
|
8
|
+
field: keyof TData & string;
|
|
9
|
+
/** Built-in aggregator id, or a custom reducer. */
|
|
10
|
+
agg: PivotAggregatorId | PivotAggregator;
|
|
11
|
+
/** Display name in the column header. Defaults to `${field} (${agg})`. */
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Turns a value cell into display text. Default: numbers with grouping separators. */
|
|
14
|
+
format?: (value: unknown) => string;
|
|
15
|
+
};
|
|
16
|
+
/** A pivot definition: the row axis, the column axis, and the measures at their intersections. */
|
|
17
|
+
export type PivotConfig<TData> = {
|
|
18
|
+
/** Outer-most first. Each entry becomes one level of row grouping. */
|
|
19
|
+
rows: ReadonlyArray<keyof TData & string>;
|
|
20
|
+
/** Outer-most first. Each entry becomes one level of column grouping. */
|
|
21
|
+
cols: ReadonlyArray<keyof TData & string>;
|
|
22
|
+
/** One or more measures aggregated under each column-axis leaf. */
|
|
23
|
+
values: ReadonlyArray<PivotValueConfig<TData>>;
|
|
24
|
+
/** Grand-total row at the bottom. Default `true`. */
|
|
25
|
+
grandTotalRow?: boolean;
|
|
26
|
+
/** Grand-total column on the right. Default `true`. */
|
|
27
|
+
grandTotalCol?: boolean;
|
|
28
|
+
/** Subtotal rows between row groups. Default `true`. */
|
|
29
|
+
rowSubtotals?: boolean;
|
|
30
|
+
/** Optional sort for column-axis values per dim level (defaults to alpha). */
|
|
31
|
+
colSort?: (a: unknown, b: unknown, level: number) => number;
|
|
32
|
+
/** Optional sort for row-axis values per dim level (defaults to alpha). */
|
|
33
|
+
rowSort?: (a: unknown, b: unknown, level: number) => number;
|
|
34
|
+
};
|
|
35
|
+
/** What a pivot row represents - a group header, a subtotal, a leaf, or the grand total. */
|
|
36
|
+
export type PivotRowKind = 'group' | 'subtotal' | 'leaf' | 'grandTotal';
|
|
37
|
+
/**
|
|
38
|
+
* Shape of one entry in `result.rows`. The first-column label lives at
|
|
39
|
+
* `__pivotLabel`; every value cell lives at the column id matching the
|
|
40
|
+
* leaf column generated for its (col-path × measure).
|
|
41
|
+
*
|
|
42
|
+
* `__pivotParentId` is the `__pivotId` of the row's row-axis parent
|
|
43
|
+
* (or `null` for top-level groups, the grand-total row, and the one
|
|
44
|
+
* synthetic "All" row when no row dims are configured). Use it with
|
|
45
|
+
* `filterCollapsedPivotRows` to hide leaves whose ancestor is
|
|
46
|
+
* collapsed - that's how expandable pivots are built on top of the
|
|
47
|
+
* model.
|
|
48
|
+
*
|
|
49
|
+
* `__pivotExpandable` is `true` for `group` rows that have at least
|
|
50
|
+
* one descendant. A renderer uses this to decide whether to show a
|
|
51
|
+
* chevron next to the label.
|
|
52
|
+
*/
|
|
53
|
+
export type PivotRow = {
|
|
54
|
+
__pivotId: string;
|
|
55
|
+
__pivotKind: PivotRowKind;
|
|
56
|
+
__pivotDepth: number;
|
|
57
|
+
__pivotLabel: string;
|
|
58
|
+
__pivotParentId: string | null;
|
|
59
|
+
__pivotExpandable: boolean;
|
|
60
|
+
/** Aggregated value cells - keyed by leaf column id. */
|
|
61
|
+
[columnId: string]: unknown;
|
|
62
|
+
};
|
|
63
|
+
/** One generated column. A leaf has a `measure`; a header group has `columns`. */
|
|
64
|
+
export type PivotColumn = {
|
|
65
|
+
id: string;
|
|
66
|
+
header: string;
|
|
67
|
+
/** Child columns, for a column-axis header group. */
|
|
68
|
+
columns?: PivotColumn[];
|
|
69
|
+
/** Index into `config.values`, for a value leaf. */
|
|
70
|
+
measure?: number;
|
|
71
|
+
/** The measure's formatter, for a value leaf. */
|
|
72
|
+
format?: (value: unknown) => string;
|
|
73
|
+
/** Suggested width in px. */
|
|
74
|
+
width?: number;
|
|
75
|
+
/** True for the first column, which holds each row's label. */
|
|
76
|
+
isRowHeader?: boolean;
|
|
77
|
+
/** True for the grand-total column group and its leaves. */
|
|
78
|
+
isTotal?: boolean;
|
|
79
|
+
};
|
|
80
|
+
/** A built pivot: the rows to display, and the generated columns for its column axis. */
|
|
81
|
+
export type PivotResult = {
|
|
82
|
+
rows: PivotRow[];
|
|
83
|
+
columns: PivotColumn[];
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Build a pivot table from flat rows: cross the row axis with the column axis,
|
|
87
|
+
* aggregate each intersection, and return rows plus the generated column tree.
|
|
88
|
+
*/
|
|
89
|
+
export declare function createPivotModel<TData>(data: ReadonlyArray<TData>, config: PivotConfig<TData>): PivotResult;
|
|
90
|
+
/**
|
|
91
|
+
* Public registry of built-in aggregators. Useful for surfacing the
|
|
92
|
+
* available options in a pivot designer UI.
|
|
93
|
+
*/
|
|
94
|
+
export declare const pivotAggregators: Record<PivotAggregatorId, PivotAggregator>;
|
|
95
|
+
/**
|
|
96
|
+
* Filter a `result.rows` array down to only the rows the user should
|
|
97
|
+
* see given the current expansion state. A row stays in the output if
|
|
98
|
+
* its entire ancestor chain (via `__pivotParentId`) is in `expandedIds`.
|
|
99
|
+
*
|
|
100
|
+
* Pass `true` to bypass filtering (everything visible - the default
|
|
101
|
+
* shape `createPivotModel` already returns). Pass an empty array /
|
|
102
|
+
* empty Set to collapse every group to its subtotal row.
|
|
103
|
+
*
|
|
104
|
+
* The result is a new array; the input is not mutated.
|
|
105
|
+
*/
|
|
106
|
+
export declare function filterCollapsedPivotRows(rows: ReadonlyArray<PivotRow>, expandedIds: ReadonlyArray<string> | Set<string> | true): PivotRow[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pivot results in TanStack Table's shape: rows nested through `subRows`
|
|
3
|
+
* (so `getSubRows` and the expanded model collapse groups), and column
|
|
4
|
+
* definitions with plain accessor functions.
|
|
5
|
+
*/
|
|
6
|
+
import type { RowpineColumnMeta, TableLike } from '../table-source';
|
|
7
|
+
import { type PivotColumn, type PivotConfig, type PivotRow } from './pivot';
|
|
8
|
+
/** A pivot row with its children attached. */
|
|
9
|
+
export type PivotTreeRow = PivotRow & {
|
|
10
|
+
subRows?: PivotTreeRow[];
|
|
11
|
+
};
|
|
12
|
+
/** Metadata on every generated column. */
|
|
13
|
+
export type PivotColumnMeta = {
|
|
14
|
+
rowpine: RowpineColumnMeta<PivotTreeRow>;
|
|
15
|
+
pivot: {
|
|
16
|
+
isRowHeader: boolean;
|
|
17
|
+
isTotal: boolean;
|
|
18
|
+
measure?: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/** A generated column: an accessor leaf or a header group. Fits TanStack's `ColumnDef<PivotTreeRow>`. */
|
|
22
|
+
export type PivotColumnDef = {
|
|
23
|
+
id: string;
|
|
24
|
+
header: string;
|
|
25
|
+
accessorFn: (row: PivotTreeRow) => unknown;
|
|
26
|
+
size?: number;
|
|
27
|
+
meta: PivotColumnMeta;
|
|
28
|
+
} | {
|
|
29
|
+
id: string;
|
|
30
|
+
header: string;
|
|
31
|
+
columns: PivotColumnDef[];
|
|
32
|
+
meta: PivotColumnMeta;
|
|
33
|
+
};
|
|
34
|
+
/** Nest flat pivot rows under their parents. The grand-total row stays last. */
|
|
35
|
+
export declare function pivotToTree(rows: ReadonlyArray<PivotRow>): PivotTreeRow[];
|
|
36
|
+
export type ToColumnsOptions = {
|
|
37
|
+
/** Excel number format for every value column, e.g. `'#,##0.00'`. */
|
|
38
|
+
numFmt?: string;
|
|
39
|
+
};
|
|
40
|
+
/** Turn a pivot's column tree into TanStack column definitions. */
|
|
41
|
+
export declare function toTanStackColumns(columns: ReadonlyArray<PivotColumn>, opts?: ToColumnsOptions): PivotColumnDef[];
|
|
42
|
+
/** Bold group, subtotal and grand-total rows in exports. Pass as `rowStyle`. */
|
|
43
|
+
export declare function pivotRowStyle(original: unknown): {
|
|
44
|
+
bold?: boolean;
|
|
45
|
+
} | undefined;
|
|
46
|
+
export type PivotTable = {
|
|
47
|
+
/** Rows for `useReactTable({ data })`, with `getSubRows: (r) => r.subRows`. */
|
|
48
|
+
data: PivotTreeRow[];
|
|
49
|
+
/** Columns for `useReactTable({ columns })`. */
|
|
50
|
+
columns: PivotColumnDef[];
|
|
51
|
+
/** The flat rows, as the engine produced them. */
|
|
52
|
+
rows: PivotRow[];
|
|
53
|
+
/** The pivot as a table the exporters read: `exportXlsx(pivot.table)`. */
|
|
54
|
+
table: TableLike;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A pivot as a read-only table for the exporters, with no TanStack instance
|
|
58
|
+
* behind it, so exporting a pivot works the same under any TanStack version.
|
|
59
|
+
*/
|
|
60
|
+
export declare function pivotAsTable(data: PivotTreeRow[], columns: PivotColumnDef[]): TableLike;
|
|
61
|
+
/**
|
|
62
|
+
* Pivot `data` and return rows and columns ready for TanStack Table.
|
|
63
|
+
* Without a license key it still runs and logs a notice once.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createPivot<TData>(data: ReadonlyArray<TData>, config: PivotConfig<TData>, opts?: ToColumnsOptions): PivotTable;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a TanStack Table instance into a plain sheet: the visible columns in
|
|
3
|
+
* screen order, and the rows the user is looking at (filtered and sorted,
|
|
4
|
+
* groups and tree rows in place). Every exporter works from this.
|
|
5
|
+
*
|
|
6
|
+
* The table is typed structurally (`TableLike`) so this file is the only
|
|
7
|
+
* place that knows TanStack's API. Both a v8 `Table<TData>` and a v9
|
|
8
|
+
* `Table<TFeatures, TData>` fit it: v9 only has the methods of the features
|
|
9
|
+
* you register, so everything past the core is optional here, and v9's
|
|
10
|
+
* start/end pinning and `table.store` state are read alongside v8's
|
|
11
|
+
* left/right pinning and `getState()`.
|
|
12
|
+
*/
|
|
13
|
+
/** Per-column export settings, read from `columnDef.meta.rowpine`. */
|
|
14
|
+
export type RowpineColumnMeta<TData = any> = {
|
|
15
|
+
/** Header text in the file. Default: the column's string header, else its id. */
|
|
16
|
+
header?: string;
|
|
17
|
+
/** Excel number format, e.g. `'"$"#,##0.00'` or `'yyyy-mm-dd'`. */
|
|
18
|
+
numFmt?: string;
|
|
19
|
+
/** Display text for CSV and PDF. Default: numbers and dates in the browser locale. */
|
|
20
|
+
format?: (value: unknown, row: TData) => string;
|
|
21
|
+
/** Column width in px. Default: the column's TanStack size. */
|
|
22
|
+
width?: number;
|
|
23
|
+
/** Horizontal alignment. Default: right for numbers, left otherwise. */
|
|
24
|
+
align?: 'left' | 'center' | 'right';
|
|
25
|
+
/** Cell colors, e.g. for conditional formatting. */
|
|
26
|
+
cellStyle?: (value: unknown, row: TData) => CellStyle | undefined;
|
|
27
|
+
/** Leave this column out of every export. */
|
|
28
|
+
exclude?: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type CellStyle = {
|
|
31
|
+
fill?: string;
|
|
32
|
+
color?: string;
|
|
33
|
+
bold?: boolean;
|
|
34
|
+
};
|
|
35
|
+
/** The parts of a TanStack column the exporters read. */
|
|
36
|
+
export interface ColumnLike {
|
|
37
|
+
id: string;
|
|
38
|
+
columnDef: {
|
|
39
|
+
header?: unknown;
|
|
40
|
+
meta?: unknown;
|
|
41
|
+
aggregationFn?: unknown;
|
|
42
|
+
};
|
|
43
|
+
parent?: ColumnLike;
|
|
44
|
+
getSize?(): number;
|
|
45
|
+
}
|
|
46
|
+
/** The parts of a TanStack row the exporters read. */
|
|
47
|
+
export interface RowLike {
|
|
48
|
+
id: string;
|
|
49
|
+
depth: number;
|
|
50
|
+
original: unknown;
|
|
51
|
+
subRows: RowLike[];
|
|
52
|
+
groupingColumnId?: string;
|
|
53
|
+
groupingValue?: unknown;
|
|
54
|
+
getValue(columnId: string): unknown;
|
|
55
|
+
getIsGrouped?(): boolean;
|
|
56
|
+
}
|
|
57
|
+
type RowModelLike = {
|
|
58
|
+
rows: RowLike[];
|
|
59
|
+
};
|
|
60
|
+
/** The parts of a TanStack table the exporters read. */
|
|
61
|
+
export interface TableLike {
|
|
62
|
+
getAllLeafColumns(): ColumnLike[];
|
|
63
|
+
getVisibleLeafColumns?(): ColumnLike[];
|
|
64
|
+
getLeftVisibleLeafColumns?(): ColumnLike[];
|
|
65
|
+
getRightVisibleLeafColumns?(): ColumnLike[];
|
|
66
|
+
getStartVisibleLeafColumns?(): ColumnLike[];
|
|
67
|
+
getEndVisibleLeafColumns?(): ColumnLike[];
|
|
68
|
+
getCenterVisibleLeafColumns?(): ColumnLike[];
|
|
69
|
+
getCoreRowModel(): RowModelLike;
|
|
70
|
+
getFilteredRowModel?(): RowModelLike;
|
|
71
|
+
getGroupedRowModel?(): RowModelLike;
|
|
72
|
+
getSortedRowModel?(): RowModelLike;
|
|
73
|
+
getRowModel(): RowModelLike;
|
|
74
|
+
getSelectedRowModel?(): RowModelLike;
|
|
75
|
+
/** v8 state. */
|
|
76
|
+
getState?(): {
|
|
77
|
+
grouping?: string[];
|
|
78
|
+
};
|
|
79
|
+
/** v9 state. */
|
|
80
|
+
store?: {
|
|
81
|
+
readonly state: {
|
|
82
|
+
grouping?: string[];
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/** v9 React: the selected state. */
|
|
86
|
+
state?: {
|
|
87
|
+
grouping?: string[];
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Which rows to export. */
|
|
91
|
+
export type RowScope =
|
|
92
|
+
/** Every row that passes the filter, on every page, in the current sort (default). */
|
|
93
|
+
'all'
|
|
94
|
+
/** Only the rows on the current page. */
|
|
95
|
+
| 'page'
|
|
96
|
+
/** Only the selected rows. */
|
|
97
|
+
| 'selected';
|
|
98
|
+
export type ReadOptions = {
|
|
99
|
+
rows?: RowScope;
|
|
100
|
+
/** Style a whole row (e.g. bold totals). Called with the row's original data. */
|
|
101
|
+
rowStyle?: (original: unknown) => CellStyle | undefined;
|
|
102
|
+
};
|
|
103
|
+
/** Aggregations that have an Excel SUBTOTAL equivalent. */
|
|
104
|
+
export type SubtotalAgg = 'sum' | 'mean' | 'min' | 'max';
|
|
105
|
+
export type SheetColumn = {
|
|
106
|
+
id: string;
|
|
107
|
+
header: string;
|
|
108
|
+
width: number;
|
|
109
|
+
align: 'left' | 'center' | 'right' | undefined;
|
|
110
|
+
numFmt?: string;
|
|
111
|
+
format?: (value: unknown, row: unknown) => string;
|
|
112
|
+
cellStyle?: (value: unknown, row: unknown) => CellStyle | undefined;
|
|
113
|
+
/** The column's aggregation when it maps to SUBTOTAL; `auto` resolves per value. */
|
|
114
|
+
subtotal?: SubtotalAgg | 'auto';
|
|
115
|
+
};
|
|
116
|
+
export type SheetRow = {
|
|
117
|
+
/** `group` is a TanStack grouped row; `data` is anything else. */
|
|
118
|
+
kind: 'group' | 'data';
|
|
119
|
+
/** Nesting depth: 0 for top-level rows. */
|
|
120
|
+
depth: number;
|
|
121
|
+
/** Raw values, aligned with the columns. Group rows hold aggregates. */
|
|
122
|
+
values: unknown[];
|
|
123
|
+
/** Which values to leave blank on screen-shaped output (grouped-by cells of leaf rows). */
|
|
124
|
+
blank: boolean[];
|
|
125
|
+
/** Index of the column showing a group row's label, and the label. */
|
|
126
|
+
labelAt?: number;
|
|
127
|
+
label?: string;
|
|
128
|
+
/** How many sheet rows directly follow this one as its descendants. */
|
|
129
|
+
descendants: number;
|
|
130
|
+
/** True when the row has child rows (a group, or a parent in tree data). */
|
|
131
|
+
hasChildren: boolean;
|
|
132
|
+
original: unknown;
|
|
133
|
+
style?: CellStyle;
|
|
134
|
+
};
|
|
135
|
+
export type Sheet = {
|
|
136
|
+
columns: SheetColumn[];
|
|
137
|
+
rows: SheetRow[];
|
|
138
|
+
/** Leading columns pinned left on screen. */
|
|
139
|
+
freezeColumns: number;
|
|
140
|
+
};
|
|
141
|
+
/** Read a TanStack table into a sheet. */
|
|
142
|
+
export declare function readTable(table: TableLike, opts?: ReadOptions): Sheet;
|
|
143
|
+
/** Text for one value, for CSV-free outputs (PDF). */
|
|
144
|
+
export declare function displayText(column: SheetColumn, value: unknown, original: unknown): string;
|
|
145
|
+
/** Alignment for a column: its own, else right when its values are numbers. */
|
|
146
|
+
export declare function alignOf(column: SheetColumn, sheet: Sheet, index: number): 'left' | 'center' | 'right';
|
|
147
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReadOptions, type Sheet, type TableLike } from '../table-source';
|
|
2
|
+
import { type XlsxModel } from './ooxml';
|
|
3
|
+
export type XlsxExportOptions = ReadOptions & {
|
|
4
|
+
fileName?: string;
|
|
5
|
+
/** Worksheet name. Default 'Sheet1'. */
|
|
6
|
+
sheetName?: string;
|
|
7
|
+
/** Fill color of group rows. Default '#eef2f0'. */
|
|
8
|
+
groupFill?: string;
|
|
9
|
+
/** Save the file in the browser. Default true. */
|
|
10
|
+
download?: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** The workbook model for a sheet. Pure: no zip, no license check. */
|
|
13
|
+
export declare function sheetToXlsxModel(sheet: Sheet, opts?: XlsxExportOptions, notes?: string[]): XlsxModel;
|
|
14
|
+
/** The workbook model for a table. Pure apart from reading the table. */
|
|
15
|
+
export declare function tableToXlsxModel(table: TableLike, opts?: XlsxExportOptions, notes?: string[]): XlsxModel;
|
|
16
|
+
/** Export the table to an .xlsx file. Returns the file as a Blob. */
|
|
17
|
+
export declare function exportXlsx(table: TableLike, opts?: XlsxExportOptions): Promise<Blob>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native OOXML (.xlsx) writer. Builds a real workbook from scratch
|
|
3
|
+
* (styles.xml + sheet.xml, zipped with jszip) with, in one file:
|
|
4
|
+
* - typed number / date cells with a number format (Excel sums + sorts them),
|
|
5
|
+
* - per-cell fills / font color / bold (conditional formatting),
|
|
6
|
+
* - a frozen header (and optional frozen columns),
|
|
7
|
+
* - explicit column widths,
|
|
8
|
+
* - outline rows (row groups Excel can collapse) and formula cells,
|
|
9
|
+
* - real cell hyperlinks.
|
|
10
|
+
*
|
|
11
|
+
* It's split into a pure `buildXlsxParts` (returns the XML parts - fully unit
|
|
12
|
+
* testable) and `packageXlsx` (zips them into a Blob via a provided JSZip).
|
|
13
|
+
*/
|
|
14
|
+
/** One worksheet cell, already resolved by the caller. */
|
|
15
|
+
export type XlsxCell = {
|
|
16
|
+
/** 'n' number, 'd' date, 's' string, 'b' boolean. */
|
|
17
|
+
t: 'n' | 'd' | 's' | 'b';
|
|
18
|
+
value: number | string | boolean | Date | null | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* A formula without the leading '=', e.g. `SUBTOTAL(9,C3:C9)`. `value` is
|
|
21
|
+
* written as its cached result, so viewers that do not recalculate still
|
|
22
|
+
* show the number.
|
|
23
|
+
*/
|
|
24
|
+
formula?: string;
|
|
25
|
+
/** Excel number-format code (for 'n' / 'd'). */
|
|
26
|
+
numFmt?: string;
|
|
27
|
+
align?: 'left' | 'center' | 'right';
|
|
28
|
+
/** Background fill hex (e.g. '#fee2e2'). */
|
|
29
|
+
fill?: string;
|
|
30
|
+
/** Font color hex. */
|
|
31
|
+
color?: string;
|
|
32
|
+
bold?: boolean;
|
|
33
|
+
/** Hyperlink URL. */
|
|
34
|
+
link?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A native Excel Table (ListObject) over the data: filter dropdowns, banded
|
|
38
|
+
* rows, structured references, and (optionally) a totals row with SUBTOTAL
|
|
39
|
+
* formulas per numeric column.
|
|
40
|
+
*/
|
|
41
|
+
export type XlsxTableSpec = {
|
|
42
|
+
/** Table name (no spaces; must start with a letter). E.g. 'Table1'. */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Add a totals row. */
|
|
45
|
+
totalsRow: boolean;
|
|
46
|
+
/** Table style id (default 'TableStyleMedium2'). */
|
|
47
|
+
style?: string;
|
|
48
|
+
/** Per-column totals function, aligned with `header`. */
|
|
49
|
+
totalsFns: ReadonlyArray<'sum' | 'average' | 'count' | undefined>;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Native Excel conditional formatting over a whole column's data range - a
|
|
53
|
+
* data bar or a 2/3-stop color scale. Unlike per-cell fills, Excel computes
|
|
54
|
+
* these itself (and recomputes when the user edits), so they're interactive
|
|
55
|
+
* and higher fidelity.
|
|
56
|
+
*/
|
|
57
|
+
export type XlsxCondFormat = {
|
|
58
|
+
kind: 'dataBar';
|
|
59
|
+
colIdx: number;
|
|
60
|
+
color: string;
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'colorScale';
|
|
63
|
+
colIdx: number;
|
|
64
|
+
colors: ReadonlyArray<string>;
|
|
65
|
+
};
|
|
66
|
+
export type XlsxModel = {
|
|
67
|
+
sheetName: string;
|
|
68
|
+
/** Header labels (row 1). */
|
|
69
|
+
header: ReadonlyArray<string>;
|
|
70
|
+
/** Data rows (each an array of cells, column-aligned with `header`). */
|
|
71
|
+
rows: ReadonlyArray<ReadonlyArray<XlsxCell>>;
|
|
72
|
+
/** Per-column widths in pixels (optional). */
|
|
73
|
+
widths?: ReadonlyArray<number>;
|
|
74
|
+
/** Freeze the header row. */
|
|
75
|
+
freezeHeader?: boolean;
|
|
76
|
+
/** Freeze this many leading columns. */
|
|
77
|
+
freezeColumns?: number;
|
|
78
|
+
/** Native column conditional formatting (data bars / color scales). */
|
|
79
|
+
condFormats?: ReadonlyArray<XlsxCondFormat>;
|
|
80
|
+
/** Wrap the data in a native Excel Table. */
|
|
81
|
+
table?: XlsxTableSpec;
|
|
82
|
+
/**
|
|
83
|
+
* Outline level per data row (0 = top level), aligned with `rows`. Rows
|
|
84
|
+
* with a level above 0 form groups Excel can collapse; the summary row sits
|
|
85
|
+
* above its detail rows, as on screen.
|
|
86
|
+
*/
|
|
87
|
+
outline?: ReadonlyArray<number>;
|
|
88
|
+
/** Plain text lines written below the data, after one empty row. */
|
|
89
|
+
notes?: ReadonlyArray<string>;
|
|
90
|
+
};
|
|
91
|
+
/** 0-based column index -> Excel letter (0 -> A, 26 -> AA). */
|
|
92
|
+
export declare function colName(index: number): string;
|
|
93
|
+
/**
|
|
94
|
+
* Excel serial date: days since 1899-12-30 on the LOCAL wall clock.
|
|
95
|
+
*
|
|
96
|
+
* An Excel serial has no timezone; it is the date and time a person reads. So
|
|
97
|
+
* it has to be built from the local parts the grid displays. Subtracting a UTC
|
|
98
|
+
* epoch from `getTime()` wrote local midnight on the 15th as 45305.9167 in
|
|
99
|
+
* UTC+2, which Excel shows as the 14th, and west of UTC added a spurious time
|
|
100
|
+
* to every pure date (#103).
|
|
101
|
+
*/
|
|
102
|
+
export declare function toSerialDate(d: Date): number;
|
|
103
|
+
/**
|
|
104
|
+
* Build the OOXML package parts for a single-sheet workbook. Returns a map of
|
|
105
|
+
* `path -> xml string` ready to be zipped by {@link packageXlsx}.
|
|
106
|
+
*/
|
|
107
|
+
export declare function buildXlsxParts(model: XlsxModel): Record<string, string>;
|
|
108
|
+
/** Zip the parts into an xlsx Blob using a provided JSZip constructor. */
|
|
109
|
+
export declare function packageXlsx(parts: Record<string, string>, JSZip: new () => {
|
|
110
|
+
file(path: string, data: string): void;
|
|
111
|
+
generateAsync(opts: {
|
|
112
|
+
type: 'blob';
|
|
113
|
+
mimeType?: string;
|
|
114
|
+
}): Promise<Blob>;
|
|
115
|
+
}): Promise<Blob>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rowpine",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pivot tables and Excel and PDF export for TanStack Table and shadcn/ui data tables.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tanstack",
|
|
7
|
+
"tanstack-table",
|
|
8
|
+
"react-table",
|
|
9
|
+
"shadcn",
|
|
10
|
+
"pivot",
|
|
11
|
+
"pivot-table",
|
|
12
|
+
"excel",
|
|
13
|
+
"xlsx",
|
|
14
|
+
"pdf",
|
|
15
|
+
"export",
|
|
16
|
+
"data-table"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://rowpine.com/",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/rowpine/rowpine/issues"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/rowpine/rowpine.git"
|
|
25
|
+
},
|
|
26
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
27
|
+
"author": "Rowpine",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"LICENSE.md",
|
|
41
|
+
"README.md"
|
|
42
|
+
],
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup && tsc -p tsconfig.build.json",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"typecheck": "tsc --noEmit"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@noble/ed25519": "^3.2.0",
|
|
54
|
+
"jszip": "^3.10.2",
|
|
55
|
+
"pdfmake": "^0.3.14"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@tanstack/table-core": "^9.3.1",
|
|
59
|
+
"@types/node": "^26.6.6",
|
|
60
|
+
"table-core-v8": "npm:@tanstack/table-core@^8.21.3",
|
|
61
|
+
"tsup": "^9.0.0",
|
|
62
|
+
"typescript": "^7.0.2",
|
|
63
|
+
"vitest": "^5.1.0"
|
|
64
|
+
}
|
|
65
|
+
}
|