openpencil/tests/helpers/performance/hit-testing.ts
Danila Poyarkov 24db186ac8
feat: select layers at Figma's click depth (#900)
* feat: select layers at Figma's click depth

Checked against Figma desktop 126 with the same pointer input in both
editors. A click walks from the page down to the deepest layer under the
cursor and stops at the first layer that is not open: top-level frames
(on the page or in a section) and sections that hold layers, component
sets, and every ancestor of the selection are open. So a click inside a
top-level frame selects its direct child, the empty part of a top-level
frame or section selects nothing, and a selected layer's siblings and
cousins are one click away. Double-click goes one level deeper and
Cmd/Ctrl-click reaches the deepest layer.

A marquee started inside a top-level frame or section selects that
container's layers; from the page, a frame or section holding layers is
selected only when fully enclosed.

* fix(vue): compare marquee hits by canvas bounds

A marquee scoped to a rotated frame mapped only two corners into its space, and page-level enclosure used a frame's unrotated rectangle. Both now compare each layer's axis-aligned canvas bounds with the marquee, so rotated frames and their children are selected by what is drawn.
2026-10-05 12:39:04 +00:00

35 lines
952 B
TypeScript

import type { Page } from '@playwright/test'
export async function measureHitTesting(page: Page, run: () => Promise<void>) {
const probe = await page.evaluateHandle(() => {
const graph = window.openPencil?.getStore?.().graph
if (!graph) throw new Error('OpenPencil graph not initialized')
const original = graph.hitTestSelectable
let calls = 0
let totalMs = 0
graph.hitTestSelectable = (...args) => {
const start = performance.now()
const result = original.apply(graph, args)
totalMs += performance.now() - start
calls++
return result
}
return {
read: () => ({ calls, totalMs }),
dispose: () => {
graph.hitTestSelectable = original
}
}
})
try {
await run()
return await probe.evaluate((probe) => probe.read())
} finally {
try {
await probe.evaluate((probe) => probe.dispose())
} finally {
await probe.dispose()
}
}
}