浏览代码

Merge pull request #43 from ckeditor/t/40

Feature: Implemented the widget selection handler. Closes #40.
Aleksander Nowodzinski 7 年之前
父节点
当前提交
546eb57fad

+ 2 - 1
packages/ckeditor5-widget/package.json

@@ -12,7 +12,8 @@
     "@ckeditor/ckeditor5-core": "^10.1.0",
     "@ckeditor/ckeditor5-engine": "^10.1.0",
     "@ckeditor/ckeditor5-utils": "^10.1.0",
-    "@ckeditor/ckeditor5-theme-lark": "^10.1.0"
+    "@ckeditor/ckeditor5-theme-lark": "^10.1.0",
+    "@ckeditor/ckeditor5-ui": "^10.1.0"
   },
   "devDependencies": {
     "@ckeditor/ckeditor5-typing": "^10.0.1",

+ 34 - 0
packages/ckeditor5-widget/src/utils.js

@@ -8,6 +8,10 @@
  */
 
 import HighlightStack from './highlightstack';
+import Position from '@ckeditor/ckeditor5-engine/src/view/position';
+import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
+
+import dragHandlerIcon from '../theme/icons/drag-handler.svg';
 
 const widgetSymbol = Symbol( 'isWidget' );
 const labelSymbol = Symbol( 'label' );
@@ -49,6 +53,7 @@ export function isWidget( element ) {
  * @param {Object} [options={}]
  * @param {String|Function} [options.label] Element's label provided to {@link ~setLabel} function. It can be passed as
  * a plain string or a function returning a string.
+ * @param {Boolean} [options.hasSelectionHandler=false] If `true`, the widget will have a selection handler added.
  * @returns {module:engine/view/element~Element} Returns same element.
  */
 export function toWidget( element, writer, options = {} ) {
@@ -61,6 +66,10 @@ export function toWidget( element, writer, options = {} ) {
 		setLabel( element, options.label, writer );
 	}
 
+	if ( options.hasSelectionHandler ) {
+		addSelectionHandler( element, writer );
+	}
+
 	setHighlightHandling(
 		element,
 		writer,
@@ -170,3 +179,28 @@ export function toWidgetEditable( editable, writer ) {
 function getFillerOffset() {
 	return null;
 }
+
+// Adds a drag handler to the editable element.
+//
+// @param {module:engine/view/editableelement~EditableElement}
+// @param {module:engine/view/writer~Writer} writer
+function addSelectionHandler( editable, writer ) {
+	const selectionHandler = writer.createUIElement( 'div', { class: 'ck ck-widget__selection-handler' }, function( domDocument ) {
+		const domElement = this.toDomElement( domDocument );
+
+		// Use the IconView from the ui library.
+		const icon = new IconView();
+		icon.set( 'content', dragHandlerIcon );
+
+		// Render the icon view right away to append its #element to the selectionHandler DOM element.
+		icon.render();
+
+		domElement.appendChild( icon.element );
+
+		return domElement;
+	} );
+
+	// Append the selection handler into the widget wrapper.
+	writer.insert( Position.createAt( editable ), selectionHandler );
+	writer.addClass( [ 'ck-widget_selectable' ], editable );
+}

+ 23 - 0
packages/ckeditor5-widget/tests/utils.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* global document */
+
 import Writer from '@ckeditor/ckeditor5-engine/src/view/writer';
 import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element';
 import ViewEditableElement from '@ckeditor/ckeditor5-engine/src/view/editableelement';
@@ -16,6 +18,7 @@ import {
 	setHighlightHandling,
 	WIDGET_CLASS_NAME
 } from '../src/utils';
+import UIElement from '@ckeditor/ckeditor5-engine/src/view/uielement';
 
 describe( 'widget utils', () => {
 	let element, writer, viewDocument;
@@ -87,6 +90,26 @@ describe( 'widget utils', () => {
 			expect( element.hasClass( 'highlight' ) ).to.be.false;
 			expect( element.hasClass( 'foo' ) ).to.be.false;
 		} );
+
+		it( 'should add element a selection handler to widget if hasSelectionHandler=true is passed', () => {
+			toWidget( element, writer, { hasSelectionHandler: true } );
+
+			expect( element.hasClass( 'ck-widget_selectable' ) ).to.be.true;
+
+			const selectionHandler = element.getChild( 0 );
+			expect( selectionHandler ).to.be.instanceof( UIElement );
+
+			const domSelectionHandler = selectionHandler.render( document );
+
+			expect( domSelectionHandler.classList.contains( 'ck' ) ).to.be.true;
+			expect( domSelectionHandler.classList.contains( 'ck-widget__selection-handler' ) ).to.be.true;
+
+			const icon = domSelectionHandler.firstChild;
+
+			expect( icon.nodeName ).to.equal( 'svg' );
+			expect( icon.classList.contains( 'ck' ) ).to.be.true;
+			expect( icon.classList.contains( 'ck-icon' ) ).to.be.true;
+		} );
 	} );
 
 	describe( 'isWidget()', () => {

+ 46 - 0
packages/ckeditor5-widget/tests/widget.js

@@ -1159,4 +1159,50 @@ describe( 'Widget', () => {
 			scrollStub.restore();
 		} );
 	} );
+
+	describe( 'selection handler', () => {
+		beforeEach( () => {
+			return VirtualTestEditor.create( { plugins: [ Widget, Typing ] } )
+				.then( newEditor => {
+					editor = newEditor;
+					model = editor.model;
+					view = editor.editing.view;
+					viewDocument = view.document;
+
+					model.schema.register( 'widget', {
+						inheritAllFrom: '$block',
+						isObject: true
+					} );
+					model.schema.register( 'paragraph', {
+						inheritAllFrom: '$block'
+					} );
+
+					editor.conversion.for( 'downcast' )
+						.add( downcastElementToElement( { model: 'paragraph', view: 'p' } ) )
+						.add( downcastElementToElement( {
+							model: 'widget',
+							view: ( modelItem, viewWriter ) => {
+								const widget = viewWriter.createContainerElement( 'div' );
+
+								return toWidget( widget, viewWriter, { hasSelectionHandler: true } );
+							}
+						} ) );
+				} );
+		} );
+
+		it( 'should select a widget on mouse click', () => {
+			setModelData( model, '<paragraph>bar</paragraph><widget></widget><paragraph>foo[]</paragraph>' );
+
+			const viewWidgetSelectionHandler = viewDocument.getRoot().getChild( 1 ).getChild( 0 );
+
+			const domEventDataMock = {
+				target: viewWidgetSelectionHandler,
+				preventDefault: sinon.spy()
+			};
+
+			viewDocument.fire( 'mousedown', domEventDataMock );
+
+			expect( getModelData( model ) ).to.equal( '<paragraph>bar</paragraph>[<widget></widget>]<paragraph>foo</paragraph>' );
+		} );
+	} );
 } );

+ 1 - 0
packages/ckeditor5-widget/theme/icons/drag-handler.svg

@@ -0,0 +1 @@
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g><path d="M4 0v1H1v3H0V.5A.5.5 0 0 1 .5 0H4zm8 0h3.5a.5.5 0 0 1 .5.5V4h-1V1h-3V0zM4 16H.5a.5.5 0 0 1-.5-.5V12h1v3h3v1zm8 0v-1h3v-3h1v3.5a.5.5 0 0 1-.5.5H12z"/><path fill-opacity=".256" d="M1 1h14v14H1z"/><g class="ck-icon__selected-indicator"><path d="M7 0h2v1H7V0zM0 7h1v2H0V7zm15 0h1v2h-1V7zm-8 8h2v1H7v-1z"/><path fill-opacity=".254" d="M1 1h14v14H1z"/></g></g></svg>

+ 29 - 5
packages/ckeditor5-widget/theme/widget.css

@@ -3,8 +3,32 @@
  * For licensing, see LICENSE.md.
  */
 
-/*
- * Note: This file should contain the wireframe styles only. But since there are no such styles,
- * it acts as a message to the builder telling that it should look for the corresponding styles
- * **in the theme** when compiling the editor.
- */
+.ck .ck-widget.ck-widget_selectable {
+	/* Make the widget wrapper a relative positioning container for the drag handler. */
+	position: relative;
+
+	& .ck-widget__selection-handler {
+		visibility: hidden;
+		position: absolute;
+
+		& .ck-icon {
+			/* Make sure the icon in not a subject to fon-size/line-height to avoid
+			unnecessary spacing around it. */
+			display: block;
+		}
+	}
+
+	/* Show the selection handler on mouse hover over the widget. */
+	&:hover {
+		& .ck-widget__selection-handler {
+			visibility: visible;
+		}
+	}
+
+	/* Show the selection handler when the widget is selected. */
+	&.ck-widget_selected {
+		& .ck-widget__selection-handler {
+			visibility: visible;
+		}
+	}
+}