Svelte Integration
ProseKit is designed to work seamlessly with Svelte.
<script lang="ts">
import 'prosekit/basic/style.css'
import { defineBasicExtension } from 'prosekit/basic'
import { createEditor, jsonFromNode, type NodeJSON } from 'prosekit/core'
import { ProseKit, useDocChange } from 'prosekit/svelte'
export let defaultContent: NodeJSON | undefined = undefined
export let onDocUpdate: ((doc: NodeJSON) => void) | undefined = undefined
const extension = defineBasicExtension()
const editor = createEditor({ extension, defaultContent })
useDocChange((doc) => onDocUpdate?.(jsonFromNode(doc)), { editor })
const mount = (element: HTMLElement) => {
editor.mount(element)
return { destroy: () => editor.unmount() }
}
</script>
<ProseKit {editor}>
<div class='box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 shadow dark:border-zinc-700 flex flex-col bg-white dark:bg-neutral-900'>
<div class='relative w-full flex-1 box-border overflow-y-scroll'>
<div use:mount class='ProseMirror box-border min-h-full px-[max(4rem,_calc(50%-20rem))] py-8 outline-none outline-0 [&_span[data-mention="user"]]:text-blue-500 [&_span[data-mention="tag"]]:text-violet-500 [&_pre]:text-white [&_pre]:bg-zinc-800'></div>
</div>
</div>
</ProseKit>
useEditor
Retrieves the current editor instance within a ProseKit
component.
const editor = useEditor()
If you pass { update: true }
, it will trigger a re-render when the editor state changes.
const editor = useEditor({ update: true })
This is useful if you want to update the UI based on the current editor state. For example, you can calculate the word count of the document after every change. Check out word-counter for a complete implementation.
useExtension
Adds an extension to the editor.
const extension = computed(() => defineMyExtension())
useExtension(extension)
useKeymap
Adds key bindings to the editor.
import type { Keymap } from 'prosekit/core'
import { useKeymap } from 'prosekit/svelte'
import { derived, type Readable } from 'svelte/store'
export function useSubmitKeymap(
hotkey: Readable<'Shift-Enter' | 'Enter'>,
onSubmit: (hotkey: string) => void,
) {
const keymap: Readable<Keymap> = derived(hotkey, (hotkey) => {
return {
[hotkey]: () => {
onSubmit(hotkey)
// Return true to stop further keypress propagation.
return true
},
}
})
useKeymap(keymap)
}
Check out keymap for a complete implementation.
defineSvelteNodeView
Renders a node using a Svelte component.
In some cases, Svelte might be a more convenient tool for implementing certain interactions. For instance, for a code block, you might want to add a language selector that lets you change the language of the code block. You can implement this using a Svelte component.
We begin by creating a CodeBlockView
component to render the node. This component receives SvelteNodeViewProps
as props, which include the node and other useful details.
<script lang="ts">
import LanguageSelector from './language-selector.svelte'
import type { CodeBlockAttrs } from 'prosekit/extensions/code-block'
import type { SvelteNodeViewProps } from 'prosekit/svelte'
export let node: SvelteNodeViewProps['node']
export let setAttrs: SvelteNodeViewProps['setAttrs']
export let contentRef: SvelteNodeViewProps['contentRef']
// Ignore "<Component> was created with unknown prop" warnings in Svelte v4
$$restProps
const attrs = $node.attrs as CodeBlockAttrs
const language = attrs.language
const setLanguage = (language: string) => {
const attrs: CodeBlockAttrs = { language }
setAttrs(attrs)
}
</script>
<LanguageSelector {language} {setLanguage} />
<pre use:contentRef data-language={language}></pre>
CodeBlockView
renders a LanguageSelector
component (the button in the top left corner) and a <pre>
element to hold the code. We bind the contentRef
to the <pre>
element, which allows the editor to manage its content.
After defining the component, we can register it as a node view using defineSvelteNodeView
. The name
is the node's name, in this case "codeBlock"
. contentAs
is the property name that contains the node's content. In this case, it's "code"
, which means a <code>
element will be rendered inside the <pre>
element. component
is the component we just defined.
import type { SvelteNodeViewComponent } from 'prosekit/svelte'
import { defineSvelteNodeView } from 'prosekit/svelte'
import CodeBlockView from './code-block-view.svelte'
const extension = defineSvelteNodeView({
name: 'codeBlock',
contentAs: 'code',
component: CodeBlockView as SvelteNodeViewComponent,
})
Check out code-block for a complete implementation.