mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-29 16:17:39 +09:00
89 lines
2.9 KiB
TypeScript
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);
|
|
};
|
|
};
|