-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
contextualballoon.ts
130 lines (112 loc) · 4.48 KB
/
contextualballoon.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/utils/ui/contextualballoon
*/
import { Rect, type PositionOptions } from 'ckeditor5/src/utils';
import { BalloonPanelView, type ContextualBalloon } from 'ckeditor5/src/ui';
import type { Editor } from 'ckeditor5/src/core';
import type { Element, Position, Range } from 'ckeditor5/src/engine';
import { getSelectionAffectedTableWidget, getTableWidgetAncestor } from './widget';
import { getSelectionAffectedTable } from '../common';
const DEFAULT_BALLOON_POSITIONS = BalloonPanelView.defaultPositions;
const BALLOON_POSITIONS = [
DEFAULT_BALLOON_POSITIONS.northArrowSouth,
DEFAULT_BALLOON_POSITIONS.northArrowSouthWest,
DEFAULT_BALLOON_POSITIONS.northArrowSouthEast,
DEFAULT_BALLOON_POSITIONS.southArrowNorth,
DEFAULT_BALLOON_POSITIONS.southArrowNorthWest,
DEFAULT_BALLOON_POSITIONS.southArrowNorthEast,
DEFAULT_BALLOON_POSITIONS.viewportStickyNorth
];
/**
* A helper utility that positions the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} instance
* with respect to the table in the editor content, if one is selected.
*
* @param editor The editor instance.
* @param target Either "cell" or "table". Determines the target the balloon will be attached to.
*/
export function repositionContextualBalloon( editor: Editor, target: string ): void {
const balloon: ContextualBalloon = editor.plugins.get( 'ContextualBalloon' );
const selection = editor.editing.view.document.selection;
let position;
if ( target === 'cell' ) {
if ( getTableWidgetAncestor( selection ) ) {
position = getBalloonCellPositionData( editor );
}
}
else if ( getSelectionAffectedTableWidget( selection ) ) {
position = getBalloonTablePositionData( editor );
}
if ( position ) {
balloon.updatePosition( position );
}
}
/**
* Returns the positioning options that control the geometry of the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
* to the selected table in the editor content.
*
* @param editor The editor instance.
*/
export function getBalloonTablePositionData( editor: Editor ): Partial<PositionOptions> {
const selection = editor.model.document.selection;
const modelTable = getSelectionAffectedTable( selection );
const viewTable = editor.editing.mapper.toViewElement( modelTable )!;
return {
target: editor.editing.view.domConverter.mapViewToDom( viewTable )!,
positions: BALLOON_POSITIONS
};
}
/**
* Returns the positioning options that control the geometry of the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
* to the selected table cell in the editor content.
*
* @param editor The editor instance.
*/
export function getBalloonCellPositionData( editor: Editor ): Partial<PositionOptions> {
const mapper = editor.editing.mapper;
const domConverter = editor.editing.view.domConverter;
const selection = editor.model.document.selection;
if ( selection.rangeCount > 1 ) {
return {
target: () => createBoundingRect( selection.getRanges(), editor ),
positions: BALLOON_POSITIONS
};
}
const modelTableCell = getTableCellAtPosition( selection.getFirstPosition()! );
const viewTableCell = mapper.toViewElement( modelTableCell )!;
return {
target: domConverter.mapViewToDom( viewTableCell ),
positions: BALLOON_POSITIONS
};
}
/**
* Returns the first selected table cell from a multi-cell or in-cell selection.
*
* @param position Document position.
*/
function getTableCellAtPosition( position: Position ): Element {
const isTableCellSelected = position.nodeAfter && position.nodeAfter.is( 'element', 'tableCell' );
return isTableCellSelected ? position.nodeAfter : position.findAncestor( 'tableCell' )!;
}
/**
* Returns bounding rectangle for given model ranges.
*
* @param ranges Model ranges that the bounding rect should be returned for.
* @param editor The editor instance.
*/
function createBoundingRect( ranges: Iterable<Range>, editor: Editor ): Rect {
const mapper = editor.editing.mapper;
const domConverter = editor.editing.view.domConverter;
const rects = Array.from( ranges ).map( range => {
const modelTableCell = getTableCellAtPosition( range.start );
const viewTableCell = mapper.toViewElement( modelTableCell )!;
return new Rect( domConverter.mapViewToDom( viewTableCell )! );
} );
return Rect.getBoundingRect( rects )!;
}