Skip to content

Commit

Permalink
Desktop: Make overwrite mode toggle more accessible
Browse files Browse the repository at this point in the history
  • Loading branch information
personalizedrefrigerator committed Nov 26, 2024
1 parent 13f71e7 commit 7f11c92
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 10 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@ packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/keyUpHandlerExtension.js
packages/editor/CodeMirror/utils/overwriteModeExtension.test.js
packages/editor/CodeMirror/utils/overwriteModeExtension.js
packages/editor/CodeMirror/utils/searchExtension.js
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/keyUpHandlerExtension.js
packages/editor/CodeMirror/utils/overwriteModeExtension.test.js
packages/editor/CodeMirror/utils/overwriteModeExtension.js
packages/editor/CodeMirror/utils/searchExtension.js
Expand Down
66 changes: 66 additions & 0 deletions packages/editor/CodeMirror/utils/keyUpHandlerExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { EditorView, ViewPlugin } from '@codemirror/view';

interface OnKeyUpEvent {
domEvent: KeyboardEvent;
view: EditorView;
// true if other keys were pressed (and possibly released) during the time
// that this key was down (and before it was released).
otherKeysWerePressed: boolean;
}

// Should return true if the event was handled
type OnKeyUpHandler = (event: OnKeyUpEvent)=> boolean;
// Should return true if the given event matches the key that should
// be handled by the extension:
type IsTargetKeyCallback = (event: KeyboardEvent)=> boolean;

// CodeMirror's built-in keyboard event handlers trigger on key down, rather than on
// key up. In some cases, it's useful to trigger keyboard shortcuts on key up instead.
// For example, if a shortcut should only activate if it isn't pressed at the same time
// as other keys.
const keyUpHandlerExtension = (isTargetKey: IsTargetKeyCallback, onKeyUp: OnKeyUpHandler) => {
return ViewPlugin.fromClass(class {
private otherKeysWerePressed = false;
private targetKeyDown = false;

public constructor(private view: EditorView) {
view.contentDOM.addEventListener('keydown', this.onKeyDown);
view.contentDOM.addEventListener('keyup', this.onKeyUp);
}

public destroy() {
this.view?.contentDOM?.removeEventListener('keyup', this.onKeyUp);
}

private onKeyDown = (event: KeyboardEvent) => {
if (isTargetKey(event)) {
this.targetKeyDown = true;
} else if (this.targetKeyDown) {
this.otherKeysWerePressed = true;
}
};

private onKeyUp = (event: KeyboardEvent) => {
if (isTargetKey(event)) {
if (this.targetKeyDown && !event.defaultPrevented) {
const handled = onKeyUp({
domEvent: event,
view: this.view,
otherKeysWerePressed: this.otherKeysWerePressed,
});

if (handled) {
event.preventDefault();
}
}

this.targetKeyDown = false;
this.otherKeysWerePressed = false;
} else if (this.targetKeyDown) {
this.otherKeysWerePressed = true;
}
};
});
};

export default keyUpHandlerExtension;
22 changes: 21 additions & 1 deletion packages/editor/CodeMirror/utils/overwriteModeExtension.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EditorSelection } from '@codemirror/state';
import createTestEditor from '../testUtil/createTestEditor';
import overwriteModeExtension, { toggleOverwrite } from './overwriteModeExtension';
import overwriteModeExtension, { overwriteModeEnabled, toggleOverwrite } from './overwriteModeExtension';
import typeText from '../testUtil/typeText';
import pressReleaseKey from '../testUtil/pressReleaseKey';

Expand Down Expand Up @@ -36,6 +36,8 @@ describe('overwriteModeExtension', () => {
const editor = await createEditor('Test!');

pressReleaseKey(editor, { key: 'Insert', code: 'Insert' });
expect(overwriteModeEnabled(editor)).toBe(true);

typeText(editor, 'Exam');
expect(editor.state.doc.toString()).toBe('Exam!');

Expand All @@ -44,6 +46,24 @@ describe('overwriteModeExtension', () => {
expect(editor.state.doc.toString()).toBe('Example!');
});

test('<insert> should not toggle overwrite mode if other keys are pressed', async () => {
// On Linux, the Orca screen reader's default "do screen reader action" key is
// <insert>. To avoid toggling insert mode when users perform screen reader actions,
// pressing <insert> should only toggle insert mode in certain cases.
const editor = await createEditor('Test');
const insertKey = { code: 'Insert', key: 'Insert ' };

editor.contentDOM.dispatchEvent(new KeyboardEvent('keydown', insertKey));
expect(overwriteModeEnabled(editor)).toBe(false);

// Pressing & releasing the space key should prevent insert mode from being enabled
pressReleaseKey(editor, { code: 'Space', key: 'Space' });

editor.contentDOM.dispatchEvent(new KeyboardEvent('keyup', insertKey));

expect(overwriteModeEnabled(editor)).toBe(false);
});

test('should insert text if the cursor is at the end of a line', async () => {
const editor = await createEditor('\nTest', true);
typeText(editor, 'Test! This is a test! ');
Expand Down
38 changes: 29 additions & 9 deletions packages/editor/CodeMirror/utils/overwriteModeExtension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { keymap, EditorView } from '@codemirror/view';
import { EditorView } from '@codemirror/view';
import { StateField, Facet, StateEffect } from '@codemirror/state';
import keyUpHandlerExtension from './keyUpHandlerExtension';

const overwriteModeFacet = Facet.define({
combine: values => values[0] ?? false,
Expand Down Expand Up @@ -62,17 +63,36 @@ const overwriteModeState = StateField.define({
],
});

export const overwriteModeEnabled = (view: EditorView) => {
return view.state.field(overwriteModeState);
};

const setOverwriteModeEnabled = (enabled: boolean, view: EditorView) => {
view.dispatch({
effects: [
toggleOverwrite.of(enabled),
EditorView.announce.of(
// TODO: Localize:
enabled ? 'Overwrite mode enabled' : 'Overwrite mode disabled',
),
],
});
};

const overwriteModeExtension = [
overwriteModeState,
keymap.of([{
key: 'Insert',
run: (view) => {
view.dispatch({
effects: toggleOverwrite.of(!view.state.field(overwriteModeState)),
});
return false;
keyUpHandlerExtension(
(event) => (
event.code === 'Insert' && !event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey
),
({ view, otherKeysWerePressed }) => {
if (otherKeysWerePressed) {
return false;
}
setOverwriteModeEnabled(!overwriteModeEnabled(view), view);
return true;
},
}]),
),
];

export default overwriteModeExtension;

0 comments on commit 7f11c92

Please sign in to comment.