feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: add keyboard navigation controller - #8924

Merged
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode
May 29, 2025
Merged

feat: add keyboard navigation controller#8924
maribethb merged 3 commits into
RaspberryPiFoundation:developfrom
maribethb:kbmode

Conversation

@maribethb

@maribethbmaribethb commented Apr 22, 2025

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Fixes#8852

Proposed Changes

  • Adds a KeyboardNavigationController singleton that has methods for setting keyboard navigation as active or not.

See matching PR in kbe RaspberryPiFoundation/blockly-keyboard-experimentation#555

Additional Information

The idea behind this is explained in #8852

When a user takes a "mouse action" like dragging blocks or the workspace with the mouse or right-clicking a block, then we decide they're not using keyboard navigation and remove the css class.

When a user takes a "keyboard action" like pressing m for move mode, using the arrow keys to navigate, pressing w to get a workspace cursor, etc. we add the css class, which can be used to show additional styling such as passive focus in certain circumstances. So far these keyboard actions are only present in the keyboard-experiment plugin, so core will never set the value to true, so there's no way for this to affect projects that aren't using the keyboard plugin.

There are some ambiguous actions like using the keyboard to copy/paste that are frequently done by both mouse and keyboard users. Those actions do not update the mode one way or the other.

In this PR I also chose not to change the mode for simple block clicks. The reason is that Ben pointed out that for mixed-mode users, clicking might be simpler with the mouse (or a tap on touch devices) while other actions use keyboard shortcuts. I think the current behavior is good. It starts as false, so a mouse-only user won't ever see the extra styling for keyboard users. Keyboard users will see the extra styling when they take any of several common actions with the keyboard. Mixed mode users will largely always see the extra styling unless they use the mouse to drag blocks. This is an improvement over the current input mode tracking in the plugin which does not handle the ambiguous cases noted above.

@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb changed the base branch from rc/v12.0.0 to developMay 28, 2025 17:32
@github-actionsgithub-actionsBot added PR: feature Adds a feature and removed PR: feature Adds a feature labels May 28, 2025
@maribethb
maribethb marked this pull request as ready for review May 28, 2025 17:34
@maribethb
maribethb requested a review from a team as a code ownerMay 28, 2025 17:34
@maribethb
maribethb requested a review from BenHenningMay 28, 2025 17:34
@maribethb

Copy link
Copy Markdown
ContributorAuthor

I will add tests but wanted a gut check on how we feel about the code in gesture before I write tests for it.

@rachel-fenichel

Copy link
Copy Markdown
Collaborator

Yeah, I think this approach makes sense. Adding a page-wide Blockly object is justified because we otherwise could hit the inconsistent state of mouse mode in one workspace and keyboard in another, with mixed visualizations as a result.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb! The general approach makes sense, though I do have some specific questions for documentation and how we should be initializing the controller (I wonder if this initialization change from the current plugin behavior is what's causing RaspberryPiFoundation/blockly-keyboard-experimentation#555 to fail its tests, but I didn't actually look to verify).

Separately, do you have plans to add tests either in RaspberryPiFoundation/blockly-keyboard-experimentation#555 or separately for verifying the specific cases of "do action and verify keyboard nav is on/off"? That seems very important to include as a way of early detecting issues with the controller integration points, and to ensure that each integration point is properly tested (since I don't think we can rely on the main flows to catch every exact point where keyboard nav should be enabled/disabled--it seems very likely for edge cases to crop up with a system like this where we have to make sure we hook it up in all the correct places at all the correct times).

Comment threadcore/gesture.ts
e.preventDefault();
e.stopPropagation();

keyboardNavigationController.setIsActive(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Out of curiosity, what happens if someone tries using the menu key? Does that simulate a right click or require a key event handler? It doesn't currently work in Blockly, and I'm unsure of the expectations for that key for accessibility (or if those expectations even matter since we have so much custom key binding, anyway).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

When you say menu key, do you mean the keyboard shortcut to open the context menu? That just opens the menu, it doesn't go through the gesture system, so it isn't affected by this.

@BenHenningBenHenningMay 29, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think even your question to try and disambiguate my question is still ambiguous. :) "Menu key" is a bit confusing.

I actually mean this key: https://en.wikipedia.org/wiki/Menu_key. From testing it seems like it does nothing, so this is probably a no-op, but it occurred to me because it could theoretically be implemented as a right click on systems where right clicks are intended for opening the context menu.

Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts
Comment threadcore/keyboard_navigation_controller.ts Outdated
*/
export class KeyboardNavigationController {
/** Whether the user is actively using keyboard navigation. */
private isActive = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's an important difference here with the version implemented in RaspberryPiFoundation/blockly-keyboard-experimentation#511: this doesn't default on when focus is received.

This brings up a broader question: how do we decide a proper default? For non-plugin users it completely makes sense to default to off since keyboard nav will be confusing and most users are likely to use mouse. However, as we move more of keyboard nav into core there's an eventual question of properly initializing this state based on the user. I'm not sure arrow keys and the like are the correct approach since we're teaching users to tab-navigate before using arrow keys.

Tab navigation right now just means focusing, and focusing happens on click. We could perhaps listen for tabs and forward them to the browser (and use the presence of pressing tab to mean 'enable keyboard nav'), but this gets tricky with the likes of #9049.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't think tabbing should activate keyboard navigation mode. I would put it in the same category as delete or ctrl+c.

If a user is using keyboard navigation, they won't see the visualizations until they start using the arrow keys or other shortcuts. In practice, that means they are missing one visualization: the workspace focus ring when any part of the workspace (including blocks on it) has active focus. Any other interaction that would show a visualization would entail first using one of the actions that turns on keyboard mode. And even in this case, they do still get the block focus ring.

This approach also lets the application set their own heuristics. They could enable it if the user has enabled some preference, or if the user was using keyboard navigation last time they loaded the workspace for example. I think it's reasonable for blockly to default to off and let the application to decide a different default if they want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think that makes sense, though I do wonder if we need to document it somewhere more clearly so that application authors know exactly what to expect with how, and when, keyboard nav mode properly enables.

The two flows I'm unsure of are:

  • A low vision user relying on a combination of tab/keyboard nav and visuals for interaction (with no mouse movement). This might be an extremely specific case though, I'm not sure.
  • A user with motor impairments that has to use limited keyboard actions, but is not visually impaired and is still largely relying on visual indicators.

Both can certainly be made better with application side customizing, as you mentioned.

For low vision users not relying on visuals then the on/off highlights shouldn't matter, and users using a combination of keyboard and mouse I suspect will use mouse to select a block before arrow keys.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I know that microbit really wants to avoid having a user-setting for this, but I think for the reasons you mentioned and others, it probably makes sense to have one. But that will be the application's responsibility to figure out :)

@maribethb

Copy link
Copy Markdown
ContributorAuthor

RaspberryPiFoundation/blockly-keyboard-experimentation#555 fails to build because keyboardNavigationController doesn't exist until this PR is merged.

I'm working on tests now but I wanted to make sure the approach was generally approved before finishing them.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

I'm adding more serious webdriver tests in the kb experiment since there's no scenario in which it's ever set to true in core.

@maribethb

Copy link
Copy Markdown
ContributorAuthor

Tests are in RaspberryPiFoundation/blockly-keyboard-experimentation#555 so this is ready for review again now.

@BenHenningBenHenning left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @maribethb. This LGTM!

@maribethb
maribethb merged commit 0498ed6 into RaspberryPiFoundation:developMay 29, 2025
@maribethb
maribethb deleted the kbmode branch July 7, 2026 20:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: featureAdds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attempt to track if the user is using keyboard navigation

4 participants

@maribethb@rachel-fenichel@BenHenning@sappm01