Store Overview
store is the reactive data hub of the Ydesign editor. Every UI state (canvas size, zoom, selection, fonts, background, …) and the canvas instance (editor) live on a single MobX-State-Tree (MST) model.
React / Vue components ──read──► store ◄──write── user interaction
│
└──► store.editor.xxxHandler ──► Fabric canvas
Components only read and write store; every canvas operation flows through handlers under store.editor. Canvas-side events are synced back to the store automatically, so the UI re-renders itself.
💡 Unlike Polotno's
Store, Ydesign does not expose aPageorElementclass. Objects are Fabric.js's nativeFabricObject. Multi-page support is planned on top ofSceneHandler— see that page.
Creating a store
import { createStore } from '@ydesign/react-editor';
const store = createStore({
key: 'YOUR_API_KEY', // credential for backend features (fonts, templates, AI)
token: 'user-token', // optional: your auth token
workspaceId: 'ws-001', // optional: multi-tenant isolation
});
The return value is a live MST instance. All state fields and actions live on it.
Relationship with <DesignEditorApp />
When you use the one-shot factory, it creates and returns the store for you:
import { createDesignEditorApp } from '@ydesign/react-editor';
const { store } = createDesignEditorApp({
container: document.getElementById('root'),
key: 'YOUR_API_KEY',
});
store.setSize({ width: 1200, height: 800 });
State fields
| Field | Type | Description |
|---|---|---|
width | number | Canvas width (pixels) |
height | number | Canvas height (pixels) |
scale | number | Current zoom; 1 means 100% |
scaleToFit | number | Base zoom used by "fit to screen" |
unit | 'px' | 'mm' | 'cm' | 'in' | Canvas unit |
dpi | number | Print resolution, defaults to 72 |
bleed | number | Bleed (for print) |
bleedVisible | boolean | Whether bleed lines are shown |
rulesVisible | boolean | Whether rulers are shown |
backgroundColor | string | object | Background color (solid or gradient object) |
selectedElementsIds | string[] | IDs of selected elements |
fonts | Font[] | Fonts used in the current design |
openedSidePanel | string | Name of the opened side panel |
editor | Editor | null | @ydesign/core Editor — holds every handler |
objects | Array<any> | Read-only snapshot of canvas objects |
editoris your "remote control" for the canvas: every canvas operation goes throughstore.editor.xxxHandler.xxx(). See Elements and Scenes.
Derived views
MST views let you access values derived from the source state:
// Currently selected Fabric objects
store.selectedElements;
// => FabricObject[]
// Flattened selection (group children included, group itself excluded)
store.selectedShapes;
// => FabricObject[]
// Lookup by id
store.getElementById('abc123');
// Search by predicate (recurses into groups)
store.find(e => e.type === 'textbox');
Common actions (UI layer)
Canvas size / unit / background
store.setSize({ width: 1200, height: 800 });
store.setUnit({ unit: 'mm', dpi: 300 });
store.setBackgroundColor('#f0f0f0');
store.setBackgroundColor({
type: 'linear',
coords: { x1: 0, y1: 0, x2: 1, y2: 1 },
colorStops: [
{ offset: 0, color: '#ff6a00' },
{ offset: 1, color: '#2253eb' },
],
});
store.setScale(1); // updates UI only; to zoom the canvas use zoomHandler
Selection / batch operations
store.selectElements(['id-1', 'id-2']);
store.addElement({
type: 'textbox',
text: 'Hello Ydesign',
left: 100,
top: 100,
fontSize: 48,
fill: '#111',
});
store.set({ opacity: 0.8 });
store.set({ fill: '#ff6a00' }, someElement);
store.deleteElements(['id-1', 'id-2']);
store.clone();
Layers
store.moveElementsUp(['id-1']);
store.moveElementsDown(['id-1']);
store.moveElementsTop(['id-1']);
store.moveElementsBottom(['id-1']);
store.canMoveElementsUp(['id-1']);
store.canMoveElementsTop(['id-1']);
See Elements · Layers.
Fonts
store.addFont({
fontFamily: 'Noto Sans SC',
url: 'https://cdn.example.com/noto-sans-sc.woff2',
});
store.removeFont('Noto Sans SC');
await store.loadFont('Noto Sans SC');
There are two font sources: user fonts (in
store.fonts, serialized with the design) and global fonts (registered process-wide, not in JSON). See Editor Configuration · Fonts.
Undo / redo
The history stack lives on editor.historyHandler:
store.editor?.historyHandler.undo();
store.editor?.historyHandler.redo();
const { hasUndo, hasRedo } = store.editor!.historyHandler.getStatus();
Import / export
const json = store.toJSON();
await store.loadJSON(json);
const dataUrl = await store.toDataURL({ multiplier: 2, format: 'png' });
const blob = await store.toBlob({ multiplier: 2, format: 'jpeg', quality: 0.9 });
await store.saveAsImage({ multiplier: 2, format: 'png', fileName: 'my-design.png' });
Full options in Scenes · Import & Export.
Reactivity
A. React observer
@ydesign/react-editor already wraps observer around its built-in components. Your own components can do the same:
import { observer } from 'mobx-react-lite';
const MyStatusBar = observer(({ store }) => {
return (
<div>
Canvas: {store.width} × {store.height} px — {store.selectedElementsIds.length} selected
</div>
);
});
B. MobX reaction / autorun
Works well for non-React logic like auto-save or dirty tracking:
import { reaction } from 'mobx';
reaction(
() => store.toJSON(),
json => {
localStorage.setItem('design-draft', JSON.stringify(json));
},
{ delay: 1000 },
);
C. store.on('change', cb)
Fires whenever store.objects (canvas object snapshot) changes:
const dispose = store.on('change', objects => {
console.log(`${objects.length} objects on canvas`);
});
dispose();
Serialization
Use MST's getSnapshot for the full store:
import { getSnapshot } from 'mobx-state-tree';
const snapshot = getSnapshot(store);
For just the canvas content (objects + workarea), store.toJSON() is simpler and lines up with Fabric's serializable shape.
Full reference
- 👉 Elements
- 👉 Scenes & import/export
- 👉 Editor Configuration (uploads / fonts / i18n / theme)
- 👉 @ydesign/core · Handlers
FAQ
Q: Why no Page / Element classes?
A: Ydesign uses Fabric's FabricObject directly. Adding another class on top adds abstraction overhead without real benefit. Polotno's Element is basically an adapter layer we feel isn't necessary.
Q: My design needs multiple pages — what should I do?
A: Only single-page is supported today. Multi-page is planned on top of SceneHandler, where each page is its own canvas JSON and switching is done via importFromJSON hot-reloads.
Q: Can I mutate store.width = 1000 directly?
A: No. MST top-level fields only accept action writes. Use store.setSize({ width: 1000, height: ... }).
Q: Why can store.editor be null?
A: Editor is created asynchronously when <Workspace /> mounts. Guard with optional chaining: store.editor?.zoomHandler.zoomToFit().