import { Point } from "@noya-app/noya-geometry"; import { SelectionRange } from "../utils/textSelection"; import { createCanvasPlugin } from "./baseCanvas"; import { BaseContextProperties, ElementProperties, HandleKeyboardEventProperties, ScreenPointProperties, TextEditableProperties, } from "./contextProperties"; import { modeSwitch, NoneMode } from "./PluginSystem"; import { PointerData } from "./pointerPlugin"; type EditingTextMode = { editingElementId: string; range: SelectionRange | null; type: "editingText"; }; type DomTextMode = NoneMode | EditingTextMode; type DomTextData = { zoom: number; } & PointerData; interface DomTextContextProperties extends BaseContextProperties, HandleKeyboardEventProperties, ScreenPointProperties, ElementProperties, TextEditableProperties { mouseDown: ({ clientPoint, elementId, }: { clientPoint: Point; elementId: string; }) => void; mouseMove: ({ clientPoint, elementId, }: { clientPoint: Point; elementId: string; }) => void; mouseUp: ({ clientPoint, elementId, }: { clientPoint: Point; elementId: string; }) => void; onBlur: (elementId: string) => void; } export type DomTextPluginOptions = {}; export function domTextPlugin( _options: DomTextPluginOptions = {} ) { return createCanvasPlugin({ name: "editText", data: { zoom: 1, clickCount: 0, lastClickTime: 0, }, handlers: (context: DomTextContextProperties) => ({ onPointerDown(event) { modeSwitch(context.getMode(), { none: () => { const data = context.getData(); if ((data.clickCount ?? 1) < 2) return; const point = context.getCanvasPoint(event.nativeEvent); const elements = context.filterElements({ at: point, predicate: context.isTextEditable, }); if (elements.length === 0) return; const elementId = context.getElementId(elements[0]); context.setMode({ type: "editingText", editingElementId: elementId, range: null, }); context.setShouldHandleKeyboardEvents(false); event.preventDefault(); context.mouseDown({ clientPoint: { x: event.clientX, y: event.clientY, }, elementId, }); }, editingText: (state) => { const point = context.getCanvasPoint(event.nativeEvent); const elements = context.filterElements({ at: point, predicate: context.isTextEditable, }); if (elements.length === 0) { context.setMode({ type: "none", }); context.onBlur(state.editingElementId); context.setShouldHandleKeyboardEvents(true); return; } const elementId = context.getElementId(elements[0]); if (elementId !== state.editingElementId) { context.setMode({ type: "none", }); context.onBlur(state.editingElementId); context.setShouldHandleKeyboardEvents(true); return; } event.preventDefault(); context.mouseDown({ clientPoint: { x: event.clientX, y: event.clientY, }, elementId: state.editingElementId, }); }, }); }, onPointerMove(event) { modeSwitch(context.getMode(), { editingText: (state) => { const point = { x: event.clientX, y: event.clientY, }; event.preventDefault(); context.mouseMove({ clientPoint: point, elementId: state.editingElementId, }); }, }); }, onPointerUp(event) { modeSwitch(context.getMode(), { editingText: (state) => { const point = { x: event.clientX, y: event.clientY, }; event.preventDefault(); context.mouseUp({ clientPoint: point, elementId: state.editingElementId, }); return; }, }); }, }), }); }