Skip to content

arjunkmrm/bonsai

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bonsai

Widget plugin SDK for engei — build custom widgets that render inside markdown preview.

Install

npm install @engei/bonsai

Create a widget

A widget is an object that satisfies the WidgetPlugin interface:

import type { WidgetPlugin } from "@engei/bonsai"

export const myPlugin: WidgetPlugin = {
  type: "my-widget",
  hydrate: (container, spec, theme) => {
    container.textContent = `Hello from ${spec.name}!`
  },
}

WidgetPlugin interface

Field Type Required Description
type string Yes Identifier matching the spec's type field
hydrate WidgetHydrator Yes Renders the widget into a DOM element. May return a cleanup function
codeBlockLang string No Auto-detect widgets from fenced code blocks with this language
toSpec (text, position?) => object No Parse code block text into a spec object. Defaults to JSON.parse

WidgetHydrator signature

type WidgetHydrator = (
  container: HTMLElement,
  spec: WidgetSpec,
  theme: "dark" | "light",
) => void | (() => void)

The spec contains widgetId, type, and any additional fields from the markdown source.

Return a cleanup function to dispose resources when the widget is removed.

Use with engei

Pass your widgets to the editor alongside the built-in defaults:

import { Editor } from "engei"
import { getDefaultWidgets } from "@engei/bonsai"
import { myPlugin } from "./my-plugin"

<Editor
  content={markdown}
  mode="preview"
  widgets={[...getDefaultWidgets(), myPlugin]}
/>

Fenced code block widgets

Widgets can auto-detect from fenced code blocks by setting codeBlockLang:

export const csvPlugin: WidgetPlugin = {
  type: "csv-table",
  codeBlockLang: "csv",
  toSpec: (text) => ({
    rows: text.split("\n").map(line => line.split(",")),
  }),
  hydrate: (container, spec, theme) => {
    const table = document.createElement("table")
    for (const row of spec.rows) {
      const tr = document.createElement("tr")
      for (const cell of row) {
        const td = document.createElement("td")
        td.textContent = cell
        tr.appendChild(td)
      }
      table.appendChild(tr)
    }
    container.appendChild(table)
  },
}

Then in markdown:

```csv
Name,Score
Alice,95
Bob,87
```

Utilities

The package exports helpers useful for building widgets:

  • loadCDN(url, globalName) — lazy-load a script from CDN, cached and deduped
  • escapeHtml(str) — escape HTML entities

Built-in widgets

Default (included in getDefaultWidgets())

Widget Type Code block lang Description
chartPlugin chart Chart.js charts from JSON config
mermaidPlugin mermaid mermaid Mermaid diagrams
diffPlugin diff diff Syntax-highlighted code diffs
globePlugin globe globe Interactive WebGL globe (COBE)
katexPlugin katex math LaTeX math rendering via KaTeX
tablePlugin table table Enhanced tables with sorting from JSON
embedPlugin embed embed Sanitized iframes (YouTube, Figma, Loom, etc.)

Optional (import individually)

These are exported but not in the default registry. Add them explicitly:

import { getDefaultWidgets, excalidrawPlugin, mapPlugin, timelinePlugin } from "@engei/bonsai"

const widgets = [...getDefaultWidgets(), excalidrawPlugin, mapPlugin, timelinePlugin]
Widget Type Code block lang Description
excalidrawPlugin excalidraw excalidraw Excalidraw whiteboard sketches from JSON
mapPlugin map map Interactive Leaflet maps with markers
timelinePlugin timeline timeline Vertical chronological timeline

Embed allowlist

The embed widget only loads iframes from trusted domains: YouTube, Vimeo, Loom, Figma, CodeSandbox, StackBlitz, CodePen, Google Docs, Airtable, Notion, Excalidraw, Miro, and Whimsical. Share URLs (e.g. youtube.com/watch?v=...) are automatically converted to embed URLs.

License

MIT

About

Widget plugin SDK for engei — build custom widgets for markdown preview.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors