Source: layout-content-page-editor-web/src/main/resources/META-INF/resources/js/components/fragment_entry_link/FragmentEditableFieldTooltip.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 {debounce} from 'frontend-js-web';
import Component from 'metal-component';
import dom from 'metal-dom';
import {Align} from 'metal-position';
import Soy from 'metal-soy';
import {Config} from 'metal-state';

import {getConnectedComponent} from '../../store/ConnectedComponent.es';
import templates from './FragmentEditableFieldTooltip.soy';

/**
 * @type number
 */
const WINDOW_RESIZE_DEBOUNCE_DELAY = 100;

/**
 * Creates a Fragment Editable Field Tooltip component.
 */
class FragmentEditableFieldTooltip extends Component {
	/**
	 * @inheritDoc
	 */
	attached() {
		this._alignTooltip();
	}

	/**
	 * @inheritDoc
	 */
	created() {
		this._handleDocumentClick = this._handleDocumentClick.bind(this);
		this._handleFragmentEntryLinkListWrapperScroll = this._handleFragmentEntryLinkListWrapperScroll.bind(
			this
		);

		this._handleWindowResize = debounce(
			this._handleWindowResize.bind(this),
			WINDOW_RESIZE_DEBOUNCE_DELAY
		);

		this._windowResizeHandler = dom.on(
			window,
			'resize',
			this._handleWindowResize
		);

		this._documentClickHandler = dom.on(
			document.body,
			'click',
			this._handleDocumentClick
		);

		const fragmentEntryLinkListWrapper = document.querySelector(
			'.fragment-entry-link-list-wrapper'
		);

		if (fragmentEntryLinkListWrapper) {
			fragmentEntryLinkListWrapper.addEventListener(
				'scroll',
				this._handleFragmentEntryLinkListWrapperScroll
			);
		}
	}

	/**
	 * @inheritDoc
	 */
	disposed() {
		this._documentClickHandler.removeListener();
		this._windowResizeHandler.removeListener();

		const fragmentEntryLinkListWrapper = document.querySelector(
			'.fragment-entry-link-list-wrapper'
		);

		if (fragmentEntryLinkListWrapper) {
			fragmentEntryLinkListWrapper.removeEventListener(
				'scroll',
				this._handleFragmentEntryLinkListWrapperScroll
			);
		}
	}

	/**
	 * @inheritDoc
	 */
	rendered() {
		this._alignTooltip();
	}

	/**
	 * Aligns the tooltip position for editable fields.
	 *
	 * @private
	 */
	_alignTooltip() {
		if (this.refs.tooltip) {
			Align.align(this.refs.tooltip, this.alignElement, Align.Top, false);
		}
	}

	/**
	 * Handles a button click.
	 *
	 * @param {MouseEvent} event
	 */
	_handleButtonClick(event) {
		const button = event.delegateTarget;
		const buttonId = button.dataset.tooltipButtonId;

		this.emit('buttonClick', {
			buttonId
		});
	}

	/**
	 * Hides the tooltip when a document click occurs outside the tooltip.
	 *
	 * @param {MouseEvent} event The document click.
	 */
	_handleDocumentClick(event) {
		if (
			this.refs.tooltip &&
			!this.refs.tooltip.contains(event.target) &&
			!this.alignElement.contains(event.target)
		) {
			this.emit('outsideTooltipClick');
		}
	}

	/**
	 * Callback executed to align the tooltip when the window is resized.
	 *
	 * @private
	 */
	_handleWindowResize() {
		this._alignTooltip();
	}

	/**
	 * @private
	 */
	_handleFragmentEntryLinkListWrapperScroll() {
		this._alignTooltip();
	}
}

/**
 * State definition.
 *
 * @static
 * @type {!Object}
 */
FragmentEditableFieldTooltip.STATE = {
	/**
	 * Reference element the tooltip alignment is based on.
	 *
	 * @default undefined
	 * @instance
	 * @memberOf FragmentEditableFieldTooltip
	 * @type {HTMLElement}
	 */
	alignElement: Config.object().required(),

	/**
	 * List of buttons rendered inside the tooltip.
	 *
	 * @default undefined
	 * @instance
	 * @memberOf FragmentEditableFieldTooltip
	 * @type {!Array<{
	 *   id: !string,
	 *   label: !string
	 * }>}
	 */
	buttons: Config.arrayOf(
		Config.shapeOf({
			icon: Config.string().required(),
			id: Config.string().required(),
			label: Config.string().required()
		})
	)
};

const ConnectedFragmentEditableFieldTooltip = getConnectedComponent(
	FragmentEditableFieldTooltip,
	['spritemap']
);

Soy.register(ConnectedFragmentEditableFieldTooltip, templates);

export {ConnectedFragmentEditableFieldTooltip, FragmentEditableFieldTooltip};
export default ConnectedFragmentEditableFieldTooltip;