- 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 |
||
|---|---|---|
| .. | ||
| example | ||
| src | ||
| ARCHITECTURE.md | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
@open-pencil/vue
Headless Vue 3 SDK for building OpenPencil-powered editors.
@open-pencil/vue sits on top of @open-pencil/core and provides:
- Vue editor injection via
provideEditor()/useEditor() - canvas integration via
useCanvas(),useCanvasInput(), anduseTextEdit() - selection, command, and panel composables
- headless structural primitives like
CanvasRoot,LayerTreeRoot,PageListRoot, andToolbarRoot
The SDK is headless by design: it provides logic and structure, while your app owns styling and product-specific UI.
Install
bun add @open-pencil/vue @open-pencil/core canvaskit-wasm
Quick start
<script setup lang="ts">
import { createEditor } from '@open-pencil/core/editor'
import { provideEditor } from '@open-pencil/vue'
const editor = createEditor({
width: 1200,
height: 800,
})
editor.createShape('RECTANGLE', 100, 100, 200, 150)
editor.zoomToFit()
provideEditor(editor)
</script>
<template>
<div class="h-screen">
<CanvasRoot v-slot="{ canvasRef }">
<canvas ref="canvasRef" class="size-full" />
</CanvasRoot>
</div>
</template>
Core concepts
Editor context
Use provideEditor(editor) once near the top of your subtree.
import { provideEditor } from '@open-pencil/vue'
provideEditor(editor)
Read it anywhere below with useEditor().
import { useEditor } from '@open-pencil/vue'
const editor = useEditor()
Canvas wiring
At the composable level, the main canvas APIs are:
useCanvas()useCanvasInput()useTextEdit()
If you want SDK-provided structure, use headless primitives like CanvasRoot and CanvasSurface.
Headless primitives
Main structural primitives include:
CanvasRootLayerTreeRootPageListRootPropertyListRootToolbarRootColorPickerRootFillPickerRootFontPickerRoot
These components coordinate structure and state, but do not impose app styling.
Public API tiers
Core API
These are the main APIs most SDK consumers should start with.
Context and canvas
provideEditor()useEditor()useCanvas()useCanvasInput()useTextEdit()
Selection and commands
useSelectionState()useSelectionCapabilities()useEditorCommands()useMenuModel()
Property panels
usePosition()useLayout()useAppearance()useTypography()useExport()useFillControls()useStrokeControls()useEffectsControls()
Variables and navigation
useVariablesEditor()usePageList()
Headless primitives
CanvasRootLayerTreeRootPageListRootPropertyListRootToolbarRoot
Advanced API
These exports are intentionally public, but they are lower-level or more specialized.
useNodeProps()useSceneComputed()useColorVariableBinding()useFillPicker()useGradientStops()useFontPicker()usePropScrub()useLayerDrag()useInlineRename()useToolbarState()useNodeFontStatus()useCanvasDrop()extractImageFilesFromClipboard()toolCursor()
Primitive context helpers
These are mostly useful when extending SDK primitives rather than building from top-level composables.
useCanvasContext()useLayerTree()useToolbar()usePropertyList()useScrubInput()
Example patterns
Minimal provider component
<script setup lang="ts">
import { provideEditor } from '@open-pencil/vue'
import type { Editor } from '@open-pencil/core/editor'
const props = defineProps<{
editor: Editor
}>()
provideEditor(props.editor)
</script>
<template>
<slot />
</template>
Read selection state
import { useSelectionState } from '@open-pencil/vue'
const { hasSelection, selectedCount, selectedNode } = useSelectionState()
Build a menu
import { useMenuModel } from '@open-pencil/vue'
const { appMenu, canvasMenu } = useMenuModel()
Build a page list
<PageListRoot v-slot="{ pages, currentPageId, switchPage }">
<ul>
<li v-for="page in pages" :key="page.id">
<button :data-active="page.id === currentPageId" @click="switchPage(page.id)">
{{ page.name }}
</button>
</li>
</ul>
</PageListRoot>
Documentation
For fuller guides and API docs, see the documentation site:
packages/docs/programmable/sdk/
Example app
Run the included example:
cd packages/vue/example
bun install
bun run dev