Skip to content

Commit

Permalink
Annote line numbers with aria-hidden
Browse files Browse the repository at this point in the history
  • Loading branch information
aomarks committed Apr 9, 2021
1 parent 99aadc9 commit 2ddd555
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- The editor now uses the CodeMirror `contenteditable` input style, which has
much better screen reader support.

- Line numbers are now annotated with `aria-hidden` so that they are not voiced
by screen readers.

## [0.8.0] - 2021-04-02

### Added
Expand Down
39 changes: 39 additions & 0 deletions src/playground-code-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export class PlaygroundCodeEditor extends LitElement {
this._valueChangingFromOutside = false;
break;
case 'lineNumbers':
this._enableOrDisableAriaLineNumberObserver();
cm.setOption('lineNumbers', this.lineNumbers);
break;
case 'type':
Expand Down Expand Up @@ -256,6 +257,7 @@ export class PlaygroundCodeEditor extends LitElement {
const cm = CodeMirror(
(dom) => {
this._cmDom = dom;
this._enableOrDisableAriaLineNumberObserver();
this._resizing = true;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
Expand Down Expand Up @@ -296,6 +298,43 @@ export class PlaygroundCodeEditor extends LitElement {
this._codemirror = cm;
}

private _ariaLineNumberObserver?: MutationObserver;

/**
* Prevent screen readers from voicing line numbers.
*
* When line numbers are active, watch for lines inserted into the DOM by
* CodeMirror, and add the "aria-hidden" attribute to their line numbers.
*
* See https://github.com/codemirror/CodeMirror/issues/6578
*/
private _enableOrDisableAriaLineNumberObserver() {
if (this.lineNumbers && !this._ariaLineNumberObserver) {
// Start observing newly added lines.
const linesParent = this._cmDom?.querySelector('.CodeMirror-code');
if (!linesParent) {
console.error('Internal playground error: .CodeMirror-code missing');
return;
}
this._ariaLineNumberObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
// Could be e.g. a text node. Cast so we can check for presence of
// querySelector with optional chaining instead of a typeof test.
(node as Partial<Element>)
.querySelector?.('.CodeMirror-gutter-wrapper')
?.setAttribute('aria-hidden', 'true');
}
}
});
this._ariaLineNumberObserver.observe(linesParent, {childList: true});
} else if (!this.lineNumbers && this._ariaLineNumberObserver) {
// Line numbers are no longer rendering.
this._ariaLineNumberObserver.disconnect();
this._ariaLineNumberObserver = undefined;
}
}

/**
* Create hidden and folded regions for playground-hide and playground-fold
* comments.
Expand Down
35 changes: 35 additions & 0 deletions src/test/playground-ide_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,39 @@ suite('playground-ide', () => {

assert.equal(cmCode.getAttribute('contenteditable'), 'true');
});

test('a11y: line numbers get aria-hidden attribute', async () => {
const ide = document.createElement('playground-ide')!;
ide.lineNumbers = true;
const editor = (await pierce(
'playground-ide',
'playground-file-editor',
'playground-code-editor'
)) as PlaygroundCodeEditor;
const queryHiddenLineNumbers = () =>
[
...editor.shadowRoot!.querySelectorAll('.CodeMirror-gutter-wrapper'),
].filter((gutter) => gutter.getAttribute('aria-hidden') === 'true');

// Initial render with line-numbers enabled.
assert.equal(queryHiddenLineNumbers().length, 2);

// Disable line numbers.
ide.lineNumbers = false;
await new Promise((r) => requestAnimationFrame(r));
assert.equal(queryHiddenLineNumbers().length, 0);

// Re-enable line numbers.
ide.lineNumbers = true;
await new Promise((r) => requestAnimationFrame(r));
assert.equal(queryHiddenLineNumbers().length, 2);

// Add a line.
const editorInternals = (editor as unknown) as {
_codemirror: PlaygroundCodeEditor['_codemirror'];
};
editorInternals._codemirror!.setValue(editor.value + '\nBaz');
await new Promise((r) => requestAnimationFrame(r));
assert.equal(queryHiddenLineNumbers().length, 3);
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"incremental": true,
"target": "es2019",
"module": "esnext",
"lib": ["ES2020", "ES2015.iterable", "DOM"],
"lib": ["ES2020", "ES2015.iterable", "DOM", "DOM.Iterable"],
"declaration": true,
"declarationMap": true,
"sourceMap": false,
Expand Down

0 comments on commit 2ddd555

Please sign in to comment.