Core Hooks
React hooks exposed by the context to bind plugin components to state, commands, signals, and config.
All hooks are imported from @opengpex/editor/core/context.
useEditorServices()
Accesses stable service facades and command executors. Does not cause component re-renders when states change.
API Specification
- Input: None.
- Internal Logic: Resolves
EditorServiceContextvalues. If inside a plugin context, wrapsactionsinto a scoped proxy that automatically prefixes local command IDs with the plugin's UID. - Output: An object of type
EditorServiceContextValuecontaining:actions: Scoped facade containing dispatchers, command bus, andadv.*system facades.geometry:GeometryService— bounding boxes, hit testing, alignment.pixels:PixelService— coordinate projection, composite, decode, rasterize, filter.layers:LayerService— safe layer add/remove/activate with composite expansion.assets:AssetService— CAS blob registry, URL management.clipboard:ClipboardService— system clipboard interaction.plugins:PluginService— plugin lifecycle management, busy state.files:FileService— format detection, unified decode/encode with metadata.fonts:FontService— font persistence and registration.storage:StateStorage— state save/restore.volatileRef:MutableRefObject— direct mutable ref for 60fps volatile state.coreVersion: Engine core version string.
Example
import { useEditorServices } from '@opengpex/editor/core/context';
function MyPlugin() {
const { actions, pixels } = useEditorServices();
const handleAction = () => {
// Within plugin scope — short ID auto-resolves:
// executes: "author.plugin_id.cmd.reset"
actions.executeCommand('cmd.reset');
};
// Use the adv facade for system-level operations
const handleFit = () => {
actions.adv.viewport.translate.fit.execute();
};
}
useEditorState()
Subscribes to the reactive global state tree, causing components to re-render when values update.
API Specification
- Input: None.
- Internal Logic: Subscribes to the reactive
EditorStateContextslice. If inside a plugin context, injects a scopedgetSignal(key, default)helper that auto-prefixes the plugin UID. - Output: An object containing:
state:EditorState— full reducer state (frames, layers, pluginConfig, interaction signals).activeFrame:Frame | null— currently active frame/artboard.activeLayer:Layer | null— active layer inside current frame.getSignal:<T>(key: string, defaultValue?: T) => T— scoped signal reader (auto-prefixes plugin UID within plugin scope).
Example
import { useEditorState } from '@opengpex/editor/core/context';
function MyPlugin() {
const { activeFrame, activeLayer } = useEditorState();
if (!activeFrame) return <div>No active frame</div>;
return <div>Active Frame: {activeFrame.id} ({activeFrame.canvas.w}×{activeFrame.canvas.h})</div>;
}
⚠️ Only destructure the specific fields you need. Subscribing to the entire
stateobject causes excessive re-renders on any state change.
usePluginSelfConfig()
Reads and writes persistent configurations for the calling plugin. Zero arguments — auto-resolves the plugin UID from PluginContext.
API Specification
- Input: None.
- Internal Logic: Resolves the plugin's UID from
PluginContextand delegates tousePluginConfig. Merges the plugin'sinitialConfigwith any dynamic overrides stored instate.pluginConfig[uid]. - Output: A tuple
[config: T, setConfig: (patch: Partial<T>) => void].
Example
import { usePluginSelfConfig } from '@opengpex/editor/core/context';
interface MyConfig {
brushSize: number;
mode: 'draw' | 'erase';
}
function BrushSettings() {
const [config, setConfig] = usePluginSelfConfig<MyConfig>();
return (
<input
type="number"
value={config.brushSize || 5}
onChange={(e) => setConfig({ brushSize: Number(e.target.value) })}
/>
);
}
usePluginConfig(pluginId: string)
Accesses and writes dynamic configurations for a specific target plugin UID.
API Specification
- Input:
pluginId: string— The UID of the target plugin (e.g.'opengpex.drawers.adjustment').
- Internal Logic: Subscribes to
state.pluginConfig[pluginId]and merges it with the plugin's registeredinitialConfig. - Output: A tuple
[config: T, setConfig: (patch: Partial<T>) => void].
Example
const [config, setConfig] = usePluginConfig<CropConfig>('opengpex.options.clip_options');
⚠️ Directly modifying another plugin's config is discouraged. Prefer cross-plugin commands for communication.
usePluginCommands()
Provides a mapping of normalized camelCase callable wrappers for all commands declared by the current plugin.
API Specification
- Input: None.
- Internal Logic: Iterates over commands in the plugin's registry scope, converts command IDs (e.g.
cmd.my_action) to camelCase +Cmdsuffix (e.g.myActionCmd), and returns execution objects. - Output: An object dictionary where each key maps to a command instance with:
.execute(payload?)— Execute the command.name— Human-readable command name.shortcutLabel— Formatted keyboard shortcut string (e.g. "⌘Z")
Naming Rules
- Split
cmd.idby delimiters (.,_,-) - Remove leading
"cmd"token if present - CamelCase remaining tokens
- Append
"Cmd"suffix
Example
import { usePluginCommands } from '@opengpex/editor/core/context';
function MyPanel() {
const { resetBoxCmd, toggleMaskCmd } = usePluginCommands();
return (
<div>
<button onClick={() => resetBoxCmd.execute()}>
Reset ({resetBoxCmd.shortcutLabel})
</button>
<button onClick={() => toggleMaskCmd.execute({ visible: true })}>
Show Mask
</button>
</div>
);
}
usePluginSignals()
Provides reactive controllers for all state signals declared in the current plugin's scope.
API Specification
- Input: None.
- Internal Logic: Scans the plugin's registered signals, converts keys (e.g.
signal.active_tab) to camelCase +Signalsuffix (e.g.activeTabSignal), and links them to reactive get/set controllers. - Output: An object map of signal controllers, each with:
.value— Reactive getter for the current signal value.set(val)— Setter to update the signal value
Example
import { usePluginSignals } from '@opengpex/editor/core/context';
function MyPanel() {
const { activeTabSignal, panelOpenSignal } = usePluginSignals();
return (
<div>
<p>Current tab: {activeTabSignal.value}</p>
<button onClick={() => activeTabSignal.set('curves')}>Curves</button>
<button onClick={() => panelOpenSignal.set(!panelOpenSignal.value)}>Toggle</button>
</div>
);
}
usePluginResource(relativePath: string)
Resolves relative paths to assets inside the plugin directory.
API Specification
- Input:
relativePath: string— Path of the asset relative to the plugin folder root.
- Internal Logic: Resolves the plugin's
_folderNamefrom PluginContext and constructs the serve API URL. - Output:
string— URL path:/api/plugins/serve/${folderName}/${relativePath}
Example
import { usePluginResource } from '@opengpex/editor/core/context';
function MyPanel() {
const iconUrl = usePluginResource('visuals/icon.svg');
return <img src={iconUrl} alt="Plugin Icon" />;
}
usePluginList()
Subscribes reactively to the list of all registered plugins. Re-renders when plugins are registered, unregistered, enabled, or disabled.
API Specification
- Input: None.
- Internal Logic: Uses
useSyncExternalStoreto subscribe toPluginServicenotifications. - Output:
BuiltPlugin[]— Current snapshot of all registered plugins.
usePluginSelfBusy()
Reactively reads the busy state of the current plugin.
API Specification
- Input: None.
- Internal Logic: Uses
useSyncExternalStoreto subscribe to the PluginService's busy set, filtered to the current plugin's UID. - Output:
boolean—truewhenctx.scoped.setBusy(true)has been called.
Example
import { usePluginSelfBusy } from '@opengpex/editor/core/context';
function GenerateButton() {
const isBusy = usePluginSelfBusy();
return (
<button disabled={isBusy}>
{isBusy ? 'Processing...' : 'Generate'}
</button>
);
}
When busy, the sidebar drawer icon automatically displays a rainbow gradient breathing animation.
useVolatileInteraction(key)
Subscribe to a specific field of the volatile interaction store without global re-renders.
API Specification
- Input:
key: keyof VolatileInteraction— The specific interaction field to subscribe to.
- Internal Logic: Uses
useSyncExternalStorewith field-level granularity. Only triggers re-render when the subscribed field changes. - Output: The current value of the specified volatile interaction field.
Example
import { useVolatileInteraction } from '@opengpex/editor/core/context';
function CursorInfo() {
const hoveredId = useVolatileInteraction('hoveredLayerId');
const cursor = useVolatileInteraction('cursorOverride');
return <div>Hovered: {hoveredId}, Cursor: {cursor}</div>;
}
Last updated: 2026-07-30