balloonpanelview.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module ui/balloonpanel/balloonpanelview
  7. */
  8. /* globals document */
  9. import View from '../view';
  10. import Template from '../template';
  11. import { getOptimalPosition } from '@ckeditor/ckeditor5-utils/src/dom/position';
  12. import toUnit from '@ckeditor/ckeditor5-utils/src/dom/tounit';
  13. const toPx = toUnit( 'px' );
  14. /**
  15. * The balloon panel view class.
  16. *
  17. * @extends module:ui/view~View
  18. */
  19. export default class BalloonPanelView extends View {
  20. /**
  21. * @inheritDoc
  22. */
  23. constructor( locale ) {
  24. super( locale );
  25. const bind = this.bindTemplate;
  26. /**
  27. * The absolute top position of the balloon panel in pixels.
  28. *
  29. * @observable
  30. * @default 0
  31. * @member {Number} #top
  32. */
  33. this.set( 'top', 0 );
  34. /**
  35. * The absolute left position of the balloon panel in pixels.
  36. *
  37. * @observable
  38. * @default 0
  39. * @member {Number} #left
  40. */
  41. this.set( 'left', 0 );
  42. /**
  43. * Balloon panel's current position. The position name is reflected in the CSS class set
  44. * to the balloon, i.e. `.ck-balloon-panel_arrow_se` for "se" position. The class
  45. * controls the minor aspects of the balloon's visual appearance like placement
  46. * of the "arrow". To support a new position, an additional CSS must be created.
  47. *
  48. * Default position names correspond with {@link #defaultPositions}.
  49. *
  50. * @observable
  51. * @default 'se'
  52. * @member {'se'|'sw'|'ne'|'nw'} #position
  53. */
  54. this.set( 'position', 'se' );
  55. /**
  56. * Controls whether the balloon panel is visible or not.
  57. *
  58. * @observable
  59. * @default false
  60. * @member {Boolean} #isVisible
  61. */
  62. this.set( 'isVisible', false );
  63. /**
  64. * Max width of the balloon panel, as in CSS.
  65. *
  66. * @observable
  67. * @member {Number} #maxWidth
  68. */
  69. /**
  70. * Collection of the child views which creates balloon panel contents.
  71. *
  72. * @readonly
  73. * @member {module:ui/viewcollection~ViewCollection}
  74. */
  75. this.content = this.createCollection();
  76. this.template = new Template( {
  77. tag: 'div',
  78. attributes: {
  79. class: [
  80. 'ck-balloon-panel',
  81. bind.to( 'position', ( value ) => `ck-balloon-panel_arrow_${ value }` ),
  82. bind.if( 'isVisible', 'ck-balloon-panel_visible' )
  83. ],
  84. style: {
  85. top: bind.to( 'top', toPx ),
  86. left: bind.to( 'left', toPx ),
  87. maxWidth: bind.to( 'maxWidth', toPx )
  88. },
  89. // Make this element `focusable` to be available for adding to FocusTracker.
  90. tabindex: -1
  91. },
  92. children: this.content
  93. } );
  94. }
  95. /**
  96. * Shows the balloon panel.
  97. *
  98. * See {@link #isVisible}.
  99. */
  100. show() {
  101. this.isVisible = true;
  102. }
  103. /**
  104. * Hides the balloon panel.
  105. *
  106. * See {@link #isVisible}.
  107. */
  108. hide() {
  109. this.isVisible = false;
  110. }
  111. /**
  112. * Attaches the balloon panel to a specified DOM element or range with a smart heuristics.
  113. *
  114. * See {@link @link module:utils/dom/position~getOptimalPosition}.
  115. *
  116. * TODO: More docs and examples.
  117. *
  118. * @param {module:utils/dom/position~Options} options Positioning options compatible with
  119. * {@link module:utils/dom/position~getOptimalPosition}. Default `positions` array is
  120. * {@link module:ui/balloonpanel/balloonpanelview~BalloonPanelView.defaultPositions}.
  121. */
  122. attachTo( options ) {
  123. this.show();
  124. const defaultPositions = BalloonPanelView.defaultPositions;
  125. const positionOptions = Object.assign( {}, {
  126. element: this.element,
  127. positions: [
  128. defaultPositions.se,
  129. defaultPositions.sw,
  130. defaultPositions.ne,
  131. defaultPositions.nw
  132. ],
  133. limiter: document.body,
  134. fitInViewport: true
  135. }, options );
  136. const { top, left, name: position } = getOptimalPosition( positionOptions );
  137. Object.assign( this, { top, left, position } );
  138. }
  139. }
  140. /**
  141. * A horizontal offset of the arrow tip from the edge of the balloon. Controlled by CSS.
  142. *
  143. * +-----|---------...
  144. * | |
  145. * | |
  146. * | |
  147. * | |
  148. * +--+ | +------...
  149. * \ | /
  150. * \|/
  151. * >|-----|<---------------- horizontal offset
  152. *
  153. * @default 30
  154. * @member {Number} module:ui/balloonpanel/balloonpanelview~BalloonPanelView.arrowHorizontalOffset
  155. */
  156. BalloonPanelView.arrowHorizontalOffset = 30;
  157. /**
  158. * A vertical offset of the arrow from the edge of the balloon. Controlled by CSS.
  159. *
  160. * +-------------...
  161. * |
  162. * |
  163. * | /-- vertical offset
  164. * | V
  165. * +--+ +-----... ---------
  166. * \ / |
  167. * \/ |
  168. * -------------------------------
  169. * ^
  170. *
  171. * @default 15
  172. * @member {Number} module:ui/balloonpanel/balloonpanelview~BalloonPanelView.arrowVerticalOffset
  173. */
  174. BalloonPanelView.arrowVerticalOffset = 15;
  175. /**
  176. * A default set of positioning functions used by the balloon panel view
  177. * when attaching using {@link #attachTo} method.
  178. *
  179. * The available positioning functions are as follows:
  180. *
  181. * * South east:
  182. *
  183. * [ Target ]
  184. * ^
  185. * +-----------------+
  186. * | Balloon |
  187. * +-----------------+
  188. *
  189. *
  190. * * South west:
  191. *
  192. * [ Target ]
  193. * ^
  194. * +-----------------+
  195. * | Balloon |
  196. * +-----------------+
  197. *
  198. *
  199. * * North east:
  200. *
  201. * +-----------------+
  202. * | Balloon |
  203. * +-----------------+
  204. * V
  205. * [ Target ]
  206. *
  207. *
  208. * * North west:
  209. *
  210. * +-----------------+
  211. * | Balloon |
  212. * +-----------------+
  213. * V
  214. * [ Target ]
  215. *
  216. * See {@link #attachTo}.
  217. *
  218. * Positioning functions must be compatible with {@link module:utils/dom/position~Position}.
  219. *
  220. * The name that position function returns will be reflected in balloon panel's class that
  221. * controls the placement of the "arrow". See {@link #position} to learn more.
  222. *
  223. * @member {Object} module:ui/balloonpanel/balloonpanelview~BalloonPanelView.defaultPositions
  224. */
  225. BalloonPanelView.defaultPositions = {
  226. se: ( targetRect ) => ( {
  227. top: targetRect.bottom + BalloonPanelView.arrowVerticalOffset,
  228. left: targetRect.left + targetRect.width / 2 - BalloonPanelView.arrowHorizontalOffset,
  229. name: 'se'
  230. } ),
  231. sw: ( targetRect, balloonRect ) => ( {
  232. top: targetRect.bottom + BalloonPanelView.arrowVerticalOffset,
  233. left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
  234. name: 'sw'
  235. } ),
  236. ne: ( targetRect, balloonRect ) => ( {
  237. top: targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset,
  238. left: targetRect.left + targetRect.width / 2 - BalloonPanelView.arrowHorizontalOffset,
  239. name: 'ne'
  240. } ),
  241. nw: ( targetRect, balloonRect ) => ( {
  242. top: targetRect.top - balloonRect.height - BalloonPanelView.arrowVerticalOffset,
  243. left: targetRect.left + targetRect.width / 2 - balloonRect.width + BalloonPanelView.arrowHorizontalOffset,
  244. name: 'nw'
  245. } )
  246. };