diff --git a/editor/src/components/editor/store/dispatch.tsx b/editor/src/components/editor/store/dispatch.tsx
index 84ffddf7177b..f908adf080f9 100644
--- a/editor/src/components/editor/store/dispatch.tsx
+++ b/editor/src/components/editor/store/dispatch.tsx
@@ -757,7 +757,12 @@ function editorDispatchInner(
const priorSimpleLocks = storedState.unpatchedEditor.lockedElements.simpleLock
const updatedSimpleLocks = doNotUpdateLocks
? priorSimpleLocks
- : updateSimpleLocks(storedState.unpatchedEditor.jsxMetadata, metadata, priorSimpleLocks)
+ : updateSimpleLocks(
+ storedState.unpatchedEditor.jsxMetadata,
+ metadata,
+ elementPathTree,
+ priorSimpleLocks,
+ )
if (result.unpatchedEditor.canvas.interactionSession != null) {
result = {
...result,
diff --git a/editor/src/core/shared/element-locking.spec.browser2.tsx b/editor/src/core/shared/element-locking.spec.browser2.tsx
new file mode 100644
index 000000000000..e8c621676ca0
--- /dev/null
+++ b/editor/src/core/shared/element-locking.spec.browser2.tsx
@@ -0,0 +1,61 @@
+import { renderTestEditorWithCode } from '../../components/canvas/ui-jsx.test-utils'
+import * as EP from './element-path'
+
+function testCode(snippet: string) {
+ return `
+import * as React from 'react'
+import { Scene, Storyboard } from 'utopia-api'
+
+export var storyboard = (
+
+
+
+
+
+)
+
+export var App = (props) => {
+ return (
+
+ ${snippet}
+
+ )
+}`
+}
+
+describe('Auto-locking elements', () => {
+ it('Root element of component locked when it is not a leaf element', async () => {
+ const renderResult = await renderTestEditorWithCode(
+ testCode('Hello
'),
+ 'await-first-dom-report',
+ )
+
+ expect(renderResult.getEditorState().editor.lockedElements.simpleLock.map(EP.toString)).toEqual(
+ ['sb/sc/app:app-root'],
+ )
+ })
+
+ it('Root element of component is not locked when it is a leaf element', async () => {
+ const renderResult = await renderTestEditorWithCode(testCode('Hello'), 'await-first-dom-report')
+
+ expect(renderResult.getEditorState().editor.lockedElements.simpleLock.map(EP.toString)).toEqual(
+ [],
+ )
+ })
+})
diff --git a/editor/src/core/shared/element-locking.ts b/editor/src/core/shared/element-locking.ts
index 7624331311b6..b38db3e39d01 100644
--- a/editor/src/core/shared/element-locking.ts
+++ b/editor/src/core/shared/element-locking.ts
@@ -8,17 +8,24 @@ import { MetadataUtils } from '../model/element-metadata-utils'
export function updateSimpleLocks(
priorMetadata: ElementInstanceMetadataMap,
newMetadata: ElementInstanceMetadataMap,
+ pathTree: ElementPathTrees,
currentSimpleLockedItems: Array,
): Array {
let result: Array = [...currentSimpleLockedItems]
for (const [key, value] of Object.entries(newMetadata)) {
- // This entry is the root element of an instance or a remix Outlet, and it isn't present in the previous metadata,
- // which implies that it has been newly added.
- if (
- (EP.isRootElementOfInstance(value.elementPath) ||
- MetadataUtils.isProbablyRemixOutlet(newMetadata, value.elementPath)) &&
- !(key in priorMetadata)
- ) {
+ // The entry isn't present in the previous metadata, which implies that it has been newly added.
+ const isNewlyAdded = !(key in priorMetadata)
+
+ // You rarely want to select a root element of a component instance, except when it is a leaf element
+ const isNonLeafRootElement =
+ EP.isRootElementOfInstance(value.elementPath) &&
+ MetadataUtils.getChildrenPathsOrdered(newMetadata, pathTree, value.elementPath).length > 0
+
+ // Remix Outlet are rarely needed to be selected
+ const isRemixOutlet = MetadataUtils.isProbablyRemixOutlet(newMetadata, value.elementPath)
+
+ // This entry is a newly added non-leaf root element or a remix Outlet, it should be autolocked
+ if (isNewlyAdded && (isNonLeafRootElement || isRemixOutlet)) {
result.push(value.elementPath)
}
}