Add landmarks (#564)
Use HTML semantic elements to set the landmarks of the page. This is helpful for assistive technologies to determine the different regions of content. In our case it's useful for jumping between the different islands that we use to group the form controls.
This commit is contained in:
parent
fc350f2ecd
commit
67eca2bda1
@ -81,9 +81,11 @@
|
|||||||
<noscript>
|
<noscript>
|
||||||
You need to enable JavaScript to run this app.
|
You need to enable JavaScript to run this app.
|
||||||
</noscript>
|
</noscript>
|
||||||
|
<header>
|
||||||
<h1 class="visually-hidden">Excalidraw</h1>
|
<h1 class="visually-hidden">Excalidraw</h1>
|
||||||
|
</header>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<aside>
|
||||||
<!-- https://github.com/tholman/github-corners -->
|
<!-- https://github.com/tholman/github-corners -->
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -111,5 +113,6 @@
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</svg>
|
</svg>
|
||||||
|
</aside>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
"cartoonist": "Cartoonist",
|
"cartoonist": "Cartoonist",
|
||||||
"fileTitle": "File title",
|
"fileTitle": "File title",
|
||||||
"colorPicker": "Color picker",
|
"colorPicker": "Color picker",
|
||||||
"canvasBackground": "Canvas background"
|
"canvasBackground": "Canvas background",
|
||||||
|
"drawingCanvas": "Drawing Canvas"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Clear the canvas & reset background color",
|
"clearReset": "Clear the canvas & reset background color",
|
||||||
@ -48,7 +49,8 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"load": "Load",
|
"load": "Load",
|
||||||
"getShareableLink": "Get shareable link",
|
"getShareableLink": "Get shareable link",
|
||||||
"close": "Close"
|
"close": "Close",
|
||||||
|
"selectLanguage": "Select Language"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "This will clear the whole canvas. Are you sure?",
|
"clearReset": "This will clear the whole canvas. Are you sure?",
|
||||||
@ -67,5 +69,10 @@
|
|||||||
"line": "Line",
|
"line": "Line",
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
"lock": "Keep selected tool active after drawing"
|
"lock": "Keep selected tool active after drawing"
|
||||||
|
},
|
||||||
|
"headings": {
|
||||||
|
"canvasActions": "Canvas actions",
|
||||||
|
"selectedShapeActions": "Selected shape actions",
|
||||||
|
"shapes": "Shapes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
"cartoonist": "Caricatura",
|
"cartoonist": "Caricatura",
|
||||||
"fileTitle": "Título del archivo",
|
"fileTitle": "Título del archivo",
|
||||||
"colorPicker": "Selector de color",
|
"colorPicker": "Selector de color",
|
||||||
"canvasBackground": "Fondo del lienzo"
|
"canvasBackground": "Fondo del lienzo",
|
||||||
|
"drawingCanvas": "Lienzo de dibujo"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Limpiar lienzo y reiniciar el color de fondo",
|
"clearReset": "Limpiar lienzo y reiniciar el color de fondo",
|
||||||
@ -49,7 +50,8 @@
|
|||||||
"load": "Cargar",
|
"load": "Cargar",
|
||||||
"getShareableLink": "Obtener enlace para compartir",
|
"getShareableLink": "Obtener enlace para compartir",
|
||||||
"showExportDialog": "Mostrar diálogo para exportar",
|
"showExportDialog": "Mostrar diálogo para exportar",
|
||||||
"close": "Cerrar"
|
"close": "Cerrar",
|
||||||
|
"selectLanguage": "Select Language"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Esto limpiará todo el lienzo. Estás seguro?",
|
"clearReset": "Esto limpiará todo el lienzo. Estás seguro?",
|
||||||
@ -66,6 +68,12 @@
|
|||||||
"ellipse": "Elipse",
|
"ellipse": "Elipse",
|
||||||
"arrow": "Flecha",
|
"arrow": "Flecha",
|
||||||
"line": "Línea",
|
"line": "Línea",
|
||||||
"text": "Texto"
|
"text": "Texto",
|
||||||
|
"lock": "Mantener la herramienta seleccionada activa después de dibujar"
|
||||||
|
},
|
||||||
|
"headings": {
|
||||||
|
"canvasActions": "Acciones del lienzo",
|
||||||
|
"selectedShapeActions": "Acciones de la forma seleccionada",
|
||||||
|
"shapes": "Formas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export function LanguageList<T>({
|
export function LanguageList<T>({
|
||||||
onClick,
|
onClick,
|
||||||
@ -9,12 +10,15 @@ export function LanguageList<T>({
|
|||||||
onClick: (value: string) => void;
|
onClick: (value: string) => void;
|
||||||
currentLanguage: string;
|
currentLanguage: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<select
|
<select
|
||||||
className="language-select"
|
className="language-select"
|
||||||
onChange={({ target }) => onClick(target.value)}
|
onChange={({ target }) => onClick(target.value)}
|
||||||
value={currentLanguage}
|
value={currentLanguage}
|
||||||
|
aria-label={t("buttons.selectLanguage")}
|
||||||
>
|
>
|
||||||
{languages.map(language => (
|
{languages.map(language => (
|
||||||
<option key={language.lng} value={language.lng}>
|
<option key={language.lng} value={language.lng}>
|
||||||
|
@ -51,6 +51,7 @@ export function LockIcon(props: LockIconProps) {
|
|||||||
id={props.id}
|
id={props.id}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
checked={props.checked}
|
checked={props.checked}
|
||||||
|
aria-label={props.title}
|
||||||
/>
|
/>
|
||||||
<div className="ToolIcon__icon">
|
<div className="ToolIcon__icon">
|
||||||
{props.checked ? ICONS.CHECKED : ICONS.UNCHECKED}
|
{props.checked ? ICONS.CHECKED : ICONS.UNCHECKED}
|
||||||
|
@ -606,18 +606,32 @@ export class App extends React.Component<any, AppState> {
|
|||||||
<FixedSideContainer side="top">
|
<FixedSideContainer side="top">
|
||||||
<div className="App-menu App-menu_top">
|
<div className="App-menu App-menu_top">
|
||||||
<Stack.Col gap={4} align="end">
|
<Stack.Col gap={4} align="end">
|
||||||
<div className="App-right-menu">
|
<section
|
||||||
<h2 className="visually-hidden">Canvas actions</h2>
|
className="App-right-menu"
|
||||||
|
aria-labelledby="canvas-actions-title"
|
||||||
|
>
|
||||||
|
<h2 className="visually-hidden" id="canvas-actions-title">
|
||||||
|
{t("headings.canvasActions")}
|
||||||
|
</h2>
|
||||||
<Island padding={4}>{this.renderCanvasActions()}</Island>
|
<Island padding={4}>{this.renderCanvasActions()}</Island>
|
||||||
</div>
|
</section>
|
||||||
<div className="App-right-menu">
|
<section
|
||||||
|
className="App-right-menu"
|
||||||
|
aria-labelledby="selected-shape-title"
|
||||||
|
>
|
||||||
|
<h2 className="visually-hidden" id="selected-shape-title">
|
||||||
|
{t("headings.selectedShapeActions")}
|
||||||
|
</h2>
|
||||||
{this.renderSelectedShapeActions(elements)}
|
{this.renderSelectedShapeActions(elements)}
|
||||||
</div>
|
</section>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
|
<section aria-labelledby="shapes-title">
|
||||||
<Stack.Col gap={4} align="start">
|
<Stack.Col gap={4} align="start">
|
||||||
<Stack.Row gap={1}>
|
<Stack.Row gap={1}>
|
||||||
<Island padding={1}>
|
<Island padding={1}>
|
||||||
<h2 className="visually-hidden">Shapes</h2>
|
<h2 className="visually-hidden" id="shapes-title">
|
||||||
|
{t("headings.shapes")}
|
||||||
|
</h2>
|
||||||
<Stack.Row gap={1}>{this.renderShapesSwitcher()}</Stack.Row>
|
<Stack.Row gap={1}>{this.renderShapesSwitcher()}</Stack.Row>
|
||||||
</Island>
|
</Island>
|
||||||
<LockIcon
|
<LockIcon
|
||||||
@ -634,9 +648,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
|
</section>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
</FixedSideContainer>
|
</FixedSideContainer>
|
||||||
|
<main>
|
||||||
<canvas
|
<canvas
|
||||||
id="canvas"
|
id="canvas"
|
||||||
style={{
|
style={{
|
||||||
@ -809,7 +825,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
element = newTextElement(element, "", this.state.currentItemFont);
|
element = newTextElement(
|
||||||
|
element,
|
||||||
|
"",
|
||||||
|
this.state.currentItemFont,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
@ -1051,7 +1071,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resizeHandle) {
|
if (resizeHandle) {
|
||||||
resizeHandle = normalizeResizeHandle(element, resizeHandle);
|
resizeHandle = normalizeResizeHandle(
|
||||||
|
element,
|
||||||
|
resizeHandle,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
normalizeDimensions(element);
|
normalizeDimensions(element);
|
||||||
|
|
||||||
@ -1174,8 +1197,13 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.setState({});
|
this.setState({});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
if (
|
||||||
elements = elements.filter(el => el.id !== resizingElement.id);
|
resizingElement &&
|
||||||
|
isInvisiblySmallElement(resizingElement)
|
||||||
|
) {
|
||||||
|
elements = elements.filter(
|
||||||
|
el => el.id !== resizingElement.id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If click occurred on already selected element
|
// If click occurred on already selected element
|
||||||
@ -1333,11 +1361,15 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}}
|
}}
|
||||||
onMouseMove={e => {
|
onMouseMove={e => {
|
||||||
const hasDeselectedButton = Boolean(e.buttons);
|
const hasDeselectedButton = Boolean(e.buttons);
|
||||||
if (hasDeselectedButton || this.state.elementType !== "selection") {
|
if (
|
||||||
|
hasDeselectedButton ||
|
||||||
|
this.state.elementType !== "selection"
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
||||||
const selectedElements = elements.filter(e => e.isSelected).length;
|
const selectedElements = elements.filter(e => e.isSelected)
|
||||||
|
.length;
|
||||||
if (selectedElements === 1) {
|
if (selectedElements === 1) {
|
||||||
const resizeElement = getElementWithResizeHandler(
|
const resizeElement = getElementWithResizeHandler(
|
||||||
elements,
|
elements,
|
||||||
@ -1354,7 +1386,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const hitElement = getElementAtPosition(elements, x, y);
|
const hitElement = getElementAtPosition(elements, x, y);
|
||||||
document.documentElement.style.cursor = hitElement ? "move" : "";
|
document.documentElement.style.cursor = hitElement ? "move" : "";
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{t("labels.drawingCanvas")}
|
||||||
|
</canvas>
|
||||||
|
</main>
|
||||||
|
<footer role="contentinfo">
|
||||||
<LanguageList
|
<LanguageList
|
||||||
onClick={lng => {
|
onClick={lng => {
|
||||||
i18n.changeLanguage(lng);
|
i18n.changeLanguage(lng);
|
||||||
@ -1362,6 +1398,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
languages={languages}
|
languages={languages}
|
||||||
currentLanguage={parseDetectedLang(i18n.language)}
|
currentLanguage={parseDetectedLang(i18n.language)}
|
||||||
/>
|
/>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user