Commit graph

971 commits

Author SHA1 Message Date
Ilya Nikitin ae2fbccf01 fix(components): update Lucide icon names in properties sections
Ensure proper semantic alignment between icon names and their visual representation
2026-03-27 17:09:34 +03:00
Ilya Nikitin e393ca7a07 fix(properties): update Lucide icon names for alignment and flip controls
Ensure proper semantic alignment between icon names and their visual representation
2026-03-27 17:09:02 +03:00
Danila Poyarkov c1d7fc80b0 Fix property panel visibility toggles, independent corners/stroke sides, extract shared ColorStyleRow
- Fix fill/stroke/effect/appearance visibility eye toggle (stale clone reads)
- Fix independent corner radii toggle not showing 4 inputs
- Fix stroke sides toggle: replace dropdown with direct toggle like corners
- Copy uniform weight to all sides when expanding stroke sides
- Batch multi-selection visibility toggles into single undo entry
- Extract ColorStyleRow shared by FillSection and StrokeSection
- Replace useFillVariableBinding/useStrokeVariableBinding with useColorVariableBinding(kind)
- AppearanceSection uses useAppearance() directly instead of slot wrapper
- Add sceneVersion dependency to useNodeProps and PropertyListRoot computeds
- Add E2E tests for visibility toggles and corner/stroke side toggles
2026-03-27 17:06:33 +03:00
Danila Poyarkov ae13bb1cbd Sync locale files with latest i18n keys 2026-03-25 21:54:12 +03:00
Danila Poyarkov e03038f4bf Translate collaboration share popover 2026-03-25 21:48:38 +03:00
Danila Poyarkov 3d9d559451 Update changelog for recent SDK and i18n work 2026-03-25 21:45:52 +03:00
Danila Poyarkov 4b9cd54983 Translate more editor UI surfaces 2026-03-25 18:47:23 +03:00
Danila Poyarkov 9f296b7357 Translate chat setup and code panel strings 2026-03-25 18:40:17 +03:00
Danila Poyarkov 2f646ffcaf Translate more visible UI strings 2026-03-25 18:26:03 +03:00
Danila Poyarkov 77032ab092 Translate visible panel chrome for Russian locale 2026-03-25 18:12:09 +03:00
Danila Poyarkov 9220ef399b Handle checkbox submenu items in app menu 2026-03-25 18:07:24 +03:00
Danila Poyarkov cf16857a76 Add locale picker to app menu 2026-03-25 18:05:58 +03:00
Danila Poyarkov e52fcf1b8f Fix Vue package declaration build 2026-03-25 17:43:16 +03:00
Danila Poyarkov 6bfc5c2189 Rename toggleUI i18n key 2026-03-25 17:40:26 +03:00
Danila Poyarkov ea4676d991 Finish i18n pass for menus and tool labels
Localize remaining menu state labels like Hide/Show and Lock/Unlock,
wire toolbar tool/action labels through the i18n stores, and localize
the app menu UI toggle tooltip.

Replace template-string JSON imports with an explicit locale loader map
so published package builds have stable locale asset references.
2026-03-25 17:39:47 +03:00
Danila Poyarkov eb2891233a Wire i18n into commands, menus, and add locale files
Commands and menu models now read labels from nanostores i18n
messages instead of hardcoded English strings. App menu rebuilt
to use i18n directly instead of matching on localized labels.

Added locale files: de, es, fr, it, pl (ru was added earlier).
All 6 translations plus English base cover menus, commands, tools,
panels, pages, and dialogs.
2026-03-25 17:05:11 +03:00
Danila Poyarkov 2b3219e106 Add i18n to Vue SDK with nanostores
nanostores + @nanostores/i18n + @nanostores/vue (~6 KB total).

English base translations defined inline in messages.ts, other
locales loaded on demand from JSON. Grouped by domain: menu,
commands, tools, panels, pages, dialogs.

useI18n() composable returns reactive translation objects plus
locale controls. Russian locale included as first translation.

Browser language auto-detected, user preference persisted in
localStorage.
2026-03-25 16:17:03 +03:00
Danila Poyarkov 1f851e29cd Normalize textAutoResize to NONE for auto-layout children on export
Figma expects text inside auto-layout frames to have no textAutoResize
field (NONE). Internally OpenPencil uses WIDTH_AND_HEIGHT so the layout
engine can measure text, but this caused overflow when exported .fig
files were opened in Figma.

Normalize at export time, matching how we already normalize font
families for the binary format.

Fixes #136
2026-03-25 06:41:45 +03:00
Danila Poyarkov 1e35c0d3a8 Preserve fig-kiwi version on roundtrip
Parse the version field from the fig-kiwi header during .fig import
and store it on SceneGraph.figKiwiVersion. Use the original version
when re-exporting. New files default to 101 (current Figma standard).

Previously hardcoded to 106, which Figma rejected on import.

Fixes #153
2026-03-25 06:02:24 +03:00
Danila Poyarkov 29876f0c2b Await font loading before first canvas render
The initial renderNow() fired before loadFonts() completed, causing
text measurement to return null on slower connections (especially
Safari). Layout computed without text dimensions got corrupted.

Move loadFonts() into the async init() flow and only render after
fonts are ready.
2026-03-25 00:32:06 +03:00
TKman c52f88e34c feat: add spacebar hold to temporarily activate Hand (pan) tool
Hold Spacebar to temporarily switch to the Hand tool for panning,
release to return to the previously active tool. This matches the
standard Figma UX pattern.

- Uses separate keydown/keyup event listeners for reliable handling
- Guards against text editing mode and key repeat
- References state.activeTool for correct tool state

Fixes #147
2026-03-24 23:47:56 +03:00
Shai Rubinstein 2e723caba0 fix: kiwi serialization bugs causing broken auto-layout in Figma import
- Zero transforms for auto-layout children (Figma computes positions)
- Write bordersTakeSpace from strokesIncludedInLayout
- Always write explicit lineHeight, defaulting to ceil(fontSize * 1.2)
- Normalize font family in derivedTextData fontMetaData and fontName
- Extend normalizeFontFamily to strip optical size suffixes (9pt, 18px)
- Extract computeExportTransform() helper for lint compliance
- Add 20 unit tests covering all fixes

Fixes #134
2026-03-24 23:47:56 +03:00
Shai Rubinstein 5139836816 fix: default TEXT nodes to solid black fill
TEXT nodes created via SceneGraph.createNode() defaulted to empty
fills, causing text to be invisible when .fig files are opened in
Figma. Now defaults to a solid black fill matching Figma's behavior.

Closes #133
2026-03-24 23:47:56 +03:00
Shai Rubinstein 2efd63ea09 fix: normalize font family names on .fig export
Strip optical size suffixes (e.g. "DM Sans 9pt" → "DM Sans") and
"Variable" suffixes when writing fontName.family to .fig files.
This ensures Figma recognizes the font instead of showing a
"Missing font" dialog.

Closes #131
2026-03-24 23:47:56 +03:00
myhency 8599750f1d feat(fonts): load multiple CJK Google Fonts for full Han/Japanese/Korean coverage
Previously, the CJK fallback loaded only a single Google Font
(Noto Sans SC) when local system fonts were unavailable. This meant
Korean and Japanese text would not render correctly in browsers that
block the Local Font Access API.

Changes:
- Replace CJK_GOOGLE_FONT (single string) with CJK_GOOGLE_FONTS array
  containing Noto Sans SC, Noto Sans JP, and Noto Sans KR
- Load all three fonts in parallel via Promise.allSettled so each
  script has proper glyph coverage
- Update text renderer to pass all loaded CJK families as fallbacks
- Maintain backward compatibility: getCJKFallbackFamily() still works,
  new getCJKFallbackFamilies() returns the full list
2026-03-24 23:47:55 +03:00
Danila Poyarkov 2956507783 Fix switch_page not persisting across MCP tool calls
Each RPC tool call created a fresh FigmaAPI instance, so switch_page
set currentPageId on a throwaway object. Sync the page change back
to the editor store after tool execution.

Fixes #112
2026-03-24 23:47:55 +03:00
Danila Poyarkov bceddd759f Guard keyboard shortcuts during canvas text editing
Backspace/Delete and tool shortcuts (V, R, F, T, etc.) fired during
canvas text editing because the guard only checked for DOM input/textarea
focus, not the canvas TextEditor state. Add editingTextId check to both
the onEventFired handler and the plain() shortcut helper.
2026-03-24 23:47:55 +03:00
Danila Poyarkov 2ace1d4e49
Merge pull request #137 from open-pencil/refactor/vue-sdk-architecture
Refactor: extract @open-pencil/vue headless SDK, split editor into modules
2026-03-24 23:29:21 +03:00
Danila Poyarkov a743da54b6 Document Vue SDK public APIs 2026-03-24 23:25:06 +03:00
Danila Poyarkov 99e646aaa4 Refine docs structure for SDK and automation 2026-03-24 23:24:08 +03:00
Danila Poyarkov 07fade6abb Use explicit Vue SDK barrel exports 2026-03-24 18:37:08 +03:00
Danila Poyarkov 80a2855bb3 Fix review issues in undo and Vue SDK 2026-03-24 18:15:40 +03:00
Danila Poyarkov bff8895775 Fix CLI tsconfig rootDir mismatch 2026-03-24 17:16:44 +03:00
Danila Poyarkov 936ba6637b Adopt package aliases across Vue SDK 2026-03-24 17:10:43 +03:00
Danila Poyarkov 1da5483fec Format remaining SDK refactor files 2026-03-24 17:02:01 +03:00
Danila Poyarkov 07af72ab24 Fix imported fig rendering and file open regressions 2026-03-24 16:52:45 +03:00
Danila Poyarkov 0a770248a2 Reorganize Vue SDK modules by domain 2026-03-24 09:50:31 +03:00
Danila Poyarkov 74bb119579 Extract Vue SDK editor controls and pickers 2026-03-24 09:50:28 +03:00
Danila Poyarkov 329dd301bd Align app UI primitives with shared ui overrides 2026-03-24 09:50:20 +03:00
Danila Poyarkov 0c30d044b2 Extract Vue SDK commands and menu model 2026-03-24 09:50:17 +03:00
Danila Poyarkov b9b960f6ef Fix broken SVG import path after shared/ restructure 2026-03-17 20:56:35 +03:00
Danila Poyarkov 29b63842fa Integrate SDK composables into all app components
FillPicker: useFillPicker() replaces inline category/switch logic
PagesPanel: usePageList() replaces inline pages computed
VariablesDialog: useVariables() replaces 11 inline CRUD functions
CanvasMenu: inline editor.copySelectionAs* instead of wrapper fns
Toolbar: ToolbarRoot + ToolbarItem wrapping tool buttons

28/42 app components now use SDK. Remaining 14 are legitimately
app-specific (chat, collab, mobile layout) or generic UI widgets
(select, toast, tip — no editor logic needed).
2026-03-17 20:39:45 +03:00
Danila Poyarkov fb2bfb5827 SDK rewrite: delete 15 useless wrappers, replace with composables
Deleted 15 fake headless component folders (slot-prop wrappers with
no provide/inject, no DOM, no ARIA). Replaced with 9 composables:
- usePosition, useLayout, useAppearance, useTypography
- useExport, useVariables, useFillPicker, useGradientStops, usePageList

Kept 6 real headless component families with provide/inject + DOM:
- Canvas (WebGL surface lifecycle)
- ScrubInput (pointer capture + input + display)
- Toolbar (wraps Reka UI Toolbar)
- LayerTree (wraps Reka UI Tree + Atlaskit DnD)
- PropertyList (array CRUD via inject)
- GradientEditor (pointer capture for stop drag)

App components updated: use composables directly instead of wrapping
in Root components. -1047 net lines, cleaner separation.
2026-03-17 20:22:41 +03:00
Danila Poyarkov 30ae92483e Restructure SDK: shared/ replaces composables/+input/+utils/+context
Reka UI source structure: component folders + shared/ for utilities.
- composables/ → shared/ (camelCase names: useCanvas, useNodeProps, etc)
- input/ → shared/input/ (internal to useCanvasInput)
- utils/ → shared/ (toolCursor)
- context.ts → shared/editorContext.ts
- assets/ → shared/assets/
- components/ deleted (legacy wrappers)
- Added shared/createContext.ts (Reka UI pattern)
2026-03-17 20:04:14 +03:00
Danila Poyarkov 920af0a14e Fix scrub lag: simplify useSceneComputed to synchronous sceneVersion
Drop the emitter-to-shallowRef bridge (rAF batched, 60 lines).
state.sceneVersion on shallowReactive IS already Vue-reactive.
useSceneComputed is now 15 lines. Zero latency during scrub.
2026-03-17 19:45:22 +03:00
Danila Poyarkov c36456d820 Fix loader not removed — add onReady callback to dismiss #loader
The loader div from index.html was never dismissed after the SDK
migration because use-canvas.ts moved to the SDK (which correctly
doesn't touch app-specific DOM). Added onReady callback in
EditorCanvas.vue to fade out and remove the loader.
2026-03-17 19:25:06 +03:00
Danila Poyarkov 43694aaeb3 Fix runtime errors: TooltipProvider scope, provideEditor in EditorView
Browser validation found 2 runtime issues:

1. TooltipProvider only wrapped EditorView, but <Tip> components in
   AppToast rendered outside it → moved TooltipProvider to App.vue
   to wrap everything including toasts.

2. SDK components call useEditor() but the app never provided the
   editor via the SDK's injection system → added provideEditor(store)
   in EditorView.vue so SDK primitives can access the editor.

Validated: zero console errors, canvas renders, demo shapes visible.
2026-03-17 17:33:04 +03:00
Danila Poyarkov 92c49f80f4 Self-review fixes: CanvasContext types, toast out of SDK, last graph access
- CanvasContext hit test return types: unknown → SceneNode | null
- Move toast.ts from SDK to app (src/utils/toast.ts) — singleton
  with unguarded window access doesn't belong in headless SDK
- ExportSection: store.graph.getNode → store.getNode
- ImageFillPicker: store.graph.images.get → store.getImage (new method)
- Add getImage(hash) to core editor create.ts

Zero store.graph access from components. Zero toast in SDK.
2026-03-17 17:13:33 +03:00
Danila Poyarkov c01e5ec141 Fix review round 2 remaining: duplicate children, JSON.stringify perf
- duplicateSelected: recursive subtree cloning via graph.cloneTree()
  Filters to top-level selection to avoid double-cloning. Children
  get new IDs wired to new parent. Single undo entry.
- PropertyListRoot isMixed: compare array lengths first (fast path),
  only JSON.stringify when lengths match (rare).
- glContext in use-canvas.ts: ownership verified correct, no change.
2026-03-17 16:58:25 +03:00
Danila Poyarkov 4d5b3482d4 Fix review round 2: emitter leaks, snapshot restore, undo ID safety
Critical:
- restorePageFromSnapshot: emit node:reordered after bulk restore
  so component sync and useSceneComputed bridge detect changes
- subscribeToGraph: capture and cleanup unbind functions on
  replaceGraph() — prevents listener leak on old graph emitter

Important:
- useNodeProps: node/nodes use useSceneComputed instead of plain
  computed — re-evaluate on any scene mutation, not just selection
- Undo forward closures: explicit id: containerId/frameId/groupId
  in createNode spreads — no more implicit ID preservation contract

Moderate:
- Extract usePropScrub composable — deduplicate previousValues +
  updateProp + commitProp from AppearanceControls + PositionControls
2026-03-17 16:54:18 +03:00