Skip to content

Commit 1e735ef

Browse files
authored
fix: Make zoom to fit plugin keyboard and screenreader accessible (#2728)
* fix: Make zoom to fit plugin keyboard and screenreader accessible * fix: Use localized ARIA label
1 parent b4fe790 commit 1e735ef

1 file changed

Lines changed: 70 additions & 11 deletions

File tree

plugins/zoom-to-fit/src/index.ts

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import * as Blockly from 'blockly/core';
99
/**
1010
* Class for zoom to fit control.
1111
*/
12-
export class ZoomToFitControl implements Blockly.IPositionable {
12+
export class ZoomToFitControl
13+
implements Blockly.IComponent, Blockly.IPositionable, Blockly.IFocusableNode
14+
{
1315
/**
1416
* The unique id for this component.
1517
*/
@@ -72,7 +74,10 @@ export class ZoomToFitControl implements Blockly.IPositionable {
7274
this.workspace.getComponentManager().addComponent({
7375
component: this,
7476
weight: 2,
75-
capabilities: [Blockly.ComponentManager.Capability.POSITIONABLE],
77+
capabilities: [
78+
Blockly.ComponentManager.Capability.POSITIONABLE,
79+
Blockly.ComponentManager.Capability.FOCUSABLE,
80+
],
7681
});
7782
this.createDom();
7883
this.initialized = true;
@@ -97,15 +102,46 @@ export class ZoomToFitControl implements Blockly.IPositionable {
97102
* Creates DOM for ui element.
98103
*/
99104
private createDom() {
100-
this.svgGroup = Blockly.utils.dom.createSvgElement(
105+
this.svgGroup = Blockly.utils.dom.createSvgElement(Blockly.utils.Svg.G, {
106+
height: `${this.height}px`,
107+
width: `${this.width}px`,
108+
class: 'zoomToFit',
109+
id: Blockly.utils.idGenerator.getNextUniqueId(),
110+
tabindex: '0',
111+
});
112+
Blockly.utils.aria.setState(
113+
this.svgGroup,
114+
Blockly.utils.aria.State.LABEL,
115+
Blockly.Msg['ZOOM_TO_FIT_ARIA_LABEL'],
116+
);
117+
Blockly.utils.aria.setRole(this.svgGroup, Blockly.utils.aria.Role.BUTTON);
118+
119+
Blockly.utils.dom.createSvgElement(
120+
Blockly.utils.Svg.RECT,
121+
{
122+
width: 40,
123+
height: 40,
124+
x: -4,
125+
y: -4,
126+
rx: 2,
127+
ry: 2,
128+
fill: 'none',
129+
class: 'blocklyFocusRing',
130+
},
131+
this.svgGroup,
132+
);
133+
134+
const image = Blockly.utils.dom.createSvgElement(
101135
Blockly.utils.Svg.IMAGE,
102136
{
103137
height: `${this.height}px`,
104138
width: `${this.width}px`,
105-
class: 'zoomToFit',
139+
class: 'zoomToFitIcon',
106140
},
141+
this.svgGroup,
107142
);
108-
this.svgGroup.setAttributeNS(
143+
144+
image.setAttributeNS(
109145
Blockly.utils.dom.XLINK_NS,
110146
'xlink:href',
111147
zoomToFitSvgDataUri,
@@ -130,16 +166,16 @@ export class ZoomToFitControl implements Blockly.IPositionable {
130166
*
131167
* @param e A pointer down event.
132168
*/
133-
private onClick(e: PointerEvent) {
169+
private onClick(e?: Event) {
134170
this.workspace.zoomToFit();
135171
const uiEvent = new (Blockly.Events.get(Blockly.Events.CLICK))(
136172
null,
137173
this.workspace.id,
138174
'zoom_reset_control',
139175
);
140176
Blockly.Events.fire(uiEvent);
141-
e.stopPropagation(); // avoid to also fire workspace click event
142-
e.preventDefault();
177+
e?.stopPropagation(); // avoid to also fire workspace click event
178+
e?.preventDefault();
143179
}
144180

145181
/**
@@ -241,6 +277,29 @@ export class ZoomToFitControl implements Blockly.IPositionable {
241277
`translate(${this.left}, ${this.top})`,
242278
);
243279
}
280+
281+
getFocusableElement(): HTMLElement | SVGElement {
282+
if (!this.svgGroup) {
283+
throw new Error('Tried to focus an uninitialized zoom to fit control');
284+
}
285+
return this.svgGroup;
286+
}
287+
288+
getFocusableTree(): Blockly.IFocusableTree {
289+
return this.workspace;
290+
}
291+
292+
onNodeFocus(): void {}
293+
294+
onNodeBlur(): void {}
295+
296+
canBeFocused(): boolean {
297+
return true;
298+
}
299+
300+
performAction(e?: Event): void {
301+
this.onClick(e);
302+
}
244303
}
245304

246305
/**
@@ -256,13 +315,13 @@ const zoomToFitSvgDataUri =
256315
'E0LjVMNSAxNy41OVYxNUgzdjZoNnYtMkg2LjQybDMuMDgtMy4wOXoiLz48L3N2Zz4=';
257316

258317
Blockly.Css.register(`
259-
.zoomToFit {
318+
.zoomToFitIcon {
260319
opacity: 0.4;
261320
}
262-
.zoomToFit:hover {
321+
.zoomToFitIcon:hover, .zoomToFit:focus .zoomToFitIcon {
263322
opacity: 0.6;
264323
}
265-
.zoomToFit:active {
324+
.zoomToFitIcon:active {
266325
opacity: 0.8;
267326
}
268327
`);

0 commit comments

Comments
 (0)