# Web Components

`mdlib-pure/element` defines two custom elements that work in plain HTML and
in any framework:

- `<mdlib-markdown>`: renders Markdown from its `source` attribute;
- `<mdlib-editor>`: a Markdown editor with toolbar and live preview.

They are the base of the [Blazor wrapper](blazor.md) and of
the [Angular wrapper](angular.md).

## Quick start

Core and every plugin are included: ~20 KB gzip. KaTeX and Mermaid are not:
pass loaders, and they are fetched only when a document uses them.

```js
import { defineElements } from 'mdlib-pure/element'
import 'mdlib-pure/theme.css' // with a bundler that handles CSS imports;
import 'mdlib-pure/editor.css' // otherwise <link> them (see below)

defineElements({
  loaders: {
    mermaid: () => import('mermaid'),
    katex: () => import('katex/contrib/auto-render').then((m) => m.default),
    katexStylesheet: '/css/katex.min.css',
  },
  configs: { docs: { highlight: (code, lang) => myHighlighter(code, lang) } },
})
```

```html
<mdlib-markdown class="md" source="# Hello" plugins="gfm math"></mdlib-markdown>
<mdlib-editor value="# Hello" plugins="all" style="block-size: 30rem"></mdlib-editor>
```

**Attributes**

| Element | Attribute | |
|---|---|---|
| both | `plugins` | space-separated names or `all`: `gfm headings math mermaid container deflist mark frontmatter` |
| both | `config` | name of options registered with `defineElements({ configs })` |
| `<mdlib-markdown>` | `source` | the Markdown |
| `<mdlib-editor>` | `value` | initial (or externally set) Markdown |
| `<mdlib-editor>` | `view` | `split` (default), `write`, `preview` |
| `<mdlib-editor>` | `delay` | ms before `input` is dispatched (default 200) |
| `<mdlib-editor>` | `label`, `name` | accessible name of the text area; form field name |
| `<mdlib-editor>` | `disabled` | no typing or formatting, value left out of the form (as inside `<fieldset disabled>`) |

Options that are functions (`highlight`, `allowUrl`, `sanitize`) and `html`
can only be set through named configs, never from attributes: content cannot
switch raw HTML on.

**Editor behavior.** The preview renders on every keystroke, in the browser.
Listeners get a debounced `input` event and `change` on blur, with the text
in `event.target.value` (the text area's own events do not escape). While the
user types, an attribute set to a value the editor already sent is treated as
a late echo and ignored, so a slow server never overwrites typing; a value
set while the editor is not focused (a Reset button) is applied. The element
is form-associated: with `name`, its value is submitted with the form;
`disabled` (or an enclosing `<fieldset disabled>`) makes it behave like a
disabled `<textarea>`, while the view buttons keep working.
Toolbar: bold, italic, heading, link, lists, quote, code (Ctrl+B, Ctrl+I,
Ctrl+K). Math and diagrams load KaTeX and Mermaid only when a document uses
them; diagrams already drawn are reused while typing.

For an editor with application-specific tags and toolbar actions, see the
[podcast sample](podcast.md). It builds a dedicated split editor with the public
parser/render APIs; custom plugins and commands are not registration options of
`defineElements()`.

`getMarkdown(plugins, config)` returns the shared `Markdown` instance behind
an attribute combination (e.g. to `parse()` the editor's text).

## Without a bundler

`dist/element.js` is an ES module. Load it with an import map or a relative
path, then define the elements:

```html
<link rel="stylesheet" href="/node_modules/mdlib-pure/styles/theme.css" />
<link rel="stylesheet" href="/node_modules/mdlib-pure/styles/editor.css" />
<script type="module">
  import { defineElements } from '/node_modules/mdlib-pure/dist/element.js'
  defineElements()
</script>

<mdlib-editor name="body" value="# Draft" plugins="gfm" style="block-size: 24rem"></mdlib-editor>
```

## Reading the editor's value

```js
const editor = document.querySelector('mdlib-editor')
editor.addEventListener('input', (e) => save(e.target.value)) // debounced (delay)
editor.addEventListener('change', (e) => save(e.target.value)) // on blur
editor.value = '# Replaced' // set it from code
```

`value` works as a property or as an attribute, before or after the element
is connected, and even before `defineElements()` runs: frameworks that set
properties on custom elements (React 19, Vue) need nothing special. Inside a
`<form>`, an editor with a `name` submits its text like a `<textarea>` would.

## Frameworks that rewrite the page

Blazor enhanced navigation, Turbo and htmx may empty or morph elements they
did not create. Both elements repair themselves: the viewer renders again,
the editor puts back its toolbar and panes and keeps the typed text. Call
`element.refresh()` to render again by hand; every `defineElements()` call
refreshes the elements already in the page (e.g. loaders registered late).

## Frameworks

- **Blazor**: use [MdlibPure.Blazor](blazor.md), which wraps
  these elements for every render mode and adds a C# AST service.
- **Angular**: use [ngx-mdlib-pure](angular.md): standalone
  components with `[(value)]`, a form control for `ngModel` and reactive
  forms, SSR with hydration, and a service for the AST.
- **React 19, Vue, Svelte**: custom elements work as-is:
  `<mdlib-editor value={text} onInput={(e) => setText(e.target.value)} />`
  in React 19 (which sets `value` as a property), `:value` in Vue. Pass
  `source` and `plugins` as attributes.

## Testing

Unit tests in jsdom (Vitest, Jest) work without polyfills: the editor skips
the form value where `ElementInternals.setFormValue()` is missing. In jsdom,
`value` and the preview are updated at once; the `input` event comes after
`delay` ms (use fake timers), `change` on blur. KaTeX and Mermaid do not run
there: test math and diagrams in a real browser.

## Security

Raw HTML and functions cannot be enabled from attributes: `html`,
`highlight`, `allowUrl` and `sanitize` only come from named configs written
in JavaScript. URLs follow the [default allowlist](security.md#url-policy);
Mermaid runs with `securityLevel: 'strict'`. More in
[Security: Web Components](security.md#web-components).
