@matrajs/mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/cli.js +362 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +240 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/docs/benchmarks-full.md +207 -0
- package/docs/changelog.md +222 -0
- package/docs/contributing.md +87 -0
- package/docs/design.md +107 -0
- package/docs/docs-ai.md +90 -0
- package/docs/docs-api.md +149 -0
- package/docs/docs-benchmarks.md +104 -0
- package/docs/docs-collab.md +128 -0
- package/docs/docs-commands.md +162 -0
- package/docs/docs-document-model.md +103 -0
- package/docs/docs-extensions.md +168 -0
- package/docs/docs-first-editor.md +136 -0
- package/docs/docs-frameworks.md +138 -0
- package/docs/docs-index.md +66 -0
- package/docs/docs-installation.md +50 -0
- package/docs/docs-mcp.md +66 -0
- package/docs/docs-position-mapping.md +112 -0
- package/docs/docs-react.md +91 -0
- package/docs/docs-recipes.md +504 -0
- package/docs/docs-shortcuts.md +94 -0
- package/docs/docs-solid.md +82 -0
- package/docs/docs-styling.md +61 -0
- package/docs/docs-svelte.md +86 -0
- package/docs/docs-versions.md +103 -0
- package/docs/docs-vue.md +126 -0
- package/docs/engine.md +315 -0
- package/docs/index.json +205 -0
- package/docs/readme.md +604 -0
- package/docs/releasing.md +65 -0
- package/docs/security.md +72 -0
- package/package.json +54 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Styling
|
|
2
|
+
|
|
3
|
+
Matra ships no appearance. What you have to style, what comes with a stylesheet you can paste, and what is state rather than UI.
|
|
4
|
+
|
|
5
|
+
Matra ships **no appearance at all**. Mount an editor and you get a `contenteditable` element holding plain, unstyled HTML — a heading looks like a heading only because your stylesheet says so. That is what headless means, and it is the reason the same package makes a comment box and a document editor.
|
|
6
|
+
|
|
7
|
+
There are three separate things to know, and they are easy to conflate.
|
|
8
|
+
|
|
9
|
+
## 1 · The document is yours to style
|
|
10
|
+
|
|
11
|
+
The editor renders ordinary tags — ``, ``, ``, ``, ``. Style them the way you style any prose on your site. Scope it to the editor and you are done:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
{`.matra-editor { outline: none; white-space: pre-wrap; }
|
|
15
|
+
.matra-editor h2 { font-size: 1.5rem; margin: 1.5rem 0 .5rem; }
|
|
16
|
+
.matra-editor p { margin: 0 0 .75rem; line-height: 1.6; }
|
|
17
|
+
.matra-editor blockquote { border-left: 2px solid #ccc; padding-left: 1rem; }
|
|
18
|
+
.matra-editor pre { background: #f4f4f4; padding: 1rem; white-space: pre-wrap; }`}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> Note: `white-space: pre-wrap` is not optional. HTML collapses runs of whitespace and drops a trailing one, so without it pressing space at the end of a line does nothing visible until the next character arrives — which reads as a dropped keystroke. It is set twice on purpose. A `pre` carries the browser's own `white-space: pre`, which beats the value inherited from the editor around it, so a code block that does not restate it grows a horizontal scrollbar inside a document that is already scrolling. `overflow-x: auto` is the other way to answer that, and it is the worse one.
|
|
22
|
+
|
|
23
|
+
## 2 · Some extensions bring a stylesheet
|
|
24
|
+
|
|
25
|
+
An extension that renders something the document does not contain — a checkbox, a drag handle, a remote caret — cannot leave its appearance entirely to you, because there is no tag for you to target. Each of those exports the CSS it needs as a string. Paste it, inject it, or copy the rules out and rewrite them; nothing is minified and nothing is obfuscated.
|
|
26
|
+
|
|
27
|
+
| Export | From | What it draws |
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
sheets.map(([name, pkg, what]) => (
|
|
31
|
+
|
|
32
|
+
| `` | `` | |
|
|
33
|
+
))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
{`import { placeholderCSS, taskListCSS, suggestionCSS } from '@matrajs/core'
|
|
38
|
+
|
|
39
|
+
const sheet = new CSSStyleSheet()
|
|
40
|
+
sheet.replaceSync(placeholderCSS + taskListCSS + suggestionCSS)
|
|
41
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]`}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or, if you would rather own them, open the file and copy the rules into your own stylesheet. They are a starting point, not a dependency — every one of them is a handful of lines and none of them is imported by the extension itself.
|
|
45
|
+
|
|
46
|
+
## 3 · A slash menu is state, not a menu
|
|
47
|
+
|
|
48
|
+
This is the one that catches people. `suggestion()` does not draw a menu. It watches for the trigger character, tracks the query as you type, exposes the range it covers, and gives you `acceptSuggestion` and `cancelSuggestion`. What it hands you is a piece of state:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
{`const state = editor.extensionState('slash')
|
|
52
|
+
// { active: true, query: 'head', range: { from: 12, to: 17 } }`}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The list, its position, its keyboard behaviour and its appearance are yours. That is deliberate: a menu is the most opinionated thing in an editor and the thing every product wants to look like its own. The menu on this site is about three hundred lines of ordinary DOM code with no framework, and it is in the repository — copy it and change it rather than starting from nothing.
|
|
56
|
+
|
|
57
|
+
The same is true of a toolbar, a bubble menu and a comment sidebar. The editor gives you the state and the commands; the interface is the part you were going to design anyway.
|
|
58
|
+
|
|
59
|
+
## What this costs you
|
|
60
|
+
|
|
61
|
+
An afternoon at the start, and no fighting later. The alternative — an editor that arrives with a theme — is a week saved on day one and then a month spent overriding selectors when the design changes. If you want a running start, every editor on [the home page](https://matrajs.com/) and [the extensions page](https://matrajs.com/extensions) is a real one, and the CSS behind them is in the same repository.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Svelte
|
|
2
|
+
|
|
3
|
+
A use: action, the editor, and a store that keeps a toolbar honest. Svelte 4 and 5 from one package.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm i @matrajs/core @matrajs/svelte
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## An editor
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
{`<script>
|
|
13
|
+
import { starterKit } from '@matrajs/core'
|
|
14
|
+
import { matra } from '@matrajs/svelte'
|
|
15
|
+
|
|
16
|
+
const { action, editor, state } = matra({
|
|
17
|
+
extensions: starterKit,
|
|
18
|
+
content: '<p>Hello.</p>',
|
|
19
|
+
})
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div use:action></div>`}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`matra()` gives you three things: the `action` to put on the element, the `editor` itself, and a `state` store that republishes whenever the document or the selection changes.
|
|
26
|
+
|
|
27
|
+
> Note: The editor is created immediately, not on mount. `editor.getJSON()`, `editor.getText()` and every command work before anything is on screen — which is what a server render, a test, and a form that submits before the user scrolls to the editor all need.
|
|
28
|
+
|
|
29
|
+
## A toolbar that tells the truth
|
|
30
|
+
|
|
31
|
+
Subscribe to `state` and ask the editor. The pressed state comes from the document rather than from what was last clicked, so it is right after an undo, after a paste, and after somebody moves the caret with the arrow keys.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
{`<script>
|
|
35
|
+
import { starterKit } from '@matrajs/core'
|
|
36
|
+
import { matra } from '@matrajs/svelte'
|
|
37
|
+
|
|
38
|
+
const { action, editor, state } = matra({ extensions: starterKit })
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<div class="toolbar">
|
|
42
|
+
<button
|
|
43
|
+
onclick={() => editor.commands.toggleBold()}
|
|
44
|
+
aria-pressed={$state.isActive('bold')}
|
|
45
|
+
>Bold</button>
|
|
46
|
+
|
|
47
|
+
<button
|
|
48
|
+
onclick={() => editor.commands.toggleHeading(2)}
|
|
49
|
+
aria-pressed={$state.isActive('heading', { level: 2 })}
|
|
50
|
+
>H2</button>
|
|
51
|
+
|
|
52
|
+
<span>{$state.getText().length} characters</span>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div use:action></div>`}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Svelte 4 and Svelte 5
|
|
59
|
+
|
|
60
|
+
One package for both. The store is a plain `svelte/store` readable, which `$state` reads identically under runes — a rune-only build would be a version boundary in exchange for nothing.
|
|
61
|
+
|
|
62
|
+
## What the action does
|
|
63
|
+
|
|
64
|
+
Two things you would otherwise write yourself, and one of them is easy to miss:
|
|
65
|
+
|
|
66
|
+
- Mounts on attach and destroys on detach, so a route change or a `` block leaves nothing behind.
|
|
67
|
+
|
|
68
|
+
- **Refuses to mount twice into one element.** An action re-run by a hot reload, or a component rendered twice, would otherwise attach a second view to one element — which is two carets fighting over it, and it looks like a browser bug rather than yours.
|
|
69
|
+
|
|
70
|
+
## Reaching the editor from elsewhere
|
|
71
|
+
|
|
72
|
+
`editor` is an ordinary object. Put it in a context, a store, or a prop — there is nothing framework-shaped about it, and the paid extensions do not know Svelte exists.
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
{`import { setContext, getContext } from 'svelte'
|
|
76
|
+
|
|
77
|
+
// in the parent
|
|
78
|
+
setContext('editor', editor)
|
|
79
|
+
|
|
80
|
+
// in any child
|
|
81
|
+
const editor = getContext('editor')`}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Styling
|
|
85
|
+
|
|
86
|
+
Matra ships no appearance. [Styling](https://matrajs.com/docs/styling) covers what you style yourself, which extensions bring a stylesheet, and why a slash menu is state rather than a menu.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Version history
|
|
2
|
+
|
|
3
|
+
Snapshots, a real word-level diff between them, and restore as one undo step.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm i @matrajs/versions
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Snapshots of the document, a **real diff** between any two of them — block pairing and then word runs inside a changed block, not a line-by-line text compare — and a restore that lands as a single undo step.
|
|
10
|
+
|
|
11
|
+
> Note: This package is [commercial](https://matrajs.com/pricing). Free to evaluate, develop against, test with, teach with and use in personal projects · a subscription buys running it in production. Nothing phones home and there is no runtime licence check.
|
|
12
|
+
|
|
13
|
+
## Setting it up
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
import { createEditor, starterKit } from '@matrajs/core'
|
|
17
|
+
import { localVersionStore, versions } from '@matrajs/versions'
|
|
18
|
+
|
|
19
|
+
const editor = createEditor({
|
|
20
|
+
extensions: [
|
|
21
|
+
...starterKit,
|
|
22
|
+
versions({
|
|
23
|
+
idleMs: 30_000,
|
|
24
|
+
keep: 50,
|
|
25
|
+
store: localVersionStore('doc-42'),
|
|
26
|
+
onChange: (state) => render(state.versions, state.diff),
|
|
27
|
+
}),
|
|
28
|
+
] as const,
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Option | Type | |
|
|
33
|
+
|
|
34
|
+
| `idleMs` | `number | null` | Snapshot once the document has been still this long. `null` turns automatic snapshots off and leaves them to `snapshotVersion`. A version per keystroke is not history, it is a keylogger with a nicer name. |
|
|
35
|
+
|
|
36
|
+
| `keep` | `number` | How many to hold. The oldest go first · the very first one never does. |
|
|
37
|
+
|
|
38
|
+
| `store` | `VersionStore` | Where the list lives between visits. Without one it lives until reload. |
|
|
39
|
+
|
|
40
|
+
| `onChange` | `(state) => void` | Called whenever the list or the preview changes. |
|
|
41
|
+
|
|
42
|
+
| `now` | `() => number` | Where "now" comes from. Injected rather than reached for, because a test that has to sleep to make two versions differ is a test that fails on a slow machine. |
|
|
43
|
+
|
|
44
|
+
A first version — `Opened` — exists from the moment the editor does, mounted or not, so a headless editor in a test, a server render or an import job always has a "before" to compare against.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
| Command | |
|
|
49
|
+
|
|
50
|
+
| `snapshotVersion(label?)` | Takes one now. Returns `false` when nothing has moved since the last version — a list of identical entries is a list nobody will scroll. |
|
|
51
|
+
|
|
52
|
+
| `restoreVersion(id)` | Puts that document back. Takes a `Before restore` snapshot first, so the way back is the version list rather than undo · and undo is not where anybody thinks to look for their work. |
|
|
53
|
+
|
|
54
|
+
| `previewVersion(id | null)` |
|
|
55
|
+
|
|
56
|
+
| `forgetVersion(id)` | Drop one from the list. |
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
import { versionList } from '@matrajs/versions'
|
|
60
|
+
|
|
61
|
+
editor.commands.snapshotVersion('before the rewrite')
|
|
62
|
+
|
|
63
|
+
const [first] = versionList(editor)
|
|
64
|
+
editor.commands.previewVersion(first.id) // show what changed
|
|
65
|
+
editor.commands.restoreVersion(first.id) // go back · one undo step
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> Note: A restore is **isolated as its own undo step**, whatever was typed a second before it. Undo grouping is a kindness to typing and a hazard to anything deliberate.
|
|
69
|
+
|
|
70
|
+
## A version
|
|
71
|
+
|
|
72
|
+
| Field | Type | |
|
|
73
|
+
|
|
74
|
+
| `id` | `number` | Continues from what the store loaded, rather than restarting at one and colliding. `label` | `string` | Defaults to `Snapshot`. `at` | `number` | Epoch milliseconds, from the clock you supplied. `doc` | `DocNode` | The document as it was. `size` | `number` | Characters at the time. ## Persisting `localVersionStore(key)` is a `localStorage` implementation, and the interface is two methods if you would rather keep them on a server.
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
interface VersionStore {
|
|
78
|
+
/** Sync, so the first render already has them. */
|
|
79
|
+
load(): Version[] | null
|
|
80
|
+
/** Called whenever the list changes · debouncing it is yours to decide. */
|
|
81
|
+
save(versions: Version[]): void
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## The diff on its own The diff is exported separately, so a review screen can compare two documents without an editor anywhere near it — on a server, in a worker, in a test.
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
import { diffDocs } from '@matrajs/versions'
|
|
89
|
+
|
|
90
|
+
const diff = diffDocs(before, after)
|
|
91
|
+
// { blocks, added, removed, changed, same }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Export | |
|
|
95
|
+
|
|
96
|
+
| `diffDocs(a, b)` | `DocDiff` — blocks paired, with word runs inside each changed one. `diffWords(a, b)` | `WordRun[]` · `` where kind is added, removed or same. `textOf(node)` | The text of a node, the way a reader sees it. `sizeOf(node)` | Its size in document coordinates. |
|
|
97
|
+
|
|
98
|
+
| `blockStarts(doc)` | Where each top-level block begins. |
|
|
99
|
+
versionClasses — the class names the preview decorations
|
|
100
|
+
use.
|
|
101
|
+
versionDiffCSSThe stylesheet for them. See Styling.
|
|
102
|
+
|
|
103
|
+
A `BlockChange` carries `before` and `after` indices — one of them `-1` when the block is new or gone — so a side-by-side view can line the two documents up without matching text a second time.
|
package/docs/docs-vue.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Vue
|
|
2
|
+
|
|
3
|
+
useEditor, useEditorState, useEditorFocus and EditorContent for Vue 3 — first-class rather than community-maintained.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm i @matrajs/vue
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Vue is first-class here rather than a community package that lags the React one. Both bindings are written together, tested together and released together, against the same version number. The engine comes with it · there is no second package.
|
|
10
|
+
|
|
11
|
+
## An editor
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { starterKit } from '@matrajs/core'
|
|
16
|
+
import { EditorContent, useEditor } from '@matrajs/vue'
|
|
17
|
+
|
|
18
|
+
const editor = useEditor({
|
|
19
|
+
extensions: starterKit,
|
|
20
|
+
content: '<p>Hello.</p>',
|
|
21
|
+
})
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<EditorContent :editor="editor" />
|
|
26
|
+
</template>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The editor is destroyed when the component unmounts, or when the surrounding effect scope is stopped — so it also works outside a component, in a composable or a store. It is `markRaw`ped, so Vue never walks the document making it reactive: the editor publishes its own changes and a proxy over a live document would cost a great deal to achieve nothing.
|
|
30
|
+
|
|
31
|
+
## A toolbar that stays honest
|
|
32
|
+
|
|
33
|
+
Commands mutate the document; Vue does not hear about that on its own, so a naive toolbar goes stale the moment the caret moves. `useEditorState` returns a readonly ref that follows both changes and selection changes.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import type { Editor } from '@matrajs/core'
|
|
38
|
+
import { useEditorState } from '@matrajs/vue'
|
|
39
|
+
|
|
40
|
+
const props = defineProps<{ editor: Editor }>()
|
|
41
|
+
|
|
42
|
+
const bold = useEditorState(props.editor, (e) => e.isActive('bold'))
|
|
43
|
+
const canBold = useEditorState(props.editor, (e) => e.can.toggleBold())
|
|
44
|
+
const characters = useEditorState(props.editor, (e) => e.getText().length)
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<button
|
|
49
|
+
:aria-pressed="bold"
|
|
50
|
+
:disabled="!canBold"
|
|
51
|
+
@mousedown.prevent="editor.commands.toggleBold()"
|
|
52
|
+
>
|
|
53
|
+
Bold
|
|
54
|
+
</button>
|
|
55
|
+
<span>{{ characters }}</span>
|
|
56
|
+
</template>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> Note: `@mousedown.prevent` rather than `@click`. A click moves focus to the button first and collapses the selection, so the command runs against a caret instead of the words that were highlighted.
|
|
60
|
+
|
|
61
|
+
`can` is the same command asking rather than doing, which is how the button knows to be disabled instead of looking enabled and doing nothing — the caret is in a code block, or the selection cannot hold that mark.
|
|
62
|
+
|
|
63
|
+
## Focus
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
const focused = useEditorFocus(editor)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A readonly ref · useful for showing a toolbar only while the editor has the caret.
|
|
70
|
+
|
|
71
|
+
## Saving
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
<script setup lang="ts">
|
|
75
|
+
import { onScopeDispose } from 'vue'
|
|
76
|
+
|
|
77
|
+
const editor = useEditor({ extensions: starterKit })
|
|
78
|
+
|
|
79
|
+
onScopeDispose(editor.on('change', () => save(editor.getJSON())))
|
|
80
|
+
</script>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`on` returns its own unsubscribe function, so it hands straight to `onScopeDispose` with nothing in between.
|
|
84
|
+
|
|
85
|
+
## v-model, if you want it
|
|
86
|
+
|
|
87
|
+
There is no `v-model` on `EditorContent` on purpose: a two-way binding over a live document means re-parsing it on every keystroke, and the parse is what loses a selection. Write it yourself where you actually need it, and only in one direction.
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
const model = defineModel<string>()
|
|
91
|
+
|
|
92
|
+
const editor = useEditor({
|
|
93
|
+
extensions: starterKit,
|
|
94
|
+
content: model.value,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
onScopeDispose(editor.on('change', () => {
|
|
98
|
+
model.value = editor.getHTML()
|
|
99
|
+
}))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> Note: Setting `model.value` from outside will not push back into the editor, which is the behaviour you want · call `editor.commands` or `editor.setContent()` for a deliberate change instead.
|
|
103
|
+
|
|
104
|
+
## Nuxt
|
|
105
|
+
|
|
106
|
+
The editor touches the DOM only when it mounts, so it renders on the server without a shim. Keep `EditorContent` out of the server pass and the rest works untouched:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
<template>
|
|
110
|
+
<ClientOnly>
|
|
111
|
+
<EditorContent :editor="editor" />
|
|
112
|
+
</ClientOnly>
|
|
113
|
+
</template>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`useEditor` itself runs on the server — the editor exists before anything is on screen, so `getJSON()`, `getHTML()` and `getText()` all answer during a server render, with no DOM polyfill.
|
|
117
|
+
|
|
118
|
+
> Note: With one condition: server-side content must be JSON or Markdown, not an HTML string. Reading HTML is a DOM job, so `content: '
|
|
119
|
+
|
|
120
|
+
…
|
|
121
|
+
|
|
122
|
+
'` needs a browser · `content: fromMarkdown(text)` and `content: storedJson` do not. Writing HTML out with `getHTML()` works either way.
|
|
123
|
+
|
|
124
|
+
## KeepAlive
|
|
125
|
+
|
|
126
|
+
The mount is guarded, so a component brought back by `` or a hot reload does not attach a second view to one element and leave two carets fighting over it.
|