mirror of
https://github.com/google/blockly.git
synced 2026-01-04 15:40:08 +01:00
feat: Expand single field block labeling (experimental) (#9484)
## The basics - [ ] ~I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)~ (I can't really due to the nature of this change--I have to rely on tests and external testing) ## The details ### Resolves Fixes part of #9456 (remainder will be in a change in the keyboard navigation plugin) ### Proposed Changes Introduce and use new `Block` function for retrieving a configurably constrained singleton field for a given block. The constraints allow for some level of configuring (such as whether to isolate to only full or editable blocks). The existing simple reporter function has been retrofitted to use this new function, instead. ### Reason for Changes This expanded support fixes the underlying use case. Separately, this change reveals two noteworthy details: 1. There's inconsistency in the codebase as to when the singleton field needs to be editable, a full-block field, both, and neither. It would be ideal to make this consistent. Interestingly, the documentation for `isSimpleReporter` seems to have been wrong since it wasn't actually fulfilling its contract of returning an editable field (this has been retained for callsites except where the check was already happening). 2. There's a possible recursion case now possible between `getSingletonFullBlockField` and `isFullBlockField` due to `FieldInput`'s `isFullBlockField` depending on `isSimpleReporter`. Ideally this would be changed in the future to avoid that potential recursion risk (possibly as part of #9307). ### Test Coverage No new automated tests are needed for this experimental work. Manual testing mainly comprised of cursory navigation and readout checks for single-field blocks to make sure nothing breaks. More thorough testing is difficult in core since the specific situation of multiple fields don't have a corresponding block to use in the playground to verify. Automated tests are also being heavily relied on for correctness since all of the nuance behind the simple reporter cases would require a deeper testing pass. ### Documentation No new documentation needed for this experimental work. ### Additional Information None.
This commit is contained in:
@@ -962,16 +962,43 @@ export class Block {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns True if this block is a value block with a single editable field.
|
||||
* @returns True if this block is a value block with a full block field.
|
||||
* @param mustBeFullBlock Whether the evaluated field must be 'full-block'.
|
||||
* @param mustBeEditable Whether the evaluated field must be editable.
|
||||
* @internal
|
||||
*/
|
||||
isSimpleReporter(): boolean {
|
||||
if (!this.outputConnection) return false;
|
||||
isSimpleReporter(
|
||||
mustBeFullBlock: boolean = false,
|
||||
mustBeEditable: boolean = false,
|
||||
): boolean {
|
||||
return (
|
||||
this.getSingletonFullBlockField(mustBeFullBlock, mustBeEditable) !== null
|
||||
);
|
||||
}
|
||||
|
||||
for (const input of this.inputList) {
|
||||
if (input.connection || input.fieldRow.length > 1) return false;
|
||||
}
|
||||
return true;
|
||||
/**
|
||||
* Determines and returns the only field of this block, or null if there isn't
|
||||
* one and this block can't be considered a simple reporter. Null will also be
|
||||
* returned if the singleton block doesn't match additional criteria, if set,
|
||||
* such as being full-block or editable.
|
||||
*
|
||||
* @param mustBeFullBlock Whether the returned field must be 'full-block'.
|
||||
* @param mustBeEditable Whether the returned field must be editable.
|
||||
* @returns The only full-block, maybe editable field of this block, or null.
|
||||
* @internal
|
||||
*/
|
||||
getSingletonFullBlockField(
|
||||
mustBeFullBlock: boolean,
|
||||
mustBeEditable: boolean,
|
||||
): Field<any> | null {
|
||||
if (!this.outputConnection) return null;
|
||||
for (const input of this.inputList) if (input.connection) return null;
|
||||
const matchingFields = Array.from(this.getFields()).filter((field) => {
|
||||
if (mustBeFullBlock && !field.isFullBlockField()) return false;
|
||||
if (mustBeEditable && !field.isCurrentlyEditable()) return false;
|
||||
return true;
|
||||
});
|
||||
return matchingFields.length === 1 ? matchingFields[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -233,10 +233,7 @@ export class BlockSvg
|
||||
* @internal
|
||||
*/
|
||||
recomputeAriaLabel() {
|
||||
if (this.isSimpleReporter()) {
|
||||
const field = Array.from(this.getFields())[0];
|
||||
if (field.isFullBlockField() && field.isCurrentlyEditable()) return;
|
||||
}
|
||||
if (this.isSimpleReporter(true, true)) return;
|
||||
|
||||
aria.setState(
|
||||
this.getFocusableElement(),
|
||||
@@ -1964,13 +1961,8 @@ export class BlockSvg
|
||||
|
||||
/** See IFocusableNode.getFocusableElement. */
|
||||
getFocusableElement(): HTMLElement | SVGElement {
|
||||
if (this.isSimpleReporter()) {
|
||||
const field = Array.from(this.getFields())[0];
|
||||
if (field && field.isFullBlockField() && field.isCurrentlyEditable()) {
|
||||
return field.getFocusableElement();
|
||||
}
|
||||
}
|
||||
return this.pathObject.svgPath;
|
||||
const singletonField = this.getSingletonFullBlockField(true, true);
|
||||
return singletonField?.getFocusableElement() ?? this.pathObject.svgPath;
|
||||
}
|
||||
|
||||
/** See IFocusableNode.getFocusableTree. */
|
||||
|
||||
@@ -26,12 +26,6 @@ import {Coordinate} from './utils/coordinate.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
function isFullBlockField(block?: BlockSvg) {
|
||||
if (!block || !block.isSimpleReporter()) return false;
|
||||
const firstField = block.getFields().next().value;
|
||||
return firstField?.isFullBlockField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to undo previous action.
|
||||
*/
|
||||
@@ -377,7 +371,7 @@ export function registerComment() {
|
||||
// Either block already has a comment so let us remove it,
|
||||
// or the block isn't just one full-block field block, which
|
||||
// shouldn't be allowed to have comments as there's no way to read them.
|
||||
(block.hasIcon(CommentIcon.TYPE) || !isFullBlockField(block))
|
||||
(block.hasIcon(CommentIcon.TYPE) || !block.isSimpleReporter(true))
|
||||
) {
|
||||
return 'enabled';
|
||||
}
|
||||
|
||||
@@ -330,12 +330,9 @@ export abstract class Field<T = any>
|
||||
this.initModel();
|
||||
this.applyColour();
|
||||
|
||||
const id =
|
||||
this.isFullBlockField() &&
|
||||
this.isCurrentlyEditable() &&
|
||||
this.sourceBlock_?.isSimpleReporter()
|
||||
? idGenerator.getNextUniqueId()
|
||||
: `${this.sourceBlock_?.id}_field_${idGenerator.getNextUniqueId()}`;
|
||||
const id = this.sourceBlock_?.isSimpleReporter(true, true)
|
||||
? idGenerator.getNextUniqueId()
|
||||
: `${this.sourceBlock_?.id}_field_${idGenerator.getNextUniqueId()}`;
|
||||
this.fieldGroup_.setAttribute('id', id);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,10 +65,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
||||
current.canBeFocused() &&
|
||||
current.isVisible() &&
|
||||
(current.isClickable() || current.isCurrentlyEditable()) &&
|
||||
!(
|
||||
current.getSourceBlock()?.isSimpleReporter() &&
|
||||
current.isFullBlockField()
|
||||
) &&
|
||||
!current.getSourceBlock()?.isSimpleReporter(true, true) &&
|
||||
current.getParentInput().isVisible()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user