diff --git a/apps/builder/app/builder/features/navigator/navigator-tree.tsx b/apps/builder/app/builder/features/navigator/navigator-tree.tsx index 81b12525daad..bfd0d7bf14fe 100644 --- a/apps/builder/app/builder/features/navigator/navigator-tree.tsx +++ b/apps/builder/app/builder/features/navigator/navigator-tree.tsx @@ -442,6 +442,12 @@ const canDrag = (instance: Instance, instanceSelector: InstanceSelector) => { if (instanceSelector.length === 1) { return false; } + + // Do not drag if the instance name is being edited + if ($editingItemSelector.get()?.join(",") === instanceSelector.join(",")) { + return false; + } + if ($isContentMode.get()) { const parentId = instanceSelector[1]; const parentInstance = $instances.get().get(parentId); @@ -542,6 +548,20 @@ export const NavigatorTree = () => { } }, [selectedInstanceSelector]); + const selectInstanceAndClearSelection = ( + instanceSelector: undefined | Instance["id"][], + event: React.MouseEvent | React.FocusEvent + ) => { + if (event.currentTarget.querySelector("[contenteditable]") === null) { + // Allow text selection and edits inside current TreeNode + // Outside if text is selected, it needs to be unselected before selecting the instance. + // Otherwise user will cmd+c the text instead of copying the instance. + window.getSelection()?.removeAllRanges(); + } + + selectInstance(instanceSelector); + }; + return ( { level={0} isSelected={selectedKey === ROOT_INSTANCE_ID} buttonProps={{ - onClick: () => selectInstance([ROOT_INSTANCE_ID]), - onFocus: () => selectInstance([ROOT_INSTANCE_ID]), + onClick: (event) => + selectInstanceAndClearSelection([ROOT_INSTANCE_ID], event), + onFocus: (event) => + selectInstanceAndClearSelection([ROOT_INSTANCE_ID], event), }} action={ { $blockChildOutline.set(undefined); }, onMouseLeave: () => $hoveredInstanceSelector.set(undefined), - onClick: () => selectInstance(item.selector), - onFocus: () => selectInstance(item.selector), + onClick: (event) => + selectInstanceAndClearSelection(item.selector, event), + onFocus: (event) => + selectInstanceAndClearSelection(item.selector, event), onKeyDown: (event) => { if (event.key === "Enter") { emitCommand("editInstanceText"); diff --git a/apps/builder/app/builder/features/pages/pages.tsx b/apps/builder/app/builder/features/pages/pages.tsx index b309cb919c2e..ef5d8f618774 100644 --- a/apps/builder/app/builder/features/pages/pages.tsx +++ b/apps/builder/app/builder/features/pages/pages.tsx @@ -2,7 +2,6 @@ import { useEffect, useRef } from "react"; import { useStore } from "@nanostores/react"; import { Tooltip, - Box, Button, SmallIconButton, TreeNode, @@ -13,6 +12,7 @@ import { TreeSortableItem, type TreeDropTarget, toast, + ScrollArea, } from "@webstudio-is/design-system"; import { ChevronRightIcon, @@ -313,7 +313,15 @@ const PagesTree = ({ } return ( - + {flatPagesTree.map((item, index) => { const handleExpand = (isExpanded: boolean, all: boolean) => { @@ -435,7 +443,7 @@ const PagesTree = ({ ); })} - + ); }; diff --git a/apps/builder/app/shared/copy-paste/init-copy-paste.ts b/apps/builder/app/shared/copy-paste/init-copy-paste.ts index 4d084212d3d1..07988e48e01c 100644 --- a/apps/builder/app/shared/copy-paste/init-copy-paste.ts +++ b/apps/builder/app/shared/copy-paste/init-copy-paste.ts @@ -49,23 +49,10 @@ const validateClipboardEvent = (event: ClipboardEvent) => { // Allows native selection of text in the Builder panels, such as CSS Preview. if (event.type === "copy") { const isInBuilderContext = window.self === window.top; + const selection = window.getSelection(); - if (isInBuilderContext) { - // Note on event.target: - // - // The spec (https://w3c.github.io/clipboard-apis/#to-fire-a-clipboard-event) - // says that if the context is not editable, the target should be the focused node. - // - // But in practice it seems that the target is based - // on where the cursor is, rather than which element has focus. - // For example, if a