Skip to content

JavaScript

phil/text-diff-bundle

The controller

assets/src/comparison_controller.js is a Stimulus controller, registered as phil--text-diff-bundle--comparison. It is placed around a difference written by either filter:

<section data-controller="phil--text-diff-bundle--comparison"
         data-phil--text-diff-bundle--comparison-counter-text-value="Change {current} of {total}"
         data-phil--text-diff-bundle--comparison-total-text-value="{total} changes"
         data-phil--text-diff-bundle--comparison-none-text-value="No changes">
    <div role="group" aria-label="View">
        <button type="button" data-action="phil--text-diff-bundle--comparison#show" data-phil--text-diff-bundle--comparison-mode-param="inline" data-phil--text-diff-bundle--comparison-target="mode">Both</button>
        <button type="button" data-action="phil--text-diff-bundle--comparison#show" data-phil--text-diff-bundle--comparison-mode-param="old" data-phil--text-diff-bundle--comparison-target="mode">Before</button>
        <button type="button" data-action="phil--text-diff-bundle--comparison#show" data-phil--text-diff-bundle--comparison-mode-param="new" data-phil--text-diff-bundle--comparison-target="mode">After</button>
    </div>
    <button type="button" data-action="phil--text-diff-bundle--comparison#previous">Previous change</button>
    <button type="button" data-action="phil--text-diff-bundle--comparison#next">Next change</button>
    <p role="status" data-phil--text-diff-bundle--comparison-target="counter"></p>
    <div data-phil--text-diff-bundle--comparison-target="output">{{ old|text_diff(new) }}</div>
</section>
TargetRole
outputWhere the marks are looked for; the controller's element when absent
counterA live region saying how many changes the view shows, or which one is current
modeA button with a mode param; aria-pressed says whether its view is shown
ValueDefaultMeaning
modeinlineThe view: inline, both texts marked; old, the old text alone; new, the new text alone
counter-text{current} of {total}The counter while a change is current
total-text{total} change(s)The counter when none is
none-textNo changesThe counter when the view shows no change
ActionWhat it does
showShows the view of its mode param; an unknown one shows the inline view
next, previousMoves to the following or preceding change the view shows, round the ends

What it does

The view is written on the element as data-text-diff-mode, which the stylesheet reads to hide the insertions of the old view and the deletions of the new one. Changing the view clears the current change.

A change is a mark the view shows: every <ins> and <del> in the inline view, a deletion followed by its insertion — nothing but white space between them — counting as one replacement; the deletions alone in the old view; the insertions alone in the new view. The current change gets the class text-diff-current, its first mark aria-current="true" and the focus, and is scrolled to the middle of the screen — at once when the reader prefers reduced motion, smoothly otherwise.

Labels come from the values, so a page writes them in its own language. Keyboard shortcuts are the page's to add, as Stimulus actions on the element:

data-action="keydown.n->phil--text-diff-bundle--comparison#next keydown.p->phil--text-diff-bundle--comparison#previous"

Its tests

tests/js runs under Node's test runner, Stimulus replaced by a stub:

node --import ./tests/js/register.mjs --test tests/js