@ckeditor/ckeditor5-widget
Advanced tools
Comparing version 0.1.1 to 0.2.0
Changelog | ||
========= | ||
## [0.2.0](https://github.com/ckeditor/ckeditor5-widget/compare/v0.1.1...v0.2.0) (2017-09-03) | ||
### Bug fixes | ||
* Added initial contenteditable state for editable widget. Closes [#9](https://github.com/ckeditor/ckeditor5-widget/issues/9). ([c6321ff](https://github.com/ckeditor/ckeditor5-widget/commit/c6321ff)) | ||
### Features | ||
* <kbd>Ctrl</kbd>+<kbd>A</kbd> in a nested editable should select nested editable's content. Closes [#13](https://github.com/ckeditor/ckeditor5-widget/issues/13). ([35a8aff](https://github.com/ckeditor/ckeditor5-widget/commit/35a8aff)) | ||
### Other changes | ||
* Adjusted widget to the editor read-only mode. Closes [#7](https://github.com/ckeditor/ckeditor5-widget/issues/7). ([2726873](https://github.com/ckeditor/ckeditor5-widget/commit/2726873)) | ||
* Introduced highlights support for widgets. Closes [#11](https://github.com/ckeditor/ckeditor5-widget/issues/11). ([0bd3d66](https://github.com/ckeditor/ckeditor5-widget/commit/0bd3d66)) | ||
## [0.1.1](https://github.com/ckeditor/ckeditor5-widget/compare/v0.1.0...v0.1.1) (2017-05-07) | ||
@@ -5,0 +21,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
/* jshint browser: false, node: true, strict: true */ | ||
/* eslint-env node */ | ||
@@ -12,3 +12,4 @@ 'use strict'; | ||
const gulp = require( 'gulp' ); | ||
const ckeditor5Lint = require( '@ckeditor/ckeditor5-dev-lint' )( { | ||
const ckeditor5Lint = require( '@ckeditor/ckeditor5-dev-lint' ); | ||
const options = { | ||
// Files ignored by `gulp lint` task. | ||
@@ -19,6 +20,6 @@ // Files from .gitignore will be added automatically during task execution. | ||
] | ||
} ); | ||
}; | ||
gulp.task( 'lint', ckeditor5Lint.lint ); | ||
gulp.task( 'lint-staged', ckeditor5Lint.lintStaged ); | ||
gulp.task( 'lint', () => ckeditor5Lint.lint( options ) ); | ||
gulp.task( 'lint-staged', () => ckeditor5Lint.lintStaged( options ) ); | ||
gulp.task( 'pre-commit', [ 'lint-staged' ] ); |
{ | ||
"name": "@ckeditor/ckeditor5-widget", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "Widget API for CKEditor 5.", | ||
"keywords": [], | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-core": "^0.8.1", | ||
"@ckeditor/ckeditor5-engine": "^0.10.0", | ||
"@ckeditor/ckeditor5-utils": "^0.9.1", | ||
"@ckeditor/ckeditor5-theme-lark": "^0.8.0" | ||
"@ckeditor/ckeditor5-core": "^0.9.0", | ||
"@ckeditor/ckeditor5-engine": "^0.11.0", | ||
"@ckeditor/ckeditor5-utils": "^0.10.0", | ||
"@ckeditor/ckeditor5-theme-lark": "^0.9.0" | ||
}, | ||
"devDependencies": { | ||
"@ckeditor/ckeditor5-dev-lint": "^2.0.2", | ||
"@ckeditor/ckeditor5-dev-lint": "^3.1.0", | ||
"eslint-config-ckeditor5": "^1.0.5", | ||
"gulp": "^3.9.1", | ||
@@ -15,0 +16,0 @@ "guppy-pre-commit": "^0.4.0" |
CKEditor 5 widget API | ||
======================================== | ||
[![Join the chat at https://gitter.im/ckeditor/ckeditor5](https://badges.gitter.im/ckeditor/ckeditor5.svg)](https://gitter.im/ckeditor/ckeditor5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-widget.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-widget) | ||
@@ -5,0 +6,0 @@ [![Build Status](https://travis-ci.org/ckeditor/ckeditor5-widget.svg)](https://travis-ci.org/ckeditor/ckeditor5-widget) |
@@ -10,2 +10,4 @@ /** | ||
import HighlightStack from './highlightstack'; | ||
const widgetSymbol = Symbol( 'isWidget' ); | ||
@@ -43,6 +45,7 @@ const labelSymbol = Symbol( 'label' ); | ||
* * adds `ck-widget` CSS class, | ||
* * adds custom property allowing to recognize widget elements by using {@link ~isWidget}. | ||
* * adds custom property allowing to recognize widget elements by using {@link ~isWidget}, | ||
* * implements `addHighlight` and `removeHighlight` custom properties to handle view highlight on widgets. | ||
* | ||
* @param {module:engine/view/element~Element} element | ||
* @param {Object} [options] | ||
* @param {Object} [options={}] | ||
* @param {String|Function} [options.label] Element's label provided to {@link ~setLabel} function. It can be passed as | ||
@@ -52,4 +55,3 @@ * a plain string or a function returning a string. | ||
*/ | ||
export function toWidget( element, options ) { | ||
options = options || {}; | ||
export function toWidget( element, options = {} ) { | ||
element.setAttribute( 'contenteditable', false ); | ||
@@ -64,6 +66,42 @@ element.getFillerOffset = getFillerOffset; | ||
setHighlightHandling( | ||
element, | ||
( element, descriptor ) => element.addClass( ...normalizeToArray( descriptor.class ) ), | ||
( element, descriptor ) => element.removeClass( ...normalizeToArray( descriptor.class ) ) | ||
); | ||
return element; | ||
// Normalizes CSS class in descriptor that can be provided in form of an array or a string. | ||
function normalizeToArray( classes ) { | ||
return Array.isArray( classes ) ? classes : [ classes ]; | ||
} | ||
} | ||
/** | ||
* Sets highlight handling methods. Uses {@link module:widget/highlightstack~HighlightStack} to | ||
* properly determine which highlight descriptor should be used at given time. | ||
* | ||
* @param {module:engine/view/element~Element} element | ||
* @param {Function} add | ||
* @param {Function} remove | ||
*/ | ||
export function setHighlightHandling( element, add, remove ) { | ||
const stack = new HighlightStack(); | ||
stack.on( 'change:top', ( evt, data ) => { | ||
if ( data.oldDescriptor ) { | ||
remove( element, data.oldDescriptor ); | ||
} | ||
if ( data.newDescriptor ) { | ||
add( element, data.newDescriptor ); | ||
} | ||
} ); | ||
element.setCustomProperty( 'addHighlight', ( element, descriptor ) => stack.add( descriptor ) ); | ||
element.setCustomProperty( 'removeHighlight', ( element, descriptor ) => stack.remove( descriptor ) ); | ||
} | ||
/** | ||
* Sets label for given element. | ||
@@ -98,4 +136,5 @@ * It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by | ||
* Adds functionality to provided {module:engine/view/editableelement~EditableElement} to act as a widget's editable: | ||
* * sets `contenteditable` attribute to `true`, | ||
* * adds `ck-editable` CSS class, | ||
* * sets `contenteditable` as `true` when {module:engine/view/editableelement~EditableElement#isReadOnly} is `false` | ||
* otherwise set `false`, | ||
* * adds `ck-editable_focused` CSS class when editable is focused and removes it when it's blurred. | ||
@@ -107,5 +146,12 @@ * | ||
export function toWidgetEditable( editable ) { | ||
editable.setAttribute( 'contenteditable', 'true' ); | ||
editable.addClass( 'ck-editable' ); | ||
// Set initial contenteditable value. | ||
editable.setAttribute( 'contenteditable', !editable.isReadOnly ); | ||
// Bind contenteditable property to element#isReadOnly. | ||
editable.on( 'change:isReadOnly', ( evt, property, is ) => { | ||
editable.setAttribute( 'contenteditable', !is ); | ||
} ); | ||
editable.on( 'change:isFocused', ( evt, property, is ) => { | ||
@@ -112,0 +158,0 @@ if ( is ) { |
@@ -18,6 +18,8 @@ /** | ||
import { isWidget, WIDGET_SELECTED_CLASS_NAME, getLabel } from './utils'; | ||
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; | ||
import { keyCodes, getCode, parseKeystroke } from '@ckeditor/ckeditor5-utils/src/keyboard'; | ||
import '../theme/theme.scss'; | ||
const selectAllKeystrokeCode = parseKeystroke( 'Ctrl+A' ); | ||
/** | ||
@@ -37,3 +39,3 @@ * The widget plugin. | ||
static get pluginName() { | ||
return 'widget/widget'; | ||
return 'Widget'; | ||
} | ||
@@ -127,10 +129,18 @@ | ||
*/ | ||
_onKeydown( eventInfo, domEventData ) { | ||
_onKeydown( eventInfo, domEventData ) { | ||
const keyCode = domEventData.keyCode; | ||
const isForward = keyCode == keyCodes.delete || keyCode == keyCodes.arrowdown || keyCode == keyCodes.arrowright; | ||
let wasHandled = false; | ||
// Checks if delete/backspace or arrow keys were handled and then prevents default event behaviour and stops | ||
// event propagation. | ||
if ( ( isDeleteKeyCode( keyCode ) && this._handleDelete( isForward ) ) || | ||
( isArrowKeyCode( keyCode ) && this._handleArrowKeys( isForward ) ) ) { | ||
// Checks if the keys were handled and then prevents the default event behaviour and stops | ||
// the propagation. | ||
if ( isDeleteKeyCode( keyCode ) ) { | ||
wasHandled = this._handleDelete( isForward ); | ||
} else if ( isArrowKeyCode( keyCode ) ) { | ||
wasHandled = this._handleArrowKeys( isForward ); | ||
} else if ( isSelectAllKeyCode( domEventData ) ) { | ||
wasHandled = this._selectAllNestedEditableContent(); | ||
} | ||
if ( wasHandled ) { | ||
domEventData.preventDefault(); | ||
@@ -220,2 +230,27 @@ eventInfo.stop(); | ||
/** | ||
* Extends the {@link module:engine/model/selection~Selection document's selection} to span the entire | ||
* content of the nested editable if already anchored in one. | ||
* | ||
* See: {@link module:engine/model/schema~Schema#getLimitElement}. | ||
* | ||
* @private | ||
*/ | ||
_selectAllNestedEditableContent() { | ||
const modelDocument = this.editor.document; | ||
const modelSelection = modelDocument.selection; | ||
const schema = modelDocument.schema; | ||
const limitElement = schema.getLimitElement( modelSelection ); | ||
if ( modelSelection.getFirstRange().root == limitElement ) { | ||
return false; | ||
} | ||
modelDocument.enqueueChanges( () => { | ||
modelSelection.setIn( limitElement ); | ||
} ); | ||
return true; | ||
} | ||
/** | ||
* Sets {@link module:engine/model/selection~Selection document's selection} over given element. | ||
@@ -270,6 +305,6 @@ * | ||
//Returns 'true' if provided key code represents one of the delete keys: delete or backspace. | ||
// Returns 'true' if provided key code represents one of the delete keys: delete or backspace. | ||
// | ||
//@param {Number} keyCode | ||
//@returns {Boolean} | ||
// @param {Number} keyCode | ||
// @returns {Boolean} | ||
function isDeleteKeyCode( keyCode ) { | ||
@@ -279,2 +314,10 @@ return keyCode == keyCodes.delete || keyCode == keyCodes.backspace; | ||
// Returns 'true' if provided (DOM) key event data corresponds with the Ctrl+A keystroke. | ||
// | ||
// @param {module:engine/view/observer/keyobserver~KeyEventData} domEventData | ||
// @returns {Boolean} | ||
function isSelectAllKeyCode( domEventData ) { | ||
return getCode( domEventData ) == selectAllKeystrokeCode; | ||
} | ||
// Returns `true` when element is a nested editable or is placed inside one. | ||
@@ -281,0 +324,0 @@ // |
@@ -15,2 +15,3 @@ /** | ||
toWidgetEditable, | ||
setHighlightHandling, | ||
WIDGET_CLASS_NAME | ||
@@ -33,3 +34,3 @@ } from '../src/utils'; | ||
it( 'should define getFillerOffset method', () => { | ||
expect( element.getFillerOffset ).to.be.function; | ||
expect( element.getFillerOffset ).to.be.a( 'function' ); | ||
expect( element.getFillerOffset() ).to.be.null; | ||
@@ -43,3 +44,2 @@ } ); | ||
it( 'should add element\'s label if one is provided', () => { | ||
element = new ViewElement( 'div' ); | ||
toWidget( element, { label: 'foo bar baz label' } ); | ||
@@ -51,3 +51,2 @@ | ||
it( 'should add element\'s label if one is provided as function', () => { | ||
element = new ViewElement( 'div' ); | ||
toWidget( element, { label: () => 'foo bar baz label' } ); | ||
@@ -57,2 +56,36 @@ | ||
} ); | ||
it( 'should set default highlight handling methods', () => { | ||
toWidget( element ); | ||
const set = element.getCustomProperty( 'addHighlight' ); | ||
const remove = element.getCustomProperty( 'removeHighlight' ); | ||
expect( typeof set ).to.equal( 'function' ); | ||
expect( typeof remove ).to.equal( 'function' ); | ||
set( element, { priority: 1, class: 'highlight', id: 'highlight' } ); | ||
expect( element.hasClass( 'highlight' ) ).to.be.true; | ||
remove( element, { priority: 1, class: 'highlight', id: 'highlight' } ); | ||
expect( element.hasClass( 'highlight' ) ).to.be.false; | ||
} ); | ||
it( 'should set default highlight handling methods - CSS classes array', () => { | ||
toWidget( element ); | ||
const set = element.getCustomProperty( 'addHighlight' ); | ||
const remove = element.getCustomProperty( 'removeHighlight' ); | ||
expect( typeof set ).to.equal( 'function' ); | ||
expect( typeof remove ).to.equal( 'function' ); | ||
set( element, { priority: 1, class: [ 'highlight', 'foo' ], id: 'highlight' } ); | ||
expect( element.hasClass( 'highlight' ) ).to.be.true; | ||
expect( element.hasClass( 'foo' ) ).to.be.true; | ||
remove( element, { priority: 1, class: [ 'foo', 'highlight' ], id: 'highlight' } ); | ||
expect( element.hasClass( 'highlight' ) ).to.be.false; | ||
expect( element.hasClass( 'foo' ) ).to.be.false; | ||
} ); | ||
} ); | ||
@@ -113,2 +146,10 @@ | ||
it( 'should add proper contenteditable value when element is read-only', () => { | ||
element.isReadOnly = false; | ||
expect( element.getAttribute( 'contenteditable' ) ).to.true; | ||
element.isReadOnly = true; | ||
expect( element.getAttribute( 'contenteditable' ) ).to.false; | ||
} ); | ||
it( 'should add proper class when element is focused', () => { | ||
@@ -122,2 +163,101 @@ element.isFocused = true; | ||
} ); | ||
describe( 'addHighlightHandling()', () => { | ||
let element, addSpy, removeSpy, set, remove; | ||
beforeEach( () => { | ||
element = new ViewElement( 'p' ); | ||
addSpy = sinon.spy(); | ||
removeSpy = sinon.spy(); | ||
setHighlightHandling( element, addSpy, removeSpy ); | ||
set = element.getCustomProperty( 'addHighlight' ); | ||
remove = element.getCustomProperty( 'removeHighlight' ); | ||
} ); | ||
it( 'should set highlight handling methods', () => { | ||
expect( typeof set ).to.equal( 'function' ); | ||
expect( typeof remove ).to.equal( 'function' ); | ||
} ); | ||
it( 'should call highlight methods when descriptor is added and removed', () => { | ||
const descriptor = { priority: 10, class: 'highlight', id: 'highlight' }; | ||
set( element, descriptor ); | ||
remove( element, descriptor ); | ||
sinon.assert.calledOnce( addSpy ); | ||
sinon.assert.calledWithExactly( addSpy, element, descriptor ); | ||
sinon.assert.calledOnce( removeSpy ); | ||
sinon.assert.calledWithExactly( removeSpy, element, descriptor ); | ||
} ); | ||
it( 'should call highlight methods when next descriptor is added', () => { | ||
const descriptor = { priority: 10, class: 'highlight', id: 'highlight-1' }; | ||
const secondDescriptor = { priority: 11, class: 'highlight', id: 'highlight-2' }; | ||
set( element, descriptor ); | ||
set( element, secondDescriptor ); | ||
sinon.assert.calledTwice( addSpy ); | ||
expect( addSpy.firstCall.args[ 1 ] ).to.equal( descriptor ); | ||
expect( addSpy.secondCall.args[ 1 ] ).to.equal( secondDescriptor ); | ||
} ); | ||
it( 'should not call highlight methods when descriptor with lower priority is added', () => { | ||
const descriptor = { priority: 10, class: 'highlight', id: 'highlight-1' }; | ||
const secondDescriptor = { priority: 9, class: 'highlight', id: 'highlight-2' }; | ||
set( element, descriptor ); | ||
set( element, secondDescriptor ); | ||
sinon.assert.calledOnce( addSpy ); | ||
expect( addSpy.firstCall.args[ 1 ] ).to.equal( descriptor ); | ||
} ); | ||
it( 'should call highlight methods when descriptor is removed changing active descriptor', () => { | ||
const descriptor = { priority: 10, class: 'highlight', id: 'highlight-1' }; | ||
const secondDescriptor = { priority: 11, class: 'highlight', id: 'highlight-2' }; | ||
set( element, descriptor ); | ||
set( element, secondDescriptor ); | ||
remove( element, secondDescriptor ); | ||
sinon.assert.calledThrice( addSpy ); | ||
expect( addSpy.firstCall.args[ 1 ] ).to.equal( descriptor ); | ||
expect( addSpy.secondCall.args[ 1 ] ).to.equal( secondDescriptor ); | ||
expect( addSpy.thirdCall.args[ 1 ] ).to.equal( descriptor ); | ||
sinon.assert.calledTwice( removeSpy ); | ||
expect( removeSpy.firstCall.args[ 1 ] ).to.equal( descriptor ); | ||
expect( removeSpy.secondCall.args[ 1 ] ).to.equal( secondDescriptor ); | ||
} ); | ||
it( 'should call highlight methods when descriptor is removed not changing active descriptor', () => { | ||
const descriptor = { priority: 10, class: 'highlight', id: 'highlight-1' }; | ||
const secondDescriptor = { priority: 9, class: 'highlight', id: 'highlight-2' }; | ||
set( element, descriptor ); | ||
set( element, secondDescriptor ); | ||
remove( element, secondDescriptor ); | ||
sinon.assert.calledOnce( addSpy ); | ||
expect( addSpy.firstCall.args[ 1 ] ).to.equal( descriptor ); | ||
sinon.assert.notCalled( removeSpy ); | ||
} ); | ||
it( 'should call highlight methods - CSS class array', () => { | ||
const descriptor = { priority: 10, class: [ 'highlight', 'a' ], id: 'highlight-1' }; | ||
const secondDescriptor = { priority: 10, class: [ 'highlight', 'b' ], id: 'highlight-2' }; | ||
set( element, descriptor ); | ||
set( element, secondDescriptor ); | ||
sinon.assert.calledTwice( addSpy ); | ||
expect( addSpy.firstCall.args[ 1 ] ).to.equal( descriptor ); | ||
expect( addSpy.secondCall.args[ 1 ] ).to.equal( secondDescriptor ); | ||
} ); | ||
} ); | ||
} ); |
@@ -25,5 +25,3 @@ /** | ||
beforeEach( () => { | ||
return VirtualTestEditor.create( { | ||
plugins: [ Widget ] | ||
} ) | ||
return VirtualTestEditor.create( { plugins: [ Widget ] } ) | ||
.then( newEditor => { | ||
@@ -40,2 +38,3 @@ editor = newEditor; | ||
doc.schema.registerItem( 'nested' ); | ||
doc.schema.limits.add( 'nested' ); | ||
doc.schema.allow( { name: '$inline', inside: 'nested' } ); | ||
@@ -175,3 +174,3 @@ doc.schema.allow( { name: 'nested', inside: 'widget' } ); | ||
it( 'fake selection should be empty if widget is not selected', () => { | ||
setModelData( doc, '<widget>foo bar</widget>' ); | ||
setModelData( doc, '<paragraph>foo</paragraph><widget>foo bar</widget>' ); | ||
@@ -182,14 +181,14 @@ expect( viewDocument.selection.fakeSelectionLabel ).to.equal( '' ); | ||
it( 'should toggle selected class', () => { | ||
setModelData( doc, '[<widget>foo</widget>]' ); | ||
setModelData( doc, '<paragraph>foo</paragraph>[<widget>foo</widget>]' ); | ||
expect( getViewData( viewDocument ) ).to.equal( | ||
'[<div class="ck-widget ck-widget_selected" contenteditable="false">foo<b></b></div>]' | ||
'<p>foo</p>[<div class="ck-widget ck-widget_selected" contenteditable="false">foo<b></b></div>]' | ||
); | ||
doc.enqueueChanges( () => { | ||
doc.selection.collapseToStart(); | ||
doc.selection.removeAllRanges(); | ||
} ); | ||
expect( getViewData( viewDocument ) ).to.equal( | ||
'[]<div class="ck-widget" contenteditable="false">foo<b></b></div>' | ||
'<p>{}foo</p><div class="ck-widget" contenteditable="false">foo<b></b></div>' | ||
); | ||
@@ -450,3 +449,3 @@ } ); | ||
setModelData( doc, '<paragraph>foo[]</paragraph><widget></widget>' ); | ||
viewDocument.on( 'keydown', keydownHandler ); | ||
viewDocument.on( 'keydown', keydownHandler ); | ||
@@ -581,3 +580,3 @@ viewDocument.fire( 'keydown', domEventDataMock ); | ||
setModelData( doc, '<paragraph>foo</paragraph>[<widget></widget>]' ); | ||
viewDocument.on( 'keydown', keydownHandler ); | ||
viewDocument.on( 'keydown', keydownHandler ); | ||
@@ -598,3 +597,3 @@ viewDocument.fire( 'keydown', domEventDataMock ); | ||
setModelData( doc, '[<widget></widget>]<paragraph>foo</paragraph>' ); | ||
viewDocument.on( 'keydown', keydownHandler ); | ||
viewDocument.on( 'keydown', keydownHandler ); | ||
@@ -756,2 +755,18 @@ viewDocument.fire( 'keydown', domEventDataMock ); | ||
describe( 'Ctrl+A', () => { | ||
test( | ||
'should select the entire content of the nested editable', | ||
'<widget><nested>foo[]</nested></widget><paragraph>bar</paragraph>', | ||
{ keyCode: keyCodes.a, ctrlKey: true }, | ||
'<widget><nested>[foo]</nested></widget><paragraph>bar</paragraph>' | ||
); | ||
test( | ||
'should not change the selection if outside of the nested editable', | ||
'<widget><nested>foo</nested></widget><paragraph>[]bar</paragraph>', | ||
{ keyCode: keyCodes.a, ctrlKey: true }, | ||
'<widget><nested>foo</nested></widget><paragraph>[]bar</paragraph>' | ||
); | ||
} ); | ||
function test( name, data, keyCodeOrMock, expected ) { | ||
@@ -758,0 +773,0 @@ it( name, () => { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72157
1598
16
4
+ Added@ckeditor/ckeditor5-core@0.9.0(transitive)
+ Added@ckeditor/ckeditor5-engine@0.11.0(transitive)
+ Added@ckeditor/ckeditor5-theme-lark@0.9.0(transitive)
+ Added@ckeditor/ckeditor5-ui@0.10.0(transitive)
+ Added@ckeditor/ckeditor5-utils@0.10.0(transitive)
- Removed@ckeditor/ckeditor5-core@0.8.1(transitive)
- Removed@ckeditor/ckeditor5-engine@0.10.0(transitive)
- Removed@ckeditor/ckeditor5-theme-lark@0.8.0(transitive)
- Removed@ckeditor/ckeditor5-ui@0.9.0(transitive)
- Removed@ckeditor/ckeditor5-utils@0.9.1(transitive)