contextualballoon.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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 table/utils/ui/contextualballoon
  7. */
  8. import { centeredBalloonPositionForLongWidgets } from '@ckeditor/ckeditor5-widget/src/utils';
  9. import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
  10. import { getTableWidgetAncestor } from './widget';
  11. import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
  12. const DEFAULT_BALLOON_POSITIONS = BalloonPanelView.defaultPositions;
  13. const BALLOON_POSITIONS = [
  14. DEFAULT_BALLOON_POSITIONS.northArrowSouth,
  15. DEFAULT_BALLOON_POSITIONS.northArrowSouthWest,
  16. DEFAULT_BALLOON_POSITIONS.northArrowSouthEast,
  17. DEFAULT_BALLOON_POSITIONS.southArrowNorth,
  18. DEFAULT_BALLOON_POSITIONS.southArrowNorthWest,
  19. DEFAULT_BALLOON_POSITIONS.southArrowNorthEast
  20. ];
  21. const TABLE_PROPERTIES_BALLOON_POSITIONS = [
  22. ...BALLOON_POSITIONS,
  23. centeredBalloonPositionForLongWidgets
  24. ];
  25. /**
  26. * A helper utility that positions the
  27. * {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} instance
  28. * with respect to the table in the editor content, if one is selected.
  29. *
  30. * @param {module:core/editor/editor~Editor} editor The editor instance.
  31. * @param {String} target Either "cell" or "table". Determines the target the balloon will
  32. * be attached to.
  33. */
  34. export function repositionContextualBalloon( editor, target ) {
  35. const balloon = editor.plugins.get( 'ContextualBalloon' );
  36. if ( getTableWidgetAncestor( editor.editing.view.document.selection ) ) {
  37. let position;
  38. if ( target === 'cell' ) {
  39. position = getBalloonCellPositionData( editor );
  40. } else {
  41. position = getBalloonTablePositionData( editor );
  42. }
  43. balloon.updatePosition( position );
  44. }
  45. }
  46. /**
  47. * Returns the positioning options that control the geometry of the
  48. * {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
  49. * to the selected table in the editor content.
  50. *
  51. * @param {module:core/editor/editor~Editor} editor The editor instance.
  52. * @returns {module:utils/dom/position~Options}
  53. */
  54. export function getBalloonTablePositionData( editor ) {
  55. const firstPosition = editor.model.document.selection.getFirstPosition();
  56. const modelTable = firstPosition.findAncestor( 'table' );
  57. const viewTable = editor.editing.mapper.toViewElement( modelTable );
  58. return {
  59. target: editor.editing.view.domConverter.viewToDom( viewTable ),
  60. positions: TABLE_PROPERTIES_BALLOON_POSITIONS
  61. };
  62. }
  63. /**
  64. * Returns the positioning options that control the geometry of the
  65. * {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
  66. * to the selected table cell in the editor content.
  67. *
  68. * @param {module:core/editor/editor~Editor} editor The editor instance.
  69. * @returns {module:utils/dom/position~Options}
  70. */
  71. export function getBalloonCellPositionData( editor ) {
  72. const mapper = editor.editing.mapper;
  73. const domConverter = editor.editing.view.domConverter;
  74. const selection = editor.model.document.selection;
  75. if ( selection.rangeCount > 1 ) {
  76. return {
  77. target: () => createBoundingRect( selection.getRanges(), modelRange => {
  78. const modelTableCell = getTableCellAtPosition( modelRange.start );
  79. const viewTableCell = mapper.toViewElement( modelTableCell );
  80. return new Rect( domConverter.viewToDom( viewTableCell ) );
  81. } ),
  82. positions: BALLOON_POSITIONS
  83. };
  84. }
  85. const modelTableCell = getTableCellAtPosition( selection.getFirstPosition() );
  86. const viewTableCell = mapper.toViewElement( modelTableCell );
  87. return {
  88. target: domConverter.viewToDom( viewTableCell ),
  89. positions: BALLOON_POSITIONS
  90. };
  91. }
  92. // Returns the first selected table cell from a multi-cell or in-cell selection.
  93. //
  94. // @param {module:engine/model/position~Position} position Document position.
  95. // @returns {module:engine/model/element~Element}
  96. function getTableCellAtPosition( position ) {
  97. const isTableCellSelected = position.nodeAfter && position.nodeAfter.is( 'tableCell' );
  98. return isTableCellSelected ? position.nodeAfter : position.findAncestor( 'tableCell' );
  99. }
  100. // Returns bounding rect for list of rects.
  101. //
  102. // @param {Array.<module:utils/dom/rect~Rect>|Array.<*>} list List of `Rect`s or any list to map by `mapFn`.
  103. // @param {Function} mapFn Mapping function for list elements.
  104. // @returns {module:utils/dom/rect~Rect}
  105. function createBoundingRect( list, mapFn ) {
  106. const rectData = {
  107. left: Number.POSITIVE_INFINITY,
  108. top: Number.POSITIVE_INFINITY,
  109. right: Number.NEGATIVE_INFINITY,
  110. bottom: Number.NEGATIVE_INFINITY
  111. };
  112. for ( const item of list ) {
  113. const rect = mapFn( item );
  114. rectData.left = Math.min( rectData.left, rect.left );
  115. rectData.top = Math.min( rectData.top, rect.top );
  116. rectData.right = Math.max( rectData.right, rect.right );
  117. rectData.bottom = Math.max( rectData.bottom, rect.bottom );
  118. }
  119. rectData.width = rectData.right - rectData.left;
  120. rectData.height = rectData.bottom - rectData.top;
  121. return new Rect( rectData );
  122. }