widgetresizer.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module widget/widgetresizer
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import getAncestors from '@ckeditor/ckeditor5-utils/src/dom/getancestors';
  10. import ResizeContext from './resizecontext';
  11. import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
  12. import global from '@ckeditor/ckeditor5-utils/src/dom/global';
  13. const WIDTH_ATTRIBUTE_NAME = 'width';
  14. /**
  15. * Widget resize feature plugin.
  16. *
  17. * Use the {@link module:widget/widgetresizer~WidgetResizer#apply} method to create resizer for a provided widget.
  18. */
  19. export default class WidgetResizer extends Plugin {
  20. /**
  21. * @inheritDoc
  22. */
  23. static get pluginName() {
  24. return 'WidgetResizer';
  25. }
  26. init() {
  27. this.contexts = [];
  28. this.activeContext = null;
  29. this._registerSchema();
  30. const mouseObserverHost = global.window.document;
  31. this._observers = {
  32. mouseMove: Object.create( DomEmitterMixin ),
  33. mouseDownUp: Object.create( DomEmitterMixin ),
  34. };
  35. const mouseMoveListener = ( event, domEventData ) => {
  36. if ( this.activeContext ) {
  37. this.activeContext.updateSize( domEventData );
  38. }
  39. };
  40. this.editor.editing.view.document.on( 'layoutChanged', () => {
  41. // Redrawing on layout change fixes issue with browser window resize or undo causing a mispositioned resizer.
  42. for ( const context of this.contexts ) {
  43. context.redraw();
  44. }
  45. } );
  46. this._observers.mouseDownUp.listenTo( mouseObserverHost, 'mousedown', ( event, domEventData ) => {
  47. const target = domEventData.target;
  48. const resizeHandler = isResizeHandler( target ) ? target : getAncestors( target ).filter( isResizeHandler )[ 0 ];
  49. if ( resizeHandler ) {
  50. this._observers.mouseMove.listenTo( mouseObserverHost, 'mousemove', mouseMoveListener );
  51. this.activeContext = this._getContextByHandler( resizeHandler );
  52. if ( this.activeContext ) {
  53. this.activeContext.begin( resizeHandler );
  54. }
  55. }
  56. } );
  57. const finishResizing = () => {
  58. if ( this.activeContext ) {
  59. this._observers.mouseMove.stopListening( mouseObserverHost, 'mousemove', mouseMoveListener );
  60. if ( this.activeContext ) {
  61. this.activeContext.commit( this.editor );
  62. }
  63. this.activeContext = null;
  64. }
  65. };
  66. this._observers.mouseDownUp.listenTo( mouseObserverHost, 'mouseup', finishResizing );
  67. function isResizeHandler( element ) {
  68. return element.classList && element.classList.contains( 'ck-widget__resizer' );
  69. }
  70. }
  71. /**
  72. * Method that applies a resizer to a given `widgetElement`.
  73. *
  74. * ```js
  75. * conversion.for( 'editingDowncast' ).elementToElement( {
  76. * model: 'image',
  77. * view: ( modelElement, viewWriter ) => {
  78. * const widget = toImageWidget( createImageViewElement( viewWriter ), viewWriter, t( 'image widget' ) );
  79. *
  80. * editor.plugins.get( 'WidgetResizer' ).apply( widget, viewWriter );
  81. *
  82. * return widget;
  83. * }
  84. * } );
  85. * ```
  86. *
  87. * You can use the `options` parameter to customize the behavior of the resizer:
  88. *
  89. * ```js
  90. * conversion.for( 'editingDowncast' ).elementToElement( {
  91. * model: 'image',
  92. * view: ( modelElement, viewWriter ) => {
  93. * const widget = toImageWidget( createImageViewElement( viewWriter ), viewWriter, t( 'image widget' ) );
  94. *
  95. * editor.plugins.get( 'WidgetResizer' ).apply( widget, viewWriter, {
  96. * getResizeHost( wrapper ) {
  97. * return wrapper.querySelector( 'img' );
  98. * },
  99. * getAspectRatio( resizeHost ) {
  100. * return resizeHost.naturalWidth / resizeHost.naturalHeight;
  101. * },
  102. * isCentered( context ) {
  103. * const imageStyle = context._getModel( editor, context.widgetWrapperElement ).getAttribute( 'imageStyle' );
  104. *
  105. * return !imageStyle || imageStyle == 'full';
  106. * }
  107. * } );
  108. *
  109. * return widget;
  110. * }
  111. * } );
  112. * ```
  113. *
  114. * @param {module:engine/view/containerelement~ContainerElement} widgetElement
  115. * @param {module:engine/view/downcastwriter~DowncastWriter} writer
  116. * @param {module:widget/widgetresizer~ResizerOptions} [options] Resizer options.
  117. */
  118. apply( widgetElement, writer, options ) {
  119. const context = new ResizeContext( options );
  120. context.attach( widgetElement, writer );
  121. this.editor.editing.view.once( 'render', () => context.redraw() );
  122. this.contexts.push( context );
  123. }
  124. /**
  125. * Returns a resize context associated with given `domResizeWrapper`.
  126. *
  127. * @param {HTMLElement} domResizeWrapper
  128. */
  129. _getContextByWrapper( domResizeWrapper ) {
  130. for ( const context of this.contexts ) {
  131. if ( domResizeWrapper.isSameNode( context.domResizeWrapper ) ) {
  132. return context;
  133. }
  134. }
  135. }
  136. _getContextByHandler( domResizeHandler ) {
  137. return this._getContextByWrapper( getAncestors( domResizeHandler )
  138. .filter( element => element.classList.contains( 'ck-widget__resizer-wrapper' ) )[ 0 ] );
  139. }
  140. _registerSchema() {
  141. this.editor.model.schema.setAttributeProperties( WIDTH_ATTRIBUTE_NAME, {
  142. isFormatting: true
  143. } );
  144. }
  145. }
  146. /**
  147. * Interface describing a resizer. It allows to specify resizing host, custom logic for calculating aspect ratio etc.
  148. *
  149. * @interface ResizerOptions
  150. */
  151. /**
  152. * Function to explicitly point the resizing host.
  153. *
  154. * By default resizer will use widget wrapper, but it's possible to point any child within widget wrapper.
  155. *
  156. * ```js
  157. * editor.plugins.get( 'WidgetResizer' ).apply( widget, conversionApi.writer, {
  158. * getResizeHost( wrapper ) {
  159. * return wrapper.querySelector( 'img' );
  160. * }
  161. * } );
  162. * ```
  163. *
  164. * @member {Function} module:widget/widgetresizer~ResizerOptions#getResizeHost
  165. */
  166. /**
  167. * @member {Function} module:widget/widgetresizer~ResizerOptions#getAspectRatio
  168. */
  169. /**
  170. * ```js
  171. * editor.plugins.get( 'WidgetResizer' ).apply( widget, conversionApi.writer, {
  172. * isCentered( context ) {
  173. * const imageStyle = context._getModel( editor, context.widgetWrapperElement ).getAttribute( 'imageStyle' );
  174. *
  175. * return !imageStyle || imageStyle == 'full';
  176. * }
  177. * } );
  178. * ```
  179. * @member {Function} module:widget/widgetresizer~ResizerOptions#isCentered
  180. */