Shift+A wraps selected nodes in auto-layout frame

Single frame selected: toggles auto-layout on/off.
Any other selection: creates a new frame around the selected nodes,
reparents them into it, and enables vertical auto-layout with HUG
sizing.
This commit is contained in:
Danila Poyarkov 2026-02-27 23:55:51 +03:00
parent 10c0a39ebe
commit ecfc6dd0c2
2 changed files with 55 additions and 1 deletions

View file

@ -74,8 +74,10 @@ export function useKeyboard(store: EditorStore) {
if (e.shiftKey && e.key === 'A') {
e.preventDefault()
const node = store.selectedNode.value
if (node && node.type === 'FRAME') {
if (node && node.type === 'FRAME' && store.selectedNodes.value.length === 1) {
store.setLayoutMode(node.id, node.layoutMode === 'NONE' ? 'VERTICAL' : 'NONE')
} else if (store.selectedNodes.value.length > 0) {
store.wrapInAutoLayout()
}
return
}

View file

@ -232,6 +232,57 @@ export function createEditorStore() {
requestRender()
}
function wrapInAutoLayout() {
const nodes = selectedNodes.value
if (nodes.length === 0) return
// Find common parent
const parentId = nodes[0].parentId ?? graph.rootId
const sameParent = nodes.every((n) => (n.parentId ?? graph.rootId) === parentId)
if (!sameParent) return
// Compute bounding box in absolute coords
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
for (const n of nodes) {
const abs = graph.getAbsolutePosition(n.id)
minX = Math.min(minX, abs.x)
minY = Math.min(minY, abs.y)
maxX = Math.max(maxX, abs.x + n.width)
maxY = Math.max(maxY, abs.y + n.height)
}
// Position relative to parent
const parentAbs =
parentId === graph.rootId ? { x: 0, y: 0 } : graph.getAbsolutePosition(parentId)
const frame = graph.createNode('FRAME', parentId, {
name: 'Frame',
x: minX - parentAbs.x,
y: minY - parentAbs.y,
width: maxX - minX,
height: maxY - minY,
layoutMode: 'VERTICAL',
primaryAxisSizing: 'HUG',
counterAxisSizing: 'HUG',
primaryAxisAlign: 'MIN',
counterAxisAlign: 'MIN',
fills: [{ type: 'SOLID', color: { r: 1, g: 1, b: 1, a: 1 }, opacity: 1, visible: true }]
})
// Reparent nodes into the new frame
for (const n of nodes) {
graph.reparentNode(n.id, frame.id)
}
computeLayout(graph, frame.id)
runLayoutForNode(frame.id)
state.selectedIds = new Set([frame.id])
requestRender()
}
function createShape(
type: NodeType,
x: number,
@ -460,6 +511,7 @@ export function createEditorStore() {
openFigFile,
updateNode,
setLayoutMode,
wrapInAutoLayout,
createShape,
duplicateSelected,
writeCopyData,