@quario/html 0.6.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 +81 -55
- package/README.md +27 -12
- package/lib/index.js +57 -14
- package/lib/style.css +0 -11
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,23 +1,79 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
# @quario/html
|
|
2
|
+
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
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.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
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
|
|
33
|
+
|
|
34
|
+
- **Styled runs render as spans.** A cell value written as a list of styled runs
|
|
35
|
+
emits one `<span style="…">` per styled run, carrying that run's whole
|
|
36
|
+
resolved inline style; unstyled runs stay bare, so a cell with no runs emits
|
|
37
|
+
exactly the markup it always did. Every interpolated value is escaped as
|
|
38
|
+
before, and the span's own CSS is escaped like any other attribute value.
|
|
39
|
+
- **A `format` inside a sentence now presents where the engine says it does.** A
|
|
40
|
+
cell mixing literal text and one interpolation under a `format` renders the
|
|
41
|
+
value plainly, as the spreadsheet and CSV targets already did; splitting the
|
|
42
|
+
value into styled runs is how a value inside a sentence is formatted.
|
|
43
|
+
- **The reference stylesheet no longer paints a look no other target has.**
|
|
44
|
+
`@quario/html/style.css` dropped the hairline under a table's header row, the
|
|
45
|
+
hairline and bold weight on its total rows, and the rule above the report
|
|
46
|
+
footer. A report that declares no borders now renders without them on screen,
|
|
47
|
+
which is what the PDF and the worksheet have always shown for the same
|
|
48
|
+
report — the difference was the stylesheet's alone, and it also surfaced as a
|
|
49
|
+
cell whose computed border colour failed keeping a black stroke here while
|
|
50
|
+
every other target drew nothing. The sheet now carries the band-role defaults,
|
|
51
|
+
the table's structure and the honor and pagination rules, and no look beyond
|
|
52
|
+
them. The classes are unchanged, so a host that wants the old look adds three
|
|
53
|
+
rules to its own sheet after the shipped one:
|
|
54
|
+
|
|
55
|
+
```css
|
|
56
|
+
.q-table thead th {
|
|
57
|
+
border-bottom: 0.5pt solid #000;
|
|
58
|
+
}
|
|
59
|
+
.q-table tfoot td {
|
|
60
|
+
border-top: 0.5pt solid #000;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
}
|
|
63
|
+
.q-item.q-report-footer {
|
|
64
|
+
border-top: 1pt solid #000;
|
|
65
|
+
padding-top: 4pt;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 0.6.0
|
|
70
|
+
|
|
71
|
+
### Minor Changes
|
|
13
72
|
|
|
14
73
|
- **A cell's own `currency` code is honoured** at the markup edge, ahead of the
|
|
15
74
|
instance's default, so a listing whose rows arrive in different currencies
|
|
16
75
|
presents each in its own. A code the engine could not accept leaves the cell
|
|
17
76
|
as plain display text rather than presenting it in the instance's currency.
|
|
18
|
-
|
|
19
|
-
### Changed
|
|
20
|
-
|
|
21
77
|
- **Numbers presented through `format` now show a fixed two fraction digits,
|
|
22
78
|
matching every other target.** `format: "number"` on `1000` renders
|
|
23
79
|
`1,000.00` where it rendered `1,000`, and `0.12345` renders `0.12` where it
|
|
@@ -27,22 +83,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
83
|
are grouping separators and symbol placement. The digits come from the
|
|
28
84
|
engine, so a cell reads the same here as it does in a workbook.
|
|
29
85
|
|
|
30
|
-
##
|
|
86
|
+
## 0.5.0
|
|
31
87
|
|
|
32
|
-
###
|
|
88
|
+
### Minor Changes
|
|
33
89
|
|
|
34
90
|
- **A spanning cell emits `colspan`**, ahead of its `style`; a span of one
|
|
35
91
|
emits no attribute. The `<colgroup>` still names every column, so the
|
|
36
92
|
geometry is the document's and only the cells merge — which means the header
|
|
37
93
|
row can carry fewer `<th>` than there are columns.
|
|
38
|
-
|
|
39
94
|
- **`valign`** renders as `vertical-align`, on a cell or on the row's `<tr>`,
|
|
40
95
|
which the browser's own `inherit` on cells carries down. On a split slot the
|
|
41
96
|
item itself becomes a grid with `align-content`, so the content moves inside
|
|
42
97
|
the box the slot wrapper already stretches.
|
|
43
|
-
|
|
44
|
-
### Changed
|
|
45
|
-
|
|
46
98
|
- **A row's box now reaches the cells** instead of being dropped. Padding and
|
|
47
99
|
border on `detail.header`, `detail.row` or a total row's `style` are emitted
|
|
48
100
|
inline on each `<th>`/`<td>`, where they also beat the reference
|
|
@@ -50,9 +102,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
50
102
|
declarations and still never carries a box, which it could not honour.
|
|
51
103
|
Fragments of documents that declared a box on a row gain borders and padding
|
|
52
104
|
they previously rendered without.
|
|
53
|
-
|
|
54
|
-
### Fixed
|
|
55
|
-
|
|
56
105
|
- **Unstyled table cells sit at the top of their row.** The reference
|
|
57
106
|
stylesheet now pins `vertical-align: top` on `.q-table tr`, which the cells
|
|
58
107
|
inherit.
|
|
@@ -60,9 +109,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
60
109
|
painted them from the top, so the same report read differently in the two.
|
|
61
110
|
A host stylesheet overrides it the ordinary way.
|
|
62
111
|
|
|
63
|
-
##
|
|
112
|
+
## 0.4.0
|
|
64
113
|
|
|
65
|
-
###
|
|
114
|
+
### Minor Changes
|
|
66
115
|
|
|
67
116
|
- **A split slot's box now fills the split's height.** Before, a slot's
|
|
68
117
|
background and border stopped at that slot's own content, leaving a gap under
|
|
@@ -70,48 +119,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
70
119
|
`display:grid`, so the item inside it fills the row; a slot's item markup is
|
|
71
120
|
unchanged. See the `@quario/layout` changelog for the rule and for what it
|
|
72
121
|
costs a report that relied on the short box.
|
|
73
|
-
|
|
74
122
|
- **A date string under `format: "date"` now presents as a date.** The
|
|
75
123
|
engine's `format()` helper revives the two read forms, so a cell that
|
|
76
124
|
rendered `2026-08-14` verbatim now renders the presented date. See the
|
|
77
125
|
`quario` changelog for the forms and the timezone rule.
|
|
78
126
|
|
|
79
|
-
##
|
|
127
|
+
## 0.3.0
|
|
80
128
|
|
|
81
|
-
###
|
|
129
|
+
### Minor Changes
|
|
82
130
|
|
|
83
131
|
- **`format` stringifies at the markup edge.** The kind is presented through
|
|
84
132
|
the engine's `format()` helper from the instance locale, then escaped like
|
|
85
133
|
any other interpolated value.
|
|
86
|
-
|
|
87
134
|
- **`page.margin` becomes `@page{margin:<n>pt}`.** Report-header `height`
|
|
88
135
|
is unread: a CSS height would pin in the body box, not from the page top.
|
|
89
|
-
|
|
90
136
|
- **`spaceBefore` / `spaceAfter` map to `margin-top` / `margin-bottom`.**
|
|
91
137
|
Report and group containers are flex columns so adjacent authored gaps add
|
|
92
138
|
rather than collapse.
|
|
93
|
-
|
|
94
139
|
- **Padding and complete border sides map to inline CSS.**
|
|
95
140
|
`box-sizing:border-box` rides the same attribute. A side that does not
|
|
96
141
|
resolve all three names emits nothing, so a missing colour never becomes
|
|
97
142
|
the browser's solid black. Header-row, row, and total-row boxes are
|
|
98
143
|
withdrawn from `<tr>` — CSS does not box a table row — while other row
|
|
99
144
|
styles still inherit.
|
|
100
|
-
|
|
101
|
-
### Changed
|
|
102
|
-
|
|
103
145
|
- **`<tfoot>` holds every total row.** Extra rows are extra `<tr>`; the
|
|
104
146
|
section still closes at `table-end`.
|
|
105
|
-
|
|
106
147
|
- **A visible text item occupies a line, and a literal newline is a break.**
|
|
107
148
|
Before, an empty item collapsed and a newline became a space. The honor
|
|
108
149
|
rules now hang off `.q-item` and `.q-table th, td` in the reference
|
|
109
150
|
stylesheet, the same pattern as `.q-break` — class is the hook, the rule
|
|
110
151
|
is overridable. Empty table cells still collapse. Host CSS still owns
|
|
111
152
|
leading; this is not `line-height: 1.4`.
|
|
112
|
-
|
|
113
|
-
### Fixed
|
|
114
|
-
|
|
115
153
|
- **A declared `false` flag now emits its CSS off-value.** `bold: false` used
|
|
116
154
|
to emit nothing, so the reference stylesheet's band-role `font-weight: bold`
|
|
117
155
|
still won on screen while PDF rendered regular. Exact `false` now writes
|
|
@@ -119,9 +157,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
119
157
|
the decorations), which beats the sheet. Omit and a non-boolean result still
|
|
120
158
|
emit nothing.
|
|
121
159
|
|
|
122
|
-
##
|
|
160
|
+
## 0.2.0
|
|
123
161
|
|
|
124
|
-
###
|
|
162
|
+
### Minor Changes
|
|
125
163
|
|
|
126
164
|
- **`html({ fonts })` says what a family name means on your page.** A report
|
|
127
165
|
declares the typeface it wants — `family: "Instrument Sans"`, or the
|
|
@@ -137,18 +175,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
137
175
|
hashes `@font-face` names, for instance. The mapping resolves families a
|
|
138
176
|
report declares; the face of text that declares none stays your
|
|
139
177
|
stylesheet's, so it is still one CSS rule away.
|
|
140
|
-
|
|
141
178
|
- **The report default rides a report root.** Every fragment is now wrapped in
|
|
142
179
|
one `<div class="q-report">`, and a report that declares a `style` carries it
|
|
143
180
|
there as an inline `font-family`/`font-size` — written once for the document
|
|
144
181
|
rather than repeated on every item, with CSS inheritance doing the rest. The
|
|
145
182
|
unlicensed marking stays outside the root, as the fragment's first element
|
|
146
183
|
and the root's sibling, so no authored style can reach it.
|
|
147
|
-
|
|
148
184
|
- **`uppercase` maps to `text-transform:uppercase`.** The markup carries the
|
|
149
185
|
text exactly as authored and only its rendering is capitalised, so what a
|
|
150
186
|
reader selects, copies, or hears from a screen reader is your own casing.
|
|
151
|
-
|
|
152
187
|
- **Splits render as a flex row of sized slots.** A
|
|
153
188
|
[split](https://getquario.com/docs) becomes
|
|
154
189
|
`<div class="q-split q-<role>">` carrying inline `display:flex`, with each
|
|
@@ -165,9 +200,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
165
200
|
One definition renders many times, so one path appears on every instance of
|
|
166
201
|
it. Escaped like every generated attribute, and off by default — the
|
|
167
202
|
attribute is weight a plain display pays for nothing.
|
|
168
|
-
|
|
169
|
-
### Changed
|
|
170
|
-
|
|
171
203
|
- **A family name that could not be written as CSS now falls back instead of
|
|
172
204
|
being rewritten.** A `family` carrying characters that cannot appear in a
|
|
173
205
|
family name was stripped down to whatever survived and emitted anyway, which
|
|
@@ -176,35 +208,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
176
208
|
text renders in the face above it: the report default, or your stylesheet's
|
|
177
209
|
baseline. Ordinary names are unaffected. To resolve a name your page knows
|
|
178
210
|
under a different one, map it with `html({ fonts })`.
|
|
179
|
-
|
|
180
211
|
- **The fragment now has a root element.** `<div class="q-report">` wraps every
|
|
181
212
|
render, whether or not a report declares a default. Host CSS that assumed
|
|
182
213
|
items and tables were the fragment's top-level children needs a descendant
|
|
183
214
|
selector; nothing else about the markup moved.
|
|
184
|
-
|
|
185
215
|
- **The reference stylesheet's baseline face moved onto `.q-report`.** It was
|
|
186
216
|
on `.q-item`, `.q-table` and `.q-unlicensed` — a direct rule, which would
|
|
187
217
|
have beaten the report default an author writes on the document. The marking
|
|
188
218
|
keeps its own rule, being outside the root. Same look, one rule up.
|
|
189
|
-
|
|
190
219
|
- **A bare `Date` escapes to ISO 8601 UTC, the same on every machine.**
|
|
191
220
|
Interpolated `Date` values rendered through `String(date)`, which bakes the
|
|
192
221
|
host's timezone and locale into the markup. They now render as
|
|
193
222
|
`toISOString()` text through the engine's shared display rule — escaped
|
|
194
223
|
like every other value — so the same report produces the same HTML
|
|
195
224
|
everywhere. Formatted dates keep coming from registered functions.
|
|
196
|
-
|
|
197
|
-
### Fixed
|
|
198
|
-
|
|
199
225
|
- **The reference stylesheet sets a base font family.** A fragment dropped
|
|
200
226
|
into a page with no font of its own inherited the browser default — a serif
|
|
201
227
|
— where every other target rendered a grotesque. `@quario/html/style.css`
|
|
202
228
|
now sets `sans-serif` on report text, as an ordinary rule any host overrides
|
|
203
229
|
by source order.
|
|
204
230
|
|
|
205
|
-
##
|
|
231
|
+
## 0.1.0
|
|
206
232
|
|
|
207
|
-
###
|
|
233
|
+
### Minor Changes
|
|
208
234
|
|
|
209
235
|
- **HTML fragments for compiled reports.** Semantic tables, stable `q-*`
|
|
210
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
|
|
@@ -175,8 +180,11 @@ Host CSS targets these classes:
|
|
|
175
180
|
### The reference stylesheet
|
|
176
181
|
|
|
177
182
|
The package ships the default look as a real stylesheet, `@quario/html/style.css`: the band-role
|
|
178
|
-
weights and sizes, the table
|
|
179
|
-
|
|
183
|
+
weights and sizes, the table's structure (full width, collapsed borders, cell padding, an
|
|
184
|
+
unstyled cell pinned to the top of its row), occupy and newline-as-break on `.q-item` and table
|
|
185
|
+
cells, and the four pagination rules above. It carries no look beyond that — no rule under the
|
|
186
|
+
header row, no bold totals, no stroke above the report footer — because no other target has one
|
|
187
|
+
either. Add them to your own sheet if you want them. Link it, import it through a bundler, or read and inline it.
|
|
180
188
|
Plain Node cannot `import` a `.css` file, so on the server:
|
|
181
189
|
|
|
182
190
|
```js
|
|
@@ -195,6 +203,13 @@ any watermark treatment of `.q-unlicensed`, left as plain text here because `pos
|
|
|
195
203
|
would paint over your whole page. `example/print.css` in the repository is a host's half showing
|
|
196
204
|
both, and `example/print.js` is the complete pipeline.
|
|
197
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
|
+
|
|
198
213
|
## License
|
|
199
214
|
|
|
200
215
|
Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
|
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, text, walk } from "quario";
|
|
12
|
+
import { display, format, isReportBand, styledRuns, text, walk } from "quario";
|
|
13
13
|
|
|
14
14
|
/** @type {Record<string, string>} */
|
|
15
15
|
let ESC = {
|
|
@@ -27,8 +27,8 @@ let esc = (value) => display(value).replace(/[&<>"']/g, (char) => ESC[char]);
|
|
|
27
27
|
// exceptions. The engine's own `text` join is the other one -- display text,
|
|
28
28
|
// where a literal is words rather than markup -- and an attribute value takes
|
|
29
29
|
// that one, escaped whole.
|
|
30
|
-
/** @type {(tokens: any[], style
|
|
31
|
-
let
|
|
30
|
+
/** @type {(tokens: any[], style: any, intl: any) => string} */
|
|
31
|
+
let joined = (tokens, style, intl) => {
|
|
32
32
|
let out = "";
|
|
33
33
|
for (let token of tokens)
|
|
34
34
|
out += "literal" in token ? token.literal : esc(shown(token.value, style, intl));
|
|
@@ -40,6 +40,29 @@ let shown = (value, style, intl) => {
|
|
|
40
40
|
return text != null ? text : value;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
// One styled run: a `<span>` carrying that run's whole resolved inline style,
|
|
44
|
+
// rather than a difference against the cell's -- the engine already composed
|
|
45
|
+
// the two, and a delta would have this target merge them back. An unstyled run
|
|
46
|
+
// stays bare, so a cell with no authored runs emits exactly the markup it
|
|
47
|
+
// always did. The span's own CSS is `esc()`d like any other attribute value.
|
|
48
|
+
/** @type {(styled: { style: any, tokens: any[] }, style: any, intl: any, map: any) => string} */
|
|
49
|
+
let piece = (styled, style, intl, map) => {
|
|
50
|
+
let body = joined(styled.tokens, styled.style ?? style, intl);
|
|
51
|
+
if (!styled.style) return body;
|
|
52
|
+
let inline = css(styled.style, map);
|
|
53
|
+
return inline ? '<span style="' + esc(inline) + '">' + body + "</span>" : body;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// A cell's markup: its styled runs in order. The grouping is the engine's
|
|
57
|
+
// `styledRuns`, so this target cannot drift from the others (SCHEMA.md, "Event
|
|
58
|
+
// stream").
|
|
59
|
+
/** @type {(map: any) => (tokens: any[], style?: any, intl?: any) => string} */
|
|
60
|
+
let cellMarkup = (map) => (tokens, style, intl) => {
|
|
61
|
+
let out = "";
|
|
62
|
+
for (let styled of styledRuns(tokens)) out += piece(styled, style, intl, map);
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
|
|
43
66
|
// base64 for an image's `data:` URI, without Buffer: this package runs in Node
|
|
44
67
|
// and in ES2024 browsers off the same `lib/`, and `btoa` is a standard global
|
|
45
68
|
// in both. The walk is chunked because `fromCharCode(...bytes)` spreads one
|
|
@@ -249,15 +272,20 @@ let css = (style, map) => {
|
|
|
249
272
|
// columns and splits are placement, not the visual defaults this target
|
|
250
273
|
// refuses to supply, so they ride the same escaped attribute path as
|
|
251
274
|
// everything else rather than a second one (CLAUDE.md, the markup edge).
|
|
252
|
-
/** @type {(fonts: any) => (event: { style?: any }, own?: string) => string} */
|
|
275
|
+
/** @type {(fonts: any) => { attr: (event: { style?: any }, own?: string) => string, map: Record<string, string> }} */
|
|
253
276
|
let styling = (fonts) => {
|
|
254
277
|
// The spread is the precedence the option promises: a host mapping is
|
|
255
278
|
// consulted before the three generics, and may replace one. `mapping`
|
|
256
|
-
// lowercased its keys to match the ones already here.
|
|
279
|
+
// lowercased its keys to match the ones already here. The table is handed
|
|
280
|
+
// back beside the attribute writer, because a styled run's span resolves a
|
|
281
|
+
// `family` against the same one.
|
|
257
282
|
let map = { ...FAMILY, ...mapping(fonts) };
|
|
258
|
-
return
|
|
259
|
-
|
|
260
|
-
|
|
283
|
+
return {
|
|
284
|
+
map,
|
|
285
|
+
attr: ({ style }, own = "") => {
|
|
286
|
+
let out = [own, style && css(style, map)].filter(Boolean).join(";");
|
|
287
|
+
return out ? ' style="' + esc(out) + '"' : "";
|
|
288
|
+
},
|
|
261
289
|
};
|
|
262
290
|
};
|
|
263
291
|
|
|
@@ -302,8 +330,8 @@ let justified = (own, style) =>
|
|
|
302
330
|
// event to "" through the same function rather than re-deciding per call site.
|
|
303
331
|
/** @type {(event: { path?: string }) => string} */
|
|
304
332
|
let pathAttr = ({ path }) => (path ? ' data-q-path="' + esc(path) + '"' : "");
|
|
305
|
-
/** @type {(list: any[], tag: string, attrs: (event: any) => string, intl: any) => string} */
|
|
306
|
-
let cells = (list, tag, attrs, intl) =>
|
|
333
|
+
/** @type {(list: any[], tag: string, attrs: (event: any) => string, intl: any, markup: any) => string} */
|
|
334
|
+
let cells = (list, tag, attrs, intl, markup) =>
|
|
307
335
|
list
|
|
308
336
|
.map(
|
|
309
337
|
(cell) =>
|
|
@@ -342,7 +370,9 @@ let cells = (list, tag, attrs, intl) =>
|
|
|
342
370
|
export function html(options) {
|
|
343
371
|
let mark = options?.paths ? pathAttr : () => "";
|
|
344
372
|
// The factory's verdict, like `mark`: the mapping is validated once, here.
|
|
345
|
-
let styleAttr = styling(options?.fonts);
|
|
373
|
+
let { attr: styleAttr, map: fontMap } = styling(options?.fonts);
|
|
374
|
+
// A cell's markup, bound to this target's font table once.
|
|
375
|
+
let markup = cellMarkup(fontMap);
|
|
346
376
|
// The pair every cell carries, bound once rather than threaded as two.
|
|
347
377
|
/** @type {(cell: any) => string} */
|
|
348
378
|
// A span covers several columns; `colspan` is what that is on this surface.
|
|
@@ -357,7 +387,7 @@ export function html(options) {
|
|
|
357
387
|
// the cells, where it arrives as ordinary inline CSS.
|
|
358
388
|
/** @type {(event: any, list: any[], tag: string, intl: any) => string} */
|
|
359
389
|
let tr = (event, list, tag, intl) =>
|
|
360
|
-
"<tr" + styleAttr(event) + ">" + cells(list, tag, attrs, intl) + "</tr>";
|
|
390
|
+
"<tr" + styleAttr(event) + ">" + cells(list, tag, attrs, intl, markup) + "</tr>";
|
|
361
391
|
return {
|
|
362
392
|
name: "html",
|
|
363
393
|
compile: (stream) => async (data) => {
|
|
@@ -504,6 +534,14 @@ export function html(options) {
|
|
|
504
534
|
// one, and escaped when it is encoded rather than per occurrence. `alt` is report data, so it is the display-text join escaped,
|
|
505
535
|
// never the markup-passing join a cell body gets; an image without one
|
|
506
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.
|
|
507
545
|
image: (event) => {
|
|
508
546
|
let slot = placed(event);
|
|
509
547
|
let source = sources.get(event.bytes);
|
|
@@ -521,10 +559,15 @@ export function html(options) {
|
|
|
521
559
|
mark(event) +
|
|
522
560
|
'><img src="' +
|
|
523
561
|
source +
|
|
562
|
+
'" width="' +
|
|
563
|
+
esc(event.width) +
|
|
564
|
+
'" height="' +
|
|
565
|
+
esc(event.height) +
|
|
524
566
|
'" alt="' +
|
|
525
567
|
esc(text(event.alt || [])) +
|
|
526
|
-
|
|
527
|
-
(event.fit === "width" ?
|
|
568
|
+
'" style="' +
|
|
569
|
+
(event.fit === "width" ? "" : "max-") +
|
|
570
|
+
'width:100%;height:auto">' +
|
|
528
571
|
"</div>",
|
|
529
572
|
);
|
|
530
573
|
},
|
package/lib/style.css
CHANGED
|
@@ -73,15 +73,6 @@
|
|
|
73
73
|
white-space: pre-line;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
.q-table thead th {
|
|
77
|
-
border-bottom: 0.5pt solid #000;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.q-table tfoot td {
|
|
81
|
-
border-top: 0.5pt solid #000;
|
|
82
|
-
font-weight: bold;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
76
|
.q-item.q-report-header {
|
|
86
77
|
font-size: 14pt;
|
|
87
78
|
font-weight: bold;
|
|
@@ -98,8 +89,6 @@
|
|
|
98
89
|
|
|
99
90
|
.q-item.q-report-footer {
|
|
100
91
|
margin-top: 10pt;
|
|
101
|
-
border-top: 1pt solid #000;
|
|
102
|
-
padding-top: 4pt;
|
|
103
92
|
}
|
|
104
93
|
|
|
105
94
|
/* A columned container is a fragmentation context on screen, not only in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/html",
|
|
3
|
-
"version": "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.
|
|
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.
|
|
47
|
+
"quario": "^0.8.0"
|
|
48
48
|
},
|
|
49
49
|
"size-limit": [
|
|
50
50
|
{
|