Skip to content

About

SolidJS bindings for tiptap

Resources

Stars

158 stars

Watchers

1 watching

Forks

Repository files navigation

solid-tiptap

SolidJS bindings for Tiptap

NPM Open in StackBlitz

solid-tiptap example

Install

npm i solid-tiptap @tiptap/core @tiptap/pm
yarn add solid-tiptap @tiptap/core @tiptap/pm
pnpm add solid-tiptap @tiptap/core @tiptap/pm

You also need the Tiptap extensions you plan to use, such as @tiptap/starter-kit.

Requirements

  • solid-js 1.7 or later.
  • @tiptap/core and @tiptap/pm 3.x.

Use solid-tiptap@0.7 for Tiptap 2.

Usage

import StarterKit from '@tiptap/starter-kit';
import { createTiptapEditor } from 'solid-tiptap';

function App() {
  let ref!: HTMLDivElement;

  const editor = createTiptapEditor(() => ({
    element: ref,
    extensions: [StarterKit],
    content: `<p>Example Text</p>`,
  }));

  return <div id="editor" ref={ref} />;
}

createTiptapEditor returns an accessor for the editor.

  • The editor is created in an effect, after the element is mounted. The accessor returns undefined until then.
  • The options function is tracked. When a signal it reads changes, the old editor is destroyed and a new one is created.
  • The editor is destroyed when the owning component unmounts.

Because the options are tracked, read signals that should not recreate the editor with untrack.

import { untrack } from 'solid-js';

const editor = createTiptapEditor(() => ({
  element: ref,
  extensions: [StarterKit],
  // Changing `initialContent` later does not recreate the editor.
  content: untrack(initialContent),
}));

Use the editor's own commands to update it after it is created.

editor()?.commands.setContent('<p>New content</p>');
editor()?.setEditable(false);

Passing the editor to other components

Render the components that need the editor inside <Show>, so they only run once the editor exists.

const editor = createTiptapEditor(() => ({ ... }));

<Show when={editor()} keyed>
  {(instance) => <Toolbar editor={instance} />}
</Show>

keyed passes the editor itself to the children. Without it, Solid passes an accessor, so you would write editor={instance()}.

Bubble and floating menus

Tiptap's BubbleMenu and FloatingMenu extensions take an element option. Pass a mounted element the same way you pass the editor element.

import BubbleMenu from '@tiptap/extension-bubble-menu';

function App() {
  let container!: HTMLDivElement;
  let menu!: HTMLDivElement;

  const editor = createTiptapEditor(() => ({
    element: container,
    extensions: [StarterKit, BubbleMenu.configure({ element: menu })],
  }));

  return (
    <>
      <div ref={menu}>
        <Show when={editor()} keyed>
          {(instance) => <Toolbar editor={instance} />}
        </Show>
      </div>
      <div ref={container} />
    </>
  );
}

Reading editor state

Tiptap does not notify Solid when the editor changes. createEditorTransaction reads a value again after every editor transaction, so you can use it in JSX and effects.

import { createEditorTransaction } from 'solid-tiptap';

const isBold = createEditorTransaction(
  () => props.editor,
  (editor) => editor.isActive('bold'),
);

createEffect(() => {
  if (isBold()) {
    // ...
  }
});

The read function receives whatever the first accessor returns. If that can be undefined, handle it in the read function.

const length = createEditorTransaction(
  editor,
  (instance) => instance?.state.doc.textContent.length ?? 0,
);

Helpers

Each helper wraps createEditorTransaction for one part of the Editor API. Each takes an editor accessor, which may return undefined. Each returns an accessor that returns undefined while there is no editor.

Helper Reads Returns
useEditorHTML editor.getHTML() string
useEditorJSON editor.getJSON() JSONContent
useEditorIsActive editor.isActive(...) boolean
useEditorIsEmpty editor.isEmpty boolean
useEditorIsEditable editor.isEditable boolean
useEditorIsFocused editor.isFocused boolean
const html = useEditorHTML(editor);

createEffect(() => {
  saveHTML(html());
});
const json = useEditorJSON(editor);

createEffect(() => {
  const data = json();
  if (data) {
    uploadJSON(data);
  }
});

useEditorIsActive takes a name accessor with optional attributes, a name accessor alone, or attributes alone.

// A node or mark by name.
const isBold = useEditorIsActive(editor, () => 'bold');

// A node or mark by name and attributes.
const isHeading = useEditorIsActive(editor, () => 'heading', { level: 1 });

// Any node or mark with these attributes.
const isCentered = useEditorIsActive(editor, { textAlign: 'center' });

API

createTiptapEditor(options)

Creates a Tiptap editor. Also exported as createEditor, useEditor and useTiptapEditor.

  • options is a function that returns Tiptap's editor options. element is required.
  • Returns () => Editor | undefined.

createEditorTransaction(editor, read)

  • editor is a function that returns an Editor or undefined.
  • read is called with the result of editor(). Its result is returned by the accessor.
  • Returns an accessor that runs read again after every editor transaction.

Types

  • UseEditorOptions<T extends HTMLElement> is the options type for createTiptapEditor.
  • BaseEditorOptions is Tiptap's EditorOptions without element, with every field optional.

Sponsors

Sponsors

License

MIT © lxsmnsyc

About

SolidJS bindings for tiptap

Resources

Stars

158 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages