|
@@ -11,6 +11,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
|
import Resizer from './widgetresize/resizer';
|
|
import Resizer from './widgetresize/resizer';
|
|
|
import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
|
|
import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
|
|
|
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
|
|
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
|
|
|
|
|
+import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
|
|
|
|
|
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
|
|
|
import { throttle } from 'lodash-es';
|
|
import { throttle } from 'lodash-es';
|
|
|
|
|
|
|
|
import '../theme/widgetresize.css';
|
|
import '../theme/widgetresize.css';
|
|
@@ -21,6 +23,7 @@ import '../theme/widgetresize.css';
|
|
|
* Use the {@link module:widget/widgetresize~WidgetResize#attachTo} method to create a resizer for the specified widget.
|
|
* Use the {@link module:widget/widgetresize~WidgetResize#attachTo} method to create a resizer for the specified widget.
|
|
|
*
|
|
*
|
|
|
* @extends module:core/plugin~Plugin
|
|
* @extends module:core/plugin~Plugin
|
|
|
|
|
+ * @mixes module:utils/observablemixin~ObservableMixin
|
|
|
*/
|
|
*/
|
|
|
export default class WidgetResize extends Plugin {
|
|
export default class WidgetResize extends Plugin {
|
|
|
/**
|
|
/**
|
|
@@ -31,11 +34,35 @@ export default class WidgetResize extends Plugin {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
init() {
|
|
init() {
|
|
|
- this.resizers = [];
|
|
|
|
|
- this.activeResizer = null;
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The currently visible resizer.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @protected
|
|
|
|
|
+ * @observable
|
|
|
|
|
+ * @member {module:widget/widgetresize/resizer~Resizer|null} #_visibleResizer
|
|
|
|
|
+ */
|
|
|
|
|
+ this.set( '_visibleResizer', null );
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * References an active resizer.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Active resizer means a resizer which handle is actively used by the end user.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @protected
|
|
|
|
|
+ * @observable
|
|
|
|
|
+ * @member {module:widget/widgetresize/resizer~Resizer|null} #_activeResizer
|
|
|
|
|
+ */
|
|
|
|
|
+ this.set( '_activeResizer', null );
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * A map of resizers created using this plugin instance.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @private
|
|
|
|
|
+ * @type {Map.<module:engine/view/containerelement~ContainerElement, module:widget/widgetresize/resizer~Resizer>}
|
|
|
|
|
+ */
|
|
|
|
|
+ this._resizers = new Map();
|
|
|
|
|
|
|
|
const domDocument = global.window.document;
|
|
const domDocument = global.window.document;
|
|
|
- const THROTTLE_THRESHOLD = 16; // 16ms = ~60fps
|
|
|
|
|
|
|
|
|
|
this.editor.model.schema.setAttributeProperties( 'width', {
|
|
this.editor.model.schema.setAttributeProperties( 'width', {
|
|
|
isFormatting: true
|
|
isFormatting: true
|
|
@@ -50,38 +77,52 @@ export default class WidgetResize extends Plugin {
|
|
|
|
|
|
|
|
const resizeHandle = domEventData.target;
|
|
const resizeHandle = domEventData.target;
|
|
|
|
|
|
|
|
- this.activeResizer = this._getResizerByHandle( resizeHandle );
|
|
|
|
|
|
|
+ this._activeResizer = this._getResizerByHandle( resizeHandle );
|
|
|
|
|
|
|
|
- if ( this.activeResizer ) {
|
|
|
|
|
- this.activeResizer.begin( resizeHandle );
|
|
|
|
|
|
|
+ if ( this._activeResizer ) {
|
|
|
|
|
+ this._activeResizer.begin( resizeHandle );
|
|
|
}
|
|
}
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
- this._observer.listenTo( domDocument, 'mousemove', throttle( ( event, domEventData ) => {
|
|
|
|
|
- if ( this.activeResizer ) {
|
|
|
|
|
- this.activeResizer.updateSize( domEventData );
|
|
|
|
|
|
|
+ this._observer.listenTo( domDocument, 'mousemove', ( event, domEventData ) => {
|
|
|
|
|
+ if ( this._activeResizer ) {
|
|
|
|
|
+ this._activeResizer.updateSize( domEventData );
|
|
|
}
|
|
}
|
|
|
- }, THROTTLE_THRESHOLD ) );
|
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
|
|
this._observer.listenTo( domDocument, 'mouseup', () => {
|
|
this._observer.listenTo( domDocument, 'mouseup', () => {
|
|
|
- if ( this.activeResizer ) {
|
|
|
|
|
- this.activeResizer.commit();
|
|
|
|
|
|
|
+ if ( this._activeResizer ) {
|
|
|
|
|
+ this._activeResizer.commit();
|
|
|
|
|
|
|
|
- this.activeResizer = null;
|
|
|
|
|
|
|
+ this._activeResizer = null;
|
|
|
}
|
|
}
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
- const redrawResizers = throttle( () => {
|
|
|
|
|
- for ( const context of this.resizers ) {
|
|
|
|
|
- context.redraw();
|
|
|
|
|
|
|
+ const redrawFocusedResizer = () => {
|
|
|
|
|
+ if ( this._visibleResizer ) {
|
|
|
|
|
+ this._visibleResizer.redraw();
|
|
|
}
|
|
}
|
|
|
- }, THROTTLE_THRESHOLD );
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const redrawFocusedResizerThrottled = throttle( redrawFocusedResizer, 200 ); // 5fps
|
|
|
|
|
+
|
|
|
|
|
+ // Redraws occurring upon a change of visible resizer must not be throttled, as it is crucial for the initial
|
|
|
|
|
+ // render. Without it the resizer frame would be misaligned with resizing host for a fraction of second.
|
|
|
|
|
+ this.on( 'change:_visibleResizer', redrawFocusedResizer );
|
|
|
|
|
|
|
|
// Redrawing on any change of the UI of the editor (including content changes).
|
|
// Redrawing on any change of the UI of the editor (including content changes).
|
|
|
- this.editor.ui.on( 'update', redrawResizers );
|
|
|
|
|
|
|
+ this.editor.ui.on( 'update', redrawFocusedResizerThrottled );
|
|
|
|
|
|
|
|
// Resizers need to be redrawn upon window resize, because new window might shrink resize host.
|
|
// Resizers need to be redrawn upon window resize, because new window might shrink resize host.
|
|
|
- this._observer.listenTo( global.window, 'resize', redrawResizers );
|
|
|
|
|
|
|
+ this._observer.listenTo( global.window, 'resize', redrawFocusedResizerThrottled );
|
|
|
|
|
+
|
|
|
|
|
+ const viewSelection = this.editor.editing.view.document.selection;
|
|
|
|
|
+
|
|
|
|
|
+ viewSelection.on( 'change', () => {
|
|
|
|
|
+ const selectedElement = viewSelection.getSelectedElement();
|
|
|
|
|
+
|
|
|
|
|
+ this._visibleResizer = this._getResizerByViewElement( selectedElement ) || null;
|
|
|
|
|
+ } );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
destroy() {
|
|
@@ -97,22 +138,40 @@ export default class WidgetResize extends Plugin {
|
|
|
|
|
|
|
|
resizer.attach();
|
|
resizer.attach();
|
|
|
|
|
|
|
|
- this.editor.editing.view.once( 'render', () => resizer.redraw() );
|
|
|
|
|
-
|
|
|
|
|
- this.resizers.push( resizer );
|
|
|
|
|
|
|
+ this._resizers.set( options.viewElement, resizer );
|
|
|
|
|
|
|
|
return resizer;
|
|
return resizer;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Returns a resizer that contains a given resize handle.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @protected
|
|
|
|
|
+ * @param {HTMLElement} domResizeHandle
|
|
|
|
|
+ * @returns {module:widget/widgetresize/resizer~Resizer}
|
|
|
|
|
+ */
|
|
|
_getResizerByHandle( domResizeHandle ) {
|
|
_getResizerByHandle( domResizeHandle ) {
|
|
|
- for ( const resizer of this.resizers ) {
|
|
|
|
|
|
|
+ for ( const resizer of this._resizers.values() ) {
|
|
|
if ( resizer.containsHandle( domResizeHandle ) ) {
|
|
if ( resizer.containsHandle( domResizeHandle ) ) {
|
|
|
return resizer;
|
|
return resizer;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Returns a resizer created for a given view element (widget element).
|
|
|
|
|
+ *
|
|
|
|
|
+ * @protected
|
|
|
|
|
+ * @param {module:engine/view/containerelement~ContainerElement} viewElement
|
|
|
|
|
+ * @returns {module:widget/widgetresize/resizer~Resizer}
|
|
|
|
|
+ */
|
|
|
|
|
+ _getResizerByViewElement( viewElement ) {
|
|
|
|
|
+ return this._resizers.get( viewElement );
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+mix( WidgetResize, ObservableMixin );
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Interface describing a resizer. It allows to specify the resizing host, custom logic for calculating aspect ratio, etc.
|
|
* Interface describing a resizer. It allows to specify the resizing host, custom logic for calculating aspect ratio, etc.
|
|
|
*
|
|
*
|