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

Allowed for configuring editor label via config.label property #16792

Merged
merged 8 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions packages/ckeditor5-core/src/editor/editorconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,48 @@ export interface EditorConfig {
* Translations to be used in the editor.
*/
translations?: ArrayOrItem<Translations>;

/**
* Label text for the `aria-label` attribute set on editor editing area. Used by assistive technologies
* to tell apart multiple editor instances (editing areas) on the page. If not set, a default
* "Rich Text Editor. Editing area [name of the area]" is used instead.
*
* ```ts
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* label: 'My editor'
* } )
* .then( ... )
* .catch( ... );
* ```
*
* If your editor implementation uses multiple roots, you should pass an object with keys corresponding to the editor
* roots names and values equal to the label that should be used for each root:
*
* ```ts
* MultiRootEditor.create(
* // Roots for the editor:
* {
* header: document.querySelector( '#header' ),
* content: document.querySelector( '#content' ),
* leftSide: document.querySelector( '#left-side' ),
* rightSide: document.querySelector( '#right-side' )
* },
* // Config:
* {
* label: {
* header: 'Header label',
* content: 'Content label',
* leftSide: 'Left side label',
* rightSide: 'Right side label'
* }
* }
* )
* .then( ... )
* .catch( ... );
* ```
*/
label?: string | Record<string, string>;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/ckeditor5-core/tests/_utils/classictesteditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export default class ClassicTestEditor extends ElementApiMixin( Editor ) {
this.ui = new ClassicTestEditorUI( this, new BoxedEditorUIView( this.locale ) );

// Expose properties normally exposed by the ClassicEditorUI.
this.ui.view.editable = new InlineEditableUIView( this.ui.view.locale, this.editing.view );
this.ui.view.editable = new InlineEditableUIView( this.ui.view.locale, this.editing.view, undefined, {
label: this.config.get( 'label' )
} );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-editor-balloon/src/ballooneditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default class BalloonEditor extends /* #__PURE__ */ ElementApiMixin( Edit

this.model.document.createRoot();

const view = new BalloonEditorUIView( this.locale, this.editing.view, this.sourceElement );
const view = new BalloonEditorUIView( this.locale, this.editing.view, this.sourceElement, this.config.get( 'label' ) );
f1ames marked this conversation as resolved.
Show resolved Hide resolved
this.ui = new BalloonEditorUI( this, view );

attachToForm( this );
Expand Down
11 changes: 5 additions & 6 deletions packages/ckeditor5-editor-balloon/src/ballooneditoruiview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,19 @@ export default class BalloonEditorUIView extends EditorUIView {
* @param editingView The editing view instance this view is related to.
* @param editableElement The editable element. If not specified, it will be automatically created by
* {@link module:ui/editableui/editableuiview~EditableUIView}. Otherwise, the given element will be used.
* @param label When set, this value will be used as an accessible `aria-label` of the
* {@link module:ui/editableui/editableuiview~EditableUIView editable view}.
*/
constructor(
locale: Locale,
editingView: EditingView,
editableElement?: HTMLElement
editableElement?: HTMLElement,
label?: string | Record<string, string>
) {
super( locale );

const t = locale.t;

this.editable = new InlineEditableUIView( locale, editingView, editableElement, {
label: editableView => {
return t( 'Rich Text Editor. Editing area: %0', editableView.name! );
}
label
} );

this.menuBarView = new MenuBarView( locale );
Expand Down
106 changes: 104 additions & 2 deletions packages/ckeditor5-editor-balloon/tests/ballooneditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,10 @@ describe( 'BalloonEditor', () => {
} );
} );

afterEach( () => {
return editor.destroy();
afterEach( async () => {
if ( editor.state !== 'destroyed' ) {
await editor.destroy();
}
} );

it( 'creates an instance which inherits from the BalloonEditor', () => {
Expand Down Expand Up @@ -275,6 +277,106 @@ describe( 'BalloonEditor', () => {
.then( done )
.catch( done );
} );

describe( 'configurable editor label (aria-label)', () => {
it( 'should be set to the defaut value if not configured', () => {
expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ) ).to.equal(
'Rich Text Editor. Editing area: main'
);
} );

it( 'should support the string format', async () => {
await editor.destroy();

editor = await BalloonEditor.create( editorElement, {
plugins: [ Paragraph, Bold ],
label: 'Custom label'
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ) ).to.equal(
'Custom label'
);
} );

it( 'should support object format', async () => {
await editor.destroy();

editor = await BalloonEditor.create( editorElement, {
plugins: [ Paragraph, Bold ],
label: {
main: 'Custom label'
}
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ) ).to.equal(
'Custom label'
);
} );

it( 'should keep an existing value from the source DOM element', async () => {
await editor.destroy();

editorElement.setAttribute( 'aria-label', 'Pre-existing value' );
const newEditor = await BalloonEditor.create( editorElement, {
plugins: [ Paragraph, Bold ]
} );

expect( newEditor.editing.view.getDomRoot().getAttribute( 'aria-label' ), 'Keep value' ).to.equal(
'Pre-existing value'
);

await newEditor.destroy();

expect( editorElement.getAttribute( 'aria-label' ), 'Restore value' ).to.equal( 'Pre-existing value' );
} );

it( 'should override the existing value from the source DOM element', async () => {
await editor.destroy();

editorElement.setAttribute( 'aria-label', 'Pre-existing value' );
editor = await BalloonEditor.create( editorElement, {
plugins: [ Paragraph, Bold ],
label: 'Custom label'
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ), 'Override value' ).to.equal(
'Custom label'
);

await editor.destroy();

expect( editorElement.getAttribute( 'aria-label' ), 'Restore value' ).to.equal( 'Pre-existing value' );
} );

it( 'should use default label when creating an editor from initial data rather than a DOM element', async () => {
await editor.destroy();

editor = await BalloonEditor.create( '<p>Initial data</p>', {
plugins: [ Paragraph, Bold ]
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ), 'Override value' ).to.equal(
'Rich Text Editor. Editing area: main'
);

await editor.destroy();
} );

it( 'should set custom label when creating an editor from initial data rather than a DOM element', async () => {
await editor.destroy();

editor = await BalloonEditor.create( '<p>Initial data</p>', {
plugins: [ Paragraph, Bold ],
label: 'Custom label'
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ), 'Override value' ).to.equal(
'Custom label'
);

await editor.destroy();
} );
} );
} );

describe( 'create - events', () => {
Expand Down
28 changes: 27 additions & 1 deletion packages/ckeditor5-editor-balloon/tests/ballooneditoruiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,39 @@ describe( 'BalloonEditorUIView', () => {
expect( view.editable.isRendered ).to.be.false;
} );

it( 'is given an accessible aria label', () => {
it( 'creates an editing root with the default aria-label', () => {
view.render();

expect( editingViewRoot.getAttribute( 'aria-label' ) ).to.equal( 'Rich Text Editor. Editing area: main' );

view.destroy();
} );

it( 'creates an editing root with the configured aria-label (string format)', () => {
const editingView = new EditingView();
const editingViewRoot = createRoot( editingView.document );
const view = new BalloonEditorUIView( locale, editingView, undefined, 'Foo' );
view.editable.name = editingViewRoot.rootName;
view.render();

expect( editingViewRoot.getAttribute( 'aria-label' ) ).to.equal( 'Foo' );

view.destroy();
} );

it( 'creates an editing root with the configured aria-label (object format)', () => {
const editingView = new EditingView();
const editingViewRoot = createRoot( editingView.document );
const view = new BalloonEditorUIView( locale, editingView, undefined, {
main: 'Foo'
} );
view.editable.name = editingViewRoot.rootName;
view.render();

expect( editingViewRoot.getAttribute( 'aria-label' ) ).to.equal( 'Foo' );

view.destroy();
} );
} );

describe( '#menuBarView', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/ckeditor5-editor-classic/src/classiceditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ export default class ClassicEditor extends /* #__PURE__ */ ElementApiMixin( Edit

const view = new ClassicEditorUIView( this.locale, this.editing.view, {
shouldToolbarGroupWhenFull,
useMenuBar: menuBarConfig.isVisible
useMenuBar: menuBarConfig.isVisible,
label: this.config.get( 'label' )
} );

this.ui = new ClassicEditorUI( this, view );
Expand Down
7 changes: 6 additions & 1 deletion packages/ckeditor5-editor-classic/src/classiceditoruiview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ export default class ClassicEditorUIView extends BoxedEditorUIView {
* @param options.shouldToolbarGroupWhenFull When set `true` enables automatic items grouping
* in the main {@link module:editor-classic/classiceditoruiview~ClassicEditorUIView#toolbar toolbar}.
* See {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull} to learn more.
* @param options.label When set, this value will be used as an accessible `aria-label` of the
* {@link module:ui/editableui/editableuiview~EditableUIView editable view}.
*/
constructor(
locale: Locale,
editingView: EditingView,
options: {
shouldToolbarGroupWhenFull?: boolean;
useMenuBar?: boolean;
label?: string | Record<string, string>;
} = {}
) {
super( locale );
Expand All @@ -64,7 +67,9 @@ export default class ClassicEditorUIView extends BoxedEditorUIView {
this.menuBarView = new MenuBarView( locale );
}

this.editable = new InlineEditableUIView( locale, editingView );
this.editable = new InlineEditableUIView( locale, editingView, undefined, {
label: options.label
} );
}

/**
Expand Down
71 changes: 69 additions & 2 deletions packages/ckeditor5-editor-classic/tests/classiceditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,10 @@ describe( 'ClassicEditor', () => {
} );
} );

afterEach( () => {
return editor.destroy();
afterEach( async () => {
if ( editor.state !== 'destroyed' ) {
await editor.destroy();
}
} );

it( 'creates an instance which inherits from the ClassicEditor', () => {
Expand Down Expand Up @@ -267,6 +269,71 @@ describe( 'ClassicEditor', () => {
it( 'attaches editable UI as view\'s DOM root', () => {
expect( editor.editing.view.getDomRoot() ).to.equal( editor.ui.view.editable.element );
} );

describe( 'configurable editor label (aria-label)', () => {
it( 'should be set to the defaut value if not configured', () => {
expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ) ).to.equal(
'Rich Text Editor. Editing area: main'
);
} );

it( 'should support the string format', async () => {
await editor.destroy();

editor = await ClassicEditor.create( editorElement, {
plugins: [ Paragraph, Bold ],
label: 'Custom label'
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ) ).to.equal(
'Custom label'
);
} );

it( 'should support object format', async () => {
await editor.destroy();

editor = await ClassicEditor.create( editorElement, {
plugins: [ Paragraph, Bold ],
label: {
main: 'Custom label'
}
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ) ).to.equal(
'Custom label'
);
} );

it( 'should use default label when creating an editor from initial data rather than a DOM element', async () => {
await editor.destroy();

editor = await ClassicEditor.create( '<p>Initial data</p>', {
plugins: [ Paragraph, Bold ]
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ), 'Override value' ).to.equal(
'Rich Text Editor. Editing area: main'
);

await editor.destroy();
} );

it( 'should set custom label when creating an editor from initial data rather than a DOM element', async () => {
await editor.destroy();

editor = await ClassicEditor.create( '<p>Initial data</p>', {
plugins: [ Paragraph, Bold ],
label: 'Custom label'
} );

expect( editor.editing.view.getDomRoot().getAttribute( 'aria-label' ), 'Override value' ).to.equal(
'Custom label'
);

await editor.destroy();
} );
} );
} );
} );

Expand Down
Loading