# Tool modules, the long samples

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from core/samples and tools.md. Do not edit: change the source and run the generator.

The sections of `../tools.md` that are too long to keep in the npm package (markup, script or module header), by id.

## scorecard

```text
The scorecard as a module: mountScorecard(container, options) renders every target at every theme and width, runs the SDK's quality
checks (accessibility, layout, spacing, touch targets, focus) on each, scores each target 0-100 and ranks them worst first. Optional
sections add the framework-level checks, so one module serves a page, a set of pages or the whole framework.

  const card = await mountScorecard(el, { targets: ['/', '/pricing.html', { name: 'Card', html: '<div class="card">...</div>' }], checks: ['accessibility'] });
  await mountScorecard(el, { sections: ['security', 'api', 'size'], data: { security: '/security-report.json', apiBaseline: '/api.baseline.json', api: '/api.current.json', budgets, sizes } });

Options: targets (required for the ranked and performance sections; a URL string, { name, url } for a page of the same origin,
{ name, html } for markup rendered with the SDK stylesheets, { name, srcdoc } for a whole document, or { name, samples: [...] } for
several frames scored as one), checks (keep only findings whose check or category is listed, e.g. ['accessibility', 'touch-target'];
default all), themes (default dark and light), widths (default 375 and 1024), historyKey (a localStorage key: keeps the runs and shows
the change since the last), historyMax (runs kept, default 40), autorun, theme, height, link (item => href for a ranked name), signal (an AbortSignal:
when it aborts the card is destroyed and a run in flight stops opening frames and paints nothing).
sections: which parts to show, from ranked, performance, size, api, tiers, sweep, security, history; default ['ranked'], which is the
scorecard as it always was.
  ranked       the score and every target worst first (needs a run)
  performance  scored categories (performance, scale, look, accessibility) with their metrics and stylesheets (a run; needs data.scoring)
  size         gzip size of built files against their budgets (data.sizes, data.budgets)
  api          the API surface against the previous release: what was removed or added (data.apiBaseline, data.api)
  tiers        composition by tier: elements per tier and the baseline debt per rule (data.tiers, written by the build)
  sweep        the last full size sweep (data.sweep)
  security     security and defect findings (data.security; fileLink(file, line) makes the place a link)
  history      the kept runs, with export, import and clear (needs historyKey)
data: each entry is a URL of the host's own origin (a relative URL is read against the page) or the parsed object. A section whose data
is missing says so with an empty state. security, apiBaseline, api, tiers, sweep, budgets, scoring (the scoring definitions), sizes (a list of
{ name, url | urls, budget, strip }), files ({ css: { name: url }, js: { name: url }, tokens: name, pairs, frame, unusedIn }).
quality (a URL or the object a sweep wrote with --write-report: { overall, scores, measured, perFile, items, at }) is shown by default for the ranked and performance sections, with the run button relabelled Run live to measure again here; a missing or empty (none) report leaves the run-first view.
extraItems({ host }) may return more ranked items (a check the host runs itself).
Returns { run(), results(), report(), ready, destroy() }. runTargets, openFrame and rankedTable are exported for a host page that wants
to draw its own view. Quality checks are js/quality.js, scoring is js/scoring.js, both unchanged; the pure section logic is
js/framework-checks.js. Nothing is requested but the module's own files and the data files named above.
```

## layout-builder

```text
The layout builder as a module: mountLayoutBuilder(container, options) is an editor for a page built from Plainkit elements. A palette lists every element of the
element API (grouped like the gallery, with search), the canvas shows the page live, a structure tree and the Edit menu select and rearrange it, and an inspector edits
the selected element's props from its API metadata. The page is a JSON document (js/layout-model.js) that round-trips to CSP-safe HTML; the host owns persistence.

  const builder = await mountLayoutBuilder(el, { model, onchange: ({ model, reason }) => draft(model), onsave: ({ model, html }) => store(model, html) });
  builder.getModel(); builder.toHtml(); builder.setModel(doc); builder.select('n3'); builder.undo(); builder.destroy();

Options: registry (the element API: an array, or a URL of api.json; default ../elements/api.json next to the module), model (a starting document, or its JSON text;
default an empty page), html (a starting page as markup instead: sanitised, what is refused is logged; setHtml(markup) loads one later), onchange({ model, reason }) (every edit, undo, redo and load; reason: insert, move, remove, duplicate, wrap, prop, text, slot, undo, redo, load),
onsave({ model, html }) (adds a File menu with Save; a returned promise is awaited, a failure is logged and shown), exporters ({ name: (model, helpers) => text }: extra export
formats a host contributes, for example Razor from the Blazor side; used by exportAs(name)), height (any CSS length; default 40rem), theme ('dark' | 'light').
Returns { element, getModel(), setModel(model) -> { ok, problems }, setHtml(markup) -> { ok, problems }, toHtml(options), exportAs(name), select(id), selection(), insert(tag), undo(), redo(), on(event, fn) -> off, destroy() };
events: change ({ model, reason }), select ({ id }), problem ({ message }).

Keyboard (canvas or structure tree focused): arrows select (Up and Down walk the page, Left the parent, Right the first child), Alt+arrows move the selection (Up and
Down reorder, Left moves it out of its parent, Right into the element before it), Delete removes, Ctrl+D duplicates, Ctrl+Z undoes, Ctrl+Y or Ctrl+Shift+Z redoes,
Ctrl+S saves. The toolbar's File and Edit menus (and the canvas's right-click menu) run the same functions and show these keys: neither is replaced by drag-and-drop (issue #174).

Pointer/touch drag-and-drop (issue #174, built on pk-sortable, core/elements/sortable/): the top-level page order is a real pk-sortable of pk-sortable-item rows,
each with its own 44px drag handle; dragging one reorders the page for real (a pk-reorder event drives M.moveNode, same as Alt+arrows). A palette button is a second
drag source, using pk-sortable's external-drop API (beginExternalDrag/externalDragOver/endExternalDrag): dropped between top-level rows it inserts there; dropped on
a container (hit-tested, not nested pk-sortable: wrapping arbitrary slotted content would break the many elements whose shadow CSS keys off ::slotted() directly, for
example pk-stack's dividers or pk-card's [slot="media"]) it inserts inside that container's default slot, which is the "slot-aware" half of the drop. Reordering
*inside* a container, and moving a node into or out of one, stay keyboard/menu-only in this iteration (Alt+Left/Right, Alt+Up/Down, Move out/in): a real, but scoped,
limitation flagged on #174 rather than pretending a further recursive pk-sortable nesting was built and verified.

Each canvas element also gets an Edit/Delete icon chip (issue #174) that follows whichever node is hovered or, since touch has no hover, selected -- a tap already
selects (the canvas is inert), so touch reaches the chip through selection alone, with no separate gesture needed. Edit focuses the properties form for that node;
Delete removes it. Up/Down/Out/In/Duplicate/Wrap are Edit menu items, canvas context-menu items and keyboard shortcuts rather than more icons (a decision recorded on #174).

The chrome is a pk-dock (issue #432): Palette, Structure and HTML are tabs of its left group, the canvas the center, Properties the right; any of them can be
resized, moved or closed and reopened from the dock's own Panels menu. Its toolbar-start slot holds the File (Save, only with onsave) and Edit menus. Below the
phone breakpoint the dock draws every panel as one tab strip and the menus stay in its toolbar, so Save and every edit stay reachable by touch.
Right click (or Shift+F10, or a touch long press, all pk-context-menu) on the canvas opens the element menu for the element under the pointer; on a palette
button it offers Add.

The canvas is a pk-design-surface rendering the model inside its inert page (a built page cannot act on the builder); selection is from element rectangles and drawn as a mark.
Its width buttons narrow the canvas but media queries still see the real viewport: the iframe device preview is a follow-up (DESIGN.md).
Built only from SDK components (pk-dock, pk-dropdown, pk-menu-item, pk-context-menu, pk-accordion, pk-tree, pk-button, pk-button-group, pk-input, pk-select, pk-checkbox, pk-textarea, pk-code-block,
pk-empty-state, pk-design-surface, pk-sortable, pk-sortable-item, pk-icon) and the element inspector. The pure logic is js/layout-builder-logic.js and js/layout-model.js. Logging scope: layout-builder.
```

## theme-editor

```text
The theme editor as a module: mountThemeEditor(container, options) lists every token the SDK's token stylesheet declares with an input
for each, applies edits live, grades the text pairs for contrast, and exports or imports the override block (the format the
server-side PkThemeOverrides helper takes: same names, same rules).

  const editor = await mountThemeEditor(el, { storageKey: 'my-theme', onchange: ({ css }) => save(css) });   // edits the whole page
  const scoped = await mountThemeEditor(el, { target: previewElement });                                    // edits only that subtree
  editor.export(); editor.overrides(); editor.setTheme('light'); editor.reset(); editor.destroy();

Options: target (a Document, the default: one adopted stylesheet of override CSS; or an Element: the current theme's overrides as
inline custom properties, so only that subtree changes), theme ('dark' | 'light', initial), onchange({ css, overrides }), tokens (URL
of the stylesheet whose token blocks are edited; default the SDK's own), pairs ([foreground, background] token names to audit;
default AA_PAIRS), storageKey (localStorage key that keeps the overrides; default none), height (a CSS length: the token list
scrolls inside it), preview (show a Preview tab with sample controls in a frame; default true).
Palette tab: a brand colour (and optionally a neutral tint and a warn colour) generates the accent, fill, hover, link and the surface and text ramps of
both themes with every pair in AA_PAIRS at 4.5:1 or better (js/brand-palette-logic.js); the swatches show each pair with its ratio, and Apply writes them
as ordinary edits. Presets tab: the built-in presets (default, high contrast, compact and roomy density; js/theme-presets-logic.js) replace the edits, and the
user's saved themes (by name; apply, rename, delete) live in localStorage under the savedKey option ('pk-theme-editor-saved'; false keeps none), best effort:
a blocked storage is logged and the themes last until the page closes.
Undo and redo (buttons, Ctrl/Cmd+Z, Shift+Z or Y outside a text field; typing in one field is one step) walk every change; the Changes tab lists each edit against the
stylesheet value (js/theme-history-logic.js), '3 changes', with a reset for each edit and for each group of tokens.
Contrast tab (the live audit): every pair in `pairs` in both themes under the current edits, as sample text with its ratio, worst first, and a jump to the token that sets each
side (js/theme-editor-logic.js auditPairs); the tab title counts the pairs below 4.5:1.
Options for an app: initial (the theme to start from, as JSON { shared, dark, light }, an override CSS block or an object; used when nothing was kept for the
viewer) and presets ([{ name, description?, theme }], theme being the same kinds of value; listed after the built-in presets and applied by name).
Export / import tab: the CSS block, a copy-paste snippet (theme.css), the JSON, and a link to the theme (js/theme-share-logic.js): the edits in the fragment as
'#pk-theme=z.<compressed>' (plain when the browser has no CompressionStream), at most 4096 characters, validated on the way in like pasted JSON, text only.
Custom SDK tab (sdk-tab.js; option sdk: false leaves it out, dist: the URL of the folder holding the shipped dist files, default the release layout next to this module): tick
Theme and/or Breakpoints (phone, tablet and wide as whole pixel widths, validated, with a live table of the elements and properties that change at each), then export: theme only
is a small zip (plainkit-theme.css, plainkit.custom.json, README.md) that loads after plainkit.css; with breakpoints it is the shipped dist re-resolved with a recomputed SRI manifest
(js/custom-sdk-logic.js), as a zip written in the page (js/zip-store.js). The settings file imports back. Only same-origin reads of the shipped files.
Option readHash: true applies the theme in the page's own #pk-theme= fragment at mount, as edits (Undo takes it back).
Returns { export(), overrides(), setTheme(name), reset(), undo(), redo(), share(), importShare(text), applyBrand(colour, { neutral, warn }), presets(), saved(), applyPreset(idOrSavedName), destroy() }.
The pure logic is js/theme-editor-logic.js and js/theme.js.
Built only from SDK components (pk-tabs, pk-input, pk-select, pk-colour-input, pk-unit-input, pk-textarea, pk-button, pk-cluster, pk-alert, pk-badge, pk-stat, pk-table).
```
