mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-30 00:27:43 +09:00
Ref https://github.com/webstudio-is/webstudio/issues/3632 Here I finally got rid from "rich-text-child" necessity. With html elements in the tree it will no longer work so we need a new way to work with rich text. Added a few new utilities to content model which detect rich text by text inside or any textual tags. Span and links can still be detected as containers rather than children when not surrounded by text and other textual elements.
156 lines
3.7 KiB
TypeScript
156 lines
3.7 KiB
TypeScript
import { getInstanceSelectorFromElement } from "~/shared/dom-utils";
|
|
import {
|
|
$hoveredInstanceOutline,
|
|
$hoveredInstanceSelector,
|
|
$instances,
|
|
$isContentMode,
|
|
$props,
|
|
$registeredComponentMetas,
|
|
} from "~/shared/nano-states";
|
|
import { $textEditingInstanceSelector } from "~/shared/nano-states";
|
|
import { emitCommand } from "./shared/commands";
|
|
import { shallowEqual } from "shallow-equal";
|
|
import { $awareness, selectInstance } from "~/shared/awareness";
|
|
import { findClosestRichText } from "~/shared/content-model";
|
|
|
|
const isElementBeingEdited = (element: Element) => {
|
|
if (element.closest("[contenteditable=true]")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const handleSelect = (event: MouseEvent) => {
|
|
const element = event.target;
|
|
|
|
if (!(element instanceof Element)) {
|
|
return;
|
|
}
|
|
|
|
if (isElementBeingEdited(element)) {
|
|
return;
|
|
}
|
|
|
|
const instanceSelector = getInstanceSelectorFromElement(element);
|
|
|
|
if (instanceSelector === undefined) {
|
|
return;
|
|
}
|
|
|
|
// Prevent unnecessary updates (2 clicks are registered before a double click)
|
|
if ($textEditingInstanceSelector.get() !== undefined) {
|
|
$textEditingInstanceSelector.set(undefined);
|
|
}
|
|
|
|
// Prevent unnecessary updates (2 clicks are registered before a double click)
|
|
if (!shallowEqual(instanceSelector, $awareness.get()?.instanceSelector)) {
|
|
selectInstance(instanceSelector);
|
|
}
|
|
};
|
|
|
|
const handleEdit = (event: MouseEvent) => {
|
|
const element = event.target;
|
|
|
|
if (!(element instanceof Element)) {
|
|
return;
|
|
}
|
|
|
|
if (isElementBeingEdited(element)) {
|
|
return;
|
|
}
|
|
|
|
const instanceSelector = getInstanceSelectorFromElement(element);
|
|
|
|
if (instanceSelector === undefined) {
|
|
return;
|
|
}
|
|
|
|
const instances = $instances.get();
|
|
|
|
let editableInstanceSelector = findClosestRichText({
|
|
instanceSelector,
|
|
instances,
|
|
props: $props.get(),
|
|
metas: $registeredComponentMetas.get(),
|
|
});
|
|
|
|
// Do not allow edit bindable text instances with expression children in Content Mode
|
|
if (editableInstanceSelector !== undefined && $isContentMode.get()) {
|
|
const instance = instances.get(editableInstanceSelector[0]);
|
|
if (instance === undefined) {
|
|
return false;
|
|
}
|
|
|
|
const hasExpressionChildren = instance.children.some(
|
|
(child) => child.type === "expression"
|
|
);
|
|
|
|
if (hasExpressionChildren) {
|
|
editableInstanceSelector = undefined;
|
|
}
|
|
}
|
|
|
|
if (editableInstanceSelector === undefined) {
|
|
// Handle non-editable instances in Content Mode:
|
|
|
|
// Reset editing state when clicking from an editable text to a non-editable instance
|
|
if ($textEditingInstanceSelector.get() !== undefined) {
|
|
$textEditingInstanceSelector.set(undefined);
|
|
}
|
|
|
|
// Select the instance when no editable parent is found
|
|
if (!shallowEqual(instanceSelector, $awareness.get()?.instanceSelector)) {
|
|
selectInstance(instanceSelector);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Avoid redundant selection if the instance is already selected
|
|
if (
|
|
!shallowEqual($awareness.get()?.instanceSelector, editableInstanceSelector)
|
|
) {
|
|
selectInstance(editableInstanceSelector);
|
|
}
|
|
|
|
$hoveredInstanceOutline.set(undefined);
|
|
$hoveredInstanceSelector.set(undefined);
|
|
|
|
$textEditingInstanceSelector.set({
|
|
selector: editableInstanceSelector,
|
|
reason: "click",
|
|
mouseX: event.clientX,
|
|
mouseY: event.clientY,
|
|
});
|
|
};
|
|
|
|
export const subscribeInstanceSelection = ({
|
|
signal,
|
|
}: {
|
|
signal: AbortSignal;
|
|
}) => {
|
|
addEventListener(
|
|
"click",
|
|
(event) => {
|
|
emitCommand("clickCanvas");
|
|
|
|
if ($isContentMode.get()) {
|
|
handleEdit(event);
|
|
return;
|
|
}
|
|
|
|
handleSelect(event);
|
|
},
|
|
{ passive: true, signal }
|
|
);
|
|
|
|
addEventListener(
|
|
"dblclick",
|
|
(event) => {
|
|
handleEdit(event);
|
|
},
|
|
{ passive: true, signal }
|
|
);
|
|
};
|