| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
- import dragHandlerIcon from '../theme/icons/drag-handler.svg';
- /**
- * @module widget/resizecontext
- */
- const HEIGHT_ATTRIBUTE_NAME = 'height';
- /**
- * Returns coordinates of top-left corner of a element, relative to the document's top-left corner.
- *
- * @param {HTMLElement} element
- * @param {String} resizerPosition Position of the resize handler, e.g. `"top-left"`, `"bottom-right"`.
- * @returns {Object} return
- * @returns {Number} return.x
- * @returns {Number} return.y
- */
- function getAbsoluteBoundaryPoint( element, resizerPosition ) {
- const nativeRectangle = element.getBoundingClientRect();
- const positionParts = resizerPosition.split( '-' );
- const ret = {
- x: positionParts[ 1 ] == 'right' ? nativeRectangle.right : nativeRectangle.left,
- y: positionParts[ 0 ] == 'bottom' ? nativeRectangle.bottom : nativeRectangle.top
- };
- ret.x += element.ownerDocument.defaultView.scrollX;
- ret.y += element.ownerDocument.defaultView.scrollY;
- return ret;
- }
- function getAspectRatio( element ) {
- const nativeRectangle = element.getBoundingClientRect();
- return nativeRectangle.width / nativeRectangle.height;
- }
- /**
- * Stores the internal state of a single resizable object.
- *
- * @class ResizeContext
- */
- export default class ResizeContext {
- constructor( options ) {
- // HTMLElement??? - @todo seems to be not needed.
- // this.resizeHost = null;
- // view/UiElement
- this.resizeWrapperElement = null;
- // view/Element
- this.widgetWrapperElement = null;
- /**
- * Container of entire resize UI.
- *
- * Note that this property is initialized only after the element bound with resizer is drawn
- * so it will be a `null` when uninitialized.
- *
- * @member {HTMLElement|null}
- */
- this.domResizeWrapper = null;
- /**
- * @member {HTMLElement|null}
- */
- this.domResizeShadow = null;
- this.options = options || {};
- // @todo: ---- options below seems like a little outside of a scope of a single context ----
- // Size before resizing.
- this.initialSize = {
- x: 0,
- y: 0
- };
- // Position of a clicked resize handler in x and y axes.
- this.direction = {
- y: 'top',
- x: 'left'
- };
- // Reference point of resizer where the dragging started. It is used to measure the distance to user cursor
- // traveled, thus how much the image should be enlarged.
- // This information is only known after DOM was rendered, so it will be updated later.
- this.referenceCoordinates = {
- y: 0,
- x: 0
- };
- }
- /**
- *
- * @param {module:engine/view/element~Element} widgetElement Widget's wrapper.
- * @param {module:engine/view/downcastwriter~DowncastWriter} writer
- */
- attach( widgetElement, writer ) {
- const that = this;
- this.widgetWrapperElement = widgetElement;
- this.resizeWrapperElement = writer.createUIElement( 'div', {
- class: 'ck ck-widget__resizer-wrapper'
- }, function( domDocument ) {
- const domElement = this.toDomElement( domDocument );
- that.domResizeShadow = that._appendShadowElement( domDocument, domElement );
- that._appendResizers( domElement );
- that.domResizeWrapper = domElement;
- return domElement;
- } );
- // Append resizer wrapper to the widget's wrapper.
- writer.insert( writer.createPositionAt( widgetElement, widgetElement.childCount ), this.resizeWrapperElement );
- writer.addClass( [ 'ck-widget_with-resizer' ], widgetElement );
- }
- /**
- *
- * @param {HTMLElement} domResizeHandler Handler used to calculate reference point.
- */
- begin( domResizeHandler ) {
- const resizeHost = this._getResizeHost();
- this.domResizeShadow.classList.add( 'ck-widget__resizer-shadow-active' );
- /**
- * Position of the handler that has initiated the resizing. E.g. `"top-left"`, `"bottom-right"` etc of `null`
- * if unknown.
- *
- * @member {String|null}
- */
- this.referenceHandlerPosition = this._getResizerPosition( domResizeHandler );
- const reversedPosition = this._invertPosition( this.referenceHandlerPosition );
- this.referenceCoordinates = getAbsoluteBoundaryPoint( resizeHost, reversedPosition );
- if ( resizeHost ) {
- this.aspectRatio = getAspectRatio( resizeHost, this.referenceHandlerPosition );
- }
- }
- commit( editor ) {
- const modelEntry = this._getModel( editor, this.widgetWrapperElement );
- const newHeight = this.domResizeShadow.clientHeight;
- this._dismissShadow();
- editor.model.change( writer => {
- writer.setAttribute( HEIGHT_ATTRIBUTE_NAME, newHeight, modelEntry );
- } );
- this.redraw();
- // Again, render will most likely change image size, so resizers needs a redraw.
- editor.editing.view.once( 'render', () => this.redraw() );
- this.referenceHandlerPosition = null;
- }
- cancel() {
- this._dismissShadow();
- this.referenceHandlerPosition = null;
- }
- destroy() {
- this.cancel();
- this.domResizeShadow = null;
- this.wrapper = null;
- this.referenceHandlerPosition = null;
- }
- updateSize( domEventData ) {
- const currentCoordinates = this._extractCoordinates( domEventData );
- const proposedSize = {
- x: Math.abs( currentCoordinates.x - this.referenceCoordinates.x ),
- y: Math.abs( currentCoordinates.y - this.referenceCoordinates.y )
- };
- // Dominant determination must take the ratio into account.
- proposedSize.dominant = proposedSize.x / this.aspectRatio > proposedSize.y ? 'x' : 'y';
- proposedSize.max = proposedSize[ proposedSize.dominant ];
- const drawnSize = {
- x: proposedSize.x,
- y: proposedSize.y
- };
- if ( proposedSize.dominant == 'x' ) {
- drawnSize.y = drawnSize.x / this.aspectRatio;
- } else {
- drawnSize.x = drawnSize.y * this.aspectRatio;
- }
- // Reset shadow bounding.
- this.domResizeShadow.style.top = 0;
- this.domResizeShadow.style.left = 0;
- this.domResizeShadow.style.bottom = 0;
- this.domResizeShadow.style.right = 0;
- this.domResizeShadow.style[ this.referenceHandlerPosition.split( '-' )[ 0 ] ] = 'auto';
- this.domResizeShadow.style[ this.referenceHandlerPosition.split( '-' )[ 1 ] ] = 'auto';
- // Apply the actual shadow dimensions.
- this.domResizeShadow.style.width = `${ drawnSize.x }px`;
- this.domResizeShadow.style.height = `${ drawnSize.y }px`;
- }
- redraw() {
- if ( this.domResizeWrapper ) {
- const widgetWrapper = this.domResizeWrapper.parentElement;
- const resizingHost = this._getResizeHost();
- if ( !widgetWrapper.isSameNode( resizingHost ) ) {
- this.domResizeWrapper.style.left = resizingHost.offsetLeft + 'px';
- this.domResizeWrapper.style.right = resizingHost.offsetLeft + 'px';
- }
- }
- }
- _getResizeHost() {
- const widgetWrapper = this.domResizeWrapper.parentElement;
- return this.options.getResizeHost ?
- this.options.getResizeHost( widgetWrapper ) : widgetWrapper;
- }
- _appendShadowElement( domDocument, domElement ) {
- const shadowElement = domDocument.createElement( 'div' );
- shadowElement.setAttribute( 'class', 'ck ck-widget__resizer-shadow' );
- domElement.appendChild( shadowElement );
- return shadowElement;
- }
- _appendResizers( domElement ) {
- const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
- for ( const currentPosition of resizerPositions ) {
- // Use the IconView from the UI library.
- const icon = new IconView();
- icon.set( 'content', dragHandlerIcon );
- icon.extendTemplate( {
- attributes: {
- 'class': `ck-widget__resizer ${ this._getResizerClass( currentPosition ) }`
- }
- } );
- // Make sure icon#element is rendered before passing to appendChild().
- icon.render();
- domElement.appendChild( icon.element );
- }
- }
- _dismissShadow() {
- this.domResizeShadow.classList.remove( 'ck-widget__resizer-shadow-active' );
- this.domResizeShadow.removeAttribute( 'style' );
- }
- /**
- *
- * @param {module:@ckeditor/ckeditor5-core/src/editor/editor~Editor} editor
- * @param {module:@ckeditor/ckeditor5-engine/src/view/element~Element} widgetWrapperElement
- * @returns {module:@ckeditor/ckeditor5-engine/src/model/element~Element|undefined}
- */
- _getModel( editor, widgetWrapperElement ) {
- return editor.editing.mapper.toModelElement( widgetWrapperElement );
- }
- _extractCoordinates( event ) {
- return {
- x: event.domEvent.pageX,
- y: event.domEvent.pageY
- };
- }
- /**
- * @private
- * @param {String} resizerPosition Expected resizer position like `"top-left"`, `"bottom-right"`.
- * @returns {String} A prefixed HTML class name for the resizer element
- */
- _getResizerClass( resizerPosition ) {
- return `ck-widget__resizer-${ resizerPosition }`;
- }
- /**
- * Determines the position of a given resize handler.
- *
- * @private
- * @param {HTMLElement} domResizeHandler Handler used to calculate reference point.
- * @returns {String|undefined} Returns a string like `"top-left"` or `undefined` if not matched.
- */
- _getResizerPosition( domResizeHandler ) {
- const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
- for ( const position of resizerPositions ) {
- if ( domResizeHandler.classList.contains( this._getResizerClass( position ) ) ) {
- return position;
- }
- }
- }
- /**
- * @param {String} position Like `"top-left"`.
- * @returns {String} Inverted `position`.
- */
- _invertPosition( position ) {
- const parts = position.split( '-' );
- const replacements = {
- top: 'bottom',
- bottom: 'top',
- left: 'right',
- right: 'left'
- };
- return `${ replacements[ parts[ 0 ] ] }-${ replacements[ parts[ 1 ] ] }`;
- }
- }
|