- PropertiesPanel.vue: 513 → 67 lines (composition only)
- Extracted: PositionSection, LayoutSection, AppearanceSection,
FillSection, StrokeSection, TypographySection
- Shared useNodeProps() composable for update/commit logic
- Typography panel: font family picker with search (queryLocalFonts),
weight select, font size, line height, letter spacing, text alignment
- Font picker shows each family in its own typeface
- Loads selected font into both CanvasKit and browser on pick
- Create text nodes with empty text instead of 'Text' placeholder
- Immediately enter editing mode on creation (blinking cursor)
- Blue border on textarea during editing (matches Figma)
- Skip rendering text node on canvas while textarea overlay is active
- Skip selection handles for the node being edited
- Remove border from textarea (was causing double outline)
- Add @font-face for Inter-Regular.ttf so textarea matches CanvasKit
- Fork kiwi-schema from source TypeScript (evanw/kiwi), drop npm dep + patch
- Deduplicate GUID/Color types into src/types.ts
- Extract UI colors and default fills into src/constants.ts
- Replace hand-rolled hex parsing with culori library
- Extract demo shapes from App.vue into src/demo.ts
- useUrlSearchParams from VueUse for test mode detection
- Move fig-file/fig-import into kiwi/ (format layer)
- Delete PoC files (poc-yoga.ts, poc-test.ts)
- TreeRoot/TreeItem from reka-ui for proper tree structure
- Click arrow to expand/collapse frames with children
- Leaf nodes (no children) show no arrow
- Drag reorder preserved with drop indicator
- Bigger arrow icons (text-xs) with hover highlight
Drag layers to reorder within parent or reparent into frames.
Blue indicator line shows insertion point, ring highlight on
drop-into containers. 4px dead zone before drag starts.
Prevents dropping into self or descendants.
Figma-style scrubby inputs: hover icon shows ew-resize cursor,
drag left/right to change value. Click the number to type.
Replaces all raw number inputs in PropertiesPanel.
Uses VueUse useEventListener for automatic cleanup.
- 9-dot alignment grid for simultaneous primary + counter axis
- Direction buttons with SVG icons (vertical/horizontal/wrap)
- W/H sizing mode dropdowns (Fixed/Hug/Fill) with click-outside
- Uniform padding input with expand toggle for per-side padding
- Gap input with visual icon
- + / − to add/remove auto layout instead of triple toggle
- Sizing mode awareness: shows Fill option when inside auto-layout
Font loading:
- Inter Regular font bundled (342KB TTF)
- TypefaceFontProvider for Paragraph API
- MakeFreeTypeFaceFromData for Font object
- Fonts load async, fallback to drawText before ready
Text rendering:
- ParagraphBuilder with font family, size, letter spacing,
line height, text alignment
- Multi-line text wraps to node width
- Text nodes render at correct position in local space
Inline editing:
- Double-click text node → HTML textarea overlay
- Positioned/sized to match node on canvas (zoom-aware)
- Live updates to scene graph on input
- Escape or blur commits edit
- Keyboard events stop propagation during editing
- Text cursor for TEXT tool
Scene graph:
- Nodes use relative coordinates (x,y relative to parent)
- getAbsolutePosition() walks up the parent chain
- reparentNode() preserves visual position when moving between frames
- Deep hit testing traverses into container children
- hitTestFrame() finds deepest frame under cursor (for drop targeting)
- flattenTree() returns indented layer list for the panel
- isDescendant() prevents circular reparenting
Renderer:
- canvas.translate(node.x, node.y) per node — children render relative
- All shapes draw at (0,0) in their local space
- Drop target highlight (blue outline on frame during drag-over)
- Selection UI uses getAbsolutePosition() for screen-space handles
Input:
- Drag onto frame → blue highlight → drop reparents nodes
- Handle hit testing uses absolute positions
- Snap guides work with absolute coordinates
Layers panel:
- Tree view with depth-based indentation (16px per level)
- Expand arrow for containers with children
- Shift+click for additive selection
Rotation:
- Rotation handle above selection (line + circle)
- Drag to rotate, Shift snaps to 15° increments
- Live preview during drag, applied on release
- Rotated nodes render correctly with canvas.rotate()
Multi-select bounding box:
- Individual outlines per node + unified group bbox
- Group resize handles on the unified bounds
- Handles rotated AABB for rotated nodes
Snap guides:
- Magenta guide lines when moving nodes near edges/centers
- Snaps to left/right/top/bottom/center of other nodes
- 5px threshold, guides clear on mouse up
Text tool:
- T shortcut or toolbar to activate
- Click to place text node (200×24, default 'Text')
- CanvasKit drawText with default font (Inter to come)
Color picker (Reka UI Popover):
- HSV saturation/brightness area with pointer drag
- Hue slider (rainbow gradient)
- Alpha slider
- Hex input + alpha percentage
- Live color updates to fill/stroke
Properties panel:
- Add/remove fills and strokes with +/× buttons
- Toggle fill visibility
- Stroke weight editing
- Multi-select shows layer count + shared opacity
- ColorPicker integrated for fill and stroke colors
Frame clipping:
- Frame children now clip to parent bounds
- Resize by dragging selection handles (8 positions: corners + edges)
- Shift+resize maintains aspect ratio
- Marquee selection: drag on empty canvas to select nodes in rectangle
- Alt+drag to duplicate and move selected nodes
- Cmd+D to duplicate in place (+20px offset)
- Cmd+A to select all
- Shift+draw constrains to square/circle
- Cursor changes to resize arrows on handle hover
- Marquee drawn as blue transparent rect with selection stroke