Skip to content
Merged
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
26 changes: 26 additions & 0 deletions packages/blockly/core/dragging/block_drag_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ export class BlockDragStrategy implements IDragStrategy {
/** Used to persist an event group when snapping is done async. */
private originalEventGroup = '';

/**
* Half of the connection of the last announced candidate, or null if that
* announcement was for the workspace. Undefined if nothing has been
* announced yet this drag.
*/
private lastAnnouncedLocal: RenderedConnection | null | undefined = undefined;
private lastAnnouncedNeighbour: RenderedConnection | null | undefined =
undefined;

/**
* Map from block IDs to reason(s) why it was disabled, used to restore
* disabled state post-drag.
Expand Down Expand Up @@ -277,6 +286,21 @@ export class BlockDragStrategy implements IDragStrategy {
announcementTemplate = Msg['ANNOUNCE_MOVE_WORKSPACE'];
announcement = announcementTemplate.replace('%1', blockLabel);
}

// Skip only when the connection target is unchanged, so identically
// worded moves to different inputs (e.g. empty else-if slots) still get read.
const local = this.connectionCandidate?.local ?? null;
const neighbour = this.connectionCandidate?.neighbour ?? null;
if (
this.lastAnnouncedLocal !== undefined &&
local === this.lastAnnouncedLocal &&
neighbour === this.lastAnnouncedNeighbour
) {
return;
}
this.lastAnnouncedLocal = local;
this.lastAnnouncedNeighbour = neighbour;

// Collapse whitespace from unused template substitutions.
aria.announceDynamicAriaState(announcement.replace(/\s+/g, ' '));
}
Expand Down Expand Up @@ -319,6 +343,8 @@ export class BlockDragStrategy implements IDragStrategy {
this.block.workspace.recordDragTargets();

this.dragging = true;
this.lastAnnouncedLocal = undefined;
this.lastAnnouncedNeighbour = undefined;
this.fireDragStartEvent();

this.startLoc = this.block.getRelativeToSurfaceXY();
Expand Down
24 changes: 24 additions & 0 deletions packages/blockly/tests/mocha/aria_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,30 @@ suite('ARIA', function () {
);
});

test('repeated drags with unchanged state are not re-announced', function () {
const block = this.workspace.newBlock('basic_block');
block.initSvg();
block.render();

const startLoc = block.getRelativeToSurfaceXY();
block.startDrag();
this.clock.tick(11);
const initialAnnouncementText = this.liveRegion.textContent;
assert.equal(initialAnnouncementText, 'Moving default on workspace.');

block.drag(new Blockly.utils.Coordinate(startLoc.x + 20, startLoc.y));
block.drag(new Blockly.utils.Coordinate(startLoc.x + 40, startLoc.y));
block.drag(new Blockly.utils.Coordinate(startLoc.x + 60, startLoc.y));
this.clock.tick(11);

// Subsequent announcements during a move will drop the getStackBlocksCountLabel()
// and toggle a non-breaking space at the end. If the move candidate hasn't
// changed, the live region shouldn't be udpated.
assert.equal(this.liveRegion.textContent, initialAnnouncementText);

block.endDrag(undefined, Blockly.DragDisposition.COMMIT);
});

test('Uses maximal assertiveness when coalescing', function () {
Blockly.utils.aria.announceDynamicAriaState('First message', {
assertiveness: Blockly.utils.aria.LiveRegionAssertiveness.OFF,
Expand Down
46 changes: 46 additions & 0 deletions packages/blockly/tests/mocha/keyboard_movement_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,52 @@ suite('Keyboard-driven movement', function () {

cancelMove(this.workspace);
});
test('re-announces when moving between identically labeled inputs', function () {
const json = {
'blocks': {
'languageVersion': 0,
'blocks': [
{
'type': 'controls_if',
'id': 'ifBlock',
'x': 0,
'y': 100,
'extraState': {
'elseIfCount': 2,
},
},
],
},
};
Blockly.serialization.workspaces.load(json, this.workspace);
const boolean = this.workspace.newBlock('logic_boolean');
boolean.initSvg();
boolean.render();
this.workspace.cleanUp();

Blockly.getFocusManager().focusNode(boolean);
startMove(this.workspace);
// Skip the first `if` value input.
moveRight(this.workspace);

this.moveAndAssert(
moveRight,
['Moving', 'to', 'else if'],
[this.getBlockLabel(boolean)],
);
const afterFirstElseIf = this.liveRegion.textContent;

this.moveAndAssert(
moveRight,
['Moving', 'to', 'else if'],
[this.getBlockLabel(boolean)],
);

const afterSecondElseIf = this.liveRegion.textContent;
assert.notEqual(afterSecondElseIf, afterFirstElseIf);

cancelMove(this.workspace);
});
test('disambiguates between unlabeled value inputs', function () {
const textJoin = this.workspace.newBlock('text_join');
textJoin.itemCount_ = 3;
Expand Down
Loading