PluginProbe
Extendify / 3.1.0
Extendify v3.1.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / QuickEdit / lib / keyboard-undo.js

keyboard-undo.js in Extendify 3.1.0, at src/QuickEdit/lib/keyboard-undo.js

81 lines 2.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 ANNOUNCE_STORAGE_KEY,
3 getStackDepth,
4 performUndo,
5 } from '../state/undo';
6
7 const isInEditable = (el) => {
8 let node = el;
9 while (node?.nodeType === 1) {
10 if (node.isContentEditable) return true;
11 const tag = node.tagName;
12 // An empty field has no native undo to protect, so it must not swallow
13 // the page-level undo — the agent chat box auto-focuses empty on the
14 // frontend after a Quick Edit save + reload.
15 if (tag === 'INPUT' || tag === 'TEXTAREA') return node.value.trim() !== '';
16 if (tag === 'SELECT') return true;
17 node = node.parentElement;
18 }
19 return false;
20 };
21
22 const onKeydown = (e) => {
23 if (!(e.metaKey || e.ctrlKey)) return;
24 if (e.shiftKey || e.altKey) return;
25 if (e.key !== 'z' && e.key !== 'Z') return;
26 // Defer to the host's own undo when an inline editor, media frame, or popover is open.
27 if (document.querySelector('.extendify-quick-edit-host')) return;
28 if (document.querySelector('.media-modal')) return;
29 if (
30 document.querySelector(
31 '.extendify-quick-edit-color-popover, .extendify-quick-edit-image-menu',
32 )
33 ) {
34 return;
35 }
36 if (isInEditable(e.target)) return;
37 if (getStackDepth() === 0) return;
38 e.preventDefault();
39 performUndo();
40 };
41
42 let attached = false;
43
44 export const attachKeyboardUndo = () => {
45 if (attached) return;
46 attached = true;
47 document.addEventListener('keydown', onKeydown, true);
48 };
49
50 export const detachKeyboardUndo = () => {
51 if (!attached) return;
52 attached = false;
53 document.removeEventListener('keydown', onKeydown, true);
54 };
55
56 // Announces the previous page's undo result after the reload, so SR users know
57 // the navigation was their own undo and not a page refresh.
58 export const announcePostReload = () => {
59 let msg = null;
60 try {
61 msg = window.sessionStorage.getItem(ANNOUNCE_STORAGE_KEY);
62 if (msg) window.sessionStorage.removeItem(ANNOUNCE_STORAGE_KEY);
63 } catch (_) {
64 return;
65 }
66 if (!msg) return;
67 const live = document.createElement('div');
68 live.setAttribute('role', 'status');
69 live.setAttribute('aria-live', 'polite');
70 live.setAttribute('aria-atomic', 'true');
71 live.style.cssText =
72 'position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden;';
73 document.body.appendChild(live);
74 // Assistive tech ignores aria-live regions that contain text on initial render;
75 // mount empty, then set text on the next tick.
76 window.setTimeout(() => {
77 live.textContent = msg;
78 }, 100);
79 window.setTimeout(() => live.remove(), 5000);
80 };
81