@ckeditor/ckeditor5-widget
Advanced tools
Comparing version 10.3.1 to 11.0.0
Changelog | ||
========= | ||
## [11.0.0](https://github.com/ckeditor/ckeditor5-widget/compare/v10.3.1...v11.0.0) (2019-02-28) | ||
### Bug fixes | ||
* Editor crashes after <kbd>Enter</kbd> key on an image that is inside a blockquote. Closes [ckeditor/ckeditor5#1555](https://github.com/ckeditor/ckeditor5/issues/1555). ([8a8842b](https://github.com/ckeditor/ckeditor5-widget/commit/8a8842b)) | ||
* Ensured only the widget toolbar attached to the view element which is deepest in the view tree will show up. Code and documentation refactoring in the `WidgetToolbarRepository`. Closes [#60](https://github.com/ckeditor/ckeditor5-widget/issues/60). ([7e11a24](https://github.com/ckeditor/ckeditor5-widget/commit/7e11a24)) | ||
* Make widget in editable clickable. Closes [ckeditor/ckeditor5-table#98](https://github.com/ckeditor/ckeditor5-table/issues/98). ([8226829](https://github.com/ckeditor/ckeditor5-widget/commit/8226829)) | ||
* Pressing <kbd>Enter</kbd> should split parent element when the inline widget is inside a `$block`. Closes [ckeditor/ckeditor5#1529](https://github.com/ckeditor/ckeditor5/issues/1529). ([847d2ab](https://github.com/ckeditor/ckeditor5-widget/commit/847d2ab)) | ||
* Fixed memory leaks during editor initialization and destruction (see [ckeditor/ckeditor5#1341](https://github.com/ckeditor/ckeditor5/issues/1341)). ([2e8f20d](https://github.com/ckeditor/ckeditor5-widget/commit/2e8f20d)) | ||
### Other changes | ||
* Introduce support and utils for creating inline widgets. Closes [[ckeditor/ckeditor5#1096](https://github.com/ckeditor/ckeditor5/issues/1096)](https://github.com/ckeditor/ckeditor5/issues/1096). ([38fa159](https://github.com/ckeditor/ckeditor5-widget/commit/38fa159)) | ||
* Renamed the `.ck-widget_selectable` class to `.ck-widget_with-selection-handler` for better semantics. Closes [#66](https://github.com/ckeditor/ckeditor5-widget/issues/66). ([178ad5f](https://github.com/ckeditor/ckeditor5-widget/commit/178ad5f)) | ||
### BREAKING CHANGES | ||
* Upgraded minimal versions of Node to `8.0.0` and npm to `5.7.1`. See: [ckeditor/ckeditor5#1507](https://github.com/ckeditor/ckeditor5/issues/1507). ([612ea3c](https://github.com/ckeditor/ckeditor5-cloud-services/commit/612ea3c)) | ||
* The `.ck-widget_selectable` class has been renamed to `.ck-widget_with-selection-handler` for better semantics. | ||
* The `visibleWhen()` function, a property of an object passed into `WidgetToolbarRepository.register()`, has been renamed to `getRelatedElement()` and must return an editing `View` element the toolbar should be attached to (instead of `Boolean`). | ||
## [10.3.1](https://github.com/ckeditor/ckeditor5-widget/compare/v10.3.0...v10.3.1) (2018-12-05) | ||
@@ -5,0 +27,0 @@ |
@@ -5,3 +5,3 @@ Software License Agreement | ||
**CKEditor 5 widget API** – https://github.com/ckeditor/ckeditor5-image <br> | ||
Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved. | ||
Copyright (c) 2003-2019, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved. | ||
@@ -8,0 +8,0 @@ Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). |
{ | ||
"name": "@ckeditor/ckeditor5-widget", | ||
"version": "10.3.1", | ||
"version": "11.0.0", | ||
"description": "Widget API for CKEditor 5.", | ||
@@ -12,27 +12,32 @@ "keywords": [ | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-core": "^11.1.0", | ||
"@ckeditor/ckeditor5-engine": "^12.0.0", | ||
"@ckeditor/ckeditor5-theme-lark": "^12.0.0", | ||
"@ckeditor/ckeditor5-ui": "^11.2.0", | ||
"@ckeditor/ckeditor5-utils": "^11.1.0" | ||
"@ckeditor/ckeditor5-core": "^12.0.0", | ||
"@ckeditor/ckeditor5-engine": "^13.0.0", | ||
"@ckeditor/ckeditor5-theme-lark": "^13.0.0", | ||
"@ckeditor/ckeditor5-ui": "^12.0.0", | ||
"@ckeditor/ckeditor5-utils": "^12.0.0" | ||
}, | ||
"devDependencies": { | ||
"@ckeditor/ckeditor5-basic-styles": "^10.1.0", | ||
"@ckeditor/ckeditor5-editor-balloon": "^11.0.2", | ||
"@ckeditor/ckeditor5-editor-classic": "^11.0.2", | ||
"@ckeditor/ckeditor5-essentials": "^10.1.3", | ||
"@ckeditor/ckeditor5-paragraph": "^10.0.4", | ||
"@ckeditor/ckeditor5-typing": "^11.0.2", | ||
"@ckeditor/ckeditor5-basic-styles": "^11.0.0", | ||
"@ckeditor/ckeditor5-clipboard": "^11.0.0", | ||
"@ckeditor/ckeditor5-editor-balloon": "^12.0.0", | ||
"@ckeditor/ckeditor5-editor-classic": "^12.0.0", | ||
"@ckeditor/ckeditor5-enter": "^11.0.0", | ||
"@ckeditor/ckeditor5-essentials": "^11.0.0", | ||
"@ckeditor/ckeditor5-heading": "^11.0.0", | ||
"@ckeditor/ckeditor5-paragraph": "^11.0.0", | ||
"@ckeditor/ckeditor5-table": "^12.0.0", | ||
"@ckeditor/ckeditor5-typing": "^12.0.0", | ||
"@ckeditor/ckeditor5-undo": "^11.0.0", | ||
"eslint": "^5.5.0", | ||
"eslint-config-ckeditor5": "^1.0.7", | ||
"husky": "^0.14.3", | ||
"eslint-config-ckeditor5": "^1.0.11", | ||
"husky": "^1.3.1", | ||
"lint-staged": "^7.0.0" | ||
}, | ||
"engines": { | ||
"node": ">=6.9.0", | ||
"npm": ">=3.0.0" | ||
"node": ">=8.0.0", | ||
"npm": ">=5.7.1" | ||
}, | ||
"author": "CKSource (http://cksource.com/)", | ||
"license": "GPL-2.0-or-later", | ||
"homepage": "https://ckeditor.com", | ||
"homepage": "https://ckeditor.com/ckeditor-5", | ||
"bugs": "https://github.com/ckeditor/ckeditor5-widget/issues", | ||
@@ -49,4 +54,3 @@ "repository": { | ||
"scripts": { | ||
"lint": "eslint --quiet '**/*.js'", | ||
"precommit": "lint-staged" | ||
"lint": "eslint --quiet '**/*.js'" | ||
}, | ||
@@ -61,3 +65,8 @@ "lint-staged": { | ||
"packages/**" | ||
] | ||
], | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
} | ||
} |
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
@@ -20,3 +20,3 @@ */ | ||
* * if two highlights have same priority - sort by CSS class provided in | ||
* {@link module:engine/conversion/downcast-converters~HighlightDescriptor}. | ||
* {@link module:engine/conversion/downcasthelpers~HighlightDescriptor}. | ||
* | ||
@@ -37,3 +37,3 @@ * This way, highlight will be applied with the same rules it is applied on texts. | ||
* @fires change:top | ||
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor} descriptor | ||
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor | ||
* @param {module:engine/view/downcastwriter~DowncastWriter} writer | ||
@@ -88,3 +88,3 @@ */ | ||
* @private | ||
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor} descriptor | ||
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor | ||
*/ | ||
@@ -137,4 +137,4 @@ _insertDescriptor( descriptor ) { | ||
// | ||
// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} a | ||
// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} b | ||
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} a | ||
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} b | ||
// @returns {Boolean} Returns true if both descriptors are defined and have same priority and classes. | ||
@@ -147,4 +147,4 @@ function compareDescriptors( a, b ) { | ||
// | ||
// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} a | ||
// @param {module:engine/conversion/downcast-converters~HighlightDescriptor} b | ||
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} a | ||
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} b | ||
// @returns {Boolean} | ||
@@ -162,3 +162,3 @@ function shouldABeBeforeB( a, b ) { | ||
// Converts CSS classes passed with {@link module:engine/conversion/downcast-converters~HighlightDescriptor} to | ||
// Converts CSS classes passed with {@link module:engine/conversion/downcasthelpers~HighlightDescriptor} to | ||
// sorted string. | ||
@@ -177,7 +177,7 @@ // | ||
* @param {Object} data Additional information about the change. | ||
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor} [data.newDescriptor] New highlight | ||
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} [data.newDescriptor] New highlight | ||
* descriptor. It will be `undefined` when last descriptor is removed from the stack. | ||
* @param {module:engine/conversion/downcast-converters~HighlightDescriptor} [data.oldDescriptor] Old highlight | ||
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} [data.oldDescriptor] Old highlight | ||
* descriptor. It will be `undefined` when first descriptor is added to the stack. | ||
* @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that can be used to modify element. | ||
*/ |
101
src/utils.js
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
@@ -16,5 +16,2 @@ */ | ||
const widgetSymbol = Symbol( 'isWidget' ); | ||
const labelSymbol = Symbol( 'label' ); | ||
/** | ||
@@ -45,5 +42,6 @@ * CSS class added to each widget element. | ||
return !!node.getCustomProperty( widgetSymbol ); | ||
return !!node.getCustomProperty( 'widget' ); | ||
} | ||
/* eslint-disable max-len */ | ||
/** | ||
@@ -58,4 +56,5 @@ * Converts the given {@link module:engine/view/element~Element} to a widget in the following way: | ||
* | ||
* This function needs to be used in conjuction with {@link module:engine/conversion/downcast-converters downcast converters} | ||
* like {@link module:engine/conversion/downcast-converters~downcastElementToElement `downcastElementToElement()`}. | ||
* This function needs to be used in conjunction with | ||
* {@link module:engine/conversion/downcasthelpers~DowncastHelpers downcast conversion helpers} | ||
* like {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`}. | ||
* Moreover, typically you will want to use `toWidget()` only for `editingDowncast`, while keeping the `dataDowncast` clean. | ||
@@ -67,3 +66,3 @@ * | ||
* editor.conversion.for( 'editingDowncast' ) | ||
* .add( downcastElementToElement( { | ||
* .elementToElement( { | ||
* model: 'widget', | ||
@@ -75,6 +74,6 @@ * view: ( modelItem, writer ) => { | ||
* } | ||
* } ) ); | ||
* } ); | ||
* | ||
* editor.conversion.for( 'dataDowncast' ) | ||
* .add( downcastElementToElement( { | ||
* .elementToElement( { | ||
* model: 'widget', | ||
@@ -84,3 +83,3 @@ * view: ( modelItem, writer ) => { | ||
* } | ||
* } ) ); | ||
* } ); | ||
* | ||
@@ -98,2 +97,3 @@ * See the full source code of the widget (with a nested editable) schema definition and converters in | ||
*/ | ||
/* eslint-enable max-len */ | ||
export function toWidget( element, writer, options = {} ) { | ||
@@ -107,3 +107,3 @@ // The selection on Edge behaves better when the whole editor contents is in a single contenteditable element. | ||
writer.addClass( WIDGET_CLASS_NAME, element ); | ||
writer.setCustomProperty( widgetSymbol, true, element ); | ||
writer.setCustomProperty( 'widget', true, element ); | ||
element.getFillerOffset = getFillerOffset; | ||
@@ -170,3 +170,3 @@ | ||
export function setLabel( element, labelOrCreator, writer ) { | ||
writer.setCustomProperty( labelSymbol, labelOrCreator, element ); | ||
writer.setCustomProperty( 'widgetLabel', labelOrCreator, element ); | ||
} | ||
@@ -181,3 +181,3 @@ | ||
export function getLabel( element ) { | ||
const labelCreator = element.getCustomProperty( labelSymbol ); | ||
const labelCreator = element.getCustomProperty( 'widgetLabel' ); | ||
@@ -200,3 +200,3 @@ if ( !labelCreator ) { | ||
* Similarly to {@link ~toWidget `toWidget()`} this function should be used in `dataDowncast` only and it is usually | ||
* used together with {@link module:engine/conversion/downcast-converters~downcastElementToElement `downcastElementToElement()`}. | ||
* used together with {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`}. | ||
* | ||
@@ -207,3 +207,3 @@ * For example, in order to convert a `<nested>` model element to `<div class="nested">` in the view, you can define | ||
* editor.conversion.for( 'editingDowncast' ) | ||
* .add( downcastElementToElement( { | ||
* .elementToElement( { | ||
* model: 'nested', | ||
@@ -215,6 +215,6 @@ * view: ( modelItem, writer ) => { | ||
* } | ||
* } ) ); | ||
* } ); | ||
* | ||
* editor.conversion.for( 'dataDowncast' ) | ||
* .add( downcastElementToElement( { | ||
* .elementToElement( { | ||
* model: 'nested', | ||
@@ -224,3 +224,3 @@ * view: ( modelItem, writer ) => { | ||
* } | ||
* } ) ); | ||
* } ); | ||
* | ||
@@ -279,3 +279,3 @@ * See the full source code of the widget (with nested editable) schema definition and converters in | ||
if ( selectedElement ) { | ||
if ( selectedElement && model.schema.isBlock( selectedElement ) ) { | ||
return model.createPositionAfter( selectedElement ); | ||
@@ -307,2 +307,61 @@ } | ||
/** | ||
* A util to be used in order to map view positions to correct model positions when implementing a widget | ||
* which renders non-empty view element for an empty model element. | ||
* | ||
* For example: | ||
* | ||
* // Model: | ||
* <placeholder type="name"></placeholder> | ||
* | ||
* // View: | ||
* <span class="placeholder">name</span> | ||
* | ||
* In such case, view positions inside `<span>` cannot be correct mapped to the model (because the model element is empty). | ||
* To handle mapping positions inside `<span class="placeholder">` to the model use this util as follows: | ||
* | ||
* editor.editing.mapper.on( | ||
* 'viewToModelPosition', | ||
* viewToModelPositionOutsideModelElement( model, viewElement => viewElement.hasClass( 'placeholder' ) ) | ||
* ); | ||
* | ||
* The callback will try to map the view offset of selection to an expected model position. | ||
* | ||
* 1. When the position is at the end (or in the middle) of the inline widget: | ||
* | ||
* // View: | ||
* <p>foo <span class="placeholder">name|</span> bar</p> | ||
* | ||
* // Model: | ||
* <paragraph>foo <placeholder type="name"></placeholder>| bar</paragraph> | ||
* | ||
* 2. When the position is at the beginning of the inline widget: | ||
* | ||
* // View: | ||
* <p>foo <span class="placeholder">|name</span> bar</p> | ||
* | ||
* // Model: | ||
* <paragraph>foo |<placeholder type="name"></placeholder> bar</paragraph> | ||
* | ||
* @param {module:engine/model/model~Model} model Model instance on which the callback operates. | ||
* @param {Function} viewElementMatcher Function that is passed a view element and should return `true` if the custom mapping | ||
* should be applied to the given view element. | ||
* @return {Function} | ||
*/ | ||
export function viewToModelPositionOutsideModelElement( model, viewElementMatcher ) { | ||
return ( evt, data ) => { | ||
const { mapper, viewPosition } = data; | ||
const viewParent = mapper.findMappedViewAncestor( viewPosition ); | ||
if ( !viewElementMatcher( viewParent ) ) { | ||
return; | ||
} | ||
const modelParent = mapper.toModelElement( viewParent ); | ||
data.modelPosition = model.createPositionAt( modelParent, viewPosition.isAtStart ? 'before' : 'after' ); | ||
}; | ||
} | ||
// Default filler offset function applied to all widget elements. | ||
@@ -337,3 +396,3 @@ // | ||
writer.insert( writer.createPositionAt( editable, 0 ), selectionHandler ); | ||
writer.addClass( [ 'ck-widget_selectable' ], editable ); | ||
writer.addClass( [ 'ck-widget_with-selection-handler' ], editable ); | ||
} |
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
@@ -12,4 +12,4 @@ */ | ||
import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver'; | ||
import { isWidget, WIDGET_SELECTED_CLASS_NAME, getLabel } from './utils'; | ||
import { keyCodes, getCode, parseKeystroke } from '@ckeditor/ckeditor5-utils/src/keyboard'; | ||
import { getLabel, isWidget, WIDGET_SELECTED_CLASS_NAME } from './utils'; | ||
import { getCode, keyCodes, parseKeystroke } from '@ckeditor/ckeditor5-utils/src/keyboard'; | ||
@@ -25,7 +25,7 @@ import '../theme/widget.css'; | ||
* | ||
* This plugin enables multiple behaiors required by the widgets: | ||
* This plugin enables multiple behaviors required by widgets: | ||
* | ||
* * The model to view selection converter for the editing pipeline (it handles widget custom selection rendering). | ||
* If a converted selection is wraps around a widget element, that selection is marked as | ||
* {@link module:engine/view/selection~Selection#isFake fake}. Additionally, proper the `ck-widget_selected` CSS class | ||
* If a converted selection wraps around a widget element, that selection is marked as | ||
* {@link module:engine/view/selection~Selection#isFake fake}. Additionally, the `ck-widget_selected` CSS class | ||
* is added to indicate that widget has been selected. | ||
@@ -155,3 +155,3 @@ * * The mouse and keyboard events handling on and around widget elements. | ||
const keyCode = domEventData.keyCode; | ||
const isForward = keyCode == keyCodes.delete || keyCode == keyCodes.arrowdown || keyCode == keyCodes.arrowright; | ||
const isForward = keyCode == keyCodes.arrowdown || keyCode == keyCodes.arrowright; | ||
let wasHandled = false; | ||
@@ -274,9 +274,18 @@ | ||
const modelSelection = model.document.selection; | ||
const objectElement = modelSelection.getSelectedElement(); | ||
const selectedElement = modelSelection.getSelectedElement(); | ||
if ( objectElement && model.schema.isObject( objectElement ) ) { | ||
if ( shouldInsertParagraph( selectedElement, model.schema ) ) { | ||
model.change( writer => { | ||
let position = writer.createPositionAt( selectedElement, isBackwards ? 'before' : 'after' ); | ||
const paragraph = writer.createElement( 'paragraph' ); | ||
writer.insert( paragraph, objectElement, isBackwards ? 'before' : 'after' ); | ||
// Split the parent when inside a block element. | ||
// https://github.com/ckeditor/ckeditor5/issues/1529 | ||
if ( model.schema.isBlock( selectedElement.parent ) ) { | ||
const paragraphLimit = model.schema.findAllowedParent( position, paragraph ); | ||
position = writer.split( position, paragraphLimit ).position; | ||
} | ||
writer.insert( paragraph, position ); | ||
writer.setSelection( paragraph, 'in' ); | ||
@@ -422,6 +431,11 @@ } ); | ||
while ( element ) { | ||
if ( !!element && element.is( 'editableElement' ) && !element.is( 'rootElement' ) ) { | ||
if ( element.is( 'editableElement' ) && !element.is( 'rootElement' ) ) { | ||
return true; | ||
} | ||
// Click on nested widget should select it. | ||
if ( isWidget( element ) ) { | ||
return false; | ||
} | ||
element = element.parent; | ||
@@ -445,1 +459,9 @@ } | ||
} | ||
// Checks if enter key should insert paragraph. This should be done only on elements of type object (excluding inline objects). | ||
// | ||
// @param {module:engine/model/element~Element} element And element to check. | ||
// @param {module:engine/model/schema~Schema} schema | ||
function shouldInsertParagraph( element, schema ) { | ||
return element && schema.isObject( element ) && !schema.isInline( element ); | ||
} |
/** | ||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
@@ -36,3 +36,3 @@ */ | ||
* items: editor.config.get( 'image.toolbar' ), | ||
* visibleWhen: viewSelection => isImageWidgetSelected( viewSelection ) | ||
* getRelatedElement: getSelectedImageWidget | ||
* } ); | ||
@@ -62,6 +62,7 @@ * } | ||
const editor = this.editor; | ||
const balloonToolbar = editor.plugins.get( 'BalloonToolbar' ); | ||
// Disables the default balloon toolbar for all widgets. | ||
if ( balloonToolbar ) { | ||
if ( editor.plugins.has( 'BalloonToolbar' ) ) { | ||
const balloonToolbar = editor.plugins.get( 'BalloonToolbar' ); | ||
this.listenTo( balloonToolbar, 'show', evt => { | ||
@@ -75,8 +76,8 @@ if ( isWidgetSelected( editor.editing.view.document.selection ) ) { | ||
/** | ||
* A map of toolbars. | ||
* A map of toolbar definitions. | ||
* | ||
* @protected | ||
* @member {Map.<string,Object>} #_toolbars | ||
* @member {Map.<String,module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition>} #_toolbarDefinitions | ||
*/ | ||
this._toolbars = new Map(); | ||
this._toolbarDefinitions = new Map(); | ||
@@ -98,5 +99,13 @@ /** | ||
destroy() { | ||
super.destroy(); | ||
for ( const toolbarConfig of this._toolbarDefinitions.values() ) { | ||
toolbarConfig.view.destroy(); | ||
} | ||
} | ||
/** | ||
* Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked | ||
* `visibleWhen` function. Toolbar items are gathered from `items` array. | ||
* `getRelatedElement` function. Toolbar items are gathered from `items` array. | ||
* The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option. | ||
@@ -110,10 +119,10 @@ * | ||
* @param {Array.<String>} options.items Array of toolbar items. | ||
* @param {Function} options.visibleWhen Callback which specifies when the toolbar should be visible for the widget. | ||
* @param {Function} options.getRelatedElement Callback which returns an element the toolbar should be attached to. | ||
* @param {String} [options.balloonClassName='ck-toolbar-container'] CSS class for the widget balloon. | ||
*/ | ||
register( toolbarId, { items, visibleWhen, balloonClassName = 'ck-toolbar-container' } ) { | ||
register( toolbarId, { items, getRelatedElement, balloonClassName = 'ck-toolbar-container' } ) { | ||
const editor = this.editor; | ||
const toolbarView = new ToolbarView(); | ||
if ( this._toolbars.has( toolbarId ) ) { | ||
if ( this._toolbarDefinitions.has( toolbarId ) ) { | ||
/** | ||
@@ -130,5 +139,5 @@ * Toolbar with the given id was already added. | ||
this._toolbars.set( toolbarId, { | ||
this._toolbarDefinitions.set( toolbarId, { | ||
view: toolbarView, | ||
visibleWhen, | ||
getRelatedElement, | ||
balloonClassName, | ||
@@ -144,9 +153,29 @@ } ); | ||
_updateToolbarsVisibility() { | ||
for ( const toolbar of this._toolbars.values() ) { | ||
if ( !this.editor.ui.focusTracker.isFocused || !toolbar.visibleWhen( this.editor.editing.view.document.selection ) ) { | ||
this._hideToolbar( toolbar ); | ||
let maxRelatedElementDepth = 0; | ||
let deepestRelatedElement = null; | ||
let deepestToolbarDefinition = null; | ||
for ( const definition of this._toolbarDefinitions.values() ) { | ||
const relatedElement = definition.getRelatedElement( this.editor.editing.view.document.selection ); | ||
if ( !this.editor.ui.focusTracker.isFocused || !relatedElement ) { | ||
this._hideToolbar( definition ); | ||
} else { | ||
this._showToolbar( toolbar ); | ||
const relatedElementDepth = relatedElement.getAncestors().length; | ||
// Many toolbars can express willingness to be displayed but they do not know about | ||
// each other. Figure out which toolbar is deepest in the view tree to decide which | ||
// should be displayed. For instance, if a selected image is inside a table cell, display | ||
// the ImageToolbar rather than the TableToolbar (#60). | ||
if ( relatedElementDepth > maxRelatedElementDepth ) { | ||
maxRelatedElementDepth = relatedElementDepth; | ||
deepestRelatedElement = relatedElement; | ||
deepestToolbarDefinition = definition; | ||
} | ||
} | ||
} | ||
if ( deepestToolbarDefinition ) { | ||
this._showToolbar( deepestToolbarDefinition, deepestRelatedElement ); | ||
} | ||
} | ||
@@ -158,15 +187,15 @@ | ||
* @private | ||
* @param {Object} toolbar | ||
* @param {module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition} toolbarDefinition | ||
*/ | ||
_hideToolbar( toolbar ) { | ||
if ( !this._isToolbarVisible( toolbar ) ) { | ||
_hideToolbar( toolbarDefinition ) { | ||
if ( !this._isToolbarVisible( toolbarDefinition ) ) { | ||
return; | ||
} | ||
this._balloon.remove( toolbar.view ); | ||
this._balloon.remove( toolbarDefinition.view ); | ||
} | ||
/** | ||
* Shows up the toolbar if the toolbar is not visible and repositions the toolbar's balloon when toolbar's | ||
* view is the most top view in balloon stack. | ||
* Shows up the toolbar if the toolbar is not visible. | ||
* Otherwise, repositions the toolbar's balloon when toolbar's view is the most top view in balloon stack. | ||
* | ||
@@ -177,12 +206,13 @@ * It might happen here that the toolbar's view is under another view. Then do nothing as the other toolbar view | ||
* @private | ||
* @param {Object} toolbar | ||
* @param {module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition} toolbarDefinition | ||
* @param {module:engine/view/element~Element} relatedElement | ||
*/ | ||
_showToolbar( toolbar ) { | ||
if ( this._isToolbarVisible( toolbar ) ) { | ||
repositionContextualBalloon( this.editor ); | ||
} else if ( !this._balloon.hasView( toolbar.view ) ) { | ||
_showToolbar( toolbarDefinition, relatedElement ) { | ||
if ( this._isToolbarVisible( toolbarDefinition ) ) { | ||
repositionContextualBalloon( this.editor, relatedElement ); | ||
} else if ( !this._balloon.hasView( toolbarDefinition.view ) ) { | ||
this._balloon.add( { | ||
view: toolbar.view, | ||
position: getBalloonPositionData( this.editor ), | ||
balloonClassName: toolbar.balloonClassName, | ||
view: toolbarDefinition.view, | ||
position: getBalloonPositionData( this.editor, relatedElement ), | ||
balloonClassName: toolbarDefinition.balloonClassName, | ||
} ); | ||
@@ -201,5 +231,5 @@ } | ||
function repositionContextualBalloon( editor ) { | ||
function repositionContextualBalloon( editor, relatedElement ) { | ||
const balloon = editor.plugins.get( 'ContextualBalloon' ); | ||
const position = getBalloonPositionData( editor ); | ||
const position = getBalloonPositionData( editor, relatedElement ); | ||
@@ -209,9 +239,8 @@ balloon.updatePosition( position ); | ||
function getBalloonPositionData( editor ) { | ||
function getBalloonPositionData( editor, relatedElement ) { | ||
const editingView = editor.editing.view; | ||
const defaultPositions = BalloonPanelView.defaultPositions; | ||
const widget = getParentWidget( editingView.document.selection ); | ||
return { | ||
target: editingView.domConverter.viewToDom( widget ), | ||
target: editingView.domConverter.viewToDom( relatedElement ), | ||
positions: [ | ||
@@ -228,21 +257,2 @@ defaultPositions.northArrowSouth, | ||
function getParentWidget( selection ) { | ||
const selectedElement = selection.getSelectedElement(); | ||
if ( selectedElement && isWidget( selectedElement ) ) { | ||
return selectedElement; | ||
} | ||
const position = selection.getFirstPosition(); | ||
let parent = position.parent; | ||
while ( parent ) { | ||
if ( parent.is( 'element' ) && isWidget( parent ) ) { | ||
return parent; | ||
} | ||
parent = parent.parent; | ||
} | ||
} | ||
function isWidgetSelected( selection ) { | ||
@@ -253,1 +263,16 @@ const viewElement = selection.getSelectedElement(); | ||
} | ||
/** | ||
* The toolbar definition object used by the toolbar repository to manage toolbars. | ||
* It contains information necessary to display the toolbar in the | ||
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} and | ||
* update it during its life (display) cycle. | ||
* | ||
* @typedef {Object} module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition | ||
* | ||
* @property {module:ui/view~View} view The UI view of the toolbar. | ||
* @property {Function} getRelatedElement A function that returns an engine {@link module:engine/view/view~View} | ||
* element the toolbar is to be attached to. For instance, an image widget or a table widget (or `null` when | ||
* there is no such element). The function accepts an instance of {@link module:engine/view/selection~Selection}. | ||
* @property {String} balloonClassName CSS class for the widget balloon when a toolbar is displayed. | ||
*/ |
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
59056
1133
15
+ Added@ckeditor/ckeditor5-core@12.3.0(transitive)
+ Added@ckeditor/ckeditor5-engine@13.2.114.0.0(transitive)
+ Added@ckeditor/ckeditor5-theme-lark@13.0.1(transitive)
+ Added@ckeditor/ckeditor5-ui@12.1.0(transitive)
+ Added@ckeditor/ckeditor5-utils@12.1.113.0.114.0.0(transitive)
+ Addedckeditor5@12.4.0(transitive)
- Removed@ckeditor/ckeditor5-core@11.1.0(transitive)
- Removed@ckeditor/ckeditor5-engine@12.0.0(transitive)
- Removed@ckeditor/ckeditor5-theme-lark@12.0.0(transitive)
- Removed@ckeditor/ckeditor5-ui@11.2.0(transitive)
- Removed@ckeditor/ckeditor5-utils@11.1.0(transitive)
- Removedckeditor5@11.2.0(transitive)