@ckeditor/ckeditor5-widget 35.2.0 → 35.3.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.
@@ -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
  }