diff --git a/packages/ckeditor5-list/src/listpropertiesui.js b/packages/ckeditor5-list/src/listpropertiesui.js
index 085afb0baf8..0decea62bcf 100644
--- a/packages/ckeditor5-list/src/listpropertiesui.js
+++ b/packages/ckeditor5-list/src/listpropertiesui.js
@@ -228,10 +228,9 @@ function getStyleButtonCreator( { editor, listStyleCommand, parentCommandName }
editor.execute( 'listStyle', { type: listStyleCommand._defaultType } );
}
}
- // If the content the selection is anchored to is not a list, let's create a list of a desired style.
+ // Otherwise, leave the creation of the styled list to the `ListStyleCommand`.
else {
editor.model.change( () => {
- editor.execute( parentCommandName );
editor.execute( 'listStyle', { type } );
} );
}
diff --git a/packages/ckeditor5-list/src/liststylecommand.js b/packages/ckeditor5-list/src/liststylecommand.js
index a5b43a4b6c0..483fd212ffc 100644
--- a/packages/ckeditor5-list/src/liststylecommand.js
+++ b/packages/ckeditor5-list/src/liststylecommand.js
@@ -8,10 +8,13 @@
*/
import { Command } from 'ckeditor5/src/core';
-import { getSelectedListItems } from './utils';
+import { getListTypeFromListStyleType, getSelectedListItems } from './utils';
/**
* The list style command. It changes the `listStyle` attribute of the selected list items.
+ *
+ * If the list type (numbered or bulleted) can be inferred from the passed style type,
+ * the command tries to convert selected items to a list of that type.
* It is used by the {@link module:list/listproperties~ListProperties list properties feature}.
*
* @extends module:core/command~Command
@@ -48,11 +51,13 @@ export default class ListStyleCommand extends Command {
* Executes the command.
*
* @param {Object} options
- * @param {String|null} options.type The type of the list style, e.g. `'disc'` or `'square'`. If `null` is specified, the default
+ * @param {String|null} [options.type] The type of the list style, e.g. `'disc'` or `'square'`. If `null` is specified, the default
* style will be applied.
* @protected
*/
execute( options = {} ) {
+ this._tryToConvertItemsToList( options );
+
const model = this.editor.model;
const listItems = getSelectedListItems( model );
@@ -97,4 +102,31 @@ export default class ListStyleCommand extends Command {
return numberedList.isEnabled || bulletedList.isEnabled;
}
+
+ /**
+ * Check if the provided list style is valid. Also change the selection to a list if it's not set yet.
+ *
+ * @param {Object} options
+ * @param {String|null} [options.type] The type of the list style. If `null` is specified, the function does nothing.
+ * @private
+ */
+ _tryToConvertItemsToList( options ) {
+ if ( !options.type ) {
+ return;
+ }
+
+ const listType = getListTypeFromListStyleType( options.type );
+
+ if ( !listType ) {
+ return;
+ }
+
+ const editor = this.editor;
+ const commandName = listType + 'List';
+ const command = editor.commands.get( commandName );
+
+ if ( !command.value ) {
+ editor.execute( commandName );
+ }
+ }
}
diff --git a/packages/ckeditor5-list/src/utils.js b/packages/ckeditor5-list/src/utils.js
index 9cce1e8a220..48481e71983 100644
--- a/packages/ckeditor5-list/src/utils.js
+++ b/packages/ckeditor5-list/src/utils.js
@@ -410,6 +410,37 @@ export function getSelectedListItems( model ) {
return listItems;
}
+const BULLETED_LIST_STYLE_TYPES = [ 'disc', 'circle', 'square' ];
+
+// There's a lot of them (https://www.w3.org/TR/css-counter-styles-3/#typedef-counter-style).
+// Let's support only those that can be selected by ListPropertiesUI.
+const NUMBERED_LIST_STYLE_TYPES = [
+ 'decimal',
+ 'decimal-leading-zero',
+ 'lower-roman',
+ 'upper-roman',
+ 'lower-latin',
+ 'upper-latin'
+];
+
+/**
+ * Checks whether the given list-style-type is supported by numbered or bulleted list.
+ *
+ * @param {String} listStyleType
+ * @returns {'bulleted'|'numbered'|null}
+ */
+export function getListTypeFromListStyleType( listStyleType ) {
+ if ( BULLETED_LIST_STYLE_TYPES.includes( listStyleType ) ) {
+ return 'bulleted';
+ }
+
+ if ( NUMBERED_LIST_STYLE_TYPES.includes( listStyleType ) ) {
+ return 'numbered';
+ }
+
+ return null;
+}
+
// Implementation of getFillerOffset for view list item element.
//
// @returns {Number|null} Block filler offset or `null` if block filler is not needed.
diff --git a/packages/ckeditor5-list/tests/liststylecommand.js b/packages/ckeditor5-list/tests/liststylecommand.js
index 3947bbf0b62..6cd4157a230 100644
--- a/packages/ckeditor5-list/tests/liststylecommand.js
+++ b/packages/ckeditor5-list/tests/liststylecommand.js
@@ -253,7 +253,7 @@ describe( 'ListStyleCommand', () => {
);
} );
- it( 'should start searching for the list items from starting position (collapsed selection)', () => {
+ it( 'should start searching for the list items from starting position (non-collapsed selection)', () => {
setData( model,
'1.' +
'2.' +
@@ -271,7 +271,7 @@ describe( 'ListStyleCommand', () => {
);
} );
- it( 'should start searching for the list items from ending position (collapsed selection)', () => {
+ it( 'should start searching for the list items from ending position (non-collapsed selection)', () => {
setData( model,
'[Foo.' +
'1.]' +
@@ -282,7 +282,7 @@ describe( 'ListStyleCommand', () => {
listStyleCommand.execute( { type: 'circle' } );
expect( getData( model ) ).to.equal(
- '[Foo.' +
+ '[Foo.' +
'1.]' +
'2.' +
'3.'
@@ -325,24 +325,108 @@ describe( 'ListStyleCommand', () => {
);
} );
- it( 'should not update anything if no listItem found in the selection', () => {
+ it( 'should not update anything if no listItem found in the selection (default style)', () => {
setData( model,
'[Foo.]' +
- '1.'
+ '1.'
);
const modelChangeStub = sinon.stub( model, 'change' ).named( 'model#change' );
- listStyleCommand.execute( { type: 'circle' } );
+ listStyleCommand.execute();
expect( getData( model ) ).to.equal(
'[Foo.]' +
- '1.'
+ '1.'
);
expect( modelChangeStub.called ).to.equal( false );
} );
+ it( 'should create a list list if no listItem found in the selection (circle, non-collapsed selection)', () => {
+ setData( model,
+ '[Foo.' +
+ 'Bar.]'
+ );
+
+ const listCommand = editor.commands.get( 'bulletedList' );
+ const spy = sinon.spy( listCommand, 'execute' );
+
+ listStyleCommand.execute( { type: 'circle' } );
+
+ expect( getData( model ) ).to.equal(
+ '[Foo.' +
+ 'Bar.]'
+ );
+
+ expect( spy.called ).to.be.true;
+
+ spy.restore();
+ } );
+
+ it( 'should create a list list if no listItem found in the selection (square, collapsed selection)', () => {
+ setData( model,
+ 'Fo[]o.' +
+ 'Bar.'
+ );
+
+ const listCommand = editor.commands.get( 'bulletedList' );
+ const spy = sinon.spy( listCommand, 'execute' );
+
+ listStyleCommand.execute( { type: 'circle' } );
+
+ expect( getData( model ) ).to.equal(
+ 'Fo[]o.' +
+ 'Bar.'
+ );
+
+ expect( spy.called ).to.be.true;
+
+ spy.restore();
+ } );
+
+ it( 'should create a list list if no listItem found in the selection (decimal, non-collapsed selection)', () => {
+ setData( model,
+ '[Foo.' +
+ 'Bar.]'
+ );
+
+ const listCommand = editor.commands.get( 'numberedList' );
+ const spy = sinon.spy( listCommand, 'execute' );
+
+ listStyleCommand.execute( { type: 'decimal' } );
+
+ expect( getData( model ) ).to.equal(
+ '[Foo.' +
+ 'Bar.]'
+ );
+
+ expect( spy.called ).to.be.true;
+
+ spy.restore();
+ } );
+
+ it( 'should create a list list if no listItem found in the selection (upper-roman, collapsed selection)', () => {
+ setData( model,
+ 'Fo[]o.' +
+ 'Bar.'
+ );
+
+ const listCommand = editor.commands.get( 'numberedList' );
+ const spy = sinon.spy( listCommand, 'execute' );
+
+ listStyleCommand.execute( { type: 'upper-roman' } );
+
+ expect( getData( model ) ).to.equal(
+ 'Fo[]o.' +
+ 'Bar.'
+ );
+
+ expect( spy.called ).to.be.true;
+
+ spy.restore();
+ } );
+
it( 'should update all items that belong to selected elements', () => {
// [x] = items that should be updated.
// All list items that belong to the same lists that selected items should be updated.
diff --git a/packages/ckeditor5-list/tests/utils.js b/packages/ckeditor5-list/tests/utils.js
index 6f7157b6a1f..e28be9124c1 100644
--- a/packages/ckeditor5-list/tests/utils.js
+++ b/packages/ckeditor5-list/tests/utils.js
@@ -11,7 +11,7 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest
import ListEditing from '../src/listediting';
import ListPropertiesEditing from '../src/listpropertiesediting';
-import { createViewListItemElement, getSiblingListItem, getSiblingNodes } from '../src/utils';
+import { createViewListItemElement, getListTypeFromListStyleType, getSiblingListItem, getSiblingNodes } from '../src/utils';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
describe( 'utils', () => {
@@ -449,4 +449,26 @@ describe( 'utils', () => {
] );
} );
} );
+
+ describe( 'getListTypeFromListStyleType()', () => {
+ const testData = [
+ [ 'decimal', 'numbered' ],
+ [ 'decimal-leading-zero', 'numbered' ],
+ [ 'lower-roman', 'numbered' ],
+ [ 'upper-roman', 'numbered' ],
+ [ 'lower-latin', 'numbered' ],
+ [ 'upper-latin', 'numbered' ],
+ [ 'disc', 'bulleted' ],
+ [ 'circle', 'bulleted' ],
+ [ 'square', 'bulleted' ],
+ [ 'default', null ],
+ [ 'style-type-that-is-not-possibly-supported-by-css', null ]
+ ];
+
+ for ( const [ style, type ] of testData ) {
+ it( `shoud return "${ type }" for "${ style }" style`, () => {
+ expect( getListTypeFromListStyleType( style ) ).to.equal( type );
+ } );
+ }
+ } );
} );