@quario/viewer 0.8.0 → 0.10.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,5 +1,42 @@
1
1
  # @quario/viewer
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **A viewer you insert again renders once more, unless its last render landed on the sheet.** It used to do that only for a render that stopped mid-flight, which left three ways for the newest render to go missing with nothing owed.
8
+
9
+ A render that failed while the element was out of the document is one of them. The error panel reaches nobody there, so the verdict reached nobody, and the element came back showing the previous pages with no panel and no re-render owed. A property the host rejected while the element was out of the document behaved the same way. So did a property written while it was out of the document.
10
+
11
+ All three now leave the render owed, and inserting the element collects it. What you see on reconnect is the panel the failure earns, or the pages the newest properties produce.
12
+
13
+ A failure the panel already showed is owed again too. Removing and inserting such an element re-runs the render and reports the same failure once. It is not announced twice.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies
18
+ - @quario/layout@0.7.0
19
+ - quario@0.10.0
20
+ - @quario/landing@0.3.0
21
+
22
+ ## 0.9.0
23
+
24
+ ### Minor Changes
25
+
26
+ - The viewer offers a Word download. Pass a `docx` target in `targets`, and the bar carries a `DOCX`
27
+ button beside the others. The buttons keep the order you gave. The file carries the content type
28
+ `application/vnd.openxmlformats-officedocument.wordprocessingml.document`.
29
+
30
+ Earlier versions dropped a `docx` target. The viewer rendered no button for it, and it reported no
31
+ failure.
32
+
33
+ ### Patch Changes
34
+
35
+ - Updated dependencies
36
+ - quario@0.9.0
37
+ - @quario/layout@0.6.0
38
+ - @quario/landing@0.2.1
39
+
3
40
  ## 0.8.0
4
41
 
5
42
  ### Minor Changes
package/README.md CHANGED
@@ -2,13 +2,28 @@
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
+
13
+ ## Contents
14
+
15
+ - [Install](#install)
16
+ - [Quick start](#quick-start)
17
+ - [Properties](#properties)
18
+ - [Events and `renderComplete`](#events-and-rendercomplete)
19
+ - [Lifecycle](#lifecycle)
20
+ - [Using with React, Vue and Svelte](#using-with-react-vue-and-svelte)
21
+ - [Zoom](#zoom)
22
+ - [What the preview is](#what-the-preview-is)
23
+ - [Errors](#errors)
24
+ - [Color scheme](#color-scheme)
25
+ - [Documentation](#documentation)
26
+ - [License](#license)
12
27
 
13
28
  ## Install
14
29
 
@@ -16,10 +31,10 @@ target it carries nothing else.
16
31
  npm install quario @quario/viewer
17
32
  ```
18
33
 
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
34
+ The engine is a peer. The viewer itself depends on no target package. Install the targets you want
35
+ to export with (`@quario/pdf`, `@quario/xlsx`, `@quario/csv`, `@quario/docx`) and pass them in. Its
36
+ own runtime dependencies are [Lit](https://lit.dev) (`lit` + `@lit/task`) and `@quario/layout`, the
37
+ paged layout it paints, plain ESM like everything else here. ESM-only, and
23
38
  browser-only by nature: the element needs a DOM. CSP-safe like the rest of quario. No
24
39
  string-to-code paths, chrome styled through constructed stylesheets, so your `style-src` never
25
40
  sees a style tag.
@@ -28,6 +43,7 @@ sees a style tag.
28
43
 
29
44
  ```js
30
45
  import { csv } from "@quario/csv";
46
+ import { docx } from "@quario/docx";
31
47
  import { pdf } from "@quario/pdf";
32
48
  import { xlsx } from "@quario/xlsx";
33
49
  import { quario } from "quario";
@@ -35,14 +51,14 @@ import "@quario/viewer/register";
35
51
 
36
52
  const view = document.querySelector("quario-viewer");
37
53
  view.report = quario().report(schema, funcs);
38
- view.targets = [pdf(), xlsx(), csv()];
54
+ view.targets = [pdf(), xlsx(), csv(), docx()];
39
55
  view.data = data;
40
56
  view.filename = "sales";
41
57
  ```
42
58
 
43
59
  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
60
+ `display: block`, and its chrome fits a width of **320px**, a phone-width embed and the narrowest
61
+ the bar supports. Narrower than that and the export buttons push the zoom
46
62
  control off the left of the bar, quietly: the bar does not wrap or scroll. Importing
47
63
  `@quario/viewer` defines nothing: the main entry exports the `QuarioViewer` class and is
48
64
  side-effect-free, and `@quario/viewer/register` performs the one-line
@@ -67,14 +83,14 @@ package to install. See [Using with React, Vue and Svelte](#using-with-react-vue
67
83
  | `colorScheme` | `"light"`, `"dark"`, or `"auto"` | `"light"` |
68
84
 
69
85
  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.
86
+ could carry. `targets` mirrors `report.render(target, data)` for the exports:
87
+ `"pdf"`/`"xlsx"`/`"csv"`/`"docx"` targets become export buttons, in the order given, and the
88
+ sheet needs none of them. The quario instance (and with it the license and the registry) stays
89
+ yours: the element takes the compiled report, never a schema.
74
90
 
75
91
  Assigning `data` (or any of the others) re-renders. Rapid successive writes render only the newest
76
92
  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
93
+ The viewer compares assignments by identity, so to re-render from the same object, assign a fresh one
78
94
  (`view.data = { ...data }`).
79
95
 
80
96
  `page` and `fonts` are the pdf target's own options: the viewer lays the report out on them, so
@@ -90,40 +106,41 @@ view.addEventListener("error", ({ detail: { error, kind } }) => {});
90
106
  await view.renderComplete; // true when the newest render landed on the sheet
91
107
  ```
92
108
 
93
- `rendered` fires each time a render lands on the sheet; `error` fires for every failure you should
109
+ `rendered` fires each time a render lands on the sheet. `error` fires for every failure you should
94
110
  know about, with `detail.kind` naming which: `"mount-render"` until a render has ever landed,
95
111
  `"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.)
112
+ non-bubbling, like `<img>`'s. Listen on the element. (Because the event carries the name `error`, an
113
+ inline `onerror` attribute on the element would fire too. `window.onerror` never sees it.)
98
114
 
99
115
  `renderComplete` awaits the newest render settling: `true` when it landed on the sheet with the pages
100
116
  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
117
+ rejects. Failures arrive on the `error` event. `rendered` fires at that same moment. Like every
118
+ outcome here, it answers for the newest render only. A superseded render's failure reaches no
103
119
  one.
104
120
 
105
121
  An image the browser cannot decode — pixel data corrupt past the size in its header, which is all
106
122
  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.
123
+ failure: `renderComplete` answers `true`, `rendered` fires, and no `error` event follows. A face in
124
+ `fonts` is a different story. The layout parses it while it measures the report, so bytes that will
125
+ not parse are a render error with a panel.
110
126
 
111
127
  Your own mistakes surface on the same channel: a report that is not compiled, or a malformed
112
128
  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.
129
+ [error panel](#errors). The viewer needs no particular target — the sheet is the layout's own.
114
130
 
115
131
  ## Lifecycle
116
132
 
117
- 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
- re-inserting the element re-renders from them, so reparenting is safe, and discarding a viewer is
120
- just discarding the element. Two viewers, or a viewer beside your own components, coexist: each
121
- element owns its own shadow root.
133
+ There is no `destroy()`. Removal from the DOM stops the work in flight and releases the
134
+ observers. An export that settles after removal downloads nothing. The properties and the pages
135
+ persist. The element still owes a render that did not land on the sheet. It therefore renders
136
+ once more when you insert it, unless its last render landed. Reparenting is safe, and you
137
+ discard a viewer by discarding the element. Two viewers, or a viewer beside your own components,
138
+ coexist: each element owns its own shadow root.
122
139
 
123
140
  ## Using with React, Vue and Svelte
124
141
 
125
142
  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
143
+ below are the whole integration. Each of the three ran before landing here. Import
127
144
  `@quario/viewer/register` once, anywhere before the component mounts, and size the element with
128
145
  your own CSS.
129
146
 
@@ -150,7 +167,7 @@ function Report({ report, targets, data }) {
150
167
  }
151
168
  ```
152
169
 
153
- Re-rendering with a new `data` prop re-renders the report; the newest write wins, as always.
170
+ Re-rendering with a new `data` prop re-renders the report. The newest write wins, as always.
154
171
  (React 18 and earlier stringify unknown props to attributes. There, hold a `ref` and assign the
155
172
  properties and listeners in an effect.)
156
173
 
@@ -213,46 +230,47 @@ listeners:
213
230
 
214
231
  The bar's magnifier opens the zoom menu: **Fit page** on its own, then 25%, 50%, 75%, 100%, 150%
215
232
  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
233
+ percentage that came out at — and the trigger carries the percentage on screen in its name
217
234
  ("Zoom, 62%"), so nothing in the bar changes width as it moves. The `zoom` property picks the mode
218
235
  the viewer opens in: a percentage between 25 and 200 — continuous, not one of the menu's stops — or
219
236
  `"fit"`, the default. A percentage the menu does not offer leaves every row unchecked.
220
237
 
221
238
  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
239
+ 100%, so the report keeps its true point size rather than growing past it. It has no floor, so a
223
240
  narrow pane fits at whatever percentage that takes — below 25% the menu has no row to return to it,
224
241
  and **Fit page** is the only way back. A fitted viewer follows its own box, so a collapsing panel
225
242
  or a resized window re-fits on its own.
226
243
 
227
- The preview **scales; it never reflows.** Zooming repaints the pages larger or smaller, like a
244
+ The preview **scales. It never reflows.** Zooming repaints the pages larger or smaller, like a
228
245
  photograph that stays sharp. Line breaks, column widths and point sizes stay what they are at
229
246
  100%, because the layout never changes under a zoom.
230
247
 
231
- Only the pages near the viewport are painted — the ones on screen and one screenful either side.
248
+ The viewer paints only the pages near the viewport — the ones on screen and one screenful either side.
232
249
  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
250
+ start. A page further off is blank paper until you scroll to it, which is why a zoom step costs the
234
251
  same on a thousand-page report as on a five-page one.
235
252
 
236
253
  ## What the preview is
237
254
 
238
255
  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 —
256
+ display list, laid out on your `page` and `fonts`. A report shorter than a page is one page. A
257
+ longer one is as many pages as the layout breaks it into, stacked down the sheet. Sizing a
258
+ container to a viewer that draws a short report therefore reserves a full page. Set `zoom` to a
259
+ percentage small enough where the box has to stay small.
260
+
261
+ Each page is a canvas, and text on it is drawn in the face the document will use. The viewer
262
+ registers a TrueType family you pass as `fonts` from your own bytes and draws it as the browser
263
+ shapes it —
246
264
  the same shaping the PDF gets from the same file, so ligatures, joined scripts and accents look
247
265
  here the way they will on paper. Text in the built-in families is drawn character by character at
248
266
  the advances the layout measured, because the font a browser has for Helvetica, Times or Courier
249
267
  only stands in for the one the PDF writes, and the correction is what makes a line fill the same
250
268
  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
269
+ browser may kern by a fraction more than the document. Where lines break and pages end is the
270
+ layout's, and identical. The viewer decodes images from their bytes — no `data:` URIs, so a host page's
253
271
  Content Security Policy needs no `img-src` grant for them.
254
272
 
255
- Rendering an unlicensed evaluation, the marking is painted across every page, the way the PDF
273
+ Rendering an unlicensed evaluation, the viewer paints the marking across every page, the way the PDF
256
274
  export marks every page: it rides on the layout, so what you see is what the document carries. A
257
275
  licensed render carries none.
258
276
 
@@ -262,8 +280,8 @@ reads `aria-busy="true"`. It reports that the viewer is working, not how far alo
262
280
  engine streams events and cannot know how many are still coming.
263
281
 
264
282
  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
283
+ not cross into the shadow root, so `@media print` rules never reach the report. The viewer also
284
+ paints the pages at whatever zoom the reader happened to leave them, so printing the page puts the viewer's
267
285
  chrome on paper at that zoom. What a printer wants is the pdf target's
268
286
  document, the same bytes the PDF export hands over. A host that wants its own Print button owns
269
287
  two lines:
@@ -280,19 +298,23 @@ the export is, watermark and all.
280
298
 
281
299
  ## Errors
282
300
 
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.
301
+ When a render or an export fails, the viewer says so on the **error panel**. The panel is a strip
302
+ across the top of the sheet, carrying the error's own message under one of three short labels:
303
+
304
+ - "Could not render the report"
305
+ - "Could not update the report — showing the previous version"
306
+ - "Could not export PDF" It replaces rather than stacks, its own
307
+ button dismisses it, and the next render that lands on the sheet clears it. A successful
308
+ export leaves it up, because the panel describes what you are looking at and a download says
309
+ nothing about that.
288
310
 
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
311
+ Every failure the panel draws, and every export failure, also fires the `error` event. The viewer rethrows
312
+ nothing to the platform behind it. Failures after the element leaves the DOM go unreported, and a
291
313
  render that a newer one has already superseded reports to no one at all.
292
314
 
293
315
  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
316
+ `detail[0] [{{ @.amount.toFixed(2) }}]: ...`. Drawing it is safe because report definitions are
317
+ trusted configuration. Report _data_ never is, and none of it appears in the path. What can carry
296
318
  data is the message itself, if your own registry functions interpolate a row into what they throw.
297
319
  That is your call, and the panel puts it on screen as text, never as markup. Compile errors are not
298
320
  part of this: `q.report(schema)` raises those before the viewer is ever handed a report.
@@ -300,7 +322,7 @@ part of this: `q.report(schema)` raises those before the viewer is ever handed a
300
322
  ## Color scheme
301
323
 
302
324
  `colorScheme` paints the **chrome** — backdrop, bar, controls, progress strip, error
303
- panel — and never the sheet. `"light"` (the default) and `"dark"` pin; `"auto"` follows
325
+ panel — and never the sheet. `"light"` (the default) and `"dark"` pin. `"auto"` follows
304
326
  the OS via CSS `color-scheme`. The pages stay white, marking included.
305
327
 
306
328
  Chrome styles live on `--qv-*` custom properties under stable `qv-*` class names. A
@@ -323,10 +345,10 @@ specification of what a report may declare, and
323
345
 
324
346
  ## License
325
347
 
326
- Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
327
- licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
348
+ Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
349
+ Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
328
350
 
329
- Pass your license key once, on the instance; it is verified offline:
351
+ Pass your license key once, on the instance. quario verifies it offline:
330
352
 
331
353
  ```js
332
354
  const q = quario({ license: "quario_..." });
package/lib/button.js CHANGED
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * The shared `qv-button` template. Every control the viewer renders comes
3
- * from here the zoom menu's trigger, the export downloads, the error
4
- * panel's dismiss — so they agree on the class, the `type` and how they are
5
- * named.
3
+ * from here, so they agree on the class, the `type` and how they are named.
6
4
  */
7
5
  import { css, html, nothing } from "lit";
8
6
 
@@ -69,13 +67,10 @@ let fallback = (value, otherwise) => value ?? otherwise;
69
67
  * popover?: string, click?: () => void,
70
68
  * content?: import('lit').TemplateResult }} control
71
69
  * `content` is the icon a control draws and `label` the visible text it
72
- * reads as; an export button carries both, the zoom trigger and the panel's
73
- * dismiss the icon alone. Neither is ever the accessible name `title` is,
74
- * which is why the icons are `aria-hidden`. `name` marks an export button
75
- * with the target it downloads, and
76
- * `popover` names the menu this button opens — the platform then owns the
77
- * opening and the invoker's own `aria-expanded`, which is why such a button
78
- * needs no click of its own.
70
+ * reads as. Neither is ever the accessible name `title` is, which is why
71
+ * the icons are `aria-hidden`. `name` marks an export button with the target
72
+ * it downloads. `popover` names the menu this button opens, and the platform
73
+ * then owns the opening and the invoker's `aria-expanded`.
79
74
  * @returns {import('lit').TemplateResult}
80
75
  */
81
76
  export let button = ({ title, label, disabled, name, popover, click, content }) => html`
package/lib/check.js CHANGED
@@ -1,10 +1,7 @@
1
1
  /**
2
- * What the host got wrong, named. The element's own mistakes a report that
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.
2
+ * What the host got wrong, named. Each check throws a `TypeError` naming the
3
+ * property and the expectation; the element rethrows them from its render
4
+ * task, so one channel carries every failure a host can cause.
8
5
  *
9
6
  * Pure policy, no DOM: this module is what the Node suite pins.
10
7
  */
@@ -57,9 +54,8 @@ export let geometry = (page) => {
57
54
 
58
55
  /**
59
56
  * The font mapping from the `fonts` property. Only the shape reaches here: a
60
- * face that will not parse, or a missing parser, is found while the report is
61
- * measured and is a render failure there. The layout names the mistake; this
62
- * names the property, the way `geometry` does.
57
+ * face that will not parse is found while the report is measured and is a
58
+ * render failure there.
63
59
  *
64
60
  * @param {any} fonts
65
61
  */
@@ -76,12 +72,10 @@ let inRange = (mode, floor, ceiling) =>
76
72
  [Number.isFinite(mode), mode >= floor, mode <= ceiling].every(Boolean);
77
73
 
78
74
  /**
79
- * The zoom mode from the `zoom` property. Fit is the default: the host owns
80
- * the box, and a report nobody can read until they touch a control is not
81
- * much of a preview. A host may author any percentage from 25 to 200 —
82
- * continuous, not one of the menu's own stops; fit itself computes anything
83
- * at or below 100, so this deliberately validates less than the element can
84
- * display.
75
+ * The zoom mode from the `zoom` property, fit by default. A host may author
76
+ * any percentage from 25 to 200 continuous, not one of the menu's stops.
77
+ * Fit itself computes anything at or below 100, so this deliberately
78
+ * validates less than the element can display.
85
79
  *
86
80
  * @param {any} zoom
87
81
  * @returns {"fit" | number}
@@ -115,10 +109,9 @@ export let name = (filename) => {
115
109
  let SCHEMES = ["light", "dark", "auto"];
116
110
 
117
111
  /**
118
- * The used `color-scheme` from the `colorScheme` property. Light is the
119
- * default: embedding is the product, and following the OS would paint a light
120
- * toolbar in a host that already chose dark. `"auto"` is the opt-in that
121
- * follows, as CSS `light dark`.
112
+ * The used `color-scheme` from the `colorScheme` property, light by default:
113
+ * following the OS would paint a light toolbar in a host that already chose
114
+ * dark. `"auto"` is the opt-in that follows, as CSS `light dark`.
122
115
  *
123
116
  * @param {any} colorScheme
124
117
  * @returns {string}
package/lib/chrome.js CHANGED
@@ -4,37 +4,23 @@
4
4
  * `stage.js` keeps it paper.
5
5
  *
6
6
  * This module also owns the **default palette**: the `:host` block below is
7
- * the one place a `--qv-*` fallback is written, and every sheet this one
8
- * included — paints from the `--_*` alias it declares. That is what makes a
9
- * color one edit rather than a hunt, and `viewer.test.js` holds both halves:
10
- * `--qv-` appears in no other module, and the aliases read are exactly the
11
- * ones declared. The fallbacks are `light-dark()`, so a `color-scheme` pin on
12
- * the host paints chrome without a class.
7
+ * the one place a `--qv-*` fallback is written, and every sheet paints from
8
+ * the `--_*` alias it declares. The fallbacks are `light-dark()`, so a
9
+ * `color-scheme` pin on the host paints chrome without a class. The other
10
+ * sheets therefore paint only where this one is adopted too presence, not
11
+ * order, since a custom property resolves down the inherited chain.
13
12
  *
14
- * The cost is that the other sheets no longer stand alone they paint only
15
- * where this one is adopted too. It is presence, not order: a custom property
16
- * resolves down the inherited chain, so where CHROME sits in `static styles`
17
- * does not matter, only that it is there.
13
+ * The two halves are written differently on purpose. A host token must always
14
+ * win, so a default is a `var()` fallback which applies only when the token
15
+ * is unset never a `:host { --qv-border: }` rule, whose pseudo-class
16
+ * specificity would beat the host's own `quario-viewer` rule (ADR 0018). The
17
+ * private aliases invert that: `:host` outranks the host rule, so nothing
18
+ * outside can shadow one by accident.
18
19
  *
19
- * Host tokens always win, and `:host` is what makes that survive here. A
20
- * `:host { --qv-border: }` *default* would carry pseudo-class specificity
21
- * and beat a host's own `quario-viewer` rule the seam ADR 0018 declines to
22
- * make a lie. A `var()` fallback applies only when the token is unset, so it
23
- * loses to a host token at any specificity. The private half inverts that on
24
- * purpose: `:host` outranks that same host rule, so nothing outside can
25
- * shadow an alias by accident.
26
- *
27
- * One of the fifteen is the sheet's rather than the chrome's: a color scheme
28
- * paints chrome only (ADR 0018), so `--qv-sheet-shadow` is the sheet's edge
29
- * against the backdrop. It is in the block because it has no exceptions, not
30
- * because it follows the pin.
31
- * `--qv-focus` and `--qv-progress` repeat the accent rather than chain a third
32
- * alias — a host is free to move one alone.
33
- *
34
- * What is not here is what another module owns: `stage.js` the surface a
35
- * report is scaled on, `panel.js` the error panel, `button.js` the controls
36
- * every group in the bar is built from. The element composes their templates
37
- * and stylesheets; the imports run one way only.
20
+ * `--qv-sheet-shadow` is the sheet's edge against the backdrop rather than
21
+ * chrome (a color scheme paints chrome only, ADR 0018); it sits here because
22
+ * it has no exceptions. `--qv-focus` and `--qv-progress` repeat the accent
23
+ * rather than chain a third alias, so a host can move one alone.
38
24
  */
39
25
  import { css, html, nothing } from "lit";
40
26
 
@@ -188,9 +174,8 @@ export let CHROME = css`
188
174
  `;
189
175
 
190
176
  /**
191
- * The render indicator. Idle it stays out of the accessibility tree an
192
- * unstarted progressbar has nothing to announce and the stylesheet above
193
- * hooks on the viewer's `aria-busy` to show it.
177
+ * The render indicator. Idle it stays out of the accessibility tree, and the
178
+ * stylesheet above hooks on the viewer's `aria-busy` to show it.
194
179
  *
195
180
  * @param {boolean} busy
196
181
  * @returns {import('lit').TemplateResult}
package/lib/index.d.ts CHANGED
@@ -56,8 +56,9 @@ export interface ViewerErrorDetail {
56
56
  * element up with the panel saying why. A superseded render's failure is
57
57
  * reported to no one.
58
58
  *
59
- * Removing the element abandons in-flight work and releases its observers;
60
- * the properties persist, and reconnecting re-renders from them. There is no
59
+ * Removing the element stops the work in flight and releases its observers;
60
+ * the properties persist, and reconnecting renders them again unless the last
61
+ * render landed. There is no
61
62
  * `destroy()` — removal is destruction only in the garbage-collection sense,
62
63
  * and reparenting is safe. An export settling after removal downloads
63
64
  * nothing.
@@ -67,7 +68,7 @@ export class QuarioViewer extends LitElement {
67
68
  report: ViewableReport | undefined;
68
69
  /**
69
70
  * The export targets, as passed to `report.render`: ones named
70
- * `"pdf"`/`"xlsx"`/`"csv"` become export buttons, in array order. The
71
+ * `"pdf"`/`"xlsx"`/`"csv"`/`"docx"` become export buttons, in array order. The
71
72
  * sheet needs none of them — it is the layout's own. An empty array is
72
73
  * a viewer with nothing to export.
73
74
  */
package/lib/index.js CHANGED
@@ -14,15 +14,13 @@
14
14
  * and `@quario/viewer/register` performs the one-line define for hosts that
15
15
  * want it (docs/adr/0005-the-surfaces-are-custom-elements.md).
16
16
  *
17
- * Nothing here is markup: the sheet is a stack of canvases, and the list's
18
- * text reaches them as strings the painter draws, so the viewer has no
19
- * markup edge and adds no report content of its own. The unlicensed marking
20
- * rides on the list, one op per page, painted last — the same placement the
21
- * pdf target writes (docs/adr/0017-the-viewer-watermarks-the-sheet.md).
17
+ * Nothing here is markup: the sheet is a stack of canvases, so the viewer has
18
+ * no markup edge. The unlicensed marking rides on the list, one op per page,
19
+ * painted last (docs/adr/0017).
22
20
  *
23
- * The error panel is the only wording the viewer authors, and it is not
24
- * report content: it names which failure occurred, because no error knows
25
- * whether it was a mount, an update or an export (panel.js).
21
+ * The error panel is the only wording the viewer authors: it names which
22
+ * failure occurred, because no error knows whether it was a mount, an update
23
+ * or an export (panel.js).
26
24
  */
27
25
  import { Task, TaskStatus } from "@lit/task";
28
26
  import { LitElement, html } from "lit";
@@ -50,15 +48,13 @@ export const TAG = "quario-viewer";
50
48
  /**
51
49
  * The viewer element. Hosts assign `report`, `targets` and `data` (plus the
52
50
  * option properties `zoom`, `page`, `fonts`, `filename` and `colorScheme`)
53
- * and listen for `rendered` and `error`; rapid successive writes render only
54
- * the newest state, because the render pipeline is a task keyed on those
55
- * properties and the task's own call-id guard drops every superseded run
56
- * whole — including its failure, which is reported to no one (ADR 0005's
57
- * narrowed guarantee).
51
+ * and listen for `rendered` and `error`. Rapid successive writes render only
52
+ * the newest state: the task's call-id guard drops every superseded run
53
+ * whole, including its failure, which is reported to no one (ADR 0005).
58
54
  *
59
- * Removal is not destruction: disconnecting abandons in-flight work and
55
+ * Removal is not destruction: disconnecting stops the work in flight and
60
56
  * releases the resize observer, the properties persist, and reconnecting
61
- * re-renders from them. There is no `destroy()`.
57
+ * renders them again unless the last render landed. There is no `destroy()`.
62
58
  */
63
59
  export class QuarioViewer extends LitElement {
64
60
  // CHROME declares the default palette every other sheet paints from, so it
@@ -118,33 +114,34 @@ export class QuarioViewer extends LitElement {
118
114
 
119
115
  // The whole async pipeline: keyed on the render properties, re-run when one
120
116
  // changes — `page` and `fonts` among them, since either moves where the
121
- // pages break. The task's call-id guard is the latest-wins machinery; side
122
- // effects the stage swap and both events live only in the callbacks
123
- // below, which that guard restricts to the newest run, never in the task
124
- // body a stale run still executes to completion. `null` is the "nothing to
125
- // show" result: unlike the task primitive's own initial-state symbol it
126
- // settles `taskComplete`, which is what lets `renderComplete` always answer.
117
+ // pages break. Side effects live only in the callbacks below, which the
118
+ // task's call-id guard restricts to the newest run, never in the task body a
119
+ // stale run still executes to completion. `null` is the "nothing to show"
120
+ // result: unlike the initial-state symbol it settles `taskComplete`, which
121
+ // is what lets `renderComplete` always answer.
127
122
  #task = new Task(this, {
128
123
  args: () => [this.report, this.targets, this.data, this.page, this.fonts, this.#landing.epoch],
129
- task: async ([report, targets, data, page, fonts], { signal }) => {
124
+ task: async ([report, targets, data, page, fonts]) => {
125
+ // First, before anything that can throw: the debt opens by starting, so
126
+ // every way of not landing keeps it without saying so (ADR 0075). A
127
+ // host-option throw out of `#begin` is one of them.
128
+ this.#landing.owe();
130
129
  if (!this.#begin(report, targets)) return null;
131
130
  // The sheet's own target: the layout list, on the host's page and
132
131
  // fonts — the same two a host passes to `pdf()`, which is what makes
133
132
  // the preview page where the document pages.
134
133
  let target = layout({ page: /** @type {any} */ (page), fonts: /** @type {any} */ (fonts) });
135
134
  let list = await /** @type {any} */ (report).render(target, data);
136
- // The engine takes no signal, so abandonment is the guards around this
137
- // body; the check only spares the swap when the answer arrives after a
138
- // disconnect mid-render.
139
- if (this.#landing.dropped(signal, this.isConnected)) return this.#landing.abandon();
140
135
  return { list, fonts };
141
136
  },
142
137
  onComplete: (result) => {
143
138
  if (result === null || !this.isConnected) return;
144
139
  // The swap sizes every page before it settles, so the reader's place
145
140
  // is held; the paint it awaits is what `rendered` waits for.
146
- this.#painting = this.#stage.swap(result.list, result.fonts).then(() => {
147
- this.#landing.land();
141
+ // The boundary takes the paint rather than a promise to report back
142
+ // with: it reads which run this is here, while this run is still the
143
+ // newest one.
144
+ this.#painting = this.#landing.land(this.#stage.swap(result.list, result.fonts)).then(() => {
148
145
  // A render that landed on the sheet takes the panel down: the panel
149
146
  // says what is wrong with what the reader is looking at, and this is
150
147
  // the moment that stops being true. A successful export is not that
@@ -264,9 +261,9 @@ export class QuarioViewer extends LitElement {
264
261
  this.#unwatch ??= this.#stage.watch(() => {
265
262
  if (this.#mode === "fit" && this.#wanted() !== this.#stage.percent()) this.#apply();
266
263
  });
267
- // Reconnect re-renders from the current properties — but only when the
268
- // sheet stopped matching them: a render was abandoned mid-flight, or a
269
- // property write landed while disconnected. The task's arguments did not
264
+ // Reconnect re-renders from the current properties — but only where a
265
+ // render is owed, which is every run that did not reach the sheet
266
+ // (ADR 0075). The task's arguments did not
270
267
  // change, so the epoch is what re-runs it; left alone, reparenting a
271
268
  // settled viewer costs nothing.
272
269
  }
@@ -283,16 +280,17 @@ export class QuarioViewer extends LitElement {
283
280
  * A property the host got wrong fails loudly even while there is nothing to
284
281
  * render yet: a misconfigured host is caught in development, not when the
285
282
  * report arrives. Nothing assigned yet is not a failure. Disconnected
286
- * elements keep processing updates, so the guard is what makes removal
287
- * abandon work rather than merely hide it.
283
+ * elements keep processing updates, so the guard is what stops a removed
284
+ * element working where nobody is looking; the debt the run opened stands
285
+ * either way, and reconnecting collects it.
288
286
  *
289
287
  * @param {unknown} report
290
288
  * @param {unknown} targets
289
+ * @returns {boolean} Whether there is a render to do.
291
290
  */
292
291
  #begin(report, targets) {
293
292
  if (this.#options.invalid) throw this.#options.invalid;
294
- if (vacant(report, targets)) return null;
295
- if (!this.isConnected) return this.#landing.abandon();
293
+ if (vacant(report, targets) || !this.isConnected) return false;
296
294
  exports(report, targets);
297
295
  return true;
298
296
  }
package/lib/panel.js CHANGED
@@ -6,15 +6,13 @@
6
6
  * Always the compound — "the error panel", never a bare "panel", which stays
7
7
  * the word for the host's own UI regions around the viewer (see CONTEXT.md).
8
8
  *
9
- * It is the one place the viewer states wording of its own. Everything else it
10
- * shows comes from the report: the fragment is the html target's, and even the
11
- * unlicensed marking's wording arrives inside it. A label cannot, because no
12
- * error knows whether it was a mount, an update or an export that failed —
13
- * which is exactly what the error event's `kind` names.
9
+ * It is the one place the viewer states wording of its own: no error knows
10
+ * whether it was a mount, an update or an export that failed, which is what
11
+ * the error event's `kind` names.
14
12
  *
15
- * What it prints beside that label is the error's own message, as a template
16
- * value. That is a markup edge, and it stays one: a message can carry report
17
- * data, because a host's registry function is free to interpolate a row into
13
+ * Beside that label it prints the error's own message, as a template value.
14
+ * That is a markup edge and stays one: a message can carry report data,
15
+ * because a host's registry function is free to interpolate a row into
18
16
  * whatever it throws (hard constraint 4).
19
17
  */
20
18
  import { css, html } from "lit";
package/lib/stage.js CHANGED
@@ -13,63 +13,34 @@
13
13
  *
14
14
  * The sheet carries the whole list's extent, but only the
15
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.
16
+ * on screen and one viewport height either side. The extent is whole and
17
+ * synchronous whatever is standing, because the sheet is given its height and
18
+ * width inline and paints the page silhouettes itself; a page leaving the
19
+ * reach is removed, which takes its backing store with it (ADR 0043, 0065).
25
20
  *
26
21
  * **A page the browser will not paint is blank, not a failure.** `start`
27
22
  * swallows and nothing here rejects: the report laid out and the list reached
28
- * the sheet, which is all a render promised, so the surfaces have no channel
29
- * for it and deliberately grow none.
30
- *
31
- * Little reaches it. An image that will not decode is the layout's own to
32
- * absorb — `paint` draws it as nothing and draws the page around it — and a
33
- * face is parsed with fontkit while the report is measured, so bytes that will
34
- * not parse are a render error long before the sheet. What is left is the face
35
- * fontkit accepted and the browser refuses, which the browser suite drives by
36
- * making `FontFace.prototype.load` reject under a record handed straight to
37
- * `swap`.
23
+ * the sheet, which is all a render promised.
38
24
  *
39
25
  * **A canvas on the sheet is never re-pointed at a second paint.** `paint()`
40
26
  * awaits before it draws, so a canvas whose paint is still in flight is
41
- * *retired* rather than painted over or emptied: it comes off the sheet, its
42
- * pixels go back, and a fresh one stands in its place, so the superseded
43
- * paint draws into an element nobody is looking at. A canvas whose paint has
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
47
- * carrying old-scale content on a new-scale canvas, with the memo below
48
- * calling it painted so that nothing repainted it again (ADR 0046).
27
+ * *retired*: it comes off the sheet and a fresh one stands in its place, so
28
+ * the superseded paint draws into an element nobody is looking at. Otherwise
29
+ * a zoom landing mid-paint could leave a page carrying old-scale content on a
30
+ * new-scale canvas, with the memo below calling it painted (ADR 0046). A
31
+ * canvas whose paint has settled is re-sized in place.
49
32
  *
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
52
- * reaches it, so a page further down the reach shows its old pixels stretched
53
- * into the new box until its turn comes the ordinary look of a zoom in
54
- * progress, not the artefact above. The cost is that a retired page is a
55
- * replaced `role="img"` node: assistive technology reading that page sees it
56
- * swapped under them. Judged acceptable because retirement only happens while
57
- * that very page is mid-repaint and about to change what it shows anyway, and
58
- * because the alternative — a stable wrapper element per page to announce
59
- * from — is a second element per page on a sheet ADR 0043 exists to keep
60
- * cheap.
33
+ * Retirement is lazy: a scale change sizes the reach's CSS boxes at once but
34
+ * retires a page only when the repaint loop reaches it, so a page further
35
+ * down shows its old pixels stretched until its turn comes. The cost is that
36
+ * a retired page is a replaced `role="img"` node, which assistive technology
37
+ * sees swapped under it accepted, because the alternative is a second
38
+ * element per page on a sheet ADR 0043 exists to keep cheap.
61
39
  *
62
40
  * What the stage does not decide is which percentage to show: `fit()`
63
41
  * measures what would make one page span the width available, and `zoom.js`
64
- * owns the policy over that answer. What is on screen, though, is the
65
- * stage's own — `percent()` reports it, so no caller keeps a second copy.
66
- *
67
- * The editor builds its own stack of pages next door, and deliberately: what
68
- * the two surfaces share is the display list and the size of a point, not the
69
- * sheet. Each is a canvas element, a device-pixel size and a `paint()` call
70
- * around policy neither could lend the other — this one repaints under zoom
71
- * and guards a superseded repaint, that one builds once per swap and lays box
72
- * elements over the result.
42
+ * owns the policy over that answer. `percent()` reports what is on screen, so
43
+ * no caller keeps a second copy.
73
44
  */
74
45
 
75
46
  import { css } from "lit";
@@ -191,20 +162,16 @@ export let stage = () => {
191
162
 
192
163
  /**
193
164
  * Scale to `percent`, holding the middle of the viewport where it was:
194
- * scaling about the sheet's corner would otherwise throw the reader back
195
- * toward the top-left of whatever they were reading. The sheet starts one
196
- * gutter down the scroll extent, so the centre converts through that
197
- * offset; horizontally the wrapper is centred by auto margins while it
198
- * fits — which is exactly when `scrollLeft` is 0 anyway — and its margins
199
- * are 0 once it overflows, so the plain ratio holds wherever it can be
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.
165
+ * scaling about the sheet's corner would throw the reader back toward the
166
+ * top-left of what they were reading. The sheet starts one gutter down the
167
+ * scroll extent, so the centre converts through that offset; horizontally
168
+ * the wrapper is centred by auto margins while it fits — exactly when
169
+ * `scrollLeft` is 0 — and its margins are 0 once it overflows. A sheet
170
+ * with no list on it has no view to hold. Asked of the list rather than
171
+ * the sheet's children, which since ADR 0065 are the reach's and can be
172
+ * none of them while a list is standing.
205
173
  *
206
- * Only the reach is repainted, and only where the scale actually changed,
207
- * so a zoom step costs a handful of pages however long the report is.
174
+ * Only the reach is repainted, and only where the scale actually changed.
208
175
  */
209
176
  scale: (percent) => {
210
177
  let held = paged.count() > 0 && {
@@ -236,17 +203,12 @@ export let stage = () => {
236
203
  },
237
204
 
238
205
  /**
239
- * Put a laid-out report on the sheet, keeping the reader where they
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
246
- * returned promise settles behind is the reach, which is what the caller's
247
- * `renderComplete` means by "the pages on screen have finished trying to
248
- * paint" — and **it never rejects**, because `start` swallows a page the
249
- * browser will not draw and the reach walks on past it.
206
+ * Put a laid-out report on the sheet, keeping the reader where they were.
207
+ * The order is the point of the method: the sheet takes its height from
208
+ * the list first, so the browser clamps the offsets going back against
209
+ * the extent the sheet will have rather than the one it had, and the
210
+ * reach is read from those offsets afterwards. The returned promise
211
+ * settles behind the reach and **never rejects**.
250
212
  */
251
213
  swap: async (next, faces) => {
252
214
  // Reading the offsets flushes layout, so only an actual reswap pays for
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/lib/zoom.js CHANGED
@@ -5,24 +5,17 @@
5
5
  * it (see stage.js).
6
6
  *
7
7
  * The preview **scales; it never reflows**. Fit shrinks the rendered sheet
8
- * like a photograph, so line breaks, column widths and point sizes stay
9
- * exactly what they are at 100%. Letting the sheet's width follow the viewer
10
- * instead would keep small text readable, but it would lay the report out
11
- * differently from the document the pdf target will page — and the `page`
12
- * property exists to promise those two agree. A legible-at-any-width reading mode
13
- * would be a separate feature under its own name, not something fit becomes
14
- * quietly.
8
+ * like a photograph, so line breaks, column widths and point sizes stay what
9
+ * they are at 100%. Letting the sheet's width follow the viewer would lay the
10
+ * report out differently from the document the pdf target pages, and the
11
+ * `page` property exists to promise those two agree.
15
12
  */
16
13
 
17
14
  /**
18
15
  * The percentages the menu offers, and — through its ends — the range the
19
16
  * `zoom` property accepts (check.js reads them, so the two cannot drift).
20
- *
21
- * The ends are therefore public API, not a menu detail: moving the first or
22
- * last entry widens or narrows what a host may author. `viewer.test.js` spells
23
- * the current range out in the message it expects, so such a move fails a test
24
- * that says so rather than passing quietly. Adding a stop between the ends is
25
- * free.
17
+ * The ends are therefore public API: moving the first or last entry widens or
18
+ * narrows what a host may author. Adding a stop between them is free.
26
19
  */
27
20
  export let STEPS = [25, 50, 75, 100, 150, 200];
28
21
 
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@quario/viewer",
3
- "version": "0.8.0",
4
- "description": "The embeddable report viewer shell for quario in the makings, not yet released",
3
+ "version": "0.10.0",
4
+ "description": "Tiny, embeddable report viewer for quario. A custom element that pages on screen and exports what you hand it.",
5
+ "keywords": [
6
+ "csp",
7
+ "custom-element",
8
+ "quario",
9
+ "report",
10
+ "viewer"
11
+ ],
5
12
  "homepage": "https://getquario.com",
6
13
  "license": "SEE LICENSE IN LICENSE",
7
14
  "repository": {
@@ -44,22 +51,21 @@
44
51
  },
45
52
  "dependencies": {
46
53
  "@lit/task": "^1.0.3",
47
- "@quario/landing": "^0.2.0",
48
- "@quario/layout": "^0.5.0",
54
+ "@quario/landing": "^0.3.0",
55
+ "@quario/layout": "^0.7.0",
49
56
  "lit": "^3.3.3"
50
57
  },
51
58
  "devDependencies": {
52
59
  "@arethetypeswrong/cli": "^0.18.3",
53
- "@cantoo/pdf-lib": "^2.9.1",
60
+ "@cantoo/pdf-lib": "~2.9.1",
54
61
  "@size-limit/preset-small-lib": "^13.0.3",
55
62
  "esbuild": "^0.28.2",
56
- "exceljs": "^4.4.0",
57
- "quario": "^0.8.0",
63
+ "quario": "^0.10.0",
58
64
  "size-limit": "^13.0.3",
59
65
  "typescript": "^7.0.2"
60
66
  },
61
67
  "peerDependencies": {
62
- "quario": "^0.8.0"
68
+ "quario": "^0.10.0"
63
69
  },
64
70
  "size-limit": [
65
71
  {