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

Implemented focusing first item after opening a dropdown #12003

Merged
merged 36 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7b1a48f
Add unit tests for focusing first element in panelview on isOpen.
mmotyczynska Jun 29, 2022
bced44c
Focus the first element in panelview on isOpen.
mmotyczynska Jun 29, 2022
fc8eeab
Tests (ui): Added unit tests for focusing the active option in dropdo…
mlewand Jul 1, 2022
fea5120
Internal (ui): List dropdowns created with addListToDropdown() will n…
mlewand Jul 1, 2022
f0ccc66
Tests (ui): Added unit tests for focusing first active item in toolba…
mlewand Jul 1, 2022
6d159f9
Other (ui): Implemented focusing active item for dropdowns that conta…
mlewand Jul 1, 2022
caa292b
Other (ui): Made it possible to disable automatic focusing of active …
mlewand Jul 1, 2022
9967e51
Tests (ui): Updated unit test for focusing #innerPanelView on arrowdown.
mmotyczynska Jul 4, 2022
ea32ff7
Remove unnecessary focus of input in ImageInsertUI - it is already ha…
mmotyczynska Jul 4, 2022
dabe144
Add unit test for arrowdown on dropdown button when dropdow was alrea…
mmotyczynska Jul 4, 2022
3e53336
Add focus for character categories dropdown in SpecialCharactersNavig…
mmotyczynska Jul 5, 2022
657faac
Change autofocus of the first active element in dropdown disabled by …
mmotyczynska Jul 5, 2022
33164a5
Merge branch 'master' into ck/11838-placement-of-focus-after-opening-…
mmotyczynska Jul 6, 2022
9d33e6b
Fix unit tests for extra dropdown open listener.
mmotyczynska Jul 6, 2022
1f40c3e
Introduced the focusChildOnDropdownOpen() dropdown helper. Enabled cu…
oleq Jul 6, 2022
5aeb03f
Enabled custom focus handling on dropdown open in the ColorUI feature.
oleq Jul 6, 2022
20aebc9
Merge branch 'ck/11838-placement-of-focus-after-opening-dropdowns-wit…
mmotyczynska Jul 7, 2022
4115316
Remove unnecessary unit test, we do not have to focus the form explic…
mmotyczynska Jul 8, 2022
a31edd9
Add unit tests for bulleted and numbered list to check focusing first…
mmotyczynska Jul 11, 2022
c3fbf72
Improve cc for addListToDropdown (for an item being an instance other…
mmotyczynska Jul 11, 2022
a52516a
Change buttonview to not prevent default action on mousedown.
mmotyczynska Jul 11, 2022
7b0091c
Log warning when the first child in dropdown does not implement focus…
mmotyczynska Jul 11, 2022
ea26871
Add test to verify that warning is logged when the first child in dro…
mmotyczynska Jul 11, 2022
945dd60
Merge remote-tracking branch 'origin/master' into ck/11838-placement-…
mmotyczynska Jul 11, 2022
1a757ac
Log warning when the active element in dropdown does not implement fo…
mmotyczynska Jul 11, 2022
2867abd
Add test to verify that warning is logged when the active element in …
mmotyczynska Jul 11, 2022
237c850
Merge branch 'master' into ck/11838-placement-of-focus-after-opening-…
oleq Jul 11, 2022
bfd72a5
DX: Docs. Minor code refactoring.
oleq Jul 11, 2022
2993d66
Tests: Code refactoring.
oleq Jul 11, 2022
c7cb7cd
Code refactoring: Renamed the option of addToolbarToDropdown() respon…
oleq Jul 11, 2022
425c8b2
Add test for checking focusing the first active button in alignment d…
mmotyczynska Jul 12, 2022
129ea23
Add test for checking focusing the first active button in highlight d…
mmotyczynska Jul 12, 2022
e6b22a1
Add test for checking focusing the first active button in imagestyle …
mmotyczynska Jul 12, 2022
78565f2
Add test for checking focusing the first active button in color dropd…
mmotyczynska Jul 12, 2022
a70a6e8
Tests: Resolved Rect warning in ColorUI tests.
oleq Jul 12, 2022
64445af
Merge branch 'master' into ck/11838-placement-of-focus-after-opening-…
oleq Jul 13, 2022
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 packages/ckeditor5-alignment/src/alignmentui.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default class AlignmentUI extends Plugin {

// Add existing alignment buttons to dropdown's toolbar.
const buttons = options.map( option => componentFactory.create( `alignment:${ option.name }` ) );
addToolbarToDropdown( dropdownView, buttons );
addToolbarToDropdown( dropdownView, buttons, { enableActiveItemFocusOnDropdownOpen: true } );

// Configure dropdown properties an behavior.
dropdownView.buttonView.set( {
Expand Down
11 changes: 11 additions & 0 deletions packages/ckeditor5-alignment/tests/alignmentui.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ describe( 'Alignment UI', () => {
expect( items.includes( 'Justify' ) ).to.be.true;
} );

it( 'should focus the first active button when dropdown is opened', () => {
const buttonAlignLeft = dropdown.toolbarView.items.get( 0 );
const buttonAlignRight = dropdown.toolbarView.items.get( 1 );
const spy = sinon.spy( buttonAlignRight, 'focus' );

buttonAlignLeft.isOn = false;
buttonAlignRight.isOn = true;
dropdown.isOpen = true;
sinon.assert.calledOnce( spy );
} );

describe( 'config', () => {
beforeEach( async () => {
// Clean up the editor created in main test suite hook.
Expand Down
5 changes: 4 additions & 1 deletion packages/ckeditor5-font/src/ui/colorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { Plugin } from 'ckeditor5/src/core';
import { createDropdown, normalizeColorOptions, getLocalizedColorOptions } from 'ckeditor5/src/ui';
import { createDropdown, normalizeColorOptions, getLocalizedColorOptions, focusChildOnDropdownOpen } from 'ckeditor5/src/ui';

import { addColorTableToDropdown } from '../utils';

Expand Down Expand Up @@ -141,6 +141,9 @@ export default class ColorUI extends Plugin {
}
} );

// Accessibility: focus the first active color when opening the dropdown.
focusChildOnDropdownOpen( dropdownView, () => dropdownView.colorTableView.staticColorsGrid.items.find( item => item.isOn ) );

return dropdownView;
} );
}
Expand Down
14 changes: 14 additions & 0 deletions packages/ckeditor5-font/tests/ui/colorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,20 @@ describe( 'ColorUI', () => {
}
} );

it( 'should focus the first active button when dropdown is opened', () => {
global.document.body.appendChild( dropdown.element );

const secondButton = dropdown.colorTableView.staticColorsGrid.items.get( 1 );
const spy = sinon.spy( secondButton, 'focus' );

secondButton.isOn = true;
dropdown.isOpen = false;
dropdown.isOpen = true;
sinon.assert.calledOnce( spy );

dropdown.element.remove();
} );

describe( 'model to command binding', () => {
it( 'isEnabled', () => {
command.isEnabled = false;
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-highlight/src/highlightui.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export default class HighlightUI extends Plugin {
buttons.push( new ToolbarSeparatorView() );
buttons.push( componentFactory.create( 'removeHighlight' ) );

addToolbarToDropdown( dropdownView, buttons );
addToolbarToDropdown( dropdownView, buttons, { enableActiveItemFocusOnDropdownOpen: true } );
bindToolbarIconStyleToActiveColor( dropdownView );

dropdownView.toolbarView.ariaLabel = t( 'Text highlight toolbar' );
Expand Down
9 changes: 9 additions & 0 deletions packages/ckeditor5-highlight/tests/highlightui.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ describe( 'HighlightUI', () => {
.to.deep.equal( [ false, true, false, false, false, false, undefined, false ] );
} );

it( 'should focus the first active button when dropdown is opened', () => {
const greenMarker = dropdown.toolbarView.items.get( 1 );
const spy = sinon.spy( greenMarker, 'focus' );

greenMarker.isOn = true;
dropdown.isOpen = true;
sinon.assert.calledOnce( spy );
} );

it( 'should mark as toggleable all markers and pens', () => {
const toolbar = dropdown.toolbarView;

Expand Down
2 changes: 0 additions & 2 deletions packages/ckeditor5-image/src/imageinsert/imageinsertui.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@ export default class ImageInsertUI extends Plugin {
const selectedElement = editor.model.document.selection.getSelectedElement();

if ( dropdownView.isOpen ) {
imageInsertView.focus();

if ( imageUtils.isImage( selectedElement ) ) {
imageInsertView.imageURLInputValue = selectedElement.getAttribute( 'src' );
insertButtonView.label = t( 'Update' );
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-image/src/imagestyle/imagestyleui.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default class ImageStyleUI extends Plugin {
const splitButtonView = dropdownView.buttonView;
const splitButtonViewArrow = splitButtonView.arrowView;

addToolbarToDropdown( dropdownView, buttonViews );
addToolbarToDropdown( dropdownView, buttonViews, { enableActiveItemFocusOnDropdownOpen: true } );

splitButtonView.set( {
label: getDropdownButtonTitle( title, defaultButton.label ),
Expand Down
11 changes: 11 additions & 0 deletions packages/ckeditor5-image/tests/imagestyle/imagestyleui.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,17 @@ describe( 'ImageStyleUI', () => {
}
} );

it( 'should focus the first active button when dropdown is opened', () => {
for ( const { view } of dropdowns ) {
const secondButton = view.toolbarView.items.get( 1 );
const spy = sinon.spy( secondButton, 'focus' );

secondButton.isOn = true;
view.isOpen = true;
sinon.assert.calledOnce( spy );
}
} );

it( 'should keep the same label of the secondary (arrow) button when the user changes styles of the image', () => {
const dropdownView = editor.ui.componentFactory.create( 'imageStyle:breakText' );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { Plugin } from 'ckeditor5/src/core';
import { ButtonView, SplitButtonView, createDropdown } from 'ckeditor5/src/ui';
import { ButtonView, SplitButtonView, createDropdown, focusChildOnDropdownOpen } from 'ckeditor5/src/ui';

import ListPropertiesView from './ui/listpropertiesview';

Expand Down Expand Up @@ -184,6 +184,9 @@ function getDropdownViewCreator( { editor, parentCommandName, buttonLabel, butto

dropdownView.panelView.children.add( listPropertiesView );

// Accessibility: focus the first active style when opening the dropdown.
focusChildOnDropdownOpen( dropdownView, () => listPropertiesView.stylesView.children.find( child => child.isOn ) );

return dropdownView;
};
}
Expand Down
18 changes: 18 additions & 0 deletions packages/ckeditor5-list/tests/listproperties/listpropertiesui.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ describe( 'ListPropertiesUI', () => {
sinon.assert.calledOnce( spy );
} );

it( 'on dropdown open should focus the first active button', () => {
const button = stylesView.children.get( 1 );
const spy = sinon.spy( button, 'focus' );

button.isOn = true;
bulletedListDropdown.isOpen = true;
sinon.assert.calledOnce( spy );
} );

describe( 'style button', () => {
let styleButtonView;

Expand Down Expand Up @@ -558,6 +567,15 @@ describe( 'ListPropertiesUI', () => {
sinon.assert.calledOnce( spy );
} );

it( 'on dropdown open should focus the first active button', () => {
const button = stylesView.children.get( 1 );
const spy = sinon.spy( button, 'focus' );

button.isOn = true;
numberedListDropdown.isOpen = true;
sinon.assert.calledOnce( spy );
} );

describe( 'style button', () => {
let styleButtonView;

Expand Down
1 change: 0 additions & 1 deletion packages/ckeditor5-media-embed/src/mediaembedui.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ export default class MediaEmbedUI extends Plugin {
// command.
form.url = command.value || '';
form.urlInputView.fieldView.select();
form.focus();
form.enableCssTransitions();
}, { priority: 'low' } );

Expand Down
7 changes: 0 additions & 7 deletions packages/ckeditor5-media-embed/tests/mediaembedui.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,6 @@ describe( 'MediaEmbedUI', () => {
sinon.assert.calledOnce( spy );
} );

it( 'should focus the form', () => {
const spy = sinon.spy( form, 'focus' );

button.fire( 'open' );
sinon.assert.calledOnce( spy );
} );

it( 'should disable CSS transitions to avoid unnecessary animations (and then enable them again)', () => {
const disableCssTransitionsSpy = sinon.spy( form, 'disableCssTransitions' );
const enableCssTransitionsSpy = sinon.spy( form, 'enableCssTransitions' );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ export default class SpecialCharactersNavigationView extends FormHeaderView {
return this.groupDropdownView.value;
}

/**
* Focuses the character categories dropdown.
*/
focus() {
this.groupDropdownView.focus();
}

/**
* Returns a dropdown that allows selecting character groups.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,14 @@ describe( 'SpecialCharactersNavigationView', () => {
} );
} );
} );

describe( 'focus()', () => {
it( 'focuses the character categories dropdown', () => {
const spy = sinon.spy( view.groupDropdownView, 'focus' );

view.focus();

sinon.assert.calledOnce( spy );
} );
} );
} );
4 changes: 0 additions & 4 deletions packages/ckeditor5-ui/src/button/buttonview.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,6 @@ export default class ButtonView extends View {
children: this.children,

on: {
mousedown: bind.to( evt => {
evt.preventDefault();
} ),

click: bind.to( evt => {
// We can't make the button disabled using the disabled attribute, because it won't be focusable.
// Though, shouldn't this condition be moved to the button controller?
Expand Down
24 changes: 22 additions & 2 deletions packages/ckeditor5-ui/src/dropdown/dropdownpanelview.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import View from '../view';
import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';

/**
* The dropdown panel view class.
Expand Down Expand Up @@ -81,13 +82,32 @@ export default class DropdownPanelView extends View {
}

/**
* Focuses the view element or first item in view collection on opening dropdown's panel.
* Focuses the first view in the {@link #children} collection.
*
* See also {@link module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable}.
*/
focus() {
if ( this.children.length ) {
this.children.first.focus();
if ( typeof this.children.first.focus === 'function' ) {
this.children.first.focus();
} else {
/**
* The child view of a dropdown could not be focused because it is missing the `focus()` method.
*
* This warning appears when a dropdown {@link module:ui/dropdown/dropdownview~DropdownView#isOpen gets open} and it
* attempts to focus the {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView#children first child} of its panel
* but the child does not implement the
* {@link module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable focusable interface}.
*
* Focusing the content of a dropdown on open greatly improves the accessibility. Please make sure the view instance
* provides the `focus()` method for the best user experience.
*
* @error ui-dropdown-panel-focus-child-missing-focus
* @param {module:ui/view~View} childView
* @param {module:ui/dropdown/dropdownpanelview~DropdownPanelView} dropdownPanel
*/
logWarning( 'ui-dropdown-panel-focus-child-missing-focus', { childView: this.children.first, dropdownPanel: this } );
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/ckeditor5-ui/src/dropdown/dropdownview.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ export default class DropdownView extends View {
/**
* Controls whether the dropdown view is open, i.e. shows or hides the {@link #panelView panel}.
*
* **Note**: When the dropdown gets open, it will attempt to call `focus()` on the first child of its {@link #panelView}.
* See {@link module:ui/dropdown/utils~addToolbarToDropdown}, {@link module:ui/dropdown/utils~addListToDropdown}, and
* {@link module:ui/dropdown/utils~focusChildOnDropdownOpen} to learn more about focus management in dropdowns.
*
* @observable
* @member {Boolean} #isOpen
*/
Expand Down Expand Up @@ -263,6 +267,9 @@ export default class DropdownView extends View {
} else {
this.panelView.position = this.panelPosition;
}

// Focus the first item in the dropdown when the dropdown opened
this.panelView.focus();
} );

// Listen for keystrokes coming from within #element.
Expand Down
Loading