@quario/html 0.7.0 → 0.9.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 +84 -56
- package/README.md +44 -31
- package/lib/index.d.ts +7 -1
- package/lib/index.js +36 -8
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,29 +1,90 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @quario/html
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 0.9.0
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
### Minor Changes
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
- **Every target factory now refuses an option it does not understand.** An unknown key, a key with
|
|
8
|
+
a value of the wrong type, or an `options` that is not an object throws a `TypeError` at the
|
|
9
|
+
factory call, naming the option path — `options.meta.title`, `options.page.size`. `@quario/docx`
|
|
10
|
+
already behaved this way. `@quario/pdf`, `@quario/html`, `@quario/xlsx` and `@quario/layout` read
|
|
11
|
+
the keys they knew and ignored the rest, so a typo cost you the option you meant to set with no
|
|
12
|
+
signal at any point. `csv()` takes no options, and it refuses an argument rather than discarding
|
|
13
|
+
one.
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
The check itself is one thing, so the engine now exports it: `hostOptions` closes a key set and
|
|
16
|
+
`hostMeta` validates the `{ title, author, subject }` contract the document targets share. A
|
|
17
|
+
fourth document property is one edit rather than three.
|
|
11
18
|
|
|
12
|
-
|
|
19
|
+
**What this changes for you.** One options object spread across several targets stops working if
|
|
20
|
+
any target does not know one of its keys — `const o = { page, fonts, meta }` passed to `pdf(o)`,
|
|
21
|
+
`html(o)` and `xlsx(o)` is three different key sets. An object holding only what its consumers
|
|
22
|
+
share is unaffected, so `{ page, fonts }` into both `pdf()` and `layout()` still works.
|
|
23
|
+
TypeScript does not warn about this: excess-property checking fires on an object literal and not
|
|
24
|
+
on a variable, so a bag held in a `const` compiles clean and throws when you call the factory.
|
|
25
|
+
|
|
26
|
+
Nullish is absence everywhere, at both levels, so `{ meta: config.meta ?? null }` and
|
|
27
|
+
`{ meta: { title: config.title } }` over a config that carries neither are both fine.
|
|
28
|
+
`html({ paths })` now takes `true` or `false` rather than anything truthy, so `paths: "no"` throws
|
|
29
|
+
instead of turning path stamping on. A `fonts` mapping given as an array is refused by
|
|
30
|
+
`@quario/html` and `@quario/layout` rather than read as families named `0`, `1`, `2`.
|
|
31
|
+
|
|
32
|
+
Every host-option failure is now a `TypeError` rather than a plain `Error`. A `catch` that tests
|
|
33
|
+
`instanceof Error` is unaffected. One that compares the constructor is not.
|
|
34
|
+
|
|
35
|
+
`@quario/docx` is relaxed in three places, each of them now accepting what it refused: `meta: null`
|
|
36
|
+
and `page: null` are absence rather than errors, a property written as `meta: { title: undefined }`
|
|
37
|
+
is a property you did not write rather than one of the wrong type, and an inherited enumerable key
|
|
38
|
+
is no longer reported as an option you wrote.
|
|
39
|
+
|
|
40
|
+
`@quario/pdf`, `@quario/xlsx` and `@quario/docx` now copy `meta` at the factory call. Mutating the
|
|
41
|
+
object you passed no longer changes what a configured target writes.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- Updated dependencies
|
|
46
|
+
- quario@0.9.0
|
|
47
|
+
|
|
48
|
+
## 0.8.0
|
|
49
|
+
|
|
50
|
+
### Minor Changes
|
|
51
|
+
|
|
52
|
+
- **A picture states its own size, so a page stops reflowing around it.** Each
|
|
53
|
+
`<img>` now carries `width` and `height` attributes holding the image's
|
|
54
|
+
natural size in pixels, which lets a browser reserve the right aspect ratio
|
|
55
|
+
before the picture decodes. The `fit` mapping is unchanged and still decides
|
|
56
|
+
the rendered width, now paired with `height:auto` so the height follows the
|
|
57
|
+
width instead of staying pinned: an image capped narrower than its file
|
|
58
|
+
scales rather than squashing. Nothing an author writes changes, and no
|
|
59
|
+
existing report renders differently once its images have decoded.
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- **Every published README says where the documentation is.** Each package now
|
|
64
|
+
carries a Documentation section pointing at the reference, at the report schema
|
|
65
|
+
that normatively specifies what a report may declare, and at the package's own
|
|
66
|
+
API. The paragraphs that used to end on an unstated contract — the event
|
|
67
|
+
stream's field semantics, the style vocabulary, page columns, the Content
|
|
68
|
+
Security Policy a fragment with images needs, the formula mangling, and each
|
|
69
|
+
target's own contract — link the page that states it. Every link is an absolute
|
|
70
|
+
URL, so it resolves from the npm package page as readily as from an installed
|
|
71
|
+
copy.
|
|
72
|
+
- Updated dependencies
|
|
73
|
+
- quario@0.8.0
|
|
74
|
+
|
|
75
|
+
## 0.7.0
|
|
76
|
+
|
|
77
|
+
### Minor Changes
|
|
13
78
|
|
|
14
79
|
- **Styled runs render as spans.** A cell value written as a list of styled runs
|
|
15
80
|
emits one `<span style="…">` per styled run, carrying that run's whole
|
|
16
81
|
resolved inline style; unstyled runs stay bare, so a cell with no runs emits
|
|
17
82
|
exactly the markup it always did. Every interpolated value is escaped as
|
|
18
83
|
before, and the span's own CSS is escaped like any other attribute value.
|
|
19
|
-
|
|
20
|
-
### Changed
|
|
21
|
-
|
|
22
84
|
- **A `format` inside a sentence now presents where the engine says it does.** A
|
|
23
85
|
cell mixing literal text and one interpolation under a `format` renders the
|
|
24
86
|
value plainly, as the spreadsheet and CSV targets already did; splitting the
|
|
25
87
|
value into styled runs is how a value inside a sentence is formatted.
|
|
26
|
-
|
|
27
88
|
- **The reference stylesheet no longer paints a look no other target has.**
|
|
28
89
|
`@quario/html/style.css` dropped the hairline under a table's header row, the
|
|
29
90
|
hairline and bold weight on its total rows, and the rule above the report
|
|
@@ -50,17 +111,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
50
111
|
}
|
|
51
112
|
```
|
|
52
113
|
|
|
53
|
-
##
|
|
114
|
+
## 0.6.0
|
|
54
115
|
|
|
55
|
-
###
|
|
116
|
+
### Minor Changes
|
|
56
117
|
|
|
57
118
|
- **A cell's own `currency` code is honoured** at the markup edge, ahead of the
|
|
58
119
|
instance's default, so a listing whose rows arrive in different currencies
|
|
59
120
|
presents each in its own. A code the engine could not accept leaves the cell
|
|
60
121
|
as plain display text rather than presenting it in the instance's currency.
|
|
61
|
-
|
|
62
|
-
### Changed
|
|
63
|
-
|
|
64
122
|
- **Numbers presented through `format` now show a fixed two fraction digits,
|
|
65
123
|
matching every other target.** `format: "number"` on `1000` renders
|
|
66
124
|
`1,000.00` where it rendered `1,000`, and `0.12345` renders `0.12` where it
|
|
@@ -70,22 +128,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
70
128
|
are grouping separators and symbol placement. The digits come from the
|
|
71
129
|
engine, so a cell reads the same here as it does in a workbook.
|
|
72
130
|
|
|
73
|
-
##
|
|
131
|
+
## 0.5.0
|
|
74
132
|
|
|
75
|
-
###
|
|
133
|
+
### Minor Changes
|
|
76
134
|
|
|
77
135
|
- **A spanning cell emits `colspan`**, ahead of its `style`; a span of one
|
|
78
136
|
emits no attribute. The `<colgroup>` still names every column, so the
|
|
79
137
|
geometry is the document's and only the cells merge — which means the header
|
|
80
138
|
row can carry fewer `<th>` than there are columns.
|
|
81
|
-
|
|
82
139
|
- **`valign`** renders as `vertical-align`, on a cell or on the row's `<tr>`,
|
|
83
140
|
which the browser's own `inherit` on cells carries down. On a split slot the
|
|
84
141
|
item itself becomes a grid with `align-content`, so the content moves inside
|
|
85
142
|
the box the slot wrapper already stretches.
|
|
86
|
-
|
|
87
|
-
### Changed
|
|
88
|
-
|
|
89
143
|
- **A row's box now reaches the cells** instead of being dropped. Padding and
|
|
90
144
|
border on `detail.header`, `detail.row` or a total row's `style` are emitted
|
|
91
145
|
inline on each `<th>`/`<td>`, where they also beat the reference
|
|
@@ -93,9 +147,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
93
147
|
declarations and still never carries a box, which it could not honour.
|
|
94
148
|
Fragments of documents that declared a box on a row gain borders and padding
|
|
95
149
|
they previously rendered without.
|
|
96
|
-
|
|
97
|
-
### Fixed
|
|
98
|
-
|
|
99
150
|
- **Unstyled table cells sit at the top of their row.** The reference
|
|
100
151
|
stylesheet now pins `vertical-align: top` on `.q-table tr`, which the cells
|
|
101
152
|
inherit.
|
|
@@ -103,9 +154,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
103
154
|
painted them from the top, so the same report read differently in the two.
|
|
104
155
|
A host stylesheet overrides it the ordinary way.
|
|
105
156
|
|
|
106
|
-
##
|
|
157
|
+
## 0.4.0
|
|
107
158
|
|
|
108
|
-
###
|
|
159
|
+
### Minor Changes
|
|
109
160
|
|
|
110
161
|
- **A split slot's box now fills the split's height.** Before, a slot's
|
|
111
162
|
background and border stopped at that slot's own content, leaving a gap under
|
|
@@ -113,48 +164,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
113
164
|
`display:grid`, so the item inside it fills the row; a slot's item markup is
|
|
114
165
|
unchanged. See the `@quario/layout` changelog for the rule and for what it
|
|
115
166
|
costs a report that relied on the short box.
|
|
116
|
-
|
|
117
167
|
- **A date string under `format: "date"` now presents as a date.** The
|
|
118
168
|
engine's `format()` helper revives the two read forms, so a cell that
|
|
119
169
|
rendered `2026-08-14` verbatim now renders the presented date. See the
|
|
120
170
|
`quario` changelog for the forms and the timezone rule.
|
|
121
171
|
|
|
122
|
-
##
|
|
172
|
+
## 0.3.0
|
|
123
173
|
|
|
124
|
-
###
|
|
174
|
+
### Minor Changes
|
|
125
175
|
|
|
126
176
|
- **`format` stringifies at the markup edge.** The kind is presented through
|
|
127
177
|
the engine's `format()` helper from the instance locale, then escaped like
|
|
128
178
|
any other interpolated value.
|
|
129
|
-
|
|
130
179
|
- **`page.margin` becomes `@page{margin:<n>pt}`.** Report-header `height`
|
|
131
180
|
is unread: a CSS height would pin in the body box, not from the page top.
|
|
132
|
-
|
|
133
181
|
- **`spaceBefore` / `spaceAfter` map to `margin-top` / `margin-bottom`.**
|
|
134
182
|
Report and group containers are flex columns so adjacent authored gaps add
|
|
135
183
|
rather than collapse.
|
|
136
|
-
|
|
137
184
|
- **Padding and complete border sides map to inline CSS.**
|
|
138
185
|
`box-sizing:border-box` rides the same attribute. A side that does not
|
|
139
186
|
resolve all three names emits nothing, so a missing colour never becomes
|
|
140
187
|
the browser's solid black. Header-row, row, and total-row boxes are
|
|
141
188
|
withdrawn from `<tr>` — CSS does not box a table row — while other row
|
|
142
189
|
styles still inherit.
|
|
143
|
-
|
|
144
|
-
### Changed
|
|
145
|
-
|
|
146
190
|
- **`<tfoot>` holds every total row.** Extra rows are extra `<tr>`; the
|
|
147
191
|
section still closes at `table-end`.
|
|
148
|
-
|
|
149
192
|
- **A visible text item occupies a line, and a literal newline is a break.**
|
|
150
193
|
Before, an empty item collapsed and a newline became a space. The honor
|
|
151
194
|
rules now hang off `.q-item` and `.q-table th, td` in the reference
|
|
152
195
|
stylesheet, the same pattern as `.q-break` — class is the hook, the rule
|
|
153
196
|
is overridable. Empty table cells still collapse. Host CSS still owns
|
|
154
197
|
leading; this is not `line-height: 1.4`.
|
|
155
|
-
|
|
156
|
-
### Fixed
|
|
157
|
-
|
|
158
198
|
- **A declared `false` flag now emits its CSS off-value.** `bold: false` used
|
|
159
199
|
to emit nothing, so the reference stylesheet's band-role `font-weight: bold`
|
|
160
200
|
still won on screen while PDF rendered regular. Exact `false` now writes
|
|
@@ -162,9 +202,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
162
202
|
the decorations), which beats the sheet. Omit and a non-boolean result still
|
|
163
203
|
emit nothing.
|
|
164
204
|
|
|
165
|
-
##
|
|
205
|
+
## 0.2.0
|
|
166
206
|
|
|
167
|
-
###
|
|
207
|
+
### Minor Changes
|
|
168
208
|
|
|
169
209
|
- **`html({ fonts })` says what a family name means on your page.** A report
|
|
170
210
|
declares the typeface it wants — `family: "Instrument Sans"`, or the
|
|
@@ -180,18 +220,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
180
220
|
hashes `@font-face` names, for instance. The mapping resolves families a
|
|
181
221
|
report declares; the face of text that declares none stays your
|
|
182
222
|
stylesheet's, so it is still one CSS rule away.
|
|
183
|
-
|
|
184
223
|
- **The report default rides a report root.** Every fragment is now wrapped in
|
|
185
224
|
one `<div class="q-report">`, and a report that declares a `style` carries it
|
|
186
225
|
there as an inline `font-family`/`font-size` — written once for the document
|
|
187
226
|
rather than repeated on every item, with CSS inheritance doing the rest. The
|
|
188
227
|
unlicensed marking stays outside the root, as the fragment's first element
|
|
189
228
|
and the root's sibling, so no authored style can reach it.
|
|
190
|
-
|
|
191
229
|
- **`uppercase` maps to `text-transform:uppercase`.** The markup carries the
|
|
192
230
|
text exactly as authored and only its rendering is capitalised, so what a
|
|
193
231
|
reader selects, copies, or hears from a screen reader is your own casing.
|
|
194
|
-
|
|
195
232
|
- **Splits render as a flex row of sized slots.** A
|
|
196
233
|
[split](https://getquario.com/docs) becomes
|
|
197
234
|
`<div class="q-split q-<role>">` carrying inline `display:flex`, with each
|
|
@@ -208,9 +245,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
208
245
|
One definition renders many times, so one path appears on every instance of
|
|
209
246
|
it. Escaped like every generated attribute, and off by default — the
|
|
210
247
|
attribute is weight a plain display pays for nothing.
|
|
211
|
-
|
|
212
|
-
### Changed
|
|
213
|
-
|
|
214
248
|
- **A family name that could not be written as CSS now falls back instead of
|
|
215
249
|
being rewritten.** A `family` carrying characters that cannot appear in a
|
|
216
250
|
family name was stripped down to whatever survived and emitted anyway, which
|
|
@@ -219,35 +253,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
219
253
|
text renders in the face above it: the report default, or your stylesheet's
|
|
220
254
|
baseline. Ordinary names are unaffected. To resolve a name your page knows
|
|
221
255
|
under a different one, map it with `html({ fonts })`.
|
|
222
|
-
|
|
223
256
|
- **The fragment now has a root element.** `<div class="q-report">` wraps every
|
|
224
257
|
render, whether or not a report declares a default. Host CSS that assumed
|
|
225
258
|
items and tables were the fragment's top-level children needs a descendant
|
|
226
259
|
selector; nothing else about the markup moved.
|
|
227
|
-
|
|
228
260
|
- **The reference stylesheet's baseline face moved onto `.q-report`.** It was
|
|
229
261
|
on `.q-item`, `.q-table` and `.q-unlicensed` — a direct rule, which would
|
|
230
262
|
have beaten the report default an author writes on the document. The marking
|
|
231
263
|
keeps its own rule, being outside the root. Same look, one rule up.
|
|
232
|
-
|
|
233
264
|
- **A bare `Date` escapes to ISO 8601 UTC, the same on every machine.**
|
|
234
265
|
Interpolated `Date` values rendered through `String(date)`, which bakes the
|
|
235
266
|
host's timezone and locale into the markup. They now render as
|
|
236
267
|
`toISOString()` text through the engine's shared display rule — escaped
|
|
237
268
|
like every other value — so the same report produces the same HTML
|
|
238
269
|
everywhere. Formatted dates keep coming from registered functions.
|
|
239
|
-
|
|
240
|
-
### Fixed
|
|
241
|
-
|
|
242
270
|
- **The reference stylesheet sets a base font family.** A fragment dropped
|
|
243
271
|
into a page with no font of its own inherited the browser default — a serif
|
|
244
272
|
— where every other target rendered a grotesque. `@quario/html/style.css`
|
|
245
273
|
now sets `sans-serif` on report text, as an ordinary rule any host overrides
|
|
246
274
|
by source order.
|
|
247
275
|
|
|
248
|
-
##
|
|
276
|
+
## 0.1.0
|
|
249
277
|
|
|
250
|
-
###
|
|
278
|
+
### Minor Changes
|
|
251
279
|
|
|
252
280
|
- **HTML fragments for compiled reports.** Semantic tables, stable `q-*`
|
|
253
281
|
classes, every interpolated value escaped. Pass `html()` to `report.render`
|
package/README.md
CHANGED
|
@@ -69,7 +69,8 @@ in your own page shell and stylesheet.
|
|
|
69
69
|
|
|
70
70
|
### `html(options?)`
|
|
71
71
|
|
|
72
|
-
The target factory
|
|
72
|
+
The target factory takes this target's host options, validates them at the call, and returns
|
|
73
|
+
the target you pass to `render`. The factory refuses an option it does not know. It throws a `TypeError` at the call for an unknown key. It throws one also for a key with a value of the wrong type.
|
|
73
74
|
`report()` compiles once and `report.render(html(), data)` resolves the fragment. Compile at
|
|
74
75
|
startup and render per request. Definition problems throw at `report()`, at compile time.
|
|
75
76
|
|
|
@@ -77,8 +78,8 @@ Two options. `{ paths: true }` stamps `data-q-path="<schema path>"` on each elem
|
|
|
77
78
|
carries one (items, images, group containers, the table and its cells), mapping rendered output
|
|
78
79
|
back to the definition behind it. Off by default. `{ fonts }` maps a declared `family` name to the
|
|
79
80
|
CSS `font-family` value it should emit — a custom property, a font stack, a quoted name —
|
|
80
|
-
consulted before the three built-in generics
|
|
81
|
-
`;` or `}`
|
|
81
|
+
consulted before the three built-in generics. Names match case-insensitively, and the factory
|
|
82
|
+
rejects a value holding `;` or `}` when it constructs the target:
|
|
82
83
|
|
|
83
84
|
```js
|
|
84
85
|
html({ fonts: { "Instrument Sans": "var(--font-instrument-sans)", mono: "var(--font-ibm-plex-mono)" } });
|
|
@@ -95,61 +96,66 @@ report.stream(data); // the raw event generator, if you want events instead
|
|
|
95
96
|
```
|
|
96
97
|
|
|
97
98
|
Engine-level options (`query` budgets, the license key) live on the instance
|
|
98
|
-
(`quario({ query, license })`)
|
|
99
|
+
(`quario({ query, license })`). See the [engine README](https://www.npmjs.com/package/quario).
|
|
99
100
|
|
|
100
|
-
Rendering is asynchronous and
|
|
101
|
-
host. Compilation stays synchronous
|
|
101
|
+
Rendering is asynchronous and returns the loop between batches, so a large report never blocks the
|
|
102
|
+
host. Compilation stays synchronous. Render-time failures reject with the same located errors.
|
|
102
103
|
|
|
103
104
|
## Output contract
|
|
104
105
|
|
|
105
106
|
These classes are the contract host CSS targets. They are stable, and changing them is a breaking
|
|
106
107
|
change.
|
|
107
108
|
|
|
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
|
|
114
|
-
| `<div class="q-item q-image q-<role>">` | Each image item, holding one `<img
|
|
115
|
-
| `<div class="q-columns" style="column-count:<n>">` | The content between a node's own bands when it declares `columns
|
|
116
|
-
| `<div class="q-split q-<role>">` | Each split, carrying inline `display:flex
|
|
109
|
+
| Emits | For |
|
|
110
|
+
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `<div class="q-report">` | The fragment's root, holding every band |
|
|
112
|
+
| `<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` |
|
|
113
|
+
| `<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"` |
|
|
114
|
+
| `<table class="q-table">` | Each table, with a real `colgroup`, `thead`, `tbody`, and a `tfoot` around the total rows the walk emits. No `tfoot` when `total` is absent or every row of it is hidden. A spanning cell carries `colspan` |
|
|
115
|
+
| `<div class="q-item q-image q-<role>">` | Each image item, holding one `<img>`. Its `src` is a base64 `data:` URI of the event's bytes, and its `width`/`height` are the picture's natural size. `fit` sets `max-width:100%` or `width:100%` on it, paired with `height:auto`. The target escapes the rendered `alt` |
|
|
116
|
+
| `<div class="q-columns" style="column-count:<n>">` | The content between a node's own bands when it declares `columns`. On the root, that is the body between report header and footer. On a group, it is inside that instance's `q-group` |
|
|
117
|
+
| `<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
118
|
|
|
118
119
|
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
120
|
target. The schema never speaks in CSS. Displaying a fragment that contains images under a Content
|
|
120
|
-
Security Policy needs `img-src data
|
|
121
|
+
Security Policy needs `img-src data:` (see
|
|
122
|
+
[Trust and CSP](https://getquario.com/docs/diving-deeper/security/)).
|
|
121
123
|
|
|
122
124
|
Style declarations map to inline CSS (`bold` → `font-weight:bold` or `font-weight:normal`, `size` → `font-size:<n>pt`,
|
|
123
125
|
`family: "mono"` → `font-family:monospace`, …). Occupy-a-line and newline-as-break are **not** inline: they hang off
|
|
124
126
|
`.q-item` and `.q-table th, td` in [`@quario/html/style.css`](#the-reference-stylesheet), the same way `.q-break`
|
|
125
|
-
honors `break: "page"`. The class is the hook
|
|
126
|
-
A fragment without that sheet still carries the classes and the text, including newlines
|
|
127
|
+
honors `break: "page"`. The class is the hook. The rule is a reference default a host overrides on source order.
|
|
128
|
+
A fragment without that sheet still carries the classes and the text, including newlines. It does not occupy or
|
|
127
129
|
break until some stylesheet says so. This target supplies no defaults in the markup: no weight or size per band
|
|
128
130
|
role, no leading, no padding, no spacing between bands, no borders. An inline `style` attribute would beat yours
|
|
129
|
-
in the cascade and force `!important` on you
|
|
131
|
+
in the cascade and force `!important` on you. The defaults and the honor rules ship as an ordinary stylesheet
|
|
130
132
|
instead. The PDF and XLSX targets carry theirs built in, because their consumers have no stylesheet. An empty
|
|
131
133
|
row set still emits the table, its header, and an empty `<tbody>`. A hidden table cell keeps its `<td>`, empty.
|
|
132
134
|
|
|
133
|
-
This target
|
|
135
|
+
This target paginates nothing: it ignores schema page bands (page furniture belongs to your print CSS)
|
|
134
136
|
and maps `break: "page"` and `reset: "page"` to the `q-break` class. Page columns are laid out:
|
|
135
137
|
a node declaring `columns` wraps the content between its own bands in the `q-columns` container
|
|
136
138
|
above, and the browser flows it.
|
|
137
139
|
|
|
140
|
+
The full contract is [The HTML target](https://getquario.com/docs/diving-deeper/html-target/),
|
|
141
|
+
with each declaration's fate in the
|
|
142
|
+
[support matrix](https://getquario.com/docs/reference/support-matrix/).
|
|
143
|
+
|
|
138
144
|
## Unlicensed marking
|
|
139
145
|
|
|
140
146
|
An unlicensed render opens the fragment with `<div class="q-unlicensed">` holding the wording
|
|
141
147
|
from `report-start.marking` as escaped text. A licensed render emits no badge. The element and its
|
|
142
|
-
position are normative
|
|
148
|
+
position are normative. Visual treatment belongs to the host. The shipped stylesheet leaves the
|
|
143
149
|
badge unstyled, because a watermark would escape the fragment's box onto the host page.
|
|
144
150
|
|
|
145
151
|
## Escaping
|
|
146
152
|
|
|
147
|
-
**
|
|
148
|
-
exempt a data value from escaping.
|
|
149
|
-
and `colspan
|
|
153
|
+
**This target escapes every interpolated value.** `{{{ }}}` is a definition error, so no schema syntax
|
|
154
|
+
can exempt a data value from escaping. It escapes every generated attribute value too — inline styles,
|
|
155
|
+
`data-` attributes, `src`, `alt` and `colspan`. Class names are constants the target owns and never carry data.
|
|
150
156
|
|
|
151
157
|
Literal template text passes through verbatim as author-controlled markup. A definition is
|
|
152
|
-
trusted configuration. Its _data_ is
|
|
158
|
+
trusted configuration. Its _data_ is not, and data can never reach the document unescaped.
|
|
153
159
|
|
|
154
160
|
## Printing to PDF
|
|
155
161
|
|
|
@@ -169,7 +175,7 @@ Host CSS targets these classes:
|
|
|
169
175
|
`.q-break { break-before: page }` honors the schema's own break hint, and `thead` repeats per
|
|
170
176
|
printed page. Yours adds the page geometry in `@page`, which only you can decide.
|
|
171
177
|
3. Print with a headless browser (`page.pdf()` in Playwright or Puppeteer, page numbers via the
|
|
172
|
-
footer template) or a Paged-CSS engine (WeasyPrint
|
|
178
|
+
footer template) or a Paged-CSS engine (WeasyPrint or Prince, with page numbers via `@page` margin
|
|
173
179
|
boxes).
|
|
174
180
|
|
|
175
181
|
### The reference stylesheet
|
|
@@ -189,21 +195,28 @@ const css = readFileSync(new URL(import.meta.resolve("@quario/html/style.css")),
|
|
|
189
195
|
const page = `<!doctype html><html><head><style>${css}</style></head><body>${fragment}</body></html>`;
|
|
190
196
|
```
|
|
191
197
|
|
|
192
|
-
Its **selectors** are contract
|
|
198
|
+
Its **selectors** are contract. Its **rules** are not. It is a reference default meant to be
|
|
193
199
|
overridden. Every rule is ordinary specificity, so your own stylesheet loaded after it wins
|
|
194
200
|
without `!important` anywhere. Link nothing at all and you get unstyled markup.
|
|
195
201
|
|
|
196
202
|
Two things it leaves to you: `@page` geometry and body type, which only a host can decide, and
|
|
197
203
|
any watermark treatment of `.q-unlicensed`, left as plain text here because `position: fixed`
|
|
198
|
-
would paint over your whole page. `example/print.css` in the repository is a host's half
|
|
204
|
+
would paint over your whole page. `example/print.css` in the repository is a host's half that does
|
|
199
205
|
both, and `example/print.js` is the complete pipeline.
|
|
200
206
|
|
|
207
|
+
## Documentation
|
|
208
|
+
|
|
209
|
+
[The quario documentation](https://getquario.com/docs/) is the reference.
|
|
210
|
+
The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
|
|
211
|
+
specification of what a report may declare, and
|
|
212
|
+
[`@quario/html`](https://getquario.com/docs/reference/html/) is this package's own API.
|
|
213
|
+
|
|
201
214
|
## License
|
|
202
215
|
|
|
203
|
-
Commercial software with readable source.
|
|
204
|
-
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
216
|
+
Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
|
|
217
|
+
Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
205
218
|
|
|
206
|
-
Pass your license key once, on the instance
|
|
219
|
+
Pass your license key once, on the instance. quario verifies it offline:
|
|
207
220
|
|
|
208
221
|
```js
|
|
209
222
|
const q = quario({ license: "quario_..." });
|
package/lib/index.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { Target } from "quario";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Host controls, taken and validated at the factory call: an option this
|
|
5
|
+
* target does not understand, or one of the wrong type, throws a `TypeError`
|
|
6
|
+
* there rather than costing the host the option in silence.
|
|
7
|
+
*/
|
|
3
8
|
export interface HtmlOptions {
|
|
4
9
|
/**
|
|
5
10
|
* Emit `data-q-path` on each element whose event carries a schema `path`,
|
|
6
11
|
* mapping rendered output back to the definition behind it — the editor's
|
|
7
|
-
* selection seam. Off by default
|
|
12
|
+
* selection seam. Off by default, and a value that is neither `true` nor
|
|
13
|
+
* `false` throws from `html()` rather than reading as on.
|
|
8
14
|
*/
|
|
9
15
|
paths?: boolean;
|
|
10
16
|
/**
|
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* never carries markup-escaped text.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { display, format, isReportBand, styledRuns, text, walk } from "quario";
|
|
12
|
+
import { display, format, hostOptions, isReportBand, styledRuns, text, walk } from "quario";
|
|
13
13
|
|
|
14
14
|
/** @type {Record<string, string>} */
|
|
15
15
|
let ESC = {
|
|
@@ -112,16 +112,30 @@ let OWN = new Set(["group-header", "group-footer"]);
|
|
|
112
112
|
// too: one option, one message shape.
|
|
113
113
|
/** @type {(path: string, value: any) => string} */
|
|
114
114
|
let cssValue = (path, value) => {
|
|
115
|
-
if (typeof value !== "string" || !value)
|
|
116
|
-
|
|
115
|
+
if (typeof value !== "string" || !value)
|
|
116
|
+
throw TypeError(path + ": expected a CSS font-family value");
|
|
117
|
+
if (UNSAFE.test(value)) throw TypeError(path + ": cannot contain ';' or '}'");
|
|
117
118
|
return value;
|
|
118
119
|
};
|
|
119
120
|
|
|
121
|
+
// A flag rather than anything truthy: `html({ paths: "no" })` reads as yes
|
|
122
|
+
// under a truthiness test, which is the opposite of what the host wrote
|
|
123
|
+
// (`docs/adr/0072`).
|
|
124
|
+
/** @type {(paths: any) => boolean} */
|
|
125
|
+
let stamping = (paths) => {
|
|
126
|
+
if (paths != null && typeof paths !== "boolean")
|
|
127
|
+
throw TypeError("options.paths: expected true or false");
|
|
128
|
+
return Boolean(paths);
|
|
129
|
+
};
|
|
130
|
+
|
|
120
131
|
/** @type {(fonts: any) => Record<string, string>} */
|
|
121
132
|
let mapping = (fonts) => {
|
|
122
133
|
if (fonts == null) return {};
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
// A record of family names, so the key set is open and `hostOptions` is the
|
|
135
|
+
// wrong tool -- but an array is a `typeof "object"` that would name families
|
|
136
|
+
// 0, 1, 2, so it is refused here.
|
|
137
|
+
if (typeof fonts !== "object" || Array.isArray(fonts))
|
|
138
|
+
throw TypeError("options.fonts: expected an object of family names to CSS values");
|
|
125
139
|
return Object.fromEntries(
|
|
126
140
|
Object.entries(fonts).map(([name, value]) => [
|
|
127
141
|
name.toLowerCase(),
|
|
@@ -368,7 +382,8 @@ let cells = (list, tag, attrs, intl, markup) =>
|
|
|
368
382
|
* The target (see SCHEMA.md, "Instances and targets").
|
|
369
383
|
*/
|
|
370
384
|
export function html(options) {
|
|
371
|
-
|
|
385
|
+
hostOptions(options, ["paths", "fonts"], "options");
|
|
386
|
+
let mark = stamping(options?.paths) ? pathAttr : () => "";
|
|
372
387
|
// The factory's verdict, like `mark`: the mapping is validated once, here.
|
|
373
388
|
let { attr: styleAttr, map: fontMap } = styling(options?.fonts);
|
|
374
389
|
// A cell's markup, bound to this target's font table once.
|
|
@@ -534,6 +549,14 @@ export function html(options) {
|
|
|
534
549
|
// one, and escaped when it is encoded rather than per occurrence. `alt` is report data, so it is the display-text join escaped,
|
|
535
550
|
// never the markup-passing join a cell body gets; an image without one
|
|
536
551
|
// carries `alt=""`, the decorative-image convention.
|
|
552
|
+
//
|
|
553
|
+
// `width`/`height` are the event's natural size, which the engine has
|
|
554
|
+
// already read and vouched for, so a picture reserves its aspect ratio
|
|
555
|
+
// before it decodes rather than reflowing the page around it. The pair
|
|
556
|
+
// is a presentational hint, not a ratio on its own, which is why the
|
|
557
|
+
// `fit` style carries `height:auto`: without it the stated height is a
|
|
558
|
+
// used height and a picture capped narrower than its file renders
|
|
559
|
+
// squashed. SCHEMA.md, "The HTML target", states that normatively.
|
|
537
560
|
image: (event) => {
|
|
538
561
|
let slot = placed(event);
|
|
539
562
|
let source = sources.get(event.bytes);
|
|
@@ -551,10 +574,15 @@ export function html(options) {
|
|
|
551
574
|
mark(event) +
|
|
552
575
|
'><img src="' +
|
|
553
576
|
source +
|
|
577
|
+
'" width="' +
|
|
578
|
+
esc(event.width) +
|
|
579
|
+
'" height="' +
|
|
580
|
+
esc(event.height) +
|
|
554
581
|
'" alt="' +
|
|
555
582
|
esc(text(event.alt || [])) +
|
|
556
|
-
|
|
557
|
-
(event.fit === "width" ?
|
|
583
|
+
'" style="' +
|
|
584
|
+
(event.fit === "width" ? "" : "max-") +
|
|
585
|
+
'width:100%;height:auto">' +
|
|
558
586
|
"</div>",
|
|
559
587
|
);
|
|
560
588
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.
|
|
42
|
+
"quario": "^0.9.0",
|
|
43
43
|
"size-limit": "^13.0.3",
|
|
44
44
|
"typescript": "^7.0.2"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"quario": "^0.
|
|
47
|
+
"quario": "^0.9.0"
|
|
48
48
|
},
|
|
49
49
|
"size-limit": [
|
|
50
50
|
{
|