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