example

<tosi-example> makes it easy to insert interactive code examples in a web page. It started life as a super lightweight, easier-to-embed implementation of b8rjs's fiddle component—which I dearly missed—but now the student is, by far, the master. And it's still super lightweight.

You're probably looking at it right now.

// this code executes in an async function body
// it has tosijs, tosijsui, and preview (the preview div) available as local variables
import { div } from 'tosijs'.elements
preview.append(div({class: 'example'}, 'fiddle de dee!'))
preview.append('Try editing some code and hitting refresh…')
<h2>Example</h2>
.preview {
  padding: 0 var(--spacing);
}

.example {
  animation: throb ease-in-out 1s infinite alternate;
}

@keyframes throb {
  from { color: blue }
  to { color: red }
}

You can also use Typescript. It will be stripped down to Javascript using sucrase.

CSS Isolation with iframe

Add the iframe attribute to render the preview inside an iframe for complete CSS isolation.

Test Blocks

Add \``test` code blocks to write inline tests that run against the preview:

<button class="demo-btn">Click me</button>
preview.querySelector('.demo-btn').onclick = () => {
  preview.querySelector('.demo-btn').textContent = 'Clicked!'
}
test('button exists', () => {
  const btn = preview.querySelector('.demo-btn')
  expect(btn).toBeDefined()
  expect(btn.textContent).toBe('Click me')
})

test('slow test shows running state', async () => {
  await waitMs(500)
  expect(true).toBe(true)
})

Tests have access to:

Async Tests

Tests can be async functions. Use waitMs for simple delays and waitFor to wait for dynamically created elements:

<button class="async-btn">Load Data</button>
<div class="result"></div>
preview.querySelector('.async-btn').onclick = () => {
  setTimeout(() => {
    preview.querySelector('.result').innerHTML = '<span class="data">Loaded!</span>'
  }, 100)
}
// Auto-click to trigger the async behavior
preview.querySelector('.async-btn').click()
test('waitFor finds dynamically created element', async () => {
  const data = await waitFor('.data')
  expect(data.textContent).toBe('Loaded!')
})

test('waitMs delays execution', async () => {
  const start = Date.now()
  await waitMs(50)
  expect(Date.now() - start).toBeGreaterThan(40)
})

context

A <tosi-example> is given a context object which is the set of values available in the javascript's execution context. The context always includes preview.

import * as tosijs from 'tosijs'
import * as tosijsui from 'tosijs-ui'

context = {
  tosijs,
  'tosijs-ui': tosijsui
}