Your IP : 18.224.32.56


Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/main/popup/src/popup/
Upload File :
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/main/popup/src/popup/popup.js

import Button from '../compatibility/button';

import { Type, Text, Tag, Event, Dom, Browser, Reflection } from 'main.core';
import { EventEmitter, BaseEvent } from 'main.core.events';
import { type PopupOptions, type PopupTarget, type PopupAnimationOptions } from './popup-types';
import { ZIndexManager, ZIndexComponent } from 'main.core.z-index-manager';
import PositionEvent from './position-event';
import CloseIconSize from './popup-close-icon-size';

declare type TargetPosition = {
	left: number,
	top: number,
	bottom: number,
	windowSize: number,
	windowScroll: number,
	popupWidth: number,
	popupHeight: number
};

const aliases = {
	onPopupWindowInit: { namespace: 'BX.Main.Popup', eventName: 'onInit' },
	onPopupWindowIsInitialized: { namespace: 'BX.Main.Popup', eventName: 'onAfterInit' },
	onPopupFirstShow: { namespace: 'BX.Main.Popup', eventName: 'onFirstShow' },
	onPopupShow: { namespace: 'BX.Main.Popup', eventName: 'onShow' },
	onAfterPopupShow: { namespace: 'BX.Main.Popup', eventName: 'onAfterShow' },
	onPopupClose: { namespace: 'BX.Main.Popup', eventName: 'onClose' },
	onPopupAfterClose: { namespace: 'BX.Main.Popup', eventName: 'onAfterClose' },
	onPopupDestroy: { namespace: 'BX.Main.Popup', eventName: 'onDestroy' },
	onPopupFullscreenLeave: { namespace: 'BX.Main.Popup', eventName: 'onFullscreenLeave' },
	onPopupFullscreenEnter: { namespace: 'BX.Main.Popup', eventName: 'onFullscreenEnter' },
	onPopupDragStart: { namespace: 'BX.Main.Popup', eventName: 'onDragStart' },
	onPopupDrag: { namespace: 'BX.Main.Popup', eventName: 'onDrag' },
	onPopupDragEnd: { namespace: 'BX.Main.Popup', eventName: 'onDragEnd' },
	onPopupResizeStart: { namespace: 'BX.Main.Popup', eventName: 'onResizeStart' },
	onPopupResize: { namespace: 'BX.Main.Popup', eventName: 'onResize' },
	onPopupResizeEnd: { namespace: 'BX.Main.Popup', eventName: 'onResizeEnd' }
};

EventEmitter.registerAliases(aliases);

const disabledScrolls: WeakMap<HTMLElement, Set<Popup>> = new WeakMap();

/**
 * @memberof BX.Main
 */
export default class Popup extends EventEmitter
{
	/**
	 * @private
	 */
	static options = {};

	/**
	 * @private
	 */
	static defaultOptions = {

		//left offset for popup about target
		angleLeftOffset: 40,

		//when popup position is 'top' offset distance between popup body and target node
		positionTopXOffset: -11,

		//offset distance between popup body and target node if use angle, sum with positionTopXOffset
		angleTopOffset: 10,

		popupZindex: 1000,
		popupOverlayZindex: 1100,

		angleMinLeft: 10,
		angleMaxLeft: 30,

		angleMinRight: 10,
		angleMaxRight: 30,

		angleMinBottom: 23,
		angleMaxBottom: 25,

		angleMinTop: 23,
		angleMaxTop: 25,

		offsetLeft: 0,
		offsetTop: 0
	};

	static setOptions(options: { [name: string]: any })
	{
		if (!Type.isPlainObject(options))
		{
			return;
		}

		for (let option in options)
		{
			this.options[option] = options[option];
		}
	}

	static getOption(option: string, defaultValue?: any)
	{
		if (!Type.isUndefined(this.options[option]))
		{
			return this.options[option];
		}
		else if (!Type.isUndefined(defaultValue))
		{
			return defaultValue;
		}
		else
		{
			return this.defaultOptions[option];
		}
	}

	constructor(options?: PopupOptions)
	{
		super();
		this.setEventNamespace('BX.Main.Popup');

		let [popupId: string, bindElement: PopupTarget, params: PopupOptions] = arguments; //compatible arguments

		this.compatibleMode = params && Type.isBoolean(params.compatibleMode) ? params.compatibleMode : true;
		if (Type.isPlainObject(options) && !bindElement && !params)
		{
			params = options;
			popupId = options.id;
			bindElement = options.bindElement;
			this.compatibleMode = false;
		}

		params = params || {};
		this.params = params;

		if (!Type.isStringFilled(popupId))
		{
			popupId = 'popup-window-' + Text.getRandom().toLowerCase();
		}

		this.emit('onInit', new BaseEvent({ compatData: [popupId, bindElement, params] }));

		/**
		 * @private
		 */
		this.uniquePopupId = popupId;
		this.params.zIndex = Type.isNumber(params.zIndex) ? parseInt(params.zIndex) : 0;
		this.params.zIndexAbsolute = Type.isNumber(params.zIndexAbsolute) ? parseInt(params.zIndexAbsolute) : 0;
		this.buttons = params.buttons && Type.isArray(params.buttons) ? params.buttons : [];
		this.offsetTop = Popup.getOption('offsetTop');
		this.offsetLeft = Popup.getOption('offsetLeft');
		this.firstShow = false;
		this.bordersWidth = 20;
		this.bindElementPos = null;
		this.closeIcon = null;
		this.resizeIcon = null;
		this.angle = null;
		this.angleArrowElement = null;
		this.overlay = null;
		this.titleBar = null;
		this.bindOptions = typeof (params.bindOptions) === 'object' ? params.bindOptions : {};
		this.autoHide = params.autoHide === true;
		this.disableScroll = params.disableScroll === true || params.isScrollBlock === true;
		this.autoHideHandler = Type.isFunction(params.autoHideHandler) ? params.autoHideHandler : null;
		this.handleAutoHide = this.handleAutoHide.bind(this);
		this.handleOverlayClick = this.handleOverlayClick.bind(this);
		this.isAutoHideBinded = false;
		this.closeByEsc = params.closeByEsc === true;
		this.isCloseByEscBinded = false;
		this.toFrontOnShow = true;

		this.cacheable = true;
		this.destroyed = false;
		this.fixed = false;

		this.width = null;
		this.height = null;
		this.minWidth = null;
		this.minHeight = null;
		this.maxWidth = null;
		this.maxHeight = null;

		this.padding = null;
		this.contentPadding = null;
		this.background = null;
		this.contentBackground = null;

		this.borderRadius = null;
		this.contentBorderRadius = null;

		this.targetContainer = Type.isElementNode(params.targetContainer) ? params.targetContainer : document.body;

		this.dragOptions = {
			cursor: '',
			callback: function() {
			},
			eventName: ''
		};

		this.dragged = false;
		this.dragPageX = 0;
		this.dragPageY = 0;

		this.animationShowClassName = null;
		this.animationCloseClassName = null;
		this.animationCloseEventType = null;

		this.handleDocumentMouseMove = this.handleDocumentMouseMove.bind(this);
		this.handleDocumentMouseUp = this.handleDocumentMouseUp.bind(this);
		this.handleDocumentKeyUp = this.handleDocumentKeyUp.bind(this);
		this.handleResizeWindow = this.handleResizeWindow.bind(this);
		this.handleResize = this.handleResize.bind(this);
		this.handleMove = this.handleMove.bind(this);
		this.onTitleMouseDown = this.onTitleMouseDown.bind(this);
		this.handleFullScreen = this.handleFullScreen.bind(this);

		this.subscribeFromOptions(params.events);

		let popupClassName = 'popup-window';

		if (params.titleBar)
		{
			popupClassName += ' popup-window-with-titlebar';
		}

		if (params.className && Type.isStringFilled(params.className))
		{
			popupClassName += ' ' + params.className;
		}

		if (params.darkMode)
		{
			popupClassName += ' popup-window-dark';
		}

		if (params.titleBar)
		{
			this.titleBar = Tag.render`
				<div class="popup-window-titlebar" id="popup-window-titlebar-${popupId}"></div>
			`;
		}

		if (params.closeIcon)
		{
			let className = 'popup-window-close-icon'
				+ (params.titleBar ? ' popup-window-titlebar-close-icon' : '');
			if (Object.values(CloseIconSize).includes(params.closeIconSize) && params.closeIconSize !== CloseIconSize.SMALL)
			{
				className += ` --${params.closeIconSize}`;
			}

			this.closeIcon = Tag.render`
				<span class="${className}" onclick="${this.handleCloseIconClick.bind(this)}"></span>
			`;



			if (Type.isPlainObject(params.closeIcon))
			{
				Dom.style(this.closeIcon, params.closeIcon);
			}
		}

		/**
		 * @private
		 */
		this.contentContainer = Tag.render
			`<div id="popup-window-content-${popupId}" class="popup-window-content"></div>`
		;

		/**
		 * @private
		 */
		this.popupContainer = Tag.render
			`<div
				class="${popupClassName}"
				id="${popupId}"
				style="display: none; position: absolute; left: 0; top: 0;"
			>${[this.titleBar, this.contentContainer, this.closeIcon]}</div>`
		;

		this.targetContainer.appendChild(this.popupContainer);

		this.zIndexComponent = ZIndexManager.register(this.popupContainer, params.zIndexOptions);

		this.buttonsContainer = null;

		if (params.contentColor && Type.isStringFilled(params.contentColor))
		{
			if (
				params.contentColor === 'white'
				|| params.contentColor === 'gray'
			)
			{
				popupClassName += ' popup-window-content-' + params.contentColor;
			}

			this.setContentColor(params.contentColor);

		}

		if (params.angle)
		{
			this.setAngle(params.angle);
		}

		if (params.overlay)
		{
			this.setOverlay(params.overlay);
		}

		this.setOffset(params);
		this.setBindElement(bindElement);
		this.setTitleBar(params.titleBar);
		this.setContent(params.content);
		this.setButtons(params.buttons);
		this.setWidth(params.width);
		this.setHeight(params.height);
		this.setMinWidth(params.minWidth);
		this.setMinHeight(params.minHeight);
		this.setMaxWidth(params.maxWidth);
		this.setMaxHeight(params.maxHeight);
		this.setResizeMode(params.resizable);
		this.setPadding(params.padding);
		this.setContentPadding(params.contentPadding);
		this.setBorderRadius(params.borderRadius);
		this.setContentBorderRadius(params.contentBorderRadius);
		this.setBackground(params.background);
		this.setContentBackground(params.contentBackground);
		this.setAnimation(params.animation);
		this.setCacheable(params.cacheable);
		this.setToFrontOnShow(params.toFrontOnShow);
		this.setFixed(params.fixed);

		// Compatibility
		if (params.contentNoPaddings)
		{
			this.setContentPadding(0);
		}
		if (params.noAllPaddings)
		{
			this.setPadding(0);
			this.setContentPadding(0);
		}

		if (params.bindOnResize !== false)
		{
			Event.bind(window, 'resize', this.handleResizeWindow);
		}

		this.emit('onAfterInit', new BaseEvent({ compatData: [popupId, this] }));
	}

	/**
	 * @private
	 */
	subscribeFromOptions(events): void
	{
		super.subscribeFromOptions(events, aliases);
	}

	getId(): string
	{
		return this.uniquePopupId;
	}

	isCompatibleMode(): boolean
	{
		return this.compatibleMode;
	}

	setContent(content: string | Element | Node)
	{
		if (!this.contentContainer || !content)
		{
			return;
		}

		if (Type.isElementNode(content))
		{
			Dom.clean(this.contentContainer);

			const hasParent = Type.isDomNode(content.parentNode);
			this.contentContainer.appendChild(content);
			if (this.isCompatibleMode() || hasParent)
			{
				content.style.display = 'block';
			}
		}
		else if (Type.isString(content))
		{
			this.contentContainer.innerHTML = content;
		}
		else
		{
			this.contentContainer.innerHTML = '&nbsp;';
		}
	}

	setButtons(buttons: [])
	{
		this.buttons = buttons && Type.isArray(buttons) ? buttons : [];

		if (this.buttonsContainer)
		{
			Dom.remove(this.buttonsContainer);
		}

		const ButtonClass = Reflection.getClass('BX.UI.Button');
		if (this.buttons.length > 0 && this.contentContainer)
		{
			const newButtons = [];
			for (let i = 0; i < this.buttons.length; i++)
			{
				const button = this.buttons[i];
				if (button instanceof Button)
				{
					button.popupWindow = this;
					newButtons.push(button.render());
				}
				else if (ButtonClass && (button instanceof ButtonClass))
				{
					button.setContext(this);
					newButtons.push(button.render());
				}
			}

			this.buttonsContainer = this.contentContainer.parentNode.appendChild(
				Tag.render`<div class="popup-window-buttons">${newButtons}</div>`
			);
		}
	}

	getButtons(): []
	{
		return this.buttons;
	}

	getButton(id: string)
	{
		for (let i = 0; i < this.buttons.length; i++)
		{
			const button = this.buttons[i];
			if (button.getId() === id)
			{
				return button;
			}
		}

		return null;
	}

	setBindElement(bindElement: Element | { left: number, top: number } | null | MouseEvent)
	{
		if (bindElement === null)
		{
			this.bindElement = null;
		}
		else if (typeof (bindElement) === 'object')
		{
			if (Type.isDomNode(bindElement) || (Type.isNumber(bindElement.top) && Type.isNumber(bindElement.left)))
			{
				this.bindElement = bindElement;
			}
			else if (Type.isNumber(bindElement.clientX) && Type.isNumber(bindElement.clientY))
			{
				this.bindElement = { left: bindElement.pageX, top: bindElement.pageY, bottom: bindElement.pageY };
			}
		}
	}

	/**
	 * @private
	 */
	getBindElementPos(bindElement: HTMLElement | any): TargetPosition | DOMRect
	{
		if (Type.isDomNode(bindElement))
		{
			if (this.isTargetDocumentBody())
			{
				return this.isFixed() ? bindElement.getBoundingClientRect() : Dom.getPosition(bindElement);
			}
			else
			{
				return this.getPositionRelativeToTarget(bindElement);
			}
		}
		else if (bindElement && typeof (bindElement) === 'object')
		{
			if (!Type.isNumber(bindElement.bottom))
			{
				bindElement.bottom = bindElement.top;
			}

			return bindElement;
		}
		else
		{
			const windowSize = this.getWindowSize();
			const windowScroll = this.getWindowScroll();

			const popupWidth = this.getPopupContainer().offsetWidth;
			const popupHeight = this.getPopupContainer().offsetHeight;

			this.bindOptions.forceTop = true;

			return {
				left: windowSize.innerWidth / 2 - popupWidth / 2 + windowScroll.scrollLeft,
				top: windowSize.innerHeight / 2 - popupHeight / 2 + (this.isFixed() ? 0 : windowScroll.scrollTop),
				bottom: windowSize.innerHeight / 2 - popupHeight / 2 + (this.isFixed() ? 0 : windowScroll.scrollTop),

				//for optimisation purposes
				windowSize: windowSize,
				windowScroll: windowScroll,
				popupWidth: popupWidth,
				popupHeight: popupHeight
			};
		}
	}

	/**
	 * @internal
	 */
	getPositionRelativeToTarget(element: HTMLElement): DOMRect
	{
		let offsetLeft = element.offsetLeft;
		let offsetTop = element.offsetTop;
		let offsetElement = element.offsetParent;

		while (offsetElement && offsetElement !== this.getTargetContainer())
		{
			offsetLeft += offsetElement.offsetLeft;
			offsetTop += offsetElement.offsetTop;
			offsetElement = offsetElement.offsetParent;
		}

		const elementRect = element.getBoundingClientRect();

		return new DOMRect(
			offsetLeft,
			offsetTop,
			elementRect.width,
			elementRect.height
		);
	}

	// private
	getWindowSize(): { innerWidth: number, innerHeight: number }
	{
		if (this.isTargetDocumentBody())
		{
			return {
				innerWidth: window.innerWidth,
				innerHeight: window.innerHeight
			};
		}
		else
		{
			return {
				innerWidth: this.getTargetContainer().offsetWidth,
				innerHeight: this.getTargetContainer().offsetHeight
			};
		}
	}

	// private
	getWindowScroll()
	{
		if (this.isTargetDocumentBody())
		{
			return {
				scrollLeft: window.pageXOffset,
				scrollTop: window.pageYOffset
			};
		}
		else
		{
			return {
				scrollLeft: this.getTargetContainer().scrollLeft,
				scrollTop: this.getTargetContainer().scrollTop
			};
		}
	}

	setAngle(params: { offset: number, position?: 'top' | 'bottom' | 'left' | 'right' })
	{
		if (params === false)
		{
			if (this.angle !== null)
			{
				Dom.remove(this.angle.element);
			}

			this.angle = null;
			this.angleArrowElement = null;
			return;
		}

		const className = 'popup-window-angly';
		if (this.angle === null)
		{
			const position = this.bindOptions.position && this.bindOptions.position === 'top' ? 'bottom' : 'top';
			const angleMinLeft = Popup.getOption(position === 'top' ? 'angleMinTop' : 'angleMinBottom');
			let defaultOffset = Type.isNumber(params.offset) ? params.offset : 0;

			const angleLeftOffset = Popup.getOption('angleLeftOffset', null);
			if (defaultOffset > 0 && Type.isNumber(angleLeftOffset))
			{
				defaultOffset += angleLeftOffset - Popup.defaultOptions.angleLeftOffset;
			}

			this.angleArrowElement = Tag.render`<div class="popup-window-angly--arrow"></div>`;
			if (this.background)
			{
				this.angleArrowElement.style.background = this.background;
			}

			this.angle = {
				element: Tag.render`
					<div class="${className} ${className}-${position}">
						${this.angleArrowElement}
					</div>
				`,
				position: position,
				offset: 0,
				defaultOffset: Math.max(defaultOffset, angleMinLeft)
				//Math.max(Type.isNumber(params.offset) ? params.offset : 0, angleMinLeft)
			};

			this.getPopupContainer().appendChild(this.angle.element);
		}

		if (typeof (params) === 'object' && params.position && ['top', 'right', 'bottom', 'left', 'hide'].includes(params.position))
		{
			Dom.removeClass(this.angle.element, className + '-' + this.angle.position);
			Dom.addClass(this.angle.element, className + '-' + params.position);

			this.angle.position = params.position;
		}

		if (typeof (params) === 'object' && Type.isNumber(params.offset))
		{
			const offset = params.offset;
			let minOffset, maxOffset;
			if (this.angle.position === 'top')
			{
				minOffset = Popup.getOption('angleMinTop');
				maxOffset = this.getPopupContainer().offsetWidth - Popup.getOption('angleMaxTop');
				maxOffset = maxOffset < minOffset ? Math.max(minOffset, offset) : maxOffset;

				this.angle.offset = Math.min(Math.max(minOffset, offset), maxOffset);
				this.angle.element.style.left = this.angle.offset + 'px';
				this.angle.element.style.marginLeft = 0;
				this.angle.element.style.removeProperty('top');
			}
			else if (this.angle.position === 'bottom')
			{
				minOffset = Popup.getOption('angleMinBottom');
				maxOffset = this.getPopupContainer().offsetWidth - Popup.getOption('angleMaxBottom');
				maxOffset = maxOffset < minOffset ? Math.max(minOffset, offset) : maxOffset;

				this.angle.offset = Math.min(Math.max(minOffset, offset), maxOffset);
				this.angle.element.style.marginLeft = this.angle.offset + 'px';
				this.angle.element.style.left = 0;
				this.angle.element.style.removeProperty('top');
			}
			else if (this.angle.position === 'right')
			{
				minOffset = Popup.getOption('angleMinRight');
				maxOffset = this.getPopupContainer().offsetHeight - Popup.getOption('angleMaxRight');
				maxOffset = maxOffset < minOffset ? Math.max(minOffset, offset) : maxOffset;

				this.angle.offset = Math.min(Math.max(minOffset, offset), maxOffset);
				this.angle.element.style.top = this.angle.offset + 'px';
				this.angle.element.style.removeProperty('left');
				this.angle.element.style.removeProperty('margin-left');
			}
			else if (this.angle.position === 'left')
			{
				minOffset = Popup.getOption('angleMinLeft');
				maxOffset = this.getPopupContainer().offsetHeight - Popup.getOption('angleMaxLeft');
				maxOffset = maxOffset < minOffset ? Math.max(minOffset, offset) : maxOffset;

				this.angle.offset = Math.min(Math.max(minOffset, offset), maxOffset);
				this.angle.element.style.top = this.angle.offset + 'px';
				this.angle.element.style.removeProperty('left');
				this.angle.element.style.removeProperty('margin-left');
			}
		}
	}

	getWidth(): number
	{
		return this.width;
	}

	setWidth(width: number)
	{
		this.setWidthProperty('width', width);
	}

	getHeight(): number
	{
		return this.height;
	}

	setHeight(height: number)
	{
		this.setHeightProperty('height', height);
	}

	getMinWidth(): number
	{
		return this.minWidth;
	}

	setMinWidth(width: number)
	{
		this.setWidthProperty('minWidth', width);
	}

	getMinHeight(): number
	{
		return this.minHeight;
	}

	setMinHeight(height: number)
	{
		this.setHeightProperty('minHeight', height);
	}

	getMaxWidth(): number
	{
		return this.maxWidth;
	}

	setMaxWidth(width: number)
	{
		this.setWidthProperty('maxWidth', width);
	}

	getMaxHeight(): number
	{
		return this.maxHeight;
	}

	setMaxHeight(height: number)
	{
		this.setHeightProperty('maxHeight', height);
	}

	/**
	 * @private
	 */
	setWidthProperty(property: string, width: number)
	{
		const props = ['width', 'minWidth', 'maxWidth'];
		if (props.indexOf(property) === -1)
		{
			return;
		}

		if (Type.isNumber(width) && width >= 0)
		{
			this[property] = width;
			this.getResizableContainer().style[property] = width + 'px';
			this.getContentContainer().style.overflowX = 'auto';
			this.getPopupContainer().classList.add('popup-window-fixed-width');

			if (this.getTitleContainer() && Browser.isIE11())
			{
				this.getTitleContainer().style[property] = width + 'px';
			}
		}
		else if (width === null || width === false)
		{
			this[property] = null;
			this.getResizableContainer().style.removeProperty(Text.toKebabCase(property));

			const hasOtherProps = props.some(function(prop) {
				return this.getResizableContainer().style.getPropertyValue(Text.toKebabCase(prop)) !== '';
			}, this);

			if (!hasOtherProps)
			{
				this.getContentContainer().style.removeProperty('overflow-x');
				this.getPopupContainer().classList.remove('popup-window-fixed-width');
			}

			if (this.getTitleContainer() && Browser.isIE11())
			{
				this.getTitleContainer().style.removeProperty(Text.toKebabCase(property));
			}
		}
	}

	/**
	 * @private
	 */
	setHeightProperty(property: string, height: number)
	{
		const props = ['height', 'minHeight', 'maxHeight'];
		if (props.indexOf(property) === -1)
		{
			return;
		}

		if (Type.isNumber(height) && height >= 0)
		{
			this[property] = height;
			this.getResizableContainer().style[property] = height + 'px';
			this.getContentContainer().style.overflowY = 'auto';
			this.getPopupContainer().classList.add('popup-window-fixed-height');
		}
		else if (height === null || height === false)
		{
			this[property] = null;
			this.getResizableContainer().style.removeProperty(Text.toKebabCase(property));

			const hasOtherProps = props.some(function(prop) {
				return this.getResizableContainer().style.getPropertyValue(Text.toKebabCase(prop)) !== '';
			}, this);

			if (!hasOtherProps)
			{
				this.getContentContainer().style.removeProperty('overflow-y');
				this.getPopupContainer().classList.remove('popup-window-fixed-height');
			}
		}
	}

	setPadding(padding: number)
	{
		if (Type.isNumber(padding) && padding >= 0)
		{
			this.padding = padding;
			this.getPopupContainer().style.padding = padding + 'px';
		}
		else if (padding === null)
		{
			this.padding = null;
			this.getPopupContainer().style.removeProperty('padding');
		}
	}

	getPadding(): number
	{
		return this.padding;
	}

	setContentPadding(padding: number)
	{
		if (Type.isNumber(padding) && padding >= 0)
		{
			this.contentPadding = padding;
			this.getContentContainer().style.padding = padding + 'px';
		}
		else if (padding === null)
		{
			this.contentPadding = null;
			this.getContentContainer().style.removeProperty('padding');
		}
	}

	getContentPadding(): number
	{
		return this.contentPadding;
	}

	setBorderRadius(radius): void
	{
		if (Type.isStringFilled(radius))
		{
			this.borderRadius = radius;
			this.getPopupContainer().style.setProperty('--popup-window-border-radius', radius);
		}
		else if (radius === null)
		{
			this.borderRadius = null;
			this.getPopupContainer().style.removeProperty('--popup-window-border-radius');
		}
	}

	setContentBorderRadius(radius): void
	{
		if (Type.isStringFilled(radius))
		{
			this.contentBorderRadius = radius;
			this.getContentContainer().style.setProperty('--popup-window-content-border-radius', radius);
		}
		else if (radius === null)
		{
			this.contentBorderRadius = null;
			this.getContentContainer().style.removeProperty('--popup-window-content-border-radius');
		}
	}

	setContentColor(color: string | null)
	{
		if (Type.isString(color) && this.contentContainer)
		{
			this.contentContainer.style.backgroundColor = color;
		}
		else if (color === null)
		{
			this.contentContainer.style.style.removeProperty('background-color');
		}
	}

	setBackground(background: string | null)
	{
		if (Type.isStringFilled(background))
		{
			this.background = background;
			this.getPopupContainer().style.background = background;

			if (this.angleArrowElement)
			{
				this.angleArrowElement.style.background = background;
			}
		}
		else if (background === null)
		{
			this.background = null;
			this.getPopupContainer().style.removeProperty('background');

			if (this.angleArrowElement)
			{
				this.angleArrowElement.style.removeProperty('background');
			}
		}
	}

	getBackground(): string | null
	{
		return this.background;
	}

	setContentBackground(background: string | null)
	{
		if (Type.isStringFilled(background))
		{
			this.contentBackground = background;
			this.getContentContainer().style.background = background;
		}
		else if (background === null)
		{
			this.contentBackground = null;
			this.getContentContainer().style.removeProperty('background');
		}
	}

	getContentBackground(): string | null
	{
		return this.contentBackground;
	}

	isDestroyed(): boolean
	{
		return this.destroyed;
	}

	setCacheable(cacheable: boolean): void
	{
		this.cacheable = cacheable !== false;
	}

	isCacheable(): boolean
	{
		return this.cacheable;
	}

	setToFrontOnShow(flag: boolean): void
	{
		this.toFrontOnShow = flag !== false;
	}

	shouldFrontOnShow(): boolean
	{
		return this.toFrontOnShow;
	}

	setFixed(flag: boolean): void
	{
		if (Type.isBoolean(flag))
		{
			this.fixed = flag;
			if (flag)
			{
				Dom.addClass(this.getPopupContainer(), '--fixed');
			}
			else
			{
				Dom.removeClass(this.getPopupContainer(), '--fixed');
			}
		}
	}

	isFixed(): boolean
	{
		return this.fixed;
	}

	setResizeMode(mode: boolean): void
	{
		if (mode === true || Type.isPlainObject(mode))
		{
			if (!this.resizeIcon)
			{
				this.resizeIcon = Tag.render`
					<div class="popup-window-resize" onmousedown="${this.handleResizeMouseDown.bind(this)}"></div>
				`;

				this.getPopupContainer().appendChild(this.resizeIcon);
			}

			//Compatibility
			this.setMinWidth(mode.minWidth);
			this.setMinHeight(mode.minHeight);
		}
		else if (mode === false && this.resizeIcon)
		{
			Dom.remove(this.resizeIcon);
			this.resizeIcon = null;
		}
	}

	getTargetContainer(): HTMLElement
	{
		return this.targetContainer;
	}

	isTargetDocumentBody(): boolean
	{
		return this.getTargetContainer() === document.body;
	}

	getPopupContainer(): Element
	{
		return this.popupContainer;
	}

	getContentContainer(): Element
	{
		return this.contentContainer;
	}

	getResizableContainer(): Element
	{
		return Browser.isIE11() ? this.getContentContainer() : this.getPopupContainer();
	}

	getTitleContainer(): Element
	{
		return this.titleBar;
	}

	/**
	 * @private
	 */
	onTitleMouseDown(event: MouseEvent): void
	{
		this._startDrag(
			event,
			{
				cursor: 'move',
				callback: this.handleMove,
				eventName: 'Drag'
			}
		);
	}

	/**
	 * @private
	 */
	handleResizeMouseDown(event): void
	{
		this._startDrag(
			event,
			{
				cursor: 'nwse-resize',
				eventName: 'Resize',
				callback: this.handleResize
			}
		);

		if (this.isTargetDocumentBody())
		{
			this.resizeContentPos = Dom.getPosition(this.getResizableContainer());
			this.resizeContentOffset =
				this.resizeContentPos.left - Dom.getPosition(this.getPopupContainer()).left;
		}
		else
		{
			this.resizeContentPos = this.getPositionRelativeToTarget(this.getResizableContainer());
			this.resizeContentOffset =
				this.resizeContentPos.left - this.getPositionRelativeToTarget(this.getPopupContainer()).left;
		}

		this.resizeContentPos.offsetX = 0;
		this.resizeContentPos.offsetY = 0;
	}

	/**
	 * @private
	 */
	handleResize(offsetX, offsetY, pageX, pageY): void
	{
		this.resizeContentPos.offsetX += offsetX;
		this.resizeContentPos.offsetY += offsetY;

		let width = this.resizeContentPos.width + this.resizeContentPos.offsetX;
		let height = this.resizeContentPos.height + this.resizeContentPos.offsetY;

		const scrollWidth =
			this.isTargetDocumentBody() ? document.documentElement.scrollWidth : this.getTargetContainer().scrollWidth
		;

		if (this.resizeContentPos.left + width + this.resizeContentOffset >= scrollWidth)
		{
			width = scrollWidth - this.resizeContentPos.left - this.resizeContentOffset;
		}

		width = Math.max(width, this.getMinWidth());
		height = Math.max(height, this.getMinHeight());

		if (this.getMaxWidth() !== null)
		{
			width = Math.min(width, this.getMaxWidth());
		}

		if (this.getMaxHeight() !== null)
		{
			height = Math.min(height, this.getMaxHeight());
		}

		this.setWidth(width);
		this.setHeight(height);
	}

	isTopAngle(): boolean
	{
		return this.angle !== null && this.angle.position === 'top';
	}

	isBottomAngle(): boolean
	{
		return this.angle !== null && this.angle.position === 'bottom';
	}

	isTopOrBottomAngle(): boolean
	{
		return this.angle !== null && (this.angle.position === 'top' || this.angle.position === 'bottom');
	}

	/**
	 * @private
	 */
	getAngleHeight(): number
	{
		return (this.isTopOrBottomAngle() ? Popup.getOption('angleTopOffset') : 0);
	}

	setOffset(params: { offsetTop: number, offsetLeft: number }): void
	{
		if (!Type.isPlainObject(params))
		{
			return;
		}

		if (Type.isNumber(params.offsetLeft))
		{
			this.offsetLeft = params.offsetLeft + Popup.getOption('offsetLeft');
		}

		if (Type.isNumber(params.offsetTop))
		{
			this.offsetTop = params.offsetTop + Popup.getOption('offsetTop');
		}
	}

	setTitleBar(params: string | { content: string }): void
	{
		if (!this.titleBar)
		{
			return;
		}

		if (typeof (params) === 'object' && Type.isDomNode(params.content))
		{
			this.titleBar.innerHTML = '';
			this.titleBar.appendChild(params.content);
		}
		else if (typeof (params) === 'string')
		{
			this.titleBar.innerHTML = '';
			this.titleBar.appendChild(
				Dom.create('span', {
					props: {
						className: 'popup-window-titlebar-text'
					},
					text: params
				})
			);
		}

		if (this.params.draggable)
		{
			this.titleBar.style.cursor = 'move';
			Event.bind(this.titleBar, 'mousedown', this.onTitleMouseDown);
		}
	}

	setClosingByEsc(enable: boolean): void
	{
		enable = Type.isBoolean(enable) ? enable : true;
		if (enable)
		{
			this.closeByEsc = true;
			this.bindClosingByEsc();
		}
		else
		{
			this.closeByEsc = false;
			this.unbindClosingByEsc();
		}
	}

	/**
	 * @private
	 */
	bindClosingByEsc(): void
	{
		if (this.closeByEsc && !this.isCloseByEscBinded)
		{
			Event.bind(document, 'keyup', this.handleDocumentKeyUp);
			this.isCloseByEscBinded = true;
		}
	}

	/**
	 * @private
	 */
	unbindClosingByEsc(): void
	{
		if (this.isCloseByEscBinded)
		{
			Event.unbind(document, 'keyup', this.handleDocumentKeyUp);
			this.isCloseByEscBinded = false;
		}
	}

	setAutoHide(enable: boolean): void
	{
		enable = Type.isBoolean(enable) ? enable : true;
		if (enable)
		{
			this.autoHide = true;
			this.bindAutoHide();
		}
		else
		{
			this.autoHide = false;
			this.unbindAutoHide();
		}
	}

	/**
	 * @private
	 */
	bindAutoHide(): void
	{
		if (this.autoHide && !this.isAutoHideBinded && this.isShown())
		{
			this.isAutoHideBinded = true;

			if (this.isCompatibleMode())
			{
				Event.bind(this.getPopupContainer(), 'click', this.handleContainerClick);
			}

			if (this.overlay && this.overlay.element)
			{
				Event.bind(this.overlay.element, 'click', this.handleOverlayClick);
			}
			else
			{
				if (this.isCompatibleMode())
				{
					Event.bind(document, 'click', this.handleAutoHide);
				}
				else
				{
					document.addEventListener('click', this.handleAutoHide, true);
				}
			}
		}
	}

	/**
	 * @private
	 */
	unbindAutoHide(): void
	{
		if (this.isAutoHideBinded)
		{
			this.isAutoHideBinded = false;

			if (this.isCompatibleMode())
			{
				Event.unbind(this.getPopupContainer(), 'click', this.handleContainerClick);
			}

			if (this.overlay && this.overlay.element)
			{
				Event.unbind(this.overlay.element, 'click', this.handleOverlayClick);
			}
			else
			{
				if (this.isCompatibleMode())
				{
					Event.unbind(document, 'click', this.handleAutoHide);
				}
				else
				{
					document.removeEventListener('click', this.handleAutoHide, true);
				}
			}
		}
	}

	/**
	 * @private
	 */
	handleAutoHide(event): void
	{
		if (this.isDestroyed())
		{
			return;
		}

		if (this.autoHideHandler !== null)
		{
			if (this.autoHideHandler(event))
			{
				this._tryCloseByEvent(event);
			}
		}
		else if (event.target !== this.getPopupContainer() && !this.getPopupContainer().contains(event.target))
		{
			this._tryCloseByEvent(event);
		}
	}

	/**
	 * @private
	 */
	_tryCloseByEvent(event): void
	{
		if (this.isCompatibleMode())
		{
			this.tryCloseByEvent(event);
		}
		else
		{
			setTimeout(() => {
				this.tryCloseByEvent(event);
			}, 0);
		}
	}

	/**
	 * @private
	 */
	tryCloseByEvent(event): void
	{
		if (event.button === 0)
		{
			this.close();
		}
	}

	/**
	 * @private
	 */
	handleOverlayClick(event): void
	{
		this.tryCloseByEvent(event);
		event.stopPropagation();
	}

	setOverlay(params: { backgroundColor?: string, opacity?: number }): void
	{
		if (this.overlay === null)
		{
			this.overlay = {
				element: Tag.render`
					<div class="popup-window-overlay" id="popup-window-overlay-${this.getId()}"></div>
				`
			};

			this.resizeOverlay();

			this.targetContainer.appendChild(this.overlay.element);
			this.getZIndexComponent().setOverlay(this.overlay.element);
		}

		if (params && Type.isNumber(params.opacity) && params.opacity >= 0 && params.opacity <= 100)
		{
			this.overlay.element.style.opacity = parseFloat(params.opacity / 100).toPrecision(3);
		}

		if (params && params.backgroundColor)
		{
			this.overlay.element.style.backgroundColor = params.backgroundColor;
		}
	}

	removeOverlay(): void
	{
		if (this.overlay !== null && this.overlay.element !== null)
		{
			Dom.remove(this.overlay.element);
			this.getZIndexComponent().setOverlay(null);
		}

		if (this.overlayTimeout)
		{
			clearInterval(this.overlayTimeout);
			this.overlayTimeout = null;
		}

		this.overlay = null;
	}

	hideOverlay(): void
	{
		if (this.overlay !== null && this.overlay.element !== null)
		{
			if (this.overlayTimeout)
			{
				clearInterval(this.overlayTimeout);
				this.overlayTimeout = null;
			}

			this.overlay.element.style.display = 'none';
		}
	}

	showOverlay(): void
	{
		if (this.overlay !== null && this.overlay.element !== null)
		{
			this.overlay.element.style.display = 'block';

			let popupHeight = this.getPopupContainer().offsetHeight;
			this.overlayTimeout = setInterval(() => {
				if (popupHeight !== this.getPopupContainer().offsetHeight)
				{
					this.resizeOverlay();
					popupHeight = this.getPopupContainer().offsetHeight;
				}
			}, 1000);
		}
	}

	resizeOverlay(): void
	{
		if (this.overlay !== null && this.overlay.element !== null)
		{
			let scrollWidth;
			let scrollHeight;
			if (this.isTargetDocumentBody())
			{
				scrollWidth = document.documentElement.scrollWidth;
				scrollHeight = Math.max(
					document.body.scrollHeight, document.documentElement.scrollHeight,
					document.body.offsetHeight, document.documentElement.offsetHeight,
					document.body.clientHeight, document.documentElement.clientHeight
				);
			}
			else
			{
				scrollWidth = this.getTargetContainer().scrollWidth;
				scrollHeight = this.getTargetContainer().scrollHeight;
			}

			this.overlay.element.style.width = scrollWidth + 'px';
			this.overlay.element.style.height = scrollHeight + 'px';
		}
	}

	getZindex(): number
	{
		return this.getZIndexComponent().getZIndex();
	}

	getZIndexComponent(): ZIndexComponent
	{
		return this.zIndexComponent;
	}

	setDisableScroll(flag: boolean): void
	{
		const disable = Type.isBoolean(flag) ? flag : true;
		if (disable)
		{
			this.disableScroll = true;
			this.#disableTargetScroll();
		}
		else
		{
			this.disableScroll = false;
			this.#enableTargetScroll();
		}
	}

	#disableTargetScroll(): void
	{
		const target = this.getTargetContainer();
		let popups: Set<Popup> = disabledScrolls.get(target);
		if (!popups)
		{
			popups = new Set();
			disabledScrolls.set(target, popups);
		}

		popups.add(this);

		Dom.addClass(target, 'popup-window-disable-scroll');
	}

	#enableTargetScroll(): void
	{
		const target = this.getTargetContainer();
		const popups: Set<Popup> = disabledScrolls.get(target) || null;
		if (popups)
		{
			popups.delete(this);
		}

		if (popups === null || popups.size === 0)
		{
			Dom.removeClass(target, 'popup-window-disable-scroll');
		}
	}

	show(): void
	{
		if (this.isShown() || this.isDestroyed())
		{
			return;
		}

		this.emit('onBeforeShow');

		this.showOverlay();
		this.getPopupContainer().style.display = 'block';

		if (this.shouldFrontOnShow())
		{
			this.bringToFront();
		}

		if (!this.firstShow)
		{
			this.emit('onFirstShow', new BaseEvent({ compatData: [this] }));
			this.firstShow = true;
		}

		this.emit('onShow', new BaseEvent({ compatData: [this] }));

		if (this.disableScroll)
		{
			this.#disableTargetScroll();
		}

		this.adjustPosition();

		this.animateOpening(() => {

			if (this.isDestroyed())
			{
				return;
			}

			Dom.removeClass(this.getPopupContainer(), this.animationShowClassName);
			this.emit('onAfterShow', new BaseEvent({ compatData: [this] }));
		});

		this.bindClosingByEsc();

		if (this.isCompatibleMode())
		{
			setTimeout(() => {
				this.bindAutoHide();
			}, 100);
		}
		else
		{
			this.bindAutoHide();
		}
	}

	close(): void
	{
		if (this.isDestroyed() || !this.isShown())
		{
			return;
		}

		this.emit('onClose', new BaseEvent({ compatData: [this] }));

		if (this.isDestroyed())
		{
			return;
		}

		if (this.disableScroll)
		{
			this.#enableTargetScroll();
		}

		this.animateClosing(() => {

			if (this.isDestroyed())
			{
				return;
			}

			this.hideOverlay();

			this.getPopupContainer().style.display = 'none';

			Dom.removeClass(this.getPopupContainer(), this.animationCloseClassName);

			this.unbindClosingByEsc();

			if (this.isCompatibleMode())
			{
				setTimeout(() => {
					this.unbindAutoHide();
				}, 0);
			}
			else
			{
				this.unbindAutoHide();
			}

			this.emit('onAfterClose', new BaseEvent({ compatData: [this] }));

			if (!this.isCacheable())
			{
				this.destroy();
			}

		});
	}

	bringToFront(): void
	{
		if (this.isShown())
		{
			ZIndexManager.bringToFront(this.getPopupContainer());
		}
	}

	toggle(): void
	{
		this.isShown() ? this.close() : this.show();
	}

	/**
	 *
	 * @private
	 */
	animateOpening(callback: Function): void
	{
		Dom.removeClass(this.getPopupContainer(), this.animationCloseClassName);

		if (this.animationShowClassName !== null)
		{
			Dom.addClass(this.getPopupContainer(), this.animationShowClassName);

			if (this.animationCloseEventType !== null)
			{
				const eventName = this.animationCloseEventType + 'end';
				this.getPopupContainer().addEventListener(eventName, function handleTransitionEnd() {
					this.removeEventListener(eventName, handleTransitionEnd);
					callback();
				});
			}
			else
			{
				callback();
			}
		}
		else
		{
			callback();
		}
	}

	/**
	 * @private
	 */
	animateClosing(callback: Function): void
	{
		Dom.removeClass(this.getPopupContainer(), this.animationShowClassName);

		if (this.animationCloseClassName !== null)
		{
			Dom.addClass(this.getPopupContainer(), this.animationCloseClassName);

			if (this.animationCloseEventType !== null)
			{
				const eventName = this.animationCloseEventType + 'end';
				this.getPopupContainer().addEventListener(eventName, function handleTransitionEnd() {
					this.removeEventListener(eventName, handleTransitionEnd);
					callback();
				});
			}
			else
			{
				callback();
			}
		}
		else
		{
			callback();
		}
	}

	setAnimation(options: PopupAnimationOptions): void
	{
		if (Type.isPlainObject(options))
		{
			this.animationShowClassName = Type.isStringFilled(options.showClassName) ? options.showClassName : null;
			this.animationCloseClassName = Type.isStringFilled(options.closeClassName) ? options.closeClassName : null;
			this.animationCloseEventType =
				options.closeAnimationType === 'animation' || options.closeAnimationType === 'transition'
					? options.closeAnimationType
					: null
			;
		}
		else if (Type.isStringFilled(options))
		{
			const animationName = options;
			if (animationName === 'fading')
			{
				this.animationShowClassName = 'popup-window-show-animation-opacity';
				this.animationCloseClassName = 'popup-window-close-animation-opacity';
				this.animationCloseEventType = 'animation';
			}
			else if (animationName === 'fading-slide')
			{
				this.animationShowClassName = 'popup-window-show-animation-opacity-transform';
				this.animationCloseClassName = 'popup-window-close-animation-opacity';
				this.animationCloseEventType = 'animation';
			}
			else if (animationName === 'scale')
			{
				this.animationShowClassName = 'popup-window-show-animation-scale';
				this.animationCloseClassName = 'popup-window-close-animation-opacity';
				this.animationCloseEventType = 'animation';
			}
		}
		else if (options === false || options === null)
		{
			this.animationShowClassName = null;
			this.animationCloseClassName = null;
			this.animationCloseEventType = null;
		}
	}

	isShown(): boolean
	{
		return !this.isDestroyed() && this.getPopupContainer().style.display === 'block';
	}

	destroy(): void
	{
		if (this.destroyed)
		{
			return;
		}

		if (this.disableScroll)
		{
			this.#enableTargetScroll();
		}

		this.destroyed = true;

		this.emit('onDestroy', new BaseEvent({ compatData: [this] }));

		this.unbindClosingByEsc();

		if (this.isCompatibleMode())
		{
			setTimeout(() => {
				this.unbindAutoHide();
			}, 0);
		}
		else
		{
			this.unbindAutoHide();
		}

		Event.unbindAll(this);
		Event.unbind(document, 'mousemove', this.handleDocumentMouseMove);
		Event.unbind(document, 'mouseup', this.handleDocumentMouseUp);
		Event.unbind(window, 'resize', this.handleResizeWindow);

		this.removeOverlay();

		ZIndexManager.unregister(this.popupContainer);
		this.zIndexComponent = null;

		Dom.remove(this.popupContainer);

		this.popupContainer = null;
		this.contentContainer = null;
		this.closeIcon = null;
		this.titleBar = null;
		this.buttonsContainer = null;
		this.angle = null;
		this.angleArrowElement = null;
		this.resizeIcon = null;
	}

	adjustPosition(bindOptions: {
		forceBindPosition?: boolean,
		forceLeft?: boolean,
		forceTop?: boolean,
		position?: 'top' | 'bootom'
	}): void
	{
		if (bindOptions && typeof (bindOptions) === 'object')
		{
			this.bindOptions = bindOptions;
		}

		const bindElementPos = this.getBindElementPos(this.bindElement);

		if (
			!this.bindOptions.forceBindPosition &&
			this.bindElementPos !== null &&
			bindElementPos.top === this.bindElementPos.top &&
			bindElementPos.left === this.bindElementPos.left
		)
		{
			return;
		}

		this.bindElementPos = bindElementPos;

		const windowSize = bindElementPos.windowSize ? bindElementPos.windowSize : this.getWindowSize();
		const windowScroll = bindElementPos.windowScroll ? bindElementPos.windowScroll : this.getWindowScroll();

		const popupWidth = bindElementPos.popupWidth ? bindElementPos.popupWidth : this.popupContainer.offsetWidth;
		const popupHeight = bindElementPos.popupHeight ? bindElementPos.popupHeight : this.popupContainer.offsetHeight;

		const angleTopOffset = Popup.getOption('angleTopOffset');

		let left =
			this.bindElementPos.left + this.offsetLeft -
			(this.isTopOrBottomAngle() ? Popup.getOption('angleLeftOffset') : 0)
		;

		if (
			!this.bindOptions.forceLeft &&
			(left + popupWidth + this.bordersWidth) >= (windowSize.innerWidth + windowScroll.scrollLeft) &&
			(windowSize.innerWidth + windowScroll.scrollLeft - popupWidth - this.bordersWidth) > 0)
		{
			const bindLeft = left;
			left = windowSize.innerWidth + windowScroll.scrollLeft - popupWidth - this.bordersWidth;
			if (this.isTopOrBottomAngle())
			{
				this.setAngle({ offset: bindLeft - left + this.angle.defaultOffset });
			}
		}
		else if (this.isTopOrBottomAngle())
		{
			this.setAngle({ offset: this.angle.defaultOffset + (left < 0 ? left : 0) });
		}

		if (left < 0)
		{
			left = 0;
		}

		let top = 0;

		if (this.bindOptions.position && this.bindOptions.position === 'top')
		{

			top = this.bindElementPos.top - popupHeight - this.offsetTop - (this.isBottomAngle() ? angleTopOffset : 0);
			if (top < 0 || (!this.bindOptions.forceTop && top < windowScroll.scrollTop))
			{
				top = this.bindElementPos.bottom + this.offsetTop;
				if (this.angle !== null)
				{
					top += angleTopOffset;
					this.setAngle({ position: 'top' });
				}
			}
			else if (this.isTopAngle())
			{
				top = top - angleTopOffset + Popup.getOption('positionTopXOffset');
				this.setAngle({ position: 'bottom' });
			}
			else
			{
				top += Popup.getOption('positionTopXOffset');
			}
		}
		else
		{
			top = this.bindElementPos.bottom + this.offsetTop + this.getAngleHeight();

			if (
				!this.bindOptions.forceTop &&
				(top + popupHeight) > (windowSize.innerHeight + windowScroll.scrollTop) &&
				(this.bindElementPos.top - popupHeight - this.getAngleHeight()) >= 0) //Can we place the PopupWindow above the bindElement?
			{
				//The PopupWindow doesn't place below the bindElement. We should place it above.
				top = this.bindElementPos.top - popupHeight;

				if (this.isTopOrBottomAngle())
				{
					top -= angleTopOffset;
					this.setAngle({ position: 'bottom' });
				}

				top += Popup.getOption('positionTopXOffset');

			}
			else if (this.isBottomAngle())
			{
				top += angleTopOffset;
				this.setAngle({ position: 'top' });
			}
		}

		if (top < 0)
		{
			top = 0;
		}

		const event = new PositionEvent();
		event.left = left;
		event.top = top;

		this.emit('onBeforeAdjustPosition', event);

		Dom.adjust(this.popupContainer, {
			style: {
				top: event.top + 'px',
				left: event.left + 'px'
			}
		});
	}

	enterFullScreen(): void
	{
		if (Popup.fullscreenStatus)
		{
			if (document.cancelFullScreen)
			{
				document.cancelFullScreen();
			}
			else if (document.mozCancelFullScreen)
			{
				document.mozCancelFullScreen();
			}
			else if (document.webkitCancelFullScreen)
			{
				document.webkitCancelFullScreen();
			}
		}
		else
		{
			if (this.contentContainer.requestFullScreen)
			{
				this.contentContainer.requestFullScreen();
				Event.bind(window, 'fullscreenchange', this.handleFullScreen);
			}
			else if (this.contentContainer.mozRequestFullScreen)
			{
				this.contentContainer.mozRequestFullScreen();
				Event.bind(window, 'mozfullscreenchange', this.handleFullScreen);
			}
			else if (this.contentContainer.webkitRequestFullScreen)
			{
				this.contentContainer.webkitRequestFullScreen();
				Event.bind(window, 'webkitfullscreenchange', this.handleFullScreen);
			}
			else
			{
				console.log('fullscreen mode is not supported');
			}
		}
	}

	/**
	 * @private
	 */
	handleFullScreen(event): void
	{
		if (Popup.fullscreenStatus)
		{
			Event.unbind(window, 'fullscreenchange', this.handleFullScreen);
			Event.unbind(window, 'webkitfullscreenchange', this.handleFullScreen);
			Event.unbind(window, 'mozfullscreenchange', this.handleFullScreen);

			Popup.fullscreenStatus = false;

			if (!this.isDestroyed())
			{
				Dom.removeClass(this.contentContainer, 'popup-window-fullscreen');
				this.emit('onFullscreenLeave');
				this.adjustPosition();
			}
		}
		else
		{
			Popup.fullscreenStatus = true;

			if (!this.isDestroyed())
			{
				Dom.addClass(this.contentContainer, 'popup-window-fullscreen');
				this.emit('onFullscreenEnter');
				this.adjustPosition();
			}
		}
	}

	/**
	 * @private
	 */
	handleCloseIconClick(event): void
	{
		this.tryCloseByEvent(event);
		event.stopPropagation();
	}

	/**
	 * @private
	 */
	handleContainerClick(event): void
	{
		event.stopPropagation();
	}

	/**
	 * @private
	 */
	handleDocumentKeyUp(event): void
	{
		if (event.keyCode === 27)
		{
			checkEscPressed(this.getZindex(), () => {
				this.close();
			});
		}
	}

	/**
	 * @private
	 */
	handleResizeWindow(): void
	{
		if (this.isShown())
		{
			this.adjustPosition();
			if (this.overlay !== null)
			{
				this.resizeOverlay();
			}
		}
	}

	/**
	 * @private
	 */
	handleMove(offsetX: number, offsetY: number, pageX: number, pageY: number): void
	{
		let left = parseInt(this.popupContainer.style.left) + offsetX;
		let top = parseInt(this.popupContainer.style.top) + offsetY;

		if (typeof (this.params.draggable) === 'object' && this.params.draggable.restrict)
		{
			//Left side
			if (left < 0)
			{
				left = 0;
			}

			let scrollWidth;
			let scrollHeight;
			if (this.isTargetDocumentBody())
			{
				scrollWidth = document.documentElement.scrollWidth;
				scrollHeight = document.documentElement.scrollHeight;
			}
			else
			{
				scrollWidth = this.getTargetContainer().scrollWidth;
				scrollHeight = this.getTargetContainer().scrollHeight;
			}

			//Right side
			const floatWidth = this.popupContainer.offsetWidth;
			const floatHeight = this.popupContainer.offsetHeight;

			if (left > (scrollWidth - floatWidth))
			{
				left = scrollWidth - floatWidth;
			}

			if (top > (scrollHeight - floatHeight))
			{
				top = scrollHeight - floatHeight;
			}

			//Top side
			if (top < 0)
			{
				top = 0;
			}
		}

		this.popupContainer.style.left = left + 'px';
		this.popupContainer.style.top = top + 'px';
	}

	/**
	 * @private
	 */
	_startDrag(event: MouseEvent, options): void
	{
		options = options || {};
		if (Type.isStringFilled(options.cursor))
		{
			this.dragOptions.cursor = options.cursor;
		}

		if (Type.isStringFilled(options.eventName))
		{
			this.dragOptions.eventName = options.eventName;
		}

		if (Type.isFunction(options.callback))
		{
			this.dragOptions.callback = options.callback;
		}

		this.dragPageX = event.pageX;
		this.dragPageY = event.pageY;
		this.dragged = false;

		Event.bind(document, 'mousemove', this.handleDocumentMouseMove);
		Event.bind(document, 'mouseup', this.handleDocumentMouseUp);

		if (document.body.setCapture)
		{
			document.body.setCapture();
		}

		document.body.ondrag = () => false;
		document.body.onselectstart = () => false;
		document.body.style.cursor = this.dragOptions.cursor;
		document.body.style.MozUserSelect = 'none';
		this.popupContainer.style.MozUserSelect = 'none';

		if (this.shouldFrontOnShow())
		{
			this.bringToFront();
		}

		event.preventDefault();
	}

	/**
	 * @private
	 */
	handleDocumentMouseMove(event): void
	{
		if (this.dragPageX === event.pageX && this.dragPageY === event.pageY)
		{
			return;
		}

		this.dragOptions.callback(
			event.pageX - this.dragPageX,
			event.pageY - this.dragPageY,
			event.pageX,
			event.pageY
		);

		this.dragPageX = event.pageX;
		this.dragPageY = event.pageY;

		if (!this.dragged)
		{
			this.emit(`on${this.dragOptions.eventName}Start`, new BaseEvent({ compatData: [this] }));
			this.dragged = true;
		}

		this.emit(`on${this.dragOptions.eventName}`, new BaseEvent({ compatData: [this] }));
	}

	/**
	 * @private
	 */
	handleDocumentMouseUp(event: MouseEvent): void
	{
		if (document.body.releaseCapture)
		{
			document.body.releaseCapture();
		}

		Event.unbind(document, 'mousemove', this.handleDocumentMouseMove);
		Event.unbind(document, 'mouseup', this.handleDocumentMouseUp);

		document.body.ondrag = null;
		document.body.onselectstart = null;
		document.body.style.cursor = '';
		document.body.style.MozUserSelect = '';
		this.popupContainer.style.MozUserSelect = '';

		this.emit(`on${this.dragOptions.eventName}End`, new BaseEvent({ compatData: [this] }));
		this.dragged = false;

		event.preventDefault();
	}
}

let escCallbackIndex = -1;
let escCallback = null;

function checkEscPressed(zIndex, callback)
{
	if (zIndex === false)
	{
		if (escCallback && escCallback.length > 0)
		{
			for (let i = 0; i < escCallback.length; i++)
			{
				escCallback[i]();
			}

			escCallback = null;
			escCallbackIndex = -1;
		}
	}
	else
	{
		if (escCallback === null)
		{
			escCallback = [];
			escCallbackIndex = -1;
			setTimeout(() => {
				checkEscPressed(false);
			}, 10);
		}

		if (zIndex > escCallbackIndex)
		{
			escCallbackIndex = zIndex;
			escCallback = [callback];
		}
		else if (zIndex === escCallbackIndex)
		{
			escCallback.push(callback);
		}
	}
}