Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #43 from ckeditor/t/40
Browse files Browse the repository at this point in the history
Feature: Implemented the widget selection handler. Closes #40.
  • Loading branch information
oleq committed Jun 27, 2018
2 parents 70e01ea + 610fde2 commit bbf9298
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 6 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
34 changes: 34 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
Expand Down Expand Up @@ -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 = {} ) {
Expand All @@ -61,6 +66,10 @@ export function toWidget( element, writer, options = {} ) {
setLabel( element, options.label, writer );
}

if ( options.hasSelectionHandler ) {
addSelectionHandler( element, writer );
}

setHighlightHandling(
element,
writer,
Expand Down Expand Up @@ -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 changes: 23 additions & 0 deletions tests/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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()', () => {
Expand Down
46 changes: 46 additions & 0 deletions tests/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 change: 1 addition & 0 deletions theme/icons/drag-handler.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 29 additions & 5 deletions theme/widget.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

0 comments on commit bbf9298

Please sign in to comment.