@quario/html 0.4.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 +53 -0
- package/README.md +29 -17
- package/lib/index.js +73 -41
- package/lib/style.css +15 -5
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,59 @@ 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
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A cell's own `currency` code is honoured** at the markup edge, ahead of the
|
|
15
|
+
instance's default, so a listing whose rows arrive in different currencies
|
|
16
|
+
presents each in its own. A code the engine could not accept leaves the cell
|
|
17
|
+
as plain display text rather than presenting it in the instance's currency.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Numbers presented through `format` now show a fixed two fraction digits,
|
|
22
|
+
matching every other target.** `format: "number"` on `1000` renders
|
|
23
|
+
`1,000.00` where it rendered `1,000`, and `0.12345` renders `0.12` where it
|
|
24
|
+
rendered `0.123`; `format: "percent"` on `0.21` renders `21.00%` where it
|
|
25
|
+
rendered `21%`. `format: "currency"` follows the currency's own minor units,
|
|
26
|
+
so a JPY amount loses the two decimals it never had. `date` is unchanged, as
|
|
27
|
+
are grouping separators and symbol placement. The digits come from the
|
|
28
|
+
engine, so a cell reads the same here as it does in a workbook.
|
|
29
|
+
|
|
30
|
+
## [0.5.0] - 2026-09-05
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **A spanning cell emits `colspan`**, ahead of its `style`; a span of one
|
|
35
|
+
emits no attribute. The `<colgroup>` still names every column, so the
|
|
36
|
+
geometry is the document's and only the cells merge — which means the header
|
|
37
|
+
row can carry fewer `<th>` than there are columns.
|
|
38
|
+
|
|
39
|
+
- **`valign`** renders as `vertical-align`, on a cell or on the row's `<tr>`,
|
|
40
|
+
which the browser's own `inherit` on cells carries down. On a split slot the
|
|
41
|
+
item itself becomes a grid with `align-content`, so the content moves inside
|
|
42
|
+
the box the slot wrapper already stretches.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **A row's box now reaches the cells** instead of being dropped. Padding and
|
|
47
|
+
border on `detail.header`, `detail.row` or a total row's `style` are emitted
|
|
48
|
+
inline on each `<th>`/`<td>`, where they also beat the reference
|
|
49
|
+
stylesheet's own cell padding. The `<tr>` still carries the row's other
|
|
50
|
+
declarations and still never carries a box, which it could not honour.
|
|
51
|
+
Fragments of documents that declared a box on a row gain borders and padding
|
|
52
|
+
they previously rendered without.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **Unstyled table cells sit at the top of their row.** The reference
|
|
57
|
+
stylesheet now pins `vertical-align: top` on `.q-table tr`, which the cells
|
|
58
|
+
inherit.
|
|
59
|
+
Before, a browser centred the shorter cells of a wrapping row while the PDF
|
|
60
|
+
painted them from the top, so the same report read differently in the two.
|
|
61
|
+
A host stylesheet overrides it the ordinary way.
|
|
62
|
+
|
|
10
63
|
## [0.4.0] - 2026-09-03
|
|
11
64
|
|
|
12
65
|
### Changed
|
package/README.md
CHANGED
|
@@ -61,8 +61,9 @@ const page = await report.render(html(), data);
|
|
|
61
61
|
</div>
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
The output is a **fragment**: no `<html>`, no `<head>`, no
|
|
65
|
-
page
|
|
64
|
+
The output is a **fragment**: no `<html>`, no `<head>`, no stylesheet of its own — the one
|
|
65
|
+
`<style>` it writes is an `@page { margin }` rule when the schema declares `page.margin`. Wrap it
|
|
66
|
+
in your own page shell and stylesheet.
|
|
66
67
|
|
|
67
68
|
## API
|
|
68
69
|
|
|
@@ -72,9 +73,16 @@ The target factory returns the target you pass to `render`.
|
|
|
72
73
|
`report()` compiles once and `report.render(html(), data)` resolves the fragment. Compile at
|
|
73
74
|
startup and render per request. Definition problems throw at `report()`, at compile time.
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
Two options. `{ paths: true }` stamps `data-q-path="<schema path>"` on each element whose event
|
|
76
77
|
carries one (items, images, group containers, the table and its cells), mapping rendered output
|
|
77
|
-
back to the definition behind it. Off by default.
|
|
78
|
+
back to the definition behind it. Off by default. `{ fonts }` maps a declared `family` name to the
|
|
79
|
+
CSS `font-family` value it should emit — a custom property, a font stack, a quoted name —
|
|
80
|
+
consulted before the three built-in generics; names match case-insensitively, and a value holding
|
|
81
|
+
`;` or `}` is rejected when the target is constructed:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
html({ fonts: { "Instrument Sans": "var(--font-instrument-sans)", mono: "var(--font-ibm-plex-mono)" } });
|
|
85
|
+
```
|
|
78
86
|
|
|
79
87
|
```js
|
|
80
88
|
const report = quario().report(schema, funcs);
|
|
@@ -97,15 +105,17 @@ host. Compilation stays synchronous; render-time failures reject with the same l
|
|
|
97
105
|
These classes are the contract host CSS targets. They are stable, and changing them is a breaking
|
|
98
106
|
change.
|
|
99
107
|
|
|
100
|
-
| Emits
|
|
101
|
-
|
|
|
102
|
-
| `<div class="q-
|
|
103
|
-
| `<div class="q-
|
|
104
|
-
| `<
|
|
105
|
-
| `<
|
|
106
|
-
| `<div class="q-
|
|
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 — 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 |
|
|
117
|
+
|
|
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
|
|
109
119
|
target. The schema never speaks in CSS. Displaying a fragment that contains images under a Content
|
|
110
120
|
Security Policy needs `img-src data:`.
|
|
111
121
|
|
|
@@ -121,8 +131,9 @@ instead. The PDF and XLSX targets carry theirs built in, because their consumers
|
|
|
121
131
|
row set still emits the table, its header, and an empty `<tbody>`. A hidden table cell keeps its `<td>`, empty.
|
|
122
132
|
|
|
123
133
|
This target is unpaginated: it ignores schema page bands (page furniture belongs to your print CSS)
|
|
124
|
-
and maps `break: "page"` and `reset: "page"` to the `q-break` class. Page columns are
|
|
125
|
-
the
|
|
134
|
+
and maps `break: "page"` and `reset: "page"` to the `q-break` class. Page columns are laid out:
|
|
135
|
+
a node declaring `columns` wraps the content between its own bands in the `q-columns` container
|
|
136
|
+
above, and the browser flows it.
|
|
126
137
|
|
|
127
138
|
## Unlicensed marking
|
|
128
139
|
|
|
@@ -134,8 +145,8 @@ badge unstyled, because a watermark would escape the fragment's box onto the hos
|
|
|
134
145
|
## Escaping
|
|
135
146
|
|
|
136
147
|
**Every interpolated value is escaped.** `{{{ }}}` is a definition error, so no schema syntax can
|
|
137
|
-
exempt a data value from escaping.
|
|
138
|
-
|
|
148
|
+
exempt a data value from escaping. Every generated attribute value is escaped too — inline styles, `data-` attributes, `src`, `alt`
|
|
149
|
+
and `colspan`; class names are constants the target owns and never carry data.
|
|
139
150
|
|
|
140
151
|
Literal template text passes through verbatim as author-controlled markup. A definition is
|
|
141
152
|
trusted configuration. Its _data_ is untrusted, and data can never reach the document unescaped.
|
|
@@ -154,6 +165,7 @@ Host CSS targets these classes:
|
|
|
154
165
|
2. The shipped sheet already puts band behavior on the emitted classes:
|
|
155
166
|
`.q-item { min-height: 1lh; white-space: pre-line }` occupies a line and breaks on newlines,
|
|
156
167
|
`.q-group { break-inside: avoid }` keeps a group header with its rows,
|
|
168
|
+
`.q-table tr { break-inside: avoid }` keeps a row whole,
|
|
157
169
|
`.q-break { break-before: page }` honors the schema's own break hint, and `thead` repeats per
|
|
158
170
|
printed page. Yours adds the page geometry in `@page`, which only you can decide.
|
|
159
171
|
3. Print with a headless browser (`page.pdf()` in Playwright or Puppeteer, page numbers via the
|
package/lib/index.js
CHANGED
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
import { display, format, isReportBand, text, walk } from "quario";
|
|
13
13
|
|
|
14
14
|
/** @type {Record<string, string>} */
|
|
15
|
-
let ESC = {
|
|
15
|
+
let ESC = {
|
|
16
|
+
"&": "&",
|
|
17
|
+
"<": "<",
|
|
18
|
+
">": ">",
|
|
19
|
+
'"': """,
|
|
20
|
+
"'": "'",
|
|
21
|
+
};
|
|
16
22
|
/** @type {(value: any) => string} */
|
|
17
23
|
let esc = (value) => display(value).replace(/[&<>"']/g, (char) => ESC[char]);
|
|
18
24
|
|
|
@@ -30,7 +36,7 @@ let markup = (tokens, style, intl) => {
|
|
|
30
36
|
};
|
|
31
37
|
/** @type {(value: any, style: any, intl: any) => any} */
|
|
32
38
|
let shown = (value, style, intl) => {
|
|
33
|
-
let text = format(value, style
|
|
39
|
+
let text = format(value, style, intl);
|
|
34
40
|
return text != null ? text : value;
|
|
35
41
|
};
|
|
36
42
|
|
|
@@ -63,6 +69,13 @@ let UNSAFE = /[;}]/;
|
|
|
63
69
|
/** @type {(value: any) => boolean} */
|
|
64
70
|
let isHex = (value) => typeof value === "string" && HEX.test(value);
|
|
65
71
|
let ALIGNMENTS = ["left", "center", "right"];
|
|
72
|
+
// A slot item's `valign` and `align` as grid placement (see `placed`); the
|
|
73
|
+
// vertical vocabulary is these keys, so the three readers of it agree.
|
|
74
|
+
/** @type {Record<string, string>} */
|
|
75
|
+
let ALIGN_CONTENT = { top: "start", middle: "center", bottom: "end" };
|
|
76
|
+
/** @type {Record<string, string>} */
|
|
77
|
+
let JUSTIFY_ITEMS = { left: "start", center: "center", right: "end" };
|
|
78
|
+
let VALIGNMENTS = Object.keys(ALIGN_CONTENT);
|
|
66
79
|
|
|
67
80
|
// A group instance's own bands. They sit outside a columned container only when
|
|
68
81
|
// that instance is the node which declared the count — an inner instance's are
|
|
@@ -123,6 +136,11 @@ let CSS = {
|
|
|
123
136
|
color: (value) => (isHex(value) ? "color:" + value : ""),
|
|
124
137
|
background: (value) => (isHex(value) ? "background-color:" + value : ""),
|
|
125
138
|
align: (value) => (ALIGNMENTS.includes(value) ? "text-align:" + value : ""),
|
|
139
|
+
// The table reading, on a `<tr>` or a cell: the browser's own sheet gives
|
|
140
|
+
// cells `vertical-align: inherit`, so a row's reaches them like `bold`
|
|
141
|
+
// does. A slot item's is placement, and `placed` below lifts the name out
|
|
142
|
+
// before this map sees it.
|
|
143
|
+
valign: (value) => (VALIGNMENTS.includes(value) ? "vertical-align:" + value : ""),
|
|
126
144
|
// The text itself is untouched, so selection and screen readers get what
|
|
127
145
|
// the author wrote; only its rendering is capitalised.
|
|
128
146
|
uppercase: flag("text-transform:uppercase", "text-transform:none"),
|
|
@@ -156,17 +174,6 @@ let borderSide = (style, side) => {
|
|
|
156
174
|
];
|
|
157
175
|
};
|
|
158
176
|
|
|
159
|
-
/** @type {(name: string) => boolean} */
|
|
160
|
-
let isBox = (name) => name.startsWith("padding") || name.startsWith("border");
|
|
161
|
-
|
|
162
|
-
/** @type {(style: any) => any} */
|
|
163
|
-
let unbox = (style) => {
|
|
164
|
-
if (!style) return;
|
|
165
|
-
let names = Object.keys(style).filter((name) => !isBox(name));
|
|
166
|
-
if (!names.length) return;
|
|
167
|
-
return Object.fromEntries(names.map((name) => [name, style[name]]));
|
|
168
|
-
};
|
|
169
|
-
|
|
170
177
|
/** @type {(style: any) => string[]} */
|
|
171
178
|
let decoLine = (style) => {
|
|
172
179
|
let parts = [];
|
|
@@ -267,14 +274,34 @@ let slotAttr = (slot) =>
|
|
|
267
274
|
esc("display:grid;" + (slot && slot.width != null ? "width:" + slot.width + "%" : "flex:1")) +
|
|
268
275
|
'"';
|
|
269
276
|
|
|
277
|
+
// A slot item's `valign`. The slot wrapper's grid stretches the item to the
|
|
278
|
+
// split's height, so the item is the box and its content has to move inside
|
|
279
|
+
// it: the item becomes a grid of its own and `align-content` places the
|
|
280
|
+
// content. Not on the wrapper -- `align-items` there would shrink the item
|
|
281
|
+
// back to its content and reopen the gap the grid closed. Lifted out of the
|
|
282
|
+
// style so the cell mapping (`vertical-align`) does not also emit on a div.
|
|
283
|
+
// The result is what `styleAttr` takes: a style and the structural prefix.
|
|
284
|
+
/** @type {(event: { style?: any }) => { style?: any, own: string }} */
|
|
285
|
+
let placed = (event) => {
|
|
286
|
+
let valign = event.style?.valign;
|
|
287
|
+
if (!Object.hasOwn(ALIGN_CONTENT, valign)) return { style: event.style, own: "" };
|
|
288
|
+
let { valign: _, ...style } = event.style;
|
|
289
|
+
return { style, own: "display:grid;align-content:" + ALIGN_CONTENT[valign] };
|
|
290
|
+
};
|
|
291
|
+
// A picture is a replaced element, which a grid places at the start rather
|
|
292
|
+
// than stretching, so the container's `text-align` no longer reaches it: the
|
|
293
|
+
// same `align` word rides `justify-items` while the item is a grid.
|
|
294
|
+
/** @type {(own: string, style: any) => string} */
|
|
295
|
+
let justified = (own, style) =>
|
|
296
|
+
own && Object.hasOwn(JUSTIFY_ITEMS, style?.align)
|
|
297
|
+
? own + ";justify-items:" + JUSTIFY_ITEMS[style.align]
|
|
298
|
+
: own;
|
|
299
|
+
|
|
270
300
|
// The identity attribute, escaped like every other generated value at this
|
|
271
301
|
// edge. `mark` is the factory's verdict: a render without `paths` maps every
|
|
272
302
|
// event to "" through the same function rather than re-deciding per call site.
|
|
273
303
|
/** @type {(event: { path?: string }) => string} */
|
|
274
304
|
let pathAttr = ({ path }) => (path ? ' data-q-path="' + esc(path) + '"' : "");
|
|
275
|
-
/** @type {() => string} */
|
|
276
|
-
let noAttr = () => "";
|
|
277
|
-
|
|
278
305
|
/** @type {(list: any[], tag: string, attrs: (event: any) => string, intl: any) => string} */
|
|
279
306
|
let cells = (list, tag, attrs, intl) =>
|
|
280
307
|
list
|
|
@@ -313,12 +340,24 @@ let cells = (list, tag, attrs, intl) =>
|
|
|
313
340
|
* The target (see SCHEMA.md, "Instances and targets").
|
|
314
341
|
*/
|
|
315
342
|
export function html(options) {
|
|
316
|
-
let mark = options?.paths ? pathAttr :
|
|
343
|
+
let mark = options?.paths ? pathAttr : () => "";
|
|
317
344
|
// The factory's verdict, like `mark`: the mapping is validated once, here.
|
|
318
345
|
let styleAttr = styling(options?.fonts);
|
|
319
346
|
// The pair every cell carries, bound once rather than threaded as two.
|
|
320
347
|
/** @type {(cell: any) => string} */
|
|
321
|
-
|
|
348
|
+
// A span covers several columns; `colspan` is what that is on this surface.
|
|
349
|
+
// Escaped like every other attribute value this target writes, though the
|
|
350
|
+
// engine has already vouched the span is a positive integer.
|
|
351
|
+
/** @type {(cell: any) => string} */
|
|
352
|
+
let attrs = (cell) =>
|
|
353
|
+
(cell.span > 1 ? ' colspan="' + esc(cell.span) + '"' : "") + styleAttr(cell) + mark(cell);
|
|
354
|
+
// One table row: the row style on the `<tr>`, from which it inherits into
|
|
355
|
+
// the cells, `valign` included. The box is not among these names — a `<tr>`
|
|
356
|
+
// could not honour one, and the engine has already resolved that half onto
|
|
357
|
+
// the cells, where it arrives as ordinary inline CSS.
|
|
358
|
+
/** @type {(event: any, list: any[], tag: string, intl: any) => string} */
|
|
359
|
+
let tr = (event, list, tag, intl) =>
|
|
360
|
+
"<tr" + styleAttr(event) + ">" + cells(list, tag, attrs, intl) + "</tr>";
|
|
322
361
|
return {
|
|
323
362
|
name: "html",
|
|
324
363
|
compile: (stream) => async (data) => {
|
|
@@ -408,7 +447,11 @@ export function html(options) {
|
|
|
408
447
|
// edge, so the badge stays childless whatever stream a host compiles
|
|
409
448
|
// against.
|
|
410
449
|
"report-start": (event) => {
|
|
411
|
-
intl = {
|
|
450
|
+
intl = {
|
|
451
|
+
locale: event.locale,
|
|
452
|
+
currency: event.currency,
|
|
453
|
+
timeZone: event.timeZone,
|
|
454
|
+
};
|
|
412
455
|
if (event.marking) out += '<div class="q-unlicensed">' + esc(event.marking) + "</div>";
|
|
413
456
|
if (event.margin != null)
|
|
414
457
|
out += "<style>@page{margin:" + esc(event.margin) + "pt}</style>";
|
|
@@ -425,12 +468,13 @@ export function html(options) {
|
|
|
425
468
|
// The stable `q-item q-<role>` classes are the whole class attribute;
|
|
426
469
|
// print CSS targets the band-role class for styling and breaks.
|
|
427
470
|
item: (event) => {
|
|
471
|
+
let slot = placed(event);
|
|
428
472
|
emit(
|
|
429
473
|
event,
|
|
430
474
|
'<div class="q-item q-' +
|
|
431
475
|
event.role +
|
|
432
476
|
'"' +
|
|
433
|
-
styleAttr(
|
|
477
|
+
styleAttr(slot, slot.own) +
|
|
434
478
|
mark(event) +
|
|
435
479
|
">" +
|
|
436
480
|
markup(event.tokens, event.style, intl) +
|
|
@@ -461,6 +505,7 @@ export function html(options) {
|
|
|
461
505
|
// never the markup-passing join a cell body gets; an image without one
|
|
462
506
|
// carries `alt=""`, the decorative-image convention.
|
|
463
507
|
image: (event) => {
|
|
508
|
+
let slot = placed(event);
|
|
464
509
|
let source = sources.get(event.bytes);
|
|
465
510
|
if (source === undefined)
|
|
466
511
|
sources.set(
|
|
@@ -472,7 +517,7 @@ export function html(options) {
|
|
|
472
517
|
'<div class="q-item q-image q-' +
|
|
473
518
|
event.role +
|
|
474
519
|
'"' +
|
|
475
|
-
styleAttr(event) +
|
|
520
|
+
styleAttr(slot, justified(slot.own, event.style)) +
|
|
476
521
|
mark(event) +
|
|
477
522
|
'><img src="' +
|
|
478
523
|
source +
|
|
@@ -522,9 +567,6 @@ export function html(options) {
|
|
|
522
567
|
for (let column of /** @type {any[]} */ (event.columns))
|
|
523
568
|
cols +=
|
|
524
569
|
"<col" + (column.width ? ' style="width:' + esc(column.width) + '%"' : "") + ">";
|
|
525
|
-
let headers = event.columns.map((/** @type {any} */ column) =>
|
|
526
|
-
Object.assign({}, column.header, { path: column.path }),
|
|
527
|
-
);
|
|
528
570
|
out +=
|
|
529
571
|
'<table class="q-table"' +
|
|
530
572
|
mark(event) +
|
|
@@ -532,31 +574,21 @@ export function html(options) {
|
|
|
532
574
|
"<colgroup>" +
|
|
533
575
|
cols +
|
|
534
576
|
"</colgroup>" +
|
|
535
|
-
"<thead
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
"</
|
|
577
|
+
"<thead>" +
|
|
578
|
+
// The header row arrives shaped like a `row` payload, so the same
|
|
579
|
+
// `<tr>` writer takes it whole (docs/adr/0053).
|
|
580
|
+
tr(event.header, event.header.cells, "th", intl) +
|
|
581
|
+
"</thead><tbody>";
|
|
540
582
|
},
|
|
541
583
|
row: (event) => {
|
|
542
|
-
out +=
|
|
543
|
-
"<tr" +
|
|
544
|
-
styleAttr({ style: unbox(event.style) }) +
|
|
545
|
-
">" +
|
|
546
|
-
cells(event.cells, "td", attrs, intl) +
|
|
547
|
-
"</tr>";
|
|
584
|
+
out += tr(event, event.cells, "td", intl);
|
|
548
585
|
},
|
|
549
586
|
"total-row": (event) => {
|
|
550
587
|
if (!tfoot) {
|
|
551
588
|
out += "</tbody><tfoot>";
|
|
552
589
|
tfoot = true;
|
|
553
590
|
}
|
|
554
|
-
out +=
|
|
555
|
-
"<tr" +
|
|
556
|
-
styleAttr({ style: unbox(event.style) }) +
|
|
557
|
-
">" +
|
|
558
|
-
cells(event.cells, "td", attrs, intl) +
|
|
559
|
-
"</tr>";
|
|
591
|
+
out += tr(event, event.cells, "td", intl);
|
|
560
592
|
},
|
|
561
593
|
"table-end": () => {
|
|
562
594
|
out += tfoot ? "</tfoot></table>" : "</tbody></table>";
|
package/lib/style.css
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* rules are not. They are a reference default -- adjust freely, or never link
|
|
16
16
|
* this file at all.
|
|
17
17
|
*
|
|
18
|
-
* The block between the `shared:` markers below is
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* The block between the `shared:` markers below is the one shipped look.
|
|
19
|
+
* The viewer and the editor used to adopt a copy of it into their shadow
|
|
20
|
+
* roots; both now paint the layout list on a canvas and carry no report
|
|
21
|
+
* stylesheet (docs/adr/0039), so `test/stylesheet.test.js` at the repo root
|
|
22
|
+
* gates this block against `example/print.css` growing a copy back.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
/* shared:start */
|
|
@@ -56,6 +56,16 @@
|
|
|
56
56
|
white-space: pre-line;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/* A wrapping row leaves its shorter cells slack, and a browser fills that
|
|
60
|
+
from the middle where the PDF paints from the top. Pin the top so an
|
|
61
|
+
unstyled table reads the same in both; an author who wants otherwise
|
|
62
|
+
declares `valign`. On the row, not the cells: the browser's own sheet gives
|
|
63
|
+
cells `vertical-align: inherit`, which is what lets a row's declaration
|
|
64
|
+
reach them, and a rule on the cells would cut that off. */
|
|
65
|
+
.q-table tr {
|
|
66
|
+
vertical-align: top;
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
.q-table th,
|
|
60
70
|
.q-table td {
|
|
61
71
|
padding: 2pt 6pt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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,17 +39,17 @@
|
|
|
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.6.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.6.0"
|
|
48
48
|
},
|
|
49
49
|
"size-limit": [
|
|
50
50
|
{
|
|
51
51
|
"path": "lib/index.js",
|
|
52
|
-
"limit": "
|
|
52
|
+
"limit": "3 kB",
|
|
53
53
|
"ignore": [
|
|
54
54
|
"quario"
|
|
55
55
|
]
|