Files
webstudio/apps/builder/app/canvas/instance-selection.ts
T

89 lines
2.9 KiB
TypeScript

import { getInstanceSelectorFromElement } from "~/shared/dom-utils";
import { findClosestEditableInstanceSelector } from "~/shared/instance-utils";
import {
$instances,
$registeredComponentMetas,
$selectedInstanceSelector,
} from "~/shared/nano-states";
import { $textEditingInstanceSelector } from "~/shared/nano-states";
import { emitCommand } from "./shared/commands";
export const subscribeInstanceSelection = () => {
let pointerDownElement: undefined | Element = undefined;
let lastPointerUpTime = 0;
let clickCount = 1;
const handlePointerDown = (event: PointerEvent) => {
pointerDownElement = event.target as Element;
// track multiple clicks when pointerdown is fired within 500ms after last pointerup
const currentTime = performance.now();
if (currentTime - lastPointerUpTime < 500) {
clickCount += 1;
} else {
clickCount = 1;
}
};
const handlePointerUp = (event: PointerEvent) => {
const element = event.target as Element;
// when user is selecting text inside content editable and mouse goes up
// on a different instance - we don't want to select a different instance
// because that would cancel the text selection.
if (pointerDownElement === undefined || pointerDownElement !== element) {
return;
}
pointerDownElement = undefined;
// track double clicks manually because pointer events do not support event.detail
// for clicks count
lastPointerUpTime = performance.now();
if (clickCount === 1) {
// notify whole app about click on document
// e.g. to hide the side panel
emitCommand("clickCanvas");
}
// prevent selecting instances inside text editor while editing text
if (element.closest("[contenteditable=true]")) {
return;
}
const instanceSelector = getInstanceSelectorFromElement(element);
if (instanceSelector === undefined) {
return;
}
// the first click in double click or the only one in regular click
if (clickCount === 1) {
$selectedInstanceSelector.set(instanceSelector);
// reset text editor when another instance is selected
$textEditingInstanceSelector.set(undefined);
}
// the second click in a double click.
if (clickCount === 2) {
const editableInstanceSelector = findClosestEditableInstanceSelector(
instanceSelector,
$instances.get(),
$registeredComponentMetas.get()
);
// enable text editor when double click on its instance or one of its descendants
if (editableInstanceSelector) {
$selectedInstanceSelector.set(editableInstanceSelector);
$textEditingInstanceSelector.set(editableInstanceSelector);
}
}
};
addEventListener("pointerdown", handlePointerDown, { passive: true });
addEventListener("pointerup", handlePointerUp, { passive: true });
return () => {
removeEventListener("pointerdown", handlePointerDown);
removeEventListener("pointerup", handlePointerUp);
};
};