/* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import $ from 'jquery'; /** * Utility methods for focus. */ export const focusUtils = { /** * @returns whether the given element is focusable by mouse. */ isFocusableByMouse(element: HTMLElement | JQuery): boolean { return $.ensure(element).closest('.unfocusable').length === 0; }, /** * @returns whether the element must not gain the focus, even if it has a tabindex. This is only true for elements with tabindex="-2". */ isFocusPrevented(element: HTMLElement | JQuery): boolean { return Number($.ensure(element).attr('tabindex')) === -2; }, /** * @param $entryPoint the entry point of the current {@link Session} * @param nativeFocusable whether to include elements that we consider to be unfocusable but would gain the focus by the browser if we did not prevent it (elements with tabindex="-2"). Default is false. * @returns all parents that are focusable by mouse inside a focus boundary (marked by elements having the class .focus-boundary) */ getParentsFocusableByMouse(element: HTMLElement | JQuery, $entryPoint: JQuery, nativeFocusable = false): JQuery { return $.ensure(element) .parentsUntil('.focus-boundary', nativeFocusable ? ':focusable-native' : ':focusable') // Stay inside focus boundaries (e.g. search forms should not consider parent table) .not($entryPoint) // Exclude $entryPoint as all elements are its descendants. However, the $entryPoint is only focusable to provide Portlet support. .filter((index, elem) => focusUtils.isFocusableByMouse(elem)); }, /** * @param $entryPoint the entry point of the current {@link Session} * @param nativeFocusable whether to include elements that we consider to be unfocusable but would gain the focus by the browser if we did not prevent it (elements with tabindex="-2"). Default is false. * @returns the given element if it is focusable by mouse, or the first parent that is focusable by mouse. */ closestFocusableByMouse(element: HTMLElement | JQuery, $entryPoint: JQuery, nativeFocusable = false): JQuery { let $element = $.ensure(element); if ($element.is(nativeFocusable ? ':focusable-native' : ':focusable') && focusUtils.isFocusableByMouse($element)) { return $element; } return focusUtils.getParentsFocusableByMouse($element, $entryPoint, nativeFocusable).first(); }, /** * @returns whether the given element has a parent which is focusable by mouse. */ containsParentFocusableByMouse(element: HTMLElement | JQuery, $entryPoint: JQuery): boolean { return focusUtils.getParentsFocusableByMouse(element, $entryPoint).length > 0; }, /** * @returns whether the given element contains content which is selectable to the user, e.g. to be copied into clipboard. */ isSelectableText(element: HTMLElement | JQuery): boolean { let $element = $(element); // Find the closest element which has a 'user-select' with a value other than 'auto'. If that value // is 'none', the text is not selectable. This code mimics the "inheritance behavior" of the CSS // property "-moz-user-select: -moz-none" as described in [1]. This does not seem to work in some // cases in Firefox, even with bug [2] fixed. As a workaround, we implement the desired behavior here. // // Note: Some additional CSS rules are required for events other than 'mousedown', see main.css. // // [1] https://developer.mozilla.org/en-US/docs/Web/CSS/user-select // [2] https://bugzilla.mozilla.org/show_bug.cgi?id=648624 let $el = $element; while ($el.css('user-select') === 'auto') { $el = $el.parent(); // Fix for Firefox: parent of BODY element is HtmlDocument. When calling $el.css on the HtmlDocument // Firefox throws an error that ownerDocument is undefined. Thus, we don't go higher than BODY element // and assume body is never selectable. if ($el.is('body')) { return false; } } if ($el.css('user-select') === 'none') { return false; } // When element or its children have text, it should be selectable. // The old implementation only looked at the text of the element itself // but not at the text of its children. With the old approach it was not // possible to select something inside a TD, for instance: //