@quario/viewer 0.9.0 → 0.11.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 +44 -0
- package/README.md +57 -8
- package/lib/button.js +23 -16
- package/lib/check.js +62 -25
- package/lib/chrome.js +25 -36
- package/lib/icons.js +3 -0
- package/lib/index.d.ts +11 -2
- package/lib/index.js +107 -65
- package/lib/links.js +221 -0
- package/lib/menu.js +6 -8
- package/lib/outline.js +166 -0
- package/lib/panel.js +6 -8
- package/lib/stage.js +100 -74
- package/lib/zoom.js +6 -13
- package/package.json +18 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @quario/viewer
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **A collapsed group does not draw its content.** The sheet paints the layout's pages, so an instance whose group declares `collapsed` reads as its header and footer alone.
|
|
8
|
+
- **A group's `label` titles its outline row.** The panel lists the layout's marks, so an instance that declares a label reads under it rather than under its first header line.
|
|
9
|
+
- **A linked run is clickable, and reachable with a keyboard.** Every run carrying an `href` now gets a region of its own over the words the page paints, so a reader clicks it or reaches it with Tab and opens it with Enter. A URL is an anchor opening in a tab of its own. An `href` naming a group's `label` is a button that steps the sheet to that instance, the way an outline row does, and a label no instance carries is no region at all. The regions follow the reach as the pages do, and move with the zoom.
|
|
10
|
+
|
|
11
|
+
The size budget rises from 8.5 kB to 9.5 kB to hold them.
|
|
12
|
+
|
|
13
|
+
- **An optional outline panel.** Set `outline` to `true` and the bar carries one more control, which opens a panel beside the sheet. The panel lists the report's group instances, nested by depth, in document order, and a row scrolls the sheet to where that instance begins. The list is the group tree the PDF target writes as bookmarks, so a report declares nothing for it. An instance whose bands all resolve hidden is not in it. The module is off by default. The reader opens and closes the panel.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- **An error quario replaces keeps the one it replaced, as `cause`.** A failure inside one of your own functions reaches you named where it happened — `detail[0].value [{{ total() }}]: ...` — which is a copy, rebuilt from the class and a prefixed message. The stack that says which line of your function threw used to go with the original. It now rides along: read `error.cause` for the throw as it happened, and the message for where quario was when it happened.
|
|
18
|
+
|
|
19
|
+
The viewer's and the editor's own property checks do the same. A `page` or `fonts` value either element refuses throws a `TypeError` naming the property, and that error now carries the layout's own account of the value behind it.
|
|
20
|
+
|
|
21
|
+
- **`renderComplete` answers for the newest render at the moment it settles.** A render that starts behind a pending `await` is the one the promise then waits for, so a host awaiting it while properties keep changing waits for the last change's render. Before, the promise could settle for the render in flight when it was asked, or miss one that started behind it.
|
|
22
|
+
- **The viewer reports a target it cannot export, rather than dropping it.** A `targets` entry whose name is not `pdf`, `xlsx`, `csv`, `docx` or `html` used to disappear: no button, no error. It is now a host mistake like any other malformed property. The error panel names the entry by its index and the name it carried, and the last good render stays up. An `html` target is still accepted and not read, so one list can serve both `render` and the viewer.
|
|
23
|
+
- Updated dependencies
|
|
24
|
+
- quario@0.11.0
|
|
25
|
+
- @quario/layout@0.8.0
|
|
26
|
+
- @quario/landing@0.3.1
|
|
27
|
+
|
|
28
|
+
## 0.10.0
|
|
29
|
+
|
|
30
|
+
### Minor Changes
|
|
31
|
+
|
|
32
|
+
- **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.
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
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.
|
|
37
|
+
|
|
38
|
+
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.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Updated dependencies
|
|
43
|
+
- @quario/layout@0.7.0
|
|
44
|
+
- quario@0.10.0
|
|
45
|
+
- @quario/landing@0.3.0
|
|
46
|
+
|
|
3
47
|
## 0.9.0
|
|
4
48
|
|
|
5
49
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -10,6 +10,23 @@ 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
|
+
- [Outline](#outline)
|
|
23
|
+
- [Links](#links)
|
|
24
|
+
- [What the preview is](#what-the-preview-is)
|
|
25
|
+
- [Errors](#errors)
|
|
26
|
+
- [Color scheme](#color-scheme)
|
|
27
|
+
- [Documentation](#documentation)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
13
30
|
## Install
|
|
14
31
|
|
|
15
32
|
```bash
|
|
@@ -66,11 +83,14 @@ package to install. See [Using with React, Vue and Svelte](#using-with-react-vue
|
|
|
66
83
|
| `fonts` | The font mapping, as passed to `pdf({ fonts })` | none |
|
|
67
84
|
| `filename` | Export download name, without extension | `"report"` |
|
|
68
85
|
| `colorScheme` | `"light"`, `"dark"`, or `"auto"` | `"light"` |
|
|
86
|
+
| `outline` | `true` to offer the outline panel | `false` |
|
|
69
87
|
|
|
70
88
|
Properties, not attributes: `report`, `targets`, `data`, `page` and `fonts` are values no attribute
|
|
71
89
|
could carry. `targets` mirrors `report.render(target, data)` for the exports:
|
|
72
90
|
`"pdf"`/`"xlsx"`/`"csv"`/`"docx"` targets become export buttons, in the order given, and the
|
|
73
|
-
sheet needs none of them.
|
|
91
|
+
sheet needs none of them. An `"html"` target is accepted and not read, so one list can serve
|
|
92
|
+
`render` and the viewer. Any other name is a host mistake: the viewer reports it on the error panel
|
|
93
|
+
with the entry's index, and renders nothing. The quario instance (and with it the license and the registry) stays
|
|
74
94
|
yours: the element takes the compiled report, never a schema.
|
|
75
95
|
|
|
76
96
|
Assigning `data` (or any of the others) re-renders. Rapid successive writes render only the newest
|
|
@@ -100,8 +120,9 @@ inline `onerror` attribute on the element would fire too. `window.onerror` never
|
|
|
100
120
|
`renderComplete` awaits the newest render settling: `true` when it landed on the sheet with the pages
|
|
101
121
|
on screen finished trying to paint, `false` when it failed or there was nothing to render. It never
|
|
102
122
|
rejects. Failures arrive on the `error` event. `rendered` fires at that same moment. Like every
|
|
103
|
-
outcome here, it answers for the newest render only
|
|
104
|
-
one.
|
|
123
|
+
outcome here, it answers for the newest render only: the render that is newest when the promise
|
|
124
|
+
settles, so a render that starts behind your `await` is the one it then waits for. A superseded
|
|
125
|
+
render's failure reaches no one.
|
|
105
126
|
|
|
106
127
|
An image the browser cannot decode — pixel data corrupt past the size in its header, which is all
|
|
107
128
|
the engine reads — is drawn as nothing, and the page is drawn around it. That is not a render
|
|
@@ -115,11 +136,12 @@ option property, becomes a `TypeError` naming the property, on the `error` event
|
|
|
115
136
|
|
|
116
137
|
## Lifecycle
|
|
117
138
|
|
|
118
|
-
There is no `destroy()`.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
element
|
|
139
|
+
There is no `destroy()`. Removal from the DOM stops the work in flight and releases the
|
|
140
|
+
observers. An export that settles after removal downloads nothing. The properties and the pages
|
|
141
|
+
persist. The element still owes a render that did not land on the sheet. It therefore renders
|
|
142
|
+
once more when you insert it, unless its last render landed. Reparenting is safe, and you
|
|
143
|
+
discard a viewer by discarding the element. Two viewers, or a viewer beside your own components,
|
|
144
|
+
coexist: each element owns its own shadow root.
|
|
123
145
|
|
|
124
146
|
## Using with React, Vue and Svelte
|
|
125
147
|
|
|
@@ -234,6 +256,18 @@ Every page keeps its size, so the scrollbar and the scroll extent are the whole
|
|
|
234
256
|
start. A page further off is blank paper until you scroll to it, which is why a zoom step costs the
|
|
235
257
|
same on a thousand-page report as on a five-page one.
|
|
236
258
|
|
|
259
|
+
## Links
|
|
260
|
+
|
|
261
|
+
A run that carries an [`href`](https://github.com/getquario/quario/blob/main/SCHEMA.md#style-declarations) gets a region of its own over the words the page
|
|
262
|
+
paints. The reader clicks it, or reaches it with the Tab key and opens it with Enter. You
|
|
263
|
+
declare nothing for this and turn nothing on.
|
|
264
|
+
|
|
265
|
+
A URL opens in a tab of its own, so a reader reading a report never loses it. The engine
|
|
266
|
+
admitted that URL against your instance's `schemes` allowlist before the viewer saw it,
|
|
267
|
+
which is where you decide what a report may point at. An `href` that starts with `#` names
|
|
268
|
+
a group's `label` and steps the sheet to that instance, the way an outline row does. A
|
|
269
|
+
label no instance carries is no region, and the words stay as they are.
|
|
270
|
+
|
|
237
271
|
## What the preview is
|
|
238
272
|
|
|
239
273
|
The pages on screen are the pages the PDF export writes: both consume one layout, `@quario/layout`'s
|
|
@@ -242,6 +276,8 @@ longer one is as many pages as the layout breaks it into, stacked down the sheet
|
|
|
242
276
|
container to a viewer that draws a short report therefore reserves a full page. Set `zoom` to a
|
|
243
277
|
percentage small enough where the box has to stay small.
|
|
244
278
|
|
|
279
|
+
The reader reads and does not reorder. The viewer paints the layout's pages and offers no sort, because a page is not a grid; a host that wants sorted rows renders the report again with them sorted. The XLSX target's `filter` option is where a reader gets a control of their own.
|
|
280
|
+
|
|
245
281
|
Each page is a canvas, and text on it is drawn in the face the document will use. The viewer
|
|
246
282
|
registers a TrueType family you pass as `fonts` from your own bytes and draws it as the browser
|
|
247
283
|
shapes it —
|
|
@@ -303,6 +339,19 @@ data is the message itself, if your own registry functions interpolate a row int
|
|
|
303
339
|
That is your call, and the panel puts it on screen as text, never as markup. Compile errors are not
|
|
304
340
|
part of this: `q.report(schema)` raises those before the viewer is ever handed a report.
|
|
305
341
|
|
|
342
|
+
## Outline
|
|
343
|
+
|
|
344
|
+
Set `outline` to `true` and the bar carries one more control. It opens a panel beside the sheet
|
|
345
|
+
that lists the report's group instances, nested by depth, in document order. A row scrolls the
|
|
346
|
+
sheet to where that instance begins. The list is the group tree the PDF target writes as
|
|
347
|
+
bookmarks, and a row reads the group's `label` where one is declared. A group instance whose bands all resolve
|
|
348
|
+
hidden is not in the list. The property turns the module on. The reader opens and closes the
|
|
349
|
+
panel, and the panel opens when the module turns on.
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
view.outline = true;
|
|
353
|
+
```
|
|
354
|
+
|
|
306
355
|
## Color scheme
|
|
307
356
|
|
|
308
357
|
`colorScheme` paints the **chrome** — backdrop, bar, controls, progress strip, error
|
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
|
|
|
@@ -26,16 +24,28 @@ export let BUTTON = css`
|
|
|
26
24
|
cursor: pointer;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
/* The interaction treatment, written once for every <button> the viewer
|
|
28
|
+
draws. A menu row (\`menu.js\`) and an outline row (\`outline.js\`) are
|
|
29
|
+
buttons too: a different shape — full width, left-aligned — but hovering
|
|
30
|
+
and focusing them mean the same thing, and stating that twice is how the
|
|
31
|
+
two drifted apart before the palette landed. Where a row differs it says
|
|
32
|
+
so itself. */
|
|
33
|
+
.qv-button:hover,
|
|
34
|
+
.qv-menuitem:hover,
|
|
35
|
+
.qv-outline-row:hover {
|
|
30
36
|
background: var(--_hover);
|
|
31
37
|
color: var(--_icon-active);
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
.qv-button:active
|
|
40
|
+
.qv-button:active,
|
|
41
|
+
.qv-menuitem:active,
|
|
42
|
+
.qv-outline-row:active {
|
|
35
43
|
background: var(--_active);
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
.qv-button:focus-visible
|
|
46
|
+
.qv-button:focus-visible,
|
|
47
|
+
.qv-menuitem:focus-visible,
|
|
48
|
+
.qv-outline-row:focus-visible {
|
|
39
49
|
outline: 2px solid var(--_focus);
|
|
40
50
|
outline-offset: 1px;
|
|
41
51
|
}
|
|
@@ -67,15 +77,12 @@ let fallback = (value, otherwise) => value ?? otherwise;
|
|
|
67
77
|
*
|
|
68
78
|
* @param {{ title: string, label?: string, disabled?: boolean, name?: string,
|
|
69
79
|
* popover?: string, click?: () => void,
|
|
70
|
-
* content
|
|
71
|
-
* `content` is the icon
|
|
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.
|
|
80
|
+
* content: import('lit').TemplateResult }} control
|
|
81
|
+
* `content` is the icon every control draws and `label` the visible text it
|
|
82
|
+
* reads as. Neither is ever the accessible name — `title` is, which is why
|
|
83
|
+
* the icons are `aria-hidden`. `name` marks an export button with the target
|
|
84
|
+
* it downloads. `popover` names the menu this button opens, and the platform
|
|
85
|
+
* then owns the opening and the invoker's `aria-expanded`.
|
|
79
86
|
* @returns {import('lit').TemplateResult}
|
|
80
87
|
*/
|
|
81
88
|
export let button = ({ title, label, disabled, name, popover, click, content }) => html`
|
|
@@ -90,6 +97,6 @@ export let button = ({ title, label, disabled, name, popover, click, content })
|
|
|
90
97
|
?disabled=${disabled}
|
|
91
98
|
@click=${click}
|
|
92
99
|
>
|
|
93
|
-
${content
|
|
100
|
+
${content}${label ?? nothing}
|
|
94
101
|
</button>
|
|
95
102
|
`;
|
package/lib/check.js
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
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
|
*/
|
|
11
8
|
|
|
12
9
|
import { checkFonts, pageBox } from "@quario/layout";
|
|
10
|
+
import { EXPORTS } from "./toolbar.js";
|
|
13
11
|
import { STEPS } from "./zoom.js";
|
|
14
12
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Name the mistake on the property that carries it. An `Error` stands in for
|
|
15
|
+
* its own message and rides along as `cause`: a replacement is a claim about
|
|
16
|
+
* what went wrong, and only the error it replaced can contradict it
|
|
17
|
+
* (docs/adr/0084).
|
|
18
|
+
*
|
|
19
|
+
* @type {(said: string | Error) => never}
|
|
20
|
+
*/
|
|
21
|
+
let fail = (said) => {
|
|
22
|
+
let message = said instanceof Error ? said.message : said;
|
|
23
|
+
throw new TypeError("quario-viewer: " + message, said instanceof Error ? { cause: said } : {});
|
|
18
24
|
};
|
|
19
25
|
|
|
20
26
|
let REPORT = [
|
|
@@ -26,10 +32,19 @@ let REPORT = [
|
|
|
26
32
|
/** @type {(report: any) => boolean} */
|
|
27
33
|
let compiled = (report) => REPORT.every((check) => check(report));
|
|
28
34
|
|
|
35
|
+
// The one target the viewer accepts and never reads: a host that hands one
|
|
36
|
+
// list to `render` and to the viewer carries it, and the sheet is the
|
|
37
|
+
// layout's own. Every other name the export map does not hold is a mistake,
|
|
38
|
+
// because the viewer would drop it without a button and without a word.
|
|
39
|
+
let UNREAD = ["html"];
|
|
40
|
+
let ACCEPTED = [...Object.keys(EXPORTS), ...UNREAD];
|
|
41
|
+
let NAMES = ACCEPTED.map((name) => '"' + name + '"').join(", ");
|
|
42
|
+
|
|
29
43
|
/**
|
|
30
44
|
* The shapes a render needs: a compiled report, and the export targets as an
|
|
31
45
|
* array — possibly empty, since the sheet is the layout's own and needs no
|
|
32
|
-
* target of the host's.
|
|
46
|
+
* target of the host's. Each entry names a target the viewer exports or the
|
|
47
|
+
* one it leaves unread; anything else is named on its index.
|
|
33
48
|
*
|
|
34
49
|
* @param {any} report
|
|
35
50
|
* @param {any} targets
|
|
@@ -37,6 +52,19 @@ let compiled = (report) => REPORT.every((check) => check(report));
|
|
|
37
52
|
export let exports = (report, targets) => {
|
|
38
53
|
if (!compiled(report)) fail("report: expected a compiled report from a quario instance");
|
|
39
54
|
if (!Array.isArray(targets)) fail("targets: expected an array of render targets");
|
|
55
|
+
targets.forEach(named);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** What a rejected entry carried, in the message's words. */
|
|
59
|
+
/** @type {(target: any) => string} */
|
|
60
|
+
let carried = (target) =>
|
|
61
|
+
target?.name === undefined ? "a value with no name" : JSON.stringify(target.name);
|
|
62
|
+
|
|
63
|
+
/** One entry of `targets`, named on its index when the viewer would drop it. */
|
|
64
|
+
/** @type {(target: any, i: number) => void} */
|
|
65
|
+
let named = (target, i) => {
|
|
66
|
+
if (ACCEPTED.includes(target?.name)) return;
|
|
67
|
+
fail("targets[" + i + "]: expected a target named one of " + NAMES + ", got " + carried(target));
|
|
40
68
|
};
|
|
41
69
|
|
|
42
70
|
/**
|
|
@@ -51,15 +79,14 @@ export let geometry = (page) => {
|
|
|
51
79
|
try {
|
|
52
80
|
return pageBox(page, "page");
|
|
53
81
|
} catch (error) {
|
|
54
|
-
return fail(/** @type {Error} */ (error)
|
|
82
|
+
return fail(/** @type {Error} */ (error));
|
|
55
83
|
}
|
|
56
84
|
};
|
|
57
85
|
|
|
58
86
|
/**
|
|
59
87
|
* 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.
|
|
88
|
+
* face that will not parse is found while the report is measured and is a
|
|
89
|
+
* render failure there.
|
|
63
90
|
*
|
|
64
91
|
* @param {any} fonts
|
|
65
92
|
*/
|
|
@@ -67,7 +94,7 @@ export let faces = (fonts) => {
|
|
|
67
94
|
try {
|
|
68
95
|
checkFonts(fonts, "fonts");
|
|
69
96
|
} catch (error) {
|
|
70
|
-
fail(/** @type {Error} */ (error)
|
|
97
|
+
fail(/** @type {Error} */ (error));
|
|
71
98
|
}
|
|
72
99
|
};
|
|
73
100
|
|
|
@@ -76,12 +103,10 @@ let inRange = (mode, floor, ceiling) =>
|
|
|
76
103
|
[Number.isFinite(mode), mode >= floor, mode <= ceiling].every(Boolean);
|
|
77
104
|
|
|
78
105
|
/**
|
|
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.
|
|
106
|
+
* The zoom mode from the `zoom` property, fit by default. A host may author
|
|
107
|
+
* any percentage from 25 to 200 — continuous, not one of the menu's stops.
|
|
108
|
+
* Fit itself computes anything at or below 100, so this deliberately
|
|
109
|
+
* validates less than the element can display.
|
|
85
110
|
*
|
|
86
111
|
* @param {any} zoom
|
|
87
112
|
* @returns {"fit" | number}
|
|
@@ -110,15 +135,27 @@ export let name = (filename) => {
|
|
|
110
135
|
return base;
|
|
111
136
|
};
|
|
112
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Whether the outline module is on, from the `outline` property, off by
|
|
140
|
+
* default. A flag, so anything but a boolean or nothing is a mistake.
|
|
141
|
+
*
|
|
142
|
+
* @param {any} outline
|
|
143
|
+
* @returns {boolean}
|
|
144
|
+
*/
|
|
145
|
+
export let outlined = (outline) => {
|
|
146
|
+
if (outline !== undefined && typeof outline !== "boolean")
|
|
147
|
+
fail("outline: expected true or false");
|
|
148
|
+
return outline === true;
|
|
149
|
+
};
|
|
150
|
+
|
|
113
151
|
/** The schemes the property accepts. Two are their own CSS value; `auto` is
|
|
114
152
|
* the one that is not, so it is the only wording written here. */
|
|
115
153
|
let SCHEMES = ["light", "dark", "auto"];
|
|
116
154
|
|
|
117
155
|
/**
|
|
118
|
-
* The used `color-scheme` from the `colorScheme` property
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* follows, as CSS `light dark`.
|
|
156
|
+
* The used `color-scheme` from the `colorScheme` property, light by default:
|
|
157
|
+
* following the OS would paint a light toolbar in a host that already chose
|
|
158
|
+
* dark. `"auto"` is the opt-in that follows, as CSS `light dark`.
|
|
122
159
|
*
|
|
123
160
|
* @param {any} colorScheme
|
|
124
161
|
* @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
|
|
|
@@ -80,13 +66,13 @@ export let CHROME = css`
|
|
|
80
66
|
font: 13px/1.4 system-ui, sans-serif;
|
|
81
67
|
}
|
|
82
68
|
|
|
83
|
-
/* Everything below the bar
|
|
84
|
-
|
|
85
|
-
the bar's own
|
|
69
|
+
/* Everything below the bar: the outline panel when it is open, then the
|
|
70
|
+
stage. It exists to be the box the error panel is positioned against:
|
|
71
|
+
anchored to the viewer instead, the panel would cover the bar's own
|
|
72
|
+
controls. */
|
|
86
73
|
.qv-body {
|
|
87
74
|
position: relative;
|
|
88
75
|
display: flex;
|
|
89
|
-
flex-direction: column;
|
|
90
76
|
flex: 1;
|
|
91
77
|
min-height: 0;
|
|
92
78
|
}
|
|
@@ -162,6 +148,10 @@ export let CHROME = css`
|
|
|
162
148
|
margin-right: auto;
|
|
163
149
|
}
|
|
164
150
|
|
|
151
|
+
.qv-outline-toggle {
|
|
152
|
+
display: flex;
|
|
153
|
+
}
|
|
154
|
+
|
|
165
155
|
.qv-exports {
|
|
166
156
|
display: flex;
|
|
167
157
|
gap: 2px;
|
|
@@ -188,9 +178,8 @@ export let CHROME = css`
|
|
|
188
178
|
`;
|
|
189
179
|
|
|
190
180
|
/**
|
|
191
|
-
* The render indicator. Idle it stays out of the accessibility tree
|
|
192
|
-
*
|
|
193
|
-
* hooks on the viewer's `aria-busy` to show it.
|
|
181
|
+
* The render indicator. Idle it stays out of the accessibility tree, and the
|
|
182
|
+
* stylesheet above hooks on the viewer's `aria-busy` to show it.
|
|
194
183
|
*
|
|
195
184
|
* @param {boolean} busy
|
|
196
185
|
* @returns {import('lit').TemplateResult}
|
package/lib/icons.js
CHANGED
|
@@ -21,5 +21,8 @@ export let check = icon(svg`<path d="M20 6 9 17l-5-5"/>`);
|
|
|
21
21
|
export let download = icon(
|
|
22
22
|
svg`<path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/>`,
|
|
23
23
|
);
|
|
24
|
+
export let listTree = icon(
|
|
25
|
+
svg`<path d="M8 5h13"/><path d="M13 12h8"/><path d="M13 19h8"/><path d="M3 10a2 2 0 0 0 2 2h3"/><path d="M3 5v12a2 2 0 0 0 2 2h3"/>`,
|
|
26
|
+
);
|
|
24
27
|
export let search = icon(svg`<path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/>`);
|
|
25
28
|
export let x = icon(svg`<path d="M18 6 6 18"/><path d="m6 6 12 12"/>`);
|
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.
|
|
@@ -96,6 +97,14 @@ export class QuarioViewer extends LitElement {
|
|
|
96
97
|
* follows the OS via CSS `color-scheme`. The sheet stays white.
|
|
97
98
|
*/
|
|
98
99
|
colorScheme: "light" | "dark" | "auto" | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Whether the outline module is on. Off by default. On, the bar carries a
|
|
102
|
+
* toggle and a panel beside the sheet lists the report's group instances,
|
|
103
|
+
* nested by depth, in document order; a row scrolls to where that instance
|
|
104
|
+
* begins. The list is the one the PDF target writes as bookmarks, so
|
|
105
|
+
* nothing in the report declares it, and a hollow instance is not in it.
|
|
106
|
+
*/
|
|
107
|
+
outline: boolean | undefined;
|
|
99
108
|
/**
|
|
100
109
|
* The newest render settling: `true` when it landed on the sheet with the
|
|
101
110
|
* pages on screen finished trying to paint, `false` when it failed or there
|