diff --git a/docs/installation/getting-started/predefined-builds.md b/docs/installation/getting-started/predefined-builds.md
index a682f8e60fa..2dc8e4cdb88 100644
--- a/docs/installation/getting-started/predefined-builds.md
+++ b/docs/installation/getting-started/predefined-builds.md
@@ -885,6 +885,7 @@ The table below presents the list of all plugins included in various builds. SelectAll
Typing
Undo
+ Accessibility help dialog
✅ |
diff --git a/docs/installation/integrations/angular.md b/docs/installation/integrations/angular.md
index fa679737406..05ec823e43c 100644
--- a/docs/installation/integrations/angular.md
+++ b/docs/installation/integrations/angular.md
@@ -25,6 +25,8 @@ Currently, the CKEditor 5 component for Angular supports integrating CKEdit
Starting from version 6.0.0 of this package, you can use native type definitions provided by CKEditor 5. Check the details about {@link installation/working-with-typescript TypeScript support}.
+
+ For best results, we suggest using TypeScript version 5.0 or newer.
## Supported Angular versions
diff --git a/docs/installation/integrations/react.md b/docs/installation/integrations/react.md
index 9f8e57bd490..769643a0284 100644
--- a/docs/installation/integrations/react.md
+++ b/docs/installation/integrations/react.md
@@ -21,6 +21,8 @@ The easiest way to use CKEditor 5 in your React application is by choosing
Starting from version 6.0.0 of this package, you can use native type definitions provided by CKEditor 5. Check the details about {@link installation/working-with-typescript TypeScript support}.
+
+ For best results, we suggest using TypeScript version 5.0 or newer.
## Quick start
diff --git a/docs/installation/working-with-typescript.md b/docs/installation/working-with-typescript.md
index a65fc23856f..9f185fa6136 100644
--- a/docs/installation/working-with-typescript.md
+++ b/docs/installation/working-with-typescript.md
@@ -16,6 +16,8 @@ CKEditor 5 is built using TypeScript and has native type definitions. All t
Using TypeScript is just an option. If you do not need its features, you can continue using CKEditor 5 in JavaScript.
+
+ For best results, we suggest using TypeScript version 5.0 or newer.
diff --git a/packages/ckeditor5-heading/src/headingui.ts b/packages/ckeditor5-heading/src/headingui.ts
index dd1e2837f1a..4a39236051d 100644
--- a/packages/ckeditor5-heading/src/headingui.ts
+++ b/packages/ckeditor5-heading/src/headingui.ts
@@ -111,8 +111,8 @@ export default class HeadingUI extends Plugin {
return areEnabled.some( isEnabled => isEnabled );
} );
- dropdownView.buttonView.bind( 'label' ).to( headingCommand, 'value', paragraphCommand, 'value', ( value, para ) => {
- const whichModel = value || para && 'paragraph';
+ dropdownView.buttonView.bind( 'label' ).to( headingCommand, 'value', paragraphCommand, 'value', ( heading, paragraph ) => {
+ const whichModel = paragraph ? 'paragraph' : heading;
if ( typeof whichModel === 'boolean' ) {
return defaultTitle;
@@ -126,6 +126,21 @@ export default class HeadingUI extends Plugin {
return titles[ whichModel ];
} );
+ dropdownView.buttonView.bind( 'ariaLabel' ).to( headingCommand, 'value', paragraphCommand, 'value', ( heading, paragraph ) => {
+ const whichModel = paragraph ? 'paragraph' : heading;
+
+ if ( typeof whichModel === 'boolean' ) {
+ return accessibleLabel;
+ }
+
+ // If none of the commands is active, display default title.
+ if ( !titles[ whichModel ] ) {
+ return accessibleLabel;
+ }
+
+ return `${ titles[ whichModel ] }, ${ accessibleLabel }`;
+ } );
+
// Execute command when an item from the dropdown is selected.
this.listenTo( dropdownView, 'execute', evt => {
const { commandName, commandValue } = evt.source as any;
diff --git a/packages/ckeditor5-heading/tests/headingui.js b/packages/ckeditor5-heading/tests/headingui.js
index 9f5a22a41f2..5df52f3900c 100644
--- a/packages/ckeditor5-heading/tests/headingui.js
+++ b/packages/ckeditor5-heading/tests/headingui.js
@@ -74,7 +74,7 @@ describe( 'HeadingUI', () => {
expect( dropdown.buttonView.isOn ).to.be.false;
expect( dropdown.buttonView.label ).to.equal( 'Paragraph' );
expect( dropdown.buttonView.tooltip ).to.equal( 'Heading' );
- expect( dropdown.buttonView.ariaLabel ).to.equal( 'Heading' );
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'Paragraph, Heading' );
expect( dropdown.buttonView.ariaLabelledBy ).to.be.undefined;
} );
@@ -156,6 +156,34 @@ describe( 'HeadingUI', () => {
paragraphCommand.value = true;
expect( dropdown.buttonView.label ).to.equal( 'Paragraph' );
} );
+
+ it( 'label when heading and paragraph commands active', () => {
+ command.value = 'heading2';
+ paragraphCommand.value = true;
+
+ expect( dropdown.buttonView.label ).to.equal( 'Paragraph' );
+ } );
+
+ it( 'ariaLabel', () => {
+ command.value = false;
+ paragraphCommand.value = false;
+
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'Heading' );
+
+ command.value = 'heading2';
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'Heading 2, Heading' );
+ command.value = false;
+
+ paragraphCommand.value = true;
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'Paragraph, Heading' );
+ } );
+
+ it( 'ariaLabel when heading and paragraph commands active', () => {
+ command.value = 'heading2';
+ paragraphCommand.value = true;
+
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'Paragraph, Heading' );
+ } );
} );
describe( 'localization', () => {
diff --git a/packages/ckeditor5-language/src/textpartlanguageui.ts b/packages/ckeditor5-language/src/textpartlanguageui.ts
index 8e8073daa06..5f2314576c5 100644
--- a/packages/ckeditor5-language/src/textpartlanguageui.ts
+++ b/packages/ckeditor5-language/src/textpartlanguageui.ts
@@ -106,6 +106,16 @@ export default class TextPartLanguageUI extends Plugin {
return ( value && titles[ value ] ) || defaultTitle;
} );
+ dropdownView.buttonView.bind( 'ariaLabel' ).to( languageCommand, 'value', value => {
+ const selectedLanguageTitle = value && titles[ value ];
+
+ if ( !selectedLanguageTitle ) {
+ return accessibleLabel;
+ }
+
+ return `${ selectedLanguageTitle }, ${ accessibleLabel }`;
+ } );
+
// Execute command when an item from the dropdown is selected.
this.listenTo( dropdownView, 'execute', evt => {
languageCommand.execute( {
diff --git a/packages/ckeditor5-language/tests/textpartlanguageui.js b/packages/ckeditor5-language/tests/textpartlanguageui.js
index 024971051c3..d7e0e6daba0 100644
--- a/packages/ckeditor5-language/tests/textpartlanguageui.js
+++ b/packages/ckeditor5-language/tests/textpartlanguageui.js
@@ -151,6 +151,18 @@ describe( 'TextPartLanguageUI', () => {
expect( dropdown.buttonView.label ).to.equal( 'Arabic' );
} );
+ it( 'ariaLabel', () => {
+ command.value = false;
+
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'Language' );
+
+ command.value = 'fr:ltr';
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'French, Language' );
+
+ command.value = 'ar:rtl';
+ expect( dropdown.buttonView.ariaLabel ).to.equal( 'Arabic, Language' );
+ } );
+
it( 'reflects the #value of the command', () => {
// Trigger lazy init.
dropdown.isOpen = true;
diff --git a/packages/ckeditor5-ui/src/input/inputbase.ts b/packages/ckeditor5-ui/src/input/inputbase.ts
index 82119933637..e3968f433d0 100644
--- a/packages/ckeditor5-ui/src/input/inputbase.ts
+++ b/packages/ckeditor5-ui/src/input/inputbase.ts
@@ -46,6 +46,20 @@ export default abstract class InputBase {
diff --git a/packages/ckeditor5-ui/tests/input/inputbase.js b/packages/ckeditor5-ui/tests/input/inputbase.js
index b9d0ee60a3a..2bd7d748975 100644
--- a/packages/ckeditor5-ui/tests/input/inputbase.js
+++ b/packages/ckeditor5-ui/tests/input/inputbase.js
@@ -219,6 +219,26 @@ describe( 'InputBase', () => {
} );
} );
+ describe( 'tabIndex', () => {
+ it( 'should react on view#tabIndex', () => {
+ expect( view.element.getAttribute( 'tabIndex' ) ).to.be.null;
+
+ view.tabIndex = 123;
+
+ expect( view.element.getAttribute( 'tabIndex' ) ).to.equal( '123' );
+ } );
+ } );
+
+ describe( 'aria-label', () => {
+ it( 'should react on view#ariaLabel', () => {
+ expect( view.element.getAttribute( 'aria-label' ) ).to.be.null;
+
+ view.ariaLabel = 'reader text';
+
+ expect( view.element.getAttribute( 'aria-label' ) ).to.equal( 'reader text' );
+ } );
+ } );
+
describe( 'aria-describedby', () => {
it( 'should react on view#hasError', () => {
expect( view.element.getAttribute( 'aria-describedby' ) ).to.be.null;