react-diff-viewer-continued 4.2.2 → 4.4.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.
- package/.github/workflows/pages.yml +36 -0
- package/.github/workflows/publish.yml +2 -10
- package/CHANGELOG.md +38 -0
- package/README.md +5 -3
- package/lib/cjs/src/expand.d.ts +1 -1
- package/lib/cjs/src/fold.d.ts +1 -1
- package/lib/cjs/src/highlight-theme.d.ts +29 -0
- package/lib/cjs/src/highlight-theme.js +99 -0
- package/lib/cjs/src/highlight.d.ts +69 -0
- package/lib/cjs/src/highlight.js +315 -0
- package/lib/cjs/src/index.d.ts +54 -3
- package/lib/cjs/src/index.js +211 -10
- package/lib/cjs/src/styles.d.ts +5 -0
- package/lib/cjs/src/styles.js +137 -56
- package/lib/cjs/src/workerBundle.d.ts +1 -1
- package/lib/cjs/src/workerBundle.js +1 -1
- package/lib/esm/src/highlight-theme.js +99 -0
- package/lib/esm/src/highlight.js +311 -0
- package/lib/esm/src/index.js +208 -9
- package/lib/esm/src/styles.js +137 -56
- package/lib/esm/src/workerBundle.js +1 -1
- package/package.json +16 -12
- package/pnpm-workspace.yaml +7 -0
- package/publish-examples.sh +0 -10
package/lib/cjs/src/index.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { ReactElement, RefObject } from "react";
|
|
|
3
3
|
import type { Change } from "diff";
|
|
4
4
|
import { type Block } from "./compute-hidden-blocks.js";
|
|
5
5
|
import { type DiffInformation, DiffMethod, DiffType, type LineInformation } from "./compute-lines.js";
|
|
6
|
-
import { type ReactDiffViewerStyles, type ReactDiffViewerStylesOverride } from "./styles.js";
|
|
6
|
+
import { type ReactDiffViewerStyles, type ReactDiffViewerStylesOverride, type ReactDiffViewerStylesVariables } from "./styles.js";
|
|
7
|
+
import { type LineTokens } from "./highlight.js";
|
|
8
|
+
import { type HighlightTheme } from "./highlight-theme.js";
|
|
7
9
|
export declare enum LineNumberPrefix {
|
|
8
10
|
LEFT = "L",
|
|
9
11
|
RIGHT = "R"
|
|
@@ -75,11 +77,26 @@ export interface ReactDiffViewerProps {
|
|
|
75
77
|
* Useful when the worker bundle fails to load in certain bundler configurations.
|
|
76
78
|
*/
|
|
77
79
|
disableWorker?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Enable first-party syntax highlighting for the given language (a Prism/
|
|
82
|
+
* refractor language name or alias, e.g. `"typescript"`, `"json"`, `"html"`).
|
|
83
|
+
* Each side is highlighted as a whole — so constructs that wrap across lines
|
|
84
|
+
* tokenise correctly — and the highlighting is merged with word-diff marks on
|
|
85
|
+
* changed lines. Takes precedence over `renderContent` for line content.
|
|
86
|
+
* Unknown languages fall back gracefully to no highlighting.
|
|
87
|
+
*/
|
|
88
|
+
highlightLanguage?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Optional overrides for the syntax-highlight colour palette. Merged over the
|
|
91
|
+
* built-in light/dark theme (selected by `useDarkTheme`). Keys are Prism token
|
|
92
|
+
* types (`keyword`, `string`, `tag`, ...) plus `default`.
|
|
93
|
+
*/
|
|
94
|
+
highlightTheme?: HighlightTheme;
|
|
78
95
|
}
|
|
79
96
|
export interface ReactDiffViewerState {
|
|
80
97
|
expandedBlocks: number[];
|
|
81
98
|
noSelect?: "left" | "right";
|
|
82
|
-
scrollableContainerRef: RefObject<HTMLDivElement
|
|
99
|
+
scrollableContainerRef: RefObject<HTMLDivElement>;
|
|
83
100
|
computedDiffResult: Record<string, ComputedDiffResult>;
|
|
84
101
|
isLoading: boolean;
|
|
85
102
|
visibleStartRow: number;
|
|
@@ -87,10 +104,16 @@ export interface ReactDiffViewerState {
|
|
|
87
104
|
charWidth: number | null;
|
|
88
105
|
cumulativeOffsets: number[] | null;
|
|
89
106
|
isScrolling: boolean;
|
|
107
|
+
highlightResult: {
|
|
108
|
+
key: string;
|
|
109
|
+
left: Map<number, LineTokens>;
|
|
110
|
+
right: Map<number, LineTokens>;
|
|
111
|
+
} | null;
|
|
90
112
|
}
|
|
91
113
|
declare class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiffViewerState> {
|
|
92
114
|
private styles;
|
|
93
115
|
private wordDiffCache;
|
|
116
|
+
private highlightPrecedenceWarned;
|
|
94
117
|
private contentColumnRef;
|
|
95
118
|
private charMeasureRef;
|
|
96
119
|
private stickyHeaderRef;
|
|
@@ -239,6 +262,31 @@ declare class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiff
|
|
|
239
262
|
* and stores the result in the local component state.
|
|
240
263
|
*/
|
|
241
264
|
private memoisedCompute;
|
|
265
|
+
/**
|
|
266
|
+
* Extracts the raw text of one side of a line for whole-side highlighting.
|
|
267
|
+
* Prefers `rawValue` (deferred word diff), then a plain string value, then the
|
|
268
|
+
* concatenation of word-diff chunk values.
|
|
269
|
+
*/
|
|
270
|
+
private lineToText;
|
|
271
|
+
/**
|
|
272
|
+
* Reconstructs one side's full document from the computed line information and
|
|
273
|
+
* highlights it in a single pass, returning per-line tokens keyed by line
|
|
274
|
+
* number. Highlighting the whole side (rather than line-by-line) is what keeps
|
|
275
|
+
* lexer state correct across line boundaries.
|
|
276
|
+
*/
|
|
277
|
+
private buildSideTokens;
|
|
278
|
+
/**
|
|
279
|
+
* Resolves the active highlight theme (built-in light/dark, with any
|
|
280
|
+
* `highlightTheme` overrides merged on top).
|
|
281
|
+
*/
|
|
282
|
+
private resolveHighlightTheme;
|
|
283
|
+
/**
|
|
284
|
+
* Computes syntax-highlight tokens for both sides when `highlightLanguage` is
|
|
285
|
+
* set, storing them in state. Lazily loads the grammar, is memoised by
|
|
286
|
+
* (diff, language, theme, dark) so it is cheap to call repeatedly, and clears
|
|
287
|
+
* the result when highlighting is disabled or the language is unavailable.
|
|
288
|
+
*/
|
|
289
|
+
private updateHighlight;
|
|
242
290
|
private static readonly ESTIMATED_ROW_HEIGHT;
|
|
243
291
|
/**
|
|
244
292
|
* Handles scroll events on the scrollable container.
|
|
@@ -258,4 +306,7 @@ declare class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiff
|
|
|
258
306
|
export default DiffViewer;
|
|
259
307
|
export { DiffMethod };
|
|
260
308
|
export { default as computeStyles } from "./styles.js";
|
|
261
|
-
export
|
|
309
|
+
export { defaultLightThemeVariables, defaultDarkThemeVariables, } from "./styles.js";
|
|
310
|
+
export { defaultLightHighlightTheme, defaultDarkHighlightTheme, } from "./highlight-theme.js";
|
|
311
|
+
export type { ReactDiffViewerStylesOverride, ReactDiffViewerStyles, ReactDiffViewerStylesVariables };
|
|
312
|
+
export type { HighlightTheme } from "./highlight-theme.js";
|
package/lib/cjs/src/index.js
CHANGED
|
@@ -7,6 +7,56 @@ import { DiffMethod, DiffType, computeLineInformationWorker, computeDiff, } from
|
|
|
7
7
|
import { Expand } from "./expand.js";
|
|
8
8
|
import computeStyles from "./styles.js";
|
|
9
9
|
import { Fold } from "./fold.js";
|
|
10
|
+
import { ensureLanguage, highlightSide, mergeHighlightWithDiff, renderHighlightedPlain, sliceLineTokens, } from "./highlight.js";
|
|
11
|
+
import { defaultDarkHighlightTheme, defaultLightHighlightTheme, } from "./highlight-theme.js";
|
|
12
|
+
const LEADING_WHITESPACE = /^[ \t]+/;
|
|
13
|
+
/**
|
|
14
|
+
* Splits a line's leading whitespace off its content so it can be rendered as a
|
|
15
|
+
* separate fixed-width indent column (see `contentFlex`/`lineIndent`/`lineBody`
|
|
16
|
+
* styles). Peeling happens *before* the value is highlighted/word-diffed, so the
|
|
17
|
+
* indent never gets swept into a syntax token or a diff chunk.
|
|
18
|
+
*
|
|
19
|
+
* Handles both shapes `renderLine` receives: a raw string, or a word-diff array
|
|
20
|
+
* whose leading whitespace may span one or more leading chunks (WORDS_WITH_SPACE
|
|
21
|
+
* isolates indentation as its own chunk) or prefix the first chunk. The remaining
|
|
22
|
+
* chunks are returned intact so their diff types/colours are preserved.
|
|
23
|
+
*/
|
|
24
|
+
function splitLeadingWhitespace(value) {
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
const match = value.match(LEADING_WHITESPACE);
|
|
27
|
+
return match
|
|
28
|
+
? { indent: match[0], rest: value.slice(match[0].length) }
|
|
29
|
+
: { indent: "", rest: value };
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
const chunks = value.map((chunk) => (Object.assign({}, chunk)));
|
|
33
|
+
let indent = "";
|
|
34
|
+
let i = 0;
|
|
35
|
+
for (; i < chunks.length; i++) {
|
|
36
|
+
const chunkValue = typeof chunks[i].value === "string"
|
|
37
|
+
? chunks[i].value
|
|
38
|
+
: null;
|
|
39
|
+
if (chunkValue === null)
|
|
40
|
+
break;
|
|
41
|
+
const match = chunkValue.match(LEADING_WHITESPACE);
|
|
42
|
+
if (!match)
|
|
43
|
+
break;
|
|
44
|
+
if (match[0].length === chunkValue.length) {
|
|
45
|
+
// Whole chunk is whitespace — consume it and keep scanning.
|
|
46
|
+
indent += chunkValue;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Chunk starts with whitespace then real content — keep the trimmed remainder.
|
|
50
|
+
indent += match[0];
|
|
51
|
+
chunks[i] = Object.assign(Object.assign({}, chunks[i]), { value: chunkValue.slice(match[0].length) });
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
return indent
|
|
55
|
+
? { indent, rest: chunks.slice(i) }
|
|
56
|
+
: { indent: "", rest: value };
|
|
57
|
+
}
|
|
58
|
+
return { indent: "", rest: value };
|
|
59
|
+
}
|
|
10
60
|
/**
|
|
11
61
|
* Applies diff styling (ins/del tags) to pre-highlighted HTML by walking through
|
|
12
62
|
* the HTML and wrapping text portions based on character positions in the diff.
|
|
@@ -154,6 +204,8 @@ class DiffViewer extends React.Component {
|
|
|
154
204
|
super(props);
|
|
155
205
|
// Cache for on-demand word diff computation
|
|
156
206
|
this.wordDiffCache = new Map();
|
|
207
|
+
// Ensures the highlightLanguage/renderContent precedence warning fires once.
|
|
208
|
+
this.highlightPrecedenceWarned = false;
|
|
157
209
|
// Refs for measuring content column width and character width
|
|
158
210
|
this.contentColumnRef = React.createRef();
|
|
159
211
|
this.charMeasureRef = React.createRef();
|
|
@@ -334,7 +386,7 @@ class DiffViewer extends React.Component {
|
|
|
334
386
|
* @param additionalPrefix Similar to prefix but for additional line number.
|
|
335
387
|
*/
|
|
336
388
|
this.renderLine = (lineNumber, type, prefix, value, additionalLineNumber, additionalPrefix) => {
|
|
337
|
-
var _a;
|
|
389
|
+
var _a, _b;
|
|
338
390
|
const lineNumberTemplate = `${prefix}-${lineNumber}`;
|
|
339
391
|
const additionalLineNumberTemplate = `${additionalPrefix}-${additionalLineNumber}`;
|
|
340
392
|
const highlightLines = (_a = this.props.highlightLines) !== null && _a !== void 0 ? _a : [];
|
|
@@ -343,16 +395,56 @@ class DiffViewer extends React.Component {
|
|
|
343
395
|
const added = type === DiffType.ADDED;
|
|
344
396
|
const removed = type === DiffType.REMOVED;
|
|
345
397
|
const changed = type === DiffType.CHANGED;
|
|
398
|
+
// Peel the leading whitespace into a separate indent column before any
|
|
399
|
+
// highlighting/word-diffing runs, so wrapped continuation lines hang-indent
|
|
400
|
+
// under the line's first character instead of the cell's left edge.
|
|
401
|
+
const { indent, rest } = splitLeadingWhitespace(value);
|
|
402
|
+
const hasWordDiff = Array.isArray(rest);
|
|
403
|
+
// First-party syntax highlighting: look up this line's whole-side tokens and
|
|
404
|
+
// rebase them past the peeled indent so they line up with the diff body.
|
|
405
|
+
const highlightMap = this.state.highlightResult
|
|
406
|
+
? prefix === LineNumberPrefix.LEFT
|
|
407
|
+
? this.state.highlightResult.left
|
|
408
|
+
: this.state.highlightResult.right
|
|
409
|
+
: null;
|
|
410
|
+
const sideLineNumber = (_b = lineNumber !== null && lineNumber !== void 0 ? lineNumber : additionalLineNumber) !== null && _b !== void 0 ? _b : undefined;
|
|
411
|
+
const fullLineTokens = highlightMap && sideLineNumber != null ? highlightMap.get(sideLineNumber) : undefined;
|
|
412
|
+
const bodyTokens = fullLineTokens ? sliceLineTokens(fullLineTokens, indent.length) : undefined;
|
|
413
|
+
// Longest body we'll merge char-by-char before falling back to plain colour.
|
|
414
|
+
const MAX_HIGHLIGHT_MERGE_LENGTH = 500;
|
|
346
415
|
let content;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
416
|
+
if (bodyTokens) {
|
|
417
|
+
if (hasWordDiff) {
|
|
418
|
+
const bodyText = rest
|
|
419
|
+
.map((chunk) => (typeof chunk.value === "string" ? chunk.value : ""))
|
|
420
|
+
.join("");
|
|
421
|
+
content =
|
|
422
|
+
bodyText.length > MAX_HIGHLIGHT_MERGE_LENGTH
|
|
423
|
+
? renderHighlightedPlain(bodyText, bodyTokens)
|
|
424
|
+
: mergeHighlightWithDiff(bodyText, bodyTokens, rest, {
|
|
425
|
+
styles: {
|
|
426
|
+
wordDiff: this.styles.wordDiff,
|
|
427
|
+
wordAdded: this.styles.wordAdded,
|
|
428
|
+
wordRemoved: this.styles.wordRemoved,
|
|
429
|
+
},
|
|
430
|
+
showHighlight: this.shouldHighlightWordDiff(),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
else if (typeof rest === "string") {
|
|
434
|
+
content = renderHighlightedPlain(rest, bodyTokens);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
content = rest;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
else if (hasWordDiff) {
|
|
441
|
+
content = this.renderWordDiff(rest, this.props.renderContent);
|
|
350
442
|
}
|
|
351
|
-
else if (this.props.renderContent && typeof
|
|
352
|
-
content = this.props.renderContent(
|
|
443
|
+
else if (this.props.renderContent && typeof rest === "string") {
|
|
444
|
+
content = this.props.renderContent(rest);
|
|
353
445
|
}
|
|
354
446
|
else {
|
|
355
|
-
content =
|
|
447
|
+
content = rest;
|
|
356
448
|
}
|
|
357
449
|
let ElementType = "div";
|
|
358
450
|
if (added && !hasWordDiff) {
|
|
@@ -361,6 +453,9 @@ class DiffViewer extends React.Component {
|
|
|
361
453
|
else if (removed && !hasWordDiff) {
|
|
362
454
|
ElementType = "del";
|
|
363
455
|
}
|
|
456
|
+
// A line is only "empty" (gets the empty-line background) when it has neither
|
|
457
|
+
// body content nor peeled indentation — a whitespace-only line is not empty.
|
|
458
|
+
const isEmpty = !content && !indent;
|
|
364
459
|
return (_jsxs(_Fragment, { children: [!this.props.hideLineNumbers && (_jsx("td", { onClick: lineNumber && this.onLineNumberClickProxy(lineNumberTemplate), className: cn(this.styles.gutter, {
|
|
365
460
|
[this.styles.emptyGutter]: !lineNumber,
|
|
366
461
|
[this.styles.diffAdded]: added,
|
|
@@ -385,13 +480,13 @@ class DiffViewer extends React.Component {
|
|
|
385
480
|
styles: this.styles,
|
|
386
481
|
})
|
|
387
482
|
: null, _jsx("td", { className: cn(this.styles.marker, {
|
|
388
|
-
[this.styles.emptyLine]:
|
|
483
|
+
[this.styles.emptyLine]: isEmpty,
|
|
389
484
|
[this.styles.diffAdded]: added,
|
|
390
485
|
[this.styles.diffRemoved]: removed,
|
|
391
486
|
[this.styles.diffChanged]: changed,
|
|
392
487
|
[this.styles.highlightedLine]: highlightLine,
|
|
393
488
|
}), children: _jsxs("pre", { children: [added && "+", removed && "-"] }) }), _jsx("td", { ref: prefix === LineNumberPrefix.LEFT && !this.state.cumulativeOffsets ? this.contentColumnRef : undefined, className: cn(this.styles.content, {
|
|
394
|
-
[this.styles.emptyLine]:
|
|
489
|
+
[this.styles.emptyLine]: isEmpty,
|
|
395
490
|
[this.styles.diffAdded]: added,
|
|
396
491
|
[this.styles.diffRemoved]: removed,
|
|
397
492
|
[this.styles.diffChanged]: changed,
|
|
@@ -416,7 +511,7 @@ class DiffViewer extends React.Component {
|
|
|
416
511
|
? "Added line"
|
|
417
512
|
: removed && !hasWordDiff
|
|
418
513
|
? "Removed line"
|
|
419
|
-
: undefined, children:
|
|
514
|
+
: undefined, children: _jsxs(ElementType, { className: cn(this.styles.contentText, this.styles.contentFlex), children: [indent ? _jsx("span", { className: this.styles.lineIndent, children: indent }) : null, _jsx("span", { className: this.styles.lineBody, children: content })] }) })] }));
|
|
420
515
|
};
|
|
421
516
|
/**
|
|
422
517
|
* Generates lines for split view.
|
|
@@ -516,6 +611,7 @@ class DiffViewer extends React.Component {
|
|
|
516
611
|
const cacheKey = this.getMemoisedKey();
|
|
517
612
|
if (!!this.state.computedDiffResult[cacheKey]) {
|
|
518
613
|
this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: false })));
|
|
614
|
+
this.updateHighlight();
|
|
519
615
|
return;
|
|
520
616
|
}
|
|
521
617
|
// Defer word diff computation when using infinite loading with reasonable container height
|
|
@@ -536,6 +632,8 @@ class DiffViewer extends React.Component {
|
|
|
536
632
|
const { lineBlocks, blocks } = computeHiddenBlocks(lineInformation, diffLines, extraLines);
|
|
537
633
|
this.state.computedDiffResult[cacheKey] = { lineInformation, lineBlocks, blocks };
|
|
538
634
|
this.setState((prev) => (Object.assign(Object.assign({}, prev), { computedDiffResult: this.state.computedDiffResult, isLoading: false })), () => {
|
|
635
|
+
// Recompute syntax highlighting now that fresh line information exists.
|
|
636
|
+
this.updateHighlight();
|
|
539
637
|
// Trigger offset recalculation after diff is computed and rendered
|
|
540
638
|
// Use requestAnimationFrame to ensure DOM is ready for measurement
|
|
541
639
|
if (this.props.infiniteLoading) {
|
|
@@ -543,6 +641,100 @@ class DiffViewer extends React.Component {
|
|
|
543
641
|
}
|
|
544
642
|
});
|
|
545
643
|
};
|
|
644
|
+
/**
|
|
645
|
+
* Extracts the raw text of one side of a line for whole-side highlighting.
|
|
646
|
+
* Prefers `rawValue` (deferred word diff), then a plain string value, then the
|
|
647
|
+
* concatenation of word-diff chunk values.
|
|
648
|
+
*/
|
|
649
|
+
this.lineToText = (info) => {
|
|
650
|
+
if (typeof info.rawValue === "string")
|
|
651
|
+
return info.rawValue;
|
|
652
|
+
if (typeof info.value === "string")
|
|
653
|
+
return info.value;
|
|
654
|
+
if (Array.isArray(info.value)) {
|
|
655
|
+
return info.value.map((chunk) => (typeof chunk.value === "string" ? chunk.value : "")).join("");
|
|
656
|
+
}
|
|
657
|
+
return "";
|
|
658
|
+
};
|
|
659
|
+
/**
|
|
660
|
+
* Reconstructs one side's full document from the computed line information and
|
|
661
|
+
* highlights it in a single pass, returning per-line tokens keyed by line
|
|
662
|
+
* number. Highlighting the whole side (rather than line-by-line) is what keeps
|
|
663
|
+
* lexer state correct across line boundaries.
|
|
664
|
+
*/
|
|
665
|
+
this.buildSideTokens = (lineInformation, side, language, theme) => {
|
|
666
|
+
var _a;
|
|
667
|
+
const entries = [];
|
|
668
|
+
for (const line of lineInformation) {
|
|
669
|
+
const info = line[side];
|
|
670
|
+
if (!info || info.lineNumber == null)
|
|
671
|
+
continue;
|
|
672
|
+
entries.push({ lineNumber: info.lineNumber, text: this.lineToText(info) });
|
|
673
|
+
}
|
|
674
|
+
entries.sort((a, b) => a.lineNumber - b.lineNumber);
|
|
675
|
+
const result = new Map();
|
|
676
|
+
if (entries.length === 0)
|
|
677
|
+
return result;
|
|
678
|
+
const perLine = highlightSide(entries.map((e) => e.text).join("\n"), language, theme);
|
|
679
|
+
if (!perLine)
|
|
680
|
+
return result;
|
|
681
|
+
for (let i = 0; i < entries.length; i++) {
|
|
682
|
+
result.set(entries[i].lineNumber, (_a = perLine[i]) !== null && _a !== void 0 ? _a : []);
|
|
683
|
+
}
|
|
684
|
+
return result;
|
|
685
|
+
};
|
|
686
|
+
/**
|
|
687
|
+
* Resolves the active highlight theme (built-in light/dark, with any
|
|
688
|
+
* `highlightTheme` overrides merged on top).
|
|
689
|
+
*/
|
|
690
|
+
this.resolveHighlightTheme = () => {
|
|
691
|
+
const base = this.props.useDarkTheme ? defaultDarkHighlightTheme : defaultLightHighlightTheme;
|
|
692
|
+
return this.props.highlightTheme ? Object.assign(Object.assign({}, base), this.props.highlightTheme) : base;
|
|
693
|
+
};
|
|
694
|
+
/**
|
|
695
|
+
* Computes syntax-highlight tokens for both sides when `highlightLanguage` is
|
|
696
|
+
* set, storing them in state. Lazily loads the grammar, is memoised by
|
|
697
|
+
* (diff, language, theme, dark) so it is cheap to call repeatedly, and clears
|
|
698
|
+
* the result when highlighting is disabled or the language is unavailable.
|
|
699
|
+
*/
|
|
700
|
+
this.updateHighlight = async () => {
|
|
701
|
+
var _a, _b, _c, _d;
|
|
702
|
+
const { highlightLanguage } = this.props;
|
|
703
|
+
if (!highlightLanguage) {
|
|
704
|
+
if (this.state.highlightResult)
|
|
705
|
+
this.setState({ highlightResult: null });
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const diffKey = this.getMemoisedKey();
|
|
709
|
+
const diffResult = this.state.computedDiffResult[diffKey];
|
|
710
|
+
// Diff not ready yet — memoisedCompute re-invokes updateHighlight when it is.
|
|
711
|
+
if (!diffResult)
|
|
712
|
+
return;
|
|
713
|
+
const dark = (_a = this.props.useDarkTheme) !== null && _a !== void 0 ? _a : false;
|
|
714
|
+
const key = `${diffKey}::${highlightLanguage}::${dark}::${JSON.stringify((_b = this.props.highlightTheme) !== null && _b !== void 0 ? _b : null)}`;
|
|
715
|
+
if (((_c = this.state.highlightResult) === null || _c === void 0 ? void 0 : _c.key) === key)
|
|
716
|
+
return;
|
|
717
|
+
const canonical = await ensureLanguage(highlightLanguage);
|
|
718
|
+
// Bail if props changed while the grammar was loading.
|
|
719
|
+
if (this.props.highlightLanguage !== highlightLanguage || this.getMemoisedKey() !== diffKey)
|
|
720
|
+
return;
|
|
721
|
+
if (!canonical) {
|
|
722
|
+
if (this.state.highlightResult)
|
|
723
|
+
this.setState({ highlightResult: null });
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (!this.highlightPrecedenceWarned &&
|
|
727
|
+
this.props.renderContent &&
|
|
728
|
+
typeof process !== "undefined" &&
|
|
729
|
+
((_d = process.env) === null || _d === void 0 ? void 0 : _d.NODE_ENV) !== "production") {
|
|
730
|
+
this.highlightPrecedenceWarned = true;
|
|
731
|
+
console.warn("[react-diff-viewer] `highlightLanguage` takes precedence over `renderContent`; `renderContent` is ignored for line content while highlighting is active.");
|
|
732
|
+
}
|
|
733
|
+
const theme = this.resolveHighlightTheme();
|
|
734
|
+
const left = this.buildSideTokens(diffResult.lineInformation, "left", canonical, theme);
|
|
735
|
+
const right = this.buildSideTokens(diffResult.lineInformation, "right", canonical, theme);
|
|
736
|
+
this.setState({ highlightResult: { key, left, right } });
|
|
737
|
+
};
|
|
546
738
|
/**
|
|
547
739
|
* Handles scroll events on the scrollable container.
|
|
548
740
|
*
|
|
@@ -855,6 +1047,7 @@ class DiffViewer extends React.Component {
|
|
|
855
1047
|
charWidth: null,
|
|
856
1048
|
cumulativeOffsets: null,
|
|
857
1049
|
isScrolling: false,
|
|
1050
|
+
highlightResult: null,
|
|
858
1051
|
};
|
|
859
1052
|
}
|
|
860
1053
|
/**
|
|
@@ -978,6 +1171,12 @@ class DiffViewer extends React.Component {
|
|
|
978
1171
|
this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: true, visibleStartRow: 0, cumulativeOffsets: null })));
|
|
979
1172
|
this.memoisedCompute();
|
|
980
1173
|
}
|
|
1174
|
+
else if (prevProps.highlightLanguage !== this.props.highlightLanguage ||
|
|
1175
|
+
prevProps.highlightTheme !== this.props.highlightTheme ||
|
|
1176
|
+
prevProps.useDarkTheme !== this.props.useDarkTheme) {
|
|
1177
|
+
// Highlighting inputs changed but the diff itself did not — just re-tokenise.
|
|
1178
|
+
this.updateHighlight();
|
|
1179
|
+
}
|
|
981
1180
|
}
|
|
982
1181
|
componentDidMount() {
|
|
983
1182
|
this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: true })));
|
|
@@ -1021,3 +1220,5 @@ DiffViewer.ESTIMATED_ROW_HEIGHT = 19;
|
|
|
1021
1220
|
export default DiffViewer;
|
|
1022
1221
|
export { DiffMethod };
|
|
1023
1222
|
export { default as computeStyles } from "./styles.js";
|
|
1223
|
+
export { defaultLightThemeVariables, defaultDarkThemeVariables, } from "./styles.js";
|
|
1224
|
+
export { defaultLightHighlightTheme, defaultDarkHighlightTheme, } from "./highlight-theme.js";
|
package/lib/cjs/src/styles.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export interface ReactDiffViewerStyles {
|
|
|
11
11
|
gutter: string;
|
|
12
12
|
highlightedLine: string;
|
|
13
13
|
lineNumber: string;
|
|
14
|
+
contentFlex: string;
|
|
15
|
+
lineIndent: string;
|
|
16
|
+
lineBody: string;
|
|
14
17
|
marker: string;
|
|
15
18
|
wordDiff: string;
|
|
16
19
|
wordAdded: string;
|
|
@@ -98,5 +101,7 @@ export interface ReactDiffViewerStylesOverride {
|
|
|
98
101
|
splitView?: Interpolation;
|
|
99
102
|
allExpandButton?: Interpolation;
|
|
100
103
|
}
|
|
104
|
+
export declare const defaultLightThemeVariables: Readonly<Required<ReactDiffViewerStylesVariables>>;
|
|
105
|
+
export declare const defaultDarkThemeVariables: Readonly<Required<ReactDiffViewerStylesVariables>>;
|
|
101
106
|
declare const _default: (styleOverride: ReactDiffViewerStylesOverride, useDarkTheme?: boolean, nonce?: string) => ReactDiffViewerStyles;
|
|
102
107
|
export default _default;
|