Source: site-navigation-admin-web/src/main/resources/META-INF/resources/js/SiteNavigationMenuDOMHandler.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 {contains, hasClass, toElement} from 'metal-dom';
import position from 'metal-position';

import {
	getChildren,
	getId,
	isMenuItem,
	MENU_CONTAINER_CLASSNAME,
	MENU_ITEM_CLASSNAME,
	MENU_ITEM_DRAGGING_CLASSNAME
} from './SiteNavigationMenuItemDOMHandler.es';

/**
 * Constant indicating how much a menuItem element should be moved
 * to the right in order to consider it nested to it's parent.
 */

const NEST_THRESHOLD = 30;

/**
 * Gets the nearest menuItem element for the given originMenuItem
 * and placeholder.
 *
 * The placeholder is used for retrieving the reference position, and
 * the originMenuItem for checking that child elements are being avoided.
 *
 * @param {HTMLElement} originMenuItem
 * @param {HTMLElement} placeholder
 * @return {HTMLElement}
 */

const getNearestMenuItem = function(originMenuItem, placeholder) {
	const container = toElement(`.${MENU_CONTAINER_CLASSNAME}`);
	const originMenuItemId = getId(originMenuItem);
	const originMenuItemRegion = position.getRegion(placeholder);

	return Array.prototype.slice
		.call(document.querySelectorAll(`.${MENU_ITEM_CLASSNAME}`))
		.filter(
			menuItem =>
				contains(container, menuItem) &&
				!contains(originMenuItem, menuItem) &&
				getId(menuItem) !== originMenuItemId &&
				!hasClass(menuItem, MENU_ITEM_DRAGGING_CLASSNAME)
		)
		.map(menuItem => {
			const menuItemRegion = position.getRegion(menuItem);

			const distance = Math.sqrt(
				Math.pow(originMenuItemRegion.left - menuItemRegion.left, 2) +
					Math.pow(originMenuItemRegion.top - menuItemRegion.top, 2)
			);

			return {
				distance,
				menuItem,
				region: menuItemRegion
			};
		})
		.reduce(
			(distanceA, distanceB) => {
				return distanceA.distance < distanceB.distance
					? distanceA
					: distanceB;
			},
			{
				distance: Infinity,
				menuItem: null
			}
		).menuItem;
};

/**
 * Inserts the given menuItem as child of given parentMenuItem
 * at the given position.
 * @param {HTMLElement} parentMenuItem
 * @param {HTMLElement} menuItem
 * @param {number} position
 * @review
 */

const insertAtPosition = function(parentMenuItem, menuItem, position) {
	const children = getChildren(parentMenuItem);

	if (position >= children.length) {
		parentMenuItem.appendChild(menuItem);
	} else {
		parentMenuItem.insertBefore(menuItem, children[position]);
	}
};

/**
 * Insert the given menuItem at the top of the navigation tree
 * @param {HTMLElement} menuItem
 */

const insertAtTop = function(menuItem) {
	const container = toElement(`.${MENU_CONTAINER_CLASSNAME}`);

	const children = getChildren(container);

	if (children.length) {
		container.insertBefore(menuItem, children[0]);
	} else {
		container.appendChild(menuItem);
	}
};

/**
 * Returns true if the first menuItem element is over
 * the second menuItem element.
 * @param {HTMLElement} menuItemA
 * @param {HTMLElement} menuItemB
 * @return {boolean}
 */

const isOver = function(menuItemA, menuItemB) {
	const menuItemARegion = position.getRegion(menuItemA);
	const menuItemBRegion = position.getRegion(menuItemB);

	return menuItemARegion.top < menuItemBRegion.top;
};

/**
 * Returns true if the given menuItem element should be nested
 * inside the given parentMenuItem element by checking their positions.
 * @param {HTMLElement} menuItem
 * @param {HTMLElement} parentMenuItem
 * @return {boolean}
 */

const shouldBeNested = (menuItem, parentMenuItem) => {
	let nested = false;

	if (isMenuItem(parentMenuItem)) {
		const menuItemRegion = position.getRegion(menuItem);
		const parentMenuItemRegion = position.getRegion(parentMenuItem);

		const nestedInParent =
			menuItemRegion.left > parentMenuItemRegion.left + NEST_THRESHOLD;

		const parentWithChildren =
			getChildren(parentMenuItem).filter(
				childMenuItem => getId(childMenuItem) !== getId(menuItem)
			).length > 0;

		nested = nestedInParent || parentWithChildren;
	}

	return nested;
};

export {
	getNearestMenuItem,
	insertAtPosition,
	insertAtTop,
	isOver,
	shouldBeNested
};