@quario/viewer 0.2.0 → 0.4.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/CHANGELOG.md +57 -0
- package/README.md +76 -70
- package/lib/check.js +18 -59
- package/lib/chrome.js +3 -4
- package/lib/index.d.ts +34 -26
- package/lib/index.js +57 -38
- package/lib/menu.js +3 -3
- package/lib/stage.js +272 -234
- package/package.json +5 -7
- package/lib/mark.js +0 -40
- package/lib/style.js +0 -107
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-09-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`fonts`.** The font mapping a host passes to `pdf({ fonts })`, so the
|
|
15
|
+
preview measures and draws in the same TrueType faces the document embeds.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`renderComplete` and `rendered` settle once the pages on screen are
|
|
20
|
+
painted.** Both already answered for the newest render reaching the sheet;
|
|
21
|
+
what that means is now stated: the pages the reader can see carry their
|
|
22
|
+
pixels, and the pages further down the report do not hold the promise up.
|
|
23
|
+
A screenshot or a pixel-reading test taken at that moment sees what the
|
|
24
|
+
reader sees.
|
|
25
|
+
|
|
26
|
+
- **A split slot's box now fills the split's height.** A slot's background and
|
|
27
|
+
border stopped at that slot's own content and now take the split's height,
|
|
28
|
+
the way a table cell's take their row's. See the `@quario/layout` changelog
|
|
29
|
+
for the rule and for what it costs a report that relied on the short box.
|
|
30
|
+
|
|
31
|
+
- **The sheet has pages, and they break where the PDF's do.** The viewer
|
|
32
|
+
depends on `@quario/layout` and paints the same display list the pdf target
|
|
33
|
+
writes, one canvas per page, in the faces the document itself uses.
|
|
34
|
+
Zoom is a repaint, so text stays crisp at 200%; the menu's first row reads
|
|
35
|
+
"Fit page" and fit sizes one page to the width. Breaking:
|
|
36
|
+
`targets` is exports only — no `"html"` target is required or read — and a
|
|
37
|
+
`page` change re-lays the report out rather than resizing a sheet. The
|
|
38
|
+
unlicensed marking is painted per page from the list; the `--qv-mark`
|
|
39
|
+
token is gone with the DOM stamps.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **A long report no longer asks for a backing store per page.** The viewer
|
|
44
|
+
paints the pages near the viewport — the ones on screen and one screenful
|
|
45
|
+
either side — and keeps every other page sized but blank. A report of a
|
|
46
|
+
few hundred pages used to ask the browser for gigabytes of canvas; past
|
|
47
|
+
what it would grant, pages came up blank with nothing reported, which on a
|
|
48
|
+
HiDPI display took only a moderately long report. Every page still holds
|
|
49
|
+
its place, so the scrollbar and the scroll extent are the whole report from
|
|
50
|
+
the first frame, and a zoom step now repaints a handful of pages instead of
|
|
51
|
+
walking the document.
|
|
52
|
+
|
|
53
|
+
## [0.3.0] - 2026-09-02
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- **Report and group containers are flex columns.** Adjacent authored
|
|
58
|
+
`spaceBefore` / `spaceAfter` margins add rather than collapse, matching
|
|
59
|
+
the html reference stylesheet.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **The adopted sheet occupies a line and preserves authored newlines.**
|
|
64
|
+
Same `min-height: 1lh` and `white-space: pre-line` the html reference
|
|
65
|
+
stylesheet now carries on `.q-item`, and `pre-line` on table cells.
|
|
66
|
+
|
|
10
67
|
## [0.2.0] - 2026-09-01
|
|
11
68
|
|
|
12
69
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
# @quario/viewer
|
|
2
2
|
|
|
3
3
|
**The embeddable report viewer for [quario](https://www.npmjs.com/package/quario), as a custom
|
|
4
|
-
element.** Write `<quario-viewer>`, assign it a compiled report, the targets
|
|
5
|
-
data; it
|
|
6
|
-
downloads for the
|
|
4
|
+
element.** Write `<quario-viewer>`, assign it a compiled report, the export targets you want, and
|
|
5
|
+
data; it lays the report out on pages — the same pages the PDF target writes — paints them on a
|
|
6
|
+
white sheet in its shadow root, and offers export downloads for the targets you passed.
|
|
7
7
|
|
|
8
|
-
The viewer is not a render target and compiles nothing.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
The viewer is not a render target and compiles nothing. The sheet is `@quario/layout`'s display
|
|
9
|
+
list, painted page by page; you pass the export targets you want, and the viewer wires a button
|
|
10
|
+
to each (`"pdf"`, `"xlsx"`, `"csv"`). The bar always carries the zoom control; with no exportable
|
|
11
|
+
target it carries nothing else.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install quario @quario/
|
|
16
|
+
npm install quario @quario/viewer
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
The engine is a peer; the viewer itself depends on no target package. Install the targets you want
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
to export with (`@quario/pdf`, `@quario/xlsx`, `@quario/csv`) and pass them in. Its own runtime
|
|
21
|
+
dependencies are [Lit](https://lit.dev) (`lit` + `@lit/task`) and `@quario/layout`, the paged
|
|
22
|
+
layout it paints, plain ESM like everything else here. ESM-only, and
|
|
22
23
|
browser-only by nature: the element needs a DOM. CSP-safe like the rest of quario. No
|
|
23
24
|
string-to-code paths, chrome styled through constructed stylesheets, so your `style-src` never
|
|
24
25
|
sees a style tag.
|
|
@@ -27,7 +28,6 @@ sees a style tag.
|
|
|
27
28
|
|
|
28
29
|
```js
|
|
29
30
|
import { csv } from "@quario/csv";
|
|
30
|
-
import { html } from "@quario/html";
|
|
31
31
|
import { pdf } from "@quario/pdf";
|
|
32
32
|
import { xlsx } from "@quario/xlsx";
|
|
33
33
|
import { quario } from "quario";
|
|
@@ -35,7 +35,7 @@ import "@quario/viewer/register";
|
|
|
35
35
|
|
|
36
36
|
const view = document.querySelector("quario-viewer");
|
|
37
37
|
view.report = quario().report(schema, funcs);
|
|
38
|
-
view.targets = [
|
|
38
|
+
view.targets = [pdf(), xlsx(), csv()];
|
|
39
39
|
view.data = data;
|
|
40
40
|
view.filename = "sales";
|
|
41
41
|
```
|
|
@@ -52,31 +52,32 @@ package to install. See [Using with React, Vue and Svelte](#using-with-react-vue
|
|
|
52
52
|
|
|
53
53
|
## Properties
|
|
54
54
|
|
|
55
|
-
| Property | Takes
|
|
56
|
-
| ------------- |
|
|
57
|
-
| `report` | A compiled report from a quario instance
|
|
58
|
-
| `targets` |
|
|
59
|
-
| `data` | The render document
|
|
60
|
-
| `zoom` | `"fit"` or a percentage between 25 and 200
|
|
61
|
-
| `page` |
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
`"pdf"`/`"xlsx"`/`"csv"`
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
| Property | Takes | Default |
|
|
56
|
+
| ------------- | ----------------------------------------------- | ----------- |
|
|
57
|
+
| `report` | A compiled report from a quario instance | - |
|
|
58
|
+
| `targets` | Export targets, possibly none | - |
|
|
59
|
+
| `data` | The render document | `undefined` |
|
|
60
|
+
| `zoom` | `"fit"` or a percentage between 25 and 200 | `"fit"` |
|
|
61
|
+
| `page` | Page geometry, as passed to `pdf({ page })` | A4, 54pt |
|
|
62
|
+
| `fonts` | The font mapping, as passed to `pdf({ fonts })` | none |
|
|
63
|
+
| `filename` | Export download name, without extension | `"report"` |
|
|
64
|
+
| `colorScheme` | `"light"`, `"dark"`, or `"auto"` | `"light"` |
|
|
65
|
+
|
|
66
|
+
Properties, not attributes: `report`, `targets`, `data`, `page` and `fonts` are values no attribute
|
|
67
|
+
could carry. `targets` mirrors `report.render(target, data)` for the exports: `"pdf"`/`"xlsx"`/`"csv"`
|
|
68
|
+
targets become export buttons, in the order given, and the sheet needs none of them. The quario
|
|
69
|
+
instance (and with it the license and the registry) stays yours: the element takes the compiled
|
|
70
|
+
report, never a schema.
|
|
70
71
|
|
|
71
72
|
Assigning `data` (or any of the others) re-renders. Rapid successive writes render only the newest
|
|
72
73
|
state: a slow render can never overwrite a newer one, and a superseded render fires no event.
|
|
73
74
|
Assignments are compared by identity, so to re-render from the same object, assign a fresh one
|
|
74
75
|
(`view.data = { ...data }`).
|
|
75
76
|
|
|
76
|
-
`page`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
`page` and `fonts` are the pdf target's own options: the viewer lays the report out on them, so
|
|
78
|
+
passing the same values to the element and to `pdf({ page, fonts })` is what makes the preview
|
|
79
|
+
page where the document pages, in the same faces. A `page` or `fonts` write re-lays the report
|
|
80
|
+
out.
|
|
80
81
|
|
|
81
82
|
## Events and `renderComplete`
|
|
82
83
|
|
|
@@ -92,10 +93,10 @@ know about, with `detail.kind` naming which: `"mount-render"` until a render has
|
|
|
92
93
|
non-bubbling, like `<img>`'s. Listen on the element. (Because the event is named `error`, an
|
|
93
94
|
inline `onerror` attribute on the element would fire too; `window.onerror` never sees it.)
|
|
94
95
|
|
|
95
|
-
`renderComplete` awaits the newest render settling: `true` when it reached the sheet
|
|
96
|
-
it failed or there was nothing to render. It never rejects; failures
|
|
97
|
-
|
|
98
|
-
reported to no one.
|
|
96
|
+
`renderComplete` awaits the newest render settling: `true` when it reached the sheet with the pages
|
|
97
|
+
on screen painted, `false` when it failed or there was nothing to render. It never rejects; failures
|
|
98
|
+
arrive on the `error` event. `rendered` fires at that same moment. Like every outcome here, it
|
|
99
|
+
answers for the newest render only. A superseded render's failure is reported to no one.
|
|
99
100
|
|
|
100
101
|
Your own mistakes surface on the same channel: a report that is not compiled, a missing
|
|
101
102
|
`"html"` target, or a malformed option property becomes a `TypeError` naming the property, on
|
|
@@ -200,54 +201,59 @@ listeners:
|
|
|
200
201
|
|
|
201
202
|
## Zoom
|
|
202
203
|
|
|
203
|
-
The bar's magnifier opens the zoom menu: **Fit
|
|
204
|
-
and 200%. A check marks the current mode — Fit
|
|
204
|
+
The bar's magnifier opens the zoom menu: **Fit page** on its own, then 25%, 50%, 75%, 100%, 150%
|
|
205
|
+
and 200%. A check marks the current mode — Fit page whenever the viewer is fitting, whatever
|
|
205
206
|
percentage that came out at — and the percentage on screen is what the trigger is named after
|
|
206
207
|
("Zoom, 62%"), so nothing in the bar changes width as it moves. The `zoom` property picks the mode
|
|
207
208
|
the viewer opens in: a percentage between 25 and 200 — continuous, not one of the menu's stops — or
|
|
208
209
|
`"fit"`, the default. A percentage the menu does not offer leaves every row unchecked.
|
|
209
210
|
|
|
210
|
-
Fit sizes
|
|
211
|
+
Fit sizes one page to the viewer's width and **only ever shrinks**: given room to spare it stops at
|
|
211
212
|
100%, so the report is shown at its true point size rather than blown up. It has no floor, so a
|
|
212
213
|
narrow pane fits at whatever percentage that takes — below 25% the menu has no row to return to it,
|
|
213
|
-
and **Fit
|
|
214
|
+
and **Fit page** is the only way back. A fitted viewer follows its own box, so a collapsing panel
|
|
214
215
|
or a resized window re-fits on its own.
|
|
215
216
|
|
|
216
|
-
The preview **scales; it never reflows.** Zooming
|
|
217
|
-
photograph. Line breaks, column widths and point sizes stay what they are at
|
|
218
|
-
|
|
219
|
-
they agree.
|
|
217
|
+
The preview **scales; it never reflows.** Zooming repaints the pages larger or smaller, like a
|
|
218
|
+
photograph that stays sharp. Line breaks, column widths and point sizes stay what they are at
|
|
219
|
+
100%, because the layout never changes under a zoom.
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
Only the pages near the viewport are painted — the ones on screen and one screenful either side.
|
|
222
|
+
Every page keeps its size, so the scrollbar and the scroll extent are the whole report from the
|
|
223
|
+
start; a page further off is blank paper until you scroll to it, which is why a zoom step costs the
|
|
224
|
+
same on a thousand-page report as on a five-page one.
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
report shorter than a page sits on paper the shape of the page you configured rather than on a band
|
|
228
|
-
the height of its own content. It is a minimum, so a longer report keeps growing on the one sheet.
|
|
229
|
-
Sizing a container to a viewer showing a short report therefore reserves a full page — set `zoom`
|
|
230
|
-
to a percentage small enough where the box has to stay small. The sheet still draws no page
|
|
231
|
-
boundaries: only the pdf target knows where pages fall.
|
|
232
|
-
|
|
233
|
-
The sheet is the html target's fragment, so a report with image items shows them through that
|
|
234
|
-
target's `data:` URIs. A host page with a Content Security Policy needs `img-src data:` for them
|
|
235
|
-
to display, the same deployment requirement the fragment carries anywhere else.
|
|
226
|
+
## What the preview is
|
|
236
227
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
228
|
+
The pages on screen are the pages the PDF export writes: both consume one layout, `@quario/layout`'s
|
|
229
|
+
display list, laid out on your `page` and `fonts`. A report shorter than a page is one page; a
|
|
230
|
+
longer one is as many as the layout breaks it into, stacked down the sheet. Sizing a container to a
|
|
231
|
+
viewer showing a short report therefore reserves a full page — set `zoom` to a percentage small
|
|
232
|
+
enough where the box has to stay small.
|
|
233
|
+
|
|
234
|
+
Each page is a canvas, and text on it is drawn in the face the document will use. A TrueType
|
|
235
|
+
family you pass as `fonts` is registered from your own bytes and drawn as the browser shapes it —
|
|
236
|
+
the same shaping the PDF gets from the same file, so ligatures, joined scripts and accents look
|
|
237
|
+
here the way they will on paper. Text in the built-in families is drawn character by character at
|
|
238
|
+
the advances the layout measured, because the font a browser has for Helvetica, Times or Courier
|
|
239
|
+
only stands in for the one the PDF writes, and the correction is what makes a line fill the same
|
|
240
|
+
width and break in the same place the document does. Inside one run of a family you supply, a
|
|
241
|
+
browser may kern by a fraction more than the document; where lines break and pages end is the
|
|
242
|
+
layout's, and identical. Images are decoded from their bytes — no `data:` URIs, so a host page's
|
|
243
|
+
Content Security Policy needs no `img-src` grant for them.
|
|
244
|
+
|
|
245
|
+
Rendering an unlicensed evaluation, the marking is painted across every page, the way the PDF
|
|
246
|
+
export marks every page: it rides on the layout, so what you see is what the document carries. A
|
|
247
|
+
licensed render carries none.
|
|
242
248
|
|
|
243
249
|
While a render is in flight a thin indeterminate bar sits on the toolbar's bottom edge, and the
|
|
244
250
|
viewer reads `aria-busy="true"`. It reports that the viewer is working, not how far along. The
|
|
245
251
|
engine streams events and cannot know how many are still coming.
|
|
246
252
|
|
|
247
253
|
There is no Print button, because the sheet is the wrong thing to print. Your page's stylesheets do
|
|
248
|
-
not cross into the shadow root, so `@media print` rules never reach the report; and
|
|
249
|
-
|
|
250
|
-
paper at
|
|
254
|
+
not cross into the shadow root, so `@media print` rules never reach the report; and the pages are
|
|
255
|
+
painted at whatever zoom the reader happened to leave them, so printing the page puts the viewer's
|
|
256
|
+
chrome on paper at that zoom. What a printer wants is the pdf target's
|
|
251
257
|
document, the same bytes the PDF export hands over. A host that wants its own Print button owns
|
|
252
258
|
two lines:
|
|
253
259
|
|
|
@@ -284,18 +290,18 @@ part of this: `q.report(schema)` raises those before the viewer is ever handed a
|
|
|
284
290
|
|
|
285
291
|
`colorScheme` paints the **chrome** — backdrop, bar, controls, progress strip, error
|
|
286
292
|
panel — and never the sheet. `"light"` (the default) and `"dark"` pin; `"auto"` follows
|
|
287
|
-
the OS via CSS `color-scheme`. The
|
|
293
|
+
the OS via CSS `color-scheme`. The pages stay white, marking included.
|
|
288
294
|
|
|
289
295
|
Chrome styles live on `--qv-*` custom properties under stable `qv-*` class names. A
|
|
290
296
|
token set on the element (or an ancestor) always wins over the pin. The names are a
|
|
291
297
|
reference seam, not a frozen vocabulary: backdrop and bar (`--qv-backdrop`, `--qv-bar`,
|
|
292
298
|
`--qv-border`, `--qv-text`), controls (`--qv-icon`, `--qv-icon-active`, `--qv-hover`,
|
|
293
299
|
`--qv-active`, `--qv-focus`), progress (`--qv-progress`), the error panel (`--qv-error`,
|
|
294
|
-
`--qv-error-text`, `--qv-error-border`, `--qv-error-hover`), and the
|
|
295
|
-
(`--qv-sheet-shadow`).
|
|
300
|
+
`--qv-error-text`, `--qv-error-border`, `--qv-error-hover`), and the pages' edge
|
|
301
|
+
(`--qv-sheet-shadow`).
|
|
296
302
|
|
|
297
|
-
The report itself
|
|
298
|
-
|
|
303
|
+
The report itself is paint, not markup: there is no report stylesheet to restyle, because what
|
|
304
|
+
you see is the layout the PDF target writes.
|
|
299
305
|
|
|
300
306
|
## License
|
|
301
307
|
|
package/lib/check.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What the host got wrong, named. The element's own mistakes — a report that
|
|
3
|
-
* is not compiled, a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* is not compiled, a malformed option property — are definition-shaped: they
|
|
4
|
+
* describe the embedding, not the report, so each check here throws a
|
|
5
|
+
* `TypeError` naming the property and the expectation. The element rethrows
|
|
6
|
+
* them from its render task, which is how one channel (the error event and
|
|
7
|
+
* the panel) carries every failure a host can cause.
|
|
8
8
|
*
|
|
9
9
|
* Pure policy, no DOM: this module is what the Node suite pins.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { pageBox } from "@quario/layout";
|
|
12
13
|
import { STEPS } from "./zoom.js";
|
|
13
14
|
|
|
14
15
|
/** @type {(message: string) => never} */
|
|
@@ -16,12 +17,6 @@ let fail = (message) => {
|
|
|
16
17
|
throw new TypeError("quario-viewer: " + message);
|
|
17
18
|
};
|
|
18
19
|
|
|
19
|
-
// The PdfPage vocabulary and defaults, mirrored from the pdf target: the
|
|
20
|
-
// sheet is a visual stand-in for the page the host configured there, so the
|
|
21
|
-
// same values must mean the same geometry.
|
|
22
|
-
/** @type {Record<string, [number, number]>} */
|
|
23
|
-
let SIZES = { A4: [595.28, 841.89], letter: [612, 792] };
|
|
24
|
-
|
|
25
20
|
let REPORT = [
|
|
26
21
|
(/** @type {any} */ report) => report && typeof report === "object",
|
|
27
22
|
(/** @type {any} */ report) => typeof report.stream === "function",
|
|
@@ -31,69 +26,33 @@ let REPORT = [
|
|
|
31
26
|
/** @type {(report: any) => boolean} */
|
|
32
27
|
let compiled = (report) => REPORT.every((check) => check(report));
|
|
33
28
|
|
|
34
|
-
/** @type {(target: any) => boolean} */
|
|
35
|
-
let htmlTarget = (target) => target?.name === "html";
|
|
36
|
-
|
|
37
29
|
/**
|
|
38
|
-
* The
|
|
39
|
-
* the
|
|
30
|
+
* The shapes a render needs: a compiled report, and the export targets as an
|
|
31
|
+
* array — possibly empty, since the sheet is the layout's own and needs no
|
|
32
|
+
* target of the host's.
|
|
40
33
|
*
|
|
41
34
|
* @param {any} report
|
|
42
35
|
* @param {any} targets
|
|
43
|
-
* @returns {any} The html target.
|
|
44
36
|
*/
|
|
45
|
-
export let
|
|
37
|
+
export let exports = (report, targets) => {
|
|
46
38
|
if (!compiled(report)) fail("report: expected a compiled report from a quario instance");
|
|
47
39
|
if (!Array.isArray(targets)) fail("targets: expected an array of render targets");
|
|
48
|
-
let found = targets.find(htmlTarget);
|
|
49
|
-
if (!found) fail('targets: no "html" target — include html() to display');
|
|
50
|
-
return found;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/** @type {(size: unknown) => [number, number] | false} */
|
|
54
|
-
let named = (size) =>
|
|
55
|
-
Object.hasOwn(SIZES, /** @type {string} */ (size)) && SIZES[/** @type {string} */ (size)];
|
|
56
|
-
|
|
57
|
-
/** @type {(size: unknown) => any} */
|
|
58
|
-
let measure = (size) => (Array.isArray(size) ? size : named(size));
|
|
59
|
-
|
|
60
|
-
/** @type {(n: number) => boolean} */
|
|
61
|
-
let positive = (n) => Number.isFinite(n) && n > 0;
|
|
62
|
-
|
|
63
|
-
/** @type {(dimensions: any) => { width: number, height: number }} */
|
|
64
|
-
let extents = (dimensions) => {
|
|
65
|
-
let width = +dimensions[0];
|
|
66
|
-
let height = +dimensions[1];
|
|
67
|
-
if (!positive(width) || !positive(height)) fail("page.size: expected finite positive dimensions");
|
|
68
|
-
return { width, height };
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/** @type {(margin: number, width: number, height: number) => boolean} */
|
|
72
|
-
let fits = (margin, width, height) =>
|
|
73
|
-
[Number.isFinite(margin), margin >= 0, 2 * margin < Math.min(width, height)].every(Boolean);
|
|
74
|
-
|
|
75
|
-
/** @type {(page: any, width: number, height: number) => number} */
|
|
76
|
-
let inset = (page, width, height) => {
|
|
77
|
-
let margin = page?.margin ?? 54;
|
|
78
|
-
if (!fits(margin, width, height))
|
|
79
|
-
fail("page.margin: expected a non-negative number smaller than half the page");
|
|
80
|
-
return margin;
|
|
81
40
|
};
|
|
82
41
|
|
|
83
42
|
/**
|
|
84
|
-
* The
|
|
85
|
-
*
|
|
86
|
-
*
|
|
43
|
+
* The page geometry from the `page` property: the layout's own vocabulary and
|
|
44
|
+
* defaults, so the same values mean the same pages on screen and on paper.
|
|
45
|
+
* The layout names the mistake; this names the property.
|
|
87
46
|
*
|
|
88
47
|
* @param {any} page
|
|
89
48
|
* @returns {{ width: number, height: number, margin: number }}
|
|
90
49
|
*/
|
|
91
50
|
export let geometry = (page) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
51
|
+
try {
|
|
52
|
+
return pageBox(page, "page");
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return fail(/** @type {Error} */ (error).message);
|
|
55
|
+
}
|
|
97
56
|
};
|
|
98
57
|
|
|
99
58
|
/** @type {(mode: any, floor: number, ceiling: number) => boolean} */
|
package/lib/chrome.js
CHANGED
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
* purpose: `:host` outranks that same host rule, so nothing outside can
|
|
25
25
|
* shadow an alias by accident.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
27
|
+
* One of the fifteen is the sheet's rather than the chrome's: a color scheme
|
|
28
28
|
* paints chrome only (ADR 0018), so `--qv-sheet-shadow` is the sheet's edge
|
|
29
|
-
* against the backdrop
|
|
30
|
-
*
|
|
29
|
+
* against the backdrop. It is in the block because it has no exceptions, not
|
|
30
|
+
* because it follows the pin.
|
|
31
31
|
* `--qv-focus` and `--qv-progress` repeat the accent rather than chain a third
|
|
32
32
|
* alias — a host is free to move one alone.
|
|
33
33
|
*
|
|
@@ -68,7 +68,6 @@ export let CHROME = css`
|
|
|
68
68
|
0 1px 2px light-dark(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.5)),
|
|
69
69
|
0 6px 24px light-dark(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.45))
|
|
70
70
|
);
|
|
71
|
-
--_mark: var(--qv-mark, #999);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
.qv-viewer {
|
package/lib/index.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import type { LitElement } from "lit";
|
|
2
|
+
import type { LayoutFonts, LayoutPage } from "@quario/layout";
|
|
2
3
|
import type { CompiledReport, Target } from "quario";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* and export agree.
|
|
6
|
+
* Page geometry, in PostScript points — the layout's own vocabulary, which
|
|
7
|
+
* the pdf target's `page` shares. The sheet is laid out on it: pass the same
|
|
8
|
+
* values to `pdf({ page })` and the preview pages where the document pages.
|
|
9
9
|
*/
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
export type ViewerPage = LayoutPage;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The font mapping: a declared `family` name to TrueType bytes, the same
|
|
14
|
+
* record a host passes to `pdf({ fonts })`. The layout measures against
|
|
15
|
+
* them and the sheet draws in them.
|
|
16
|
+
*/
|
|
17
|
+
export type ViewerFonts = LayoutFonts;
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* A compiled report, from a quario instance's `report()`. The instance —
|
|
@@ -40,16 +42,18 @@ export interface ViewerErrorDetail {
|
|
|
40
42
|
* The embeddable report viewer, `<quario-viewer>`. Assign `report`,
|
|
41
43
|
* `targets` and `data` as properties (they are values no attribute could
|
|
42
44
|
* carry) and listen for `rendered` and `error`; rapid successive writes
|
|
43
|
-
* render only the newest state.
|
|
44
|
-
*
|
|
45
|
+
* render only the newest state. The sheet is the report laid out on pages
|
|
46
|
+
* by `@quario/layout` — the list the pdf target writes — painted on one
|
|
47
|
+
* canvas per page. Importing this module defines nothing — import
|
|
48
|
+
* `@quario/viewer/register` for the one-line define, or call
|
|
45
49
|
* `customElements.define` with a tag of your own.
|
|
46
50
|
*
|
|
47
51
|
* Failures split along one line: the host's own mistakes (a report that is
|
|
48
|
-
* not compiled, a
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
52
|
+
* not compiled, a malformed option property) throw a `TypeError` naming the
|
|
53
|
+
* property, delivered through the `error` event and the error panel like
|
|
54
|
+
* every other failure; a report that could not be rendered leaves the
|
|
55
|
+
* element up with the panel saying why. A superseded render's failure is
|
|
56
|
+
* reported to no one.
|
|
53
57
|
*
|
|
54
58
|
* Removing the element abandons in-flight work and releases its observers;
|
|
55
59
|
* the properties persist, and reconnecting re-renders from them. There is no
|
|
@@ -61,16 +65,17 @@ export class QuarioViewer extends LitElement {
|
|
|
61
65
|
/** The compiled report to display and export. */
|
|
62
66
|
report: ViewableReport | undefined;
|
|
63
67
|
/**
|
|
64
|
-
* The
|
|
65
|
-
* `"
|
|
66
|
-
*
|
|
68
|
+
* The export targets, as passed to `report.render`: ones named
|
|
69
|
+
* `"pdf"`/`"xlsx"`/`"csv"` become export buttons, in array order. The
|
|
70
|
+
* sheet needs none of them — it is the layout's own. An empty array is
|
|
71
|
+
* a viewer with nothing to export.
|
|
67
72
|
*/
|
|
68
73
|
targets: readonly Target[] | undefined;
|
|
69
74
|
/** The render document. Assigning re-renders; the newest write wins. */
|
|
70
75
|
data: unknown;
|
|
71
76
|
/**
|
|
72
77
|
* The zoom to open at: `'fit'` (the default) or a percentage. Fit sizes
|
|
73
|
-
*
|
|
78
|
+
* one page to the viewer's width and is shrink-only — it never enlarges
|
|
74
79
|
* past 100%, so a viewer with room to spare shows the report at its true
|
|
75
80
|
* point size. An authored percentage must be between 25 and 200, and is
|
|
76
81
|
* continuous in that range rather than one of the zoom menu's own stops;
|
|
@@ -79,8 +84,10 @@ export class QuarioViewer extends LitElement {
|
|
|
79
84
|
* computes them.
|
|
80
85
|
*/
|
|
81
86
|
zoom: "fit" | number | undefined;
|
|
82
|
-
/**
|
|
87
|
+
/** Page geometry. Pass the same values to `pdf({ page })`. */
|
|
83
88
|
page: ViewerPage | undefined;
|
|
89
|
+
/** Font mapping. Pass the same record to `pdf({ fonts })`. */
|
|
90
|
+
fonts: ViewerFonts | undefined;
|
|
84
91
|
/** Export download name, without extension. Default `'report'`. */
|
|
85
92
|
filename: string | undefined;
|
|
86
93
|
/**
|
|
@@ -89,10 +96,11 @@ export class QuarioViewer extends LitElement {
|
|
|
89
96
|
*/
|
|
90
97
|
colorScheme: "light" | "dark" | "auto" | undefined;
|
|
91
98
|
/**
|
|
92
|
-
* The newest render settling: `true` when it reached the sheet
|
|
93
|
-
* when it failed or there was nothing to
|
|
94
|
-
* are the `error` event's — and like every
|
|
95
|
-
* the newest render only.
|
|
99
|
+
* The newest render settling: `true` when it reached the sheet with the
|
|
100
|
+
* pages on screen painted, `false` when it failed or there was nothing to
|
|
101
|
+
* render. Never rejects — failures are the `error` event's — and like every
|
|
102
|
+
* outcome here it answers for the newest render only. `rendered` fires at
|
|
103
|
+
* that same moment.
|
|
96
104
|
*/
|
|
97
105
|
get renderComplete(): Promise<boolean>;
|
|
98
106
|
|