Elements
The elements are the browser half of the vocabulary: one framework-free custom element per block kind. A host gives each its parsed data and listens for one event, pbs-answer.
Using an element
Section titled “Using an element”import "pi-better-show/elements"; // defines every pbs-* tag (a no-op without a DOM)import { configure } from "pi-better-show/elements";
el.data = segment.data; // or <pbs-form data-json='{...}'>el.addEventListener("pbs-answer", (event) => send(encodeAnswer(event.detail)));el.answered = event.detail.fields; // once delivered: locks the element and shows what was sentA host gives an element the block’s parsed data and listens for pbs-answer (bubbles, composed, cancelable) whose detail is a core Answer. An element never reads or writes HTML: text reaches the page as text nodes, and links are http(s) only with rel="noopener noreferrer" and target="_blank". Each element is a shadow root with constructed stylesheets (adoptedStyleSheets), so it runs under default-src 'self': no <style> element, no style attribute. examples/ is such a page; run it with bun run demo (loopback, port 4177).
| Tag | Block | data |
Answer | Parts |
|---|---|---|---|---|
pbs-callout |
alert |
AlertData |
callout header icon title body |
|
pbs-cards |
cards |
CardsData |
grid card title icon body |
|
pbs-options |
options |
OptionsData |
pick |
grid option title badge body pros cons actions pick status |
pbs-form |
form |
FormData |
form |
form title question label hint choices choice badge other textarea error actions submit status sent |
pbs-review |
review |
ReviewData |
review |
review title items item item-title pick media image enlarge frame unavailable body comment actions submit status lightbox close sent |
pbs-mindmap |
mindmap |
MindmapData |
mindmap tree item node toggle label group |
|
pbs-suggest |
suggest |
SuggestData |
suggest |
suggest chips chip status |
pbs-checklist |
checklist |
ChecklistData |
checklist |
checklist title items item progress actions submit status sent |
pbs-table |
table |
TableData |
table bar filter count scroll header head sort body row cell empty |
|
pbs-steps |
steps |
StepsData |
steps step rail marker content title badge when body |
|
pbs-stats |
stats |
StatsData |
grid stat label value delta note |
|
pbs-diff |
diff |
DiffData |
diff scroll line |
|
pbs-diagram |
mermaid |
MermaidData |
diagram image source note |
|
pbs-tree |
tree |
TreeData |
treeview bar expand-all collapse-all tree item node toggle name count tags tag note more group |
TAGS maps a block kind to its tag. There is no whole-document element: the extension renders Markdown on its page server, and other hosts use their own renderer.
Attributes and properties
Section titled “Attributes and properties”Every element has these.
| Name | Kind | Meaning |
|---|---|---|
data / data-json |
property / attribute | The block’s data; bad JSON or a shape the element cannot draw shows an error panel and fires pbs-error. |
answered / data-answered |
property / attribute | The submitted [name, value][]: locks the element and shows them. undefined reopens it. |
ref, or doc and block attributes |
property / attributes | The block’s address, copied into every answer’s ref. |
density |
attribute and property | comfortable (default) or compact. |
scheme |
attribute and property | light or dark, to force a scheme; else the element follows the host’s color-scheme. |
lang |
attribute (or an ancestor’s, or the document’s) | zh* shows Traditional Chinese, anything else English. |
refresh() |
method | Draws again. Call it after the host changes color-scheme or the document lang, which an element cannot observe. |
Answers
Section titled “Answers”A pick (pbs-options) or a chip (pbs-suggest) sends at once and locks itself once a listener takes the event; a listener that calls preventDefault() refuses it and the element stays open. A form, review, or checklist stays editable after sending (the status line says “Sent.”), so the user can send a changed answer; the host locks it by setting answered. A form’s required questions are checked before sending. Ctrl/Cmd+Enter sends from a text box.
Configure
Section titled “Configure”The host looks after what an element must not do itself: where diagram images, review images, and prototypes come from, the words, and whether a diagram can be pointed at.
configure({ strings: { "zh-Hant": { send: "送出" } }, // per locale, per word (see Strings) diagramUrl: (source, scheme) => `/diagram.svg?...`, // an image URL for pbs-diagram; without it the source is shown imageUrl: (ref) => `/files/${ref}`, // a review item's `image`; default admits http(s) and raster data URLs only pageUrl: (ref) => `/pages/${ref}`, // a review item's `page`, shown in a sandboxed frame; without it, its name diagramMap: async (url) => readTargets(url), // makes a diagram's nodes and edges clickable (below); without it the diagram is an image});Serve diagram images from pi-better-show/server (mermaidSvg(source, { scheme, transparent: true })); no diagram markup enters the page. resetConfiguration() restores the defaults.
Pointing at a diagram
Section titled “Pointing at a diagram”With diagramMap set, a diagram’s nodes and edges become buttons a reader can comment on.
What the host provides
With diagramMap set, pbs-diagram lays a transparent button over each node, edge (its line and its label box), and subgraph header of the image. diagramMap(url) receives the URL diagramUrl returned and resolves to a DiagramMap: { viewBox, targets: [{ kind: "node" | "edge" | "group", text, shapes: [{ tag, attrs }] }] }, shapes in the image’s own coordinates and carrying geometry attributes only. The host reads and parses the drawing, because elements never parse markup; the extension’s page does it for the SVG mermaidSvg draws (g.node, .edge, g.edge-label, g.subgraph with their data-* labels: flowcharts and state diagrams; other diagram types give no targets and stay images). The targets come in reading order, the order the arrow keys follow; whatever the order, groups are drawn below edges and edges below nodes. A click, or Enter or Space on a focused button, dispatches pbs-diagram-target (bubbles, composed) with detail: { kind, text }, from the button itself, so composedPath()[0] is where a host returns focus. The diagram is one tab stop, on the button last focused (the first at the start); the arrow keys move to the next or previous button, Home and End to the first or last. The buttons sit in a role="group" whose description (aria-describedby, an SVG desc) tells a screen reader that the arrow keys move between them; the DOM keeps the drawing layers, not the reading order. Each button has an aria-label (“Comment on edge: …”) and shows a hover and focus tint drawn over the image, so nothing moves.
Tokens
Section titled “Tokens”Set a --pbs-* property on an element or any ancestor; each has a light-dark() default that follows the host’s color-scheme.
| Token | Meaning |
|---|---|
--pbs-bg --pbs-surface |
Panel background; tinted surface (table header, code) |
--pbs-fg --pbs-muted |
Text; secondary text |
--pbs-line --pbs-control |
Panel and divider lines; the edge of an input (3:1 against its surface) |
--pbs-accent --pbs-on-accent |
Buttons, links, selection; text on them |
--pbs-note --pbs-ok --pbs-warn --pbs-risk |
Tones |
--pbs-radius --pbs-gap |
Corner radius; space between blocks and inside them |
--pbs-font --pbs-mono --pbs-font-size |
Type |
--pbs-font-display |
Titles and headings (defaults to --pbs-font) |
Structure carries part names for ::part() restyling. A panel has the same border on all four sides, or none; a tone shows as a tint, a badge, an icon, and the heading colour, never a coloured or thicker one-sided border. examples/demo.css maps the Roundtable and Straw boss tokens as examples.
Keyboard
Section titled “Keyboard”Every control is a native control, labelled. pbs-mindmap follows the ARIA tree pattern (arrows move, Right and Left open and close, Enter and Space toggle, Home and End jump); pbs-tree follows it too, and opens folders to depth 2 (deeper folders that hold a tagged node open their path) with “expand all” and “collapse all” buttons; pbs-table sorts from header buttons (aria-sort) and filters from a labelled search box.