react-diff-viewer-continued 4.0.6 → 4.1.0

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.
@@ -1,60 +1,196 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
+ import cn from "classnames";
12
+ import * as React from "react";
13
+ import memoize from "memoize-one";
14
+ import { computeHiddenBlocks } from "./compute-hidden-blocks.js";
15
+ import { DiffMethod, DiffType, computeLineInformationWorker, computeDiff, } from "./compute-lines.js";
16
+ import { Expand } from "./expand.js";
17
+ import computeStyles from "./styles.js";
18
+ import { Fold } from "./fold.js";
19
+ /**
20
+ * Applies diff styling (ins/del tags) to pre-highlighted HTML by walking through
21
+ * the HTML and wrapping text portions based on character positions in the diff.
22
+ */
23
+ function applyDiffToHighlightedHtml(html, diffArray, styles) {
24
+ const ranges = [];
25
+ let pos = 0;
26
+ for (const diff of diffArray) {
27
+ const value = typeof diff.value === "string" ? diff.value : "";
28
+ if (value.length > 0) {
29
+ ranges.push({ start: pos, end: pos + value.length, type: diff.type });
30
+ pos += value.length;
31
+ }
32
+ }
33
+ const segments = [];
34
+ let i = 0;
35
+ while (i < html.length) {
36
+ if (html[i] === "<") {
37
+ const tagEnd = html.indexOf(">", i);
38
+ if (tagEnd === -1) {
39
+ // Malformed HTML, treat rest as text
40
+ segments.push({ type: "text", content: html.slice(i) });
41
+ break;
42
+ }
43
+ segments.push({ type: "tag", content: html.slice(i, tagEnd + 1) });
44
+ i = tagEnd + 1;
45
+ }
46
+ else {
47
+ // Find the next tag or end of string
48
+ let textEnd = html.indexOf("<", i);
49
+ if (textEnd === -1)
50
+ textEnd = html.length;
51
+ segments.push({ type: "text", content: html.slice(i, textEnd) });
52
+ i = textEnd;
53
+ }
7
54
  }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
55
+ // Helper to decode HTML entities for character counting
56
+ function decodeEntities(text) {
57
+ return text
58
+ .replace(/&lt;/g, "<")
59
+ .replace(/&gt;/g, ">")
60
+ .replace(/&amp;/g, "&")
61
+ .replace(/&quot;/g, '"')
62
+ .replace(/&#39;/g, "'")
63
+ .replace(/&nbsp;/g, "\u00A0");
64
+ }
65
+ // Helper to get the wrapper tag for a diff type
66
+ function getWrapper(type) {
67
+ if (type === DiffType.ADDED) {
68
+ return {
69
+ open: `<ins class="${styles.wordDiff} ${styles.wordAdded}">`,
70
+ close: "</ins>",
71
+ };
72
+ }
73
+ if (type === DiffType.REMOVED) {
74
+ return {
75
+ open: `<del class="${styles.wordDiff} ${styles.wordRemoved}">`,
76
+ close: "</del>",
77
+ };
78
+ }
79
+ return {
80
+ open: `<span class="${styles.wordDiff}">`,
81
+ close: "</span>",
24
82
  };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.DiffMethod = exports.LineNumberPrefix = void 0;
40
- const jsx_runtime_1 = require("react/jsx-runtime");
41
- const classnames_1 = __importDefault(require("classnames"));
42
- const React = __importStar(require("react"));
43
- const memoize_one_1 = __importDefault(require("memoize-one"));
44
- const compute_hidden_blocks_js_1 = require("./compute-hidden-blocks.js");
45
- const compute_lines_js_1 = require("./compute-lines.js");
46
- Object.defineProperty(exports, "DiffMethod", { enumerable: true, get: function () { return compute_lines_js_1.DiffMethod; } });
47
- const expand_js_1 = require("./expand.js");
48
- const styles_js_1 = __importDefault(require("./styles.js"));
49
- const fold_js_1 = require("./fold.js");
50
- var LineNumberPrefix;
83
+ }
84
+ // Process segments, tracking text position
85
+ let textPos = 0;
86
+ let result = "";
87
+ for (const segment of segments) {
88
+ if (segment.type === "tag") {
89
+ result += segment.content;
90
+ }
91
+ else {
92
+ // Text segment - we need to split it according to diff ranges
93
+ const text = segment.content;
94
+ const decodedText = decodeEntities(text);
95
+ // Walk through the text, character by character (in decoded form)
96
+ // but output the original encoded form
97
+ let localDecodedPos = 0;
98
+ let localEncodedPos = 0;
99
+ while (localDecodedPos < decodedText.length) {
100
+ const globalPos = textPos + localDecodedPos;
101
+ // Find the range that covers this position
102
+ const range = ranges.find((r) => globalPos >= r.start && globalPos < r.end);
103
+ if (!range) {
104
+ // No range covers this position (shouldn't happen, but be safe)
105
+ // Just output the character
106
+ const char = text[localEncodedPos];
107
+ result += char;
108
+ localEncodedPos++;
109
+ localDecodedPos++;
110
+ continue;
111
+ }
112
+ // How many decoded characters until the end of this range?
113
+ const charsUntilRangeEnd = range.end - globalPos;
114
+ // How many decoded characters until the end of this text segment?
115
+ const charsUntilTextEnd = decodedText.length - localDecodedPos;
116
+ // Take the minimum
117
+ const charsToTake = Math.min(charsUntilRangeEnd, charsUntilTextEnd);
118
+ // Now we need to find the corresponding encoded substring
119
+ // Walk through encoded text, counting decoded characters
120
+ let encodedChunkEnd = localEncodedPos;
121
+ let decodedCount = 0;
122
+ while (decodedCount < charsToTake && encodedChunkEnd < text.length) {
123
+ if (text[encodedChunkEnd] === "&") {
124
+ // Find entity end
125
+ const entityEnd = text.indexOf(";", encodedChunkEnd);
126
+ if (entityEnd !== -1 && entityEnd - encodedChunkEnd < 10) {
127
+ encodedChunkEnd = entityEnd + 1;
128
+ }
129
+ else {
130
+ encodedChunkEnd++;
131
+ }
132
+ }
133
+ else {
134
+ encodedChunkEnd++;
135
+ }
136
+ decodedCount++;
137
+ }
138
+ const chunk = text.slice(localEncodedPos, encodedChunkEnd);
139
+ const wrapper = getWrapper(range.type);
140
+ if (wrapper) {
141
+ result += wrapper.open + chunk + wrapper.close;
142
+ }
143
+ else {
144
+ result += chunk;
145
+ }
146
+ localEncodedPos = encodedChunkEnd;
147
+ localDecodedPos += charsToTake;
148
+ }
149
+ textPos += decodedText.length;
150
+ }
151
+ }
152
+ return result;
153
+ }
154
+ export var LineNumberPrefix;
51
155
  (function (LineNumberPrefix) {
52
156
  LineNumberPrefix["LEFT"] = "L";
53
157
  LineNumberPrefix["RIGHT"] = "R";
54
- })(LineNumberPrefix || (exports.LineNumberPrefix = LineNumberPrefix = {}));
158
+ })(LineNumberPrefix || (LineNumberPrefix = {}));
55
159
  class DiffViewer extends React.Component {
56
160
  constructor(props) {
57
161
  super(props);
162
+ // Cache for on-demand word diff computation
163
+ this.wordDiffCache = new Map();
164
+ /**
165
+ * Computes word diff on-demand for a line, with caching.
166
+ * This is used when word diff was deferred during initial computation.
167
+ */
168
+ this.getWordDiffValues = (left, right, lineIndex) => {
169
+ // Handle empty left/right
170
+ if (!left || !right) {
171
+ return { leftValue: left === null || left === void 0 ? void 0 : left.value, rightValue: right === null || right === void 0 ? void 0 : right.value };
172
+ }
173
+ // If no raw values, word diff was already computed or disabled
174
+ // Use explicit undefined check since empty string is a valid raw value
175
+ if (left.rawValue === undefined || right.rawValue === undefined) {
176
+ return { leftValue: left.value, rightValue: right.value };
177
+ }
178
+ // Check cache
179
+ const cacheKey = `${lineIndex}-${left.rawValue}-${right.rawValue}`;
180
+ let cached = this.wordDiffCache.get(cacheKey);
181
+ if (!cached) {
182
+ // Compute word diff on-demand
183
+ // Use CHARS method for on-demand computation since rawValue is always a string
184
+ // (JSON/YAML methods only work with objects, not the string lines we have here)
185
+ const compareMethod = (this.props.compareMethod === DiffMethod.JSON || this.props.compareMethod === DiffMethod.YAML)
186
+ ? DiffMethod.CHARS
187
+ : this.props.compareMethod;
188
+ const computed = computeDiff(left.rawValue, right.rawValue, compareMethod);
189
+ cached = { left: computed.left, right: computed.right };
190
+ this.wordDiffCache.set(cacheKey, cached);
191
+ }
192
+ return { leftValue: cached.left, rightValue: cached.right };
193
+ };
58
194
  /**
59
195
  * Resets code block expand to the initial stage. Will be exposed to the parent component via
60
196
  * refs.
@@ -85,7 +221,7 @@ class DiffViewer extends React.Component {
85
221
  *
86
222
  * @param styles User supplied style overrides.
87
223
  */
88
- this.computeStyles = (0, memoize_one_1.default)(styles_js_1.default);
224
+ this.computeStyles = memoize(computeStyles);
89
225
  /**
90
226
  * Returns a function with clicked line number in the closure. Returns an no-op function when no
91
227
  * onLineNumberClick handler is supplied.
@@ -98,6 +234,19 @@ class DiffViewer extends React.Component {
98
234
  }
99
235
  return () => { };
100
236
  };
237
+ /**
238
+ * Checks if the current compare method should show word-level highlighting.
239
+ * Character, word-level, JSON, and YAML diffs benefit from highlighting individual changes.
240
+ * JSON/YAML use CHARS internally for word-level diff, so they should be highlighted.
241
+ */
242
+ this.shouldHighlightWordDiff = () => {
243
+ const { compareMethod } = this.props;
244
+ return (compareMethod === DiffMethod.CHARS ||
245
+ compareMethod === DiffMethod.WORDS ||
246
+ compareMethod === DiffMethod.WORDS_WITH_SPACE ||
247
+ compareMethod === DiffMethod.JSON ||
248
+ compareMethod === DiffMethod.YAML);
249
+ };
101
250
  /**
102
251
  * Maps over the word diff and constructs the required React elements to show word diff.
103
252
  *
@@ -105,18 +254,61 @@ class DiffViewer extends React.Component {
105
254
  * @param renderer Optional renderer to format diff words. Useful for syntax highlighting.
106
255
  */
107
256
  this.renderWordDiff = (diffArray, renderer) => {
257
+ var _a, _b;
258
+ const showHighlight = this.shouldHighlightWordDiff();
259
+ const { compareMethod } = this.props;
260
+ // Don't apply syntax highlighting for JSON/YAML - their word diffs are computed
261
+ // on-demand from raw strings and syntax highlighting creates messy fragmented tokens.
262
+ const skipSyntaxHighlighting = compareMethod === DiffMethod.JSON || compareMethod === DiffMethod.YAML;
263
+ // Reconstruct the full line from diff chunks
264
+ const fullLine = diffArray
265
+ .map((d) => (typeof d.value === "string" ? d.value : ""))
266
+ .join("");
267
+ // For very long lines (>500 chars), skip fancy processing - just render plain text
268
+ // without word-level highlighting to avoid performance issues
269
+ const MAX_LINE_LENGTH = 500;
270
+ if (fullLine.length > MAX_LINE_LENGTH) {
271
+ return [_jsx("span", { children: fullLine }, "long-line")];
272
+ }
273
+ // If we have a renderer and syntax highlighting is enabled, try to highlight
274
+ // the full line first, then apply diff styling to preserve proper tokenization.
275
+ if (renderer && !skipSyntaxHighlighting) {
276
+ // Get the syntax-highlighted content
277
+ const highlighted = renderer(fullLine);
278
+ // Check if the renderer uses dangerouslySetInnerHTML (common with Prism, highlight.js, etc.)
279
+ const htmlContent = (_b = (_a = highlighted === null || highlighted === void 0 ? void 0 : highlighted.props) === null || _a === void 0 ? void 0 : _a.dangerouslySetInnerHTML) === null || _b === void 0 ? void 0 : _b.__html;
280
+ if (typeof htmlContent === "string") {
281
+ // Apply diff styling to the highlighted HTML
282
+ const styledHtml = applyDiffToHighlightedHtml(htmlContent, diffArray, {
283
+ wordDiff: this.styles.wordDiff,
284
+ wordAdded: showHighlight ? this.styles.wordAdded : "",
285
+ wordRemoved: showHighlight ? this.styles.wordRemoved : "",
286
+ });
287
+ // Clone the element with the modified HTML
288
+ return [
289
+ React.cloneElement(highlighted, {
290
+ key: "highlighted-diff",
291
+ dangerouslySetInnerHTML: { __html: styledHtml },
292
+ }),
293
+ ];
294
+ }
295
+ // Renderer doesn't use dangerouslySetInnerHTML - fall through to per-chunk rendering
296
+ }
297
+ // Fallback: render each chunk separately (used for JSON/YAML or non-HTML renderers)
108
298
  return diffArray.map((wordDiff, i) => {
109
- const content = renderer
110
- ? renderer(wordDiff.value)
111
- : (typeof wordDiff.value === 'string'
112
- ? wordDiff.value
113
- // If wordDiff.value is DiffInformation, we don't handle it, unclear why. See c0c99f5712.
114
- : undefined);
115
- return wordDiff.type === compute_lines_js_1.DiffType.ADDED ? ((0, jsx_runtime_1.jsx)("ins", { className: (0, classnames_1.default)(this.styles.wordDiff, {
116
- [this.styles.wordAdded]: wordDiff.type === compute_lines_js_1.DiffType.ADDED,
117
- }), children: content }, i)) : wordDiff.type === compute_lines_js_1.DiffType.REMOVED ? ((0, jsx_runtime_1.jsx)("del", { className: (0, classnames_1.default)(this.styles.wordDiff, {
118
- [this.styles.wordRemoved]: wordDiff.type === compute_lines_js_1.DiffType.REMOVED,
119
- }), children: content }, i)) : ((0, jsx_runtime_1.jsx)("span", { className: (0, classnames_1.default)(this.styles.wordDiff), children: content }, i));
299
+ let content;
300
+ if (typeof wordDiff.value === "string") {
301
+ content = wordDiff.value;
302
+ }
303
+ else {
304
+ // If wordDiff.value is DiffInformation[], we don't handle it. See c0c99f5712.
305
+ content = undefined;
306
+ }
307
+ return wordDiff.type === DiffType.ADDED ? (_jsx("ins", { className: cn(this.styles.wordDiff, {
308
+ [this.styles.wordAdded]: showHighlight,
309
+ }), children: content }, i)) : wordDiff.type === DiffType.REMOVED ? (_jsx("del", { className: cn(this.styles.wordDiff, {
310
+ [this.styles.wordRemoved]: showHighlight,
311
+ }), children: content }, i)) : (_jsx("span", { className: cn(this.styles.wordDiff), children: content }, i));
120
312
  });
121
313
  };
122
314
  /**
@@ -137,9 +329,9 @@ class DiffViewer extends React.Component {
137
329
  const additionalLineNumberTemplate = `${additionalPrefix}-${additionalLineNumber}`;
138
330
  const highlightLine = this.props.highlightLines.includes(lineNumberTemplate) ||
139
331
  this.props.highlightLines.includes(additionalLineNumberTemplate);
140
- const added = type === compute_lines_js_1.DiffType.ADDED;
141
- const removed = type === compute_lines_js_1.DiffType.REMOVED;
142
- const changed = type === compute_lines_js_1.DiffType.CHANGED;
332
+ const added = type === DiffType.ADDED;
333
+ const removed = type === DiffType.REMOVED;
334
+ const changed = type === DiffType.CHANGED;
143
335
  let content;
144
336
  const hasWordDiff = Array.isArray(value);
145
337
  if (hasWordDiff) {
@@ -158,20 +350,20 @@ class DiffViewer extends React.Component {
158
350
  else if (removed && !hasWordDiff) {
159
351
  ElementType = "del";
160
352
  }
161
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!this.props.hideLineNumbers && ((0, jsx_runtime_1.jsx)("td", { onClick: lineNumber && this.onLineNumberClickProxy(lineNumberTemplate), className: (0, classnames_1.default)(this.styles.gutter, {
353
+ return (_jsxs(_Fragment, { children: [!this.props.hideLineNumbers && (_jsx("td", { onClick: lineNumber && this.onLineNumberClickProxy(lineNumberTemplate), className: cn(this.styles.gutter, {
162
354
  [this.styles.emptyGutter]: !lineNumber,
163
355
  [this.styles.diffAdded]: added,
164
356
  [this.styles.diffRemoved]: removed,
165
357
  [this.styles.diffChanged]: changed,
166
358
  [this.styles.highlightedGutter]: highlightLine,
167
- }), children: (0, jsx_runtime_1.jsx)("pre", { className: this.styles.lineNumber, children: lineNumber }) })), !this.props.splitView && !this.props.hideLineNumbers && ((0, jsx_runtime_1.jsx)("td", { onClick: additionalLineNumber &&
168
- this.onLineNumberClickProxy(additionalLineNumberTemplate), className: (0, classnames_1.default)(this.styles.gutter, {
359
+ }), children: _jsx("pre", { className: this.styles.lineNumber, children: lineNumber }) })), !this.props.splitView && !this.props.hideLineNumbers && (_jsx("td", { onClick: additionalLineNumber &&
360
+ this.onLineNumberClickProxy(additionalLineNumberTemplate), className: cn(this.styles.gutter, {
169
361
  [this.styles.emptyGutter]: !additionalLineNumber,
170
362
  [this.styles.diffAdded]: added,
171
363
  [this.styles.diffRemoved]: removed,
172
364
  [this.styles.diffChanged]: changed,
173
365
  [this.styles.highlightedGutter]: highlightLine,
174
- }), children: (0, jsx_runtime_1.jsx)("pre", { className: this.styles.lineNumber, children: additionalLineNumber }) })), this.props.renderGutter
366
+ }), children: _jsx("pre", { className: this.styles.lineNumber, children: additionalLineNumber }) })), this.props.renderGutter
175
367
  ? this.props.renderGutter({
176
368
  lineNumber,
177
369
  type,
@@ -181,13 +373,13 @@ class DiffViewer extends React.Component {
181
373
  additionalPrefix,
182
374
  styles: this.styles,
183
375
  })
184
- : null, (0, jsx_runtime_1.jsx)("td", { className: (0, classnames_1.default)(this.styles.marker, {
376
+ : null, _jsx("td", { className: cn(this.styles.marker, {
185
377
  [this.styles.emptyLine]: !content,
186
378
  [this.styles.diffAdded]: added,
187
379
  [this.styles.diffRemoved]: removed,
188
380
  [this.styles.diffChanged]: changed,
189
381
  [this.styles.highlightedLine]: highlightLine,
190
- }), children: (0, jsx_runtime_1.jsxs)("pre", { children: [added && "+", removed && "-"] }) }), (0, jsx_runtime_1.jsx)("td", { className: (0, classnames_1.default)(this.styles.content, {
382
+ }), children: _jsxs("pre", { children: [added && "+", removed && "-"] }) }), _jsx("td", { className: cn(this.styles.content, {
191
383
  [this.styles.emptyLine]: !content,
192
384
  [this.styles.diffAdded]: added,
193
385
  [this.styles.diffRemoved]: removed,
@@ -205,7 +397,7 @@ class DiffViewer extends React.Component {
205
397
  ? "Added line"
206
398
  : removed && !hasWordDiff
207
399
  ? "Removed line"
208
- : undefined, children: (0, jsx_runtime_1.jsx)(ElementType, { className: this.styles.contentText, children: content }) })] }));
400
+ : undefined, children: _jsx(ElementType, { className: this.styles.contentText, children: content }) })] }));
209
401
  };
210
402
  /**
211
403
  * Generates lines for split view.
@@ -216,7 +408,9 @@ class DiffViewer extends React.Component {
216
408
  * @param index React key for the lines.
217
409
  */
218
410
  this.renderSplitView = ({ left, right }, index) => {
219
- return ((0, jsx_runtime_1.jsxs)("tr", { className: this.styles.line, children: [this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, left.value), this.renderLine(right.lineNumber, right.type, LineNumberPrefix.RIGHT, right.value)] }, index));
411
+ // Compute word diff on-demand if deferred
412
+ const { leftValue, rightValue } = this.getWordDiffValues(left, right, index);
413
+ return (_jsxs("tr", { className: this.styles.line, children: [this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue), this.renderLine(right.lineNumber, right.type, LineNumberPrefix.RIGHT, rightValue)] }, index));
220
414
  };
221
415
  /**
222
416
  * Generates lines for inline view.
@@ -227,20 +421,22 @@ class DiffViewer extends React.Component {
227
421
  * @param index React key for the lines.
228
422
  */
229
423
  this.renderInlineView = ({ left, right }, index) => {
424
+ // Compute word diff on-demand if deferred
425
+ const { leftValue, rightValue } = this.getWordDiffValues(left, right, index);
230
426
  let content;
231
- if (left.type === compute_lines_js_1.DiffType.REMOVED && right.type === compute_lines_js_1.DiffType.ADDED) {
232
- return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)("tr", { className: this.styles.line, children: this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, left.value, null) }), (0, jsx_runtime_1.jsx)("tr", { className: this.styles.line, children: this.renderLine(null, right.type, LineNumberPrefix.RIGHT, right.value, right.lineNumber, LineNumberPrefix.RIGHT) })] }, index));
427
+ if (left.type === DiffType.REMOVED && right.type === DiffType.ADDED) {
428
+ return (_jsxs(React.Fragment, { children: [_jsx("tr", { className: this.styles.line, children: this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue, null) }), _jsx("tr", { className: this.styles.line, children: this.renderLine(null, right.type, LineNumberPrefix.RIGHT, rightValue, right.lineNumber, LineNumberPrefix.RIGHT) })] }, index));
233
429
  }
234
- if (left.type === compute_lines_js_1.DiffType.REMOVED) {
235
- content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, left.value, null);
430
+ if (left.type === DiffType.REMOVED) {
431
+ content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue, null);
236
432
  }
237
- if (left.type === compute_lines_js_1.DiffType.DEFAULT) {
238
- content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, left.value, right.lineNumber, LineNumberPrefix.RIGHT);
433
+ if (left.type === DiffType.DEFAULT) {
434
+ content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue, right.lineNumber, LineNumberPrefix.RIGHT);
239
435
  }
240
- if (right.type === compute_lines_js_1.DiffType.ADDED) {
241
- content = this.renderLine(null, right.type, LineNumberPrefix.RIGHT, right.value, right.lineNumber);
436
+ if (right.type === DiffType.ADDED) {
437
+ content = this.renderLine(null, right.type, LineNumberPrefix.RIGHT, rightValue, right.lineNumber);
242
438
  }
243
- return ((0, jsx_runtime_1.jsx)("tr", { className: this.styles.line, children: content }, index));
439
+ return (_jsx("tr", { className: this.styles.line, children: content }, index));
244
440
  };
245
441
  /**
246
442
  * Returns a function with clicked block number in the closure.
@@ -259,51 +455,191 @@ class DiffViewer extends React.Component {
259
455
  */
260
456
  this.renderSkippedLineIndicator = (num, blockNumber, leftBlockLineNumber, rightBlockLineNumber) => {
261
457
  const { hideLineNumbers, splitView } = this.props;
262
- const message = this.props.codeFoldMessageRenderer ? (this.props.codeFoldMessageRenderer(num, leftBlockLineNumber, rightBlockLineNumber)) : ((0, jsx_runtime_1.jsxs)("span", { className: this.styles.codeFoldContent, children: ["Expand ", num, " lines ..."] }));
263
- const content = ((0, jsx_runtime_1.jsx)("td", { className: this.styles.codeFoldContentContainer, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: this.styles.codeFoldExpandButton, onClick: this.onBlockClickProxy(blockNumber), tabIndex: 0, children: message }) }));
458
+ const message = this.props.codeFoldMessageRenderer ? (this.props.codeFoldMessageRenderer(num, leftBlockLineNumber, rightBlockLineNumber)) : (_jsxs("span", { className: this.styles.codeFoldContent, children: ["@@ -", leftBlockLineNumber - num, ",", num, " +", rightBlockLineNumber - num, ",", num, " @@"] }));
459
+ const content = (_jsx("td", { className: this.styles.codeFoldContentContainer, children: _jsx("button", { type: "button", className: this.styles.codeFoldExpandButton, onClick: this.onBlockClickProxy(blockNumber), tabIndex: 0, children: message }) }));
264
460
  const isUnifiedViewWithoutLineNumbers = !splitView && !hideLineNumbers;
265
- return ((0, jsx_runtime_1.jsxs)("tr", { className: this.styles.codeFold, children: [!hideLineNumbers && (0, jsx_runtime_1.jsx)("td", { className: this.styles.codeFoldGutter }), this.props.renderGutter ? ((0, jsx_runtime_1.jsx)("td", { className: this.styles.codeFoldGutter })) : null, (0, jsx_runtime_1.jsx)("td", { className: (0, classnames_1.default)({
461
+ const expandGutter = (_jsx("td", { className: this.styles.codeFoldGutter, children: _jsx(Expand, {}) }));
462
+ return (_jsxs("tr", { className: this.styles.codeFold, onClick: this.onBlockClickProxy(blockNumber), role: "button", tabIndex: 0, children: [!hideLineNumbers && expandGutter, this.props.renderGutter ? (_jsx("td", { className: this.styles.codeFoldGutter })) : null, _jsx("td", { className: cn({
266
463
  [this.styles.codeFoldGutter]: isUnifiedViewWithoutLineNumbers,
267
- }) }), isUnifiedViewWithoutLineNumbers ? ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)("td", {}), content] })) : ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [content, this.props.renderGutter ? (0, jsx_runtime_1.jsx)("td", {}) : null, (0, jsx_runtime_1.jsx)("td", {}), (0, jsx_runtime_1.jsx)("td", {}), !hideLineNumbers ? (0, jsx_runtime_1.jsx)("td", {}) : null] }))] }, `${leftBlockLineNumber}-${rightBlockLineNumber}`));
464
+ }) }), isUnifiedViewWithoutLineNumbers ? (_jsxs(React.Fragment, { children: [_jsx("td", {}), content] })) : (_jsxs(React.Fragment, { children: [content, this.props.renderGutter ? _jsx("td", {}) : null, _jsx("td", {}), _jsx("td", {}), !hideLineNumbers ? _jsx("td", {}) : null] }))] }, `${leftBlockLineNumber}-${rightBlockLineNumber}`));
465
+ };
466
+ /**
467
+ *
468
+ * Generates a unique cache key based on the current props used in diff computation.
469
+ *
470
+ * This key is used to memoize results and avoid recomputation for the same inputs.
471
+ * @returns A stringified JSON key representing the current diff settings and input values.
472
+ *
473
+ */
474
+ this.getMemoisedKey = () => {
475
+ const { oldValue, newValue, disableWordDiff, compareMethod, linesOffset, alwaysShowLines, extraLinesSurroundingDiff, } = this.props;
476
+ return JSON.stringify({
477
+ oldValue,
478
+ newValue,
479
+ disableWordDiff,
480
+ compareMethod,
481
+ linesOffset,
482
+ alwaysShowLines,
483
+ extraLinesSurroundingDiff,
484
+ });
268
485
  };
269
486
  /**
270
- * Generates the entire diff view.
487
+ * Computes and memoizes the diff result between `oldValue` and `newValue`.
488
+ *
489
+ * If a memoized result exists for the current input configuration, it uses that.
490
+ * Otherwise, it runs the diff logic in a Web Worker to avoid blocking the UI.
491
+ * It also computes hidden line blocks for collapsing unchanged sections,
492
+ * and stores the result in the local component state.
271
493
  */
272
- this.renderDiff = () => {
273
- const { oldValue, newValue, splitView, disableWordDiff, compareMethod, linesOffset, } = this.props;
274
- const { lineInformation, diffLines } = (0, compute_lines_js_1.computeLineInformation)(oldValue, newValue, disableWordDiff, compareMethod, linesOffset, this.props.alwaysShowLines);
494
+ this.memoisedCompute = () => __awaiter(this, void 0, void 0, function* () {
495
+ var _a;
496
+ const { oldValue, newValue, disableWordDiff, compareMethod, linesOffset } = this.props;
497
+ const cacheKey = this.getMemoisedKey();
498
+ if (!!this.state.computedDiffResult[cacheKey]) {
499
+ this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: false })));
500
+ return;
501
+ }
502
+ // Defer word diff computation when using infinite loading with reasonable container height
503
+ // This significantly improves initial render time for large diffs
504
+ const containerHeight = (_a = this.props.infiniteLoading) === null || _a === void 0 ? void 0 : _a.containerHeight;
505
+ const containerHeightPx = containerHeight
506
+ ? typeof containerHeight === 'number'
507
+ ? containerHeight
508
+ : parseInt(containerHeight, 10) || 0
509
+ : 0;
510
+ const shouldDeferWordDiff = !disableWordDiff &&
511
+ !!this.props.infiniteLoading &&
512
+ containerHeightPx > 0 &&
513
+ containerHeightPx < 2000;
514
+ const { lineInformation, diffLines } = yield computeLineInformationWorker(oldValue, newValue, disableWordDiff, compareMethod, linesOffset, this.props.alwaysShowLines, shouldDeferWordDiff);
275
515
  const extraLines = this.props.extraLinesSurroundingDiff < 0
276
516
  ? 0
277
517
  : Math.round(this.props.extraLinesSurroundingDiff);
278
- const { lineBlocks, blocks } = (0, compute_hidden_blocks_js_1.computeHiddenBlocks)(lineInformation, diffLines, extraLines);
279
- const diffNodes = lineInformation.map((line, lineIndex) => {
280
- if (this.props.showDiffOnly) {
518
+ const { lineBlocks, blocks } = computeHiddenBlocks(lineInformation, diffLines, extraLines);
519
+ this.state.computedDiffResult[cacheKey] = { lineInformation, lineBlocks, blocks };
520
+ this.setState((prev) => (Object.assign(Object.assign({}, prev), { computedDiffResult: this.state.computedDiffResult, isLoading: false })));
521
+ });
522
+ /**
523
+ * Handles scroll events on the scrollable container.
524
+ *
525
+ * Updates the visible start row for virtualization.
526
+ */
527
+ this.onScroll = () => {
528
+ const container = this.state.scrollableContainerRef.current;
529
+ if (!container || !this.props.infiniteLoading)
530
+ return;
531
+ const newStartRow = Math.floor(container.scrollTop / DiffViewer.ESTIMATED_ROW_HEIGHT);
532
+ // Only update state if the start row changed (avoid unnecessary re-renders)
533
+ if (newStartRow !== this.state.visibleStartRow) {
534
+ this.setState({ visibleStartRow: newStartRow });
535
+ }
536
+ };
537
+ /**
538
+ * Generates the entire diff view with virtualization support.
539
+ */
540
+ this.renderDiff = () => {
541
+ var _a;
542
+ const { splitView, infiniteLoading, showDiffOnly } = this.props;
543
+ const { computedDiffResult, expandedBlocks, visibleStartRow, scrollableContainerRef } = this.state;
544
+ const cacheKey = this.getMemoisedKey();
545
+ const { lineInformation = [], lineBlocks = [], blocks = [] } = (_a = computedDiffResult[cacheKey]) !== null && _a !== void 0 ? _a : {};
546
+ // Calculate visible range for virtualization
547
+ let visibleRowStart = 0;
548
+ let visibleRowEnd = Infinity;
549
+ const buffer = 5; // render extra rows above/below viewport
550
+ if (infiniteLoading && scrollableContainerRef.current) {
551
+ const container = scrollableContainerRef.current;
552
+ const viewportRows = Math.ceil(container.clientHeight / DiffViewer.ESTIMATED_ROW_HEIGHT);
553
+ visibleRowStart = Math.max(0, visibleStartRow - buffer);
554
+ visibleRowEnd = visibleStartRow + viewportRows + buffer;
555
+ }
556
+ // First pass: build a map of lineIndex -> renderedRowIndex
557
+ // This accounts for code folding where some lines don't render or render as fold indicators
558
+ const lineToRowMap = new Map();
559
+ const seenBlocks = new Set();
560
+ let currentRow = 0;
561
+ for (let i = 0; i < lineInformation.length; i++) {
562
+ const blockIndex = lineBlocks[i];
563
+ if (showDiffOnly && blockIndex !== undefined) {
564
+ if (!expandedBlocks.includes(blockIndex)) {
565
+ // Line is in a collapsed block
566
+ const lastLineOfBlock = blocks[blockIndex].endLine === i;
567
+ if (!seenBlocks.has(blockIndex) && lastLineOfBlock) {
568
+ // This line renders as a fold indicator
569
+ seenBlocks.add(blockIndex);
570
+ lineToRowMap.set(i, currentRow);
571
+ currentRow++;
572
+ }
573
+ // Other lines in collapsed block don't render
574
+ }
575
+ else {
576
+ // Block is expanded, line renders normally
577
+ lineToRowMap.set(i, currentRow);
578
+ currentRow++;
579
+ }
580
+ }
581
+ else {
582
+ // Not in a block or showDiffOnly is false, line renders normally
583
+ lineToRowMap.set(i, currentRow);
584
+ currentRow++;
585
+ }
586
+ }
587
+ const totalRenderedRows = currentRow;
588
+ // Second pass: render only lines in the visible range
589
+ const diffNodes = [];
590
+ let topPadding = 0;
591
+ let firstVisibleFound = false;
592
+ seenBlocks.clear();
593
+ for (let lineIndex = 0; lineIndex < lineInformation.length; lineIndex++) {
594
+ const line = lineInformation[lineIndex];
595
+ const rowIndex = lineToRowMap.get(lineIndex);
596
+ // Skip lines that don't render (hidden in collapsed blocks)
597
+ if (rowIndex === undefined)
598
+ continue;
599
+ // Skip lines before visible range
600
+ if (rowIndex < visibleRowStart) {
601
+ continue;
602
+ }
603
+ // Stop after visible range
604
+ if (rowIndex > visibleRowEnd) {
605
+ break;
606
+ }
607
+ // Calculate top padding from the first visible row
608
+ if (!firstVisibleFound) {
609
+ topPadding = rowIndex * DiffViewer.ESTIMATED_ROW_HEIGHT;
610
+ firstVisibleFound = true;
611
+ }
612
+ // Render the line
613
+ if (showDiffOnly) {
281
614
  const blockIndex = lineBlocks[lineIndex];
282
615
  if (blockIndex !== undefined) {
283
616
  const lastLineOfBlock = blocks[blockIndex].endLine === lineIndex;
284
- if (!this.state.expandedBlocks.includes(blockIndex) &&
617
+ if (!expandedBlocks.includes(blockIndex) &&
285
618
  lastLineOfBlock) {
286
- return ((0, jsx_runtime_1.jsx)(React.Fragment, { children: this.renderSkippedLineIndicator(blocks[blockIndex].lines, blockIndex, line.left.lineNumber, line.right.lineNumber) }, lineIndex));
619
+ diffNodes.push(_jsx(React.Fragment, { children: this.renderSkippedLineIndicator(blocks[blockIndex].lines, blockIndex, line.left.lineNumber, line.right.lineNumber) }, lineIndex));
620
+ continue;
287
621
  }
288
- if (!this.state.expandedBlocks.includes(blockIndex)) {
289
- return null;
622
+ if (!expandedBlocks.includes(blockIndex)) {
623
+ continue;
290
624
  }
291
625
  }
292
626
  }
293
- return splitView
627
+ diffNodes.push(splitView
294
628
  ? this.renderSplitView(line, lineIndex)
295
- : this.renderInlineView(line, lineIndex);
296
- });
629
+ : this.renderInlineView(line, lineIndex));
630
+ }
297
631
  return {
298
632
  diffNodes,
299
633
  blocks,
300
634
  lineInformation,
635
+ totalRenderedRows,
636
+ topPadding,
301
637
  };
302
638
  };
303
639
  this.render = () => {
304
640
  const { oldValue, newValue, useDarkTheme, leftTitle, rightTitle, splitView, compareMethod, hideLineNumbers, nonce, } = this.props;
305
641
  if (typeof compareMethod === "string" &&
306
- compareMethod !== compute_lines_js_1.DiffMethod.JSON) {
642
+ compareMethod !== DiffMethod.JSON) {
307
643
  if (typeof oldValue !== "string" || typeof newValue !== "string") {
308
644
  throw Error('"oldValue" and "newValue" should be strings');
309
645
  }
@@ -323,16 +659,16 @@ class DiffViewer extends React.Component {
323
659
  let deletions = 0;
324
660
  let additions = 0;
325
661
  for (const l of nodes.lineInformation) {
326
- if (l.left.type === compute_lines_js_1.DiffType.ADDED) {
662
+ if (l.left.type === DiffType.ADDED) {
327
663
  additions++;
328
664
  }
329
- if (l.right.type === compute_lines_js_1.DiffType.ADDED) {
665
+ if (l.right.type === DiffType.ADDED) {
330
666
  additions++;
331
667
  }
332
- if (l.left.type === compute_lines_js_1.DiffType.REMOVED) {
668
+ if (l.left.type === DiffType.REMOVED) {
333
669
  deletions++;
334
670
  }
335
- if (l.right.type === compute_lines_js_1.DiffType.REMOVED) {
671
+ if (l.right.type === DiffType.REMOVED) {
336
672
  deletions++;
337
673
  }
338
674
  }
@@ -341,39 +677,67 @@ class DiffViewer extends React.Component {
341
677
  const blocks = [];
342
678
  for (let i = 0; i < 5; i++) {
343
679
  if (percentageAddition > i * 20) {
344
- blocks.push((0, jsx_runtime_1.jsx)("span", { className: (0, classnames_1.default)(this.styles.block, this.styles.blockAddition) }, i));
680
+ blocks.push(_jsx("span", { className: cn(this.styles.block, this.styles.blockAddition) }, i));
345
681
  }
346
682
  else {
347
- blocks.push((0, jsx_runtime_1.jsx)("span", { className: (0, classnames_1.default)(this.styles.block, this.styles.blockDeletion) }, i));
683
+ blocks.push(_jsx("span", { className: cn(this.styles.block, this.styles.blockDeletion) }, i));
348
684
  }
349
685
  }
350
686
  const allExpanded = this.state.expandedBlocks.length === nodes.blocks.length;
351
- return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: this.styles.summary, role: "banner", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: this.styles.allExpandButton, onClick: () => {
352
- this.setState({
353
- expandedBlocks: allExpanded
354
- ? []
355
- : nodes.blocks.map((b) => b.index),
356
- });
357
- }, children: allExpanded ? (0, jsx_runtime_1.jsx)(fold_js_1.Fold, {}) : (0, jsx_runtime_1.jsx)(expand_js_1.Expand, {}) }), " ", totalChanges, (0, jsx_runtime_1.jsx)("div", { style: { display: "flex", gap: "1px" }, children: blocks }), this.props.summary ? (0, jsx_runtime_1.jsx)("span", { children: this.props.summary }) : null] }), (0, jsx_runtime_1.jsx)("table", { className: (0, classnames_1.default)(this.styles.diffContainer, {
358
- [this.styles.splitView]: splitView,
359
- }), onMouseUp: () => {
360
- const elements = document.getElementsByClassName("right");
361
- for (let i = 0; i < elements.length; i++) {
362
- const element = elements.item(i);
363
- element.classList.remove(this.styles.noSelect);
364
- }
365
- const elementsLeft = document.getElementsByClassName("left");
366
- for (let i = 0; i < elementsLeft.length; i++) {
367
- const element = elementsLeft.item(i);
368
- element.classList.remove(this.styles.noSelect);
369
- }
370
- }, children: (0, jsx_runtime_1.jsxs)("tbody", { children: [(0, jsx_runtime_1.jsxs)("tr", { children: [!this.props.hideLineNumbers ? (0, jsx_runtime_1.jsx)("td", { width: "50px" }) : null, !splitView && !this.props.hideLineNumbers ? ((0, jsx_runtime_1.jsx)("td", { width: "50px" })) : null, this.props.renderGutter ? (0, jsx_runtime_1.jsx)("td", { width: "50px" }) : null, (0, jsx_runtime_1.jsx)("td", { width: "28px" }), (0, jsx_runtime_1.jsx)("td", { width: "100%" }), splitView ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!this.props.hideLineNumbers ? (0, jsx_runtime_1.jsx)("td", { width: "50px" }) : null, this.props.renderGutter ? (0, jsx_runtime_1.jsx)("td", { width: "50px" }) : null, (0, jsx_runtime_1.jsx)("td", { width: "28px" }), (0, jsx_runtime_1.jsx)("td", { width: "100%" })] })) : null] }), leftTitle || rightTitle ? ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { colSpan: splitView ? colSpanOnSplitView : colSpanOnInlineView, className: (0, classnames_1.default)(this.styles.titleBlock, this.styles.column), children: leftTitle ? ((0, jsx_runtime_1.jsx)("pre", { className: this.styles.contentText, children: leftTitle })) : null }), splitView ? ((0, jsx_runtime_1.jsx)("th", { colSpan: colSpanOnSplitView, className: (0, classnames_1.default)(this.styles.titleBlock, this.styles.column), children: rightTitle ? ((0, jsx_runtime_1.jsx)("pre", { className: this.styles.contentText, children: rightTitle })) : null })) : null] })) : null, nodes.diffNodes] }) })] }));
687
+ const LoadingElement = this.props.loadingElement;
688
+ const scrollDivStyle = this.props.infiniteLoading ? {
689
+ overflowY: 'scroll',
690
+ overflowX: 'hidden',
691
+ height: this.props.infiniteLoading.containerHeight
692
+ } : {};
693
+ const totalContentHeight = nodes.totalRenderedRows * DiffViewer.ESTIMATED_ROW_HEIGHT;
694
+ const tableElement = (_jsxs("table", { className: cn(this.styles.diffContainer, {
695
+ [this.styles.splitView]: splitView,
696
+ }), onMouseUp: () => {
697
+ const elements = document.getElementsByClassName("right");
698
+ for (let i = 0; i < elements.length; i++) {
699
+ const element = elements.item(i);
700
+ element.classList.remove(this.styles.noSelect);
701
+ }
702
+ const elementsLeft = document.getElementsByClassName("left");
703
+ for (let i = 0; i < elementsLeft.length; i++) {
704
+ const element = elementsLeft.item(i);
705
+ element.classList.remove(this.styles.noSelect);
706
+ }
707
+ }, children: [_jsxs("colgroup", { children: [!this.props.hideLineNumbers && _jsx("col", { width: "50px" }), !splitView && !this.props.hideLineNumbers && _jsx("col", { width: "50px" }), this.props.renderGutter && _jsx("col", { width: "50px" }), _jsx("col", { width: "28px" }), _jsx("col", { width: "auto" }), splitView && (_jsxs(_Fragment, { children: [!this.props.hideLineNumbers && _jsx("col", { width: "50px" }), this.props.renderGutter && _jsx("col", { width: "50px" }), _jsx("col", { width: "28px" }), _jsx("col", { width: "auto" })] }))] }), _jsx("tbody", { children: nodes.diffNodes })] }));
708
+ return (_jsxs("div", { style: Object.assign(Object.assign({}, scrollDivStyle), { position: 'relative' }), onScroll: this.onScroll, ref: this.state.scrollableContainerRef, children: [(!this.props.hideSummary || leftTitle || rightTitle) && (_jsxs("div", { className: this.styles.stickyHeader, children: [!this.props.hideSummary && (_jsxs("div", { className: this.styles.summary, role: "banner", children: [_jsx("button", { type: "button", className: this.styles.allExpandButton, onClick: () => {
709
+ this.setState({
710
+ expandedBlocks: allExpanded
711
+ ? []
712
+ : nodes.blocks.map((b) => b.index),
713
+ });
714
+ }, children: allExpanded ? _jsx(Fold, {}) : _jsx(Expand, {}) }), " ", totalChanges, _jsx("div", { style: { display: "flex", gap: "1px" }, children: blocks }), this.props.summary ? _jsx("span", { children: this.props.summary }) : null] })), (leftTitle || rightTitle) && (_jsxs("div", { className: this.styles.columnHeaders, children: [_jsx("div", { className: this.styles.titleBlock, children: leftTitle ? (_jsx("pre", { className: this.styles.contentText, children: leftTitle })) : null }), splitView && (_jsx("div", { className: this.styles.titleBlock, children: rightTitle ? (_jsx("pre", { className: this.styles.contentText, children: rightTitle })) : null }))] }))] })), this.state.isLoading && LoadingElement && _jsx(LoadingElement, {}), this.props.infiniteLoading ? (_jsx("div", { style: { minHeight: totalContentHeight, paddingTop: nodes.topPadding }, children: tableElement })) : (tableElement)] }));
371
715
  };
372
716
  this.state = {
373
717
  expandedBlocks: [],
374
718
  noSelect: undefined,
719
+ scrollableContainerRef: React.createRef(),
720
+ computedDiffResult: {},
721
+ isLoading: false,
722
+ visibleStartRow: 0
375
723
  };
376
724
  }
725
+ componentDidUpdate(prevProps) {
726
+ if (prevProps.oldValue !== this.props.oldValue ||
727
+ prevProps.newValue !== this.props.newValue ||
728
+ prevProps.compareMethod !== this.props.compareMethod ||
729
+ prevProps.disableWordDiff !== this.props.disableWordDiff ||
730
+ prevProps.linesOffset !== this.props.linesOffset) {
731
+ // Clear word diff cache when diff changes
732
+ this.wordDiffCache.clear();
733
+ this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: true, visibleStartRow: 0 })));
734
+ this.memoisedCompute();
735
+ }
736
+ }
737
+ componentDidMount() {
738
+ this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: true })));
739
+ this.memoisedCompute();
740
+ }
377
741
  }
378
742
  DiffViewer.defaultProps = {
379
743
  oldValue: "",
@@ -381,7 +745,7 @@ DiffViewer.defaultProps = {
381
745
  splitView: true,
382
746
  highlightLines: [],
383
747
  disableWordDiff: false,
384
- compareMethod: compute_lines_js_1.DiffMethod.CHARS,
748
+ compareMethod: DiffMethod.CHARS,
385
749
  styles: {},
386
750
  hideLineNumbers: false,
387
751
  extraLinesSurroundingDiff: 3,
@@ -390,4 +754,7 @@ DiffViewer.defaultProps = {
390
754
  linesOffset: 0,
391
755
  nonce: "",
392
756
  };
393
- exports.default = DiffViewer;
757
+ // Estimated row height based on lineHeight: 1.6em with 12px base font
758
+ DiffViewer.ESTIMATED_ROW_HEIGHT = 19;
759
+ export default DiffViewer;
760
+ export { DiffMethod };