resizerstate.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /**
  2. * @license Copyright (c) 2003-2020, 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/widgetresize/resizerstate
  7. */
  8. import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
  9. import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
  10. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  11. /**
  12. * Stores the internal state of a single resizable object.
  13. *
  14. */
  15. export default class ResizeState {
  16. /**
  17. * @param {module:widget/widgetresize~ResizerOptions} options Resizer options.
  18. */
  19. constructor( options ) {
  20. /**
  21. * The original width (pixels) of the resized object when the resize process was started.
  22. *
  23. * @readonly
  24. * @member {Number} #originalWidth
  25. */
  26. /**
  27. * The original height (pixels) of the resized object when the resize process was started.
  28. *
  29. * @readonly
  30. * @member {Number} #originalHeight
  31. */
  32. /**
  33. * The original width (percents) of the resized object when the resize process was started.
  34. *
  35. * @readonly
  36. * @member {Number} #originalWidthPercents
  37. */
  38. /**
  39. * The position of the handle that initiated the resizing. E.g. `"top-left"`, `"bottom-right"` etc. or `null`
  40. * if unknown.
  41. *
  42. * @readonly
  43. * @observable
  44. * @member {String|null} #activeHandlePosition
  45. */
  46. this.set( 'activeHandlePosition', null );
  47. /**
  48. * The width (percents) proposed, but not committed yet, in the current resize process.
  49. *
  50. * @readonly
  51. * @observable
  52. * @member {Number|null} #proposedWidthPercents
  53. */
  54. this.set( 'proposedWidthPercents', null );
  55. /**
  56. * The width (pixels) proposed, but not committed yet, in the current resize process.
  57. *
  58. * @readonly
  59. * @observable
  60. * @member {Number|null} #proposedWidthPixels
  61. */
  62. this.set( 'proposedWidth', null );
  63. /**
  64. * The height (pixels) proposed, but not committed yet, in the current resize process.
  65. *
  66. * @readonly
  67. * @observable
  68. * @member {Number|null} #proposedHeightPixels
  69. */
  70. this.set( 'proposedHeight', null );
  71. this.set( 'proposedHandleHostWidth', null );
  72. this.set( 'proposedHandleHostHeight', null );
  73. /**
  74. * A width to height ratio of the resized image.
  75. *
  76. * @readonly
  77. * @member {Number} #aspectRatio
  78. */
  79. /**
  80. * @private
  81. * @type {module:widget/widgetresize~ResizerOptions}
  82. */
  83. this._options = options;
  84. /**
  85. * The reference point of the resizer where the dragging started. It is used to measure the distance the user cursor
  86. * traveled, so how much the image should be enlarged.
  87. * This information is only known after the DOM was rendered, so it will be updated later.
  88. *
  89. * @private
  90. * @type {Object}
  91. */
  92. this._referenceCoordinates = null;
  93. }
  94. /**
  95. *
  96. * @param {HTMLElement} domResizeHandle The handle used to calculate the reference point.
  97. * @param {HTMLElement} domHandleHost
  98. * @param {HTMLElement} domResizeHost
  99. */
  100. begin( domResizeHandle, domHandleHost, domResizeHost ) {
  101. const clientRect = new Rect( domHandleHost );
  102. this.activeHandlePosition = getHandlePosition( domResizeHandle );
  103. this._referenceCoordinates = getAbsoluteBoundaryPoint( domHandleHost, getOppositePosition( this.activeHandlePosition ) );
  104. this.originalWidth = clientRect.width;
  105. this.originalHeight = clientRect.height;
  106. this.aspectRatio = clientRect.width / clientRect.height;
  107. const widthStyle = domResizeHost.style.width;
  108. if ( widthStyle && widthStyle.match( /^\d+\.?\d*%$/ ) ) {
  109. this.originalWidthPercents = parseFloat( widthStyle );
  110. } else {
  111. this.originalWidthPercents = calculateHostPercentageWidth( domResizeHost, clientRect );
  112. }
  113. }
  114. update( newSize ) {
  115. this.proposedWidth = newSize.width;
  116. this.proposedHeight = newSize.height;
  117. this.proposedWidthPercents = newSize.widthPercents;
  118. this.proposedHandleHostWidth = newSize.handleHostWidth;
  119. this.proposedHandleHostHeight = newSize.handleHostHeight;
  120. }
  121. }
  122. mix( ResizeState, ObservableMixin );
  123. // Calculates a relative width of a `domResizeHost` compared to it's parent in percents.
  124. //
  125. // @private
  126. // @param {HTMLElement} domResizeHost
  127. // @param {module:utils/dom/rect~Rect} resizeHostRect
  128. // @returns {Number}
  129. function calculateHostPercentageWidth( domResizeHost, resizeHostRect ) {
  130. const domResizeHostParent = domResizeHost.parentElement;
  131. // Need to use computed style as it properly excludes parent's paddings from the returned value.
  132. const parentWidth = parseFloat( domResizeHostParent.ownerDocument.defaultView.getComputedStyle( domResizeHostParent ).width );
  133. return resizeHostRect.width / parentWidth * 100;
  134. }
  135. // Returns coordinates of the top-left corner of an element, relative to the document's top-left corner.
  136. //
  137. // @private
  138. // @param {HTMLElement} element
  139. // @param {String} resizerPosition The position of the resize handle, e.g. `"top-left"`, `"bottom-right"`.
  140. // @returns {Object} return
  141. // @returns {Number} return.x
  142. // @returns {Number} return.y
  143. function getAbsoluteBoundaryPoint( element, resizerPosition ) {
  144. const elementRect = new Rect( element );
  145. const positionParts = resizerPosition.split( '-' );
  146. const ret = {
  147. x: positionParts[ 1 ] == 'right' ? elementRect.right : elementRect.left,
  148. y: positionParts[ 0 ] == 'bottom' ? elementRect.bottom : elementRect.top
  149. };
  150. ret.x += element.ownerDocument.defaultView.scrollX;
  151. ret.y += element.ownerDocument.defaultView.scrollY;
  152. return ret;
  153. }
  154. // @private
  155. // @param {String} resizerPosition The expected resizer position, like `"top-left"`, `"bottom-right"`.
  156. // @returns {String} A prefixed HTML class name for the resizer element.
  157. function getResizerHandleClass( resizerPosition ) {
  158. return `ck-widget__resizer__handle-${ resizerPosition }`;
  159. }
  160. // Determines the position of a given resize handle.
  161. //
  162. // @private
  163. // @param {HTMLElement} domHandle Handle used to calculate the reference point.
  164. // @returns {String|undefined} Returns a string like `"top-left"` or `undefined` if not matched.
  165. function getHandlePosition( domHandle ) {
  166. const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
  167. for ( const position of resizerPositions ) {
  168. if ( domHandle.classList.contains( getResizerHandleClass( position ) ) ) {
  169. return position;
  170. }
  171. }
  172. }
  173. // @private
  174. // @param {String} position Like `"top-left"`.
  175. // @returns {String} Inverted `position`, e.g. it returns `"bottom-right"` if `"top-left"` was given as `position`.
  176. function getOppositePosition( position ) {
  177. const parts = position.split( '-' );
  178. const replacements = {
  179. top: 'bottom',
  180. bottom: 'top',
  181. left: 'right',
  182. right: 'left'
  183. };
  184. return `${ replacements[ parts[ 0 ] ] }-${ replacements[ parts[ 1 ] ] }`;
  185. }