npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@joshualevy029/tiptap-editor

v0.3.3

Published

Rich-text editor component built with Tiptap and Material UI.

Readme

@joshualevy029/tiptap-editor

Editor rich-text em React construído sobre Tiptap v3, com UI em Material UI e ícones Iconify. Pensado para conteúdo didático: fórmulas matemáticas (MathLive + KaTeX), fórmulas químicas (mhchem), tabelas com balloon estilo CKEditor, imagens com resizer, colunas estilo jornal e variantes com visual de folha (documento paginado e página de apostila).

Buy me a coffee

Sumário

Compatibilidade

| Ambiente | Suporte | |---|---| | React | 18 ou 19 | | MUI (peer) | v9 | | Next.js | App Router e Pages Router (componente client-side — ver Uso com Next.js) | | Node no projeto consumidor | Qualquer (runtime é browser). Instalação via registro: funciona em Node 18+. Instalação via git: exige Node ≥ 20.19 (o build roda no install) |

Instalação

# via registro (recomendado — dist pré-buildado, funciona em Node 18)
npm install @joshualevy029/tiptap-editor

# ou via git (o build roda no install; exige Node >= 20.19)
npm install git+ssh://[email protected]/JoshuaLevy029/tiptap-editor.git

Peer dependencies (o projeto host precisa tê-las instaladas):

npm install react react-dom @mui/material @emotion/react @emotion/styled

Tiptap, KaTeX, MathLive e Iconify são dependências internas — você não precisa instalá-las nem conhecê-las.

Uso rápido

import { useState } from "react";
import { Editor } from "@joshualevy029/tiptap-editor";

export function MinhaTela() {
  const [content, setContent] = useState("");

  return <Editor defaultValue={content} onChange={setContent} />;
}

<Editor /> sem nenhuma prop já funciona com todas as features ativas. O valor emitido pelo onChange é uma string pronta para persistir no banco.

Props do Editor

Referência de todas as props. As detalhadas têm seção própria (link na coluna).

| Prop | Tipo | Descrição | | --- | --- | --- | | defaultValue | string | Conteúdo inicial (modo não controlado), no formato de saveAs. | | value | string | Conteúdo controlado — o documento segue esta prop. | | onChange | (value: string) => void | Chamado a cada alteração, com o conteúdo serializado (saveAs). | | saveAs | "html" \| "markdown" | Formato de onChange/defaultValue/value. Default: "html". Ver Formato do conteúdo. | | placeholder | string | Texto livre exibido enquanto o editor está vazio. Vale para todas as variantes. | | readOnly | boolean | Somente leitura (oculta a toolbar). Default: false. | | features | EditorFeatures | Ativa/desativa/configura cada feature. Ver Features. | | ui | EditorUiOverrides | Customização de UI por item da toolbar. Ver Customização de UI. | | variant | "standard" \| "document" \| "apostila" \| "input" \| "textarea" | Layout do editor. Default: "standard". Ver Variantes e Campos compactos. | | showToolbar | boolean | Exibe/oculta a toolbar. Default: true em bloco, false em compacto. Ver Ocultar a toolbar. | | appearance | EditorAppearance | Modo (light/dark) e cores sem ThemeProvider. Ver Aparência. | | onSubmit | (value: string) => void | variant="input": chamado no Enter, com o conteúdo serializado. | | minRows / maxRows | number | variant="textarea": altura mínima/máxima em linhas. Default: 2 / 8. | | size | "small" \| "medium" | Densidade das variantes compactas. Default: "medium". | | sx | SxProps<Theme> | sx do MUI aplicado à raiz, mesclado com o estilo interno. | | className, style, id, aria-*, data-*, onClick, tabIndex… | atributos de <div> | Qualquer prop padrão de um <div> é encaminhada ao elemento raiz do editor. | | ref | Ref<EditorHandle> | Acesso imperativo (getHTML, focus, clear…). Ver Ref imperativa. |

As únicas props não encaminhadas são as consumidas pelo editor (value, defaultValue, onChange, saveAs, features, variant, …) e as que quebrariam o conteúdo (children, dangerouslySetInnerHTML). onChange e onSubmit têm assinatura própria do editor (recebem a string, não um evento).

// O placeholder é texto livre, por instância, em qualquer variante:
<Editor placeholder="Digite o enunciado…" />
<Editor variant="input" placeholder="Comente e mencione com @…" />

Formato do conteúdo: HTML ou Markdown

A prop saveAs define o formato do onChange e de defaultValue/value:

<Editor defaultValue="<p>Olá <strong>mundo</strong></p>" onChange={setHtml} />
<Editor defaultValue={"# Olá\n\n**mundo**"} onChange={setMd} saveAs="markdown" />
  • Default: "html".
  • Markdown usa o serializador oficial @tiptap/markdown; fórmulas saem como $...$ / $$...$$.
  • Preservados no carregamento (mesmo sem botão na toolbar, pois fazem parte da integridade do conteúdo): títulos, negrito, itálico, tachado, código inline e em bloco (com linguagem), listas (aninhadas), link, blockquote, task list (- [ ]), linha horizontal (---) e tabela GFM.
  • Lossy em Markdown (sem sintaxe equivalente — só sobrevivem em HTML): cor do texto/fundo, marca-texto, fonte, tamanho, alinhamento e recuo. Para o catálogo completo, persista HTML.

Modo controlado e não controlado

// Não controlado: defaultValue só na montagem; o editor é dono do estado
<Editor defaultValue={inicial} onChange={setContent} />

// Controlado: o documento segue a prop value
<Editor value={content} onChange={setContent} />

No modo controlado, o eco do próprio onChange é ignorado (não reseta cursor); apenas mudanças externas de value substituem o documento. Para carregar conteúdo vindo de um fetch no modo não controlado, monte o editor quando o dado chegar: {data && <Editor defaultValue={data} />}.

Ref imperativa

const ref = useRef<EditorHandle>(null);

<Editor ref={ref} />;

ref.current?.getHTML();      // string HTML
ref.current?.getMarkdown();  // string Markdown
ref.current?.getJSON();      // JSON do Tiptap (ProseMirror doc)
ref.current?.focus();
ref.current?.clear();
ref.current?.getEditor();    // instância Tiptap (escape hatch)

Features: ativar, desativar e configurar

Todas as features vêm ativas por padrão (exceto mention, que precisa de uma fonte de dados — ver abaixo). Cada chave da prop features aceita:

  • true / omitida → ativa com os defaults;
  • false → desativada (some da toolbar e a extensão nem carrega);
  • um objeto de configuração → ativa com ajustes.
// Desligar só o que você não quer (o resto continua ativo)
<Editor features={{ table: false, chemistry: false }} />

// Ou o oposto: começar de tudo desligado e ligar só o essencial
<Editor
  features={{
    // desative o resto explicitamente…
    textType: false, image: false, table: false, math: false, /* … */
    // …e ative o que precisa
    bold: true,
    italic: true,
    bulletList: true,
  }}
/>

// Configurar features específicas
<Editor
  features={{
    textType: { levels: [1, 2, 3] },
    image: { onUpload: async (file) => minhaApi.upload(file) },
  }}
/>

Dica de tipagem: use satisfies EditorFeatures no objeto de config para o TypeScript validar as chaves e pegar erros de digitação.

Chaves disponíveis

As 26 chaves (todas em features.<chave>): textType, bold, italic, bulletList, orderedList, indent, columns, image, table, undoRedo, textAlign, lineHeight, backgroundColor, textColor, fontFamily, fontSize, highlight, specialCharacters, strike, underline, subscript, superscript, sourceCode, math, chemistry, mention.

Catálogo

| Chave | Feature | Configuração | |---|---|---| | textType | Parágrafo/Título 1–6 | levels: (1\|2\|...\|6)[] | | bold / italic / strike / underline | Formatação básica | — | | subscript / superscript | Sub/sobrescrito | — | | bulletList | Lista com marcadores | styles: ("disc"\|"dash")[] | | orderedList | Lista numerada | types: ("1"\|"a"\|"A"\|"i"\|"I")[] | | indent | Recuo | maxLevel, step | | columns | Colunas estilo jornal | counts: number[] (2–4) | | image | Imagem (upload/URL, resizer, alt, legenda, alinhamento) | ver abaixo | | table | Tabela (balloon com linhas/colunas/mesclar/propriedades) | resizable: boolean | | undoRedo | Desfazer/refazer | — | | textAlign | Alinhamento | alignments | | lineHeight | Entrelinha | options: {label, value}[] | | textColor / backgroundColor | Cores do texto/fundo | colors: string[] | | highlight | Marca-texto | colors: string[] | | fontFamily | Fonte | fonts: {label, value}[] | | fontSize | Tamanho | sizes: {label, value}[] | | specialCharacters | Caracteres especiais (busca + categorias) | sets | | sourceCode | Código-fonte | mode: "html" \| "codeBlock" | | math | Fórmulas matemáticas (MathLive + KaTeX) | templates, keyboardLayouts | | chemistry | Fórmulas químicas (mhchem) | groups, templates | | mention | Menção @ com dropdown de sugestões | items (obrigatório), char |

Imagem: upload

O editor entrega o File e usa a string retornada como src — pode ser a URL devolvida pela sua API ou um data-URL base64:

image: {
  onUpload: async (file) => {
    const { url } = await api.upload(file);   // sua API
    return url;
  },
  accept: ["image/png", "image/jpeg"],
  maxSizeBytes: 5 * 1024 * 1024,
  onUploadError: (error, file) => toast.error(error.message),
}

Sem onUpload, o editor converte para base64 (default zero-config). No documento, a imagem selecionada ganha resizer nos cantos e toolbar flutuante com alinhamento, texto alternativo (alt) e legenda.

Matemática e química

math: {
  templates: ["\\hat{#0}", "\\frac{#0}{#?}"],       // menu "Modelos personalizados"
  keyboardLayouts: ["numeric", "greek", meuLayout],  // abas do teclado virtual
},
chemistry: {
  groups: [{ label: "Setas", items: [{ label: "→", value: "->" }] }],
  templates: [{ label: "Combustão", value: "CH4 + 2O2 -> CO2 + 2H2O" }],
}

O dialog de matemática tem editor visual (MathLive), modo LaTeX bruto sempre disponível e preservação byte a byte do código (fórmulas nunca são corrompidas por round-trip). O de química usa mhchem com paleta lateral por categorias.

Caracteres especiais

specialCharacters: {
  sets: [{ label: "Meus símbolos", characters: ["©", { label: "seta", value: "→" }] }],
}

Menção (@)

Ao digitar @, abre um dropdown de sugestões. A menção fica ativa apenas quando você fornece items — é você quem decide o que sugerir (usuários, termos, tópicos…), síncrono ou assíncrono:

<Editor
  features={{
    mention: {
      // recebe o texto após o "@" e devolve os itens a sugerir
      items: async (query) => {
        const users = await api.buscarUsuarios(query);
        return users.map((u) => ({ id: u.id, label: u.nome }));
      },
      char: "@", // gatilho (default "@"); use "#" para tópicos, por exemplo
    },
  }}
/>
  • Cada item é { id: string; label: string }. O label aparece no dropdown e no chip inserido; o id fica salvo no HTML (data-id) para você linkar depois.
  • Navegação por teclado (↑ ↓ Enter) e clique já vêm prontos.
  • No HTML, a menção sai como <span data-type="mention" data-id="42" data-label="Fulano" class="mention">@Fulano</span>.
  • Sem items, a feature simplesmente não é carregada (não há o que sugerir).

Customização de UI (prop ui)

Ajuste a aparência de qualquer item da toolbar sem mexer no comportamento:

<Editor
  ui={{
    bold: { icon: "mdi:format-bold", tooltip: "Negrito (Ctrl+B)" },
    textType: { icon: <MeuIcone /> },           // ReactNode também
    italic: { hidden: true },                    // some da toolbar (feature segue ativa)
    orderedList: { hideOptions: ["I", "i"] },    // oculta opções do menu
  }}
/>

| Campo | Efeito | |---|---| | icon | Nome Iconify (string) ou ReactNode | | tooltip | Substitui tooltip e nome acessível | | hidden | Remove o botão da toolbar; atalhos/parse continuam funcionando | | hideOptions | Oculta opções de menus pelo value |

Variantes: DocumentEditor e ApostilaEditor

import { DocumentEditor, ApostilaEditor } from "@joshualevy029/tiptap-editor";
  • DocumentEditor — folhas paginadas estilo Word/Docs: página A4 (ou Carta/Ofício) sobre fundo cinza, zoom 50–150%, painel de configuração (formato, orientação, margens em cm, cor da folha) e paginação visual: blocos que estouram a altura útil pulam para a folha seguinte.
  • ApostilaEditor — folha única no formato de página de apostila (736px, padding 40/24, tipografia 16px/1.6). Interpreta HTML legado com div.cols como bloco de colunas editável.

Ambos aceitam todas as props do Editor (features, saveAs, ui…).

Campos compactos: input e textarea

Além das variantes de bloco, há duas variantes compactas — ideais para uma caixa de comentário com menção @. A toolbar fica oculta por padrão nelas.

// Uma linha: Enter dispara onSubmit e nunca quebra a linha.
<Editor
  variant="input"
  features={{ mention: { items } }}
  value={comment}
  onChange={setComment}
  onSubmit={(value) => {
    enviar(value);   // value no formato de saveAs (HTML por padrão)
    setComment("");  // o editor não limpa sozinho — você decide
  }}
  placeholder="Comente e mencione com @…"
/>

// Multilinha que cresce entre minRows e maxRows.
<Editor
  variant="textarea"
  minRows={3}
  maxRows={8}
  features={{ mention: { items } }}
/>

| Prop | Aplica a | Descrição | | --- | --- | --- | | onSubmit(value) | input | Chamado no Enter, com o conteúdo serializado (saveAs). | | minRows | textarea | Altura mínima em linhas (default 2). | | maxRows | textarea | Altura máxima antes de rolar (default 8). | | size | input / textarea | Densidade estilo MUI: "small" | "medium" (default "medium"). Ajusta fonte e padding. |

Ocultar a toolbar (showToolbar)

showToolbar controla a exibição da toolbar. O default depende da variante: true nas de bloco (standard/document/apostila) e false nas compactas (input/textarea).

<Editor showToolbar={false} />              {/* bloco sem toolbar */}
<Editor variant="textarea" showToolbar />    {/* compacto com toolbar */}

Tema, aparência e dark mode

O componente herda o ThemeProvider MUI do host. Há duas formas de customizar cores e modo, combináveis:

1. Prop appearance (rápida, sem ThemeProvider)

Sobrescreve modo e cores pontuais sem precisar montar um tema. Omitida, o editor herda o tema do host. Os campos de paleta propagam via um ThemeProvider aninhado; os específicos da toolbar são aplicados direto.

<Editor
  appearance={{
    mode: "dark",              // "light" | "dark" — omita para herdar do host
    accent: "#7c3aed",         // chip de menção, seleção, itens ativos
    background: "#1e1e2e",      // fundo da área de edição
    text: "#e4e4e7",           // cor do texto padrão
    border: "#3f3f52",         // bordas e divisores (tabela, toolbar)
    toolbarBackground: "#2a2a3c",
    iconColor: "#a1a1aa",        // ícones das features (itens inativos)
    buttonBackground: "#facc15", // fundo dos botões ativos
    activeIconColor: "#000000",  // ícone do botão ativo (contraste com o fundo)
    iconSize: 22,                // tamanho dos ícones (px ou string CSS)
    buttonSize: 40,              // tamanho (quadrado, mínimo) dos botões
  }}
/>

iconSize e buttonSize aceitam número (px) ou string CSS. buttonSize é o lado mínimo do botão; botões com mais de um elemento (menus, seletor de cor) crescem além disso na largura. Use activeIconColor quando buttonBackground for uma cor forte, para o ícone ativo não sumir sobre o fundo.

Todos os campos são independentes: você pode, por exemplo, usar mode: "dark" e sobrescrever apenas buttonBackground — o resto das cores segue os defaults do modo escuro.

O dark/light é controlado pelo host (via appearance.mode ou tema) — não há botão de alternância embutido.

2. createEditorTheme (controle total via MUI)

Para hosts que já usam MUI e querem um tema completo. Faz deep-merge sobre a base grafite, então sobrescritas parciais (ex.: só palette.mode) preservam o resto:

import { createEditorTheme } from "@joshualevy029/tiptap-editor";

<ThemeProvider theme={createEditorTheme({ palette: { mode: "dark" } })}>
  <Editor />
</ThemeProvider>;

createEditorTheme(options) aceita qualquer ThemeOptions do MUI.

Uso com Next.js

O editor é um componente client-side (usa DOM/contenteditable).

App Router — marque o arquivo que o usa:

"use client";

import { Editor } from "@joshualevy029/tiptap-editor";

Pages Router (ou para evitar SSR por completo):

import dynamic from "next/dynamic";

const Editor = dynamic(
  () => import("@joshualevy029/tiptap-editor").then((m) => m.Editor),
  { ssr: false },
);

O componente já monta com immediatelyRender: false (seguro para hidratação). Os CSS do KaTeX são importados pela própria lib.

Exibindo o conteúdo salvo

  • Com o próprio pacote: <Editor defaultValue={html} readOnly /> — render fiel, fórmulas via KaTeX embutido.
  • Com MathJax no host: as fórmulas saem no HTML como <span data-type="inline-math" data-latex="..."> / <div data-type="block-math" data-latex="...">. Converta data-latex em delimitadores (\(...\) / $$...$$) antes do typeset e carregue a extensão mhchem para química.

Desenvolvimento

npm install
npm run dev        # playground (Vite) — inclui a aba "Modificador"
npm test           # vitest (93 testes)
npm run typecheck  # lib + playground + tests
npm run build      # dist/ (ESM + CJS + d.ts)

O playground abre na aba "Modificador": um painel interativo para ajustar variante, toolbar, aparência (cores e tamanhos) e features ao vivo e ver o editor reagir — útil para explorar toda a API sem escrever código.

A especificação completa (decisões, invariantes P1–P3 das fórmulas, roadmap headless) vive em SPEC.md.

Apoie o projeto

Se este editor te ajudou, considere apoiar o desenvolvimento: