next-live 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/dist/editor.cjs +247 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +80 -0
- package/dist/editor.d.ts +80 -0
- package/dist/editor.js +225 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.cjs +1265 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +580 -0
- package/dist/index.d.ts +580 -0
- package/dist/index.js +1192 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +287 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +142 -0
- package/dist/server.d.ts +142 -0
- package/dist/server.js +282 -0
- package/dist/server.js.map +1 -0
- package/dist/shared.js +30 -0
- package/dist/shared.js.map +1 -0
- package/docs/01-getting-started.md +244 -0
- package/docs/02-module-registry.md +487 -0
- package/docs/03-sharing-your-app-libraries.md +206 -0
- package/docs/04-scaling.md +234 -0
- package/docs/05-security.md +204 -0
- package/docs/06-api-reference.md +337 -0
- package/docs/07-troubleshooting.md +289 -0
- package/docs/08-integration-guide.md +340 -0
- package/docs/09-non-ui-snippets.md +124 -0
- package/docs/10-validating-in-ci.md +192 -0
- package/docs/README.md +76 -0
- package/package.json +105 -0
package/dist/editor.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { LiveContext, errorPosition } from './shared.js';
|
|
3
|
+
import { useContext, useRef, useId, useState, useCallback } from 'react';
|
|
4
|
+
import { themes, Highlight } from 'prism-react-renderer';
|
|
5
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var DEFAULT_ERROR_LINE_STYLE = {
|
|
8
|
+
background: "rgba(179, 38, 30, 0.35)",
|
|
9
|
+
boxShadow: "inset 3px 0 0 #b3261e"
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_FOCUS_RING_STYLE = {
|
|
12
|
+
outline: "2px solid #4d9fff",
|
|
13
|
+
outlineOffset: "2px"
|
|
14
|
+
};
|
|
15
|
+
var SHARED_TEXT_STYLE = {
|
|
16
|
+
margin: 0,
|
|
17
|
+
border: 0,
|
|
18
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',
|
|
19
|
+
fontSize: "0.8125rem",
|
|
20
|
+
lineHeight: 1.6,
|
|
21
|
+
fontVariantLigatures: "none",
|
|
22
|
+
tabSize: 2,
|
|
23
|
+
whiteSpace: "pre",
|
|
24
|
+
wordBreak: "normal",
|
|
25
|
+
overflowWrap: "normal"
|
|
26
|
+
};
|
|
27
|
+
var VISUALLY_HIDDEN = {
|
|
28
|
+
position: "absolute",
|
|
29
|
+
width: 1,
|
|
30
|
+
height: 1,
|
|
31
|
+
padding: 0,
|
|
32
|
+
margin: -1,
|
|
33
|
+
overflow: "hidden",
|
|
34
|
+
clip: "rect(0 0 0 0)",
|
|
35
|
+
whiteSpace: "nowrap",
|
|
36
|
+
border: 0
|
|
37
|
+
};
|
|
38
|
+
function prefersVisibleFocus(element) {
|
|
39
|
+
try {
|
|
40
|
+
return element.matches(":focus-visible");
|
|
41
|
+
} catch {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function LiveEditor(props) {
|
|
46
|
+
const {
|
|
47
|
+
className,
|
|
48
|
+
style,
|
|
49
|
+
theme = themes.vsDark,
|
|
50
|
+
readOnly: readOnlyProp,
|
|
51
|
+
tabSize = 2,
|
|
52
|
+
padding = 16,
|
|
53
|
+
errorLineStyle = DEFAULT_ERROR_LINE_STYLE,
|
|
54
|
+
errorLineClassName,
|
|
55
|
+
code: codeProp,
|
|
56
|
+
onChange: onChangeProp,
|
|
57
|
+
language: languageProp,
|
|
58
|
+
error: errorProp,
|
|
59
|
+
prism,
|
|
60
|
+
focusRingStyle = DEFAULT_FOCUS_RING_STYLE,
|
|
61
|
+
"aria-label": ariaLabel = "Live code editor",
|
|
62
|
+
renderEditor
|
|
63
|
+
} = props;
|
|
64
|
+
const live = useContext(LiveContext);
|
|
65
|
+
const highlightRef = useRef(null);
|
|
66
|
+
const hintId = useId();
|
|
67
|
+
const [tabEscapes, setTabEscapes] = useState(false);
|
|
68
|
+
const [focusRing, setFocusRing] = useState(false);
|
|
69
|
+
if (codeProp === void 0 && live === null) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"<LiveEditor> needs either a surrounding <LiveProvider> or a `code` prop. Pass `code` (and `onChange` to make it editable) to use it standalone."
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const code = codeProp ?? live?.code ?? "";
|
|
75
|
+
const language = languageProp ?? live?.language ?? "tsx";
|
|
76
|
+
const error = errorProp !== void 0 ? errorProp : live?.error ?? null;
|
|
77
|
+
const setCode = onChangeProp ?? (codeProp === void 0 ? live?.setCode : void 0);
|
|
78
|
+
const readOnly = readOnlyProp ?? setCode === void 0;
|
|
79
|
+
const position = error ? errorPosition(error) : null;
|
|
80
|
+
const errorLine = position?.line;
|
|
81
|
+
const errorColumn = position?.column;
|
|
82
|
+
const handleChange = useCallback(
|
|
83
|
+
(value) => {
|
|
84
|
+
setCode?.(value);
|
|
85
|
+
},
|
|
86
|
+
[setCode]
|
|
87
|
+
);
|
|
88
|
+
const renderProps = {
|
|
89
|
+
code,
|
|
90
|
+
onChange: handleChange,
|
|
91
|
+
language,
|
|
92
|
+
error,
|
|
93
|
+
errorLine,
|
|
94
|
+
errorColumn
|
|
95
|
+
};
|
|
96
|
+
if (renderEditor) {
|
|
97
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderEditor(renderProps) });
|
|
98
|
+
}
|
|
99
|
+
const syncScroll = (event) => {
|
|
100
|
+
const node = highlightRef.current;
|
|
101
|
+
if (!node) return;
|
|
102
|
+
node.scrollTop = event.currentTarget.scrollTop;
|
|
103
|
+
node.scrollLeft = event.currentTarget.scrollLeft;
|
|
104
|
+
};
|
|
105
|
+
const handleKeyDown = (event) => {
|
|
106
|
+
if (event.key === "Escape") {
|
|
107
|
+
setTabEscapes(true);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (event.key !== "Tab") {
|
|
111
|
+
if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
112
|
+
if (tabEscapes) setTabEscapes(false);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (tabEscapes || readOnly) {
|
|
117
|
+
setTabEscapes(false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
const target = event.currentTarget;
|
|
122
|
+
const { selectionStart, selectionEnd, value } = target;
|
|
123
|
+
const indent = " ".repeat(tabSize);
|
|
124
|
+
const next = value.slice(0, selectionStart) + indent + value.slice(selectionEnd);
|
|
125
|
+
handleChange(next);
|
|
126
|
+
requestAnimationFrame(() => {
|
|
127
|
+
target.selectionStart = target.selectionEnd = selectionStart + indent.length;
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const layerStyle = {
|
|
131
|
+
...SHARED_TEXT_STYLE,
|
|
132
|
+
tabSize,
|
|
133
|
+
padding,
|
|
134
|
+
gridArea: "1 / 1",
|
|
135
|
+
minWidth: 0,
|
|
136
|
+
minHeight: 0,
|
|
137
|
+
overflow: "auto"
|
|
138
|
+
};
|
|
139
|
+
return /* @__PURE__ */ jsxs(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
className,
|
|
143
|
+
style: {
|
|
144
|
+
// A single-cell grid makes both layers size to the larger of the two,
|
|
145
|
+
// which absolute positioning cannot do without a fixed height.
|
|
146
|
+
display: "grid",
|
|
147
|
+
position: "relative",
|
|
148
|
+
background: theme.plain.backgroundColor ?? "#1e1e1e",
|
|
149
|
+
...style,
|
|
150
|
+
// The ring is painted on the wrapper rather than the textarea, whose
|
|
151
|
+
// own outline would be clipped by its scroll box. Applied after the
|
|
152
|
+
// caller's `style` so it is not accidentally overwritten.
|
|
153
|
+
...focusRing && focusRingStyle ? focusRingStyle : null
|
|
154
|
+
},
|
|
155
|
+
children: [
|
|
156
|
+
/* @__PURE__ */ jsx(Highlight, { prism, code, language, theme, children: ({ style: prismStyle, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ jsx(
|
|
157
|
+
"pre",
|
|
158
|
+
{
|
|
159
|
+
ref: highlightRef,
|
|
160
|
+
"aria-hidden": "true",
|
|
161
|
+
style: { ...prismStyle, ...layerStyle },
|
|
162
|
+
children: tokens.map((line, i) => {
|
|
163
|
+
const isErrorLine = errorLineStyle !== null && errorLine === i + 1;
|
|
164
|
+
const lineProps = getLineProps({ line });
|
|
165
|
+
return (
|
|
166
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
167
|
+
/* @__PURE__ */ jsx(
|
|
168
|
+
"div",
|
|
169
|
+
{
|
|
170
|
+
...lineProps,
|
|
171
|
+
className: [lineProps.className, isErrorLine ? errorLineClassName : void 0].filter(Boolean).join(" "),
|
|
172
|
+
style: isErrorLine ? { ...lineProps.style, ...errorLineStyle } : lineProps.style,
|
|
173
|
+
children: line.map((token, key) => (
|
|
174
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
175
|
+
/* @__PURE__ */ jsx("span", { ...getTokenProps({ token }) }, key)
|
|
176
|
+
))
|
|
177
|
+
},
|
|
178
|
+
i
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
) }),
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
"textarea",
|
|
186
|
+
{
|
|
187
|
+
value: code,
|
|
188
|
+
onChange: (event) => handleChange(event.target.value),
|
|
189
|
+
onScroll: syncScroll,
|
|
190
|
+
onKeyDown: handleKeyDown,
|
|
191
|
+
onFocus: (event) => setFocusRing(prefersVisibleFocus(event.currentTarget)),
|
|
192
|
+
onBlur: () => {
|
|
193
|
+
setFocusRing(false);
|
|
194
|
+
setTabEscapes(false);
|
|
195
|
+
},
|
|
196
|
+
readOnly,
|
|
197
|
+
spellCheck: false,
|
|
198
|
+
autoCapitalize: "off",
|
|
199
|
+
autoCorrect: "off",
|
|
200
|
+
autoComplete: "off",
|
|
201
|
+
"aria-label": ariaLabel,
|
|
202
|
+
"aria-describedby": readOnly ? void 0 : hintId,
|
|
203
|
+
"aria-keyshortcuts": readOnly ? void 0 : "Escape",
|
|
204
|
+
style: {
|
|
205
|
+
...layerStyle,
|
|
206
|
+
resize: "none",
|
|
207
|
+
// The wrapper paints the ring instead - see above.
|
|
208
|
+
outline: "none",
|
|
209
|
+
background: "transparent",
|
|
210
|
+
// The text itself is invisible; only the highlighted layer is read.
|
|
211
|
+
// The caret is painted separately so it stays visible.
|
|
212
|
+
color: "transparent",
|
|
213
|
+
caretColor: theme.plain.color ?? "#fff"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
),
|
|
217
|
+
!readOnly && /* @__PURE__ */ jsx("span", { id: hintId, style: VISUALLY_HIDDEN, children: "Tab inserts spaces. Press Escape and then Tab to move focus out of the editor." })
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export { LiveEditor };
|
|
224
|
+
//# sourceMappingURL=editor.js.map
|
|
225
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/LiveEditor.tsx"],"names":[],"mappings":"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","file":"editor.js","sourcesContent":["'use client';\n\nimport { useCallback, useContext, useId, useRef, useState } from 'react';\nimport type { CSSProperties, ReactNode } from 'react';\nimport { Highlight, themes } from 'prism-react-renderer';\nimport type { Prism, PrismTheme } from 'prism-react-renderer';\nimport { errorPosition } from '../core/positions';\nimport { LiveContext } from '../context/LiveContext';\n\nexport interface LiveEditorRenderProps {\n code: string;\n onChange: (code: string) => void;\n language: string;\n error: Error | null;\n errorLine?: number;\n errorColumn?: number;\n}\n\nexport interface LiveEditorProps {\n className?: string;\n style?: CSSProperties;\n theme?: PrismTheme;\n /** Read-only mode, for showing an app's source without allowing edits. */\n readOnly?: boolean;\n /** Spaces inserted by the Tab key. Default 2. */\n tabSize?: number;\n padding?: number;\n /** Paint-only highlight for the error line. Pass `null` to disable. */\n errorLineStyle?: CSSProperties | null;\n errorLineClassName?: string;\n /**\n * Standalone mode: the code to show, when there is no `<LiveProvider>`\n * above. Inside a provider this overrides the provider's code, which is\n * rarely what you want - omit it and let the context supply it.\n */\n code?: string;\n /**\n * Standalone mode: notified on every edit. Without it - and without a\n * provider to call `setCode` on - there is nothing to write an edit to, so\n * the editor renders read-only.\n */\n onChange?: (code: string) => void;\n /** Highlighting language. Defaults to the provider's, then `'tsx'`. */\n language?: string;\n /** Error to underline. Defaults to the provider's. */\n error?: Error | null;\n /**\n * A Prism instance with extra languages registered, for highlighting\n * anything outside `prism-react-renderer`'s built-in set.\n *\n * ```ts\n * import { Prism } from 'prism-react-renderer';\n * (globalThis as any).Prism = Prism;\n * await import('prismjs/components/prism-rust');\n * ```\n */\n prism?: typeof Prism;\n /**\n * Ring painted around the editor while it holds keyboard focus. Pass `null`\n * to disable - but then paint your own, or keyboard users cannot see where\n * they are (WCAG 2.4.7).\n */\n focusRingStyle?: CSSProperties | null;\n /** Overrides the default accessible name. */\n 'aria-label'?: string;\n /**\n * Replaces the built-in editor entirely - drop in CodeMirror, Monaco, or\n * anything else while keeping the rest of the provider wiring.\n */\n renderEditor?: (props: LiveEditorRenderProps) => ReactNode;\n}\n\nconst DEFAULT_ERROR_LINE_STYLE: CSSProperties = {\n background: 'rgba(179, 38, 30, 0.35)',\n boxShadow: 'inset 3px 0 0 #b3261e',\n};\n\n/**\n * Two pixels of high-contrast outline, offset so it reads against both the\n * editor background and the page behind it.\n */\nconst DEFAULT_FOCUS_RING_STYLE: CSSProperties = {\n outline: '2px solid #4d9fff',\n outlineOffset: '2px',\n};\n\n/**\n * Typography shared by the textarea and the highlighted layer beneath it.\n *\n * Every value here has to match in both layers or the visible text will drift\n * out of alignment with the caret - that is the whole difficulty of this\n * technique, so the styles live in one object rather than being repeated.\n */\nconst SHARED_TEXT_STYLE: CSSProperties = {\n margin: 0,\n border: 0,\n fontFamily:\n 'ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace',\n fontSize: '0.8125rem',\n lineHeight: 1.6,\n fontVariantLigatures: 'none',\n tabSize: 2,\n whiteSpace: 'pre',\n wordBreak: 'normal',\n overflowWrap: 'normal',\n};\n\n/** Hides the focus hint from sight while leaving it on the accessibility tree. */\nconst VISUALLY_HIDDEN: CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0 0 0 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * `:focus-visible` is what decides whether a focus ring should be painted, but\n * it can only be asked of a live element - and jsdom, plus a few older\n * engines, do not implement it. Treating an unanswerable question as \"yes\"\n * fails safe: a ring that shows after a mouse click is a cosmetic complaint,\n * a ring that never shows is an accessibility defect.\n */\nfunction prefersVisibleFocus(element: HTMLElement): boolean {\n try {\n return element.matches(':focus-visible');\n } catch {\n return true;\n }\n}\n\n/**\n * A code editor built from a transparent `<textarea>` layered over syntax\n * highlighted output.\n *\n * There is no editor engine here by design - that keeps the bundle small and\n * sidesteps the SSR problems full editors bring. When a real editor is wanted,\n * pass `renderEditor`.\n *\n * Usually rendered inside a `<LiveProvider>`, which supplies the code and\n * receives the edits. It also works standalone - pass `code`, and `onChange`\n * if it should be editable - which is how a page shows a highlighted,\n * read-only snippet without spinning up a provider to run it.\n */\nexport function LiveEditor(props: LiveEditorProps): ReactNode {\n const {\n className,\n style,\n theme = themes.vsDark,\n readOnly: readOnlyProp,\n tabSize = 2,\n padding = 16,\n errorLineStyle = DEFAULT_ERROR_LINE_STYLE,\n errorLineClassName,\n code: codeProp,\n onChange: onChangeProp,\n language: languageProp,\n error: errorProp,\n prism,\n focusRingStyle = DEFAULT_FOCUS_RING_STYLE,\n 'aria-label': ariaLabel = 'Live code editor',\n renderEditor,\n } = props;\n\n // Read the context directly rather than through `useLiveContext`, which\n // throws when there is no provider. Standalone rendering is supported, so\n // \"no provider\" is a valid state here rather than a mistake.\n const live = useContext(LiveContext);\n const highlightRef = useRef<HTMLPreElement>(null);\n const hintId = useId();\n\n /**\n * Set by Escape, cleared by anything else. While set, Tab moves focus\n * instead of indenting.\n *\n * A code editor has to swallow Tab to be usable, and swallowing Tab with no\n * way out is a keyboard trap - WCAG 2.1.2, a Level A failure. Escape-then-Tab\n * is the convention CodeMirror and Monaco use, and `aria-describedby` below\n * announces it, which 2.1.2 requires whenever the escape is non-obvious.\n */\n const [tabEscapes, setTabEscapes] = useState(false);\n const [focusRing, setFocusRing] = useState(false);\n\n if (codeProp === undefined && live === null) {\n throw new Error(\n '<LiveEditor> needs either a surrounding <LiveProvider> or a `code` prop. ' +\n 'Pass `code` (and `onChange` to make it editable) to use it standalone.',\n );\n }\n\n const code = codeProp ?? live?.code ?? '';\n const language = languageProp ?? live?.language ?? 'tsx';\n const error = errorProp !== undefined ? errorProp : (live?.error ?? null);\n\n // An editor with nowhere to send an edit is read-only whether it says so or\n // not; making that explicit keeps the textarea's behaviour honest and stops\n // keystrokes being silently swallowed.\n const setCode = onChangeProp ?? (codeProp === undefined ? live?.setCode : undefined);\n const readOnly = readOnlyProp ?? setCode === undefined;\n\n const position = error ? errorPosition(error) : null;\n const errorLine = position?.line;\n const errorColumn = position?.column;\n\n const handleChange = useCallback(\n (value: string) => {\n setCode?.(value);\n },\n [setCode],\n );\n\n const renderProps: LiveEditorRenderProps = {\n code,\n onChange: handleChange,\n language,\n error,\n errorLine,\n errorColumn,\n };\n\n if (renderEditor) {\n return <>{renderEditor(renderProps)}</>;\n }\n\n // The highlighted layer does not scroll on its own - it is not focusable -\n // so it has to be driven from the textarea to stay aligned.\n const syncScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {\n const node = highlightRef.current;\n if (!node) return;\n node.scrollTop = event.currentTarget.scrollTop;\n node.scrollLeft = event.currentTarget.scrollLeft;\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Escape') {\n // Arms the exit. Nothing is prevented: Escape has no meaning in a\n // textarea, but a dialog wrapping the editor may well want it.\n setTabEscapes(true);\n return;\n }\n\n if (event.key !== 'Tab') {\n // Any other key means the author kept typing, so re-arm indentation.\n // Modifier presses on their own are not \"typing\" and must not disarm the\n // exit - Shift+Tab fires a Shift keydown first, and disarming there\n // would make backwards escape impossible.\n if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {\n if (tabEscapes) setTabEscapes(false);\n }\n return;\n }\n\n // Tab. Let it through when armed, or when there is nothing to edit.\n if (tabEscapes || readOnly) {\n setTabEscapes(false);\n return;\n }\n\n event.preventDefault();\n const target = event.currentTarget;\n const { selectionStart, selectionEnd, value } = target;\n const indent = ' '.repeat(tabSize);\n const next = value.slice(0, selectionStart) + indent + value.slice(selectionEnd);\n handleChange(next);\n requestAnimationFrame(() => {\n target.selectionStart = target.selectionEnd = selectionStart + indent.length;\n });\n };\n\n const layerStyle: CSSProperties = {\n ...SHARED_TEXT_STYLE,\n tabSize,\n padding,\n gridArea: '1 / 1',\n minWidth: 0,\n minHeight: 0,\n overflow: 'auto',\n };\n\n return (\n <div\n className={className}\n style={{\n // A single-cell grid makes both layers size to the larger of the two,\n // which absolute positioning cannot do without a fixed height.\n display: 'grid',\n position: 'relative',\n background: theme.plain.backgroundColor ?? '#1e1e1e',\n ...style,\n // The ring is painted on the wrapper rather than the textarea, whose\n // own outline would be clipped by its scroll box. Applied after the\n // caller's `style` so it is not accidentally overwritten.\n ...(focusRing && focusRingStyle ? focusRingStyle : null),\n }}\n >\n <Highlight prism={prism} code={code} language={language} theme={theme}>\n {({ style: prismStyle, tokens, getLineProps, getTokenProps }) => (\n <pre\n ref={highlightRef}\n aria-hidden=\"true\"\n style={{ ...prismStyle, ...layerStyle }}\n >\n {tokens.map((line, i) => {\n const isErrorLine = errorLineStyle !== null && errorLine === i + 1;\n const lineProps = getLineProps({ line });\n return (\n // eslint-disable-next-line react/no-array-index-key\n <div\n key={i}\n {...lineProps}\n className={[lineProps.className, isErrorLine ? errorLineClassName : undefined]\n .filter(Boolean)\n .join(' ')}\n style={\n isErrorLine\n ? { ...lineProps.style, ...errorLineStyle }\n : lineProps.style\n }\n >\n {line.map((token, key) => (\n // eslint-disable-next-line react/no-array-index-key\n <span key={key} {...getTokenProps({ token })} />\n ))}\n </div>\n );\n })}\n </pre>\n )}\n </Highlight>\n\n <textarea\n value={code}\n onChange={(event) => handleChange(event.target.value)}\n onScroll={syncScroll}\n onKeyDown={handleKeyDown}\n onFocus={(event) => setFocusRing(prefersVisibleFocus(event.currentTarget))}\n onBlur={() => {\n setFocusRing(false);\n // Leaving and returning should not silently keep the exit armed.\n setTabEscapes(false);\n }}\n readOnly={readOnly}\n spellCheck={false}\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n autoComplete=\"off\"\n aria-label={ariaLabel}\n aria-describedby={readOnly ? undefined : hintId}\n aria-keyshortcuts={readOnly ? undefined : 'Escape'}\n style={{\n ...layerStyle,\n resize: 'none',\n // The wrapper paints the ring instead - see above.\n outline: 'none',\n background: 'transparent',\n // The text itself is invisible; only the highlighted layer is read.\n // The caret is painted separately so it stays visible.\n color: 'transparent',\n caretColor: theme.plain.color ?? '#fff',\n }}\n />\n\n {!readOnly && (\n <span id={hintId} style={VISUALLY_HIDDEN}>\n Tab inserts spaces. Press Escape and then Tab to move focus out of the\n editor.\n </span>\n )}\n </div>\n );\n}\n"]}
|