Move the highlightable textarea into its own component.
This commit is contained in:
parent
33ed9c4f56
commit
d158fafd77
52
src/Editor.module.css
Normal file
52
src/Editor.module.css
Normal file
@ -0,0 +1,52 @@
|
||||
.EditorTextWrapper {
|
||||
/* TODO: This flex should be in the parent */
|
||||
flex: 1;
|
||||
flex-basis: 0;
|
||||
position: relative;
|
||||
background: white;
|
||||
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas,
|
||||
"DejaVu Sans Mono", monospace;
|
||||
font-weight: normal;
|
||||
font-size: 1.3rem;
|
||||
text-align: initial;
|
||||
}
|
||||
|
||||
.EditorTextArea,
|
||||
.EditorUnderlay {
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.EditorTextArea {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
resize: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.EditorUnderlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
color: transparent;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-y: scroll;
|
||||
|
||||
.EditorHighlighted {
|
||||
background: #ffff00;
|
||||
}
|
||||
}
|
4
src/Editor.module.css.d.ts
vendored
Normal file
4
src/Editor.module.css.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export const EditorTextWrapper: string;
|
||||
export const EditorTextArea: string;
|
||||
export const EditorUnderlay: string;
|
||||
export const EditorHighlighted: string;
|
@ -0,0 +1,54 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Highlight } from "./highlight";
|
||||
import { buildShadow } from "./shadow";
|
||||
import styles from "./Editor.module.css";
|
||||
|
||||
interface EditorProps {
|
||||
value: string;
|
||||
setValue: Function;
|
||||
highlights: Highlight[];
|
||||
clearHighlights: Function;
|
||||
}
|
||||
function Editor({
|
||||
value,
|
||||
setValue,
|
||||
highlights,
|
||||
clearHighlights,
|
||||
}: EditorProps): React.ReactNode {
|
||||
function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
setValue(event.target.value);
|
||||
clearHighlights();
|
||||
}
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const shadowRef = useRef<HTMLDivElement>(null);
|
||||
function onTextAreaScroll() {
|
||||
if (shadowRef.current !== null && textAreaRef.current !== null) {
|
||||
const textAreaScrollTop = textAreaRef.current.scrollTop;
|
||||
shadowRef.current.scrollTop = textAreaScrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Make sure the text area and shadow div start out in sync.
|
||||
onTextAreaScroll();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.EditorTextWrapper}>
|
||||
<textarea
|
||||
ref={textAreaRef}
|
||||
onChange={handleChange}
|
||||
className={styles.EditorTextArea}
|
||||
value={value}
|
||||
onScroll={onTextAreaScroll}
|
||||
/>
|
||||
<div ref={shadowRef} className={styles.EditorUnderlay}>
|
||||
{buildShadow(highlights, value)}
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Editor;
|
@ -4,55 +4,3 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Explorer-textwrapper {
|
||||
flex: 1;
|
||||
flex-basis: 0;
|
||||
position: relative;
|
||||
background: white;
|
||||
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas,
|
||||
"DejaVu Sans Mono", monospace;
|
||||
font-weight: normal;
|
||||
font-size: 1.3rem;
|
||||
text-align: initial;
|
||||
}
|
||||
|
||||
.Explorer-textarea,
|
||||
.Explorer-underlay {
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.Explorer-textarea {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
resize: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.Explorer-underlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
color: transparent;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-y: scroll;
|
||||
|
||||
.highlighted {
|
||||
background: #ffff00;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import "./Explorer.css";
|
||||
import { Highlight } from "./highlight";
|
||||
import { buildShadow } from "./shadow";
|
||||
import OrgAst, { OrgNodeReference } from "./OrgAst";
|
||||
import { parse_org } from "../../organic/target/wasm32-unknown-unknown/js/wasm";
|
||||
import Editor from "./Editor";
|
||||
|
||||
const default_org_source: string = `* Welcome to the Organic Ast Explorer!
|
||||
|
||||
@ -25,11 +25,6 @@ interface ExplorerProps {
|
||||
defaultValue?: string;
|
||||
}
|
||||
function Explorer({ defaultValue = default_org_source }: ExplorerProps) {
|
||||
function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
setValue(event.target.value);
|
||||
clearHighlights();
|
||||
}
|
||||
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
const [highlights, setHighlights] = useState<Array<Highlight>>([]);
|
||||
@ -66,35 +61,15 @@ function Explorer({ defaultValue = default_org_source }: ExplorerProps) {
|
||||
setHighlights([]);
|
||||
}
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const shadowRef = useRef<HTMLDivElement>(null);
|
||||
function onTextAreaScroll() {
|
||||
if (shadowRef.current !== null && textAreaRef.current !== null) {
|
||||
const textAreaScrollTop = textAreaRef.current.scrollTop;
|
||||
shadowRef.current.scrollTop = textAreaScrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Make sure the text area and shadow div start out in sync.
|
||||
onTextAreaScroll();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="Explorer">
|
||||
<div className="Explorer-textwrapper">
|
||||
<textarea
|
||||
ref={textAreaRef}
|
||||
onChange={handleChange}
|
||||
className="Explorer-textarea"
|
||||
value={value}
|
||||
onScroll={onTextAreaScroll}
|
||||
/>
|
||||
<div ref={shadowRef} className="Explorer-underlay">
|
||||
{buildShadow(highlights, value)}
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<Editor
|
||||
value={value}
|
||||
setValue={setValue}
|
||||
highlights={highlights}
|
||||
clearHighlights={clearHighlights}
|
||||
/>
|
||||
<OrgAst
|
||||
setHighlight={setHighlight}
|
||||
clearHighlights={clearHighlights}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { ReactNode, useState } from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import { Highlight } from "./highlight";
|
||||
import styles from "./Editor.module.css";
|
||||
|
||||
function buildShadow(highlights: Highlight[], text: string): ReactNode[] {
|
||||
let output: ReactNode[] = [];
|
||||
@ -22,7 +23,7 @@ function buildShadow(highlights: Highlight[], text: string): ReactNode[] {
|
||||
} else if (state == ShadowState.Highlight && !thisCharHighlighted) {
|
||||
// End the span
|
||||
output.push(
|
||||
<span key={i} className="highlighted">
|
||||
<span key={i} className={styles.EditorHighlighted}>
|
||||
{buffer}
|
||||
</span>,
|
||||
);
|
||||
@ -37,7 +38,7 @@ function buildShadow(highlights: Highlight[], text: string): ReactNode[] {
|
||||
output.push(buffer);
|
||||
} else if (state == ShadowState.Highlight) {
|
||||
output.push(
|
||||
<span key={i} className="highlighted">
|
||||
<span key={i} className={styles.EditorHighlighted}>
|
||||
{buffer}
|
||||
</span>,
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user