@quario/html 0.7.0 → 0.8.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,29 +1,45 @@
1
- # Changelog
1
+ # @quario/html
2
2
 
3
- All notable changes to @quario/html are documented in this file.
3
+ ## 0.8.0
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ ### Minor Changes
7
6
 
8
- ## [Unreleased]
7
+ - **A picture states its own size, so a page stops reflowing around it.** Each
8
+ `<img>` now carries `width` and `height` attributes holding the image's
9
+ natural size in pixels, which lets a browser reserve the right aspect ratio
10
+ before the picture decodes. The `fit` mapping is unchanged and still decides
11
+ the rendered width, now paired with `height:auto` so the height follows the
12
+ width instead of staying pinned: an image capped narrower than its file
13
+ scales rather than squashing. Nothing an author writes changes, and no
14
+ existing report renders differently once its images have decoded.
9
15
 
10
- ## [0.7.0] - 2026-09-07
16
+ ### Patch Changes
11
17
 
12
- ### Added
18
+ - **Every published README says where the documentation is.** Each package now
19
+ carries a Documentation section pointing at the reference, at the report schema
20
+ that normatively specifies what a report may declare, and at the package's own
21
+ API. The paragraphs that used to end on an unstated contract — the event
22
+ stream's field semantics, the style vocabulary, page columns, the Content
23
+ Security Policy a fragment with images needs, the formula mangling, and each
24
+ target's own contract — link the page that states it. Every link is an absolute
25
+ URL, so it resolves from the npm package page as readily as from an installed
26
+ copy.
27
+ - Updated dependencies
28
+ - quario@0.8.0
29
+
30
+ ## 0.7.0
31
+
32
+ ### Minor Changes
13
33
 
14
34
  - **Styled runs render as spans.** A cell value written as a list of styled runs
15
35
  emits one `<span style="…">` per styled run, carrying that run's whole
16
36
  resolved inline style; unstyled runs stay bare, so a cell with no runs emits
17
37
  exactly the markup it always did. Every interpolated value is escaped as
18
38
  before, and the span's own CSS is escaped like any other attribute value.
19
-
20
- ### Changed
21
-
22
39
  - **A `format` inside a sentence now presents where the engine says it does.** A
23
40
  cell mixing literal text and one interpolation under a `format` renders the
24
41
  value plainly, as the spreadsheet and CSV targets already did; splitting the
25
42
  value into styled runs is how a value inside a sentence is formatted.
26
-
27
43
  - **The reference stylesheet no longer paints a look no other target has.**
28
44
  `@quario/html/style.css` dropped the hairline under a table's header row, the
29
45
  hairline and bold weight on its total rows, and the rule above the report
@@ -50,17 +66,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
50
66
  }
51
67
  ```
52
68
 
53
- ## [0.6.0] - 2026-09-07
69
+ ## 0.6.0
54
70
 
55
- ### Added
71
+ ### Minor Changes
56
72
 
57
73
  - **A cell's own `currency` code is honoured** at the markup edge, ahead of the
58
74
  instance's default, so a listing whose rows arrive in different currencies
59
75
  presents each in its own. A code the engine could not accept leaves the cell
60
76
  as plain display text rather than presenting it in the instance's currency.
61
-
62
- ### Changed
63
-
64
77
  - **Numbers presented through `format` now show a fixed two fraction digits,
65
78
  matching every other target.** `format: "number"` on `1000` renders
66
79
  `1,000.00` where it rendered `1,000`, and `0.12345` renders `0.12` where it
@@ -70,22 +83,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
70
83
  are grouping separators and symbol placement. The digits come from the
71
84
  engine, so a cell reads the same here as it does in a workbook.
72
85
 
73
- ## [0.5.0] - 2026-09-05
86
+ ## 0.5.0
74
87
 
75
- ### Added
88
+ ### Minor Changes
76
89
 
77
90
  - **A spanning cell emits `colspan`**, ahead of its `style`; a span of one
78
91
  emits no attribute. The `<colgroup>` still names every column, so the
79
92
  geometry is the document's and only the cells merge — which means the header
80
93
  row can carry fewer `<th>` than there are columns.
81
-
82
94
  - **`valign`** renders as `vertical-align`, on a cell or on the row's `<tr>`,
83
95
  which the browser's own `inherit` on cells carries down. On a split slot the
84
96
  item itself becomes a grid with `align-content`, so the content moves inside
85
97
  the box the slot wrapper already stretches.
86
-
87
- ### Changed
88
-
89
98
  - **A row's box now reaches the cells** instead of being dropped. Padding and
90
99
  border on `detail.header`, `detail.row` or a total row's `style` are emitted
91
100
  inline on each `<th>`/`<td>`, where they also beat the reference
@@ -93,9 +102,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
93
102
  declarations and still never carries a box, which it could not honour.
94
103
  Fragments of documents that declared a box on a row gain borders and padding
95
104
  they previously rendered without.
96
-
97
- ### Fixed
98
-
99
105
  - **Unstyled table cells sit at the top of their row.** The reference
100
106
  stylesheet now pins `vertical-align: top` on `.q-table tr`, which the cells
101
107
  inherit.
@@ -103,9 +109,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
103
109
  painted them from the top, so the same report read differently in the two.
104
110
  A host stylesheet overrides it the ordinary way.
105
111
 
106
- ## [0.4.0] - 2026-09-03
112
+ ## 0.4.0
107
113
 
108
- ### Changed
114
+ ### Minor Changes
109
115
 
110
116
  - **A split slot's box now fills the split's height.** Before, a slot's
111
117
  background and border stopped at that slot's own content, leaving a gap under
@@ -113,48 +119,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
113
119
  `display:grid`, so the item inside it fills the row; a slot's item markup is
114
120
  unchanged. See the `@quario/layout` changelog for the rule and for what it
115
121
  costs a report that relied on the short box.
116
-
117
122
  - **A date string under `format: "date"` now presents as a date.** The
118
123
  engine's `format()` helper revives the two read forms, so a cell that
119
124
  rendered `2026-08-14` verbatim now renders the presented date. See the
120
125
  `quario` changelog for the forms and the timezone rule.
121
126
 
122
- ## [0.3.0] - 2026-09-02
127
+ ## 0.3.0
123
128
 
124
- ### Added
129
+ ### Minor Changes
125
130
 
126
131
  - **`format` stringifies at the markup edge.** The kind is presented through
127
132
  the engine's `format()` helper from the instance locale, then escaped like
128
133
  any other interpolated value.
129
-
130
134
  - **`page.margin` becomes `@page{margin:<n>pt}`.** Report-header `height`
131
135
  is unread: a CSS height would pin in the body box, not from the page top.
132
-
133
136
  - **`spaceBefore` / `spaceAfter` map to `margin-top` / `margin-bottom`.**
134
137
  Report and group containers are flex columns so adjacent authored gaps add
135
138
  rather than collapse.
136
-
137
139
  - **Padding and complete border sides map to inline CSS.**
138
140
  `box-sizing:border-box` rides the same attribute. A side that does not
139
141
  resolve all three names emits nothing, so a missing colour never becomes
140
142
  the browser's solid black. Header-row, row, and total-row boxes are
141
143
  withdrawn from `<tr>` — CSS does not box a table row — while other row
142
144
  styles still inherit.
143
-
144
- ### Changed
145
-
146
145
  - **`<tfoot>` holds every total row.** Extra rows are extra `<tr>`; the
147
146
  section still closes at `table-end`.
148
-
149
147
  - **A visible text item occupies a line, and a literal newline is a break.**
150
148
  Before, an empty item collapsed and a newline became a space. The honor
151
149
  rules now hang off `.q-item` and `.q-table th, td` in the reference
152
150
  stylesheet, the same pattern as `.q-break` — class is the hook, the rule
153
151
  is overridable. Empty table cells still collapse. Host CSS still owns
154
152
  leading; this is not `line-height: 1.4`.
155
-
156
- ### Fixed
157
-
158
153
  - **A declared `false` flag now emits its CSS off-value.** `bold: false` used
159
154
  to emit nothing, so the reference stylesheet's band-role `font-weight: bold`
160
155
  still won on screen while PDF rendered regular. Exact `false` now writes
@@ -162,9 +157,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
162
157
  the decorations), which beats the sheet. Omit and a non-boolean result still
163
158
  emit nothing.
164
159
 
165
- ## [0.2.0] - 2026-09-01
160
+ ## 0.2.0
166
161
 
167
- ### Added
162
+ ### Minor Changes
168
163
 
169
164
  - **`html({ fonts })` says what a family name means on your page.** A report
170
165
  declares the typeface it wants — `family: "Instrument Sans"`, or the
@@ -180,18 +175,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
180
175
  hashes `@font-face` names, for instance. The mapping resolves families a
181
176
  report declares; the face of text that declares none stays your
182
177
  stylesheet's, so it is still one CSS rule away.
183
-
184
178
  - **The report default rides a report root.** Every fragment is now wrapped in
185
179
  one `<div class="q-report">`, and a report that declares a `style` carries it
186
180
  there as an inline `font-family`/`font-size` — written once for the document
187
181
  rather than repeated on every item, with CSS inheritance doing the rest. The
188
182
  unlicensed marking stays outside the root, as the fragment's first element
189
183
  and the root's sibling, so no authored style can reach it.
190
-
191
184
  - **`uppercase` maps to `text-transform:uppercase`.** The markup carries the
192
185
  text exactly as authored and only its rendering is capitalised, so what a
193
186
  reader selects, copies, or hears from a screen reader is your own casing.
194
-
195
187
  - **Splits render as a flex row of sized slots.** A
196
188
  [split](https://getquario.com/docs) becomes
197
189
  `<div class="q-split q-<role>">` carrying inline `display:flex`, with each
@@ -208,9 +200,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
208
200
  One definition renders many times, so one path appears on every instance of
209
201
  it. Escaped like every generated attribute, and off by default — the
210
202
  attribute is weight a plain display pays for nothing.
211
-
212
- ### Changed
213
-
214
203
  - **A family name that could not be written as CSS now falls back instead of
215
204
  being rewritten.** A `family` carrying characters that cannot appear in a
216
205
  family name was stripped down to whatever survived and emitted anyway, which
@@ -219,35 +208,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
219
208
  text renders in the face above it: the report default, or your stylesheet's
220
209
  baseline. Ordinary names are unaffected. To resolve a name your page knows
221
210
  under a different one, map it with `html({ fonts })`.
222
-
223
211
  - **The fragment now has a root element.** `<div class="q-report">` wraps every
224
212
  render, whether or not a report declares a default. Host CSS that assumed
225
213
  items and tables were the fragment's top-level children needs a descendant
226
214
  selector; nothing else about the markup moved.
227
-
228
215
  - **The reference stylesheet's baseline face moved onto `.q-report`.** It was
229
216
  on `.q-item`, `.q-table` and `.q-unlicensed` — a direct rule, which would
230
217
  have beaten the report default an author writes on the document. The marking
231
218
  keeps its own rule, being outside the root. Same look, one rule up.
232
-
233
219
  - **A bare `Date` escapes to ISO 8601 UTC, the same on every machine.**
234
220
  Interpolated `Date` values rendered through `String(date)`, which bakes the
235
221
  host's timezone and locale into the markup. They now render as
236
222
  `toISOString()` text through the engine's shared display rule — escaped
237
223
  like every other value — so the same report produces the same HTML
238
224
  everywhere. Formatted dates keep coming from registered functions.
239
-
240
- ### Fixed
241
-
242
225
  - **The reference stylesheet sets a base font family.** A fragment dropped
243
226
  into a page with no font of its own inherited the browser default — a serif
244
227
  — where every other target rendered a grotesque. `@quario/html/style.css`
245
228
  now sets `sans-serif` on report text, as an ordinary rule any host overrides
246
229
  by source order.
247
230
 
248
- ## [0.1.0] - 2026-08-27
231
+ ## 0.1.0
249
232
 
250
- ### Added
233
+ ### Minor Changes
251
234
 
252
235
  - **HTML fragments for compiled reports.** Semantic tables, stable `q-*`
253
236
  classes, every interpolated value escaped. Pass `html()` to `report.render`
package/README.md CHANGED
@@ -105,19 +105,20 @@ host. Compilation stays synchronous; render-time failures reject with the same l
105
105
  These classes are the contract host CSS targets. They are stable, and changing them is a breaking
106
106
  change.
107
107
 
108
- | Emits | For |
109
- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
110
- | `<div class="q-report">` | The fragment's root, holding every band |
111
- | `<div class="q-item q-<role>">` | Every item. Roles: `q-report-header`, `q-empty`, `q-group-header`, `q-detail`, `q-group-footer`, `q-report-footer` |
112
- | `<div class="q-group" data-group="name">` | Each group instance, wrapping its header, nested content, and footer. Adds `q-break` when the group declares `break: "page"` or `reset: "page"` |
113
- | `<table class="q-table">` | Each table, with a real `colgroup`, `thead`, `tbody`, and a `tfoot` around the total rows the walk emits — none when `total` is absent or every row of it is hidden. A spanning cell carries `colspan` |
114
- | `<div class="q-item q-image q-<role>">` | Each image item, holding one `<img>` whose `src` is a base64 `data:` URI of the event's bytes; `fit` sets `max-width:100%` or `width:100%` on it, and the rendered `alt` is escaped |
115
- | `<div class="q-columns" style="column-count:<n>">` | The content between a node's own bands when it declares `columns`: on the root, the body between report header and footer; on a group, inside that instance's `q-group` |
116
- | `<div class="q-split q-<role>">` | Each split, carrying inline `display:flex`; its slots are `<div class="q-slot">` carrying inline `display:grid` and their share (a slot's `valign` as `align-content`), each holding the slot item's ordinary container |
108
+ | Emits | For |
109
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
110
+ | `<div class="q-report">` | The fragment's root, holding every band |
111
+ | `<div class="q-item q-<role>">` | Every item. Roles: `q-report-header`, `q-empty`, `q-group-header`, `q-detail`, `q-group-footer`, `q-report-footer` |
112
+ | `<div class="q-group" data-group="name">` | Each group instance, wrapping its header, nested content, and footer. Adds `q-break` when the group declares `break: "page"` or `reset: "page"` |
113
+ | `<table class="q-table">` | Each table, with a real `colgroup`, `thead`, `tbody`, and a `tfoot` around the total rows the walk emits — none when `total` is absent or every row of it is hidden. A spanning cell carries `colspan` |
114
+ | `<div class="q-item q-image q-<role>">` | Each image item, holding one `<img>` whose `src` is a base64 `data:` URI of the event's bytes and whose `width`/`height` are the picture's natural size; `fit` sets `max-width:100%` or `width:100%` on it, paired with `height:auto`, and the rendered `alt` is escaped |
115
+ | `<div class="q-columns" style="column-count:<n>">` | The content between a node's own bands when it declares `columns`: on the root, the body between report header and footer; on a group, inside that instance's `q-group` |
116
+ | `<div class="q-split q-<role>">` | Each split, carrying inline `display:flex`; its slots are `<div class="q-slot">` carrying inline `display:grid` and their share (a slot's `valign` as `align-content`), each holding the slot item's ordinary container |
117
117
 
118
118
  A column `width` becomes an inline `width:<n>%` on its `<col>`. A row's `style` lands on its `<tr>`, except the box, which the engine has already resolved onto the cells (each `<td>` carries `box-sizing:border-box`). The classes above belong to this
119
119
  target. The schema never speaks in CSS. Displaying a fragment that contains images under a Content
120
- Security Policy needs `img-src data:`.
120
+ Security Policy needs `img-src data:` (see
121
+ [Trust and CSP](https://getquario.com/docs/diving-deeper/security/)).
121
122
 
122
123
  Style declarations map to inline CSS (`bold` → `font-weight:bold` or `font-weight:normal`, `size` → `font-size:<n>pt`,
123
124
  `family: "mono"` → `font-family:monospace`, …). Occupy-a-line and newline-as-break are **not** inline: they hang off
@@ -135,6 +136,10 @@ and maps `break: "page"` and `reset: "page"` to the `q-break` class. Page column
135
136
  a node declaring `columns` wraps the content between its own bands in the `q-columns` container
136
137
  above, and the browser flows it.
137
138
 
139
+ The full contract is [The HTML target](https://getquario.com/docs/diving-deeper/html-target/),
140
+ with each declaration's fate in the
141
+ [support matrix](https://getquario.com/docs/reference/support-matrix/).
142
+
138
143
  ## Unlicensed marking
139
144
 
140
145
  An unlicensed render opens the fragment with `<div class="q-unlicensed">` holding the wording
@@ -198,6 +203,13 @@ any watermark treatment of `.q-unlicensed`, left as plain text here because `pos
198
203
  would paint over your whole page. `example/print.css` in the repository is a host's half showing
199
204
  both, and `example/print.js` is the complete pipeline.
200
205
 
206
+ ## Documentation
207
+
208
+ [The quario documentation](https://getquario.com/docs/) is the reference.
209
+ The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
210
+ specification of what a report may declare, and
211
+ [`@quario/html`](https://getquario.com/docs/reference/html/) is this package's own API.
212
+
201
213
  ## License
202
214
 
203
215
  Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
package/lib/index.js CHANGED
@@ -534,6 +534,14 @@ export function html(options) {
534
534
  // one, and escaped when it is encoded rather than per occurrence. `alt` is report data, so it is the display-text join escaped,
535
535
  // never the markup-passing join a cell body gets; an image without one
536
536
  // carries `alt=""`, the decorative-image convention.
537
+ //
538
+ // `width`/`height` are the event's natural size, which the engine has
539
+ // already read and vouched for, so a picture reserves its aspect ratio
540
+ // before it decodes rather than reflowing the page around it. The pair
541
+ // is a presentational hint, not a ratio on its own, which is why the
542
+ // `fit` style carries `height:auto`: without it the stated height is a
543
+ // used height and a picture capped narrower than its file renders
544
+ // squashed. SCHEMA.md, "The HTML target", states that normatively.
537
545
  image: (event) => {
538
546
  let slot = placed(event);
539
547
  let source = sources.get(event.bytes);
@@ -551,10 +559,15 @@ export function html(options) {
551
559
  mark(event) +
552
560
  '><img src="' +
553
561
  source +
562
+ '" width="' +
563
+ esc(event.width) +
564
+ '" height="' +
565
+ esc(event.height) +
554
566
  '" alt="' +
555
567
  esc(text(event.alt || [])) +
556
- // Both keep the aspect ratio, since neither names a height.
557
- (event.fit === "width" ? '" style="width:100%">' : '" style="max-width:100%">') +
568
+ '" style="' +
569
+ (event.fit === "width" ? "" : "max-") +
570
+ 'width:100%;height:auto">' +
558
571
  "</div>",
559
572
  );
560
573
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/html",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "The HTML render target for quario — in the makings, not yet released",
5
5
  "homepage": "https://getquario.com",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -39,12 +39,12 @@
39
39
  "devDependencies": {
40
40
  "@arethetypeswrong/cli": "^0.18.3",
41
41
  "@size-limit/preset-small-lib": "^13.0.3",
42
- "quario": "^0.7.0",
42
+ "quario": "^0.8.0",
43
43
  "size-limit": "^13.0.3",
44
44
  "typescript": "^7.0.2"
45
45
  },
46
46
  "peerDependencies": {
47
- "quario": "^0.7.0"
47
+ "quario": "^0.8.0"
48
48
  },
49
49
  "size-limit": [
50
50
  {