react-diff-viewer-continued 4.1.0 → 4.1.2

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.
@@ -8,7 +8,14 @@
8
8
  "Bash(pnpm test:*)",
9
9
  "Bash(CI=1 pnpm test:*)",
10
10
  "Bash(CI=1 npm test:*)",
11
- "Bash(pnpm remove:*)"
11
+ "Bash(pnpm remove:*)",
12
+ "Bash(npm test:*)",
13
+ "Bash(npx tsc:*)",
14
+ "Bash(ls:*)",
15
+ "Bash(pnpm run build:*)",
16
+ "Bash(pnpm exec vitest:*)",
17
+ "Bash(grep:*)",
18
+ "Bash(pnpm run test:*)"
12
19
  ],
13
20
  "deny": [],
14
21
  "ask": []
@@ -31,6 +31,9 @@ jobs:
31
31
  - name: Install dependencies
32
32
  run: pnpm i
33
33
 
34
+ - name: Build worker bundle
35
+ run: pnpm run build:worker
36
+
34
37
  - name: Run unit tests
35
38
  run: pnpm run test
36
39
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.2 (2026-02-07)
4
+
5
+ ### Bug Fixes
6
+
7
+ - fix virtualization offsets and missing key display on structural json diff
8
+ - bundle worker code as a blob
9
+
10
+ ## 4.1.1 (2026-02-07)
11
+
12
+ ### Bug Fixes
13
+
14
+ - fix problem with missing worker in compiled version
15
+
3
16
  ## 4.1.0 (2026-02-04)
4
17
 
5
18
  ### Features
@@ -64,17 +64,5 @@ declare const computeDiff: (oldValue: string | Record<string, unknown>, newValue
64
64
  * @param showLines lines that are always shown, regardless of diff
65
65
  */
66
66
  declare const computeLineInformation: (oldString: string | Record<string, unknown>, newString: string | Record<string, unknown>, disableWordDiff?: boolean, lineCompareMethod?: DiffMethod | ((oldStr: string, newStr: string) => diff.Change[]), linesOffset?: number, showLines?: string[], deferWordDiff?: boolean) => ComputedLineInformation;
67
- /**
68
- * Computes line diff information using a Web Worker to avoid blocking the UI thread.
69
- * This offloads the expensive `computeLineInformation` logic to a separate thread.
70
- *
71
- * @param oldString Old string to compare.
72
- * @param newString New string to compare with old string.
73
- * @param disableWordDiff Flag to enable/disable word diff.
74
- * @param lineCompareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
75
- * @param linesOffset line number to start counting from
76
- * @param showLines lines that are always shown, regardless of diff
77
- * @returns Promise<ComputedLineInformation> - Resolves with line-by-line diff data from the worker.
78
- */
79
67
  declare const computeLineInformationWorker: (oldString: string | Record<string, unknown>, newString: string | Record<string, unknown>, disableWordDiff?: boolean, lineCompareMethod?: DiffMethod | ((oldStr: string, newStr: string) => diff.Change[]), linesOffset?: number, showLines?: string[], deferWordDiff?: boolean) => Promise<ComputedLineInformation>;
80
68
  export { computeLineInformation, computeLineInformationWorker, computeDiff };
@@ -151,9 +151,22 @@ function diffObjects(oldObj, newObj, indent, format = 'json') {
151
151
  // Values differ - recursively diff them
152
152
  const keyPrefix = innerIndent + JSON.stringify(key) + ': ';
153
153
  const valueDiff = diffStructurally(oldObj[key], newObj[key], indent + 1, format);
154
- // Prepend key to first change so they're on the same line
154
+ // Prepend key to the appropriate changes
155
155
  if (valueDiff.length > 0) {
156
- valueDiff[0].value = keyPrefix + valueDiff[0].value;
156
+ if (!valueDiff[0].removed && !valueDiff[0].added) {
157
+ // First change is neutral (e.g., opening brace of nested object) - prepend key to it only
158
+ valueDiff[0].value = keyPrefix + valueDiff[0].value;
159
+ }
160
+ else {
161
+ // First change is removed or added - this is a primitive value change
162
+ // Both the removed (old) and added (new) lines need the key
163
+ const firstRemoved = valueDiff.find(c => c.removed);
164
+ const firstAdded = valueDiff.find(c => c.added);
165
+ if (firstRemoved)
166
+ firstRemoved.value = keyPrefix + firstRemoved.value;
167
+ if (firstAdded)
168
+ firstAdded.value = keyPrefix + firstAdded.value;
169
+ }
157
170
  }
158
171
  // Add comma to last change if needed
159
172
  if (comma && valueDiff.length > 0) {
@@ -489,20 +502,56 @@ const computeLineInformation = (oldString, newString, disableWordDiff = false, l
489
502
  * @param showLines lines that are always shown, regardless of diff
490
503
  * @returns Promise<ComputedLineInformation> - Resolves with line-by-line diff data from the worker.
491
504
  */
505
+ // Import the bundled worker code (generated by scripts/build-worker.js)
506
+ import { WORKER_CODE } from './workerBundle';
507
+ // Cached Blob URL for the worker - created once and reused
508
+ let workerBlobUrl = null;
509
+ let workerAvailable = null;
510
+ // Create a Blob URL from the bundled worker code
511
+ const getWorkerBlobUrl = () => {
512
+ if (workerBlobUrl !== null)
513
+ return workerBlobUrl;
514
+ if (typeof Worker === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined') {
515
+ workerAvailable = false;
516
+ return null;
517
+ }
518
+ try {
519
+ const blob = new Blob([WORKER_CODE], { type: 'application/javascript' });
520
+ workerBlobUrl = URL.createObjectURL(blob);
521
+ workerAvailable = true;
522
+ }
523
+ catch (_a) {
524
+ workerAvailable = false;
525
+ workerBlobUrl = null;
526
+ }
527
+ return workerBlobUrl;
528
+ };
492
529
  const computeLineInformationWorker = (oldString, newString, disableWordDiff = false, lineCompareMethod = DiffMethod.CHARS, linesOffset = 0, showLines = [], deferWordDiff = false) => {
493
- // Fall back to synchronous computation if Worker is not available (e.g., in Node.js/test environments)
494
- if (typeof Worker === 'undefined') {
495
- return Promise.resolve(computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff));
530
+ const fallback = () => computeLineInformation(oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff);
531
+ const blobUrl = getWorkerBlobUrl();
532
+ if (!blobUrl) {
533
+ return Promise.resolve(fallback());
496
534
  }
497
- return new Promise((resolve, reject) => {
498
- const worker = new Worker(new URL('./computeWorker.ts', import.meta.url), { type: 'module' });
535
+ return new Promise((resolve) => {
536
+ let worker;
537
+ try {
538
+ worker = new Worker(blobUrl);
539
+ }
540
+ catch (_a) {
541
+ // Worker instantiation failed - fall back to synchronous computation
542
+ workerAvailable = false;
543
+ resolve(fallback());
544
+ return;
545
+ }
499
546
  worker.onmessage = (e) => {
500
547
  resolve(e.data);
501
548
  worker.terminate();
502
549
  };
503
- worker.onerror = (err) => {
504
- reject(err);
550
+ worker.onerror = () => {
551
+ // Worker error - fall back and mark as unavailable for future calls
552
+ workerAvailable = false;
505
553
  worker.terminate();
554
+ resolve(fallback());
506
555
  };
507
556
  worker.postMessage({ oldString, newString, disableWordDiff, lineCompareMethod, linesOffset, showLines, deferWordDiff });
508
557
  });
@@ -65,6 +65,10 @@ export interface ReactDiffViewerProps {
65
65
  * Hide the summary bar (expand/collapse button, change count, filename)
66
66
  */
67
67
  hideSummary?: boolean;
68
+ /**
69
+ * Show debug overlay with virtualization info (for development)
70
+ */
71
+ showDebugInfo?: boolean;
68
72
  }
69
73
  export interface ReactDiffViewerState {
70
74
  expandedBlocks?: number[];
@@ -73,10 +77,17 @@ export interface ReactDiffViewerState {
73
77
  computedDiffResult: Record<string, ComputedDiffResult>;
74
78
  isLoading: boolean;
75
79
  visibleStartRow: number;
80
+ contentColumnWidth: number | null;
81
+ charWidth: number | null;
82
+ cumulativeOffsets: number[] | null;
76
83
  }
77
84
  declare class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiffViewerState> {
78
85
  private styles;
79
86
  private wordDiffCache;
87
+ private contentColumnRef;
88
+ private charMeasureRef;
89
+ private stickyHeaderRef;
90
+ private resizeObserver;
80
91
  static defaultProps: ReactDiffViewerProps;
81
92
  constructor(props: ReactDiffViewerProps);
82
93
  /**
@@ -94,6 +105,38 @@ declare class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiff
94
105
  * this value is used to expand/fold unmodified code.
95
106
  */
96
107
  private onBlockExpand;
108
+ /**
109
+ * Gets the height of the sticky header, if present.
110
+ */
111
+ private getStickyHeaderHeight;
112
+ /**
113
+ * Measures the width of a single character in the monospace font.
114
+ * Falls back to 7.2px if measurement fails.
115
+ */
116
+ private measureCharWidth;
117
+ /**
118
+ * Measures the available width for content in a content column.
119
+ * Falls back to estimating from container width if direct measurement fails.
120
+ */
121
+ private measureContentColumnWidth;
122
+ /**
123
+ * Gets the text length from a value that may be a string or DiffInformation array.
124
+ */
125
+ private getTextLength;
126
+ /**
127
+ * Builds cumulative vertical offsets for each line based on character count and column width.
128
+ * This allows accurate scroll position calculations with variable row heights.
129
+ */
130
+ private buildCumulativeOffsets;
131
+ /**
132
+ * Binary search to find the line index at a given scroll offset.
133
+ */
134
+ private findLineAtOffset;
135
+ /**
136
+ * Recalculates cumulative offsets based on current measurements.
137
+ * Called on resize and when blocks are expanded/collapsed.
138
+ */
139
+ private recalculateOffsets;
97
140
  /**
98
141
  * Computes final styles for the diff viewer. It combines the default styles with the user
99
142
  * supplied overrides. The computed styles are cached with performance in mind.
@@ -200,6 +243,7 @@ declare class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiff
200
243
  private renderDiff;
201
244
  componentDidUpdate(prevProps: ReactDiffViewerProps): void;
202
245
  componentDidMount(): void;
246
+ componentWillUnmount(): void;
203
247
  render: () => ReactElement;
204
248
  }
205
249
  export default DiffViewer;