@syncfusion/ej2-richtexteditor
Version:
Essential JS 2 RichTextEditor component
1,034 lines (1,028 loc) • 51.2 kB
JavaScript
import { detach, append, selectAll, select, isNullOrUndefined as isNOU, closest } from '@syncfusion/ej2-base';
import { addClass, removeClass, Browser, setStyleAttribute } from '@syncfusion/ej2-base';
import { Tooltip } from '@syncfusion/ej2-popups';
import * as events from '../base/constant';
import * as classes from '../base/classes';
import { setToolbarStatus, updateUndoRedoStatus } from '../base/util';
import { BaseToolbar } from './base-toolbar';
import { DropDownButtons } from './dropdown-buttons';
import { ColorPickerInput } from './color-picker';
import { QuickPopupRenderer } from '../renderer/quick-popup-renderer';
import { isIDevice } from '../../common/util';
/**
* `Quick toolbar` module is used to handle Quick toolbar actions.
*/
var BaseQuickToolbar = /** @class */ (function () {
function BaseQuickToolbar(type, parent, locator) {
this.previousToolbarStatus = {};
this.parent = parent;
this.locator = locator;
this.isRendered = false;
this.isDestroyed = false;
this.type = type;
this.dropDownButtons = new DropDownButtons(this.parent, this.locator);
this.colorPickerObj = new ColorPickerInput(this.parent, this.locator);
this.popupWidth = null;
this.popupHeight = null;
this.tipPointerHeight = this.parent.userAgentData.isMobileDevice() ? 16 : 10;
}
BaseQuickToolbar.prototype.appendToolbarElement = function () {
this.toolbarElement = this.parent.createElement('div', { className: classes.CLS_QUICK_TB });
switch (this.type) {
case 'Image':
this.toolbarElement.classList.add(classes.CLS_IMG_QUICK_TB);
break;
case 'Link':
this.toolbarElement.classList.add(classes.CLS_LINK_QUICK_TB);
break;
case 'Table':
this.toolbarElement.classList.add(classes.CLS_TABLE_QUICK_TB);
break;
case 'Audio':
this.toolbarElement.classList.add(classes.CLS_AUDIO_QUICK_TB);
break;
case 'Video':
this.toolbarElement.classList.add(classes.CLS_VIDEO_QUICK_TB);
break;
case 'Text':
this.toolbarElement.classList.add(classes.CLS_TEXT_QUICK_TB);
break;
case 'Inline':
this.toolbarElement.classList.add(classes.CLS_TOOLBAR);
this.toolbarElement.classList.add(classes.CLS_INLINE_TOOLBAR);
break;
}
this.popupObj.element.appendChild(this.toolbarElement);
};
/**
* render method
*
* @param {IQuickToolbarOptions} args - specifies the arguments
* @returns {void}
* @hidden
*/
BaseQuickToolbar.prototype.render = function (args) {
this.stringItems = args.toolbarItems;
var quickPopupRenderer = new QuickPopupRenderer(this.parent);
this.popupObj = quickPopupRenderer.renderPopup(args.popupType);
this.element = this.popupObj.element;
this.tipPointerElem = this.element.querySelector('.e-rte-tip-pointer');
this.appendToolbarElement();
this.createToolbar(args.toolbarItems, args.mode, args.cssClass);
this.addEventListener();
this.addCSSClass();
};
BaseQuickToolbar.prototype.createToolbar = function (items, mode, cssClass) {
this.quickTBarObj = new BaseToolbar(this.parent, this.locator);
this.quickTBarObj.render({
container: 'quick',
target: this.toolbarElement,
items: items,
mode: mode,
cssClass: cssClass
});
};
/**
* Show the Quick toolbar popup.
*
* @param {Element} target - The target element relative to which the Quick toolbar is opened.
* @param {MouseEvent | KeyboardEvent} [originalEvent] - The original event causing the Quick toolbar to open.
* @returns {void}
*/
BaseQuickToolbar.prototype.showPopup = function (target, originalEvent) {
var _this = this;
var selection = this.parent.formatter.editorManager.nodeSelection.get(this.parent.inputElement.ownerDocument);
if (isNOU(selection) && selection.rangeCount < 0) {
return;
}
this.renderSubComponents(target);
var relativeElem = this.getRelativeElement(selection, target);
if (relativeElem === null) {
return;
}
var iframeRect;
if (this.parent.iframeSettings.enable) {
iframeRect = this.parent.contentModule.getPanel().getBoundingClientRect();
}
var range = selection.getRangeAt(0);
var clientRects = range.getClientRects();
var direction = this.getSelectionDirection(selection);
var triggerType = isNOU(originalEvent) ? 'none' : originalEvent.type;
if (triggerType === 'mouseup' && originalEvent.detail && originalEvent.detail === 3) {
triggerType = 'trippleclick';
}
var rangeDomRect = clientRects.length === 0 ? range.getBoundingClientRect() :
direction === 'Backward' ? clientRects[0] : clientRects[clientRects.length - 1];
var offsetCalculationParam = {
blockElement: relativeElem,
blockRect: relativeElem.getBoundingClientRect(),
range: range,
rangeRect: rangeDomRect,
iframeRect: iframeRect,
contentPanelElement: this.parent.contentModule.getPanel(),
editPanelDomRect: this.parent.contentModule.getEditPanel().getBoundingClientRect(),
direction: direction,
type: triggerType
};
this.popupWidth = this.getPopupDimension(this.popupObj, 'width');
this.popupHeight = this.getPopupDimension(this.popupObj, 'height');
this.toolbarHeight = this.parent.getToolbarElement().getBoundingClientRect().height;
var offsetX = this.calculateOffsetX(offsetCalculationParam);
var offsetY = this.calculateOffsetY(offsetCalculationParam);
var eventArgs = {
popup: this.popupObj, cancel: false, targetElement: relativeElem,
type: triggerType, positionX: offsetX, positionY: offsetY
};
this.enableDisableToolbarItems();
eventArgs = this.handleVerticalCollision(offsetCalculationParam, eventArgs);
this.setTipPointerPostion(this.currentTipPosition);
if (this.type === 'Audio' || this.type === 'Image' || this.type === 'Video') {
if (this.currentTipPosition === 'Bottom-Center' || this.currentTipPosition === 'Bottom-Left' || this.currentTipPosition === 'Bottom-Right') {
eventArgs.positionY = eventArgs.positionY - 2; // Tip should be above the Outline of the media elements.
}
else if (this.currentTipPosition === 'Top-Center' || this.currentTipPosition === 'Top-Left' || this.currentTipPosition === 'Top-Right') {
eventArgs.positionY = eventArgs.positionY + 2; // Tip should be above the Outline of the media elements.
}
}
this.parent.trigger(events.beforeQuickToolbarOpen, eventArgs, function (beforeQuickToolbarArgs) {
if (!beforeQuickToolbarArgs.cancel) {
var popupProps = {
offsetX: beforeQuickToolbarArgs.positionX,
offsetY: beforeQuickToolbarArgs.positionY,
relateTo: beforeQuickToolbarArgs.targetElement
};
_this.popupObj.setProperties(popupProps);
_this.popupObj.dataBind();
removeClass([_this.element], [classes.CLS_HIDE, classes.CLS_POPUP_OPEN]);
_this.popupObj.show();
_this.isRendered = true;
_this.previousTarget = target;
}
});
};
BaseQuickToolbar.prototype.renderSubComponents = function (target) {
addClass([this.element], [classes.CLS_HIDE]);
if (this.parent.iframeSettings.enable) {
append([this.element], this.parent.rootContainer.querySelector('.' + classes.CLS_RTE_IFRAME_CONTENT));
}
else {
append([this.element], this.parent.contentModule.getPanel());
}
this.dropDownButtons.renderDropDowns({ container: this.toolbarElement, containerType: 'quick', items: this.stringItems }, target);
if (this.type === 'Text' && this.dropDownButtons) {
if (this.dropDownButtons.formatDropDown) {
var content = ('<span style="display: inline-flex;' + 'width:' + this.parent.format.width + '" >' +
'<span class="e-rte-dropdown-btn-text></span></span>');
this.dropDownButtons.formatDropDown.setProperties({ content: content });
this.dropDownButtons.formatDropDown.dataBind();
}
if (this.dropDownButtons.fontSizeDropDown) {
var content = ('<span style="display: inline-flex;' + 'width:' + this.parent.fontSize.width + '" >' +
'<span class="e-rte-dropdown-btn-text></span></span>');
this.dropDownButtons.fontSizeDropDown.setProperties({ content: content });
this.dropDownButtons.fontSizeDropDown.dataBind();
}
if (this.dropDownButtons.fontNameDropDown) {
var content = ('<span style="display: inline-flex;' + 'width:' + this.parent.fontFamily.width + '" >' +
'<span class="e-rte-dropdown-btn-text></span></span>');
this.dropDownButtons.fontNameDropDown.setProperties({ content: content });
this.dropDownButtons.fontNameDropDown.dataBind();
}
}
this.colorPickerObj.renderColorPickerInput({ container: this.toolbarElement, containerType: 'quick', items: this.stringItems });
if (this.parent.showTooltip) {
this.tooltip = new Tooltip({
target: '#' + this.element.id + ' [title]',
openDelay: 400,
showTipPointer: true,
beforeRender: this.tooltipBeforeRender.bind(this),
windowCollision: true,
position: 'BottomCenter',
cssClass: this.parent.getCssClass()
});
this.tooltip.appendTo(this.toolbarElement);
}
if (this.element.style.maxWidth !== '75%') {
setStyleAttribute(this.element, { maxWidth: '75%' });
}
};
BaseQuickToolbar.prototype.tooltipBeforeRender = function (args) {
if (args.target.querySelector('.e-active')) {
args.cancel = true;
if (!isNOU(args.target.getAttribute('title'))) {
this.parent.notify(events.closeTooltip, { target: args.target, isTitle: true });
}
}
};
/**
* The method to hide the Quick toolbar.
*
* @returns {void}
* @hidden
*/
BaseQuickToolbar.prototype.hidePopup = function () {
var isSourceCodeEnabled = !isNOU(this.parent.rootContainer) && this.parent.rootContainer.classList.contains('e-source-code-enabled');
if (Browser.isDevice && !isIDevice()) {
removeClass([this.parent.getToolbar()], [classes.CLS_HIDE]);
}
if (!isNOU(this.element.querySelectorAll('[data-title]'))) {
var removeHandEle = this.element.querySelectorAll('[data-title]');
removeHandEle.forEach(function (e) {
var event = new MouseEvent('mouseout', { bubbles: true, cancelable: true });
e.dispatchEvent(event);
});
}
if (!isNOU(document.querySelector('.e-tooltip-wrap'))) {
if (!isNOU(document.querySelector('#' + this.element.id + ' [data-tooltip-id]'))) {
var tooltipTargetEle = document.querySelector('#' + this.element.id + ' [data-tooltip-id]');
var dataContent = tooltipTargetEle.getAttribute('data-content');
tooltipTargetEle.removeAttribute('data-content');
tooltipTargetEle.setAttribute('title', dataContent);
tooltipTargetEle.removeAttribute('data-tooltip-id');
}
if (!isNOU(this.tooltip)) {
this.tooltip.destroy();
}
}
else {
if (!isNOU(this.tooltip)) {
this.tooltip.destroy();
}
}
var rangeInsideCodeBlock = false;
if (!isNOU(this.parent.codeBlockModule)) {
var range = this.parent.getRange();
rangeInsideCodeBlock = !isNOU(this.parent.formatter.editorManager.codeBlockObj.
isValidCodeBlockStructure(range.startContainer))
|| !isNOU(this.parent.formatter.editorManager.codeBlockObj.isValidCodeBlockStructure(range.endContainer));
}
if (!isNOU(this.parent.getToolbar()) && !rangeInsideCodeBlock) {
if (this.parent.inlineMode.enable) {
if (!isSourceCodeEnabled) {
this.parent.enableToolbarItem(this.parent.toolbarSettings.items);
}
}
else {
if (!isSourceCodeEnabled && !isNOU(this.parent.toolbarModule.baseToolbar.toolbarObj)) {
this.parent.enableToolbarItem(this.parent.toolbarSettings.items);
}
}
}
this.removeEleFromDOM();
this.isRendered = false;
};
BaseQuickToolbar.prototype.removeEleFromDOM = function () {
var element = this.popupObj.element;
if (this.isRendered) {
this.dropDownButtons.destroyDropDowns();
this.colorPickerObj.destroyColorPicker();
detach(element);
var args = this.popupObj;
this.parent.trigger(events.quickToolbarClose, args);
}
};
/**
* Updates the status of the quick toolbar by enabling or disabling toolbar items
* based on the current selection and editor state.
*
* @param {IToolbarStatus} args - An object containing the current toolbar status, including details about the selection and applied formatting.
* @returns {void}
* @public
* @hidden
*/
BaseQuickToolbar.prototype.updateStatus = function (args) {
var options = {
args: args,
dropDownModule: this.dropDownButtons,
parent: this.parent,
tbElements: selectAll('.' + classes.CLS_TB_ITEM, this.element),
tbItems: this.quickTBarObj.toolbarObj.items
};
setToolbarStatus(options, true, this.parent);
if (this.type === 'Text' && this.parent.quickToolbarSettings.text && this.parent.quickToolbarSettings.text.length > 0 && this.parent.quickToolbarModule.textQTBar) {
var options_1 = {
args: args,
dropDownModule: this.parent.quickToolbarModule.textQTBar.dropDownButtons,
parent: this.parent,
tbElements: selectAll('.' + classes.CLS_TB_ITEM, this.parent.quickToolbarModule.textQTBar.element),
tbItems: this.parent.quickToolbarModule.textQTBar.quickTBarObj.toolbarObj.items
};
setToolbarStatus(options_1, true, this.parent);
updateUndoRedoStatus(this.parent.quickToolbarModule.textQTBar.quickTBarObj, this.parent.formatter.editorManager.undoRedoManager.getUndoStatus());
}
if (!select('.' + classes.CLS_RTE_SOURCE_CODE_TXTAREA, this.parent.element)) {
updateUndoRedoStatus(this.parent.getBaseToolbarObject(), this.parent.formatter.editorManager.undoRedoManager.getUndoStatus());
}
this.previousToolbarStatus = args;
};
/**
* Destroys the Quick toolbar.
*
* @function destroy
* @returns {void}
* @hidden
*/
BaseQuickToolbar.prototype.destroy = function () {
if (this.isDestroyed) {
return;
}
if (this.tooltip && !this.tooltip.isDestroyed) {
this.tooltip.destroy();
this.tooltip = null;
}
this.removeEventListener();
this.quickTBarObj.destroy();
this.quickTBarObj = null;
if (this.popupObj && !this.popupObj.isDestroyed) {
this.removeEleFromDOM();
this.popupObj.destroy();
}
this.colorPickerObj = null;
this.dropDownButtons = null;
this.stringItems = null;
this.dropDownButtons = null;
this.colorPickerObj = null;
this.toolbarElement = null;
this.popupWidth = null;
this.popupHeight = null;
this.tipPointerElem = null;
this.previousTarget = null;
this.isDestroyed = true;
};
/**
* addEventListener method
*
* @returns {void}
* @hidden
*/
BaseQuickToolbar.prototype.addEventListener = function () {
if (this.parent.isDestroyed) {
return;
}
this.parent.on(events.destroy, this.destroy, this);
this.parent.on(events.modelChanged, this.onPropertyChanged, this);
if (this.parent.inlineMode.enable || (this.type === 'Text' && this.parent.quickToolbarSettings.text && this.parent.quickToolbarSettings.text.length > 0)) {
this.parent.on(events.toolbarUpdated, this.updateStatus, this);
}
this.parent.on(events.bindCssClass, this.addCSSClass, this);
};
/**
* Called internally if any of the property value changed.
*
* @param {RichTextEditorModel} e - specifies the model element
* @returns {void}
* @hidden
*/
BaseQuickToolbar.prototype.onPropertyChanged = function (e) {
if (!isNOU(e.newProp.inlineMode)) {
for (var _i = 0, _a = Object.keys(e.newProp.inlineMode); _i < _a.length; _i++) {
var prop = _a[_i];
switch (prop) {
case 'enable':
if (e.newProp.inlineMode.enable) {
this.parent.on(events.toolbarUpdated, this.updateStatus, this);
}
else {
this.parent.off(events.toolbarUpdated, this.updateStatus);
}
break;
}
}
}
};
/**
* removeEventListener method
*
* @returns {void}
* @hidden
*/
BaseQuickToolbar.prototype.removeEventListener = function () {
this.parent.off(events.destroy, this.destroy);
this.parent.off(events.modelChanged, this.onPropertyChanged);
if (this.parent.inlineMode.enable || (this.type === 'Text' && this.parent.quickToolbarSettings.text && this.parent.quickToolbarSettings.text.length > 0)) {
this.parent.off(events.toolbarUpdated, this.updateStatus);
}
this.parent.off(events.bindCssClass, this.addCSSClass);
};
BaseQuickToolbar.prototype.getPopupDimension = function (popup, type) {
var element = popup.element;
element.classList.remove(classes.CLS_POPUP_CLOSE);
var dimension = type === 'width' ? element.clientWidth : element.clientHeight;
element.classList.add(classes.CLS_POPUP_CLOSE);
return dimension;
};
// To get the relative element of the popup of the quick toolbar.
BaseQuickToolbar.prototype.getRelativeElement = function (selection, currentTarget) {
var focusNode = selection.focusNode;
var blockElement;
switch (this.type) {
case 'Inline':
case 'Text':
blockElement = this.parent.formatter.editorManager.domTree.isBlockNode(focusNode) ? focusNode :
this.parent.formatter.editorManager.domTree.getParentBlockNode(focusNode);
if (blockElement.nodeName === 'TD' || blockElement.nodeName === 'TH') {
blockElement = closest(currentTarget, 'table');
}
break;
case 'Link':
blockElement = focusNode.nodeType === 3 ? focusNode.parentElement : focusNode;
blockElement = closest(blockElement, 'a');
break;
case 'Image':
case 'Audio':
case 'Video':
blockElement = currentTarget;
break;
case 'Table':
blockElement = closest(currentTarget, 'table');
break;
}
return blockElement;
};
// To calculate the popup offsetX position based on the range and block element position.
BaseQuickToolbar.prototype.calculateOffsetX = function (args) {
var width = this.popupWidth;
var tipPointerOffset = 16.5; // Rounded width of the Tip pointer + left offset value. 8 + 8.5
var finalX;
switch (this.type) {
case 'Text':
case 'Inline': {
var rangeEdge = args.direction === 'Backward' ? args.rangeRect.left : args.rangeRect.right;
var relativePosition = this.parent.iframeSettings.enable === false ? rangeEdge - args.blockRect.left : rangeEdge;
if (relativePosition < width / 4) {
finalX = relativePosition - tipPointerOffset;
this.currentTipPosition = 'Top-Left';
}
else if (relativePosition > width / 4 && relativePosition < width / 2) {
finalX = relativePosition - width / 4;
this.currentTipPosition = 'Top-LeftMiddle';
}
else if (relativePosition > width / 2 && relativePosition < (width * 3 / 4)) {
finalX = relativePosition - width / 2;
this.currentTipPosition = 'Top-Center';
}
else if (relativePosition > (width * 3 / 4) && relativePosition < width) {
finalX = relativePosition - (width * 3 / 4);
this.currentTipPosition = 'Top-RightMiddle';
}
else {
finalX = relativePosition - width + tipPointerOffset;
this.currentTipPosition = 'Top-Right';
}
break;
}
case 'Link':
case 'Image':
case 'Audio':
case 'Video':
case 'Table': {
var availableLeft = args.blockRect.left - args.editPanelDomRect.left;
var availableRight = args.editPanelDomRect.right - args.blockRect.right;
if (args.blockRect.width > width || (availableLeft > width / 2 && availableRight > width / 2)) {
finalX = args.blockRect.width / 2 - width / 2;
if (this.type === 'Link') {
this.currentTipPosition = 'Top-Center';
}
else {
this.currentTipPosition = 'Bottom-Center';
}
}
else if (availableRight < width / 2) {
finalX = -(width - args.blockRect.width);
if (this.type === 'Link') {
this.currentTipPosition = 'Top-Right';
}
else {
this.currentTipPosition = 'Bottom-Right';
}
}
else if (availableLeft < width / 2) {
finalX = 0;
if (this.type === 'Link') {
this.currentTipPosition = 'Top-Left';
}
else {
this.currentTipPosition = 'Bottom-Left';
}
}
if (this.parent.iframeSettings.enable) {
finalX = finalX + args.blockRect.left;
}
break;
}
}
return finalX;
};
// To calculate the popup offsetY position based on the range and block element position.
BaseQuickToolbar.prototype.calculateOffsetY = function (args) {
var finalY;
switch (this.type) {
case 'Text':
case 'Inline':
case 'Link':
if (this.parent.iframeSettings.enable) {
finalY = args.rangeRect.bottom + this.tipPointerHeight;
}
else {
finalY = args.rangeRect.bottom - args.blockRect.top + this.tipPointerHeight;
}
break;
case 'Image':
case 'Audio':
case 'Video':
case 'Table': {
if (this.parent.iframeSettings.enable) {
finalY = -100;
}
else {
finalY = -(this.popupHeight + this.tipPointerHeight);
}
break;
}
}
return finalY;
};
// To update the tip pointer position on the element.
BaseQuickToolbar.prototype.setTipPointerPostion = function (type) {
this.tipPointerElem.className = '';
this.tipPointerElem.classList.add(classes.CLS_QUICK_TBAR_TIP_POINTER);
if (type === 'None') {
return;
}
var typeArray = type.split('-');
var verticalPosition = typeArray[0];
var horizontalPosition = typeArray[1];
this.tipPointerElem.classList.add('e-rte-tip-' + verticalPosition.toLowerCase());
this.tipPointerElem.classList.add('e-rte-tip-' + horizontalPosition.toLowerCase());
};
// To check whether the selection is top to bottom or bottom to top.
BaseQuickToolbar.prototype.getSelectionDirection = function (selection) {
if (selection && selection.rangeCount > 0 && selection.getRangeAt(0).collapsed) {
return 'Forward';
}
var range = new Range();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
if (range.collapsed) {
return 'Backward';
}
else {
return 'Forward';
}
};
BaseQuickToolbar.prototype.addCSSClass = function () {
if (this.popupObj && this.parent.cssClass) {
removeClass([this.popupObj.element], this.parent.cssClass.replace(/\s+/g, ' ').trim().split(' '));
addClass([this.popupObj.element], this.parent.cssClass.replace(/\s+/g, ' ').trim().split(' '));
}
};
// To Disable the Main taoolbar items when the quick toolbar are opened.
BaseQuickToolbar.prototype.enableDisableToolbarItems = function () {
if (this.type === 'Audio' || this.type === 'Image' || this.type === 'Video') {
this.parent.disableToolbarItem(this.parent.toolbarSettings.items);
this.parent.enableToolbarItem(['Undo', 'Redo']);
}
};
BaseQuickToolbar.prototype.handleVerticalCollision = function (offsetParams, args) {
if (this.parent.iframeSettings.enable) {
if (this.type === 'Audio' || this.type === 'Image' || this.type === 'Table' || this.type === 'Video') {
args = this.handleIFrameMediaCollision(offsetParams, args);
}
else {
args = this.handleIFrameTextVerticalCollision(offsetParams, args);
}
args.targetElement = this.parent.contentModule.getPanel().parentElement;
}
else {
if (this.type === 'Audio' || this.type === 'Image' || this.type === 'Table' || this.type === 'Video') {
args = this.handleMediaVerticalCollision(offsetParams, args);
}
else {
args = this.handleTextVerticalCollision(offsetParams, args);
}
}
return args;
};
// In this method we change the popup properties to position the popup on top, bottom of the target element also achieve sticky collision using the 'fit' collision type.
BaseQuickToolbar.prototype.handleMediaVerticalCollision = function (offsetParams, args) {
var scrollTopParentElement = this.parent.scrollParentElements && this.parent.scrollParentElements.length > 0 &&
this.parent.scrollParentElements[0].nodeName !== '#document' ? this.parent.scrollParentElements[0] : this.parent.inputElement;
var scrollParentRect = scrollTopParentElement.getBoundingClientRect();
var blockRect = offsetParams.blockRect;
var toolbarRect = this.parent.getToolbarElement().getBoundingClientRect();
var isBottomToolbar = this.parent.toolbarSettings.position === 'Bottom';
var isFloating = this.parent.toolbarSettings.enableFloating;
var isFloatingTop = isFloating && this.parent.toolbarSettings.position === 'Top';
var isFloatingBot = isFloating && this.parent.toolbarSettings.position === 'Bottom';
var parentRect = offsetParams.editPanelDomRect;
var spaceAbove = this.getSpaceAbove(offsetParams, isFloatingTop, toolbarRect, scrollParentRect);
var spaceBelow = this.getSpaceBelow(offsetParams, isFloatingBot, toolbarRect, scrollParentRect);
var yPosition;
var yCollision;
var totalPopupHeight = (this.tipPointerHeight + this.popupHeight);
var isTopPosition = this.isElemVisible(blockRect, 'top', false) && spaceAbove > totalPopupHeight;
var isBotPosition = this.isElemVisible(blockRect, 'bottom', false) && spaceBelow > totalPopupHeight;
if (isTopPosition) {
yPosition = 'top';
yCollision = 'flip';
this.currentTipPosition = this.currentTipPosition.replace('Top', 'Bottom');
args.positionY = -(this.popupHeight + this.tipPointerHeight);
}
else if (isBotPosition) {
yPosition = 'bottom';
yCollision = 'flip';
this.currentTipPosition = this.currentTipPosition.replace('Bottom', 'Top');
args.positionY = this.tipPointerHeight;
}
else if ((spaceAbove < totalPopupHeight && spaceBelow < totalPopupHeight)) {
yPosition = 'top';
yCollision = 'fit';
var withToolbarHeight = -(blockRect.top) + toolbarRect.bottom; // WHen floating Main toolbar will hide the quick toolbar so need to add the main toolbar height.
var withOutToolbarHeight = scrollTopParentElement === this.parent.inputElement ?
-(blockRect.top) : (-blockRect.top) + parentRect.top; // When there is no floating Main toolbar wont hide the quick toolbar so no need to add main toolbar height.
if (isBottomToolbar) {
args.positionY = withOutToolbarHeight;
}
else {
if (isFloating) {
if (toolbarRect.top < 0) { // When the Toolbar is hidden beyond a viewport inside a scrollable container with overflow auto and static height.
args.positionY = -blockRect.top;
}
else {
args.positionY = withToolbarHeight;
}
}
else {
if (scrollTopParentElement === this.parent.inputElement) {
args.positionY = withOutToolbarHeight;
}
else {
if (parentRect.top < 0) { // WHen the Parent is hidden we need to calculate against the viewport.
args.positionY = -blockRect.top;
}
else {
args.positionY = -(blockRect.top - scrollParentRect.top);
}
}
}
}
this.currentTipPosition = 'None';
}
var newProps = {
position: { Y: yPosition, X: this.popupObj.position.X },
collision: { Y: yCollision, X: this.popupObj.collision.X }
};
this.popupObj.setProperties(newProps);
this.popupObj.dataBind();
return args;
};
// Returns true when the eleemnt is partially visible. Returns false when the element is not fully visible.
BaseQuickToolbar.prototype.isElemVisible = function (elemRect, value, isIFrame, iframeRect) {
if (isIFrame) {
var normalisedTop = iframeRect.top + elemRect.top;
var normalisedBottom = iframeRect.top + elemRect.bottom;
if (value === 'top') {
return normalisedTop >= 0 && normalisedTop <= window.innerHeight;
}
else {
return normalisedBottom <= window.innerHeight && normalisedBottom >= 0;
}
}
else {
if (value === 'top') {
return elemRect.top >= 0 && elemRect.top <= window.innerHeight;
}
else {
return elemRect.bottom <= window.innerHeight && elemRect.bottom >= 0;
}
}
};
BaseQuickToolbar.prototype.handleIFrameMediaCollision = function (offsetParams, args) {
var scrollTopParentElement = this.parent.scrollParentElements && this.parent.scrollParentElements.length > 0 &&
this.parent.scrollParentElements[0].nodeName !== '#document' ? this.parent.scrollParentElements[0] : this.parent.inputElement;
var scrollParentRect = scrollTopParentElement.getBoundingClientRect();
var isFloating = this.parent.toolbarSettings.enableFloating;
var toolbarElemRect = this.parent.getToolbarElement().getBoundingClientRect();
var toolbarRect = this.parent.getToolbarElement().getBoundingClientRect();
var isBottomToolbar = this.parent.toolbarSettings.position === 'Bottom';
var blockRect = offsetParams.blockRect;
var isFloatingTop = isFloating && this.parent.toolbarSettings.position === 'Top';
var isFloatingBot = isFloating && this.parent.toolbarSettings.position === 'Bottom';
var spaceAbove = this.getIFrameSpaceAbove(offsetParams, isFloatingTop, toolbarElemRect, scrollParentRect);
var spaceBelow = this.getIFrameSpaceBelow(offsetParams, isFloatingBot, toolbarElemRect, scrollParentRect);
var totalPopupHeight = (this.tipPointerHeight + this.popupHeight);
if (this.isElemVisible(blockRect, 'top', true, offsetParams.iframeRect) && spaceAbove > totalPopupHeight) {
args.positionY = blockRect.top - totalPopupHeight;
this.currentTipPosition = this.currentTipPosition.replace('Top', 'Bottom');
}
else if (this.isElemVisible(blockRect, 'bottom', true, offsetParams.iframeRect) && spaceBelow > totalPopupHeight) {
args.positionY = blockRect.bottom + (this.tipPointerHeight);
this.currentTipPosition = this.currentTipPosition.replace('Bottom', 'Top');
}
else if ((spaceAbove < totalPopupHeight && spaceBelow < totalPopupHeight)) {
var withToolbarHeight = -(offsetParams.iframeRect.top) + toolbarElemRect.bottom; // WHen floating Main toolbar will hide the quick toolbar so need to add the main toolbar height.
var withOutToolbarHeight = scrollTopParentElement === this.parent.inputElement ?
-(offsetParams.iframeRect.top) : -(offsetParams.iframeRect.top) + scrollParentRect.top; // When there is no floating Main toolbar wont hide the quick toolbar so no need to add main toolbar height.
if (isBottomToolbar) {
args.positionY = withOutToolbarHeight;
}
else {
if (isFloating) {
if (toolbarRect.top < 0) { // When the Toolbar is hidden beyond a viewport inside a scrollable container with overflow auto and static height.
args.positionY = -(offsetParams.iframeRect.top);
}
else {
args.positionY = withToolbarHeight;
}
}
else {
if (scrollTopParentElement === this.parent.inputElement) {
if (offsetParams.iframeRect.top < 0) {
args.positionY = withOutToolbarHeight;
}
else {
args.positionY = withToolbarHeight;
}
}
else {
if (offsetParams.iframeRect.top < 0) { // WHen the Parent is hidden we need to calculate against the viewport.
args.positionY = withOutToolbarHeight;
}
else {
args.positionY = withToolbarHeight;
}
}
}
}
this.currentTipPosition = 'None';
}
return args;
};
BaseQuickToolbar.prototype.getSpaceAbove = function (args, isFloatingTop, toolbarRect, scrollParentRect) {
var spaceAbove;
var blockRect = args.blockRect;
var parentRect = args.editPanelDomRect;
var collision = this.getTopCollisionType(blockRect, parentRect, isFloatingTop ? toolbarRect : scrollParentRect);
if (isFloatingTop) {
switch (collision) {
case 'ParentElement':
case 'ScrollableContainer': // When the toolbar is floating at top.
spaceAbove = blockRect.top - toolbarRect.top - toolbarRect.height;
break;
case 'ViewPort':
case 'Hidden':
spaceAbove = blockRect.top;
break;
}
}
else {
switch (collision) {
case 'ParentElement':
spaceAbove = blockRect.top - parentRect.top;
break;
case 'ScrollableContainer':
spaceAbove = scrollParentRect.top - parentRect.top;
break;
case 'ViewPort':
case 'Hidden':
spaceAbove = blockRect.top;
break;
}
}
return spaceAbove;
};
BaseQuickToolbar.prototype.getSpaceBelow = function (args, isFloatingBot, toolbarRect, scrollParentRect) {
var spaceBelow;
var blockRect = args.blockRect;
var parentRect = args.editPanelDomRect;
var collision = this.getBottomCollisionType(blockRect, parentRect, isFloatingBot
? toolbarRect : scrollParentRect);
if (isFloatingBot) {
switch (collision) {
case 'Hidden':
case 'ParentElement':
case 'ScrollableContainer':
spaceBelow = parentRect.bottom - blockRect.bottom - toolbarRect.height;
break;
case 'ViewPort':
spaceBelow = blockRect.bottom;
break;
}
}
else {
switch (collision) {
case 'Hidden':
case 'ParentElement':
spaceBelow = parentRect.bottom - blockRect.bottom;
break;
case 'ScrollableContainer':
spaceBelow = scrollParentRect.bottom - blockRect.bottom;
break;
case 'ViewPort':
spaceBelow = window.innerHeight - blockRect.bottom;
break;
}
}
var toolbarHeight = isFloatingBot ? this.toolbarHeight : 0;
if ((window.innerHeight - blockRect.bottom - toolbarHeight) < (this.popupHeight + this.tipPointerHeight)) {
spaceBelow = 0;
}
return spaceBelow;
};
BaseQuickToolbar.prototype.handleTextVerticalCollision = function (offsetParams, args) {
var scrollTopParentElement = this.parent.scrollParentElements && this.parent.scrollParentElements.length > 0 &&
this.parent.scrollParentElements[0].nodeName !== '#document' ? this.parent.scrollParentElements[0] : this.parent.inputElement;
var scrollParentRect = scrollTopParentElement.getBoundingClientRect();
var blockRect = offsetParams.blockRect;
var toolbarRect = this.parent.getToolbarElement().getBoundingClientRect();
var isFloating = this.parent.toolbarSettings.enableFloating;
var isFloatingTop = isFloating && this.parent.toolbarSettings.position === 'Top';
var isFloatingBot = isFloating && this.parent.toolbarSettings.position === 'Bottom';
var topViewPortSpace = blockRect.top;
var botViewPortSpace = blockRect.bottom;
var spaceAbove = this.getSpaceAbove(offsetParams, isFloatingTop, toolbarRect, scrollParentRect);
var spaceBelow = this.getSpaceBelow(offsetParams, isFloatingBot, toolbarRect, scrollParentRect);
var totalPopupHeight = (this.tipPointerHeight + this.popupHeight);
var isTopPosition = this.isElemVisible(blockRect, 'top', false) && spaceAbove > totalPopupHeight && topViewPortSpace > totalPopupHeight;
var isBotPosition = offsetParams.direction === 'Backward' && isTopPosition ? false : this.isElemVisible(blockRect, 'bottom', false) && spaceBelow > totalPopupHeight && botViewPortSpace > totalPopupHeight;
if (isBotPosition) {
return args; // Default Bottom position no need to change offset.
}
else if (isTopPosition) {
args.positionY = -(this.popupHeight + 10) + (offsetParams.rangeRect.top - offsetParams.blockRect.top);
this.currentTipPosition = this.currentTipPosition.replace('Top', 'Bottom');
}
return args;
};
BaseQuickToolbar.prototype.handleIFrameTextVerticalCollision = function (offsetParams, args) {
var scrollTopParentElement = this.parent.scrollParentElements && this.parent.scrollParentElements.length > 0 &&
this.parent.scrollParentElements[0].nodeName !== '#document' ? this.parent.scrollParentElements[0] : this.parent.inputElement;
var scrollParentRect = scrollTopParentElement.getBoundingClientRect();
var isFloating = this.parent.toolbarSettings.enableFloating;
var toolbarRect = this.parent.getToolbarElement().getBoundingClientRect();
var isFloatingTop = isFloating && this.parent.toolbarSettings.position === 'Top';
var isFloatingBot = isFloating && this.parent.toolbarSettings.position === 'Bottom';
var spaceAbove = this.getIFrameSpaceAbove(offsetParams, isFloatingTop, toolbarRect, scrollParentRect);
var spaceBelow = this.getIFrameSpaceBelow(offsetParams, isFloatingBot, toolbarRect, scrollParentRect);
var blockRect = offsetParams.blockRect;
var totalPopupHeight = (this.tipPointerHeight + this.popupHeight);
var isTopPosition = this.isElemVisible(blockRect, 'top', true, offsetParams.iframeRect) && spaceAbove > totalPopupHeight;
var isBotPosition = offsetParams.direction === 'Backward' && isTopPosition ? false : this.isElemVisible(blockRect, 'bottom', true, offsetParams.iframeRect) && spaceBelow > totalPopupHeight;
if (isBotPosition) {
return args;
}
else if (isTopPosition) {
args.positionY = offsetParams.rangeRect.top - totalPopupHeight;
this.currentTipPosition = this.currentTipPosition.replace('Top', 'Bottom');
}
return args;
};
/**
* Retrieves the previous toolbar status before the recent update.
*
* @returns {IToolbarStatus} An object representing the status of the toolbar including formatting and selection details.
* @public
* @hidden
*/
BaseQuickToolbar.prototype.getPreviousStatus = function () {
return this.previousToolbarStatus;
};
BaseQuickToolbar.prototype.getTopCollisionType = function (blockRect, parentRect, scrollParentRect) {
if (blockRect.top < 0 || blockRect.top >= window.innerHeight) {
return 'Hidden';
}
else {
if (parentRect.top > 0) {
return 'ParentElement';
}
else {
if (scrollParentRect.top < 0) {
return 'ViewPort';
}
if (scrollParentRect.top > 0) {
return 'ScrollableContainer';
}
}
}
return 'ParentElement';
};
BaseQuickToolbar.prototype.getBottomCollisionType = function (blockRect, parentRect, scrollParentRect) {
if (blockRect.bottom < 0 || blockRect.bottom >= window.innerHeight) {
return 'Hidden';
}
else {
if (scrollParentRect.bottom >= window.innerHeight && parentRect.bottom >= window.innerHeight) {
return 'ViewPort';
}
else {
if (parentRect.bottom <= scrollParentRect.bottom) {
return 'ParentElement';
}
else {
return 'ScrollableContainer';
}
}
}
};
BaseQuickToolbar.prototype.getIFrameSpaceAbove = function (args, isFloatingTop, toolbarRect, scrollParentRect) {
var spaceAbove;
var blockRect = args.blockRect;
var parentRect = args.editPanelDomRect;
var isScrollParentElemInputElem = args.editPanelDomRect.top === scrollParentRect.top && args.editPanelDomRect.bottom
=== args.editPanelDomRect.bottom;
if (isScrollParentElemInputElem) {
scrollParentRect = args.editPanelDomRect;
}
var collision = this.getIFrameTopCollisionType(blockRect, parentRect, isFloatingTop ? toolbarRect : scrollParentRect, args.iframeRect);
if (isFloatingTop) {
switch (collision) {
case 'ParentElement':
case 'ScrollableContainer': // When the toolbar is floating at top.
spaceAbove = (args.iframeRect.top + blockRect.top) - toolbarRect.top - toolbarRect.height;
break;
case 'ViewPort':
case 'Hidden':
spaceAbove = (args.iframeRect.top + blockRect.top) - toolbarRect.height;
break;
}
}
else {
switch (collision) {
case 'ScrollableContainer':
if ((args.iframeRect.top + args.blockRect.top) > scrollParentRect.top) {
spaceAbove = (args.iframeRect.top + args.blockRect.top) - scrollParentRect.top;
}
else {
spaceAbove = -(scrollParentRect.top - (args.iframeRect.top + args.blockRect.top));
}
break;
case 'ParentElement':
spaceAbove = (args.iframeRect.top + blockRect.top) - (args.iframeRect.top + args.editPanelDomRect.top);
break;
case 'ViewPort':
case 'Hidden':
if (toolbarRect.top < 0) {
spaceAbove = (args.iframeRect.top + blockRect.top);
}
else {
spaceAbove = (args.iframeRect.top + blockRect.top) - toolbarRect.bottom;
}
break;
}
}
return spaceAbove;
};
BaseQuickToolbar.prototype.getIFrameSpaceBelow = function (args, isFloatingBot, toolbarRect, scrollParentRect) {
var spaceBelow;
var blockRect = args.blockRect;
var parentRect = args.editPanelDomRect;
var isScrollParentElemInputElem = args.editPanelDomRect.top === scrollParentRect.top && args.editPanelDomRect.bottom
=== args.editPanelDomRect.bottom;
if (isScrollParentElemInputElem) {
scrollParentRect = args.iframeRect;
}
var collision = this.getIFrameBottomCollisionType(blockRect, parentRect, isFloatingBot
? toolbarRect : scrollParentRect, args.iframeRect);
if (isFloatingBot) {
switch (collision) {
case 'Hidden':
case 'ParentElement':
case 'ScrollableContainer':
spaceBelow = args.iframeRect.bottom - (args.iframeRect.top + blockRect.bottom);
break;
case 'ViewPort':
spaceBelow = window.innerHeight - (args.iframeRect.top + blockRect.bottom);
break;
}
}
else {
switch (collision) {
case 'Hidden':
case 'ParentElement':
case 'ScrollableContainer':
spaceBelow = scrollParentRect.bottom - (args.iframeRect.top + blockRect.bottom);
break;
case 'ViewPort':
spaceBelow = window.innerHeight - (args.iframeRect.top + blockRect.bottom);
break;
}
}
var blockElemFromViewPort = args.iframeRect.top + blockRect.bottom;
var totalPopupHeight = (this.popupHeight + this.tipPointerHeight);
var exceedsWindow = blockElemFromViewPort > window.innerHeight;
var exceedsScrollableParent = blockElemFromViewPort > args.iframeRect.bottom &&
blockElemFromViewPort + totalPopupHeight > scrollParentRect.bottom;
if (exceedsWindow || exceedsScrollableParent) {
spaceBelow = 0;
}
return spaceBelow;
};
BaseQuickToolbar.prototype.getIFrameTopCollisionType = function (blockRect, parentRect, scrollParentRect, iframeRect) {
if ((iframeRect.top + blockRect.top) < 0 || (iframeRect.top + blockRect.top) >= window.innerHeight) {
return 'Hidden';
}
else {
if (iframeRect.top + parentRect.top > 0) {
return 'ParentElement';
}
else {