@ckeditor/ckeditor5-table 35.0.1 → 35.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,8 +7,9 @@
7
7
  * @module table/ui/colorinputview
8
8
  */
9
9
 
10
- import { View, InputTextView, ButtonView, createDropdown, ColorGridView } from 'ckeditor5/src/ui';
10
+ import { View, InputTextView, ButtonView, createDropdown, ColorGridView, FocusCycler, ViewCollection } from 'ckeditor5/src/ui';
11
11
  import { icons } from 'ckeditor5/src/core';
12
+ import { FocusTracker, KeystrokeHandler } from 'ckeditor5/src/utils';
12
13
 
13
14
  import '../../theme/colorinput.css';
14
15
 
@@ -34,8 +35,6 @@ export default class ColorInputView extends View {
34
35
  constructor( locale, options ) {
35
36
  super( locale );
36
37
 
37
- const bind = this.bindTemplate;
38
-
39
38
  /**
40
39
  * The value of the input.
41
40
  *
@@ -45,14 +44,6 @@ export default class ColorInputView extends View {
45
44
  */
46
45
  this.set( 'value', '' );
47
46
 
48
- /**
49
- * The `id` attribute of the input (i.e. to pair with the `<label>` element).
50
- *
51
- * @observable
52
- * @member {String} #id
53
- */
54
- this.set( 'id' );
55
-
56
47
  /**
57
48
  * Controls whether the input view is in read-only mode.
58
49
  *
@@ -62,16 +53,6 @@ export default class ColorInputView extends View {
62
53
  */
63
54
  this.set( 'isReadOnly', false );
64
55
 
65
- /**
66
- * Set to `true` when the field has some error. Usually controlled via
67
- * {@link module:ui/labeledinput/labeledinputview~LabeledInputView#errorText}.
68
- *
69
- * @observable
70
- * @member {Boolean} #hasError
71
- * @default false
72
- */
73
- this.set( 'hasError', false );
74
-
75
56
  /**
76
57
  * An observable flag set to `true` when the input is focused by the user.
77
58
  * `false` otherwise.
@@ -94,40 +75,54 @@ export default class ColorInputView extends View {
94
75
  this.set( 'isEmpty', true );
95
76
 
96
77
  /**
97
- * The `id` of the element describing this field. When the field has
98
- * some error, it helps screen readers read the error text.
78
+ * A cached reference to the options passed to the constructor.
79
+ *
80
+ * @member {Object}
81
+ */
82
+ this.options = options;
83
+
84
+ /**
85
+ * Tracks information about the DOM focus in the view.
99
86
  *
100
- * @observable
101
- * @member {String} #ariaDescribedById
87
+ * @readonly
88
+ * @member {module:utils/focustracker~FocusTracker}
102
89
  */
103
- this.set( 'ariaDescribedById' );
90
+ this.focusTracker = new FocusTracker();
104
91
 
105
92
  /**
106
- * A cached reference to the options passed to the constructor.
93
+ * A collection of views that can be focused in the view.
107
94
  *
108
- * @member {Object}
95
+ * @readonly
96
+ * @protected
97
+ * @member {module:ui/viewcollection~ViewCollection}
109
98
  */
110
- this.options = options;
99
+ this._focusables = new ViewCollection();
111
100
 
112
101
  /**
113
102
  * An instance of the dropdown allowing to select a color from a grid.
114
103
  *
115
- * @protected
116
104
  * @member {module:ui/dropdown/dropdown~DropdownView}
117
105
  */
118
- this._dropdownView = this._createDropdownView();
106
+ this.dropdownView = this._createDropdownView();
119
107
 
120
108
  /**
121
109
  * An instance of the input allowing the user to type a color value.
122
110
  *
123
- * @protected
124
111
  * @member {module:ui/inputtext/inputtextview~InputTextView}
125
112
  */
126
- this._inputView = this._createInputTextView();
113
+ this.inputView = this._createInputTextView();
114
+
115
+ /**
116
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
117
+ *
118
+ * @readonly
119
+ * @member {module:utils/keystrokehandler~KeystrokeHandler}
120
+ */
121
+ this.keystrokes = new KeystrokeHandler();
127
122
 
128
123
  /**
129
124
  * The flag that indicates whether the user is still typing.
130
- * If set to true, it means that the text input field ({@link #_inputView}) still has the focus.
125
+ * If set to true, it means that the text input field ({@link #inputView}) still has the focus.
131
126
  * So, we should interrupt the user by replacing the input's value.
132
127
  *
133
128
  * @protected
@@ -135,36 +130,72 @@ export default class ColorInputView extends View {
135
130
  */
136
131
  this._stillTyping = false;
137
132
 
133
+ /**
134
+ * Helps cycling over focusable items in the view.
135
+ *
136
+ * @readonly
137
+ * @protected
138
+ * @member {module:ui/focuscycler~FocusCycler}
139
+ */
140
+ this._focusCycler = new FocusCycler( {
141
+ focusables: this._focusables,
142
+ focusTracker: this.focusTracker,
143
+ keystrokeHandler: this.keystrokes,
144
+ actions: {
145
+ // Navigate items backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
146
+ focusPrevious: 'shift + tab',
147
+
148
+ // Navigate items forwards using the <kbd>Tab</kbd> key.
149
+ focusNext: 'tab'
150
+ }
151
+ } );
152
+
138
153
  this.setTemplate( {
139
154
  tag: 'div',
140
155
  attributes: {
141
156
  class: [
142
157
  'ck',
143
- 'ck-input-color',
144
- bind.if( 'hasError', 'ck-error' )
145
- ],
146
- id: bind.to( 'id' ),
147
- 'aria-invalid': bind.if( 'hasError', true ),
148
- 'aria-describedby': bind.to( 'ariaDescribedById' )
158
+ 'ck-input-color'
159
+ ]
149
160
  },
150
161
  children: [
151
- this._dropdownView,
152
- this._inputView
162
+ this.dropdownView,
163
+ this.inputView
153
164
  ]
154
165
  } );
155
166
 
156
167
  this.on( 'change:value', ( evt, name, inputValue ) => this._setInputValue( inputValue ) );
157
168
  }
158
169
 
170
+ /**
171
+ * @inheritDoc
172
+ */
173
+ render() {
174
+ super.render();
175
+
176
+ // Start listening for the keystrokes coming from the dropdown panel view.
177
+ this.keystrokes.listenTo( this.dropdownView.panelView.element );
178
+ }
179
+
159
180
  /**
160
181
  * Focuses the input.
161
182
  */
162
183
  focus() {
163
- this._inputView.focus();
184
+ this.inputView.focus();
164
185
  }
165
186
 
166
187
  /**
167
- * Creates and configures the {@link #_dropdownView}.
188
+ * @inheritDoc
189
+ */
190
+ destroy() {
191
+ super.destroy();
192
+
193
+ this.focusTracker.destroy();
194
+ this.keystrokes.destroy();
195
+ }
196
+
197
+ /**
198
+ * Creates and configures the {@link #dropdownView}.
168
199
  *
169
200
  * @private
170
201
  */
@@ -215,6 +246,12 @@ export default class ColorInputView extends View {
215
246
  dropdown.panelView.children.add( colorGrid );
216
247
  dropdown.bind( 'isEnabled' ).to( this, 'isReadOnly', value => !value );
217
248
 
249
+ this._focusables.add( removeColorButton );
250
+ this._focusables.add( colorGrid );
251
+
252
+ this.focusTracker.add( removeColorButton.element );
253
+ this.focusTracker.add( colorGrid.element );
254
+
218
255
  return dropdown;
219
256
  }
220
257
 
@@ -222,7 +259,7 @@ export default class ColorInputView extends View {
222
259
  * Creates and configures an instance of {@link module:ui/inputtext/inputtextview~InputTextView}.
223
260
  *
224
261
  * @private
225
- * @returns {module:ui/inputtext/inputtextview~InputTextView} A configured instance to be set as {@link #_inputView}.
262
+ * @returns {module:ui/inputtext/inputtextview~InputTextView} A configured instance to be set as {@link #inputView}.
226
263
  */
227
264
  _createInputTextView() {
228
265
  const locale = this.locale;
@@ -275,7 +312,7 @@ export default class ColorInputView extends View {
275
312
  removeColorButton.label = removeColorButtonLabel;
276
313
  removeColorButton.on( 'execute', () => {
277
314
  this.value = defaultColor;
278
- this._dropdownView.isOpen = false;
315
+ this.dropdownView.isOpen = false;
279
316
  this.fire( 'input' );
280
317
  } );
281
318
 
@@ -283,7 +320,7 @@ export default class ColorInputView extends View {
283
320
  }
284
321
 
285
322
  /**
286
- * Creates and configures the color grid inside the {@link #_dropdownView}.
323
+ * Creates and configures the color grid inside the {@link #dropdownView}.
287
324
  *
288
325
  * @private
289
326
  */
@@ -295,7 +332,7 @@ export default class ColorInputView extends View {
295
332
 
296
333
  colorGrid.on( 'execute', ( evtData, data ) => {
297
334
  this.value = data.value;
298
- this._dropdownView.isOpen = false;
335
+ this.dropdownView.isOpen = false;
299
336
  this.fire( 'input' );
300
337
  } );
301
338
  colorGrid.bind( 'selectedColor' ).to( this, 'value' );
@@ -304,7 +341,7 @@ export default class ColorInputView extends View {
304
341
  }
305
342
 
306
343
  /**
307
- * Sets {@link #_inputView}'s value property to the color value or color label,
344
+ * Sets {@link #inputView}'s value property to the color value or color label,
308
345
  * if there is one and the user is not typing.
309
346
  *
310
347
  * Handles cases like:
@@ -322,9 +359,9 @@ export default class ColorInputView extends View {
322
359
  const mappedColor = this.options.colorDefinitions.find( def => normalizedInputValue === normalizeColor( def.color ) );
323
360
 
324
361
  if ( mappedColor ) {
325
- this._inputView.value = mappedColor.label;
362
+ this.inputView.value = mappedColor.label;
326
363
  } else {
327
- this._inputView.value = inputValue || '';
364
+ this.inputView.value = inputValue || '';
328
365
  }
329
366
  }
330
367
  }
@@ -7,7 +7,9 @@
7
7
  * @module table/ui/inserttableview
8
8
  */
9
9
 
10
- import { View } from 'ckeditor5/src/ui';
10
+ import { View, ButtonView, addKeyboardHandlingForGrid } from 'ckeditor5/src/ui';
11
+
12
+ import { KeystrokeHandler, FocusTracker } from 'ckeditor5/src/utils';
11
13
 
12
14
  import './../../theme/inserttable.css';
13
15
 
@@ -36,6 +38,22 @@ export default class InsertTableView extends View {
36
38
  */
37
39
  this.items = this._createGridCollection();
38
40
 
41
+ /**
42
+ * Listen to `keydown` events fired in this view's main element.
43
+ *
44
+ * @readonly
45
+ * @member {module:utils/keystrokeHandler~KeystrokeHandler}
46
+ */
47
+ this.keystrokes = new KeystrokeHandler();
48
+
49
+ /**
50
+ * Tracks information about the DOM focus in the grid.
51
+ *
52
+ * @readonly
53
+ * @member {module:utils/focustracker~FocusTracker}
54
+ */
55
+ this.focusTracker = new FocusTracker();
56
+
39
57
  /**
40
58
  * The currently selected number of rows of the new table.
41
59
  *
@@ -81,7 +99,11 @@ export default class InsertTableView extends View {
81
99
  {
82
100
  tag: 'div',
83
101
  attributes: {
84
- class: [ 'ck-insert-table-dropdown__label' ]
102
+ class: [
103
+ 'ck',
104
+ 'ck-insert-table-dropdown__label'
105
+ ],
106
+ 'aria-hidden': true
85
107
  },
86
108
  children: [
87
109
  {
@@ -102,8 +124,21 @@ export default class InsertTableView extends View {
102
124
  }
103
125
  } );
104
126
 
127
+ // #rows and #columns are set via changes to #focusTracker on mouse over.
105
128
  this.on( 'boxover', ( evt, domEvt ) => {
106
129
  const { row, column } = domEvt.target.dataset;
130
+ this.items.get( ( parseInt( row, 10 ) - 1 ) * 10 + ( parseInt( column, 10 ) - 1 ) ).focus();
131
+ } );
132
+
133
+ // This allows the #rows and #columns to be updated when:
134
+ // * the user navigates the grid using the keyboard,
135
+ // * the user moves the mouse over grid items.
136
+ this.focusTracker.on( 'change:focusedElement', ( evt, name, focusedElement ) => {
137
+ if ( !focusedElement ) {
138
+ return;
139
+ }
140
+
141
+ const { row, column } = focusedElement.dataset;
107
142
 
108
143
  // As row & column indexes are zero-based transform it to number of selected rows & columns.
109
144
  this.set( {
@@ -112,29 +147,39 @@ export default class InsertTableView extends View {
112
147
  } );
113
148
  } );
114
149
 
115
- this.on( 'change:columns', () => {
116
- this._highlightGridBoxes();
117
- } );
150
+ this.on( 'change:columns', () => this._highlightGridBoxes() );
151
+ this.on( 'change:rows', () => this._highlightGridBoxes() );
152
+ }
153
+
154
+ render() {
155
+ super.render();
118
156
 
119
- this.on( 'change:rows', () => {
120
- this._highlightGridBoxes();
157
+ addKeyboardHandlingForGrid( {
158
+ keystrokeHandler: this.keystrokes,
159
+ focusTracker: this.focusTracker,
160
+ gridItems: this.items,
161
+ numberOfColumns: 10
121
162
  } );
163
+
164
+ for ( const item of this.items ) {
165
+ this.focusTracker.add( item.element );
166
+ }
167
+
168
+ this.keystrokes.listenTo( this.element );
122
169
  }
123
170
 
124
171
  /**
125
172
  * @inheritDoc
126
173
  */
127
174
  focus() {
128
- // The dropdown panel expects DropdownPanelFocusable interface on views passed to dropdown panel. See #30.
129
- // The method should be implemented while working on keyboard support for this view. See #22.
175
+ this.items.get( 0 ).focus();
130
176
  }
131
177
 
132
178
  /**
133
179
  * @inheritDoc
134
180
  */
135
181
  focusLast() {
136
- // The dropdown panel expects DropdownPanelFocusable interface on views passed to dropdown panel. See #30.
137
- // The method should be implemented while working on keyboard support for this view. See #22.
182
+ this.items.get( 0 ).focus();
138
183
  }
139
184
 
140
185
  /**
@@ -158,6 +203,34 @@ export default class InsertTableView extends View {
158
203
  } );
159
204
  }
160
205
 
206
+ /**
207
+ * Creates a new Button for the grid.
208
+ *
209
+ * @private
210
+ * @param {module:utils/locale~Locale} locale The locale instance.
211
+ * @param {Number} row Row number.
212
+ * @param {Number} column Column number.
213
+ * @param {String} label The grid button label.
214
+ * @returns {module:ui/button/buttonview~ButtonView}
215
+ */
216
+ _createGridButton( locale, row, column, label ) {
217
+ const button = new ButtonView( locale );
218
+
219
+ button.set( {
220
+ label,
221
+ class: 'ck-insert-table-dropdown-grid-box'
222
+ } );
223
+
224
+ button.extendTemplate( {
225
+ attributes: {
226
+ 'data-row': row,
227
+ 'data-column': column
228
+ }
229
+ } );
230
+
231
+ return button;
232
+ }
233
+
161
234
  /**
162
235
  * @private
163
236
  * @returns {module:ui/viewcollection~ViewCollection} A view collection containing boxes to be placed in a table grid.
@@ -169,8 +242,9 @@ export default class InsertTableView extends View {
169
242
  for ( let index = 0; index < 100; index++ ) {
170
243
  const row = Math.floor( index / 10 );
171
244
  const column = index % 10;
245
+ const label = `${ row + 1 } × ${ column + 1 }`;
172
246
 
173
- boxes.push( new TableSizeGridBoxView( this.locale, row + 1, column + 1 ) );
247
+ boxes.push( this._createGridButton( this.locale, row + 1, column + 1, label ) );
174
248
  }
175
249
 
176
250
  return this.createCollection( boxes );
@@ -182,41 +256,3 @@ export default class InsertTableView extends View {
182
256
  * @event boxover
183
257
  */
184
258
  }
185
-
186
- /**
187
- * A single grid box view element.
188
- *
189
- * This class is used to render the table size selection grid in {@link module:table/ui/inserttableview~InsertTableView}.
190
- *
191
- * @private
192
- */
193
- class TableSizeGridBoxView extends View {
194
- /**
195
- * @inheritDoc
196
- */
197
- constructor( locale, row, column ) {
198
- super( locale );
199
-
200
- const bind = this.bindTemplate;
201
-
202
- /**
203
- * Controls whether the grid box view is "on".
204
- *
205
- * @observable
206
- * @member {Boolean} #isOn
207
- */
208
- this.set( 'isOn', false );
209
-
210
- this.setTemplate( {
211
- tag: 'div',
212
- attributes: {
213
- class: [
214
- 'ck-insert-table-dropdown-grid-box',
215
- bind.if( 'isOn', 'ck-on' )
216
- ],
217
- 'data-row': row,
218
- 'data-column': column
219
- }
220
- } );
221
- }
222
- }
@@ -7,6 +7,8 @@
7
7
  * @module table/utils/common
8
8
  */
9
9
 
10
+ import { downcastAttributeToStyle, upcastStyleToAttribute } from './../converters/tableproperties';
11
+
10
12
  /**
11
13
  * A common method to update the numeric value. If a value is the default one, it will be unset.
12
14
  *
@@ -55,3 +57,25 @@ export function isHeadingColumnCell( tableUtils, tableCell ) {
55
57
 
56
58
  return !!headingColumns && column < headingColumns;
57
59
  }
60
+
61
+ /**
62
+ * Enables conversion for an attribute for simple view-model mappings.
63
+ *
64
+ * @param {module:engine/model/schema~Schema} schema
65
+ * @param {module:engine/conversion/conversion~Conversion} conversion
66
+ * @param {Object} options
67
+ * @param {String} options.modelAttribute
68
+ * @param {String} options.styleName
69
+ * @param {String} options.defaultValue The default value for the specified `modelAttribute`.
70
+ * @param {Boolean} [options.reduceBoxSides=false]
71
+ */
72
+ export function enableProperty( schema, conversion, options ) {
73
+ const { modelAttribute } = options;
74
+
75
+ schema.extend( 'tableCell', {
76
+ allowAttributes: [ modelAttribute ]
77
+ } );
78
+
79
+ upcastStyleToAttribute( conversion, { viewElement: /^(td|th)$/, ...options } );
80
+ downcastAttributeToStyle( conversion, { modelElement: 'tableCell', ...options } );
81
+ }
@@ -372,29 +372,29 @@ export const defaultColors = [
372
372
  */
373
373
  export function getLabeledColorInputCreator( options ) {
374
374
  return ( labeledFieldView, viewUid, statusUid ) => {
375
- const inputView = new ColorInputView( labeledFieldView.locale, {
375
+ const colorInputView = new ColorInputView( labeledFieldView.locale, {
376
376
  colorDefinitions: colorConfigToColorGridDefinitions( options.colorConfig ),
377
377
  columns: options.columns,
378
378
  defaultColorValue: options.defaultColorValue
379
379
  } );
380
380
 
381
- inputView.set( {
381
+ colorInputView.inputView.set( {
382
382
  id: viewUid,
383
383
  ariaDescribedById: statusUid
384
384
  } );
385
385
 
386
- inputView.bind( 'isReadOnly' ).to( labeledFieldView, 'isEnabled', value => !value );
387
- inputView.bind( 'hasError' ).to( labeledFieldView, 'errorText', value => !!value );
386
+ colorInputView.bind( 'isReadOnly' ).to( labeledFieldView, 'isEnabled', value => !value );
387
+ colorInputView.bind( 'hasError' ).to( labeledFieldView, 'errorText', value => !!value );
388
388
 
389
- inputView.on( 'input', () => {
389
+ colorInputView.on( 'input', () => {
390
390
  // UX: Make the error text disappear and disable the error indicator as the user
391
391
  // starts fixing the errors.
392
392
  labeledFieldView.errorText = null;
393
393
  } );
394
394
 
395
- labeledFieldView.bind( 'isEmpty', 'isFocused' ).to( inputView );
395
+ labeledFieldView.bind( 'isEmpty', 'isFocused' ).to( colorInputView );
396
396
 
397
- return inputView;
397
+ return colorInputView;
398
398
  };
399
399
  }
400
400
 
@@ -12,9 +12,12 @@
12
12
  --ck-table-column-resizer-position-offset: calc(var(--ck-table-column-resizer-width) * -0.5 - 0.5px);
13
13
  }
14
14
 
15
+ .ck-content .table .ck-table-resized {
16
+ table-layout: fixed;
17
+ }
18
+
15
19
  .ck-content .table table {
16
20
  overflow: hidden;
17
- table-layout: fixed;
18
21
  }
19
22
 
20
23
  .ck-content .table td,
@@ -22,7 +25,7 @@
22
25
  position: relative;
23
26
  }
24
27
 
25
- .ck-content .table .table-column-resizer {
28
+ .ck.ck-editor__editable .table .ck-table-column-resizer {
26
29
  position: absolute;
27
30
  /* The resizer element resides in each cell so to occupy the entire height of the table, which is unknown from a CSS point of view,
28
31
  it is extended to an extremely high height. Even for screens with a very high pixel density, the resizer will fulfill its role as
@@ -37,23 +40,23 @@
37
40
  z-index: var(--ck-z-default);
38
41
  }
39
42
 
43
+ .ck.ck-editor__editable.ck-column-resize_disabled .table .ck-table-column-resizer {
44
+ display: none;
45
+ }
46
+
40
47
  /* The resizer elements, which are extended to an extremely high height, break the drag & drop feature in Chrome. To make it work again,
41
48
  all resizers must be hidden while the table is dragged. */
42
- .ck-content .table[draggable] .table-column-resizer {
49
+ .ck.ck-editor__editable .table[draggable] .ck-table-column-resizer {
43
50
  display: none;
44
51
  }
45
52
 
46
- .ck-content .table .table-column-resizer:hover,
47
- .ck-content .table .table-column-resizer__active {
53
+ .ck.ck-editor__editable .table .ck-table-column-resizer:hover,
54
+ .ck.ck-editor__editable .table .ck-table-column-resizer__active {
48
55
  background-color: var(--ck-color-table-column-resizer-hover);
49
56
  opacity: 0.25;
50
57
  }
51
58
 
52
- .ck-content[dir=rtl] .table .table-column-resizer {
59
+ .ck.ck-editor__editable[dir=rtl] .table .ck-table-column-resizer {
53
60
  left: var(--ck-table-column-resizer-position-offset);
54
61
  right: unset;
55
62
  }
56
-
57
- .ck-content.ck-read-only .table .table-column-resizer {
58
- display: none;
59
- }