mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 05:26:24 +09:00
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:
7 files changed
+68
-32
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in new issue
Block a user