/*!
 * OpenUI5
 * (c) Copyright 2026 SAP SE or an SAP affiliate company.
 * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
 */

/**
 * @namespace
 * @name sap.ui.core.delegate
 * @public
 */

// Provides class sap.ui.core.delegate.ItemNavigation
sap.ui.define([
	'sap/base/i18n/Localization',
	'sap/ui/base/EventProvider',
	"sap/base/assert",
	"sap/base/Log",
	"sap/ui/events/KeyCodes",
	"sap/ui/thirdparty/jquery",
	"sap/ui/core/Element",
	"sap/ui/dom/jquery/Selectors" // jQuery custom selectors ":sapFocusable"
],
	function(Localization, EventProvider, assert, Log, KeyCodes, jQuery, Element) {
	"use strict";
	/* eslint-disable no-lonely-if */

	/**
	 * Creates an <code>ItemNavigation</code> delegate that can be attached to controls requiring
	 * capabilities for keyboard navigation between items.
	 *
	 * @class Delegate for the navigation between DOM nodes with the keyboard.
	 *
	 * The <code>ItemNavigation</code> provides keyboard and mouse navigation between DOM nodes representing items.
	 * This means that controls rendering a list of items can attach this delegate to get a common keyboard and mouse support
	 * to navigate between these items.
	 * It is possible to navigate between the items via the arrow keys.
	 * If needed, paging using the Page Up and Page Down keys is possible. (To activate call <code>setPageSize</code> with a value &gt; 0.)
	 * HOME and END keys are also supported.
	 * Focusing an item via mouse also is also supported. For mouse navigation, the <code>mousedown</code> event is used.
	 *
	 * As the <code>ItemNavigation</code> works with DOM nodes, the items and the control area must be provided as
	 * DOM references. There is one root DOM reference (set via <code>setRootDomRef</code>).
	 * All item DOM references (set via <code>setItemDomRefs</code>) must be places somewhere inside of this root DOM reference.
	 * Only focusable items are used for the navigation, meaning disabled items or separator items are just ignored by navigating
	 * through the items. In some cases however, it makes sense to put the non-focusable items in the array of the DOM references to
	 * keep the indexes stable or like in the calling control.
	 * <b>Hint:</b> To make a DOM reference focusable a <code>tabindex</code> of -1 can be set.
	 *
	 * <b>Note</b> After re-rendering of the control or changing the DOM nodes of the control, the
	 * DOM references of the <code>ItemNavigation</code> must be updated. Then the same item
	 * (corresponding to the index) will get the focus.
	 *
	 * The <code>ItemNavigation</code> adjusts the <code>tabindex</code> of all DOM references relating to the current
	 * focused item. So if the control containing the items gets the focus (e.g. via tab navigation),
	 * it is always the focused items which will be focused.
	 *
	 * <b>Note:</b> If the <code>ItemNavigation</code> is nested in another <code>ItemNavigation</code>
	 * (e.g. <code>SegmentedButton</code> in <code>Toolbar</code>), the <code>RootDomRef</code> will always have <code>tabindex</code> -1.
	 *
	 * Per default the <code>ItemNavigation</code> cycles over the items.
	 * It navigates again to the first item if the Arrow Down or Arrow Right key is pressed while
	 * the last item has the focus. It navigates to the last item if arrow up or
	 * arrow left is pressed while the first item has the focus.
	 * If you want to stop the navigation at the first and last item,
	 * call the <code>setCycling</code> method with a value of <code>false</code>.
	 *
	 * It is possible to have multiple columns in the item navigation. If multiple columns
	 * are used, the keyboard navigation changes. The Arrow Right and Arrow Left keys will take the user to the next or previous
	 * item, and the Arrow Up and Arrow Down keys will navigate the same way but in a vertical direction.
	 *
	 * The <code>ItemNavigation</code> also allows setting a selected index that is used to identify
	 * the selected item. Initially, if no other focus is set, the selected item will be the focused one.
	 * Note that navigating through the items will not change the selected item, only the focus.
	 * (For example a radio group has one selected item.)
	 *
	 * @author SAP SE
	 *
	 * @extends sap.ui.base.EventProvider
	 *
	 * @param {Element} oDomRef The root DOM reference that includes all items
	 * @param {Element[]} aItemDomRefs Array of DOM references representing the items for the navigation
	 * @param {boolean} [bNotInTabChain=false] Whether the selected element should be in the tab chain or not
	 *
	 * @version 1.136.17
	 * @alias sap.ui.core.delegate.ItemNavigation
	 * @public
	 */
	var ItemNavigation = EventProvider.extend("sap.ui.core.delegate.ItemNavigation", /** @lends sap.ui.core.delegate.ItemNavigation.prototype */ {
		constructor: function(oDomRef, aItemDomRefs, bNotInTabChain) {

			EventProvider.apply(this);

			// the surrounding dom ref that is focused initially
			this.oDomRef = null;

			if (oDomRef) {
				this.setRootDomRef(oDomRef);
			}

			// the array of dom refs representing the items
			this.aItemDomRefs = [];
			if (aItemDomRefs) {
				this.setItemDomRefs(aItemDomRefs);
			}

			// initialize Tabindex
			this.iTabIndex = -1;

			// whether the active element should get a tabindex of 0 or -1
			this.iActiveTabIndex = bNotInTabChain ? -1 : 0;

			// the initial focusedindex
			this.iFocusedIndex = -1;

			// the initial selected index (if any)
			this.iSelectedIndex = -1;

			// default for cycling
			this.bCycling = true;

			// default for table mode
			this.bTableMode = false;

			// the pagesize for pageup and down events
			this.iPageSize = -1;

			// a marker to enable focusin to decide HOW the focus arrived
			this._bMouseDownHappened = false;

			// default disabled modifiers these modifiers will not be handled by ItemNavigation
			this.oDisabledModifiers = {
				sapend : ["alt", "shift"],
				saphome : ["alt", "shift"]
			};
		}
	});

	ItemNavigation.Events = {
		BeforeFocus: "BeforeFocus",
		AfterFocus: "AfterFocus",
		BorderReached: "BorderReached",
		FocusAgain: "FocusAgain",
		FocusLeave: "FocusLeave"
	};

	/**
	 * The <code>BeforeFocus</code> event is fired before the actual item is focused.
	 * Listeners may prevent the focus by calling the <code>preventDefault</code> method on the event object.
	 *
	 * @name sap.ui.core.delegate.ItemNavigation#BeforeFocus
	 * @event
	 * @param {int} index Index of the item
	 * @param {jQuery.Event} event Event that leads to the focus change
	 * @public
	 */

	/**
	 * The <code>AfterFocus</code> event is fired after the actual item is focused.
	 * The control can register to this event and react on the focus change.
	 *
	 * @name sap.ui.core.delegate.ItemNavigation#AfterFocus
	 * @event
	 * @param {int} index Index of the item
	 * @param {jQuery.Event} event Event that leads to the focus change
	 * @public
	 */

	/**
	 * The <code>BorderReached</code> event is fired if the border of the items is reached and
	 * no cycling is used, meaning an application can react on this.
	 *
	 * For example if the first item is focused and the Arrow Left key is pressed.
	 *
	 * @name sap.ui.core.delegate.ItemNavigation#BorderReached
	 * @event
	 * @param {int} index Index of the item
	 * @param {jQuery.Event} event Event that leads to the focus change
	 * @public
	 */

	/**
	 * The <code>FocusAgain</code> event is fired if the current focused item is focused again
	 * (e.g. click again on focused item.)
	 *
	 * @name sap.ui.core.delegate.ItemNavigation#FocusAgain
	 * @event
	 * @param {int} index Index of the item
	 * @param {jQuery.Event} event Event that leads to the focus change
	 * @public
	 */

	/**
	 * The <code>FocusLeave</code> event fired if the focus is set outside the control handled by the <code>ItemNavigation</code>.
	 *
	 * @name sap.ui.core.delegate.ItemNavigation#FocusLeave
	 * @event
	 * @param {int} index Index of the item
	 * @param {jQuery.Event} event Event that leads to the focus change
	 * @public
	 */

	/**
	 * Sets the disabled modifiers
	 * These modifiers will not be handled by the <code>ItemNavigation</code>
	 *
	 * <pre>
	 * Example: Disable shift + up handling of the <code>ItemNavigation</code>
	 *
	 * oItemNavigation.setDisabledModifiers({
	 *     sapnext : ["shift"]
	 * });
	 *
	 * Possible keys are : "shift", "alt", "ctrl", "meta"
	 * Possible events are : "sapnext", "sapprevious", "saphome", "sapend"
	 * </pre>
	 *
	 * @param {Object} oDisabledModifiers Object that includes event type with disabled keys as an array
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setDisabledModifiers = function(oDisabledModifiers) {
		this.oDisabledModifiers = oDisabledModifiers;
		return this;
	};

	/**
	 * Returns disabled modifiers
	 * These modifiers will not be handled by the <code>ItemNavigation</code>
	 *
	 * @param {object} oDisabledModifiers Object that includes event type with disabled keys as an array
	 * @return {object} Object that includes event type with disabled keys as an array
	 * @public
	 */
	ItemNavigation.prototype.getDisabledModifiers = function(oDisabledModifiers) {
		return this.oDisabledModifiers;
	};

	/**
	 * Check whether given event has disabled modifier or not
	 *
	 * @param {jQuery.Event} oEvent jQuery event
	 * @return {boolean} Flag if disabled modifiers are set
	 * @public
	 */
	ItemNavigation.prototype.hasDisabledModifier = function(oEvent) {
		var aDisabledKeys = this.oDisabledModifiers[oEvent.type.replace("modifiers", "")];
		if (Array.isArray(aDisabledKeys)) {
			for (var i = 0; i < aDisabledKeys.length; i++) {
				if (oEvent[aDisabledKeys[i] + "Key"]) {
					return true;
				}
			}
		}

		return false;
	};

	/**
	 * Sets the root DOM reference surrounding the items
	 *
	 * @param {Element} oDomRef Root DOM reference
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setRootDomRef = function(oDomRef) {
		this.oDomRef = oDomRef;

		// in nested ItemNavigation the tabindex must only be set at the root DOM from the parent ItemNavigation
		if (!jQuery(this.oDomRef).data("sap.INItem")) {
			if (this.iFocusedIndex >= 0) {
				jQuery(this.oDomRef).attr("tabindex", this.iTabIndex);
			} else {
				jQuery(this.oDomRef).attr("tabindex", this.iActiveTabIndex);
			}
		}

		jQuery(this.oDomRef).data("sap.INRoot", this);

		return this;
	};

	/**
	 * Returns the root DOM reference surrounding the items
	 *
	 * @return {Element} Root DOM reference
	 * @public
	 */
	ItemNavigation.prototype.getRootDomRef = function() {
		return this.oDomRef;
	};

	/**
	 * Returns the array of item DOM references
	 *
	 * @return {Element[]} Array of item DOM references
	 * @public
	 */
	ItemNavigation.prototype.getItemDomRefs = function() {
		return this.aItemDomRefs;
	};

	/**
	 * Sets the item DOM references as an array for the items
	 *
	 * @param {Element[]} aItemDomRefs Array of DOM references or DOM node list object, representing the items
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setItemDomRefs = function(aItemDomRefs) {
		assert(typeof aItemDomRefs === "object" && typeof aItemDomRefs.length === "number", "aItemDomRefs must be an array of DOM elements");
		this.aItemDomRefs = aItemDomRefs;

		// recalculate focused index
		if (this.iFocusedIndex > -1) {
			var iItemsLength = aItemDomRefs.length;
			if (this.iFocusedIndex > iItemsLength - 1) {
				this.iFocusedIndex = iItemsLength - 1;
			}

			var oActiveElement = document.activeElement;
			if (oActiveElement && oActiveElement != this.aItemDomRefs[this.iFocusedIndex]) {
				for (var i = 0; i < iItemsLength; i++) {
					if (oActiveElement == this.aItemDomRefs[i]) {
						this.iFocusedIndex = i;
						break;
					}
				}
			}
		}

		// in nested ItemNavigation the tabindex must only be set if it's the focused item of the parent ItemNavigation
		for (var i = 0; i < this.aItemDomRefs.length; i++) {
			if (this.aItemDomRefs[i]) { // separators return null here
				var $Item = jQuery(this.aItemDomRefs[i]);

				// as this code can be executed even if the items are not visible (e.g. Popup is closed)
				// no focusable check can be performed. So only for the currently focused item
				// the tabindex is set to 0. For all items with tabindex 0 the tabindex is set to -1
				// Items without tabindex are checked for focusable on the first focusin on the root.
				if (i == this.iFocusedIndex && !$Item.data("sap.INRoot")) {
					$Item.attr("tabindex", this.iActiveTabIndex);
				} else if ($Item.attr("tabindex") == "0") { // set tabindex to -1 only if already set to 0
					$Item.attr("tabindex", -1);
				}

				$Item.data("sap.INItem", true);
				$Item.data("sap.InNavArea", true); //Item is in navigation area - allow navigation mode and edit mode

				if ($Item.data("sap.INRoot") && i != this.iFocusedIndex) {

					// item is root of a nested ItemNavigation -> set tabindexes from its items to -1
					$Item.data("sap.INRoot").setNestedItemsTabindex();
				}
			}
		}

		return this;
	};

	/**
	 * Sets the <code>tabindex</code> of the items.
	 *
	 * This cannot be done while setting items because at this point of time the items might
	 * be invisible (because e.g. a popup closed), meaning the focusable check will fail.
	 * So the items <code>tabindex</code>es are set if the rootDom is focused the first time.
	 *
	 * @return {this} <code>this</code> to allow method chaining
	 * @private
	 */
	ItemNavigation.prototype.setItemsTabindex = function() {

		for (var i = 0; i < this.aItemDomRefs.length; i++) {
			if (this.aItemDomRefs[i]) { // separators return null here
				var $Item = jQuery(this.aItemDomRefs[i]);
				if ($Item.is(":sapFocusable")) {

					// not focusable items (like labels) must not get a tabindex attribute
					if (i == this.iFocusedIndex && !$Item.data("sap.INRoot")) {
						$Item.attr("tabindex", this.iActiveTabIndex);
					} else {
						$Item.attr("tabindex", -1);
					}
				}
			}
		}

		return this;
	};

	/**
	 * Sets <code>tabindex</code> of item to -1
	 * if called from parent <code>ItemNavigation</code> if ItemNavigation is nested.
	 * In the nested case the <code>tabindex</code> is ruled by the parent <code>ItemNavigation</code>,
	 * only the top items can have <code>tabindex</code> = 0.
	 *
	 * @return {this} <code>this</code> to allow method chaining
	 * @private
	 */
	ItemNavigation.prototype.setNestedItemsTabindex = function() {
		if (jQuery(this.oDomRef).data("sap.INItem")) {
			for (var i = 0; i < this.aItemDomRefs.length; i++) {
				if (this.aItemDomRefs[i] && jQuery(this.aItemDomRefs[i]).attr("tabindex") == "0") { // separators return null here
					jQuery(this.aItemDomRefs[i]).attr("tabindex", -1);
				}
			}
		}

		return this;
	};

	/**
	 * Destroys the delegate and releases all DOM references.
	 *
	 */
	ItemNavigation.prototype.destroy = function() {

		if (this.oDomRef) {
			jQuery(this.oDomRef).removeData("sap.INRoot");
			this.oDomRef = null;
		}

		if (this.aItemDomRefs) {
			for (var i = 0; i < this.aItemDomRefs.length; i++) {
				if (this.aItemDomRefs[i]) { // separators return null here
					jQuery(this.aItemDomRefs[i]).removeData("sap.INItem");
					jQuery(this.aItemDomRefs[i]).removeData("sap.InNavArea");
				}
			}

			this.aItemDomRefs = null;
		}

		this._bItemTabIndex = undefined;
		this.iFocusedIndex = -1;
	};

	/**
	 * Sets whether the <code>ItemNavigation</code> should cycle through the items.
	 *
	 * If cycling is disabled the navigation stops at the first and last item, if the corresponding arrow keys are used.
	 *
	 * @param {boolean} bCycling Set to true if cycling should be done, else false
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setCycling = function(bCycling) {
		this.bCycling = bCycling;
		return this;
	};

	/**
	 * Sets whether the <code>ItemNavigation</code> should use the table mode to navigate through
	 * the items (navigation in a grid).
	 *
	 * @param {boolean} bTableMode Set to true if table mode should be used, else false
	 * @param {boolean} [bTableList] This sets a different behavior for table mode.
	 * In this mode we keep using table navigation but there are some differences. e.g.
	 * <ul>
	 * 	<li>Page-up moves focus to the first row, not to the first cell like in table mode</li>
	 * 	<li>Page-down moves focus to the last row, not to the last cell like in table mode</li>
	 * </ul>
	 *
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setTableMode = function(bTableMode, bTableList) {
		this.bTableMode = bTableMode;
		this.bTableList = bTableMode ? bTableList : false;
		return this;
	};

	/**
	 * Sets the page size of the item navigation to allow Page Up and Page Down keys.
	 *
	 * @param {int} iPageSize The page size, needs to be at least 1
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setPageSize = function(iPageSize) {
		this.iPageSize = iPageSize;
		return this;
	};

	/**
	 * Sets the selected index if the used control supports selection.
	 *
	 * @param {int} iIndex Index of the first selected item
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setSelectedIndex = function(iIndex) {
		this.iSelectedIndex = iIndex;
		return this;
	};

	/**
	 * Sets whether the items are displayed in columns.
	 *
	 * If columns are used, the Arrow Up and Arrow Down keys navigate to the next or previous
	 * item of the column. If the first or last item of the column is reached, the next focused
	 * item is then in the next or previous column.
	 *
	 * @param {int} iColumns Count of columns for the table mode or cycling mode
	 * @param {boolean} bNoColumnChange Forbids jumping to an other column with Arrow Up and Arrow Down keys
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setColumns = function(iColumns, bNoColumnChange) {
		this.iColumns = iColumns;
		this.bNoColumnChange = bNoColumnChange;
		return this;
	};

	/**
	 * Sets behavior of HOME and END keys if columns are used.
	 *
	 * @param {boolean} bStayInRow HOME -> go to first item in row; END -> go to last item in row
	 * @param {boolean} bCtrlEnabled HOME/END with CTRL -> go to first/last item of all
	 * @return {this} <code>this</code> to allow method chaining
	 * @public
	 */
	ItemNavigation.prototype.setHomeEndColumnMode = function(bStayInRow, bCtrlEnabled) {

			this._bStayInRow = bStayInRow;
			this._bCtrlEnabled = bCtrlEnabled;

			return this;

	};

	/**
	 * Sets the focus to the item with the given index.
	 *
	 * @param {int} iIndex Index of the item to focus
	 * @param {jQuery.Event} oEvent Event that leads to focus change
	 * @param {boolean} bPreventScroll Whether scrolling should be prevented when focusing the DOM element
	 * @private
	 */
	ItemNavigation.prototype.focusItem = function(iIndex, oEvent, bPreventScroll) {

		Log.info("FocusItem: " + iIndex + " iFocusedIndex: " + this.iFocusedIndex, "focusItem", "ItemNavigation");

		if (iIndex == this.iFocusedIndex && this.aItemDomRefs[this.iFocusedIndex] == document.activeElement) {
			this.fireEvent(ItemNavigation.Events.FocusAgain, {
				index: iIndex,
				event: oEvent
			});
			return; // item already focused -> nothing to do
		}

		// if there is no item to put the focus on, we don't even try it, if working in table mode we just focus the next item
		if (!this.aItemDomRefs[iIndex] || !jQuery(this.aItemDomRefs[iIndex]).is(":sapFocusable")) {
			if (this.bTableMode) {
				var iCol = iIndex % this.iColumns;
				var iOldIndex = iIndex;
				if (oEvent && oEvent.keyCode == KeyCodes.ARROW_RIGHT) {
					if (iCol < this.iColumns - 1) {
						iIndex += Localization.getRTL() ? -1 : 1;
					}
				} else if (oEvent && oEvent.keyCode == KeyCodes.ARROW_LEFT) {
					if (iCol > 1) {
						iIndex -= Localization.getRTL() ? -1 : 1;
					}
				} else {
					if (iCol > 1) {
						iIndex -= 1;
					}
				}
				if (iIndex != iOldIndex) {
					this.focusItem(iIndex, oEvent);
				}
			}
			return;
		}

		if (!this.fireEvent(ItemNavigation.Events.BeforeFocus, {
			index: iIndex,
			event: oEvent
		}, /* bAllowPreventDefault */ true)) {
			Log.info("Focus prevented on ID: " + this.aItemDomRefs[this.iFocusedIndex].id, "focusItem", "ItemNavigation");
			return;
		}

		this.setFocusedIndex(iIndex);
		this.bISetFocus = true;

		if (oEvent && jQuery(this.aItemDomRefs[this.iFocusedIndex]).data("sap.INRoot")) {

			// store event type for nested ItemNavigations
			var oItemItemNavigation = jQuery(this.aItemDomRefs[this.iFocusedIndex]).data("sap.INRoot");
			oItemItemNavigation._sFocusEvent = oEvent.type;
		}

		Log.info("Set Focus on ID: " + this.aItemDomRefs[this.iFocusedIndex].id, "focusItem", "ItemNavigation");
		this.aItemDomRefs[this.iFocusedIndex].focus({
			preventScroll: bPreventScroll
		});

		this.fireEvent(ItemNavigation.Events.AfterFocus, {
			index: iIndex,
			event: oEvent
		});
	};

	/**
	 * Sets the focused index to the given index.
	 *
	 * @param {int} iIndex Index of the item
	 * @return {this} <code>this</code> to allow method chaining
	 * @private
	 */
	ItemNavigation.prototype.setFocusedIndex = function(iIndex) {
		var $Item;

		if (this.aItemDomRefs.length < 0) {
			// no items -> don't change TabIndex
			this.iFocusedIndex = -1;
			return this;
		}

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

		if (iIndex > this.aItemDomRefs.length - 1) {
			iIndex = this.aItemDomRefs.length - 1;
		}

		jQuery(this.oDomRef).attr("tabindex", this.iTabIndex);

		if (this.iFocusedIndex !== -1 && this.aItemDomRefs.length > this.iFocusedIndex) {
			jQuery(this.aItemDomRefs[this.iFocusedIndex]).attr("tabindex", -1);

			// if focus is in nested ItemNavigation but is moved to an other item, remove tabindex from nested item
			$Item = jQuery(this.aItemDomRefs[this.iFocusedIndex]);
			if ($Item.data("sap.INRoot") && iIndex != this.iFocusedIndex) {
				jQuery($Item.data("sap.INRoot").aItemDomRefs[$Item.data("sap.INRoot").iFocusedIndex]).attr("tabindex", -1);
			}
		}

		this.iFocusedIndex = iIndex;
		var oFocusItem = this.aItemDomRefs[this.iFocusedIndex];

		$Item = jQuery(this.aItemDomRefs[this.iFocusedIndex]);
		if (!$Item.data("sap.INRoot")) {

			// in nested ItemNavigation the nested item gets the tabindex
			jQuery(oFocusItem).attr("tabindex", this.iActiveTabIndex);
		}

		return this;
	};

	/**
	 * Returns the focused DOM reference.
	 *
	 * @return {Element} Focused DOM reference
	 * @private
	 */
	ItemNavigation.prototype.getFocusedDomRef = function() {
		return this.aItemDomRefs[this.iFocusedIndex];
	};

	/**
	 * Returns index of the focused item.
	 *
	 * @return {int} focused index
	 * @private
	 */
	ItemNavigation.prototype.getFocusedIndex = function() {
		return this.iFocusedIndex;
	};

	/**
	 * Handles the onfocusin event.
	 *
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onfocusin = function(oEvent) {

		var oSource = oEvent.target;
		var i = 0;

		if (oSource == this.oDomRef) {

			// focus occured on the main dom ref
			// on first focus - set tabindex for items
			if (!this._bItemTabIndex) {
				this.setItemsTabindex();
				this._bItemTabIndex = true;
			}

			// if the focus came by clicking AND it did not target one of the elements, but the root element, do nothing
			// (otherwise clicking the scrollbar re-focuses the previously focused element, which causes the browser to scroll it into view)
			if (this._bMouseDownHappened) {
				return;
			}

			var iIndex;

			if (jQuery(this.oDomRef).data("sap.INItem") && this._sFocusEvent && !jQuery(this.oDomRef).data("sap.InNavArea")) {

				// if nested ItemNavigation need to know if focused by parent ItemNavigation (not in Navigation mode)
				switch (this._sFocusEvent) {
					case "sapnext":
						iIndex = 0;
						break;

					case "sapprevious":
						iIndex = this.aItemDomRefs.length - 1;
						break;

					default:
						if (this.iSelectedIndex != -1) {
							iIndex = this.iSelectedIndex;
						} else if (this.iFocusedIndex != -1) {
							iIndex = this.iFocusedIndex;
						} else {
							iIndex = 0;
						}
						break;
				}

				this._sFocusEvent = undefined;
			} else {
				if (this.iSelectedIndex != -1) {
					iIndex = this.iSelectedIndex;
				} else if (this.iFocusedIndex != -1) {
					iIndex = this.iFocusedIndex;
				} else {
					iIndex = 0;
				}
			}

			this.focusItem(iIndex, oEvent);

			if (this.iFocusedIndex == -1) {

				// no item focused, maybe not focusable -> try the next one
				for (i = iIndex + 1; i < this.aItemDomRefs.length; i++) {
					this.focusItem(i, oEvent);
					if (this.iFocusedIndex == i) {
						break;
					}
				}

				if (this.iFocusedIndex == -1 && iIndex > 0) {

					// still no item selected, try to find a previous one
					for (i = iIndex - 1; i >= 0; i--) {
						this.focusItem(i, oEvent);
						if (this.iFocusedIndex == i) {
							break;
						}
					}
				}
			}

			// cancel the bubbling of event in this case
			oEvent.preventDefault();
			oEvent.stopPropagation();
		} else if (!this.bISetFocus) {

			// check if this is really the already focused item
			// in case of a click on a label this could be an other item
			if (this.aItemDomRefs && oEvent.target != this.aItemDomRefs[this.iFocusedIndex]) {
				for (i = 0; i < this.aItemDomRefs.length; i++) {
					if (oEvent.target == this.aItemDomRefs[i]) {
						this.focusItem(i, oEvent);
						break;
					}
				}
			} else {

				// give focused item to registered application
				this.fireEvent(ItemNavigation.Events.AfterFocus,{index:this.iFocusedIndex, event:oEvent});
			}
		}

		this.bISetFocus = false;
	};

	/**
	 * Handles the onsapfocusleave event
	 *
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsapfocusleave = function(oEvent) {
		if (!oEvent.relatedControlId || !this.oDomRef || !this.oDomRef.contains(Element.getElementById(oEvent.relatedControlId).getFocusDomRef())) {

			// entirely leaving the control handled by this ItemNavigation instance
			var iIndex;
			if (this.iSelectedIndex != -1) {
				iIndex = this.iSelectedIndex;
			} else if (this.iFocusedIndex != -1) {
				iIndex = this.iFocusedIndex;
			} else {
				iIndex = 0;
			}
			this.setFocusedIndex(iIndex);

			var $DomRef;

			if (jQuery(this.oDomRef).data("sap.INItem")) {

				// if in nested ItemNavigation and focus moves to item of parent ItemNavigation -> do not set Tabindex
				var oParentDomRef;
				$DomRef = jQuery(this.oDomRef);

				while (!oParentDomRef) {
					$DomRef = $DomRef.parent();
					if ($DomRef.data("sap.INRoot")) {
						oParentDomRef = $DomRef.get(0);
					}
				}

				if (!oEvent.relatedControlId || oParentDomRef.contains(Element.getElementById(oEvent.relatedControlId).getFocusDomRef())) {
					jQuery(this.aItemDomRefs[this.iFocusedIndex]).attr("tabindex", -1);
				}
			}

			$DomRef = jQuery(this.oDomRef);
			if ($DomRef.data("sap.InNavArea") === false) { // check for false to avoid undefinded

				// if in action mode switch back to navigation mode
				$DomRef.data("sap.InNavArea", true);
			}

			this.fireEvent(ItemNavigation.Events.FocusLeave, {
				index: iIndex,
				event: oEvent
			});
		}
	};

	/**
	 * Handles the onmousedown event
	 * Sets the focus to the item if it occured on an item
	 *
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onmousedown = function(oEvent) {

		// set the focus to the clicked element or back to the last
		var oSource = oEvent.target;

		var checkFocusableParent = function(oDomRef, oItem) {

			// as table cell might have focusable content that have not focusable DOM insinde
			// the table cell should not get the focus but the focusable element inside
			var bFocusableParent = false;
			var $CheckDom = jQuery(oDomRef);
			while (!$CheckDom.is(":sapFocusable") && $CheckDom.get(0) != oItem) {
				$CheckDom = $CheckDom.parent();
			}

			if ($CheckDom.get(0) != oItem) {
				// focusable Dom found inside item
				bFocusableParent = true;
			}

			return bFocusableParent;

		};

		if (this.oDomRef && this.oDomRef.contains(oSource)) {

			// the mouse down occured inside the main dom ref
			for (var i = 0; i < this.aItemDomRefs.length; i++) {
				var oItem = this.aItemDomRefs[i];
				if (oItem && oItem.contains(oSource)) {
					if (!this.bTableMode) {

						// the mousedown occured inside of an item
						// prevent scrolling when setting focus to the DOM element to make sure
						// that the "mouseup" event is fired on the same DOM element
						this.focusItem(i, oEvent, true /* prevent scrolling*/);

						// no oEvent.preventDefault(); because cursor will not be set in Textfield
						// no oEvent.stopPropagation(); because e.g. DatePicker can not close popup
					} else {
						// only focus the items if the click did not happen on a
						// focusable element!
						if (oItem === oSource || !checkFocusableParent(oSource, oItem)) {
							// prevent scrolling when setting focus to the DOM element to make sure
							// that the "mouseup" event is fired on the same DOM element
							this.focusItem(i, oEvent, true /* prevent scrolling*/);

							// the table mode requires not to prevent the default
							// behavior on click since we want to allow text selection
							// click into the control, ...
						}
					}
					return;
				}
			}

			if (oSource == this.oDomRef) {

				// root DomRef of item navigation has been clicked
				// focus will also come in a moment - let it know that it was the mouse who caused the focus
				this._bMouseDownHappened = true;
				var that = this;
				window.setTimeout(
					function(){
						that._bMouseDownHappened = false;
					}, 20
				);
			}
		}
	};

	/**
	 * Handles the onsapnext event
	 * Sets the focus to the next item
	 *
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsapnext = function(oEvent) {

		if (!this.oDomRef || !this.oDomRef.contains(oEvent.target)) {

			// current element is not part of the navigation content
			return;
		}

		if (jQuery(this.oDomRef).data("sap.InNavArea")) {

			// control is in navigation mode -> no ItemNavigation
			return;
		}

		// in the table mode we only react on events of the domrefs
		if (this.bTableMode && this.aItemDomRefs.indexOf(oEvent.target) === -1) {
			return;
		}

		// focus the next item
		var iIndex = this.iFocusedIndex,
			bFirstTime = true,
			bBorderReached = false;

		if (iIndex > -1) {
			if (this.bTableMode) {

				var iRowCount = this.aItemDomRefs.length / this.iColumns,
					iRow = Math.floor(iIndex / this.iColumns),
					iCol = iIndex % this.iColumns;

				if (oEvent.keyCode == KeyCodes.ARROW_DOWN) {
					if (iRow  < iRowCount - 1) {
						iIndex += this.iColumns;
					}
				} else {
					if (iCol < this.iColumns - 1) {
						iIndex += 1;
					}
				}
			} else {
				do {
					if (this.iColumns > 1 && oEvent.keyCode == KeyCodes.ARROW_DOWN) {
						if ((iIndex + this.iColumns) >= this.aItemDomRefs.length) {
							if (!this.bNoColumnChange) {
								// on bottom -> begin on top of next column
								if ((iIndex % this.iColumns) < (this.iColumns - 1)) {
									iIndex = (iIndex % this.iColumns) + 1;
								} else if (this.bCycling) {
									iIndex = 0;
								}
							} else {
								// do not go to an other item;
								iIndex = this.iFocusedIndex;
								bBorderReached = true;
							}
						} else {
							iIndex = iIndex + this.iColumns;
						}
					} else {
						if (iIndex == this.aItemDomRefs.length - 1) {

							// last item
							if (jQuery(this.oDomRef).data("sap.INItem")) {

								// if nested ItemNavigations leave here to ItemNavigation of parent
								return;
							} else if (this.bCycling) {

								// in cycling case focus first one, if not - don't change
								iIndex = 0;
							} else {

								// last one, no next item, so leave focus on already focused item (to prevent endless loops)
								iIndex = this.iFocusedIndex;
								bBorderReached = true;
							}
						} else {
							iIndex++;
						}
					}

					if (iIndex === this.iFocusedIndex) {
						if (bFirstTime) {
							bFirstTime = false;
						} else {
							throw new Error("ItemNavigation has no visible/existing items and is hence unable to select the next one");
						}
					}

				// if item is not visible or a dummy item go to the next one
				// !jQuery(this.aItemDomRefs[iIndex]).is(":visible") and jQuery(this.aItemDomRefs[iIndex]).css("visibility") === "hidden"
				// - is not needed as .is(":sapFocusable") do these checks already
				} while (!this.aItemDomRefs[iIndex] || !jQuery(this.aItemDomRefs[iIndex]).is(":sapFocusable"));
			}

			this.focusItem(iIndex, oEvent);

			if (bBorderReached) {
				this.fireEvent(ItemNavigation.Events.BorderReached, {
					index: iIndex,
					event: oEvent
				});
			}

			// cancel the event otherwise the browser will scroll
			oEvent.preventDefault();
			oEvent.stopPropagation();
		}
	};

	/**
	 * Ensure the sapnext event with modifiers is also handled
	 *
	 * @see #onsapnext
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsapnextmodifiers = function(oEvent) {
		if (this.hasDisabledModifier(oEvent)) {
			return;
		}

		this.onsapnext(oEvent);
	};

	/**
	 * Handles the onsapprevious event
	 * Sets the focus to the previous item
	 *
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsapprevious = function(oEvent) {

		if (!this.oDomRef || !this.oDomRef.contains(oEvent.target)) {

			// current element is not part of the navigation content
			return;
		}

		if (jQuery(this.oDomRef).data("sap.InNavArea")) {

			// control is in navigation mode -> no ItemNavigation
			return;
		}

		// in the table mode we only react on events of the domrefs
		if (this.bTableMode && this.aItemDomRefs.indexOf(oEvent.target) === -1) {
			return;
		}

		// focus the previous item
		var iIndex = this.iFocusedIndex,
			bFirstTime = true,
			bBorderReached = false;
		var iCol = 0;

		if (iIndex > -1) {
			if (this.bTableMode) {
				var iRow = Math.floor(iIndex / this.iColumns);
				iCol = iIndex % this.iColumns;

				if (oEvent.keyCode == KeyCodes.ARROW_UP) {
					if (iRow  > 0) {
						iIndex -= this.iColumns;
					}
				} else {
					if (iCol > 0) {
						iIndex -= 1;
					}
				}
			} else {
				do {
					if (this.iColumns > 1 && oEvent.keyCode == KeyCodes.ARROW_UP) {
						if ((iIndex - this.iColumns) < 0) {
							if (!this.bNoColumnChange) {
								// on top -> begin on end of previous column
								iCol = 0;

								if ((iIndex % this.iColumns) > 0) {
									iCol = (iIndex % this.iColumns) - 1;
								} else if (this.bCycling) {
									iCol = Math.min(this.iColumns - 1, this.aItemDomRefs.length - 1);
								}

								if (iIndex === 0 && iCol === 0) {
									iIndex = 0;
								} else {
									var iRows = Math.ceil(this.aItemDomRefs.length / this.iColumns);
									iIndex = iCol + ((iRows - 1) * this.iColumns);

									if (iIndex >= this.aItemDomRefs.length) {
										iIndex = iIndex - this.iColumns;
									}
								}
							} else {
								// do not go to an other item;
								iIndex = this.iFocusedIndex;
								bBorderReached = true;
							}
						} else {
							iIndex = iIndex - this.iColumns;
						}
					} else {
						if (iIndex == 0) {

							// first item
							if (jQuery(this.oDomRef).data("sap.INItem")) {

								// if nested ItemNavigations leave here to ItemNavigation of parent
								return;
							} else if (this.bCycling) {

								// in cycling case focus last one, if not - don't change
								iIndex = this.aItemDomRefs.length - 1;
							} else {

								// first one, no next item, so leave focus on already focused item (to prevent endless loops)
								iIndex = this.iFocusedIndex;
								bBorderReached = true;
							}
						} else {
							iIndex--;
						}
					}

					if (iIndex == this.iFocusedIndex) {
						if (bFirstTime) {
							bFirstTime = false;
						} else {
							throw new Error("ItemNavigation has no visible/existing items and is hence unable to select the previous one");
						}
					}

				// if item is not visible or a dummy item go to the next one
				} while (!this.aItemDomRefs[iIndex] || !jQuery(this.aItemDomRefs[iIndex]).is(":sapFocusable"));
			}

			this.focusItem(iIndex, oEvent);

			if (bBorderReached) {
				this.fireEvent(ItemNavigation.Events.BorderReached, {
					index: iIndex,
					event: oEvent
				});
			}

			// cancel the event otherwise the browser will scroll
			oEvent.preventDefault();
			oEvent.stopPropagation();
		}
	};

	/**
	 * Ensure the sapprevious event with modifiers is also handled
	 *
	 * @see #onsapprevious
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsappreviousmodifiers = function(oEvent) {
		if (this.hasDisabledModifier(oEvent)) {
			return;
		}

		this.onsapprevious(oEvent);
	};

	/**
	 * Handles the onsappageup event
	 * Sets the focus to the previous page item of <code>iPageSize</code> > 0
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsappageup = function(oEvent) {

		if (!this.oDomRef || !this.oDomRef.contains(oEvent.target)) {

			// current element is not part of the navigation content
			return;
		}

		// in the table mode we only react on events of the domrefs
		if (this.bTableMode && this.aItemDomRefs.indexOf(oEvent.target) === -1) {
			return;
		}

		var iIndex = 0;
		var bBorderReached = false;

		if (this.iPageSize > 0) {
			iIndex = this.iFocusedIndex;

			if (iIndex > -1) {
				iIndex = iIndex - this.iPageSize;

				while (iIndex > 0 && !jQuery(this.aItemDomRefs[iIndex]).is(":sapFocusable")) {
					iIndex--;
				}

				if (iIndex < 0) {
					if (!this.bNoColumnChange) {
						iIndex = 0;
					} else {
						iIndex = this.iFocusedIndex;
						bBorderReached = true;
					}
				}

				this.focusItem(iIndex, oEvent);
			}
		} else if (this.bTableMode) {
			iIndex = this.iFocusedIndex % this.iColumns;
			this.focusItem(iIndex, oEvent);
		}

		if (bBorderReached) {
			this.fireEvent(ItemNavigation.Events.BorderReached, {
				index: iIndex,
				event: oEvent
			});
		}

		// cancel the event otherwise the browser will scroll
		oEvent.preventDefault();
		oEvent.stopPropagation();

	};

	/**
	 * Handles the onsappagedown event.
	 *
	 * Sets the focus to the previous page item of <code>iPageSize</code> > 0
	 *
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsappagedown = function(oEvent) {

		if (!this.oDomRef || !this.oDomRef.contains(oEvent.target)) {

			// current element is not part of the navigation content
			return;
		}

		// in the table mode we only react on events of the domrefs
		if (this.bTableMode && this.aItemDomRefs.indexOf(oEvent.target) === -1) {
			return;
		}

		var iIndex = 0;
		var bBorderReached = false;

		if (this.iPageSize > 0) {
			iIndex = this.iFocusedIndex;

			if (iIndex > -1) {
				iIndex = iIndex + this.iPageSize;

				while (iIndex < this.aItemDomRefs.length - 1 && !jQuery(this.aItemDomRefs[iIndex]).is(":sapFocusable")) {
					iIndex++;
				}

				if (iIndex > this.aItemDomRefs.length - 1) {
					if (!this.bNoColumnChange) {
						iIndex = this.aItemDomRefs.length - 1;
					} else {
						iIndex = this.iFocusedIndex;
						bBorderReached = true;
					}
				}

				this.focusItem(iIndex, oEvent);
			}
		} else if (this.bTableMode) {
			var iRowCount = this.aItemDomRefs.length / this.iColumns,
				iCol = this.iFocusedIndex % this.iColumns;

			iIndex = (iRowCount - 1) * this.iColumns + iCol;
			this.focusItem(iIndex, oEvent);
		}

		if (bBorderReached) {
			this.fireEvent(ItemNavigation.Events.BorderReached, {
				index: iIndex,
				event: oEvent
			});
		}

		// cancel the event otherwise the browser will scroll
		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * Handles the onsaphome event
	 *
	 * Sets the focus to first visible and focusable item
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsaphome = function(oEvent) {

		if (!this.oDomRef || !this.oDomRef.contains(oEvent.target)) {

			// current element is not part of the navigation content
			// or shift or alt key is pressed
			return;
		}

		// in the table mode we only react on events of the domrefs
		if (this.bTableMode && this.aItemDomRefs.indexOf(oEvent.target) === -1) {
			return;
		}

		var iIndex = 0;
		var iRow = 0;
		if (this.bTableMode) {

			if (!this.bTableList && !(oEvent.metaKey || oEvent.ctrlKey)) {
				iRow = Math.floor(this.iFocusedIndex / this.iColumns);
				iIndex = iRow * this.iColumns;
			}
		} else {
			if ((oEvent.metaKey || oEvent.ctrlKey) && !this._bCtrlEnabled) {

				// do not handle ctrl
				return;
			}

			if (this._bStayInRow && !(this._bCtrlEnabled && (oEvent.metaKey || oEvent.ctrlKey)) && this.iColumns > 0) {
				iRow = Math.floor(this.iFocusedIndex / this.iColumns);
				iIndex = iRow * this.iColumns;
			} else {
				while (!this.aItemDomRefs[iIndex] || !jQuery(this.aItemDomRefs[iIndex]).is(":sapFocusable")) {
					iIndex++;

					if (iIndex == this.aItemDomRefs.length) {

						// no visible item -> no new focus
						return;
					}
				}
			}
		}

		this.focusItem(iIndex, oEvent);

		// cancel the event otherwise the browser will scroll
		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * Ensure the sapprevious event with modifiers is also handled
	 *
	 * @see #onsaphome
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsaphomemodifiers = function(oEvent) {
		if (this.hasDisabledModifier(oEvent)) {
			return;
		}

		this.onsaphome(oEvent);
	};

	/**
	 * Handles the onsapend event
	 *
	 * Sets the focus to last visible and focusable item
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsapend = function(oEvent) {

		if (!this.oDomRef || !this.oDomRef.contains(oEvent.target)) {

			// current element is not part of the navigation content
			// or shift or alt key is pressed
			return;
		}

		// in the table mode we only react on events of the domrefs
		if (this.bTableMode && this.aItemDomRefs.indexOf(oEvent.target) === -1) {
			return;
		}

		var iIndex = this.aItemDomRefs.length - 1;
		var iRow = 0;

		if (this.bTableMode) {
			if (!this.bTableList && !(oEvent.metaKey || oEvent.ctrlKey)) {
				iRow = Math.floor(this.iFocusedIndex / this.iColumns);
				iIndex = iRow * this.iColumns + this.iColumns - 1;
			}
		} else {

			if ((oEvent.metaKey || oEvent.ctrlKey) && !this._bCtrlEnabled) {

				// do not handle ctrl
				return;
			}

			if (this._bStayInRow && !(this._bCtrlEnabled && (oEvent.metaKey || oEvent.ctrlKey)) && this.iColumns > 0) {
				iRow = Math.floor(this.iFocusedIndex / this.iColumns);
				iIndex = (iRow + 1) * this.iColumns - 1;
				if (iIndex >= this.aItemDomRefs.length) {
					// item not exist -> use last one
					iIndex = this.aItemDomRefs.length - 1;
				}
			} else {
				while (!this.aItemDomRefs[iIndex] || !jQuery(this.aItemDomRefs[iIndex]).is(":sapFocusable")) {
					iIndex--;

					if (iIndex < 0) {

						// no visible item -> no new focus
						return;
					}
				}
			}
		}

		this.focusItem(iIndex, oEvent);

		// cancel the event otherwise the browser will scroll
		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * Ensure the sapprevious event with modifiers is also handled.
	 *
	 * @see #onsapend
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onsapendmodifiers = function(oEvent) {
		if (this.hasDisabledModifier(oEvent)) {
			return;
		}

		this.onsapend(oEvent);
	};

	/**
	 * Sets <code>tabindex</code> of the root DOM reference to 0.
	 * Is used, for example in image map for IE browser in order to avoid <code>tabindex</code> -1 on image
	 * with which it would not be tabable at all.
	 *
	 * @private
	 */
	ItemNavigation.prototype.setTabIndex0 = function() {
		this.iTabIndex = 0;
		this.iActiveTabIndex = 0;
	};

	/**
	 * toggle navigation/action mode on F2
	 *
	 * @param {jQuery.Event} oEvent the browser event
	 * @private
	 */
	ItemNavigation.prototype.onkeyup = function(oEvent) {

		if (oEvent.keyCode == KeyCodes.F2) {

			var $DomRef = jQuery(this.oDomRef);

			if ($DomRef.data("sap.InNavArea")) {
				$DomRef.data("sap.InNavArea", false);
			} else if ($DomRef.data("sap.InNavArea") === false) { // check for false to avoid undefined
				$DomRef.data("sap.InNavArea", true);
			}

			oEvent.preventDefault();
			oEvent.stopPropagation();
		}
	};

	return ItemNavigation;

});