@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/lib/outline.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outline panel: the report's group tree as a list of rows beside the
|
|
3
|
+
* sheet, each one a step to where that instance begins. The tree is the
|
|
4
|
+
* layout list's own `marks` — the entries the PDF target writes as bookmarks
|
|
5
|
+
* — so the panel derives it exactly as that target does, and an author
|
|
6
|
+
* declares nothing for it (`docs/adr/0044` for the icon, ADR 0047 for the
|
|
7
|
+
* chrome). A hollow instance is not a mark, so it is not a row.
|
|
8
|
+
*
|
|
9
|
+
* Optional: a host turns it on with the `outline` property, and the bar then
|
|
10
|
+
* carries one toggle. The reader opens and closes the panel; the property
|
|
11
|
+
* says only whether the module is there at all.
|
|
12
|
+
*
|
|
13
|
+
* A title goes in as a template value, never as markup — a group's key is
|
|
14
|
+
* report data — so this module has no markup edge to escape at (hard
|
|
15
|
+
* constraint 4).
|
|
16
|
+
*/
|
|
17
|
+
import { css, html } from "lit";
|
|
18
|
+
import { button } from "./button.js";
|
|
19
|
+
import { listTree } from "./icons.js";
|
|
20
|
+
|
|
21
|
+
export let OUTLINE = css`
|
|
22
|
+
.qv-outline {
|
|
23
|
+
flex: none;
|
|
24
|
+
width: 220px;
|
|
25
|
+
min-width: 0;
|
|
26
|
+
overflow: auto;
|
|
27
|
+
padding: 6px;
|
|
28
|
+
background: var(--_bar);
|
|
29
|
+
border-right: 1px solid var(--_border);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.qv-outline ol {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
35
|
+
list-style: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Each level steps in by one indent, written on the list rather than the
|
|
39
|
+
row, so a row's own box stays the full width the hover fills. */
|
|
40
|
+
.qv-outline ol ol {
|
|
41
|
+
padding-left: 12px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.qv-outline-row {
|
|
45
|
+
display: block;
|
|
46
|
+
width: 100%;
|
|
47
|
+
padding: 4px 8px;
|
|
48
|
+
border: none;
|
|
49
|
+
border-radius: 5px;
|
|
50
|
+
background: transparent;
|
|
51
|
+
color: inherit;
|
|
52
|
+
font: inherit;
|
|
53
|
+
text-align: left;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
text-overflow: ellipsis;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.qv-outline-empty {
|
|
61
|
+
margin: 0;
|
|
62
|
+
padding: 4px 8px;
|
|
63
|
+
color: var(--_icon);
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {{ title: string, depth: number, page: number, x: number, y: number }} Mark
|
|
69
|
+
* @typedef {{ mark: Mark, children: Node[] }} Node
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The marks as a tree: a mark is a child of the nearest mark before it at
|
|
74
|
+
* the depth above. Depth is the group's nesting, so the walk that emitted
|
|
75
|
+
* the marks in document order has already put every child after its parent.
|
|
76
|
+
*
|
|
77
|
+
* @param {readonly Mark[]} marks
|
|
78
|
+
* @returns {Node[]}
|
|
79
|
+
*/
|
|
80
|
+
export let tree = (marks) => {
|
|
81
|
+
/** @type {Node[]} */
|
|
82
|
+
let roots = [];
|
|
83
|
+
/** @type {Node[]} */
|
|
84
|
+
let open = [];
|
|
85
|
+
for (let mark of marks) {
|
|
86
|
+
/** @type {Node} */
|
|
87
|
+
let node = { mark, children: [] };
|
|
88
|
+
open.length = mark.depth;
|
|
89
|
+
(open[mark.depth - 1]?.children ?? roots).push(node);
|
|
90
|
+
open.push(node);
|
|
91
|
+
}
|
|
92
|
+
return roots;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** @type {(event: HTMLElement) => HTMLElement[]} */
|
|
96
|
+
let rows = (panel) => /** @type {HTMLElement[]} */ ([...panel.querySelectorAll(".qv-outline-row")]);
|
|
97
|
+
|
|
98
|
+
/** @type {Record<string, (list: HTMLElement[], at: number) => HTMLElement>} */
|
|
99
|
+
let MOVE = {
|
|
100
|
+
ArrowDown: (list, at) => list[Math.min(at + 1, list.length - 1)],
|
|
101
|
+
ArrowUp: (list, at) => list[Math.max(at - 1, 0)],
|
|
102
|
+
Home: (list) => list[0],
|
|
103
|
+
End: (list) => list[list.length - 1],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Arrow keys walk the rows in document order, whatever their depth; Tab
|
|
108
|
+
* leaves the panel as it left the bar. The platform owns activation.
|
|
109
|
+
*
|
|
110
|
+
* @param {KeyboardEvent} event
|
|
111
|
+
*/
|
|
112
|
+
let navigate = (event) => {
|
|
113
|
+
let move = MOVE[event.key];
|
|
114
|
+
if (!move) return;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
let list = rows(/** @type {HTMLElement} */ (event.currentTarget));
|
|
117
|
+
let at = list.indexOf(/** @type {HTMLElement} */ (event.target));
|
|
118
|
+
move(list, at).focus();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {Node[]} nodes
|
|
123
|
+
* @param {(mark: Mark) => void} go
|
|
124
|
+
* @returns {import('lit').TemplateResult}
|
|
125
|
+
*/
|
|
126
|
+
let branch = (nodes, go) => html`
|
|
127
|
+
<ol>
|
|
128
|
+
${nodes.map(
|
|
129
|
+
(node) => html`
|
|
130
|
+
<li>
|
|
131
|
+
<button type="button" class="qv-outline-row" @click=${() => go(node.mark)}>
|
|
132
|
+
${node.mark.title}
|
|
133
|
+
</button>
|
|
134
|
+
${node.children.length ? branch(node.children, go) : ""}
|
|
135
|
+
</li>
|
|
136
|
+
`,
|
|
137
|
+
)}
|
|
138
|
+
</ol>
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The bar's toggle, drawn only while the module is on.
|
|
143
|
+
*
|
|
144
|
+
* @param {{ open: boolean, toggle: () => void }} state
|
|
145
|
+
*/
|
|
146
|
+
export let outlineToggle = ({ open, toggle }) => html`
|
|
147
|
+
<div class="qv-outline-toggle">
|
|
148
|
+
${button({ title: open ? "Hide outline" : "Show outline", click: toggle, content: listTree })}
|
|
149
|
+
</div>
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The panel itself. A report with no groups has no tree, and the panel says
|
|
154
|
+
* so rather than standing empty.
|
|
155
|
+
*
|
|
156
|
+
* @param {{ marks: readonly Mark[], go: (mark: Mark) => void }} state
|
|
157
|
+
*/
|
|
158
|
+
export let outlinePanel = ({ marks, go }) => html`
|
|
159
|
+
<nav class="qv-outline" aria-label="Outline" @keydown=${navigate}>
|
|
160
|
+
${
|
|
161
|
+
marks.length
|
|
162
|
+
? branch(tree(marks), go)
|
|
163
|
+
: html`<p class="qv-outline-empty">This report has no groups.</p>`
|
|
164
|
+
}
|
|
165
|
+
</nav>
|
|
166
|
+
`;
|
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,68 +13,46 @@
|
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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.
|
|
32
|
+
*
|
|
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.
|
|
49
39
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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.
|
|
40
|
+
* The linked runs get a **focusable region each**, in `links.js`, hung on the
|
|
41
|
+
* sheet over the rectangles the painter drew them in and rebuilt as the band
|
|
42
|
+
* under the reader or the scale moves. They follow the reach for the reason the
|
|
43
|
+
* pages do, and this file owns the two numbers they need: the band, and the
|
|
44
|
+
* scale on screen.
|
|
61
45
|
*
|
|
62
46
|
* What the stage does not decide is which percentage to show: `fit()`
|
|
63
47
|
* 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.
|
|
48
|
+
* owns the policy over that answer. `percent()` reports what is on screen, so
|
|
49
|
+
* no caller keeps a second copy.
|
|
73
50
|
*/
|
|
74
51
|
|
|
75
52
|
import { css } from "lit";
|
|
76
53
|
import { GUTTER, sheet } from "@quario/landing";
|
|
77
54
|
import { PX_PER_POINT } from "@quario/layout";
|
|
55
|
+
import { overlay } from "./links.js";
|
|
78
56
|
|
|
79
57
|
// `GUTTER` comes from `@quario/landing`, which walks the reach over it: the CSS
|
|
80
58
|
// below and the sheet's own arithmetic have to agree about where a page sits,
|
|
@@ -130,6 +108,7 @@ export let SURFACE = css`
|
|
|
130
108
|
* percent: () => number, scale: (percent: number) => void,
|
|
131
109
|
* resize: (geometry: PageBox) => void,
|
|
132
110
|
* swap: (list: any, fonts: any) => Promise<void>,
|
|
111
|
+
* reveal: (page: number, y: number) => void,
|
|
133
112
|
* watch: (changed: () => void) => () => void }} Stage
|
|
134
113
|
*/
|
|
135
114
|
|
|
@@ -169,7 +148,42 @@ export let stage = () => {
|
|
|
169
148
|
* the elements above stay this file's, because the CSS is the viewer's own
|
|
170
149
|
* public surface. */
|
|
171
150
|
let paged = sheet(scroll, sheetEl, ratio);
|
|
172
|
-
|
|
151
|
+
|
|
152
|
+
/** The band of the sheet the reader is looking at, in the sheet's own CSS
|
|
153
|
+
* pixels: the frame a page is placed in. The sheet starts one gutter down
|
|
154
|
+
* the scroll extent, which is the same offset `reveal` scrolls through. */
|
|
155
|
+
let view = () => {
|
|
156
|
+
let top = scroll.scrollTop - GUTTER;
|
|
157
|
+
return { top, bottom: top + scroll.clientHeight };
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** The focusable regions over the linked runs. The viewer's own and not the
|
|
161
|
+
* editor's, which is the whole of ADR 0085's "a link in the editor's
|
|
162
|
+
* preview is not clickable". */
|
|
163
|
+
let links = overlay({
|
|
164
|
+
sheet: sheetEl,
|
|
165
|
+
paged,
|
|
166
|
+
ratio,
|
|
167
|
+
view,
|
|
168
|
+
reveal: (page, y) => api.reveal(page, y),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/** One region pass a frame, however many scrolls ask — the same coalescing
|
|
172
|
+
* the sheet's own reach pass uses, and a separate listener because the two
|
|
173
|
+
* answer different questions about the same scroll. */
|
|
174
|
+
let following = false;
|
|
175
|
+
let follow = () => {
|
|
176
|
+
if (following) return;
|
|
177
|
+
following = true;
|
|
178
|
+
requestAnimationFrame(() => {
|
|
179
|
+
following = false;
|
|
180
|
+
links.update();
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
scroll.addEventListener("scroll", follow);
|
|
184
|
+
|
|
185
|
+
/** @type {Stage} */
|
|
186
|
+
let api = {
|
|
173
187
|
element: scroll,
|
|
174
188
|
|
|
175
189
|
/**
|
|
@@ -191,23 +205,19 @@ export let stage = () => {
|
|
|
191
205
|
|
|
192
206
|
/**
|
|
193
207
|
* 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.
|
|
208
|
+
* scaling about the sheet's corner would throw the reader back toward the
|
|
209
|
+
* top-left of what they were reading. The sheet starts one gutter down the
|
|
210
|
+
* scroll extent, so the centre converts through that offset; horizontally
|
|
211
|
+
* the wrapper is centred by auto margins while it fits — exactly when
|
|
212
|
+
* `scrollLeft` is 0 — and its margins are 0 once it overflows. A sheet
|
|
213
|
+
* with no list on it has no view to hold. Asked of the list rather than
|
|
214
|
+
* the sheet's children, which since ADR 0065 are the reach's and can be
|
|
215
|
+
* none of them while a list is standing.
|
|
205
216
|
*
|
|
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.
|
|
217
|
+
* Only the reach is repainted, and only where the scale actually changed.
|
|
208
218
|
*/
|
|
209
219
|
scale: (percent) => {
|
|
210
|
-
let held = paged.
|
|
220
|
+
let held = paged.width() !== null && {
|
|
211
221
|
top: scroll.scrollTop,
|
|
212
222
|
left: scroll.scrollLeft,
|
|
213
223
|
height: scroll.clientHeight,
|
|
@@ -222,6 +232,7 @@ export let stage = () => {
|
|
|
222
232
|
scroll.scrollLeft = (held.left + held.width / 2) * ratioOf - held.width / 2;
|
|
223
233
|
}
|
|
224
234
|
void paged.repaint();
|
|
235
|
+
links.update();
|
|
225
236
|
},
|
|
226
237
|
|
|
227
238
|
/**
|
|
@@ -236,22 +247,17 @@ export let stage = () => {
|
|
|
236
247
|
},
|
|
237
248
|
|
|
238
249
|
/**
|
|
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.
|
|
250
|
+
* Put a laid-out report on the sheet, keeping the reader where they were.
|
|
251
|
+
* The order is the point of the method: the sheet takes its height from
|
|
252
|
+
* the list first, so the browser clamps the offsets going back against
|
|
253
|
+
* the extent the sheet will have rather than the one it had, and the
|
|
254
|
+
* reach is read from those offsets afterwards. The returned promise
|
|
255
|
+
* settles behind the reach and **never rejects**.
|
|
250
256
|
*/
|
|
251
257
|
swap: async (next, faces) => {
|
|
252
258
|
// Reading the offsets flushes layout, so only an actual reswap pays for
|
|
253
259
|
// it: with no list standing there is nothing scrolled to preserve.
|
|
254
|
-
let held = paged.
|
|
260
|
+
let held = paged.width() !== null && {
|
|
255
261
|
top: scroll.scrollTop,
|
|
256
262
|
left: scroll.scrollLeft,
|
|
257
263
|
};
|
|
@@ -260,6 +266,24 @@ export let stage = () => {
|
|
|
260
266
|
scroll.scrollTop = held.top;
|
|
261
267
|
scroll.scrollLeft = held.left;
|
|
262
268
|
}
|
|
269
|
+
// After the offsets go back, so the band the regions are built for is
|
|
270
|
+
// the one the reader is left looking at.
|
|
271
|
+
links.swap(next);
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Scroll so that a point on a page sits at the top of the view: the
|
|
276
|
+
* outline's step to where an instance begins. The offset is the sheet's
|
|
277
|
+
* own — `topOf()` is where the sheet places that page, and the point
|
|
278
|
+
* scales as the page does — so this restates no stacking (ADR 0070).
|
|
279
|
+
* Horizontal stays where the reader left it.
|
|
280
|
+
*
|
|
281
|
+
* @param {number} page
|
|
282
|
+
* @param {number} y In points, down that page.
|
|
283
|
+
*/
|
|
284
|
+
reveal: (page, y) => {
|
|
285
|
+
scroll.scrollTop = GUTTER + paged.topOf(page) + y * ratio();
|
|
286
|
+
paged.follow();
|
|
263
287
|
},
|
|
264
288
|
|
|
265
289
|
/**
|
|
@@ -273,10 +297,12 @@ export let stage = () => {
|
|
|
273
297
|
// A resize that changes the fit repaints through `changed()`; one
|
|
274
298
|
// that does not still moved the viewport the reach is measured in.
|
|
275
299
|
paged.follow();
|
|
300
|
+
follow();
|
|
276
301
|
changed();
|
|
277
302
|
});
|
|
278
303
|
observer.observe(scroll);
|
|
279
304
|
return () => observer.disconnect();
|
|
280
305
|
},
|
|
281
306
|
};
|
|
307
|
+
return api;
|
|
282
308
|
};
|
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.11.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": {
|
|
@@ -33,19 +40,20 @@
|
|
|
33
40
|
"access": "public"
|
|
34
41
|
},
|
|
35
42
|
"scripts": {
|
|
36
|
-
"check": "npm run size && npm test
|
|
43
|
+
"check": "npm run size && npm test",
|
|
44
|
+
"coverage:check": "c8 report --src lib/ --temp-directory=../../coverage/tmp --reporter=text --check-coverage --100",
|
|
37
45
|
"size": "size-limit",
|
|
38
|
-
"test": "npm run test:unit && npm run test:types",
|
|
46
|
+
"test": "npm run test:unit && npm run test:browser && npm run test:types && npm run coverage:check",
|
|
39
47
|
"test:browser": "node test/browser/setup.js",
|
|
40
48
|
"test:types": "tsc && attw --pack . --profile esm-only",
|
|
41
|
-
"test:unit": "node --disallow-code-generation-from-strings --test --test-concurrency=1 test/*.test.js",
|
|
49
|
+
"test:unit": "c8 --clean=false --src lib/ --reporter=none --temp-directory=../../coverage/tmp node --disallow-code-generation-from-strings --test --test-concurrency=1 test/*.test.js",
|
|
42
50
|
"prepack": "node -e \"require('fs').copyFileSync('../../LICENSE','LICENSE')\"",
|
|
43
51
|
"postpack": "node -e \"require('fs').rmSync('LICENSE',{force:true})\""
|
|
44
52
|
},
|
|
45
53
|
"dependencies": {
|
|
46
54
|
"@lit/task": "^1.0.3",
|
|
47
|
-
"@quario/landing": "^0.
|
|
48
|
-
"@quario/layout": "^0.
|
|
55
|
+
"@quario/landing": "^0.3.1",
|
|
56
|
+
"@quario/layout": "^0.8.0",
|
|
49
57
|
"lit": "^3.3.3"
|
|
50
58
|
},
|
|
51
59
|
"devDependencies": {
|
|
@@ -53,13 +61,12 @@
|
|
|
53
61
|
"@cantoo/pdf-lib": "~2.9.1",
|
|
54
62
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
55
63
|
"esbuild": "^0.28.2",
|
|
56
|
-
"
|
|
57
|
-
"quario": "^0.9.0",
|
|
64
|
+
"quario": "^0.11.0",
|
|
58
65
|
"size-limit": "^13.0.3",
|
|
59
66
|
"typescript": "^7.0.2"
|
|
60
67
|
},
|
|
61
68
|
"peerDependencies": {
|
|
62
|
-
"quario": "^0.
|
|
69
|
+
"quario": "^0.11.0"
|
|
63
70
|
},
|
|
64
71
|
"size-limit": [
|
|
65
72
|
{
|
|
@@ -71,7 +78,7 @@
|
|
|
71
78
|
"lit",
|
|
72
79
|
"@lit/task"
|
|
73
80
|
],
|
|
74
|
-
"limit": "
|
|
81
|
+
"limit": "9.5 kB"
|
|
75
82
|
}
|
|
76
83
|
],
|
|
77
84
|
"engines": {
|