@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
205 lines (204 loc) • 6.77 kB
JavaScript
import { useValue } from "@tldraw/state-react";
import { useEffect } from "react";
import { activeElementShouldCaptureKeys, preventDefault, stopEventPropagation } from "../utils/dom.mjs";
import { isAccelKey } from "../utils/keyboard.mjs";
import { useContainer } from "./useContainer.mjs";
import { useEditor } from "./useEditor.mjs";
function useDocumentEvents() {
const editor = useEditor();
const container = useContainer();
const isEditing = useValue("isEditing", () => editor.getEditingShapeId(), [editor]);
const isAppFocused = useValue("isFocused", () => editor.getIsFocused(), [editor]);
useEffect(() => {
if (!container) return;
function onDrop(e) {
if (e.isSpecialRedispatchedEvent) return;
preventDefault(e);
stopEventPropagation(e);
const cvs = container.querySelector(".tl-canvas");
if (!cvs) return;
const newEvent = new DragEvent(e.type, e);
newEvent.isSpecialRedispatchedEvent = true;
cvs.dispatchEvent(newEvent);
}
container.addEventListener("dragover", onDrop);
container.addEventListener("drop", onDrop);
return () => {
container.removeEventListener("dragover", onDrop);
container.removeEventListener("drop", onDrop);
};
}, [container]);
useEffect(() => {
if (typeof window === "undefined" || !("matchMedia" in window)) return;
let remove = null;
const updatePixelRatio = () => {
if (remove != null) {
remove();
}
const mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
const media = matchMedia(mqString);
const safariCb = (ev) => {
if (ev.type === "change") {
updatePixelRatio();
}
};
if (media.addEventListener) {
media.addEventListener("change", updatePixelRatio);
} else if (media.addListener) {
media.addListener(safariCb);
}
remove = () => {
if (media.removeEventListener) {
media.removeEventListener("change", updatePixelRatio);
} else if (media.removeListener) {
media.removeListener(safariCb);
}
};
editor.updateInstanceState({ devicePixelRatio: window.devicePixelRatio });
};
updatePixelRatio();
return () => {
remove?.();
};
}, [editor]);
useEffect(() => {
if (!isAppFocused) return;
const handleKeyDown = (e) => {
if (e.altKey && // todo: When should we allow the alt key to be used? Perhaps states should declare which keys matter to them?
(editor.isIn("zoom") || !editor.getPath().endsWith(".idle")) && !areShortcutsDisabled(editor)) {
preventDefault(e);
}
if (e.isKilled) return;
e.isKilled = true;
const hasSelectedShapes = !!editor.getSelectedShapeIds().length;
switch (e.key) {
case "=":
case "-":
case "0": {
if (e.metaKey || e.ctrlKey) {
preventDefault(e);
return;
}
break;
}
case "Tab": {
if (areShortcutsDisabled(editor)) {
return;
}
if (hasSelectedShapes && !isEditing) {
preventDefault(e);
}
break;
}
case "ArrowLeft":
case "ArrowRight":
case "ArrowUp":
case "ArrowDown": {
if (areShortcutsDisabled(editor)) {
return;
}
if (hasSelectedShapes && (e.metaKey || e.ctrlKey)) {
preventDefault(e);
}
break;
}
case ",": {
return;
}
case "Escape": {
if (editor.getEditingShape() || editor.getSelectedShapeIds().length > 0) {
preventDefault(e);
}
if (editor.menus.getOpenMenus().length > 0) return;
if (editor.inputs.keys.has("Escape")) {
} else {
editor.inputs.keys.add("Escape");
editor.cancel();
container.focus();
}
return;
}
default: {
if (areShortcutsDisabled(editor)) {
return;
}
}
}
const info = {
type: "keyboard",
name: e.repeat ? "key_repeat" : "key_down",
key: e.key,
code: e.code,
shiftKey: e.shiftKey,
altKey: e.altKey,
ctrlKey: e.metaKey || e.ctrlKey,
metaKey: e.metaKey,
accelKey: isAccelKey(e)
};
editor.dispatch(info);
};
const handleKeyUp = (e) => {
if (e.isKilled) return;
e.isKilled = true;
if (areShortcutsDisabled(editor)) {
return;
}
if (e.key === ",") {
return;
}
const info = {
type: "keyboard",
name: "key_up",
key: e.key,
code: e.code,
shiftKey: e.shiftKey,
altKey: e.altKey,
ctrlKey: e.metaKey || e.ctrlKey,
metaKey: e.metaKey,
accelKey: isAccelKey(e)
};
editor.dispatch(info);
};
function handleTouchStart(e) {
if (container.contains(e.target)) {
const touchXPosition = e.touches[0].pageX;
const touchXRadius = e.touches[0].radiusX || 0;
if (touchXPosition - touchXRadius < 10 || touchXPosition + touchXRadius > editor.getViewportScreenBounds().width - 10) {
if (e.target?.tagName === "BUTTON") {
;
e.target?.click();
}
preventDefault(e);
}
}
}
const handleWheel = (e) => {
if (container.contains(e.target) && (e.ctrlKey || e.metaKey)) {
preventDefault(e);
}
};
container.addEventListener("touchstart", handleTouchStart, { passive: false });
container.addEventListener("wheel", handleWheel, { passive: false });
document.addEventListener("gesturestart", preventDefault);
document.addEventListener("gesturechange", preventDefault);
document.addEventListener("gestureend", preventDefault);
container.addEventListener("keydown", handleKeyDown);
container.addEventListener("keyup", handleKeyUp);
return () => {
container.removeEventListener("touchstart", handleTouchStart);
container.removeEventListener("wheel", handleWheel);
document.removeEventListener("gesturestart", preventDefault);
document.removeEventListener("gesturechange", preventDefault);
document.removeEventListener("gestureend", preventDefault);
container.removeEventListener("keydown", handleKeyDown);
container.removeEventListener("keyup", handleKeyUp);
};
}, [editor, container, isAppFocused, isEditing]);
}
function areShortcutsDisabled(editor) {
return editor.menus.hasOpenMenus() || activeElementShouldCaptureKeys();
}
export {
useDocumentEvents
};
//# sourceMappingURL=useDocumentEvents.mjs.map