UNPKG

@syncfusion/ej2-richtexteditor

Version:
1,034 lines (1,028 loc) 51.2 kB
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 {