@quario/viewer 0.1.0 → 0.2.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,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-09-01
11
+
12
+ ### Changed
13
+
14
+ - **The sheet is now page-shaped, so short reports reserve a full page.**
15
+ `page` sized the sheet's width and padding and ignored its height, so a
16
+ report shorter than a page was drawn on paper the shape of its own content —
17
+ an A4 half-page invoice came out twice as wide as it was tall while the PDF
18
+ export was a true A4 page. The sheet now takes at least one page of height as
19
+ well. It remains a minimum, so a longer report keeps growing on the one
20
+ continuous sheet, and nothing about a report of a page or more changes.
21
+
22
+ **This is visible if you size a container to the viewer.** An A4 sheet at
23
+ fit-width is roughly a thousand pixels tall against five hundred before, so a
24
+ box fitted to a short report roughly doubles. Set `zoom` to a percentage small
25
+ enough where the box has to stay small. The sheet still draws no page
26
+ boundaries.
27
+
28
+ - **The adopted sheet's baseline face moved onto `.q-report`.** It was on
29
+ `.q-item` and `.q-table`, where a direct rule would have beaten a report's
30
+ own declared family. Same look; the fragment's new root is what carries it.
31
+
32
+ ### Fixed
33
+
34
+ - **The sheet no longer draws report text in the platform's own face.** It
35
+ hard-coded `system-ui`, which is SF Pro on macOS, Segoe UI on Windows and
36
+ Roboto on Android — so the same report showed a different typeface to every
37
+ visitor, and none of them matched what the PDF exported. Text declaring no
38
+ `family` now renders in `sans-serif`, the same declaration the PDF target
39
+ resolves to Helvetica. The viewer's own chrome keeps `system-ui`, which is
40
+ what a toolbar should do.
41
+
10
42
  ## [0.1.0] - 2026-08-27
11
43
 
12
44
  ### Added
package/README.md CHANGED
@@ -73,8 +73,8 @@ state: a slow render can never overwrite a newer one, and a superseded render fi
73
73
  Assignments are compared by identity, so to re-render from the same object, assign a fresh one
74
74
  (`view.data = { ...data }`).
75
75
 
76
- `page` is the pdf target's vocabulary and defaults, and it is **purely visual**: it sizes the white
77
- sheet (width and padding) the browser lays the report out on. The PDF target's own geometry lives
76
+ `page` is the pdf target's vocabulary and defaults, and it is **purely visual**: it gives the white
77
+ sheet the browser lays the report out on its width, its padding, and at least one page of height. The PDF target's own geometry lives
78
78
  in the host's `pdf({ page })` configuration. Pass the same values to both so preview and export
79
79
  agree.
80
80
 
@@ -223,6 +223,13 @@ they agree.
223
223
  On-screen layout is the browser's: one continuous sheet, no pagination. The PDF export paginates
224
224
  independently in the pdf target. The preview approximates; the exports are exact.
225
225
 
226
+ The sheet is page-shaped: `page` gives it both its width and at least one page of height, so a
227
+ report shorter than a page sits on paper the shape of the page you configured rather than on a band
228
+ the height of its own content. It is a minimum, so a longer report keeps growing on the one sheet.
229
+ Sizing a container to a viewer showing a short report therefore reserves a full page — set `zoom`
230
+ to a percentage small enough where the box has to stay small. The sheet still draws no page
231
+ boundaries: only the pdf target knows where pages fall.
232
+
226
233
  The sheet is the html target's fragment, so a report with image items shows them through that
227
234
  target's `data:` URIs. A host page with a Content Security Policy needs `img-src data:` for them
228
235
  to display, the same deployment requirement the fragment carries anywhere else.
package/lib/index.js CHANGED
@@ -34,15 +34,6 @@ import { REPORT } from "./style.js";
34
34
  import { EXPORTS, download, exportGroup } from "./toolbar.js";
35
35
  import { wanted } from "./zoom.js";
36
36
 
37
- let OPTION = ["page", "zoom", "filename", "colorScheme"];
38
- let SUBJECT = ["data", "report"];
39
-
40
- /** @type {(el: LitElement, changed: Map<string, unknown>) => boolean} */
41
- let optionWrite = (el, changed) => !el.hasUpdated || OPTION.some((key) => changed.has(key));
42
-
43
- /** @type {(changed: Map<string, unknown>) => boolean} */
44
- let subjectWrite = (changed) => SUBJECT.some((key) => changed.has(key));
45
-
46
37
  /** @type {(report: unknown, targets: unknown) => boolean} */
47
38
  let vacant = (report, targets) => report === undefined && targets === undefined;
48
39
 
@@ -202,10 +193,14 @@ export class QuarioViewer extends LitElement {
202
193
  // last good values and is stashed for the task, whose next run reports it
203
194
  // through the one failure channel; flipping validity re-runs the task by
204
195
  // epoch, because the render arguments themselves did not change.
205
- if (optionWrite(this, changed)) this.#checkOptions(changed);
196
+ if (
197
+ !this.hasUpdated ||
198
+ ["page", "zoom", "filename", "colorScheme"].some((key) => changed.has(key))
199
+ )
200
+ this.#checkOptions(changed);
206
201
  // A new document is a new subject: dismissing the last failure said
207
202
  // nothing about this one.
208
- if (subjectWrite(changed)) this.#dismissed = false;
203
+ if (["data", "report"].some((key) => changed.has(key))) this.#dismissed = false;
209
204
  }
210
205
 
211
206
  render() {
@@ -300,26 +295,6 @@ export class QuarioViewer extends LitElement {
300
295
  }
301
296
  }
302
297
 
303
- /** @param {Map<string, unknown>} changed */
304
- #commitPage(changed) {
305
- let box = geometry(this.page);
306
- if (!this.hasUpdated || changed.has("page")) {
307
- this.#box = box;
308
- this.#reapply = true;
309
- }
310
- }
311
-
312
- /** @param {Map<string, unknown>} changed */
313
- #commitZoom(changed) {
314
- let mode = level(this.zoom);
315
- // Committed only when the host wrote `zoom`, so re-validating on a
316
- // filename change cannot clobber the mode the reader clicked to.
317
- if (!this.hasUpdated || changed.has("zoom")) {
318
- this.#mode = mode;
319
- this.#reapply = true;
320
- }
321
- }
322
-
323
298
  /**
324
299
  * Each property validates and commits on its own, stashing the first
325
300
  * failure: one bad property must not block a good write to another. All
@@ -338,8 +313,22 @@ export class QuarioViewer extends LitElement {
338
313
  #checkOptions(changed) {
339
314
  let was = this.#invalid;
340
315
  this.#invalid = undefined;
341
- this.#take(() => this.#commitPage(changed));
342
- this.#take(() => this.#commitZoom(changed));
316
+ this.#take(() => {
317
+ let box = geometry(this.page);
318
+ if (!this.hasUpdated || changed.has("page")) {
319
+ this.#box = box;
320
+ this.#reapply = true;
321
+ }
322
+ });
323
+ this.#take(() => {
324
+ let mode = level(this.zoom);
325
+ // Committed only when the host wrote `zoom`, so re-validating on a
326
+ // filename change cannot clobber the mode the reader clicked to.
327
+ if (!this.hasUpdated || changed.has("zoom")) {
328
+ this.#mode = mode;
329
+ this.#reapply = true;
330
+ }
331
+ });
343
332
  this.#take(() => {
344
333
  this.#name = name(this.filename);
345
334
  });
package/lib/stage.js CHANGED
@@ -58,7 +58,12 @@ export let SURFACE = css`
58
58
  transform-origin: 0 0;
59
59
  background: #fff;
60
60
  color: #000;
61
- font: 10pt/1.4 system-ui, sans-serif;
61
+ /* Sheet geometry only. The report text's own family comes from the shared
62
+ report stylesheet, which the html target ships byte-identically -- a
63
+ preview and an export that disagree on typeface is the bug this splits
64
+ apart (docs/adr/0014). */
65
+ font-size: 10pt;
66
+ line-height: 1.4;
62
67
  box-shadow: var(--_sheet-shadow);
63
68
  }
64
69
 
@@ -107,7 +112,9 @@ let measure = (sheet, text, pxPerPt) => {
107
112
  probe.style.cssText =
108
113
  "position:absolute;visibility:hidden;white-space:nowrap;font:" +
109
114
  FACE +
110
- "pt system-ui,sans-serif";
115
+ // The face the marking is actually drawn in, or the measurement sizes
116
+ // wording that will be laid out in something else.
117
+ "pt sans-serif";
111
118
  probe.textContent = text;
112
119
  sheet.append(probe);
113
120
  let width = probe.offsetWidth / pxPerPt;
@@ -154,7 +161,9 @@ let stampAt = (face, y) => {
154
161
  mark.style.top = y + "pt";
155
162
  let wording = document.createElement("span");
156
163
  wording.textContent = face.text;
157
- wording.style.font = face.fontSize + "pt system-ui, sans-serif";
164
+ // The same face the PDF target draws the marking in (`families.sans[0]`,
165
+ // Helvetica): quario's own wording, so the two must not disagree either.
166
+ wording.style.font = face.fontSize + "pt sans-serif";
158
167
  wording.style.transform = "translate(-50%, -50%) rotate(" + face.turn + "deg)";
159
168
  mark.append(wording);
160
169
  return mark;
@@ -296,14 +305,20 @@ export let stage = () => {
296
305
  /**
297
306
  * Give the sheet its point geometry. Page points are CSS points, so the
298
307
  * host's values transfer unit-for-unit; the wrapper re-sizes against the
299
- * result so the scroll extent keeps describing what is there. Height is
300
- * the watermark stamp period (full page box), not a sheet height.
308
+ * result so the scroll extent keeps describing what is there.
309
+ *
310
+ * The sheet is the page's shape: its width, and at least one page tall,
311
+ * the same rule the editor's sheet follows. A minimum rather than a
312
+ * height, because the sheet stays one continuous surface -- so the height
313
+ * goes on serving as the watermark stamp period along it too
314
+ * (docs/adr/0035).
301
315
  *
302
316
  * @param {PageBox} next
303
317
  */
304
318
  resize: (next) => {
305
319
  page = next;
306
320
  sheet.style.width = next.width + "pt";
321
+ sheet.style.minHeight = next.height + "pt";
307
322
  sheet.style.padding = next.margin + "pt";
308
323
  marking(true);
309
324
  paint();
package/lib/style.js CHANGED
@@ -23,6 +23,19 @@ import { css } from "lit";
23
23
 
24
24
  export let REPORT = css`
25
25
  /* shared:start */
26
+
27
+ /*
28
+ * The baseline face hangs off the report root rather than off .q-item and
29
+ * .q-table: a direct rule beats an inherited one, so a rule on the items
30
+ * themselves would override the report default an author writes on the
31
+ * document, which the root carries as an inline font-family. The marking is
32
+ * outside the root and keeps its own rule for that reason.
33
+ */
34
+ .q-report,
35
+ .q-unlicensed {
36
+ font-family: sans-serif;
37
+ }
38
+
26
39
  .q-table {
27
40
  width: 100%;
28
41
  border-collapse: collapse;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/viewer",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -48,28 +48,30 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@arethetypeswrong/cli": "^0.18.3",
51
- "@quario/csv": "^0.1.0",
52
- "@quario/html": "^0.1.0",
53
- "@quario/pdf": "^0.1.0",
54
- "@quario/xlsx": "^0.1.0",
51
+ "@quario/csv": "^0.2.0",
52
+ "@quario/html": "^0.2.0",
53
+ "@quario/pdf": "^0.2.0",
54
+ "@quario/xlsx": "^0.2.0",
55
55
  "@size-limit/preset-small-lib": "^13.0.3",
56
56
  "esbuild": "^0.28.2",
57
57
  "exceljs": "^4.4.0",
58
58
  "pdf-lib": "^1.17.1",
59
- "quario": "^0.1.0",
59
+ "quario": "^0.2.0",
60
60
  "size-limit": "^13.0.3",
61
61
  "typescript": "^7.0.2"
62
62
  },
63
63
  "peerDependencies": {
64
- "quario": "^0.1.0"
64
+ "quario": "^0.2.0"
65
65
  },
66
66
  "size-limit": [
67
67
  {
68
68
  "path": "lib/index.js",
69
69
  "ignore": [
70
- "quario"
70
+ "quario",
71
+ "lit",
72
+ "@lit/task"
71
73
  ],
72
- "limit": "14 kB"
74
+ "limit": "8.5 kB"
73
75
  }
74
76
  ],
75
77
  "engines": {