
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@lax-wp/editor
Advanced tools
A modern, feature-rich WordPress-style editor built with React and TipTap. This editor provides a clean, intuitive interface for rich text editing with customizable toolbars and extensive formatting options.
useExport hook for programmatic export functionalityonEditorReady callback provides insertVariable method{{variableName}} placeholders{{variableName}} syntaxnpm install lax-wp-editor
# or
yarn add lax-wp-editor
# or
pnpm add lax-wp-editor
import React from 'react';
import { Editor } from 'lax-wp-editor'; // CSS is automatically included!
function App() {
return (
<div style={{ height: '100vh', width: '100%' }}>
<Editor />
</div>
);
}
export default App;
✨ New in v1.2.0: CSS is now automatically imported! You no longer need to manually import the styles.
For older versions (v1.1.x), you need to manually import styles:
import 'lax-wp-editor/styles'; // or import 'lax-wp-editor/dist/lax-wp-editor.css';
The editor accepts a config prop with the following options:
import { Editor, type EditorConfig } from 'lax-wp-editor';
function App() {
const config: EditorConfig = {
// Initial content (HTML string)
content: '<p>Your initial content here</p>',
// Toolbar type: 'professional' or 'classic'
defaultToolbar: 'professional',
// Enable/disable features
showBubbleMenu: true, // Show bubble menu on text selection
showFloatingMenu: false, // Show floating menu on empty lines
showPageSizeSelector: true, // Show page size selector
enablePagination: true, // Enable pagination
// Content change callback with debounce
debounceTimeForContentChange: 300, // Debounce time in milliseconds (default: 300ms)
onContentChange: (editor) => {
const html = editor.getHTML();
console.log('Content changed:', html);
},
};
return (
<div style={{ height: '100vh', width: '100%' }}>
<Editor config={config} />
</div>
);
}
You can enable AI autocompletion by providing your own completion function:
import { Editor, type EditorConfig } from 'lax-wp-editor';
function App() {
const config: EditorConfig = {
aiAutocompletion: {
enabled: true,
minWordsToTriggerAutoCompletion: 5, // Trigger after 5 words (default: 3)
debounceTime: 300, // Wait 300ms before calling API (default: 100ms)
// Required: Provide your custom fetch function
fetchCompletion: async (text: string) => {
const response = await fetch('https://your-api.com/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({ prompt: text }),
});
const data = await response.json();
return data.completion; // Return the completion text
},
},
};
return (
<div style={{ height: '100vh', width: '100%' }}>
<Editor config={config} />
</div>
);
}
⚠️ Important: AI autocompletion requires you to provide a
fetchCompletionfunction. The editor does not include a default AI service.
Keyboard Shortcuts for AI Autocompletion:
Tab - Accept suggestionEscape - Dismiss suggestionCtrl+Shift+Space (Windows/Linux) or Cmd+Shift+Space (Mac) - Toggle autocompletion on/offThe editor includes a comprehensive export system that allows users to download their content in multiple formats:
import { Editor, useExport } from 'lax-wp-editor';
function MyEditorComponent({ editor }) {
const {
downloadTextFile,
downloadJsonFile,
downloadMarkdownFile,
downloadHtmlFile
} = useExport(editor);
return (
<div>
<button onClick={downloadTextFile}>Export as Text</button>
<button onClick={downloadJsonFile}>Export as JSON</button>
<button onClick={downloadMarkdownFile}>Export as Markdown</button>
<button onClick={downloadHtmlFile}>Export as HTML</button>
</div>
);
}
Available Export Formats:
| Format | Description | Method |
|---|---|---|
| Text | Plain text (.txt) | downloadTextFile() |
| JSON | TipTap JSON format (.json) | downloadJsonFile() |
| Markdown | Markdown format (.md) | downloadMarkdownFile() |
| HTML | HTML format (.html) | downloadHtmlFile() |
💡 Tip: The Export tab is built into the toolbar and provides quick access to all export formats. You can also use the
useExporthook to create custom export buttons.
The editor supports dynamic variable text that can be inserted programmatically from your application. Variables are displayed as placeholders that get replaced with actual values.
Enable Variable Text:
import { useState } from 'react';
import { Editor } from 'lax-wp-editor';
function App() {
const [insertVariable, setInsertVariable] = useState<((key: string, value?: string) => void) | null>(null);
const handleInsertName = () => {
insertVariable?.('userName');
};
const handleInsertEmailWithValue = () => {
insertVariable?.('email', 'newemail@example.com');
};
return (
<div>
<button onClick={handleInsertName}>Insert User Name</button>
<button onClick={handleInsertEmailWithValue}>Insert Email</button>
<Editor
config={{
enableVariableText: true,
variableValues: {
userName: 'John Doe',
email: 'john@example.com',
company: 'Acme Inc',
},
onEditorReady: ({ insertVariable: insert }) => {
// Store the insertVariable method when editor is ready
setInsertVariable(() => insert);
},
}}
/>
</div>
);
}
Variable Text Configuration:
| Option | Type | Description |
|---|---|---|
enableVariableText | boolean | Enable/disable variable text feature |
variableValues | Record<string, string> | Key-value pairs for variable replacements |
onEditorReady | (methods) => void | Callback that provides editor methods including insertVariable |
Insert Variable Method:
insertVariable(key: string, value?: string)
key: The variable name/keyvalue: (Optional) Update the variable value before insertingHow Variables Work:
variableValues config{{variableName}}{{variableName}} syntax (if enabled)Example Use Cases:
| Option | Type | Default | Description |
|---|---|---|---|
content | string | "" | Initial HTML content for the editor |
defaultToolbar | 'professional' | 'classic' | 'professional' | Toolbar style |
showBubbleMenu | boolean | true | Show bubble menu on text selection |
showFloatingMenu | boolean | false | Show floating menu on empty lines |
showPageSizeSelector | boolean | true | Show page size selector |
enablePagination | boolean | true | Enable pagination |
debounceTimeForContentChange | number | 300 | Debounce time (ms) for onContentChange callback |
onContentChange | (editor: Editor) => void | undefined | Callback when content changes (debounced) |
enableVariableText | boolean | false | Enable variable text feature |
variableValues | Record<string, string> | {} | Variable name to value mappings |
onEditorReady | (methods) => void | undefined | Callback with editor methods when ready |
onShare | () => void | undefined | Callback when share button is clicked |
aiAutocompletion | AIAutocompletionConfig | See below | AI autocompletion configuration |
AIAutocompletionConfig:
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable/disable AI autocompletion |
minWordsToTriggerAutoCompletion | number | 3 | Minimum words to trigger autocompletion |
debounceTime | number | 100 | Debounce time (ms) before calling API |
fetchCompletion | (text: string) => Promise<string> | undefined | Required - Custom fetch function for AI completions |
If you encounter the localsInner error, it's caused by multiple versions of ProseMirror packages in your application's dependency tree.
This package uses @tiptap/pm for all ProseMirror functionality (recommended by Tiptap).
Solution: Add this to YOUR CONSUMER APPLICATION's package.json to force all ProseMirror packages to use the same versions:
For npm users (v8.3.0+):
{
"overrides": {
"prosemirror-commands": "$prosemirror-commands",
"prosemirror-dropcursor": "$prosemirror-dropcursor",
"prosemirror-gapcursor": "$prosemirror-gapcursor",
"prosemirror-history": "$prosemirror-history",
"prosemirror-inputrules": "$prosemirror-inputrules",
"prosemirror-keymap": "$prosemirror-keymap",
"prosemirror-model": "$prosemirror-model",
"prosemirror-schema-list": "$prosemirror-schema-list",
"prosemirror-state": "$prosemirror-state",
"prosemirror-tables": "$prosemirror-tables",
"prosemirror-transform": "$prosemirror-transform",
"prosemirror-view": "$prosemirror-view"
}
}
For Yarn users:
{
"resolutions": {
"prosemirror-commands": "^1.x",
"prosemirror-dropcursor": "^1.x",
"prosemirror-gapcursor": "^1.x",
"prosemirror-history": "^1.x",
"prosemirror-inputrules": "^1.x",
"prosemirror-keymap": "^1.x",
"prosemirror-model": "^1.x",
"prosemirror-schema-list": "^1.x",
"prosemirror-state": "^1.x",
"prosemirror-tables": "^1.x",
"prosemirror-transform": "^1.x",
"prosemirror-view": "^1.x"
}
}
For pnpm users:
{
"pnpm": {
"overrides": {
"prosemirror-commands": "^1.x",
"prosemirror-dropcursor": "^1.x",
"prosemirror-gapcursor": "^1.x",
"prosemirror-history": "^1.x",
"prosemirror-inputrules": "^1.x",
"prosemirror-keymap": "^1.x",
"prosemirror-model": "^1.x",
"prosemirror-schema-list": "^1.x",
"prosemirror-state": "^1.x",
"prosemirror-tables": "^1.x",
"prosemirror-transform": "^1.x",
"prosemirror-view": "^1.x"
}
}
}
3. Clean install in YOUR APPLICATION:
# In your application directory (not lax-wp-editor)
rm -rf node_modules package-lock.json # or yarn.lock / pnpm-lock.yaml
npm install # or yarn / pnpm install
4. Verify all packages are deduped:
npm ls prosemirror-view
# All instances should show "deduped" and point to the same version
Note: This package uses
@tiptap/pminternally (not direct ProseMirror imports) to prevent version conflicts. The deduplication step is only needed if other packages in your project import ProseMirror directly.
import { Editor, ToolbarProvider } from 'lax-wp-editor';
import 'lax-wp-editor/styles';
function MyEditor() {
return (
<ToolbarProvider>
<Editor />
</ToolbarProvider>
);
}
import { Editor, ToolbarProvider, defaultEditorConfig } from 'lax-wp-editor';
import 'lax-wp-editor/styles';
function MyEditor() {
const config = {
...defaultEditorConfig,
// Add your custom configuration
};
return (
<ToolbarProvider>
<Editor config={config} />
</ToolbarProvider>
);
}
The editor supports three toolbar modes:
import { Editor, ToolbarProvider, TOOLBAR_TYPES_ENUM } from 'lax-wp-editor';
import 'lax-wp-editor/styles';
function MyEditor() {
return (
<ToolbarProvider initialToolbar={TOOLBAR_TYPES_ENUM.CLASSIC}>
<Editor />
</ToolbarProvider>
);
}
Users can switch between toolbar modes using the built-in toolbar dropdown menu, which includes icons for each mode:
Editor: The main editor componentToolbarProvider: Context provider for toolbar stateToolbar: Main toolbar component with tabbed interfaceProfessionalToolbar: Professional-style toolbar (multi-row layout)ClassicToolbar: Classic-style toolbar (single-row layout)ToolbarDropdown: Dropdown to switch between toolbar modesThe editor includes a tabbed toolbar interface with the following tabs:
Home: Basic formatting options (bold, italic, underline, alignment, etc.)Insert: Insert elements (tables, images, dividers, etc.)Table: Table-specific operations (add/remove rows/columns, merge cells, etc.)Page: Page settings (size, orientation, margins, background)Export: Export content in multiple formats (Text, JSON, Markdown, HTML)HeadingOptions: Heading selection dropdownHomeOptions: Basic formatting optionsFontStyleOptions: Font styling optionsParagraphStyleOption: Paragraph stylingExportOptions: Export functionality for multiple file formatsTableOptions: Table manipulation controlsInsertOptions: Insert various elements into the documentSvgIcon: SVG icon componentPageSizeSelector: Page size and orientation selectorButton: Base button componentColorPicker: Color picker for text and background colorsuseTiptapEditorState: Access editor stateuseHeadingStyleMethods: Heading manipulation methodsuseFontStyleMethods: Font styling methodsuseHomeOptionMethods: Basic formatting methodsuseParagraphStyleMethods: Paragraph styling methodsusePageMethods: Page size and layout managementuseExport: Export content in multiple formats (Text, JSON, Markdown, HTML)useTableMethods: Table manipulation methodsuseLinks: Link management methodsusePresentationMode: Presentation mode controlsuseZoom: Zoom controls for the editorimport { EditorConfig } from 'lax-wp-editor';
const config: EditorConfig = {
// Your configuration options
};
import { TOOLBAR_TYPES_ENUM } from 'lax-wp-editor';
// Available toolbar types:
TOOLBAR_TYPES_ENUM.PROFESSIONAL // Professional toolbar (multi-row, all options visible)
TOOLBAR_TYPES_ENUM.CLASSIC // Classic toolbar (single-row, compact)
TOOLBAR_TYPES_ENUM.HIDE_TOOLBAR // Hide toolbar completely
The toolbar includes 5 tabs:
The package includes default styles that you can import:
import 'lax-wp-editor/styles';
You can also customize the styles by overriding CSS variables or using your own CSS.
The package includes full TypeScript definitions:
import { Editor, EditorConfig, PageSize } from 'lax-wp-editor';
| Prop | Type | Default | Description |
|---|---|---|---|
config | EditorConfig | defaultEditorConfig | Editor configuration |
onUpdate | (content: string) => void | - | Callback when content changes |
initialContent | string | - | Initial editor content |
| Prop | Type | Default | Description |
|---|---|---|---|
initialToolbar | ToolbarType | PROFESSIONAL | Initial toolbar type |
children | ReactNode | - | Child components |
| Prop | Type | Default | Description |
|---|---|---|---|
onToolbarChange | (toolbarType: string) => void | - | Callback when toolbar type changes |
Returns an object with the following methods:
| Method | Description |
|---|---|
downloadTextFile() | Downloads content as plain text (.txt) |
downloadJsonFile() | Downloads content as TipTap JSON format (.json) |
downloadMarkdownFile() | Downloads content as Markdown (.md) |
downloadHtmlFile() | Downloads content as HTML (.html) |
import { Editor, Toolbar, HeadingOptions, HomeOptions } from 'lax-wp-editor';
function CustomEditor() {
return (
<div>
<Toolbar>
<HeadingOptions />
<HomeOptions />
</Toolbar>
<Editor />
</div>
);
}
import { Editor, PageSizeSelector, usePageMethods } from 'lax-wp-editor';
function EditorWithPageSize() {
const { pageSize, setPageSize } = usePageMethods();
return (
<div>
<PageSizeSelector />
<Editor />
</div>
);
}
import { Editor, useExport } from 'lax-wp-editor';
function EditorWithExport() {
const editorRef = useRef(null);
const {
downloadTextFile,
downloadJsonFile,
downloadMarkdownFile,
downloadHtmlFile
} = useExport(editorRef.current);
return (
<div>
<div className="export-buttons">
<button onClick={downloadTextFile}>Download as Text</button>
<button onClick={downloadJsonFile}>Download as JSON</button>
<button onClick={downloadMarkdownFile}>Download as Markdown</button>
<button onClick={downloadHtmlFile}>Download as HTML</button>
</div>
<Editor ref={editorRef} />
</div>
);
}
import { Editor, ToolbarProvider, ToolbarDropdown } from 'lax-wp-editor';
function EditorWithToolbarSwitch() {
return (
<ToolbarProvider>
<ToolbarDropdown onToolbarChange={(type) => console.log('Toolbar changed to:', type)} />
<Editor />
</ToolbarProvider>
);
}
npm run build
npm run lint
wysiwyg, editor, react, tiptap, wordpress, rich-text, text-editor, markdown, html-editor, document-editor, export, typescript
Contributions are welcome! Here's how you can help:
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)Please make sure to:
MIT © Rifat Hasan Shaun
Rifat Hasan Shaun
If you have any questions or need help, please:
FAQs
A modern, feature-rich editor built with React and TipTap
We found that @lax-wp/editor demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.