@quario/viewer 0.7.0 → 0.9.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
@@ -1,15 +1,70 @@
1
- # Changelog
2
-
3
- All notable changes to @quario/viewer are documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [0.7.0] - 2026-09-07
11
-
12
- ### Added
1
+ # @quario/viewer
2
+
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - The viewer offers a Word download. Pass a `docx` target in `targets`, and the bar carries a `DOCX`
8
+ button beside the others. The buttons keep the order you gave. The file carries the content type
9
+ `application/vnd.openxmlformats-officedocument.wordprocessingml.document`.
10
+
11
+ Earlier versions dropped a `docx` target. The viewer rendered no button for it, and it reported no
12
+ failure.
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies
17
+ - quario@0.9.0
18
+ - @quario/layout@0.6.0
19
+ - @quario/landing@0.2.1
20
+
21
+ ## 0.8.0
22
+
23
+ ### Minor Changes
24
+
25
+ - **An image's box is now as wide as the container it sits in.** A
26
+ `background` or `border*` on an image item hugged the picture and now spans
27
+ the content width, or the slot's share inside a split; the picture itself
28
+ does not move. See the `@quario/layout` changelog for the rule and for what
29
+ it costs a report that relied on a border hugging a logo.
30
+ - **Only the pages you can see are on the sheet.** A page outside the reach
31
+ used to be an empty canvas holding its place; now it has no element at all,
32
+ and the sheet carries the document's extent itself — its height and width are
33
+ written from the layout, and it paints the page silhouettes. The stretch a
34
+ reader scrolls through is exactly what it was, and a scale change no longer
35
+ costs the browser a relayout of every page: that was around 10 microseconds a
36
+ page, so a five-thousand-page report paid roughly 50 ms of it each time.
37
+
38
+ This changes the sheet's markup, which matters if you style it. `.qv-page` is
39
+ now absolutely positioned inside `.qv-sheet`, and is present only for the pages
40
+ on screen and one viewport height either side. A rule that gave a page a
41
+ margin, or that relied on the pages being a flow of siblings, no longer
42
+ applies — the sheet places its own pages, and does it with inline styles a
43
+ stylesheet rule cannot override. `--qv-sheet-shadow` is unchanged.
44
+
45
+ One cost, stated: a page outside the reach has no `role="img"` node, so
46
+ assistive technology sees the few images of the reach renumbering as the
47
+ reader scrolls rather than a document of named pages.
48
+
49
+ ### Patch Changes
50
+
51
+ - **Every published README says where the documentation is.** Each package now
52
+ carries a Documentation section pointing at the reference, at the report schema
53
+ that normatively specifies what a report may declare, and at the package's own
54
+ API. The paragraphs that used to end on an unstated contract — the event
55
+ stream's field semantics, the style vocabulary, page columns, the Content
56
+ Security Policy a fragment with images needs, the formula mangling, and each
57
+ target's own contract — link the page that states it. Every link is an absolute
58
+ URL, so it resolves from the npm package page as readily as from an installed
59
+ copy.
60
+ - Updated dependencies
61
+ - @quario/layout@0.5.0
62
+ - @quario/landing@0.2.0
63
+ - quario@0.8.0
64
+
65
+ ## 0.7.0
66
+
67
+ ### Minor Changes
13
68
 
14
69
  - **`fonts` is validated as a host property.** It was the one the viewer never
15
70
  checked, so a malformed record reached the target inside the render and came
@@ -18,9 +73,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
73
  on the target's `options.fonts`. A face that will not parse, or a missing
19
74
  parser, is still found while the report is measured and remains a render
20
75
  failure there.
21
-
22
- ### Changed
23
-
24
76
  - **A rejected host property reports as `host-option`.** `ViewerErrorKind`
25
77
  gains a fourth member, so a host switching exhaustively over the kind needs
26
78
  a case for it. A `page`, `zoom`, `filename`, `colorScheme` or `fonts` the
@@ -33,9 +85,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
33
85
  are refused where the render begins rather than when the property is written,
34
86
  so nothing yet tells them apart from a render that failed.
35
87
 
36
- ## [0.6.0] - 2026-09-07
88
+ ## 0.6.0
37
89
 
38
- ### Changed
90
+ ### Minor Changes
39
91
 
40
92
  - **Numbers presented through `format` now show a fixed two fraction digits**,
41
93
  the same as every other target: `1,000.00` where the preview showed `1,000`,
@@ -43,9 +95,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
43
95
  a universal two. The digits come from the engine, so a page on screen and
44
96
  the PDF of it agree. A formatted cell is also up to three characters wider,
45
97
  so a line that just fitted can wrap and move a page break.
46
-
47
- ### Fixed
48
-
49
98
  - **A page the browser cannot fully paint no longer fails the render.** An
50
99
  image whose pixel data is corrupt past the size in its header left that page
51
100
  blank _and_ rejected `renderComplete`, so a host awaiting it saw an unhandled
@@ -55,29 +104,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55
104
  fires, and no `error` event is raised. It never rejects for this reason
56
105
  again: a page that cannot be drawn is blank, not a failed render.
57
106
 
58
- ## [0.5.0] - 2026-09-05
107
+ ## 0.5.0
59
108
 
60
- ### Changed
109
+ ### Minor Changes
61
110
 
62
111
  - **Previewing with `fonts` now needs `fontkit`, not `@pdf-lib/fontkit`.**
63
112
  Install `fontkit` instead. The viewer measures host TrueType faces through
64
113
  `@quario/layout`, whose optional parser this is; the old package crashed on
65
114
  any OpenType face needing a shaping state machine, so those faces now
66
115
  preview where they used to throw.
67
-
68
116
  - **The bar's controls are drawn icons.** The zoom trigger, the menu's check
69
117
  and the error panel's dismiss are drawn from Lucide's set rather than by
70
118
  hand, at one weight and one grid, and the dismiss no longer relies on the
71
119
  host's font to draw a multiplication sign. They paint in `currentColor`, so
72
120
  `--qv-icon` and `--qv-icon-active` still recolour them.
73
-
74
121
  - **An export button names its format in words.** Each one is a download icon
75
122
  with `PDF`, `XLSX` or `CSV` beside it, in place of the sheet drawing that
76
123
  lettered the format inside itself. The buttons are wider; the accessible
77
124
  name is unchanged.
78
-
79
- ### Fixed
80
-
81
125
  - **A zoom no longer leaves a page part-drawn.** Changing the zoom while a
82
126
  page was still painting could leave that page carrying content at the old
83
127
  scale on a canvas sized for the new one — the rest of it blank — and nothing
@@ -85,27 +129,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
85
129
  whose paint is still in flight is now replaced rather than reused, so the
86
130
  superseded paint has nowhere to land.
87
131
 
88
- ## [0.4.0] - 2026-09-03
132
+ ## 0.4.0
89
133
 
90
- ### Added
134
+ ### Minor Changes
91
135
 
92
136
  - **`fonts`.** The font mapping a host passes to `pdf({ fonts })`, so the
93
137
  preview measures and draws in the same TrueType faces the document embeds.
94
-
95
- ### Changed
96
-
97
138
  - **`renderComplete` and `rendered` settle once the pages on screen are
98
139
  painted.** Both already answered for the newest render reaching the sheet;
99
140
  what that means is now stated: the pages the reader can see carry their
100
141
  pixels, and the pages further down the report do not hold the promise up.
101
142
  A screenshot or a pixel-reading test taken at that moment sees what the
102
143
  reader sees.
103
-
104
144
  - **A split slot's box now fills the split's height.** A slot's background and
105
145
  border stopped at that slot's own content and now take the split's height,
106
146
  the way a table cell's take their row's. See the `@quario/layout` changelog
107
147
  for the rule and for what it costs a report that relied on the short box.
108
-
109
148
  - **The sheet has pages, and they break where the PDF's do.** The viewer
110
149
  depends on `@quario/layout` and paints the same display list the pdf target
111
150
  writes, one canvas per page, in the faces the document itself uses.
@@ -115,9 +154,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
115
154
  `page` change re-lays the report out rather than resizing a sheet. The
116
155
  unlicensed marking is painted per page from the list; the `--qv-mark`
117
156
  token is gone with the DOM stamps.
118
-
119
- ### Fixed
120
-
121
157
  - **A long report no longer asks for a backing store per page.** The viewer
122
158
  paints the pages near the viewport — the ones on screen and one screenful
123
159
  either side — and keeps every other page sized but blank. A report of a
@@ -128,23 +164,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
128
164
  the first frame, and a zoom step now repaints a handful of pages instead of
129
165
  walking the document.
130
166
 
131
- ## [0.3.0] - 2026-09-02
167
+ ## 0.3.0
132
168
 
133
- ### Changed
169
+ ### Minor Changes
134
170
 
135
171
  - **Report and group containers are flex columns.** Adjacent authored
136
172
  `spaceBefore` / `spaceAfter` margins add rather than collapse, matching
137
173
  the html reference stylesheet.
138
-
139
- ### Fixed
140
-
141
174
  - **The adopted sheet occupies a line and preserves authored newlines.**
142
175
  Same `min-height: 1lh` and `white-space: pre-line` the html reference
143
176
  stylesheet now carries on `.q-item`, and `pre-line` on table cells.
144
177
 
145
- ## [0.2.0] - 2026-09-01
178
+ ## 0.2.0
146
179
 
147
- ### Changed
180
+ ### Minor Changes
148
181
 
149
182
  - **The sheet is now page-shaped, so short reports reserve a full page.**
150
183
  `page` sized the sheet's width and padding and ignored its height, so a
@@ -163,9 +196,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
163
196
  - **The adopted sheet's baseline face moved onto `.q-report`.** It was on
164
197
  `.q-item` and `.q-table`, where a direct rule would have beaten a report's
165
198
  own declared family. Same look; the fragment's new root is what carries it.
166
-
167
- ### Fixed
168
-
169
199
  - **The sheet no longer draws report text in the platform's own face.** It
170
200
  hard-coded `system-ui`, which is SF Pro on macOS, Segoe UI on Windows and
171
201
  Roboto on Android — so the same report showed a different typeface to every
@@ -174,9 +204,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
174
204
  resolves to Helvetica. The viewer's own chrome keeps `system-ui`, which is
175
205
  what a toolbar should do.
176
206
 
177
- ## [0.1.0] - 2026-08-27
207
+ ## 0.1.0
178
208
 
179
- ### Added
209
+ ### Minor Changes
180
210
 
181
211
  - **An embeddable `<quario-viewer>`.** Assign a compiled report, the targets
182
212
  to render with, and data; it shows the HTML report on a continuous sheet,
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  **The embeddable report viewer for [quario](https://www.npmjs.com/package/quario), as a custom
4
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
5
+ data. It lays the report out on pages — the same pages the PDF target writes — paints them on a
6
6
  white sheet in its shadow root, and offers export downloads for the targets you passed.
7
7
 
8
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.
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"`, `"docx"`). The bar always carries the zoom control. With no
11
+ exportable target it carries nothing else.
12
12
 
13
13
  ## Install
14
14
 
@@ -16,10 +16,10 @@ target it carries nothing else.
16
16
  npm install quario @quario/viewer
17
17
  ```
18
18
 
19
- The engine is a peer; the viewer itself depends on no target package. Install the targets you want
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
19
+ The engine is a peer. The viewer itself depends on no target package. Install the targets you want
20
+ to export with (`@quario/pdf`, `@quario/xlsx`, `@quario/csv`, `@quario/docx`) and pass them in. Its
21
+ own runtime dependencies are [Lit](https://lit.dev) (`lit` + `@lit/task`) and `@quario/layout`, the
22
+ paged layout it paints, plain ESM like everything else here. ESM-only, and
23
23
  browser-only by nature: the element needs a DOM. CSP-safe like the rest of quario. No
24
24
  string-to-code paths, chrome styled through constructed stylesheets, so your `style-src` never
25
25
  sees a style tag.
@@ -28,6 +28,7 @@ sees a style tag.
28
28
 
29
29
  ```js
30
30
  import { csv } from "@quario/csv";
31
+ import { docx } from "@quario/docx";
31
32
  import { pdf } from "@quario/pdf";
32
33
  import { xlsx } from "@quario/xlsx";
33
34
  import { quario } from "quario";
@@ -35,14 +36,14 @@ import "@quario/viewer/register";
35
36
 
36
37
  const view = document.querySelector("quario-viewer");
37
38
  view.report = quario().report(schema, funcs);
38
- view.targets = [pdf(), xlsx(), csv()];
39
+ view.targets = [pdf(), xlsx(), csv(), docx()];
39
40
  view.data = data;
40
41
  view.filename = "sales";
41
42
  ```
42
43
 
43
44
  with `<quario-viewer></quario-viewer>` in your markup, sized by your own CSS. The element is
44
- `display: block`, and its chrome is laid out to fit a width of **320px** a phone-width embed is
45
- the narrowest the bar is designed for. Narrower than that and the export buttons push the zoom
45
+ `display: block`, and its chrome fits a width of **320px**, a phone-width embed and the narrowest
46
+ the bar supports. Narrower than that and the export buttons push the zoom
46
47
  control off the left of the bar, quietly: the bar does not wrap or scroll. Importing
47
48
  `@quario/viewer` defines nothing: the main entry exports the `QuarioViewer` class and is
48
49
  side-effect-free, and `@quario/viewer/register` performs the one-line
@@ -67,14 +68,14 @@ package to install. See [Using with React, Vue and Svelte](#using-with-react-vue
67
68
  | `colorScheme` | `"light"`, `"dark"`, or `"auto"` | `"light"` |
68
69
 
69
70
  Properties, not attributes: `report`, `targets`, `data`, `page` and `fonts` are values no attribute
70
- could carry. `targets` mirrors `report.render(target, data)` for the exports: `"pdf"`/`"xlsx"`/`"csv"`
71
- targets become export buttons, in the order given, and the sheet needs none of them. The quario
72
- instance (and with it the license and the registry) stays yours: the element takes the compiled
73
- report, never a schema.
71
+ could carry. `targets` mirrors `report.render(target, data)` for the exports:
72
+ `"pdf"`/`"xlsx"`/`"csv"`/`"docx"` targets become export buttons, in the order given, and the
73
+ sheet needs none of them. The quario instance (and with it the license and the registry) stays
74
+ yours: the element takes the compiled report, never a schema.
74
75
 
75
76
  Assigning `data` (or any of the others) re-renders. Rapid successive writes render only the newest
76
77
  state: a slow render can never overwrite a newer one, and a superseded render fires no event.
77
- Assignments are compared by identity, so to re-render from the same object, assign a fresh one
78
+ The viewer compares assignments by identity, so to re-render from the same object, assign a fresh one
78
79
  (`view.data = { ...data }`).
79
80
 
80
81
  `page` and `fonts` are the pdf target's own options: the viewer lays the report out on them, so
@@ -90,32 +91,32 @@ view.addEventListener("error", ({ detail: { error, kind } }) => {});
90
91
  await view.renderComplete; // true when the newest render landed on the sheet
91
92
  ```
92
93
 
93
- `rendered` fires each time a render lands on the sheet; `error` fires for every failure you should
94
+ `rendered` fires each time a render lands on the sheet. `error` fires for every failure you should
94
95
  know about, with `detail.kind` naming which: `"mount-render"` until a render has ever landed,
95
96
  `"update-render"` after, `"export"` for a download that could not be produced. Both events are
96
- non-bubbling, like `<img>`'s. Listen on the element. (Because the event is named `error`, an
97
- inline `onerror` attribute on the element would fire too; `window.onerror` never sees it.)
97
+ non-bubbling, like `<img>`'s. Listen on the element. (Because the event carries the name `error`, an
98
+ inline `onerror` attribute on the element would fire too. `window.onerror` never sees it.)
98
99
 
99
100
  `renderComplete` awaits the newest render settling: `true` when it landed on the sheet with the pages
100
101
  on screen finished trying to paint, `false` when it failed or there was nothing to render. It never
101
- rejects; failures arrive on the `error` event. `rendered` fires at that same moment. Like every
102
- outcome here, it answers for the newest render only. A superseded render's failure is reported to no
102
+ rejects. Failures arrive on the `error` event. `rendered` fires at that same moment. Like every
103
+ outcome here, it answers for the newest render only. A superseded render's failure reaches no
103
104
  one.
104
105
 
105
106
  An image the browser cannot decode — pixel data corrupt past the size in its header, which is all
106
107
  the engine reads — is drawn as nothing, and the page is drawn around it. That is not a render
107
- failure: `renderComplete` answers `true`, `rendered` fires, and no `error` event is raised. A face in
108
- `fonts` is a different story: it is parsed while the report is measured, so bytes that will not parse
109
- are a render error with a panel.
108
+ failure: `renderComplete` answers `true`, `rendered` fires, and no `error` event follows. A face in
109
+ `fonts` is a different story. The layout parses it while it measures the report, so bytes that will
110
+ not parse are a render error with a panel.
110
111
 
111
112
  Your own mistakes surface on the same channel: a report that is not compiled, or a malformed
112
113
  option property, becomes a `TypeError` naming the property, on the `error` event and the
113
- [error panel](#errors). No particular target is required — the sheet is the layout's own.
114
+ [error panel](#errors). The viewer needs no particular target — the sheet is the layout's own.
114
115
 
115
116
  ## Lifecycle
116
117
 
117
118
  There is no `destroy()`. Removing the element from the DOM abandons in-flight work and releases
118
- its observers; an export that settles after removal downloads nothing. The properties persist, and
119
+ its observers. An export that settles after removal downloads nothing. The properties persist, and
119
120
  re-inserting the element re-renders from them, so reparenting is safe, and discarding a viewer is
120
121
  just discarding the element. Two viewers, or a viewer beside your own components, coexist: each
121
122
  element owns its own shadow root.
@@ -123,7 +124,7 @@ element owns its own shadow root.
123
124
  ## Using with React, Vue and Svelte
124
125
 
125
126
  No wrapper packages: each framework sets properties on custom elements directly, so the samples
126
- below are the whole integration. All three were run before landing here. Import
127
+ below are the whole integration. Each of the three ran before landing here. Import
127
128
  `@quario/viewer/register` once, anywhere before the component mounts, and size the element with
128
129
  your own CSS.
129
130
 
@@ -150,7 +151,7 @@ function Report({ report, targets, data }) {
150
151
  }
151
152
  ```
152
153
 
153
- Re-rendering with a new `data` prop re-renders the report; the newest write wins, as always.
154
+ Re-rendering with a new `data` prop re-renders the report. The newest write wins, as always.
154
155
  (React 18 and earlier stringify unknown props to attributes. There, hold a `ref` and assign the
155
156
  properties and listeners in an effect.)
156
157
 
@@ -213,46 +214,47 @@ listeners:
213
214
 
214
215
  The bar's magnifier opens the zoom menu: **Fit page** on its own, then 25%, 50%, 75%, 100%, 150%
215
216
  and 200%. A check marks the current mode — Fit page whenever the viewer is fitting, whatever
216
- percentage that came out at — and the percentage on screen is what the trigger is named after
217
+ percentage that came out at — and the trigger carries the percentage on screen in its name
217
218
  ("Zoom, 62%"), so nothing in the bar changes width as it moves. The `zoom` property picks the mode
218
219
  the viewer opens in: a percentage between 25 and 200 — continuous, not one of the menu's stops — or
219
220
  `"fit"`, the default. A percentage the menu does not offer leaves every row unchecked.
220
221
 
221
222
  Fit sizes one page to the viewer's width and **only ever shrinks**: given room to spare it stops at
222
- 100%, so the report is shown at its true point size rather than blown up. It has no floor, so a
223
+ 100%, so the report keeps its true point size rather than growing past it. It has no floor, so a
223
224
  narrow pane fits at whatever percentage that takes — below 25% the menu has no row to return to it,
224
225
  and **Fit page** is the only way back. A fitted viewer follows its own box, so a collapsing panel
225
226
  or a resized window re-fits on its own.
226
227
 
227
- The preview **scales; it never reflows.** Zooming repaints the pages larger or smaller, like a
228
+ The preview **scales. It never reflows.** Zooming repaints the pages larger or smaller, like a
228
229
  photograph that stays sharp. Line breaks, column widths and point sizes stay what they are at
229
230
  100%, because the layout never changes under a zoom.
230
231
 
231
- Only the pages near the viewport are painted — the ones on screen and one screenful either side.
232
+ The viewer paints only the pages near the viewport — the ones on screen and one screenful either side.
232
233
  Every page keeps its size, so the scrollbar and the scroll extent are the whole report from the
233
- start; a page further off is blank paper until you scroll to it, which is why a zoom step costs the
234
+ start. A page further off is blank paper until you scroll to it, which is why a zoom step costs the
234
235
  same on a thousand-page report as on a five-page one.
235
236
 
236
237
  ## What the preview is
237
238
 
238
239
  The pages on screen are the pages the PDF export writes: both consume one layout, `@quario/layout`'s
239
- display list, laid out on your `page` and `fonts`. A report shorter than a page is one page; a
240
- longer one is as many as the layout breaks it into, stacked down the sheet. Sizing a container to a
241
- viewer showing a short report therefore reserves a full page set `zoom` to a percentage small
242
- enough where the box has to stay small.
243
-
244
- Each page is a canvas, and text on it is drawn in the face the document will use. A TrueType
245
- family you pass as `fonts` is registered from your own bytes and drawn as the browser shapes it —
240
+ display list, laid out on your `page` and `fonts`. A report shorter than a page is one page. A
241
+ longer one is as many pages as the layout breaks it into, stacked down the sheet. Sizing a
242
+ container to a viewer that draws a short report therefore reserves a full page. Set `zoom` to a
243
+ percentage small enough where the box has to stay small.
244
+
245
+ Each page is a canvas, and text on it is drawn in the face the document will use. The viewer
246
+ registers a TrueType family you pass as `fonts` from your own bytes and draws it as the browser
247
+ shapes it —
246
248
  the same shaping the PDF gets from the same file, so ligatures, joined scripts and accents look
247
249
  here the way they will on paper. Text in the built-in families is drawn character by character at
248
250
  the advances the layout measured, because the font a browser has for Helvetica, Times or Courier
249
251
  only stands in for the one the PDF writes, and the correction is what makes a line fill the same
250
252
  width and break in the same place the document does. Inside one run of a family you supply, a
251
- browser may kern by a fraction more than the document; where lines break and pages end is the
252
- layout's, and identical. Images are decoded from their bytes — no `data:` URIs, so a host page's
253
+ browser may kern by a fraction more than the document. Where lines break and pages end is the
254
+ layout's, and identical. The viewer decodes images from their bytes — no `data:` URIs, so a host page's
253
255
  Content Security Policy needs no `img-src` grant for them.
254
256
 
255
- Rendering an unlicensed evaluation, the marking is painted across every page, the way the PDF
257
+ Rendering an unlicensed evaluation, the viewer paints the marking across every page, the way the PDF
256
258
  export marks every page: it rides on the layout, so what you see is what the document carries. A
257
259
  licensed render carries none.
258
260
 
@@ -262,8 +264,8 @@ reads `aria-busy="true"`. It reports that the viewer is working, not how far alo
262
264
  engine streams events and cannot know how many are still coming.
263
265
 
264
266
  There is no Print button, because the sheet is the wrong thing to print. Your page's stylesheets do
265
- not cross into the shadow root, so `@media print` rules never reach the report; and the pages are
266
- painted at whatever zoom the reader happened to leave them, so printing the page puts the viewer's
267
+ not cross into the shadow root, so `@media print` rules never reach the report. The viewer also
268
+ paints the pages at whatever zoom the reader happened to leave them, so printing the page puts the viewer's
267
269
  chrome on paper at that zoom. What a printer wants is the pdf target's
268
270
  document, the same bytes the PDF export hands over. A host that wants its own Print button owns
269
271
  two lines:
@@ -280,19 +282,23 @@ the export is, watermark and all.
280
282
 
281
283
  ## Errors
282
284
 
283
- When a render or an export fails, the viewer says so on the **error panel**, a strip across the top
284
- of the sheet carrying a short label — "Could not render the report", "Could not update the report — showing the previous version", or "Could not export PDF" — and the error's own message. It replaces rather than stacks, is
285
- dismissed by its own button, and is cleared by the next render that lands on the sheet. A successful
286
- export leaves it up, because the panel describes what you are looking at and a download says
287
- nothing about that.
285
+ When a render or an export fails, the viewer says so on the **error panel**. The panel is a strip
286
+ across the top of the sheet, carrying the error's own message under one of three short labels:
287
+
288
+ - "Could not render the report"
289
+ - "Could not update the report — showing the previous version"
290
+ - "Could not export PDF" It replaces rather than stacks, its own
291
+ button dismisses it, and the next render that lands on the sheet clears it. A successful
292
+ export leaves it up, because the panel describes what you are looking at and a download says
293
+ nothing about that.
288
294
 
289
- Every failure the panel draws, and every export failure, also fires the `error` event; nothing is
290
- rethrown to the platform behind it. Failures after the element is removed are not reported, and a
295
+ Every failure the panel draws, and every export failure, also fires the `error` event. The viewer rethrows
296
+ nothing to the platform behind it. Failures after the element leaves the DOM go unreported, and a
291
297
  render that a newer one has already superseded reports to no one at all.
292
298
 
293
299
  The message carries quario's **located error**, the band/item path and the offending source, as in
294
- `detail[0] [{{ @.amount.toFixed(2) }}]: ...`. Showing it is safe because report definitions are
295
- trusted configuration; report _data_ never is, and none of it appears in the path. What can carry
300
+ `detail[0] [{{ @.amount.toFixed(2) }}]: ...`. Drawing it is safe because report definitions are
301
+ trusted configuration. Report _data_ never is, and none of it appears in the path. What can carry
296
302
  data is the message itself, if your own registry functions interpolate a row into what they throw.
297
303
  That is your call, and the panel puts it on screen as text, never as markup. Compile errors are not
298
304
  part of this: `q.report(schema)` raises those before the viewer is ever handed a report.
@@ -300,7 +306,7 @@ part of this: `q.report(schema)` raises those before the viewer is ever handed a
300
306
  ## Color scheme
301
307
 
302
308
  `colorScheme` paints the **chrome** — backdrop, bar, controls, progress strip, error
303
- panel — and never the sheet. `"light"` (the default) and `"dark"` pin; `"auto"` follows
309
+ panel — and never the sheet. `"light"` (the default) and `"dark"` pin. `"auto"` follows
304
310
  the OS via CSS `color-scheme`. The pages stay white, marking included.
305
311
 
306
312
  Chrome styles live on `--qv-*` custom properties under stable `qv-*` class names. A
@@ -314,12 +320,19 @@ reference seam, not a frozen vocabulary: backdrop and bar (`--qv-backdrop`, `--q
314
320
  The report itself is paint, not markup: there is no report stylesheet to restyle, because what
315
321
  you see is the layout the PDF target writes.
316
322
 
323
+ ## Documentation
324
+
325
+ [The quario documentation](https://getquario.com/docs/) is the reference.
326
+ The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
327
+ specification of what a report may declare, and
328
+ [`@quario/viewer`](https://getquario.com/docs/reference/viewer/) is this element's own API.
329
+
317
330
  ## License
318
331
 
319
- Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
320
- licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
332
+ Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
333
+ Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
321
334
 
322
- Pass your license key once, on the instance; it is verified offline:
335
+ Pass your license key once, on the instance. quario verifies it offline:
323
336
 
324
337
  ```js
325
338
  const q = quario({ license: "quario_..." });
package/lib/icons.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * default, 14px in the gutter and the menu's tick slot — because `x` appears
5
5
  * in two contexts at once (ADR 0044).
6
6
  *
7
- * Generated by `scripts/icons.mjs` from lucide 1.40.0. Do not edit:
7
+ * Generated by `scripts/icons.mjs` from lucide 1.42.0. Do not edit:
8
8
  * `npm run icons` rewrites it, and `npm run check` regenerates it into a
9
9
  * temp directory and compares.
10
10
  *
package/lib/index.d.ts CHANGED
@@ -67,7 +67,7 @@ export class QuarioViewer extends LitElement {
67
67
  report: ViewableReport | undefined;
68
68
  /**
69
69
  * The export targets, as passed to `report.render`: ones named
70
- * `"pdf"`/`"xlsx"`/`"csv"` become export buttons, in array order. The
70
+ * `"pdf"`/`"xlsx"`/`"csv"`/`"docx"` become export buttons, in array order. The
71
71
  * sheet needs none of them — it is the layout's own. An empty array is
72
72
  * a viewer with nothing to export.
73
73
  */
package/lib/stage.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * The stage: the surface the pages are painted on. It owns the scroll
3
3
  * container the reader looks through, the wrapper that carries the gutter,
4
- * and the sheet — one canvas per page of the layout list, painted by
5
- * `@quario/layout`'s `paint` — their elements, their CSS, and every number
6
- * relating them.
4
+ * and the sheet — the layout list's whole extent, with a canvas for each page
5
+ * of the reach painted by `@quario/layout`'s `paint` — their elements, their
6
+ * CSS, and every number relating them.
7
7
  *
8
8
  * Zoom is a repaint, not a transform: each page canvas is sized to the
9
9
  * percentage in CSS pixels and painted at that scale times the device pixel
@@ -11,15 +11,17 @@
11
11
  * reader saw. The list never changes under a zoom — the preview scales and
12
12
  * never reflows (zoom.js says why).
13
13
  *
14
- * Every page keeps its CSS size, but only the
15
- * [reach](../../../CONTEXT.md#reach) carries pixels — the pages on screen and
16
- * one viewport height either side. The extent the reader scrolls through is
17
- * whole and synchronous whatever is painted; what comes and goes is the
18
- * backing store, released by sizing a canvas to 0 × 0. A scroll or a resize
19
- * runs the same arithmetic over the list's own geometry, coalesced to one
20
- * pass a frame. ADR 0043 says what that is worth: painting the whole sheet
21
- * asked a thousand-page report for gigabytes of backing store, and past what
22
- * the browser would grant the pages simply came up blank.
14
+ * The sheet carries the whole list's extent, but only the
15
+ * [reach](../../../CONTEXT.md#reach) carries elements and pixels — the pages
16
+ * on screen and one viewport height either side. The extent the reader scrolls
17
+ * through is whole and synchronous whatever is standing, because the sheet is
18
+ * given its height and width inline and paints the page silhouettes itself; a
19
+ * page leaving the reach is removed, which takes its backing store with it
20
+ * (ADR 0065). A scroll or a resize runs the same arithmetic over the list's own
21
+ * geometry, coalesced to one pass a frame. ADR 0043 says what the reach is
22
+ * worth: painting the whole sheet asked a thousand-page report for gigabytes of
23
+ * backing store, and past what the browser would grant the pages simply came up
24
+ * blank.
23
25
  *
24
26
  * **A page the browser will not paint is blank, not a failure.** `start`
25
27
  * swallows and nothing here rejects: the report laid out and the list reached
@@ -39,13 +41,14 @@
39
41
  * *retired* rather than painted over or emptied: it comes off the sheet, its
40
42
  * pixels go back, and a fresh one stands in its place, so the superseded
41
43
  * paint draws into an element nobody is looking at. A canvas whose paint has
42
- * settled has nothing that could land late and is re-sized or emptied in
43
- * place as before. Without this a zoom landing mid-paint could leave a page
44
+ * settled has nothing that could land late and is re-sized in place as before;
45
+ * a page leaving the reach is removed either way, which takes its pixels with
46
+ * it whether that paint has landed or not. Without this a zoom landing mid-paint could leave a page
44
47
  * carrying old-scale content on a new-scale canvas, with the memo below
45
48
  * calling it painted so that nothing repainted it again (ADR 0046).
46
49
  *
47
- * Retirement is lazy, and only the paint is guarded. A scale change sizes
48
- * every page's CSS box at once but retires a page only when the repaint loop
50
+ * Retirement is lazy, and only the paint is guarded. A scale change sizes the
51
+ * reach's CSS boxes at once but retires a page only when the repaint loop
49
52
  * reaches it, so a page further down the reach shows its old pixels stretched
50
53
  * into the new box until its turn comes — the ordinary look of a zoom in
51
54
  * progress, not the artefact above. The cost is that a retired page is a
@@ -70,12 +73,14 @@
70
73
  */
71
74
 
72
75
  import { css } from "lit";
73
- import { GAP, GUTTER, sheet } from "@quario/landing";
76
+ import { GUTTER, sheet } from "@quario/landing";
74
77
  import { PX_PER_POINT } from "@quario/layout";
75
78
 
76
- // `GUTTER` and `GAP` come from `@quario/landing`, which walks the reach over
77
- // them: the CSS below and the sheet's own arithmetic have to agree about where
78
- // a page sits, and two copies of that is a reach that paints the wrong pages.
79
+ // `GUTTER` comes from `@quario/landing`, which walks the reach over it: the CSS
80
+ // below and the sheet's own arithmetic have to agree about where a page sits,
81
+ // and two copies of that is a reach that paints the wrong pages. The gap
82
+ // between pages is that package's too, and is not named here at all — the sheet
83
+ // positions its own pages now, so this file has nothing to space.
79
84
 
80
85
  export let SURFACE = css`
81
86
  /* Where scrollbars take width, the gutter is held whether one is showing or
@@ -101,16 +106,17 @@ export let SURFACE = css`
101
106
  margin: ${GUTTER}px auto;
102
107
  }
103
108
 
104
- /* The sheet is the stack of pages. */
105
- .qv-sheet {
106
- display: flex;
107
- flex-direction: column;
108
- gap: ${GAP}px;
109
- }
109
+ /* The sheet is the whole document's extent, and the pages of the reach are
110
+ positioned inside it. It has no rule here because it has no look: its
111
+ position, its height, its width and its page silhouettes are all written
112
+ inline by \`@quario/landing\` from the list's own geometry, which is the one
113
+ place any of them is decided (ADR 0065). A page outside the reach has no
114
+ element, so what shows there is the sheet's own paint.
110
115
 
111
- /* One page. The painter fills it white; the background here is what a page
112
- outside the reach shows, and what one inside it shows between being sized
113
- and being painted. */
116
+ One page of the reach, at the size and offset the sheet gives it. The
117
+ painter fills it white; the background here is what it shows between being
118
+ placed and being painted, and the shadow is what lifts it off the sheet —
119
+ the two this file does decide. */
114
120
  .qv-page {
115
121
  display: block;
116
122
  background: #fff;
@@ -191,14 +197,17 @@ export let stage = () => {
191
197
  * offset; horizontally the wrapper is centred by auto margins while it
192
198
  * fits — which is exactly when `scrollLeft` is 0 anyway — and its margins
193
199
  * are 0 once it overflows, so the plain ratio holds wherever it can be
194
- * seen. The browser clamps whatever it cannot honour. An empty sheet has
195
- * no view to hold, which is what mounting at an authored zoom takes.
200
+ * seen. The browser clamps whatever it cannot honour. A sheet with no list
201
+ * on it has no view to hold, which is what mounting at an authored zoom
202
+ * takes. Asked of the list rather than of the sheet's children, which
203
+ * since ADR 0065 are the reach's and can be none of them while a list is
204
+ * standing — and it saves a DOM read besides.
196
205
  *
197
206
  * Only the reach is repainted, and only where the scale actually changed,
198
207
  * so a zoom step costs a handful of pages however long the report is.
199
208
  */
200
209
  scale: (percent) => {
201
- let held = sheetEl.firstChild && {
210
+ let held = paged.count() > 0 && {
202
211
  top: scroll.scrollTop,
203
212
  left: scroll.scrollLeft,
204
213
  height: scroll.clientHeight,
@@ -228,12 +237,12 @@ export let stage = () => {
228
237
 
229
238
  /**
230
239
  * Put a laid-out report on the sheet, keeping the reader where they
231
- * were: one canvas per page, the reach among them painted at the applied
232
- * scale. The order is the point of the method: the pages come off
233
- * `pageOf` already at their size, so the browser clamps the offsets going
234
- * back against the extent the sheet will have rather than the one it had
235
- * — and the reach is read from those offsets, so it is chosen after they
236
- * are in. What the
240
+ * were: the extent whole at once, with the reach's pages painted at the
241
+ * applied scale. The order is the point of the method: the sheet takes its
242
+ * height from the list before anything else, so the browser clamps the
243
+ * offsets going back against the extent the sheet will have rather than
244
+ * the one it had — and the reach is read from those offsets, so it is
245
+ * chosen after they are in. What the
237
246
  * returned promise settles behind is the reach, which is what the caller's
238
247
  * `renderComplete` means by "the pages on screen have finished trying to
239
248
  * paint" — and **it never rejects**, because `start` swallows a page the
@@ -241,8 +250,8 @@ export let stage = () => {
241
250
  */
242
251
  swap: async (next, faces) => {
243
252
  // Reading the offsets flushes layout, so only an actual reswap pays for
244
- // it: on an empty sheet there is nothing scrolled to preserve.
245
- let held = sheetEl.firstChild && {
253
+ // it: with no list standing there is nothing scrolled to preserve.
254
+ let held = paged.count() > 0 && {
246
255
  top: scroll.scrollTop,
247
256
  left: scroll.scrollLeft,
248
257
  };
package/lib/toolbar.js CHANGED
@@ -22,6 +22,7 @@ export let EXPORTS = {
22
22
  pdf: "application/pdf",
23
23
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
24
24
  csv: "text/csv",
25
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
25
26
  };
26
27
 
27
28
  // The anchor joins the document for the click — detached-anchor downloads
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/viewer",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "The embeddable report viewer shell for quario — in the makings, not yet released",
5
5
  "homepage": "https://getquario.com",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -44,22 +44,22 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@lit/task": "^1.0.3",
47
- "@quario/landing": "^0.1.0",
48
- "@quario/layout": "^0.4.0",
47
+ "@quario/landing": "^0.2.1",
48
+ "@quario/layout": "^0.6.0",
49
49
  "lit": "^3.3.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@arethetypeswrong/cli": "^0.18.3",
53
- "@cantoo/pdf-lib": "^2.9.1",
53
+ "@cantoo/pdf-lib": "~2.9.1",
54
54
  "@size-limit/preset-small-lib": "^13.0.3",
55
55
  "esbuild": "^0.28.2",
56
56
  "exceljs": "^4.4.0",
57
- "quario": "^0.7.0",
57
+ "quario": "^0.9.0",
58
58
  "size-limit": "^13.0.3",
59
59
  "typescript": "^7.0.2"
60
60
  },
61
61
  "peerDependencies": {
62
- "quario": "^0.7.0"
62
+ "quario": "^0.9.0"
63
63
  },
64
64
  "size-limit": [
65
65
  {