DEV Community

Cover image for ↩️ Native Undo & Redo for the Web

↩️ Native Undo & Redo for the Web

Sam Thorogood on April 23, 2018

How can you support Undo and Redo natively for elements that don't support it by default? Think drag and drop reordering, games, or rich text edito...
Collapse
 
rodneyrehm profile image
Rodney Rehm • Edited

Q: This input might confuse accessible user-agents. Please leave me a comment if you have ideas on how to solve this for screen readers etc!

I'd expect adding aria-hidden="true" would complement your efforts to avoid keyboard focus and effectively hide the element from screen readers.

We focus on the undoer, but this is prevented by the event handler we set up initially

That's not quite correct as the element receives focus for a very brief time. For visually abled users shifting focus back to the element that had focus before undoer briefly got it, will likely be unobservable, except maybe for the cursor position to change.

However, for users relying on screen readers this may cause an audible indication of focus having been shifted, even though it was shifted back to where it was before. Things may get worse than that if you're dealing with a situation where "nothing has focus" (document.activeElement === document.body). It's pretty likely that the screen reader's "reading position" follows to the focused element, essentially resetting their whereabouts on every pushNewUndoState().

Collapse
 
krusadercell profile image
KrusaderCell

Nice article. I have one question, though. Why are you using splice in this._stack.splice(nextID, this._stack.length - nextID, data); and not push i.e. this._stack.push(data);? For me it's seems way more complicated to do all that in order to only add new element as the last element of the Array.

Collapse
 
samthor profile image
Sam Thorogood

Good question!

It's subtle but we actually want to remove all elements after nextID.

Let's say I do three actions.. 1, 2, 3. I now have three (well four, if you include initial state) states on my stack.

If I undo two of those (3 and 2), my current state will be action 1. But I'm able to redo those two by typing Ctrl-Shift-Z.

I'm also able do perform another action, which would invalidate those previous states, because I can no longer redo them. That's what the splice is doing—removing the rest of the stack and pushing a new state on.

I hope that explanation helps!

Collapse
 
donniedamato profile image
Donnie D'Amato

I dived into this issue in another way using MutationObserver. Here's a codepen: codepen.io/fauxserious/pen/eEOMgr