Skip to content

Commit a17d681

Browse files
committed
Finished pass of source to TS
1 parent d0e96bc commit a17d681

File tree

15 files changed

+187
-171
lines changed

15 files changed

+187
-171
lines changed

rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const prodPlugins = plugins.concat([
5353
]);
5454

5555
const base = {
56-
input: "src/index.js",
56+
input: "src/index.ts",
5757
external: [
5858
"react",
5959
"react-dom",

src/components/Editor/index.js renamed to src/components/Editor/index.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
1-
import Highlight, { Prism } from "prism-react-renderer";
2-
import PropTypes from "prop-types";
3-
import React, { useCallback, useEffect, useRef, useState } from "react";
1+
import Highlight, { Language, Prism, PrismTheme } from "prism-react-renderer";
2+
import { CSSProperties, useCallback, useEffect, useRef, useState } from "react";
43
import { useEditable } from "use-editable";
5-
import { theme as liveTheme } from "../../constants/theme.ts";
4+
import { theme as liveTheme } from "../../constants/theme";
65

7-
const CodeEditor = (props) => {
6+
export type Props = {
7+
className?: string;
8+
code: string;
9+
disabled: boolean;
10+
language: Language;
11+
prism?: typeof Prism;
12+
style?: CSSProperties;
13+
tabMode: "focus" | "indentation";
14+
theme?: PrismTheme;
15+
onChange(value: string): void;
16+
};
17+
18+
const CodeEditor = (props: Props) => {
819
const editorRef = useRef(null);
920
const [code, setCode] = useState(props.code || "");
1021

1122
useEffect(() => {
1223
setCode(props.code);
1324
}, [props.code]);
1425

15-
const onEditableChange = useCallback((_code) => {
26+
const onEditableChange = useCallback((_code: string) => {
1627
setCode(_code.slice(0, -1));
1728
}, []);
1829

@@ -41,6 +52,7 @@ const CodeEditor = (props) => {
4152
getLineProps,
4253
getTokenProps,
4354
style: _style,
55+
/* @ts-ignore — this property exists but the lib's types are incorrect */
4456
theme: _theme,
4557
}) => (
4658
<pre
@@ -79,20 +91,8 @@ const CodeEditor = (props) => {
7991
);
8092
};
8193

82-
CodeEditor.propTypes = {
83-
className: PropTypes.string,
84-
code: PropTypes.string,
85-
disabled: PropTypes.bool,
86-
language: PropTypes.string,
87-
onChange: PropTypes.func,
88-
prism: PropTypes.object,
89-
style: PropTypes.object,
90-
tabMode: PropTypes.oneOf(["focus", "indentation"]),
91-
theme: PropTypes.object,
92-
};
93-
9494
CodeEditor.defaultProps = {
9595
tabMode: "indentation",
96-
};
96+
} satisfies Pick<Props, 'tabMode'>;
9797

9898
export default CodeEditor;

src/components/Live/LiveContext.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
import { createContext } from "react";
1+
import { Language, PrismTheme } from "prism-react-renderer";
2+
import { ComponentType, createContext } from "react";
23

3-
const LiveContext = createContext({});
4+
type ContextValue = {
5+
error?: string;
6+
element?: ComponentType | null;
7+
code: string;
8+
disabled: boolean;
9+
language: Language;
10+
theme?: PrismTheme;
11+
onError(error: Error): void;
12+
onChange(value: string): void;
13+
}
14+
15+
const LiveContext = createContext<ContextValue>({} as ContextValue);
416

517
export default LiveContext;

src/components/Live/LiveEditor.js renamed to src/components/Live/LiveEditor.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { useContext } from "react";
2-
import LiveContext from "./LiveContext.ts";
3-
import Editor from "../Editor";
2+
import LiveContext from "./LiveContext";
3+
import Editor, { Props as EditorProps } from "../Editor";
44

5-
export default function LiveEditor(props) {
5+
export default function LiveEditor(props: Partial<EditorProps>) {
66
const { code, language, theme, disabled, onChange } = useContext(LiveContext);
77

88
return (
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useContext } from "react";
2-
import LiveContext from "./LiveContext.ts";
2+
import LiveContext from "./LiveContext";
33

4-
export default function LiveError(props) {
4+
export default function LiveError<T extends Record<string, unknown>>(props: T) {
55
const { error } = useContext(LiveContext);
66
return error ? <pre {...props}>{error}</pre> : null;
77
}

src/components/Live/LivePreview.js

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, { PropsWithChildren, useContext } from "react";
2+
import LiveContext from "./LiveContext";
3+
4+
type Props = {
5+
Component: React.ComponentType<PropsWithChildren<Record<string, unknown>>
6+
};
7+
8+
const fallbackComponent = (props: PropsWithChildren<Record<string, unknown>>) => (
9+
<div {...props} />
10+
);
11+
12+
function LivePreview({ Component = fallbackComponent, ...rest }: Props) {
13+
const { element: Element } = useContext(LiveContext);
14+
return <Component {...rest}>{Element ? <Element /> : null}</Component>;
15+
}
16+
export default LivePreview;

src/components/Live/LiveProvider.js

Lines changed: 0 additions & 113 deletions
This file was deleted.

src/components/Live/LiveProvider.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useContext } from "react";
22
import { act } from "react-dom/test-utils";
33
import { renderElementAsync } from "../../utils/transpile/index.ts";
44
import { render } from "../../utils/test/renderer";
5-
import LiveProvider from "./LiveProvider";
5+
import LiveProvider from "./LiveProvider.tsx";
66
import LiveContext from "./LiveContext.ts";
77

88
jest.mock("../../utils/transpile");
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { useEffect, useState, ComponentType, PropsWithChildren } from "react";
2+
import LiveContext from "./LiveContext";
3+
import { generateElement, renderElementAsync } from "../../utils/transpile";
4+
import type { Language, PrismTheme } from "prism-react-renderer";
5+
6+
type ProviderState = {
7+
element?: ComponentType | null;
8+
error?: string;
9+
};
10+
11+
type Props = {
12+
code: string;
13+
disabled: boolean;
14+
enableTypeScript: boolean;
15+
language: Language;
16+
noInline: boolean;
17+
scope: Record<string, unknown>;
18+
theme: PrismTheme;
19+
transformCode(code: string): void;
20+
};
21+
22+
function LiveProvider({
23+
children,
24+
code = "",
25+
language = "tsx",
26+
theme,
27+
enableTypeScript = true,
28+
disabled = false,
29+
scope,
30+
transformCode,
31+
noInline = false,
32+
}: PropsWithChildren<Props>) {
33+
const [state, setState] = useState<ProviderState>({
34+
error: undefined,
35+
element: undefined,
36+
});
37+
38+
async function transpileAsync(newCode: string) {
39+
const errorCallback = (error: Error) => {
40+
setState({ error: error.toString(), element: undefined });
41+
};
42+
43+
// - transformCode may be synchronous or asynchronous.
44+
// - transformCode may throw an exception or return a rejected promise, e.g.
45+
// if newCode is invalid and cannot be transformed.
46+
// - Not using async-await to since it requires targeting ES 2017 or
47+
// importing regenerator-runtime... in the next major version of
48+
// react-live, should target ES 2017+
49+
try {
50+
const transformResult = transformCode ? transformCode(newCode) : newCode;
51+
try {
52+
const transformedCode = await Promise.resolve(transformResult);
53+
const renderElement = (element: ComponentType) =>
54+
setState({ error: undefined, element });
55+
56+
if (typeof transformedCode !== "string") {
57+
throw new Error("Code failed to transform");
58+
}
59+
60+
// Transpilation arguments
61+
const input = {
62+
code: transformedCode,
63+
scope,
64+
enableTypeScript,
65+
};
66+
67+
if (noInline) {
68+
setState({ error: undefined, element: null }); // Reset output for async (no inline) evaluation
69+
renderElementAsync(input, renderElement, errorCallback);
70+
} else {
71+
renderElement(generateElement(input, errorCallback));
72+
}
73+
} catch (error) {
74+
return errorCallback(error as Error);
75+
}
76+
} catch (e) {
77+
errorCallback(e as Error);
78+
return Promise.resolve();
79+
}
80+
}
81+
82+
const onError = (error: Error) => setState({ error: error.toString() });
83+
84+
useEffect(() => {
85+
transpileAsync(code).catch(onError);
86+
}, [code, scope, noInline, transformCode]);
87+
88+
const onChange = (newCode: string) => {
89+
transpileAsync(newCode).catch(onError);
90+
};
91+
92+
return (
93+
<LiveContext.Provider
94+
value={{
95+
...state,
96+
code,
97+
language,
98+
theme,
99+
disabled,
100+
onError,
101+
onChange,
102+
}}
103+
>
104+
{children}
105+
</LiveContext.Provider>
106+
);
107+
}
108+
109+
export default LiveProvider;

0 commit comments

Comments
 (0)