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

Commit

Permalink
Merge pull request #227 from ckeditor/i/6049
Browse files Browse the repository at this point in the history
Feature: Introduced the table cell properties UI. Closes ckeditor/ckeditor5#6049.
  • Loading branch information
jodator authored Jan 28, 2020
2 parents d905bef + 10ab4ae commit 1eb4b4c
Show file tree
Hide file tree
Showing 16 changed files with 2,439 additions and 6 deletions.
30 changes: 29 additions & 1 deletion lang/contexts.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,33 @@
"Split cell vertically": "Label for the split table cell vertically button.",
"Split cell horizontally": "Label for the split table cell horizontally button.",
"Merge cells": "Label for the merge table cells button.",
"Table toolbar": "The label used by assistive technologies describing a table toolbar attached to a table widget."
"Table toolbar": "The label used by assistive technologies describing a table toolbar attached to a table widget.",
"Cell properties": "The label describing the form allowing to specify the properties of a selected table cell.",
"Border": "The label describing a group of border–related form fields (border style, color, etc.).",
"Style": "The label for the dropdown that allows configuring the border style of a table or a table cell.",
"Width": "The label for the input that allows configuring the border width of a table or a table cell.",
"Color": "The label for the input that allows configuring the border color of a table or a table cell.",
"Background": "The label for the input that allows configuring the background color of a table or a table cell.",
"Padding": "The label for the input that allows configuring the padding of a table cell.",
"Text alignment": "The label for the group of toolbars that allows configuring the text alignment in a table cell.",
"Horizontal text alignment toolbar": "The label used by assistive technologies describing a toolbar that allows configuring the horizontal text alignment in a table cell.",
"Vertical text alignment toolbar": "The label used by assistive technologies describing a toolbar that allows configuring the vertical text alignment in a table cell.",
"Save": "The label for the button that saves the changes made to the table or table cell properties.",
"Cancel": "The label for the button that rejects the changes made to the table or table cell properties.",
"None": "The label for the border style dropdown when no style is applied to a table or a table cell.",
"Solid": "The label for the border style dropdown when the solid border is applied to a table or a table cell.",
"Dotted": "The label for the border style dropdown when the dotted border is applied to a table or a table cell.",
"Dashed": "The label for the border style dropdown when the dashed border is applied to a table or a table cell.",
"Double": "The label for the border style dropdown when the double border is applied to a table or a table cell.",
"Groove": "The label for the border style dropdown when the groove border is applied to a table or a table cell.",
"Ridge": "The label for the border style dropdown when the ridge border is applied to a table or a table cell.",
"Inset": "The label for the border style dropdown when the inset border is applied to a table or a table cell.",
"Outset": "The label for the border style dropdown when the outset border is applied to a table or a table cell.",
"Align cell text to the left": "The label used by assistive technologies describing a button that aligns the table cell text to the left.",
"Align cell text to the center": "The label used by assistive technologies describing a button that aligns the table cell text to the center.",
"Align cell text to the right": "The label used by assistive technologies describing a button that aligns the table cell text to the right.",
"Justify cell text": "The label used by assistive technologies describing a button that justifies the table cell text.",
"Align cell text to the top": "The label used by assistive technologies describing a button that aligns the table cell text to the top.",
"Align cell text to the middle": "The label used by assistive technologies describing a button that aligns the table cell text to the middle.",
"Align cell text to the bottom": "The label used by assistive technologies describing a button that aligns the table cell text to the bottom."
}
8 changes: 4 additions & 4 deletions src/tablecellproperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

import TableCellPropertiesUI from './tablecellproperties/tablecellpropertiesui';
import TableCellPropertiesEditing from './tablecellproperties/tablecellpropertiesediting';

/**
* The table cell properties feature.
*
* This is a "glue" plugin which loads the
* {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing table properties editing feature} and
* table cell properties UI feature.
* {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing table cell properties editing feature} and
* the {@link module:table/tablecellproperties/tablecellpropertiesui~TableCellPropertiesUI table cell properties UI feature}.
*
* @extends module:core/plugin~Plugin
*/
Expand All @@ -32,6 +32,6 @@ export default class TableCellProperties extends Plugin {
* @inheritDoc
*/
static get requires() {
return [ TableCellPropertiesEditing ];
return [ TableCellPropertiesEditing, TableCellPropertiesUI ];
}
}
278 changes: 278 additions & 0 deletions src/tablecellproperties/tablecellpropertiesui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module table/tablecellproperties/tablecellpropertiesui
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
import { getTableWidgetAncestor } from '../utils';
import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler';
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
import TableCellPropertiesView from './ui/tablecellpropertiesview';
import tableCellProperties from './../../theme/icons/table-cell-properties.svg';
import { repositionContextualBalloon, getBalloonCellPositionData } from '../ui/utils';

const DEFAULT_BORDER_STYLE = 'none';
const DEFAULT_HORIZONTAL_ALIGNMENT = 'left';
const DEFAULT_VERTICAL_ALIGNMENT = 'middle';

/**
* The table cell properties UI plugin. It introduces the `'tableCellProperties'` button
* that opens a form allowing to specify visual styling of a table cell.
*
* It uses the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
*
* @extends module:core/plugin~Plugin
*/
export default class TableCellPropertiesUI extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ContextualBalloon ];
}

/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCellPropertiesUI';
}

/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;

/**
* The contextual balloon plugin instance.
*
* @private
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
*/
this._balloon = editor.plugins.get( ContextualBalloon );

/**
* The cell properties form view displayed inside the balloon.
*
* @member {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
*/
this.view = this._createPropertiesView();

/**
* The batch used to undo all changes made by the form (which are live, as the user types)
* when "Cancel" was pressed. Each time the view is shown, a new batch is created.
*
* @protected
* @member {module:engine/model/batch~Batch}
*/
this._undoStepBatch = null;

editor.ui.componentFactory.add( 'tableCellProperties', locale => {
const view = new ButtonView( locale );

view.set( {
label: t( 'Cell properties' ),
icon: tableCellProperties,
tooltip: true
} );

this.listenTo( view, 'execute', () => this._showView() );

return view;
} );
}

/**
* @inheritDoc
*/
destroy() {
super.destroy();

// Destroy created UI components as they are not automatically destroyed.
// See https://github.com/ckeditor/ckeditor5/issues/1341.
this.view.destroy();
}

/**
* Creates the {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} instance.
*
* @private
* @returns {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} The cell
* properties form view instance.
*/
_createPropertiesView() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
const view = new TableCellPropertiesView( editor.locale );

// Render the view so its #element is available for the clickOutsideHandler.
view.render();

this.listenTo( view, 'submit', () => {
this._hideView();
} );

this.listenTo( view, 'cancel', () => {
editor.execute( 'undo', this._undoStepBatch );
this._hideView();
} );

// Close the balloon on Esc key press.
view.keystrokes.set( 'Esc', ( data, cancel ) => {
this._hideView();
cancel();
} );

// Reposition the balloon or hide the form if a table cell is no longer selected.
this.listenTo( editor.ui, 'update', () => {
if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
this._hideView();
} else if ( this._isViewVisible ) {
repositionContextualBalloon( editor );
}
} );

// Close on click outside of balloon panel element.
clickOutsideHandler( {
emitter: view,
activator: () => this._isViewInBalloon,
contextElements: [ this._balloon.view.element ],
callback: () => this._hideView()
} );

// Create the "UI -> editor data" binding.
// These listeners update the editor data (via table commands) when any observable
// property of the view has changed. This makes the view live, which means the changes are
// visible in the editing as soon as the user types or changes fields' values.
view.on( 'change:borderStyle', this._getPropertyChangeCallback( 'tableCellBorderStyle' ) );
view.on( 'change:borderColor', this._getPropertyChangeCallback( 'tableCellBorderColor' ) );
view.on( 'change:borderWidth', this._getPropertyChangeCallback( 'tableCellBorderWidth' ) );
view.on( 'change:padding', this._getPropertyChangeCallback( 'tableCellPadding' ) );
view.on( 'change:backgroundColor', this._getPropertyChangeCallback( 'tableCellBackgroundColor' ) );
view.on( 'change:horizontalAlignment', this._getPropertyChangeCallback( 'tableCellHorizontalAlignment' ) );
view.on( 'change:verticalAlignment', this._getPropertyChangeCallback( 'tableCellVerticalAlignment' ) );

return view;
}

/**
* In this method the "editor data -> UI" binding is happening.
*
* When executed, this method obtains selected cell property values from various table commands
* and passes them to the {@link #view}.
*
* This way, the UI stays up–to–date with the editor data.
*
* @private
*/
_fillViewFormFromCommandValues() {
const commands = this.editor.commands;

this.view.set( {
borderStyle: commands.get( 'tableCellBorderStyle' ).value || DEFAULT_BORDER_STYLE,
borderColor: commands.get( 'tableCellBorderColor' ).value || '',
borderWidth: commands.get( 'tableCellBorderWidth' ).value || '',
padding: commands.get( 'tableCellPadding' ).value || '',
backgroundColor: commands.get( 'tableCellBackgroundColor' ).value || '',
horizontalAlignment: commands.get( 'tableCellHorizontalAlignment' ).value || DEFAULT_HORIZONTAL_ALIGNMENT,
verticalAlignment: commands.get( 'tableCellVerticalAlignment' ).value || DEFAULT_VERTICAL_ALIGNMENT,
} );
}

/**
* Shows the {@link #view} in the {@link #_balloon}.
*
* **Note**: Each time a view is shown, the new {@link #_undoStepBatch} is created that contains
* all changes made to the document when the view is visible, allowing a single undo step
* for all of them.
*
* @protected
*/
_showView() {
const editor = this.editor;

this._balloon.add( {
view: this.view,
position: getBalloonCellPositionData( editor )
} );

// Create a new batch. Clicking "Cancel" will undo this batch.
this._undoStepBatch = editor.model.createBatch();

// Update the view with the model values.
this._fillViewFormFromCommandValues();

// Basic a11y.
this.view.focus();
}

/**
* Removes the {@link #view} from the {@link #_balloon}.
*
* @protected
*/
_hideView() {
if ( !this._isViewInBalloon ) {
return;
}

const editor = this.editor;

this.stopListening( editor.ui, 'update' );

// Blur any input element before removing it from DOM to prevent issues in some browsers.
// See https://github.com/ckeditor/ckeditor5/issues/1501.
this.view.saveButtonView.focus();

this._balloon.remove( this.view );

// Make sure the focus is not lost in the process by putting it directly
// into the editing view.
this.editor.editing.view.focus();
}

/**
* Returns `true` when the {@link #view} is the visible in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewVisible() {
return this._balloon.visibleView === this.view;
}

/**
* Returns `true` when the {@link #view} is in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewInBalloon() {
return this._balloon.hasView( this.view );
}

/**
* Creates a callback that when executed upon {@link #view view's} property change
* executes a related editor command with the new property value.
*
* @private
* @param {String} commandName
* @returns {Function}
*/
_getPropertyChangeCallback( commandName ) {
return ( evt, propertyName, newValue ) => {
this.editor.execute( commandName, {
value: newValue,
batch: this._undoStepBatch
} );
};
}
}
Loading

0 comments on commit 1eb4b4c

Please sign in to comment.