* fix: select top-level auto layout frames by their empty area Checked against Figma desktop 126 with real pointer input. The gaps and padding of a top-level frame with auto layout (any direction or grid, filled or not, on the page or in a section) select, highlight, and drag the frame, while clicks on its layers still select those layers. A plain top-level frame's empty area stays background, as #900 recorded; its oracle scenarios only used frames without auto layout. Auto layout frames are hit by their bounds even without a fill, and a press on such a frame's empty area no longer starts a marquee. * feat(canvas): outline auto layout children with dotted lines, as in Figma Hovering a horizontal or vertical auto layout frame outlines its visible direct children, and a single selected layer dots the border of its auto layout parent, in the container's outline color. Grid and plain frames, grandchildren, and multi-selections show none. Line weight and dash were measured from Figma 126 screenshots: 1 px with 1.75 px dashes and 1.25 px gaps in screen space. The editor gains a transforming flag that canvas input sets while layers move, resize, or rotate; the outlines and the padding and gap markers of a selected auto layout frame hide while it is set, as Figma's do. The children outline a selected auto layout frame drew under the pointer now uses the same stroke, and the dash effect is freed after each draw. * fix(canvas): select unfilled frames by their empty area only on a click Live Figma 126, with real pointer input on a top-level card holding an unfilled auto layout row and an unfilled plain frame, with and without auto layout on the card: a click on either frame's empty area selects that frame, while a ⌘-click looks through it to the card. Only top-level auto layout frames now own their empty area in hit testing, so a deep hit no longer stops at a nested unfilled auto layout frame. A click that ends on an open container selects the frame child under the point by its bounds, filled or not, which plain frames did not get before; boards that open by themselves keep their empty area as background. * fix(canvas): select an empty unfilled frame on the page by its bounds Live Figma 126: a click inside an empty frame without a fill selects it, on the page and in a section. The section case already did after the previous commit; on the page the click found nothing, since an unfilled frame without children is not hit. A click that hits nothing now looks for a frame under the point among the scope's children, which skips boards that hold layers, as their empty area stays background. * refactor(scene-graph): read the containers the selection opens in their own helper * fix(canvas): open a selected container, so repeated clicks reach deeper layers In Figma, clicking again at the same point inside a selected frame, component, or instance selects the layer under the pointer, so repeated clicks walk down to a text in a nested component such as shadcn's calendar. The click hit test opened only the ancestors of the selection, so a selected nested container stopped the walk at itself and only a double-click went deeper. A selected container with layers now opens like its ancestors; groups and booleans stay closed, as in Figma. Pressing inside the selection still drags it, and its empty area keeps it selected. |
||
|---|---|---|
| .. | ||
| assets | ||
| src | ||
| tests | ||
| AGENTS.md | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.test.json | ||
| tsdown.config.ts | ||
@open-pencil/core
Editor engine for OpenPencil: the CanvasKit (Skia WASM) renderer, Yoga layout, the framework-agnostic editor with undo and selection, the Figma Plugin API compatibility layer, the AI/MCP tool definitions, clipboard and vector conversion, and format-neutral document I/O.
It depends on @open-pencil/scene-graph, @open-pencil/pen, @open-pencil/kiwi, and @open-pencil/fig, and keeps browser DOM out so it runs in the app, the CLI, the MCP server, and headless scripts alike.
Import the root barrel or the targeted subpaths listed in package.json exports, for example @open-pencil/core/io, @open-pencil/core/geometry, and @open-pencil/core/canvaskit.
Scripts get two globals through compileScript from @open-pencil/core/tools: figma, the Figma Plugin API over a document, and openpencil (OpenPencilAPI from @open-pencil/core/openpencil-api), what OpenPencil adds to it in the same style, such as making a component behave as a Reka UI control:
import { FigmaAPI } from '@open-pencil/core/figma-api'
import { compileScript } from '@open-pencil/core/tools'
await compileScript(`
const set = figma.currentPage.findOne((n) => n.name === 'Switch')
openpencil.setBehaviour(set, { kind: 'switch', values: { value: 'State' } })
`)(new FigmaAPI(graph))
- Programmable overview and SDK docs: https://openpencil.dev/programmable/
- Source and issues: https://github.com/open-pencil/open-pencil
MIT License.