diff --git a/packages/ckeditor5-list/src/listconfig.ts b/packages/ckeditor5-list/src/listconfig.ts index 7703be832c4..a5578df085f 100644 --- a/packages/ckeditor5-list/src/listconfig.ts +++ b/packages/ckeditor5-list/src/listconfig.ts @@ -168,6 +168,74 @@ export interface ListPropertiesStyleConfig { * @default false */ useAttribute?: boolean; + + /** + * Defines which list styles should be available in the UI. + * Accepts a configuration object with numbered and bulleted styles. + * + * ```ts + * { + * list: { + * properties: { + * styles: { + * listStyleTypes: { + * numbered: [ 'decimal', 'lower-roman', 'upper-roman' ], + * bulleted: [ 'disc', 'circle' ] + * } + * } + * } + * } + * } + * ``` + * + * When the `listTypes` configuration is set, `listStyleTypes` will only take effect for the enabled list types. + * For example, with the following configuration: + * + * ```ts + * { + * list: { + * properties: { + * styles: { + * listTypes: 'numbered', + * listStyleTypes: { + * numbered: [ 'decimal', 'lower-roman' ], + * bulleted: [ 'disc', 'circle' ] + * } + * } + * } + * } + * } + * ``` + * + * Only the numbered list styles will be available in the UI, as the `listTypes` property limits style selection to numbered lists only. + * + * **Note**: This configuration works only with + * {@link module:list/listproperties~ListProperties list properties}. + * + * @default { + * numbered: [ 'decimal', 'decimal-leading-zero', 'lower-roman', 'upper-roman', 'lower-latin', 'upper-latin' ], + * bulleted: [ 'disc', 'circle', 'square' ] + * } + */ + listStyleTypes?: ListStyleTypesConfig; +} + +export interface ListStyleTypesConfig { + numbered?: Array; + bulleted?: Array; } export type ListPropertiesStyleListType = 'numbered' | 'bulleted'; + +export type NumberedListStyleType = + | 'decimal' + | 'decimal-leading-zero' + | 'lower-roman' + | 'upper-roman' + | 'lower-latin' + | 'upper-latin'; + +export type BulletedListStyleType = + | 'disc' + | 'circle' + | 'square'; diff --git a/packages/ckeditor5-list/src/listproperties/listpropertiesui.ts b/packages/ckeditor5-list/src/listproperties/listpropertiesui.ts index 07c027189c9..fda3c8c8cd8 100644 --- a/packages/ckeditor5-list/src/listproperties/listpropertiesui.ts +++ b/packages/ckeditor5-list/src/listproperties/listpropertiesui.ts @@ -367,17 +367,27 @@ function createListPropertiesView( { if ( normalizedConfig.styles.listTypes.includes( listType ) ) { const listStyleCommand: LegacyListStyleCommand | ListStyleCommand = editor.commands.get( 'listStyle' )!; - const styleButtonCreator = getStyleButtonCreator( { editor, parentCommandName, listStyleCommand } ); - // The command can be ListStyleCommand or DocumentListStyleCommand. - const isStyleTypeSupported = getStyleTypeSupportChecker( listStyleCommand ); + const configuredListStylesTypes = normalizedConfig.styles.listStyleTypes; + let filteredDefinitions = styleDefinitions; - styleButtonViews = styleDefinitions.filter( isStyleTypeSupported ).map( styleButtonCreator ); + if ( configuredListStylesTypes ) { + const allowedTypes = configuredListStylesTypes[ listType ]; + + if ( allowedTypes ) { + filteredDefinitions = styleDefinitions.filter( def => allowedTypes.includes( def.type ) ); + } + } + + const isStyleTypeSupported = getStyleTypeSupportChecker( listStyleCommand ); + styleButtonViews = filteredDefinitions + .filter( isStyleTypeSupported ) + .map( styleButtonCreator ); } const listPropertiesView = new ListPropertiesView( locale, { @@ -455,7 +465,20 @@ function getMenuBarStylesMenuCreator( parentCommandName, listStyleCommand } ); - const styleButtonViews = styleDefinitions.filter( isStyleTypeSupported ).map( styleButtonCreator ); + + const configuredListStylesTypes = normalizedConfig.styles.listStyleTypes; + let filteredDefinitions = styleDefinitions; + + if ( configuredListStylesTypes ) { + const listType = listCommand.type as 'numbered' | 'bulleted'; + const allowedTypes = configuredListStylesTypes[ listType ]; + + if ( allowedTypes ) { + filteredDefinitions = styleDefinitions.filter( def => allowedTypes.includes( def.type ) ); + } + } + + const styleButtonViews = filteredDefinitions.filter( isStyleTypeSupported ).map( styleButtonCreator ); const listPropertiesView = new ListPropertiesView( locale, { styleGridAriaLabel, enabledProperties: { diff --git a/packages/ckeditor5-list/src/listproperties/utils/config.ts b/packages/ckeditor5-list/src/listproperties/utils/config.ts index 90b4dd37ba0..dda66d52a8b 100644 --- a/packages/ckeditor5-list/src/listproperties/utils/config.ts +++ b/packages/ckeditor5-list/src/listproperties/utils/config.ts @@ -53,8 +53,8 @@ export function getNormalizedConfig( config: ListPropertiesConfig ): NormalizedL * @returns An object with normalized list properties styles. */ function getNormalizedStylesConfig( styles: ListPropertiesConfig[ 'styles' ] ): NormalizedListPropertiesConfig[ 'styles' ] { - const normalizedConfig = { - listTypes: [ 'bulleted', 'numbered' ] as Array, + const normalizedConfig: NormalizedListPropertiesConfig[ 'styles' ] = { + listTypes: [ 'bulleted', 'numbered' ], useAttribute: false }; @@ -74,6 +74,10 @@ function getNormalizedStylesConfig( styles: ListPropertiesConfig[ 'styles' ] ): normalizedConfig.listTypes; normalizedConfig.useAttribute = !!styles.useAttribute; + + if ( styles.listStyleTypes ) { + normalizedConfig.listStyleTypes = styles.listStyleTypes; + } } return normalizedConfig; @@ -85,6 +89,10 @@ function getNormalizedStylesConfig( styles: ListPropertiesConfig[ 'styles' ] ): export type NormalizedListPropertiesConfig = { styles: { listTypes: Array; + listStyleTypes?: { + numbered?: Array; + bulleted?: Array; + }; useAttribute: boolean; }; startIndex: boolean; diff --git a/packages/ckeditor5-list/tests/listproperties/listpropertiesui.js b/packages/ckeditor5-list/tests/listproperties/listpropertiesui.js index 3c53a92374d..097ed1addb2 100644 --- a/packages/ckeditor5-list/tests/listproperties/listpropertiesui.js +++ b/packages/ckeditor5-list/tests/listproperties/listpropertiesui.js @@ -255,6 +255,119 @@ describe( 'ListPropertiesUI', () => { expect( componentFactory.has( 'numberedList' ) ).to.be.false; } ); } ); + + describe( 'listStyleTypes config entry', () => { + it( 'should register buttons filtered by listStyleTypes for bulleted list', () => { + return withEditor( { + styles: { + listStyleTypes: { + bulleted: [ 'disc', 'circle' ] + } + } + }, editor => { + const componentFactory = editor.ui.componentFactory; + const bulletedListDropdown = componentFactory.create( 'bulletedList' ); + + bulletedListDropdown.render(); + document.body.appendChild( bulletedListDropdown.element ); + + // Trigger lazy init + bulletedListDropdown.isOpen = true; + bulletedListDropdown.isOpen = false; + + const listPropertiesView = bulletedListDropdown.panelView.children.first; + const stylesView = listPropertiesView.stylesView; + + expect( stylesView.children.map( b => b.tooltip ) ).to.deep.equal( [ 'Disc', 'Circle' ] ); + + bulletedListDropdown.element.remove(); + } ); + } ); + + it( 'should register buttons filtered by listStyleTypes for numbered list', () => { + return withEditor( { + styles: { + listStyleTypes: { + numbered: [ 'decimal', 'lower-roman' ] + } + } + }, editor => { + const componentFactory = editor.ui.componentFactory; + const numberedListDropdown = componentFactory.create( 'numberedList' ); + + numberedListDropdown.render(); + document.body.appendChild( numberedListDropdown.element ); + + // Trigger lazy init + numberedListDropdown.isOpen = true; + numberedListDropdown.isOpen = false; + + const listPropertiesView = numberedListDropdown.panelView.children.first; + const stylesView = listPropertiesView.stylesView; + + expect( stylesView.children.map( b => b.tooltip ) ).to.deep.equal( [ 'Decimal', 'Lower–roman' ] ); + + numberedListDropdown.element.remove(); + } ); + } ); + + it( 'should register all buttons when listStyleTypes is undefined', () => { + return withEditor( { + styles: true + }, editor => { + const componentFactory = editor.ui.componentFactory; + const numberedListDropdown = componentFactory.create( 'numberedList' ); + + numberedListDropdown.render(); + document.body.appendChild( numberedListDropdown.element ); + + // Trigger lazy init + numberedListDropdown.isOpen = true; + numberedListDropdown.isOpen = false; + + const listPropertiesView = numberedListDropdown.panelView.children.first; + const stylesView = listPropertiesView.stylesView; + + expect( stylesView.children.map( b => b.tooltip ) ).to.deep.equal( [ + 'Decimal', + 'Decimal with leading zero', + 'Lower–roman', + 'Upper-roman', + 'Lower-latin', + 'Upper-latin' + ] ); + + numberedListDropdown.element.remove(); + } ); + } ); + + it( 'should register no buttons when listStyleTypes has empty array', () => { + return withEditor( { + styles: { + listStyleTypes: { + numbered: [], + bulleted: [] + } + } + }, editor => { + const componentFactory = editor.ui.componentFactory; + const numberedListDropdown = componentFactory.create( 'numberedList' ); + + numberedListDropdown.render(); + document.body.appendChild( numberedListDropdown.element ); + + // Trigger lazy init + numberedListDropdown.isOpen = true; + numberedListDropdown.isOpen = false; + + const listPropertiesView = numberedListDropdown.panelView.children.first; + + expect( listPropertiesView.stylesView ).to.be.null; + + numberedListDropdown.element.remove(); + } ); + } ); + } ); } ); describe( 'bulleted list dropdown', () => { @@ -1072,6 +1185,119 @@ describe( 'ListPropertiesUI', () => { expect( componentFactory.has( 'menuBar:bulletedList' ) ).to.be.false; } ); } ); + + describe( 'listStyleTypes config entry', () => { + it( 'should register buttons filtered by listStyleTypes for bulleted list in menu bar', () => { + return withEditor( { + styles: { + listStyleTypes: { + bulleted: [ 'disc', 'circle' ] + } + } + }, editor => { + const componentFactory = editor.ui.componentFactory; + const bulletedListMenu = componentFactory.create( 'menuBar:bulletedList' ); + + bulletedListMenu.render(); + document.body.appendChild( bulletedListMenu.element ); + + // Trigger lazy init + bulletedListMenu.isOpen = true; + bulletedListMenu.isOpen = false; + + const listPropertiesView = bulletedListMenu.panelView.children.first; + const stylesView = listPropertiesView.stylesView; + + expect( stylesView.children.map( b => b.tooltip ) ).to.deep.equal( [ 'Disc', 'Circle' ] ); + + bulletedListMenu.element.remove(); + } ); + } ); + + it( 'should register buttons filtered by listStyleTypes for numbered list in menu bar', () => { + return withEditor( { + styles: { + listStyleTypes: { + numbered: [ 'decimal', 'lower-roman' ] + } + } + }, editor => { + const componentFactory = editor.ui.componentFactory; + const numberedListMenu = componentFactory.create( 'menuBar:numberedList' ); + + numberedListMenu.render(); + document.body.appendChild( numberedListMenu.element ); + + // Trigger lazy init + numberedListMenu.isOpen = true; + numberedListMenu.isOpen = false; + + const listPropertiesView = numberedListMenu.panelView.children.first; + const stylesView = listPropertiesView.stylesView; + + expect( stylesView.children.map( b => b.tooltip ) ).to.deep.equal( [ 'Decimal', 'Lower–roman' ] ); + + numberedListMenu.element.remove(); + } ); + } ); + + it( 'should register all buttons when listStyleTypes is undefined in menu bar', () => { + return withEditor( { + styles: true + }, editor => { + const componentFactory = editor.ui.componentFactory; + const numberedListMenu = componentFactory.create( 'menuBar:numberedList' ); + + numberedListMenu.render(); + document.body.appendChild( numberedListMenu.element ); + + // Trigger lazy init + numberedListMenu.isOpen = true; + numberedListMenu.isOpen = false; + + const listPropertiesView = numberedListMenu.panelView.children.first; + const stylesView = listPropertiesView.stylesView; + + expect( stylesView.children.map( b => b.tooltip ) ).to.deep.equal( [ + 'Decimal', + 'Decimal with leading zero', + 'Lower–roman', + 'Upper-roman', + 'Lower-latin', + 'Upper-latin' + ] ); + + numberedListMenu.element.remove(); + } ); + } ); + + it( 'should register no buttons when listStyleTypes has empty array in menu bar', () => { + return withEditor( { + styles: { + listStyleTypes: { + numbered: [], + bulleted: [] + } + } + }, editor => { + const componentFactory = editor.ui.componentFactory; + const numberedListMenu = componentFactory.create( 'menuBar:numberedList' ); + + numberedListMenu.render(); + document.body.appendChild( numberedListMenu.element ); + + // Trigger lazy init + numberedListMenu.isOpen = true; + numberedListMenu.isOpen = false; + + const listPropertiesView = numberedListMenu.panelView.children.first; + + expect( listPropertiesView.stylesView ).to.be.null; + + numberedListMenu.element.remove(); + } ); + } ); + } ); } ); describe( 'bulleted list menu', () => { diff --git a/packages/ckeditor5-list/tests/manual/list-properties.html b/packages/ckeditor5-list/tests/manual/list-properties.html index b69b3a12cb3..dedb87c109b 100644 --- a/packages/ckeditor5-list/tests/manual/list-properties.html +++ b/packages/ckeditor5-list/tests/manual/list-properties.html @@ -27,3 +27,7 @@

Just styles

No properties enabled

+ +

Hidden lower-latin and upper-latin styles

+ +
diff --git a/packages/ckeditor5-list/tests/manual/list-properties.js b/packages/ckeditor5-list/tests/manual/list-properties.js index fa16236ff73..1f7eaa21ffb 100644 --- a/packages/ckeditor5-list/tests/manual/list-properties.js +++ b/packages/ckeditor5-list/tests/manual/list-properties.js @@ -222,3 +222,38 @@ ClassicEditor .catch( err => { console.error( err.stack ); } ); + +// ------------------------------------------------------------------ + +ClassicEditor + .create( document.querySelector( '#editor-i' ), { + ...config, + menuBar: { + isVisible: true + }, + list: { + properties: { + styles: { + listStyleTypes: { + numbered: [ + 'decimal', + 'decimal-leading-zero', + 'lower-roman', + 'upper-roman' + ], + bulleted: [ + 'disc', + 'circle', + 'square' + ] + } + } + } + } + } ) + .then( editor => { + CKEditorInspector.attach( { 'No properties enabled': editor } ); + } ) + .catch( err => { + console.error( err.stack ); + } );