- {colors.map(color => (
+ {colors.map((color, i) => (
);
};
-function ColorInput({
- color,
- onChange,
- label,
-}: {
- color: string | null;
- onChange: (color: string) => void;
- label: string;
-}) {
- const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
- const [innerValue, setInnerValue] = React.useState(color);
+const ColorInput = React.forwardRef(
+ (
+ {
+ color,
+ onChange,
+ label,
+ }: {
+ color: string | null;
+ onChange: (color: string) => void;
+ label: string;
+ },
+ ref,
+ ) => {
+ const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
+ const [innerValue, setInnerValue] = React.useState(color);
+ const inputRef = React.useRef(null);
- React.useEffect(() => {
- setInnerValue(color);
- }, [color]);
+ React.useEffect(() => {
+ setInnerValue(color);
+ }, [color]);
- return (
-
-
#
-
{
- const value = e.target.value;
- if (value.match(colorRegex)) {
- onChange(value === "transparent" ? "transparent" : "#" + value);
- }
- setInnerValue(value);
- }}
- value={(innerValue || "").replace(/^#/, "")}
- onPaste={e => onChange(e.clipboardData.getData("text"))}
- onBlur={() => setInnerValue(color)}
- />
-
- );
-}
+ React.useImperativeHandle(ref, () => inputRef.current);
+
+ return (
+
+
#
+
{
+ const value = e.target.value;
+ if (value.match(colorRegex)) {
+ onChange(value === "transparent" ? "transparent" : "#" + value);
+ }
+ setInnerValue(value);
+ }}
+ value={(innerValue || "").replace(/^#/, "")}
+ onPaste={e => onChange(e.clipboardData.getData("text"))}
+ onBlur={() => setInnerValue(color)}
+ ref={inputRef}
+ />
+
+ );
+ },
+);
export function ColorPicker({
type,
@@ -103,7 +158,10 @@ export function ColorPicker({
onChange: (color: string) => void;
label: string;
}) {
+ const { t } = useTranslation();
+
const [isActive, setActive] = React.useState(false);
+ const pickerButton = React.useRef
(null);
return (
@@ -113,6 +171,7 @@ export function ColorPicker({
aria-label={label}
style={color ? { backgroundColor: color } : undefined}
onClick={() => setActive(!isActive)}
+ ref={pickerButton}
/>
{
onChange(changedColor);
}}
+ onClose={() => {
+ setActive(false);
+ pickerButton.current?.focus();
+ }}
label={label}
+ t={t}
/>
) : null}
diff --git a/src/components/EditableText.tsx b/src/components/EditableText.tsx
index 1159b307..eeda1d62 100644
--- a/src/components/EditableText.tsx
+++ b/src/components/EditableText.tsx
@@ -6,6 +6,7 @@ import { selectNode, removeSelection } from "../utils";
type Props = {
value: string;
onChange: (value: string) => void;
+ label: string;
};
export class EditableText extends Component {
@@ -33,6 +34,8 @@ export class EditableText extends Component {
contentEditable="true"
data-type="wysiwyg"
className="project-name"
+ role="textbox"
+ aria-label={this.props.label}
onBlur={this.handleBlur}
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus}
diff --git a/src/components/ExportDialog.tsx b/src/components/ExportDialog.tsx
index 0acc30ca..1f11d195 100644
--- a/src/components/ExportDialog.tsx
+++ b/src/components/ExportDialog.tsx
@@ -13,6 +13,7 @@ import { ActionsManagerInterface, UpdaterFn } from "../actions/types";
import Stack from "./Stack";
import { useTranslation } from "react-i18next";
+import { KEYS } from "../keys";
const probablySupportsClipboard =
"toBlob" in HTMLCanvasElement.prototype &&
@@ -55,6 +56,9 @@ function ExportModal({
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
const previewRef = useRef(null);
const { exportBackground, viewBackgroundColor } = appState;
+ const pngButton = useRef(null);
+ const closeButton = useRef(null);
+ const onlySelectedInput = useRef(null);
const exportedElements = exportSelected
? elements.filter(element => element.isSelected)
@@ -84,13 +88,43 @@ function ExportModal({
scale,
]);
+ useEffect(() => {
+ pngButton.current?.focus();
+ }, []);
+
+ function handleKeyDown(e: React.KeyboardEvent) {
+ if (e.key === KEYS.TAB) {
+ const { activeElement } = document;
+ if (e.shiftKey) {
+ if (activeElement === pngButton.current) {
+ closeButton.current?.focus();
+ e.preventDefault();
+ }
+ } else {
+ if (activeElement === closeButton.current) {
+ pngButton.current?.focus();
+ e.preventDefault();
+ }
+ if (activeElement === onlySelectedInput.current) {
+ closeButton.current?.focus();
+ e.preventDefault();
+ }
+ }
+ }
+ }
+
return (
-
+
-