Skip to content

Commit

Permalink
fix: add tab gate triggered by ESC key and disabled by refocusing the
Browse files Browse the repository at this point in the history
editor or container
  • Loading branch information
stephen-pearce committed Oct 15, 2024
1 parent 0156fdb commit cb98b51
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/clean-jeans-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-live": patch
---

Add tab gate to disable focus trap ([#399](https://github.com/FormidableLabs/react-live/pull/399))
6 changes: 5 additions & 1 deletion packages/react-live/src/components/Editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Highlight, Prism, themes } from "prism-react-renderer";
import { CSSProperties, useEffect, useRef, useState } from "react";
import { useEditable } from "use-editable";
import useTabGate from "../../hooks/useTabGate";

export type Props = {
className?: string;
Expand All @@ -16,10 +17,13 @@ export type Props = {

const CodeEditor = (props: Props) => {
const { tabMode = "indentation" } = props;
const containerRef = useRef(null);
const editorRef = useRef(null);
const [code, setCode] = useState(props.code || "");
const { theme } = props;

useTabGate(containerRef, editorRef, tabMode === "indentation");

useEffect(() => {
setCode(props.code);
}, [props.code]);
Expand All @@ -36,7 +40,7 @@ const CodeEditor = (props: Props) => {
}, [code]);

return (
<div className={props.className} style={props.style}>
<div className={props.className} style={props.style} ref={containerRef}>
<Highlight
code={code}
theme={props.theme || themes.nightOwl}
Expand Down
58 changes: 58 additions & 0 deletions packages/react-live/src/hooks/useTabGate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { RefObject, useEffect, useState } from "react";

export default function useTabGate(
container: RefObject<HTMLElement>,
editor: RefObject<HTMLElement>,
enabled = true
) {
const [tabGate, setTabGate] = useState(false);

const setTabIndex = (element: RefObject<HTMLElement>, index: number) =>
element.current && (element.current.tabIndex = index);

const resetTabIndexes = () => {
if (container.current?.hasAttribute("tabIndex")) {
container.current?.removeAttribute("tabIndex");
}
setTabIndex(editor, 0);
};

const containerBlur = (event: FocusEvent) => {
if (event.relatedTarget !== container.current) resetTabIndexes();
};

const catchEscape = (event: KeyboardEvent) => {
if (event.code === "Escape" && enabled) setTabGate(true);
};

const containerFocus = () => editor.current?.focus();

const editorFocus = () => {
resetTabIndexes();
setTabGate(false);
};

useEffect(() => {
container.current?.addEventListener("blur", containerBlur);
container.current?.addEventListener("focus", containerFocus);
editor.current?.addEventListener("keydown", catchEscape);
editor.current?.addEventListener("focus", editorFocus);

return () => {
container.current?.removeEventListener("blur", containerBlur);
container.current?.removeEventListener("focus", containerFocus);
editor.current?.removeEventListener("keydown", catchEscape);
editor.current?.removeEventListener("focus", editorFocus);
};
}, []);

useEffect(() => {
if (!tabGate) return;

setTabIndex(container, 0);
editor.current?.blur();
setTabIndex(editor, -1);
}, [tabGate]);

return tabGate;
}

0 comments on commit cb98b51

Please sign in to comment.