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;