Track view-scoped overlay placements and retain portalled editors

This commit is contained in:
2026-11-09 18:28:13 +08:00
parent e6b9999202
commit 2f385433be
9 changed files with 73 additions and 6 deletions
+3 -1
View File
@@ -7,16 +7,18 @@ import { AlignCenter, AlignLeft, AlignRight, Bold, Italic, RotateCw, Underline }
import type { CanvasNode } from "@/lib/canvas";
import type { TextStyle } from "@/lib/canvas-elements";
import { NodeEditing, ResizeToggle } from "./transformable-node";
import {useCanvasOverlay} from "./canvas-overlays";
import styles from "./canvas.module.css";
export function NodeStyleToolbar({ id, data, selected }: NodeProps<CanvasNode>) {
const overlay=useCanvasOverlay(id,"node-style");
const reduce = useReducedMotion();
const editing = useContext(NodeEditing);
const style = data.textStyle || {};
const text = data.kind === "text" || data.kind === "script" || data.kind === "plain-text";
const patch = (value: TextStyle, fontSize?: number) => editing.style(id, value, fontSize);
return <NodeToolbar style={{ zIndex: 100, pointerEvents: "auto" }} isVisible={true} position={Position.Top} offset={34}>
<AnimatePresence>{selected && <motion.div initial={{ opacity: 0, y: reduce ? 0 : 5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: reduce ? 0 : 5 }} transition={{ duration: reduce ? 0 : 0.14 }} className={`${styles.styleToolbar} nodrag nopan nowheel`} data-node-style={id} role="toolbar" aria-label="元素样式"
<AnimatePresence>{selected && <motion.div ref={overlay} initial={{ opacity: 0, y: reduce ? 0 : 5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: reduce ? 0 : 5 }} transition={{ duration: reduce ? 0 : 0.14 }} className={`${styles.styleToolbar} nodrag nopan nowheel`} data-node-style={id} role="toolbar" aria-label="元素样式"
onPointerDown={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()} onDoubleClick={(event) => event.stopPropagation()}
onKeyDown={(event) => { if (event.key !== "Escape") event.stopPropagation(); }}>
{!(["image", "plain-image"].includes(data.kind)) && <label title="文字或图标颜色" className={styles.colorPicker}><span style={{ background: style.color || "#37352f" }} /><input aria-label="文字或图标颜色" type="color" value={style.color || "#37352f"} onChange={(event) => patch({ color: event.target.value })} /></label>}