Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions core/focus_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ export class FocusManager {
*/
static readonly PASSIVE_FOCUS_NODE_CSS_CLASS_NAME = 'blocklyPassiveFocus';

static readonly ACTIVE_FOCUS_WITHIN_TREE_CSS_CLASS_NAME =
'blocklyTreeHasActiveFocus';

static readonly PASSIVE_FOCUS_WITHIN_TREE_CSS_CLASS_NAME =
'blocklyTreeHasPassiveFocus';

static readonly ACTIVE_FOCUS_WITHIN_SUBTREE_CSS_CLASS_NAME =
'blocklySubtreeHasActiveFocus';

static readonly PASSIVE_FOCUS_WITHIN_SUBTREE_CSS_CLASS_NAME =
'blocklySubtreeHasPassiveFocus';

// Represents the single node that will receive active focus when ephemeral focus ends.
static readonly WAITING_FOR_EPHEMERAL_FOCUS_CSS_CLASS_NAME =
'blocklyWaitingForEphemeralFocus';

private focusedNode: IFocusableNode | null = null;
private previouslyFocusedNode: IFocusableNode | null = null;
private registeredTrees: Array<TreeRegistration> = [];
Expand Down Expand Up @@ -365,6 +381,12 @@ export class FocusManager {
const prevTree = prevNode?.getFocusableTree();
if (prevNode) {
this.passivelyFocusNode(prevNode, nextTree);
if (this.currentlyHoldsEphemeralFocus) {
dom.removeClass(
prevNode.getFocusableElement(),
FocusManager.WAITING_FOR_EPHEMERAL_FOCUS_CSS_CLASS_NAME,
);
}
}

// If there's a focused node in the new node's tree, ensure it's reset.
Expand All @@ -382,6 +404,11 @@ export class FocusManager {
if (!this.currentlyHoldsEphemeralFocus) {
// Only change the actively focused node if ephemeral state isn't held.
this.activelyFocusNode(nodeToFocus, prevTree ?? null);
} else {
dom.addClass(
nodeToFocus.getFocusableElement(),
FocusManager.WAITING_FOR_EPHEMERAL_FOCUS_CSS_CLASS_NAME,
);
}
this.updateFocusedNode(nodeToFocus);
if (mustRestoreUpdatingNode) {
Expand Down Expand Up @@ -422,6 +449,10 @@ export class FocusManager {

if (this.focusedNode) {
this.passivelyFocusNode(this.focusedNode, null);
dom.addClass(
this.focusedNode.getFocusableElement(),
FocusManager.WAITING_FOR_EPHEMERAL_FOCUS_CSS_CLASS_NAME,
);
}
focusableElement.focus();

Expand All @@ -438,6 +469,10 @@ export class FocusManager {

if (this.focusedNode) {
this.activelyFocusNode(this.focusedNode, null);
dom.removeClass(
this.focusedNode.getFocusableElement(),
FocusManager.WAITING_FOR_EPHEMERAL_FOCUS_CSS_CLASS_NAME,
);

// Even though focus was restored, check if it's lost again. It's
// possible for the browser to force focus away from all elements once
Expand Down Expand Up @@ -539,6 +574,16 @@ export class FocusManager {
// longer tabbable now that it holds active focus.
tree.getRootFocusableNode().getFocusableElement().tabIndex = -1;
}

const treeRoot = tree.getRootFocusableNode().getFocusableElement();
dom.addClass(
treeRoot,
FocusManager.ACTIVE_FOCUS_WITHIN_TREE_CSS_CLASS_NAME,
);
dom.removeClass(
treeRoot,
FocusManager.PASSIVE_FOCUS_WITHIN_TREE_CSS_CLASS_NAME,
);
}
node.onNodeFocus();
this.lockFocusStateChanges = false;
Expand Down Expand Up @@ -588,6 +633,16 @@ export class FocusManager {
// tabbable since it no longer holds active focus.
tree.getRootFocusableNode().getFocusableElement().tabIndex = 0;
}

const treeRoot = tree.getRootFocusableNode().getFocusableElement();
dom.addClass(
treeRoot,
FocusManager.PASSIVE_FOCUS_WITHIN_TREE_CSS_CLASS_NAME,
);
dom.removeClass(
treeRoot,
FocusManager.ACTIVE_FOCUS_WITHIN_TREE_CSS_CLASS_NAME,
);
}
node.onNodeBlur();
this.lockFocusStateChanges = false;
Expand Down Expand Up @@ -633,6 +688,16 @@ export class FocusManager {
dom.removeClass(element, FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME);
}

private recomputeSubtreeCssClasses(): void {
// Collect all focused elements.
// const passiveElems = document.querySelectorAll(
// FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
// );
// const activeElem = this.focusedNode?.getFocusableElement() ?? null;
// const focusedElems = [...(activeElem ? [activeElem] : []), ...passiveElems];
// For each element, collect all... TODO: finish.
}

private static focusManager: FocusManager | null = null;

/**
Expand Down