8
0

contextualballoon.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module ui/contextualballoon
  7. */
  8. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  9. import BalloonPanelView from './panel/balloon/balloonpanelview';
  10. /**
  11. * Common contextual balloon of the Editor.
  12. *
  13. * This class reuses the same {module:ui/view~View} for each contextual balloon panel in the editor UI, makes
  14. * possible to add multiple panels to the same balloon (stored in the stack, last one in the stack is visible)
  15. * and prevents of displaying more than one contextual balloon panel at the same time.
  16. */
  17. export default class ContextualBalloon {
  18. /**
  19. * Creates ContextualBalloon instance.
  20. *
  21. * @constructor
  22. */
  23. constructor() {
  24. /**
  25. * Balloon panel view.
  26. *
  27. * @readonly
  28. * @member {module:ui/panel/balloon/balloonpanelview~BalloonPanelView} #view
  29. */
  30. this.view = new BalloonPanelView();
  31. /**
  32. * Stack of panels injected to the balloon. Last one in the stack is displayed
  33. * as content of {@link module:ui/contextualballoon~ContextualBalloon#view}.
  34. *
  35. * @private
  36. * @member {Map} #_stack
  37. */
  38. this._stack = new Map();
  39. }
  40. /**
  41. * Returns configuration of currently visible panel or `null` when there is no panel in the stack.
  42. *
  43. * @returns {module:ui/contextualballoon~Panel|null}
  44. */
  45. get visible() {
  46. return this._stack.get( this.view.content.get( 0 ) ) || null;
  47. }
  48. /**
  49. * Returns `true` when panel of given view is in the stack otherwise returns `false`.
  50. *
  51. * @param {module:ui:view~View} view
  52. * @returns {Boolean}
  53. */
  54. isPanelInStack( view ) {
  55. return this._stack.has( view );
  56. }
  57. /**
  58. * Adds panel to the stack and makes this panel visible.
  59. *
  60. * @param {module:ui/contextualballoon~Panel} panelData Configuration of the panel.
  61. */
  62. add( panelData ) {
  63. if ( this._stack.get( panelData.view ) ) {
  64. /**
  65. * Trying to add configuration of the same panel more than once.
  66. *
  67. * @error contextualballoon-add-item-exist
  68. */
  69. throw new CKEditorError( 'contextualballoon-add-panel-exist: Cannot add the same panel twice.' );
  70. }
  71. // When adding panel to the not empty balloon.
  72. if ( this.visible ) {
  73. // Remove displayed content from the view.
  74. this.view.content.remove( this.visible.view );
  75. }
  76. // Add new panel to the stack.
  77. this._stack.set( panelData.view, panelData );
  78. // And display it.
  79. this._showPanel( panelData );
  80. }
  81. /**
  82. * Removes panel of given {@link: module:ui/view~View} from the stack of panels.
  83. * If removed panel was visible then the panel before in the stack will be visible instead.
  84. * When there is no panel in the stack then balloon will hide.
  85. *
  86. * @param {module:ui/view~View} view View of panel which will be removed from the balloon.
  87. */
  88. remove( view ) {
  89. if ( !this._stack.get( view ) ) {
  90. /**
  91. * Trying to remove configuration of the panel not defined in the stack.
  92. *
  93. * @error contextualballoon-remove-panel-not-exist
  94. */
  95. throw new CKEditorError( 'contextualballoon-remove-panel-not-exist: Cannot remove configuration of not existing panel.' );
  96. }
  97. // When visible panel is being removed.
  98. if ( this.visible.view === view ) {
  99. // We need to remove it from the view content.
  100. this.view.content.remove( view );
  101. // And then remove from the stack.
  102. this._stack.delete( view );
  103. // Next we need to check if there is other panel in stack to show.
  104. const lastPanel = Array.from( this._stack ).pop();
  105. // If it is.
  106. if ( lastPanel ) {
  107. // Just show it.
  108. this._showPanel( lastPanel[ 1 ] );
  109. // Otherwise.
  110. } else {
  111. // Hide balloon panel.
  112. this.view.hide();
  113. }
  114. // Otherwise.
  115. } else {
  116. // Just remove given panel from the stack.
  117. this._stack.delete( view );
  118. }
  119. }
  120. /**
  121. * Updates position of balloon panel according to position data
  122. * of the first panel in the {#_stack}.
  123. */
  124. updatePosition() {
  125. this.view.attachTo( this._getBalloonPosition() );
  126. }
  127. /**
  128. * Sets panel as a content of the balloon and attaches balloon using position options of the first panel.
  129. *
  130. * @private
  131. * @param {module:ui/contextualballoon~Panel} panelData Configuration of the panel.
  132. */
  133. _showPanel( panelData ) {
  134. this.view.content.add( panelData.view );
  135. this.view.attachTo( this._getBalloonPosition() );
  136. }
  137. /**
  138. * Returns position options of the first panel in the stack.
  139. * This helps to keep balloon in the same position when panels are changed.
  140. *
  141. * @private
  142. * @returns {module:utils/dom/position~Options}
  143. */
  144. _getBalloonPosition() {
  145. return Array.from( this._stack )[ 0 ][ 1 ].position;
  146. }
  147. }
  148. /**
  149. * An object describing configuration of single panel added to the balloon stack.
  150. *
  151. * @typedef {Object} module:ui/contextualballoon~Panel
  152. *
  153. * @property {module:ui/view~View} view Panel content view.
  154. * @property {module:utils/dom/position~Options} position Positioning options.
  155. */