@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 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 look, occupy and newline-as-break on `.q-item` and table cells, and
179
- the four pagination rules above. Link it, import it through a bundler, or read and inline it.
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?: any, intl?: any) => string} */
31
- let markup = (tokens, style, intl) => {
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 ({ style }, own = "") => {
259
- let out = [own, style && css(style, map)].filter(Boolean).join(";");
260
- return out ? ' style="' + esc(out) + '"' : "";
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.6.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.6.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.6.0"
47
+ "quario": "^0.7.0"
48
48
  },
49
49
  "size-limit": [
50
50
  {