From 506d417bb756c87aeaab52d4ce42e309a8129ad1 Mon Sep 17 00:00:00 2001 From: Claudio Guglielmo Date: Mon, 11 Dec 2023 12:38:45 +0100 Subject: [PATCH] FocusManager: don't prevent dragStart event Focus manager calls preventDefault() if user presses mouse down on a non-focusable element to ensure the currently active element does not lose focus. Unfortunately, this also prevents the dragstart event which is needed if a widget requires drag & drop support. --- eclipse-scout-core/src/focus/FocusManager.ts | 24 ++++++++++++------- eclipse-scout-core/src/focus/focusUtils.ts | 25 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/eclipse-scout-core/src/focus/FocusManager.ts b/eclipse-scout-core/src/focus/FocusManager.ts index 0202b8ee093..350df4a07b6 100644 --- a/eclipse-scout-core/src/focus/FocusManager.ts +++ b/eclipse-scout-core/src/focus/FocusManager.ts @@ -413,38 +413,46 @@ export class FocusManager implements FocusManagerOptions { * Returns whether to accept a 'mousedown event'. */ protected _acceptFocusChangeOnMouseDown($element: JQuery): boolean { - // 1. Prevent focus gain when glasspane is clicked. - // Even if the glasspane is not focusable, this check is required because the glasspane might be contained in a focusable container - // like table. Use case: outline modality with table-page as 'outlineContent'. + // Prevent focus gain when glasspane is clicked. + // Even if the glasspane is not focusable, this check is required because the glasspane might be contained in a focusable container + // like table. Use case: outline modality with table-page as 'outlineContent'. if ($element.hasClass('glasspane')) { return false; } - // 2. Prevent focus gain if covert by glasspane. + // Prevent focus gain if covert by glasspane. if (this.isElementCovertByGlassPane($element)) { return false; } - // 3. Prevent focus gain on elements excluded to gain focus by mouse, e.g. buttons. + // Prevent focus gain on elements excluded to gain focus by mouse, e.g. buttons. if (!focusUtils.isFocusableByMouse($element)) { return false; } - // 4. Allow focus gain on focusable elements. + // Allow focus gain on focusable elements. if ($element.is(':focusable')) { return true; } - // 5. Allow focus gain on elements with selectable content, e.g. the value of a label field. + // Allow focus gain on elements with selectable content, e.g. the value of a label field. if (focusUtils.isSelectableText($element)) { return true; } - // 6. Allow focus gain on elements with a focusable parent, e.g. when clicking on a row in a table. + // Allow focus gain on elements with a focusable parent, e.g. when clicking on a row in a table. if (focusUtils.containsParentFocusableByMouse($element, $element.entryPoint())) { return true; } + // Don't prevent default action for draggable elements which is dragstart event + if (focusUtils.isDraggable($element)) { + // Unfortunately, preventDefault will not only prevent dragstart but also focus gain + // If the draggable element is not focusable, we need to restore the focus later otherwise the desktop would be focused + focusUtils.restoreFocusLater(this.session.$entryPoint); + return true; + } + return false; } diff --git a/eclipse-scout-core/src/focus/focusUtils.ts b/eclipse-scout-core/src/focus/focusUtils.ts index ad313e27bb3..5dc34de0a9f 100644 --- a/eclipse-scout-core/src/focus/focusUtils.ts +++ b/eclipse-scout-core/src/focus/focusUtils.ts @@ -76,6 +76,14 @@ export const focusUtils = { return $element.text().trim().length > 0; }, + /** + * @returns true if the element or one of its parents is draggable. + */ + isDraggable(element: HTMLElement | JQuery): boolean { + let $element = $.ensure(element); + return $element.attr('draggable') === 'true' || $element.parents('[draggable="true"]').length > 0; + }, + /** * Returns true if the given HTML element is the active element in its own document, false otherwise * @param element @@ -94,5 +102,22 @@ export const focusUtils = { activeElement = ownerDocument.activeElement; } return activeElement === element; + }, + + /** + * Stores the currently focused element and focuses this element again in the next animation frame if the focus changed to the entry point element. + * This is useful if the current task would focus the entry point element which cannot be prevented. + */ + restoreFocusLater($entryPoint: JQuery) { + // queueMicrotask does not work, it looks like the microtask will be executed before the focus change. + // requestAnimationFrame also prevents flickering (compared to setTimeout) + let doc = $entryPoint.document(true); + let prevFocusedElement = doc.activeElement as HTMLElement; + requestAnimationFrame(() => { + let focusedElement = doc.activeElement; + if (focusedElement === $entryPoint[0]) { + prevFocusedElement.focus(); + } + }); } };