tldraw
Version:
A tiny little drawing editor.
165 lines (164 loc) • 5.29 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import {
debugFlags,
unsafe__withoutCapture,
useContainer,
useEditor,
useMaybeEditor,
useReactor,
useValue
} from "@tldraw/editor";
import { memo, useCallback, useEffect, useRef } from "react";
import { useA11y } from "../context/a11y.mjs";
import { useTranslation } from "../hooks/useTranslation/useTranslation.mjs";
import { TldrawUiButton } from "./primitives/Button/TldrawUiButton.mjs";
function SkipToMainContent() {
const editor = useEditor();
const msg = useTranslation();
const button = useRef(null);
const handleNavigateToFirstShape = useCallback(
(e) => {
editor.markEventAsHandled(e);
button.current?.blur();
const shapes = editor.getCurrentPageShapesInReadingOrder();
if (!shapes.length) return;
editor.setSelectedShapes([shapes[0].id]);
editor.zoomToSelectionIfOffscreen(256, {
animation: {
duration: editor.options.animationMediumMs
},
inset: 0
});
editor.timers.setTimeout(() => editor.getContainer().focus(), 100);
},
[editor]
);
return /* @__PURE__ */ jsx(
TldrawUiButton,
{
ref: button,
type: "low",
tabIndex: 0,
className: "tl-skip-to-main-content",
onClick: handleNavigateToFirstShape,
children: msg("a11y.skip-to-main-content")
}
);
}
const DefaultA11yAnnouncer = memo(function TldrawUiA11yAnnouncer() {
const a11y = useA11y();
const translation = useTranslation();
const msg = useValue("a11y-msg", () => a11y.currentMsg.get(), []);
useA11yDebug(msg.msg);
useSelectedShapesAnnouncer();
return msg.msg && /* @__PURE__ */ jsx(
"div",
{
"aria-label": translation("a11y.status"),
"aria-live": msg.priority || "assertive",
role: "status",
"aria-hidden": "false",
style: {
position: "absolute",
top: "-10000px",
left: "-10000px"
},
children: msg.msg
}
);
});
function generateShapeAnnouncementMessage(args) {
const { editor, selectedShapeIds, msg } = args;
let a11yLive = "";
const numShapes = selectedShapeIds.length;
if (numShapes > 1) {
a11yLive = msg("a11y.multiple-shapes").replace("{num}", numShapes.toString());
} else if (numShapes === 1) {
const shapeId = selectedShapeIds[0];
const shape = editor.getShape(shapeId);
if (!shape) return "";
const shapeUtil = editor.getShapeUtil(shape.type);
const isMedia = ["image", "video"].includes(shape.type);
let shapeType = "";
if (shape.type === "geo") {
shapeType = msg(`geo-style.${shape.props.geo}`);
} else if (isMedia) {
shapeType = msg(`a11y.shape-${shape.type}`);
} else {
shapeType = msg(`tool.${shape.type}`);
}
const readingOrderShapes = editor.getCurrentPageShapesInReadingOrder();
const currentShapeIndex = (readingOrderShapes.findIndex((s) => s.id === shapeId) + 1).toString();
const totalShapes = readingOrderShapes.length.toString();
const shapeIndex = msg("a11y.shape-index").replace("{num}", currentShapeIndex).replace("{total}", totalShapes);
const describingText = shapeUtil.getAriaDescriptor(shape) || shapeUtil.getText(shape) || "";
a11yLive = (describingText ? `${describingText}, ` : "") + `${shapeType}. ${shapeIndex}`;
}
return a11yLive;
}
const useSelectedShapesAnnouncer = () => {
const editor = useMaybeEditor();
const a11y = useA11y();
const msg = useTranslation();
const rPrevSelectedShapeIds = useRef([]);
useReactor(
"announce selection",
() => {
if (!editor) return;
const isInSelecting = editor.isIn("select.idle");
if (isInSelecting) {
const selectedShapeIds = editor.getSelectedShapeIds();
if (selectedShapeIds !== rPrevSelectedShapeIds.current) {
rPrevSelectedShapeIds.current = selectedShapeIds;
unsafe__withoutCapture(() => {
const a11yLive = generateShapeAnnouncementMessage({
editor,
selectedShapeIds,
msg
});
if (a11yLive) {
a11y.announce({ msg: a11yLive });
}
});
}
}
},
[editor, a11y, msg]
);
};
const useA11yDebug = (msg) => {
const container = useContainer();
useEffect(() => {
if (debugFlags.a11y.get()) {
const log = (msg2) => {
console.debug(
`%ca11y%c: ${msg2}`,
`color: white; background: #40C057; padding: 2px;border-radius: 3px;`,
"font-weight: normal"
);
};
const handleKeyUp = (e) => {
const el = document.activeElement;
if (e.key === "Tab" && el && el !== document.body && !el.classList.contains("tl-container")) {
const label = el.getAttribute("aria-label") || el.getAttribute("title") || el.textContent;
if (label) {
log(label);
}
}
};
if (msg) {
log(msg);
}
document.addEventListener("keyup", handleKeyUp);
return () => document.removeEventListener("keyup", handleKeyUp);
}
return void 0;
}, [container, msg]);
};
export {
DefaultA11yAnnouncer,
SkipToMainContent,
generateShapeAnnouncementMessage,
useSelectedShapesAnnouncer
};
//# sourceMappingURL=A11y.mjs.map