resizecontext.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
  2. import dragHandlerIcon from '../theme/icons/drag-handler.svg';
  3. const HEIGHT_ATTRIBUTE_NAME = 'height';
  4. /**
  5. * Returns coordinates of top-left corner of a element, relative to the document's top-left corner.
  6. *
  7. * @param {HTMLElement} element
  8. * @returns {Object} return
  9. * @returns {Number} return.x
  10. * @returns {Number} return.y
  11. */
  12. function getAbsolutePosition( element ) {
  13. const nativeRectangle = element.getBoundingClientRect();
  14. return {
  15. x: nativeRectangle.left + element.ownerDocument.defaultView.scrollX,
  16. y: nativeRectangle.top + element.ownerDocument.defaultView.scrollY
  17. };
  18. }
  19. export default class ResizeContext {
  20. constructor() {
  21. // HTMLElement???
  22. this.resizeHost = null;
  23. // view/UiElement
  24. this.resizeWrapperElement = null;
  25. // view/Element
  26. this.widgetWrapperElement = null;
  27. // HTMLElement|null - might be uninitialized
  28. this.domResizeWrapper = null;
  29. this.domResizeShadow = null;
  30. // @todo: ---- options below seems like a little outside of a scope of a single context ----
  31. // Size before resizing.
  32. this.initialSize = {
  33. x: 0,
  34. y: 0
  35. };
  36. // Position of a clicked resize handler in x and y axes.
  37. this.direction = {
  38. y: 'top',
  39. x: 'left'
  40. };
  41. // Reference point of resizer where the dragging started. It is used to measure the distance to user cursor
  42. // traveled, thus how much the image should be enlarged.
  43. // This information is only known after DOM was rendered, so it will be updated later.
  44. this.referenceCoordinates = {
  45. y: 0,
  46. x: 0
  47. };
  48. }
  49. /**
  50. *
  51. * @param {module:engine/view/element~Element} widgetElement Widget's wrapper.
  52. * @param {module:engine/view/downcastwriter~DowncastWriter} writer
  53. */
  54. attach( widgetElement, writer ) {
  55. const that = this;
  56. this.widgetWrapperElement = widgetElement;
  57. this.resizeWrapperElement = writer.createUIElement( 'div', {
  58. class: 'ck ck-widget__resizer-wrapper'
  59. }, function( domDocument ) {
  60. const domElement = this.toDomElement( domDocument );
  61. that.domResizeShadow = that._appendShadowElement( domDocument, domElement );
  62. that._appendResizers( domElement );
  63. that.domResizeWrapper = domElement;
  64. return domElement;
  65. } );
  66. // Append resizer wrapper to the widget's wrapper.
  67. writer.insert( writer.createPositionAt( widgetElement, widgetElement.childCount ), this.resizeWrapperElement );
  68. writer.addClass( [ 'ck-widget_with-resizer' ], widgetElement );
  69. }
  70. /**
  71. *
  72. * @param {HTMLElement} domResizeHandler Handler used to calculate reference point.
  73. */
  74. begin( domResizeHandler ) {
  75. this.domResizeShadow.classList.add( 'ck-widget__resizer-shadow-active' );
  76. this.referenceCoordinates = getAbsolutePosition( domResizeHandler );
  77. }
  78. commit( editor ) {
  79. const modelEntry = this._getModel( editor, this.widgetWrapperElement );
  80. const newHeight = this.domResizeShadow.clientHeight;
  81. this._dismissShadow();
  82. editor.model.change( writer => {
  83. writer.setAttribute( HEIGHT_ATTRIBUTE_NAME, newHeight, modelEntry );
  84. } );
  85. }
  86. cancel() {
  87. this._dismissShadow();
  88. }
  89. destroy() {
  90. this.cancel();
  91. this.domResizeShadow = null;
  92. this.wrapper = null;
  93. }
  94. updateSize( domEventData ) {
  95. const currentCoordinates = this._extractCoordinates( domEventData );
  96. const yDistance = this.referenceCoordinates.y - currentCoordinates.y;
  97. // For top, left handler:
  98. // yDistance > 0 - element is enlarged
  99. // yDistance < 0 - element is shrinked
  100. if ( yDistance > 0 ) {
  101. // console.log( 'enlarging' );
  102. this.domResizeShadow.style.top = ( yDistance * -1 ) + 'px';
  103. } else {
  104. // console.log( 'shrinking' );
  105. this.domResizeShadow.style.top = ( yDistance * -1 ) + 'px';
  106. }
  107. }
  108. redraw() {
  109. if ( this.domResizeWrapper ) {
  110. const resizingHost = this.domResizeWrapper.parentElement.querySelector( 'img' );
  111. this.domResizeWrapper.style.left = resizingHost.offsetLeft + 'px';
  112. this.domResizeWrapper.style.right = resizingHost.offsetLeft + 'px';
  113. }
  114. }
  115. _appendShadowElement( domDocument, domElement ) {
  116. const shadowElement = domDocument.createElement( 'div' );
  117. shadowElement.setAttribute( 'class', 'ck ck-widget__resizer-shadow' );
  118. domElement.appendChild( shadowElement );
  119. return shadowElement;
  120. }
  121. _appendResizers( domElement ) {
  122. const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
  123. for ( const currentPosition of resizerPositions ) {
  124. // Use the IconView from the UI library.
  125. const icon = new IconView();
  126. icon.set( 'content', dragHandlerIcon );
  127. icon.extendTemplate( {
  128. attributes: {
  129. 'class': `ck-widget__resizer ck-widget__resizer-${ currentPosition }`
  130. }
  131. } );
  132. // Make sure icon#element is rendered before passing to appendChild().
  133. icon.render();
  134. domElement.appendChild( icon.element );
  135. }
  136. }
  137. _dismissShadow() {
  138. this.domResizeShadow.classList.remove( 'ck-widget__resizer-shadow-active' );
  139. this.domResizeShadow.removeAttribute( 'style' );
  140. }
  141. /**
  142. *
  143. * @param {module:@ckeditor/ckeditor5-core/src/editor/editor~Editor} editor
  144. * @param {module:@ckeditor/ckeditor5-engine/src/view/element~Element} widgetWrapperElement
  145. * @returns {module:@ckeditor/ckeditor5-engine/src/model/element~Element|undefined}
  146. */
  147. _getModel( editor, widgetWrapperElement ) {
  148. return editor.editing.mapper.toModelElement( widgetWrapperElement );
  149. }
  150. _extractCoordinates( event ) {
  151. return {
  152. x: event.domEvent.pageX,
  153. y: event.domEvent.pageY
  154. };
  155. }
  156. }