@modeldesk/respec
v0.1.1
Published
Reading features for ReSpec documents built from ModelDesk content bundles
Readme
ModelDesk ReSpec runtime
Reading features for ReSpec documents whose content comes from a
ModelDesk content bundle. The runtime works off the data-md-* markers the bundle
emits and nothing else, so it knows no metamodel. It is one classic script with its stylesheet inside and no
dependencies.
Use
Load the script without class="remove", so that a published copy keeps it, and pin it with its integrity
hash (the release prints the sha384 hash of each published version):
<script class="remove">
var respecConfig = { specStatus: 'unofficial' };
</script>
<script
src="https://cdn.jsdelivr.net/npm/@modeldesk/[email protected]/dist/modeldesk-respec.js"
integrity="sha384-ytqM8F2hafxjLjoYd+eWA+WUrGOvq5v93wQvBwr/WEsUnR8CIMsCmuMmm2rFLlNV"
crossorigin="anonymous"
></script>
<script src="https://www.w3.org/Tools/respec/respec-w3c" class="remove" defer></script>- Load it as a plain script, without
asyncordefer: it has to be in place before ReSpec starts processing. - While ReSpec processes the document, the runtime runs as the last entry of
respecConfig.postProcess, afterdata-includehas put the diagram SVGs in the page and before an export saves it. Your own entries stay where they are. ReSpec ignores apostProcessthat is a single function rather than an array; the runtime wraps it in an array, so it runs as well. respecConfigmay be declared before or after the script.- A saved copy carries the markup the runtime added. ReSpec's export marks the copy with a
<meta name="generator">; there the runtime runs itself when the DOM is ready, even if arespecConfigwas kept. It reuses that markup and only attaches its event handlers. - The stylesheet is injected as a
<style>element. A page whose Content Security Policy restrictsstyle-srchas to allow it (with'unsafe-inline'or the stylesheet's hash), or the diagrams show without the runtime's styles.
The interface text is Dutch when the document's lang starts with nl, English otherwise.
Diagrams
A bundle emits each diagram as a figure[data-md-diagram] that includes the SVG and holds the PNG as a
fallback.
Strip. A slim strip above each diagram holds its full-view button on the right, so the button covers nothing the diagram draws and comes before the diagram's links in the Tab order. The button is 26 by 26 pixels; its edge, its icon and its focus ring stand at 3:1 or more against the page. A published copy keeps its strip; one saved by an older runtime, with the button over the drawing, has the button moved into one.
Inline. The SVG is shown at its own width through its
viewBox, shrunk to the column when wider and never enlarged. Node and line links work as page links; hover and keyboard focus outline a node. A line that draws a model element (a relatiesoort, a generalisatie) answers within a few pixels of its route, and hover and focus light up the whole line, route and labels, in the focus colour; keyboard focus also draws the focus ring around the line's link. An element drawn as lines is one Tab stop and one link to a screen reader: the first line's route, named by the line's title without the isolates that keep its parts apart on screen (aria-label) and described by its end labels (its<desc>, isolates removed). Its labels' links, and the routes of any further lines of the same element, are for the pointer only (tabindex="-1",aria-hidden="true"). Host link styles (underline, colour, background, border) are reset inside the diagram. The page never listens to the wheel over a diagram, so scrolling past one scrolls the page.Element list. Off unless the document asks for it:
var respecConfig = { // … modeldesk: { features: { diagrams: { elementList: true } } }, };Then, between each diagram and its caption, a
<details data-md-detail>lists every linked node, then every linked line under the heading "Relaties" ("Relations" in a document that is not Dutch), each labelled from its<title>and sorted by label, as a way to each element's section from the keyboard. A line is listed once, although the SVG stamps it twice: around its route and around its labels. The heading's id comes from the figure'sdata-md-diagram, made unique in the page, and labels the list of lines (aria-labelledby). A configuration that is not an object literal at every step is ignored. A published copy runs without a configuration: it keeps the lists it was saved with and builds none. The list is not the diagram's text alternative; the document is, since every element the diagram links to has a section of its own, and the SVG carries its names as text.Full view. The strip's button, or a click on the drawing outside a link, opens a dialog over the whole window. Drag to pan; zoom with the wheel, a pinch or the buttons; on the keyboard,
+and-zoom,0fits, the arrow keys pan and Escape closes. Pan and zoom rewrite the SVG'sviewBoxand never transform it, so the drawing is sharp at every zoom level.- A click on a node or a line closes the view and goes to its section. A click that asks for another tab (Ctrl, Cmd, Shift, middle button) is left to the browser. Tab reaches nodes and lines, and pans one that is off the stage into view.
- Links that do not point into the document do nothing in the full view.
- The view shows a copy of the diagram without scripts, embedded documents, animations or event handlers.
- A ReSpec save while the view is open leaves it out.
PNG. A figure whose SVG did not load, and an image in a section of a document from an older bundle, open the same full view on the PNG, at its own size at most. The figure gets the same strip, and so does an image that stands alone in its block; an image in running text, a heading, a caption, a table cell, a list item or a control opens on a click alone, since a strip there would break the text or nest a button. On such a figure the runtime removes ReSpec's error tooltip from the include, keeping ReSpec's id.
Landing. A jump from a diagram highlights the heading it lands on for a moment. A table row, row group or table that is a link target (an enumeration value, a constraint, a generalisatie, a relatiesoort in a relatierol table) lands a third of the way down the window, so its table header and the heading above it stay in view.
Print. The diagram prints at page width, in one piece, without the strip or the element list, and with no gap where the strip was; the full view never prints.
Animations and transitions are off for readers who ask for reduced motion. Text from the model (titles, captions, labels) is only ever written as text, never parsed as HTML.
The runtime is held to WCAG 2.1 AA, which Dutch government publications meet through the Webrichtlijnen:
npm run test:browser runs axe-core over an inline figure, the open full view, the element list and a PNG
figure, walks a figure and its full view with the keyboard, and reads Chromium's accessibility tree for a
line's name and description. A screen-reader pass and Firefox and Safari remain manual.
Node and line links are realized when the bundle is written. An SVG that carries element stamps but no links is shown unlinked and gets no element list. The runtime tells a line from a node by the invisible band the serializer draws along a line's route to take the pointer; every group with that line's stamp is the line's.
Development
npm test # vitest under jsdom
npm run test:browser # what jsdom cannot show (layout, painted styles, pointer capture), in Chromium
npm run build # dist/modeldesk-respec.js
npm run test:bundle # the built script itself in Chromium (run the build first); the release runs it on the packed file
npm run dev # serves the package and rebuilds dev/dist/ on change; open the printed /dev/ URL
npm run typecheck
npm run lintdev/index.html is a ReSpec document for the manual walk:
- an SVG figure as a bundle emits it;
- a second one with edges, markers and clipped outlines;
- a figure whose SVG include fails, so its PNG stays;
- an image the way older bundles show diagrams;
- enough text to judge landing and print.
To try a real bundle, pull one into the git-ignored dev/bundle/ by running
modeldesk bundle pull --model <modelId> --stack respec --template <template> from that folder, and include its
fragments from a page there. Include a fragment that carries figures without data-include-replace: ReSpec does
not process the includes inside replaced content, so every figure would keep its PNG. The element list is off
there unless the page's respecConfig adds modeldesk: { features: { diagrams: { elementList: true } } }, as
dev/index.html does.
The build refuses any input from outside src/: the package is published under the MIT licence without a
third-party notice, so neither a dependency nor a private workspace package may end up in the script. It also
refuses a bundle that names a way to parse HTML or evaluate a string, which the lint refuses in the sources.
Layout
src/
index.ts entry: registers init with ReSpec or runs it on a published copy
register.ts the respecConfig.postProcess hook
config.ts the runtime's own options, under respecConfig.modeldesk
init.ts puts the stylesheet in the page and mounts every feature
styles.ts composes the feature styles; motion behind prefers-reduced-motion
copy.ts interface text, Dutch and English
dom.ts DOM construction and HTML-only lookups
markers.ts the documentation contract's markers the runtime reads
navigation.ts going to a fragment, and the event that announces it
features/
feature.ts the Feature interface
index.ts the features the runtime mounts, with their styles
diagrams/ figures, element list, full view and its copy of the SVG, viewBox arithmetic, PNG figures
landing/ the landing highlightA feature is a module under src/features/<id>/ with a mount(document, config) that finds its markers and
reuses what a published copy already carries, and its share of the stylesheet. Features do not import each
other; one that reacts to another listens for an event on the document. Realizing links in an SVG that has
element stamps only, from an element-to-anchor map, belongs in features/diagrams/ before the element list is
built, since the list reads the links.
