@quario/viewer 0.9.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 +19 -0
- package/README.md +21 -5
- package/lib/button.js +5 -10
- package/lib/check.js +12 -19
- package/lib/chrome.js +17 -32
- package/lib/index.d.ts +3 -2
- package/lib/index.js +33 -35
- package/lib/panel.js +6 -8
- package/lib/stage.js +33 -71
- package/lib/zoom.js +6 -13
- package/package.json +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
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
|
+
|
|
3
22
|
## 0.9.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -10,6 +10,21 @@ list, painted page by page. You pass the export targets you want, and the viewer
|
|
|
10
10
|
to each (`"pdf"`, `"xlsx"`, `"csv"`, `"docx"`). The bar always carries the zoom control. With no
|
|
11
11
|
exportable target it carries nothing else.
|
|
12
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)
|
|
27
|
+
|
|
13
28
|
## Install
|
|
14
29
|
|
|
15
30
|
```bash
|
|
@@ -115,11 +130,12 @@ option property, becomes a `TypeError` naming the property, on the `error` event
|
|
|
115
130
|
|
|
116
131
|
## Lifecycle
|
|
117
132
|
|
|
118
|
-
There is no `destroy()`.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
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.
|
|
123
139
|
|
|
124
140
|
## Using with React, Vue and Svelte
|
|
125
141
|
|
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.
|
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/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,8 +51,8 @@
|
|
|
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": {
|
|
@@ -53,13 +60,12 @@
|
|
|
53
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.9.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
|
{
|