@bochenw/react-diff-viewer-continued 4.3.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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +521 -0
  3. package/lib/cjs/src/comment-row.d.ts +33 -0
  4. package/lib/cjs/src/comment-row.js +58 -0
  5. package/lib/cjs/src/compute-hidden-blocks.d.ts +13 -0
  6. package/lib/cjs/src/compute-hidden-blocks.js +36 -0
  7. package/lib/cjs/src/compute-lines.d.ts +68 -0
  8. package/lib/cjs/src/compute-lines.js +559 -0
  9. package/lib/cjs/src/computeWorker.d.ts +1 -0
  10. package/lib/cjs/src/computeWorker.js +10 -0
  11. package/lib/cjs/src/diff-row.d.ts +40 -0
  12. package/lib/cjs/src/diff-row.js +136 -0
  13. package/lib/cjs/src/expand.d.ts +1 -0
  14. package/lib/cjs/src/expand.js +4 -0
  15. package/lib/cjs/src/fold.d.ts +1 -0
  16. package/lib/cjs/src/fold.js +4 -0
  17. package/lib/cjs/src/index.d.ts +236 -0
  18. package/lib/cjs/src/index.js +783 -0
  19. package/lib/cjs/src/line-number-prefix.d.ts +4 -0
  20. package/lib/cjs/src/line-number-prefix.js +5 -0
  21. package/lib/cjs/src/render-word-diff.d.ts +22 -0
  22. package/lib/cjs/src/render-word-diff.js +212 -0
  23. package/lib/cjs/src/skipped-line-indicator.d.ts +29 -0
  24. package/lib/cjs/src/skipped-line-indicator.js +29 -0
  25. package/lib/cjs/src/styles.d.ts +102 -0
  26. package/lib/cjs/src/styles.js +430 -0
  27. package/lib/cjs/src/workerBundle.d.ts +5 -0
  28. package/lib/cjs/src/workerBundle.js +7 -0
  29. package/lib/esm/src/comment-row.js +58 -0
  30. package/lib/esm/src/compute-hidden-blocks.js +36 -0
  31. package/lib/esm/src/compute-lines.js +559 -0
  32. package/lib/esm/src/computeWorker.js +10 -0
  33. package/lib/esm/src/diff-row.js +136 -0
  34. package/lib/esm/src/expand.js +4 -0
  35. package/lib/esm/src/fold.js +4 -0
  36. package/lib/esm/src/index.js +780 -0
  37. package/lib/esm/src/line-number-prefix.js +5 -0
  38. package/lib/esm/src/render-word-diff.js +211 -0
  39. package/lib/esm/src/skipped-line-indicator.js +29 -0
  40. package/lib/esm/src/styles.js +431 -0
  41. package/lib/esm/src/workerBundle.js +7 -0
  42. package/package.json +90 -0
@@ -0,0 +1,4 @@
1
+ export declare enum LineNumberPrefix {
2
+ LEFT = "L",
3
+ RIGHT = "R"
4
+ }
@@ -0,0 +1,5 @@
1
+ export var LineNumberPrefix;
2
+ (function (LineNumberPrefix) {
3
+ LineNumberPrefix["LEFT"] = "L";
4
+ LineNumberPrefix["RIGHT"] = "R";
5
+ })(LineNumberPrefix || (LineNumberPrefix = {}));
@@ -0,0 +1,22 @@
1
+ import type { JSX, ReactElement } from "react";
2
+ import { type DiffInformation, DiffMethod } from "./compute-lines.js";
3
+ import type { ReactDiffViewerStyles } from "./styles.js";
4
+ import type { Change } from "diff";
5
+ /**
6
+ * Applies diff styling (ins/del tags) to pre-highlighted HTML by walking through
7
+ * the HTML and wrapping text portions based on character positions in the diff.
8
+ */
9
+ export declare function applyDiffToHighlightedHtml(html: string, diffArray: DiffInformation[], styles: {
10
+ wordDiff: string;
11
+ wordAdded: string;
12
+ wordRemoved: string;
13
+ }): string;
14
+ /**
15
+ * Maps over the word diff and constructs the required React elements to show word diff.
16
+ *
17
+ * @param diffArray Word diff information derived from line information.
18
+ * @param styles Computed styles for the diff viewer.
19
+ * @param compareMethod The diff comparison method being used.
20
+ * @param renderer Optional renderer to format diff words. Useful for syntax highlighting.
21
+ */
22
+ export declare function renderWordDiff(diffArray: DiffInformation[], styles: ReactDiffViewerStyles, compareMethod: DiffMethod | ((oldStr: string, newStr: string) => Change[]), renderer?: (chunk: string) => JSX.Element): ReactElement[];
@@ -0,0 +1,212 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import cn from "classnames";
3
+ import * as React from "react";
4
+ import { DiffMethod, DiffType, } from "./compute-lines.js";
5
+ /**
6
+ * Applies diff styling (ins/del tags) to pre-highlighted HTML by walking through
7
+ * the HTML and wrapping text portions based on character positions in the diff.
8
+ */
9
+ export function applyDiffToHighlightedHtml(html, diffArray, styles) {
10
+ const ranges = [];
11
+ let pos = 0;
12
+ for (const diff of diffArray) {
13
+ const value = typeof diff.value === "string" ? diff.value : "";
14
+ if (value.length > 0) {
15
+ ranges.push({ start: pos, end: pos + value.length, type: diff.type });
16
+ pos += value.length;
17
+ }
18
+ }
19
+ const segments = [];
20
+ let i = 0;
21
+ while (i < html.length) {
22
+ if (html[i] === "<") {
23
+ const tagEnd = html.indexOf(">", i);
24
+ if (tagEnd === -1) {
25
+ // Malformed HTML, treat rest as text
26
+ segments.push({ type: "text", content: html.slice(i) });
27
+ break;
28
+ }
29
+ segments.push({ type: "tag", content: html.slice(i, tagEnd + 1) });
30
+ i = tagEnd + 1;
31
+ }
32
+ else {
33
+ // Find the next tag or end of string
34
+ let textEnd = html.indexOf("<", i);
35
+ if (textEnd === -1)
36
+ textEnd = html.length;
37
+ segments.push({ type: "text", content: html.slice(i, textEnd) });
38
+ i = textEnd;
39
+ }
40
+ }
41
+ // Helper to decode HTML entities for character counting
42
+ function decodeEntities(text) {
43
+ return text
44
+ .replace(/&lt;/g, "<")
45
+ .replace(/&gt;/g, ">")
46
+ .replace(/&amp;/g, "&")
47
+ .replace(/&quot;/g, '"')
48
+ .replace(/&#39;/g, "'")
49
+ .replace(/&#x27;/g, "'")
50
+ .replace(/&nbsp;/g, "\u00A0");
51
+ }
52
+ // Helper to get the wrapper tag for a diff type
53
+ function getWrapper(type) {
54
+ if (type === DiffType.ADDED) {
55
+ return {
56
+ open: `<ins class="${styles.wordDiff} ${styles.wordAdded}">`,
57
+ close: "</ins>",
58
+ };
59
+ }
60
+ if (type === DiffType.REMOVED) {
61
+ return {
62
+ open: `<del class="${styles.wordDiff} ${styles.wordRemoved}">`,
63
+ close: "</del>",
64
+ };
65
+ }
66
+ return {
67
+ open: `<span class="${styles.wordDiff}">`,
68
+ close: "</span>",
69
+ };
70
+ }
71
+ // Process segments, tracking text position
72
+ let textPos = 0;
73
+ let result = "";
74
+ for (const segment of segments) {
75
+ if (segment.type === "tag") {
76
+ result += segment.content;
77
+ }
78
+ else {
79
+ // Text segment - we need to split it according to diff ranges
80
+ const text = segment.content;
81
+ const decodedText = decodeEntities(text);
82
+ // Walk through the text, character by character (in decoded form)
83
+ // but output the original encoded form
84
+ let localDecodedPos = 0;
85
+ let localEncodedPos = 0;
86
+ while (localDecodedPos < decodedText.length) {
87
+ const globalPos = textPos + localDecodedPos;
88
+ // Find the range that covers this position
89
+ const range = ranges.find((r) => globalPos >= r.start && globalPos < r.end);
90
+ if (!range) {
91
+ // No range covers this position (shouldn't happen, but be safe)
92
+ // Just output the character
93
+ const char = text[localEncodedPos];
94
+ result += char;
95
+ localEncodedPos++;
96
+ localDecodedPos++;
97
+ continue;
98
+ }
99
+ // How many decoded characters until the end of this range?
100
+ const charsUntilRangeEnd = range.end - globalPos;
101
+ // How many decoded characters until the end of this text segment?
102
+ const charsUntilTextEnd = decodedText.length - localDecodedPos;
103
+ // Take the minimum
104
+ const charsToTake = Math.min(charsUntilRangeEnd, charsUntilTextEnd);
105
+ // Now we need to find the corresponding encoded substring
106
+ // Walk through encoded text, counting decoded characters
107
+ let encodedChunkEnd = localEncodedPos;
108
+ let decodedCount = 0;
109
+ while (decodedCount < charsToTake && encodedChunkEnd < text.length) {
110
+ if (text[encodedChunkEnd] === "&") {
111
+ // Find entity end
112
+ const entityEnd = text.indexOf(";", encodedChunkEnd);
113
+ if (entityEnd !== -1 && entityEnd - encodedChunkEnd < 10) {
114
+ encodedChunkEnd = entityEnd + 1;
115
+ }
116
+ else {
117
+ encodedChunkEnd++;
118
+ }
119
+ }
120
+ else {
121
+ encodedChunkEnd++;
122
+ }
123
+ decodedCount++;
124
+ }
125
+ const chunk = text.slice(localEncodedPos, encodedChunkEnd);
126
+ const wrapper = getWrapper(range.type);
127
+ if (wrapper) {
128
+ result += wrapper.open + chunk + wrapper.close;
129
+ }
130
+ else {
131
+ result += chunk;
132
+ }
133
+ localEncodedPos = encodedChunkEnd;
134
+ localDecodedPos += charsToTake;
135
+ }
136
+ textPos += decodedText.length;
137
+ }
138
+ }
139
+ return result;
140
+ }
141
+ /**
142
+ * Checks if the current compare method should show word-level highlighting.
143
+ */
144
+ function shouldHighlightWordDiff(compareMethod) {
145
+ return (compareMethod === DiffMethod.CHARS ||
146
+ compareMethod === DiffMethod.WORDS ||
147
+ compareMethod === DiffMethod.WORDS_WITH_SPACE ||
148
+ compareMethod === DiffMethod.JSON ||
149
+ compareMethod === DiffMethod.YAML);
150
+ }
151
+ /**
152
+ * Maps over the word diff and constructs the required React elements to show word diff.
153
+ *
154
+ * @param diffArray Word diff information derived from line information.
155
+ * @param styles Computed styles for the diff viewer.
156
+ * @param compareMethod The diff comparison method being used.
157
+ * @param renderer Optional renderer to format diff words. Useful for syntax highlighting.
158
+ */
159
+ export function renderWordDiff(diffArray, styles, compareMethod, renderer) {
160
+ var _a, _b;
161
+ const showHighlight = shouldHighlightWordDiff(compareMethod);
162
+ // Reconstruct the full line from diff chunks
163
+ const fullLine = diffArray
164
+ .map((d) => (typeof d.value === "string" ? d.value : ""))
165
+ .join("");
166
+ // For very long lines (>500 chars), skip fancy processing - just render plain text
167
+ // without word-level highlighting to avoid performance issues
168
+ const MAX_LINE_LENGTH = 500;
169
+ if (fullLine.length > MAX_LINE_LENGTH) {
170
+ return [_jsx("span", { children: fullLine }, "long-line")];
171
+ }
172
+ // If we have a renderer, try to highlight the full line first,
173
+ // then apply diff styling to preserve proper tokenization.
174
+ if (renderer) {
175
+ // Get the syntax-highlighted content
176
+ const highlighted = renderer(fullLine);
177
+ // Check if the renderer uses dangerouslySetInnerHTML (common with Prism, highlight.js, etc.)
178
+ 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;
179
+ if (typeof htmlContent === "string") {
180
+ // Apply diff styling to the highlighted HTML
181
+ const styledHtml = applyDiffToHighlightedHtml(htmlContent, diffArray, {
182
+ wordDiff: styles.wordDiff,
183
+ wordAdded: showHighlight ? styles.wordAdded : "",
184
+ wordRemoved: showHighlight ? styles.wordRemoved : "",
185
+ });
186
+ // Clone the element with the modified HTML
187
+ return [
188
+ React.cloneElement(highlighted, {
189
+ key: "highlighted-diff",
190
+ dangerouslySetInnerHTML: { __html: styledHtml },
191
+ }),
192
+ ];
193
+ }
194
+ // Renderer doesn't use dangerouslySetInnerHTML - fall through to per-chunk rendering
195
+ }
196
+ // Fallback: render each chunk separately (used for JSON/YAML or non-HTML renderers)
197
+ return diffArray.map((wordDiff, i) => {
198
+ let content;
199
+ if (typeof wordDiff.value === "string") {
200
+ content = wordDiff.value;
201
+ }
202
+ else {
203
+ // If wordDiff.value is DiffInformation[], we don't handle it. See c0c99f5712.
204
+ content = undefined;
205
+ }
206
+ return wordDiff.type === DiffType.ADDED ? (_jsx("ins", { className: cn(styles.wordDiff, {
207
+ [styles.wordAdded]: showHighlight,
208
+ }), children: content }, i)) : wordDiff.type === DiffType.REMOVED ? (_jsx("del", { className: cn(styles.wordDiff, {
209
+ [styles.wordRemoved]: showHighlight,
210
+ }), children: content }, i)) : (_jsx("span", { className: cn(styles.wordDiff), children: content }, i));
211
+ });
212
+ }
@@ -0,0 +1,29 @@
1
+ import * as React from "react";
2
+ import type { ReactElement } from "react";
3
+ import { type DiffInformation, DiffType } from "./compute-lines.js";
4
+ import type { ReactDiffViewerStyles } from "./styles.js";
5
+ import { LineNumberPrefix } from "./line-number-prefix.js";
6
+ export interface SkippedLineIndicatorProps {
7
+ num: number;
8
+ blockNumber: number;
9
+ leftBlockLineNumber: number;
10
+ rightBlockLineNumber: number;
11
+ hideLineNumbers: boolean;
12
+ splitView: boolean;
13
+ styles: ReactDiffViewerStyles;
14
+ onBlockClick: (id: number) => void;
15
+ codeFoldMessageRenderer?: (totalFoldedLines: number, leftStartLineNumber: number, rightStartLineNumber: number) => ReactElement;
16
+ renderGutter?: (data: {
17
+ lineNumber: number;
18
+ type: DiffType;
19
+ prefix: LineNumberPrefix;
20
+ value: string | DiffInformation[];
21
+ additionalLineNumber: number;
22
+ additionalPrefix: LineNumberPrefix;
23
+ styles: ReactDiffViewerStyles;
24
+ }) => ReactElement;
25
+ }
26
+ /**
27
+ * Memoized component that renders the code fold / skipped line indicator row.
28
+ */
29
+ export declare const SkippedLineIndicator: React.NamedExoticComponent<SkippedLineIndicatorProps>;
@@ -0,0 +1,29 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import cn from "classnames";
3
+ import * as React from "react";
4
+ import { Expand } from "./expand.js";
5
+ /**
6
+ * Custom equality function for React.memo — skips comparing callback/render props.
7
+ */
8
+ function skippedLineIndicatorPropsAreEqual(prev, next) {
9
+ return (prev.num === next.num &&
10
+ prev.blockNumber === next.blockNumber &&
11
+ prev.leftBlockLineNumber === next.leftBlockLineNumber &&
12
+ prev.rightBlockLineNumber === next.rightBlockLineNumber &&
13
+ prev.hideLineNumbers === next.hideLineNumbers &&
14
+ prev.splitView === next.splitView &&
15
+ prev.styles === next.styles);
16
+ }
17
+ /**
18
+ * Memoized component that renders the code fold / skipped line indicator row.
19
+ */
20
+ export const SkippedLineIndicator = React.memo(function SkippedLineIndicator({ num, blockNumber, leftBlockLineNumber, rightBlockLineNumber, hideLineNumbers, splitView, styles, onBlockClick, codeFoldMessageRenderer, renderGutter, }) {
21
+ const handleClick = () => onBlockClick(blockNumber);
22
+ const message = codeFoldMessageRenderer ? (codeFoldMessageRenderer(num, leftBlockLineNumber, rightBlockLineNumber)) : (_jsxs("span", { className: styles.codeFoldContent, children: ["@@ -", leftBlockLineNumber - num, ",", num, " +", rightBlockLineNumber - num, ",", num, " @@"] }));
23
+ const content = (_jsx("td", { className: styles.codeFoldContentContainer, children: _jsx("button", { type: "button", className: styles.codeFoldExpandButton, onClick: handleClick, tabIndex: 0, children: message }) }));
24
+ const isUnifiedViewWithoutLineNumbers = !splitView && !hideLineNumbers;
25
+ const expandGutter = (_jsx("td", { className: styles.codeFoldGutter, children: _jsx(Expand, {}) }));
26
+ return (_jsxs("tr", { className: styles.codeFold, onClick: handleClick, role: "button", tabIndex: 0, children: [!hideLineNumbers && expandGutter, renderGutter ? (_jsx("td", { className: styles.codeFoldGutter })) : null, _jsx("td", { className: cn({
27
+ [styles.codeFoldGutter]: isUnifiedViewWithoutLineNumbers,
28
+ }) }), isUnifiedViewWithoutLineNumbers ? (_jsxs(React.Fragment, { children: [_jsx("td", {}), content] })) : (_jsxs(React.Fragment, { children: [content, renderGutter ? _jsx("td", {}) : null, _jsx("td", {}), _jsx("td", {}), !hideLineNumbers ? _jsx("td", {}) : null] }))] }, `${leftBlockLineNumber}-${rightBlockLineNumber}`));
29
+ }, skippedLineIndicatorPropsAreEqual);
@@ -0,0 +1,102 @@
1
+ import type { Interpolation } from "@emotion/react";
2
+ export interface ReactDiffViewerStyles {
3
+ diffContainer?: string;
4
+ diffRemoved?: string;
5
+ diffAdded?: string;
6
+ diffChanged?: string;
7
+ line?: string;
8
+ highlightedGutter?: string;
9
+ contentText?: string;
10
+ lineContent?: string;
11
+ gutter?: string;
12
+ highlightedLine?: string;
13
+ lineNumber?: string;
14
+ marker?: string;
15
+ wordDiff?: string;
16
+ wordAdded?: string;
17
+ wordRemoved?: string;
18
+ codeFoldGutter?: string;
19
+ codeFoldExpandButton?: string;
20
+ summary?: string;
21
+ codeFoldContentContainer?: string;
22
+ emptyGutter?: string;
23
+ emptyLine?: string;
24
+ codeFold?: string;
25
+ stickyHeader?: string;
26
+ columnHeaders?: string;
27
+ titleBlock?: string;
28
+ content?: string;
29
+ column?: string;
30
+ noSelect?: string;
31
+ noWrap?: string;
32
+ splitView?: string;
33
+ allExpandButton?: string;
34
+ commentRow?: string;
35
+ commentCell?: string;
36
+ [key: string]: string | undefined;
37
+ }
38
+ export interface ReactDiffViewerStylesVariables {
39
+ diffViewerBackground?: string;
40
+ diffViewerTitleBackground?: string;
41
+ diffViewerColor?: string;
42
+ diffViewerTitleColor?: string;
43
+ diffViewerTitleBorderColor?: string;
44
+ addedBackground?: string;
45
+ addedColor?: string;
46
+ removedBackground?: string;
47
+ removedColor?: string;
48
+ changedBackground?: string;
49
+ wordAddedBackground?: string;
50
+ wordRemovedBackground?: string;
51
+ addedGutterBackground?: string;
52
+ removedGutterBackground?: string;
53
+ gutterBackground?: string;
54
+ gutterBackgroundDark?: string;
55
+ highlightBackground?: string;
56
+ highlightGutterBackground?: string;
57
+ codeFoldGutterBackground?: string;
58
+ codeFoldBackground?: string;
59
+ emptyLineBackground?: string;
60
+ gutterColor?: string;
61
+ addedGutterColor?: string;
62
+ removedGutterColor?: string;
63
+ codeFoldContentColor?: string;
64
+ }
65
+ export interface ReactDiffViewerStylesOverride {
66
+ variables?: {
67
+ dark?: ReactDiffViewerStylesVariables;
68
+ light?: ReactDiffViewerStylesVariables;
69
+ };
70
+ diffContainer?: Interpolation;
71
+ diffRemoved?: Interpolation;
72
+ diffAdded?: Interpolation;
73
+ diffChanged?: Interpolation;
74
+ marker?: Interpolation;
75
+ emptyGutter?: Interpolation;
76
+ highlightedLine?: Interpolation;
77
+ lineNumber?: Interpolation;
78
+ highlightedGutter?: Interpolation;
79
+ contentText?: Interpolation;
80
+ gutter?: Interpolation;
81
+ lineContent?: Interpolation;
82
+ line?: Interpolation;
83
+ wordDiff?: Interpolation;
84
+ wordAdded?: Interpolation;
85
+ wordRemoved?: Interpolation;
86
+ codeFoldGutter?: Interpolation;
87
+ codeFoldExpandButton?: Interpolation;
88
+ summary?: Interpolation;
89
+ codeFoldContentContainer?: Interpolation;
90
+ codeFold?: Interpolation;
91
+ emptyLine?: Interpolation;
92
+ content?: Interpolation;
93
+ noSelect?: Interpolation;
94
+ column?: Interpolation;
95
+ titleBlock?: Interpolation;
96
+ splitView?: Interpolation;
97
+ allExpandButton?: Interpolation;
98
+ commentRow?: Interpolation;
99
+ commentCell?: Interpolation;
100
+ }
101
+ declare const _default: (styleOverride: ReactDiffViewerStylesOverride, useDarkTheme?: boolean, nonce?: string) => ReactDiffViewerStyles;
102
+ export default _default;