v2.0.0

<bmx-code-diff>

The differences between two texts, side by side or in one column: deleted and added lines coloured, the words that changed within them marked, unchanged stretches folded away with a button to open them, and the code coloured in its language. Or show a patch (a unified diff, from git or anything else) without the files it came from.

17 properties · 2 events · 10 methods · 3 parts

Example

Show as a patch

F7 and Shift+F7 move between changes. Revert a change, or take it into the original, with the buttons above it.

A patch, without its files

Give it the output of git diff (or any unified diff): each file and hunk is shown with its line numbers.

Show markup
<bmx-code-diff id="ex-diff" language="typescript" original-label="orders.ts (main)" modified-label="orders.ts (proposed)" actions="both" style="--bmx-code-diff-height: 30rem"></bmx-code-diff>

<div class="row" style="margin-block-start: 0.75rem; gap: 0.5rem; flex-wrap: wrap; align-items: center">
  <label><input type="checkbox" id="ex-diff-ws"> Ignore whitespace</label>
  <label><input type="checkbox" id="ex-diff-wrap" checked> Wrap lines</label>
  <bmx-button variant="outline" tone="neutral" id="ex-diff-patch">Show as a patch</bmx-button>
</div>
<p class="note" id="ex-diff-out">F7 and Shift+F7 move between changes. Revert a change, or take it into the original, with the buttons above it.</p>
<pre class="note" id="ex-diff-patch-out" style="white-space: pre-wrap; max-block-size: 16rem; overflow: auto" hidden></pre>

<h3>A patch, without its files</h3>

<p class="note">Give it the output of <code>git diff</code> (or any unified diff): each file and hunk is shown with its line numbers.</p>
<bmx-code-diff id="ex-diff-patch-view" view="unified" style="--bmx-code-diff-height: 20rem"></bmx-code-diff>

<script type="module">
  await customElements.whenDefined('bmx-code-diff');
  const before = `import { round } from './money';

const TAX_RATE = 0.2;

export function orderTotal(lines, discount = 0) {
  let total = 0;
  for (const line of lines) {
    total += line.quantity * line.price;
  }
  const taxed = total * (1 + TAX_RATE);
  return round(taxed - discount);
}

export function lineCount(lines) {
  return lines.length;
}

export const labels = { draft: 'Draft', paid: 'Paid' };
`;
  const after = `import { round } from './money';
import { checkSku } from './sku';

const TAX_RATE = 0.2;

export function orderTotal(lines, discount = 0) {
  let total = 0;
  for (const line of lines) {
    checkSku(line.sku);
    total += line.quantity * line.unitPrice;
  }
  const taxed = total * (1 + TAX_RATE);
  return round(Math.max(0, taxed - discount));
}

export const labels = { draft: 'Draft', paid: 'Paid', refunded: 'Refunded' };
`;
  const diff = document.getElementById('ex-diff');
  diff.original = before;
  diff.modified = after;
  diff.addEventListener('bmxCodeDiffChange', e => {
    document.getElementById('ex-diff-out').textContent = e.detail.action === 'revert' ? 'Reverted in the proposed text.' : 'Taken into the original.';
  });
  document.getElementById('ex-diff-ws').addEventListener('change', e => (diff.ignoreWhitespace = e.target.checked ? 'all' : 'none'));
  document.getElementById('ex-diff-wrap').addEventListener('change', e => (diff.wrap = e.target.checked));
  document.getElementById('ex-diff-patch').addEventListener('click', async () => {
    const out = document.getElementById('ex-diff-patch-out');
    out.hidden = false;
    out.textContent = await diff.toPatch();
  });

  document.getElementById('ex-diff-patch-view').patch = `diff --git a/src/store.py b/src/store.py
index 3b18e51..a9c2f04 100644
--- a/src/store.py
+++ b/src/store.py
@@ -10,7 +10,8 @@ def load(path):
     with open(path) as f:
-        data = json.load(f)
+        data = json.load(f, strict=False)
+        log.info("loaded %s", path)
     return data
 
 
@@ -40,3 +41,3 @@ class Store:
     def save(self):
-        self.flush()
+        self.flush(sync=True)
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-# Store
+# Store service
 
 Keeps orders until they are paid.
`;
</script>

diff.original = before; diff.modified = after;

With actions, each change has buttons to revert it in the modified text or take it into the original; bmxCodeDiffChange reports both texts as they then are. toPatch() writes the differences as a unified diff.

Keyboard: F7 and Shift+F7 (or Alt+Down and Alt+Up, or n and p) move from change to change and read out where each one is.

Properties

PropertyAttributeTypeDefaultDescription
actions actions 'none' | 'revert' | 'accept' | 'both' 'none' Buttons on each change: revert (put the original back), accept (take it into the original), both, or none.
context context number 3 Unchanged lines shown around each change; the rest fold away. -1 shows everything.
ignoreCase ignore-case boolean false Letters compare without their case.
ignoreWhitespace ignore-whitespace BmxCodeWhitespace 'none' Whitespace that is not a difference: none, trailing (at line ends) or all.
label label string — What the differences are of, said by screen readers.
language language string — The language to colour: a name, an extension or a file name. From a patch's file names when not set.
lineNumbers line-numbers boolean true Show line numbers.
modified modified string '' The second text. Changed by revert.
modifiedLabel modified-label string —
original original string '' The first text. Changed by accept.
originalLabel original-label string — What the first and second texts are called: file names, Yesterday, Proposed.
patch patch string — A unified diff (a patch) to show instead of two texts. Several files are shown one after another.
strings strings Partial<BmxCodeDiffStrings> | string — Words it shows or says: any of BmxCodeDiffStrings. JSON in markup.
toolbar toolbar boolean true Show the toolbar: counts, labels, the next and previous change, and the view switch.
view view 'split' | 'unified' | 'auto' 'auto' split (side by side), unified (one column), or auto: side by side when there is room.
wordDiff word-diff boolean true Mark the words that changed within changed lines.
wrap wrap boolean true Wrap long lines; otherwise each side scrolls across.

Events

EventDetailDescription
bmxCodeDiffChange BmxCodeDiffChangeDetail A change was reverted or accepted with its buttons.
bmxCodeDiffCompute BmxCodeDiffStatsDetail The differences were worked out (again).

Methods

MethodSignatureDescription
acceptChange acceptChange(index: number) => Promise<boolean> Take one change into the original text.
expandAll expandAll() => Promise<void> Open every folded stretch of unchanged lines.
getChanges getChanges() => Promise<BmxCodeDiffChange[]> Every change, lines from 1.
getStats getStats() => Promise<BmxCodeDiffStatsDetail> How many lines were added and removed, in how many changes.
goToChange goToChange(index: number) => Promise<void> Scroll to a change by index, from 0.
nextChange nextChange() => Promise<number> Scroll to the next change (wrapping round). Returns its index, from 0, or -1 when there are none.
previousChange previousChange() => Promise<number>
revertChange revertChange(index: number) => Promise<boolean> Revert one change (put the original's lines back in the modified text).
setFocus setFocus() => Promise<void>
toPatch toPatch(options?: { context?: number; originalName?: string; modifiedName?: string; }) => Promise<string> The differences as a unified diff (a patch), with context lines around each change.

CSS shadow parts

PartDescription
body the scrolling lines.
frame the border and background.
toolbar the counts, the labels and the buttons above the lines.

CSS custom properties

PropertyDescription
--bmx-code-diff-added The background of added lines.
--bmx-code-diff-added-word The background of added words.
--bmx-code-diff-font The typeface of the lines. Default the library's monospace.
--bmx-code-diff-font-size The size of the lines. Default 0.8125rem.
--bmx-code-diff-height The greatest height before the lines scroll. Default 32rem.
--bmx-code-diff-removed The background of removed lines.
--bmx-code-diff-removed-word The background of removed words.