Document Vue SDK public APIs

This commit is contained in:
Danila Poyarkov 2026-03-24 23:25:06 +03:00
parent 99e646aaa4
commit a743da54b6
30 changed files with 438 additions and 95 deletions

View file

@ -1,125 +1,227 @@
# @open-pencil/vue
Headless Vue 3 SDK for embedding an OpenPencil design editor. Renderless components expose logic via scoped slots — bring your own UI.
Headless Vue 3 SDK for building OpenPencil-powered editors.
`@open-pencil/vue` sits on top of `@open-pencil/core` and provides:
- Vue editor injection via `provideEditor()` / `useEditor()`
- canvas integration via `useCanvas()`, `useCanvasInput()`, and `useTextEdit()`
- selection, command, and panel composables
- headless structural primitives like `CanvasRoot`, `LayerTreeRoot`, `PageListRoot`, and `ToolbarRoot`
The SDK is headless by design: it provides logic and structure, while your app owns styling and product-specific UI.
## Install
```bash
npm install @open-pencil/vue @open-pencil/core canvaskit-wasm
bun add @open-pencil/vue @open-pencil/core canvaskit-wasm
```
## Quick start
```vue
<script setup>
<script setup lang="ts">
import { createEditor } from '@open-pencil/core/editor'
import { OpenPencilProvider, OpenPencilCanvas } from '@open-pencil/vue'
import { provideEditor } from '@open-pencil/vue'
const editor = createEditor({
width: 1200,
height: 800,
})
const editor = createEditor()
editor.createShape('RECTANGLE', 100, 100, 200, 150)
editor.zoomToFit()
provideEditor(editor)
</script>
<template>
<OpenPencilProvider :editor="editor">
<OpenPencilCanvas style="width: 100%; height: 100vh" />
</OpenPencilProvider>
<div class="h-screen">
<CanvasRoot v-slot="{ canvasRef }">
<canvas ref="canvasRef" class="size-full" />
</CanvasRoot>
</div>
</template>
```
## Components
## Core concepts
All components are **renderless** — they provide data and actions via scoped slots. You control the markup and styling.
### Editor context
### `<OpenPencilProvider>`
Use `provideEditor(editor)` once near the top of your subtree.
Provides the editor instance to all child components via Vue injection.
```ts
import { provideEditor } from '@open-pencil/vue'
```vue
<OpenPencilProvider :editor="editor">
<slot />
</OpenPencilProvider>
provideEditor(editor)
```
### `<OpenPencilCanvas>`
Renders the CanvasKit/Skia drawing surface. Handles WebGL/WebGPU setup, resize, and the render loop.
| Prop | Type | Description |
|------|------|-------------|
| `showRulers` | `boolean?` | Override ruler visibility |
| `preserveDrawingBuffer` | `boolean?` | Keep buffer for screenshots |
| `onReady` | `() => void` | Called when surface is ready |
### `<PageList>`
Exposes page data and navigation.
```vue
<PageList v-slot="{ pages, currentPageId, switchPage, addPage, deletePage, renamePage }">
<div v-for="page in pages" :key="page.id" @click="switchPage(page.id)">
{{ page.name }}
</div>
</PageList>
```
### `<LayerTree>`
Exposes the flattened layer tree with selection state.
```vue
<LayerTree v-slot="{ layers, selectedIds, select, toggleVisibility, toggleLock }">
<div
v-for="layer in layers" :key="layer.node.id"
:style="{ paddingLeft: layer.depth * 16 + 'px' }"
@click="select([layer.node.id])"
>
{{ layer.node.name }}
</div>
</LayerTree>
```
### `<ToolSelector>`
Exposes the active tool and setter.
```vue
<ToolSelector v-slot="{ activeTool, setTool }">
<button :class="{ active: activeTool === 'SELECT' }" @click="setTool('SELECT')">Select</button>
<button :class="{ active: activeTool === 'RECTANGLE' }" @click="setTool('RECTANGLE')">Rectangle</button>
</ToolSelector>
```
### `<NodeProperties>`
Exposes the selected node and an update function.
```vue
<NodeProperties v-slot="{ node, update }">
<div v-if="node">
<input :value="node.x" @change="update({ x: +$event.target.value })" />
</div>
</NodeProperties>
```
## Composables
### `useEditor()`
Access the editor instance from any descendant of `<OpenPencilProvider>`.
Read it anywhere below with `useEditor()`.
```ts
import { useEditor } from '@open-pencil/vue'
const editor = useEditor()
editor.createShape('ELLIPSE', 0, 0, 100, 100)
```
### `useCanvas(canvasRef, editor, options?)`
### Canvas wiring
Low-level composable for CanvasKit surface lifecycle. Used internally by `<OpenPencilCanvas>` — use directly only if you need custom canvas setup.
At the composable level, the main canvas APIs are:
## Example
- `useCanvas()`
- `useCanvasInput()`
- `useTextEdit()`
If you want SDK-provided structure, use headless primitives like `CanvasRoot` and `CanvasSurface`.
### Headless primitives
Main structural primitives include:
- `CanvasRoot`
- `LayerTreeRoot`
- `PageListRoot`
- `PropertyListRoot`
- `ToolbarRoot`
- `ColorPickerRoot`
- `FillPickerRoot`
- `FontPickerRoot`
These components coordinate structure and state, but do not impose app styling.
## Public API tiers
### Core API
These are the main APIs most SDK consumers should start with.
#### Context and canvas
- `provideEditor()`
- `useEditor()`
- `useCanvas()`
- `useCanvasInput()`
- `useTextEdit()`
#### Selection and commands
- `useSelectionState()`
- `useSelectionCapabilities()`
- `useEditorCommands()`
- `useMenuModel()`
#### Property panels
- `usePosition()`
- `useLayout()`
- `useAppearance()`
- `useTypography()`
- `useExport()`
- `useFillControls()`
- `useStrokeControls()`
- `useEffectsControls()`
#### Variables and navigation
- `useVariablesEditor()`
- `usePageList()`
#### Headless primitives
- `CanvasRoot`
- `LayerTreeRoot`
- `PageListRoot`
- `PropertyListRoot`
- `ToolbarRoot`
### Advanced API
These exports are intentionally public, but they are lower-level or more specialized.
- `useNodeProps()`
- `useSceneComputed()`
- `useFillVariableBinding()`
- `useFillPicker()`
- `useGradientStops()`
- `useFontPicker()`
- `usePropScrub()`
- `useLayerDrag()`
- `useInlineRename()`
- `useToolbarState()`
- `useNodeFontStatus()`
- `useCanvasDrop()`
- `extractImageFilesFromClipboard()`
- `toolCursor()`
### Primitive context helpers
These are mostly useful when extending SDK primitives rather than building from top-level composables.
- `useCanvasContext()`
- `useLayerTree()`
- `useToolbar()`
- `usePropertyList()`
- `useScrubInput()`
## Example patterns
### Minimal provider component
```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>
```
### Read selection state
```ts
import { useSelectionState } from '@open-pencil/vue'
const { hasSelection, selectedCount, selectedNode } = useSelectionState()
```
### Build a menu
```ts
import { useMenuModel } from '@open-pencil/vue'
const { appMenu, canvasMenu } = useMenuModel()
```
### Build a page list
```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>
```
## Documentation
For fuller guides and API docs, see the documentation site:
- `packages/docs/programmable/sdk/`
## Example app
Run the included example:

View file

@ -2,11 +2,9 @@
import { ref } from 'vue'
import { useEditor } from '@open-pencil/vue/context/editorContext'
import { useCanvas } from '@open-pencil/vue/shared/useCanvas'
import { useCanvas, type UseCanvasOptions } from '@open-pencil/vue'
import { provideCanvas } from './context'
import type { UseCanvasOptions } from '@open-pencil/vue/shared/useCanvas'
const props = withDefaults(defineProps<UseCanvasOptions>(), {
showRulers: undefined
})

View file

@ -29,6 +29,13 @@ import type {
DragState
} from '@open-pencil/vue/shared/input/types'
/**
* Wires pointer and mouse interaction to an OpenPencil canvas.
*
* This composable coordinates selection, dragging, resizing, rotation,
* panning, drawing tools, scoped hit testing, and text-edit interaction.
* It is primarily intended for editor shell components that own the canvas.
*/
export function useCanvasInput(
canvasRef: Ref<HTMLCanvasElement | null>,
editor: Editor,

View file

@ -14,6 +14,13 @@ import type { Editor } from '@open-pencil/core/editor'
const CARET_BLINK_MS = 530
/**
* Bridges DOM text input and the editor's canvas text-editing model.
*
* This composable manages textarea-backed input, IME composition, caret
* blinking, keyboard editing behavior, text formatting shortcuts, and syncing
* text/style-run updates back into the scene graph.
*/
export function useTextEdit(canvasRef: Ref<HTMLCanvasElement | null>, store: Editor) {
const textareaRef = shallowRef<HTMLTextAreaElement | null>(null)
let isComposing = false

View file

@ -19,6 +19,12 @@ function gradientCSS(stops: GradientStop[]): string {
return stops.map((s) => `${colorToCSS(s.color)} ${s.position * 100}%`).join(', ')
}
/**
* Returns category and conversion helpers for a single fill value.
*
* This composable is useful for fill pickers that switch between solid,
* gradient, and image modes while keeping a live fill model in sync.
*/
export function useFillPicker(fill: Ref<Fill>, onUpdate: (fill: Fill) => void) {
const category = computed(() => FILL_CATEGORY[fill.value.type] ?? 'SOLID')

View file

@ -1,12 +1,21 @@
import { useFilter } from 'reka-ui'
import { computed, onMounted, ref, watch } from 'vue'
/**
* Options for {@link useFontPicker}.
*/
export interface UseFontPickerOptions {
/** Writable model for the selected font family. */
modelValue: { value: string }
/** Async source for available font families. */
listFamilies: () => Promise<string[]>
/** Optional callback fired after a family is selected. */
onSelect?: (family: string) => void
}
/**
* Returns searchable font-picker state and selection helpers.
*/
export function useFontPicker(options: UseFontPickerOptions) {
const families = ref<string[]>([])
const searchTerm = ref('')

View file

@ -24,6 +24,12 @@ const DEFAULT_TRANSFORMS: Record<GradientSubtype, GradientTransform> = {
GRADIENT_DIAMOND: { m00: 0.5, m01: 0, m02: 0.5, m10: 0, m11: 0.5, m12: 0.5 }
}
/**
* Returns gradient-stop state and mutation helpers for a fill.
*
* Use this composable for gradient editors that need subtype switching,
* active-stop selection, stop dragging, and stop color/opacity editing.
*/
export function useGradientStops(fill: Ref<Fill>, onUpdate: (fill: Fill) => void) {
const activeStopIndex = ref(0)
const stops = computed(() => fill.value.gradientStops ?? [])

View file

@ -3,6 +3,12 @@ import { computed } from 'vue'
import { useEditor } from '@open-pencil/vue/context/editorContext'
import { useSceneComputed } from '@open-pencil/vue/internal/useSceneComputed'
/**
* Returns reactive page state and page-management actions.
*
* Use this composable to build page switchers, page lists, or navigation
* panels without manually reading the graph in each component.
*/
export function usePageList() {
const editor = useEditor()

View file

@ -4,6 +4,12 @@ import type { Tool, EditorToolDef } from '@open-pencil/core/editor'
const CATEGORY_COUNT = 3
/**
* Returns responsive toolbar UI state for mobile category paging.
*
* This composable is presentation-oriented and complements {@link useToolbar}
* when building toolbar shells.
*/
export function useToolbarState() {
const mobileCategory = ref(0)
const slideDirection = ref(1)

View file

@ -4,10 +4,18 @@ import { computed, type Component } from 'vue'
import { useVariablesDialogState } from './useVariablesDialogState'
import { useVariablesTable } from './useVariablesTable'
/**
* Composes variables dialog state, table columns, and TanStack table wiring
* into a single higher-level variables editor API.
*/
export function useVariablesEditor(options: {
/** Component used for color variable editing. */
colorInput: Component
/** Icon map keyed by variable resolved type. */
icons: Record<string, Component>
/** Fallback icon when no specific icon matches a variable type. */
fallbackIcon: Component
/** Icon used for destructive remove actions. */
deleteIcon: Component
}) {
const ctx = useVariablesDialogState()

View file

@ -7,6 +7,9 @@ import { useSelectionState } from '@open-pencil/vue/selection/useSelectionState'
import type { Component, ComputedRef } from 'vue'
/**
* Stable command identifiers exposed by {@link useEditorCommands}.
*/
export type EditorCommandId =
| 'edit.undo'
| 'edit.redo'
@ -30,10 +33,17 @@ export type EditorCommandId =
| 'view.zoomFit'
| 'view.zoomSelection'
/**
* Reactive editor command descriptor.
*/
export interface EditorCommand {
/** Stable command id. */
id: EditorCommandId
/** Human-readable label for UI. */
label: string
/** Whether the command can currently run. */
enabled: ComputedRef<boolean>
/** Executes the command. */
run: () => void
}
@ -53,6 +63,13 @@ export interface EditorCommandMenuSeparator {
export type EditorCommandMenuEntry = EditorCommandMenuItem | EditorCommandMenuSeparator
/**
* Builds a command-oriented interface on top of the current editor.
*
* Use this composable when building menus, toolbars, keyboard handlers, or
* any other UI that should talk in terms of commands instead of raw editor
* method calls.
*/
export function useEditorCommands() {
const editor = useEditor()
const selection = useSelectionState()

View file

@ -4,6 +4,9 @@ import { useEditorCommands } from '@open-pencil/vue/commands/useEditorCommands'
import { useEditor } from '@open-pencil/vue/context/editorContext'
import { useSelectionState } from '@open-pencil/vue/selection/useSelectionState'
/**
* Action entry used by menu models returned from {@link useMenuModel}.
*/
export interface MenuActionNode {
separator?: false
label: string
@ -21,6 +24,13 @@ export interface MenuSeparatorNode {
export type MenuEntry = MenuActionNode | MenuSeparatorNode
/**
* Returns ready-to-render menu models derived from the current editor state.
*
* This is a higher-level API than {@link useEditorCommands}: it groups
* commands into app and canvas menu structures and computes context-sensitive
* labels like Hide/Show and Lock/Unlock.
*/
export function useMenuModel() {
const editor = useEditor()
const { menuItem: commandMenuItem, otherPages, moveSelectionToPage } = useEditorCommands()

View file

@ -3,12 +3,30 @@ import { inject, provide } from 'vue'
import type { Editor } from '@open-pencil/core/editor'
import type { InjectionKey } from 'vue'
/**
* Injection key for the current OpenPencil editor instance.
*
* Most SDK consumers should use {@link provideEditor} and {@link useEditor}
* instead of interacting with this symbol directly.
*/
export const EDITOR_KEY: InjectionKey<Editor> = Symbol('open-pencil-editor')
/**
* Provides an OpenPencil editor instance to the current Vue subtree.
*
* Call this once near the top of your editor shell so descendant composables
* and headless primitives can access the editor with {@link useEditor}.
*/
export function provideEditor(editor: Editor) {
provide(EDITOR_KEY, editor)
}
/**
* Returns the current injected OpenPencil editor.
*
* Throws if called outside a subtree where {@link provideEditor} has already
* been called.
*/
export function useEditor(): Editor {
const editor = inject(EDITOR_KEY)
if (!editor) {

View file

@ -13,6 +13,12 @@ const CORNER_RADIUS_TYPES = new Set([
'INSTANCE'
])
/**
* Returns appearance-related state and actions for the current selection.
*
* Use this composable for visibility, opacity, and corner-radius controls in
* property panels.
*/
export function useAppearance() {
const editor = useEditor()
const { nodes, node, active, isMulti, merged, updateProp, commitProp } = useNodeProps()

View file

@ -16,6 +16,12 @@ const EFFECT_LABELS: Record<string, string> = {
const EFFECT_TYPES = Object.keys(EFFECT_LABELS) as EffectType[]
/**
* Returns effect-editing helpers for property panels.
*
* This composable manages default effect creation, expanded-row state,
* scrub-preview behavior, and effect type/color updates.
*/
export function useEffectsControls() {
const editor = useEditor()

View file

@ -5,14 +5,25 @@ import { useSceneComputed } from '@open-pencil/vue/internal/useSceneComputed'
import type { ExportFormat } from '@open-pencil/core'
/**
* Single export preset row managed by {@link useExport}.
*/
interface ExportSetting {
/** Export scale multiplier. */
scale: number
/** Output file format. */
format: ExportFormat
}
const SCALES = [0.5, 0.75, 1, 1.5, 2, 3, 4] as const
const FORMATS: ExportFormat[] = ['PNG', 'JPG', 'WEBP', 'SVG']
/**
* Returns selection-aware export settings for export panel UIs.
*
* This composable manages export presets such as scale and format while also
* exposing the current export target label derived from the selection.
*/
export function useExport() {
const editor = useEditor()

View file

@ -2,6 +2,12 @@ import { DEFAULT_SHAPE_FILL } from '@open-pencil/core'
import { useFillVariableBinding } from './useFillVariableBinding'
/**
* Returns fill-related panel helpers and a reusable default fill value.
*
* This composable extends variable-binding behavior with SDK-level defaults for
* fill editing UIs.
*/
export function useFillControls() {
const ctx = useFillVariableBinding()

View file

@ -5,6 +5,12 @@ import { useEditor } from '@open-pencil/vue/context/editorContext'
import type { Variable } from '@open-pencil/core'
/**
* Returns helpers for binding fill colors to color variables.
*
* This composable is used by fill editing UIs that need variable search,
* binding, and unbinding behavior.
*/
export function useFillVariableBinding() {
const store = useEditor()
const colorVariables = computed(() => store.getVariablesByType('COLOR'))

View file

@ -44,6 +44,12 @@ const TRACK_SIZING_OPTIONS: { value: GridTrackSizing; label: string }[] = [
{ value: 'AUTO', label: 'Auto' }
]
/**
* Returns layout-related state and actions for the current selection.
*
* Use this composable to build auto-layout and grid panels that need sizing,
* padding, alignment, and track editing behavior.
*/
export function useLayout() {
const editor = useEditor()

View file

@ -5,11 +5,20 @@ import { useSceneComputed } from '@open-pencil/vue/internal/useSceneComputed'
import type { Effect, Fill, SceneNode, Stroke } from '@open-pencil/core'
/** Sentinel value returned when a property differs across multiple selected nodes. */
export const MIXED = Symbol('mixed')
/** Property value that may either be concrete or mixed across the selection. */
export type MixedValue<T> = T | typeof MIXED
type ArrayItem = Fill | Stroke | Effect | Record<string, unknown>
/**
* Returns shared property-panel helpers for the current selection.
*
* This composable centralizes mixed-value detection, multi-selection updates,
* array-item editing, and commit semantics used by higher-level controls.
*/
export function useNodeProps() {
const store = useEditor()
const node = useSceneComputed(() => store.getSelectedNode() ?? null)

View file

@ -6,6 +6,12 @@ import { useSceneComputed } from '@open-pencil/vue/internal/useSceneComputed'
import type { SceneNode } from '@open-pencil/core'
/**
* Returns position-related state and actions for the current selection.
*
* This composable is designed for property panels that edit x/y, size,
* rotation, alignment, flipping, and multi-node transforms.
*/
export function usePosition() {
const editor = useEditor()

View file

@ -30,6 +30,12 @@ const DEFAULT_STROKE: Stroke = {
align: 'CENTER'
}
/**
* Returns stroke-related helpers for property panels.
*
* This composable provides alignment options, side presets, a default stroke,
* and helpers for per-side border weight editing.
*/
export function useStrokeControls() {
const store = useEditor()
const sideMenuOpen = ref(false)

View file

@ -14,10 +14,21 @@ const WEIGHTS = Object.entries(FONT_WEIGHT_NAMES).map(([value, label]) => ({
label
}))
/**
* Options for {@link useTypography}.
*/
export interface UseTypographyOptions {
/**
* Optional font loader invoked before changing family or weight.
*/
loadFont?: (family: string, style: string) => Promise<unknown>
}
/**
* Returns typography-related state and actions for the current text selection.
*
* This composable is designed for text property panels and formatting controls.
*/
export function useTypography(options: UseTypographyOptions = {}) {
const editor = useEditor()

View file

@ -7,22 +7,35 @@ export type {
} from '@open-pencil/core/editor'
export { createEditor, EDITOR_TOOLS, TOOL_SHORTCUTS } from '@open-pencil/core/editor'
/**
* Public editor-context API for the Vue SDK.
*
* These are the primary entry points for making an editor available to a Vue
* subtree and reading it back inside composables and headless primitives.
*/
export { provideEditor, useEditor, EDITOR_KEY } from './context/editorContext'
/** Canvas and input integration composables. */
export { useCanvas } from './shared/useCanvas'
export type { UseCanvasOptions } from './shared/useCanvas'
export { useCanvasInput } from './Canvas/useCanvasInput'
export { useTextEdit } from './Canvas/useTextEdit'
export { useCanvasDrop, extractImageFilesFromClipboard } from './Canvas/useCanvasDrop'
/** Low-level selection, graph, and derived-state helpers. */
export { useNodeProps, MIXED } from './controls/useNodeProps'
export type { MixedValue } from './controls/useNodeProps'
export { useSceneComputed } from './internal/useSceneComputed'
export { useSelectionState } from './selection/useSelectionState'
export { useSelectionCapabilities } from './selection/useSelectionCapabilities'
/** Command and menu composition helpers. */
export { useEditorCommands } from './commands/useEditorCommands'
export type { EditorCommand, EditorCommandId } from './commands/useEditorCommands'
export { useMenuModel } from './commands/useMenuModel'
export type { MenuEntry } from './commands/useMenuModel'
/** Miscellaneous editor-shell helpers. */
export { useViewportKind } from './viewport/useViewportKind'
export { useLayerDrag } from './LayerTree/useLayerDrag'
export { useInlineRename } from './shared/useInlineRename'
@ -30,25 +43,30 @@ export { useToolbarState } from './Toolbar/useToolbarState'
export { useNodeFontStatus } from './shared/useFontStatus'
export { usePropScrub } from './controls/usePropScrub'
export { toolCursor } from './internal/toolCursor'
/** Property-panel composables. */
export { usePosition } from './controls/usePosition'
export { useLayout } from './controls/useLayout'
export { useAppearance } from './controls/useAppearance'
export { useTypography } from './controls/useTypography'
export type { UseTypographyOptions } from './controls/useTypography'
export { useExport } from './controls/useExport'
export { useFillControls } from './controls/useFillControls'
export { useFillVariableBinding } from './controls/useFillVariableBinding'
export { useEffectsControls } from './controls/useEffectsControls'
export { useStrokeControls } from './controls/useStrokeControls'
/** Variables, page navigation, and picker helpers. */
export { useVariables } from './VariablesEditor/useVariables'
export { useVariablesDialogState } from './VariablesEditor/useVariablesDialogState'
export { useVariablesEditor } from './VariablesEditor/useVariablesEditor'
export { useVariablesTable } from './VariablesEditor/useVariablesTable'
export { useFillControls } from './controls/useFillControls'
export { useFillPicker } from './FillPicker/useFillPicker'
export { useFillVariableBinding } from './controls/useFillVariableBinding'
export { useEffectsControls } from './controls/useEffectsControls'
export { useGradientStops } from './GradientEditor/useGradientStops'
export { useStrokeControls } from './controls/useStrokeControls'
export { usePageList } from './PageList/usePageList'
export { useFillPicker } from './FillPicker/useFillPicker'
export { useGradientStops } from './GradientEditor/useGradientStops'
export { useFontPicker } from './FontPicker/useFontPicker'
/** Headless structural primitives and their local contexts. */
export { CanvasRoot, CanvasSurface, useCanvasContext } from './Canvas'
export type { CanvasContext } from './Canvas'
export { ColorInputRoot, ColorPickerRoot } from './ColorPicker'

View file

@ -1,5 +1,11 @@
import { computed, type ComputedRef } from 'vue'
/**
* Convenience wrapper for scene-derived computed state.
*
* Use this for values that should clearly read as editor/scene-backed derived
* state in higher-level composables.
*/
export function useSceneComputed<T>(fn: () => T): ComputedRef<T> {
return computed(fn)
}

View file

@ -3,6 +3,13 @@ import { computed } from 'vue'
import { useSceneComputed } from '@open-pencil/vue/internal/useSceneComputed'
import { useSelectionState } from '@open-pencil/vue/selection/useSelectionState'
/**
* Returns reactive booleans describing which selection-dependent actions are
* currently available.
*
* This is useful for menus, toolbars, shortcuts, and action buttons that need
* command-friendly capability checks.
*/
export function useSelectionCapabilities() {
const {
editor,

View file

@ -5,6 +5,12 @@ import { useSceneComputed } from '@open-pencil/vue/internal/useSceneComputed'
import type { SceneNode } from '@open-pencil/core'
/**
* Returns reactive selection-derived state for the current editor.
*
* Use this composable to drive UI from the current selection without manually
* reading graph state in every component.
*/
export function useSelectionState() {
const editor = useEditor()

View file

@ -39,12 +39,36 @@ async function initWebGPU(ck: CanvasKit): Promise<WebGPUContext | null> {
return { device, deviceContext }
}
/**
* Options for {@link useCanvas}.
*/
export interface UseCanvasOptions {
/**
* Forces ruler visibility on or off for this canvas.
*
* When omitted, the composable falls back to viewport and URL-param logic.
*/
showRulers?: boolean
/**
* Keeps the drawing buffer after presenting frames.
*
* Useful for screenshot or pixel-readback workflows, but may increase memory
* usage depending on the browser and GPU backend.
*/
preserveDrawingBuffer?: boolean
/**
* Called once the rendering surface is ready.
*/
onReady?: () => void
}
/**
* Connects an OpenPencil editor to a real canvas element using CanvasKit.
*
* This composable owns renderer creation, surface recreation on resize,
* render scheduling, and renderer-backed hit testing helpers used by higher-
* level canvas interaction code.
*/
export function useCanvas(
canvasRef: Ref<HTMLCanvasElement | null>,
editor: Editor,

View file

@ -4,6 +4,12 @@ import { isFontLoaded, DEFAULT_FONT_FAMILY } from '@open-pencil/core'
import type { SceneNode } from '@open-pencil/core'
/**
* Returns missing-font information for a text node getter.
*
* This is useful for typography panels and warnings that need to surface fonts
* that are referenced by a node but not yet loaded in the current runtime.
*/
export function useNodeFontStatus(node: () => SceneNode | null | undefined) {
const missingFonts = computed(() => {
const n = node()

View file

@ -3,6 +3,9 @@ import { computed } from 'vue'
const breakpoints = useBreakpoints({ mobile: 768 })
/**
* Returns coarse viewport kind flags used by responsive editor UI.
*/
export function useViewportKind() {
const isMobile = breakpoints.smaller('mobile')
const isDesktop = computed(() => !isMobile.value)