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.
@@ -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
- expandedBlocks: prevState,
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 and syntax highlighting is enabled, try to highlight
289
- // the full line first, then apply diff styling to preserve proper tokenization.
290
- if (renderer && !skipSyntaxHighlighting) {
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
- const newStartRow = Math.floor(container.scrollTop / DiffViewer.ESTIMATED_ROW_HEIGHT);
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
- const viewportRows = Math.ceil(container.clientHeight / DiffViewer.ESTIMATED_ROW_HEIGHT);
575
- visibleRowStart = Math.max(0, visibleStartRow - buffer);
576
- visibleRowEnd = visibleStartRow + viewportRows + buffer;
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 = rowIndex * DiffViewer.ESTIMATED_ROW_HEIGHT;
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
- const totalContentHeight = nodes.totalRenderedRows * DiffViewer.ESTIMATED_ROW_HEIGHT;
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: { minHeight: totalContentHeight, paddingTop: nodes.topPadding }, children: tableElement })) : (tableElement)] }));
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;
@@ -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,