Skip to main content

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 a Page or Element class. Objects are Fabric.js's native FabricObject. Multi-page support is planned on top of SceneHandler — 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

FieldTypeDescription
widthnumberCanvas width (pixels)
heightnumberCanvas height (pixels)
scalenumberCurrent zoom; 1 means 100%
scaleToFitnumberBase zoom used by "fit to screen"
unit'px' | 'mm' | 'cm' | 'in'Canvas unit
dpinumberPrint resolution, defaults to 72
bleednumberBleed (for print)
bleedVisiblebooleanWhether bleed lines are shown
rulesVisiblebooleanWhether rulers are shown
backgroundColorstring | objectBackground color (solid or gradient object)
selectedElementsIdsstring[]IDs of selected elements
fontsFont[]Fonts used in the current design
openedSidePanelstringName of the opened side panel
editorEditor | null@ydesign/core Editor — holds every handler
objectsArray<any>Read-only snapshot of canvas objects

editor is your "remote control" for the canvas: every canvas operation goes through store.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


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().