mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-29 16:17:39 +09:00
## Description Pure renaming, nothing else. ## Code Review - [ ] hi @TrySound , I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env.example` and the `builder/env-check.js` if mandatory
125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
import { idAttribute } from "@webstudio-is/react-sdk";
|
|
import { $hoveredInstanceSelector, $instances } from "~/shared/nano-states";
|
|
import { $hoveredInstanceOutline } from "~/shared/nano-states";
|
|
import {
|
|
getAllElementsBoundingBox,
|
|
getElementsByInstanceSelector,
|
|
getInstanceSelectorFromElement,
|
|
} from "~/shared/dom-utils";
|
|
import { subscribeScrollState } from "./shared/scroll-state";
|
|
import type { InstanceSelector } from "~/shared/tree-utils";
|
|
|
|
type TimeoutId = undefined | ReturnType<typeof setTimeout>;
|
|
|
|
export const subscribeInstanceHovering = () => {
|
|
let hoveredElement: undefined | Element = undefined;
|
|
let isScrolling = false;
|
|
|
|
const updateHoveredInstance = (element: Element) => {
|
|
const instanceSelector = getInstanceSelectorFromElement(element);
|
|
if (instanceSelector) {
|
|
$hoveredInstanceSelector.set(instanceSelector);
|
|
}
|
|
};
|
|
|
|
let mouseOutTimeoutId: TimeoutId = undefined;
|
|
|
|
const handleMouseOver = (event: MouseEvent) => {
|
|
if (event.target instanceof Element) {
|
|
const element = event.target.closest(`[${idAttribute}]`) ?? undefined;
|
|
if (element !== undefined) {
|
|
clearTimeout(mouseOutTimeoutId);
|
|
// store hovered element locally to update outline when scroll ends
|
|
hoveredElement = element;
|
|
updateHoveredInstance(element);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleMouseOut = () => {
|
|
mouseOutTimeoutId = setTimeout(() => {
|
|
hoveredElement = undefined;
|
|
$hoveredInstanceSelector.set(undefined);
|
|
$hoveredInstanceOutline.set(undefined);
|
|
}, 100);
|
|
|
|
// Fixes the bug, that new hover occures during timeout
|
|
const unsubscribe = $hoveredInstanceSelector.listen(() => {
|
|
clearTimeout(mouseOutTimeoutId);
|
|
unsubscribe();
|
|
});
|
|
};
|
|
|
|
const eventOptions = { passive: true };
|
|
window.addEventListener("mouseover", handleMouseOver, eventOptions);
|
|
window.addEventListener("mouseout", handleMouseOut, eventOptions);
|
|
|
|
const updateHoveredRect = (
|
|
elements: Element[],
|
|
instanceSelector: Readonly<InstanceSelector>
|
|
) => {
|
|
if (elements.length === 0) {
|
|
return;
|
|
}
|
|
|
|
if (instanceSelector.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const [instanceId] = instanceSelector;
|
|
const instances = $instances.get();
|
|
const instance = instances.get(instanceId);
|
|
if (instance === undefined) {
|
|
return;
|
|
}
|
|
|
|
if (!isScrolling) {
|
|
$hoveredInstanceOutline.set({
|
|
instanceId: instance.id,
|
|
rect: getAllElementsBoundingBox(elements),
|
|
});
|
|
}
|
|
};
|
|
|
|
// remove hover outline when scroll starts
|
|
// and show it with new rect when scroll ends
|
|
const unsubscribeScrollState = subscribeScrollState({
|
|
onScrollStart() {
|
|
isScrolling = true;
|
|
$hoveredInstanceOutline.set(undefined);
|
|
},
|
|
onScrollEnd() {
|
|
isScrolling = false;
|
|
if (hoveredElement !== undefined) {
|
|
const instanceSelector = getInstanceSelectorFromElement(hoveredElement);
|
|
|
|
if (instanceSelector === undefined) {
|
|
return;
|
|
}
|
|
|
|
updateHoveredRect([hoveredElement], instanceSelector);
|
|
}
|
|
},
|
|
});
|
|
|
|
// update rect whenever hovered instance is changed
|
|
const unsubscribeHoveredInstanceId = $hoveredInstanceSelector.subscribe(
|
|
(instanceSelector) => {
|
|
if (instanceSelector) {
|
|
const elements = getElementsByInstanceSelector(instanceSelector);
|
|
updateHoveredRect(elements, instanceSelector);
|
|
} else {
|
|
$hoveredInstanceOutline.set(undefined);
|
|
}
|
|
}
|
|
);
|
|
|
|
return () => {
|
|
window.removeEventListener("mouseover", handleMouseOver);
|
|
window.removeEventListener("mouseout", handleMouseOut);
|
|
unsubscribeScrollState();
|
|
clearTimeout(mouseOutTimeoutId);
|
|
unsubscribeHoveredInstanceId();
|
|
};
|
|
};
|