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

Commit 1eb4b4c

Browse files
authored
Merge pull request #227 from ckeditor/i/6049
Feature: Introduced the table cell properties UI. Closes ckeditor/ckeditor5#6049.
2 parents d905bef + 10ab4ae commit 1eb4b4c

16 files changed

+2439
-6
lines changed

lang/contexts.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,33 @@
1717
"Split cell vertically": "Label for the split table cell vertically button.",
1818
"Split cell horizontally": "Label for the split table cell horizontally button.",
1919
"Merge cells": "Label for the merge table cells button.",
20-
"Table toolbar": "The label used by assistive technologies describing a table toolbar attached to a table widget."
20+
"Table toolbar": "The label used by assistive technologies describing a table toolbar attached to a table widget.",
21+
"Cell properties": "The label describing the form allowing to specify the properties of a selected table cell.",
22+
"Border": "The label describing a group of border–related form fields (border style, color, etc.).",
23+
"Style": "The label for the dropdown that allows configuring the border style of a table or a table cell.",
24+
"Width": "The label for the input that allows configuring the border width of a table or a table cell.",
25+
"Color": "The label for the input that allows configuring the border color of a table or a table cell.",
26+
"Background": "The label for the input that allows configuring the background color of a table or a table cell.",
27+
"Padding": "The label for the input that allows configuring the padding of a table cell.",
28+
"Text alignment": "The label for the group of toolbars that allows configuring the text alignment in a table cell.",
29+
"Horizontal text alignment toolbar": "The label used by assistive technologies describing a toolbar that allows configuring the horizontal text alignment in a table cell.",
30+
"Vertical text alignment toolbar": "The label used by assistive technologies describing a toolbar that allows configuring the vertical text alignment in a table cell.",
31+
"Save": "The label for the button that saves the changes made to the table or table cell properties.",
32+
"Cancel": "The label for the button that rejects the changes made to the table or table cell properties.",
33+
"None": "The label for the border style dropdown when no style is applied to a table or a table cell.",
34+
"Solid": "The label for the border style dropdown when the solid border is applied to a table or a table cell.",
35+
"Dotted": "The label for the border style dropdown when the dotted border is applied to a table or a table cell.",
36+
"Dashed": "The label for the border style dropdown when the dashed border is applied to a table or a table cell.",
37+
"Double": "The label for the border style dropdown when the double border is applied to a table or a table cell.",
38+
"Groove": "The label for the border style dropdown when the groove border is applied to a table or a table cell.",
39+
"Ridge": "The label for the border style dropdown when the ridge border is applied to a table or a table cell.",
40+
"Inset": "The label for the border style dropdown when the inset border is applied to a table or a table cell.",
41+
"Outset": "The label for the border style dropdown when the outset border is applied to a table or a table cell.",
42+
"Align cell text to the left": "The label used by assistive technologies describing a button that aligns the table cell text to the left.",
43+
"Align cell text to the center": "The label used by assistive technologies describing a button that aligns the table cell text to the center.",
44+
"Align cell text to the right": "The label used by assistive technologies describing a button that aligns the table cell text to the right.",
45+
"Justify cell text": "The label used by assistive technologies describing a button that justifies the table cell text.",
46+
"Align cell text to the top": "The label used by assistive technologies describing a button that aligns the table cell text to the top.",
47+
"Align cell text to the middle": "The label used by assistive technologies describing a button that aligns the table cell text to the middle.",
48+
"Align cell text to the bottom": "The label used by assistive technologies describing a button that aligns the table cell text to the bottom."
2149
}

src/tablecellproperties.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
*/
99

1010
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
11-
11+
import TableCellPropertiesUI from './tablecellproperties/tablecellpropertiesui';
1212
import TableCellPropertiesEditing from './tablecellproperties/tablecellpropertiesediting';
1313

1414
/**
1515
* The table cell properties feature.
1616
*
1717
* This is a "glue" plugin which loads the
18-
* {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing table properties editing feature} and
19-
* table cell properties UI feature.
18+
* {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing table cell properties editing feature} and
19+
* the {@link module:table/tablecellproperties/tablecellpropertiesui~TableCellPropertiesUI table cell properties UI feature}.
2020
*
2121
* @extends module:core/plugin~Plugin
2222
*/
@@ -32,6 +32,6 @@ export default class TableCellProperties extends Plugin {
3232
* @inheritDoc
3333
*/
3434
static get requires() {
35-
return [ TableCellPropertiesEditing ];
35+
return [ TableCellPropertiesEditing, TableCellPropertiesUI ];
3636
}
3737
}
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/**
2+
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4+
*/
5+
6+
/**
7+
* @module table/tablecellproperties/tablecellpropertiesui
8+
*/
9+
10+
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
11+
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
12+
import { getTableWidgetAncestor } from '../utils';
13+
import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler';
14+
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
15+
import TableCellPropertiesView from './ui/tablecellpropertiesview';
16+
import tableCellProperties from './../../theme/icons/table-cell-properties.svg';
17+
import { repositionContextualBalloon, getBalloonCellPositionData } from '../ui/utils';
18+
19+
const DEFAULT_BORDER_STYLE = 'none';
20+
const DEFAULT_HORIZONTAL_ALIGNMENT = 'left';
21+
const DEFAULT_VERTICAL_ALIGNMENT = 'middle';
22+
23+
/**
24+
* The table cell properties UI plugin. It introduces the `'tableCellProperties'` button
25+
* that opens a form allowing to specify visual styling of a table cell.
26+
*
27+
* It uses the
28+
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
29+
*
30+
* @extends module:core/plugin~Plugin
31+
*/
32+
export default class TableCellPropertiesUI extends Plugin {
33+
/**
34+
* @inheritDoc
35+
*/
36+
static get requires() {
37+
return [ ContextualBalloon ];
38+
}
39+
40+
/**
41+
* @inheritDoc
42+
*/
43+
static get pluginName() {
44+
return 'TableCellPropertiesUI';
45+
}
46+
47+
/**
48+
* @inheritDoc
49+
*/
50+
init() {
51+
const editor = this.editor;
52+
const t = editor.t;
53+
54+
/**
55+
* The contextual balloon plugin instance.
56+
*
57+
* @private
58+
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
59+
*/
60+
this._balloon = editor.plugins.get( ContextualBalloon );
61+
62+
/**
63+
* The cell properties form view displayed inside the balloon.
64+
*
65+
* @member {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
66+
*/
67+
this.view = this._createPropertiesView();
68+
69+
/**
70+
* The batch used to undo all changes made by the form (which are live, as the user types)
71+
* when "Cancel" was pressed. Each time the view is shown, a new batch is created.
72+
*
73+
* @protected
74+
* @member {module:engine/model/batch~Batch}
75+
*/
76+
this._undoStepBatch = null;
77+
78+
editor.ui.componentFactory.add( 'tableCellProperties', locale => {
79+
const view = new ButtonView( locale );
80+
81+
view.set( {
82+
label: t( 'Cell properties' ),
83+
icon: tableCellProperties,
84+
tooltip: true
85+
} );
86+
87+
this.listenTo( view, 'execute', () => this._showView() );
88+
89+
return view;
90+
} );
91+
}
92+
93+
/**
94+
* @inheritDoc
95+
*/
96+
destroy() {
97+
super.destroy();
98+
99+
// Destroy created UI components as they are not automatically destroyed.
100+
// See https://github.com/ckeditor/ckeditor5/issues/1341.
101+
this.view.destroy();
102+
}
103+
104+
/**
105+
* Creates the {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} instance.
106+
*
107+
* @private
108+
* @returns {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} The cell
109+
* properties form view instance.
110+
*/
111+
_createPropertiesView() {
112+
const editor = this.editor;
113+
const viewDocument = editor.editing.view.document;
114+
const view = new TableCellPropertiesView( editor.locale );
115+
116+
// Render the view so its #element is available for the clickOutsideHandler.
117+
view.render();
118+
119+
this.listenTo( view, 'submit', () => {
120+
this._hideView();
121+
} );
122+
123+
this.listenTo( view, 'cancel', () => {
124+
editor.execute( 'undo', this._undoStepBatch );
125+
this._hideView();
126+
} );
127+
128+
// Close the balloon on Esc key press.
129+
view.keystrokes.set( 'Esc', ( data, cancel ) => {
130+
this._hideView();
131+
cancel();
132+
} );
133+
134+
// Reposition the balloon or hide the form if a table cell is no longer selected.
135+
this.listenTo( editor.ui, 'update', () => {
136+
if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
137+
this._hideView();
138+
} else if ( this._isViewVisible ) {
139+
repositionContextualBalloon( editor );
140+
}
141+
} );
142+
143+
// Close on click outside of balloon panel element.
144+
clickOutsideHandler( {
145+
emitter: view,
146+
activator: () => this._isViewInBalloon,
147+
contextElements: [ this._balloon.view.element ],
148+
callback: () => this._hideView()
149+
} );
150+
151+
// Create the "UI -> editor data" binding.
152+
// These listeners update the editor data (via table commands) when any observable
153+
// property of the view has changed. This makes the view live, which means the changes are
154+
// visible in the editing as soon as the user types or changes fields' values.
155+
view.on( 'change:borderStyle', this._getPropertyChangeCallback( 'tableCellBorderStyle' ) );
156+
view.on( 'change:borderColor', this._getPropertyChangeCallback( 'tableCellBorderColor' ) );
157+
view.on( 'change:borderWidth', this._getPropertyChangeCallback( 'tableCellBorderWidth' ) );
158+
view.on( 'change:padding', this._getPropertyChangeCallback( 'tableCellPadding' ) );
159+
view.on( 'change:backgroundColor', this._getPropertyChangeCallback( 'tableCellBackgroundColor' ) );
160+
view.on( 'change:horizontalAlignment', this._getPropertyChangeCallback( 'tableCellHorizontalAlignment' ) );
161+
view.on( 'change:verticalAlignment', this._getPropertyChangeCallback( 'tableCellVerticalAlignment' ) );
162+
163+
return view;
164+
}
165+
166+
/**
167+
* In this method the "editor data -> UI" binding is happening.
168+
*
169+
* When executed, this method obtains selected cell property values from various table commands
170+
* and passes them to the {@link #view}.
171+
*
172+
* This way, the UI stays up–to–date with the editor data.
173+
*
174+
* @private
175+
*/
176+
_fillViewFormFromCommandValues() {
177+
const commands = this.editor.commands;
178+
179+
this.view.set( {
180+
borderStyle: commands.get( 'tableCellBorderStyle' ).value || DEFAULT_BORDER_STYLE,
181+
borderColor: commands.get( 'tableCellBorderColor' ).value || '',
182+
borderWidth: commands.get( 'tableCellBorderWidth' ).value || '',
183+
padding: commands.get( 'tableCellPadding' ).value || '',
184+
backgroundColor: commands.get( 'tableCellBackgroundColor' ).value || '',
185+
horizontalAlignment: commands.get( 'tableCellHorizontalAlignment' ).value || DEFAULT_HORIZONTAL_ALIGNMENT,
186+
verticalAlignment: commands.get( 'tableCellVerticalAlignment' ).value || DEFAULT_VERTICAL_ALIGNMENT,
187+
} );
188+
}
189+
190+
/**
191+
* Shows the {@link #view} in the {@link #_balloon}.
192+
*
193+
* **Note**: Each time a view is shown, the new {@link #_undoStepBatch} is created that contains
194+
* all changes made to the document when the view is visible, allowing a single undo step
195+
* for all of them.
196+
*
197+
* @protected
198+
*/
199+
_showView() {
200+
const editor = this.editor;
201+
202+
this._balloon.add( {
203+
view: this.view,
204+
position: getBalloonCellPositionData( editor )
205+
} );
206+
207+
// Create a new batch. Clicking "Cancel" will undo this batch.
208+
this._undoStepBatch = editor.model.createBatch();
209+
210+
// Update the view with the model values.
211+
this._fillViewFormFromCommandValues();
212+
213+
// Basic a11y.
214+
this.view.focus();
215+
}
216+
217+
/**
218+
* Removes the {@link #view} from the {@link #_balloon}.
219+
*
220+
* @protected
221+
*/
222+
_hideView() {
223+
if ( !this._isViewInBalloon ) {
224+
return;
225+
}
226+
227+
const editor = this.editor;
228+
229+
this.stopListening( editor.ui, 'update' );
230+
231+
// Blur any input element before removing it from DOM to prevent issues in some browsers.
232+
// See https://github.com/ckeditor/ckeditor5/issues/1501.
233+
this.view.saveButtonView.focus();
234+
235+
this._balloon.remove( this.view );
236+
237+
// Make sure the focus is not lost in the process by putting it directly
238+
// into the editing view.
239+
this.editor.editing.view.focus();
240+
}
241+
242+
/**
243+
* Returns `true` when the {@link #view} is the visible in the {@link #_balloon}.
244+
*
245+
* @private
246+
* @type {Boolean}
247+
*/
248+
get _isViewVisible() {
249+
return this._balloon.visibleView === this.view;
250+
}
251+
252+
/**
253+
* Returns `true` when the {@link #view} is in the {@link #_balloon}.
254+
*
255+
* @private
256+
* @type {Boolean}
257+
*/
258+
get _isViewInBalloon() {
259+
return this._balloon.hasView( this.view );
260+
}
261+
262+
/**
263+
* Creates a callback that when executed upon {@link #view view's} property change
264+
* executes a related editor command with the new property value.
265+
*
266+
* @private
267+
* @param {String} commandName
268+
* @returns {Function}
269+
*/
270+
_getPropertyChangeCallback( commandName ) {
271+
return ( evt, propertyName, newValue ) => {
272+
this.editor.execute( commandName, {
273+
value: newValue,
274+
batch: this._undoStepBatch
275+
} );
276+
};
277+
}
278+
}

0 commit comments

Comments
 (0)