fix(editor): guard drill selection text editing
This commit is contained in:
parent
aa9f03407a
commit
1f273f880e
|
|
@ -15,6 +15,7 @@ type TextEditInputOptions = {
|
|||
hitTestSectionTitle: (cx: number, cy: number) => SceneNode | null
|
||||
hitTestComponentLabel: (cx: number, cy: number) => SceneNode | null
|
||||
getClickCount: () => number
|
||||
wasSelectedBeforeClickSequence: (id: string) => boolean
|
||||
setDrag: SetDrag
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ export function createTextEditInput(options: TextEditInputOptions) {
|
|||
hitTestSectionTitle,
|
||||
hitTestComponentLabel,
|
||||
getClickCount,
|
||||
wasSelectedBeforeClickSequence,
|
||||
setDrag
|
||||
} = options
|
||||
|
||||
|
|
@ -97,9 +99,7 @@ export function createTextEditInput(options: TextEditInputOptions) {
|
|||
? getContainerDescendantHit(selectedId, cx, cy)
|
||||
: hitTestInScope(cx, cy, false)
|
||||
editor.enterContainer(selectedId)
|
||||
if (hit?.type === 'TEXT') {
|
||||
startTextEditingAt(hit, cx, cy)
|
||||
} else if (hit) {
|
||||
if (hit) {
|
||||
editor.select([hit.id])
|
||||
} else {
|
||||
editor.clearSelection()
|
||||
|
|
@ -112,6 +112,11 @@ export function createTextEditInput(options: TextEditInputOptions) {
|
|||
if (!hit) return
|
||||
|
||||
if (hit.type === 'TEXT') {
|
||||
const isTopLevelText = hit.parentId === editor.state.currentPageId
|
||||
if (!isTopLevelText && !wasSelectedBeforeClickSequence(hit.id)) {
|
||||
editor.select([hit.id])
|
||||
return
|
||||
}
|
||||
startTextEditingAt(hit, cx, cy)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export function useCanvasInput(
|
|||
) {
|
||||
const drag = ref<DragState | null>(null)
|
||||
const cursorOverride = ref<string | null>(null)
|
||||
const selectedIdsBeforeClickSequence = ref<ReadonlySet<string>>(new Set())
|
||||
const spaceHeld = useSpaceHeld()
|
||||
const { recordClick, getClickCount } = createClickCounter()
|
||||
|
||||
|
|
@ -63,6 +64,7 @@ export function useCanvasInput(
|
|||
hitTestSectionTitle,
|
||||
hitTestComponentLabel,
|
||||
getClickCount,
|
||||
wasSelectedBeforeClickSequence: (id) => selectedIdsBeforeClickSequence.value.has(id),
|
||||
setDrag
|
||||
})
|
||||
|
||||
|
|
@ -79,7 +81,9 @@ export function useCanvasInput(
|
|||
editor.setHoveredNode(null)
|
||||
const { sx, sy, cx, cy } = getCoords(e)
|
||||
|
||||
recordClick(sx, sy)
|
||||
const selectedIdsBeforeMouseDown = new Set(editor.state.selectedIds)
|
||||
const clickCount = recordClick(sx, sy)
|
||||
if (clickCount === 1) selectedIdsBeforeClickSequence.value = selectedIdsBeforeMouseDown
|
||||
handleToolMouseDown({
|
||||
event: e,
|
||||
cx,
|
||||
|
|
|
|||
94
tests/e2e/text/double-click-edit.spec.ts
Normal file
94
tests/e2e/text/double-click-edit.spec.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
import { getEditingTextId } from '#tests/helpers/store'
|
||||
|
||||
async function addTopLevelText(page: Page) {
|
||||
return page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.state.zoom = 1
|
||||
store.state.panX = 0
|
||||
store.state.panY = 0
|
||||
const id = store.createShape('TEXT', 200, 200, 150, 30)
|
||||
store.graph.updateNode(id, { text: 'Hello World', fontSize: 18 })
|
||||
store.select([id])
|
||||
store.requestRender()
|
||||
return id
|
||||
})
|
||||
}
|
||||
|
||||
async function addFrameWithNestedText(page: Page) {
|
||||
return page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.state.zoom = 1
|
||||
store.state.panX = 0
|
||||
store.state.panY = 0
|
||||
|
||||
const pageId = store.state.currentPageId
|
||||
const frame = store.graph.createNode('FRAME', pageId, {
|
||||
name: 'Card',
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 220,
|
||||
height: 120,
|
||||
fills: [{ type: 'SOLID', color: { r: 1, g: 1, b: 1, a: 1 }, opacity: 1, visible: true }]
|
||||
})
|
||||
const text = store.graph.createNode('TEXT', frame.id, {
|
||||
name: 'Nested label',
|
||||
text: 'Nested label',
|
||||
x: 20,
|
||||
y: 20,
|
||||
width: 140,
|
||||
height: 30,
|
||||
fontSize: 18
|
||||
})
|
||||
store.select([frame.id])
|
||||
store.requestRender()
|
||||
return { frameId: frame.id, textId: text.id }
|
||||
})
|
||||
}
|
||||
|
||||
test('double-clicking top-level text enters text edit mode', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
const canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
canvas.errors.length = 0
|
||||
await canvas.clearCanvas()
|
||||
|
||||
const textId = await addTopLevelText(page)
|
||||
await canvas.waitForRender()
|
||||
await canvas.pressKey('Escape')
|
||||
await canvas.dblclick(275, 215)
|
||||
|
||||
await expect.poll(() => getEditingTextId(page), { timeout: 3000 }).toBe(textId)
|
||||
})
|
||||
|
||||
test('double-click drill selects nested text before editing it', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
const canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
canvas.errors.length = 0
|
||||
await canvas.clearCanvas()
|
||||
|
||||
const ids = await addFrameWithNestedText(page)
|
||||
await canvas.waitForRender()
|
||||
|
||||
await canvas.dblclick(125, 125)
|
||||
|
||||
await expect.poll(() => getEditingTextId(page), { timeout: 1000 }).toBeNull()
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return [...store.state.selectedIds]
|
||||
})
|
||||
)
|
||||
.toEqual([ids.textId])
|
||||
|
||||
await page.waitForTimeout(600)
|
||||
await canvas.dblclick(125, 125)
|
||||
await expect.poll(() => getEditingTextId(page), { timeout: 3000 }).toBe(ids.textId)
|
||||
})
|
||||
Loading…
Reference in a new issue