浏览代码

Moved Balloon Panel component to ckeditor5-ui-default and ckeditor5-theme-lark repositories.

Oskar Wrobel 9 年之前
父节点
当前提交
2e07d16fb3

+ 59 - 45
packages/ckeditor5-link/src/link.js

@@ -4,9 +4,10 @@
  */
 
 import Feature from '../core/feature.js';
-import Model from '../ui/model.js';
-
 import LinkEngine from './linkengine.js';
+import ClickObserver from '../engine/view/observer/clickobserver.js';
+
+import Model from '../ui/model.js';
 
 import ButtonController from '../ui/button/button.js';
 import ButtonView from '../ui/button/buttonview.js';
@@ -35,10 +36,31 @@ export default class Link extends Feature {
 	 */
 	init() {
 		const editor = this.editor;
-		const t = editor.t;
-		const command = editor.commands.get( 'link' );
+		const viewDocument = editor.editing.view;
+		const linkCommand = editor.commands.get( 'link' );
+
+		/**
+		 * @TODO
+		 */
+		this.balloonPanel = this._createBalloonPanel();
+		this._createToolbarLinkButton();
+
+		// Register document click observer
+		viewDocument.addObserver( ClickObserver );
+
+		// Handle click on document and show panel when click target is a link element.
+		viewDocument.on( 'click', () => {
+			if ( viewDocument.selection.isCollapsed && linkCommand.value !== undefined ) {
+				this._attachPanelToElement();
+			}
+		} );
+	}
 
-		this._createBalloonPanel();
+	_createToolbarLinkButton() {
+		const editor = this.editor;
+		const viewDocument = editor.editing.view;
+		const linkCommand = editor.commands.get( 'link' );
+		const t = editor.t;
 
 		// Create button model.
 		const buttonModel = new Model( {
@@ -48,9 +70,11 @@ export default class Link extends Feature {
 			icon: 'link'
 		} );
 
+		buttonModel.bind( 'url' ).to( linkCommand, 'value' );
+
 		// Show Balloon Panel on button click.
 		this.listenTo( buttonModel, 'execute', () => {
-			if ( !this.editor.editing.view.isFocused ) {
+			if ( !viewDocument.isFocused ) {
 				return;
 			}
 
@@ -59,14 +83,6 @@ export default class Link extends Feature {
 
 		// Add link button to feature components.
 		editor.ui.featureComponents.add( 'link', ButtonController, ButtonView, buttonModel );
-
-		// Show Balloon Panel on click directly on some link element.
-		// @TODO: Get click event from editor instead of DOM.
-		this.editor.ui.editable.view.element.addEventListener( 'click', () => {
-			if ( editor.document.selection.isCollapsed && command.value !== undefined ) {
-				this._attachPanelToElement();
-			}
-		} );
 	}
 
 	/**
@@ -105,69 +121,67 @@ export default class Link extends Feature {
 	 */
 	_createBalloonPanel() {
 		const editor = this.editor;
+		const viewDocument = editor.editing.view;
 		const t = editor.t;
-		const command = this.editor.commands.get( 'link' );
-		const editingView = editor.editing.view;
+		const linkCommand = editor.commands.get( 'link' );
 
 		// Create the model of the panel.
 		const panelModel = new Model( {
 			maxWidth: 300,
-			url: command.value
+			url: linkCommand.value
 		} );
 
 		// Bind panel model to command.
-		panelModel.bind( 'url' ).to( command, 'value' );
+		panelModel.bind( 'url' ).to( linkCommand, 'value' );
+
+		// Create balloon Panel instance.
+		const balloonPanel = new LinkBalloonPanel( panelModel, new LinkBalloonPanelView( editor.locale ) );
 
-		// Observe #execute event from within the model of the panel, which means that
-		// the "Save" button has been clicked.
+		// Observe #execute event from within the model of the panel, which means that the "Save" button has been clicked.
 		this.listenTo( panelModel, 'execute', () => {
-			const urlValue = this.balloonPanel.urlInput.value;
+			const urlValue = balloonPanel.urlInput.value;
 
-			// TODO: validate panelModel#url with some RegExp imported from v4.
+			// TODO: Validate panelModel#url with some RegExp imported from v4.
 			if ( urlValue ) {
-				this.editor.execute( 'link', urlValue );
-				this.balloonPanel.view.hide();
+				editor.execute( 'link', urlValue );
+				balloonPanel.view.hide();
 			} else {
 				window.alert( t( `"${ urlValue }" URL address is incorrect.` ) );
-				this.balloonPanel.urlInput.view.focus();
+				balloonPanel.urlInput.view.focus();
 			}
 		} );
 
-		/**
-		 * TODO
-		 *
-		 * @member {} todo
-		 */
-		this.balloonPanel = new LinkBalloonPanel( panelModel, new LinkBalloonPanelView( editor.locale ) );
+		// Always focus editor on panel hide.
+		this.listenTo( panelModel, 'hide', () => viewDocument.focus() );
 
-		// TODO: It's a lame FocusManager.
-		editingView.on( 'blur', ( evt, domEvtData ) => {
-			if ( domEvtData.domEvent.relatedTarget === this.balloonPanel.urlInput.input.view.element ) {
+		// TODO: Create real focus manager.
+		viewDocument.on( 'focus', () => balloonPanel.view.hide() );
+		viewDocument.on( 'blur', ( evt, domEvtData ) => {
+			if ( domEvtData.domEvent.relatedTarget === balloonPanel.urlInput.input.view.element ) {
 				domEvtData.domEvent.preventDefault();
-			} else {
-				this.balloonPanel.view.hide();
 			}
 		} );
 
-		editor.ui.add( 'body', this.balloonPanel );
+		editor.ui.add( 'body', balloonPanel );
+
+		return balloonPanel;
 	}
 
 	_attachPanelToElement() {
-		// Adjust balloon position.
-		const editingView = this.editor.editing.view;
+		const viewDocument = this.editor.editing.view;
 		const editableViewElement = this.editor.ui.editable.view.element;
-		const firstParent = editingView.selection.getFirstPosition().parent;
-		const firstParentAncestors = firstParent.getAncestors();
-		const anchor = firstParentAncestors.find( ( ancestor ) => ancestor.name === 'a' );
+		const viewSelectionParent = viewDocument.selection.getFirstPosition().parent;
+		const viewSelectionParentAncestors = viewSelectionParent.getAncestors();
+		const linkElement = viewSelectionParentAncestors.find( ( ancestor ) => ancestor.name === 'a' );
 
-		if ( anchor ) {
+		if ( linkElement ) {
 			this.balloonPanel.view.attachTo(
-				editingView.domConverter.getCorrespondingDomElement( anchor ),
+				viewDocument.domConverter.getCorrespondingDomElement( linkElement ),
 				editableViewElement
 			);
 		} else {
 			this.balloonPanel.view.attachTo(
-				editingView.domConverter.viewRangeToDom( editingView.selection.getFirstRange() ),
+				viewDocument.domConverter.viewRangeToDom( viewDocument.selection.getFirstRange() ),
 				editableViewElement
 			);
 		}

+ 0 - 31
packages/ckeditor5-link/src/ui/balloonpanel.js

@@ -1,31 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import Controller from '../../ui/controller.js';
-
-/**
- * The balloon panel controller class.
- *
- * @memberOf ui.balloonPanel
- * @extends ui.Controller
- */
-export default class BalloonPanel extends Controller {
-	/**
-	 * Creates an instance of {@link ui.balloonPanel.BalloonPanel} class.
-	 *
-	 * @param {ui.balloonPanel.BalloonPanelModel} model Model of this balloon panel.
-	 * @param {ui.View} view View of this balloon panel.
-	 */
-	constructor( model, view ) {
-		super( model, view );
-
-		view.model.set( 'top', 0 );
-		view.model.set( 'left', 0 );
-		view.model.set( 'isVisible', false );
-		view.model.bind( 'arrow', 'maxWidth', 'maxHeight' ).to( model );
-
-		this.addCollection( 'content' );
-	}
-}

+ 0 - 227
packages/ckeditor5-link/src/ui/balloonpanelview.js

@@ -1,227 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import View from '../../ui/view.js';
-import Template from '../../ui/template.js';
-
-const arrowLeftOffset = 30;
-const arrowTopOffset = 15;
-
-/**
- * The balloon panel view class.
- *
- * See {@link ui.balloonPanel.BalloonPanel}.
- *
- * @memberOf ui.balloonPanel
- * @extends ui.View
- */
-export default class BalloonPanelView extends View {
-	/**
-	 * @inheritDoc
-	 */
-	constructor( locale ) {
-		super( locale );
-
-		const bind = this.bind;
-
-		this.template = new Template( {
-			tag: 'div',
-			attributes: {
-				class: [
-					'ck-balloon-panel',
-					'ck-link-balloon-panel',
-					bind.to( 'arrow', ( value ) =>  `ck-balloon-panel_arrow_${ value }` ),
-					bind.if( 'isVisible', 'ck-balloon-panel_visible' ),
-				],
-
-				style: {
-					top: bind.to( 'top', ( value ) => `${ value }px` ),
-					left: bind.to( 'left', ( value ) => `${ value }px` ),
-					maxWidth: bind.to( 'maxWidth', ( value ) => `${ value }px` ),
-					maxHeight: bind.to( 'maxHeight', ( value ) => `${ value }px` ),
-				}
-			}
-		} );
-
-		this.register( 'content', el => el );
-	}
-
-	show() {
-		this.model.isVisible = true;
-	}
-
-	hide() {
-		this.model.isVisible = false;
-	}
-
-	getVisibleViewportRect( limiterElement ) {
-		const limiterRect = new AbsoluteDomRect( limiterElement, 'limiter' );
-		const windowScrollX = window.scrollX;
-		const windowScrollY = window.scrollY;
-		const bodyWidth = document.body.clientWidth;
-		const bodyHeight = document.body.clientHeight;
-
-		// 	[Viewport]
-		// 	+---------------------------------------+
-		// 	|                        [Limiter]      |
-		// 	|                        +----------------------+
-		// 	|                        |##############|       |
-		// 	|                        |##############|       |
-		// 	|                        |##############|       |
-		// 	|                        +-------^--------------+
-		// 	|                                |      |
-		// 	+--------------------------------|------+
-		//                                   |
-		//                                    \- [Visible Viewport Rect]
-		//
-		return new AbsoluteDomRect( {
-			top: Math.max( limiterRect.top, windowScrollY ),
-			left: Math.max( limiterRect.left, windowScrollX ),
-			right: Math.min( limiterRect.right, bodyWidth + windowScrollX ),
-			bottom: Math.min( limiterRect.bottom, bodyHeight + windowScrollY )
-		}, 'visibleViewportRect' );
-	}
-
-	attachTo( elementOrRange, limiterElement ) {
-		this.show();
-
-		const elementOrRangeRect = new AbsoluteDomRect( elementOrRange, 'elementOrRange' );
-		const panelRect = new AbsoluteDomRect( this.element );
-		const visibleViewportRect = this.getVisibleViewportRect( limiterElement );
-
-		// visibleViewportRect.paint();
-		// elementOrRangeRect.paint();
-
-		this.smartAttachTo( [
-			// [     ]
-			//    ^
-			// +--------------+
-			// |              |
-			// +--------------+
-			panelRect.clone( 'se' ).moveTo( {
-				top: elementOrRangeRect.bottom + arrowTopOffset,
-				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - arrowLeftOffset
-			} ),
-
-			//          [     ]
-			//             ^
-			// +--------------+
-			// |              |
-			// +--------------+
-			panelRect.clone( 'sw' ).moveTo( {
-				top: elementOrRangeRect.bottom + arrowTopOffset,
-				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - panelRect.width + arrowLeftOffset
-			} ),
-
-			// +--------------+
-			// |              |
-			// +--------------+
-			//    V
-			// [     ]
-			panelRect.clone( 'ne' ).moveTo( {
-				top: elementOrRangeRect.top - panelRect.height - arrowTopOffset,
-				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - arrowLeftOffset
-			} ),
-
-			// +--------------+
-			// |              |
-			// +--------------+
-			//             V
-			//          [     ]
-			panelRect.clone( 'nw' ).moveTo( {
-				top: elementOrRangeRect.top - panelRect.height - arrowTopOffset,
-				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - panelRect.width + arrowLeftOffset
-			} )
-		], visibleViewportRect );
-	}
-
-	smartAttachTo( rects, visibleViewportRect ) {
-		let maxIntersectRect;
-		let maxIntersectArea = -1;
-
-		for ( let rect of rects ) {
-			const intersectArea = rect.getIntersectArea( visibleViewportRect );
-			// rect.paint();
-
-			if ( intersectArea > maxIntersectArea ) {
-				maxIntersectRect = rect;
-				maxIntersectArea = intersectArea;
-			}
-		}
-
-		this.model.arrow = maxIntersectRect.name;
-		this.model.top = maxIntersectRect.top;
-		this.model.left = maxIntersectRect.left;
-	}
-}
-
-// AbsoluteDomRect always corresponds with what position: absolute would behave.
-class AbsoluteDomRect {
-	constructor( elementOrRangeOrRect, name ) {
-		if ( name ) {
-			this.name = name;
-		}
-
-		if ( elementOrRangeOrRect instanceof HTMLElement || elementOrRangeOrRect instanceof Range ) {
-			const elementRect = elementOrRangeOrRect.getBoundingClientRect();
-			const bodyRect = document.body.getBoundingClientRect();
-
-			this.top = elementRect.top - bodyRect.top;
-			this.right = elementRect.right - bodyRect.left;
-			this.bottom = elementRect.bottom - bodyRect.top;
-			this.left = elementRect.left - bodyRect.left;
-			this.width = elementRect.width;
-			this.height = elementRect.height;
-		} else {
-			Object.assign( this, elementOrRangeOrRect );
-
-			if ( typeof width == 'undefined' ) {
-				this.width = this.right - this.left;
-			}
-
-			if ( typeof height == 'undefined' ) {
-				this.height = this.bottom - this.top;
-			}
-		}
-	}
-
-	clone( newName ) {
-		return new AbsoluteDomRect( this, newName );
-	}
-
-	moveTo( { top, left } ) {
-		this.top = top;
-		this.right = left + this.width;
-		this.bottom = top + this.height;
-		this.left = left;
-
-		return this;
-	}
-
-	getIntersectArea( rect ) {
-		const hOverlap = Math.max( 0, Math.min( this.right, rect.right ) - Math.max( this.left, rect.left ) );
-		const vOverlap = Math.max( 0, Math.min( this.bottom, rect.bottom ) - Math.max( this.top, rect.top ) );
-
-		return hOverlap * vOverlap;
-	}
-
-	paint() {
-		this.painter = document.createElement( 'div' );
-		this.painter.style.backgroundColor = 'rgba(255,0,0,.05)';
-		this.painter.style.color = '#000';
-		this.painter.style.position = 'absolute';
-		this.painter.style.display = 'flex';
-		this.painter.style.justifyContent = 'center';
-		this.painter.style.alignItems = 'center';
-		this.painter.style.left = this.left + 'px';
-		this.painter.style.top = this.top + 'px';
-		this.painter.style.width = this.width + 'px';
-		this.painter.style.height = this.height + 'px';
-		this.painter.style.outline = '1px solid rgba(255,0,0,.3)';
-		this.painter.innerHTML = this.name;
-
-		document.body.appendChild( this.painter );
-	}
-}

+ 0 - 28
packages/ckeditor5-link/src/ui/box.js

@@ -1,28 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import Controller from '../../ui/controller.js';
-
-/**
- * The box controller class.
- *
- * @memberOf ui.box
- * @extends ui.Controller
- */
-export default class Box extends Controller {
-	/**
-	 * Creates an instance of {@link ui.box.Box} class.
-	 *
-	 * @param {ui.box.BoxModel} model Model of this box.
-	 * @param {ui.View} view View of this box.
-	 */
-	constructor( model, view ) {
-		super( model, view );
-
-		view.model.bind( 'alignRight' ).to( model );
-
-		this.addCollection( 'content' );
-	}
-}

+ 0 - 38
packages/ckeditor5-link/src/ui/boxview.js

@@ -1,38 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import View from '../../ui/view.js';
-import Template from '../../ui/template.js';
-
-/**
- * The box view class.
- *
- * See {@link ui.box.Box}.
- *
- * @memberOf ui.box
- * @extends ui.View
- */
-export default class BoxView extends View {
-	/**
-	 * @inheritDoc
-	 */
-	constructor( locale ) {
-		super( locale );
-
-		const bind = this.bind;
-
-		this.template = new Template( {
-			tag: 'div',
-			attributes: {
-				class: [
-					'ck-box',
-					bind.if( 'alignRight', 'ck-box_align_right' )
-				]
-			}
-		} );
-
-		this.register( 'content', el => el );
-	}
-}

+ 0 - 26
packages/ckeditor5-link/src/ui/inputlabel.js

@@ -1,26 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import Controller from '../../ui/controller.js';
-
-/**
- * The input label controller class.
- *
- * @memberOf ui.input
- * @extends ui.Controller
- */
-export default class InputLabel extends Controller {
-	/**
-	 * Creates an instance of {@link ui.input.InputLabel} class.
-	 *
-	 * @param {ui.input.InputLabelModel} model Model of this label.
-	 * @param {ui.View} view View of this label.
-	 */
-	constructor( model, view ) {
-		super( model, view );
-
-		view.model.bind( 'for', 'text' ).to( model );
-	}
-}

+ 0 - 41
packages/ckeditor5-link/src/ui/inputlabelview.js

@@ -1,41 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import View from '../../ui/view.js';
-import Template from '../../ui/template.js';
-
-/**
- * The input label view class.
- *
- * See {@link ui.input.InputLabel}.
- *
- * @memberOf ui.input
- * @extends ui.View
- */
-export default class InputLabelView extends View {
-	/**
-	 * @inheritDoc
-	 */
-	constructor( locale ) {
-		super( locale );
-
-		const bind = this.bind;
-
-		this.template = new Template( {
-			tag: 'label',
-			attributes: {
-				class: [
-					'ck-input__label'
-				],
-				for: bind.to( 'for' )
-			},
-			children: [
-				{
-					text: bind.to( 'text' )
-				}
-			]
-		} );
-	}
-}

+ 0 - 32
packages/ckeditor5-link/src/ui/inputtext.js

@@ -1,32 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import uid from '../../utils/uid.js';
-import Controller from '../../ui/controller.js';
-
-/**
- * The text input controller class.
- *
- * @memberOf ui.input
- * @extends ui.Controller
- */
-export default class InputText extends Controller {
-	/**
-	 * Creates an instance of {@link ui.input.InputText} class.
-	 *
-	 * @param {ui.input.InputTextModel} model Model of this input.
-	 * @param {ui.View} view View of this input.
-	 */
-	constructor( model, view ) {
-		super( model, view );
-
-		view.model.bind( 'value' ).to( model );
-		view.model.set( 'uid', `ck-input-${ uid() }` );
-	}
-
-	get value() {
-		return this.view.element.value;
-	}
-}

+ 0 - 43
packages/ckeditor5-link/src/ui/inputtextview.js

@@ -1,43 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import View from '../../ui/view.js';
-import Template from '../../ui/template.js';
-
-/**
- * The text input view class.
- *
- * See {@link ui.input.InputText}.
- *
- * @memberOf ui.input
- * @extends ui.View
- */
-export default class InputTextView extends View {
-	/**
-	 * @inheritDoc
-	 */
-	constructor( locale ) {
-		super( locale );
-
-		const bind = this.bind;
-
-		this.template = new Template( {
-			tag: 'input',
-			attributes: {
-				type: 'text',
-				class: [
-					'ck-input',
-					'ck-input-text'
-				],
-				id: bind.to( 'uid' ),
-				value: bind.to( 'value' )
-			}
-		} );
-	}
-
-	focus() {
-		this.element.focus();
-	}
-}

+ 0 - 71
packages/ckeditor5-link/src/ui/labeledinput.js

@@ -1,71 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import Model from '../../ui/model.js';
-import Controller from '../../ui/controller.js';
-
-import InputText from './inputtext.js';
-import InputTextView from './inputtextview.js';
-
-import InputLabel from './inputlabel.js';
-import InputLabelView from './inputlabelview.js';
-
-/**
- * The labeled input controller class.
- *
- * @memberOf ui.input.labeled
- * @extends ui.Controller
- */
-export default class LabeledInput extends Controller {
-	/**
-	 * Creates an instance of {@link ui.input.labeled.LabeledInput} class.
-	 *
-	 * @param {ui.input.labeled.LabeledInputModel} model Model of this input.
-	 * @param {ui.View} view View of this input.
-	 */
-	constructor( model, view ) {
-		super( model, view );
-
-		const contentCollection = this.addCollection( 'content' );
-
-		/**
-		 * TODO
-		 *
-		 * @member {} todo
-		 */
-		this.input = this._createInput();
-
-		/**
-		 * TODO
-		 *
-		 * @member {} todo
-		 */
-		this.label = this._createLabel();
-
-		contentCollection.add( this.input );
-		contentCollection.add( this.label, 0 );
-	}
-
-	get value() {
-		return this.input.value;
-	}
-
-	_createInput() {
-		const model = new Model();
-
-		model.bind( 'value', 'label' ).to( this.model );
-
-		return new InputText( model, new InputTextView( this.locale ) );
-	}
-
-	_createLabel() {
-		const model = new Model();
-
-		model.bind( 'for' ).to( this.input.view.model, 'uid' );
-		model.bind( 'text' ).to( this.model, 'label' );
-
-		return new InputLabel( model, new InputLabelView( this.locale ) );
-	}
-}

+ 0 - 39
packages/ckeditor5-link/src/ui/labeledinputview.js

@@ -1,39 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import View from '../../ui/view.js';
-import Template from '../../ui/template.js';
-
-/**
- * The labeled input view class.
- *
- * See {@link ui.input.labeled.LabeledInput}.
- *
- * @memberOf ui.input.labeled
- * @extends ui.View
- */
-export default class LabeledInputView extends View {
-	/**
-	 * @inheritDoc
-	 */
-	constructor( locale ) {
-		super( locale );
-
-		this.template = new Template( {
-			tag: 'div',
-			attributes: {
-				class: [
-					'ck-labeled-input',
-				]
-			}
-		} );
-
-		this.register( 'content', el => el );
-	}
-
-	focus() {
-		this.regions.get( 'content' ).views.get( 1 ).element.focus();
-	}
-}

+ 26 - 11
packages/ckeditor5-link/src/ui/linkballoonpanel.js

@@ -6,11 +6,13 @@
 import Model from '../../ui/model.js';
 import Button from '../../ui/button/button.js';
 import ButtonView from '../../ui/button/buttonview.js';
-import BalloonPanel from './balloonpanel.js';
-import LabeledInput from './labeledinput.js';
-import LabeledInputView from './labeledinputview.js';
-import Box from './box.js';
-import BoxView from './boxview.js';
+import BalloonPanel from '../../ui/balloonpanel/balloonpanel.js';
+import LabeledInput from '../../ui/labeledinput/labeledinput.js';
+import LabeledInputView from '../../ui/labeledinput/labeledinputview.js';
+import Form from '../../ui/form/form.js';
+import FormView from '../../ui/form/formview.js';
+import Box from '../../ui/box/box.js';
+import BoxView from '../../ui/box/boxview.js';
 
 /**
  * The link balloon panel controller class.
@@ -28,10 +30,24 @@ export default class LinkBalloonPanel extends BalloonPanel {
 	constructor( model, view ) {
 		super( model, view );
 
-		const contentCollection = this.collections.get( 'content' );
+		this.add( 'content', this._createForm() );
+	}
+
+	_createForm() {
+		const formModel = new Model();
+
+		formModel.delegate( 'execute' ).to( this.model );
 
-		contentCollection.add( this._createLabeledInput() );
-		contentCollection.add( this._createButtons() );
+		/**
+		 * TODO
+		 *
+		 * @member {} todo
+		 */
+		this.form = new Form( formModel, new FormView( this.locale ) );
+		this.form.add( 'content', this._createLabeledInput() );
+		this.form.add( 'content', this._createButtons() );
+
+		return this.form;
 	}
 
 	_createLabeledInput() {
@@ -83,11 +99,10 @@ export default class LinkBalloonPanel extends BalloonPanel {
 			isEnabled: true,
 			isOn: false,
 			label: t( 'Save' ),
-			withText: true
+			withText: true,
+			type: 'submit'
 		} );
 
-		saveModel.delegate( 'execute' ).to( this.model );
-
 		const button = new Button( saveModel, new ButtonView( this.locale ) );
 
 		button.view.element.classList.add( 'ck-button-action' );

+ 1 - 1
packages/ckeditor5-link/src/ui/linkballoonpanelview.js

@@ -4,7 +4,7 @@
  */
 
 import Template from '../../ui/template.js';
-import BalloonPanelView from './balloonpanelview.js';
+import BalloonPanelView from '../../ui/balloonpanel/balloonpanelview.js';
 
 /**
  * The link balloon panel view class.

+ 0 - 135
packages/ckeditor5-link/theme/components/balloonpanel.scss

@@ -1,135 +0,0 @@
-// Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-$ck-balloon-arrow-offset: 2px;
-$ck-balloon-arrow-height: 15px;
-$ck-balloon-arrow-half-width: 10px;
-
-$ck-def-z: (
-	'default': 1,
-	'modal': 999
-	// 'topmost': 9999
-);
-
-// TODO: Move it to _zindex.scss.
-@function ck-z( $layer: 'default', $index: 0 ) {
-	@return map-get( $ck-def-z, $layer ) + $index;
-}
-
-.ck-balloon-panel {
-	@include ck-unselectable();
-	@include ck-rounded-corners();
-
-	min-width: 50px;
-	min-height: 15px;
-
-	display: none;
-	position: absolute;
-	background: ck-color( 'background' );
-	border: 1px solid ck-border-color( 'foreground' );
-
-	// TODO: Create helper for shadows.
-	box-shadow: 0 0 5px ck-color( 'foreground', -15 );
-	z-index: ck-z( 'modal' );
-
-	&:before,
-	&:after {
-		content: "";
-		position: absolute;
-
-		width: 0;
-		height: 0;
-		border-style: solid;
-	}
-
-	&:before {
-		z-index: ck-z( 'default' );
-	}
-
-	&:after {
-		z-index: ck-z( 'default', 1 );
-	}
-
-	&_arrow_se,
-	&_arrow_sw {
-		&:before,
-		&:after {
-			border-width: 0 $ck-balloon-arrow-half-width $ck-balloon-arrow-height $ck-balloon-arrow-half-width;
-		}
-
-		&:before {
-			z-index: ck-z( 'default' );
-			border-color: transparent transparent ck-border-color( 'foreground' ) transparent;
-		}
-
-		&:after {
-			z-index: ck-z( 'default', 1 );
-			border-color: transparent transparent ck-color( 'background' ) transparent;
-			margin-top: $ck-balloon-arrow-offset;
-		}
-	}
-
-	&_arrow_ne,
-	&_arrow_nw {
-		&:before,
-		&:after {
-			border-width: $ck-balloon-arrow-height $ck-balloon-arrow-half-width 0 $ck-balloon-arrow-half-width;
-		}
-
-		&:before {
-			z-index: ck-z( 'default' );
-			border-color: ck-border-color( 'foreground' ) transparent  transparent;
-		}
-
-		&:after {
-			z-index: ck-z( 'default', 1 );
-			border-color: ck-color( 'background' ) transparent transparent transparent;
-			margin-bottom: $ck-balloon-arrow-offset;
-		}
-	}
-
-	&_arrow_se {
-		&:before,
-		&:after {
-			left: 2 * $ck-balloon-arrow-half-width;
-			top: -$ck-balloon-arrow-height;
-		}
-	}
-
-	&_arrow_sw {
-		&:before,
-		&:after {
-			right: 2 * $ck-balloon-arrow-half-width;
-			top: -$ck-balloon-arrow-height;
-		}
-	}
-
-	&_arrow_ne {
-		&:before,
-		&:after {
-			left: 2 * $ck-balloon-arrow-half-width;
-			bottom: -$ck-balloon-arrow-height;
-		}
-	}
-
-	&_arrow_nw {
-		&:before,
-		&:after {
-			right: 2 * $ck-balloon-arrow-half-width;
-			bottom: -$ck-balloon-arrow-height;
-		}
-	}
-
-	&_visible {
-		display: block;
-	}
-
-	// Box to accommodate buttons.
-	.ck-box {
-		margin-top: ck-spacing( 'medium' );
-
-		* + * {
-			margin-left: ck-spacing( 'medium' );
-		}
-	}
-}

+ 0 - 8
packages/ckeditor5-link/theme/components/box.scss

@@ -1,8 +0,0 @@
-// Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-.ck-box {
-	&_align_right {
-		text-align: right;
-	}
-}

+ 0 - 7
packages/ckeditor5-link/theme/components/inputlabel.scss

@@ -1,7 +0,0 @@
-// Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-.ck-input__label {
-	display: block;
-	font-weight: bold;
-}

+ 0 - 12
packages/ckeditor5-link/theme/components/inputtext.scss

@@ -1,12 +0,0 @@
-// Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-.ck-input {
-	&-text {
-		@include ck-rounded-corners();
-
-		border: 1px solid ck-border-color( 'foreground' );
-		padding: ck-spacing( 'small' );
-		min-width: 200px;
-	}
-}

+ 0 - 5
packages/ckeditor5-link/theme/components/labeledinput.scss

@@ -1,5 +0,0 @@
-// Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
-// For licensing, see LICENSE.md or http://ckeditor.com/license
-
-.ck-labeled-input {
-}

+ 0 - 5
packages/ckeditor5-link/theme/theme.scss

@@ -1,9 +1,4 @@
 // Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
 // For licensing, see LICENSE.md or http://ckeditor.com/license
 
-@import 'components/box';
-@import 'components/inputtext';
-@import 'components/inputlabel';
-@import 'components/labeledinput';
-@import 'components/balloonpanel';
 @import 'components/linkballoonpanel';