The HTML target
Intent
@quario/html serialises the event stream into a fragment you drop into your
own page. It’s not a document. There is no <html>, no <head>, and no
presentation beyond what a definition declared. The fragment goes into a
template, an email body or a server-rendered response.
Design decisions
This target is a fragment, not a painter, and not a flow target. It has no pages of its own to break or to hand to a reader’s application, which is why page bands go unread below. Painters, flow targets, and a fragment sets out the family.
The fragment carries no pagination, and leaves page bands unread. Page
furniture belongs to the host’s print CSS. A group’s break: "page" maps to a
class instead of vanishing, so your print stylesheet can act on it.
This target escapes every interpolated value, and leaves literal text alone. Escaping happens here, at the markup edge, because the event stream carries unescaped data. Literal template text is author-controlled markup, which is the same split every markup consumer has to make.
It escapes generated attributes too. Data attributes, inline styles,
src, alt and colspan all carry data. Class names are constants this
target owns.
The q-* selectors are the contract, and the stylesheet carries no look of
its own. It holds the band-role defaults, the table’s structure, and the honor
and pagination rules. They’re ordinary rules a later host sheet overrides on
source order. Since 0.7.0 it draws no hairline under a table’s header row, and
no rule or bold weight on its total rows. It draws no rule above the report
footer either, because no other target ever drew them. A report that declares no
borders now reads the same on screen as in the PDF and the worksheet.
Styled runs render as spans. One <span style="…"> per styled run,
carrying its whole resolved inline style. Unstyled runs stay bare, so a cell
without runs emits the markup it always did.
A fragment without the sheet still carries the classes and the text. It doesn’t occupy, break or paginate until some stylesheet says so.
The report default rides the root as an inline style. That’s the one place the rest of the document can inherit it from.
The unlicensed marking sits outside the report root. Inside it, an author’s
declared size would reach it. It’s the fragment’s first element and the
root’s sibling, holding only the escaped wording.
API walkthrough
Rendering
import { html } from '@quario/html'
const fragment = await report.render(html(), data)Rendering is asynchronous and hands the event loop back between batches, so a large report doesn’t block the host. Compilation stays synchronous, and render errors reject with their location attached.
The same definition, data, and options produce an identical fragment. The target
stamps nothing, and an unformatted Date writes as ISO 8601 UTC rather than in
the host's zone.
Styling the output
@import '@quario/html/style.css';Two rules in that sheet are worth knowing, because a fragment without them
behaves differently. .q-item carries min-height: 1lh and white-space: pre-line, so a visible text item occupies a line and a literal newline is a
break. Table cells carry pre-line too, and empty cells have no min-height,
so they don’t occupy.
The classes are yours to restyle. Override on source order, the way you would any vendor sheet.
A host that wants the look the sheet carried before 0.7.0 adds three rules after the shipped one.
.q-table thead th { border-bottom: 0.5pt solid #000; }
.q-table tfoot td { border-top: 0.5pt solid #000; font-weight: bold; }
.q-item.q-report-footer { border-top: 1pt solid #000; padding-top: 4pt; }Mapping a font family
import { html } from '@quario/html'
const target = html({ fonts: { Inter: 'var(--font-inter)' } })A declared family name maps to a CSS font-family value. Names match
case-insensitively, and a mapping may cover the generics like any other name. A
value containing ; or } throws from html() rather than reaching the page.
This mapping resolves names a report declared, and never the face of text that declared nothing. That baseline lives in the reference stylesheet, which is where a host moves it.
Mapping output back to the definition
import { html } from '@quario/html'
const target = html({ paths: true })Each element whose event carries a schema path gets data-q-path. It’s off
by default, and it’s the selection seam the editor builds on.
Printing
A fragment plus a print stylesheet is a legitimate route to paper, and it’s the one the browser already knows how to drive. The trade is that pagination becomes the browser’s, so page bands, repeated table headers, and deterministic bytes aren’t part of the bargain. Where those matter, render through the PDF target instead, from the same definition.
A note on Content Security Policy
Conditional formatting emits inline style attributes, so a restrictive
style-src has to account for them. An image item embeds bytes as a data:
URI, which needs img-src data:. Neither is a hole, and neither is the
no-string-to-code guarantee, which covers script execution.
Trust and CSP has the whole picture.