Skip to main content
Allows watching for various playground events. It takes 2 arguments: event name and a callback function that will be called on every event. The watch method returns an object with a single method (remove), which when called will remove the callback from watching further events.

Signature

playground.watch(
  event: "load" | "ready" | "code" | "console" | "tests" | "destroy",
  fn: (data?: any) => void
): { remove: () => void }

Parameters

event
string
required
The event name to watch. Must be one of:
  • "load" - Called when the playground first loads
  • "ready" - Called when a new project is loaded and ready to run
  • "code" - Called when code changes
  • "console" - Called when there’s console output
  • "tests" - Called when tests run
  • "destroy" - Called when playground is destroyed
fn
function
required
Callback function to execute when the event occurs. Receives event-specific data as an argument:
  • "load": No data
  • "ready": { config: Config }
  • "code": { code: Code, config: Config }
  • "console": { method: string, args: any[] }
  • "tests": { results: TestResult[], error?: string }
  • "destroy": No data

Returns

watcher
{ remove: () => void }
An object with a remove() method that, when called, unsubscribes the callback from the event.

Usage

Watch for Code Changes

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container");

const codeWatcher = playground.watch("code", ({ code, config }) => {
  // This runs on every code change
  console.log("Code changed!");
  console.log("Script content:", code.script.content);
  console.log("Script language:", code.script.language);
  console.log("Project title:", config.title);
});

// Later, stop watching
// codeWatcher.remove();

Watch Console Output

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container");

const consoleWatcher = playground.watch("console", ({ method, args }) => {
  // Mirror console output to parent page
  console[method](...args);
  
  // Or display in custom console UI
  if (method === 'error') {
    displayError(args);
  }
});

Watch Test Results

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container", {
  config: {
    tests: {
      language: "javascript",
      content: "test('example', () => expect(1).toBe(1));",
    },
  },
});

const testsWatcher = playground.watch("tests", ({ results, error }) => {
  if (error) {
    console.error("Test error:", error);
    return;
  }
  
  results.forEach((testResult) => {
    console.log("Test:", testResult.testPath.join(' > '));
    console.log("Status:", testResult.status); // "pass", "fail", or "skip"
    console.log("Duration:", testResult.duration, "ms");
    
    if (testResult.status === 'fail') {
      console.log("Errors:", testResult.errors);
    }
  });
});

Watch Playground Load

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container", {
  loading: "click", // Don't auto-load
});

const loadWatcher = playground.watch("load", () => {
  console.log("Playground loaded!");
  // Show UI elements, enable buttons, etc.
  document.getElementById('run-button').disabled = false;
});

// Manually load when needed
await playground.load();

Watch for Ready State

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container");

const readyWatcher = playground.watch("ready", ({ config }) => {
  console.log("New project ready!");
  console.log("Project title:", config.title);
  console.log("Active editor:", config.activeEditor);
  
  // Update UI to reflect the new project
  document.getElementById('project-title').textContent = config.title;
});

// Load different projects
await playground.setConfig({ /* new config */ });
// "ready" event fires when new project is loaded

Watch for Destroy

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container");

const destroyWatcher = playground.watch("destroy", () => {
  console.log("Playground destroyed");
  // Clean up any external resources
  // Update UI state
});

await playground.destroy();
// "destroy" event fires

Auto-Save on Code Changes

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container", {
  config: {
    autoupdate: true,
    autosave: false, // We'll handle saving manually
  },
});

let saveTimeout;

const codeWatcher = playground.watch("code", async ({ code, config }) => {
  // Debounce saves
  clearTimeout(saveTimeout);
  
  saveTimeout = setTimeout(async () => {
    console.log("Auto-saving...");
    
    const fullConfig = await playground.getConfig(true);
    localStorage.setItem('autosave', JSON.stringify(fullConfig));
    
    console.log("Saved!");
  }, 2000); // Save 2 seconds after last change
});

Display Live Code Stats

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container");

const codeWatcher = playground.watch("code", ({ code }) => {
  // Update stats display
  const stats = {
    markup: code.markup.content.length,
    style: code.style.content.length,
    script: code.script.content.length,
    total: code.markup.content.length + 
           code.style.content.length + 
           code.script.content.length,
  };
  
  document.getElementById('stats').innerHTML = `
    <div>Markup: ${stats.markup} chars</div>
    <div>Style: ${stats.style} chars</div>
    <div>Script: ${stats.script} chars</div>
    <div><strong>Total: ${stats.total} chars</strong></div>
  `;
});

Multiple Watchers

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container");

// Watch multiple events
const watchers = [
  playground.watch("load", () => {
    console.log("Loaded");
  }),
  
  playground.watch("ready", ({ config }) => {
    console.log("Ready:", config.title);
  }),
  
  playground.watch("code", ({ code }) => {
    console.log("Code changed");
  }),
  
  playground.watch("console", ({ method, args }) => {
    console[method](...args);
  }),
];

// Later, remove all watchers
function cleanup() {
  watchers.forEach(watcher => watcher.remove());
}

Conditional Event Handling

import { createPlayground } from "livecodes";

const playground = await createPlayground("#container");

const codeWatcher = playground.watch("code", ({ code, config }) => {
  // Only log changes to JavaScript
  if (code.script.language === 'javascript') {
    console.log("JavaScript changed:", code.script.content);
  }
  
  // Alert on errors in console
  // (This would need to be in a console watcher)
});

const consoleWatcher = playground.watch("console", ({ method, args }) => {
  // Only show errors and warnings
  if (method === 'error' || method === 'warn') {
    showNotification(method, args);
  }
});

Event Types

"load" Event

Fired when the playground iframe loads and initializes.
playground.watch("load", () => {
  // No data passed
  console.log("Playground UI loaded");
});

"ready" Event

Fired when a project is loaded and ready to run (including imports).
playground.watch("ready", ({ config }) => {
  console.log("Config:", config);
});

"code" Event

Fired when code changes in any editor, or when languages, external resources, or custom settings change.
playground.watch("code", ({ code, config }) => {
  console.log("Code:", code);
  console.log("Config:", config);
});

"console" Event

Fired on every console method call from the result page.
playground.watch("console", ({ method, args }) => {
  // method: "log", "info", "warn", "error", "table", etc.
  // args: array of arguments passed to console method
});

"tests" Event

Fired when tests complete running.
playground.watch("tests", ({ results, error }) => {
  if (error) {
    console.error("Test error:", error);
  } else {
    console.log("Test results:", results);
  }
});

"destroy" Event

Fired when the playground is destroyed.
playground.watch("destroy", () => {
  // No data passed
  console.log("Playground destroyed");
});

Notes

  • Watchers automatically notify the playground that you want to receive events, so the playground knows to send data.
  • When all watchers for an event are removed, the playground stops sending that event’s data.
  • The code event provides the same data structure as getCode().
  • Console events are only sent when you have a console watcher active.
  • Test events are only sent when you have a tests watcher active.
  • Always call remove() on watchers you no longer need to prevent memory leaks.
  • getCode() - Get current code (same structure as code event data)
  • getConfig() - Get current config (same structure as ready event data)
  • destroy() - Destroy the playground (triggers destroy event)

Build docs developers (and LLMs) love