@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 +37 -0
- package/README.md +84 -62
- package/lib/button.js +5 -10
- package/lib/check.js +12 -19
- package/lib/chrome.js +17 -32
- package/lib/index.d.ts +4 -3
- package/lib/index.js +33 -35
- package/lib/panel.js +6 -8
- package/lib/stage.js +33 -71
- package/lib/toolbar.js +1 -0
- package/lib/zoom.js +6 -13
- package/package.json +14 -8
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
|
|
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
|
|
10
|
-
to each (`"pdf"`, `"xlsx"`, `"csv"`). The bar always carries the zoom control
|
|
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
|
|
20
|
-
to export with (`@quario/pdf`, `@quario/xlsx`, `@quario/csv`) and pass them in. Its
|
|
21
|
-
dependencies are [Lit](https://lit.dev) (`lit` + `@lit/task`) and `@quario/layout`, the
|
|
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
|
|
45
|
-
the
|
|
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:
|
|
71
|
-
targets become export buttons, in the order given, and the
|
|
72
|
-
instance (and with it the license and the registry) stays
|
|
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
|
-
|
|
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
|
|
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
|
|
97
|
-
inline `onerror` attribute on the element would fire too
|
|
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
|
|
102
|
-
outcome here, it answers for the newest render only. A superseded render's failure
|
|
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
|
|
108
|
-
`fonts` is a different story
|
|
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).
|
|
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()`.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
element
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
240
|
-
longer one is as many as the layout breaks it into, stacked down the sheet. Sizing a
|
|
241
|
-
viewer
|
|
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.
|
|
245
|
-
family you pass as `fonts`
|
|
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
|
|
252
|
-
layout's, and identical.
|
|
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
|
|
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
|
|
266
|
-
|
|
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
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
290
|
-
|
|
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) }}]: ...`.
|
|
295
|
-
trusted configuration
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
61
|
-
*
|
|
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
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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
|
|
119
|
-
*
|
|
120
|
-
*
|
|
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
|
|
8
|
-
*
|
|
9
|
-
* color
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
|
192
|
-
*
|
|
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
|
|
60
|
-
* the properties persist, and reconnecting
|
|
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,
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
54
|
-
* the newest state
|
|
55
|
-
*
|
|
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
|
|
55
|
+
* Removal is not destruction: disconnecting stops the work in flight and
|
|
60
56
|
* releases the resize observer, the properties persist, and reconnecting
|
|
61
|
-
*
|
|
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.
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
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]
|
|
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
|
-
|
|
147
|
-
|
|
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
|
|
268
|
-
//
|
|
269
|
-
//
|
|
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
|
|
287
|
-
*
|
|
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
|
|
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
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
|
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
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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.
|
|
65
|
-
*
|
|
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
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
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
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
4
|
-
"description": "
|
|
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.
|
|
48
|
-
"@quario/layout": "^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": "
|
|
60
|
+
"@cantoo/pdf-lib": "~2.9.1",
|
|
54
61
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
55
62
|
"esbuild": "^0.28.2",
|
|
56
|
-
"
|
|
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.
|
|
68
|
+
"quario": "^0.10.0"
|
|
63
69
|
},
|
|
64
70
|
"size-limit": [
|
|
65
71
|
{
|