tldraw
Version:
A tiny little drawing editor.
163 lines (162 loc) • 4.72 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import { preventDefault, track, useEditor } from "@tldraw/editor";
import {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState
} from "react";
import { useTranslation } from "../hooks/useTranslation/useTranslation.mjs";
const CHAT_MESSAGE_TIMEOUT_CLOSING = 2e3;
const CHAT_MESSAGE_TIMEOUT_CHATTING = 5e3;
const CursorChatBubble = track(function CursorChatBubble2() {
const editor = useEditor();
const { isChatting, chatMessage } = editor.getInstanceState();
const rTimeout = useRef(-1);
const [value, setValue] = useState("");
useEffect(() => {
const closingUp = !isChatting && chatMessage;
if (closingUp || isChatting) {
const duration = isChatting ? CHAT_MESSAGE_TIMEOUT_CHATTING : CHAT_MESSAGE_TIMEOUT_CLOSING;
rTimeout.current = editor.timers.setTimeout(() => {
editor.updateInstanceState({ chatMessage: "", isChatting: false });
setValue("");
editor.focus();
}, duration);
}
return () => {
clearTimeout(rTimeout.current);
};
}, [editor, chatMessage, isChatting]);
if (isChatting)
return /* @__PURE__ */ jsx(CursorChatInput, { value, setValue, chatMessage });
return chatMessage.trim() ? /* @__PURE__ */ jsx(NotEditingChatMessage, { chatMessage }) : null;
});
function usePositionBubble(ref) {
const editor = useEditor();
useLayoutEffect(() => {
const elm = ref.current;
if (!elm) return;
const { x, y } = editor.inputs.currentScreenPoint;
ref.current?.style.setProperty("transform", `translate(${x}px, ${y}px)`);
function positionChatBubble(e) {
const { minX, minY } = editor.getViewportScreenBounds();
ref.current?.style.setProperty(
"transform",
`translate(${e.clientX - minX}px, ${e.clientY - minY}px)`
);
}
window.addEventListener("pointermove", positionChatBubble);
return () => {
window.removeEventListener("pointermove", positionChatBubble);
};
}, [ref, editor]);
}
const NotEditingChatMessage = ({ chatMessage }) => {
const editor = useEditor();
const ref = useRef(null);
usePositionBubble(ref);
return /* @__PURE__ */ jsx(
"div",
{
ref,
className: "tl-cursor-chat tl-cursor-chat__bubble",
style: { backgroundColor: editor.user.getColor() },
children: chatMessage
}
);
};
const CursorChatInput = track(function CursorChatInput2({
chatMessage,
value,
setValue
}) {
const editor = useEditor();
const msg = useTranslation();
const ref = useRef(null);
const placeholder = chatMessage || msg("cursor-chat.type-to-chat");
usePositionBubble(ref);
useLayoutEffect(() => {
const elm = ref.current;
if (!elm) return;
const textMeasurement = editor.textMeasure.measureText(value || placeholder, {
fontFamily: "var(--font-body)",
fontSize: 12,
fontWeight: "500",
fontStyle: "normal",
maxWidth: null,
lineHeight: 1,
padding: "6px"
});
elm.style.setProperty("width", textMeasurement.w + "px");
}, [editor, value, placeholder]);
useLayoutEffect(() => {
const raf = editor.timers.requestAnimationFrame(() => {
ref.current?.focus();
});
return () => {
cancelAnimationFrame(raf);
};
}, [editor]);
const stopChatting = useCallback(() => {
editor.updateInstanceState({ isChatting: false });
editor.focus();
}, [editor]);
const handleChange = useCallback(
(e) => {
const { value: value2 } = e.target;
setValue(value2.slice(0, 64));
editor.updateInstanceState({ chatMessage: value2 });
},
[editor, setValue]
);
const handleKeyDown = useCallback(
(e) => {
const elm = ref.current;
if (!elm) return;
const { value: currentValue } = elm;
switch (e.key) {
case "Enter": {
preventDefault(e);
e.stopPropagation();
if (!currentValue) {
stopChatting();
return;
}
setValue("");
break;
}
case "Escape": {
preventDefault(e);
e.stopPropagation();
stopChatting();
break;
}
}
},
[stopChatting, setValue]
);
const handlePaste = useCallback((e) => {
e.stopPropagation();
}, []);
return /* @__PURE__ */ jsx(
"input",
{
ref,
className: `tl-cursor-chat`,
style: { backgroundColor: editor.user.getColor() },
onBlur: stopChatting,
onChange: handleChange,
onKeyDown: handleKeyDown,
onPaste: handlePaste,
value,
placeholder,
spellCheck: false
}
);
});
export {
CursorChatBubble
};
//# sourceMappingURL=CursorChatBubble.mjs.map