@ckeditor/ckeditor5-widget 35.2.0 → 35.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,528 +2,435 @@
2
2
  * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module widget/widgetresize/resizer
8
7
  */
9
-
10
8
  import Template from '@ckeditor/ckeditor5-ui/src/template';
11
9
  import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
12
10
  import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
13
-
14
- import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
15
- import mix from '@ckeditor/ckeditor5-utils/src/mix';
16
-
11
+ import { Observable } from '@ckeditor/ckeditor5-utils/src/observablemixin';
17
12
  import ResizeState from './resizerstate';
18
13
  import SizeView from './sizeview';
19
-
20
14
  /**
21
15
  * Represents a resizer for a single resizable object.
22
16
  *
23
17
  * @mixes module:utils/observablemixin~ObservableMixin
24
18
  */
25
- export default class Resizer {
26
- /**
27
- * @param {module:widget/widgetresize~ResizerOptions} options Resizer options.
28
- */
29
- constructor( options ) {
30
- /**
31
- * Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
32
- *
33
- * Note that a new state is created for each resize transaction.
34
- *
35
- * @readonly
36
- * @member {module:widget/widgetresize/resizerstate~ResizerState} #state
37
- */
38
-
39
- /**
40
- * A view displaying the proposed new element size during the resizing.
41
- *
42
- * @protected
43
- * @readonly
44
- * @member {module:widget/widgetresize/sizeview~SizeView} #_sizeView
45
- */
46
-
47
- /**
48
- * Options passed to the {@link #constructor}.
49
- *
50
- * @private
51
- * @type {module:widget/widgetresize~ResizerOptions}
52
- */
53
- this._options = options;
54
-
55
- /**
56
- * A wrapper that is controlled by the resizer. This is usually a widget element.
57
- *
58
- * @private
59
- * @type {module:engine/view/element~Element|null}
60
- */
61
- this._viewResizerWrapper = null;
62
-
63
- /**
64
- * The width of the resized {@link module:widget/widgetresize~ResizerOptions#viewElement viewElement} before the resizing started.
65
- *
66
- * @private
67
- * @member {Number|String|undefined} #_initialViewWidth
68
- */
69
-
70
- /**
71
- * Flag that indicates whether resizer can be used.
72
- *
73
- * @observable
74
- */
75
- this.set( 'isEnabled', true );
76
-
77
- /**
78
- * Flag that indicates that resizer is currently focused.
79
- *
80
- * @observable
81
- */
82
- this.set( 'isSelected', false );
83
-
84
- /**
85
- * Flag that indicates whether resizer is rendered (visible on the screen).
86
- *
87
- * @readonly
88
- * @observable
89
- */
90
- this.bind( 'isVisible' ).to( this, 'isEnabled', this, 'isSelected', ( isEnabled, isSelected ) => isEnabled && isSelected );
91
-
92
- this.decorate( 'begin' );
93
- this.decorate( 'cancel' );
94
- this.decorate( 'commit' );
95
- this.decorate( 'updateSize' );
96
-
97
- this.on( 'commit', event => {
98
- // State might not be initialized yet. In this case, prevent further handling and make sure that the resizer is
99
- // cleaned up (#5195).
100
- if ( !this.state.proposedWidth && !this.state.proposedWidthPercents ) {
101
- this._cleanup();
102
- event.stop();
103
- }
104
- }, { priority: 'high' } );
105
-
106
- this.on( 'change:isVisible', () => {
107
- if ( this.isVisible ) {
108
- this.show();
109
- this.redraw();
110
- } else {
111
- this.hide();
112
- }
113
- } );
114
- }
115
-
116
- /**
117
- * Makes resizer visible in the UI.
118
- */
119
- show() {
120
- const editingView = this._options.editor.editing.view;
121
-
122
- editingView.change( writer => {
123
- writer.removeClass( 'ck-hidden', this._viewResizerWrapper );
124
- } );
125
- }
126
-
127
- /**
128
- * Hides resizer in the UI.
129
- */
130
- hide() {
131
- const editingView = this._options.editor.editing.view;
132
-
133
- editingView.change( writer => {
134
- writer.addClass( 'ck-hidden', this._viewResizerWrapper );
135
- } );
136
- }
137
-
138
- /**
139
- * Attaches the resizer to the DOM.
140
- */
141
- attach() {
142
- const that = this;
143
- const widgetElement = this._options.viewElement;
144
- const editingView = this._options.editor.editing.view;
145
-
146
- editingView.change( writer => {
147
- const viewResizerWrapper = writer.createUIElement( 'div', {
148
- class: 'ck ck-reset_all ck-widget__resizer'
149
- }, function( domDocument ) {
150
- const domElement = this.toDomElement( domDocument );
151
-
152
- that._appendHandles( domElement );
153
- that._appendSizeUI( domElement );
154
-
155
- return domElement;
156
- } );
157
-
158
- // Append the resizer wrapper to the widget's wrapper.
159
- writer.insert( writer.createPositionAt( widgetElement, 'end' ), viewResizerWrapper );
160
- writer.addClass( 'ck-widget_with-resizer', widgetElement );
161
-
162
- this._viewResizerWrapper = viewResizerWrapper;
163
-
164
- if ( !this.isVisible ) {
165
- this.hide();
166
- }
167
- } );
168
- }
169
-
170
- /**
171
- * Starts the resizing process.
172
- *
173
- * Creates a new {@link #state} for the current process.
174
- *
175
- * @fires begin
176
- * @param {HTMLElement} domResizeHandle Clicked handle.
177
- */
178
- begin( domResizeHandle ) {
179
- this.state = new ResizeState( this._options );
180
-
181
- this._sizeView._bindToState( this._options, this.state );
182
-
183
- this._initialViewWidth = this._options.viewElement.getStyle( 'width' );
184
-
185
- this.state.begin( domResizeHandle, this._getHandleHost(), this._getResizeHost() );
186
- }
187
-
188
- /**
189
- * Updates the proposed size based on `domEventData`.
190
- *
191
- * @fires updateSize
192
- * @param {Event} domEventData
193
- */
194
- updateSize( domEventData ) {
195
- const newSize = this._proposeNewSize( domEventData );
196
- const editingView = this._options.editor.editing.view;
197
-
198
- editingView.change( writer => {
199
- const unit = this._options.unit || '%';
200
- const newWidth = ( unit === '%' ? newSize.widthPercents : newSize.width ) + unit;
201
-
202
- writer.setStyle( 'width', newWidth, this._options.viewElement );
203
- } );
204
-
205
- // Get an actual image width, and:
206
- // * reflect this size to the resize wrapper
207
- // * apply this **real** size to the state
208
- const domHandleHost = this._getHandleHost();
209
- const domHandleHostRect = new Rect( domHandleHost );
210
-
211
- newSize.handleHostWidth = Math.round( domHandleHostRect.width );
212
- newSize.handleHostHeight = Math.round( domHandleHostRect.height );
213
-
214
- // Handle max-width limitation.
215
- const domResizeHostRect = new Rect( domHandleHost );
216
-
217
- newSize.width = Math.round( domResizeHostRect.width );
218
- newSize.height = Math.round( domResizeHostRect.height );
219
-
220
- this.redraw( domHandleHostRect );
221
-
222
- this.state.update( newSize );
223
- }
224
-
225
- /**
226
- * Applies the geometry proposed with the resizer.
227
- *
228
- * @fires commit
229
- */
230
- commit() {
231
- const unit = this._options.unit || '%';
232
- const newValue = ( unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth ) + unit;
233
-
234
- // Both cleanup and onCommit callback are very likely to make view changes. Ensure that it is made in a single step.
235
- this._options.editor.editing.view.change( () => {
236
- this._cleanup();
237
- this._options.onCommit( newValue );
238
- } );
239
- }
240
-
241
- /**
242
- * Cancels and rejects the proposed resize dimensions, hiding the UI.
243
- *
244
- * @fires cancel
245
- */
246
- cancel() {
247
- this._cleanup();
248
- }
249
-
250
- /**
251
- * Destroys the resizer.
252
- */
253
- destroy() {
254
- this.cancel();
255
- }
256
-
257
- /**
258
- * Redraws the resizer.
259
- *
260
- * @param {module:utils/dom/rect~Rect} [handleHostRect] Handle host rectangle might be given to improve performance.
261
- */
262
- redraw( handleHostRect ) {
263
- const domWrapper = this._domResizerWrapper;
264
-
265
- // Refresh only if resizer exists in the DOM.
266
- if ( !existsInDom( domWrapper ) ) {
267
- return;
268
- }
269
-
270
- const widgetWrapper = domWrapper.parentElement;
271
- const handleHost = this._getHandleHost();
272
- const resizerWrapper = this._viewResizerWrapper;
273
- const currentDimensions = [
274
- resizerWrapper.getStyle( 'width' ),
275
- resizerWrapper.getStyle( 'height' ),
276
- resizerWrapper.getStyle( 'left' ),
277
- resizerWrapper.getStyle( 'top' )
278
- ];
279
- let newDimensions;
280
-
281
- if ( widgetWrapper.isSameNode( handleHost ) ) {
282
- const clientRect = handleHostRect || new Rect( handleHost );
283
-
284
- newDimensions = [
285
- clientRect.width + 'px',
286
- clientRect.height + 'px',
287
- undefined,
288
- undefined
289
- ];
290
- }
291
- // In case a resizing host is not a widget wrapper, we need to compensate
292
- // for any additional offsets the resize host might have. E.g. wrapper padding
293
- // or simply another editable. By doing that the border and resizers are shown
294
- // only around the resize host.
295
- else {
296
- newDimensions = [
297
- handleHost.offsetWidth + 'px',
298
- handleHost.offsetHeight + 'px',
299
- handleHost.offsetLeft + 'px',
300
- handleHost.offsetTop + 'px'
301
- ];
302
- }
303
-
304
- // Make changes to the view only if the resizer should actually get new dimensions.
305
- // Otherwise, if View#change() was always called, this would cause EditorUI#update
306
- // loops because the WidgetResize plugin listens to EditorUI#update and updates
307
- // the resizer.
308
- // https://github.com/ckeditor/ckeditor5/issues/7633
309
- if ( compareArrays( currentDimensions, newDimensions ) !== 'same' ) {
310
- this._options.editor.editing.view.change( writer => {
311
- writer.setStyle( {
312
- width: newDimensions[ 0 ],
313
- height: newDimensions[ 1 ],
314
- left: newDimensions[ 2 ],
315
- top: newDimensions[ 3 ]
316
- }, resizerWrapper );
317
- } );
318
- }
319
- }
320
-
321
- containsHandle( domElement ) {
322
- return this._domResizerWrapper.contains( domElement );
323
- }
324
-
325
- static isResizeHandle( domElement ) {
326
- return domElement.classList.contains( 'ck-widget__resizer__handle' );
327
- }
328
-
329
- /**
330
- * Cleans up the context state.
331
- *
332
- * @protected
333
- */
334
- _cleanup() {
335
- this._sizeView._dismiss();
336
-
337
- const editingView = this._options.editor.editing.view;
338
-
339
- editingView.change( writer => {
340
- writer.setStyle( 'width', this._initialViewWidth, this._options.viewElement );
341
- } );
342
- }
343
-
344
- /**
345
- * Calculates the proposed size as the resize handles are dragged.
346
- *
347
- * @private
348
- * @param {Event} domEventData Event data that caused the size update request. It should be used to calculate the proposed size.
349
- * @returns {Object} return
350
- * @returns {Number} return.width Proposed width.
351
- * @returns {Number} return.height Proposed height.
352
- */
353
- _proposeNewSize( domEventData ) {
354
- const state = this.state;
355
- const currentCoordinates = extractCoordinates( domEventData );
356
- const isCentered = this._options.isCentered ? this._options.isCentered( this ) : true;
357
-
358
- // Enlargement defines how much the resize host has changed in a given axis. Naturally it could be a negative number
359
- // meaning that it has been shrunk.
360
- //
361
- // +----------------+--+
362
- // | | |
363
- // | img | |
364
- // | /handle host | |
365
- // +----------------+ | ^
366
- // | | | - enlarge y
367
- // +-------------------+ v
368
- // <-->
369
- // enlarge x
370
- const enlargement = {
371
- x: state._referenceCoordinates.x - ( currentCoordinates.x + state.originalWidth ),
372
- y: ( currentCoordinates.y - state.originalHeight ) - state._referenceCoordinates.y
373
- };
374
-
375
- if ( isCentered && state.activeHandlePosition.endsWith( '-right' ) ) {
376
- enlargement.x = currentCoordinates.x - ( state._referenceCoordinates.x + state.originalWidth );
377
- }
378
-
379
- // Objects needs to be resized twice as much in horizontal axis if centered, since enlargement is counted from
380
- // one resized corner to your cursor. It needs to be duplicated to compensate for the other side too.
381
- if ( isCentered ) {
382
- enlargement.x *= 2;
383
- }
384
-
385
- // const resizeHost = this._getResizeHost();
386
-
387
- // The size proposed by the user. It does not consider the aspect ratio.
388
- const proposedSize = {
389
- width: Math.abs( state.originalWidth + enlargement.x ),
390
- height: Math.abs( state.originalHeight + enlargement.y )
391
- };
392
-
393
- // Dominant determination must take the ratio into account.
394
- proposedSize.dominant = proposedSize.width / state.aspectRatio > proposedSize.height ? 'width' : 'height';
395
- proposedSize.max = proposedSize[ proposedSize.dominant ];
396
-
397
- // Proposed size, respecting the aspect ratio.
398
- const targetSize = {
399
- width: proposedSize.width,
400
- height: proposedSize.height
401
- };
402
-
403
- if ( proposedSize.dominant == 'width' ) {
404
- targetSize.height = targetSize.width / state.aspectRatio;
405
- } else {
406
- targetSize.width = targetSize.height * state.aspectRatio;
407
- }
408
-
409
- return {
410
- width: Math.round( targetSize.width ),
411
- height: Math.round( targetSize.height ),
412
- widthPercents: Math.min( Math.round( state.originalWidthPercents / state.originalWidth * targetSize.width * 100 ) / 100, 100 )
413
- };
414
- }
415
-
416
- /**
417
- * Obtains the resize host.
418
- *
419
- * Resize host is an object that receives dimensions which are the result of resizing.
420
- *
421
- * @protected
422
- * @returns {HTMLElement}
423
- */
424
- _getResizeHost() {
425
- const widgetWrapper = this._domResizerWrapper.parentElement;
426
-
427
- return this._options.getResizeHost( widgetWrapper );
428
- }
429
-
430
- /**
431
- * Obtains the handle host.
432
- *
433
- * Handle host is an object that the handles are aligned to.
434
- *
435
- * Handle host will not always be an entire widget itself. Take an image as an example. The image widget
436
- * contains an image and a caption. Only the image should be surrounded with handles.
437
- *
438
- * @protected
439
- * @returns {HTMLElement}
440
- */
441
- _getHandleHost() {
442
- const widgetWrapper = this._domResizerWrapper.parentElement;
443
-
444
- return this._options.getHandleHost( widgetWrapper );
445
- }
446
-
447
- /**
448
- * DOM container of the entire resize UI.
449
- *
450
- * Note that this property will have a value only after the element bound with the resizer is rendered
451
- * (otherwise `null`).
452
- *
453
- * @private
454
- * @member {HTMLElement|null}
455
- */
456
- get _domResizerWrapper() {
457
- return this._options.editor.editing.view.domConverter.mapViewToDom( this._viewResizerWrapper );
458
- }
459
-
460
- /**
461
- * Renders the resize handles in the DOM.
462
- *
463
- * @private
464
- * @param {HTMLElement} domElement The resizer wrapper.
465
- */
466
- _appendHandles( domElement ) {
467
- const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
468
-
469
- for ( const currentPosition of resizerPositions ) {
470
- domElement.appendChild( ( new Template( {
471
- tag: 'div',
472
- attributes: {
473
- class: `ck-widget__resizer__handle ${ getResizerClass( currentPosition ) }`
474
- }
475
- } ).render() ) );
476
- }
477
- }
478
-
479
- /**
480
- * Sets up the {@link #_sizeView} property and adds it to the passed `domElement`.
481
- *
482
- * @private
483
- * @param {HTMLElement} domElement
484
- */
485
- _appendSizeUI( domElement ) {
486
- this._sizeView = new SizeView();
487
-
488
- // Make sure icon#element is rendered before passing to appendChild().
489
- this._sizeView.render();
490
-
491
- domElement.appendChild( this._sizeView.element );
492
- }
493
-
494
- /**
495
- * @event begin
496
- */
497
-
498
- /**
499
- * @event updateSize
500
- */
501
-
502
- /**
503
- * @event commit
504
- */
505
-
506
- /**
507
- * @event cancel
508
- */
19
+ export default class Resizer extends Observable {
20
+ /**
21
+ * @param {module:widget/widgetresize~ResizerOptions} options Resizer options.
22
+ */
23
+ constructor(options) {
24
+ super();
25
+ /**
26
+ * Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
27
+ *
28
+ * Note that a new state is created for each resize transaction.
29
+ *
30
+ * @readonly
31
+ * @member {module:widget/widgetresize/resizerstate~ResizerState} #state
32
+ */
33
+ /**
34
+ * A view displaying the proposed new element size during the resizing.
35
+ *
36
+ * @protected
37
+ * @readonly
38
+ * @member {module:widget/widgetresize/sizeview~SizeView} #_sizeView
39
+ */
40
+ /**
41
+ * Options passed to the {@link #constructor}.
42
+ *
43
+ * @private
44
+ * @type {module:widget/widgetresize~ResizerOptions}
45
+ */
46
+ this._options = options;
47
+ /**
48
+ * A wrapper that is controlled by the resizer. This is usually a widget element.
49
+ *
50
+ * @private
51
+ * @type {module:engine/view/element~Element|null}
52
+ */
53
+ this._viewResizerWrapper = null;
54
+ /**
55
+ * The width of the resized {@link module:widget/widgetresize~ResizerOptions#viewElement viewElement} before the resizing started.
56
+ *
57
+ * @private
58
+ * @member {Number|String|undefined} #_initialViewWidth
59
+ */
60
+ /**
61
+ * Flag that indicates whether resizer can be used.
62
+ *
63
+ * @observable
64
+ */
65
+ this.set('isEnabled', true);
66
+ /**
67
+ * Flag that indicates that resizer is currently focused.
68
+ *
69
+ * @observable
70
+ */
71
+ this.set('isSelected', false);
72
+ /**
73
+ * Flag that indicates whether resizer is rendered (visible on the screen).
74
+ *
75
+ * @readonly
76
+ * @observable
77
+ */
78
+ this.bind('isVisible').to(this, 'isEnabled', this, 'isSelected', (isEnabled, isSelected) => isEnabled && isSelected);
79
+ this.decorate('begin');
80
+ this.decorate('cancel');
81
+ this.decorate('commit');
82
+ this.decorate('updateSize');
83
+ this.on('commit', event => {
84
+ // State might not be initialized yet. In this case, prevent further handling and make sure that the resizer is
85
+ // cleaned up (#5195).
86
+ if (!this.state.proposedWidth && !this.state.proposedWidthPercents) {
87
+ this._cleanup();
88
+ event.stop();
89
+ }
90
+ }, { priority: 'high' });
91
+ }
92
+ get state() {
93
+ return this._state;
94
+ }
95
+ /**
96
+ * Makes resizer visible in the UI.
97
+ */
98
+ show() {
99
+ const editingView = this._options.editor.editing.view;
100
+ editingView.change(writer => {
101
+ writer.removeClass('ck-hidden', this._viewResizerWrapper);
102
+ });
103
+ }
104
+ /**
105
+ * Hides resizer in the UI.
106
+ */
107
+ hide() {
108
+ const editingView = this._options.editor.editing.view;
109
+ editingView.change(writer => {
110
+ writer.addClass('ck-hidden', this._viewResizerWrapper);
111
+ });
112
+ }
113
+ /**
114
+ * Attaches the resizer to the DOM.
115
+ */
116
+ attach() {
117
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
118
+ const that = this;
119
+ const widgetElement = this._options.viewElement;
120
+ const editingView = this._options.editor.editing.view;
121
+ editingView.change(writer => {
122
+ const viewResizerWrapper = writer.createUIElement('div', {
123
+ class: 'ck ck-reset_all ck-widget__resizer'
124
+ }, function (domDocument) {
125
+ const domElement = this.toDomElement(domDocument);
126
+ that._appendHandles(domElement);
127
+ that._appendSizeUI(domElement);
128
+ return domElement;
129
+ });
130
+ // Append the resizer wrapper to the widget's wrapper.
131
+ writer.insert(writer.createPositionAt(widgetElement, 'end'), viewResizerWrapper);
132
+ writer.addClass('ck-widget_with-resizer', widgetElement);
133
+ this._viewResizerWrapper = viewResizerWrapper;
134
+ if (!this.isVisible) {
135
+ this.hide();
136
+ }
137
+ });
138
+ this.on('change:isVisible', () => {
139
+ if (this.isVisible) {
140
+ this.show();
141
+ this.redraw();
142
+ }
143
+ else {
144
+ this.hide();
145
+ }
146
+ });
147
+ }
148
+ /**
149
+ * Starts the resizing process.
150
+ *
151
+ * Creates a new {@link #state} for the current process.
152
+ *
153
+ * @fires begin
154
+ * @param {HTMLElement} domResizeHandle Clicked handle.
155
+ */
156
+ begin(domResizeHandle) {
157
+ this._state = new ResizeState(this._options);
158
+ this._sizeView._bindToState(this._options, this.state);
159
+ this._initialViewWidth = this._options.viewElement.getStyle('width');
160
+ this.state.begin(domResizeHandle, this._getHandleHost(), this._getResizeHost());
161
+ }
162
+ /**
163
+ * Updates the proposed size based on `domEventData`.
164
+ *
165
+ * @fires updateSize
166
+ * @param {Event} domEventData
167
+ */
168
+ updateSize(domEventData) {
169
+ const newSize = this._proposeNewSize(domEventData);
170
+ const editingView = this._options.editor.editing.view;
171
+ editingView.change(writer => {
172
+ const unit = this._options.unit || '%';
173
+ const newWidth = (unit === '%' ? newSize.widthPercents : newSize.width) + unit;
174
+ writer.setStyle('width', newWidth, this._options.viewElement);
175
+ });
176
+ // Get an actual image width, and:
177
+ // * reflect this size to the resize wrapper
178
+ // * apply this **real** size to the state
179
+ const domHandleHost = this._getHandleHost();
180
+ const domHandleHostRect = new Rect(domHandleHost);
181
+ const handleHostWidth = Math.round(domHandleHostRect.width);
182
+ const handleHostHeight = Math.round(domHandleHostRect.height);
183
+ // Handle max-width limitation.
184
+ const domResizeHostRect = new Rect(domHandleHost);
185
+ newSize.width = Math.round(domResizeHostRect.width);
186
+ newSize.height = Math.round(domResizeHostRect.height);
187
+ this.redraw(domHandleHostRect);
188
+ this.state.update({
189
+ ...newSize,
190
+ handleHostWidth,
191
+ handleHostHeight
192
+ });
193
+ }
194
+ /**
195
+ * Applies the geometry proposed with the resizer.
196
+ *
197
+ * @fires commit
198
+ */
199
+ commit() {
200
+ const unit = this._options.unit || '%';
201
+ const newValue = (unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth) + unit;
202
+ // Both cleanup and onCommit callback are very likely to make view changes. Ensure that it is made in a single step.
203
+ this._options.editor.editing.view.change(() => {
204
+ this._cleanup();
205
+ this._options.onCommit(newValue);
206
+ });
207
+ }
208
+ /**
209
+ * Cancels and rejects the proposed resize dimensions, hiding the UI.
210
+ *
211
+ * @fires cancel
212
+ */
213
+ cancel() {
214
+ this._cleanup();
215
+ }
216
+ /**
217
+ * Destroys the resizer.
218
+ */
219
+ destroy() {
220
+ this.cancel();
221
+ }
222
+ /**
223
+ * Redraws the resizer.
224
+ *
225
+ * @param {module:utils/dom/rect~Rect} [handleHostRect] Handle host rectangle might be given to improve performance.
226
+ */
227
+ redraw(handleHostRect) {
228
+ const domWrapper = this._domResizerWrapper;
229
+ // Refresh only if resizer exists in the DOM.
230
+ if (!existsInDom(domWrapper)) {
231
+ return;
232
+ }
233
+ const widgetWrapper = domWrapper.parentElement;
234
+ const handleHost = this._getHandleHost();
235
+ const resizerWrapper = this._viewResizerWrapper;
236
+ const currentDimensions = [
237
+ resizerWrapper.getStyle('width'),
238
+ resizerWrapper.getStyle('height'),
239
+ resizerWrapper.getStyle('left'),
240
+ resizerWrapper.getStyle('top')
241
+ ];
242
+ let newDimensions;
243
+ if (widgetWrapper.isSameNode(handleHost)) {
244
+ const clientRect = handleHostRect || new Rect(handleHost);
245
+ newDimensions = [
246
+ clientRect.width + 'px',
247
+ clientRect.height + 'px',
248
+ undefined,
249
+ undefined
250
+ ];
251
+ }
252
+ // In case a resizing host is not a widget wrapper, we need to compensate
253
+ // for any additional offsets the resize host might have. E.g. wrapper padding
254
+ // or simply another editable. By doing that the border and resizers are shown
255
+ // only around the resize host.
256
+ else {
257
+ newDimensions = [
258
+ handleHost.offsetWidth + 'px',
259
+ handleHost.offsetHeight + 'px',
260
+ handleHost.offsetLeft + 'px',
261
+ handleHost.offsetTop + 'px'
262
+ ];
263
+ }
264
+ // Make changes to the view only if the resizer should actually get new dimensions.
265
+ // Otherwise, if View#change() was always called, this would cause EditorUI#update
266
+ // loops because the WidgetResize plugin listens to EditorUI#update and updates
267
+ // the resizer.
268
+ // https://github.com/ckeditor/ckeditor5/issues/7633
269
+ if (compareArrays(currentDimensions, newDimensions) !== 'same') {
270
+ this._options.editor.editing.view.change(writer => {
271
+ writer.setStyle({
272
+ width: newDimensions[0],
273
+ height: newDimensions[1],
274
+ left: newDimensions[2],
275
+ top: newDimensions[3]
276
+ }, resizerWrapper);
277
+ });
278
+ }
279
+ }
280
+ containsHandle(domElement) {
281
+ return this._domResizerWrapper.contains(domElement);
282
+ }
283
+ static isResizeHandle(domElement) {
284
+ return domElement.classList.contains('ck-widget__resizer__handle');
285
+ }
286
+ /**
287
+ * Cleans up the context state.
288
+ *
289
+ * @protected
290
+ */
291
+ _cleanup() {
292
+ this._sizeView._dismiss();
293
+ const editingView = this._options.editor.editing.view;
294
+ editingView.change(writer => {
295
+ writer.setStyle('width', this._initialViewWidth, this._options.viewElement);
296
+ });
297
+ }
298
+ /**
299
+ * Calculates the proposed size as the resize handles are dragged.
300
+ *
301
+ * @private
302
+ * @param {Event} domEventData Event data that caused the size update request. It should be used to calculate the proposed size.
303
+ * @returns {Object} return
304
+ * @returns {Number} return.width Proposed width.
305
+ * @returns {Number} return.height Proposed height.
306
+ */
307
+ _proposeNewSize(domEventData) {
308
+ const state = this.state;
309
+ const currentCoordinates = extractCoordinates(domEventData);
310
+ const isCentered = this._options.isCentered ? this._options.isCentered(this) : true;
311
+ // Enlargement defines how much the resize host has changed in a given axis. Naturally it could be a negative number
312
+ // meaning that it has been shrunk.
313
+ //
314
+ // +----------------+--+
315
+ // | | |
316
+ // | img | |
317
+ // | /handle host | |
318
+ // +----------------+ | ^
319
+ // | | | - enlarge y
320
+ // +-------------------+ v
321
+ // <-->
322
+ // enlarge x
323
+ const enlargement = {
324
+ x: state._referenceCoordinates.x - (currentCoordinates.x + state.originalWidth),
325
+ y: (currentCoordinates.y - state.originalHeight) - state._referenceCoordinates.y
326
+ };
327
+ if (isCentered && state.activeHandlePosition.endsWith('-right')) {
328
+ enlargement.x = currentCoordinates.x - (state._referenceCoordinates.x + state.originalWidth);
329
+ }
330
+ // Objects needs to be resized twice as much in horizontal axis if centered, since enlargement is counted from
331
+ // one resized corner to your cursor. It needs to be duplicated to compensate for the other side too.
332
+ if (isCentered) {
333
+ enlargement.x *= 2;
334
+ }
335
+ // const resizeHost = this._getResizeHost();
336
+ // The size proposed by the user. It does not consider the aspect ratio.
337
+ let width = Math.abs(state.originalWidth + enlargement.x);
338
+ let height = Math.abs(state.originalHeight + enlargement.y);
339
+ // Dominant determination must take the ratio into account.
340
+ const dominant = width / state.aspectRatio > height ? 'width' : 'height';
341
+ if (dominant == 'width') {
342
+ height = width / state.aspectRatio;
343
+ }
344
+ else {
345
+ width = height * state.aspectRatio;
346
+ }
347
+ return {
348
+ width: Math.round(width),
349
+ height: Math.round(height),
350
+ widthPercents: Math.min(Math.round(state.originalWidthPercents / state.originalWidth * width * 100) / 100, 100)
351
+ };
352
+ }
353
+ /**
354
+ * Obtains the resize host.
355
+ *
356
+ * Resize host is an object that receives dimensions which are the result of resizing.
357
+ *
358
+ * @protected
359
+ * @returns {HTMLElement}
360
+ */
361
+ _getResizeHost() {
362
+ const widgetWrapper = this._domResizerWrapper.parentElement;
363
+ return this._options.getResizeHost(widgetWrapper);
364
+ }
365
+ /**
366
+ * Obtains the handle host.
367
+ *
368
+ * Handle host is an object that the handles are aligned to.
369
+ *
370
+ * Handle host will not always be an entire widget itself. Take an image as an example. The image widget
371
+ * contains an image and a caption. Only the image should be surrounded with handles.
372
+ *
373
+ * @protected
374
+ * @returns {HTMLElement}
375
+ */
376
+ _getHandleHost() {
377
+ const widgetWrapper = this._domResizerWrapper.parentElement;
378
+ return this._options.getHandleHost(widgetWrapper);
379
+ }
380
+ /**
381
+ * DOM container of the entire resize UI.
382
+ *
383
+ * Note that this property will have a value only after the element bound with the resizer is rendered
384
+ * (otherwise `null`).
385
+ *
386
+ * @private
387
+ * @member {HTMLElement|null}
388
+ */
389
+ get _domResizerWrapper() {
390
+ return this._options.editor.editing.view.domConverter.mapViewToDom(this._viewResizerWrapper);
391
+ }
392
+ /**
393
+ * Renders the resize handles in the DOM.
394
+ *
395
+ * @private
396
+ * @param {HTMLElement} domElement The resizer wrapper.
397
+ */
398
+ _appendHandles(domElement) {
399
+ const resizerPositions = ['top-left', 'top-right', 'bottom-right', 'bottom-left'];
400
+ for (const currentPosition of resizerPositions) {
401
+ domElement.appendChild((new Template({
402
+ tag: 'div',
403
+ attributes: {
404
+ class: `ck-widget__resizer__handle ${getResizerClass(currentPosition)}`
405
+ }
406
+ }).render()));
407
+ }
408
+ }
409
+ /**
410
+ * Sets up the {@link #_sizeView} property and adds it to the passed `domElement`.
411
+ *
412
+ * @private
413
+ * @param {HTMLElement} domElement
414
+ */
415
+ _appendSizeUI(domElement) {
416
+ this._sizeView = new SizeView();
417
+ // Make sure icon#element is rendered before passing to appendChild().
418
+ this._sizeView.render();
419
+ domElement.appendChild(this._sizeView.element);
420
+ }
509
421
  }
510
-
511
- mix( Resizer, ObservableMixin );
512
-
513
422
  // @private
514
423
  // @param {String} resizerPosition Expected resizer position like `"top-left"`, `"bottom-right"`.
515
424
  // @returns {String} A prefixed HTML class name for the resizer element
516
- function getResizerClass( resizerPosition ) {
517
- return `ck-widget__resizer__handle-${ resizerPosition }`;
425
+ function getResizerClass(resizerPosition) {
426
+ return `ck-widget__resizer__handle-${resizerPosition}`;
518
427
  }
519
-
520
- function extractCoordinates( event ) {
521
- return {
522
- x: event.pageX,
523
- y: event.pageY
524
- };
428
+ function extractCoordinates(event) {
429
+ return {
430
+ x: event.pageX,
431
+ y: event.pageY
432
+ };
525
433
  }
526
-
527
- function existsInDom( element ) {
528
- return element && element.ownerDocument && element.ownerDocument.contains( element );
434
+ function existsInDom(element) {
435
+ return element && element.ownerDocument && element.ownerDocument.contains(element);
529
436
  }