Source: layout-content-page-editor-web/src/main/resources/META-INF/resources/js/components/edit_mode/DisabledAreaPopover.es.js

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

import Component from 'metal-component';
import {delegate, on} from 'metal-dom';
import {Align} from 'metal-position';
import Soy from 'metal-soy';
import {Config} from 'metal-state';

import templates from './DisabledAreaPopover.soy';

/**
 * Mapping from metal-position to popover positions
 * @review
 * @type {!object}
 */
const POPOVER_POSITIONS = {
	[Align.Bottom]: 'bottom',
	[Align.BottomLeft]: 'bottom',
	[Align.BottomRight]: 'bottom',
	[Align.Left]: 'left',
	[Align.Right]: 'right',
	[Align.Top]: 'top',
	[Align.TopLeft]: 'top',
	[Align.TopRight]: 'top'
};

/**
 * DisabledAreaPopover
 * @review
 */
class DisabledAreaPopover extends Component {
	/**
	 * @inheritDoc
	 * @review
	 */
	created() {
		this._attachListener();

		this._documentListeners = [
			on(document.body, 'click', this._handleDocumentClick.bind(this)),

			on(
				'.fragment-entry-link-list-wrapper',
				'scroll',
				this._handleFragmentEntryLinkListScroll.bind(this)
			)
		];
	}

	/**
	 * @inheritDoc
	 * @review
	 */
	disposed() {
		this._detachListener();

		this._documentListeners.forEach(documentListener =>
			documentListener.removeListener()
		);
	}

	/**
	 * @inheritdoc
	 * @review
	 */
	rendered() {
		if (this._position) {
			requestAnimationFrame(() => {
				let popoverPosition = Align.TopCenter;

				const suggestedAlign = Align.suggestAlignBestRegion(
					this.refs.popover,
					this._clickedElement,
					popoverPosition
				);

				if (suggestedAlign.position !== popoverPosition) {
					popoverPosition = Align.BottomCenter;
				}

				Align.align(
					this.refs.popover,
					this._clickedElement,
					popoverPosition,
					false
				);

				this._setPosition(popoverPosition);
			});
		}
	}

	/**
	 * @inheritDoc
	 * @review
	 */
	syncSelector() {
		this._detachListener();
		this._attachListener();
	}

	/**
	 * Attachs a click listener for elements defined in selector
	 * property and calls _detachListener for old selectors if necessary.
	 * @private
	 * @review
	 */
	_attachListener() {
		if (this._elementListener) {
			this._detachListener();
		}

		if (this.selector) {
			this._elementListener = delegate(
				document.body,
				'click',
				this.selector,
				this._handleElementClick.bind(this)
			);
		}
	}

	/**
	 * Dettachs a click listener (if any).
	 * @private
	 * @review
	 */
	_detachListener() {
		if (this._elementListener) {
			this._elementListener.removeListener();

			this._elementListener = null;
		}
	}

	/**
	 * Handles a click over DOM (but not over a defined element)
	 * updating the _clickedElement property and setting visibility to false.
	 * @param {MouseEvent} event
	 * @private
	 * @review
	 */
	_handleDocumentClick() {
		this._hidePopover();
	}

	/**
	 * Handles a click over any element defined in selector property by
	 * updating the _clickedElement property and setting visibility to true.
	 * @param {MouseEvent} event
	 * @private
	 * @review
	 */
	_handleElementClick(event) {
		event.stopImmediatePropagation();

		this._clickedElement = event.delegateTarget;

		this._setPosition(Align.TopCenter);
	}

	/**
	 * Handles a click over DOM (but not over a defined element)
	 * updating the _clickedElement property and setting visibility to false.
	 * @param {MouseEvent} event
	 * @private
	 * @review
	 */
	_handleFragmentEntryLinkListScroll() {
		this._hidePopover();
	}

	/**
	 * Hides the popover
	 * @private
	 * @review
	 */
	_hidePopover() {
		this._clickedElement = null;
		this._position = null;
	}

	/**
	 * Sets the popover position from the position given by metal-position
	 * align method.
	 * @param {number} alignPosition
	 * @private
	 * @review
	 */
	_setPosition(alignPosition) {
		if (this._position !== POPOVER_POSITIONS[alignPosition]) {
			this._position = POPOVER_POSITIONS[alignPosition];
		}
	}
}

/**
 * State definition.
 * @review
 * @static
 * @type {!Object}
 */
DisabledAreaPopover.STATE = {
	/**
	 * Clicked element
	 * @default null
	 * @instance
	 * @memberOf DisabledAreaPopover
	 * @private
	 * @review
	 * @type {!object}
	 */
	_clickedElement: Config.object().value(null),

	/**
	 * Listeners attached to document
	 * @default []
	 * @instance
	 * @memberOf DisabledAreaPopover
	 * @private
	 * @review
	 * @type {!Array<object>}
	 */
	_documentListeners: Config.arrayOf(Config.object()).value([]),

	/**
	 * Click listener attached to DOM
	 * @default null
	 * @instance
	 * @memberOf DisabledAreaPopover
	 * @private
	 * @review
	 * @type {!object}
	 */
	_elementListener: Config.object().value(null),

	/**
	 * Popover position
	 * @default null
	 * @instance
	 * @memberOf DisabledAreaPopover
	 * @private
	 * @review
	 * @type {!string}
	 */
	_position: Config.string().value(null),

	/**
	 * Selector for elements where this popover should be shown
	 * @default undefined
	 * @instance
	 * @memberOf DisabledAreaPopover
	 * @review
	 * @type {!string}
	 */
	selector: Config.string().required()
};

Soy.register(DisabledAreaPopover, templates);

export {DisabledAreaPopover};
export default DisabledAreaPopover;