mirror of
https://github.com/google/blockly.git
synced 2025-12-16 06:10:12 +01:00
fix: Revert drop down and widget div PRs (#9222)
* Revert "fix: Auto-close widget divs on lost focus (#9216)" This reverts commitbea183d85d. * Revert "fix: Auto close drop-down divs on lost focus (reapply) (#9213)" This reverts commit0e16b0405a.
This commit is contained in:
@@ -213,8 +213,6 @@ export function setColour(backgroundColour: string, borderColour: string) {
|
|||||||
* passed in here then callers should manage ephemeral focus directly
|
* passed in here then callers should manage ephemeral focus directly
|
||||||
* otherwise focus may not properly restore when the widget closes. Defaults
|
* otherwise focus may not properly restore when the widget closes. Defaults
|
||||||
* to true.
|
* to true.
|
||||||
* @param autoCloseOnLostFocus Whether the drop-down should automatically hide
|
|
||||||
* if it loses DOM focus for any reason.
|
|
||||||
* @returns True if the menu rendered below block; false if above.
|
* @returns True if the menu rendered below block; false if above.
|
||||||
*/
|
*/
|
||||||
export function showPositionedByBlock<T>(
|
export function showPositionedByBlock<T>(
|
||||||
@@ -223,13 +221,11 @@ export function showPositionedByBlock<T>(
|
|||||||
opt_onHide?: () => void,
|
opt_onHide?: () => void,
|
||||||
opt_secondaryYOffset?: number,
|
opt_secondaryYOffset?: number,
|
||||||
manageEphemeralFocus: boolean = true,
|
manageEphemeralFocus: boolean = true,
|
||||||
autoCloseOnLostFocus: boolean = true,
|
|
||||||
): boolean {
|
): boolean {
|
||||||
return showPositionedByRect(
|
return showPositionedByRect(
|
||||||
getScaledBboxOfBlock(block),
|
getScaledBboxOfBlock(block),
|
||||||
field as Field,
|
field as Field,
|
||||||
manageEphemeralFocus,
|
manageEphemeralFocus,
|
||||||
autoCloseOnLostFocus,
|
|
||||||
opt_onHide,
|
opt_onHide,
|
||||||
opt_secondaryYOffset,
|
opt_secondaryYOffset,
|
||||||
);
|
);
|
||||||
@@ -249,8 +245,6 @@ export function showPositionedByBlock<T>(
|
|||||||
* passed in here then callers should manage ephemeral focus directly
|
* passed in here then callers should manage ephemeral focus directly
|
||||||
* otherwise focus may not properly restore when the widget closes. Defaults
|
* otherwise focus may not properly restore when the widget closes. Defaults
|
||||||
* to true.
|
* to true.
|
||||||
* @param autoCloseOnLostFocus Whether the drop-down should automatically hide
|
|
||||||
* if it loses DOM focus for any reason.
|
|
||||||
* @returns True if the menu rendered below block; false if above.
|
* @returns True if the menu rendered below block; false if above.
|
||||||
*/
|
*/
|
||||||
export function showPositionedByField<T>(
|
export function showPositionedByField<T>(
|
||||||
@@ -258,14 +252,12 @@ export function showPositionedByField<T>(
|
|||||||
opt_onHide?: () => void,
|
opt_onHide?: () => void,
|
||||||
opt_secondaryYOffset?: number,
|
opt_secondaryYOffset?: number,
|
||||||
manageEphemeralFocus: boolean = true,
|
manageEphemeralFocus: boolean = true,
|
||||||
autoCloseOnLostFocus: boolean = true,
|
|
||||||
): boolean {
|
): boolean {
|
||||||
positionToField = true;
|
positionToField = true;
|
||||||
return showPositionedByRect(
|
return showPositionedByRect(
|
||||||
getScaledBboxOfField(field as Field),
|
getScaledBboxOfField(field as Field),
|
||||||
field as Field,
|
field as Field,
|
||||||
manageEphemeralFocus,
|
manageEphemeralFocus,
|
||||||
autoCloseOnLostFocus,
|
|
||||||
opt_onHide,
|
opt_onHide,
|
||||||
opt_secondaryYOffset,
|
opt_secondaryYOffset,
|
||||||
);
|
);
|
||||||
@@ -310,15 +302,12 @@ function getScaledBboxOfField(field: Field): Rect {
|
|||||||
* according to the drop-down div's lifetime. Note that if a false value is
|
* according to the drop-down div's lifetime. Note that if a false value is
|
||||||
* passed in here then callers should manage ephemeral focus directly
|
* passed in here then callers should manage ephemeral focus directly
|
||||||
* otherwise focus may not properly restore when the widget closes.
|
* otherwise focus may not properly restore when the widget closes.
|
||||||
* @param autoCloseOnLostFocus Whether the drop-down should automatically hide
|
|
||||||
* if it loses DOM focus for any reason.
|
|
||||||
* @returns True if the menu rendered below block; false if above.
|
* @returns True if the menu rendered below block; false if above.
|
||||||
*/
|
*/
|
||||||
function showPositionedByRect(
|
function showPositionedByRect(
|
||||||
bBox: Rect,
|
bBox: Rect,
|
||||||
field: Field,
|
field: Field,
|
||||||
manageEphemeralFocus: boolean,
|
manageEphemeralFocus: boolean,
|
||||||
autoCloseOnLostFocus: boolean,
|
|
||||||
opt_onHide?: () => void,
|
opt_onHide?: () => void,
|
||||||
opt_secondaryYOffset?: number,
|
opt_secondaryYOffset?: number,
|
||||||
): boolean {
|
): boolean {
|
||||||
@@ -347,7 +336,6 @@ function showPositionedByRect(
|
|||||||
secondaryY,
|
secondaryY,
|
||||||
manageEphemeralFocus,
|
manageEphemeralFocus,
|
||||||
opt_onHide,
|
opt_onHide,
|
||||||
autoCloseOnLostFocus,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,11 +354,9 @@ function showPositionedByRect(
|
|||||||
* @param primaryY Desired origin point y, in absolute px.
|
* @param primaryY Desired origin point y, in absolute px.
|
||||||
* @param secondaryX Secondary/alternative origin point x, in absolute px.
|
* @param secondaryX Secondary/alternative origin point x, in absolute px.
|
||||||
* @param secondaryY Secondary/alternative origin point y, 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
|
* @param manageEphemeralFocus Whether ephemeral focus should be managed
|
||||||
* according to the widget div's lifetime.
|
* 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.
|
* @returns True if the menu rendered at the primary origin point.
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@@ -383,7 +369,6 @@ export function show<T>(
|
|||||||
secondaryY: number,
|
secondaryY: number,
|
||||||
manageEphemeralFocus: boolean,
|
manageEphemeralFocus: boolean,
|
||||||
opt_onHide?: () => void,
|
opt_onHide?: () => void,
|
||||||
autoCloseOnLostFocus?: boolean,
|
|
||||||
): boolean {
|
): boolean {
|
||||||
owner = newOwner as Field;
|
owner = newOwner as Field;
|
||||||
onHide = opt_onHide || null;
|
onHide = opt_onHide || null;
|
||||||
@@ -409,18 +394,7 @@ export function show<T>(
|
|||||||
// Ephemeral focus must happen after the div is fully visible in order to
|
// Ephemeral focus must happen after the div is fully visible in order to
|
||||||
// ensure that it properly receives focus.
|
// ensure that it properly receives focus.
|
||||||
if (manageEphemeralFocus) {
|
if (manageEphemeralFocus) {
|
||||||
const autoCloseCallback = autoCloseOnLostFocus
|
returnEphemeralFocus = getFocusManager().takeEphemeralFocus(div);
|
||||||
? (hasFocus: boolean) => {
|
|
||||||
// If focus is ever lost, close the drop-down.
|
|
||||||
if (!hasFocus) {
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
returnEphemeralFocus = getFocusManager().takeEphemeralFocus(
|
|
||||||
div,
|
|
||||||
autoCloseCallback,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return atOrigin;
|
return atOrigin;
|
||||||
@@ -719,6 +693,7 @@ export function hideWithoutAnimation() {
|
|||||||
onHide();
|
onHide();
|
||||||
onHide = null;
|
onHide = null;
|
||||||
}
|
}
|
||||||
|
clearContent();
|
||||||
owner = null;
|
owner = null;
|
||||||
|
|
||||||
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
||||||
@@ -727,13 +702,6 @@ export function hideWithoutAnimation() {
|
|||||||
returnEphemeralFocus();
|
returnEphemeralFocus();
|
||||||
returnEphemeralFocus = null;
|
returnEphemeralFocus = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content must be cleared after returning ephemeral focus since otherwise it
|
|
||||||
// may force focus changes which could desynchronize the focus manager and
|
|
||||||
// make it think the user directed focus away from the drop-down div (which
|
|
||||||
// will then notify it to not restore focus back to any previously focused
|
|
||||||
// node).
|
|
||||||
clearContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,14 +17,6 @@ import {FocusableTreeTraverser} from './utils/focusable_tree_traverser.js';
|
|||||||
*/
|
*/
|
||||||
export type ReturnEphemeralFocus = () => void;
|
export type ReturnEphemeralFocus = () => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Type declaration for an optional callback to observe when an element with
|
|
||||||
* ephemeral focus has its DOM focus changed before ephemeral focus is returned.
|
|
||||||
*
|
|
||||||
* See FocusManager.takeEphemeralFocus for more details.
|
|
||||||
*/
|
|
||||||
export type EphemeralFocusChangedInDom = (hasDomFocus: boolean) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an IFocusableTree that has been registered for focus management in
|
* Represents an IFocusableTree that has been registered for focus management in
|
||||||
* FocusManager.
|
* FocusManager.
|
||||||
@@ -86,10 +78,7 @@ export class FocusManager {
|
|||||||
private previouslyFocusedNode: IFocusableNode | null = null;
|
private previouslyFocusedNode: IFocusableNode | null = null;
|
||||||
private registeredTrees: Array<TreeRegistration> = [];
|
private registeredTrees: Array<TreeRegistration> = [];
|
||||||
|
|
||||||
private ephemerallyFocusedElement: HTMLElement | SVGElement | null = null;
|
private currentlyHoldsEphemeralFocus: boolean = false;
|
||||||
private ephemeralDomFocusChangedCallback: EphemeralFocusChangedInDom | null =
|
|
||||||
null;
|
|
||||||
private ephemerallyFocusedElementCurrentlyHasFocus: boolean = false;
|
|
||||||
private lockFocusStateChanges: boolean = false;
|
private lockFocusStateChanges: boolean = false;
|
||||||
private recentlyLostAllFocus: boolean = false;
|
private recentlyLostAllFocus: boolean = false;
|
||||||
private isUpdatingFocusedNode: boolean = false;
|
private isUpdatingFocusedNode: boolean = false;
|
||||||
@@ -129,21 +118,6 @@ export class FocusManager {
|
|||||||
} else {
|
} else {
|
||||||
this.defocusCurrentFocusedNode();
|
this.defocusCurrentFocusedNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
const ephemeralFocusElem = this.ephemerallyFocusedElement;
|
|
||||||
if (ephemeralFocusElem) {
|
|
||||||
const hadFocus = this.ephemerallyFocusedElementCurrentlyHasFocus;
|
|
||||||
const hasFocus =
|
|
||||||
!!element &&
|
|
||||||
element instanceof Node &&
|
|
||||||
ephemeralFocusElem.contains(element);
|
|
||||||
if (hadFocus !== hasFocus) {
|
|
||||||
this.ephemerallyFocusedElementCurrentlyHasFocus = hasFocus;
|
|
||||||
if (this.ephemeralDomFocusChangedCallback) {
|
|
||||||
this.ephemeralDomFocusChangedCallback(hasFocus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register root document focus listeners for tracking when focus leaves all
|
// Register root document focus listeners for tracking when focus leaves all
|
||||||
@@ -339,7 +313,7 @@ export class FocusManager {
|
|||||||
*/
|
*/
|
||||||
focusNode(focusableNode: IFocusableNode): void {
|
focusNode(focusableNode: IFocusableNode): void {
|
||||||
this.ensureManagerIsUnlocked();
|
this.ensureManagerIsUnlocked();
|
||||||
const mustRestoreUpdatingNode = !this.ephemerallyFocusedElement;
|
const mustRestoreUpdatingNode = !this.currentlyHoldsEphemeralFocus;
|
||||||
if (mustRestoreUpdatingNode) {
|
if (mustRestoreUpdatingNode) {
|
||||||
// Disable state syncing from DOM events since possible calls to focus()
|
// Disable state syncing from DOM events since possible calls to focus()
|
||||||
// below will loop a call back to focusNode().
|
// below will loop a call back to focusNode().
|
||||||
@@ -421,7 +395,7 @@ export class FocusManager {
|
|||||||
this.removeHighlight(nextTreeRoot);
|
this.removeHighlight(nextTreeRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.ephemerallyFocusedElement) {
|
if (!this.currentlyHoldsEphemeralFocus) {
|
||||||
// Only change the actively focused node if ephemeral state isn't held.
|
// Only change the actively focused node if ephemeral state isn't held.
|
||||||
this.activelyFocusNode(nodeToFocus, prevTree ?? null);
|
this.activelyFocusNode(nodeToFocus, prevTree ?? null);
|
||||||
}
|
}
|
||||||
@@ -449,50 +423,24 @@ export class FocusManager {
|
|||||||
* the returned lambda is called. Additionally, only 1 ephemeral focus context
|
* the returned lambda is called. Additionally, only 1 ephemeral focus context
|
||||||
* can be active at any given time (attempting to activate more than one
|
* can be active at any given time (attempting to activate more than one
|
||||||
* simultaneously will result in an error being thrown).
|
* simultaneously will result in an error being thrown).
|
||||||
*
|
|
||||||
* Important details regarding the onFocusChangedInDom callback:
|
|
||||||
* - This method will be called initially with a value of 'true' indicating
|
|
||||||
* that the ephemeral element has been focused, so callers can rely on that,
|
|
||||||
* if needed, for initialization logic.
|
|
||||||
* - It's safe to end ephemeral focus in this callback (and is encouraged for
|
|
||||||
* callers that wish to automatically end ephemeral focus when the user
|
|
||||||
* directs focus outside of the element).
|
|
||||||
* - The element AND all of its descendants are tracked for focus. That means
|
|
||||||
* the callback will ONLY be called with a value of 'false' if focus
|
|
||||||
* completely leaves the DOM tree for the provided focusable element.
|
|
||||||
* - It's invalid to return focus on the very first call to the callback,
|
|
||||||
* however this is expected to be impossible, anyway, since this method
|
|
||||||
* won't return until after the first call to the callback (thus there will
|
|
||||||
* be no means to return ephemeral focus).
|
|
||||||
*
|
|
||||||
* @param focusableElement The element that should be focused until returned.
|
|
||||||
* @param onFocusChangedInDom An optional callback which will be notified
|
|
||||||
* whenever the provided element's focus changes before ephemeral focus is
|
|
||||||
* returned. See the details above for specifics.
|
|
||||||
* @returns A ReturnEphemeralFocus that must be called when ephemeral focus
|
|
||||||
* should end.
|
|
||||||
*/
|
*/
|
||||||
takeEphemeralFocus(
|
takeEphemeralFocus(
|
||||||
focusableElement: HTMLElement | SVGElement,
|
focusableElement: HTMLElement | SVGElement,
|
||||||
onFocusChangedInDom: EphemeralFocusChangedInDom | null = null,
|
|
||||||
): ReturnEphemeralFocus {
|
): ReturnEphemeralFocus {
|
||||||
this.ensureManagerIsUnlocked();
|
this.ensureManagerIsUnlocked();
|
||||||
if (this.ephemerallyFocusedElement) {
|
if (this.currentlyHoldsEphemeralFocus) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`Attempted to take ephemeral focus when it's already held, ` +
|
`Attempted to take ephemeral focus when it's already held, ` +
|
||||||
`with new element: ${focusableElement}.`,
|
`with new element: ${focusableElement}.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.ephemerallyFocusedElement = focusableElement;
|
this.currentlyHoldsEphemeralFocus = true;
|
||||||
this.ephemeralDomFocusChangedCallback = onFocusChangedInDom;
|
|
||||||
|
|
||||||
if (this.focusedNode) {
|
if (this.focusedNode) {
|
||||||
this.passivelyFocusNode(this.focusedNode, null);
|
this.passivelyFocusNode(this.focusedNode, null);
|
||||||
}
|
}
|
||||||
focusableElement.focus();
|
focusableElement.focus();
|
||||||
this.ephemerallyFocusedElementCurrentlyHasFocus = true;
|
|
||||||
|
|
||||||
const focusedNodeAtStart = this.focusedNode;
|
|
||||||
let hasFinishedEphemeralFocus = false;
|
let hasFinishedEphemeralFocus = false;
|
||||||
return () => {
|
return () => {
|
||||||
if (hasFinishedEphemeralFocus) {
|
if (hasFinishedEphemeralFocus) {
|
||||||
@@ -502,22 +450,9 @@ export class FocusManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
hasFinishedEphemeralFocus = true;
|
hasFinishedEphemeralFocus = true;
|
||||||
this.ephemerallyFocusedElement = null;
|
this.currentlyHoldsEphemeralFocus = false;
|
||||||
this.ephemeralDomFocusChangedCallback = null;
|
|
||||||
|
|
||||||
const hadEphemeralFocusAtEnd =
|
if (this.focusedNode) {
|
||||||
this.ephemerallyFocusedElementCurrentlyHasFocus;
|
|
||||||
this.ephemerallyFocusedElementCurrentlyHasFocus = false;
|
|
||||||
|
|
||||||
// If the user forced away DOM focus during ephemeral focus, then
|
|
||||||
// determine whether focus should be restored back to a focusable node
|
|
||||||
// after ephemeral focus ends. Generally it shouldn't be, but in some
|
|
||||||
// cases (such as the user focusing an actual focusable node) it then
|
|
||||||
// should be.
|
|
||||||
const hasNewFocusedNode = focusedNodeAtStart !== this.focusedNode;
|
|
||||||
const shouldRestoreToNode = hasNewFocusedNode || hadEphemeralFocusAtEnd;
|
|
||||||
|
|
||||||
if (this.focusedNode && shouldRestoreToNode) {
|
|
||||||
this.activelyFocusNode(this.focusedNode, null);
|
this.activelyFocusNode(this.focusedNode, null);
|
||||||
|
|
||||||
// Even though focus was restored, check if it's lost again. It's
|
// Even though focus was restored, check if it's lost again. It's
|
||||||
@@ -535,11 +470,6 @@ export class FocusManager {
|
|||||||
this.focusNode(capturedNode);
|
this.focusNode(capturedNode);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
} else {
|
|
||||||
// If the ephemeral element lost focus then do not force it back since
|
|
||||||
// that likely will override the user's own attempt to move focus away
|
|
||||||
// from the ephemeral experience.
|
|
||||||
this.defocusCurrentFocusedNode();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -548,7 +478,7 @@ export class FocusManager {
|
|||||||
* @returns whether something is currently holding ephemeral focus
|
* @returns whether something is currently holding ephemeral focus
|
||||||
*/
|
*/
|
||||||
ephemeralFocusTaken(): boolean {
|
ephemeralFocusTaken(): boolean {
|
||||||
return !!this.ephemerallyFocusedElement;
|
return this.currentlyHoldsEphemeralFocus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -586,7 +516,7 @@ export class FocusManager {
|
|||||||
// The current node will likely be defocused while ephemeral focus is held,
|
// The current node will likely be defocused while ephemeral focus is held,
|
||||||
// but internal manager state shouldn't change since the node should be
|
// but internal manager state shouldn't change since the node should be
|
||||||
// restored upon exiting ephemeral focus mode.
|
// restored upon exiting ephemeral focus mode.
|
||||||
if (this.focusedNode && !this.ephemerallyFocusedElement) {
|
if (this.focusedNode && !this.currentlyHoldsEphemeralFocus) {
|
||||||
this.passivelyFocusNode(this.focusedNode, null);
|
this.passivelyFocusNode(this.focusedNode, null);
|
||||||
this.updateFocusedNode(null);
|
this.updateFocusedNode(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,6 @@ export function createDom() {
|
|||||||
* passed in here then callers should manage ephemeral focus directly
|
* passed in here then callers should manage ephemeral focus directly
|
||||||
* otherwise focus may not properly restore when the widget closes. Defaults
|
* otherwise focus may not properly restore when the widget closes. Defaults
|
||||||
* to true.
|
* to true.
|
||||||
* @param autoCloseOnLostFocus Whether the widget should automatically hide if
|
|
||||||
* it loses DOM focus for any reason.
|
|
||||||
*/
|
*/
|
||||||
export function show(
|
export function show(
|
||||||
newOwner: unknown,
|
newOwner: unknown,
|
||||||
@@ -108,7 +106,6 @@ export function show(
|
|||||||
newDispose: () => void,
|
newDispose: () => void,
|
||||||
workspace?: WorkspaceSvg | null,
|
workspace?: WorkspaceSvg | null,
|
||||||
manageEphemeralFocus: boolean = true,
|
manageEphemeralFocus: boolean = true,
|
||||||
autoCloseOnLostFocus: boolean = true,
|
|
||||||
) {
|
) {
|
||||||
hide();
|
hide();
|
||||||
owner = newOwner;
|
owner = newOwner;
|
||||||
@@ -134,18 +131,7 @@ export function show(
|
|||||||
dom.addClass(div, themeClassName);
|
dom.addClass(div, themeClassName);
|
||||||
}
|
}
|
||||||
if (manageEphemeralFocus) {
|
if (manageEphemeralFocus) {
|
||||||
const autoCloseCallback = autoCloseOnLostFocus
|
returnEphemeralFocus = getFocusManager().takeEphemeralFocus(div);
|
||||||
? (hasFocus: boolean) => {
|
|
||||||
// If focus is ever lost, close the widget.
|
|
||||||
if (!hasFocus) {
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
returnEphemeralFocus = getFocusManager().takeEphemeralFocus(
|
|
||||||
div,
|
|
||||||
autoCloseCallback,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,18 +146,6 @@ export function hide() {
|
|||||||
|
|
||||||
const div = containerDiv;
|
const div = containerDiv;
|
||||||
if (!div) return;
|
if (!div) return;
|
||||||
|
|
||||||
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
|
||||||
|
|
||||||
if (returnEphemeralFocus) {
|
|
||||||
returnEphemeralFocus();
|
|
||||||
returnEphemeralFocus = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content must be cleared after returning ephemeral focus since otherwise it
|
|
||||||
// may force focus changes which could desynchronize the focus manager and
|
|
||||||
// make it think the user directed focus away from the widget div (which will
|
|
||||||
// then notify it to not restore focus back to any previously focused node).
|
|
||||||
div.style.display = 'none';
|
div.style.display = 'none';
|
||||||
div.style.left = '';
|
div.style.left = '';
|
||||||
div.style.top = '';
|
div.style.top = '';
|
||||||
@@ -189,6 +163,12 @@ export function hide() {
|
|||||||
dom.removeClass(div, themeClassName);
|
dom.removeClass(div, themeClassName);
|
||||||
themeClassName = '';
|
themeClassName = '';
|
||||||
}
|
}
|
||||||
|
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
||||||
|
|
||||||
|
if (returnEphemeralFocus) {
|
||||||
|
returnEphemeralFocus();
|
||||||
|
returnEphemeralFocus = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -252,34 +252,6 @@ suite('DropDownDiv', function () {
|
|||||||
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
|
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
|
||||||
assert.strictEqual(document.activeElement, dropDownDivElem);
|
assert.strictEqual(document.activeElement, dropDownDivElem);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('without auto close on lost focus lost focus does not hide drop-down div', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
Blockly.DropDownDiv.showPositionedByField(field, null, null, true, false);
|
|
||||||
|
|
||||||
// Focus an element outside of the drop-down.
|
|
||||||
document.getElementById('nonTreeElementForEphemeralFocus').focus();
|
|
||||||
|
|
||||||
// Even though the drop-down lost focus, it should still be visible.
|
|
||||||
const dropDownDivElem = document.querySelector('.blocklyDropDownDiv');
|
|
||||||
assert.strictEqual(dropDownDivElem.style.opacity, '1');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with auto close on lost focus lost focus hides drop-down div', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
Blockly.DropDownDiv.showPositionedByField(field, null, null, true, true);
|
|
||||||
|
|
||||||
// Focus an element outside of the drop-down.
|
|
||||||
document.getElementById('nonTreeElementForEphemeralFocus').focus();
|
|
||||||
|
|
||||||
// The drop-down should now be hidden since it lost focus.
|
|
||||||
const dropDownDivElem = document.querySelector('.blocklyDropDownDiv');
|
|
||||||
assert.strictEqual(dropDownDivElem.style.opacity, '0');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
suite('showPositionedByBlock()', function () {
|
suite('showPositionedByBlock()', function () {
|
||||||
@@ -353,48 +325,6 @@ suite('DropDownDiv', function () {
|
|||||||
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
|
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
|
||||||
assert.strictEqual(document.activeElement, dropDownDivElem);
|
assert.strictEqual(document.activeElement, dropDownDivElem);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('without auto close on lost focus lost focus does not hide drop-down div', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
Blockly.DropDownDiv.showPositionedByBlock(
|
|
||||||
field,
|
|
||||||
block,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Focus an element outside of the drop-down.
|
|
||||||
document.getElementById('nonTreeElementForEphemeralFocus').focus();
|
|
||||||
|
|
||||||
// Even though the drop-down lost focus, it should still be visible.
|
|
||||||
const dropDownDivElem = document.querySelector('.blocklyDropDownDiv');
|
|
||||||
assert.strictEqual(dropDownDivElem.style.opacity, '1');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with auto close on lost focus lost focus hides drop-down div', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
Blockly.DropDownDiv.showPositionedByBlock(
|
|
||||||
field,
|
|
||||||
block,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Focus an element outside of the drop-down.
|
|
||||||
document.getElementById('nonTreeElementForEphemeralFocus').focus();
|
|
||||||
|
|
||||||
// the drop-down should now be hidden since it lost focus.
|
|
||||||
const dropDownDivElem = document.querySelector('.blocklyDropDownDiv');
|
|
||||||
assert.strictEqual(dropDownDivElem.style.opacity, '0');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
suite('hideWithoutAnimation()', function () {
|
suite('hideWithoutAnimation()', function () {
|
||||||
|
|||||||
@@ -5624,6 +5624,21 @@ suite('FocusManager', function () {
|
|||||||
/* Ephemeral focus tests. */
|
/* Ephemeral focus tests. */
|
||||||
|
|
||||||
suite('takeEphemeralFocus()', function () {
|
suite('takeEphemeralFocus()', function () {
|
||||||
|
setup(function () {
|
||||||
|
// Ensure ephemeral-specific elements are focusable.
|
||||||
|
document.getElementById('nonTreeElementForEphemeralFocus').tabIndex = -1;
|
||||||
|
document.getElementById('nonTreeGroupForEphemeralFocus').tabIndex = -1;
|
||||||
|
});
|
||||||
|
teardown(function () {
|
||||||
|
// Ensure ephemeral-specific elements have their tab indexes reset for a clean state.
|
||||||
|
document
|
||||||
|
.getElementById('nonTreeElementForEphemeralFocus')
|
||||||
|
.removeAttribute('tabindex');
|
||||||
|
document
|
||||||
|
.getElementById('nonTreeGroupForEphemeralFocus')
|
||||||
|
.removeAttribute('tabindex');
|
||||||
|
});
|
||||||
|
|
||||||
test('with no focused node does not change states', function () {
|
test('with no focused node does not change states', function () {
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
this.focusManager.registerTree(this.testFocusableTree2);
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
this.focusManager.registerTree(this.testFocusableGroup2);
|
||||||
@@ -5960,176 +5975,5 @@ suite('FocusManager', function () {
|
|||||||
);
|
);
|
||||||
assert.strictEqual(document.activeElement, nodeElem);
|
assert.strictEqual(document.activeElement, nodeElem);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with focus change callback initially calls focus change callback with initial state', function () {
|
|
||||||
const callback = sinon.fake();
|
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
|
||||||
const ephemeralElement = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus',
|
|
||||||
);
|
|
||||||
|
|
||||||
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
|
|
||||||
|
|
||||||
assert.strictEqual(callback.callCount, 1);
|
|
||||||
assert.isTrue(callback.firstCall.calledWithExactly(true));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with focus change callback finishes ephemeral does not calls focus change callback again', function () {
|
|
||||||
const callback = sinon.fake();
|
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
|
||||||
const ephemeralElement = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus',
|
|
||||||
);
|
|
||||||
const finishFocusCallback = this.focusManager.takeEphemeralFocus(
|
|
||||||
ephemeralElement,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
callback.resetHistory();
|
|
||||||
|
|
||||||
finishFocusCallback();
|
|
||||||
|
|
||||||
assert.isFalse(callback.called);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with focus change callback set focus to ephemeral child does not call focus change callback again', function () {
|
|
||||||
const callback = sinon.fake();
|
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
|
||||||
const ephemeralElement = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus',
|
|
||||||
);
|
|
||||||
const ephemeralElementChild = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus.child1',
|
|
||||||
);
|
|
||||||
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
|
|
||||||
callback.resetHistory();
|
|
||||||
|
|
||||||
ephemeralElementChild.focus();
|
|
||||||
|
|
||||||
// Focusing a child element shouldn't invoke the callback since the
|
|
||||||
// ephemeral element's tree still holds focus.
|
|
||||||
assert.isFalse(callback.called);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with focus change callback set focus to non-ephemeral element calls focus change callback', function () {
|
|
||||||
const callback = sinon.fake();
|
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
|
||||||
const ephemeralElement = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus',
|
|
||||||
);
|
|
||||||
const ephemeralElement2 = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus2',
|
|
||||||
);
|
|
||||||
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
|
|
||||||
|
|
||||||
ephemeralElement2.focus();
|
|
||||||
|
|
||||||
// There should be a second call that indicates focus was lost.
|
|
||||||
assert.strictEqual(callback.callCount, 2);
|
|
||||||
assert.isTrue(callback.secondCall.calledWithExactly(false));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with focus change callback set focus to non-ephemeral element then back calls focus change callback again', function () {
|
|
||||||
const callback = sinon.fake();
|
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
|
||||||
const ephemeralElement = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus',
|
|
||||||
);
|
|
||||||
const ephemeralElementChild = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus.child1',
|
|
||||||
);
|
|
||||||
const ephemeralElement2 = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus2',
|
|
||||||
);
|
|
||||||
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
|
|
||||||
ephemeralElement2.focus();
|
|
||||||
|
|
||||||
ephemeralElementChild.focus();
|
|
||||||
|
|
||||||
// The latest call should be returning focus.
|
|
||||||
assert.strictEqual(callback.callCount, 3);
|
|
||||||
assert.isTrue(callback.thirdCall.calledWithExactly(true));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with focus change callback set focus to non-ephemeral element with auto return finishes ephemeral does not restore to focused node', function () {
|
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
|
||||||
this.focusManager.focusNode(this.testFocusableTree2Node1);
|
|
||||||
const ephemeralElement = document.getElementById(
|
|
||||||
'nonTreeGroupForEphemeralFocus',
|
|
||||||
);
|
|
||||||
const ephemeralElement2 = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus2',
|
|
||||||
);
|
|
||||||
const finishFocusCallback = this.focusManager.takeEphemeralFocus(
|
|
||||||
ephemeralElement,
|
|
||||||
(hasFocus) => {
|
|
||||||
if (!hasFocus) finishFocusCallback();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Force focus away, triggering the callback's automatic returning logic.
|
|
||||||
ephemeralElement2.focus();
|
|
||||||
|
|
||||||
// The original node should not be focused since the ephemeral element
|
|
||||||
// lost its own DOM focus while ephemeral focus was active. Instead, the
|
|
||||||
// newly active element should still hold focus.
|
|
||||||
const activeElems = Array.from(
|
|
||||||
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
|
|
||||||
);
|
|
||||||
const passiveElems = Array.from(
|
|
||||||
document.querySelectorAll(PASSIVE_FOCUS_NODE_CSS_SELECTOR),
|
|
||||||
);
|
|
||||||
assert.isEmpty(activeElems);
|
|
||||||
assert.strictEqual(passiveElems.length, 1);
|
|
||||||
assert.includesClass(
|
|
||||||
this.testFocusableTree2Node1.getFocusableElement().classList,
|
|
||||||
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
|
|
||||||
);
|
|
||||||
assert.isNull(this.focusManager.getFocusedNode());
|
|
||||||
assert.strictEqual(document.activeElement, ephemeralElement2);
|
|
||||||
assert.isFalse(this.focusManager.ephemeralFocusTaken());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with focus on non-ephemeral element ephemeral ended does not restore to focused node', function () {
|
|
||||||
this.focusManager.registerTree(this.testFocusableTree2);
|
|
||||||
this.focusManager.registerTree(this.testFocusableGroup2);
|
|
||||||
this.focusManager.focusNode(this.testFocusableTree2Node1);
|
|
||||||
const ephemeralElement = document.getElementById(
|
|
||||||
'nonTreeGroupForEphemeralFocus',
|
|
||||||
);
|
|
||||||
const ephemeralElement2 = document.getElementById(
|
|
||||||
'nonTreeElementForEphemeralFocus2',
|
|
||||||
);
|
|
||||||
const finishFocusCallback =
|
|
||||||
this.focusManager.takeEphemeralFocus(ephemeralElement);
|
|
||||||
// Force focus away, triggering the callback's automatic returning logic.
|
|
||||||
ephemeralElement2.focus();
|
|
||||||
|
|
||||||
finishFocusCallback();
|
|
||||||
|
|
||||||
// The original node should not be focused since the ephemeral element
|
|
||||||
// lost its own DOM focus while ephemeral focus was active. Instead, the
|
|
||||||
// newly active element should still hold focus.
|
|
||||||
const activeElems = Array.from(
|
|
||||||
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
|
|
||||||
);
|
|
||||||
const passiveElems = Array.from(
|
|
||||||
document.querySelectorAll(PASSIVE_FOCUS_NODE_CSS_SELECTOR),
|
|
||||||
);
|
|
||||||
assert.isEmpty(activeElems);
|
|
||||||
assert.strictEqual(passiveElems.length, 1);
|
|
||||||
assert.includesClass(
|
|
||||||
this.testFocusableTree2Node1.getFocusableElement().classList,
|
|
||||||
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
|
|
||||||
);
|
|
||||||
assert.isNull(this.focusManager.getFocusedNode());
|
|
||||||
assert.strictEqual(document.activeElement, ephemeralElement2);
|
|
||||||
assert.isFalse(this.focusManager.ephemeralFocusTaken());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -94,13 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="testUnfocusableElement">Unfocusable element</div>
|
<div id="testUnfocusableElement">Unfocusable element</div>
|
||||||
<div id="nonTreeElementForEphemeralFocus" tabindex="-1">
|
<div id="nonTreeElementForEphemeralFocus" />
|
||||||
<div
|
|
||||||
id="nonTreeElementForEphemeralFocus.child1"
|
|
||||||
tabindex="-1"
|
|
||||||
style="margin-left: 1em"></div>
|
|
||||||
</div>
|
|
||||||
<div id="nonTreeElementForEphemeralFocus2" tabindex="-1"></div>
|
|
||||||
<svg width="250" height="250">
|
<svg width="250" height="250">
|
||||||
<g id="testFocusableGroup1">
|
<g id="testFocusableGroup1">
|
||||||
<g id="testFocusableGroup1.node1">
|
<g id="testFocusableGroup1.node1">
|
||||||
@@ -142,7 +136,7 @@
|
|||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="nonTreeGroupForEphemeralFocus" tabindex="-1"></g>
|
<g id="nonTreeGroupForEphemeralFocus"></g>
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Load mocha et al. before Blockly and the test modules so that
|
<!-- Load mocha et al. before Blockly and the test modules so that
|
||||||
we can safely import the test modules that make calls
|
we can safely import the test modules that make calls
|
||||||
|
|||||||
@@ -423,92 +423,5 @@ suite('WidgetDiv', function () {
|
|||||||
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
|
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
|
||||||
assert.strictEqual(document.activeElement, blockFocusableElem);
|
assert.strictEqual(document.activeElement, blockFocusableElem);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('for showing nested div with ephemeral focus restores DOM focus', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
const nestedDiv = document.createElement('div');
|
|
||||||
nestedDiv.tabIndex = -1;
|
|
||||||
Blockly.WidgetDiv.getDiv().appendChild(nestedDiv);
|
|
||||||
Blockly.WidgetDiv.show(field, false, () => {}, null, true);
|
|
||||||
nestedDiv.focus(); // It's valid to focus this during ephemeral focus.
|
|
||||||
|
|
||||||
// Hiding will cause the now focused child div to be removed, leading to
|
|
||||||
// ephemeral focus being lost if the implementation doesn't handle
|
|
||||||
// returning ephemeral focus correctly.
|
|
||||||
Blockly.WidgetDiv.hide();
|
|
||||||
|
|
||||||
// Hiding the div should restore focus back to the block.
|
|
||||||
const blockFocusableElem = block.getFocusableElement();
|
|
||||||
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
|
|
||||||
assert.strictEqual(document.activeElement, blockFocusableElem);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('without auto close on lost focus lost focus does not hide widget div', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
Blockly.WidgetDiv.show(field, false, () => {}, null, true, false);
|
|
||||||
|
|
||||||
// Focus an element outside of the widget.
|
|
||||||
document.getElementById('nonTreeElementForEphemeralFocus').focus();
|
|
||||||
|
|
||||||
// Even though the widget lost focus, it should still be visible.
|
|
||||||
const widgetDivElem = document.querySelector('.blocklyWidgetDiv');
|
|
||||||
assert.strictEqual(widgetDivElem.style.display, 'block');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with auto close on lost focus lost focus hides widget div', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
Blockly.WidgetDiv.show(field, false, () => {}, null, true, true);
|
|
||||||
|
|
||||||
// Focus an element outside of the widget.
|
|
||||||
document.getElementById('nonTreeElementForEphemeralFocus').focus();
|
|
||||||
|
|
||||||
// The widget should now be hidden since it lost focus.
|
|
||||||
const widgetDivElem = document.querySelector('.blocklyWidgetDiv');
|
|
||||||
assert.strictEqual(widgetDivElem.style.display, 'none');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with auto close on lost focus lost focus with nested div hides widget div', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
const nestedDiv = document.createElement('div');
|
|
||||||
nestedDiv.tabIndex = -1;
|
|
||||||
Blockly.WidgetDiv.getDiv().appendChild(nestedDiv);
|
|
||||||
Blockly.WidgetDiv.show(field, false, () => {}, null, true, true);
|
|
||||||
nestedDiv.focus(); // It's valid to focus this during ephemeral focus.
|
|
||||||
|
|
||||||
// Focus an element outside of the widget.
|
|
||||||
document.getElementById('nonTreeElementForEphemeralFocus').focus();
|
|
||||||
|
|
||||||
// The widget should now be hidden since it lost focus.
|
|
||||||
const widgetDivElem = document.querySelector('.blocklyWidgetDiv');
|
|
||||||
assert.strictEqual(widgetDivElem.style.display, 'none');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('with auto close on lost focus lost focus with nested div does not restore DOM focus', function () {
|
|
||||||
const block = this.setUpBlockWithField();
|
|
||||||
const field = Array.from(block.getFields())[0];
|
|
||||||
Blockly.getFocusManager().focusNode(block);
|
|
||||||
const nestedDiv = document.createElement('div');
|
|
||||||
nestedDiv.tabIndex = -1;
|
|
||||||
Blockly.WidgetDiv.getDiv().appendChild(nestedDiv);
|
|
||||||
Blockly.WidgetDiv.show(field, false, () => {}, null, true, true);
|
|
||||||
nestedDiv.focus(); // It's valid to focus this during ephemeral focus.
|
|
||||||
|
|
||||||
// Focus an element outside of the widget.
|
|
||||||
const elem = document.getElementById('nonTreeElementForEphemeralFocus');
|
|
||||||
elem.focus();
|
|
||||||
|
|
||||||
// Auto hiding should not restore focus back to the block since ephemeral
|
|
||||||
// focus was lost before it was returned.
|
|
||||||
assert.isNull(Blockly.getFocusManager().getFocusedNode());
|
|
||||||
assert.strictEqual(document.activeElement, elem);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user