@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 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()`. Removing the element from the DOM abandons in-flight work and releases
119
- its observers. An export that settles after removal downloads nothing. The properties persist, and
120
- re-inserting the element re-renders from them, so reparenting is safe, and discarding a viewer is
121
- just discarding the element. Two viewers, or a viewer beside your own components, coexist: each
122
- 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.
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 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.
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/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.9.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,8 +51,8 @@
44
51
  },
45
52
  "dependencies": {
46
53
  "@lit/task": "^1.0.3",
47
- "@quario/landing": "^0.2.1",
48
- "@quario/layout": "^0.6.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
- "exceljs": "^4.4.0",
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.9.0"
68
+ "quario": "^0.10.0"
63
69
  },
64
70
  "size-limit": [
65
71
  {