Skip to content

Conversation

@BenHenning
Copy link
Collaborator

@BenHenning BenHenning commented Apr 21, 2025

The basics

The details

Resolves

Fixes #8913
Fixes #8914
Fixes part of #8771

Proposed Changes

This updates WorkspaceSvg and BlockSvg to be focusable, that is, it makes the workspace a IFocusableTree and blocks IFocusableNodes.

Some important details:

  • While this introduces focusable tree support for Workspace it doesn't include two other components that are obviously needed by the keyboard navigation plugin's playground: fields and connections. These will be introduced in subsequent PRs.
  • Blocks are set up to automatically synchronize their selection state with their focus state. This will eventually help to replace LineCursor's responsibility for managing selection state itself.
  • The tabindex property for the workspace and its ARIA label have been moved down to the .blocklyWorkspace element itself rather than its wrapper. This helps address some tab stop issues that are already addressed in the plugin (via monkey patches), but also to ensure that the workspace's main SVG group interacts correctly with FocusManager.
  • WorkspaceSvg is being initially set up to default to its first top block when being focused for the first time. This is to match parity with the keyboard navigation plugin, however the latter also has functionality for defaulting to a position when no blocks are present. It's not clear how to actually support this under the new focus-based system (without adding an ephemeral element on which to focus), or if it's even necessary (since the workspace root can hold focus).

Reason for Changes

This is part of an ongoing effort to ensure key components of Blockly are focusable so that they can be keyboard-navigable (with other needed changes yet both in Core Blockly and the keyboard navigation plugin).

Test Coverage

No new tests have been added. It's certainly possible to add unit tests for the focusable configurations being introduced in this PR, but it may not be highly beneficial. It's largely assumed that the individual implementations should work due to a highly tested FocusManager, and it may be the case that the interactions of the components working together is far more important to verify (that is, the end user flows). The latter is planned to be tackled as part of #8915.

Documentation

No documentation changes should be needed here.

Additional Information

This includes changes that have been pulled from #8875.

This adds basic support for WorkspaceSvg being focusable via its blocks,
though fields and connections are not yet supported.
Copy link
Collaborator Author

@BenHenning BenHenning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-reviewed changes that aren't part of #8909.

@BenHenning BenHenning marked this pull request as ready for review April 21, 2025 23:53
@BenHenning BenHenning requested a review from a team as a code owner April 21, 2025 23:53
@BenHenning BenHenning requested a review from gonfunko April 21, 2025 23:53
@BenHenning BenHenning added the PR: feature Adds a feature label Apr 21, 2025
@BenHenning
Copy link
Collaborator Author

PTAL @gonfunko. FYI this includes changes from #8909 which can be ignored (there are no additional changes in those files in this PR).

@BenHenning BenHenning changed the title feat: Make WorkspaceSvg and BlockSvg focusable feat: Make WorkspaceSvg and BlockSvg focusable [Blocked on: #8909] Apr 21, 2025
@BenHenning BenHenning mentioned this pull request Apr 22, 2025
1 task
@BenHenning BenHenning changed the title feat: Make WorkspaceSvg and BlockSvg focusable [Blocked on: #8909] feat: Make WorkspaceSvg and BlockSvg focusable [Blocked on PR: #8909] Apr 22, 2025
this.svgGroup_ = dom.createSvgElement(Svg.G, {'class': 'blocklyWorkspace'});
this.svgGroup_ = dom.createSvgElement(Svg.G, {
'class': 'blocklyWorkspace',
// Only the main workspace should be tabbable.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Say "top level" rather than "main", because Blockly.getMainWorkspace() already exists and refers to the currently active workspace.

From context, I'm assuming that the distinction is between top level and flyout or mutator workspaces.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct--fixed!


/** See IFocusableNode.onNodeFocus. */
onNodeFocus(): void {
if (!this.isShadow()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if it is a shadow block?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was actually copying https://github.com/google/blockly/blob/ac7fea11cffc4c445cedae77675290861ceec2f6/core/keyboard_nav/line_cursor.ts#L652-L657 but through a lot of code transforming. In retrospect, this is probably wrong.

To be fair, I'm not 100% certain how shadow blocks will behave and that will need testing. The field will receive focus, and the shadow block might be focusable if clicked on but will never otherwise be navigated to per the cursor's behavior (I think). So this should be safe to remove and just call setSelected() directly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not sure. I think call setSelected directly here and do some manual verification, then file a ticket to follow up with comprehensive tests for that case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far I'm not seeing any issues, so I don't think there's a particular concern to address.

I've made a note in #8915 regarding this specific case.

@BenHenning BenHenning changed the title feat: Make WorkspaceSvg and BlockSvg focusable [Blocked on PR: #8909] feat: Make WorkspaceSvg and BlockSvg focusable Apr 23, 2025
Copy link
Collaborator Author

@BenHenning BenHenning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-reviewed latest changes & followed up to review comments.

Note that #8909 has been merged so this PR is only the changes related to workspace & blocks. Subsequently, I realized I forgot to include the tab index change to make blocks focusable, so that's been fixed in the latest changes.


/** See IFocusableNode.onNodeFocus. */
onNodeFocus(): void {
if (!this.isShadow()) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was actually copying https://github.com/google/blockly/blob/ac7fea11cffc4c445cedae77675290861ceec2f6/core/keyboard_nav/line_cursor.ts#L652-L657 but through a lot of code transforming. In retrospect, this is probably wrong.

To be fair, I'm not 100% certain how shadow blocks will behave and that will need testing. The field will receive focus, and the shadow block might be focusable if clicked on but will never otherwise be navigated to per the cursor's behavior (I think). So this should be safe to remove and just call setSelected() directly.

this.svgGroup_ = dom.createSvgElement(Svg.G, {'class': 'blocklyWorkspace'});
this.svgGroup_ = dom.createSvgElement(Svg.G, {
'class': 'blocklyWorkspace',
// Only the main workspace should be tabbable.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct--fixed!

@BenHenning
Copy link
Collaborator Author

PTAL @gonfunko if you would like to double check the latest changes.

PTAL @rachel-fenichel per your comments.

Copy link
Collaborator Author

@BenHenning BenHenning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-reviewed latest brances change.

@BenHenning
Copy link
Collaborator Author

Thanks all for the reviews. :) Going ahead and merging (with squash this time...).

@BenHenning BenHenning merged commit d7680cf into RaspberryPiFoundation:rc/v12.0.0 Apr 24, 2025
7 checks passed
@BenHenning BenHenning deleted the make-workspace-focusable branch April 24, 2025 21:49
@BenHenning BenHenning mentioned this pull request Apr 24, 2025
RoboErikG added a commit to RoboErikG/blockly that referenced this pull request Apr 25, 2025
This was referenced Apr 25, 2025
RoboErikG added a commit that referenced this pull request Apr 25, 2025
* Revert "feat: Make toolbox and flyout focusable (#8920)"

This reverts commit 5bc8380.

* Revert "feat: Make WorkspaceSvg and BlockSvg focusable (#8916)"

This reverts commit d7680cf.
@BenHenning BenHenning mentioned this pull request Apr 29, 2025
BenHenning added a commit to BenHenning/blockly that referenced this pull request Apr 29, 2025
…#8916)

## The basics

- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)

## The details
### Resolves

Fixes RaspberryPiFoundation#8913
Fixes RaspberryPiFoundation#8914
Fixes part of RaspberryPiFoundation#8771

### Proposed Changes

This updates `WorkspaceSvg` and `BlockSvg` to be focusable, that is, it makes the workspace a `IFocusableTree` and blocks `IFocusableNode`s.

Some important details:
- While this introduces focusable tree support for `Workspace` it doesn't include two other components that are obviously needed by the keyboard navigation plugin's playground: fields and connections. These will be introduced in subsequent PRs.
- Blocks are set up to automatically synchronize their selection state with their focus state. This will eventually help to replace `LineCursor`'s responsibility for managing selection state itself.
- The tabindex property for the workspace and its ARIA label have been moved down to the `.blocklyWorkspace` element itself rather than its wrapper. This helps address some tab stop issues that are already addressed in the plugin (via monkey patches), but also to ensure that the workspace's main SVG group interacts correctly with `FocusManager`.
- `WorkspaceSvg` is being initially set up to default to its first top block when being focused for the first time. This is to match parity with the keyboard navigation plugin, however the latter also has functionality for defaulting to a position when no blocks are present. It's not clear how to actually support this under the new focus-based system (without adding an ephemeral element on which to focus), or if it's even necessary (since the workspace root can hold focus).

### Reason for Changes

This is part of an ongoing effort to ensure key components of Blockly are focusable so that they can be keyboard-navigable (with other needed changes yet both in Core Blockly and the keyboard navigation plugin).

### Test Coverage

No new tests have been added. It's certainly possible to add unit tests for the focusable configurations being introduced in this PR, but it may not be highly beneficial. It's largely assumed that the individual implementations should work due to a highly tested FocusManager, and it may be the case that the interactions of the components working together is far more important to verify (that is, the end user flows). The latter is planned to be tackled as part of RaspberryPiFoundation#8915.

### Documentation

No documentation changes should be needed here.

### Additional Information

This includes changes that have been pulled from RaspberryPiFoundation#8875.
BenHenning added a commit that referenced this pull request Apr 30, 2025
_Note: This is a roll forward of #8916 that was reverted in #8933. See Additional Information below._

## The basics

- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)

## The details
### Resolves

Fixes #8913
Fixes #8914
Fixes part of #8771

### Proposed Changes

This updates `WorkspaceSvg` and `BlockSvg` to be focusable, that is, it makes the workspace a `IFocusableTree` and blocks `IFocusableNode`s.

Some important details:
- While this introduces focusable tree support for `Workspace` it doesn't include two other components that are obviously needed by the keyboard navigation plugin's playground: fields and connections. These will be introduced in subsequent PRs.
- Blocks are set up to automatically synchronize their selection state with their focus state. This will eventually help to replace `LineCursor`'s responsibility for managing selection state itself.
- The tabindex property for the workspace and its ARIA label have been moved down to the `.blocklyWorkspace` element itself rather than its wrapper. This helps address some tab stop issues that are already addressed in the plugin (via monkey patches), but also to ensure that the workspace's main SVG group interacts correctly with `FocusManager`.
- `WorkspaceSvg` is being initially set up to default to its first top block when being focused for the first time. This is to match parity with the keyboard navigation plugin, however the latter also has functionality for defaulting to a position when no blocks are present. It's not clear how to actually support this under the new focus-based system (without adding an ephemeral element on which to focus), or if it's even necessary (since the workspace root can hold focus).
- `css.ts` was updated to remove `blocklyActiveFocus` and `blocklyPassiveFocus` since these have unintended highlighting consequences that aren't actually desirable yet. Instead, the exact styling for active/passive focus will be iterated in the keyboard navigation plugin project and moved to Core once finalized.

### Reason for Changes

This is part of an ongoing effort to ensure key components of Blockly are focusable so that they can be keyboard-navigable (with other needed changes yet both in Core Blockly and the keyboard navigation plugin).

### Test Coverage

No new tests have been added. It's certainly possible to add unit tests for the focusable configurations being introduced in this PR, but it may not be highly beneficial. It's largely assumed that the individual implementations should work due to a highly tested FocusManager, and it may be the case that the interactions of the components working together is far more important to verify (that is, the end user flows). The latter is planned to be tackled as part of #8915.

### Documentation

No documentation changes should be needed here.

### Additional Information

This includes changes that have been pulled from #8875.

This was originally merged in #8916 but was reverted in #8933 due to RaspberryPiFoundation/blockly-keyboard-experimentation#481. This actually contains no differences from the original PR except for `css.ts` which are documented above. It does employ a new merge strategy: all of the necessary PRs to move both Core and the plugin over to using `FocusManager` will be staged and merged in quick succession as ensuring the plugin works for each constituent change (vs. the final one) is quite complex. Thus, this PR *does* break the plugin, and won't be merged until its subsequent PRs are approved and also ready for merging.

Edit: See #8938 (comment) for why this actually is being merged a bit sooner than originally planned. Keeping the original reasoning above for context.
BenHenning added a commit that referenced this pull request Apr 30, 2025
_Note: This is a roll forward of #8920 that was reverted in #8933. See Additional Information below._

## The basics

- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)

## The details
### Resolves

Fixes #8918
Fixes #8919
Fixes part of #8943
Fixes part of #8771

### Proposed Changes

This updates several classes in order to make toolboxes and flyouts focusable:
- `IFlyout` is now an `IFocusableTree` with corresponding implementations in `FlyoutBase`.
- `IToolbox` is now an `IFocusableTree` with corresponding implementations in `Toolbox`.
- `IToolboxItem` is now an `IFocusableNode` with corresponding implementations in `ToolboxItem`.
- As the primary toolbox items, `ToolboxCategory` and `ToolboxSeparator` were updated to have -1 tab indexes and defined IDs to help `ToolboxItem` fulfill its contracted for `IFocusableNode.getFocusableElement`.
- `FlyoutButton` is now an `IFocusableNode` (with corresponding ID generation, tab index setting, and ID matching for retrieval in `WorkspaceSvg`).

Each of these two new focusable trees have specific noteworthy behaviors behaviors:
- `Toolbox` will automatically indicate that its first item should be focused (if one is present), even overriding the ability to focus the toolbox's root (however there are some cases where that can still happen).
- `Toolbox` will automatically synchronize its selection state with its item nodes being focused.
- `FlyoutBase`, now being a focusable tree, has had a tab index of 0 added. Normally a tab index of -1 is all that's needed, but the keyboard navigation plugin specifically uses 0 for flyout so that the flyout is tabbable. This is a **new** tab stop being introduced.
- `FlyoutBase` holds a workspace (for rendering blocks) and, since `WorkspaceSvg` is already set up to be a focusable tree, it's represented as a subtree to `FlyoutBase`. This does introduce some wonky behaviors: the flyout's root will have passive focus while its contents have active focus. This could be manually disabled with some CSS if it ends up being a confusing user experience.
- Both `FlyoutBase` and `WorkspaceSvg` have built-in behaviors for detecting when a user tries navigating away from an open flyout to ensure that the flyout is closed when it's supposed to be. That is, the flyout is auto-hideable and a non-flyout, non-toolbox node has then been focused. This matches parity with the `T`/`Esc` flows supported in the keyboard navigation plugin playground.

One other thing to note: `Toolbox` had a few tests to update that were trying to reinit a toolbox without first disposing of it (which was caught by one of `FocusManager`'s state guardrails).

This only addresses part of #8943: it adds support for `FlyoutButton` which covers both buttons and labels. However, a longer-term solution may be to change `FlyoutItem` itself to force using an `IFocusableNode` as its element.

### Reason for Changes

This is part of an ongoing effort to ensure key components of Blockly are focusable so that they can be keyboard-navigable (with other needed changes yet both in Core Blockly and the keyboard navigation plugin).

### Test Coverage

No new tests have been added. It's certainly possible to add unit tests for the focusable configurations being introduced in this PR, but it may not be highly beneficial. It's largely assumed that the individual implementations should work due to a highly tested FocusManager, and it may be the case that the interactions of the components working together is far more important to verify (that is, the end user flows). The latter is planned to be tackled as part of #8915.

### Documentation

No documentation changes should be needed here.

### Additional Information

This includes changes that have been pulled from #8875.

This was originally merged in #8916 but was reverted in #8933 due to RaspberryPiFoundation/blockly-keyboard-experimentation#481. Note that this does contain a number of differences from the original PR (namely, changes in `WorkspaceSvg` and `FlyoutButton` in order to make `FlyoutButton`s focusable). Otherwise, this has the same caveats as those noted in #8938 with regards to the experimental keyboard navigation plugin.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: feature Adds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants