@nxgt/material-core 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 +21 -0
- package/README.md +335 -0
- package/dist/chunks/index-0xc5q9tp.js +81 -0
- package/dist/chunks/index-0xc5q9tp.js.map +10 -0
- package/dist/chunks/index-2gcgcmc0.js +36 -0
- package/dist/chunks/index-2gcgcmc0.js.map +10 -0
- package/dist/chunks/index-3ca6xe71.js +36 -0
- package/dist/chunks/index-3ca6xe71.js.map +10 -0
- package/dist/chunks/index-5r6rzp43.js +90 -0
- package/dist/chunks/index-5r6rzp43.js.map +10 -0
- package/dist/chunks/index-6tn0wjsb.js +13 -0
- package/dist/chunks/index-6tn0wjsb.js.map +10 -0
- package/dist/chunks/index-8gf9e2d4.js +76 -0
- package/dist/chunks/index-8gf9e2d4.js.map +11 -0
- package/dist/chunks/index-eh0e5ex7.js +61 -0
- package/dist/chunks/index-eh0e5ex7.js.map +10 -0
- package/dist/chunks/index-g7rh8fpv.js +57 -0
- package/dist/chunks/index-g7rh8fpv.js.map +10 -0
- package/dist/chunks/index-h3vjydz7.js +113 -0
- package/dist/chunks/index-h3vjydz7.js.map +10 -0
- package/dist/chunks/index-paaaedrm.js +37 -0
- package/dist/chunks/index-paaaedrm.js.map +10 -0
- package/dist/chunks/index-w3ajz57t.js +72 -0
- package/dist/chunks/index-w3ajz57t.js.map +10 -0
- package/dist/chunks/index-z3yjh0a8.js +642 -0
- package/dist/chunks/index-z3yjh0a8.js.map +18 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +9 -0
- package/dist/lib/currency-digits.d.ts +8 -0
- package/dist/lib/currency-digits.d.ts.map +1 -0
- package/dist/lib/currency-digits.js +24 -0
- package/dist/lib/currency-digits.js.map +10 -0
- package/dist/lib/field-blur-layers.d.ts +35 -0
- package/dist/lib/field-blur-layers.d.ts.map +1 -0
- package/dist/lib/field-blur-layers.js +52 -0
- package/dist/lib/field-blur-layers.js.map +10 -0
- package/dist/lib/pdf-download.d.ts +36 -0
- package/dist/lib/pdf-download.d.ts.map +1 -0
- package/dist/lib/pdf-download.js +15 -0
- package/dist/lib/pdf-download.js.map +9 -0
- package/dist/lib/pdf-file-name.d.ts +10 -0
- package/dist/lib/pdf-file-name.d.ts.map +1 -0
- package/dist/lib/pdf-file-name.js +12 -0
- package/dist/lib/pdf-file-name.js.map +9 -0
- package/dist/lib/pdf-source.d.ts +58 -0
- package/dist/lib/pdf-source.d.ts.map +1 -0
- package/dist/lib/pdf-source.js +21 -0
- package/dist/lib/pdf-source.js.map +9 -0
- package/dist/lib/print-format.utils.d.ts +72 -0
- package/dist/lib/print-format.utils.d.ts.map +1 -0
- package/dist/lib/print-format.utils.js +14 -0
- package/dist/lib/print-format.utils.js.map +9 -0
- package/dist/lib/print-fraction-digits.d.ts +13 -0
- package/dist/lib/print-fraction-digits.d.ts.map +1 -0
- package/dist/lib/print-fraction-digits.js +11 -0
- package/dist/lib/print-fraction-digits.js.map +9 -0
- package/dist/lib/print-table.utils.d.ts +69 -0
- package/dist/lib/print-table.utils.d.ts.map +1 -0
- package/dist/lib/print-table.utils.js +15 -0
- package/dist/lib/print-table.utils.js.map +9 -0
- package/dist/lib/print-value.utils.d.ts +41 -0
- package/dist/lib/print-value.utils.d.ts.map +1 -0
- package/dist/lib/print-value.utils.js +13 -0
- package/dist/lib/print-value.utils.js.map +9 -0
- package/dist/pdf/file-loader.d.ts +19 -0
- package/dist/pdf/file-loader.d.ts.map +1 -0
- package/dist/pdf/file-loader.js +11 -0
- package/dist/pdf/file-loader.js.map +9 -0
- package/dist/pdf/http.d.ts +23 -0
- package/dist/pdf/http.d.ts.map +1 -0
- package/dist/pdf/http.js +13 -0
- package/dist/pdf/http.js.map +9 -0
- package/dist/pdf/index.d.ts +12 -0
- package/dist/pdf/index.d.ts.map +1 -0
- package/dist/pdf/index.js +99 -0
- package/dist/pdf/index.js.map +9 -0
- package/dist/pdf/model/builders.d.ts +59 -0
- package/dist/pdf/model/builders.d.ts.map +1 -0
- package/dist/pdf/model/data-table.d.ts +25 -0
- package/dist/pdf/model/data-table.d.ts.map +1 -0
- package/dist/pdf/model/index.d.ts +5 -0
- package/dist/pdf/model/index.d.ts.map +1 -0
- package/dist/pdf/model/index.js +60 -0
- package/dist/pdf/model/index.js.map +9 -0
- package/dist/pdf/model/nodes.d.ts +135 -0
- package/dist/pdf/model/nodes.d.ts.map +1 -0
- package/dist/pdf/model/theme.d.ts +17 -0
- package/dist/pdf/model/theme.d.ts.map +1 -0
- package/dist/pdf/model/types.d.ts +124 -0
- package/dist/pdf/model/types.d.ts.map +1 -0
- package/dist/pdf/render/context.d.ts +51 -0
- package/dist/pdf/render/context.d.ts.map +1 -0
- package/dist/pdf/render/document.d.ts +22 -0
- package/dist/pdf/render/document.d.ts.map +1 -0
- package/dist/pdf/render/index.d.ts +9 -0
- package/dist/pdf/render/index.d.ts.map +1 -0
- package/dist/pdf/render/index.js +44 -0
- package/dist/pdf/render/index.js.map +9 -0
- package/dist/pdf/render/nodes/blocks.d.ts +20 -0
- package/dist/pdf/render/nodes/blocks.d.ts.map +1 -0
- package/dist/pdf/render/nodes/header.d.ts +15 -0
- package/dist/pdf/render/nodes/header.d.ts.map +1 -0
- package/dist/pdf/render/nodes/layout.d.ts +45 -0
- package/dist/pdf/render/nodes/layout.d.ts.map +1 -0
- package/dist/pdf/render/nodes/table.d.ts +15 -0
- package/dist/pdf/render/nodes/table.d.ts.map +1 -0
- package/dist/pdf/render/page.d.ts +31 -0
- package/dist/pdf/render/page.d.ts.map +1 -0
- package/dist/pdf/render/render-node.d.ts +11 -0
- package/dist/pdf/render/render-node.d.ts.map +1 -0
- package/dist/pdf/render/styles.d.ts +304 -0
- package/dist/pdf/render/styles.d.ts.map +1 -0
- package/dist/pdf-viewer/index.d.ts +14 -0
- package/dist/pdf-viewer/index.d.ts.map +1 -0
- package/dist/pdf-viewer/index.js +380 -0
- package/dist/pdf-viewer/index.js.map +15 -0
- package/dist/pdf-viewer/pdf-document-search.d.ts +44 -0
- package/dist/pdf-viewer/pdf-document-search.d.ts.map +1 -0
- package/dist/pdf-viewer/pdf-escape.d.ts +7 -0
- package/dist/pdf-viewer/pdf-escape.d.ts.map +1 -0
- package/dist/pdf-viewer/pdf-page-geometry.d.ts +62 -0
- package/dist/pdf-viewer/pdf-page-geometry.d.ts.map +1 -0
- package/dist/pdf-viewer/pdf-page-sizes.d.ts +38 -0
- package/dist/pdf-viewer/pdf-page-sizes.d.ts.map +1 -0
- package/dist/pdf-viewer/pdf-scroll-spy.d.ts +43 -0
- package/dist/pdf-viewer/pdf-scroll-spy.d.ts.map +1 -0
- package/dist/pdf-viewer/pdf-text-search.d.ts +26 -0
- package/dist/pdf-viewer/pdf-text-search.d.ts.map +1 -0
- package/dist/vite/index.d.ts +81 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +31 -0
- package/dist/vite/index.js.map +10 -0
- package/docs/README.md +9 -0
- package/docs/roadmap.md +35 -0
- package/docs/troubleshooting.md +105 -0
- package/package.json +187 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Steve Tsala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# @nxgt/material-core
|
|
2
|
+
|
|
3
|
+
The framework-free core that [`@nxgt/material`](https://www.npmjs.com/package/@nxgt/material)
|
|
4
|
+
(React) and [`@nxgt/material-vue`](https://www.npmjs.com/package/@nxgt/material-vue)
|
|
5
|
+
(Vue) share, as one copy: the PDF document model and its react-pdf renderer,
|
|
6
|
+
the print formatters, the PDF file helpers, the PDF viewer's framework-free
|
|
7
|
+
half (page geometry, scroll spy, text search), the currency field's decimals,
|
|
8
|
+
the fields' blur rule and the Vite plugin that bundles pdfkit's standard
|
|
9
|
+
fonts.
|
|
10
|
+
|
|
11
|
+
**You usually do not install it yourself.** Both libraries depend on it and
|
|
12
|
+
re-export what they always exported, at the same subpaths:
|
|
13
|
+
`@nxgt/material/print`, `@nxgt/material-vue/pdf`, `/lib` and `/vite` read
|
|
14
|
+
from here, and so do their `PdfReader`, `CurrencyField` and form fields. Import it directly when you want the model or a formatter without
|
|
15
|
+
a UI library.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { heading, pdfDocument, text } from '@nxgt/material-core/pdf/model';
|
|
19
|
+
import { formatAmount } from '@nxgt/material-core/print-format';
|
|
20
|
+
|
|
21
|
+
const invoice = pdfDocument({
|
|
22
|
+
pages: [{ body: [heading('Invoice'), text(formatAmount(12.5, 'EUR'))] }],
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> **0.x.** The subpaths below are what the two libraries build on; they may
|
|
27
|
+
> still move between minor versions until 1.0.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
bun add @nxgt/material-core
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
TypeScript consumers resolve it as a bundler does
|
|
36
|
+
(`"moduleResolution": "bundler"`); `nodenext` is not supported. The built
|
|
37
|
+
files are plain ES modules and also load in Node without a bundler.
|
|
38
|
+
|
|
39
|
+
| Optional peer | Needed by |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `react`, `@react-pdf/renderer@^4.9` | `@nxgt/material-core/pdf/render` and `/pdf` — rendering a document |
|
|
42
|
+
| `vite@^6 \|\| ^7 \|\| ^8` | `@nxgt/material-core/vite` — its declarations name Vite's `Plugin` type (types only); the plugin itself runs in your Vite config |
|
|
43
|
+
|
|
44
|
+
Every other subpath imports none of them, and is safe to import on a server
|
|
45
|
+
with no DOM.
|
|
46
|
+
|
|
47
|
+
## Subpaths
|
|
48
|
+
|
|
49
|
+
| Subpath | What it holds |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `@nxgt/material-core` | The model, the formatters, the file helpers, `http`, `file-loader` — every document and print subpath that imports no framework. The viewer, currency and blur helpers are on their own subpaths only |
|
|
52
|
+
| `/pdf/model` | The document model: `pdfDocument`, `page`, `heading`, `text`, `table`, `dataTable`, themes and their types — plain, JSON-serialisable data |
|
|
53
|
+
| `/pdf/render` | The react-pdf renderer: `renderPdfElement`, `createPdfContext`, `renderPdfPage`, the node renderers |
|
|
54
|
+
| `/pdf` | `/pdf/model` and `/pdf/render` together |
|
|
55
|
+
| `/pdf/http` | `contentDisposition`, `pdfBytesResponse`, `toWebStream` — serving a rendered PDF |
|
|
56
|
+
| `/pdf/file-loader` | `createPdfFileLoader`, `IDLE_PDF_FILE` — the state behind `usePdfFile` / `usePdf` |
|
|
57
|
+
| `/print-format` | `formatAmount`, `formatDate`, `createFormatters` — `Intl` formatting |
|
|
58
|
+
| `/print-fraction-digits` | `fractionDigitsOption`, `MAX_FRACTION_DIGITS` |
|
|
59
|
+
| `/print-value` | `PrintValue`, `formatPrintValue`, `isEmptyPrintValue` |
|
|
60
|
+
| `/print-table` | `definePrintColumns`, `resolvePrintTable`, `resolvePrintFields`, `toPrintValue` |
|
|
61
|
+
| `/pdf-source` | `PdfSource` and its helpers (`toPdfjsSource`, `pdfSourceKey`, …) |
|
|
62
|
+
| `/pdf-file-name` | `resolvePdfFileName`, `DEFAULT_PDF_FILE_NAME` |
|
|
63
|
+
| `/pdf-download` | `downloadPdfFile`, `printPdfFile` — in a browser |
|
|
64
|
+
| `/pdf-viewer` | The PDF viewer's pure half: page geometry and fit (`readerFitScale`, `placeholderSize`, `rotatedSize`, `PdfPageSize`, `FitMode`), page sizes (`readPageSizes`, `createPageSizesLoader`), the scroll spy (`createScrollSpy`, `revealHit`), text search (`pageTextFromItems`, `findTextMatches`, `searchDocument`), `escapeHtml`, `mapPdfOutline` |
|
|
65
|
+
| `/currency-digits` | `currencyFractionDigits` — the decimals `CurrencyField` shows |
|
|
66
|
+
| `/field-blur-layers` | `layerOf`, `openerOf`, `FieldLayers` — whose overlay a layer of the page is, for a field's blur |
|
|
67
|
+
| `/vite` | `pdfkitStandardFonts()` — see below |
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
### The root entry: model, formatters and file helpers
|
|
72
|
+
|
|
73
|
+
`@nxgt/material-core` re-exports every document and print subpath that
|
|
74
|
+
imports no framework, so one import builds a document on a server with no
|
|
75
|
+
`react` installed:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { createFormatters, dataTable, definePrintColumns, heading, pdfDocument, text } from '@nxgt/material-core';
|
|
79
|
+
|
|
80
|
+
type Line = { item: string; amount: number };
|
|
81
|
+
|
|
82
|
+
const { formatDate } = createFormatters({ locale: 'fr-FR', timeZone: 'Europe/Paris' });
|
|
83
|
+
const lines: Line[] = [{ item: 'Design', amount: 1200 }, { item: 'Hosting', amount: 90 }];
|
|
84
|
+
|
|
85
|
+
const invoice = pdfDocument({
|
|
86
|
+
language: 'fr',
|
|
87
|
+
pages: [{
|
|
88
|
+
body: [
|
|
89
|
+
heading('Facture 42'),
|
|
90
|
+
text(formatDate('2026-03-01')),
|
|
91
|
+
dataTable(definePrintColumns<Line>([
|
|
92
|
+
{ key: 'item', label: 'Item', flex: 3 },
|
|
93
|
+
{ key: 'amount', label: 'Amount', format: 'amount', total: 'sum' },
|
|
94
|
+
]), lines, { currency: 'EUR' }),
|
|
95
|
+
],
|
|
96
|
+
}],
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The document is plain JSON: build it where the data is, render it where
|
|
101
|
+
`react` and `@react-pdf/renderer` are installed.
|
|
102
|
+
|
|
103
|
+
### Rendering a document: `/pdf/render`
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { renderToBuffer } from '@react-pdf/renderer';
|
|
107
|
+
import { pdfDocument, text } from '@nxgt/material-core/pdf/model';
|
|
108
|
+
import { renderPdfElement } from '@nxgt/material-core/pdf/render';
|
|
109
|
+
|
|
110
|
+
const bytes = await renderToBuffer(
|
|
111
|
+
renderPdfElement(pdfDocument({ pages: [{ body: [text('Hello')] }] })),
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`@nxgt/material/print` and `@nxgt/material-vue/pdf` wrap this in
|
|
116
|
+
`renderPdfBuffer`, `renderPdfStream` and friends, with the library's
|
|
117
|
+
translations; use theirs in an app built on either.
|
|
118
|
+
|
|
119
|
+
### Model and renderer in one import: `/pdf`
|
|
120
|
+
|
|
121
|
+
`/pdf` is `/pdf/model` and `/pdf/render` together, for code that builds and
|
|
122
|
+
renders in the same place. It needs the optional peers, like `/pdf/render`.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { pdf } from '@react-pdf/renderer';
|
|
126
|
+
import { heading, pdfDocument, renderPdfElement } from '@nxgt/material-core/pdf';
|
|
127
|
+
|
|
128
|
+
const blob = await pdf(
|
|
129
|
+
renderPdfElement(pdfDocument({ pages: [{ body: [heading('Report')] }] })),
|
|
130
|
+
).toBlob();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Serving a rendered PDF: `/pdf/http`
|
|
134
|
+
|
|
135
|
+
`pdfBytesResponse` wraps bytes in a Web `Response` with `Content-Type`,
|
|
136
|
+
`Content-Length` and a `Content-Disposition` every client reads;
|
|
137
|
+
`toWebStream` turns react-pdf's Node stream into a Web `ReadableStream`.
|
|
138
|
+
Both run in any handler that returns a `Response` (Hono, a React Router
|
|
139
|
+
loader, Bun.serve, …).
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { renderToBuffer, renderToStream } from '@react-pdf/renderer';
|
|
143
|
+
import { pdfDocument, text } from '@nxgt/material-core/pdf/model';
|
|
144
|
+
import { renderPdfElement } from '@nxgt/material-core/pdf/render';
|
|
145
|
+
import { contentDisposition, pdfBytesResponse, toWebStream } from '@nxgt/material-core/pdf/http';
|
|
146
|
+
|
|
147
|
+
const doc = pdfDocument({ pages: [{ body: [text('Invoice 42')] }] });
|
|
148
|
+
|
|
149
|
+
// Buffered: the whole file, with its length.
|
|
150
|
+
export async function download(): Promise<Response> {
|
|
151
|
+
// A copy: a Node Buffer's `buffer` may be shared, a Response's may not.
|
|
152
|
+
const bytes = new Uint8Array(await renderToBuffer(renderPdfElement(doc)));
|
|
153
|
+
return pdfBytesResponse(bytes, {
|
|
154
|
+
fileName: 'INV-42', // `.pdf` is added: INV-42.pdf
|
|
155
|
+
disposition: 'inline', // 'attachment' by default
|
|
156
|
+
headers: { 'Cache-Control': 'no-store' },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Streamed: no length, the headers are yours.
|
|
161
|
+
export async function stream(): Promise<Response> {
|
|
162
|
+
const source = await renderToStream(renderPdfElement(doc));
|
|
163
|
+
return new Response(toWebStream(source), {
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'application/pdf',
|
|
166
|
+
'Content-Disposition': contentDisposition('inline', 'INV-42.pdf'),
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Rendering to an object URL: `/pdf/file-loader`
|
|
173
|
+
|
|
174
|
+
`createPdfFileLoader` is the state machine behind `usePdfFile` (React) and
|
|
175
|
+
`usePdf` (Vue), without the framework: it renders with the function you give
|
|
176
|
+
it, keeps one object URL alive at a time, revokes the one it replaces, and
|
|
177
|
+
drops a render that a newer `load` overtook. Use it to write the same hook in
|
|
178
|
+
another framework; it does not serve a PDF.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { pdf } from '@react-pdf/renderer';
|
|
182
|
+
import { createPdfFileLoader, IDLE_PDF_FILE, type PdfFileSnapshot } from '@nxgt/material-core/pdf/file-loader';
|
|
183
|
+
import { type PdfDocument, pdfDocument, text } from '@nxgt/material-core/pdf/model';
|
|
184
|
+
import { renderPdfElement } from '@nxgt/material-core/pdf/render';
|
|
185
|
+
|
|
186
|
+
let file: PdfFileSnapshot = IDLE_PDF_FILE; // { blob, url, loading, error }
|
|
187
|
+
|
|
188
|
+
const loader = createPdfFileLoader<PdfDocument>(
|
|
189
|
+
(next) => { file = next; }, // called on every change
|
|
190
|
+
(doc) => pdf(renderPdfElement(doc)).toBlob(),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
await loader.load(pdfDocument({ pages: [{ body: [text('Preview')] }] }));
|
|
194
|
+
console.log(file.url); // blob:… — for an <iframe>, a reader or a link
|
|
195
|
+
await loader.load(null); // back to IDLE_PDF_FILE, URL revoked
|
|
196
|
+
loader.dispose(); // on unmount: revokes the URL, ignores renders in flight
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Formatting values: `/print-format`, `/print-value`, `/print-table`, `/print-fraction-digits`
|
|
200
|
+
|
|
201
|
+
Plain `Intl` formatting, no framework: amounts, dates, typed print values and
|
|
202
|
+
tables resolved from column definitions.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { createFormatters, formatAmount } from '@nxgt/material-core/print-format';
|
|
206
|
+
import { fractionDigitsOption } from '@nxgt/material-core/print-fraction-digits';
|
|
207
|
+
import { definePrintColumns, resolvePrintTable } from '@nxgt/material-core/print-table';
|
|
208
|
+
import { formatPrintValue } from '@nxgt/material-core/print-value';
|
|
209
|
+
|
|
210
|
+
formatAmount(1234.5, 'EUR', { locale: 'fr-FR' }); // '1 234,50 €'
|
|
211
|
+
formatAmount(1234.5, 'JPY', { locale: 'en-US' }); // '¥1,235'
|
|
212
|
+
fractionDigitsOption(Number.NaN); // undefined: the currency's own decimals apply
|
|
213
|
+
|
|
214
|
+
const { formatDate } = createFormatters({ locale: 'en-US', timeZone: 'Europe/Paris' });
|
|
215
|
+
formatDate('2026-03-01'); // 'Mar 1, 2026' — a date-only value keeps its day
|
|
216
|
+
|
|
217
|
+
type Line = { item: string; amount: number };
|
|
218
|
+
const table = resolvePrintTable(
|
|
219
|
+
definePrintColumns<Line>([
|
|
220
|
+
{ key: 'item', label: 'Item' },
|
|
221
|
+
{ key: 'amount', label: 'Amount', format: 'amount', total: 'sum' },
|
|
222
|
+
]),
|
|
223
|
+
[{ item: 'Design', amount: 1200 }],
|
|
224
|
+
{ currency: 'EUR' },
|
|
225
|
+
);
|
|
226
|
+
table.rows.map((row) => row.map((cell) => formatPrintValue(cell, { locale: 'en-US' })));
|
|
227
|
+
// [['Design', '€1,200.00']]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### PDF files in a browser: `/pdf-source`, `/pdf-file-name`, `/pdf-download`
|
|
231
|
+
|
|
232
|
+
A `PdfSource` is what both libraries' `PdfReader` opens: a URL, a `Blob`, an
|
|
233
|
+
`ArrayBuffer`, a `Uint8Array` or `{ url, httpHeaders }`. These helpers name
|
|
234
|
+
it, key it, save it and print it.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { downloadPdfFile, printPdfFile } from '@nxgt/material-core/pdf-download';
|
|
238
|
+
import { resolvePdfFileName } from '@nxgt/material-core/pdf-file-name';
|
|
239
|
+
import { type PdfSource, pdfSourceKey } from '@nxgt/material-core/pdf-source';
|
|
240
|
+
|
|
241
|
+
const source: PdfSource = { url: '/api/invoices/42.pdf', httpHeaders: { Authorization: 'Bearer …' } };
|
|
242
|
+
|
|
243
|
+
resolvePdfFileName(source); // '42.pdf' — fileName, then the File's name, then the URL
|
|
244
|
+
pdfSourceKey(source); // a stable key, to tell when the source changed
|
|
245
|
+
|
|
246
|
+
await downloadPdfFile(source, { fileName: 'Invoice 42' }); // saves 'Invoice 42.pdf'
|
|
247
|
+
await printPdfFile(source); // prints from a hidden frame, or opens a tab
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`downloadPdfFile` and `printPdfFile` touch `document`: call them from a click
|
|
251
|
+
handler, never at import or on a server.
|
|
252
|
+
|
|
253
|
+
### The PDF viewer's pure half: `/pdf-viewer`
|
|
254
|
+
|
|
255
|
+
What both libraries' `PdfReader` computes without pdf.js or a framework:
|
|
256
|
+
the zoom that fits a page, a page's placeholder size, where a search matches
|
|
257
|
+
in a page's text. It takes pdf.js's objects by shape (`numPages`,
|
|
258
|
+
`getPage`, `getTextContent`), so it never imports pdf.js itself.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { findTextMatches, pageTextFromItems, placeholderSize, readerFitScale } from '@nxgt/material-core/pdf-viewer';
|
|
262
|
+
|
|
263
|
+
const letter = { width: 612, height: 792 }; // PDF points
|
|
264
|
+
readerFitScale('width', [letter], 0, { width: 1000, height: 800 }); // 1.58…
|
|
265
|
+
placeholderSize(letter, 1.5, 90); // { width: 1188, height: 918 } — turned
|
|
266
|
+
|
|
267
|
+
const page = pageTextFromItems([{ str: 'Café au', hasEOL: true }, { str: 'lait' }]);
|
|
268
|
+
findTextMatches(page.text, 'cafe au lait'); // [{ start: 0, end: 12 }] — case, accents and line breaks ignored
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### A currency's decimals: `/currency-digits`
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { currencyFractionDigits } from '@nxgt/material-core/currency-digits';
|
|
275
|
+
|
|
276
|
+
currencyFractionDigits('EUR'); // 2
|
|
277
|
+
currencyFractionDigits('JPY'); // 0
|
|
278
|
+
currencyFractionDigits('KWD'); // 3
|
|
279
|
+
currencyFractionDigits('EUR', 0); // 0 — an explicit value wins, as in formatAmount
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### A field's blur across overlays: `/field-blur-layers`
|
|
283
|
+
|
|
284
|
+
A field whose popover, listbox or dialog renders into `<body>` must not count
|
|
285
|
+
focus moving there as leaving the field. `layerOf` names the top-level layer
|
|
286
|
+
a node is in; `openerOf` says whether the field opened it, going by
|
|
287
|
+
`aria-controls` / `aria-owns`. Both read the DOM when called, never at import.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { type FieldLayers, layerOf, openerOf } from '@nxgt/material-core/field-blur-layers';
|
|
291
|
+
|
|
292
|
+
const field = document.querySelector<HTMLElement>('#country')!;
|
|
293
|
+
const layers: FieldLayers = { before: new Set(), home: new Set([layerOf(field)]), opened: new Set() };
|
|
294
|
+
|
|
295
|
+
field.addEventListener('focusout', (event) => {
|
|
296
|
+
const next = event.relatedTarget as Node | null;
|
|
297
|
+
const layer = next && layerOf(next);
|
|
298
|
+
if (layer && !layers.home.has(layer) && openerOf([field], layers, layer) === 'ours') return; // still ours
|
|
299
|
+
// …the field was left: validate it
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Bundling pdfkit's fonts: `/vite`
|
|
304
|
+
|
|
305
|
+
An SSR server bundled into a single file with no `node_modules` needs
|
|
306
|
+
pdfkit's standard fonts inlined, or it throws on every request:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
// vite.config.ts
|
|
310
|
+
import { pdfkitStandardFonts } from '@nxgt/material-core/vite';
|
|
311
|
+
import { defineConfig } from 'vite';
|
|
312
|
+
|
|
313
|
+
export default defineConfig({ plugins: [pdfkitStandardFonts()] });
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`@nxgt/material/vite` and `@nxgt/material-vue/vite` export the same
|
|
317
|
+
function. The doc comment on `pdfkitStandardFonts` explains when you need it
|
|
318
|
+
and when keeping the library external is the lighter answer.
|
|
319
|
+
|
|
320
|
+
## Traps
|
|
321
|
+
|
|
322
|
+
| Symptom | Fix |
|
|
323
|
+
| --- | --- |
|
|
324
|
+
| `Cannot find package '@react-pdf/renderer'` (or `'react'`) when importing `/pdf` or `/pdf/render` | `bun add react @react-pdf/renderer@^4.9` — or import from `/pdf/model` where you only build documents |
|
|
325
|
+
| `Cannot find module '#standard-fonts/Helvetica'` on every request of a bundled server | `pdfkitStandardFonts()` from `/vite`, or keep the renderer external |
|
|
326
|
+
|
|
327
|
+
Every message, with its cause, is in [troubleshooting](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md).
|
|
328
|
+
|
|
329
|
+
## Documentation
|
|
330
|
+
|
|
331
|
+
- [docs/README.md](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/README.md) — every page, and when to read it
|
|
332
|
+
- [Troubleshooting](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md) — every error message, and its fix
|
|
333
|
+
- [Roadmap](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/roadmap.md) — what is next
|
|
334
|
+
|
|
335
|
+
MIT licensed.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/lib/print-table.utils.ts
|
|
2
|
+
function definePrintColumns2(columns) {
|
|
3
|
+
return columns;
|
|
4
|
+
}
|
|
5
|
+
function columnId(column) {
|
|
6
|
+
return "accessor" in column ? column.id : column.id ?? column.key;
|
|
7
|
+
}
|
|
8
|
+
function rawValue(column, row, index) {
|
|
9
|
+
if ("accessor" in column)
|
|
10
|
+
return column.accessor(row, index);
|
|
11
|
+
return row[column.key];
|
|
12
|
+
}
|
|
13
|
+
var toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
14
|
+
function currencyOf(column, row, options) {
|
|
15
|
+
const own = typeof column.currency === "function" ? row === undefined ? undefined : column.currency(row) : column.currency;
|
|
16
|
+
return own ?? options.currency ?? null;
|
|
17
|
+
}
|
|
18
|
+
function toPrintValue2(column, value, row, options = {}) {
|
|
19
|
+
const { format } = column;
|
|
20
|
+
if (typeof format === "function") {
|
|
21
|
+
return row === undefined ? String(value ?? "") : format(value, row);
|
|
22
|
+
}
|
|
23
|
+
if (value === null || value === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
switch (format) {
|
|
26
|
+
case "amount":
|
|
27
|
+
return {
|
|
28
|
+
amount: toNumber(value),
|
|
29
|
+
currency: currencyOf(column, row, options)
|
|
30
|
+
};
|
|
31
|
+
case "number":
|
|
32
|
+
return { number: toNumber(value) };
|
|
33
|
+
case "percent":
|
|
34
|
+
return { number: toNumber(value), style: "percent" };
|
|
35
|
+
case "date":
|
|
36
|
+
if (value instanceof Date)
|
|
37
|
+
return { date: value.toISOString() };
|
|
38
|
+
return typeof value === "string" || typeof value === "number" ? { date: value } : null;
|
|
39
|
+
default:
|
|
40
|
+
if (value instanceof Date)
|
|
41
|
+
return { date: value.toISOString() };
|
|
42
|
+
return typeof value === "number" ? value : String(value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var isNumeric = (format) => format === "amount" || format === "number" || format === "percent";
|
|
46
|
+
function totalOf(column, rows, options) {
|
|
47
|
+
if (!column.total)
|
|
48
|
+
return null;
|
|
49
|
+
const figure = column.total === "sum" ? rows.reduce((sum, row, i) => sum + (toNumber(rawValue(column, row, i)) ?? 0), 0) : column.total(rows);
|
|
50
|
+
if (typeof figure === "string")
|
|
51
|
+
return figure;
|
|
52
|
+
const currencies = new Set(rows.map((r) => currencyOf(column, r, options)));
|
|
53
|
+
if (column.format === "amount" && currencies.size > 1)
|
|
54
|
+
return null;
|
|
55
|
+
return toPrintValue2(column, figure, rows[0], options);
|
|
56
|
+
}
|
|
57
|
+
function resolvePrintTable2(columns, rows, options = {}) {
|
|
58
|
+
const resolved = columns.map((column) => ({
|
|
59
|
+
id: columnId(column),
|
|
60
|
+
label: column.label,
|
|
61
|
+
align: column.align ?? (isNumeric(column.format) ? "right" : "left"),
|
|
62
|
+
...column.width !== undefined && { width: column.width },
|
|
63
|
+
...column.flex !== undefined && { flex: column.flex }
|
|
64
|
+
}));
|
|
65
|
+
const body = rows.map((row, i) => columns.map((column) => toPrintValue2(column, rawValue(column, row, i), row, options)));
|
|
66
|
+
const footer = columns.some((column) => column.total) ? [
|
|
67
|
+
columns.map((column, index) => index === 0 && !column.total ? options.totalLabel ?? "Total" : totalOf(column, rows, options))
|
|
68
|
+
] : [];
|
|
69
|
+
return { columns: resolved, rows: body, footer, empty: rows.length === 0 };
|
|
70
|
+
}
|
|
71
|
+
function resolvePrintFields2(fields, row, options = {}) {
|
|
72
|
+
return fields.map((field) => ({
|
|
73
|
+
label: field.label,
|
|
74
|
+
value: toPrintValue2(field, rawValue(field, row, 0), row, options)
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { definePrintColumns2, toPrintValue2, resolvePrintTable2, resolvePrintFields2 };
|
|
79
|
+
|
|
80
|
+
//# debugId=D31CA70FB327C98664756E2164756E21
|
|
81
|
+
//# sourceMappingURL=index-0xc5q9tp.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/print-table.utils.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Printed tables from column definitions and plain rows.\n *\n * One column list drives a table (`resolvePrintTable`) and a detail block\n * (`resolvePrintFields`). Cells come out as {@link PrintValue}s — still data —\n * so the renderer formats them with the document's locale and time zone.\n * Framework-free: no React, no Vue, no react-pdf.\n */\nimport type { PrintValue } from './print-value.utils';\n\nexport type PrintColumnFormat = 'amount' | 'date' | 'number' | 'percent';\n\n/** How a column reads its value: a key of the row, or an accessor. */\nexport type PrintColumnSource<T> =\n\t| { key: keyof T & string; id?: string }\n\t| { id: string; accessor: (row: T, index: number) => unknown };\n\nexport type PrintColumn<T> = PrintColumnSource<T> & {\n\tlabel: string;\n\t/** Points, or a percentage of the table (`'25%'`); otherwise it flexes. */\n\twidth?: number | `${number}%`;\n\t/** Share of the remaining width; 1 by default. */\n\tflex?: number;\n\t/** `'right'` by default for amounts, numbers and percentages. */\n\talign?: 'left' | 'center' | 'right';\n\t/** How to print the value; a function returns the text itself. */\n\tformat?: PrintColumnFormat | ((value: unknown, row: T) => string);\n\t/** ISO 4217 code for `format: 'amount'`, or one per row. */\n\tcurrency?: string | ((row: T) => string | null | undefined);\n\t/** A footer total: the sum of the column, or your own figure. */\n\ttotal?: 'sum' | ((rows: T[]) => number | string | null);\n};\n\nexport type PrintTableOptions = {\n\t/** Currency of `amount` columns that name none. */\n\tcurrency?: string;\n\t/** Text in the first footer cell when that column has no total. */\n\ttotalLabel?: string;\n};\n\nexport type ResolvedPrintColumn = {\n\tid: string;\n\tlabel: string;\n\talign: 'left' | 'center' | 'right';\n\twidth?: number | `${number}%`;\n\tflex?: number;\n};\n\nexport type ResolvedPrintTable = {\n\tcolumns: ResolvedPrintColumn[];\n\trows: PrintValue[][];\n\t/** One totals row, or none when no column has a `total`. */\n\tfooter: PrintValue[][];\n\tempty: boolean;\n};\n\n/** Types a column list, so `key` is checked against the row type. */\nexport function definePrintColumns<T>(\n\tcolumns: PrintColumn<T>[],\n): PrintColumn<T>[] {\n\treturn columns;\n}\n\nfunction columnId<T>(column: PrintColumn<T>): string {\n\treturn 'accessor' in column ? column.id : (column.id ?? column.key);\n}\n\nfunction rawValue<T>(column: PrintColumn<T>, row: T, index: number): unknown {\n\tif ('accessor' in column) return column.accessor(row, index);\n\treturn row[column.key];\n}\n\nconst toNumber = (value: unknown): number | null =>\n\ttypeof value === 'number' && Number.isFinite(value) ? value : null;\n\nfunction currencyOf<T>(\n\tcolumn: PrintColumn<T>,\n\trow: T | undefined,\n\toptions: PrintTableOptions,\n): string | null {\n\tconst own =\n\t\ttypeof column.currency === 'function'\n\t\t\t? row === undefined\n\t\t\t\t? undefined\n\t\t\t\t: column.currency(row)\n\t\t\t: column.currency;\n\treturn own ?? options.currency ?? null;\n}\n\n/** A column's value as a {@link PrintValue}, ready for the renderer. */\nexport function toPrintValue<T>(\n\tcolumn: PrintColumn<T>,\n\tvalue: unknown,\n\trow: T | undefined,\n\toptions: PrintTableOptions = {},\n): PrintValue {\n\tconst { format } = column;\n\tif (typeof format === 'function') {\n\t\treturn row === undefined ? String(value ?? '') : format(value, row);\n\t}\n\tif (value === null || value === undefined) return null;\n\tswitch (format) {\n\t\tcase 'amount':\n\t\t\treturn {\n\t\t\t\tamount: toNumber(value),\n\t\t\t\tcurrency: currencyOf(column, row, options),\n\t\t\t};\n\t\tcase 'number':\n\t\t\treturn { number: toNumber(value) };\n\t\tcase 'percent':\n\t\t\treturn { number: toNumber(value), style: 'percent' };\n\t\tcase 'date':\n\t\t\tif (value instanceof Date) return { date: value.toISOString() };\n\t\t\treturn typeof value === 'string' || typeof value === 'number'\n\t\t\t\t? { date: value }\n\t\t\t\t: null;\n\t\tdefault:\n\t\t\tif (value instanceof Date) return { date: value.toISOString() };\n\t\t\treturn typeof value === 'number' ? value : String(value);\n\t}\n}\n\nconst isNumeric = (format: unknown) =>\n\tformat === 'amount' || format === 'number' || format === 'percent';\n\nfunction totalOf<T>(\n\tcolumn: PrintColumn<T>,\n\trows: T[],\n\toptions: PrintTableOptions,\n): PrintValue {\n\tif (!column.total) return null;\n\tconst figure =\n\t\tcolumn.total === 'sum'\n\t\t\t? rows.reduce(\n\t\t\t\t\t(sum, row, i) => sum + (toNumber(rawValue(column, row, i)) ?? 0),\n\t\t\t\t\t0,\n\t\t\t\t)\n\t\t\t: column.total(rows);\n\tif (typeof figure === 'string') return figure;\n\t// Amounts in several currencies have no sum: print it empty.\n\tconst currencies = new Set(rows.map((r) => currencyOf(column, r, options)));\n\tif (column.format === 'amount' && currencies.size > 1) return null;\n\treturn toPrintValue(column, figure, rows[0], options);\n}\n\n/**\n * Columns, cells and totals of a printed table. A footer row is added when a\n * column has a `total`; its first cell, when that column has none, holds\n * `totalLabel`.\n */\nexport function resolvePrintTable<T>(\n\tcolumns: PrintColumn<T>[],\n\trows: T[],\n\toptions: PrintTableOptions = {},\n): ResolvedPrintTable {\n\tconst resolved = columns.map((column) => ({\n\t\tid: columnId(column),\n\t\tlabel: column.label,\n\t\talign: column.align ?? (isNumeric(column.format) ? 'right' : 'left'),\n\t\t...(column.width !== undefined && { width: column.width }),\n\t\t...(column.flex !== undefined && { flex: column.flex }),\n\t}));\n\tconst body = rows.map((row, i) =>\n\t\tcolumns.map((column) =>\n\t\t\ttoPrintValue(column, rawValue(column, row, i), row, options),\n\t\t),\n\t);\n\tconst footer = columns.some((column) => column.total)\n\t\t? [\n\t\t\t\tcolumns.map((column, index) =>\n\t\t\t\t\tindex === 0 && !column.total\n\t\t\t\t\t\t? (options.totalLabel ?? 'Total')\n\t\t\t\t\t\t: totalOf(column, rows, options),\n\t\t\t\t),\n\t\t\t]\n\t\t: [];\n\treturn { columns: resolved, rows: body, footer, empty: rows.length === 0 };\n}\n\n/** Label and value pairs for one row: a detail block from the same columns. */\nexport function resolvePrintFields<T>(\n\tfields: PrintColumn<T>[],\n\trow: T,\n\toptions: PrintTableOptions = {},\n): { label: string; value: PrintValue }[] {\n\treturn fields.map((field) => ({\n\t\tlabel: field.label,\n\t\tvalue: toPrintValue(field, rawValue(field, row, 0), row, options),\n\t}));\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "D31CA70FB327C98664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/pdf/http.ts
|
|
2
|
+
function contentDisposition2(disposition, fileName) {
|
|
3
|
+
const ascii = fileName.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
|
|
4
|
+
const encoded = encodeURIComponent(fileName).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
5
|
+
return `${disposition}; filename="${ascii}"; filename*=UTF-8''${encoded}`;
|
|
6
|
+
}
|
|
7
|
+
var withPdfExtension = (name) => /\.pdf$/i.test(name) ? name : `${name}.pdf`;
|
|
8
|
+
function pdfBytesResponse2(bytes, options = {}) {
|
|
9
|
+
const headers = new Headers(options.headers);
|
|
10
|
+
headers.set("Content-Type", "application/pdf");
|
|
11
|
+
headers.set("Content-Length", String(bytes.byteLength));
|
|
12
|
+
const name = withPdfExtension((options.fileName ?? "").replace(/[\\/]/g, "_").trim() || "document");
|
|
13
|
+
headers.set("Content-Disposition", contentDisposition2(options.disposition ?? "attachment", name));
|
|
14
|
+
return new Response(bytes, { headers });
|
|
15
|
+
}
|
|
16
|
+
function toWebStream2(source) {
|
|
17
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
18
|
+
const encoder = new TextEncoder;
|
|
19
|
+
return new ReadableStream({
|
|
20
|
+
async pull(controller) {
|
|
21
|
+
const { value, done } = await iterator.next();
|
|
22
|
+
if (done)
|
|
23
|
+
controller.close();
|
|
24
|
+
else
|
|
25
|
+
controller.enqueue(typeof value === "string" ? encoder.encode(value) : new Uint8Array(value));
|
|
26
|
+
},
|
|
27
|
+
async cancel() {
|
|
28
|
+
await iterator.return?.();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { contentDisposition2, pdfBytesResponse2, toWebStream2 };
|
|
34
|
+
|
|
35
|
+
//# debugId=3A91FB74B1A6197664756E2164756E21
|
|
36
|
+
//# sourceMappingURL=index-2gcgcmc0.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pdf/http.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Serving a rendered PDF, framework-free: the headers of a response and a\n * Web stream over react-pdf's Node stream. No `node:*` import, so a browser\n * bundle stays clean.\n */\n\nexport type PdfResponseOptions = {\n\t/** Name of the file; `.pdf` is added when missing. `document.pdf` by default. */\n\tfileName?: string;\n\t/** `'inline'` shows it in the browser; `'attachment'` (default) saves it. */\n\tdisposition?: 'inline' | 'attachment';\n\t/** More headers, such as `Cache-Control`. */\n\theaders?: HeadersInit;\n};\n\n/**\n * `Content-Disposition` with an ASCII `filename` every client reads and a\n * UTF-8 `filename*` for the rest (RFC 6266).\n */\nexport function contentDisposition(\n\tdisposition: 'inline' | 'attachment',\n\tfileName: string,\n): string {\n\tconst ascii = fileName.replace(/[^\\x20-\\x7e]/g, '_').replace(/[\"\\\\]/g, '_');\n\tconst encoded = encodeURIComponent(fileName).replace(\n\t\t/['()*]/g,\n\t\t(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n\t);\n\treturn `${disposition}; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n\nconst withPdfExtension = (name: string) =>\n\t/\\.pdf$/i.test(name) ? name : `${name}.pdf`;\n\n/** A Web `Response` carrying PDF bytes, with type, length and file name. */\nexport function pdfBytesResponse(\n\tbytes: Uint8Array<ArrayBuffer>,\n\toptions: PdfResponseOptions = {},\n): Response {\n\tconst headers = new Headers(options.headers);\n\theaders.set('Content-Type', 'application/pdf');\n\theaders.set('Content-Length', String(bytes.byteLength));\n\tconst name = withPdfExtension(\n\t\t(options.fileName ?? '').replace(/[\\\\/]/g, '_').trim() || 'document',\n\t);\n\theaders.set(\n\t\t'Content-Disposition',\n\t\tcontentDisposition(options.disposition ?? 'attachment', name),\n\t);\n\treturn new Response(bytes, { headers });\n}\n\n/** A Web `ReadableStream` over an async iterable of chunks (react-pdf's stream). */\nexport function toWebStream(\n\tsource: AsyncIterable<Uint8Array | string>,\n): ReadableStream<Uint8Array> {\n\tconst iterator = source[Symbol.asyncIterator]();\n\tconst encoder = new TextEncoder();\n\treturn new ReadableStream<Uint8Array>({\n\t\tasync pull(controller) {\n\t\t\tconst { value, done } = await iterator.next();\n\t\t\tif (done) controller.close();\n\t\t\telse\n\t\t\t\tcontroller.enqueue(\n\t\t\t\t\ttypeof value === 'string'\n\t\t\t\t\t\t? encoder.encode(value)\n\t\t\t\t\t\t: new Uint8Array(value),\n\t\t\t\t);\n\t\t},\n\t\tasync cancel() {\n\t\t\tawait iterator.return?.();\n\t\t},\n\t});\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAmBO,SAAS,mBAAkB,CACjC,aACA,UACS;AAAA,EACT,MAAM,QAAQ,SAAS,QAAQ,iBAAiB,GAAG,EAAE,QAAQ,UAAU,GAAG;AAAA,EAC1E,MAAM,UAAU,mBAAmB,QAAQ,EAAE,QAC5C,WACA,CAAC,MAAM,IAAI,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,YAAY,GACrD;AAAA,EACA,OAAO,GAAG,0BAA0B,4BAA4B;AAAA;AAGjE,IAAM,mBAAmB,CAAC,SACzB,UAAU,KAAK,IAAI,IAAI,OAAO,GAAG;AAG3B,SAAS,iBAAgB,CAC/B,OACA,UAA8B,CAAC,GACpB;AAAA,EACX,MAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;AAAA,EAC3C,QAAQ,IAAI,gBAAgB,iBAAiB;AAAA,EAC7C,QAAQ,IAAI,kBAAkB,OAAO,MAAM,UAAU,CAAC;AAAA,EACtD,MAAM,OAAO,kBACX,QAAQ,YAAY,IAAI,QAAQ,UAAU,GAAG,EAAE,KAAK,KAAK,UAC3D;AAAA,EACA,QAAQ,IACP,uBACA,oBAAmB,QAAQ,eAAe,cAAc,IAAI,CAC7D;AAAA,EACA,OAAO,IAAI,SAAS,OAAO,EAAE,QAAQ,CAAC;AAAA;AAIhC,SAAS,YAAW,CAC1B,QAC6B;AAAA,EAC7B,MAAM,WAAW,OAAO,OAAO,eAAe;AAAA,EAC9C,MAAM,UAAU,IAAI;AAAA,EACpB,OAAO,IAAI,eAA2B;AAAA,SAC/B,KAAI,CAAC,YAAY;AAAA,MACtB,QAAQ,OAAO,SAAS,MAAM,SAAS,KAAK;AAAA,MAC5C,IAAI;AAAA,QAAM,WAAW,MAAM;AAAA,MAE1B;AAAA,mBAAW,QACV,OAAO,UAAU,WACd,QAAQ,OAAO,KAAK,IACpB,IAAI,WAAW,KAAK,CACxB;AAAA;AAAA,SAEI,OAAM,GAAG;AAAA,MACd,MAAM,SAAS,SAAS;AAAA;AAAA,EAE1B,CAAC;AAAA;",
|
|
8
|
+
"debugId": "3A91FB74B1A6197664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/pdf/model/theme.ts
|
|
2
|
+
var defaultPdfTheme2 = {
|
|
3
|
+
colors: {
|
|
4
|
+
primary: "#485096",
|
|
5
|
+
secondary: "#347cb5",
|
|
6
|
+
text: "#0f172a",
|
|
7
|
+
muted: "#62748e",
|
|
8
|
+
border: "#e2e8f0",
|
|
9
|
+
headerBg: "#1e293b",
|
|
10
|
+
headerText: "#ffffff",
|
|
11
|
+
rowAlt: "#f8fafc",
|
|
12
|
+
logoBg: "#e2e8f0",
|
|
13
|
+
qrBg: "#f1f5f9",
|
|
14
|
+
success: "#15803d",
|
|
15
|
+
error: "#b91c1c"
|
|
16
|
+
},
|
|
17
|
+
fonts: { sans: "Helvetica", serif: "Times-Roman", mono: "Courier" }
|
|
18
|
+
};
|
|
19
|
+
function resolvePdfTheme2(overrides) {
|
|
20
|
+
if (!overrides)
|
|
21
|
+
return defaultPdfTheme2;
|
|
22
|
+
return {
|
|
23
|
+
colors: { ...defaultPdfTheme2.colors, ...overrides.colors },
|
|
24
|
+
fonts: { ...defaultPdfTheme2.fonts, ...overrides.fonts }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function resolvePdfColor2(color, theme) {
|
|
28
|
+
if (color === undefined)
|
|
29
|
+
return;
|
|
30
|
+
return Object.keys(theme.colors).includes(color) ? theme.colors[color] : color;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { defaultPdfTheme2, resolvePdfTheme2, resolvePdfColor2 };
|
|
34
|
+
|
|
35
|
+
//# debugId=6961231B3F4D7DCC64756E2164756E21
|
|
36
|
+
//# sourceMappingURL=index-3ca6xe71.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pdf/model/theme.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { PdfColor, PdfThemeColor, PdfThemeOverrides } from './types';\n\nexport type PdfTheme = {\n\tcolors: Record<PdfThemeColor, string>;\n\tfonts: Record<'sans' | 'serif' | 'mono', string>;\n};\n\n/**\n * The light screen theme, in colours react-pdf reads (it takes no oklch):\n * `primary`, `secondary`, `muted`, `border` and `qrBg` are the screen's\n * `--primary`, `--secondary`, `--muted-foreground`, `--border` and `--muted`.\n * Fonts are PDF standard fonts, so nothing has to be registered.\n */\nexport const defaultPdfTheme: PdfTheme = {\n\tcolors: {\n\t\tprimary: '#485096',\n\t\tsecondary: '#347cb5',\n\t\ttext: '#0f172a',\n\t\tmuted: '#62748e',\n\t\tborder: '#e2e8f0',\n\t\theaderBg: '#1e293b',\n\t\theaderText: '#ffffff',\n\t\trowAlt: '#f8fafc',\n\t\tlogoBg: '#e2e8f0',\n\t\tqrBg: '#f1f5f9',\n\t\tsuccess: '#15803d',\n\t\terror: '#b91c1c',\n\t},\n\tfonts: { sans: 'Helvetica', serif: 'Times-Roman', mono: 'Courier' },\n};\n\n/** The default theme with `overrides` applied. */\nexport function resolvePdfTheme(overrides?: PdfThemeOverrides): PdfTheme {\n\tif (!overrides) return defaultPdfTheme;\n\treturn {\n\t\tcolors: { ...defaultPdfTheme.colors, ...overrides.colors },\n\t\tfonts: { ...defaultPdfTheme.fonts, ...overrides.fonts },\n\t};\n}\n\n/** A theme colour name resolved to its value; any other colour as it is. */\nexport function resolvePdfColor(\n\tcolor: PdfColor | undefined,\n\ttheme: PdfTheme,\n): string | undefined {\n\tif (color === undefined) return undefined;\n\treturn Object.keys(theme.colors).includes(color)\n\t\t? theme.colors[color as PdfThemeColor]\n\t\t: color;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAaO,IAAM,mBAA4B;AAAA,EACxC,QAAQ;AAAA,IACP,SAAS;AAAA,IACT,WAAW;AAAA,IACX,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACR;AAAA,EACA,OAAO,EAAE,MAAM,aAAa,OAAO,eAAe,MAAM,UAAU;AACnE;AAGO,SAAS,gBAAe,CAAC,WAAyC;AAAA,EACxE,IAAI,CAAC;AAAA,IAAW,OAAO;AAAA,EACvB,OAAO;AAAA,IACN,QAAQ,KAAK,iBAAgB,WAAW,UAAU,OAAO;AAAA,IACzD,OAAO,KAAK,iBAAgB,UAAU,UAAU,MAAM;AAAA,EACvD;AAAA;AAIM,SAAS,gBAAe,CAC9B,OACA,OACqB;AAAA,EACrB,IAAI,UAAU;AAAA,IAAW;AAAA,EACzB,OAAO,OAAO,KAAK,MAAM,MAAM,EAAE,SAAS,KAAK,IAC5C,MAAM,OAAO,SACb;AAAA;",
|
|
8
|
+
"debugId": "6961231B3F4D7DCC64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/lib/pdf-source.ts
|
|
2
|
+
var tag = (value) => Object.prototype.toString.call(value);
|
|
3
|
+
function isArrayBuffer(value) {
|
|
4
|
+
return tag(value) === "[object ArrayBuffer]";
|
|
5
|
+
}
|
|
6
|
+
function isBytes(value) {
|
|
7
|
+
return isArrayBuffer(value) || ArrayBuffer.isView(value);
|
|
8
|
+
}
|
|
9
|
+
function isBlob2(value) {
|
|
10
|
+
if (typeof Blob !== "undefined" && value instanceof Blob)
|
|
11
|
+
return true;
|
|
12
|
+
const kind = tag(value);
|
|
13
|
+
return kind === "[object Blob]" || kind === "[object File]";
|
|
14
|
+
}
|
|
15
|
+
function isUrl(value) {
|
|
16
|
+
return typeof URL !== "undefined" && value instanceof URL;
|
|
17
|
+
}
|
|
18
|
+
function isPdfUrlSource2(value) {
|
|
19
|
+
return typeof value === "object" && value !== null && !isBytes(value) && !isBlob2(value) && !isUrl(value) && "url" in value;
|
|
20
|
+
}
|
|
21
|
+
function pdfSourceUrl2(source) {
|
|
22
|
+
if (typeof source === "string")
|
|
23
|
+
return source;
|
|
24
|
+
if (isUrl(source))
|
|
25
|
+
return source.href;
|
|
26
|
+
if (isPdfUrlSource2(source))
|
|
27
|
+
return String(source.url);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
var ids = new WeakMap;
|
|
31
|
+
var lastId = 0;
|
|
32
|
+
function identity(value) {
|
|
33
|
+
let id = ids.get(value);
|
|
34
|
+
if (id === undefined) {
|
|
35
|
+
lastId += 1;
|
|
36
|
+
id = lastId;
|
|
37
|
+
ids.set(value, id);
|
|
38
|
+
}
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
41
|
+
function pdfSourceKey2(source) {
|
|
42
|
+
if (source == null)
|
|
43
|
+
return "";
|
|
44
|
+
const url = pdfSourceUrl2(source);
|
|
45
|
+
if (url !== null) {
|
|
46
|
+
if (!isPdfUrlSource2(source))
|
|
47
|
+
return `url:${url}`;
|
|
48
|
+
const headers = Object.entries(source.httpHeaders ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
49
|
+
return `url+:${JSON.stringify([url, headers, Boolean(source.withCredentials)])}`;
|
|
50
|
+
}
|
|
51
|
+
if (typeof source !== "object")
|
|
52
|
+
return `invalid:${String(source)}`;
|
|
53
|
+
return `bytes:${identity(source)}`;
|
|
54
|
+
}
|
|
55
|
+
function copyPdfData2(data) {
|
|
56
|
+
if (data.byteLength === 0)
|
|
57
|
+
return new Uint8Array(0);
|
|
58
|
+
if (isArrayBuffer(data))
|
|
59
|
+
return new Uint8Array(data.slice(0));
|
|
60
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
|
|
61
|
+
}
|
|
62
|
+
function toPdfjsSource2(source) {
|
|
63
|
+
if (isBlob2(source))
|
|
64
|
+
return source;
|
|
65
|
+
if (isBytes(source)) {
|
|
66
|
+
return new Blob([copyPdfData2(source)], { type: "application/pdf" });
|
|
67
|
+
}
|
|
68
|
+
const url = pdfSourceUrl2(source);
|
|
69
|
+
if (url === null) {
|
|
70
|
+
throw new TypeError(`PdfReader cannot open ${tag(source)}: pass a URL, a Blob, an ArrayBuffer, a Uint8Array or { url, httpHeaders }.`);
|
|
71
|
+
}
|
|
72
|
+
if (!isPdfUrlSource2(source))
|
|
73
|
+
return { url };
|
|
74
|
+
return {
|
|
75
|
+
url,
|
|
76
|
+
...source.httpHeaders && { httpHeaders: source.httpHeaders },
|
|
77
|
+
...source.withCredentials && { withCredentials: true }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function toPdfjsParams2(source) {
|
|
81
|
+
const params = toPdfjsSource2(source);
|
|
82
|
+
if (!isBlob2(params))
|
|
83
|
+
return params;
|
|
84
|
+
return { data: new Uint8Array(await params.arrayBuffer()) };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { isBlob2, isPdfUrlSource2, pdfSourceUrl2, pdfSourceKey2, copyPdfData2, toPdfjsSource2, toPdfjsParams2 };
|
|
88
|
+
|
|
89
|
+
//# debugId=B1F232AD977E14CE64756E2164756E21
|
|
90
|
+
//# sourceMappingURL=index-5r6rzp43.js.map
|