DocMosaicdocs
Concepts

Designer

The mental model behind the editor - state, actions, and the Editor.* compound namespace that ties them together.

The DocMosaic editor is a designer for printable documents - users arrange image sections on a virtual page and export a PDF. @docmosaic/react exposes that designer as a small set of compound primitives that snap together; @docmosaic/core is the headless state machine they all read from. Same model, two surfaces.

Mental model

Think of the editor as three concentric layers:

  1. State - a single Document carrying pages, a flat sections array, and metadata (name, pageSize, orientation). All geometry is in PDF points.
  2. Actions - a stable 18-method surface (addSection, updateSection, bringToFront, undo, …) that produces the next document. The bundled reducer wraps these with a history timeline so undo/redo "just work."
  3. UI - the Editor.* compound namespace. Every primitive reads its slice of state from context and dispatches actions back; nothing is prop-drilled.
Editor.Root                        ← owns Document + history + DnD provider
 ├─ Editor.Properties              ← document name, page size, orientation
 ├─ Editor.Toolbar                 ← undo/redo, preview, download, add-section
 ├─ Editor.Pages                   ← left rail of page thumbnails
 ├─ Editor.Canvas                  ← interactive workspace (drag, resize, zoom)
 │   └─ Editor.Section             ← one image rectangle on the canvas
 └─ Editor.Preview                 ← full-document preview dialog

Editor.Root arranges its children into the default shell automatically - Pages is forced to the left of Canvas regardless of source order - so the flat composition above works out of the box.

Composition

import { Editor } from '@docmosaic/react';
import '@docmosaic/react/styles.css';

export function MyEditor() {
    return (
        <Editor.Root>
            <Editor.Properties />
            <Editor.Toolbar />
            <Editor.Pages />
            <Editor.Canvas>
                <Editor.Section />
            </Editor.Canvas>
            <Editor.Preview />
        </Editor.Root>
    );
}

The CSS import seeds the --editor-* tokens documented in Theming. Drop any primitive - or build your own from useEditor() - and the rest of the tree keeps working.

Selection model

The editor tracks a single selected section at a time (ui.selectedSectionId). Selection drives:

  • The visible toolbar on Editor.Section (fit / layer / duplicate / delete buttons).
  • Keyboard nudge + delete bindings (see Keybindings).
  • Resize handles and the focus outline.

Clicking a section selects it; clicking the canvas background, hitting Escape, or deleting the selected section clears the selection. Multi-select is intentionally not part of the v1 surface - most flows that "want" multi-select are better expressed via layer actions on a single section.

Drag, resize, upload

The Canvas wires every section to three gestures:

  • Drag - pointer down on a section, move, pointer up. The reducer applies UPDATE_SECTION with the new (x, y).
  • Resize - drag the right, bottom, or bottom-right handle. The Canvas captures the start size and dispatches incremental UPDATE_SECTION calls.
  • Image upload - drop a file onto a section (or click the empty-state). The reader produces a data URL, which becomes section.imageUrl.

Drag-and-drop is powered by react-dnd with a multi-backend (HTML5 on desktop, auto-transitioning to touch on mobile). Editor.Root mounts a single <DndProvider> for the whole tree - don't nest another.

Headless mode

When the compound shell isn't a fit (custom layout, native app, Server Components downstream), use the headless hook directly. It owns the same reducer + history:

import { createDocument } from '@docmosaic/core';
import { useDocumentState } from '@docmosaic/react';

function CustomEditor() {
    const { document, canUndo, canRedo, actions } = useDocumentState({
        initialDocument: createDocument(),
    });

    return (
        <div>
            <button disabled={!canUndo} onClick={actions.undo}>
                Undo
            </button>
            <button disabled={!canRedo} onClick={actions.redo}>
                Redo
            </button>
            <button onClick={actions.addSection}>Add section</button>
            <p>
                {document.name} - {document.sections.length} sections
            </p>
        </div>
    );
}

actions is referentially stable; document, canUndo, and canRedo re-render on every change. Wrap the same value in EditorProvider if you want compound primitives to see your custom-built state.

Controlled vs. uncontrolled

Editor.Root works in both modes:

  • Uncontrolled (default) - omit document. The root owns state internally. Pass defaultDocument to seed it.
  • Controlled - pass document + onDocumentChange. Every mutation calls back out; the parent is responsible for re-rendering. Undo/redo are disabled because the timeline lives outside.
const [doc, setDoc] = useState(createDocument());

<Editor.Root document={doc} onDocumentChange={setDoc}>
    {/* … */}
</Editor.Root>;

Don't mix modes mid-render - the root warns in development if you do.

Read-only mode

Editor.Root accepts a readOnly boolean that flips the editor into viewer mode. Every mutating interaction (drag, resize, drop, file upload, page add/delete/reorder, undo/redo, keyboard nudge/delete, drawing-mode strokes) is suppressed. Mutating toolbar buttons (Add*, Draw, Undo, Redo) hide themselves; Preview, Print, and Download stay live so the viewer can still export.

<Editor.Root defaultDocument={signedContract} readOnly>
    <Editor.Properties />
    <Editor.Toolbar />
    <Editor.Canvas />
    <Editor.Preview />
</Editor.Root>

Selection (click, marquee), zoom, and pan all keep working - read-only is about mutation, not navigation. For a canvas-level override (read-only canvas inside an editable root), use Editor.StaticCanvas.

See also

  • Document model - the Document / Page / Section hierarchy in detail
  • History - undo/redo and the timeline
  • Unit system - why geometry is stored in points
  • Theming - the --editor-* CSS-variable surface
  • Keybindings - default shortcuts and how to override
  • Layers - z-index actions for overlapping sections