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

erl-mathtextx-editor

v0.2.5

Published

Visual math editor component for solutest.id — CKEditor replacement with zero-LaTeX approach

Downloads

969

Readme

erl-mathtextx-editor

Visual Math Editor Component — Zero LaTeX Required

npm version License: MIT

Embeddable visual math editor widget untuk CMS dan platform edukasi. User tidak perlu tahu LaTeX — semua input matematika dilakukan secara visual.


✨ Fitur Utama

  • 🎯 Visual Math Input — Insert math langsung inline tanpa dialog (Ctrl+M)
  • 📝 Rich Text Editor — Bold, italic, tables, lists, links, images
  • 🧮 Equation Editor — Dialog MathType-style dengan tab, grid, KaTeX preview (Edit existing math)
  • 📋 100+ Formula Templates — Algebra, calculus, trigonometry, chemistry, matrix
  • 🏆 Mode Olimpiade — Toolbar khusus untuk kompetisi matematika dengan simbol set, Greek letters, NT/Combo
  • 🖼️ Free-form Image Drag — Drag gambar ke posisi bebas (pixel-perfect)
  • 📄 DOCX Import — Import file .docx via mammoth.js (toolbar + drag-drop)
  • 🗂️ Google Docs Paste — Paste dari Google Docs (equations + document) auto-cleaned
  • 👁️ Content Viewer — Read-only renderer dengan KaTeX + DOMPurify
  • 🎨 Table Editor — 6 templates, column resize, cell merge/split
  • 🔒 XSS Protection — DOMPurify sanitization di paste + serializer
  • 🛡️ Error Boundary — Anti white-screen crash protection

📦 Installation

npm install erl-mathtextx-editor

🚀 Quick Start

1. Basic Editor

import { MathTextXEditor } from 'erl-mathtextx-editor'
import 'erl-mathtextx-editor/styles'

function App() {
  return (
    <MathTextXEditor
      onChange={(html) => console.log(html)}
      placeholder="Tulis soal di sini..."
    />
  )
}

2. Editor dengan Save Handler

import { useRef } from 'react'
import { MathTextXEditor, getHTML } from 'erl-mathtextx-editor'
import 'erl-mathtextx-editor/styles'

function QuestionForm() {
  const editorRef = useRef<HTMLDivElement>(null)

  const handleSave = () => {
    if (!editorRef.current) return
    const html = getHTML(editorRef.current)
    fetch('/api/questions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ content: html }),
    })
  }

  return (
    <div>
      <MathTextXEditor ref={editorRef} placeholder="Tulis pertanyaan..." onSave={handleSave} />
      <button onClick={handleSave}>Simpan</button>
    </div>
  )
}

3. Edit Existing Content

import { MathTextXEditor } from 'erl-mathtextx-editor'
import 'erl-mathtextx-editor/styles'

function EditQuestion({ existingHtml }: { existingHtml: string }) {
  return (
    <MathTextXEditor
      content={existingHtml}
      onChange={(html) => console.log('Updated:', html)}
    />
  )
}

4. Read-Only Mode (Viewer)

import { ContentViewer } from 'erl-mathtextx-editor/viewer'
import 'erl-mathtextx-editor/viewer/styles'

function ExamQuestion({ questionHtml }: { questionHtml: string }) {
  return <ContentViewer content={questionHtml} />
}

5. Multi-Instance (Soal + Pilihan Jawaban)

import { useState } from 'react'
import { MathTextXEditor } from 'erl-mathtextx-editor'
import 'erl-mathtextx-editor/styles'

function MultipleChoiceForm() {
  const [question, setQuestion] = useState('')
  const [options, setOptions] = useState(
    ['A', 'B', 'C', 'D'].map((id) => ({ id, content: '' }))
  )

  return (
    <div>
      <label>Pertanyaan:</label>
      <MathTextXEditor
        content={question}
        onChange={setQuestion}
        placeholder="Tulis pertanyaan..."
        minHeight="150px"
      />
      {options.map((opt) => (
        <div key={opt.id}>
          <label>Opsi {opt.id}:</label>
          <MathTextXEditor
            content={opt.content}
            onChange={(html) => setOptions((prev) => prev.map((o) => o.id === opt.id ? { ...o, content: html } : o))}
            placeholder={`Jawaban ${opt.id}...`}
            minHeight="60px"
          />
        </div>
      ))}
    </div>
  )
}

6. Next.js (App Router)

'use client'
import dynamic from 'next/dynamic'
import 'erl-mathtextx-editor/styles'

const MathTextXEditor = dynamic(
  () => import('erl-mathtextx-editor').then((mod) => mod.MathTextXEditor),
  { ssr: false }
)

export default function EditorPage() {
  return (
    <MathTextXEditor
      placeholder="Tulis soal matematika..."
      onChange={(html) => console.log(html)}
      minHeight="300px"
    />
  )
}

7. Vite + React

import { MathTextXEditor } from 'erl-mathtextx-editor'
import 'erl-mathtextx-editor/styles'

function App() {
  return (
    <MathTextXEditor
      placeholder="Tulis soal..."
      onChange={(html) => console.log(html)}
    />
  )
}
export default App

🎛️ Toolbar Modes

Editor menyediakan 3 preset toolbar yang bisa dipilih via prop toolbarMode:

Basic

Toolbar standar dengan tombol format teks dasar, insert media, dan math formula.

Advanced

Toolbar lengkap dengan font family, text color, alignment, table operations, superscript/subscript, chemistry formula, dan formatting lanjutan.

Olimpiade

Toolbar minimal untuk kompetisi matematika — hanya tombol esensial:

| Grup | Tombol | |---|---| | Undo/Redo | Undo, Redo | | Format | Bold, Italic, Underline | | Structure | Paragraph, H1, H2 | | Lists | Bullet List, Ordered List, Outdent, Indent | | Math | Math Formula (inline), Block Math | | Actions | Remove Format |

Math Toolbar di mode Olimpiade menampilkan section khusus: Basic, Relation, Set, Greek, Structure — tanpa section Calc yang kurang relevan untuk olimpiade.

Custom Mode

Jika preset tidak sesuai, Anda bisa atur sendiri via internalToolbarMode state atau kontrol toolbar secara manual menggunakan komponen terpisah (MainToolbar, MathToolbar, MathTypeDialog).


🧰 Image Upload

Editor mendukung upload gambar dari: Insert dialog, drag-drop, paste dari clipboard, dan DOCX import. Semuanya melalui satu callback onImageUpload.

Basic Upload

import { MathTextXEditor } from 'erl-mathtextx-editor'
import 'erl-mathtextx-editor/styles'

function EditorWithUpload() {
  const handleImageUpload = async (file: File): Promise<string> => {
    const formData = new FormData()
    formData.append('image', file)
    const res = await fetch('/api/upload', { method: 'POST', body: formData })
    if (!res.ok) throw new Error('Upload failed: ' + res.statusText)
    const data = await res.json()
    return data.url // Expected: { "url": "https://cdn.example.com/img.jpg" }
  }

  return (
    <MathTextXEditor
      placeholder="Tulis soal..."
      onImageUpload={handleImageUpload}
    />
  )
}

Re-upload Gambar dari Paste (Google Docs / Website)

import { MathTextXEditor } from 'erl-mathtextx-editor'
import 'erl-mathtextx-editor/styles'

function EditorWithPasteReupload() {
  const handleImageUpload = async (file: File): Promise<string> => {
    const formData = new FormData()
    formData.append('image', file)
    const res = await fetch('/api/upload', { method: 'POST', body: formData })
    return (await res.json()).url
  }

  const handleBeforePasteHTML = async (html: string): Promise<string> => {
    // Download + re-upload external images from pasted HTML
    const imgRegex = /<img\s+[^>]*src="([^"]+)"[^>]*>/gi
    const replacements: Array<[string, string]> = []
    let match

    while ((match = imgRegex.exec(html)) !== null) {
      const src = match[1]
      if (src.startsWith('data:') || src.includes('cdn.example.com')) continue
      try {
        const blob = await (await fetch(src)).blob()
        const file = new File([blob], 'image.' + (blob.type.split('/')[1] || 'jpg'))
        replacements.push([src, await handleImageUpload(file)])
      } catch { console.warn('Skip image:', src) }
    }

    let result = html
    for (const [oldSrc, newSrc] of replacements) result = result.replaceAll(oldSrc, newSrc)
    return result
  }

  return (
    <MathTextXEditor
      placeholder="Tulis soal..."
      onImageUpload={handleImageUpload}
      onBeforePasteHTML={handleBeforePasteHTML}
    />
  )
}

Base64 Fallback (Tanpa Server)

const handleImageUpload = async (file: File): Promise<string> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => resolve(reader.result as string)
    reader.onerror = reject
    reader.readAsDataURL(file)
  })
}
// ⚠️ Base64 hanya cocok untuk gambar < 100KB. Untuk produksi, gunakan upload ke server.

📋 Props API

MathTextXEditor

| Prop | Type | Default | Description | |------|------|---------|-------------| | content | string | '' | Initial HTML content | | onChange | (html: string) => void | — | Callback on content change (debounced 150ms) | | onSave | (html: string) => void | — | Callback on Ctrl+S | | onImageUpload | (file: File) => Promise<string> | — | Custom image upload handler | | onBeforePasteHTML | (html: string) => Promise<string> | — | Transform pasted HTML (re-upload images) | | placeholder | string | 'Tulis soal...' | Placeholder text | | minHeight | string | '200px' | Minimum editor height | | maxHeight | string | — | Maximum editor height | | autoFocus | boolean | false | Auto-focus editor on mount | | toolbarMode | 'basic' \| 'advanced' \| 'olimpiade' | 'basic' | Toolbar preset | | onInsertBlockMath | () => void | — | Callback to insert a block-level math node directly | | editable | boolean | true | Set false for read-only mode | | className | string | — | Additional CSS class |

ContentViewer

| Prop | Type | Default | Description | |------|------|---------|-------------| | content | string | — | HTML content to render (required) | | className | string | — | Additional CSS class |


📦 Exports

Main Package (erl-mathtextx-editor)

import {
  MathTextXEditor,       // Main editor component
  ContentViewer,         // Read-only renderer
  MathTypeDialog,        // Standalone equation editor dialog
  TemplatePanel,         // Standalone formula template panel
  MainToolbar,           // Standalone text formatting toolbar
  MathToolbar,           // Standalone math symbols toolbar
  SymbolPalette,         // Standalone symbol picker
  WordCount,             // Status bar word/char counter
  LinkDialog,            // Standalone link insert/edit dialog
  ImageEditDialog,       // Standalone image edit dialog
  InsertTableDialog,     // Standalone table insert dialog
  TableMenu,             // Standalone table context menu
  CellPropertiesDialog,  // Standalone cell properties dialog
  TablePropertiesDialog, // Standalone table properties dialog
  TableTemplatesDialog,  // Standalone table template picker
  MathInlineNode,        // TipTap inline math extension
  MathBlockNode,         // TipTap block math extension
  getHTML,               // Serialize editor → HTML string
  getJSON,               // Serialize editor → JSON
  sanitizeCKEditorHTML,  // Clean CKEditor HTML for compatibility
  toCompatibleHTML,      // Convert to CKEditor-compatible format
  createExtensions,      // Create TipTap extensions programmatically
  mathTemplates,         // Template definitions
  getTemplatesByLevel,   // Filter templates by education level
  getTemplatesByCategory,// Filter templates by category
  getTemplateCategories, // Get all template categories
  countWords,            // Word count utility
  countCharacters,       // Character count utility
  getTemplateStyles,     // Table template CSS generator
} from 'erl-mathtextx-editor'

import 'erl-mathtextx-editor/styles'

Viewer Only (erl-mathtextx-editor/viewer)

import { ContentViewer } from 'erl-mathtextx-editor/viewer'
import 'erl-mathtextx-editor/viewer/styles'

⌨️ Keyboard Shortcuts

| Shortcut | Action | |----------|--------| | Ctrl+B | Bold | | Ctrl+I | Italic | | Ctrl+U | Underline | | Ctrl+K | Insert/edit link | | Ctrl+M | Insert inline math directly (without dialog) | | Ctrl+Shift+T | Insert table | | Ctrl+S | Save document | | Shift+Ctrl+V | Paste as plain text | | Esc (equation editor dialog) | Close dialog |


⚠️ Troubleshooting

| Error | Solution | |---|---| | Can't resolve 'erl-mathtextx-editor' | npm install erl-mathtextx-editor | | Can't resolve 'erl-mathtextx-editor/styles' | Version ≥ 0.1.3. Alternatif: import 'erl-mathtextx-editor/dist/assets/erl-mathtextx-editor.css' | | MathTextXEditor is not a function | Pastikan React ≥ 18 (npm ls react) | | window is not defined (Next.js) | Gunakan dynamic() dengan { ssr: false } | | Unexpected token 'export' (CRA) | Webpack config: resolve.mainFields: ['main', 'module'] | | MathLive fonts error | Set (window as any).MATHLIVE_FONTS_PATH = '/fonts' + copy font ke public/fonts/ |


✅ Verified Import Paths

| Import | Resolves to | |---|---| | erl-mathtextx-editor | dist/erl-mathtextx-editor.js | | erl-mathtextx-editor/styles | dist/assets/erl-mathtextx-editor.css | | erl-mathtextx-editor/viewer | dist/viewer.js | | erl-mathtextx-editor/viewer/styles | dist/viewer-styles.js |


🛠️ Tech Stack

  • UI Framework: React 18+
  • Editor Engine: TipTap / ProseMirror
  • Math Input: MathLive (WYSIWYG math)
  • Math Rendering: KaTeX
  • DOCX Import: mammoth.js
  • XSS Protection: DOMPurify
  • Graph Plotting: Function Plot
  • Syntax Highlight: lowlight (100+ languages)
  • Build: Vite (Library Mode)

📄 License

MIT © Erlangga Team


🔗 Links