Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance accessibility of autocomplete #4637

Merged
merged 26 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d7f849f
Add unit tests for id attributes in autocomplete and its items.
Comandeer Apr 22, 2021
10c56f9
Add ids to autocomplete and its items.
Comandeer Apr 22, 2021
88ac4fc
Add unit test for ARIA attributes added to editable.
Comandeer Apr 22, 2021
942722c
Add ARIA attributes to the editable.
Comandeer Apr 22, 2021
933517b
Update failing test for autocomplete item.
Comandeer Apr 22, 2021
0613af7
Move a11y related tests to the separate suite.
Comandeer Apr 22, 2021
defaf9a
Add ARIA attributes only to the inline editables.
Comandeer Apr 22, 2021
5fa638e
Update [aria-expanded] attribute on opening/closing autocomplete.
Comandeer Apr 22, 2021
bcd5965
Add updating of [aria-activedescendant] attribute on opening and clos…
Comandeer Apr 22, 2021
3987725
Add additional tests for autocomplete navigation + slightly adjust co…
Comandeer Apr 22, 2021
a22964a
Remove ARIA attributes on destroy.
Comandeer Apr 22, 2021
8b43de1
Ensure that changes are applied only for inline editors.
Comandeer Apr 22, 2021
c72339e
Add appropriate roles to autocomplete and its items.
Comandeer Apr 22, 2021
f4ed65c
Ensure that there is still editable to be changed.
Comandeer Apr 22, 2021
4804768
Add manual test.
Comandeer Apr 22, 2021
427d0a2
Update names of methods operating on ARIA attributes on editable.
Comandeer Apr 27, 2021
d375315
Update comment in the code.
Comandeer Apr 27, 2021
6b58438
Use isActive from event
sculpt0r Apr 28, 2021
21ad8ee
Move autocomplete destroy into teardown hook.
Comandeer Apr 29, 2021
2b2af11
Fix typo in test: tearDown, not teardown!
Comandeer Apr 29, 2021
73e8157
Add unit test for propagating change-isActive event when the autocomp…
Comandeer May 10, 2021
0314ed9
Ignore a11y tests in unsupported environments.
Comandeer May 10, 2021
f465615
Slightly adjusting unit tests: nicer tests names and assertion messages.
Comandeer May 10, 2021
c733ac3
Adjusting manual test's description.
Comandeer May 10, 2021
019cfba
Fix: single quote char in unit test message
sculpt0r May 10, 2021
1bbc255
Add changelog
sculpt0r May 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ CKEditor 4 Changelog
## CKEditor 4.16.1

Fixed Issues:

* [#4617](https://github.com/ckeditor/ckeditor4/issues/4617): Fixed: [Autocomplete](https://ckeditor.com/cke4/addon/autocomplete) is not accessible in inline editors.
* [#4493](https://github.com/ckeditor/ckeditor4/issues/4493): Fixed: [Rich Combo](https://ckeditor.com/cke4/addon/richcombo) label does not reflects the current value of the combobox.
* [#1572](https://github.com/ckeditor/ckeditor4/issues/1572): Fixed: Paragraph before or after a [widget](https://ckeditor.com/cke4/addon/widget) can not be removed. Thanks to [bunglegrind](https://github.com/bunglegrind)!
* [#4301](https://github.com/ckeditor/ckeditor4/issues/4301): Fixed: Pasted content is overwritten when pasted in initially empty editor with [`div` enter mode](https://ckeditor.com/docs/ckeditor4/latest/features/enterkey.html).
Expand Down
103 changes: 97 additions & 6 deletions plugins/autocomplete/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@
this._listeners.push( this.view.on( 'change-selectedItemId', this.onSelectedItemId, this ) );
this._listeners.push( this.view.on( 'click-item', this.onItemClick, this ) );

// (#4617)
this._listeners.push( this.model.on( 'change-isActive', this.updateAriaAttributesOnEditable, this ) );

// Update view position on viewport change.
// Note: CKEditor's event system has a limitation that one function
// cannot be used as listener for the same event more than once. Hence, wrapper functions.
Expand All @@ -293,16 +296,19 @@
e.data.preventDefault();
}, null, null, 9999 ) );

// Register keybindings if editor is already initialized.
// Register keybindings and add ARIA attributes to the editable right away
// if editor is already initialized.
if ( editable ) {
this.registerPanelNavigation();
this.addAriaAttributesToEditable();
}

// Note: CKEditor's event system has a limitation that one function
// cannot be used as listener for the same event more than once. Hence, wrapper function.
// (#4107)
editor.on( 'contentDom', function() {
this.registerPanelNavigation();
this.addAriaAttributesToEditable();
}, this );
},

Expand All @@ -317,6 +323,73 @@
}, this, null, 5 ) );
},

/**
* @since 4.16.1
*/
addAriaAttributesToEditable: function() {
var editable = this.editor.editable(),
autocompleteId = this.view.element.getAttribute( 'id' );

if ( !editable.isInline() ) {
return;
}

editable.setAttribute( 'aria-controls', autocompleteId );
editable.setAttribute( 'aria-activedescendant', '' );
editable.setAttribute( 'aria-autocomplete', 'list' );
editable.setAttribute( 'aria-expanded', 'false' );
},

/**
* @since 4.16.1
*/
updateAriaAttributesOnEditable: function( evt ) {
var editable = this.editor.editable(),
isActive = evt.data;

if ( !editable.isInline() ) {
return;
}

editable.setAttribute( 'aria-expanded', isActive ? 'true' : 'false' );

if ( !isActive ) {
editable.setAttribute( 'aria-activedescendant', '' );
}
},

/**
* @since 4.16.1
*/
updateAriaActiveDescendantAttributeOnEditable: function( id ) {
var editable = this.editor.editable();

if ( !editable.isInline() ) {
return;
}

editable.setAttribute( 'aria-activedescendant', id );
},

/**
* @since 4.16.1
*/
removeAriaAttributesFromEditable: function() {
var editable = this.editor.editable();

if ( !editable || !editable.isInline() ) {
return;
}

editable.removeAttributes( [
'aria-controls',
'aria-expanded',
'aria-activedescendant'
] );

editable.setAttribute( 'aria-autocomplete', 'none' );
},

/**
* Closes the view and sets its {@link CKEDITOR.plugins.autocomplete.model#isActive state} to inactive.
*/
Expand Down Expand Up @@ -371,6 +444,7 @@
this._listeners = [];

this.view.element && this.view.element.remove();
this.removeAriaAttributesFromEditable();
},

/**
Expand Down Expand Up @@ -526,8 +600,13 @@
* @private
*/
onSelectedItemId: function( evt ) {
this.model.setItem( evt.data );
this.view.selectItem( evt.data );
var itemId = evt.data,
selectedItem = this.view.getItemById( itemId );

this.model.setItem( itemId );
this.view.selectItem( itemId );

this.updateAriaActiveDescendantAttributeOnEditable( selectedItem.getAttribute( 'id' ) );
},

/**
Expand Down Expand Up @@ -726,11 +805,16 @@
* @returns {CKEDITOR.dom.element}
*/
createElement: function() {
var el = new CKEDITOR.dom.element( 'ul', this.document );
var el = new CKEDITOR.dom.element( 'ul', this.document ),
id = CKEDITOR.tools.getNextId();

// Id is needed to correctly bind autocomplete with the editable (#4617).
el.setAttribute( 'id', id );
el.addClass( 'cke_autocomplete_panel' );
// Below float panels and context menu, but above maximized editor (-5).
el.setStyle( 'z-index', this.editor.config.baseFloatZIndex - 3 );
// Add also appropriate role (#4617).
el.setAttribute( 'role', 'listbox' );

return el;
},
Expand All @@ -742,8 +826,15 @@
* @returns {CKEDITOR.dom.element}
*/
createItem: function( item ) {
var encodedItem = encodeItem( item );
return CKEDITOR.dom.element.createFromHtml( this.itemTemplate.output( encodedItem ), this.document );
var encodedItem = encodeItem( item ),
itemElement = CKEDITOR.dom.element.createFromHtml( this.itemTemplate.output( encodedItem ), this.document ),
id = CKEDITOR.tools.getNextId();

// Add attributes needed for a11y support (#4617).
itemElement.setAttribute( 'id', id );
itemElement.setAttribute( 'role', 'option' );

return itemElement;
},

/**
Expand Down
Loading