feat(flowchart): keyboard shortcuts overlay (C19)

After 17 commits of feature work the standalone window had a lot of
hidden keyboard shortcuts (Cmd+Z, Cmd+C/V/D, Delete, Alt+drag, etc.)
that no user could discover. This adds:

  - 'Shortcuts (?)' toolbar button next to Export Visio.
  - '?' key (or Shift+/) toggles the overlay; Esc closes it.
  - A read-only modal that lists every canvas-relevant shortcut in
    a 2-column table (Action / Shortcut) with <kbd> tags for the
    key combos, styled to look like physical keys.
  - Click on the dimmed overlay backdrop also closes it.
  - Reuses the existing .fc-modal fade + scale-in animation.

The overlay sits inside #fc-left so it overlays the canvas, not the
side panel. Hidden by default via the HTML hidden attribute.

Implementation: btnHelp / shortcutsModal / shortcutsOverlay /
shortcutsClose are added to the bundle's els bag. A small
showShortcutsOverlay / hideShortcutsOverlay pair toggles their
.hidden. The keydown handler gets two new branches at the top:

  - Esc: close the overlay if open.
  - ? (computed from ev.key === '?' or 'Shift+/', no modifier
    keys held): toggle the overlay.

Both branches return early so the existing Cmd+Z / Cmd+C / etc.
handlers below them still work.

Tests: 3 new (1386 total) covering the toolbar toggle, the
? + Esc keyboard flow, and that every shortcut the user can
trigger is listed in the modal.

Amit Haridas
This commit is contained in:
2026-09-30 22:43:05 +05:30
parent dfce7fd3ac
commit 1a357bbd6a
3 changed files with 214 additions and 1 deletions
+47
View File
@@ -744,4 +744,51 @@ describe('flowchart-bundle: Export Image buttons (v4.13.0)', () => {
expect(apiMock.saveFile).toHaveBeenCalledTimes(1);
expect(document.getElementById('fc-status').textContent).toBe('Export cancelled');
});
// v4.13.0 — keyboard shortcuts overlay. Triggered by the toolbar
// button or the ? key, dismissed by the Close button or Esc.
describe('shortcuts overlay (v4.13.0)', () => {
test('toolbar button toggles the overlay open', async () => {
await loadBundle();
const modal = document.getElementById('fc-shortcuts-modal');
expect(modal.hidden).toBe(true);
document.getElementById('fc-btn-help').click();
expect(modal.hidden).toBe(false);
});
test('? key opens the overlay, Esc closes it', async () => {
await loadBundle();
const modal = document.getElementById('fc-shortcuts-modal');
expect(modal.hidden).toBe(true);
document.dispatchEvent(new KeyboardEvent('keydown', { key: '?' }));
expect(modal.hidden).toBe(false);
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
expect(modal.hidden).toBe(true);
});
test('overlay lists the canvas-relevant shortcuts', async () => {
await loadBundle();
document.getElementById('fc-btn-help').click();
const table = document.querySelector('#fc-shortcuts-modal .fc-shortcuts-table');
expect(table).not.toBeNull();
const rows = Array.from(table.querySelectorAll('tbody tr')).map(
(tr) => tr.children[0].textContent
);
expect(rows).toEqual(
expect.arrayContaining([
'Undo',
'Redo',
'Copy selection',
'Paste',
'Duplicate',
'Delete selected',
'Connect from a node',
'Multi-select',
'Show this overlay',
])
);
});
});
});