v1.0.0
After a detailed polish releasing the first stable version
Fixed
-
A sample's own
<script>stopped running after the first edit. A js demo keeps its
script inside the sample, because the element takes one fence — and an edit was applied
to the loaded frame withinnerHTML, which never executes a script it inserts. The
first paint went throughsrcdocand worked, so the demo only died from the first
keystroke on: still rendered, still correctly marked up, nothing in the console. Only
jsurls and thereloadattribute forced the rebuild; the sample's own script was
not looked at.A sample containing a
<script>now rebuilds the frame rather than patching it, on the
same longer debounce ajsurl already used. Nothing to change in a page: samples with
no script in them still patch, and keep their scroll position and stylesheets as before. -
A sample's own elements never came alive when
jspointed at a custom element
bundle — which is most of what this element is for. The scripts went into<head>
undeferred, socustomElements.defineran before the body was parsed and the parser
then upgraded each element the instant it opened its tag, with none of its light-DOM
children there yet. Every element that reads its own children on connect found nothing
and bailed.The failure was silent and total: the sample rendered, the markup was right, the
stylesheet applied, nothing appeared in the console — and not one element was wired.
Native behaviour inside the sample (a<details>toggling) still worked, which is
exactly what made it read as "the preview is a bit unresponsive" rather than as a bug.
It also made the options panel look broken from the outside: its knobs were writing
correctly the whole time, into elements that were not listening.jsscripts now carrydefer, which is what these libraries already document as their
requirement, and which keeps execution order across several urls. An inline<script>
in the sample is untouched — that one is the author's, and it is in body where they
wrote it. -
The editable code block was a keyboard trap. Tab indents in there, which left a
keyboard user who tabbed in with nothing to press — WCAG 2.1.2 Level
A, and the one
failure here with no workaround from the outside. Esc now hands Tab back: the next
Tab moves focus, and leaving the block re-arms it, so tab-to-indent is unchanged for
anyone who does not need to leave by keyboard. -
The editor says what it is. CodeJar leaves a block that is editable and nothing
else, so the element now addsrole="textbox",aria-multiline="true",
aria-keyshortcuts="Escape"and anaria-labelnaming the language. Anaria-label
oraria-labelledbyalready on the block is left alone. -
The focus ring follows focus. It hung off
code-preview:focus-within, so clicking
a width button or a tab lit the code block up instead. It is now on the block. -
Switching tab no longer drops focus on the floor. Setting
tabfrom a script or
from markup while the reader was inside the pane being hidden left focus on an element
that was about to disappear, which the browser answers by moving it to the body — the
next Tab starts again at the top of the page, with the whole document between a screen
reader and the widget it was just in. Focus now moves to the tab being switched to,
which is where clicking or arrowing to that tab had already left it. Focus outside the
pane is not touched, so the frame's own load — which calls the same code — cannot yank a
reader into the tab strip. -
A keystroke that changed nothing reloaded the preview. CodeJar reports an update on
every keyup, not only the ones that edited the text — the arrows, Tab, every modifier,
and now the Esc this element asks people to press — and each one rebuilt the frame a
quarter-second later for a sample that had not moved. That reload throws away everything
live inside the preview: a script's state, and the focus a keyboard user had put on a
control in there. So an accessible component could not be demonstrated in its own
preview — Tab into the frame, focus a control, and it vanished under you a moment later.
The frame is now rendered only when the source it would render has actually changed.Editing the sample still rebuilds, and still costs whatever was live in there. That one
is the sample changing, which is the point. -
An Escape pressed outside the editor released its Tab. The listener sits on the
element, so an Escape in an options-panel field or on a width button also flipped the
editor's tab-to-indent off and rewrote the keyboard hint — about an editor the reader
was not in. Leaving the editor re-armed it, so no trap could result, but the hint could
claim a state that was no longer true. Only an Escape from inside the editor counts now. -
A failed manifest fetch was cached for the life of the page. One transient network
error cost every preview sharing that url its options panel until a reload. A rejected
fetch is now evicted from the cache, so a preview mounting later tries again. -
An attribute
<select>now says its default. Its empty option reads
default (quiet)when the manifest documents one, the same way a custom property's
already did — it used to say onlydefault, with the manifest's answer dropped. -
A duplicate width in
viewport-widthsno longer renders a duplicate button. -
The color swatch treats an alpha it cannot parse as unknown — the swatch stays
where it was, like every other value it cannot be sure about, rather than showing the
color as opaque. -
An attribute name containing a
.is matched literally when the options panel
reads or rewrites the sample, rather than as a regex wildcard. -
Publishing runs the tests. CI runs on branches and pull requests, not on tags, so
the publish workflow ran none at all — a tag cut from a broken commit would have
published untested code.npm testnow runs beforenpm publish.
Added
-
Every color and font the stylesheet reads now has a
--code-preview-name.
--code-preview-bg,--code-preview-fg,--code-preview-fg-muted,
--code-preview-border,--code-preview-accent,--code-preview-danger,
--code-preview-radiusand--code-preview-font-monojoin the four
--code-preview-*sizing properties that were already there, so nothing about the
element's look is reachable except through its own namespace.Nothing to change in a page. Each one falls back to the unprefixed name it used to
read before its default —var(--code-preview-bg, var(--bg, #fff))— so a host page
themed through--border,--bg,--accent,--fg,--fg-muted,--danger,
--radiusor--font-monolooks exactly as it did. The prefixed name is only the
first lookup, which is what makes it possible to move this element alone without
moving the page around it:code-preview { --code-preview-bg: #161b22; }
dist/code-preview-hljs.cssreads--code-preview-fg-mutedthe same way, so the
optional syntax theme moves with the element rather than with the page. -
The options panel lists what the sample fires. A third group,
Events, built from
the manifest'sevents[]— every documented event is listed whether or not it has fired,
with a count and the lastdetailbeside it once it has. An element whose whole API is a
CustomEventwas otherwise a preview that appears to do nothing when you click it.The rows are
<div class="code-preview-event">with a
<span class="code-preview-event-value">readout, and the<fieldset>around them
carriesaria-live="polite". Nothing here is a control, so nothing writes to the sample
or to the frame's stylesheet.The listeners go on the frame's document, in the capture phase: capture is what hears
an event that does not bubble — most of them, dispatched on the element itself — and the
document is what survives theinnerHTMLpatch a keystroke does. A rebuilt frame is a
new document with a new sample in it, so its counts start again from—.They are also attached whichever tab is open, which is a behaviour change inside the
panel: the controls used to be re-read only when the Options tab was activated, and an
event fired while the reader is looking at the code still has to be counted. -
The event readout is highlighted, and says when it changed. A
detailis now written
as spans carrying highlight.js's own token classes —hljs-attrfor a key,hljs-string,
hljs-number,hljs-literal,hljs-tagfor a node — so a docs page that already ships a
syntax theme colors it with no extra css.dist/code-preview-hljs.cssscopes its rules to
:is(pre code, .code-preview-event-value)for the same reason; a theme of your own that
targeted thepre codeform still wins on any real code block. The readout's text is
unchanged, so anything readingtextContentreads what it read before.A
detailis one line and stays one line: a string over 42 characters is clipped, a
function isƒ, anything nested is{…}and an array is its length. The sample's own
console is where a full payload is read.The readout is now two cells —
<span class="code-preview-event-count">and
<span class="code-preview-event-detail">inside the same
.code-preview-event-value— and the row no longer borrows the knobs' column grid. A
knob's second column is a field wide, which put a two-character count an inch from the
name it belongs to; the name takes what it needs and the count follows it, with the
details lined up in a column of their own. -
An event says so over the preview. The name of a documented event appears in a
<div class="code-preview-toast">inside.code-preview-viewportfor about a second and a
half whenever the sample fires one — that is where the reader is looking when they click
the thing that fires it. One box per element, reused, and opacity only, so there is
nothing in it forprefers-reduced-motionto object to.no-toaston the element
turns it off, for a sample that fires on everypointermove; the panel still counts..code-preview-viewportis nowposition: relative— it is the toast's containing block,
so the notice lands on the sample rather than on the toolbar. A sample tall enough to
scroll (pastmax-height: 70vh) scrolls its toast with it, until anchor positioning is
available everywhere.The row also flashes when its count goes up.
-
A keyboard hint,
<p class="code-preview-hint">, appended to the element for every
editable sample:Press Esc, then Tab, to leave the editor, becomingTab now leaves the editoronce Esc has been pressed. It is thearia-describedbyof the editor and a
role="status"live region, so the same sentence reaches a screen reader and the
screen. The stylesheet keeps it invisible until the block has focus and positions it
absolutely, so it costs no layout — which is whycode-previewis now
position: relative.It shows for a keyboard and not for a pointer, because it is advice about a key and
someone who clicked in can click back out. The element sets.is-key-focuson
itself when focus arrives on a keypress rather than a click, so the rule is
code-preview.is-key-focus:has(pre:focus-within); it is dropped again onfocusout,
and added late if someone who clicked in starts typing, since from that keystroke on
they are in the same trap. Not:focus-visible— acontenteditablematches that on a
mouse click too, because a browser assumes anything taking text input wants its focus
ring — so intent is tracked with akeydown/pointerdownpair per document, added once
however many editors a page has. A screen reader is unaffected either way:
aria-describedbyis read on arrival however focus got there.An editable block gets
padding-block-end: var(--code-preview-hint-space, 2.25rem)to
hold the room the hint sits in. Reserved from upgrade rather than added on focus:
growing the block at the moment someone clicks into it would shift the page under their
cursor. Set--code-preview-hint-spaceto the block's normal padding to turn the
reservation off.It is a child of the element rather than of the code block, because a copy-button script
that reads the block'sinnerTextwould otherwise put the sentence on the clipboard —
so the tab strip hides it itself, withdisplay: noneon any tab butcode. Left
showing it would be a live region describing an editor the reader has switched away
from.
Changed
-
The options panel's three groups collapse. Attributes, Custom properties and Events
each open on arrival and can be closed, so a panel documenting all three is no longer
taller than the sample above it.Each group is a
<details class="code-preview-group" open>with a<summary>, where
the panel that shipped in 0.2.0 used a<fieldset>with a<legend>. A stylesheet of
your own targeting.code-preview-group > legendwants> summaryinstead;
.code-preview-groupand.code-preview-knobsare unchanged. Nothing is lost naming
the set —<details>maps torole="group"and its summary is that group's accessible
name, exactly as the legend was — and the disclosure is the browser's, so there is no
new ARIA and no new key handling. The Events group is still thearia-liveregion;
closed, it announces nothing, which is the bargain the hidden pane already made — the
toast is what says an event fired. -
The tabs, the width buttons and the group summaries have a hover state, which none
of them had: a wash tinted from--fg-mutedplus the full--fgtext color, behind
@media (hover: hover)so a tap does not leave it stuck on. A wash rather than the
color alone, because the color is already how a tab says it is the selected one. -
Spelling is en-US throughout —
color, notcolour, in the docs, the comments and
this file. No identifier, class or attribute changed: the API was already--color-*
and<input type="color">. -
The color swatch is the color.
<input type="color">draws the value as a square
inset inside its own padding and border, which at 1.75rem is more chrome than color, and
the chrome was already drawn around it by this stylesheet. The value now fills the button
(::-webkit-color-swatch-wrapper,::-webkit-color-swatch,::-moz-color-swatch, one
rule each — a selector list containing a pseudo-element the engine does not know is a
list it drops whole).That only pays if the color is true, so the swatch now follows the field: the value is
resolved by setting it on the swatch and reading the computed color back, which is what
turns a named color,hsl(…)or acolor-mix(…)into channels. A value nothing can
resolve leaves the swatch where it was, rather than claiming a color the sample does not
have. -
transparentis drawn as a crossed-out square, a thin red cross over black, the way
a mac shows no color. There is no transparent in a color picker:<input type="color">
holds an opaque#rrggbband nothing else, and the neweralphaattribute only buys
#rrggbbaa— still not the keyword, which is a real default in a themeable library. The
text field remains the control; the swatch stops lying about it. The class is
.code-preview-swatch.is-transparent, and the cross takes--danger. -
The
<select>caret is drawn rather than left to the platform, which put it hard
against the field's right edge with 0.375rem of padding on the other side. It now sits at
the same 0.375rem, and takescurrentColor— two gradients making one triangle, so there
is no data uri to recolor per theme and no extra element. -
Group titles are uppercased —
Attributes,Custom properties,Events. Only the
legends: the names below them are verbatim attribute and custom-property names, where
case is meaning.
Full Changelog: v0.2.0...v1.0.0