@quario/html 0.6.0 → 0.7.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 +43 -0
- package/README.md +5 -2
- package/lib/index.js +42 -12
- package/lib/style.css +0 -11
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-09-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Styled runs render as spans.** A cell value written as a list of styled runs
|
|
15
|
+
emits one `<span style="…">` per styled run, carrying that run's whole
|
|
16
|
+
resolved inline style; unstyled runs stay bare, so a cell with no runs emits
|
|
17
|
+
exactly the markup it always did. Every interpolated value is escaped as
|
|
18
|
+
before, and the span's own CSS is escaped like any other attribute value.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **A `format` inside a sentence now presents where the engine says it does.** A
|
|
23
|
+
cell mixing literal text and one interpolation under a `format` renders the
|
|
24
|
+
value plainly, as the spreadsheet and CSV targets already did; splitting the
|
|
25
|
+
value into styled runs is how a value inside a sentence is formatted.
|
|
26
|
+
|
|
27
|
+
- **The reference stylesheet no longer paints a look no other target has.**
|
|
28
|
+
`@quario/html/style.css` dropped the hairline under a table's header row, the
|
|
29
|
+
hairline and bold weight on its total rows, and the rule above the report
|
|
30
|
+
footer. A report that declares no borders now renders without them on screen,
|
|
31
|
+
which is what the PDF and the worksheet have always shown for the same
|
|
32
|
+
report — the difference was the stylesheet's alone, and it also surfaced as a
|
|
33
|
+
cell whose computed border colour failed keeping a black stroke here while
|
|
34
|
+
every other target drew nothing. The sheet now carries the band-role defaults,
|
|
35
|
+
the table's structure and the honor and pagination rules, and no look beyond
|
|
36
|
+
them. The classes are unchanged, so a host that wants the old look adds three
|
|
37
|
+
rules to its own sheet after the shipped one:
|
|
38
|
+
|
|
39
|
+
```css
|
|
40
|
+
.q-table thead th {
|
|
41
|
+
border-bottom: 0.5pt solid #000;
|
|
42
|
+
}
|
|
43
|
+
.q-table tfoot td {
|
|
44
|
+
border-top: 0.5pt solid #000;
|
|
45
|
+
font-weight: bold;
|
|
46
|
+
}
|
|
47
|
+
.q-item.q-report-footer {
|
|
48
|
+
border-top: 1pt solid #000;
|
|
49
|
+
padding-top: 4pt;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
10
53
|
## [0.6.0] - 2026-09-07
|
|
11
54
|
|
|
12
55
|
### Added
|
package/README.md
CHANGED
|
@@ -175,8 +175,11 @@ Host CSS targets these classes:
|
|
|
175
175
|
### The reference stylesheet
|
|
176
176
|
|
|
177
177
|
The package ships the default look as a real stylesheet, `@quario/html/style.css`: the band-role
|
|
178
|
-
weights and sizes, the table
|
|
179
|
-
|
|
178
|
+
weights and sizes, the table's structure (full width, collapsed borders, cell padding, an
|
|
179
|
+
unstyled cell pinned to the top of its row), occupy and newline-as-break on `.q-item` and table
|
|
180
|
+
cells, and the four pagination rules above. It carries no look beyond that — no rule under the
|
|
181
|
+
header row, no bold totals, no stroke above the report footer — because no other target has one
|
|
182
|
+
either. Add them to your own sheet if you want them. Link it, import it through a bundler, or read and inline it.
|
|
180
183
|
Plain Node cannot `import` a `.css` file, so on the server:
|
|
181
184
|
|
|
182
185
|
```js
|
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) => {
|
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.7.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.7.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.7.0"
|
|
48
48
|
},
|
|
49
49
|
"size-limit": [
|
|
50
50
|
{
|