Skip to content

Commit 5a80a2e

Browse files
author
Stephen Pearce
committed
fix: add tab gate triggered by ESC key and disabled by refocusing the
editor or container
1 parent 0156fdb commit 5a80a2e

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

packages/react-live/src/components/Editor/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Highlight, Prism, themes } from "prism-react-renderer";
22
import { CSSProperties, useEffect, useRef, useState } from "react";
33
import { useEditable } from "use-editable";
4+
import useTabGate from "../../hooks/useTabGate";
45

56
export type Props = {
67
className?: string;
@@ -16,10 +17,13 @@ export type Props = {
1617

1718
const CodeEditor = (props: Props) => {
1819
const { tabMode = "indentation" } = props;
20+
const containerRef = useRef(null);
1921
const editorRef = useRef(null);
2022
const [code, setCode] = useState(props.code || "");
2123
const { theme } = props;
2224

25+
useTabGate(containerRef, editorRef, tabMode === "indentation");
26+
2327
useEffect(() => {
2428
setCode(props.code);
2529
}, [props.code]);
@@ -36,7 +40,7 @@ const CodeEditor = (props: Props) => {
3640
}, [code]);
3741

3842
return (
39-
<div className={props.className} style={props.style}>
43+
<div className={props.className} style={props.style} ref={containerRef}>
4044
<Highlight
4145
code={code}
4246
theme={props.theme || themes.nightOwl}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { RefObject, useEffect, useState } from "react";
2+
3+
export default function useTabGate(
4+
container: RefObject<HTMLElement>,
5+
editor: RefObject<HTMLElement>,
6+
enabled = true
7+
) {
8+
const [tabGate, setTabGate] = useState(false);
9+
10+
const setTabIndex = (element: RefObject<HTMLElement>, index: number) =>
11+
element.current && (element.current.tabIndex = index);
12+
13+
const resetTabIndexes = () => {
14+
if (container.current?.hasAttribute("tabIndex")) {
15+
container.current?.removeAttribute("tabIndex");
16+
}
17+
setTabIndex(editor, 0);
18+
};
19+
20+
const containerBlur = (event: FocusEvent) => {
21+
if (event.relatedTarget !== container.current) resetTabIndexes();
22+
};
23+
24+
const catchEscape = (event: KeyboardEvent) => {
25+
if (event.code === "Escape" && enabled) setTabGate(true);
26+
};
27+
28+
const containerFocus = () => editor.current?.focus();
29+
30+
const editorFocus = () => {
31+
resetTabIndexes();
32+
setTabGate(false);
33+
};
34+
35+
useEffect(() => {
36+
container.current?.addEventListener("blur", containerBlur);
37+
container.current?.addEventListener("focus", containerFocus);
38+
editor.current?.addEventListener("keydown", catchEscape);
39+
editor.current?.addEventListener("focus", editorFocus);
40+
41+
return () => {
42+
container.current?.removeEventListener("blur", containerBlur);
43+
container.current?.removeEventListener("focus", containerFocus);
44+
editor.current?.removeEventListener("keydown", catchEscape);
45+
editor.current?.removeEventListener("focus", editorFocus);
46+
};
47+
}, []);
48+
49+
useEffect(() => {
50+
if (!tabGate) return;
51+
52+
setTabIndex(container, 0);
53+
editor.current?.blur();
54+
setTabIndex(editor, -1);
55+
}, [tabGate]);
56+
57+
return tabGate;
58+
}

0 commit comments

Comments
 (0)