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.
- package/.claude/settings.local.json +8 -1
- package/.github/workflows/release.yml +3 -0
- package/CHANGELOG.md +13 -0
- package/lib/cjs/src/compute-lines.d.ts +0 -12
- package/lib/cjs/src/compute-lines.js +58 -9
- package/lib/cjs/src/index.d.ts +44 -0
- package/lib/cjs/src/index.js +270 -24
- package/lib/cjs/src/styles.d.ts +1 -0
- package/lib/cjs/src/styles.js +15 -0
- package/lib/cjs/src/workerBundle.d.ts +5 -0
- package/lib/cjs/src/workerBundle.js +7 -0
- package/lib/esm/src/compute-lines.js +58 -9
- package/lib/esm/src/index.js +265 -23
- package/lib/esm/src/styles.js +15 -0
- package/lib/esm/src/workerBundle.js +7 -0
- package/package.json +4 -2
- package/scripts/build-worker.js +50 -0
package/lib/esm/src/index.js
CHANGED
|
@@ -151,6 +151,11 @@ class DiffViewer extends React.Component {
|
|
|
151
151
|
styles;
|
|
152
152
|
// Cache for on-demand word diff computation
|
|
153
153
|
wordDiffCache = new Map();
|
|
154
|
+
// Refs for measuring content column width and character width
|
|
155
|
+
contentColumnRef = React.createRef();
|
|
156
|
+
charMeasureRef = React.createRef();
|
|
157
|
+
stickyHeaderRef = React.createRef();
|
|
158
|
+
resizeObserver = null;
|
|
154
159
|
static defaultProps = {
|
|
155
160
|
oldValue: "",
|
|
156
161
|
newValue: "",
|
|
@@ -174,7 +179,10 @@ class DiffViewer extends React.Component {
|
|
|
174
179
|
scrollableContainerRef: React.createRef(),
|
|
175
180
|
computedDiffResult: {},
|
|
176
181
|
isLoading: false,
|
|
177
|
-
visibleStartRow: 0
|
|
182
|
+
visibleStartRow: 0,
|
|
183
|
+
contentColumnWidth: null,
|
|
184
|
+
charWidth: null,
|
|
185
|
+
cumulativeOffsets: null,
|
|
178
186
|
};
|
|
179
187
|
}
|
|
180
188
|
/**
|
|
@@ -227,8 +235,130 @@ class DiffViewer extends React.Component {
|
|
|
227
235
|
onBlockExpand = (id) => {
|
|
228
236
|
const prevState = this.state.expandedBlocks.slice();
|
|
229
237
|
prevState.push(id);
|
|
230
|
-
this.setState({
|
|
231
|
-
|
|
238
|
+
this.setState({ expandedBlocks: prevState }, () => this.recalculateOffsets());
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Gets the height of the sticky header, if present.
|
|
242
|
+
*/
|
|
243
|
+
getStickyHeaderHeight() {
|
|
244
|
+
return this.stickyHeaderRef.current?.offsetHeight || 0;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Measures the width of a single character in the monospace font.
|
|
248
|
+
* Falls back to 7.2px if measurement fails.
|
|
249
|
+
*/
|
|
250
|
+
measureCharWidth() {
|
|
251
|
+
const span = this.charMeasureRef.current;
|
|
252
|
+
if (!span)
|
|
253
|
+
return 7.2; // fallback
|
|
254
|
+
return span.getBoundingClientRect().width || 7.2;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Measures the available width for content in a content column.
|
|
258
|
+
* Falls back to estimating from container width if direct measurement fails.
|
|
259
|
+
*/
|
|
260
|
+
measureContentColumnWidth() {
|
|
261
|
+
// Try direct measurement first
|
|
262
|
+
const cell = this.contentColumnRef.current;
|
|
263
|
+
if (cell && cell.clientWidth > 0) {
|
|
264
|
+
const style = window.getComputedStyle(cell);
|
|
265
|
+
const padding = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
|
|
266
|
+
const width = cell.clientWidth - padding;
|
|
267
|
+
if (width > 0)
|
|
268
|
+
return width;
|
|
269
|
+
}
|
|
270
|
+
// Fallback: estimate from container width
|
|
271
|
+
// In split view: container has 2 content columns + gutters (50px each) + markers (28px each)
|
|
272
|
+
// In unified view: 1 content column + 2 gutters + 1 marker
|
|
273
|
+
const container = this.state.scrollableContainerRef.current;
|
|
274
|
+
if (!container || container.clientWidth <= 0)
|
|
275
|
+
return null;
|
|
276
|
+
const containerWidth = container.clientWidth;
|
|
277
|
+
const gutterWidth = this.props.hideLineNumbers ? 0 : 50;
|
|
278
|
+
const markerWidth = 28;
|
|
279
|
+
const gutterCount = this.props.splitView ? 2 : 2; // left gutter(s)
|
|
280
|
+
const markerCount = this.props.splitView ? 2 : 1;
|
|
281
|
+
const contentColumns = this.props.splitView ? 2 : 1;
|
|
282
|
+
const fixedWidth = gutterCount * gutterWidth + markerCount * markerWidth;
|
|
283
|
+
const availableWidth = containerWidth - fixedWidth;
|
|
284
|
+
return Math.max(100, availableWidth / contentColumns); // minimum 100px
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Gets the text length from a value that may be a string or DiffInformation array.
|
|
288
|
+
*/
|
|
289
|
+
getTextLength(value) {
|
|
290
|
+
if (!value)
|
|
291
|
+
return 0;
|
|
292
|
+
if (typeof value === 'string')
|
|
293
|
+
return value.length;
|
|
294
|
+
return value.reduce((sum, d) => sum + (typeof d.value === 'string' ? d.value.length : 0), 0);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Builds cumulative vertical offsets for each line based on character count and column width.
|
|
298
|
+
* This allows accurate scroll position calculations with variable row heights.
|
|
299
|
+
*/
|
|
300
|
+
buildCumulativeOffsets(lineInformation, lineBlocks, blocks, expandedBlocks, showDiffOnly, charWidth, columnWidth, splitView) {
|
|
301
|
+
const offsets = [0];
|
|
302
|
+
const seenBlocks = new Set();
|
|
303
|
+
for (let i = 0; i < lineInformation.length; i++) {
|
|
304
|
+
const line = lineInformation[i];
|
|
305
|
+
if (showDiffOnly) {
|
|
306
|
+
const blockIndex = lineBlocks[i];
|
|
307
|
+
if (blockIndex !== undefined && !expandedBlocks.includes(blockIndex)) {
|
|
308
|
+
const isLastLine = blocks[blockIndex].endLine === i;
|
|
309
|
+
if (!seenBlocks.has(blockIndex) && isLastLine) {
|
|
310
|
+
seenBlocks.add(blockIndex);
|
|
311
|
+
offsets.push(offsets[offsets.length - 1] + DiffViewer.ESTIMATED_ROW_HEIGHT);
|
|
312
|
+
}
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Calculate visual rows for this line
|
|
317
|
+
const leftLen = line.left?.value ? this.getTextLength(line.left.value) : 0;
|
|
318
|
+
const rightLen = line.right?.value ? this.getTextLength(line.right.value) : 0;
|
|
319
|
+
const maxLen = splitView ? Math.max(leftLen, rightLen) : (leftLen || rightLen);
|
|
320
|
+
const charsPerRow = Math.floor(columnWidth / charWidth);
|
|
321
|
+
const visualRows = charsPerRow > 0 ? Math.max(1, Math.ceil(maxLen / charsPerRow)) : 1;
|
|
322
|
+
offsets.push(offsets[offsets.length - 1] + visualRows * DiffViewer.ESTIMATED_ROW_HEIGHT);
|
|
323
|
+
}
|
|
324
|
+
return offsets;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Binary search to find the line index at a given scroll offset.
|
|
328
|
+
*/
|
|
329
|
+
findLineAtOffset(scrollTop, offsets) {
|
|
330
|
+
let low = 0;
|
|
331
|
+
let high = offsets.length - 2;
|
|
332
|
+
while (low < high) {
|
|
333
|
+
const mid = Math.floor((low + high + 1) / 2);
|
|
334
|
+
if (offsets[mid] <= scrollTop) {
|
|
335
|
+
low = mid;
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
high = mid - 1;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return low;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Recalculates cumulative offsets based on current measurements.
|
|
345
|
+
* Called on resize and when blocks are expanded/collapsed.
|
|
346
|
+
*/
|
|
347
|
+
recalculateOffsets = () => {
|
|
348
|
+
if (!this.props.infiniteLoading)
|
|
349
|
+
return;
|
|
350
|
+
const columnWidth = this.measureContentColumnWidth();
|
|
351
|
+
const charWidth = this.measureCharWidth();
|
|
352
|
+
if (!columnWidth)
|
|
353
|
+
return;
|
|
354
|
+
const cacheKey = this.getMemoisedKey();
|
|
355
|
+
const { lineInformation, lineBlocks, blocks } = this.state.computedDiffResult[cacheKey] ?? {};
|
|
356
|
+
if (!lineInformation)
|
|
357
|
+
return;
|
|
358
|
+
const offsets = this.buildCumulativeOffsets(lineInformation, lineBlocks, blocks, this.state.expandedBlocks, this.props.showDiffOnly, charWidth, columnWidth, this.props.splitView);
|
|
359
|
+
this.setState({ cumulativeOffsets: offsets, contentColumnWidth: columnWidth, charWidth }, () => {
|
|
360
|
+
// Force a scroll position update to recalculate visible rows with new offsets
|
|
361
|
+
this.onScroll();
|
|
232
362
|
});
|
|
233
363
|
};
|
|
234
364
|
/**
|
|
@@ -271,10 +401,6 @@ class DiffViewer extends React.Component {
|
|
|
271
401
|
*/
|
|
272
402
|
renderWordDiff = (diffArray, renderer) => {
|
|
273
403
|
const showHighlight = this.shouldHighlightWordDiff();
|
|
274
|
-
const { compareMethod } = this.props;
|
|
275
|
-
// Don't apply syntax highlighting for JSON/YAML - their word diffs are computed
|
|
276
|
-
// on-demand from raw strings and syntax highlighting creates messy fragmented tokens.
|
|
277
|
-
const skipSyntaxHighlighting = compareMethod === DiffMethod.JSON || compareMethod === DiffMethod.YAML;
|
|
278
404
|
// Reconstruct the full line from diff chunks
|
|
279
405
|
const fullLine = diffArray
|
|
280
406
|
.map((d) => (typeof d.value === "string" ? d.value : ""))
|
|
@@ -285,9 +411,9 @@ class DiffViewer extends React.Component {
|
|
|
285
411
|
if (fullLine.length > MAX_LINE_LENGTH) {
|
|
286
412
|
return [_jsx("span", { children: fullLine }, "long-line")];
|
|
287
413
|
}
|
|
288
|
-
// If we have a renderer
|
|
289
|
-
//
|
|
290
|
-
if (renderer
|
|
414
|
+
// If we have a renderer, try to highlight the full line first,
|
|
415
|
+
// then apply diff styling to preserve proper tokenization.
|
|
416
|
+
if (renderer) {
|
|
291
417
|
// Get the syntax-highlighted content
|
|
292
418
|
const highlighted = renderer(fullLine);
|
|
293
419
|
// Check if the renderer uses dangerouslySetInnerHTML (common with Prism, highlight.js, etc.)
|
|
@@ -394,7 +520,7 @@ class DiffViewer extends React.Component {
|
|
|
394
520
|
[this.styles.diffRemoved]: removed,
|
|
395
521
|
[this.styles.diffChanged]: changed,
|
|
396
522
|
[this.styles.highlightedLine]: highlightLine,
|
|
397
|
-
}), children: _jsxs("pre", { children: [added && "+", removed && "-"] }) }), _jsx("td", { className: cn(this.styles.content, {
|
|
523
|
+
}), children: _jsxs("pre", { children: [added && "+", removed && "-"] }) }), _jsx("td", { ref: prefix === LineNumberPrefix.LEFT && !this.state.cumulativeOffsets ? this.contentColumnRef : undefined, className: cn(this.styles.content, {
|
|
398
524
|
[this.styles.emptyLine]: !content,
|
|
399
525
|
[this.styles.diffAdded]: added,
|
|
400
526
|
[this.styles.diffRemoved]: removed,
|
|
@@ -538,7 +664,13 @@ class DiffViewer extends React.Component {
|
|
|
538
664
|
...prev,
|
|
539
665
|
computedDiffResult: this.state.computedDiffResult,
|
|
540
666
|
isLoading: false,
|
|
541
|
-
}))
|
|
667
|
+
}), () => {
|
|
668
|
+
// Trigger offset recalculation after diff is computed and rendered
|
|
669
|
+
// Use requestAnimationFrame to ensure DOM is ready for measurement
|
|
670
|
+
if (this.props.infiniteLoading) {
|
|
671
|
+
requestAnimationFrame(() => this.recalculateOffsets());
|
|
672
|
+
}
|
|
673
|
+
});
|
|
542
674
|
};
|
|
543
675
|
// Estimated row height based on lineHeight: 1.6em with 12px base font
|
|
544
676
|
static ESTIMATED_ROW_HEIGHT = 19;
|
|
@@ -551,7 +683,13 @@ class DiffViewer extends React.Component {
|
|
|
551
683
|
const container = this.state.scrollableContainerRef.current;
|
|
552
684
|
if (!container || !this.props.infiniteLoading)
|
|
553
685
|
return;
|
|
554
|
-
|
|
686
|
+
// Account for sticky header height in scroll calculations
|
|
687
|
+
const headerHeight = this.getStickyHeaderHeight();
|
|
688
|
+
const contentScrollTop = Math.max(0, container.scrollTop - headerHeight);
|
|
689
|
+
const { cumulativeOffsets } = this.state;
|
|
690
|
+
const newStartRow = cumulativeOffsets
|
|
691
|
+
? this.findLineAtOffset(contentScrollTop, cumulativeOffsets)
|
|
692
|
+
: Math.floor(contentScrollTop / DiffViewer.ESTIMATED_ROW_HEIGHT);
|
|
555
693
|
// Only update state if the start row changed (avoid unnecessary re-renders)
|
|
556
694
|
if (newStartRow !== this.state.visibleStartRow) {
|
|
557
695
|
this.setState({ visibleStartRow: newStartRow });
|
|
@@ -562,7 +700,7 @@ class DiffViewer extends React.Component {
|
|
|
562
700
|
*/
|
|
563
701
|
renderDiff = () => {
|
|
564
702
|
const { splitView, infiniteLoading, showDiffOnly } = this.props;
|
|
565
|
-
const { computedDiffResult, expandedBlocks, visibleStartRow, scrollableContainerRef } = this.state;
|
|
703
|
+
const { computedDiffResult, expandedBlocks, visibleStartRow, scrollableContainerRef, cumulativeOffsets } = this.state;
|
|
566
704
|
const cacheKey = this.getMemoisedKey();
|
|
567
705
|
const { lineInformation = [], lineBlocks = [], blocks = [] } = computedDiffResult[cacheKey] ?? {};
|
|
568
706
|
// Calculate visible range for virtualization
|
|
@@ -571,9 +709,32 @@ class DiffViewer extends React.Component {
|
|
|
571
709
|
const buffer = 5; // render extra rows above/below viewport
|
|
572
710
|
if (infiniteLoading && scrollableContainerRef.current) {
|
|
573
711
|
const container = scrollableContainerRef.current;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
712
|
+
// Account for sticky header height in scroll calculations
|
|
713
|
+
const headerHeight = this.getStickyHeaderHeight();
|
|
714
|
+
const contentScrollTop = Math.max(0, container.scrollTop - headerHeight);
|
|
715
|
+
if (cumulativeOffsets) {
|
|
716
|
+
// Variable height mode: use binary search to find visible range
|
|
717
|
+
const totalHeight = cumulativeOffsets[cumulativeOffsets.length - 1] || 0;
|
|
718
|
+
const lastRowIndex = cumulativeOffsets.length - 2;
|
|
719
|
+
visibleRowStart = Math.max(0, this.findLineAtOffset(contentScrollTop, cumulativeOffsets) - buffer);
|
|
720
|
+
visibleRowEnd = this.findLineAtOffset(contentScrollTop + container.clientHeight, cumulativeOffsets) + buffer;
|
|
721
|
+
// IMPORTANT: The calculated offsets may overestimate row heights (based on char count),
|
|
722
|
+
// but actual CSS rendering might produce shorter rows. To prevent empty space,
|
|
723
|
+
// ensure we render at least enough rows to fill the viewport using ESTIMATED_ROW_HEIGHT
|
|
724
|
+
// as a conservative minimum.
|
|
725
|
+
const minRowsToFillViewport = Math.ceil(container.clientHeight / DiffViewer.ESTIMATED_ROW_HEIGHT);
|
|
726
|
+
visibleRowEnd = Math.max(visibleRowEnd, visibleRowStart + minRowsToFillViewport + buffer);
|
|
727
|
+
// Also ensure we render all rows when near the bottom
|
|
728
|
+
if (contentScrollTop + container.clientHeight >= totalHeight - buffer * DiffViewer.ESTIMATED_ROW_HEIGHT) {
|
|
729
|
+
visibleRowEnd = lastRowIndex + buffer;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
// Fixed height fallback
|
|
734
|
+
const viewportRows = Math.ceil(container.clientHeight / DiffViewer.ESTIMATED_ROW_HEIGHT);
|
|
735
|
+
visibleRowStart = Math.max(0, visibleStartRow - buffer);
|
|
736
|
+
visibleRowEnd = visibleStartRow + viewportRows + buffer;
|
|
737
|
+
}
|
|
577
738
|
}
|
|
578
739
|
// First pass: build a map of lineIndex -> renderedRowIndex
|
|
579
740
|
// This accounts for code folding where some lines don't render or render as fold indicators
|
|
@@ -611,6 +772,7 @@ class DiffViewer extends React.Component {
|
|
|
611
772
|
const diffNodes = [];
|
|
612
773
|
let topPadding = 0;
|
|
613
774
|
let firstVisibleFound = false;
|
|
775
|
+
let lastRenderedRowIndex = -1;
|
|
614
776
|
seenBlocks.clear();
|
|
615
777
|
for (let lineIndex = 0; lineIndex < lineInformation.length; lineIndex++) {
|
|
616
778
|
const line = lineInformation[lineIndex];
|
|
@@ -628,9 +790,13 @@ class DiffViewer extends React.Component {
|
|
|
628
790
|
}
|
|
629
791
|
// Calculate top padding from the first visible row
|
|
630
792
|
if (!firstVisibleFound) {
|
|
631
|
-
topPadding =
|
|
793
|
+
topPadding = cumulativeOffsets
|
|
794
|
+
? cumulativeOffsets[rowIndex] || 0
|
|
795
|
+
: rowIndex * DiffViewer.ESTIMATED_ROW_HEIGHT;
|
|
632
796
|
firstVisibleFound = true;
|
|
633
797
|
}
|
|
798
|
+
// Track the last rendered row for bottom padding calculation
|
|
799
|
+
lastRenderedRowIndex = rowIndex;
|
|
634
800
|
// Render the line
|
|
635
801
|
if (showDiffOnly) {
|
|
636
802
|
const blockIndex = lineBlocks[lineIndex];
|
|
@@ -650,12 +816,37 @@ class DiffViewer extends React.Component {
|
|
|
650
816
|
? this.renderSplitView(line, lineIndex)
|
|
651
817
|
: this.renderInlineView(line, lineIndex));
|
|
652
818
|
}
|
|
819
|
+
// Calculate total content height
|
|
820
|
+
const totalContentHeight = cumulativeOffsets
|
|
821
|
+
? cumulativeOffsets[cumulativeOffsets.length - 1] || 0
|
|
822
|
+
: totalRenderedRows * DiffViewer.ESTIMATED_ROW_HEIGHT;
|
|
823
|
+
// Calculate bottom padding: space after the last rendered row
|
|
824
|
+
const bottomPadding = cumulativeOffsets && lastRenderedRowIndex >= 0
|
|
825
|
+
? totalContentHeight - (cumulativeOffsets[lastRenderedRowIndex + 1] || totalContentHeight)
|
|
826
|
+
: 0;
|
|
653
827
|
return {
|
|
654
828
|
diffNodes,
|
|
655
829
|
blocks,
|
|
656
830
|
lineInformation,
|
|
657
831
|
totalRenderedRows,
|
|
658
832
|
topPadding,
|
|
833
|
+
bottomPadding,
|
|
834
|
+
totalContentHeight,
|
|
835
|
+
renderedCount: diffNodes.length,
|
|
836
|
+
// Debug info
|
|
837
|
+
debug: {
|
|
838
|
+
visibleRowStart,
|
|
839
|
+
visibleRowEnd,
|
|
840
|
+
totalRows: totalRenderedRows,
|
|
841
|
+
offsetsLength: cumulativeOffsets?.length ?? 0,
|
|
842
|
+
renderedCount: diffNodes.length,
|
|
843
|
+
scrollTop: scrollableContainerRef.current?.scrollTop ?? 0,
|
|
844
|
+
headerHeight: this.getStickyHeaderHeight(),
|
|
845
|
+
contentScrollTop: scrollableContainerRef.current
|
|
846
|
+
? Math.max(0, scrollableContainerRef.current.scrollTop - this.getStickyHeaderHeight())
|
|
847
|
+
: 0,
|
|
848
|
+
clientHeight: scrollableContainerRef.current?.clientHeight ?? 0,
|
|
849
|
+
}
|
|
659
850
|
};
|
|
660
851
|
};
|
|
661
852
|
componentDidUpdate(prevProps) {
|
|
@@ -666,10 +857,16 @@ class DiffViewer extends React.Component {
|
|
|
666
857
|
prevProps.linesOffset !== this.props.linesOffset) {
|
|
667
858
|
// Clear word diff cache when diff changes
|
|
668
859
|
this.wordDiffCache.clear();
|
|
860
|
+
// Reset scroll position to top
|
|
861
|
+
const container = this.state.scrollableContainerRef.current;
|
|
862
|
+
if (container) {
|
|
863
|
+
container.scrollTop = 0;
|
|
864
|
+
}
|
|
669
865
|
this.setState((prev) => ({
|
|
670
866
|
...prev,
|
|
671
867
|
isLoading: true,
|
|
672
|
-
visibleStartRow: 0
|
|
868
|
+
visibleStartRow: 0,
|
|
869
|
+
cumulativeOffsets: null,
|
|
673
870
|
}));
|
|
674
871
|
this.memoisedCompute();
|
|
675
872
|
}
|
|
@@ -680,6 +877,19 @@ class DiffViewer extends React.Component {
|
|
|
680
877
|
isLoading: true
|
|
681
878
|
}));
|
|
682
879
|
this.memoisedCompute();
|
|
880
|
+
// Set up ResizeObserver for recalculating offsets on container resize
|
|
881
|
+
if (typeof ResizeObserver !== 'undefined' && this.props.infiniteLoading) {
|
|
882
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
883
|
+
requestAnimationFrame(() => this.recalculateOffsets());
|
|
884
|
+
});
|
|
885
|
+
const container = this.state.scrollableContainerRef.current;
|
|
886
|
+
if (container) {
|
|
887
|
+
this.resizeObserver.observe(container);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
componentWillUnmount() {
|
|
892
|
+
this.resizeObserver?.disconnect();
|
|
683
893
|
}
|
|
684
894
|
render = () => {
|
|
685
895
|
const { oldValue, newValue, useDarkTheme, leftTitle, rightTitle, splitView, compareMethod, hideLineNumbers, nonce, } = this.props;
|
|
@@ -735,9 +945,12 @@ class DiffViewer extends React.Component {
|
|
|
735
945
|
overflowX: 'hidden',
|
|
736
946
|
height: this.props.infiniteLoading.containerHeight
|
|
737
947
|
} : {};
|
|
738
|
-
|
|
948
|
+
// Only apply noWrap when infiniteLoading is enabled but we don't have cumulative offsets yet
|
|
949
|
+
// Once offsets are calculated, we enable pre-wrap for proper text wrapping
|
|
950
|
+
const shouldNoWrap = !!this.props.infiniteLoading && !this.state.cumulativeOffsets;
|
|
739
951
|
const tableElement = (_jsxs("table", { className: cn(this.styles.diffContainer, {
|
|
740
952
|
[this.styles.splitView]: splitView,
|
|
953
|
+
[this.styles.noWrap]: shouldNoWrap,
|
|
741
954
|
}), onMouseUp: () => {
|
|
742
955
|
const elements = document.getElementsByClassName("right");
|
|
743
956
|
for (let i = 0; i < elements.length; i++) {
|
|
@@ -750,13 +963,42 @@ class DiffViewer extends React.Component {
|
|
|
750
963
|
element.classList.remove(this.styles.noSelect);
|
|
751
964
|
}
|
|
752
965
|
}, 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 })] }));
|
|
753
|
-
return (_jsxs("div", { style: { ...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: () => {
|
|
966
|
+
return (_jsxs("div", { style: { ...scrollDivStyle, position: 'relative' }, onScroll: this.onScroll, ref: this.state.scrollableContainerRef, children: [(!this.props.hideSummary || leftTitle || rightTitle) && (_jsxs("div", { ref: this.stickyHeaderRef, 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: () => {
|
|
754
967
|
this.setState({
|
|
755
968
|
expandedBlocks: allExpanded
|
|
756
969
|
? []
|
|
757
970
|
: nodes.blocks.map((b) => b.index),
|
|
758
|
-
});
|
|
759
|
-
}, 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: {
|
|
971
|
+
}, () => this.recalculateOffsets());
|
|
972
|
+
}, 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: {
|
|
973
|
+
height: nodes.totalContentHeight,
|
|
974
|
+
position: 'relative',
|
|
975
|
+
}, children: _jsx("div", { style: {
|
|
976
|
+
position: 'absolute',
|
|
977
|
+
top: nodes.topPadding,
|
|
978
|
+
left: 0,
|
|
979
|
+
right: 0,
|
|
980
|
+
}, children: tableElement }) })) : (tableElement), _jsx("span", { ref: this.charMeasureRef, style: {
|
|
981
|
+
position: 'absolute',
|
|
982
|
+
top: 0,
|
|
983
|
+
left: '-9999px',
|
|
984
|
+
visibility: 'hidden',
|
|
985
|
+
whiteSpace: 'pre',
|
|
986
|
+
fontFamily: 'monospace',
|
|
987
|
+
fontSize: 12,
|
|
988
|
+
}, "aria-hidden": "true", children: "M" }), this.props.infiniteLoading && this.props.showDebugInfo && (_jsxs("div", { style: {
|
|
989
|
+
position: 'fixed',
|
|
990
|
+
top: 10,
|
|
991
|
+
right: 10,
|
|
992
|
+
background: 'rgba(0,0,0,0.85)',
|
|
993
|
+
color: '#0f0',
|
|
994
|
+
padding: '10px',
|
|
995
|
+
fontFamily: 'monospace',
|
|
996
|
+
fontSize: '11px',
|
|
997
|
+
zIndex: 9999,
|
|
998
|
+
borderRadius: '4px',
|
|
999
|
+
maxWidth: '300px',
|
|
1000
|
+
lineHeight: 1.4,
|
|
1001
|
+
}, children: [_jsx("div", { style: { fontWeight: 'bold', marginBottom: '5px', color: '#fff' }, children: "Debug Info" }), _jsxs("div", { children: ["scrollTop: ", nodes.debug.scrollTop] }), _jsxs("div", { children: ["headerHeight: ", nodes.debug.headerHeight] }), _jsxs("div", { children: ["contentScrollTop: ", nodes.debug.contentScrollTop] }), _jsxs("div", { children: ["clientHeight: ", nodes.debug.clientHeight] }), _jsxs("div", { style: { marginTop: '5px', borderTop: '1px solid #444', paddingTop: '5px' }, children: [_jsxs("div", { children: ["visibleRowStart: ", nodes.debug.visibleRowStart] }), _jsxs("div", { children: ["visibleRowEnd: ", nodes.debug.visibleRowEnd] })] }), _jsxs("div", { style: { marginTop: '5px', borderTop: '1px solid #444', paddingTop: '5px' }, children: [_jsxs("div", { children: ["totalRows: ", nodes.debug.totalRows] }), _jsxs("div", { children: ["offsetsLength: ", nodes.debug.offsetsLength] }), _jsxs("div", { children: ["renderedCount: ", nodes.debug.renderedCount] })] }), _jsxs("div", { style: { marginTop: '5px', borderTop: '1px solid #444', paddingTop: '5px' }, children: [_jsxs("div", { children: ["topPadding: ", nodes.topPadding.toFixed(0)] }), _jsxs("div", { children: ["bottomPadding: ", nodes.bottomPadding.toFixed(0)] }), _jsxs("div", { children: ["totalContentHeight: ", nodes.totalContentHeight.toFixed(0)] })] }), _jsxs("div", { style: { marginTop: '5px', borderTop: '1px solid #444', paddingTop: '5px', color: '#ff0' }, children: [_jsxs("div", { children: ["cumulativeOffsets: ", this.state.cumulativeOffsets ? 'SET' : 'NULL'] }), _jsxs("div", { children: ["columnWidth: ", this.state.contentColumnWidth?.toFixed(0) ?? 'N/A', "px"] }), _jsxs("div", { children: ["charWidth: ", this.state.charWidth?.toFixed(2) ?? 'N/A', "px"] }), _jsxs("div", { children: ["charsPerRow: ", this.state.contentColumnWidth && this.state.charWidth ? Math.floor(this.state.contentColumnWidth / this.state.charWidth) : 'N/A'] })] }), this.state.cumulativeOffsets && (_jsxs("div", { style: { marginTop: '5px', borderTop: '1px solid #444', paddingTop: '5px', color: '#0ff', fontSize: '10px' }, children: [_jsxs("div", { children: ["offsets[", nodes.debug.visibleRowEnd, "]: ", this.state.cumulativeOffsets[nodes.debug.visibleRowEnd]?.toFixed(0) ?? 'N/A'] }), _jsxs("div", { children: ["offsets[", nodes.debug.totalRows - 1, "]: ", this.state.cumulativeOffsets[nodes.debug.totalRows - 1]?.toFixed(0) ?? 'N/A'] }), _jsxs("div", { children: ["offsets[", nodes.debug.totalRows, "]: ", this.state.cumulativeOffsets[nodes.debug.totalRows]?.toFixed(0) ?? 'N/A'] }), _jsxs("div", { style: { marginTop: '3px' }, children: ["viewportEnd: ", (nodes.debug.contentScrollTop + nodes.debug.clientHeight).toFixed(0)] }), _jsxs("div", { style: { marginTop: '3px', color: '#f0f' }, children: ["scrollHeight: ", this.state.scrollableContainerRef.current?.scrollHeight ?? 'N/A'] }), _jsxs("div", { children: ["maxScrollTop: ", (this.state.scrollableContainerRef.current?.scrollHeight ?? 0) - nodes.debug.clientHeight] })] }))] }))] }));
|
|
760
1002
|
};
|
|
761
1003
|
}
|
|
762
1004
|
export default DiffViewer;
|
package/lib/esm/src/styles.js
CHANGED
|
@@ -67,6 +67,7 @@ export default (styleOverride, useDarkTheme = false, nonce = "") => {
|
|
|
67
67
|
const { css, cx } = createEmotion({ key: "react-diff", nonce });
|
|
68
68
|
const content = css({
|
|
69
69
|
width: "auto",
|
|
70
|
+
overflow: "hidden",
|
|
70
71
|
label: "content",
|
|
71
72
|
});
|
|
72
73
|
const splitView = css({
|
|
@@ -108,6 +109,9 @@ export default (styleOverride, useDarkTheme = false, nonce = "") => {
|
|
|
108
109
|
},
|
|
109
110
|
label: "diff-container",
|
|
110
111
|
borderCollapse: "collapse",
|
|
112
|
+
"@media (max-width: 768px)": {
|
|
113
|
+
minWidth: "unset",
|
|
114
|
+
},
|
|
111
115
|
});
|
|
112
116
|
const lineContent = css({
|
|
113
117
|
overflow: "hidden",
|
|
@@ -125,6 +129,16 @@ export default (styleOverride, useDarkTheme = false, nonce = "") => {
|
|
|
125
129
|
userSelect: "none",
|
|
126
130
|
label: "unselectable",
|
|
127
131
|
});
|
|
132
|
+
const noWrap = css({
|
|
133
|
+
label: "no-wrap",
|
|
134
|
+
pre: {
|
|
135
|
+
whiteSpace: "pre",
|
|
136
|
+
},
|
|
137
|
+
[`.${contentText}`]: {
|
|
138
|
+
whiteSpace: "pre",
|
|
139
|
+
lineBreak: "auto",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
128
142
|
const allExpandButton = css({
|
|
129
143
|
background: "transparent",
|
|
130
144
|
border: "none",
|
|
@@ -371,6 +385,7 @@ export default (styleOverride, useDarkTheme = false, nonce = "") => {
|
|
|
371
385
|
blockDeletion,
|
|
372
386
|
wordRemoved,
|
|
373
387
|
noSelect: unselectable,
|
|
388
|
+
noWrap,
|
|
374
389
|
codeFoldGutter,
|
|
375
390
|
codeFoldExpandButton,
|
|
376
391
|
codeFoldContentContainer,
|