mirror of
https://github.com/google/blockly.git
synced 2026-05-01 17:40:11 +02:00
feat: allow setting custom role description for blocks (#9777)
* feat: allow setting custom role description for blocks * fix: update test
This commit is contained in:
@@ -53,6 +53,7 @@ import {Coordinate} from './utils/coordinate.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
import * as idGenerator from './utils/idgenerator.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
import {replaceMessageReferences} from './utils/parsing.js';
|
||||
import {Size} from './utils/size.js';
|
||||
import type {Workspace} from './workspace.js';
|
||||
|
||||
@@ -243,6 +244,9 @@ export class Block {
|
||||
inputsInlineDefault?: boolean;
|
||||
workspace: Workspace;
|
||||
|
||||
/** A custom provider for generating the aria role description for this block. */
|
||||
private ariaRoleDescriptionProvider: string | (() => string) | undefined;
|
||||
|
||||
/**
|
||||
* @param workspace The block's workspace.
|
||||
* @param prototypeName Name of the language object containing type-specific
|
||||
@@ -1548,6 +1552,32 @@ export class Block {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom aria role description provider for this block. If not set,
|
||||
* uses a default provider based on the block's properties (e.g. whether it has
|
||||
* inputs, outputs, etc.).
|
||||
*
|
||||
* @param description The description or function to provide the description.
|
||||
* If a string, we'll replace message references in the string, e.g.
|
||||
* `%{BKY_CUSTOM_MESSAGE}` will be replaced with the value of
|
||||
* `Blockly.Msg['CUSTOM_MESSAGE']`.}'
|
||||
*/
|
||||
setAriaRoleDescriptionProvider(description: string | (() => string)) {
|
||||
this.ariaRoleDescriptionProvider = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The custom string to use as the role description for this block,
|
||||
* or undefined if no custom description is set.
|
||||
*/
|
||||
getAriaRoleDescription(): string | undefined {
|
||||
if (!this.ariaRoleDescriptionProvider) return undefined;
|
||||
if (typeof this.ariaRoleDescriptionProvider === 'function') {
|
||||
return this.ariaRoleDescriptionProvider();
|
||||
}
|
||||
return replaceMessageReferences(this.ariaRoleDescriptionProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a human-readable text representation of this block and any children.
|
||||
*
|
||||
@@ -1802,6 +1832,11 @@ export class Block {
|
||||
const localizedValue = parsing.replaceMessageReferences(rawValue);
|
||||
this.setHelpUrl(localizedValue);
|
||||
}
|
||||
|
||||
if (json['ariaRoleDescription'] !== undefined) {
|
||||
this.setAriaRoleDescriptionProvider(json['ariaRoleDescription']);
|
||||
}
|
||||
|
||||
if (typeof json['extensions'] === 'string') {
|
||||
console.warn(
|
||||
warningPrefix +
|
||||
|
||||
@@ -87,13 +87,17 @@ export function configureAriaRole(block: BlockSvg) {
|
||||
setRole(focusableElement, Role.FIGURE);
|
||||
}
|
||||
|
||||
let roleDescription = Msg['BLOCK_LABEL_STATEMENT'];
|
||||
if (block.statementInputCount) {
|
||||
let roleDescription;
|
||||
const customDescription = block.getAriaRoleDescription();
|
||||
if (customDescription) {
|
||||
roleDescription = customDescription;
|
||||
} else if (block.statementInputCount) {
|
||||
roleDescription = Msg['BLOCK_LABEL_CONTAINER'];
|
||||
} else if (block.outputConnection) {
|
||||
roleDescription = Msg['BLOCK_LABEL_VALUE'];
|
||||
} else {
|
||||
roleDescription = Msg['BLOCK_LABEL_STATEMENT'];
|
||||
}
|
||||
|
||||
setState(focusableElement, State.ROLEDESCRIPTION, roleDescription);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface JsonBlockDefinition {
|
||||
inputsInline?: boolean;
|
||||
tooltip?: string;
|
||||
helpUrl?: string;
|
||||
ariaRoleDescription?: string;
|
||||
extensions?: string[];
|
||||
mutator?: string;
|
||||
enableContextMenu?: boolean;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"author": "Ellen Spertus <ellen.spertus@gmail.com>",
|
||||
"lastupdated": "2026-04-29 12:42:30.774691",
|
||||
"lastupdated": "2026-04-29 16:09:43.926632",
|
||||
"locale": "en",
|
||||
"messagedocumentation" : "qqq"
|
||||
},
|
||||
@@ -474,7 +474,7 @@
|
||||
"BLOCK_LABEL_REPLACEABLE": "replaceable",
|
||||
"BLOCK_LABEL_HAS_INPUT": "has input",
|
||||
"BLOCK_LABEL_HAS_INPUTS": "has inputs",
|
||||
"BLOCK_LABEL_STATEMENT": "statement",
|
||||
"BLOCK_LABEL_STATEMENT": "command",
|
||||
"BLOCK_LABEL_CONTAINER": "container",
|
||||
"BLOCK_LABEL_VALUE": "value",
|
||||
"BLOCK_LABEL_STACK_BLOCKS": "%1 stack blocks",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Ajeje Brazorf",
|
||||
"Amire80",
|
||||
@@ -482,7 +482,7 @@
|
||||
"BLOCK_LABEL_REPLACEABLE": "Part of an accessibility label for a block that indicates that it is replaceable, i.e. that it is a shadow block.",
|
||||
"BLOCK_LABEL_HAS_INPUT": "Part of an accessibility label for a block that indicates that it has a single input.",
|
||||
"BLOCK_LABEL_HAS_INPUTS": "Part of an accessibility label for a block that indicates that it has more than one input.",
|
||||
"BLOCK_LABEL_STATEMENT": "Part of an accessibility label for a block that indicates that it is a statement block, i.e. that it has a next or previous connection.",
|
||||
"BLOCK_LABEL_STATEMENT": "Part of an accessibility label for a block that indicates that it is a statement block, i.e. that it has a next or previous connection. 'command' here is used in the sense of a computer command, or a command block in Scratch.",
|
||||
"BLOCK_LABEL_CONTAINER": "Part of an accessibility label for a block that indicates that it is a container block, i.e. that it has one or more statement inputs.",
|
||||
"BLOCK_LABEL_VALUE": "Part of an accessibility label for a block that indicates that it is a value block, i.e. that it has an output connection.",
|
||||
"BLOCK_LABEL_STACK_BLOCKS": "Accessibility label for a block that indicates it is a stack of two or more blocks.",
|
||||
|
||||
@@ -1880,7 +1880,9 @@ Blockly.Msg.BLOCK_LABEL_HAS_INPUTS = 'has inputs';
|
||||
/** @type {string} */
|
||||
/// Part of an accessibility label for a block that indicates that it is
|
||||
/// a statement block, i.e. that it has a next or previous connection.
|
||||
Blockly.Msg.BLOCK_LABEL_STATEMENT = 'statement';
|
||||
/// "command" here is used in the sense of a computer command, or a
|
||||
/// command block in Scratch.
|
||||
Blockly.Msg.BLOCK_LABEL_STATEMENT = 'command';
|
||||
/** @type {string} */
|
||||
/// Part of an accessibility label for a block that indicates that it is
|
||||
/// a container block, i.e. that it has one or more statement inputs.
|
||||
|
||||
@@ -295,7 +295,7 @@ suite('ARIA', function () {
|
||||
block.getFocusableElement(),
|
||||
Blockly.utils.aria.State.ROLEDESCRIPTION,
|
||||
);
|
||||
assert.equal(roleDescription, 'statement');
|
||||
assert.equal(roleDescription, 'command');
|
||||
});
|
||||
|
||||
test('Value blocks have correct role description', function () {
|
||||
|
||||
@@ -774,4 +774,36 @@ suite('Block JSON initialization', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
suite('blockFromJson', function () {
|
||||
test('Custom aria role description', function () {
|
||||
const testBlockDefinition = {
|
||||
'type': 'test_block',
|
||||
'ariaRoleDescription': 'Custom aria description',
|
||||
};
|
||||
Blockly.common.defineBlocksWithJsonArray([testBlockDefinition]);
|
||||
const block = this.workspace.newBlock('test_block');
|
||||
assert.equal(
|
||||
block.getAriaRoleDescription(),
|
||||
'Custom aria description',
|
||||
'Expected getAriaRoleDescription to return the custom description.',
|
||||
);
|
||||
delete Blockly.Blocks['test_block'];
|
||||
});
|
||||
test('Custom aria role description with message reference', function () {
|
||||
const testBlockDefinition = {
|
||||
'type': 'test_block',
|
||||
'ariaRoleDescription': '%{BKY_CUSTOM_ROLE_DESCRIPTION}',
|
||||
};
|
||||
Blockly.Msg['CUSTOM_ROLE_DESCRIPTION'] = 'Custom aria description';
|
||||
Blockly.common.defineBlocksWithJsonArray([testBlockDefinition]);
|
||||
const block = this.workspace.newBlock('test_block');
|
||||
assert.equal(
|
||||
block.getAriaRoleDescription(),
|
||||
'Custom aria description',
|
||||
'Expected getAriaRoleDescription to return the custom description.',
|
||||
);
|
||||
delete Blockly.Blocks['test_block'];
|
||||
delete Blockly.Msg['CUSTOM_ROLE_DESCRIPTION'];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user