Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions blocks/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'BOOL',
'ariaTypeName': 'Boolean',
'options': [
['%{BKY_LOGIC_BOOLEAN_TRUE}', 'TRUE'],
['%{BKY_LOGIC_BOOLEAN_FALSE}', 'FALSE'],
Expand Down Expand Up @@ -117,13 +118,14 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'OP',
'ariaTypeName': 'Operator',
'options': [
['=', 'EQ'],
['\u2260', 'NEQ'],
['\u200F<', 'LT'],
['\u200F\u2264', 'LTE'],
['\u200F>', 'GT'],
['\u200F\u2265', 'GTE'],
['=', 'EQ', 'Equals'],
['\u2260', 'NEQ', 'Does not equal'],
['\u200F<', 'LT', 'Less than'],
['\u200F\u2264', 'LTE', 'Less than or equal to'],
['\u200F>', 'GT', 'Greater than'],
['\u200F\u2265', 'GTE', 'Greater than or equal to'],
],
},
{
Expand All @@ -150,9 +152,10 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'OP',
'ariaTypeName': 'Boolean operator',
'options': [
['%{BKY_LOGIC_OPERATION_AND}', 'AND'],
['%{BKY_LOGIC_OPERATION_OR}', 'OR'],
['%{BKY_LOGIC_OPERATION_AND}', 'AND', 'And'],
['%{BKY_LOGIC_OPERATION_OR}', 'OR', 'Or'],
],
},
{
Expand Down
18 changes: 14 additions & 4 deletions blocks/loops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'MODE',
'ariaTypeName': 'Repeat type',
'options': [
['%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}', 'WHILE'],
['%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}', 'UNTIL'],
['%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}', 'WHILE', 'While'],
['%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}', 'UNTIL', 'Until'],
],
},
{
Expand Down Expand Up @@ -199,9 +200,18 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'FLOW',
'ariaTypeName': 'Continue type',
'options': [
['%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}', 'BREAK'],
['%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}', 'CONTINUE'],
[
'%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}',
'BREAK',
'Break loop',
],
[
'%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}',
'CONTINUE',
'Continue loop',
],
],
},
],
Expand Down
88 changes: 49 additions & 39 deletions blocks/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([
'type': 'field_number',
'name': 'NUM',
'value': 0,
'ariaName': 'Number',
'ariaTypeName': 'Number',
},
],
'output': 'Number',
Expand All @@ -55,7 +55,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'OP',
'ariaName': 'Arithmetic operation',
'ariaTypeName': 'Operator',
'options': [
['%{BKY_MATH_ADDITION_SYMBOL}', 'ADD', 'Plus'],
['%{BKY_MATH_SUBTRACTION_SYMBOL}', 'MINUS', 'Minus'],
Expand Down Expand Up @@ -85,14 +85,15 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'OP',
'ariaTypeName': 'Function',
'options': [
['%{BKY_MATH_SINGLE_OP_ROOT}', 'ROOT'],
['%{BKY_MATH_SINGLE_OP_ABSOLUTE}', 'ABS'],
['-', 'NEG'],
['ln', 'LN'],
['log10', 'LOG10'],
['e^', 'EXP'],
['10^', 'POW10'],
['%{BKY_MATH_SINGLE_OP_ROOT}', 'ROOT', 'Square root of '],
['%{BKY_MATH_SINGLE_OP_ABSOLUTE}', 'ABS', 'Absolute value of'],
['-', 'NEG', 'Negative value of'],
['ln', 'LN', 'Natural logarithm of'],
['log10', 'LOG10', 'Logarithm base 10 of'],
['e^', 'EXP', 'e to the power of'],
['10^', 'POW10', '10 to the power of'],
],
},
{
Expand All @@ -115,13 +116,14 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'OP',
'ariaTypeName': 'Trigonometry function',
'options': [
['%{BKY_MATH_TRIG_SIN}', 'SIN'],
['%{BKY_MATH_TRIG_COS}', 'COS'],
['%{BKY_MATH_TRIG_TAN}', 'TAN'],
['%{BKY_MATH_TRIG_ASIN}', 'ASIN'],
['%{BKY_MATH_TRIG_ACOS}', 'ACOS'],
['%{BKY_MATH_TRIG_ATAN}', 'ATAN'],
['%{BKY_MATH_TRIG_SIN}', 'SIN', 'Sine of'],
['%{BKY_MATH_TRIG_COS}', 'COS', 'Cosine of'],
['%{BKY_MATH_TRIG_TAN}', 'TAN', 'Tangent of'],
['%{BKY_MATH_TRIG_ASIN}', 'ASIN', 'Arc sine of'],
['%{BKY_MATH_TRIG_ACOS}', 'ACOS', 'Arc cosine of'],
['%{BKY_MATH_TRIG_ATAN}', 'ATAN', 'Arc tangent of'],
],
},
{
Expand All @@ -144,13 +146,14 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'CONSTANT',
'ariaTypeName': 'Math constant',
'options': [
['\u03c0', 'PI'],
['e', 'E'],
['\u03c6', 'GOLDEN_RATIO'],
['sqrt(2)', 'SQRT2'],
['sqrt(\u00bd)', 'SQRT1_2'],
['\u221e', 'INFINITY'],
['\u03c0', 'PI', 'Pi'],
['e', 'E', "Euler's number"],
['\u03c6', 'GOLDEN_RATIO', 'Golden ratio'],
['sqrt(2)', 'SQRT2', 'Square root of 2'],
['sqrt(\u00bd)', 'SQRT1_2', 'Square root of one half'],
['\u221e', 'INFINITY', 'Infinity'],
],
},
],
Expand All @@ -174,14 +177,15 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'PROPERTY',
'ariaTypeName': 'Number comparison',
'options': [
['%{BKY_MATH_IS_EVEN}', 'EVEN'],
['%{BKY_MATH_IS_ODD}', 'ODD'],
['%{BKY_MATH_IS_PRIME}', 'PRIME'],
['%{BKY_MATH_IS_WHOLE}', 'WHOLE'],
['%{BKY_MATH_IS_POSITIVE}', 'POSITIVE'],
['%{BKY_MATH_IS_NEGATIVE}', 'NEGATIVE'],
['%{BKY_MATH_IS_DIVISIBLE_BY}', 'DIVISIBLE_BY'],
['%{BKY_MATH_IS_EVEN}', 'EVEN', 'Is even'],
['%{BKY_MATH_IS_ODD}', 'ODD', 'Is odd'],
['%{BKY_MATH_IS_PRIME}', 'PRIME', 'Is prime'],
['%{BKY_MATH_IS_WHOLE}', 'WHOLE', 'Is whole number'],
['%{BKY_MATH_IS_POSITIVE}', 'POSITIVE', 'Is positive'],
['%{BKY_MATH_IS_NEGATIVE}', 'NEGATIVE', 'Is negative'],
['%{BKY_MATH_IS_DIVISIBLE_BY}', 'DIVISIBLE_BY', 'Is divisible by'],
],
},
],
Expand Down Expand Up @@ -223,10 +227,11 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'OP',
'ariaTypeName': 'Rounding operation',
'options': [
['%{BKY_MATH_ROUND_OPERATOR_ROUND}', 'ROUND'],
['%{BKY_MATH_ROUND_OPERATOR_ROUNDUP}', 'ROUNDUP'],
['%{BKY_MATH_ROUND_OPERATOR_ROUNDDOWN}', 'ROUNDDOWN'],
['%{BKY_MATH_ROUND_OPERATOR_ROUND}', 'ROUND', 'Round to nearest'],
['%{BKY_MATH_ROUND_OPERATOR_ROUNDUP}', 'ROUNDUP', 'Round up'],
['%{BKY_MATH_ROUND_OPERATOR_ROUNDDOWN}', 'ROUNDDOWN', 'Round down'],
],
},
{
Expand All @@ -250,15 +255,20 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'OP',
'ariaTypeName': 'List operation',
'options': [
['%{BKY_MATH_ONLIST_OPERATOR_SUM}', 'SUM'],
['%{BKY_MATH_ONLIST_OPERATOR_MIN}', 'MIN'],
['%{BKY_MATH_ONLIST_OPERATOR_MAX}', 'MAX'],
['%{BKY_MATH_ONLIST_OPERATOR_AVERAGE}', 'AVERAGE'],
['%{BKY_MATH_ONLIST_OPERATOR_MEDIAN}', 'MEDIAN'],
['%{BKY_MATH_ONLIST_OPERATOR_MODE}', 'MODE'],
['%{BKY_MATH_ONLIST_OPERATOR_STD_DEV}', 'STD_DEV'],
['%{BKY_MATH_ONLIST_OPERATOR_RANDOM}', 'RANDOM'],
['%{BKY_MATH_ONLIST_OPERATOR_SUM}', 'SUM', 'Sum'],
['%{BKY_MATH_ONLIST_OPERATOR_MIN}', 'MIN', 'Minimum'],
['%{BKY_MATH_ONLIST_OPERATOR_MAX}', 'MAX', 'Maximum'],
['%{BKY_MATH_ONLIST_OPERATOR_AVERAGE}', 'AVERAGE', 'Average'],
['%{BKY_MATH_ONLIST_OPERATOR_MEDIAN}', 'MEDIAN', 'Median'],
['%{BKY_MATH_ONLIST_OPERATOR_MODE}', 'MODE', 'Mode'],
[
'%{BKY_MATH_ONLIST_OPERATOR_STD_DEV}',
'STD_DEV',
'Standard deviation',
],
['%{BKY_MATH_ONLIST_OPERATOR_RANDOM}', 'RANDOM', 'Random value'],
],
},
{
Expand Down
16 changes: 9 additions & 7 deletions blocks/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'END',
'ariaTypeName': 'Search direction',
'options': [
['%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}', 'FIRST'],
['%{BKY_TEXT_INDEXOF_OPERATOR_LAST}', 'LAST'],
['%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}', 'FIRST', 'First'],
['%{BKY_TEXT_INDEXOF_OPERATOR_LAST}', 'LAST', 'Last'],
],
},
{
Expand All @@ -171,12 +172,13 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_dropdown',
'name': 'WHERE',
'ariaTypeName': 'Search operation',
'options': [
['%{BKY_TEXT_CHARAT_FROM_START}', 'FROM_START'],
['%{BKY_TEXT_CHARAT_FROM_END}', 'FROM_END'],
['%{BKY_TEXT_CHARAT_FIRST}', 'FIRST'],
['%{BKY_TEXT_CHARAT_LAST}', 'LAST'],
['%{BKY_TEXT_CHARAT_RANDOM}', 'RANDOM'],
['%{BKY_TEXT_CHARAT_FROM_START}', 'FROM_START', 'From string start'],
['%{BKY_TEXT_CHARAT_FROM_END}', 'FROM_END', 'From string end'],
['%{BKY_TEXT_CHARAT_FIRST}', 'FIRST', 'First character'],
['%{BKY_TEXT_CHARAT_LAST}', 'LAST', 'Last character'],
['%{BKY_TEXT_CHARAT_RANDOM}', 'RANDOM', 'Random character'],
],
},
],
Expand Down
8 changes: 4 additions & 4 deletions core/block_svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ export class BlockSvg
);
}

private computeAriaLabel(): string {
const {blockSummary, inputCount} = buildBlockSummary(this);
computeAriaLabel(verbose: boolean = false): string {
const {blockSummary, inputCount} = buildBlockSummary(this, verbose);
const inputSummary = inputCount
? ` ${inputCount} ${inputCount > 1 ? 'inputs' : 'input'}`
: '';
Expand Down Expand Up @@ -2051,7 +2051,7 @@ interface BlockSummary {
inputCount: number;
}

function buildBlockSummary(block: BlockSvg): BlockSummary {
function buildBlockSummary(block: BlockSvg, verbose: boolean): BlockSummary {
let inputCount = 0;
function recursiveInputSummary(
block: BlockSvg,
Expand All @@ -2066,7 +2066,7 @@ function buildBlockSummary(block: BlockSvg): BlockSummary {
if (field.EDITABLE && !field.isFullBlockField() && !isNestedInput) {
inputCount++;
}
return [field.getText() ?? field.getValue()];
return field.computeAriaLabel(verbose);
});
if (
input.isVisible() &&
Expand Down
49 changes: 46 additions & 3 deletions core/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,51 @@ export abstract class Field<T = any>
}
}

getAriaName(): string | null {
return this.config?.ariaName ?? null;
/**
* Gets a an ARIA-friendly label representation of this field's type.
*
* @returns An ARIA representation of the field's type or null if it is
* unspecified.
*/
getAriaTypeName(): string | null {
return this.config?.ariaTypeName ?? null;
}

/**
* Gets a an ARIA-friendly label representation of this field's value.
*
* @returns An ARIA representation of the field's value.
*/
abstract getAriaValue(): string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this not be abstract and have a default implementation that returned an empty string (or null)? I found myself going through and returning empty strings for quite a few custom fields in order to get up and running with this change, and having a default implementation would reduce the overhead.

Copy link
Collaborator Author

@BenHenning BenHenning Nov 20, 2025

Choose a reason for hiding this comment

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

I was on the fence of going with a default implementation but this gives strong incentive to. Within core Blockly we more or less need to explicitly decide what to provide here in all cases so I didn't have enough context to decide on a default value.

Could you maybe provide some context on why so many of your custom fields have an empty string? I'd expect that to be atypical in general since I expect that basically all fields have a non-default value (except maybe text input when nothing has yet been inputted).


/**
* Computes a descriptive ARIA label to represent this field with configurable
* verbosity.
*
* A 'verbose' label includes type information, if available, whereas a
* non-verbose label only contains the field's value.
*
* Note that this will always return the latest representation of the field's
* label which may differ from any previously set ARIA label for the field
* itself. Implementations are largely responsible for ensuring that the
* field's ARIA label is set correctly at relevant moments in the field's
* lifecycle (such as when its value changes).
*
* Finally, it is never guaranteed that implementations use the label returned
* by this method for their actual ARIA label. Some implementations may rely
* on other context to convey information like the field's value. Example:
* checkboxes represent their checked/non-checked status (i.e. value) through
* a separate ARIA property.
*
* @param verbose Whether to include the field's type information in the
* returned label, if available.
*/
computeAriaLabel(verbose: boolean = false): string {
const components: Array<string | null> = [this.getAriaValue()];
if (verbose) {
components.push(this.getAriaTypeName());
}
return components.filter((item) => item !== null).join(', ');
}

/**
Expand Down Expand Up @@ -1429,7 +1472,7 @@ export interface FieldConfig {
type: string;
name?: string;
tooltip?: string;
ariaName?: string;
ariaTypeName?: string;
}

/**
Expand Down
10 changes: 9 additions & 1 deletion core/field_checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,18 @@ export class FieldCheckbox extends Field<CheckboxBool> {
this.recomputeAria();
}

override getAriaValue(): string {
return this.value_ ? 'checked' : 'not checked';
}

private recomputeAria() {
const element = this.getFocusableElement();
aria.setRole(element, aria.Role.CHECKBOX);
aria.setState(element, aria.State.LABEL, this.getAriaName() ?? 'Checkbox');
aria.setState(
element,
aria.State.LABEL,
this.getAriaTypeName() ?? 'Checkbox',
);
aria.setState(element, aria.State.CHECKED, !!this.value_);
}

Expand Down
13 changes: 5 additions & 8 deletions core/field_dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ export class FieldDropdown extends Field<string> {
this.recomputeAria();
}

override getAriaValue(): string {
return this.computeLabelForOption(this.selectedOption);
}

protected recomputeAria() {
if (!this.fieldGroup_) return; // There's no element to set currently.
const element = this.getFocusableElement();
Expand All @@ -214,14 +218,7 @@ export class FieldDropdown extends Field<string> {
aria.clearState(element, aria.State.CONTROLS);
}

const label = [
this.computeLabelForOption(this.selectedOption),
this.getAriaName(),
]
.filter((item) => !!item)
.join(', ');

aria.setState(element, aria.State.LABEL, label);
aria.setState(element, aria.State.LABEL, super.computeAriaLabel(true));
}

/**
Expand Down
Loading
Loading