## The basics
- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)
## The details
### Resolves
Fixes part of https://github.com/RaspberryPiFoundation/blockly-keyboard-experimentation/issues/764
Fixes part of #9450 (infrastructure needs)
### Proposed Changes
Introduces support for two new "where am I?" shortcuts for helping to provide location context for users:
- `I`: re-reads the current selected block with full verbosity (i.e. also includes the block's field types with their values in the readout).
- `shift+I`: reads the current selected block's parent with full verbosity.
Note that this includes some functional changes to `Field` to allow for more powerful customization of a field's ARIA representation (by splitting up value and type), though a field's value defaults potentially to null which will be ignored in the final ARIA computed label. This seems necessary per the discussion here: https://github.com/RaspberryPiFoundation/blockly/pull/9470/files#r2541508565 but more consideration may be needed here as part of #9307.
Some limitations in the new shortcuts:
- They will not read out anything if a block is not selected (e.g. for fields and icons).
- They read out input blocks when the input block is selected.
- They cannot read out anything while in move mode (due to the behavior here in the plugin which automatically cancels moves if an unknown shortcut is pressed: a36f3662b0/src/actions/mover.ts (L166-L191)).
- The readout is limited by the problems of dynamic ARIA announcements (per #9460).
### Reason for Changes
https://github.com/RaspberryPiFoundation/blockly-keyboard-experimentation/issues/764 provides context on the specific needs addressed here.
### Test Coverage
Self tested. No new automated tests needed for experimental work.
### Documentation
No new documentation needed for experimental work.
### Additional Information
This was spun out of #9470 with the intent of getting shortcuts initially working checked in even if the entirety of the experience is incomplete.
## The basics
- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)
## The details
### Resolves
Fixes#9466
### Proposed Changes
Ensures `FieldInput`'s ARIA labels are recomputed when an invalid value is attempted to be set.
### Reason for Changes
Previously the `FieldInput` would continuously update its ARIA label as the value changed, including for invalid values. If the editor was cancelled this would correctly revert but if an invalid value was attempted to be saved then it would cancel the update but not correct the ARIA label.
### Test Coverage
No new tests are needed for this experimental change. This has been manually verified locally using `FieldNumber` in Core's advanced playground.
### Documentation
No documentation changes are needed for this experimental change.
### Additional Information
None.
* fix: Miscellaneous improvements for screenreader support.
* fix: Include field name in ARIA label.
* fix: Update block ARIA labels when inputs are shown/hidden.
* fix: Make field row label generation more robust.
## The basics
- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)
## The details
### Resolves
Fixes#8206Fixes#8210Fixes#8213Fixes#8255Fixes#8211Fixes#8212Fixes#8254
Fixes part of #9301
Fixes part of #9304
### Proposed Changes
This PR completes the remaining ARIA roles and properties needed for all core fields. Specifically:
- #8206: A better name needed to be used for the checkbox value, plus there was an ARIA property missing for actually representing the checkbox state. The latter needed to be updated upon toggling the checkbox, as well. These changes bring checkbox fields in compliance with the ARIA checkbox pattern documented here: https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/.
- #8210: This one required a lot of changes in order to adapt to the ARIA combobox pattern documented here: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/. Specifically:
- Menus needed to have a unique ID that's also exposed in order to link the combobox element to its menu when open.
- ARIA's `activedescendant` proved very useful in ensuring that the current dropdown selection is correctly read when the combobox has focus but its menu isn't opened.
- The default properties available for options (label and value) aren't very good for readout, so a custom ARIA property was added for much clearer option readouts. This is only demonstrated for the math arithmetic block for now.
- The text element is normally hidden for ARIA but it's useful in conjunction with `activedescendant` to represent the current value selection.
- Images have been handled here as well (partly as part of #8255) by leveraging their alt text for readouts. This actually seems to work quite well both for current value and selection.
- #8213: Much of the improvements here come from the combobox (`FieldDropdown`) improvements explained above. However one additional bit was done to provide an explicit 'Variable <name>' readout for the purpose of clarity. This demonstrates some contextualization of the value of the field which may be a generally useful pattern to copy in other field contexts.
- #8255: Image fields have been refined since they were redundantly specifying 'image' when an `image` ARIA role is already being used. Now only the alt text is supplied along with the role context. Note that images need special handling since they can sometimes be navigable (such as when they have click handlers).
- #8211: Text input fields have had their labeling improved like all other fields, and the field's value is now exposed via its `text` element since this will show up as a `StaticText` node in the accessibility tree and automatically be read as part of the field's value.
- #8212: This gets the same benefits as the previous point since those improvements were included for both text and number input. However, existing `valuemin` and `valuemax` ARIA properties have been removed. It seems these are really only useful when introducing a slider mechanism (see https://www.w3.org/WAI/ARIA/apg/patterns/slider/) and from testing seems to not really be utilized for the basic text input that `FieldNumber` currently uses. It may be the case that this is a better pattern to use in the future, but it's more likely that other custom fields could benefit from more specific patterns like slider rather than `FieldNumber` being changed in that way.
- #8254 and part of #9304: Field labels have been completely removed from the accessibility node tree since they can never be navigated to (as #8254 explains all labels will be included as part of the block's ARIA label itself for readout parity with navigation options).
Note that it doesn't cover external fields (such as those supplied in blockly-samples), nor does it fully set up the infrastructure work for those. Ultimately that work needs to happen as part of #9301.
Beyond the role work above, this PR also introduces some fundamental work for #9301. Specifically:
- It demonstrates how block definitions could be used to introduce accessibility label customizations (in this case for the options of the arithmetic operator block's drop-down field, plus the block itself).
- It sets up some central label computation for all fields, though more thought is needed on whether this is sufficient for custom fields outside of core Blockly and on how to properly contextualize labels for field values. Core Blockly's fields are fairly simple for representing values which is why that aspect of #9301 didn't need to be solved in this PR. Note that the field labeling here is being used to improve all of the fields above, but also it tries to aggressively fall back to the _next best_ label to be used (though it's possible to run out of options which is why fields still need contextually-specific fallbacks).
### Reason for Changes
Generally the initial approach for implementing labels is leveraging as specific ARIA roles as exist to directly represent the element. This PR is completing that work for all of core Blockly's built-in fields, and laying some of the groundwork for generalizing this support for custom fields.
Having specific roles does potentially introduce inconsistencies across screen readers (though should improve consistency across sites for a single screen reader), and expectations for behaviors (like shortcuts) that may need to be ignored or only partially supported (#9313 is discussing this).
### Test Coverage
Only manual testing has been completed since this is experimental work.
Video demonstrating most of the changes:
[Screen recording 2025-10-01 4.05.35 PM.webm](https://github.com/user-attachments/assets/c7961caa-eae0-4585-8fd9-87d7cbe65988)
### Documentation
N/A -- Experimental work.
### Additional Information
This has only been tested on ChromeVox.
## The basics
- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)
## The details
### Resolves
Fixes part of #8207
Fixes part of #3370
### Proposed Changes
This introduces initial broad ARIA integration in order to enable at least basic screen reader support when using keyboard navigation.
Largely this involves introducing ARIA roles and labels in a bunch of places, sometimes done in a way to override normal built-in behaviors of the accessibility node tree in order to get a richer first-class output for Blockly (such as for blocks and workspaces).
### Reason for Changes
ARIA is the fundamental basis for configuring how focusable nodes in Blockly are represented to the user when using a screen reader. As such, all focusable nodes requires labels and roles in order to correctly communicate their contexts.
The specific approach taken in this PR is to simply add labels and roles to all nodes where obvious with some extra work done for `WorkspaceSvg` and `BlockSvg` in order to represent blocks as a tree (since that seems to be the best fitting ARIA role per those available: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles). The custom work specifically for blocks includes:
- Overriding the role description to be 'block' rather than 'tree item' (which is the default).
- Overriding the position, level, and number of sibling counts since those are normally determined based on the DOM tree and blocks are not laid out in the tree the same way they are visually or logically (so these computations were incorrect). This is also the reason for a bunch of extra computation logic being introduced.
One note on some of the labels being nonsensical (e.g. 'DoNotOverride?'): this was done intentionally to try and ensure _all_ focusable nodes (that can be focused) have labels, even when the specifics of what that label should be aren't yet clear. More components had these temporary labels until testing revealed how exactly they would behave from a screen reader perspective (at which point their roles and labels were updated as needed). The temporary labels act as an indicator when navigating through the UI, and some of the nodes can't easily be reached (for reasons) and thus may never actually need a label. More work is needed in understanding both what components need labels and what those labels should be, but that will be done beyond this PR.
### Test Coverage
No tests are added to this as it's experimental and not a final implementation.
The keyboard navigation tests are failing due to a visibility expansion of `connectionCandidate` in `BlockDragStrategy`. There's no way to avoid this breakage, unfortunately. Instead, this PR will be merged and then https://github.com/google/blockly-keyboard-experimentation/pull/684 will be finalized and merged to fix it. There's some additional work that will happen both in that branch and in a later PR in core Blockly to integrate the two experimentation branches as part of #9283 so that CI passes correctly for both branches.
### Documentation
No documentation is needed at this time.
### Additional Information
This work is experimental and is meant to serve two purposes:
- Provide a foundation for testing and iterating the core screen reader experience in Blockly.
- Provide a reference point for designing a long-term solution that accounts for all requirements collected during user testing.
This code should never be merged into `develop` as it stands. Instead, it will be redesigned with maintainability, testing, and correctness in mind at a future date (see https://github.com/google/blockly-keyboard-experimentation/discussions/673).
## The basics
- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)
## The details
### Resolves
Fixes https://github.com/google/blockly-keyboard-experimentation/issues/578
Fixes part of #8915
### Proposed Changes
Ensures fields update focus to the next field when tabbing between field editors. The old behavior can be observed in [#578](https://github.com/google/blockly-keyboard-experimentation/issues/578) and the new behavior can be observed here:
[Screen recording 2025-06-25 1.39.28 PM.webm](https://github.com/user-attachments/assets/e00fcb55-5c20-4d5c-81a8-be9049cc0d7e)
### Reason for Changes
Having focus reset back to the original field editor that was opened is an unexpected experience for users. This approach is better.
Note that there are some separate changes added here, as well:
- Connections and fields now check if their block IDs contain their indicator prefixes since this will all-but-guarantee focus breaks for those nodes. This is an excellent example of why #9171 is needed.
- Some minor naming updates for `FieldInput`: it was incorrectly implying that key events are sent for changes to the `input` element used by the field editor, but they are actually `InputEvent`s per https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event.
### Test Coverage
New tests were added for field editing in general (since this seems to be missing), including tabbing support to ensure the fixes originally introduced in #9049.
One new test has been added specifically for verifying that focus updates with tabbing. This has been verified to fail with the fix removed (as have all tabbing tests with the tabbing code from #9049 removed).
Some specific notes for the test changes:
- There's a slight test dependency inversion happening here. `FieldInput` is being tested with a specific `FieldNumber` class via real block loading. This isn't ideal, but it seems fine given the circumstances (otherwise a lot of extra setup would be necessary for the tests).
- The workspace actually needs to be made visible during tests in order for focus to work correctly (though it's reset at the end of each test, but this may cause some flickering while the tests are running).
- It's the case that a bunch of tests were actually setting up blocks incorrectly (i.e. not defining a must-have `id` property which caused some issues with the new field and connection ID validation checks). These tests have been corrected, but it's worth noting that the blocks are likely still technically wrong since they are not conforming to their TypeScript contracts.
- Similar to the previous point, one test was incorrectly setting the first ID to be returned by the ID generator as `undefined` since (presumably due to a copy-and-paste error when the test was introduced) it was referencing a `TEST_BLOCK_ID` property that hadn't been defined for that test suite. This has been corrected as, without it, there are failures due to the new validation checks.
- For the connection database checks, a new ID is generated instead of fixing the block ID to ensure that it's always unique even if called multiple times (otherwise a block ID would need to be piped through from the calling tests, or an invalid situation would need to be introduced where multiple blocks shared an ID; the former seemed unnecessary and the latter seemed nonideal).
- There are distinct Geras/Zelos tests to validate the case where a full-block field should have its parent block, rather than the field itself, focused on tabbing. See this conversation for more context: https://github.com/google/blockly/pull/9173#discussion_r2172921455.
### Documentation
No documentation changes should be needed here.
### Additional Information
Nothing to add.
## The basics
- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)
## The details
### Resolves
Needed for fixing https://github.com/google/blockly-samples/issues/2514 and https://github.com/google/blockly-samples/issues/2515.
### Proposed Changes
Update `FieldInput` along with drop-down and widget divs to support disabling the automatic ephemeral focus functionality.
### Reason for Changes
As mentioned in https://github.com/google/blockly-samples/issues/2514#issuecomment-2881539117 both https://github.com/google/blockly-samples/issues/2514 and https://github.com/google/blockly-samples/issues/2515 were caused by the custom fields leading to both drop-down and widget divs simultaneously taking ephemeral focus (and that's not currently allowed by `FocusManager`). This change updates both widget and drop-down divs with _optional_ parameters to conditionally disable automatic ephemeral focus so that `FieldInput` can, in turn, be customized with disabling automatic ephemeral focus for its inline editor. Being able to disable ephemeral focus for `FieldInput` allows the custom fields' own drop-down divs to take and manage ephemeral focus, instead, avoiding the duplicate scenario that led to the runtime failure.
Note that the drop-down div change in this PR is completely optional, but it's added for consistency and to avoid future scenarios of breakage when trying to use both divs together (as a fix is required in Core without monkeypatching).
It's worth noting that there could be a possibility for a more 'proper' fix in `FocusManager` by allowing multiple calls to `takeEphemeralFocus`, but it's unclear exactly how to solve this consistently (which is why it results in a hard failure today). The main issue is that the current focused node can change from underneath the manager (due to DOM focus changes), and the current focused element may also change. It's not clear if the first, last, or some other call to `takeEphemeralFocus` should take precedent or which node to return focus once ephemeral focus ends (in cases with multiple subsequent calls).
### Test Coverage
No new tests were added, though common field cases were tested manually in core's simple playground and in the plugin-specific playgrounds (per the original regressions). The keyboard navigation plugin test environment was also verified to ensure that this didn't alter any existing behavior (it should be a no-op except for the two custom field plugins).
Automated tests would be nice to add for all three classes, perhaps as part of #8915.
### Documentation
The code documentation changes here should be sufficient.
### Additional Information
These changes are being done directly to ease solving the above samples issues. See https://github.com/google/blockly-samples/pull/2521 for the follow-up fixes to samples.
* refactor: Backport LineCursor to core.
* fix: Fix instantiation of LineCursor.
* fix: Fix tests.
* chore: Assauge the linter.
* chore: Fix some typos.
* feat: Make padding configurable for scrollBoundsIntoView.
* chore: Merge in the latest changes from keyboard-experimentation.
* refactor: Clarify name and docs for findSiblingOrParentSibling().
* fix: Improve scrollBoundsIntoView() behavior.
* fix: Export CursorOptions.
* refactor: Further clarify second parameter of setCurNode().
* fix: Revert change that could prevent scrolling bounds into view.
* refactor(events): Use "export ... from" where applicable
* refactor(events): Introduce EventType enum
Introduce an enum for the event .type values. We can't actually
use it as the type of the .type property on Abstract events,
because we want to allow developers to add their own custom
event types inheriting from this type, but at least this way we
can be reasonably sure that all of our own event subclasses have
distinct .type values—plus consistent use of enum syntax
(EventType.TYPE_NAME) is probably good for readability overall.
Put it in a separate module from the rest of events/utils.ts
because it would be helpful if event utils could use
event instanceof SomeEventType
for type narrowing but but at the moment most events are in
modules that depend on events/utils.ts for their .type
constant, and although circular ESM dependencies should work
in principle there are various restrictions and this
particular circularity causes issues at the moment.
A few of the event classes also depend on utils.ts for fire()
or other functions, which will be harder to deal with, but at
least this commit is win in terms of reducing the complexity
of our dependencies, making most of the Abstract event subclass
module dependent on type.ts, which has no imports, rather than
on utils.ts which has multiple imports.
* chore(deps): Add pretter-plugin-organize-imports
* chore: Remove insignificant blank lines in import sections
Since prettier-plugin-organize-imports sorts imports within
sections separated by blank lines, but preserves the section
divisions, remove any blank lines that are not dividing imports
into meaningful sections.
Do not remove blank lines separating side-effect-only imports
from main imports.
* chore: Remove unneded eslint-disable directives
* chore: Organise imports
* fix: prevent block change event firing if editing is cancelled
* refactor: formatted the code to specifications for commit 251c796d
ran npm run format
* chore: delete dead code
* chore: moves location updating into the block
* chore: change dragging to use update component locations
* fix: field widgets not being moved when blocks are editted
* chore: remove unnecessary resizeEditor_ calls
* chore: format
* chore: fix build
* fix: tests
* chore: PR comments
* chore: format
On the desktop, widgetDispose_ will call onFinishEditing_ on close.
This was missing in the mobile counterpart, so any cleanups in onFinishEditing_ would not be called.
Also update the message string comment while we are touching this code.
* fix(build): Restore erroneously-deleted filter function
This was deleted in PR #7406 as it was mainly being used to
filter core/ vs. test/mocha/ deps into separate deps files -
but it turns out also to be used for filtering error
messages too. Oops.
* refactor(tests): Migrate advanced compilation test to ES Modules
* refactor(build): Migrate main.js to TypeScript
This turns out to be pretty straight forward, even if it would
cause crashing if one actually tried to import this module
instead of just feeding it to Closure Compiler.
* chore(build): Remove goog.declareModuleId calls
Replace goog.declareModuleId calls with a comment recording the
former module ID for posterity (or at least until we decide
how to reformat the renamings file.
* chore(tests): Delete closure/goog/*
For the moment we still need something to serve as base.js for
the benefit of closure-make-deps, so we keep a vestigial
base.js around, containing only the @provideGoog declaration.
* refactor(build): Remove vestigial base.js
By changing slightly the command line arguments to
closure-make-deps and closure-calculate-chunks the need to have
any base.js is eliminated.
* chore: Typo fix for PR #7415
* Copy core/events/events_block_change.ts to core/events/events_block_field_intermediate_change.ts
* New intermediate event type for field edits.
* Addressing PR feedback.
* Ran npm run format.
* Fixed procedure mutator responding to param edits.
* Intermediate events now inherit from BlockBase.
* Addressing feedback on PR.
* chore: format
We introduced the SKIP_SETUP sentinel value when converting
Field and its subclasses to ES6 class syntax, because super
must be called before any other code in a subclass
constructor, breaking the previous mechanism where subclasses
would set some properties before calling their superclass
constructor.
SKIP_SETUP was a singleton value of class Sentinel.
Recently, in PR #6639 @btw17 introduced the isSentinel type
predicate to improve the typing of Field. Unfortunately, there
were some aspects of this change that were not very elegant:
- isSentinel was declared as a static method on Field (rather
than on Sentinel itself).
- It only checks against the specific value SKIP_SETUP,
rather than checking if the argument is instanceof Sentinel
(though perhaps this is for efficiency?)
Additionally - as a result of the migration from ES6 to TS, and
predating PR #6639 - The signature for the Field constructor's
first argument was typed T|Sentinel, with subclass constructors
generally being <some type(s)>|Sentinel.
This creates a small problem when attempting to port Fields from
core to plugins, because Sentinel is not reexported by
core/utils.ts (and therefore not from core/blockly.ts either).
The latter problem could be solved simply by reexporting Sentinel,
or by having plugin constructors not accept SKIP_SETUP (though
this potentially makes them more difficult to subclass), but
neither is particularly desirable.
Instead, this PR proposes that we:
- Make Field.SKIP_SETUP a (unique) Symbol.
- Change the value argument to the Field constructor to accept
T|typeof Field.SKIP_SETUP - where typeof Field.SKIP_SETUP is
(like a literal type) a type that accepts just the single
value SKIP_SETUP.
- Remove the Sentinel class and core/utils/sentinel.ts.
Not treating this as a breaking change:
- Removes Field.isSentinel - though this addition has not yet
been published, so it can only break our own as-yet-unreleased
code in samples.
- Changes the type of Field.SKIP_SETUP and the first argument
of the Field constructor from Sentinel to typeof SKIP_SETUP
(a unique Symbol) - but given that Sentinel has never been
exported this should not break any actual external code.
* Reduce usage of obsolete .keyCode property.
* Rename private properties/methods which violate eslint rules.
* Use arrays of bound events rather than individual properties.
* Improve typing info.
* Also fix O(n^2) recursive performance issue in theme's getComponentStyle function.
* And replace String(...) with '${...}' (smaller, faster).
* .toString() is considered harmful.
* chore: replace `AnyDuringMigration` for field value functions
* chore: removed unnecessary `KeyboardEvent` comments and `AnyDuringMigration` casts
* chore: cleaned up `doValueUpdate_` and `doClassValidation_` in `Field` subclasses
* fix: updated `FieldValidator` to allow returning `undefined` and restructured `setValue`
* fix: implemented initial `core/field_checkbox.ts` feedback
* fix: updated `Field` to accept `U` and `undefined` and reverted subclass constructor handling of the input value
* fix: reverted `getVars` to returning `string[]` and added related comment
* chore: removed unnecessary comment
* fix: updated `processValidation_` to no longer allow returning `undefined`
* fix: removed `Un` type alias for `undefined`
* chore: removed unnecessary string cast in `core/field_colour.ts`
* fix: updated `doClassValidation_` not to expect `null` since it will never come up in `setValue`
* fix: updated `doClassValidation_` to only allow `undefined` when the new value exists
* Updated `FieldValidator` type to expect `newValue` to exist
* cleanup: updated `picker` from type `Element` to type `HTMLElement`
* fix: updated `doValueInvalid_` type info in `core/field_input.ts` to handle `string` and `undefined`
* fix: reverted `getValue` in `core/field_checkbox.ts` to previous logic
* fix: updated the `Field` constructor to allow `value` to be optional
* chore: consolidated `Validation` with `FieldValidator` and `doClassValidation_`
* fix: reverted generic param `U` while handling diverging user input is being discussed
* fix: updated `doClassValidation_` return comment to work for TSDoc
* fix: misc keyboard event function tweaks
* chore: add validator and field value types
* chore/clean up unused default and unnecessary field type setting
* chore: removed IRegisterableField and updated various Field instances
* fix: remove unused field image validator function
* chore: added pass-through constructor to field_textinput