rowpine 0.1.0 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +41 -23
  2. package/dist/index.js +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,45 +1,51 @@
1
1
  # rowpine
2
2
 
3
- Pivot tables and Excel and PDF export for TanStack Table and shadcn/ui data tables. Works with TanStack Table v8 and v9.
3
+ **Pivot tables, a pivot designer, and Excel and PDF export for TanStack Table and shadcn/ui data tables.** Works with TanStack Table v8 and v9.
4
4
 
5
- ```bash
6
- npm install rowpine
7
- ```
5
+ [Website](https://rowpine.com) · [Demos](https://rowpine.com/examples/) · [Docs](https://rowpine.com/docs/) · [Pricing](https://rowpine.com/pricing.html)
6
+
7
+ ![The Rowpine pivot designer with regions and countries down the side and quarters across](https://rowpine.com/docs/img/fields-and-wells.png)
8
8
 
9
- The UI (an export menu, a pivot table and its field chooser) installs from the shadcn registry:
9
+ ## Install
10
10
 
11
11
  ```bash
12
- npx shadcn add https://rowpine.com/r/export-menu.json https://rowpine.com/r/pivot.json
12
+ npm install rowpine
13
+ npx shadcn add https://rowpine.com/r/export-menu.json
14
+ npx shadcn add https://rowpine.com/r/pivot.json
13
15
  ```
14
16
 
17
+ This package is the engine: the file writers, the pivot and the license check. The two `shadcn add` lines copy the export menu, the pivot table and the pivot designer into your project as source.
18
+
15
19
  ## Export
16
20
 
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.
21
+ Pass the TanStack table you already render. The file matches the screen: visible columns in their order, every filtered row on every page, the current sort, pinned columns frozen, groups as Excel outline rows with `SUBTOTAL` formulas, typed numbers and dates.
18
22
 
19
23
  ```ts
20
- import { exportCsv, exportPdf, exportXlsx, setLicenseKey } from 'rowpine'
24
+ import { exportCsv, exportPdf, exportXlsx, setLicenseKey } from "rowpine"
21
25
 
22
26
  setLicenseKey(import.meta.env.VITE_ROWPINE_KEY)
23
27
 
24
- await exportXlsx(table, { fileName: 'orders' })
25
- await exportPdf(table, { fileName: 'orders', title: 'Orders' })
26
- await exportCsv(table, { fileName: 'orders' }) // free, no key needed
28
+ await exportXlsx(table, { fileName: "orders" })
29
+ await exportPdf(table, { fileName: "orders", title: "Orders" })
30
+ await exportCsv(table, { fileName: "orders" }) // free, no key needed
27
31
  ```
28
32
 
29
- `rows: 'all' | 'page' | 'selected'` picks the rows (default `'all'`: every filtered row on every page).
33
+ ![An exported sheet with a group total cell selected showing a SUBTOTAL formula](https://rowpine.com/docs/img/subtotal-formulas.png)
34
+
35
+ `rows: "all" | "page" | "selected"` picks the rows (default `"all"`: every filtered row on every page). `rowStyle` styles whole rows. `download: false` returns the Blob without saving it.
30
36
 
31
37
  Per-column settings go in `meta.rowpine`:
32
38
 
33
39
  ```ts
34
40
  {
35
- accessorKey: 'amount',
36
- header: 'Amount',
37
- aggregationFn: 'sum',
41
+ accessorKey: "amount",
42
+ header: "Amount",
43
+ aggregationFn: "sum",
38
44
  meta: {
39
45
  rowpine: {
40
- numFmt: '"$"#,##0.00', // Excel number format
46
+ numFmt: '"$"#,##0.00', // Excel number format
41
47
  format: (v) => money.format(v as number), // PDF text
42
- cellStyle: (v) => ((v as number) > 5000 ? { fill: '#dcfce7' } : undefined),
48
+ cellStyle: (v) => ((v as number) > 5000 ? { fill: "#dcfce7" } : undefined),
43
49
  },
44
50
  },
45
51
  }
@@ -47,15 +53,21 @@ Per-column settings go in `meta.rowpine`:
47
53
 
48
54
  Also: `header`, `width`, `align`, `exclude`.
49
55
 
56
+ ## PDF
57
+
58
+ Page size and orientation, a title, headline numbers, a watermark, group rows with their totals, the header row on every page. Wide tables shrink and then split so every column prints.
59
+
60
+ ![A PDF report with a title, two headline numbers, grouped rows and a DRAFT watermark](https://rowpine.com/docs/img/pdf-title-kpis-watermark.png)
61
+
50
62
  ## Pivot
51
63
 
52
64
  ```ts
53
- import { createPivot, exportXlsx, pivotRowStyle } from 'rowpine'
65
+ import { createPivot, exportXlsx, pivotRowStyle } from "rowpine"
54
66
 
55
67
  const pivot = createPivot(orders, {
56
- rows: ['region', 'country'],
57
- cols: ['quarter'],
58
- values: [{ field: 'amount', agg: 'sum', label: 'Amount' }],
68
+ rows: ["region", "country"],
69
+ cols: ["quarter"],
70
+ values: [{ field: "amount", agg: "sum", label: "Amount" }],
59
71
  })
60
72
 
61
73
  pivot.data // rows nested through subRows, for useTable / useReactTable
@@ -63,10 +75,16 @@ pivot.columns // column definitions with accessor functions
63
75
  await exportXlsx(pivot.table, { rowStyle: pivotRowStyle })
64
76
  ```
65
77
 
66
- Aggregations: `sum`, `avg`, `min`, `max`, `count`, `countDistinct`, `first`, `last`, or a function. Subtotals and grand totals are on by default (`rowSubtotals`, `grandTotalRow`, `grandTotalCol`).
78
+ Aggregations: `sum`, `avg`, `min`, `max`, `count`, `countDistinct`, `first`, `last`, or a function. Subtotals and grand totals are on by default (`rowSubtotals`, `grandTotalRow`, `grandTotalCol`). The pivot designer's layout rules are exported too (`addToWell`, `setAggregatorAt`, `pivotFromLayout` and more), so you can drive a layout from your own UI.
79
+
80
+ ## Demos
81
+
82
+ Ten demos play a short story on the live component and end with the exported file. [Open the demos](https://rowpine.com/examples/).
83
+
84
+ [![The Orders to Excel demo ending with its exported file](https://rowpine.com/img/demos/orders-to-excel.jpg)](https://rowpine.com/examples/#/demo/orders-to-excel)
67
85
 
68
86
  ## License key
69
87
 
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.
88
+ CSV export is free. Excel, PDF and pivots need a key; without one everything still runs, and Excel and PDF files carry one notice line. A key covers every version released during its year of updates, for good. Keys are checked in the browser, with no call to a server. [Pricing](https://rowpine.com/pricing.html).
71
89
 
72
90
  Commercial license: see LICENSE.md.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
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
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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;"},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"?>
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-14";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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;"},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
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
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
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"?>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rowpine",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Pivot tables and Excel and PDF export for TanStack Table and shadcn/ui data tables.",
5
5
  "keywords": [
6
6
  "tanstack",