Merge pull request #291 from webstudio-is/outline_hovered_in_navigator

Outline on canvas item that's hovered in navigator
This commit is contained in:
Roman Pominov authored and GitHub committed 2022-09-20 17:47:39 +03:00
commit 0932a3e79c
7 files changed
+68 -32

No files matched your search

+2 -6
View File
@@ -14,14 +14,13 @@ import {
useDeleteInstance,
useInsertInstance,
usePopulateRootInstance,
usePublishHoveredInstanceData,
usePublishHoveredInstanceRect,
usePublishRootInstance,
usePublishSelectedInstanceData,
usePublishSelectedInstanceDataRect,
usePublishTextEditingInstanceId,
useReparentInstance,
useSetHoveredInstance,
usePublishHoveredInstanceData,
useUnselectInstance,
useUpdateSelectedInstance,
} from "./shared/instance";
@@ -41,7 +40,6 @@ import {
useSubscribeScrollState,
} from "~/shared/nano-states";
import { registerContainers } from "./shared/immerhin";
import { useTrackHoveredElement } from "./shared/use-track-hovered-element";
import { usePublishScrollState } from "./shared/use-publish-scroll-state";
import { useDragAndDrop } from "./shared/use-drag-drop";
import { setInstanceChildrenMutable } from "~/shared/tree-utils";
@@ -90,19 +88,17 @@ const DesignMode = ({ treeId, buildId }: DesignModeProps) => {
useUpdateStyle();
useManageProps();
usePublishSelectedInstanceData(treeId);
usePublishHoveredInstanceData();
useHandleBreakpoints();
useInsertInstance();
useReparentInstance();
useDeleteInstance();
usePublishRootInstance();
useTrackSelectedElement();
useTrackHoveredElement();
useSetHoveredInstance();
usePublishHoveredInstanceData();
useSync({ buildId, treeId });
useUpdateSelectedInstance();
usePublishSelectedInstanceDataRect();
usePublishHoveredInstanceRect();
useUnselectInstance();
usePublishScrollState();
useSubscribeScrollState();
+29 -16
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import ObjectId from "bson-objectid";
import {
type Instance,
@@ -29,7 +29,6 @@ import {
import {
useSelectedInstance,
useSelectedElement,
useHoveredElement,
useHoveredInstance,
} from "./nano-states";
import {
@@ -38,8 +37,12 @@ import {
useTextEditingInstanceId,
} from "~/shared/nano-states";
import { useMeasure } from "~/shared/dom-hooks";
import { findInstanceByElement } from "~/shared/dom-utils";
import {
findInstanceByElement,
getInstanceElementById,
} from "~/shared/dom-utils";
import { publish } from "~/shared/pubsub";
import { useTrackHoveredElement } from "./use-track-hovered-element";
declare module "~/shared/pubsub" {
export interface PubsubMap {
@@ -263,23 +266,33 @@ export const usePublishSelectedInstanceDataRect = () => {
}, [rect]);
};
export const usePublishHoveredInstanceRect = () => {
const [element] = useHoveredElement();
const publishRect = useCallback(() => {
if (element === undefined) return;
publish({
type: "hoveredInstanceRect",
payload: element.getBoundingClientRect(),
});
}, [element]);
useEffect(publishRect, [publishRect]);
};
export const useSetHoveredInstance = () => {
const [rootInstance] = useRootInstance();
const [hoveredElement] = useHoveredElement();
const [, setHoveredInstance] = useHoveredInstance();
const [hoveredElement, setHoveredElement] = useState<HTMLElement | undefined>(
undefined
);
useTrackHoveredElement(setHoveredElement);
useSubscribe(
"navigatorHoveredInstance",
useCallback((instance) => {
setHoveredElement(
instance && (getInstanceElementById(instance.id) || undefined)
);
}, [])
);
useEffect(() => {
if (hoveredElement === undefined) return;
publish({
type: "hoveredInstanceRect",
payload: hoveredElement.getBoundingClientRect(),
});
}, [hoveredElement]);
useEffect(() => {
let instance;
if (rootInstance !== undefined && hoveredElement) {
@@ -15,6 +15,3 @@ const selectedElementContainer = createValueContainer<
HTMLElement | undefined
>();
export const useSelectedElement = () => useValue(selectedElementContainer);
const hoveredElementContainer = createValueContainer<HTMLElement | undefined>();
export const useHoveredElement = () => useValue(hoveredElementContainer);
@@ -1,14 +1,15 @@
import { useEffect } from "react";
import { useSelectedElement, useHoveredElement } from "./nano-states";
import { useSelectedElement } from "./nano-states";
import { useRootInstance } from "~/shared/nano-states";
const eventOptions = {
passive: true,
};
export const useTrackHoveredElement = () => {
export const useTrackHoveredElement = (
onChange: (element: HTMLElement | undefined) => void
) => {
const [rootInstance] = useRootInstance();
const [, setHoveredElement] = useHoveredElement();
const [selectedElement] = useSelectedElement();
useEffect(() => {
@@ -21,12 +22,12 @@ export const useTrackHoveredElement = () => {
) {
return;
}
setHoveredElement(element);
onChange(element);
};
const handleMouseOut = () => {
if (rootInstance === undefined) return;
setHoveredElement(undefined);
onChange(undefined);
};
window.addEventListener("mouseover", handleMouseOver, eventOptions);
@@ -36,5 +37,5 @@ export const useTrackHoveredElement = () => {
window.removeEventListener("mouseover", handleMouseOver);
window.removeEventListener("mouseout", handleMouseOut);
};
}, [rootInstance, selectedElement, setHoveredElement]);
}, [rootInstance, selectedElement, onChange]);
};
@@ -1,5 +1,5 @@
import { Flex } from "@webstudio-is/design-system";
import type { Instance } from "@webstudio-is/react-sdk";
import { type Instance } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { useCallback } from "react";
import { useSelectedInstanceData } from "~/designer/shared/nano-states";
@@ -15,6 +15,7 @@ declare module "~/shared/pubsub" {
dropTarget: { instanceId: Instance["id"]; position: number | "end" };
};
deleteInstance: { id: Instance["id"] };
navigatorHoveredInstance: { id: Instance["id"] } | undefined;
}
}
@@ -67,6 +68,16 @@ export const Navigator = ({ publish, isClosable, onClose }: NavigatorProps) => {
[publish]
);
const handleHover = useCallback(
(instance: Instance | undefined) => {
publish({
type: "navigatorHoveredInstance",
payload: instance && { id: instance.id },
});
},
[publish]
);
if (rootInstance === undefined) return null;
return (
<Flex css={{ height: "100%", flexDirection: "column" }}>
@@ -76,6 +87,7 @@ export const Navigator = ({ publish, isClosable, onClose }: NavigatorProps) => {
root={rootInstance}
selectedItemId={selectedInstanceData?.id}
onSelect={handleSelect}
onHover={handleHover}
onDragEnd={handleDragEnd}
onDelete={handleDelete}
/>
@@ -143,6 +143,8 @@ export type TreeNodeProps<Data extends { id: string }> = {
selectedItemId?: string;
parentIsSelected?: boolean;
onSelect?: (itemId: string) => void;
onMouseEnter?: (item: Data) => void;
onMouseLeave?: (item: Data) => void;
level?: number;
animate?: boolean;
@@ -161,6 +163,8 @@ export const TreeNode = <Data extends { id: string }>({
setIsExpanded,
selectedItemId,
onSelect,
onMouseEnter,
onMouseLeave,
onExpandTransitionEnd,
forceHoverStateAtItem,
renderItem,
@@ -213,6 +217,8 @@ export const TreeNode = <Data extends { id: string }>({
data-drop-target-id={itemData.id}
>
<ItemWrapper
onMouseEnter={() => onMouseEnter?.(itemData)}
onMouseLeave={() => onMouseLeave?.(itemData)}
isSelected={isSelected}
parentIsSelected={parentIsSelected}
enableHoverState={forceHoverStateAtItem === undefined}
@@ -30,6 +30,7 @@ export type TreeProps<Data extends { id: string }> = {
selectedItemId?: string;
onSelect?: (itemId: string) => void;
onHover?: (item: Data | undefined) => void;
animate?: boolean;
onDragEnd: (event: {
itemId: string;
@@ -49,6 +50,7 @@ export const Tree = <Data extends { id: string }>({
renderItem,
selectedItemId,
onSelect,
onHover,
animate,
onDragEnd,
onDelete,
@@ -238,6 +240,13 @@ export const Tree = <Data extends { id: string }>({
onEsc: dragHandlers.cancelCurrentDrag,
});
const [onMouseEnter, onMouseLeave] = useMemo(() => {
if (onHover === undefined) {
return [undefined, undefined];
}
return [(item: Data) => onHover(item), () => onHover(undefined)] as const;
}, [onHover]);
return (
<Box
css={{
@@ -265,6 +274,8 @@ export const Tree = <Data extends { id: string }>({
getItemChildren={getItemChildren}
animate={animate}
onSelect={onSelect}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
selectedItemId={selectedItemId}
itemData={root}
level={0}