@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 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 to render with, and
5
- data; it displays the rendered report on a white sheet in its shadow root and offers export
6
- downloads for the exportable targets you passed.
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. You pass the configured targets you want;
9
- the viewer displays the `"html"` target's output and wires an export button to each exportable
10
- target in the list (`"pdf"`, `"xlsx"`, `"csv"`). The bar always carries the zoom control; with no
11
- exportable target it carries nothing else.
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/html @quario/viewer
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
- beside it (`@quario/pdf`, `@quario/xlsx`, `@quario/csv`) and pass them in. Its own runtime dependency
21
- is [Lit](https://lit.dev) (`lit` + `@lit/task`), plain ESM like everything else here. ESM-only, and
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 = [html(), pdf(), xlsx(), csv()];
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 | Default |
56
- | ------------- | -------------------------------------------- | ----------- |
57
- | `report` | A compiled report from a quario instance | - |
58
- | `targets` | Target objects; one named `"html"` required | - |
59
- | `data` | The render document | `undefined` |
60
- | `zoom` | `"fit"` or a percentage between 25 and 200 | `"fit"` |
61
- | `page` | Sheet geometry (the pdf target's vocabulary) | A4, 54pt |
62
- | `filename` | Export download name, without extension | `"report"` |
63
- | `colorScheme` | `"light"`, `"dark"`, or `"auto"` | `"light"` |
64
-
65
- Properties, not attributes: `report`, `targets`, `data` and `page` are values no attribute could
66
- carry. `targets` mirrors `report.render(target, data)`: the `"html"` target renders the sheet, and
67
- `"pdf"`/`"xlsx"`/`"csv"` targets become export buttons, in the order given. The quario instance (and
68
- with it the license and the registry) stays yours: the element takes the compiled report, never a
69
- schema.
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` is the pdf target's vocabulary and defaults, and it is **purely visual**: it gives the white
77
- sheet the browser lays the report out on its width, its padding, and at least one page of height. The PDF target's own geometry lives
78
- in the host's `pdf({ page })` configuration. Pass the same values to both so preview and export
79
- agree.
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, `false` when
96
- it failed or there was nothing to render. It never rejects; failures arrive on the `error` event.
97
- Like every outcome here, it answers for the newest render only. A superseded render's failure is
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 sheet** on its own, then 25%, 50%, 75%, 100%, 150%
204
- and 200%. A check marks the current mode — Fit sheet whenever the viewer is fitting, whatever
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 the sheet to the viewer's width and **only ever shrinks**: given room to spare it stops at
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 sheet** is the only way back. A fitted viewer follows its own box, so a collapsing panel
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 shrinks or enlarges the rendered sheet like a
217
- photograph. Line breaks, column widths and point sizes stay what they are at 100%, so the sheet
218
- stays the document the pdf target will page. Pass the same `page` values to preview and export so
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
- ## What the preview is
222
-
223
- On-screen layout is the browser's: one continuous sheet, no pagination. The PDF export paginates
224
- independently in the pdf target. The preview approximates; the exports are exact.
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
- The sheet is page-shaped: `page` gives it both its width and at least one page of height, so a
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
- Rendering an unlicensed evaluation, rotated watermarks sit on the sheet one per page-sized band
238
- of the continuous preview and scroll and zoom with the report, the way the PDF export marks every
239
- page. The wording arrives inside the html target's output, on its `.q-unlicensed` badge; the viewer
240
- hides that element from sight (keeping it readable by assistive technology) and draws the stamps on
241
- the sheet itself. A licensed render carries neither.
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 above or below
249
- 100% the sheet is under a `transform: scale()`, so printing the page puts the viewer's chrome on
250
- paper at whatever zoom the reader happened to leave it. What a printer wants is the pdf target's
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 sheet stays white, watermark included.
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 sheet's edge
295
- (`--qv-sheet-shadow`). `--qv-mark` is the watermark face and does not follow the scheme.
300
+ `--qv-error-text`, `--qv-error-border`, `--qv-error-hover`), and the pages' edge
301
+ (`--qv-sheet-shadow`).
296
302
 
297
- The report itself uses the viewer's built-in `q-*` stylesheet, the screen sibling of
298
- `@quario/html/style.css`.
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 missing html target, a malformed option property — are
4
- * definition-shaped: they describe the embedding, not the report, so each
5
- * check here throws a `TypeError` naming the property and the expectation.
6
- * The element rethrows them from its render task, which is how one channel
7
- * (the error event and the panel) carries every failure a host can cause.
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 target the sheet displays: the one named `"html"`, checked along with
39
- * the shapes around it.
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 display = (report, targets) => {
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 sheet's geometry from the `page` property. Width and margin size the
85
- * sheet; height is the stamp period for the unlicensed watermark (full page
86
- * box, same as the PDF target margin does not shrink it).
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
- let size = page?.size ?? "A4";
93
- let dimensions = measure(size);
94
- if (!dimensions) fail('page.size: unknown page size "' + size + '"');
95
- let { width, height } = extents(dimensions);
96
- return { width, height, margin: inset(page, width, height) };
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
- * Two of the sixteen are the sheet's rather than the chrome's: a color scheme
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 and `--qv-mark` is scheme-free by design. They are in
30
- * the block because it has no exceptions, not because they follow the pin.
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
- * Sheet geometry, in PostScript points — the pdf target's `PdfPage`
6
- * vocabulary, because the sheet stands in visually for the page configured
7
- * there. Purely visual: pass the same values to `pdf({ page })` so preview
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 interface ViewerPage {
11
- /** Named size or `[width, height]` in points. Default `'A4'`. */
12
- size?: "A4" | "letter" | [number, number];
13
- /** Margin on all four sides, in points. Default `54` (0.75 in). */
14
- margin?: number;
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. Importing this module defines nothing
44
- * import `@quario/viewer/register` for the one-line define, or call
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 missing `"html"` target, a malformed option property)
49
- * throw a `TypeError` naming the property, delivered through the `error`
50
- * event and the error panel like every other failure; a report that could
51
- * not be rendered leaves the element up with the panel saying why. A
52
- * superseded render's failure is reported to no one.
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 configured targets, as passed to `report.render` one must be named
65
- * `"html"` to display; ones named `"pdf"`/`"xlsx"`/`"csv"` become
66
- * export buttons, in array order.
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
- * the sheet to the viewer's width and is shrink-only — it never enlarges
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
- /** Sheet geometry. The preview approximates; exports are exact. */
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, `false`
93
- * when it failed or there was nothing to render. Never rejects — failures
94
- * are the `error` event's — and like every outcome here it answers for
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