Socket
Book a DemoInstallSign in
Socket

docx-diff-editor

Package Overview
Dependencies
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

docx-diff-editor

React component for DOCX document comparison with track changes visualization

latest
Source
npmnpm
Version
1.0.62
Version published
Maintainers
1
Created
Source

docx-diff-editor

A React component for DOCX document comparison with track changes visualization. Built on top of SuperDoc.

Features

  • 📄 Compare two DOCX documents side by side
  • 🔍 Character-level diff with track changes
  • 📊 Block-level structural diffing for tables, lists, paragraphs, and images
  • 🔄 Structure-aware merge - inserted/deleted blocks appear in the editor with track marks
  • ✅ Accept/reject individual changes (both text and structural)
  • 🎨 Visual track changes (insert, delete, format)
  • 📋 Structural Changes Pane for table rows, list items, images
  • 🤖 Extract enriched change context for LLM processing
  • 📤 Export merged document to DOCX

Installation

npm install docx-diff-editor

Peer Dependencies

This package requires React to be installed in your project:

# If you don't have React already
npm install react react-dom

Note: React must be provided by your project (not bundled) to avoid duplicate React instances which cause hooks and context to break. SuperDoc is bundled with this package - you don't need to install it separately.

Quick Start

import { useRef } from 'react';
import { DocxDiffEditor, DocxDiffEditorRef } from 'docx-diff-editor';
import 'docx-diff-editor/styles.css';

function App() {
  const editorRef = useRef<DocxDiffEditorRef>(null);

  const handleCompare = async () => {
    // Set the source document (can be File, HTML, or JSON)
    await editorRef.current?.setSource('<h1>Original</h1><p>Hello world</p>');

    // Compare with a new version
    const result = await editorRef.current?.compareWith(
      '<h1>Original</h1><p>Hello universe</p>'
    );

    console.log(`Found ${result.totalChanges} changes`);

    // Note: Subsequent compareWith calls compare against current editor state
    // (with track changes accepted), not the original source.
    // To compare against original again, call setSource() first.
  };

  return (
    <div style={{ height: '600px' }}>
      <button onClick={handleCompare}>Compare Documents</button>
      <DocxDiffEditor
        ref={editorRef}
        showToolbar
        showRulers
        onReady={() => console.log('Editor ready!')}
      />
    </div>
  );
}

Content Formats

The component accepts three content formats:

FormatTypeExample
FileFileDOCX file from <input type="file">
HTMLstring'<h1>Title</h1><p>Content</p>'
JSONProseMirrorJSON{ type: 'doc', content: [...] }
// From File
await editor.setSource(fileInput.files[0]);

// From HTML
await editor.setSource('<h1>Hello</h1><p>World</p>');

// From JSON
await editor.setSource({ type: 'doc', content: [...] });

API Reference

Props

PropTypeDefaultDescription
initialSourceDocxContent-Initial document to load
templateDocxFile-Template DOCX for styles (when using HTML/JSON)
showToolbarbooleantrueShow the editor toolbar
showRulersbooleanfalseShow document rulers
author{ name, email }-Author info for track changes
onReady() => void-Called when editor is ready
onSourceLoaded(json) => void-Called when source is loaded
onComparisonComplete(result) => void-Called after comparison
onError(error: EditorError) => void-Called on errors (see Error Handling)
classNamestring-Container class
toolbarClassNamestring-Toolbar container class
editorClassNamestring-Editor container class
structuralPanePositionStructuralPanePosition'bottom-right'Position of structural changes pane
structuralPaneCollapsedbooleanfalseStart with pane collapsed
hideStructuralPanebooleanfalseHide structural changes pane entirely

Ref Methods

interface DocxDiffEditorRef {
  // Set the source/base document
  // Returns void on success, SetSourceError on failure (editor preserved)
  setSource(content: DocxContent): Promise<void | SetSourceError>;

  // Compare current editor content with new content, show track changes
  // Note: Compares against current editor state (not original source)
  // Returns ComparisonResult on success, ComparisonError on failure (editor preserved)
  compareWith(content: DocxContent): Promise<CompareWithResult>;

  // Get diff data
  getDiffSegments(): DiffSegment[];

  // Get enriched context for LLM
  getEnrichedChangesContext(): EnrichedChange[];

  // Get document content
  getContent(): ProseMirrorJSON;
  getSourceContent(): ProseMirrorJSON | null;

  // Export to DOCX
  exportDocx(): Promise<Blob>;

  // Reset comparison
  resetComparison(): void;

  // Check if ready
  isReady(): boolean;

  // Get current page count
  getPages(): number;

  // Get document metadata and statistics
  getDocumentInfo(): DocumentInfo | null;

  // Get document core properties
  getProperties(): Promise<DocumentProperties | null>;

  // Set document core properties (partial update)
  setProperties(properties: Partial<DocumentProperties>): Promise<boolean>;

  // Parse HTML to ProseMirror JSON
  parseHtml(html: string): Promise<ProseMirrorJSON>;
}

ComparisonResult

interface ComparisonResult {
  success: true;                      // Indicates successful comparison
  totalChanges: number;
  insertions: number;
  deletions: number;
  formatChanges: number;
  structuralChanges: number;
  summary: string[];
  mergedJson: ProseMirrorJSON;
  structuralChangeInfos: StructuralChangeInfo[];
  usedFallback?: boolean;             // True if track visualization unavailable
}

Error Handling

The component provides graceful error handling that preserves the editor state on recoverable failures.

Error Types

interface EditorError {
  error: Error;           // The underlying error
  type: 'fatal' | 'operation';  // Fatal = editor unusable, Operation = editor still works
  operation?: 'setSource' | 'compareWith' | 'parseHtml' | 'export' | 'init';
  recoverable: boolean;   // True if editor is still functional
  message: string;        // Human-readable message
  phase?: 'parsing' | 'diffing' | 'merging' | 'applying';  // For compareWith
}

Result-Based Error Handling

Both setSource() and compareWith() return result objects instead of throwing:

// compareWith returns a union type
const result = await editorRef.current?.compareWith(newContent);

if (!result.success) {
  // Editor is still intact! Show a friendly message
  showModal({
    title: 'Comparison Failed',
    message: result.message,
    phase: result.phase,  // 'parsing', 'diffing', 'merging', or 'applying'
  });
  return;
}

// Success - access the usual fields
console.log(`Found ${result.totalChanges} changes`);

Using the onError Callback

<DocxDiffEditor
  ref={editorRef}
  onError={(errorInfo) => {
    if (errorInfo.type === 'fatal') {
      // Editor is unusable - show full error page or reinitialize
      console.error('Fatal editor error:', errorInfo.error);
    } else {
      // Editor still works - just show a notification
      toast.error(`${errorInfo.operation} failed: ${errorInfo.message}`);
    }
  }}
/>

Recovery Behavior

When operations fail, the component attempts a multi-tier recovery:

  • Primary: Apply the requested change
  • Fallback: Try a simpler version (e.g., without track marks)
  • Rollback: Restore the previous editor state
  • Fatal: Only if all recovery attempts fail, show the error overlay

This ensures users don't lose their work due to invalid comparison content or parsing errors.

DocumentInfo

interface DocumentInfo {
  // Metadata
  documentGuid: string | null;
  isModified: boolean;
  version: number | null;
  // Statistics
  words: number;
  characters: number;
  paragraphs: number;
  pages: number;
}

DocumentProperties

interface DocumentProperties {
  title?: string;
  author?: string;
  subject?: string;
  description?: string;
  keywords?: string;
  category?: string;
  lastModifiedBy?: string;
  revision?: string;
  created?: Date;
  modified?: Date;
}

Getting LLM Context

Extract enriched changes with semantic context for AI/LLM processing:

const context = editorRef.current?.getEnrichedChangesContext();

// Example: Send to your LLM API
await fetch('/api/summarize', {
  method: 'POST',
  body: JSON.stringify({ changes: context }),
});

// Returns array of EnrichedChange:
// {
//   type: 'replacement',
//   oldText: 'world',
//   newText: 'universe',
//   location: {
//     nodeType: 'paragraph',
//     sectionTitle: 'Introduction',
//     description: '"Introduction" section'
//   },
//   surroundingText: 'Hello world, welcome to...'
// }

Document Properties

Read and update document metadata (stored in docProps/core.xml):

// Get current properties
const props = await editorRef.current?.getProperties();
if (props) {
  console.log(`Title: ${props.title}`);
  console.log(`Author: ${props.author}`);
  console.log(`Created: ${props.created?.toLocaleDateString()}`);
  console.log(`Modified: ${props.modified?.toLocaleDateString()}`);
}

// Update properties (partial update - only specified fields are changed)
await editorRef.current?.setProperties({
  title: 'Quarterly Report Q4 2026',
  author: 'Jane Smith',
  subject: 'Financial Summary',
  keywords: 'report, quarterly, finance, 2026',
  modified: new Date(),
});

Parsing HTML to JSON

Convert HTML strings to ProseMirror JSON without visible rendering. Inline styles are preserved!

// Using the ref method (requires editor to be initialized)
const json = await editorRef.current?.parseHtml('<h1>Title</h1><p>Content here</p>');
console.log(json); // { type: 'doc', content: [...] }

// Lists work correctly - numbering definitions are synced to the main document
const listJson = await editorRef.current?.parseHtml(
  '<ul><li>Item 1</li><li>Item 2</li></ul>'
);

// Inline styles are converted to marks
const styledJson = await editorRef.current?.parseHtml(
  '<p><span style="color: red; font-weight: bold;">styled text</span></p>'
);
// Result: text with textStyle mark (color) and bold mark

// Use with other methods
await editorRef.current?.updateContent(json);

// Or use the standalone function (requires SuperDoc class)
import { parseHtmlToJson } from 'docx-diff-editor';
const json = await parseHtmlToJson(htmlString, SuperDocClass);

Linked Parsing for Lists

When the main editor is ready, parseHtml() automatically uses a linked child editor approach. This ensures that list numbering definitions (for <ol> and <ul> elements) are synced to the main document's numbering store.

This prevents crashes when parsed content with lists is later spliced into the main document and rendered via compareWith().

If the main editor isn't ready yet, the method falls back to an isolated SuperDoc instance.

Supported Inline Styles

CSS PropertyProseMirror Mark
colortextStyle.color
font-sizetextStyle.fontSize
font-familytextStyle.fontFamily
font-weight: boldbold
font-style: italicitalic
text-decoration: underlineunderline
text-decoration: line-throughstrike
background-colorhighlight.color

Customization

CSS Variables

Override CSS variables to customize colors:

:root {
  --dde-primary-color: #6366F1;
  --dde-insert-color: #22C55E;
  --dde-delete-color: #F43F5E;
  --dde-format-color: #F59E0B;
}

Available Variables

VariableDefaultDescription
--dde-primary-color#007ACCPrimary/accent color
--dde-insert-color#10B981Insertion highlight color
--dde-delete-color#EF4444Deletion highlight color
--dde-format-color#F59E0BFormat change highlight
--dde-text-color#374151Main text color
--dde-bg-color#FFFFFFBackground color
--dde-border-color#E5E7EBBorder color

Track Changes

The component supports three types of track changes:

TypeVisualDescription
InsertGreen underlineNew text added
DeleteRed strikethroughText removed
FormatGold highlightFormatting changed

Structural Changes Pane

When comparing documents with structural differences (tables, lists, images), a floating pane appears showing these changes with Accept/Reject controls.

How It Works

The component uses a structure-aware merge approach:

  • Block alignment: Documents are aligned at the block level (paragraphs, tables, lists) using content fingerprinting
  • Recursive merge: Tables and lists are merged recursively (row-by-row, item-by-item)
  • Character-level diff: Within matched blocks, character-level diffing is applied
  • Insert/delete marking: New blocks get trackInsert marks; deleted blocks are preserved with trackDelete marks
  • Shared IDs: Each structural change has a unique ID linking the track marks to the pane entry

This means inserted tables, paragraphs, and list items actually appear in the editor (with green highlighting), and deleted content remains visible (with red strikethrough) until you accept or reject the changes.

What's Detected

Change TypeDescription
Table RowsInserted or deleted rows
Table ColumnsAdded or removed columns
List ItemsNew or removed list items (including nested)
ParagraphsEntire paragraphs added or deleted
ImagesNew or removed images

Pane Features

  • Floating Position: Configurable position (top-right, bottom-right, top-left, bottom-left)
  • Collapsible: Click header to minimize to just the title bar
  • Accept/Reject: Per-change or bulk actions
  • Counter Badge: Shows remaining changes
  • Auto-Hide: Disappears when all changes are resolved
  • Bubble Sync: Stays in sync when changes are accepted via SuperDoc's bubbles

Configuration

<DocxDiffEditor
  ref={editorRef}
  structuralPanePosition="bottom-right"  // Position of the pane
  structuralPaneCollapsed={false}         // Start expanded
  hideStructuralPane={false}              // Show the pane
/>

Accessing Structural Changes Programmatically

const result = await editorRef.current?.compareWith(newDocument);

// Get structural change count
console.log(`${result.structuralChanges} structural changes detected`);

// Access detailed info
result.structuralChangeInfos.forEach(change => {
  console.log(`${change.type}: ${change.location} - ${change.preview}`);
});

License

Apache 2.0

Keywords

docx

FAQs

Package last updated on 15 Jan 2026

Did you know?

Socket

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.

Install

Related posts