Refine docs structure for SDK and automation
This commit is contained in:
parent
07fade6abb
commit
99e646aaa4
|
|
@ -13,7 +13,7 @@ Bun workspace with three packages:
|
|||
- `packages/docs` — `@open-pencil/docs`: VitePress documentation site. Run with `cd packages/docs && bun run dev`.
|
||||
- `packages/mcp` — `@open-pencil/mcp`: MCP server for AI coding tools. Stdio + HTTP (Hono). Reuses `createServer()` factory with all core tools.
|
||||
|
||||
- `packages/vue` — `@open-pencil/vue`: headless Vue 3 SDK (Reka UI-style). Renderless components, composables. The app is a consumer.
|
||||
- `packages/vue` — `@open-pencil/vue`: headless Vue 3 SDK (Reka UI-style) for building custom OpenPencil-powered editor shells and embedded editing surfaces. Renderless components and composables. The app is one consumer of the SDK.
|
||||
|
||||
The root app (`src/`) is the Tauri/Vite desktop editor. Its `src/engine/` files are thin re-export shims from `@open-pencil/core`. `src/composables/use-canvas.ts` re-exports from `@open-pencil/vue`.
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ bun run test # Playwright E2E
|
|||
- `CHANGELOG.md` — all user-facing changes, grouped by version. "Unreleased" section at top for in-progress work.
|
||||
- `README.md` — user-facing: features, getting started, CLI, project structure. No implementation details.
|
||||
- `AGENTS.md` (this file) — contributor/agent reference: architecture, conventions, how to release.
|
||||
- `packages/docs/` — VitePress site deployed at `openpencil.dev`. User guide, reference, development docs.
|
||||
- `packages/docs/` — VitePress site deployed at `openpencil.dev`. User guide, SDK, automation, reference, and development docs.
|
||||
|
||||
When adding features, update `CHANGELOG.md` (Unreleased section) and `README.md` (if user-facing). Update `AGENTS.md` when architecture or conventions change.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# OpenPencil
|
||||
|
||||
Open-source design editor. Opens Figma files, built-in AI, fully programmable.
|
||||
Open-source design editor. Opens Figma files, built-in AI, fully programmable, and designed as a toolkit for building custom editors.
|
||||
|
||||
> **Status:** Active development. Not ready for production use.
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ Or download from the [releases page](https://github.com/open-pencil/open-pencil/
|
|||
- **Opens .fig files** — read and write native Figma files, copy & paste nodes between apps
|
||||
- **AI builds designs** — describe what you want in chat, 90+ tools create and modify nodes. Connect Anthropic, OpenAI, Google AI, OpenRouter, or any compatible endpoint
|
||||
- **Fully programmable** — headless CLI, Figma Plugin API via `eval`, MCP server for AI agents
|
||||
- **Toolkit for custom editors** — Vue SDK primitives and composables for embedding OpenPencil into other apps or building workflow-specific editing surfaces
|
||||
- **Real-time collaboration** — P2P via WebRTC, no server, no account. Cursors, presence, follow mode
|
||||
- **Auto layout & CSS Grid** — flex and grid layout via Yoga WASM, with gap, padding, alignment, track sizing
|
||||
- **Tailwind CSS export** — export any selection as HTML with Tailwind v4 utility classes
|
||||
|
|
|
|||
8
bun.lock
8
bun.lock
|
|
@ -123,6 +123,9 @@
|
|||
"packages/docs": {
|
||||
"name": "@open-pencil/docs",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^5.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.4",
|
||||
},
|
||||
|
|
@ -157,6 +160,9 @@
|
|||
"canvaskit-wasm": ">=0.39.0",
|
||||
"vue": "^3.4.0",
|
||||
},
|
||||
"optionalPeers": [
|
||||
"canvaskit-wasm",
|
||||
],
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
|
|
@ -596,6 +602,8 @@
|
|||
|
||||
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
|
||||
|
||||
"@iconify/vue": ["@iconify/vue@5.0.0", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3" } }, "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg=="],
|
||||
|
||||
"@internationalized/date": ["@internationalized/date@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q=="],
|
||||
|
||||
"@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ interface SidebarLabels {
|
|||
}
|
||||
|
||||
interface ProgrammableLabels {
|
||||
overview: string
|
||||
cli: string
|
||||
inspecting: string
|
||||
exporting: string
|
||||
|
|
@ -38,13 +39,85 @@ interface ProgrammableLabels {
|
|||
}
|
||||
|
||||
interface NavLabels {
|
||||
guide: string
|
||||
userGuide: string
|
||||
programmable: string
|
||||
sdk: string
|
||||
reference: string
|
||||
development: string
|
||||
openApp: string
|
||||
}
|
||||
|
||||
const SDK_COMPONENT_PAGES = [
|
||||
{ text: 'CanvasRoot', slug: 'canvas-root' },
|
||||
{ text: 'CanvasSurface', slug: 'canvas-surface' },
|
||||
{ text: 'LayerTreeRoot', slug: 'layer-tree-root' },
|
||||
{ text: 'LayerTreeItem', slug: 'layer-tree-item' },
|
||||
{ text: 'ToolbarRoot', slug: 'toolbar-root' },
|
||||
{ text: 'ToolbarItem', slug: 'toolbar-item' },
|
||||
{ text: 'PageListRoot', slug: 'page-list-root' },
|
||||
{ text: 'PropertyListRoot', slug: 'property-list-root' },
|
||||
{ text: 'PropertyListItem', slug: 'property-list-item' },
|
||||
{ text: 'ColorPickerRoot', slug: 'color-picker-root' },
|
||||
{ text: 'ColorInputRoot', slug: 'color-input-root' },
|
||||
{ text: 'FillPickerRoot', slug: 'fill-picker-root' },
|
||||
{ text: 'FontPickerRoot', slug: 'font-picker-root' },
|
||||
{ text: 'GradientEditorRoot', slug: 'gradient-editor-root' },
|
||||
{ text: 'GradientEditorBar', slug: 'gradient-editor-bar' },
|
||||
{ text: 'GradientEditorStop', slug: 'gradient-editor-stop' },
|
||||
{ text: 'ScrubInputRoot', slug: 'scrub-input-root' },
|
||||
{ text: 'ScrubInputField', slug: 'scrub-input-field' },
|
||||
{ text: 'ScrubInputDisplay', slug: 'scrub-input-display' },
|
||||
{ text: 'LayoutControlsRoot', slug: 'layout-controls-root' },
|
||||
{ text: 'AppearanceControlsRoot', slug: 'appearance-controls-root' },
|
||||
{ text: 'PositionControlsRoot', slug: 'position-controls-root' },
|
||||
{ text: 'TypographyControlsRoot', slug: 'typography-controls-root' },
|
||||
] as const
|
||||
|
||||
const SDK_COMPOSABLE_PAGES = [
|
||||
{ text: 'provideEditor', slug: 'provide-editor' },
|
||||
{ text: 'useEditor', slug: 'use-editor' },
|
||||
{ text: 'useCanvas', slug: 'use-canvas' },
|
||||
{ text: 'useCanvasInput', slug: 'use-canvas-input' },
|
||||
{ text: 'useTextEdit', slug: 'use-text-edit' },
|
||||
{ text: 'useSelectionState', slug: 'use-selection-state' },
|
||||
{ text: 'useSelectionCapabilities', slug: 'use-selection-capabilities' },
|
||||
{ text: 'useEditorCommands', slug: 'use-editor-commands' },
|
||||
{ text: 'useMenuModel', slug: 'use-menu-model' },
|
||||
{ text: 'usePosition', slug: 'use-position' },
|
||||
{ text: 'useLayout', slug: 'use-layout' },
|
||||
{ text: 'useAppearance', slug: 'use-appearance' },
|
||||
{ text: 'useTypography', slug: 'use-typography' },
|
||||
{ text: 'useExport', slug: 'use-export' },
|
||||
{ text: 'useFillControls', slug: 'use-fill-controls' },
|
||||
{ text: 'useStrokeControls', slug: 'use-stroke-controls' },
|
||||
{ text: 'useEffectsControls', slug: 'use-effects-controls' },
|
||||
{ text: 'useVariablesEditor', slug: 'use-variables-editor' },
|
||||
{ text: 'usePageList', slug: 'use-page-list' },
|
||||
] as const
|
||||
|
||||
const SDK_ADVANCED_PAGES = [
|
||||
{ text: 'useNodeProps', slug: 'use-node-props' },
|
||||
{ text: 'useSceneComputed', slug: 'use-scene-computed' },
|
||||
{ text: 'useFillVariableBinding', slug: 'use-fill-variable-binding' },
|
||||
{ text: 'useFillPicker', slug: 'use-fill-picker' },
|
||||
{ text: 'useGradientStops', slug: 'use-gradient-stops' },
|
||||
{ text: 'useFontPicker', slug: 'use-font-picker' },
|
||||
{ text: 'usePropScrub', slug: 'use-prop-scrub' },
|
||||
{ text: 'useLayerDrag', slug: 'use-layer-drag' },
|
||||
{ text: 'useInlineRename', slug: 'use-inline-rename' },
|
||||
{ text: 'useToolbarState', slug: 'use-toolbar-state' },
|
||||
{ text: 'useNodeFontStatus', slug: 'use-node-font-status' },
|
||||
{ text: 'useCanvasDrop', slug: 'use-canvas-drop' },
|
||||
{ text: 'extractImageFilesFromClipboard', slug: 'extract-image-files-from-clipboard' },
|
||||
{ text: 'toolCursor', slug: 'tool-cursor' },
|
||||
{ text: 'useCanvasContext', slug: 'use-canvas-context' },
|
||||
{ text: 'useLayerTree', slug: 'use-layer-tree' },
|
||||
{ text: 'useToolbar', slug: 'use-toolbar' },
|
||||
{ text: 'usePropertyList', slug: 'use-property-list' },
|
||||
{ text: 'useScrubInput', slug: 'use-scrub-input' },
|
||||
] as const
|
||||
|
||||
const userGuideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.SidebarItem[] => [
|
||||
{
|
||||
text: l.gettingAround,
|
||||
|
|
@ -78,7 +151,14 @@ const userGuideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.Sideba
|
|||
},
|
||||
]
|
||||
|
||||
const programmableSidebar = (prefix: string, p: ProgrammableLabels): DefaultTheme.SidebarItem[] => [
|
||||
const programmableSidebar = (
|
||||
prefix: string,
|
||||
p: ProgrammableLabels,
|
||||
): DefaultTheme.SidebarItem[] => [
|
||||
{
|
||||
text: p.overview,
|
||||
link: `${prefix}/programmable/`,
|
||||
},
|
||||
{
|
||||
text: p.aiChat,
|
||||
link: `${prefix}/programmable/ai-chat`,
|
||||
|
|
@ -106,6 +186,57 @@ const programmableSidebar = (prefix: string, p: ProgrammableLabels): DefaultThem
|
|||
},
|
||||
]
|
||||
|
||||
|
||||
const sdkSidebar = (prefix: string): DefaultTheme.SidebarItem[] => [
|
||||
{
|
||||
text: 'Vue SDK',
|
||||
items: [
|
||||
{ text: 'Overview', link: `${prefix}/programmable/sdk/` },
|
||||
{ text: 'Getting Started', link: `${prefix}/programmable/sdk/getting-started` },
|
||||
{ text: 'Architecture', link: `${prefix}/programmable/sdk/architecture` },
|
||||
{ text: 'Custom Shell', link: `${prefix}/programmable/sdk/guides/custom-editor-shell` },
|
||||
{ text: 'Property Panels', link: `${prefix}/programmable/sdk/guides/property-panels` },
|
||||
{ text: 'Navigation Panels', link: `${prefix}/programmable/sdk/guides/navigation-panels` },
|
||||
{
|
||||
text: 'API Reference',
|
||||
items: [
|
||||
{ text: 'Overview', link: `${prefix}/programmable/sdk/api/` },
|
||||
{
|
||||
text: 'Components',
|
||||
items: [
|
||||
{ text: 'Overview', link: `${prefix}/programmable/sdk/api/components/` },
|
||||
...SDK_COMPONENT_PAGES.map((page) => ({
|
||||
text: page.text,
|
||||
link: `${prefix}/programmable/sdk/api/components/${page.slug}`,
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Composables',
|
||||
items: [
|
||||
{ text: 'Overview', link: `${prefix}/programmable/sdk/api/composables/` },
|
||||
...SDK_COMPOSABLE_PAGES.map((page) => ({
|
||||
text: page.text,
|
||||
link: `${prefix}/programmable/sdk/api/composables/${page.slug}`,
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
items: [
|
||||
{ text: 'Overview', link: `${prefix}/programmable/sdk/api/advanced/` },
|
||||
...SDK_ADVANCED_PAGES.map((page) => ({
|
||||
text: page.text,
|
||||
link: `${prefix}/programmable/sdk/api/advanced/${page.slug}`,
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const guideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.SidebarItem[] => [
|
||||
{
|
||||
text: l.guide,
|
||||
|
|
@ -120,12 +251,11 @@ const guideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.SidebarIte
|
|||
},
|
||||
]
|
||||
|
||||
const referenceSidebar = (prefix: string, label: string, l: SidebarLabels): DefaultTheme.SidebarItem[] => [
|
||||
const referenceSidebar = (prefix: string, label: string): DefaultTheme.SidebarItem[] => [
|
||||
{
|
||||
text: label,
|
||||
items: [
|
||||
{ text: 'Keyboard Shortcuts', link: `${prefix}/reference/keyboard-shortcuts` },
|
||||
{ text: l.contextMenu, link: `${prefix}/user-guide/context-menu` },
|
||||
{ text: 'CLI', link: `${prefix}/reference/cli` },
|
||||
{ text: 'Node Types', link: `${prefix}/reference/node-types` },
|
||||
{ text: 'Scene Graph', link: `${prefix}/reference/scene-graph` },
|
||||
|
|
@ -146,13 +276,13 @@ const developmentSidebar = (prefix: string, label: string): DefaultTheme.Sidebar
|
|||
},
|
||||
]
|
||||
|
||||
const EN_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspecting Files', exporting: 'Exporting', analyzing: 'Analyzing Designs', scripting: 'Scripting', jsxRenderer: 'JSX Renderer', mcpServer: 'MCP Server', aiChat: 'AI Chat', collaboration: 'Collaboration' }
|
||||
const DE_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Dateien inspizieren', exporting: 'Exportieren', analyzing: 'Designs analysieren', scripting: 'Skripte', jsxRenderer: 'JSX-Renderer', mcpServer: 'MCP-Server', aiChat: 'KI-Chat', collaboration: 'Zusammenarbeit' }
|
||||
const IT_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Ispezione file', exporting: 'Esportazione', analyzing: 'Analisi design', scripting: 'Scripting', jsxRenderer: 'Renderer JSX', mcpServer: 'Server MCP', aiChat: 'Chat IA', collaboration: 'Collaborazione' }
|
||||
const FR_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspecter les fichiers', exporting: 'Exporter', analyzing: 'Analyser les designs', scripting: 'Scripts', jsxRenderer: 'Moteur JSX', mcpServer: 'Serveur MCP', aiChat: 'Chat IA', collaboration: 'Collaboration' }
|
||||
const ES_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspeccionar archivos', exporting: 'Exportar', analyzing: 'Analizar diseños', scripting: 'Scripts', jsxRenderer: 'Renderizador JSX', mcpServer: 'Servidor MCP', aiChat: 'Chat IA', collaboration: 'Colaboración' }
|
||||
const PL_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspekcja plików', exporting: 'Eksportowanie', analyzing: 'Analiza projektów', scripting: 'Skrypty', jsxRenderer: 'Renderer JSX', mcpServer: 'Serwer MCP', aiChat: 'Czat AI', collaboration: 'Współpraca' }
|
||||
const RU_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Инспекция файлов', exporting: 'Экспорт', analyzing: 'Анализ дизайна', scripting: 'Скрипты', jsxRenderer: 'JSX-рендерер', mcpServer: 'MCP-сервер', aiChat: 'ИИ-чат', collaboration: 'Совместная работа' }
|
||||
const EN_PROG: ProgrammableLabels = { overview: 'Overview', cli: 'CLI', inspecting: 'Inspecting Files', exporting: 'Exporting', analyzing: 'Analyzing Designs', scripting: 'Scripting', jsxRenderer: 'JSX Renderer', mcpServer: 'MCP Server', aiChat: 'AI Chat', collaboration: 'Collaboration' }
|
||||
const DE_PROG: ProgrammableLabels = { overview: 'Überblick', cli: 'CLI', inspecting: 'Dateien inspizieren', exporting: 'Exportieren', analyzing: 'Designs analysieren', scripting: 'Skripte', jsxRenderer: 'JSX-Renderer', mcpServer: 'MCP-Server', aiChat: 'KI-Chat', collaboration: 'Zusammenarbeit' }
|
||||
const IT_PROG: ProgrammableLabels = { overview: 'Panoramica', cli: 'CLI', inspecting: 'Ispezione file', exporting: 'Esportazione', analyzing: 'Analisi design', scripting: 'Scripting', jsxRenderer: 'Renderer JSX', mcpServer: 'Server MCP', aiChat: 'Chat IA', collaboration: 'Collaborazione' }
|
||||
const FR_PROG: ProgrammableLabels = { overview: 'Vue d’ensemble', cli: 'CLI', inspecting: 'Inspecter les fichiers', exporting: 'Exporter', analyzing: 'Analyser les designs', scripting: 'Scripts', jsxRenderer: 'Moteur JSX', mcpServer: 'Serveur MCP', aiChat: 'Chat IA', collaboration: 'Collaboration' }
|
||||
const ES_PROG: ProgrammableLabels = { overview: 'Resumen', cli: 'CLI', inspecting: 'Inspeccionar archivos', exporting: 'Exportar', analyzing: 'Analizar diseños', scripting: 'Scripts', jsxRenderer: 'Renderizador JSX', mcpServer: 'Servidor MCP', aiChat: 'Chat IA', collaboration: 'Colaboración' }
|
||||
const PL_PROG: ProgrammableLabels = { overview: 'Przegląd', cli: 'CLI', inspecting: 'Inspekcja plików', exporting: 'Eksportowanie', analyzing: 'Analiza projektów', scripting: 'Skrypty', jsxRenderer: 'Renderer JSX', mcpServer: 'Serwer MCP', aiChat: 'Czat AI', collaboration: 'Współpraca' }
|
||||
const RU_PROG: ProgrammableLabels = { overview: 'Обзор', cli: 'CLI', inspecting: 'Инспекция файлов', exporting: 'Экспорт', analyzing: 'Анализ дизайна', scripting: 'Скрипты', jsxRenderer: 'JSX-рендерер', mcpServer: 'MCP-сервер', aiChat: 'ИИ-чат', collaboration: 'Совместная работа' }
|
||||
|
||||
const localeThemeConfig = (
|
||||
prefix: string,
|
||||
|
|
@ -161,16 +291,19 @@ const localeThemeConfig = (
|
|||
prog: ProgrammableLabels,
|
||||
): DefaultTheme.Config => ({
|
||||
nav: [
|
||||
{ text: nav.guide, link: `${prefix}/guide/getting-started` },
|
||||
{ text: nav.userGuide, link: `${prefix}/user-guide/` },
|
||||
{ text: nav.programmable, link: `${prefix}/programmable/` },
|
||||
{ text: nav.sdk, link: `${prefix}/programmable/sdk/` },
|
||||
{ text: nav.reference, link: `${prefix}/reference/keyboard-shortcuts` },
|
||||
{ text: nav.development, link: `${prefix}/development/contributing` },
|
||||
{ text: nav.openApp, link: 'https://app.openpencil.dev' },
|
||||
],
|
||||
sidebar: {
|
||||
[`${prefix}/user-guide/`]: userGuideSidebar(prefix, sidebar),
|
||||
[`${prefix}/programmable/sdk/`]: sdkSidebar(prefix),
|
||||
[`${prefix}/programmable/`]: programmableSidebar(prefix, prog),
|
||||
[`${prefix}/reference/`]: referenceSidebar(prefix, nav.reference, sidebar),
|
||||
[`${prefix}/reference/`]: referenceSidebar(prefix, nav.reference),
|
||||
[`${prefix}/`]: [
|
||||
...guideSidebar(prefix, sidebar),
|
||||
...developmentSidebar(prefix, nav.development),
|
||||
|
|
@ -302,37 +435,37 @@ export default defineConfig({
|
|||
label: 'Deutsch',
|
||||
lang: 'de',
|
||||
description: 'Open-Source, KI-nativer Design-Editor. Figma-Alternative.',
|
||||
themeConfig: localeThemeConfig('/de', { userGuide: 'Benutzerhandbuch', programmable: 'KI & Automatisierung', reference: 'Referenz', development: 'Entwicklung', openApp: 'App öffnen' }, DE, DE_PROG),
|
||||
themeConfig: localeThemeConfig('/de', { guide: 'Guide', userGuide: 'Benutzerhandbuch', programmable: 'Automation', sdk: 'SDK', reference: 'Referenz', development: 'Entwicklung', openApp: 'App öffnen' }, DE, DE_PROG),
|
||||
},
|
||||
it: {
|
||||
label: 'Italiano',
|
||||
lang: 'it',
|
||||
description: 'Editor di design open-source, IA-nativo. Alternativa a Figma.',
|
||||
themeConfig: localeThemeConfig('/it', { userGuide: 'Guida utente', programmable: 'IA & Automazione', reference: 'Riferimento', development: 'Sviluppo', openApp: 'Apri app' }, IT, IT_PROG),
|
||||
themeConfig: localeThemeConfig('/it', { guide: 'Guida', userGuide: 'Guida utente', programmable: 'Automation', sdk: 'SDK', reference: 'Riferimento', development: 'Sviluppo', openApp: 'Apri app' }, IT, IT_PROG),
|
||||
},
|
||||
fr: {
|
||||
label: 'Français',
|
||||
lang: 'fr',
|
||||
description: 'Éditeur de design open-source, IA-natif. Alternative à Figma.',
|
||||
themeConfig: localeThemeConfig('/fr', { userGuide: 'Guide utilisateur', programmable: 'IA & Automatisation', reference: 'Référence', development: 'Développement', openApp: "Ouvrir l'app" }, FR, FR_PROG),
|
||||
themeConfig: localeThemeConfig('/fr', { guide: 'Guide', userGuide: 'Guide utilisateur', programmable: 'Automation', sdk: 'SDK', reference: 'Référence', development: 'Développement', openApp: "Ouvrir l'app" }, FR, FR_PROG),
|
||||
},
|
||||
es: {
|
||||
label: 'Español',
|
||||
lang: 'es',
|
||||
description: 'Editor de diseño open-source, IA-nativo. Alternativa a Figma.',
|
||||
themeConfig: localeThemeConfig('/es', { userGuide: 'Guía del usuario', programmable: 'IA & Automatización', reference: 'Referencia', development: 'Desarrollo', openApp: 'Abrir app' }, ES, ES_PROG),
|
||||
themeConfig: localeThemeConfig('/es', { guide: 'Guía', userGuide: 'Guía del usuario', programmable: 'Automation', sdk: 'SDK', reference: 'Referencia', development: 'Desarrollo', openApp: 'Abrir app' }, ES, ES_PROG),
|
||||
},
|
||||
pl: {
|
||||
label: 'Polski',
|
||||
lang: 'pl',
|
||||
description: "Open-source'owy edytor graficzny z natywnym AI. Alternatywa dla Figmy.",
|
||||
themeConfig: localeThemeConfig('/pl', { userGuide: 'Podręcznik', programmable: 'AI i automatyzacja', reference: 'Referencja', development: 'Rozwój', openApp: 'Otwórz app' }, PL, PL_PROG),
|
||||
themeConfig: localeThemeConfig('/pl', { guide: 'Przewodnik', userGuide: 'Podręcznik', programmable: 'Automation', sdk: 'SDK', reference: 'Referencja', development: 'Rozwój', openApp: 'Otwórz app' }, PL, PL_PROG),
|
||||
},
|
||||
ru: {
|
||||
label: 'Русский',
|
||||
lang: 'ru',
|
||||
description: 'Дизайн-редактор с открытым исходным кодом. Альтернатива Figma с встроенным ИИ.',
|
||||
themeConfig: localeThemeConfig('/ru', { userGuide: 'Руководство', programmable: 'ИИ и автоматизация', reference: 'Справочник', development: 'Разработка', openApp: 'Открыть приложение' }, RU, RU_PROG),
|
||||
themeConfig: localeThemeConfig('/ru', { guide: 'Руководство', userGuide: 'Руководство', programmable: 'Automation', sdk: 'SDK', reference: 'Справочник', development: 'Разработка', openApp: 'Открыть приложение' }, RU, RU_PROG),
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -340,8 +473,10 @@ export default defineConfig({
|
|||
search: { provider: 'local' },
|
||||
|
||||
nav: [
|
||||
{ text: 'Guide', link: '/guide/getting-started' },
|
||||
{ text: 'User Guide', link: '/user-guide/' },
|
||||
{ text: 'AI & Automation', link: '/programmable/' },
|
||||
{ text: 'Automation', link: '/programmable/' },
|
||||
{ text: 'SDK', link: '/programmable/sdk/' },
|
||||
{ text: 'Reference', link: '/reference/keyboard-shortcuts' },
|
||||
{ text: 'Development', link: '/development/contributing' },
|
||||
{ text: 'Open App', link: 'https://app.openpencil.dev' },
|
||||
|
|
@ -349,8 +484,9 @@ export default defineConfig({
|
|||
|
||||
sidebar: {
|
||||
'/user-guide/': userGuideSidebar('', EN),
|
||||
'/programmable/sdk/': sdkSidebar(''),
|
||||
'/programmable/': programmableSidebar('', EN_PROG),
|
||||
'/reference/': referenceSidebar('', 'Reference', EN),
|
||||
'/reference/': referenceSidebar('', 'Reference'),
|
||||
'/': [
|
||||
...guideSidebar('', EN),
|
||||
{
|
||||
|
|
|
|||
51
packages/docs/.vitepress/theme/components/SdkCard.vue
Normal file
51
packages/docs/.vitepress/theme/components/SdkCard.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
to: string
|
||||
description?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a :href="to" class="sdk-card">
|
||||
<div class="sdk-card__title">{{ title }}</div>
|
||||
<div v-if="description" class="sdk-card__description">
|
||||
{{ description }}
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sdk-card {
|
||||
display: block;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 16px;
|
||||
background: color-mix(in srgb, var(--vp-c-bg-soft) 72%, transparent);
|
||||
padding: 16px;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.sdk-card:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-bg-soft);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.sdk-card__title {
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.sdk-card__description {
|
||||
margin-top: 8px;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
13
packages/docs/.vitepress/theme/components/SdkCardGroup.vue
Normal file
13
packages/docs/.vitepress/theme/components/SdkCardGroup.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<div class="sdk-card-group">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sdk-card-group {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
}
|
||||
</style>
|
||||
89
packages/docs/.vitepress/theme/components/SdkEventsTable.vue
Normal file
89
packages/docs/.vitepress/theme/components/SdkEventsTable.vue
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<script setup lang="ts">
|
||||
interface Row {
|
||||
name: string
|
||||
payload?: string
|
||||
description: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
rows: Row[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sdk-table-wrap">
|
||||
<table class="sdk-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Payload</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in rows" :key="row.name">
|
||||
<td><code class="sdk-table__code">{{ row.name }}</code></td>
|
||||
<td>
|
||||
<code v-if="row.payload" class="sdk-table__chip">{{ row.payload }}</code>
|
||||
<span v-else>—</span>
|
||||
</td>
|
||||
<td class="sdk-table__description-cell">{{ row.description }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sdk-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.sdk-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sdk-table th,
|
||||
.sdk-table td {
|
||||
padding: 16px;
|
||||
vertical-align: top;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.sdk-table thead th {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sdk-table thead th:first-child {
|
||||
border-top-left-radius: 14px;
|
||||
}
|
||||
|
||||
.sdk-table thead th:last-child {
|
||||
border-top-right-radius: 14px;
|
||||
}
|
||||
|
||||
.sdk-table__code {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.sdk-table__chip {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sdk-table__description-cell {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
18
packages/docs/.vitepress/theme/components/SdkField.vue
Normal file
18
packages/docs/.vitepress/theme/components/SdkField.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
name: string
|
||||
type?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border border-white/10 bg-white/[0.02] px-4 py-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<code class="text-sm font-medium text-white">{{ name }}</code>
|
||||
<code v-if="type" class="text-xs text-violet-300">{{ type }}</code>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-zinc-300">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div class="mt-6 space-y-3">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
98
packages/docs/.vitepress/theme/components/SdkPropsTable.vue
Normal file
98
packages/docs/.vitepress/theme/components/SdkPropsTable.vue
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<script setup lang="ts">
|
||||
interface Row {
|
||||
name: string
|
||||
type?: string
|
||||
description: string
|
||||
required?: boolean
|
||||
default?: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
rows: Row[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sdk-table-wrap">
|
||||
<table class="sdk-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Prop</th>
|
||||
<th>Default</th>
|
||||
<th>Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in rows" :key="row.name">
|
||||
<td>
|
||||
<code class="sdk-table__code">{{ row.name }}<span v-if="row.required">*</span></code>
|
||||
<div class="sdk-table__description">{{ row.description }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<code v-if="row.default" class="sdk-table__chip">{{ row.default }}</code>
|
||||
<span v-else>—</span>
|
||||
</td>
|
||||
<td>
|
||||
<code v-if="row.type" class="sdk-table__chip">{{ row.type }}</code>
|
||||
<span v-else>—</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sdk-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.sdk-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sdk-table th,
|
||||
.sdk-table td {
|
||||
padding: 16px;
|
||||
vertical-align: top;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.sdk-table thead th {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sdk-table thead th:first-child {
|
||||
border-top-left-radius: 14px;
|
||||
}
|
||||
|
||||
.sdk-table thead th:last-child {
|
||||
border-top-right-radius: 14px;
|
||||
}
|
||||
|
||||
.sdk-table__code {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.sdk-table__chip {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sdk-table__description {
|
||||
margin-top: 8px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
interface LinkItem {
|
||||
text: string
|
||||
to: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
items: LinkItem[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="mt-4 space-y-2">
|
||||
<li v-for="item in items" :key="item.to">
|
||||
<a :href="item.to" class="text-sm text-violet-300 hover:text-violet-200">{{ item.text }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
88
packages/docs/.vitepress/theme/components/SdkSlotsTable.vue
Normal file
88
packages/docs/.vitepress/theme/components/SdkSlotsTable.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<script setup lang="ts">
|
||||
interface Row {
|
||||
name: string
|
||||
props?: string
|
||||
description: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
rows: Row[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sdk-table-wrap">
|
||||
<table class="sdk-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slot</th>
|
||||
<th>Props</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in rows" :key="row.name">
|
||||
<td><code class="sdk-table__code">{{ row.name }}</code></td>
|
||||
<td>
|
||||
<code v-if="row.props" class="sdk-table__chip">{{ row.props }}</code>
|
||||
<span v-else>—</span>
|
||||
</td>
|
||||
<td class="sdk-table__description-cell">{{ row.description }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sdk-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.sdk-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sdk-table th,
|
||||
.sdk-table td {
|
||||
padding: 16px;
|
||||
vertical-align: top;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.sdk-table thead th {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sdk-table thead th:first-child {
|
||||
border-top-left-radius: 14px;
|
||||
}
|
||||
|
||||
.sdk-table thead th:last-child {
|
||||
border-top-right-radius: 14px;
|
||||
}
|
||||
|
||||
.sdk-table__code {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.sdk-table__chip {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.sdk-table__description-cell {
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,26 @@
|
|||
import DefaultTheme from 'vitepress/theme'
|
||||
|
||||
import HomeLayout from './HomeLayout.vue'
|
||||
import SdkCard from './components/SdkCard.vue'
|
||||
import SdkCardGroup from './components/SdkCardGroup.vue'
|
||||
import SdkEventsTable from './components/SdkEventsTable.vue'
|
||||
import SdkField from './components/SdkField.vue'
|
||||
import SdkFieldGroup from './components/SdkFieldGroup.vue'
|
||||
import SdkPropsTable from './components/SdkPropsTable.vue'
|
||||
import SdkRelatedLinks from './components/SdkRelatedLinks.vue'
|
||||
import SdkSlotsTable from './components/SdkSlotsTable.vue'
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout: HomeLayout,
|
||||
enhanceApp({ app }) {
|
||||
app.component('SdkCard', SdkCard)
|
||||
app.component('SdkCardGroup', SdkCardGroup)
|
||||
app.component('SdkEventsTable', SdkEventsTable)
|
||||
app.component('SdkField', SdkField)
|
||||
app.component('SdkFieldGroup', SdkFieldGroup)
|
||||
app.component('SdkPropsTable', SdkPropsTable)
|
||||
app.component('SdkRelatedLinks', SdkRelatedLinks)
|
||||
app.component('SdkSlotsTable', SdkSlotsTable)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
OpenPencil runs in the browser — no installation required. Open [app.openpencil.dev](https://app.openpencil.dev) to start designing.
|
||||
|
||||
If you want to build on top of it instead of only using the default app, see the [Programmable](/programmable/) section and the [Vue SDK](/programmable/sdk/).
|
||||
|
||||
## Download Desktop App
|
||||
|
||||
Pre-built binaries for macOS, Windows, and Linux are available on the [releases page](https://github.com/open-pencil/open-pencil/releases/latest).
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ description: Open-source Figma alternative. Opens .fig files, built-in AI, fully
|
|||
hero:
|
||||
name: OpenPencil
|
||||
text: Open-Source Design Editor
|
||||
tagline: Opens Figma files. Built-in AI. Fully programmable. Free forever.
|
||||
tagline: Opens Figma files. Built-in AI. Fully programmable. Also a toolkit for building custom editors.
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Try Online
|
||||
|
|
@ -24,7 +24,10 @@ features:
|
|||
details: Opens .fig files natively. Copy & paste between Figma and OpenPencil. Kiwi binary codec with round-trip fidelity.
|
||||
- icon: ⚡
|
||||
title: Programmable
|
||||
details: Headless CLI to inspect, export, and analyze .fig files. Figma Plugin API via eval. Tailwind CSS export. JSON output for CI.
|
||||
details: Headless CLI to inspect, export, and analyze .fig files. Figma Plugin API via eval. Tailwind CSS export. JSON output for CI and automation.
|
||||
- icon: 🧩
|
||||
title: Toolkit, not just app
|
||||
details: Build custom editing surfaces with the Vue SDK, embed OpenPencil into other products, or assemble workflow-specific editors around the same core.
|
||||
- icon: 🤖
|
||||
title: AI-Native
|
||||
details: Built-in chat with 90 tools — create shapes, set styles, manage layout, analyze tokens. MCP server for Claude Code, Cursor, Windsurf.
|
||||
|
|
|
|||
|
|
@ -11,5 +11,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
---
|
||||
layout: doc
|
||||
title: AI & Automation
|
||||
description: Every operation in OpenPencil is scriptable — AI chat, CLI, JSX renderer, MCP server, real-time collaboration.
|
||||
title: Automation
|
||||
description: AI chat, CLI, JSX renderer, MCP server, and other automation surfaces built on the OpenPencil editor engine.
|
||||
---
|
||||
|
||||
# AI & Automation
|
||||
# Automation
|
||||
|
||||
OpenPencil treats design files as data. Every operation available in the editor — creating shapes, setting fills, managing auto-layout, exporting assets — is also available from the terminal, from AI agents, and from code. No plugins to install, no API keys, no waiting list.
|
||||
|
||||
The editor UI and the automation interfaces use the same engine. If you can do it by clicking, you can do it by scripting.
|
||||
|
||||
## The bigger idea
|
||||
|
||||
OpenPencil is not just meant to be a design app.
|
||||
|
||||
It is also meant to be a toolkit: something you can embed into other products, wrap with your own UI, and use to build editing workflows that fit your own domain.
|
||||
|
||||
That is why the automation surface matters. The app, the CLI, the AI tools, the JSX renderer, the MCP server, and the SDK all build on the same underlying editor engine.
|
||||
|
||||
## AI Chat
|
||||
|
||||
The built-in assistant has access to 87 tools that cover the full surface of the editor. Describe what you want in natural language — "add a 16px drop shadow to all buttons", "create a card component with dark mode variant", "export every frame on this page at 2×".
|
||||
|
|
@ -22,6 +30,12 @@ Real-time multiplayer editing over peer-to-peer WebRTC. No server, no account. S
|
|||
|
||||
[Collaboration →](./collaboration)
|
||||
|
||||
## Vue SDK
|
||||
|
||||
Build OpenPencil-powered editors with the same Vue SDK the app uses internally. The SDK exposes editor context, canvas wiring, selection state, command models, property-panel composables, and headless primitives.
|
||||
|
||||
[Vue SDK →](./sdk/)
|
||||
|
||||
## JSX Renderer
|
||||
|
||||
Describe UI as JSX — the same syntax LLMs already know from React. A single call can create an entire component tree with frames, text, auto-layout, fills, and strokes. Compact, declarative, and diffable.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: extractImageFilesFromClipboard
|
||||
description: Clipboard utility for extracting accepted image files.
|
||||
---
|
||||
|
||||
# extractImageFilesFromClipboard
|
||||
|
||||
`extractImageFilesFromClipboard(event)` filters clipboard files down to accepted image types used by the canvas drop flow.
|
||||
|
||||
Use it when implementing paste-from-clipboard image workflows.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useCanvasDrop](./use-canvas-drop)
|
||||
- [useCanvas](../composables/use-canvas)
|
||||
39
packages/docs/programmable/sdk/api/advanced/index.md
Normal file
39
packages/docs/programmable/sdk/api/advanced/index.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: Advanced
|
||||
description: Lower-level and specialized APIs in @open-pencil/vue.
|
||||
---
|
||||
|
||||
# Advanced
|
||||
|
||||
These APIs are public, but they are more specialized than the main component and composable surface.
|
||||
|
||||
## Selection and scene helpers
|
||||
|
||||
- [useNodeProps](./use-node-props)
|
||||
- [useSceneComputed](./use-scene-computed)
|
||||
- [usePropScrub](./use-prop-scrub)
|
||||
|
||||
## Picker and editor internals
|
||||
|
||||
- [useFillVariableBinding](./use-fill-variable-binding)
|
||||
- [useFillPicker](./use-fill-picker)
|
||||
- [useGradientStops](./use-gradient-stops)
|
||||
- [useFontPicker](./use-font-picker)
|
||||
- [useToolbarState](./use-toolbar-state)
|
||||
- [useNodeFontStatus](./use-node-font-status)
|
||||
|
||||
## Editor-shell utilities
|
||||
|
||||
- [useLayerDrag](./use-layer-drag)
|
||||
- [useInlineRename](./use-inline-rename)
|
||||
- [useCanvasDrop](./use-canvas-drop)
|
||||
- [extractImageFilesFromClipboard](./extract-image-files-from-clipboard)
|
||||
- [toolCursor](./tool-cursor)
|
||||
|
||||
## Primitive context helpers
|
||||
|
||||
- [useCanvasContext](./use-canvas-context)
|
||||
- [useLayerTree](./use-layer-tree)
|
||||
- [useToolbar](./use-toolbar)
|
||||
- [usePropertyList](./use-property-list)
|
||||
- [useScrubInput](./use-scrub-input)
|
||||
15
packages/docs/programmable/sdk/api/advanced/tool-cursor.md
Normal file
15
packages/docs/programmable/sdk/api/advanced/tool-cursor.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: toolCursor
|
||||
description: Helper that resolves the cursor string for an editor tool.
|
||||
---
|
||||
|
||||
# toolCursor
|
||||
|
||||
`toolCursor(tool, override?)` maps an editor tool to the cursor the SDK should use, while still allowing an explicit override.
|
||||
|
||||
Use it when building custom canvas shells or tool UIs that need consistent cursor behavior.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useCanvas](../composables/use-canvas)
|
||||
- [useEditorCommands](../composables/use-editor-commands)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: useCanvasContext
|
||||
description: Primitive context helper for CanvasRoot descendants.
|
||||
---
|
||||
|
||||
# useCanvasContext
|
||||
|
||||
`useCanvasContext()` reads the local canvas context provided by `CanvasRoot`.
|
||||
|
||||
Use it inside descendants like `CanvasSurface` or your own custom canvas children.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [CanvasRoot](../components/canvas-root)
|
||||
- [CanvasSurface](../components/canvas-surface)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useCanvasDrop
|
||||
description: Image file drag-and-drop helper for canvas surfaces.
|
||||
---
|
||||
|
||||
# useCanvasDrop
|
||||
|
||||
`useCanvasDrop(canvasRef, editor)` wires dragenter, dragover, dragleave, and drop handling so image files dropped on the canvas are placed into the scene.
|
||||
|
||||
Use it when building custom canvas surfaces that accept image drops.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [CanvasRoot](../components/canvas-root)
|
||||
- [CanvasSurface](../components/canvas-surface)
|
||||
- [extractImageFilesFromClipboard](./extract-image-files-from-clipboard)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useFillPicker
|
||||
description: Low-level fill-category and conversion helper.
|
||||
---
|
||||
|
||||
# useFillPicker
|
||||
|
||||
`useFillPicker(fill, onUpdate)` returns derived fill category state plus conversion helpers for switching between solid, gradient, and image fills.
|
||||
|
||||
Use it when building custom fill pickers beyond `FillPickerRoot`.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [FillPickerRoot](../components/fill-picker-root)
|
||||
- [useFillControls](../composables/use-fill-controls)
|
||||
- [useGradientStops](./use-gradient-stops)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: useFillVariableBinding
|
||||
description: Variable-binding helper for fill color editors.
|
||||
---
|
||||
|
||||
# useFillVariableBinding
|
||||
|
||||
`useFillVariableBinding()` exposes search, binding, and unbinding helpers for color variables used by fill editors.
|
||||
|
||||
Use it when building fill UIs that need to connect solid fills to design variables.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useFillControls](../composables/use-fill-controls)
|
||||
- [FillPickerRoot](../components/fill-picker-root)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: useFontPicker
|
||||
description: Searchable font-picker state and selection helper.
|
||||
---
|
||||
|
||||
# useFontPicker
|
||||
|
||||
`useFontPicker(options)` manages available font families, search state, open state, and family selection for font-picker UIs.
|
||||
|
||||
Use it when building custom font-picking UIs beyond `FontPickerRoot`.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [FontPickerRoot](../components/font-picker-root)
|
||||
- [useTypography](../composables/use-typography)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useGradientStops
|
||||
description: Gradient-stop state and mutation helper for fill editors.
|
||||
---
|
||||
|
||||
# useGradientStops
|
||||
|
||||
`useGradientStops(fill, onUpdate)` manages active-stop state, subtype switching, stop dragging, and color or opacity updates for gradient fills.
|
||||
|
||||
Use it when building custom gradient editors beyond the packaged primitives.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [GradientEditorRoot](../components/gradient-editor-root)
|
||||
- [GradientEditorBar](../components/gradient-editor-bar)
|
||||
- [GradientEditorStop](../components/gradient-editor-stop)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: useInlineRename
|
||||
description: Inline rename state and focus-management helper.
|
||||
---
|
||||
|
||||
# useInlineRename
|
||||
|
||||
`useInlineRename(onCommit)` manages editing state, focus, outside-click handling, and keyboard behavior for inline rename flows.
|
||||
|
||||
Use it in custom page lists, layer trees, or similar rename-in-place UIs.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [PageListRoot](../components/page-list-root)
|
||||
- [LayerTreeItem](../components/layer-tree-item)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useLayerDrag
|
||||
description: Drag-and-drop wiring helper for layer tree reordering.
|
||||
---
|
||||
|
||||
# useLayerDrag
|
||||
|
||||
`useLayerDrag(editor, indentPerLevel?)` wires pragmatic-drag-and-drop behavior for layer tree rows and maps drop instructions to editor reorder operations.
|
||||
|
||||
Use it when extending or replacing the default layer-tree drag UI.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [LayerTreeRoot](../components/layer-tree-root)
|
||||
- [LayerTreeItem](../components/layer-tree-item)
|
||||
- [useLayerTree](./use-layer-tree)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: useLayerTree
|
||||
description: Primitive context helper for LayerTreeRoot descendants.
|
||||
---
|
||||
|
||||
# useLayerTree
|
||||
|
||||
`useLayerTree()` reads the local layer-tree context provided by `LayerTreeRoot`.
|
||||
|
||||
Use it inside custom layer-tree descendants that need access to tree items, selection state, expansion state, or row actions.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [LayerTreeRoot](../components/layer-tree-root)
|
||||
- [LayerTreeItem](../components/layer-tree-item)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: useNodeFontStatus
|
||||
description: Missing-font status helper for text nodes.
|
||||
---
|
||||
|
||||
# useNodeFontStatus
|
||||
|
||||
`useNodeFontStatus(node)` returns missing-font information for a text-node getter.
|
||||
|
||||
Use it in typography panels and warnings that need to surface unavailable font families.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useTypography](../composables/use-typography)
|
||||
- [TypographyControlsRoot](../components/typography-controls-root)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: useNodeProps
|
||||
description: Low-level selection and mixed-value helper for property panels.
|
||||
---
|
||||
|
||||
# useNodeProps
|
||||
|
||||
`useNodeProps()` is the low-level property-panel helper behind higher-level composables like `useAppearance`, `useLayout`, and `useTypography`.
|
||||
|
||||
Use it when you need mixed-value detection, multi-selection updates, array-item editing, or undo-aware property commits.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useAppearance](../composables/use-appearance)
|
||||
- [useLayout](../composables/use-layout)
|
||||
- [useTypography](../composables/use-typography)
|
||||
- [PropertyListRoot](../components/property-list-root)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: usePropScrub
|
||||
description: Low-level helper for drag-to-scrub property updates with commit support.
|
||||
---
|
||||
|
||||
# usePropScrub
|
||||
|
||||
`usePropScrub(editor)` coordinates live property updates during scrubbing and commits undo-aware changes when the interaction finishes.
|
||||
|
||||
Use it when building numeric controls that scrub selected node properties directly.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ScrubInputRoot](../components/scrub-input-root)
|
||||
- [useNodeProps](./use-node-props)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: usePropertyList
|
||||
description: Primitive context helper for PropertyListRoot descendants.
|
||||
---
|
||||
|
||||
# usePropertyList
|
||||
|
||||
`usePropertyList()` reads the local property-list context provided by `PropertyListRoot`.
|
||||
|
||||
Use it inside descendants that need current items, mixed-state info, or row-level handlers for fills, strokes, or effects.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [PropertyListRoot](../components/property-list-root)
|
||||
- [PropertyListItem](../components/property-list-item)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useSceneComputed
|
||||
description: Convenience wrapper for scene-derived computed state.
|
||||
---
|
||||
|
||||
# useSceneComputed
|
||||
|
||||
`useSceneComputed(fn)` is a thin computed wrapper used to make scene-backed derived state explicit in higher-level composables.
|
||||
|
||||
Use it when you want intent-revealing computed state that clearly depends on editor scene data.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useSelectionState](../composables/use-selection-state)
|
||||
- [useSelectionCapabilities](../composables/use-selection-capabilities)
|
||||
- [useNodeProps](./use-node-props)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useScrubInput
|
||||
description: Primitive context helper for ScrubInputRoot descendants.
|
||||
---
|
||||
|
||||
# useScrubInput
|
||||
|
||||
`useScrubInput()` reads the local scrub-input context provided by `ScrubInputRoot`.
|
||||
|
||||
Use it inside `ScrubInputField`, `ScrubInputDisplay`, or custom descendants that need editing and scrubbing state.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ScrubInputRoot](../components/scrub-input-root)
|
||||
- [ScrubInputField](../components/scrub-input-field)
|
||||
- [ScrubInputDisplay](../components/scrub-input-display)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useToolbarState
|
||||
description: Presentation-oriented toolbar state helper for mobile category paging.
|
||||
---
|
||||
|
||||
# useToolbarState
|
||||
|
||||
`useToolbarState()` returns mobile-category paging state and helpers like `goPrev()` and `goNext()` for responsive toolbar shells.
|
||||
|
||||
Use it when building toolbar layouts on top of `ToolbarRoot`.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ToolbarRoot](../components/toolbar-root)
|
||||
- [ToolbarItem](../components/toolbar-item)
|
||||
- [useToolbar](./use-toolbar)
|
||||
16
packages/docs/programmable/sdk/api/advanced/use-toolbar.md
Normal file
16
packages/docs/programmable/sdk/api/advanced/use-toolbar.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: useToolbar
|
||||
description: Primitive context helper for ToolbarRoot descendants.
|
||||
---
|
||||
|
||||
# useToolbar
|
||||
|
||||
`useToolbar()` reads the local toolbar context provided by `ToolbarRoot`.
|
||||
|
||||
Use it inside toolbar descendants that need access to tools, active state, or tool selection helpers.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ToolbarRoot](../components/toolbar-root)
|
||||
- [ToolbarItem](../components/toolbar-item)
|
||||
- [useToolbarState](./use-toolbar-state)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: AppearanceControlsRoot
|
||||
description: Headless root primitive for opacity, visibility, and corner-radius controls.
|
||||
---
|
||||
|
||||
# AppearanceControlsRoot
|
||||
|
||||
`AppearanceControlsRoot` exposes the slot contract returned by `useAppearance()` as a structural primitive.
|
||||
|
||||
Use it when you want reusable appearance controls with custom presentation.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useAppearance](../composables/use-appearance)
|
||||
- [Property Panels guide](../../guides/property-panels)
|
||||
16
packages/docs/programmable/sdk/api/components/canvas-root.md
Normal file
16
packages/docs/programmable/sdk/api/components/canvas-root.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: CanvasRoot
|
||||
description: Headless canvas primitive for OpenPencil rendering surfaces.
|
||||
---
|
||||
|
||||
# CanvasRoot
|
||||
|
||||
`CanvasRoot` is the structural canvas primitive in `@open-pencil/vue`.
|
||||
|
||||
Use it when you want SDK-provided canvas structure and context with app-owned layout and styling.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useCanvas](../composables/use-canvas)
|
||||
- [useCanvasInput](../composables/use-canvas-input)
|
||||
- [useTextEdit](../composables/use-text-edit)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: CanvasSurface
|
||||
description: Canvas element primitive that binds to the nearest CanvasRoot context.
|
||||
---
|
||||
|
||||
# CanvasSurface
|
||||
|
||||
`CanvasSurface` renders the actual `<canvas>` element used by the SDK canvas stack.
|
||||
|
||||
Use it inside `CanvasRoot` when you want SDK-managed canvas refs and rendering integration, but app-owned layout and styling.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [CanvasRoot](./canvas-root)
|
||||
- [useCanvasContext](../advanced/use-canvas-context)
|
||||
- [useCanvas](../composables/use-canvas)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: ColorInputRoot
|
||||
description: Headless color input helper with hex parsing and update helpers.
|
||||
---
|
||||
|
||||
# ColorInputRoot
|
||||
|
||||
`ColorInputRoot` is a headless helper for color input UIs.
|
||||
|
||||
It derives a hex value from a color and exposes update helpers for hex and full-color changes.
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'color', type: 'Color', description: 'Current color value.', required: true },
|
||||
{ name: 'editable', type: 'boolean | undefined', description: 'Whether the consumer should present the value as editable.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'update', payload: 'color: Color', description: 'Emitted when the color changes.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'default', props: '{ color: Color, editable: boolean, hex: string, updateFromHex: (value: string) => void, updateColor: (color: Color) => void }', description: 'Main color input render contract.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<ColorInputRoot :color="color" @update="color = $event" v-slot="{ hex, updateFromHex }">
|
||||
<input :value="hex" @input="updateFromHex(($event.target as HTMLInputElement).value)" />
|
||||
</ColorInputRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ColorPickerRoot](./color-picker-root)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
title: ColorPickerRoot
|
||||
description: Headless popover-based color picker primitive.
|
||||
---
|
||||
|
||||
# ColorPickerRoot
|
||||
|
||||
`ColorPickerRoot` is a headless popover-based color picker primitive.
|
||||
|
||||
It provides:
|
||||
|
||||
- a trigger slot with swatch background styling
|
||||
- a default trigger fallback
|
||||
- a content slot with `color` and `update()`
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'color', type: 'Color', description: 'Current color value.', required: true },
|
||||
{ name: 'contentClass', type: 'string | undefined', description: 'Optional class for the popover content.' },
|
||||
{ name: 'swatchClass', type: 'string | undefined', description: 'Optional class for the default trigger button.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'update', payload: 'color: Color', description: 'Emitted when the color changes.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'trigger', props: '{ style: Record<string, string> }', description: 'Custom trigger with swatch background style.' },
|
||||
{ name: 'default', props: '{ color: Color, update: (color: Color) => void }', description: 'Main color editor content.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<ColorPickerRoot :color="color" @update="color = $event">
|
||||
<template #trigger="{ style }">
|
||||
<button class="size-6 rounded border" :style="style" />
|
||||
</template>
|
||||
|
||||
<template #default="{ color, update }">
|
||||
<MyColorEditor :color="color" @change="update" />
|
||||
</template>
|
||||
</ColorPickerRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ColorInputRoot](./color-input-root)
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
title: FillPickerRoot
|
||||
description: Headless popover-based fill picker primitive.
|
||||
---
|
||||
|
||||
# FillPickerRoot
|
||||
|
||||
`FillPickerRoot` is a headless popover-based fill picker for solid, gradient, and image fills.
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'fill', type: 'Fill', description: 'Current fill value.', required: true },
|
||||
{ name: 'contentClass', type: 'string | undefined', description: 'Optional class for the popover content.' },
|
||||
{ name: 'swatchClass', type: 'string | undefined', description: 'Optional class for the default trigger button.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'update', payload: 'fill: Fill', description: 'Emitted when the fill changes.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'trigger', props: 'swatch style', description: 'Custom trigger with swatch background style.' },
|
||||
{ name: 'default', props: 'fill state + conversion helpers', description: 'Main fill editor content.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
### Trigger slot props
|
||||
|
||||
```ts
|
||||
{
|
||||
style: Record<string, string>
|
||||
}
|
||||
```
|
||||
|
||||
### Default slot props
|
||||
|
||||
```ts
|
||||
{
|
||||
fill: Fill
|
||||
category: 'SOLID' | 'GRADIENT' | 'IMAGE'
|
||||
toSolid: () => void
|
||||
toGradient: () => void
|
||||
toImage: () => void
|
||||
update: (fill: Fill) => void
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<FillPickerRoot :fill="fill" @update="fill = $event">
|
||||
<template #default="{ fill, category, toSolid, toGradient, update }">
|
||||
<div>{{ category }}</div>
|
||||
<button @click="toSolid">Solid</button>
|
||||
<button @click="toGradient">Gradient</button>
|
||||
<MyFillEditor :fill="fill" @change="update" />
|
||||
</template>
|
||||
</FillPickerRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [GradientEditorRoot](./gradient-editor-root)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: FontPickerRoot
|
||||
description: Headless searchable font picker built on Reka Combobox.
|
||||
---
|
||||
|
||||
# FontPickerRoot
|
||||
|
||||
`FontPickerRoot` is a headless searchable font picker built on Reka UI Combobox primitives.
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'listFamilies', type: '() => Promise<string[]>', description: 'Async source for available font families.', required: true },
|
||||
{ name: 'triggerClass', type: 'string | undefined', description: 'Optional class for the default trigger.' },
|
||||
{ name: 'contentClass', type: 'string | undefined', description: 'Optional class for dropdown content.' },
|
||||
{ name: 'itemClass', type: 'string | undefined', description: 'Optional class for default items.' },
|
||||
{ name: 'searchClass', type: 'string | undefined', description: 'Optional class for the search input.' },
|
||||
{ name: 'viewportClass', type: 'string | undefined', description: 'Optional class for the scroll viewport.' },
|
||||
{ name: 'emptyClass', type: 'string | undefined', description: 'Optional class for empty states.' },
|
||||
{ name: 'emptySearchText', type: 'string | undefined', description: 'Text shown when search returns no fonts.' },
|
||||
{ name: 'emptyFontsText', type: 'string | undefined', description: 'Text shown when no fonts are available.' },
|
||||
{ name: 'emptyFontsHint', type: 'string | undefined', description: 'Optional helper text for the empty-fonts state.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Model
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'v-model', type: 'string', description: 'Selected font family.', required: true }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'select', payload: 'family: string', description: 'Emitted after a font family is selected.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'trigger', props: '{ value: string, open: boolean }', description: 'Custom trigger content.' },
|
||||
{ name: 'search', props: '{ searchTerm: string, setInputRef: (el: HTMLInputElement | null) => void }', description: 'Custom search input slot.' },
|
||||
{ name: 'item', props: '{ family: string, selected: boolean }', description: 'Custom item renderer.' },
|
||||
{ name: 'indicator', props: '{ selected: boolean }', description: 'Custom selected indicator.' },
|
||||
{ name: 'empty', description: 'Shown when no fonts are available.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<FontPickerRoot v-model="fontFamily" :list-families="listFamilies">
|
||||
<template #trigger="{ value }">
|
||||
<button class="w-full truncate">{{ value }}</button>
|
||||
</template>
|
||||
</FontPickerRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useTypography](../composables/use-typography)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: GradientEditorBar
|
||||
description: Headless draggable bar primitive for gradient stops.
|
||||
---
|
||||
|
||||
# GradientEditorBar
|
||||
|
||||
`GradientEditorBar` is the draggable bar primitive used inside gradient editors.
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'stops', type: 'GradientStop[]', description: 'Current gradient stops.', required: true },
|
||||
{ name: 'activeStopIndex', type: 'number', description: 'Active stop index.', required: true },
|
||||
{ name: 'barBackground', type: 'string', description: 'CSS background string for the bar.', required: true }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'selectStop', payload: 'index: number', description: 'Emitted when a stop is selected.' },
|
||||
{ name: 'dragStop', payload: 'index: number, position: number', description: 'Emitted while a stop is dragged.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'default', props: 'bar state + drag handlers', description: 'Full gradient bar render contract.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
### Default slot props
|
||||
|
||||
```ts
|
||||
{
|
||||
stops: GradientStop[]
|
||||
activeStopIndex: number
|
||||
barBackground: string
|
||||
barRef: (el: unknown) => void
|
||||
onStopPointerDown: (index: number, event: PointerEvent) => void
|
||||
onPointerMove: (event: PointerEvent) => void
|
||||
onPointerUp: () => void
|
||||
draggingIndex: number | null
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<GradientEditorBar
|
||||
:stops="stops"
|
||||
:active-stop-index="activeStopIndex"
|
||||
:bar-background="barBackground"
|
||||
@select-stop="selectStop"
|
||||
@drag-stop="dragStop"
|
||||
v-slot="ctx"
|
||||
>
|
||||
<MyGradientBar v-bind="ctx" />
|
||||
</GradientEditorBar>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [GradientEditorRoot](./gradient-editor-root)
|
||||
- [GradientEditorStop](./gradient-editor-stop)
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
title: GradientEditorRoot
|
||||
description: Headless root primitive for gradient stop editing.
|
||||
---
|
||||
|
||||
# GradientEditorRoot
|
||||
|
||||
`GradientEditorRoot` is a headless root primitive for gradient editing.
|
||||
|
||||
It owns:
|
||||
|
||||
- active stop state
|
||||
- subtype switching
|
||||
- stop add/remove/update logic
|
||||
- active color editing
|
||||
- derived bar background
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'fill', type: 'Fill', description: 'Current gradient fill value.', required: true }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'update', payload: 'fill: Fill', description: 'Emitted when the gradient fill changes.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'default', props: 'editor state + handlers', description: 'Full gradient editor render contract.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
### Default slot props
|
||||
|
||||
```ts
|
||||
{
|
||||
stops: GradientStop[]
|
||||
subtype: GradientSubtype
|
||||
subtypes: Array<{ value: GradientSubtype; label: string }>
|
||||
activeStopIndex: number
|
||||
activeColor: Color
|
||||
barBackground: string
|
||||
setSubtype: (type: GradientSubtype) => void
|
||||
selectStop: (index: number) => void
|
||||
addStop: () => void
|
||||
removeStop: (index: number) => void
|
||||
updateStopPosition: (index: number, position: number) => void
|
||||
updateStopColor: (index: number, hex: string) => void
|
||||
updateStopOpacity: (index: number, opacity: number) => void
|
||||
updateActiveColor: (color: Color) => void
|
||||
dragStop: (index: number, position: number) => void
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<GradientEditorRoot :fill="fill" @update="fill = $event" v-slot="ctx">
|
||||
<MyGradientUI v-bind="ctx" />
|
||||
</GradientEditorRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [GradientEditorBar](./gradient-editor-bar)
|
||||
- [GradientEditorStop](./gradient-editor-stop)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: GradientEditorStop
|
||||
description: Headless slot primitive for a single gradient stop row.
|
||||
---
|
||||
|
||||
# GradientEditorStop
|
||||
|
||||
`GradientEditorStop` is a headless primitive for rendering and editing a single gradient stop.
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'stop', type: 'GradientStop', description: 'Current stop value.', required: true },
|
||||
{ name: 'index', type: 'number', description: 'Current stop index.', required: true },
|
||||
{ name: 'active', type: 'boolean', description: 'Whether this stop is active.', required: true }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'select', payload: 'index: number', description: 'Emitted when the stop is selected.' },
|
||||
{ name: 'updatePosition', payload: 'index: number, position: number', description: 'Emitted when the stop position changes.' },
|
||||
{ name: 'updateColor', payload: 'index: number, hex: string', description: 'Emitted when the stop color changes.' },
|
||||
{ name: 'updateOpacity', payload: 'index: number, opacity: number', description: 'Emitted when the stop opacity changes.' },
|
||||
{ name: 'remove', payload: 'index: number', description: 'Emitted when the stop is removed.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'default', props: 'stop state + update handlers', description: 'Full gradient stop render contract.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
### Default slot props
|
||||
|
||||
```ts
|
||||
{
|
||||
stop: GradientStop
|
||||
index: number
|
||||
active: boolean
|
||||
positionPercent: number
|
||||
opacityPercent: number
|
||||
hex: string
|
||||
css: string
|
||||
select: () => void
|
||||
updatePosition: (position: number) => void
|
||||
updateColor: (hex: string) => void
|
||||
updateOpacity: (opacity: number) => void
|
||||
remove: () => void
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<GradientEditorStop :stop="stop" :index="index" :active="active" v-slot="ctx">
|
||||
<MyGradientStopRow v-bind="ctx" />
|
||||
</GradientEditorStop>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [GradientEditorRoot](./gradient-editor-root)
|
||||
- [GradientEditorBar](./gradient-editor-bar)
|
||||
46
packages/docs/programmable/sdk/api/components/index.md
Normal file
46
packages/docs/programmable/sdk/api/components/index.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: Components
|
||||
description: Component reference for headless Vue primitives in @open-pencil/vue.
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
`@open-pencil/vue` exposes headless structural primitives for canvas wiring, navigation UI, property panels, and focused input controls.
|
||||
|
||||
## Core editor primitives
|
||||
|
||||
<SdkCardGroup>
|
||||
<SdkCard title="CanvasRoot" to="/programmable/sdk/api/components/canvas-root" description="Headless canvas structure and context." />
|
||||
<SdkCard title="CanvasSurface" to="/programmable/sdk/api/components/canvas-surface" description="Canvas element primitive bound to CanvasRoot context." />
|
||||
<SdkCard title="LayerTreeRoot" to="/programmable/sdk/api/components/layer-tree-root" description="Headless layer tree primitive." />
|
||||
<SdkCard title="LayerTreeItem" to="/programmable/sdk/api/components/layer-tree-item" description="Single layer-tree row primitive." />
|
||||
<SdkCard title="ToolbarRoot" to="/programmable/sdk/api/components/toolbar-root" description="Headless toolbar primitive." />
|
||||
<SdkCard title="ToolbarItem" to="/programmable/sdk/api/components/toolbar-item" description="Single toolbar tool primitive." />
|
||||
<SdkCard title="PageListRoot" to="/programmable/sdk/api/components/page-list-root" description="Headless page list primitive." />
|
||||
</SdkCardGroup>
|
||||
|
||||
## Property panel primitives
|
||||
|
||||
<SdkCardGroup>
|
||||
<SdkCard title="PropertyListRoot" to="/programmable/sdk/api/components/property-list-root" description="Headless property list primitive." />
|
||||
<SdkCard title="PropertyListItem" to="/programmable/sdk/api/components/property-list-item" description="Single fills, strokes, or effects row primitive." />
|
||||
<SdkCard title="PositionControlsRoot" to="/programmable/sdk/api/components/position-controls-root" description="Position, size, and transform controls." />
|
||||
<SdkCard title="LayoutControlsRoot" to="/programmable/sdk/api/components/layout-controls-root" description="Auto-layout and sizing controls." />
|
||||
<SdkCard title="AppearanceControlsRoot" to="/programmable/sdk/api/components/appearance-controls-root" description="Opacity, visibility, and corner-radius controls." />
|
||||
<SdkCard title="TypographyControlsRoot" to="/programmable/sdk/api/components/typography-controls-root" description="Font, alignment, and formatting controls." />
|
||||
</SdkCardGroup>
|
||||
|
||||
## Pickers and inputs
|
||||
|
||||
<SdkCardGroup>
|
||||
<SdkCard title="ColorPickerRoot" to="/programmable/sdk/api/components/color-picker-root" description="Popover-based color picker primitive." />
|
||||
<SdkCard title="ColorInputRoot" to="/programmable/sdk/api/components/color-input-root" description="Headless color input helper." />
|
||||
<SdkCard title="FillPickerRoot" to="/programmable/sdk/api/components/fill-picker-root" description="Popover-based fill picker primitive." />
|
||||
<SdkCard title="FontPickerRoot" to="/programmable/sdk/api/components/font-picker-root" description="Searchable font picker primitive." />
|
||||
<SdkCard title="GradientEditorRoot" to="/programmable/sdk/api/components/gradient-editor-root" description="Root primitive for gradient editing." />
|
||||
<SdkCard title="GradientEditorBar" to="/programmable/sdk/api/components/gradient-editor-bar" description="Draggable gradient bar primitive." />
|
||||
<SdkCard title="GradientEditorStop" to="/programmable/sdk/api/components/gradient-editor-stop" description="Single gradient stop primitive." />
|
||||
<SdkCard title="ScrubInputRoot" to="/programmable/sdk/api/components/scrub-input-root" description="Drag-to-scrub numeric input root." />
|
||||
<SdkCard title="ScrubInputField" to="/programmable/sdk/api/components/scrub-input-field" description="Editing input field primitive." />
|
||||
<SdkCard title="ScrubInputDisplay" to="/programmable/sdk/api/components/scrub-input-display" description="Non-editing display primitive." />
|
||||
</SdkCardGroup>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: LayerTreeItem
|
||||
description: Headless row primitive for a single layer tree node.
|
||||
---
|
||||
|
||||
# LayerTreeItem
|
||||
|
||||
`LayerTreeItem` renders one layer-tree row and exposes selection, expand, visibility, lock, and rename handlers through its default slot.
|
||||
|
||||
Use it when you want app-owned row markup with SDK-provided layer-tree behavior.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [LayerTreeRoot](./layer-tree-root)
|
||||
- [useLayerTree](../advanced/use-layer-tree)
|
||||
- [useLayerDrag](../advanced/use-layer-drag)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: LayerTreeRoot
|
||||
description: Headless structural primitive for layer tree interfaces.
|
||||
---
|
||||
|
||||
# LayerTreeRoot
|
||||
|
||||
`LayerTreeRoot` is the SDK primitive for rendering a layer tree with app-owned markup and styling.
|
||||
|
||||
Use it when you want reusable tree structure and interaction wiring without built-in presentation.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useSelectionState](../composables/use-selection-state)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: LayoutControlsRoot
|
||||
description: Headless root primitive for auto-layout and sizing controls.
|
||||
---
|
||||
|
||||
# LayoutControlsRoot
|
||||
|
||||
`LayoutControlsRoot` exposes the slot contract returned by `useLayout()` as a structural primitive.
|
||||
|
||||
Use it when you want a reusable layout-controls shell with app-owned markup.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useLayout](../composables/use-layout)
|
||||
- [Property Panels guide](../../guides/property-panels)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
title: PageListRoot
|
||||
description: Headless structural primitive for page list UIs.
|
||||
---
|
||||
|
||||
# PageListRoot
|
||||
|
||||
`PageListRoot` is a headless structural primitive for page list interfaces.
|
||||
|
||||
It provides slot props for:
|
||||
|
||||
- pages
|
||||
- current page id
|
||||
- divider detection
|
||||
- page actions like add, switch, rename, and delete
|
||||
|
||||
## Usage
|
||||
|
||||
Use it when you want SDK-provided page-list structure with app-specific rendering and styling.
|
||||
|
||||
## Basic example
|
||||
|
||||
```vue
|
||||
<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>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [usePageList](../composables/use-page-list)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: PositionControlsRoot
|
||||
description: Headless root primitive for position, size, alignment, and transform controls.
|
||||
---
|
||||
|
||||
# PositionControlsRoot
|
||||
|
||||
`PositionControlsRoot` exposes position, size, rotation, align, flip, and rotate handlers for the current selection.
|
||||
|
||||
Use it when you want custom position controls without reimplementing editor wiring.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [usePosition](../composables/use-position)
|
||||
- [Property Panels guide](../../guides/property-panels)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: PropertyListItem
|
||||
description: Headless item primitive for a single fills, strokes, or effects row.
|
||||
---
|
||||
|
||||
# PropertyListItem
|
||||
|
||||
`PropertyListItem` exposes update, patch, remove, and visibility handlers for one array item inside `PropertyListRoot`.
|
||||
|
||||
Use it when building custom list-row UIs for fills, strokes, or effects.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [PropertyListRoot](./property-list-root)
|
||||
- [usePropertyList](../advanced/use-property-list)
|
||||
- [useNodeProps](../advanced/use-node-props)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
title: PropertyListRoot
|
||||
description: Headless structural primitive for fills, strokes, and effects list UIs.
|
||||
---
|
||||
|
||||
# PropertyListRoot
|
||||
|
||||
`PropertyListRoot` is a headless structural primitive for array-based property editors.
|
||||
|
||||
It is intended for property UIs like:
|
||||
|
||||
- fills
|
||||
- strokes
|
||||
- effects
|
||||
|
||||
It provides slot props for:
|
||||
|
||||
- current items
|
||||
- mixed-state detection
|
||||
- add/remove/update/patch operations
|
||||
- visibility toggling per item
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
<PropertyListRoot prop-key="fills" v-slot="{ items, add, remove }">
|
||||
<div v-for="(fill, index) in items" :key="index">
|
||||
<button @click="remove(index)">Remove</button>
|
||||
</div>
|
||||
<button @click="add(defaultFill)">Add fill</button>
|
||||
</PropertyListRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [SDK API Overview](../)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
title: ScrubInputDisplay
|
||||
description: Read-only display primitive for ScrubInputRoot non-editing mode.
|
||||
---
|
||||
|
||||
# ScrubInputDisplay
|
||||
|
||||
`ScrubInputDisplay` renders the non-editing display for `ScrubInputRoot`.
|
||||
|
||||
It only renders while the scrub input is not in editing mode.
|
||||
|
||||
## Usage
|
||||
|
||||
Use it inside a `ScrubInputRoot` subtree.
|
||||
|
||||
## Props and attrs
|
||||
|
||||
<SdkFieldGroup>
|
||||
<SdkField name="$attrs" type="span attributes">Passed through to the rendered span element.</SdkField>
|
||||
</SdkFieldGroup>
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<ScrubInputRoot v-model:model-value="value">
|
||||
<ScrubInputDisplay class="cursor-ew-resize" />
|
||||
</ScrubInputRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ScrubInputRoot](./scrub-input-root)
|
||||
- [ScrubInputField](./scrub-input-field)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
title: ScrubInputField
|
||||
description: Input element primitive for ScrubInputRoot editing mode.
|
||||
---
|
||||
|
||||
# ScrubInputField
|
||||
|
||||
`ScrubInputField` renders the editable input element for `ScrubInputRoot`.
|
||||
|
||||
It only renders while the scrub input is in editing mode.
|
||||
|
||||
## Usage
|
||||
|
||||
Use it inside a `ScrubInputRoot` subtree.
|
||||
|
||||
## Props and attrs
|
||||
|
||||
<SdkFieldGroup>
|
||||
<SdkField name="$attrs" type="input attributes">Passed through to the rendered input element.</SdkField>
|
||||
</SdkFieldGroup>
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<ScrubInputRoot v-model:model-value="value">
|
||||
<ScrubInputField class="w-16" />
|
||||
</ScrubInputRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ScrubInputRoot](./scrub-input-root)
|
||||
- [ScrubInputDisplay](./scrub-input-display)
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: ScrubInputRoot
|
||||
description: Headless root primitive for drag-to-scrub numeric input.
|
||||
---
|
||||
|
||||
# ScrubInputRoot
|
||||
|
||||
`ScrubInputRoot` is the headless root primitive for drag-to-scrub numeric input.
|
||||
|
||||
It manages:
|
||||
|
||||
- mixed-value display
|
||||
- editing vs scrubbing state
|
||||
- pointer-driven numeric scrubbing
|
||||
- commit semantics for finished edits
|
||||
|
||||
## Props
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'modelValue', type: 'number | symbol', description: 'Current numeric value or mixed sentinel.', required: true },
|
||||
{ name: 'min', type: 'number | undefined', description: 'Minimum allowed value.', default: '-Infinity' },
|
||||
{ name: 'max', type: 'number | undefined', description: 'Maximum allowed value.', default: 'Infinity' },
|
||||
{ name: 'step', type: 'number | undefined', description: 'Scrub step multiplier.', default: '1' },
|
||||
{ name: 'sensitivity', type: 'number | undefined', description: 'Pointer sensitivity multiplier.', default: '1' },
|
||||
{ name: 'placeholder', type: 'string | undefined', description: 'Placeholder for mixed values.', default: 'Mixed' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Model
|
||||
|
||||
<SdkPropsTable
|
||||
:rows="[
|
||||
{ name: 'v-model:modelValue', type: 'number', description: 'Writable numeric model.', required: true }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
<SdkEventsTable
|
||||
:rows="[
|
||||
{ name: 'update:modelValue', payload: 'value: number', description: 'Emitted while scrubbing or editing.' },
|
||||
{ name: 'commit', payload: 'value: number, previous: number', description: 'Emitted when an edit interaction is committed.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Slots
|
||||
|
||||
<SdkSlotsTable
|
||||
:rows="[
|
||||
{ name: 'default', props: '{ modelValue: number | symbol, displayValue: string, isMixed: boolean, editing: boolean, scrubbing: boolean, startScrub: (event: PointerEvent) => void, startEdit: () => void, commitEdit: (event: Event) => void, keydown: (event: KeyboardEvent) => void, placeholder: string }', description: 'Full scrub-input render contract.' }
|
||||
]"
|
||||
/>
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<ScrubInputRoot v-model:model-value="value" @commit="commit" v-slot="ctx">
|
||||
<div @pointerdown="ctx.startScrub">
|
||||
<ScrubInputDisplay />
|
||||
<ScrubInputField class="w-16" />
|
||||
</div>
|
||||
</ScrubInputRoot>
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ScrubInputField](./scrub-input-field)
|
||||
- [ScrubInputDisplay](./scrub-input-display)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: ToolbarItem
|
||||
description: Headless toolbar item primitive for a single editor tool.
|
||||
---
|
||||
|
||||
# ToolbarItem
|
||||
|
||||
`ToolbarItem` exposes active-state and selection behavior for one toolbar tool.
|
||||
|
||||
Use it inside `ToolbarRoot` when you want custom button markup but shared tool-selection wiring.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [ToolbarRoot](./toolbar-root)
|
||||
- [useToolbar](../advanced/use-toolbar)
|
||||
- [useToolbarState](../advanced/use-toolbar-state)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: ToolbarRoot
|
||||
description: Headless structural primitive for editor toolbar UIs.
|
||||
---
|
||||
|
||||
# ToolbarRoot
|
||||
|
||||
`ToolbarRoot` is the headless toolbar primitive from `@open-pencil/vue`.
|
||||
|
||||
Use it when you want reusable toolbar structure and context with your own buttons, styling, and layout.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useEditorCommands](../composables/use-editor-commands)
|
||||
- [useSelectionCapabilities](../composables/use-selection-capabilities)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: TypographyControlsRoot
|
||||
description: Headless root primitive for font, alignment, and formatting controls.
|
||||
---
|
||||
|
||||
# TypographyControlsRoot
|
||||
|
||||
`TypographyControlsRoot` exposes typography state and handlers from `useTypography()` as a structural primitive.
|
||||
|
||||
Use it when you want custom typography controls with SDK-managed font and formatting behavior.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useTypography](../composables/use-typography)
|
||||
- [FontPickerRoot](./font-picker-root)
|
||||
- [Property Panels guide](../../guides/property-panels)
|
||||
39
packages/docs/programmable/sdk/api/composables/index.md
Normal file
39
packages/docs/programmable/sdk/api/composables/index.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: Composables
|
||||
description: Core composable APIs in @open-pencil/vue.
|
||||
---
|
||||
|
||||
# Composables
|
||||
|
||||
These are the main composables most `@open-pencil/vue` consumers will use.
|
||||
|
||||
## Context and canvas
|
||||
|
||||
- [provideEditor](./provide-editor)
|
||||
- [useEditor](./use-editor)
|
||||
- [useCanvas](./use-canvas)
|
||||
- [useCanvasInput](./use-canvas-input)
|
||||
- [useTextEdit](./use-text-edit)
|
||||
|
||||
## Selection and commands
|
||||
|
||||
- [useSelectionState](./use-selection-state)
|
||||
- [useSelectionCapabilities](./use-selection-capabilities)
|
||||
- [useEditorCommands](./use-editor-commands)
|
||||
- [useMenuModel](./use-menu-model)
|
||||
|
||||
## Property panels
|
||||
|
||||
- [usePosition](./use-position)
|
||||
- [useLayout](./use-layout)
|
||||
- [useAppearance](./use-appearance)
|
||||
- [useTypography](./use-typography)
|
||||
- [useExport](./use-export)
|
||||
- [useFillControls](./use-fill-controls)
|
||||
- [useStrokeControls](./use-stroke-controls)
|
||||
- [useEffectsControls](./use-effects-controls)
|
||||
|
||||
## Variables and navigation
|
||||
|
||||
- [useVariablesEditor](./use-variables-editor)
|
||||
- [usePageList](./use-page-list)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: provideEditor
|
||||
description: Provide an OpenPencil editor instance to a Vue subtree using injection.
|
||||
---
|
||||
|
||||
# provideEditor
|
||||
|
||||
`provideEditor(editor)` makes an OpenPencil editor available to descendant composables and headless primitives through Vue injection.
|
||||
|
||||
This is the foundation for `useEditor()`.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { provideEditor } from '@open-pencil/vue'
|
||||
|
||||
provideEditor(editor)
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```vue
|
||||
<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>
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
The current SDK uses `provideEditor()` and `useEditor()` directly. Some older examples and error messages still refer to an `OpenPencilProvider` component, but the injection model is the real API surface to prefer in docs and app code.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useEditor](./use-editor)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: useAppearance
|
||||
description: Control visibility, opacity, and corner radius state for the current selection.
|
||||
---
|
||||
|
||||
# useAppearance
|
||||
|
||||
`useAppearance()` is the appearance-focused control composable for property panels.
|
||||
|
||||
It exposes selection-derived UI state for:
|
||||
|
||||
- visibility
|
||||
- opacity
|
||||
- corner radius
|
||||
- independent corner radii
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useAppearance } from '@open-pencil/vue'
|
||||
|
||||
const appearance = useAppearance()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const {
|
||||
visibilityState,
|
||||
opacityPercent,
|
||||
cornerRadiusValue,
|
||||
toggleVisibility,
|
||||
toggleIndependentCorners,
|
||||
} = useAppearance()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Toggle selection visibility
|
||||
|
||||
```ts
|
||||
appearance.toggleVisibility()
|
||||
```
|
||||
|
||||
### Edit per-corner radii
|
||||
|
||||
```ts
|
||||
appearance.updateCornerProp('topLeftRadius', 12)
|
||||
appearance.commitCornerProp('topLeftRadius', 12, 8)
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [SDK API Overview](../)
|
||||
- [useLayout](./use-layout)
|
||||
- [useTypography](./use-typography)
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
title: useCanvasInput
|
||||
description: Wire canvas pointer input, dragging, selection, resize, rotation, and tool behavior.
|
||||
---
|
||||
|
||||
# useCanvasInput
|
||||
|
||||
`useCanvasInput()` connects pointer and mouse interaction to the editor canvas.
|
||||
|
||||
It handles interaction concerns like:
|
||||
|
||||
- selection
|
||||
- dragging
|
||||
- resize
|
||||
- rotation
|
||||
- panning
|
||||
- pen/draw flows
|
||||
- text editing interaction
|
||||
- scope-aware hit testing
|
||||
|
||||
## Usage
|
||||
|
||||
This composable is typically paired with `useCanvas()` and hit-test helpers from the renderer.
|
||||
|
||||
```ts
|
||||
useCanvasInput(
|
||||
canvasRef,
|
||||
editor,
|
||||
hitTestSectionTitle,
|
||||
hitTestComponentLabel,
|
||||
hitTestFrameTitle,
|
||||
)
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const canvas = useCanvas(canvasRef, editor)
|
||||
|
||||
useCanvasInput(
|
||||
canvasRef,
|
||||
editor,
|
||||
canvas.hitTestSectionTitle,
|
||||
canvas.hitTestComponentLabel,
|
||||
canvas.hitTestFrameTitle,
|
||||
)
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Track cursor movement in canvas space
|
||||
|
||||
```ts
|
||||
useCanvasInput(
|
||||
canvasRef,
|
||||
editor,
|
||||
hitTestSectionTitle,
|
||||
hitTestComponentLabel,
|
||||
hitTestFrameTitle,
|
||||
(cx, cy) => {
|
||||
console.log(cx, cy)
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
This composable is lower-level than most panel logic. It is best suited for editor shells and canvas containers.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useCanvas](./use-canvas)
|
||||
- [useEditor](./use-editor)
|
||||
100
packages/docs/programmable/sdk/api/composables/use-canvas.md
Normal file
100
packages/docs/programmable/sdk/api/composables/use-canvas.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
title: useCanvas
|
||||
description: Attach CanvasKit-backed rendering to a canvas element for an OpenPencil editor.
|
||||
---
|
||||
|
||||
# useCanvas
|
||||
|
||||
`useCanvas()` connects an editor to a real `<canvas>` element.
|
||||
|
||||
It handles:
|
||||
|
||||
- CanvasKit initialization
|
||||
- surface creation
|
||||
- render scheduling
|
||||
- resize handling
|
||||
- optional ruler visibility
|
||||
- renderer readiness callback
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useCanvas, useEditor } from '@open-pencil/vue'
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const editor = useEditor()
|
||||
|
||||
useCanvas(canvasRef, editor)
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useCanvas, useEditor } from '@open-pencil/vue'
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const editor = useEditor()
|
||||
|
||||
useCanvas(canvasRef, editor, {
|
||||
showRulers: true,
|
||||
onReady: () => {
|
||||
console.log('Renderer ready')
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="canvasRef" class="size-full" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Disable rulers for an embedded preview
|
||||
|
||||
```ts
|
||||
useCanvas(canvasRef, editor, {
|
||||
showRulers: false,
|
||||
})
|
||||
```
|
||||
|
||||
### Keep drawing buffer for screenshots
|
||||
|
||||
```ts
|
||||
useCanvas(canvasRef, editor, {
|
||||
preserveDrawingBuffer: true,
|
||||
})
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `useCanvas()` is renderer-facing and browser-only in practice
|
||||
- it is responsible for the live canvas pipeline, not app-level file flows
|
||||
- it should usually be paired with `useCanvasInput()` for interaction handling
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useEditor](./use-editor)
|
||||
- [useCanvasInput](./use-canvas-input)
|
||||
- [useTextEdit](./use-text-edit)
|
||||
|
||||
## Type
|
||||
|
||||
```ts
|
||||
interface UseCanvasOptions {
|
||||
showRulers?: boolean
|
||||
preserveDrawingBuffer?: boolean
|
||||
onReady?: () => void
|
||||
}
|
||||
|
||||
function useCanvas(
|
||||
canvasRef: Ref<HTMLCanvasElement | null>,
|
||||
editor: Editor,
|
||||
options?: UseCanvasOptions,
|
||||
): void
|
||||
```
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
title: useEditorCommands
|
||||
description: Build menus, actions, and command-driven UI on top of the editor.
|
||||
---
|
||||
|
||||
# useEditorCommands
|
||||
|
||||
`useEditorCommands()` exposes a command-oriented layer over editor actions.
|
||||
|
||||
It is useful when building:
|
||||
|
||||
- app menus
|
||||
- context menus
|
||||
- toolbars
|
||||
- keyboard-command adapters
|
||||
- page-move submenus
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useEditorCommands } from '@open-pencil/vue'
|
||||
|
||||
const { commands, menuItem, runCommand, moveSelectionToPage, otherPages } = useEditorCommands()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const { menuItem } = useEditorCommands()
|
||||
|
||||
const editMenu = [
|
||||
menuItem('edit.undo', '⌘Z'),
|
||||
menuItem('edit.redo', '⇧⌘Z'),
|
||||
{ separator: true },
|
||||
menuItem('selection.delete'),
|
||||
]
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Run a command directly
|
||||
|
||||
```ts
|
||||
const { runCommand } = useEditorCommands()
|
||||
runCommand('selection.duplicate')
|
||||
```
|
||||
|
||||
### Build a “move to page” submenu
|
||||
|
||||
```ts
|
||||
const { otherPages, moveSelectionToPage } = useEditorCommands()
|
||||
|
||||
const items = otherPages.value.map(page => ({
|
||||
label: page.name,
|
||||
action: () => moveSelectionToPage(page.id),
|
||||
}))
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useMenuModel](./use-menu-model)
|
||||
- [useSelectionState](./use-selection-state)
|
||||
- [useEditor](./use-editor)
|
||||
|
||||
## Main types
|
||||
|
||||
```ts
|
||||
type EditorCommandId =
|
||||
| 'edit.undo'
|
||||
| 'edit.redo'
|
||||
| 'selection.selectAll'
|
||||
| 'selection.duplicate'
|
||||
| 'selection.delete'
|
||||
| 'selection.group'
|
||||
| 'selection.ungroup'
|
||||
| 'selection.createComponent'
|
||||
| 'selection.createComponentSet'
|
||||
| 'selection.createInstance'
|
||||
| 'selection.detachInstance'
|
||||
| 'selection.goToMainComponent'
|
||||
| 'selection.wrapInAutoLayout'
|
||||
| 'selection.bringToFront'
|
||||
| 'selection.sendToBack'
|
||||
| 'selection.toggleVisibility'
|
||||
| 'selection.toggleLock'
|
||||
| 'selection.moveToPage'
|
||||
| 'view.zoom100'
|
||||
| 'view.zoomFit'
|
||||
| 'view.zoomSelection'
|
||||
```
|
||||
73
packages/docs/programmable/sdk/api/composables/use-editor.md
Normal file
73
packages/docs/programmable/sdk/api/composables/use-editor.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
title: useEditor
|
||||
description: Access the current injected OpenPencil editor instance.
|
||||
---
|
||||
|
||||
# useEditor
|
||||
|
||||
`useEditor()` returns the current injected OpenPencil editor.
|
||||
|
||||
It is the main entry point for SDK composables and headless primitives that need editor access.
|
||||
|
||||
## Usage
|
||||
|
||||
`useEditor()` must be called inside a subtree where `provideEditor(editor)` has already been called.
|
||||
|
||||
```ts
|
||||
import { useEditor } from '@open-pencil/vue'
|
||||
|
||||
const editor = useEditor()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useEditor } from '@open-pencil/vue'
|
||||
|
||||
const editor = useEditor()
|
||||
const pageId = computed(() => editor.state.currentPageId)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Current page: {{ pageId }}</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Read selected nodes
|
||||
|
||||
```ts
|
||||
const editor = useEditor()
|
||||
const selected = editor.getSelectedNodes()
|
||||
```
|
||||
|
||||
### Trigger commands
|
||||
|
||||
```ts
|
||||
const editor = useEditor()
|
||||
editor.zoomToFit()
|
||||
editor.undoAction()
|
||||
```
|
||||
|
||||
## Error behavior
|
||||
|
||||
If called outside an editor provider tree, `useEditor()` throws with a helpful message.
|
||||
|
||||
That is intentional — this API should fail loudly when the editor context is missing.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [provideEditor](./provide-editor)
|
||||
- [useCanvas](./use-canvas)
|
||||
- [useSelectionState](./use-selection-state)
|
||||
- [useEditorCommands](./use-editor-commands)
|
||||
|
||||
## Type
|
||||
|
||||
```ts
|
||||
function useEditor(): Editor
|
||||
```
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
title: useEffectsControls
|
||||
description: Effects-panel helpers for shadows, blurs, expansion state, and scrub/commit flows.
|
||||
---
|
||||
|
||||
# useEffectsControls
|
||||
|
||||
`useEffectsControls()` is the effects-property composable used by effects panels.
|
||||
|
||||
It provides helpers for:
|
||||
|
||||
- default effects
|
||||
- shadow vs blur logic
|
||||
- expanded item state
|
||||
- scrub-preview editing
|
||||
- commit-on-finish updates
|
||||
- effect type and color changes
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useEffectsControls } from '@open-pencil/vue'
|
||||
|
||||
const effects = useEffectsControls()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const { effectOptions, createDefaultEffect, toggleExpand, scrubEffect, commitEffect } = useEffectsControls()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Add a default effect
|
||||
|
||||
```ts
|
||||
const effect = effects.createDefaultEffect()
|
||||
```
|
||||
|
||||
### Preview scrub changes, then commit
|
||||
|
||||
```ts
|
||||
effects.scrubEffect(node, index, { radius: 12 })
|
||||
effects.commitEffect(node, index, { radius: 12 })
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [PropertyListRoot](../components/property-list-root)
|
||||
57
packages/docs/programmable/sdk/api/composables/use-export.md
Normal file
57
packages/docs/programmable/sdk/api/composables/use-export.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
title: useExport
|
||||
description: Manage export settings like scale and format for the current selection.
|
||||
---
|
||||
|
||||
# useExport
|
||||
|
||||
`useExport()` is the export-panel composable for selected nodes.
|
||||
|
||||
It manages:
|
||||
|
||||
- export settings rows
|
||||
- selected node ids
|
||||
- export name labeling
|
||||
- supported scales and formats
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useExport } from '@open-pencil/vue'
|
||||
|
||||
const exportState = useExport()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const {
|
||||
settings,
|
||||
nodeName,
|
||||
scales,
|
||||
formats,
|
||||
addSetting,
|
||||
updateScale,
|
||||
updateFormat,
|
||||
} = useExport()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Add another export preset
|
||||
|
||||
```ts
|
||||
exportState.addSetting()
|
||||
```
|
||||
|
||||
### Change the first export to 2x WEBP
|
||||
|
||||
```ts
|
||||
exportState.updateScale(0, 2)
|
||||
exportState.updateFormat(0, 'WEBP')
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useSelectionState](./use-selection-state)
|
||||
- [useEditor](./use-editor)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
title: useFillControls
|
||||
description: Fill-panel composable with default fill behavior and variable binding support.
|
||||
---
|
||||
|
||||
# useFillControls
|
||||
|
||||
`useFillControls()` is the fill-property composable used by fill editing UIs.
|
||||
|
||||
It builds on variable-binding support and adds a reusable default fill value.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useFillControls } from '@open-pencil/vue'
|
||||
|
||||
const fills = useFillControls()
|
||||
```
|
||||
|
||||
## What it gives you
|
||||
|
||||
Along with fill binding behavior, it exposes:
|
||||
|
||||
- `defaultFill`
|
||||
- fill variable-binding helpers
|
||||
- selection-aware fill editing state
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Add a new fill row
|
||||
|
||||
```ts
|
||||
propertyList.add(fills.defaultFill)
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [PropertyListRoot](../components/property-list-root)
|
||||
63
packages/docs/programmable/sdk/api/composables/use-layout.md
Normal file
63
packages/docs/programmable/sdk/api/composables/use-layout.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: useLayout
|
||||
description: Work with auto-layout, sizing, padding, alignment, and grid tracks.
|
||||
---
|
||||
|
||||
# useLayout
|
||||
|
||||
`useLayout()` is the main control composable for layout-related panels.
|
||||
|
||||
It exposes state and actions for:
|
||||
|
||||
- flex vs grid mode
|
||||
- width/height sizing
|
||||
- padding
|
||||
- alignment
|
||||
- grid template track editing
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useLayout } from '@open-pencil/vue'
|
||||
|
||||
const layout = useLayout()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const {
|
||||
isGrid,
|
||||
isFlex,
|
||||
widthSizing,
|
||||
heightSizing,
|
||||
setWidthSizing,
|
||||
setHeightSizing,
|
||||
} = useLayout()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Toggle between uniform and individual padding UI
|
||||
|
||||
```ts
|
||||
layout.toggleIndividualPadding()
|
||||
```
|
||||
|
||||
### Update grid tracks
|
||||
|
||||
```ts
|
||||
layout.updateGridTrack('gridTemplateColumns', 0, { sizing: 'FIXED', value: 240 })
|
||||
layout.addTrack('gridTemplateRows')
|
||||
```
|
||||
|
||||
### Change alignment
|
||||
|
||||
```ts
|
||||
layout.setAlignment('CENTER', 'MAX')
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [usePosition](./use-position)
|
||||
- [useEditor](./use-editor)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
title: useMenuModel
|
||||
description: Build app and canvas menu models from the current editor state.
|
||||
---
|
||||
|
||||
# useMenuModel
|
||||
|
||||
`useMenuModel()` builds higher-level menu structures on top of editor commands and selection state.
|
||||
|
||||
It is useful when you want ready-to-render menu groups instead of composing commands manually.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useMenuModel } from '@open-pencil/vue'
|
||||
|
||||
const { appMenu, canvasMenu, selectionLabelMenu } = useMenuModel()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const { canvasMenu } = useMenuModel()
|
||||
```
|
||||
|
||||
Render `canvasMenu.value` into your context menu component.
|
||||
|
||||
## Practical examples
|
||||
|
||||
### App-style top menu
|
||||
|
||||
`appMenu` groups entries into:
|
||||
|
||||
- Edit
|
||||
- View
|
||||
- Object
|
||||
- Arrange
|
||||
|
||||
### Context menu with page moves
|
||||
|
||||
`canvasMenu` includes dynamic items like “Move to page” based on current selection and available pages.
|
||||
|
||||
### Selection labels
|
||||
|
||||
`selectionLabelMenu` exposes context-sensitive labels like:
|
||||
|
||||
- `Hide` / `Show`
|
||||
- `Lock` / `Unlock`
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useEditorCommands](./use-editor-commands)
|
||||
- [useSelectionState](./use-selection-state)
|
||||
- [useSelectionCapabilities](./use-selection-capabilities)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
title: usePageList
|
||||
description: Read pages and drive page switching, creation, deletion, and renaming.
|
||||
---
|
||||
|
||||
# usePageList
|
||||
|
||||
`usePageList()` is the page-management composable behind page list UIs.
|
||||
|
||||
It exposes:
|
||||
|
||||
- `pages`
|
||||
- `currentPageId`
|
||||
- `switchPage`
|
||||
- `addPage`
|
||||
- `deletePage`
|
||||
- `renamePage`
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { usePageList } from '@open-pencil/vue'
|
||||
|
||||
const pageList = usePageList()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const { pages, currentPageId, switchPage, addPage } = usePageList()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Switch pages
|
||||
|
||||
```ts
|
||||
switchPage(pageId)
|
||||
```
|
||||
|
||||
### Create a new page
|
||||
|
||||
```ts
|
||||
addPage()
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [PageListRoot](../components/page-list-root)
|
||||
- [useMenuModel](./use-menu-model)
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: usePosition
|
||||
description: Read and update selected node position, size, rotation, alignment, and flipping.
|
||||
---
|
||||
|
||||
# usePosition
|
||||
|
||||
`usePosition()` is a control composable for position-related UI.
|
||||
|
||||
It exposes selected-node values like:
|
||||
|
||||
- `x`
|
||||
- `y`
|
||||
- `width`
|
||||
- `height`
|
||||
- `rotation`
|
||||
|
||||
and actions like:
|
||||
|
||||
- align
|
||||
- flip
|
||||
- rotate
|
||||
- scrub/update numeric properties
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { usePosition } from '@open-pencil/vue'
|
||||
|
||||
const position = usePosition()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const { x, y, width, height, rotation, updateProp, commitProp } = usePosition()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Align selected nodes
|
||||
|
||||
```ts
|
||||
position.align('horizontal', 'center')
|
||||
position.align('vertical', 'min')
|
||||
```
|
||||
|
||||
### Flip selection
|
||||
|
||||
```ts
|
||||
position.flip('horizontal')
|
||||
position.flip('vertical')
|
||||
```
|
||||
|
||||
### Rotate selection
|
||||
|
||||
```ts
|
||||
position.rotate(90)
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useLayout](./use-layout)
|
||||
- [useAppearance](./use-appearance)
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
title: useSelectionCapabilities
|
||||
description: Derive command-friendly booleans for selection-driven UI and actions.
|
||||
---
|
||||
|
||||
# useSelectionCapabilities
|
||||
|
||||
`useSelectionCapabilities()` exposes reactive booleans for whether common editor actions are currently allowed.
|
||||
|
||||
Use it when building:
|
||||
|
||||
- menus
|
||||
- toolbars
|
||||
- keyboard shortcuts
|
||||
- action buttons
|
||||
- contextual panels
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useSelectionCapabilities } from '@open-pencil/vue'
|
||||
|
||||
const caps = useSelectionCapabilities()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useSelectionCapabilities } from '@open-pencil/vue'
|
||||
|
||||
const { canDelete, canDuplicate, canCreateComponent } = useSelectionCapabilities()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<button :disabled="!canDuplicate">Duplicate</button>
|
||||
<button :disabled="!canDelete">Delete</button>
|
||||
<button :disabled="!canCreateComponent">Make component</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Gate menu entries
|
||||
|
||||
```ts
|
||||
const { canMoveToPage, canGoToMainComponent } = useSelectionCapabilities()
|
||||
```
|
||||
|
||||
### Enable zoom commands only when useful
|
||||
|
||||
```ts
|
||||
const { canZoomToSelection } = useSelectionCapabilities()
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useSelectionState](./use-selection-state)
|
||||
- [useEditorCommands](./use-editor-commands)
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
title: useSelectionState
|
||||
description: Reactive selection-derived editor state for current node, count, and selection type.
|
||||
---
|
||||
|
||||
# useSelectionState
|
||||
|
||||
`useSelectionState()` exposes reactive selection-derived state from the current editor.
|
||||
|
||||
Use it when you need to render UI based on:
|
||||
|
||||
- whether anything is selected
|
||||
- how many nodes are selected
|
||||
- the primary selected node
|
||||
- whether the current selection is an instance, component, or group
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useSelectionState } from '@open-pencil/vue'
|
||||
|
||||
const selection = useSelectionState()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useSelectionState } from '@open-pencil/vue'
|
||||
|
||||
const { hasSelection, selectedCount, isInstance } = useSelectionState()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-xs text-muted">
|
||||
<span v-if="!hasSelection">No selection</span>
|
||||
<span v-else>
|
||||
{{ selectedCount }} selected
|
||||
<span v-if="isInstance">· instance</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## What it returns
|
||||
|
||||
Useful values include:
|
||||
|
||||
- `selectedIds`
|
||||
- `hasSelection`
|
||||
- `selectedNode`
|
||||
- `selectedCount`
|
||||
- `selectedNodeType`
|
||||
- `isInstance`
|
||||
- `isComponent`
|
||||
- `isGroup`
|
||||
- `canCreateComponentSet`
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Show instance-only actions
|
||||
|
||||
```ts
|
||||
const { isInstance } = useSelectionState()
|
||||
```
|
||||
|
||||
### Enable component-set creation UI
|
||||
|
||||
```ts
|
||||
const { canCreateComponentSet } = useSelectionState()
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useSelectionCapabilities](./use-selection-capabilities)
|
||||
- [useEditorCommands](./use-editor-commands)
|
||||
- [useEditor](./use-editor)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: useStrokeControls
|
||||
description: Stroke-panel helpers for alignment, side selection, and per-side stroke weights.
|
||||
---
|
||||
|
||||
# useStrokeControls
|
||||
|
||||
`useStrokeControls()` is the stroke-property composable used by stroke editing panels.
|
||||
|
||||
It provides:
|
||||
|
||||
- stroke align options
|
||||
- side presets like all, top, bottom, left, right, custom
|
||||
- default stroke data
|
||||
- helpers for per-side border weights
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useStrokeControls } from '@open-pencil/vue'
|
||||
|
||||
const strokes = useStrokeControls()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const { alignOptions, sideOptions, currentAlign, currentSides, selectSide } = useStrokeControls()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Set stroke alignment
|
||||
|
||||
```ts
|
||||
strokes.updateAlign('INSIDE', activeNode)
|
||||
```
|
||||
|
||||
### Limit a stroke to one side
|
||||
|
||||
```ts
|
||||
strokes.selectSide('TOP', activeNode)
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [PropertyListRoot](../components/property-list-root)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: useTextEdit
|
||||
description: Manage DOM text editing, composition, formatting, and syncing for canvas text nodes.
|
||||
---
|
||||
|
||||
# useTextEdit
|
||||
|
||||
`useTextEdit()` bridges DOM text input and the editor’s canvas text editing model.
|
||||
|
||||
It coordinates:
|
||||
|
||||
- textarea-backed text input
|
||||
- IME composition
|
||||
- caret blinking
|
||||
- delete/backspace behavior
|
||||
- formatting commands like bold/italic/underline
|
||||
- syncing text changes back into the graph
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
useTextEdit(canvasRef, editor)
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
Use this in the canvas owner component together with `useCanvas()` and `useCanvasInput()`.
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Support formatting shortcuts
|
||||
|
||||
`useTextEdit()` already handles keyboard formatting actions like bold, italic, and underline while text editing is active.
|
||||
|
||||
### Keep canvas and text editor in sync
|
||||
|
||||
It updates graph text and style runs as the user types or edits formatted ranges.
|
||||
|
||||
## Notes
|
||||
|
||||
This is a canvas/editor integration composable, not a generic text-field composable.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useCanvas](./use-canvas)
|
||||
- [useCanvasInput](./use-canvas-input)
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: useTypography
|
||||
description: Read and update font family, weight, size, alignment, and formatting for text nodes.
|
||||
---
|
||||
|
||||
# useTypography
|
||||
|
||||
`useTypography()` is the text-property control composable for text editing panels.
|
||||
|
||||
It exposes:
|
||||
|
||||
- font family
|
||||
- font weight
|
||||
- font size
|
||||
- formatting state
|
||||
- missing-font status
|
||||
- helpers for changing family, weight, alignment, and decorations
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { useTypography } from '@open-pencil/vue'
|
||||
|
||||
const typography = useTypography()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```ts
|
||||
const {
|
||||
fontFamily,
|
||||
fontWeight,
|
||||
fontSize,
|
||||
activeFormatting,
|
||||
setFamily,
|
||||
setWeight,
|
||||
setAlign,
|
||||
} = useTypography()
|
||||
```
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Load and switch a font family
|
||||
|
||||
```ts
|
||||
const typography = useTypography({
|
||||
loadFont: async (family, style) => {
|
||||
await myFontLoader(family, style)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Toggle formatting
|
||||
|
||||
```ts
|
||||
typography.toggleBold()
|
||||
typography.toggleItalic()
|
||||
typography.toggleDecoration('UNDERLINE')
|
||||
```
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [useTextEdit](./use-text-edit)
|
||||
- [useSelectionState](./use-selection-state)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
title: useVariablesEditor
|
||||
description: Compose the variables dialog state, table columns, and TanStack table wiring.
|
||||
---
|
||||
|
||||
# useVariablesEditor
|
||||
|
||||
`useVariablesEditor()` is a higher-level variables-domain composable for building a variables dialog or editor screen.
|
||||
|
||||
It combines:
|
||||
|
||||
- variables dialog state
|
||||
- variables table columns
|
||||
- TanStack Vue Table wiring
|
||||
- collection/mode helpers
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
const variables = useVariablesEditor({
|
||||
colorInput: ColorInput,
|
||||
icons,
|
||||
fallbackIcon,
|
||||
deleteIcon,
|
||||
})
|
||||
```
|
||||
|
||||
## What it returns
|
||||
|
||||
It includes the lower-level dialog/table state plus:
|
||||
|
||||
- `columns`
|
||||
- `table`
|
||||
- `hasCollections`
|
||||
|
||||
## Practical examples
|
||||
|
||||
### Build a variables dialog
|
||||
|
||||
Use `useVariablesEditor()` when you want one composable that already wires the table and action handlers together.
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [SDK API Overview](../)
|
||||
20
packages/docs/programmable/sdk/api/index.md
Normal file
20
packages/docs/programmable/sdk/api/index.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: API Reference
|
||||
description: Reference documentation for @open-pencil/vue components, composables, and advanced APIs.
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
||||
The Vue SDK reference is organized into three sections.
|
||||
|
||||
<SdkCardGroup>
|
||||
<SdkCard title="Components" to="/programmable/sdk/api/components/" description="Headless structural primitives like CanvasRoot, ToolbarRoot, FontPickerRoot, and ScrubInputRoot." />
|
||||
<SdkCard title="Composables" to="/programmable/sdk/api/composables/" description="Editor-facing composables for context, canvas wiring, selection, commands, and property panels." />
|
||||
<SdkCard title="Advanced" to="/programmable/sdk/api/advanced/" description="Lower-level helpers, primitive contexts, and specialized APIs for advanced integrations." />
|
||||
</SdkCardGroup>
|
||||
|
||||
## Suggested path
|
||||
|
||||
- Start with **Components** if you are building reusable editor UI primitives.
|
||||
- Start with **Composables** if you are wiring editor state and actions.
|
||||
- Use **Advanced** only when you need lower-level helpers or primitive contexts.
|
||||
115
packages/docs/programmable/sdk/architecture.md
Normal file
115
packages/docs/programmable/sdk/architecture.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: SDK Architecture
|
||||
description: Folder structure, public API boundaries, and composition patterns in @open-pencil/vue.
|
||||
---
|
||||
|
||||
# SDK Architecture
|
||||
|
||||
`@open-pencil/vue` is the Vue-facing layer over `@open-pencil/core`.
|
||||
|
||||
It does not own the editor model itself. It adapts the core editor into:
|
||||
|
||||
- Vue injection
|
||||
- reactive composables
|
||||
- headless structural primitives
|
||||
- canvas and input wiring
|
||||
|
||||
## Folder structure
|
||||
|
||||
This package is organized by domain.
|
||||
|
||||
### Component families
|
||||
|
||||
- `Canvas/`
|
||||
- `ColorPicker/`
|
||||
- `FillPicker/`
|
||||
- `FontPicker/`
|
||||
- `GradientEditor/`
|
||||
- `LayerTree/`
|
||||
- `PageList/`
|
||||
- `PropertyList/`
|
||||
- `ScrubInput/`
|
||||
- `Toolbar/`
|
||||
|
||||
These contain structural/headless primitives and local helpers.
|
||||
|
||||
### Controls
|
||||
|
||||
`controls/` contains property-panel and editor control composables:
|
||||
|
||||
- `usePosition`
|
||||
- `useLayout`
|
||||
- `useAppearance`
|
||||
- `useTypography`
|
||||
- `useExport`
|
||||
- `useFillControls`
|
||||
- `useStrokeControls`
|
||||
- `useEffectsControls`
|
||||
- `useNodeProps`
|
||||
- `usePropScrub`
|
||||
|
||||
### Variables
|
||||
|
||||
`VariablesEditor/` contains variables-domain composables and state wiring.
|
||||
|
||||
### Selection
|
||||
|
||||
`selection/` contains selection-derived editor state and capabilities.
|
||||
|
||||
### Context
|
||||
|
||||
`context/` contains editor injection helpers:
|
||||
|
||||
- `EDITOR_KEY`
|
||||
- `provideEditor`
|
||||
- `useEditor`
|
||||
|
||||
### Internal
|
||||
|
||||
`internal/` contains cross-cutting utilities not intended as primary headless primitives.
|
||||
|
||||
## Public API philosophy
|
||||
|
||||
### Prefer composables
|
||||
|
||||
If the problem is mostly control logic, state derivation, or editor actions, expose a composable.
|
||||
|
||||
### Keep headless primitives for meaningful structure
|
||||
|
||||
Use component roots when they coordinate structure, children, slots, or context.
|
||||
|
||||
Examples:
|
||||
|
||||
- `PageListRoot`
|
||||
- `PropertyListRoot`
|
||||
- `ToolbarRoot`
|
||||
|
||||
### Avoid broad context-dump slots
|
||||
|
||||
Prefer focused slot props or direct composable usage over giant `v-slot="ctx"` payloads.
|
||||
|
||||
## App vs SDK responsibility
|
||||
|
||||
### SDK owns
|
||||
|
||||
- editor integration
|
||||
- reusable headless logic
|
||||
- reusable UI structure without styling assumptions
|
||||
- canvas rendering integration
|
||||
|
||||
### App owns
|
||||
|
||||
- styling
|
||||
- layout shells
|
||||
- routing
|
||||
- product file flows
|
||||
- toasts, menus, and app-specific UX
|
||||
|
||||
## Practical rule of thumb
|
||||
|
||||
If a piece of logic could be reused in a different OpenPencil-based app without bringing app styling with it, it probably belongs in `@open-pencil/vue`.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [SDK Getting Started](./getting-started)
|
||||
- [API Reference](./api/)
|
||||
124
packages/docs/programmable/sdk/getting-started.md
Normal file
124
packages/docs/programmable/sdk/getting-started.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
---
|
||||
title: SDK Getting Started
|
||||
description: Set up @open-pencil/vue with createEditor, provideEditor, and a canvas.
|
||||
---
|
||||
|
||||
# SDK Getting Started
|
||||
|
||||
## Installation
|
||||
|
||||
The SDK lives in the monorepo today and is consumed by the app itself.
|
||||
|
||||
```ts
|
||||
import { createEditor } from '@open-pencil/core/editor'
|
||||
import { provideEditor, useCanvas } from '@open-pencil/vue'
|
||||
```
|
||||
|
||||
## Mental model
|
||||
|
||||
There are three layers:
|
||||
|
||||
1. `@open-pencil/core` — framework-agnostic editor engine
|
||||
2. `@open-pencil/vue` — Vue composables and headless primitives
|
||||
3. your app — styling, routing, file flows, product-specific UI
|
||||
|
||||
## Minimal setup
|
||||
|
||||
### 1. Create an editor
|
||||
|
||||
```ts
|
||||
import { createEditor } from '@open-pencil/core/editor'
|
||||
|
||||
const editor = createEditor({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
})
|
||||
```
|
||||
|
||||
### 2. Provide it to Vue
|
||||
|
||||
```vue
|
||||
<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>
|
||||
```
|
||||
|
||||
You can think of this as the provider layer for the editor tree. The docs prefer `provideEditor()` directly because that is the current real API surface.
|
||||
|
||||
### 3. Attach a canvas
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useCanvas, useEditor } from '@open-pencil/vue'
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const editor = useEditor()
|
||||
|
||||
useCanvas(canvasRef, editor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="canvasRef" class="size-full" />
|
||||
</template>
|
||||
```
|
||||
|
||||
## Using composables
|
||||
|
||||
Once the editor is provided, child components can read selection and issue commands:
|
||||
|
||||
```ts
|
||||
import { useEditorCommands, useSelectionState } from '@open-pencil/vue'
|
||||
|
||||
const selection = useSelectionState()
|
||||
const commands = useEditorCommands()
|
||||
```
|
||||
|
||||
## Basic example
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useCanvas, useEditor, useSelectionState } from '@open-pencil/vue'
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const editor = useEditor()
|
||||
const { selectedCount } = useSelectionState()
|
||||
|
||||
useCanvas(canvasRef, editor, {
|
||||
onReady: () => {
|
||||
console.log('Canvas ready')
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid h-full grid-rows-[1fr_auto]">
|
||||
<canvas ref="canvasRef" class="size-full" />
|
||||
<div class="border-t px-3 py-2 text-xs text-muted">
|
||||
Selected: {{ selectedCount }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Architecture](./architecture)
|
||||
- [API Reference](./api/)
|
||||
- [useEditor](./api/composables/use-editor)
|
||||
- [useCanvas](./api/composables/use-canvas)
|
||||
105
packages/docs/programmable/sdk/guides/custom-editor-shell.md
Normal file
105
packages/docs/programmable/sdk/guides/custom-editor-shell.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
title: Custom Editor Shell
|
||||
description: Build your own editor shell with provideEditor, CanvasRoot, menus, panels, and toolbars.
|
||||
---
|
||||
|
||||
# Custom Editor Shell
|
||||
|
||||
A typical OpenPencil Vue app has three layers:
|
||||
|
||||
1. `@open-pencil/core` creates the editor
|
||||
2. `@open-pencil/vue` adapts it into Vue composables and headless primitives
|
||||
3. your app renders the shell, styling, and product UX
|
||||
|
||||
## Why this matters
|
||||
|
||||
The built-in OpenPencil app is only one possible shell.
|
||||
|
||||
You can build a very different one for a focused workflow: an embedded editor inside another product, an internal asset tool, a template editor, an annotation UI, or an AI-assisted editing surface with custom controls.
|
||||
|
||||
That is the main reason the SDK exists.
|
||||
|
||||
## Recommended composition
|
||||
|
||||
A practical shell often looks like this:
|
||||
|
||||
- provider at the top with `provideEditor()`
|
||||
- canvas in the center
|
||||
- page/layer navigation on one side
|
||||
- properties on the other side
|
||||
- menus and toolbars driven by composables
|
||||
|
||||
## Example
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { createEditor } from '@open-pencil/core/editor'
|
||||
import {
|
||||
provideEditor,
|
||||
CanvasRoot,
|
||||
CanvasSurface,
|
||||
ToolbarRoot,
|
||||
PageListRoot,
|
||||
LayerTreeRoot,
|
||||
} from '@open-pencil/vue'
|
||||
|
||||
const editor = createEditor({ width: 1440, height: 900 })
|
||||
provideEditor(editor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid h-screen grid-cols-[240px_1fr_320px] grid-rows-[48px_1fr]">
|
||||
<ToolbarRoot v-slot="{ tools, activeTool, setTool }">
|
||||
<header class="col-span-3 flex items-center gap-2 border-b px-3">
|
||||
<button
|
||||
v-for="tool in tools"
|
||||
:key="tool.id"
|
||||
:data-active="activeTool === tool.id"
|
||||
@click="setTool(tool.id)"
|
||||
>
|
||||
{{ tool.label }}
|
||||
</button>
|
||||
</header>
|
||||
</ToolbarRoot>
|
||||
|
||||
<aside class="border-r">
|
||||
<PageListRoot v-slot="{ pages, currentPageId, switchPage }">
|
||||
<nav>
|
||||
<button
|
||||
v-for="page in pages"
|
||||
:key="page.id"
|
||||
:data-active="page.id === currentPageId"
|
||||
@click="switchPage(page.id)"
|
||||
>
|
||||
{{ page.name }}
|
||||
</button>
|
||||
</nav>
|
||||
</PageListRoot>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<CanvasRoot>
|
||||
<CanvasSurface class="size-full" />
|
||||
</CanvasRoot>
|
||||
</main>
|
||||
|
||||
<aside class="border-l">
|
||||
Properties panel here
|
||||
</aside>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Why this split works
|
||||
|
||||
- the SDK owns editor integration and reusable headless logic
|
||||
- your app owns layout, styling, and product-specific actions
|
||||
- composables can power menus and panels without extra wrapper components
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [provideEditor](../api/composables/provide-editor)
|
||||
- [useCanvas](../api/composables/use-canvas)
|
||||
- [ToolbarRoot](../api/components/toolbar-root)
|
||||
- [PageListRoot](../api/components/page-list-root)
|
||||
- [LayerTreeRoot](../api/components/layer-tree-root)
|
||||
60
packages/docs/programmable/sdk/guides/navigation-panels.md
Normal file
60
packages/docs/programmable/sdk/guides/navigation-panels.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: Navigation Panels
|
||||
description: Build page and layer sidebars with PageListRoot, LayerTreeRoot, and selection state.
|
||||
---
|
||||
|
||||
# Navigation Panels
|
||||
|
||||
OpenPencil sidebars usually combine two concerns:
|
||||
|
||||
- page navigation
|
||||
- layer navigation
|
||||
|
||||
The Vue SDK provides headless primitives for both.
|
||||
|
||||
## Page navigation
|
||||
|
||||
Use `PageListRoot` or `usePageList()`.
|
||||
|
||||
```vue
|
||||
<PageListRoot v-slot="{ pages, currentPageId, switchPage, addPage }">
|
||||
<div>
|
||||
<button v-for="page in pages" :key="page.id" @click="switchPage(page.id)">
|
||||
{{ page.name }}
|
||||
</button>
|
||||
<button @click="addPage()">New page</button>
|
||||
</div>
|
||||
</PageListRoot>
|
||||
```
|
||||
|
||||
## Layer navigation
|
||||
|
||||
Use `LayerTreeRoot` when you want SDK-managed tree structure but app-owned presentation.
|
||||
|
||||
```vue
|
||||
<LayerTreeRoot v-slot="{ items, selectedIds, select, toggleExpand, getKey, getChildren }">
|
||||
<TreeView
|
||||
:items="items"
|
||||
:selected-ids="selectedIds"
|
||||
:get-key="getKey"
|
||||
:get-children="getChildren"
|
||||
@select="select"
|
||||
@toggle-expand="toggleExpand"
|
||||
/>
|
||||
</LayerTreeRoot>
|
||||
```
|
||||
|
||||
## Practical pattern
|
||||
|
||||
A common layout is:
|
||||
|
||||
- pages at the top of the sidebar
|
||||
- layers below
|
||||
- details or inline rename controls embedded in your row components
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [usePageList](../api/composables/use-page-list)
|
||||
- [PageListRoot](../api/components/page-list-root)
|
||||
- [LayerTreeRoot](../api/components/layer-tree-root)
|
||||
- [useSelectionState](../api/composables/use-selection-state)
|
||||
83
packages/docs/programmable/sdk/guides/property-panels.md
Normal file
83
packages/docs/programmable/sdk/guides/property-panels.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
title: Property Panels
|
||||
description: Build property panels with control composables and headless list primitives.
|
||||
---
|
||||
|
||||
# Property Panels
|
||||
|
||||
Property panels in `@open-pencil/vue` are intentionally composable-first.
|
||||
|
||||
If a panel mostly needs selection-derived values and update actions, prefer composables.
|
||||
If a panel needs reusable array/list structure, use a headless primitive like `PropertyListRoot`.
|
||||
|
||||
## Common control composables
|
||||
|
||||
For standard property sections, start with:
|
||||
|
||||
- `usePosition()`
|
||||
- `useLayout()`
|
||||
- `useAppearance()`
|
||||
- `useTypography()`
|
||||
- `useExport()`
|
||||
|
||||
For list-style panels, use:
|
||||
|
||||
- `useFillControls()`
|
||||
- `useStrokeControls()`
|
||||
- `useEffectsControls()`
|
||||
|
||||
## Example: position panel
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { usePosition } from '@open-pencil/vue'
|
||||
|
||||
const { x, y, width, height, updateProp, commitProp } = usePosition()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<input :value="x" @input="updateProp('x', Number(($event.target as HTMLInputElement).value))" />
|
||||
<input :value="y" @input="updateProp('y', Number(($event.target as HTMLInputElement).value))" />
|
||||
<input :value="width" @input="updateProp('width', Number(($event.target as HTMLInputElement).value))" />
|
||||
<input :value="height" @input="updateProp('height', Number(($event.target as HTMLInputElement).value))" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Example: fills panel
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { PropertyListRoot, useFillControls } from '@open-pencil/vue'
|
||||
|
||||
const fillControls = useFillControls()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PropertyListRoot prop-key="fills" v-slot="{ items, add, remove }">
|
||||
<div v-for="(fill, index) in items" :key="index">
|
||||
{{ fill.type }}
|
||||
<button @click="remove(index)">Remove</button>
|
||||
</div>
|
||||
|
||||
<button @click="add(fillControls.defaultFill)">Add fill</button>
|
||||
</PropertyListRoot>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Rule of thumb
|
||||
|
||||
- use composables for direct control logic
|
||||
- use structural primitives when repeated list/tree/slot coordination is the hard part
|
||||
|
||||
## Related APIs
|
||||
|
||||
- [usePosition](../api/composables/use-position)
|
||||
- [useLayout](../api/composables/use-layout)
|
||||
- [useAppearance](../api/composables/use-appearance)
|
||||
- [useTypography](../api/composables/use-typography)
|
||||
- [useFillControls](../api/composables/use-fill-controls)
|
||||
- [useStrokeControls](../api/composables/use-stroke-controls)
|
||||
- [useEffectsControls](../api/composables/use-effects-controls)
|
||||
- [PropertyListRoot](../api/components/property-list-root)
|
||||
59
packages/docs/programmable/sdk/index.md
Normal file
59
packages/docs/programmable/sdk/index.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
title: Vue SDK
|
||||
description: Build OpenPencil-powered editors with headless Vue composables and primitives.
|
||||
---
|
||||
|
||||
# Vue SDK
|
||||
|
||||
`@open-pencil/vue` exists so OpenPencil can be more than a standalone design app.
|
||||
|
||||
The goal is to make OpenPencil a toolkit you can embed into other products, internal tools, and workflow-specific editors — not just a single default UI.
|
||||
|
||||
The OpenPencil app is one composition of that toolkit. The SDK is how you build a different one.
|
||||
|
||||
It gives you:
|
||||
|
||||
- injected editor context
|
||||
- CanvasKit-backed canvas rendering
|
||||
- selection, commands, menu, and property-panel composables
|
||||
- headless structural primitives like `PageListRoot`, `PropertyListRoot`, and `ToolbarRoot`
|
||||
|
||||
## Start here
|
||||
|
||||
<SdkCardGroup>
|
||||
<SdkCard title="Getting Started" to="/programmable/sdk/getting-started" description="Install the package, create an editor instance, and mount the core primitives." />
|
||||
<SdkCard title="Architecture" to="/programmable/sdk/architecture" description="See how composables, primitives, and editor context fit together." />
|
||||
<SdkCard title="Guides" to="/programmable/sdk/guides/custom-editor-shell" description="Build custom shells, property panels, and navigation panels." />
|
||||
<SdkCard title="API Reference" to="/programmable/sdk/api/" description="Browse components, composables, and advanced public APIs." />
|
||||
</SdkCardGroup>
|
||||
|
||||
## Why the SDK exists
|
||||
|
||||
Different products and teams need different editing surfaces.
|
||||
|
||||
Sometimes you want a full design editor. Sometimes you want a focused canvas inside another app. Sometimes you want an internal workflow tool, a template editor, or an AI-assisted editing surface built around a narrow use case.
|
||||
|
||||
The SDK is the layer that makes those possible.
|
||||
|
||||
## Design principles
|
||||
|
||||
- **Headless first**: logic and structure, not app styling
|
||||
- **Composable over wrapper**: use composables when there is no meaningful structural coordination
|
||||
- **Intentional public API**: stable exports from `packages/vue/src/index.ts`
|
||||
- **Framework-aware**: Vue integration over `@open-pencil/core`
|
||||
|
||||
## How to think about the package
|
||||
|
||||
The SDK has two main layers:
|
||||
|
||||
1. **Composables** for editor state and actions
|
||||
2. **Primitives** for meaningful UI structure
|
||||
|
||||
If you only need editor state and actions, start with composables.
|
||||
If you are building reusable editor UI building blocks, start with primitives.
|
||||
|
||||
## API sections
|
||||
|
||||
- [Components](/programmable/sdk/api/components/)
|
||||
- [Composables](/programmable/sdk/api/composables/)
|
||||
- [Advanced](/programmable/sdk/api/advanced/)
|
||||
Loading…
Reference in a new issue