38 lines
2.6 KiB
TypeScript
38 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import type { CanvasNodeData } from "@/lib/canvas-elements";
|
|
import {useScopedNodeEditing} from "./node-editing";
|
|
import { textAppearance } from "./text-appearance";
|
|
import styles from "./canvas.module.css";
|
|
|
|
/** Single clicks select; native double-click timing controls entry into editing. */
|
|
export function CanvasTextEditor({ id, data, selected, plain = false }: { id: string; data: CanvasNodeData; selected: boolean; plain?: boolean }) {
|
|
const actions = useScopedNodeEditing(id,data.__headlessTarget);
|
|
const [editing, setEditing] = useState(false);
|
|
const textarea = useRef<HTMLTextAreaElement>(null);
|
|
const press = useRef({ x: 0, y: 0, moved: false });
|
|
useEffect(() => { if (!selected) setEditing(false); }, [selected]);
|
|
useEffect(() => {
|
|
if (!editing) return;
|
|
const element = textarea.current;
|
|
element?.focus({ preventScroll: true });
|
|
element?.setSelectionRange(element.value.length, element.value.length);
|
|
}, [editing]);
|
|
function begin() { actions.begin(id); setEditing(true); }
|
|
const active = editing && selected;
|
|
const label = plain ? "纯文本" : data.title;
|
|
return <div className={`${styles.textEditor} ${active ? "nodrag" : ""}`}>
|
|
<textarea ref={textarea} style={textAppearance(data)} className={`${plain ? styles.plainText : styles.cardText} nodrag nowheel`}
|
|
aria-label={label} readOnly={!active} tabIndex={active ? 0 : -1}
|
|
placeholder={plain ? "写下文字…" : data.kind === "script" ? "写下你的脚本…" : "留住一个想法…"}
|
|
value={data.text} maxLength={100000} onChange={(event) => actions.edit(id, event.target.value)}
|
|
onBlur={() => { setEditing(false); actions.endEditing?.(id); }} onKeyDown={(event) => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); setEditing(false); event.currentTarget.blur(); } }} />
|
|
{!active && <div className={styles.textSelectLayer} role="button" tabIndex={selected ? 0 : -1} aria-label={`单击选中,双击编辑${label}`}
|
|
onPointerDown={(event) => { press.current = { x: event.clientX, y: event.clientY, moved: false }; }}
|
|
onPointerMove={(event) => { if (Math.hypot(event.clientX - press.current.x, event.clientY - press.current.y) > 4) press.current.moved = true; }}
|
|
onDoubleClick={(event) => { if (!press.current.moved) { event.preventDefault(); event.stopPropagation(); if (!selected) actions.select(id); begin(); } }}
|
|
onKeyDown={(event) => { if (selected && (event.key === "Enter" || event.key === "F2")) { event.preventDefault(); event.stopPropagation(); begin(); } }} />}
|
|
</div>;
|
|
}
|