mirror of
https://github.com/google/blockly.git
synced 2026-01-10 10:27:08 +01:00
fix: Auto-close widget divs on lost focus (#9216)
## The basics - [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change) ## The details ### Resolves Fixes https://github.com/google/blockly-keyboard-experimentation/issues/563 ### Proposed Changes This expands the functionality introduced in #9213 to also include widget divs. ### Reason for Changes MakeCode makes use of widget div in several field editors, so the issues described in https://github.com/google/blockly-keyboard-experimentation/issues/563 aren't fully mitigated with #9213 alone. This PR essentially adds the same support for auto-closing as drop-down divs now have, and enables this functionality by default. Note the drop-down div change: it was missed in #9123 that the API change for drop-down div's `show` function is actually API-breaking, so this updates that API to be properly backward compatible (and reverts one test change that makes use of it). The `FocusManager` change actually corrects an implementation issue from #9123: not updating the tracked focus status before calling the callback can result in focus being inadvertently restored if the callback triggers returning focus due to a lost focus situation. This was wrong for drop-down divs, too, but it's harder to notice there because the dismissal of the drop-down div happens on a timer (which means there's sufficient time for `FocusManager`'s state to correct prior to attempting to return from the ephemeral focus state). Demonstration of fixed behavior (since the inline number editor uses a widget div): [Screen recording 2025-07-08 2.12.31 PM.webm](https://github.com/user-attachments/assets/7c3c7c3c-224c-48f4-b4af-bde86feecfa8) ### Test Coverage New widget div tests have been added to verify the new parameter and auto-close functionality. The `FocusManager` test was updated to account for the new, and correct, behavior around the internal tracked ephemeral focus state. Note that some `tabindex` state has been clarified and cleaned up in the test index page and `FocusManager`. It's fine (and preferable) for ephemeral-used elements to always be focusable rather than making them dynamically so (which avoids state bleed across test runs which was happening one of the new tests). https://github.com/google/blockly-keyboard-experimentation/pull/649 includes additional tests for validating widget behaviors. ### Documentation No new documentation should be needed here--the API documentation changes should be sufficient. One documentation update was made in `dropdowndiv.ts` that corrects the documentation parameter ordering. ### Additional Information Nothing further to add.
This commit is contained in:
@@ -346,8 +346,8 @@ function showPositionedByRect(
|
||||
secondaryX,
|
||||
secondaryY,
|
||||
manageEphemeralFocus,
|
||||
autoCloseOnLostFocus,
|
||||
opt_onHide,
|
||||
autoCloseOnLostFocus,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -366,9 +366,9 @@ function showPositionedByRect(
|
||||
* @param primaryY Desired origin point y, in absolute px.
|
||||
* @param secondaryX Secondary/alternative origin point x, in absolute px.
|
||||
* @param secondaryY Secondary/alternative origin point y, in absolute px.
|
||||
* @param opt_onHide Optional callback for when the drop-down is hidden.
|
||||
* @param manageEphemeralFocus Whether ephemeral focus should be managed
|
||||
* according to the widget div's lifetime.
|
||||
* @param opt_onHide Optional callback for when the drop-down is hidden.
|
||||
* @param autoCloseOnLostFocus Whether the drop-down should automatically hide
|
||||
* if it loses DOM focus for any reason.
|
||||
* @returns True if the menu rendered at the primary origin point.
|
||||
@@ -382,8 +382,8 @@ export function show<T>(
|
||||
secondaryX: number,
|
||||
secondaryY: number,
|
||||
manageEphemeralFocus: boolean,
|
||||
autoCloseOnLostFocus: boolean,
|
||||
opt_onHide?: () => void,
|
||||
autoCloseOnLostFocus?: boolean,
|
||||
): boolean {
|
||||
owner = newOwner as Field;
|
||||
onHide = opt_onHide || null;
|
||||
|
||||
@@ -138,10 +138,10 @@ export class FocusManager {
|
||||
element instanceof Node &&
|
||||
ephemeralFocusElem.contains(element);
|
||||
if (hadFocus !== hasFocus) {
|
||||
this.ephemerallyFocusedElementCurrentlyHasFocus = hasFocus;
|
||||
if (this.ephemeralDomFocusChangedCallback) {
|
||||
this.ephemeralDomFocusChangedCallback(hasFocus);
|
||||
}
|
||||
this.ephemerallyFocusedElementCurrentlyHasFocus = hasFocus;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,6 +99,8 @@ export function createDom() {
|
||||
* passed in here then callers should manage ephemeral focus directly
|
||||
* otherwise focus may not properly restore when the widget closes. Defaults
|
||||
* to true.
|
||||
* @param autoCloseOnLostFocus Whether the widget should automatically hide if
|
||||
* it loses DOM focus for any reason.
|
||||
*/
|
||||
export function show(
|
||||
newOwner: unknown,
|
||||
@@ -106,6 +108,7 @@ export function show(
|
||||
newDispose: () => void,
|
||||
workspace?: WorkspaceSvg | null,
|
||||
manageEphemeralFocus: boolean = true,
|
||||
autoCloseOnLostFocus: boolean = true,
|
||||
) {
|
||||
hide();
|
||||
owner = newOwner;
|
||||
@@ -131,7 +134,18 @@ export function show(
|
||||
dom.addClass(div, themeClassName);
|
||||
}
|
||||
if (manageEphemeralFocus) {
|
||||
returnEphemeralFocus = getFocusManager().takeEphemeralFocus(div);
|
||||
const autoCloseCallback = autoCloseOnLostFocus
|
||||
? (hasFocus: boolean) => {
|
||||
// If focus is ever lost, close the widget.
|
||||
if (!hasFocus) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
: null;
|
||||
returnEphemeralFocus = getFocusManager().takeEphemeralFocus(
|
||||
div,
|
||||
autoCloseCallback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user