@quario/viewer 0.5.0 → 0.6.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
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-09-07
11
+
12
+ ### Changed
13
+
14
+ - **Numbers presented through `format` now show a fixed two fraction digits**,
15
+ the same as every other target: `1,000.00` where the preview showed `1,000`,
16
+ `21.00%` where it showed `21%`, and a currency's own minor units in place of
17
+ a universal two. The digits come from the engine, so a page on screen and
18
+ the PDF of it agree. A formatted cell is also up to three characters wider,
19
+ so a line that just fitted can wrap and move a page break.
20
+
21
+ ### Fixed
22
+
23
+ - **A page the browser cannot fully paint no longer fails the render.** An
24
+ image whose pixel data is corrupt past the size in its header left that page
25
+ blank _and_ rejected `renderComplete`, so a host awaiting it saw an unhandled
26
+ rejection instead of an answer, and every page after it on screen went
27
+ unpainted too. Such an image is now drawn as nothing and the page is drawn
28
+ around it, marking and all; `renderComplete` resolves `true`, `rendered`
29
+ fires, and no `error` event is raised. It never rejects for this reason
30
+ again: a page that cannot be drawn is blank, not a failed render.
31
+
10
32
  ## [0.5.0] - 2026-09-05
11
33
 
12
34
  ### Changed
package/README.md CHANGED
@@ -97,13 +97,20 @@ non-bubbling, like `<img>`'s. Listen on the element. (Because the event is named
97
97
  inline `onerror` attribute on the element would fire too; `window.onerror` never sees it.)
98
98
 
99
99
  `renderComplete` awaits the newest render settling: `true` when it landed on the sheet with the pages
100
- on screen painted, `false` when it failed or there was nothing to render. It never rejects; failures
101
- arrive on the `error` event. `rendered` fires at that same moment. Like every outcome here, it
102
- answers for the newest render only. A superseded render's failure is reported to no one.
100
+ on screen finished trying to paint, `false` when it failed or there was nothing to render. It never
101
+ rejects; failures arrive on the `error` event. `rendered` fires at that same moment. Like every
102
+ outcome here, it answers for the newest render only. A superseded render's failure is reported to no
103
+ one.
103
104
 
104
- Your own mistakes surface on the same channel: a report that is not compiled, a missing
105
- `"html"` target, or a malformed option property becomes a `TypeError` naming the property, on
106
- the `error` event and the [error panel](#errors).
105
+ An image the browser cannot decode pixel data corrupt past the size in its header, which is all
106
+ the engine reads — is drawn as nothing, and the page is drawn around it. That is not a render
107
+ failure: `renderComplete` answers `true`, `rendered` fires, and no `error` event is raised. A face in
108
+ `fonts` is a different story: it is parsed while the report is measured, so bytes that will not parse
109
+ are a render error with a panel.
110
+
111
+ Your own mistakes surface on the same channel: a report that is not compiled, or a malformed
112
+ option property, becomes a `TypeError` naming the property, on the `error` event and the
113
+ [error panel](#errors). No particular target is required — the sheet is the layout's own.
107
114
 
108
115
  ## Lifecycle
109
116
 
@@ -250,7 +257,8 @@ export marks every page: it rides on the layout, so what you see is what the doc
250
257
  licensed render carries none.
251
258
 
252
259
  While a render is in flight a thin indeterminate bar sits on the toolbar's bottom edge, and the
253
- viewer reads `aria-busy="true"`. It reports that the viewer is working, not how far along. The
260
+ viewer's inner container the `.qv-viewer` div in its shadow root, not the element itself
261
+ reads `aria-busy="true"`. It reports that the viewer is working, not how far along. The
254
262
  engine streams events and cannot know how many are still coming.
255
263
 
256
264
  There is no Print button, because the sheet is the wrong thing to print. Your page's stylesheets do
@@ -261,9 +269,9 @@ document, the same bytes the PDF export hands over. A host that wants its own Pr
261
269
  two lines:
262
270
 
263
271
  ```js
264
- // `pdfTarget` is the pdf() from the quick start above, and `data` whatever you
265
- // last rendered — so what prints is exactly what the PDF export would produce.
266
- const bytes = await rpt.render(pdfTarget, data);
272
+ // The same compiled report and data the element holds, through the pdf()
273
+ // target from the quick start — so what prints is exactly the PDF export.
274
+ const bytes = await view.report.render(pdf(), view.data);
267
275
  window.open(URL.createObjectURL(new Blob([bytes], { type: "application/pdf" })));
268
276
  ```
269
277
 
@@ -273,7 +281,7 @@ the export is, watermark and all.
273
281
  ## Errors
274
282
 
275
283
  When a render or an export fails, the viewer says so on the **error panel**, a strip across the top
276
- of the sheet carrying a short label and the error's own message. It replaces rather than stacks, is
284
+ of the sheet carrying a short label — "Could not render the report", "Could not update the report — showing the previous version", or "Could not export PDF" — and the error's own message. It replaces rather than stacks, is
277
285
  dismissed by its own button, and is cleared by the next render that lands on the sheet. A successful
278
286
  export leaves it up, because the panel describes what you are looking at and a download says
279
287
  nothing about that.
package/lib/index.d.ts CHANGED
@@ -97,10 +97,11 @@ export class QuarioViewer extends LitElement {
97
97
  colorScheme: "light" | "dark" | "auto" | undefined;
98
98
  /**
99
99
  * The newest render settling: `true` when it landed on the sheet with the
100
- * pages on screen painted, `false` when it failed or there was nothing to
101
- * render. Never rejects failures are the `error` event's and like every
102
- * outcome here it answers for the newest render only. `rendered` fires at
103
- * that same moment.
100
+ * pages on screen finished trying to paint, `false` when it failed or there
101
+ * was nothing to render. A page the browser could not draw is blank and the
102
+ * render still landed. Never rejects failures are the
103
+ * `error` event's — and like every outcome here it answers for the newest
104
+ * render only. `rendered` fires at that same moment.
104
105
  */
105
106
  get renderComplete(): Promise<boolean>;
106
107
 
package/lib/index.js CHANGED
@@ -112,7 +112,13 @@ export class QuarioViewer extends LitElement {
112
112
  #exporting = new Set();
113
113
  /** @type {(() => void) | undefined} */
114
114
  #unwatch;
115
- /** @type {Promise<void>} The newest swap's paint, which `renderComplete` waits behind. */
115
+ /**
116
+ * The newest swap's paint, which `renderComplete` waits behind. Bare, with
117
+ * no catch of its own: the stage settles a page it cannot draw rather than
118
+ * rejecting, so the chain below is reached whatever the pixels did.
119
+ *
120
+ * @type {Promise<void>}
121
+ */
116
122
  #painting = Promise.resolve();
117
123
 
118
124
  // The whole async pipeline: keyed on the render properties, re-run when one
@@ -186,10 +192,13 @@ export class QuarioViewer extends LitElement {
186
192
  }
187
193
 
188
194
  /**
189
- * The newest render settling: `true` when it landed on the sheet, `false`
190
- * when it failed or there was nothing to render. It never rejects a
191
- * failed render is handled, on the panel and through the error event and
192
- * like every outcome here it answers for the newest render only.
195
+ * The newest render settling: `true` when it landed on the sheet — after
196
+ * its pages have finished trying to paint `false` when it failed or there
197
+ * was nothing to render. A page the browser could not draw is blank and the
198
+ * render still landed, so `true` promises the sheet is done changing, not
199
+ * that every page carries pixels. It never rejects — a failed
200
+ * render is handled, on the panel and through the error event — and like
201
+ * every outcome here it answers for the newest render only.
193
202
  *
194
203
  * @returns {Promise<boolean>}
195
204
  */
package/lib/stage.js CHANGED
@@ -21,6 +21,19 @@
21
21
  * asked a thousand-page report for gigabytes of backing store, and past what
22
22
  * the browser would grant the pages simply came up blank.
23
23
  *
24
+ * **A page the browser will not paint is blank, not a failure.** `start`
25
+ * swallows and nothing here rejects: the report laid out and the list reached
26
+ * the sheet, which is all a render promised, so the surfaces have no channel
27
+ * for it and deliberately grow none.
28
+ *
29
+ * Little reaches it. An image that will not decode is the layout's own to
30
+ * absorb — `paint` draws it as nothing and draws the page around it — and a
31
+ * face is parsed with fontkit while the report is measured, so bytes that will
32
+ * not parse are a render error long before the sheet. What is left is the face
33
+ * fontkit accepted and the browser refuses, which the browser suite drives by
34
+ * making `FontFace.prototype.load` reject under a record handed straight to
35
+ * `swap`.
36
+ *
24
37
  * **A canvas on the sheet is never re-pointed at a second paint.** `paint()`
25
38
  * awaits before it draws, so a canvas whose paint is still in flight is
26
39
  * *retired* rather than painted over or emptied: it comes off the sheet, its
@@ -287,6 +300,20 @@ export let stage = () => {
287
300
  * the next pass over the reach tries again instead of counting the page as
288
301
  * painted; one that settles joins `settled`.
289
302
  *
303
+ * **The paint this answers with never rejects**, which is the whole of why
304
+ * `swap` does not either. A page that could not be drawn is not a render
305
+ * that failed: the report laid out and the list reached the sheet, so there
306
+ * is nothing for the error panel to say and nothing to report through the
307
+ * error event. Swallowed here, the one place a paint begins, so no caller
308
+ * has to remember to; one bad page then costs its own pixels rather than
309
+ * every page after it in the reach, which this loop awaits one at a time.
310
+ *
311
+ * See the header for how little can still reach it — the layout absorbs a
312
+ * bad image and refuses a bad face while measuring — and why it stays
313
+ * anyway. A paint that failed is forgotten so the next pass over the reach
314
+ * tries again, which is what makes a failure for a passing reason
315
+ * recoverable.
316
+ *
290
317
  * @type {(i: number) => Promise<void>}
291
318
  */
292
319
  let start = (i) => {
@@ -302,9 +329,8 @@ export let stage = () => {
302
329
  () => {
303
330
  settled.add(canvas);
304
331
  },
305
- (failure) => {
332
+ () => {
306
333
  backed.delete(canvas);
307
- throw failure;
308
334
  },
309
335
  );
310
336
  backed.set(canvas, { scale, painted });
@@ -422,9 +448,8 @@ export let stage = () => {
422
448
  * keeps no epoch of its own, so a swap painting behind it is never cut off
423
449
  * part-painted, and a page it starts is one a swap arriving at the same page
424
450
  * waits behind rather than skips. Nothing awaits these paints here: a scroll
425
- * is not a render and has no failure channel of its own, and a paint that
426
- * failed is forgotten, so the next pass over the reach reports it the way
427
- * any other paint does.
451
+ * is not a render, and a page that will not paint is blank whoever asked for
452
+ * it `start` settles either way, so there is nothing here to catch.
428
453
  */
429
454
  let pending = false;
430
455
  let follow = () => {
@@ -433,7 +458,7 @@ export let stage = () => {
433
458
  requestAnimationFrame(() => {
434
459
  pending = false;
435
460
  if (!list) return;
436
- for (let i of keep()) paintPage(i).catch(() => {});
461
+ for (let i of keep()) void paintPage(i);
437
462
  });
438
463
  };
439
464
  scroll.addEventListener("scroll", follow);
@@ -510,7 +535,9 @@ export let stage = () => {
510
535
  * — and the reach is read from those offsets, so it is chosen after they
511
536
  * are in. What the
512
537
  * returned promise settles behind is the reach, which is what the caller's
513
- * `renderComplete` means by "the pages on screen are painted".
538
+ * `renderComplete` means by "the pages on screen have finished trying to
539
+ * paint" — and **it never rejects**, because `start` swallows a page the
540
+ * browser will not draw and the reach walks on past it.
514
541
  */
515
542
  swap: async (next, faces) => {
516
543
  // Reading the offsets flushes layout, so only an actual reswap pays for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/viewer",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "The embeddable report viewer shell for quario — in the makings, not yet released",
5
5
  "homepage": "https://getquario.com",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@lit/task": "^1.0.3",
47
- "@quario/layout": "^0.2.0",
47
+ "@quario/layout": "^0.3.0",
48
48
  "lit": "^3.3.3"
49
49
  },
50
50
  "devDependencies": {
@@ -53,12 +53,12 @@
53
53
  "@size-limit/preset-small-lib": "^13.0.3",
54
54
  "esbuild": "^0.28.2",
55
55
  "exceljs": "^4.4.0",
56
- "quario": "^0.5.0",
56
+ "quario": "^0.6.0",
57
57
  "size-limit": "^13.0.3",
58
58
  "typescript": "^7.0.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "quario": "^0.5.0"
61
+ "quario": "^0.6.0"
62
62
  },
63
63
  "size-limit": [
64
64
  {