react-monaco-json-merge 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,333 @@
1
+ # React Monaco JSON Merge
2
+
3
+ <div align="center">
4
+
5
+ **A powerful React component for 3-way JSON merging with semantic comparison, built on Monaco Editor**
6
+
7
+ [![React](https://img.shields.io/badge/React-19.2.0-61DAFB?logo=react)](https://react.dev/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7.3-3178C6?logo=typescript)](https://www.typescriptlang.org/)
9
+ [![Vite](https://img.shields.io/badge/Vite-6.4.1-646CFF?logo=vite)](https://vite.dev/)
10
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
11
+
12
+ **[๐ŸŒ Live Demo](https://ivkirill.github.io/react-monaco-json-merge/)**
13
+
14
+ </div>
15
+
16
+ ## โœจ Features
17
+
18
+ ### Core Capabilities
19
+
20
+ - **๐Ÿ”€ 3-Way Merge** - Compare base, theirs, and ours versions side-by-side
21
+ - **๐ŸŽจ Monaco Editor Integration** - Powered by VS Code's Monaco Editor for a familiar editing experience
22
+ - **๐Ÿง  Semantic JSON Comparison** - Uses JSON Patch (RFC 6902) for intelligent, structure-aware diffing
23
+ - **๐Ÿ“‹ Schema-Aware** - Optional JSON Schema support for enhanced conflict detection and validation
24
+ - **โœ… Interactive Resolution** - Checkboxes for accepting/rejecting changes
25
+ - **๐Ÿ”€ Smart Merging** - Automatically merges compatible conflicts
26
+ - **โšก Real-time Validation** - JSON validation with error highlighting
27
+ - **๐ŸŽฏ 4-Column Mode** - Optional result preview column for live merge preview
28
+ - **๐ŸŽจ Theme Support** - Light and dark themes (VS Code/monaco themes)
29
+ - **โ™ฟ Accessible** - Keyboard navigation and screen reader support
30
+
31
+ ### Technical Highlights
32
+
33
+ - **Zero Line-based Diffs** - Uses semantic JSON comparison, ignoring formatting changes
34
+ - **Array Matching by ID** - Schema-aware array item matching (not just by index)
35
+ - **Conflict Type Detection** - Identifies SAME_CHANGE, INPUT1_ONLY, INPUT2_ONLY, and TRUE_CONFLICT
36
+ - **Deep Merge Support** - Automatically merges nested objects when possible
37
+ - **TypeScript First** - Fully typed with comprehensive type definitions
38
+
39
+ ## ๐Ÿš€ Quick Start
40
+
41
+ > **๐Ÿ“บ [Try the Live Demo](https://ivkirill.github.io/react-monaco-json-merge/)** - See the editor in action!
42
+
43
+ ### Installation
44
+
45
+ ```bash
46
+ npm install react-monaco-json-merge
47
+ ```
48
+
49
+ ### Basic Usage
50
+
51
+ ```tsx
52
+ import { JsonDiffMergeEditor } from 'react-monaco-json-merge';
53
+ import 'react-monaco-json-merge/dist/style.css';
54
+
55
+ function App() {
56
+ const base = JSON.stringify({ name: "John", age: 30 }, null, 2);
57
+ const theirs = JSON.stringify({ name: "John", age: 31, city: "NYC" }, null, 2);
58
+ const ours = JSON.stringify({ name: "Jane", age: 30 }, null, 2);
59
+
60
+ return (
61
+ <JsonDiffMergeEditor
62
+ base={base}
63
+ original={theirs}
64
+ modified={ours}
65
+ showResultColumn={true}
66
+ height="600px"
67
+ onMergeResolve={(content, resolution) => {
68
+ if (resolution?.isValid) {
69
+ console.log('Merged JSON:', JSON.parse(content));
70
+ }
71
+ }}
72
+ />
73
+ );
74
+ }
75
+ ```
76
+
77
+ ## ๐Ÿ“– Documentation
78
+
79
+ ### Props
80
+
81
+ | Prop | Type | Default | Description |
82
+ |------|------|---------|-------------|
83
+ | `base` | `string` | `""` | Common ancestor JSON (stringified) |
84
+ | `original` | `string` | `""` | "Theirs" version JSON (stringified) |
85
+ | `modified` | `string` | `""` | "Ours" version JSON (stringified) |
86
+ | `showResultColumn` | `boolean` | `false` | Show merged result preview column |
87
+ | `theme` | `string` | `"vs"` | Monaco Editor theme (`"vs"`, `"vs-dark"`, `"hc-black"`) |
88
+ | `height` | `string \| number` | `"100%"` | Editor height |
89
+ | `width` | `string \| number` | `"100%"` | Editor width |
90
+ | `schema` | `JSONSchema` | `undefined` | JSON Schema for validation and array matching |
91
+ | `labels` | `object` | `undefined` | Custom column labels |
92
+ | `onMergeResolve` | `function` | `undefined` | Callback when merge resolution changes |
93
+ | `options` | `object` | `{}` | Monaco Editor options |
94
+ | `comparisonMode` | `"split" \| "sequential"` | `"split"` | How to display the comparison |
95
+ | `baseIndex` | `0 \| 1 \| 2` | `1` | Position of base column (0=left, 1=middle, 2=right) |
96
+
97
+ ### Advanced Example
98
+
99
+ ```tsx
100
+ import { JsonDiffMergeEditor } from 'react-monaco-json-merge';
101
+ import type { JSONSchema } from 'react-monaco-json-merge';
102
+
103
+ const schema: JSONSchema = {
104
+ type: "object",
105
+ properties: {
106
+ users: {
107
+ type: "array",
108
+ items: {
109
+ type: "object",
110
+ properties: {
111
+ id: { type: "string" },
112
+ name: { type: "string" }
113
+ },
114
+ required: ["id"]
115
+ }
116
+ }
117
+ }
118
+ };
119
+
120
+ function AdvancedEditor() {
121
+ return (
122
+ <JsonDiffMergeEditor
123
+ base={baseJSON}
124
+ original={theirsJSON}
125
+ modified={oursJSON}
126
+ schema={schema}
127
+ showResultColumn={true}
128
+ theme="vs-dark"
129
+ height="700px"
130
+ labels={{
131
+ input1: "Remote Changes",
132
+ base: "Common Ancestor",
133
+ input2: "Local Changes",
134
+ result: "Merged Result"
135
+ }}
136
+ onMergeResolve={(content, resolution) => {
137
+ if (resolution?.isValid) {
138
+ // Save merged content
139
+ saveToFile(content);
140
+ } else {
141
+ // Handle validation errors
142
+ console.error('Merge has conflicts:', resolution?.conflictIssues);
143
+ }
144
+ }}
145
+ options={{
146
+ fontSize: 14,
147
+ minimap: { enabled: false },
148
+ lineNumbers: 'on'
149
+ }}
150
+ />
151
+ );
152
+ }
153
+ ```
154
+
155
+ ## ๐Ÿ—๏ธ Architecture
156
+
157
+ ### Semantic vs Line-Based Diff
158
+
159
+ Unlike traditional diff tools that compare text line-by-line, this editor uses **semantic JSON comparison**:
160
+
161
+ 1. **Parses JSON** to structured objects
162
+ 2. **Generates JSON Patch** operations (RFC 6902)
163
+ 3. **Groups patches** by JSON path
164
+ 4. **Maps to line numbers** using jsonc-parser
165
+ 5. **Applies Monaco decorations** based on JSON structure
166
+
167
+ **Benefits:**
168
+ - Ignores formatting changes (whitespace, key order)
169
+ - Schema-aware array matching (by ID, not index)
170
+ - Better conflict detection for nested JSON
171
+ - Highlights actual value changes, not formatting
172
+
173
+ ### Conflict Types
174
+
175
+ The editor identifies four conflict types:
176
+
177
+ - **`SAME_CHANGE`** - Both sides made identical changes (auto-merged)
178
+ - **`INPUT1_ONLY`** - Only "theirs" changed (can be accepted)
179
+ - **`INPUT2_ONLY`** - Only "ours" changed (can be accepted)
180
+ - **`TRUE_CONFLICT`** - Both sides changed to different values (requires resolution)
181
+
182
+ ### Smart Merging
183
+
184
+ When both checkboxes are selected for a `TRUE_CONFLICT`:
185
+ - If both values are objects: **deep merge** is performed
186
+ - If values are identical: uses either value
187
+ - If incompatible types: merge fails, shows warning
188
+
189
+ ## ๐Ÿ› ๏ธ Development
190
+
191
+ ### Prerequisites
192
+
193
+ - **Node.js** >= 20.19.0
194
+ - **npm** >= 10.0.0
195
+
196
+ ### Setup
197
+
198
+ ```bash
199
+ # Clone the repository
200
+ git clone <repository-url>
201
+ cd react-monaco-json-merge
202
+
203
+ # Install dependencies
204
+ npm install
205
+
206
+ # Start development server
207
+ npm run dev
208
+ ```
209
+
210
+ ### Available Scripts
211
+
212
+ ```bash
213
+ # Development
214
+ npm run dev # Start Vite dev server
215
+ npm run build # Build for production
216
+ npm run preview # Preview production build
217
+
218
+ # Code Quality
219
+ npm run type-check # TypeScript type checking
220
+ npm run lint # Lint code with Biome
221
+ npm run lint:fix # Fix linting issues
222
+ npm run format # Format code
223
+
224
+ # Testing
225
+ npm run test # Run tests in watch mode
226
+ npm run test:run # Run tests once
227
+ npm run test:ui # Run tests with UI
228
+ npm run test:coverage # Generate coverage report
229
+
230
+ # Utilities
231
+ npm run validate # Run all checks (type-check + lint + test)
232
+ npm run clean # Remove build artifacts
233
+ ```
234
+
235
+ ### Project Structure
236
+
237
+ ```
238
+ react-monaco-json-merge/
239
+ โ”œโ”€โ”€ src/
240
+ โ”‚ โ”œโ”€โ”€ components/
241
+ โ”‚ โ”‚ โ””โ”€โ”€ editor.tsx # Main JsonDiffMergeEditor component
242
+ โ”‚ โ”œโ”€โ”€ data/
243
+ โ”‚ โ”‚ โ””โ”€โ”€ sampleData.ts # Sample data for demo
244
+ โ”‚ โ”œโ”€โ”€ utils/
245
+ โ”‚ โ”‚ โ”œโ”€โ”€ diffMerge.ts # Merge logic
246
+ โ”‚ โ”‚ โ”œโ”€โ”€ jsonPatchDiff.ts # Diff computation
247
+ โ”‚ โ”‚ โ”œโ”€โ”€ editorDecorations.ts # Monaco decorations
248
+ โ”‚ โ”‚ โ”œโ”€โ”€ helpers.ts # Utility functions
249
+ โ”‚ โ”‚ โ””โ”€โ”€ schema.ts # Schema utilities
250
+ โ”‚ โ”œโ”€โ”€ types/
251
+ โ”‚ โ”‚ โ””โ”€โ”€ index.ts # TypeScript definitions
252
+ โ”‚ โ”œโ”€โ”€ styles/
253
+ โ”‚ โ”‚ โ””โ”€โ”€ editor.css # Editor styles
254
+ โ”‚ โ”œโ”€โ”€ Demo.tsx # Demo application
255
+ โ”‚ โ””โ”€โ”€ main.tsx # Entry point
256
+ โ”œโ”€โ”€ package.json
257
+ โ”œโ”€โ”€ tsconfig.json
258
+ โ”œโ”€โ”€ vite.config.ts
259
+ โ””โ”€โ”€ README.md
260
+ ```
261
+
262
+ ## ๐Ÿงช Testing
263
+
264
+ The project includes comprehensive test coverage:
265
+
266
+ - **Unit Tests** - Utilities and helpers (94+ tests)
267
+ - **Integration Tests** - Full editor rendering scenarios
268
+ - **Rendering Tests** - Conflict detection and highlighting
269
+ - **Schema Tests** - JSON Schema variant handling
270
+
271
+ Run tests with:
272
+ ```bash
273
+ npm run test
274
+ ```
275
+
276
+ View coverage report:
277
+ ```bash
278
+ npm run test:coverage
279
+ ```
280
+
281
+ ## ๐Ÿ“ฆ Dependencies
282
+
283
+ ### Core
284
+ - **React 19** - UI framework
285
+ - **Monaco Editor** - Code editor
286
+ - **fast-json-patch** - JSON Patch (RFC 6902) implementation
287
+ - **jsonc-parser** - JSON with comments parsing
288
+
289
+ ### Utilities
290
+ - **fast-deep-equal** - Deep equality comparison
291
+ - **sort-keys** - Object key sorting
292
+
293
+ ### Development
294
+ - **TypeScript** - Type safety
295
+ - **Vite** - Build tool
296
+ - **Vitest** - Testing framework
297
+ - **Biome** - Linting and formatting
298
+
299
+ ## ๐Ÿค Contributing
300
+
301
+ Contributions are welcome! Please feel free to submit a Pull Request.
302
+
303
+ 1. Fork the repository
304
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
305
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
306
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
307
+ 5. Open a Pull Request
308
+
309
+ ### Development Guidelines
310
+
311
+ - Follow TypeScript best practices
312
+ - Write tests for new features
313
+ - Ensure all tests pass (`npm run validate`)
314
+ - Follow the existing code style (enforced by Biome)
315
+ - Update documentation as needed
316
+
317
+ ## ๐Ÿ“„ License
318
+
319
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
320
+
321
+ ## ๐Ÿ™ Acknowledgments
322
+
323
+ - [Monaco Editor](https://microsoft.github.io/monaco-editor/) - The editor that powers this component
324
+ - [fast-json-patch](https://github.com/Starcounter-Jack/JSON-Patch) - JSON Patch implementation
325
+ - [JSON Schema](https://json-schema.org/) - Schema validation standard
326
+
327
+ ## ๐Ÿ“ž Support
328
+
329
+ For issues, questions, or contributions, please open an issue on GitHub.
330
+
331
+ ---
332
+
333
+ **Made with โค๏ธ for better JSON merging experiences**
Binary file
@@ -0,0 +1 @@
1
+ .monaco-diff-editor .monaco-editor .codicon-diff-insert,.monaco-diff-editor .monaco-editor .codicon-diff-remove{left:44px!important;right:initial!important}.monaco-diff-editor.side-by-side .monaco-editor .codicon-diff-remove{left:62px!important}.monaco-diff-editor .lightbulb-glyph,.monaco-diff-editor .codicon-lightbulb,.monaco-diff-editor .lightBulbWidget{display:none!important}.monaco-editor .conflict-issue-line.conflict-issue-error{background-color:#ff00001a}.monaco-editor .conflict-issue-line.conflict-issue-warning{background-color:#ffaa001a}.monaco-editor .conflict-issue-line.conflict-issue-smart-merge{background-color:#0096ff1a}.monaco-editor.vs .conflict-issue-line.conflict-issue-error{background-color:#ff00000d}.monaco-editor.vs .conflict-issue-line.conflict-issue-warning{background-color:#ff88000d}.monaco-editor.vs .conflict-issue-line.conflict-issue-smart-merge{background-color:#0078d40d}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#1e1e1e;color:#d4d4d4}.demo-container{max-width:100%;margin:0 auto;padding:20px}.demo-header{text-align:center;margin-bottom:30px;padding:20px;background:#252526;border-radius:8px}.demo-header h1{color:#4fc3f7;margin-bottom:10px;font-size:28px}.demo-header p{color:#888;font-size:14px}.demo-content{display:grid;grid-template-columns:320px 1fr;gap:20px}.controls-panel{background:#252526;padding:20px;border-radius:8px;height:fit-content;position:sticky;top:20px}.controls-panel h2{color:#4fc3f7;font-size:18px;margin-bottom:20px;padding-bottom:10px;border-bottom:1px solid #3c3c3c}.control-group{margin-bottom:20px}.control-group label{display:block;margin-bottom:8px}.control-group label:has(input[type=checkbox]){display:flex;align-items:center;gap:8px;white-space:nowrap}.control-group strong{color:#9cdcfe;font-size:13px;display:block;margin-bottom:8px}.control-group select{width:100%;padding:8px 12px;background:#3c3c3c;color:#d4d4d4;border:1px solid #555;border-radius:4px;font-size:13px;cursor:pointer}.control-group select:hover{border-color:#0e639c}.control-group select:focus{outline:none;border-color:#4fc3f7}.radio-group{display:flex;flex-direction:column;gap:8px;margin-top:8px}.radio-group label{display:flex;align-items:center;gap:8px;padding:8px;background:#3c3c3c;border-radius:4px;cursor:pointer;transition:background .2s}.radio-group label:hover{background:#4a4a4a}.radio-group input[type=radio]{cursor:pointer}.control-group input[type=checkbox]{margin-right:8px;cursor:pointer;flex-shrink:0}.control-group input[type=range]{cursor:pointer;accent-color:#0e639c}.control-group input[type=range]::-webkit-slider-thumb{cursor:pointer}.control-group input[type=range]::-moz-range-thumb{cursor:pointer}.info-section{background:#1e1e1e;padding:15px;border-radius:6px;margin-top:20px;border-left:3px solid #0e639c}.info-section h3{color:#ce9178;font-size:14px;margin-bottom:12px}.info-section ul{padding-left:20px;line-height:1.8}.info-section li{margin-bottom:6px;color:#d4d4d4;font-size:13px}.info-section strong{color:#9cdcfe}.editor-section{display:flex;flex-direction:column;gap:20px}.editor-container{background:#252526;padding:20px;border-radius:8px;min-height:600px}.resolution-panel{background:#252526;padding:20px;border-radius:8px}.resolution-panel h3{color:#4fc3f7;margin-bottom:15px;font-size:18px}.resolution-panel p{margin-bottom:10px;line-height:1.6;font-size:14px}.resolution-panel .error{color:#f48771;background:#f487711a;padding:8px 12px;border-radius:4px;border-left:3px solid #f48771}.resolution-panel ul{padding-left:20px;margin:10px 0}.resolution-panel li{margin-bottom:6px;font-size:13px}.resolution-panel li.error{color:#f48771}.resolution-panel li.warning{color:#dcdcaa}.resolution-panel li.smart-merge{color:#4fc3f7}.merged-preview{margin-top:15px}.merged-preview pre{background:#1e1e1e;padding:15px;border-radius:4px;overflow-x:auto;font-size:12px;margin:10px 0;max-height:200px;overflow-y:auto;border:1px solid #3c3c3c;font-family:Monaco,Menlo,Consolas,monospace}.resolution-panel button{margin-top:15px;padding:12px 24px;background:#0e639c;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;font-weight:500;transition:background .2s;width:100%}.resolution-panel button:hover:not(:disabled){background:#17b}.resolution-panel button:disabled{background:#555;cursor:not-allowed;opacity:.6}.demo-footer{margin-top:30px;padding:20px;background:#252526;border-radius:8px;text-align:center;color:#888;font-size:14px}.demo-footer strong{color:#4fc3f7}@media(max-width:1200px){.demo-content{grid-template-columns:1fr}.controls-panel{position:static}}@media(max-width:768px){.demo-container{padding:10px}.demo-header h1{font-size:22px}.editor-container{padding:10px}}