HelixOverviewComponentsMapsGraphIntroductionInstallationDeriving a themeSandboxTheme builderVisual testingCortexDocsEngramBuildingTraceBuildingRecallDeclaredServicesInsightsGet in touchSource on GitHub
Tooling
SandboxOpen the sandbox.You can edit every example on a component's page. When you change the markup,
the preview redraws. When you change the stylesheet beside it, the preview
restyles.To build a playground of your own, use the same elements: the sandbox from
@fusion.dev/helix/sandbox, and the editor from @fusion.dev/helix-sandbox.
The isolation described below makes it safe to run markup that someone else
wrote.How the preview is isolatedThe preview runs in a sandboxed iframe with an opaque origin. The script that
drives it is a static classic script, served as a file. No route renders it.Markup you type into the sandbox runs in the frame. An <img onerror> in it
cannot read the cookies or localStorage of the page around it, or the editor's
contents. Before we isolated the frame, it could. We measured both.The classic script is required for the isolation to work. From an opaque origin,
a module import or a fetch back to the site's origin is cross-origin, and it
fails without CORS headers. A classic <script src> is not CORS-checked. It is
the only way to load the component library into a document that the page cannot
reach into and that cannot reach back out.How the source panes and the preview communicateA source pane dispatches helix-before-edit, which bubbles. The sandbox passes
the value to the preview slotted into it. The source pane and the preview do not
reference each other, so you can replace either one or use it alone.The event includes the pane's name, so one sandbox can hold several panes
without tracking which element sent each event. The names the sandbox reads:A pane named html sets the preview's markup.A pane named css sets its stylesheet.A pane named js sets a script, which runs as a module after the markup.A pane named for a module file, ending in .js or .mjs, such as
utils.js, is a module the other scripts import by its path.A pane named package.json sets the npm packages the scripts import by name,
from its dependencies.The sandbox ignores a pane with any other name, so that pane only displays its
contents.A pane's text goes in its value, or inside the pane where value is not set.
Put markup inside a <template>, so the page does not render it. The sandbox
also hands each pane's text to the preview when it starts, so you write the
text once, in the pane. A preview given its own source keeps it until the
first edit.<helix-sandbox><helix-sandbox-sourcename="html"slot="source"value="<p>Hello</p>"></helix-sandbox-source><helix-sandbox-sourcename="css"slot="source"value="p { color: teal }"></helix-sandbox-source><helix-sandbox-previewslot="preview"src="/frame.html"></helix-sandbox-preview></helix-sandbox>Cancel helix-before-edit to refuse an edit. The pane keeps its text, and the
preview does not receive the refused one.Several source panes are files. The sandbox shows one at a time under a row of
tabs, each named by the pane's label or, without one, its name, and file
holds the name of the pane shown. The arrow keys move between the tabs, and
Home and End go to the first and the last. Choosing a tab sends
helix-before-change with the pane's name, which you can cancel, and
change once file holds it. The panes not shown carry hidden.Packages and module filesIn the built-in document, an example imports npm packages by name and its own
files by path, as it would in a project. A package.json pane lists the
packages and their versions, and each package loads from the preview's cdn,
https://esm.sh/ by default, at the version or range given. Each module pane
is a file the scripts import by a relative path:<helix-sandbox><helix-sandbox-sourcelabel="main.js"name="js"slot="source">
import confetti from 'canvas-confetti'
import { cheer } from './cheer.js'
confetti()
cheer()
</helix-sandbox-source><helix-sandbox-sourcename="cheer.js"slot="source">
export const cheer = () => console.log('Done')
</helix-sandbox-source><helix-sandbox-sourcename="package.json"slot="source">
{ "dependencies": { "canvas-confetti": "^1.9" } }
</helix-sandbox-source><helix-sandbox-previewslot="preview"></helix-sandbox-preview></helix-sandbox>The preview takes the same lists as dependencies and modules without a
sandbox around it. imports maps a name to an address of your own, and wins
over a package of the same name. The preview loads its document again when one
of them changes, because a browser reads an import map only as a document
starts. A src document declares its own import map.Editing keysIn a source pane, Tab inserts two spaces. Shift+Tab moves focus back, and
Escape then Tab moves it forward. The field's description says this to a
screen reader. The other keys follow CodeMirror. Mod is Command on a Mac and
Control elsewhere.KeyDoesTab, with text selectedIndents every line the selection touchesMod+]Indents the linesMod+[Outdents the linesMod+/Comments the lines out, or back inEnterBreaks the line and keeps its indentation, one level deeper after an opening bracketThree editing aids are off until you set them:close-brackets closes brackets and quotes as you type them. A closing
character typed before the same one steps over it, and Backspace between an
empty pair deletes both.match-brackets marks the bracket beside the caret and its pair while the
field has focus.complete offers the words of the source that start with the word you are
typing, and the words in completions. The arrow keys move through the list,
Enter or Tab takes a word, and Escape closes it. manifestWords reads the tag
names, attribute names, and custom properties of a custom elements manifest
into completions.A pane is :state(modified) while it holds an edit, and so is the sandbox
around it. The file tab of an edited pane has a dot after its name, and a
screen reader reads it as modified. reset() on a pane, or on the sandbox for
every pane, puts back the source it was given.Your own editorAny element can be the source pane. Put it in the source slot, give it a
name, and dispatch helix-before-edit with its text on each change, bubbling,
as helix-sandbox-source does. The sandbox reads its value to seed the
preview, and helix-caret from it fills the status bar's position. A wrapper
around CodeMirror:import{HelixBeforeEditEvent}from'@fusion.dev/helix-core'import{EditorView,basicSetup}from'codemirror'classCodeEditorextendsHTMLElement{name=this.getAttribute('name')??'html'value=this.getAttribute('value')??''connectedCallback(){newEditorView({doc:this.value,extensions:[basicSetup,EditorView.updateListener.of((update)=>{if(update.docChanged){this.value=update.state.doc.toString()this.dispatchEvent(newHelixBeforeEditEvent({value:this.value}))}}),],parent:this,})}}customElements.define('code-editor',CodeEditor)<helix-sandbox><code-editorname="html"slot="source"value="<p>Hello</p>"></code-editor><helix-sandbox-previewslot="preview"></helix-sandbox-preview></helix-sandbox>Each request has a notification once it is applied: helix-edit after
helix-before-edit, helix-split after helix-before-split, and
helix-panel-resize after helix-before-panel-resize. Listen for these to
store the arrangement or the source.Set controls on helix-sandbox-console for a clear button and a level
filter above the log. Without them, clear() empties the log, and level
hides the quieter lines.Set controls on helix-sandbox-preview for a reload button and a button that
opens the example in a new tab, in the preview's corner. Without them,
reload() and open() do the same. The tab runs the example in a frame
sandboxed as the preview's own is.An error the example throws and does not catch shows over the preview, in its
error part, until the next source is sent. Hide the part where you draw your
own message from :state(failed).The frame documenthelix-sandbox-preview runs each example in a sandboxed frame. The frame
document either comes with the preview or is one you host.Without src, the preview loads a built-in document, so a sandbox needs
nothing hosted. It runs the example's HTML, CSS, and scripts, and reports the
console, uncaught errors, the height, and presses. It defines no element, so
set src to a document of your own when your examples use Helix components.
Its script is inline, so a page whose Content Security Policy refuses inline
scripts needs a src too.With src, the preview loads your document. It loads the styles and the
elements your examples use, and it talks to the preview through postMessage.The messages:MessageSent byWhat it carrieshelix-preview-consoleThe frameOne console call: level (log, info, warn or error) and texthelix-preview-pressThe frameNothing. Send it on pointerdown, so menus outside the frame closehelix-preview-readyThe frameNothing. Send it once the listener for renders is in placehelix-preview-renderThe previewsource, styles, layout, direction and an optional colorModehelix-preview-sizeThe framesize, the height of the content in CSS pixelsEach message is an object with a type naming it. The preview sends
helix-preview-render once the frame posts helix-preview-ready or fires
load, and again after each edit settles. The frame writes source into its
body and styles into a stylesheet. The preview accepts a message only from
its own frame, and only from an opaque origin, so the frame keeps the sandbox
it was given. The types are exported from @fusion.dev/helix/sandbox as
PreviewMessage and its members.servePreview from @fusion.dev/helix/sandbox is that protocol, written for
you. Call it once in the frame document, after your elements are defined. It
writes each render into #preview-root, runs the example's scripts, relays
its console, its uncaught errors, and its unhandled promise rejections,
reports its height and its presses, and adds the rules for the four layouts.The frame's own script must be a classic script, bundled into one file. A
module does not load there. The frame's origin is opaque, so a module import
back to your site is a cross-origin request, which fails unless your server
answers it with CORS headers. A stylesheet <link> is not checked, so the
styles load as they are.A frame follows. /frame.js is the bundle. /helix.css is the stylesheet
from @fusion.dev/helix-styles, and it imports primitives.css, tokens.css,
base.css, and elements.css from beside it, so serve the five files from
the package's dist folder together:<!doctype html><linkhref="/helix.css"rel="stylesheet"><divid="preview-root"></div><scriptdefersrc="/frame.js"></script>// frame.ts, bundled to /frame.js as a classic scriptimport'@fusion.dev/helix'// registers every family, because an example can use any of themimport{servePreview}from'@fusion.dev/helix/sandbox'servePreview()Pass onReplace to take down what an example mounted before the next one
replaces it, and onRender to act after each render, such as mounting a map.Console output from the exampleLogs from the example appear in the console panel. Logs from the page around it
do not. We patch the console inside the frame on purpose, so the panel shows
what the example logs and nothing else.The panel is a Log. It keeps the last 200 lines, and a line logged again
straight after itself is one line with a count. A screen reader hears each new
line with its level, and a repeat once.The editor and the highlighter@fusion.dev/helix-sandbox is the editor the component pages use, and the
highlighter that colors their code. Install it beside the library:npm install @fusion.dev/helix @fusion.dev/helix-styles @fusion.dev/helix-sandboxEach entry does one thing:@fusion.dev/helix-sandbox/editor registers helix-sandbox-editor, a
source pane with CodeMirror behind it.@fusion.dev/helix-sandbox/highlight turns a snippet into highlighted HTML,
on a server or in a page, with no editor.@fusion.dev/helix-sandbox/code-block colors every Code Block on the page.The editor is a source pane, so it goes in the source slot, with a name
the sandbox reads:<helix-sandbox><helix-sandbox-editorlanguage="html"name="html"slot="source"value="<p>Hello</p>"></helix-sandbox-editor><helix-sandbox-previewslot="preview"></helix-sandbox-preview></helix-sandbox>import'@fusion.dev/helix/sandbox'import'@fusion.dev/helix-sandbox/editor'The Sandbox reference lists
the editor's properties, events, methods, and states.highlight reads the same grammars as the editor. By default each token is a
span with Lezer's tok- class, for a stylesheet of your own. Pass
syntaxHighlighter for the editor's classes, and put syntaxRules in the
stylesheet where the result is drawn for the editor's colors:import{highlight,syntaxHighlighter}from'@fusion.dev/helix-sandbox/highlight'constmarkup=highlight('const total = 1','ts',syntaxHighlighter)