Files
blockly/core/interfaces/i_focusable_node.ts
Ben Henning 4074cee31b feat!: Make everything ISelectable focusable (#9004)
* feat!: Make bubbles, comments, and icons focusable

* feat!: Make ISelectable and ICopyable focusable.

* feat: Consolidate selection calls.

Now everything is based on focus with selection only being used as a
proxy.

* feat: Invert responsibility for setSelected().

Now setSelected() is only for quasi-external use.

* feat: Push up shadow check to getters.

Needed new common-level helper.

* chore: Lint fixes.

* feat!: Allow IFocusableNode to disable focus.

* chore: post-merge lint fixes

* fix: Fix tests + text bubble focusing.

This fixed then regressed a circular dependency causing the node and
advanced compilation steps to fail. This investigation is ongoing.

* fix: Clean up & fix imports.

This ensures the node and advanced compilation test steps now pass.

* fix: Lint fixes + revert commented out logic.

* chore: Remove unnecessary cast.

Addresses reviewer comment.

* fix: Some issues and a bunch of clean-ups.

This addresses a bunch of review comments, and fixes selecting workspace
comments.

* chore: Lint fix.

* fix: Remove unnecessary shadow consideration.

* chore: Revert import.

* chore: Some doc updates & added a warn statement.
2025-05-09 08:16:14 -07:00

114 lines
4.5 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {IFocusableTree} from './i_focusable_tree.js';
/** Represents anything that can have input focus. */
export interface IFocusableNode {
/**
* Returns the DOM element that can be explicitly requested to receive focus.
*
* IMPORTANT: Please note that this element is expected to have a visual
* presence on the page as it will both be explicitly focused and have its
* style changed depending on its current focus state (i.e. blurred, actively
* focused, and passively focused). The element will have one of two styles
* attached (where no style indicates blurred/not focused):
* - blocklyActiveFocus
* - blocklyPassiveFocus
*
* The returned element must also have a valid ID specified, and unique to the
* element relative to its nearest IFocusableTree parent. It must also have a
* negative tabindex (since the focus manager itself will manage its tab index
* and a tab index must be present in order for the element to be focusable in
* the DOM).
*
* The returned element must be visible if the node is ever focused via
* FocusManager.focusNode() or FocusManager.focusTree(). It's allowed for an
* element to be hidden until onNodeFocus() is called, or become hidden with a
* call to onNodeBlur().
*
* It's expected the actual returned element will not change for the lifetime
* of the node (that is, its properties can change but a new element should
* never be returned).
*
* @returns The HTMLElement or SVGElement which can both receive focus and be
* visually represented as actively or passively focused for this node.
*/
getFocusableElement(): HTMLElement | SVGElement;
/**
* Returns the closest parent tree of this node (in cases where a tree has
* distinct trees underneath it), which represents the tree to which this node
* belongs.
*
* @returns The node's IFocusableTree.
*/
getFocusableTree(): IFocusableTree;
/**
* Called when this node receives active focus.
*
* Note that it's fine for implementations to change visibility modifiers, but
* they should avoid the following:
* - Creating or removing DOM elements (including via the renderer or drawer).
* - Affecting focus via DOM focus() calls or the FocusManager.
*/
onNodeFocus(): void;
/**
* Called when this node loses active focus. It may still have passive focus.
*
* This has the same implementation restrictions as onNodeFocus().
*/
onNodeBlur(): void;
/**
* Indicates whether this node allows focus. If this returns false then none
* of the other IFocusableNode methods will be called.
*
* Note that special care must be taken if implementations of this function
* dynamically change their return value value over the lifetime of the node
* as certain environment conditions could affect the focusability of this
* node's DOM element (such as whether the element has a positive or zero
* tabindex). Also, changing from a true to a false value while the node holds
* focus will not immediately change the current focus of the node nor
* FocusManager's internal state, and thus may result in some of the node's
* functions being called later on when defocused (since it was previously
* considered focusable at the time of being focused).
*
* Implementations should generally always return true here unless there are
* circumstances under which this node should be skipped for focus
* considerations. Examples may include being disabled, read-only, a purely
* visual decoration, or a node with no visual representation that must
* implement this interface (e.g. due to a parent interface extending it).
* Keep in mind accessibility best practices when determining whether a node
* should be focusable since even disabled and read-only elements are still
* often relevant to providing organizational context to users (particularly
* when using a screen reader).
*
* @returns Whether this node can be focused by FocusManager.
*/
canBeFocused(): boolean;
}
/**
* Determines whether the provided object fulfills the contract of
* IFocusableNode.
*
* @param object The object to test.
* @returns Whether the provided object can be used as an IFocusableNode.
*/
export function isFocusableNode(object: any | null): object is IFocusableNode {
return (
object &&
'getFocusableElement' in object &&
'getFocusableTree' in object &&
'onNodeFocus' in object &&
'onNodeBlur' in object &&
'canBeFocused' in object
);
}