From fd82b772a7313c5977464dd10e15054dd024bffc Mon Sep 17 00:00:00 2001 From: Wojciech Dron Date: Wed, 25 Feb 2026 00:11:36 +0100 Subject: [PATCH] feat: Added HideUnchangedLines function --- src/BlazorTextDiff/TextDiff.razor | 68 ++++++++++++++++++- src/BlazorTextDiff/TextDiffPane.razor | 32 ++++++++- src/BlazorTextDiff/wwwroot/css/BlazorDiff.css | 40 +++++++++++ .../wwwroot/css/BlazorDiff.min.css | 2 +- tests/BlazorTextDiff.Tests/TextDiffTests.cs | 33 +++++++++ 5 files changed, 170 insertions(+), 5 deletions(-) diff --git a/src/BlazorTextDiff/TextDiff.razor b/src/BlazorTextDiff/TextDiff.razor index 5604141..828112a 100644 --- a/src/BlazorTextDiff/TextDiff.razor +++ b/src/BlazorTextDiff/TextDiff.razor @@ -12,14 +12,21 @@ } -
+
@if (diff is not null) {
- +
- +
}
@@ -50,6 +57,16 @@ [Parameter] public int MaxHeight { get; set; } = 300; [Parameter] public bool CollapseContent { get; set; } + /// + /// When true, unchanged lines will be hidden, showing only changed lines and some context. + /// + [Parameter] public bool HideUnchangedLines { get; set; } = false; + + /// + /// The number of unchanged lines to show around each change when is true. + /// + [Parameter] public int ContextLines { get; set; } = 3; + /// /// The text before any changes. /// @@ -77,6 +94,7 @@ private SideBySideDiffModel? diff; private bool isCollapsed; + private bool[]? visibleLines; /// /// When parameters set update the component. @@ -108,6 +126,36 @@ } } + /// Creates a visibility state array for each line based on diff changes + private void CalculateVisibility() + { + if (diff == null || !HideUnchangedLines) + { + visibleLines = null; + return; + } + + var lineCount = diff.OldText.Lines.Count; + visibleLines = new bool[lineCount]; + + for (var i = 0; i < lineCount; i++) + { + var oldLineUnchanged = diff.OldText.Lines[i].Type == ChangeType.Unchanged; + var newLineUnchanged = diff.NewText.Lines[i].Type == ChangeType.Unchanged; + + if (oldLineUnchanged && newLineUnchanged) + continue; + + var contextLinesStart = Math.Max(0, i - ContextLines); + var contextLinesEnd = Math.Min(lineCount - 1, i + ContextLines); + + for (var j = contextLinesStart; j <= contextLinesEnd; j++) + { + visibleLines[j] = true; + } + } + } + /// /// For lines whose only sub-piece changes are whitespace characters, /// downgrade them to Unchanged so they don't show as modified. @@ -145,4 +193,18 @@ { isCollapsed = !isCollapsed; } + + private void ShowHiddenLines((int Start, int Count) range) + { + if (visibleLines == null) + return; + + var rangeEnd = Math.Min(range.Start + range.Count, visibleLines.Length); + for (var i = range.Start; i < rangeEnd; i++) + { + visibleLines[i] = true; + } + + StateHasChanged(); + } } diff --git a/src/BlazorTextDiff/TextDiffPane.razor b/src/BlazorTextDiff/TextDiffPane.razor index d78f92b..c1d4ba6 100644 --- a/src/BlazorTextDiff/TextDiffPane.razor +++ b/src/BlazorTextDiff/TextDiffPane.razor @@ -4,8 +4,36 @@ @if (Model?.Lines is not null) { - @foreach (var diffLine in Model.Lines) + @for (var i = 0; i < Model.Lines.Count; i++) { + var diffLine = Model.Lines[i]; + if (VisibleLines is not null && !VisibleLines[i]) + { + // If we are at the start of a hidden block, show a placeholder + if (i == 0 || VisibleLines[i - 1]) + { + var hiddenCount = 0; + var lineIdx = i; + + // count next hidden lines + for (var j = lineIdx; j < Model.Lines.Count && !VisibleLines[j]; j++) + { + hiddenCount++; + } + + + + + + } + continue; + }
... + +
@((MarkupString)(diffLine.Position?.ToString() ?? " ")) @@ -24,4 +52,6 @@ @code { [Parameter, EditorRequired] public DiffPaneModel Model { get; set; } = default!; [Parameter] public PanePosition PanePosition { get; set; } + [Parameter] public bool[]? VisibleLines { get; set; } + [Parameter] public EventCallback<(int Start, int Count)> OnShowHiddenLines { get; set; } } \ No newline at end of file diff --git a/src/BlazorTextDiff/wwwroot/css/BlazorDiff.css b/src/BlazorTextDiff/wwwroot/css/BlazorDiff.css index ba38554..8b0d14c 100644 --- a/src/BlazorTextDiff/wwwroot/css/BlazorDiff.css +++ b/src/BlazorTextDiff/wwwroot/css/BlazorDiff.css @@ -415,6 +415,46 @@ table.diff tr:hover td.line { background-color: color-mix(in srgb, var(--diff-border-primary) 30%, transparent); } +.diff-hidden-summary td { + background-color: var(--diff-bg-tertiary) !important; + color: var(--diff-text-muted); + font-size: 12px; + padding: 2px 12px !important; + border-top: 1px solid var(--diff-border-primary); + border-bottom: 1px solid var(--diff-border-primary); +} + +.diff-hidden-summary .line-number { + text-align: center !important; + border-right: 1px solid var(--diff-border-primary); +} + +.diff-hidden-summary .hidden-count { + font-style: italic; + font-weight: 500; +} + +.show-hidden-button { + background: none; + border: none; + color: var(--diff-text-muted); + cursor: pointer; + padding: 0; + font-family: inherit; + font-size: inherit; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s, background-color 0.2s; +} + +.show-hidden-button:hover { + color: var(--diff-text-accent); + background-color: var(--diff-bg-secondary); +} + /* Responsive improvements */ @media (max-width: 768px) { .diff-container .diff-panes { diff --git a/src/BlazorTextDiff/wwwroot/css/BlazorDiff.min.css b/src/BlazorTextDiff/wwwroot/css/BlazorDiff.min.css index 57917c0..727a729 100644 --- a/src/BlazorTextDiff/wwwroot/css/BlazorDiff.min.css +++ b/src/BlazorTextDiff/wwwroot/css/BlazorDiff.min.css @@ -1 +1 @@ -:root{--diff-bg-primary:#ffffff;--diff-bg-secondary:#f6f8fa;--diff-bg-tertiary:#f1f3f4;--diff-border-primary:#e1e4e8;--diff-border-secondary:#30363d;--diff-text-primary:#24292e;--diff-text-secondary:#586069;--diff-text-muted:#656d76;--diff-text-accent:#0969da;--diff-addition-bg:#e6ffed;--diff-addition-border:#2ea043;--diff-addition-highlight:#7ce89b;--diff-deletion-bg:#ffeef0;--diff-deletion-border:#f85149;--diff-deletion-highlight:#f9a8b0;--diff-modification-bg:#fff8c5;--diff-modification-border:#fb8500;--diff-modification-highlight:#ffc833;--diff-char-radius:3px;--diff-char-padding:1px 2px;--diff-word-radius:3px;--diff-word-padding:1px 0;--diff-shadow:0 1px 3px rgba(0,0,0,0.1);--diff-shadow-hover:0 2px 6px rgba(0,0,0,0.15);--stats-primary-bg:#0969da;--stats-primary-text:#ffffff;--stats-success-bg:#2ea043;--stats-success-text:#ffffff;--stats-danger-bg:#f85149;--stats-danger-text:#ffffff;--stats-warning-bg:#fb8500;--stats-warning-text:#ffffff;--stats-info-bg:#0ea5e9;--stats-info-text:#ffffff}@media(prefers-color-scheme:dark){:root{--diff-bg-primary:#0d1117;--diff-bg-secondary:#161b22;--diff-bg-tertiary:#21262d;--diff-border-primary:#30363d;--diff-border-secondary:#21262d;--diff-text-primary:#f0f6fc;--diff-text-secondary:#8b949e;--diff-text-muted:#6e7681;--diff-text-accent:#58a6ff;--diff-addition-bg:#033a16;--diff-addition-border:#2ea043;--diff-addition-highlight:#196c2e;--diff-deletion-bg:#490202;--diff-deletion-border:#f85149;--diff-deletion-highlight:#c93c37;--diff-modification-bg:#341a00;--diff-modification-border:#fb8500;--diff-modification-highlight:#7d5200;--diff-shadow:0 1px 3px rgba(0,0,0,0.3);--diff-shadow-hover:0 2px 6px rgba(0,0,0,0.4);--stats-primary-bg:#1f6feb;--stats-success-bg:#238636;--stats-danger-bg:#da3633;--stats-warning-bg:#fb8500;--stats-info-bg:#0ea5e9}}.diff-container{background-color:var(--diff-bg-primary);border:1px solid var(--diff-border-primary);border-radius:8px;overflow:hidden;box-shadow:var(--diff-shadow);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto',sans-serif;transition:box-shadow .2s ease-in-out}.diff-container:hover{box-shadow:var(--diff-shadow-hover)}.diff-container .diff-expand-notice{display:block;width:100%;text-align:center;border:none;border-top:1px solid var(--diff-border-primary);margin:0;background-color:var(--diff-bg-secondary);padding:10px 12px;color:var(--diff-text-secondary);font-size:13px;font-weight:500;cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.diff-container .diff-expand-notice:hover{background-color:var(--diff-bg-tertiary);color:var(--diff-text-accent)}.diff-container .diff-expand-notice:focus-visible{outline:2px solid var(--diff-text-accent);outline-offset:-2px}.diff-container .diff-header{border-bottom:1px solid var(--diff-border-primary);background:linear-gradient(135deg,var(--diff-bg-secondary) 0%,var(--diff-bg-tertiary) 100%);border-top-left-radius:8px;border-top-right-radius:8px;display:flex;align-items:center;justify-content:space-between;min-height:50px;padding:0 16px}.diff-container .diff-footer{border-top:1px solid var(--diff-border-primary);background:linear-gradient(135deg,var(--diff-bg-secondary) 0%,var(--diff-bg-tertiary) 100%);border-bottom-left-radius:8px;border-bottom-right-radius:8px;display:flex;align-items:center;justify-content:space-between;min-height:50px;padding:0 16px}.diff-container .diff-panes{display:flex;background-color:var(--diff-bg-primary)}.diff-container .diff-panes .diff-pane{flex:1;border-right:1px solid var(--diff-border-primary)}.diff-container .diff-panes .diff-pane:last-child{border-right:none}.diff-container .diff-panes .diff-pane .line-text{word-break:break-all;word-wrap:break-word}.diff-stats-container{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.diff-stats-badge{display:inline-flex;align-items:center;padding:4px 8px;border-radius:6px;font-size:12px;font-weight:500;line-height:1.2;white-space:nowrap;transition:all .2s ease-in-out;border:1px solid transparent}.diff-stats-badge:hover{transform:translateY(-1px);box-shadow:var(--diff-shadow)}.diff-stats-badge.primary{background-color:var(--stats-primary-bg);color:var(--stats-primary-text)}.diff-stats-badge.success{background-color:var(--stats-success-bg);color:var(--stats-success-text)}.diff-stats-badge.danger{background-color:var(--stats-danger-bg);color:var(--stats-danger-text)}.diff-stats-badge.warning{background-color:var(--stats-warning-bg);color:var(--stats-warning-text)}.diff-stats-badge.info{background-color:var(--stats-info-bg);color:var(--stats-info-text)}.diff-stats-badge .badge-icon{margin-right:4px;font-size:10px}.diff-pane-left{margin:0;padding:0;overflow:auto;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;font-size:14px;line-height:1.5}.diff-pane-left .line-number.modified{background-color:var(--diff-modification-bg);border-right:3px solid var(--diff-modification-border)}.diff-pane-left .line-number.deleted{background-color:var(--diff-deletion-bg);border-right:3px solid var(--diff-deletion-border)}.diff-pane-left .deleted-line{background-color:var(--diff-deletion-bg);border-left:3px solid var(--diff-deletion-border)}.diff-pane-left .modified-line{background-color:var(--diff-modification-bg);border-left:3px solid var(--diff-modification-border)}.diff-pane-right{margin:0;padding:0;overflow:auto;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;font-size:14px;line-height:1.5}.diff-pane-right .line-number.modified{background-color:var(--diff-modification-bg);border-right:3px solid var(--diff-modification-border)}.diff-pane-right .line-number.inserted{background-color:var(--diff-addition-bg);border-right:3px solid var(--diff-addition-border)}.diff-pane-right .modified-line{background-color:var(--diff-modification-bg);border-left:3px solid var(--diff-modification-border)}.diff-pane-right .inserted-line{background-color:var(--diff-addition-bg);border-left:3px solid var(--diff-addition-border)}.diff-pane-right .deleted-line{background-color:var(--diff-deletion-bg);border-left:3px solid var(--diff-deletion-border)}.diff-pane-right .inserted-word,.diff-pane-left .inserted-word{background-color:var(--diff-addition-bg);border-radius:var(--diff-word-radius);padding:var(--diff-word-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .modified-word,.diff-pane-left .modified-word{background-color:var(--diff-modification-bg);border-radius:var(--diff-word-radius);padding:var(--diff-word-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .deleted-word,.diff-pane-left .deleted-word{background-color:var(--diff-deletion-bg);border-radius:var(--diff-word-radius);padding:var(--diff-word-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .inserted-character,.diff-pane-left .inserted-character{background-color:var(--diff-addition-highlight);border-radius:var(--diff-char-radius);padding:var(--diff-char-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .modified-character,.diff-pane-left .modified-character{background-color:var(--diff-modification-highlight);border-radius:var(--diff-char-radius);padding:var(--diff-char-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .deleted-character,.diff-pane-left .deleted-character{background-color:var(--diff-deletion-highlight);border-radius:var(--diff-char-radius);padding:var(--diff-char-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.inserted-character+.inserted-character,.modified-character+.modified-character,.deleted-character+.deleted-character{border-top-left-radius:0;border-bottom-left-radius:0;padding-left:0}.inserted-character:has(+.inserted-character),.modified-character:has(+.modified-character),.deleted-character:has(+.deleted-character){border-top-right-radius:0;border-bottom-right-radius:0;padding-right:0}table.diff{background-color:var(--diff-bg-primary);width:100%;border-collapse:collapse;margin:0;border-spacing:0}table.diff td.line-number{width:1%;min-width:60px;padding:4px 12px;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;font-size:12px;line-height:22px;color:var(--diff-text-muted);text-align:right;white-space:nowrap;vertical-align:top;cursor:pointer;user-select:none;background-color:var(--diff-bg-secondary);border-right:1px solid var(--diff-border-primary);font-weight:400;transition:color .2s ease-in-out,background-color .2s ease-in-out}table.diff td.line-number:hover{color:var(--diff-text-accent);background-color:var(--diff-bg-tertiary)}table.diff td.line{padding:4px 12px;line-height:22px;vertical-align:top;font-size:14px;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;white-space:pre-wrap;word-wrap:break-word;color:var(--diff-text-primary)}table.diff td .unchanged-line{background-color:var(--diff-bg-primary)}table.diff tr:hover td.line-number{background-color:var(--diff-bg-tertiary)}table.diff tr:hover td.line{background-color:color-mix(in srgb,var(--diff-border-primary) 30%,transparent)}@media(max-width:768px){.diff-container .diff-panes{flex-direction:column}.diff-container .diff-panes .diff-pane{border-right:none;border-bottom:1px solid var(--diff-border-primary)}.diff-container .diff-panes .diff-pane:last-child{border-bottom:none}table.diff td.line-number{min-width:50px;padding:2px 8px;font-size:11px}table.diff td.line{padding:2px 8px;font-size:13px}.diff-stats-container{gap:4px}.diff-stats-badge{font-size:11px;padding:3px 6px}}@media(prefers-reduced-motion:reduce){.diff-container,.diff-stats-badge,table.diff td.line-number,table.diff td.line{transition:none}.diff-container .diff-header,.diff-container .diff-footer{background:var(--diff-bg-secondary)}}table.diff td.line-number:focus{outline:2px solid var(--diff-text-accent);outline-offset:-2px}.diff-stats-badge:focus{outline:2px solid var(--diff-text-accent);outline-offset:2px}@media print{.diff-container{box-shadow:none;border:1px solid #000}.diff-stats-badge{border:1px solid #000;background-color:transparent!important;color:#000!important}} \ No newline at end of file +:root{--diff-bg-primary:#ffffff;--diff-bg-secondary:#f6f8fa;--diff-bg-tertiary:#f1f3f4;--diff-border-primary:#e1e4e8;--diff-border-secondary:#30363d;--diff-text-primary:#24292e;--diff-text-secondary:#586069;--diff-text-muted:#656d76;--diff-text-accent:#0969da;--diff-addition-bg:#e6ffed;--diff-addition-border:#2ea043;--diff-addition-highlight:#7ce89b;--diff-deletion-bg:#ffeef0;--diff-deletion-border:#f85149;--diff-deletion-highlight:#f9a8b0;--diff-modification-bg:#fff8c5;--diff-modification-border:#fb8500;--diff-modification-highlight:#ffc833;--diff-char-radius:3px;--diff-char-padding:1px 2px;--diff-word-radius:3px;--diff-word-padding:1px 0;--diff-shadow:0 1px 3px rgba(0,0,0,0.1);--diff-shadow-hover:0 2px 6px rgba(0,0,0,0.15);--stats-primary-bg:#0969da;--stats-primary-text:#ffffff;--stats-success-bg:#2ea043;--stats-success-text:#ffffff;--stats-danger-bg:#f85149;--stats-danger-text:#ffffff;--stats-warning-bg:#fb8500;--stats-warning-text:#ffffff;--stats-info-bg:#0ea5e9;--stats-info-text:#ffffff}@media (prefers-color-scheme:dark){:root{--diff-bg-primary:#0d1117;--diff-bg-secondary:#161b22;--diff-bg-tertiary:#21262d;--diff-border-primary:#30363d;--diff-border-secondary:#21262d;--diff-text-primary:#f0f6fc;--diff-text-secondary:#8b949e;--diff-text-muted:#6e7681;--diff-text-accent:#58a6ff;--diff-addition-bg:#033a16;--diff-addition-border:#2ea043;--diff-addition-highlight:#196c2e;--diff-deletion-bg:#490202;--diff-deletion-border:#f85149;--diff-deletion-highlight:#c93c37;--diff-modification-bg:#341a00;--diff-modification-border:#fb8500;--diff-modification-highlight:#7d5200;--diff-shadow:0 1px 3px rgba(0,0,0,0.3);--diff-shadow-hover:0 2px 6px rgba(0,0,0,0.4);--stats-primary-bg:#1f6feb;--stats-success-bg:#238636;--stats-danger-bg:#da3633;--stats-warning-bg:#fb8500;--stats-info-bg:#0ea5e9}}.diff-container{background-color:var(--diff-bg-primary);border:1px solid var(--diff-border-primary);border-radius:8px;overflow:hidden;box-shadow:var(--diff-shadow);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto',sans-serif;transition:box-shadow 0.2s ease-in-out}.diff-container:hover{box-shadow:var(--diff-shadow-hover)}.diff-container .diff-expand-notice{display:block;width:100%;text-align:center;border:none;border-top:1px solid var(--diff-border-primary);margin:0;background-color:var(--diff-bg-secondary);padding:10px 12px;color:var(--diff-text-secondary);font-size:13px;font-weight:500;cursor:pointer;transition:background-color 0.15s ease-in-out,color 0.15s ease-in-out}.diff-container .diff-expand-notice:hover{background-color:var(--diff-bg-tertiary);color:var(--diff-text-accent)}.diff-container .diff-expand-notice:focus-visible{outline:2px solid var(--diff-text-accent);outline-offset:-2px}.diff-container .diff-header{border-bottom:1px solid var(--diff-border-primary);background:linear-gradient(135deg,var(--diff-bg-secondary) 0%,var(--diff-bg-tertiary) 100%);border-top-left-radius:8px;border-top-right-radius:8px;display:flex;align-items:center;justify-content:space-between;min-height:50px;padding:0 16px}.diff-container .diff-footer{border-top:1px solid var(--diff-border-primary);background:linear-gradient(135deg,var(--diff-bg-secondary) 0%,var(--diff-bg-tertiary) 100%);border-bottom-left-radius:8px;border-bottom-right-radius:8px;display:flex;align-items:center;justify-content:space-between;min-height:50px;padding:0 16px}.diff-container .diff-panes{display:flex;background-color:var(--diff-bg-primary)}.diff-container .diff-panes .diff-pane{flex:1;border-right:1px solid var(--diff-border-primary)}.diff-container .diff-panes .diff-pane:last-child{border-right:none}.diff-container .diff-panes .diff-pane .line-text{word-break:break-all;word-wrap:break-word}.diff-stats-container{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.diff-stats-badge{display:inline-flex;align-items:center;padding:4px 8px;border-radius:6px;font-size:12px;font-weight:500;line-height:1.2;white-space:nowrap;transition:all 0.2s ease-in-out;border:1px solid transparent}.diff-stats-badge:hover{transform:translateY(-1px);box-shadow:var(--diff-shadow)}.diff-stats-badge.primary{background-color:var(--stats-primary-bg);color:var(--stats-primary-text)}.diff-stats-badge.success{background-color:var(--stats-success-bg);color:var(--stats-success-text)}.diff-stats-badge.danger{background-color:var(--stats-danger-bg);color:var(--stats-danger-text)}.diff-stats-badge.warning{background-color:var(--stats-warning-bg);color:var(--stats-warning-text)}.diff-stats-badge.info{background-color:var(--stats-info-bg);color:var(--stats-info-text)}.diff-stats-badge .badge-icon{margin-right:4px;font-size:10px}.diff-pane-left{margin:0;padding:0;overflow:auto;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;font-size:14px;line-height:1.5}.diff-pane-left .line-number.modified{background-color:var(--diff-modification-bg);border-right:3px solid var(--diff-modification-border)}.diff-pane-left .line-number.deleted{background-color:var(--diff-deletion-bg);border-right:3px solid var(--diff-deletion-border)}.diff-pane-left .deleted-line{background-color:var(--diff-deletion-bg);border-left:3px solid var(--diff-deletion-border)}.diff-pane-left .modified-line{background-color:var(--diff-modification-bg);border-left:3px solid var(--diff-modification-border)}.diff-pane-right{margin:0;padding:0;overflow:auto;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;font-size:14px;line-height:1.5}.diff-pane-right .line-number.modified{background-color:var(--diff-modification-bg);border-right:3px solid var(--diff-modification-border)}.diff-pane-right .line-number.inserted{background-color:var(--diff-addition-bg);border-right:3px solid var(--diff-addition-border)}.diff-pane-right .modified-line{background-color:var(--diff-modification-bg);border-left:3px solid var(--diff-modification-border)}.diff-pane-right .inserted-line{background-color:var(--diff-addition-bg);border-left:3px solid var(--diff-addition-border)}.diff-pane-right .deleted-line{background-color:var(--diff-deletion-bg);border-left:3px solid var(--diff-deletion-border)}.diff-pane-right .inserted-word,.diff-pane-left .inserted-word{background-color:var(--diff-addition-bg);border-radius:var(--diff-word-radius);padding:var(--diff-word-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .modified-word,.diff-pane-left .modified-word{background-color:var(--diff-modification-bg);border-radius:var(--diff-word-radius);padding:var(--diff-word-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .deleted-word,.diff-pane-left .deleted-word{background-color:var(--diff-deletion-bg);border-radius:var(--diff-word-radius);padding:var(--diff-word-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .inserted-character,.diff-pane-left .inserted-character{background-color:var(--diff-addition-highlight);border-radius:var(--diff-char-radius);padding:var(--diff-char-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .modified-character,.diff-pane-left .modified-character{background-color:var(--diff-modification-highlight);border-radius:var(--diff-char-radius);padding:var(--diff-char-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.diff-pane-right .deleted-character,.diff-pane-left .deleted-character{background-color:var(--diff-deletion-highlight);border-radius:var(--diff-char-radius);padding:var(--diff-char-padding);box-decoration-break:clone;-webkit-box-decoration-break:clone}.inserted-character + .inserted-character,.modified-character + .modified-character,.deleted-character + .deleted-character{border-top-left-radius:0;border-bottom-left-radius:0;padding-left:0}.inserted-character:has(+ .inserted-character),.modified-character:has(+ .modified-character),.deleted-character:has(+ .deleted-character){border-top-right-radius:0;border-bottom-right-radius:0;padding-right:0}table.diff{background-color:var(--diff-bg-primary);width:100%;border-collapse:collapse;margin:0;border-spacing:0}table.diff td.line-number{width:1%;min-width:60px;padding:4px 12px;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;font-size:12px;line-height:22px;color:var(--diff-text-muted);text-align:right;white-space:nowrap;vertical-align:top;cursor:pointer;user-select:none;background-color:var(--diff-bg-secondary);border-right:1px solid var(--diff-border-primary);font-weight:400;transition:color 0.2s ease-in-out,background-color 0.2s ease-in-out}table.diff td.line-number:hover{color:var(--diff-text-accent);background-color:var(--diff-bg-tertiary)}table.diff td.line{padding:4px 12px;line-height:22px;vertical-align:top;font-size:14px;font-family:'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier New',monospace;white-space:pre-wrap;word-wrap:break-word;color:var(--diff-text-primary)}table.diff td .unchanged-line{background-color:var(--diff-bg-primary)}table.diff tr:hover td.line-number{background-color:var(--diff-bg-tertiary)}table.diff tr:hover td.line{background-color:color-mix(in srgb,var(--diff-border-primary) 30%,transparent)}.diff-hidden-summary td{background-color:var(--diff-bg-tertiary) !important;color:var(--diff-text-muted);font-size:12px;padding:2px 12px !important;border-top:1px solid var(--diff-border-primary);border-bottom:1px solid var(--diff-border-primary)}.diff-hidden-summary .line-number{text-align:center !important;border-right:1px solid var(--diff-border-primary)}.diff-hidden-summary .hidden-count{font-style:italic;font-weight:500}.show-hidden-button{background:none;border:none;color:var(--diff-text-muted);cursor:pointer;padding:0;font-family:inherit;font-size:inherit;width:100%;height:100%;display:flex;align-items:center;justify-content:center;transition:color 0.2s,background-color 0.2s}.show-hidden-button:hover{color:var(--diff-text-accent);background-color:var(--diff-bg-secondary)}@media (max-width:768px){.diff-container .diff-panes{flex-direction:column}.diff-container .diff-panes .diff-pane{border-right:none;border-bottom:1px solid var(--diff-border-primary)}.diff-container .diff-panes .diff-pane:last-child{border-bottom:none}table.diff td.line-number{min-width:50px;padding:2px 8px;font-size:11px}table.diff td.line{padding:2px 8px;font-size:13px}.diff-stats-container{gap:4px}.diff-stats-badge{font-size:11px;padding:3px 6px}}@media (prefers-reduced-motion:reduce){.diff-container,.diff-stats-badge,table.diff td.line-number,table.diff td.line{transition:none}.diff-container .diff-header,.diff-container .diff-footer{background:var(--diff-bg-secondary)}}table.diff td.line-number:focus{outline:2px solid var(--diff-text-accent);outline-offset:-2px}.diff-stats-badge:focus{outline:2px solid var(--diff-text-accent);outline-offset:2px}@media print{.diff-container{box-shadow:none;border:1px solid #000}.diff-stats-badge{border:1px solid #000;background-color:transparent !important;color:#000 !important}} \ No newline at end of file diff --git a/tests/BlazorTextDiff.Tests/TextDiffTests.cs b/tests/BlazorTextDiff.Tests/TextDiffTests.cs index dea0ba4..69a19b1 100644 --- a/tests/BlazorTextDiff.Tests/TextDiffTests.cs +++ b/tests/BlazorTextDiff.Tests/TextDiffTests.cs @@ -222,4 +222,37 @@ public void MultiLineText_RendersAllLines() var rightRows = rightPaneDiv.QuerySelectorAll("tr"); Assert.True(rightRows.Length >= 4, $"Expected at least 4 rows in right pane, got {rightRows.Length}"); } + + [Fact] + public void HideUnchangedLines_HidesExcessContext() + { + // 10 lines, change at line 5 + var oldText = string.Join("\n", Enumerable.Range(1, 10).Select(i => $"Line {i}")); + var newText = oldText.Replace("Line 5", "Modified Line 5"); + + var cut = RenderComponent(parameters => parameters + .Add(p => p.OldText, oldText) + .Add(p => p.NewText, newText) + .Add(p => p.HideUnchangedLines, true) + .Add(p => p.ContextLines, 1)); + + // Line 5 is changed. + // Context 1: Lines 4, 5, 6 should be visible. + // Lines 1, 2, 3 should be hidden (3 lines). + // Lines 7, 8, 9, 10 should be hidden (4 lines). + + var hiddenSummaries = cut.FindAll(".diff-hidden-summary"); + Assert.Equal(4, hiddenSummaries.Count); // One at top, one at bottom for each side + + Assert.Contains("3 lines hidden", hiddenSummaries[0].TextContent); + Assert.Contains("4 lines hidden", hiddenSummaries[1].TextContent); + + // Check that visible lines are indeed shown + var lineTexts = cut.FindAll(".line-text").Select(el => el.TextContent).ToList(); + Assert.Contains("Line 4", lineTexts); + Assert.Contains("Modified Line 5", lineTexts); + Assert.Contains("Line 6", lineTexts); + Assert.DoesNotContain("Line 1", lineTexts); + Assert.DoesNotContain("Line 10", lineTexts); + } }