# labellife-design-tool

A fully featured, modular React design editor library for building label, print, and graphic design applications. Built on top of **Konva.js** for high-performance canvas rendering and **MobX-State-Tree** for reactive state management.

[![npm version](https://img.shields.io/npm/v/labellife-design-tool.svg)](https://www.npmjs.com/package/labellife-design-tool)
[![license](https://img.shields.io/npm/l/labellife-design-tool.svg)](LICENSE)

---

## Table of Contents

1. [Features](#features)
2. [Installation](#installation)
3. [Quick Start](#quick-start)
4. [Architecture Overview](#architecture-overview)
5. [Store API](#store-api)
6. [Element Types](#element-types)
7. [UI Components](#ui-components)
8. [Side Panels](#side-panels)
9. [Toolbar](#toolbar)
10. [Input Fields](#input-fields)
11. [Image Effects & Filters](#image-effects--filters)
12. [Image Masking](#image-masking)
13. [Export & Download](#export--download)
14. [Configuration](#configuration)
15. [Keyboard Shortcuts](#keyboard-shortcuts)
16. [JSON Schema](#json-schema)
17. [Dependencies](#dependencies)
18. [Roadmap](#roadmap)
19. [License](#license)

---

## Features

### Core Editor
- **Canvas Editor** — Zoom, pan, drag-and-drop, multi-select, and keyboard shortcuts
- **Rich Element Types** — Images, text, shapes (60+ presets), lines, SVGs, and groups
- **Context-Sensitive Toolbar** — Adapts controls to the selected element type
- **Multi-Page Support** — Add, remove, reorder, and navigate between pages
- **Undo/Redo** — Full history stack with snapshot-based state management
- **Responsive Layout** — Flexible container system that adapts to any viewport

### Design Capabilities
- **Image Editing** — Crop, flip, border, corner radius, masks (circle, triangle, star, diamond, hexagon, pentagon, heart, cloud, cross), and visual effects (blur, brightness, grayscale, sepia, warm, cold)
- **Text Editing** — Inline editing, 200+ Google Fonts with auto-loading, font weight/style, text decoration, alignment, letter spacing, line height, stroke, background highlight, and curved text
- **Shapes Library** — 60+ vector shapes including basic geometry, arrows, decorative shapes, organic blobs, and custom SVG paths
- **Lines** — Solid, dashed, dotted styles with configurable arrow heads
- **Shadow System** — Configurable blur, offset, color, and opacity on any element
- **Layer Management** — Reorder, lock/unlock, show/hide, delete per element
- **Grouping** — Group and ungroup elements with preserved relative positioning

### Template & Input System
- **Input Fields** — Define placeholder elements (Text, Date, Integer, Image) that prompt end-users for values when a template is loaded
- **Input Field Popup** — Built-in sequential dialog that collects user input for each field, with full style customization
- **Required Fields** — Mark input fields as required to prevent skipping
- **Custom Dialog Support** — Replace the default popup with your own component

### Import / Export
- **Export** — PNG, JPEG, PDF (multi-page), and JSON serialization
- **Import** — Load designs from JSON, import images via drag-and-drop or file picker

### Extensibility
- **Pluggable Panels** — Extend the side panel with custom sections
- **Internationalization** — Deep-merge translation system with dot-notation key resolution
- **Theming** — Light/dark theme support
- **Unit Conversion** — px, mm, cm, in, pt, pc with configurable DPI

---

## Installation

```bash
npm install labellife-design-tool
```

### Peer Dependencies

The following must be installed in your host application:

```bash
npm install react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styled
```

---

## Quick Start

```jsx
import React, { useMemo } from 'react';
import { createStore } from 'labellife-design-tool/model/store';
import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-tool';
import { SidePanel } from 'labellife-design-tool/side-panel';
import { Toolbar } from 'labellife-design-tool/toolbar/toolbar';
import { ZoomButtons } from 'labellife-design-tool/toolbar/zoom-buttons';
import { Workspace } from 'labellife-design-tool/canvas/workspace';

function App() {
  const store = useMemo(() => {
    const s = createStore();
    s.setSize(800, 600);
    s.addPage();
    return s;
  }, []);

  return (
    <DesignContainer style={{ width: '100vw', height: '100vh' }}>
      <SidePanelWrap>
        <SidePanel store={store} />
      </SidePanelWrap>
      <WorkspaceWrap>
        <Toolbar store={store} />
        <Workspace store={store} />
        <ZoomButtons store={store} />
      </WorkspaceWrap>
    </DesignContainer>
  );
}
```

---

## Architecture Overview

```
┌──────────────────────────────────────────────────────┐
│  DesignContainer                                     │
│  ┌────────────┬─────────────────────────────────────┐│
│  │  SidePanel │  WorkspaceWrap                      ││
│  │            │  ┌────────────────────────────────┐ ││
│  │  • Text    │  │  Toolbar (context-sensitive)    │ ││
│  │  • Shapes  │  ├────────────────────────────────┤ ││
│  │  • Upload  │  │  Workspace (Konva Stage)        │ ││
│  │  • Input   │  │                                │ ││
│  │  • Bg      │  │  Canvas: zoom, pan, select     │ ││
│  │  • Layers  │  │  Elements rendered here         │ ││
│  │            │  │  Input Fields Popup (optional)  │ ││
│  │            │  │                                │ ││
│  │            │  ├────────────────────────────────┤ ││
│  │            │  │  ZoomButtons                    │ ││
│  └────────────┴──┴────────────────────────────────┘ ││
└──────────────────────────────────────────────────────┘
```

**State management** uses MobX-State-Tree. The `store` is the single source of truth — all UI components observe it reactively.

---

## Store API

### Creating a Store

```js
import { createStore } from 'labellife-design-tool/model/store';

const store = createStore();
```

### Canvas Size

```js
store.setSize(800, 600);                  // Set canvas dimensions in pixels
store.setUnit({ unit: 'mm', dpi: 300 }); // Set working unit and DPI
```

### Page Management

```js
const page = store.addPage();             // Add a new page (returns the page)
store.deletePages([page.id]);             // Delete pages by ID
store.selectPage(page.id);                // Set active page
store.selectPages([id1, id2]);            // Select multiple pages
store.setPageZIndex(page.id, 0);          // Reorder pages

store.activePage;                         // Currently active page
store.pages;                              // All pages
```

### Element Management

```js
// Add elements to the active page
store.activePage.addElement({
  type: 'text',
  text: 'Hello World',
  x: 100, y: 100,
  fontSize: 32,
  fontFamily: 'Roboto',
});

store.activePage.addElement({
  type: 'image',
  src: 'https://example.com/photo.jpg',
  x: 50, y: 50,
  width: 400, height: 300,
});

// Selection
store.selectElements([elementId]);        // Select elements by ID
store.selectedElements;                   // Array of selected elements

// Deletion
store.deleteElements([elementId]);        // Delete elements by ID

// Grouping
store.groupElements([id1, id2]);          // Group elements together
store.ungroupElements([groupId]);         // Ungroup a group

// Find
store.getElementById(id);                // Find element by ID across all pages
store.find(callback);                    // Find first element matching callback
```

### Element Actions

Every element supports:

```js
element.set({ x: 200, opacity: 0.5 });   // Update properties
element.clone();                           // Duplicate the element
element.moveUp();                          // Move up in layer order
element.moveDown();                        // Move down in layer order
element.moveTop();                         // Move to front
element.moveBottom();                      // Move to back
element.setZIndex(3);                      // Set explicit z-index
element.toJSON();                          // Serialize to plain object
```

### History (Undo / Redo)

```js
store.history.undo();
store.history.redo();
store.history.canUndo;                    // Boolean
store.history.canRedo;                    // Boolean
store.history.clear();                    // Reset history
store.history.addUndoState();             // Snapshot current state
store.history.ignore(() => { ... });      // Batch changes without recording
```

### Serialization

```js
const json = store.toJSON();              // Export design as JSON object
store.loadJSON(json);                     // Load design from JSON
store.loadJSON(jsonString);               // Also accepts a JSON string
store.clear();                            // Reset store to empty state
store.validate(json);                     // Returns array of validation errors
```

> **Note:** `loadJSON` automatically loads referenced Google Fonts and triggers the [Input Fields popup](#input-fields) if the template contains input field elements.

### Events

```js
const dispose = store.on('change', (data) => {
  console.log('Store changed', data);
});
dispose(); // Remove listener
```

### Font Management

```js
store.loadFont('Open Sans');              // Load a Google Font by name
store.addFont({ fontFamily: 'Custom', url: '...' });
store.removeFont('Custom');
```

---

## Element Types

### Image

| Property | Type | Default | Description |
|---|---|---|---|
| `src` | string | `''` | Image URL |
| `cropX`, `cropY` | number | `0` | Crop origin (0–1 normalized) |
| `cropWidth`, `cropHeight` | number | `1` | Crop size (0–1 normalized) |
| `cornerRadius` | number | `0` | Rounded corners in pixels |
| `flipX`, `flipY` | boolean | `false` | Horizontal/vertical flip |
| `clipSrc` | string | `''` | Mask shape identifier |
| `borderColor` | string | `''` | Border color (hex) |
| `borderSize` | number | `0` | Border width in pixels |
| `keepRatio` | boolean | `true` | Lock aspect ratio on resize |

### Text

| Property | Type | Default | Description |
|---|---|---|---|
| `text` | string | `''` | Text content |
| `fontSize` | number | `20` | Font size in pixels |
| `fontFamily` | string | `'Roboto'` | Font family name |
| `fontWeight` | string/number | `'normal'` | Font weight (100–900 or keyword) |
| `fontStyle` | string | `'normal'` | `'normal'` or `'italic'` |
| `textDecoration` | string | `''` | `'underline'`, `'line-through'`, etc. |
| `fill` | string | `'#000000'` | Text color |
| `align` | string | `'center'` | `'left'`, `'center'`, `'right'` |
| `verticalAlign` | string | `'top'` | `'top'`, `'middle'`, `'bottom'` |
| `stroke` | string | `''` | Text stroke color |
| `strokeWidth` | number | `0` | Text stroke width |
| `lineHeight` | number | `1.2` | Line height multiplier |
| `letterSpacing` | number | `0` | Letter spacing in pixels |
| `backgroundEnabled` | boolean | `false` | Enable text background highlight |
| `backgroundColor` | string | `'#ffffff'` | Background color |
| `backgroundOpacity` | number | `1` | Background opacity (0–1) |
| `backgroundCornerRadius` | number | `0` | Background corner radius |
| `backgroundPadding` | number | `0` | Background padding |
| `curveEnabled` | boolean | `false` | Enable curved text |
| `curvePower` | number | `0` | Curve intensity |

### Figure (Shape)

| Property | Type | Default | Description |
|---|---|---|---|
| `subType` | string | `'rect'` | Shape type (see list below) |
| `fill` | string | `'#BFBFBF'` | Fill color |
| `stroke` | string | `''` | Stroke color |
| `strokeWidth` | number | `0` | Stroke width |
| `cornerRadius` | number | `0` | Corner radius (rect only) |
| `pathData` | string | `''` | Custom SVG path data |
| `dash` | number[] | `[]` | Dash pattern |

**Built-in shape subtypes:** `rect`, `circle`, `triangle`, `star`, `diamond`, `hexagon`, `polygon`, `path`

**60+ preset shapes** available in the Elements panel, including: arrows, speech bubble, cross, cloud, egg, heart, house, shield, lightning, flower, sun, hourglass, leaf, drop, wave, and many more.

### Line

| Property | Type | Default | Description |
|---|---|---|---|
| `color` | string | `'#000000'` | Line color |
| `dash` | number[] | `[]` | Dash pattern (e.g. `[10, 5]`) |
| `startHead` | string | `''` | Start head: `''`, `'arrow'`, `'circle'`, `'square'` |
| `endHead` | string | `''` | End head: `''`, `'arrow'`, `'circle'`, `'square'` |

### Common Properties (All Elements)

| Property | Type | Default | Description |
|---|---|---|---|
| `x`, `y` | number | `0` | Position |
| `width`, `height` | number | `100` | Dimensions |
| `rotation` | number | `0` | Rotation in degrees |
| `opacity` | number | `1` | Opacity (0–1) |
| `visible` | boolean | `true` | Visibility |
| `draggable` | boolean | `true` | Can be dragged |
| `resizable` | boolean | `true` | Can be resized |
| `contentEditable` | boolean | `true` | Content is editable |
| `selectable` | boolean | `true` | Can be selected |
| `removable` | boolean | `true` | Can be deleted |
| `name` | string | `''` | Display name (shown in layers) |
| `custom` | object | `{}` | Arbitrary custom data (preserved through serialization) |

### Shadow (All Elements)

| Property | Type | Default | Description |
|---|---|---|---|
| `shadowEnabled` | boolean | `false` | Enable drop shadow |
| `shadowBlur` | number | `5` | Shadow blur radius |
| `shadowOffsetX` | number | `0` | Horizontal shadow offset |
| `shadowOffsetY` | number | `0` | Vertical shadow offset |
| `shadowColor` | string | `'black'` | Shadow color |
| `shadowOpacity` | number | `1` | Shadow opacity |

---

## UI Components

### Layout

```jsx
import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-tool';
```

| Component | Description |
|---|---|
| `DesignContainer` | Root layout container. Accepts `style` prop for dimensions. |
| `SidePanelWrap` | Wrapper for the side panel area. |
| `WorkspaceWrap` | Flex container for toolbar, canvas, and zoom controls. |

### Workspace

```jsx
import { Workspace } from 'labellife-design-tool/canvas/workspace';

<Workspace store={store} />
```

| Prop | Type | Default | Description |
|---|---|---|---|
| `store` | Store | *required* | The MobX-State-Tree store instance |
| `showInputFieldsPopup` | boolean | `true` | Show the input fields popup when a template with input fields is loaded. Set to `false` to disable. |
| `onInputFieldsComplete` | function | `undefined` | Callback fired after the user has completed (submitted or skipped) all input fields. Only called when the popup is enabled. |
| `components` | object | `{}` | Override slots (e.g. `{ PageControls: MyComponent }`) |

The workspace provides:
- **Infinite canvas** with zoom and pan (mouse wheel + drag)
- **Click selection** and **multi-select** (Shift+click)
- **Transform handles** for resize and rotation
- **Auto-fit** to container on mount
- **Background rendering** per page
- **Input fields popup** (automatically shown when a loaded template contains input fields)

### Zoom Buttons

```jsx
import { ZoomButtons } from 'labellife-design-tool/toolbar/zoom-buttons';

<ZoomButtons store={store} />
```

Provides zoom in, zoom out, and fit-to-screen controls.

---

## Side Panels

### Default Configuration

```jsx
import { SidePanel } from 'labellife-design-tool/side-panel';

<SidePanel store={store} />
```

By default, the side panel includes: **Text**, **Elements**, **Upload**, **Background**, and **Layers**.

### Custom Section Order

```jsx
import {
  SidePanel,
  TextSection,
  ElementsSection,
  UploadSection,
  InputFieldsSection,
  BackgroundSection,
  LayersSection,
} from 'labellife-design-tool/side-panel';

<SidePanel
  store={store}
  sections={[
    TextSection,
    ElementsSection,
    UploadSection,
    InputFieldsSection,
    BackgroundSection,
    LayersSection,
  ]}
  defaultSection="text"
/>
```

### Built-in Sections

| Section | Tab Name | Description |
|---|---|---|
| `TextSection` | Text | Add heading, subheading, and body text presets |
| `ElementsSection` | Elements | 60+ shapes and 6 line styles |
| `UploadSection` | Upload | Drag-and-drop or file picker for images |
| `InputFieldsSection` | Input Fields | Add placeholder input elements (Text, Date, Integer, Image) for template-based workflows |
| `BackgroundSection` | Background | Color picker with 22 preset colors |
| `LayersSection` | Layers | Layer list with visibility, lock, reorder, and delete |

### Creating Custom Sections

```jsx
import { SectionTab } from 'labellife-design-tool/side-panel';

const MyCustomSection = {
  name: 'my-panel',
  Tab: (props) => (
    <SectionTab name="My Panel" {...props}>
      <MyIcon />
    </SectionTab>
  ),
  Panel: ({ store }) => (
    <div>
      <h3>My Custom Panel</h3>
      {/* Your custom panel content */}
    </div>
  ),
};

<SidePanel store={store} sections={[MyCustomSection, TextSection, LayersSection]} />
```

**Section API:**

| Property | Type | Description |
|---|---|---|
| `name` | string | Unique identifier |
| `Tab` | Component | Receives `{ onClick, active }` — renders the sidebar tab |
| `Panel` | Component | Receives `{ store }` — renders the panel content |
| `visibleInList` | boolean | Set to `false` to hide the tab (panel still accessible programmatically) |

---

## Toolbar

```jsx
import { Toolbar } from 'labellife-design-tool/toolbar/toolbar';

<Toolbar store={store} />
```

The toolbar is **context-sensitive** — it automatically shows relevant controls based on the selected element type:

| Element Type | Available Controls |
|---|---|
| **Image** | Flip (H/V), Effects, Fit to page, Apply/Remove mask, Crop |
| **Text** | Font family, Font size, Bold, Italic, Underline, Alignment, Fill color, Stroke |
| **Figure** | Fill color, Stroke color, Border width, Corner radius |
| **Line** | Line color |
| **All** | Opacity, Move up/down, Lock/Unlock, Duplicate, Delete |

The toolbar includes built-in **Import** and **Download** buttons by default.

### Customizing Toolbar Buttons

```jsx
<Toolbar
  store={store}
  components={{
    // Add custom action buttons
    ActionControls: ({ store }) => (
      <button onClick={() => store.saveAsImage()}>Export</button>
    ),
    // Replace or hide the Import button
    ImportButton: null,          // Set to null to hide
    // Replace or hide the Download button
    DownloadButton: MyDownload,  // Provide a custom component
  }}
/>
```

---

## Input Fields

Input fields allow template designers (admins) to define placeholder elements that prompt end-users for values when a template is loaded. This is useful for creating reusable templates where certain fields — such as a name, date, or quantity — need to be filled in by the user.

### Overview

The input fields system consists of three parts:

1. **Admin Panel** (`InputFieldsSection`) — A side panel where the admin adds input field elements to the template and configures their prompt text and whether they are required.
2. **Metadata** — Each input field element stores its configuration in the `custom` property, which is preserved through JSON serialization.
3. **User Popup** — When a template containing input fields is loaded via `store.loadJSON()`, a sequential dialog automatically prompts the user to fill in each field.

### Supported Input Types

| Type | Description | Input Control |
|---|---|---|
| `text` | Free-form text entry | Text input |
| `date` | Date value with configurable format | Date picker |
| `integer` | Whole number value | Number input (integers only) |
| `image` | Image upload placeholder | File picker with preview |

### Adding Input Fields (Admin Side)

Include `InputFieldsSection` in your side panel sections:

```jsx
import { InputFieldsSection } from 'labellife-design-tool/side-panel';

<SidePanel
  store={store}
  sections={[TextSection, ElementsSection, InputFieldsSection, LayersSection]}
/>
```

For each input type, the admin can configure:

- **Prompt Text** — The message displayed to the end-user (e.g. "Enter your name", "Select the event date").
- **Required** — Whether the field must be filled in. If required, the Skip button is hidden and the Confirm button is disabled until a value is entered.
- **Date Format** *(date type only)* — Choose from `MM-DD-YYYY`, `Month Name-DD-YYYY`, `Month Name-DD`, `YYYY`, or `MM-DD`.

### Input Field Metadata

Each input field element stores the following in its `custom` property:

```json
{
  "inputField": true,
  "inputType": "text",
  "placeholder": "Enter text",
  "promptText": "Please enter your name",
  "required": false
}
```

For date fields, an additional `dateFormat` property is included:

```json
{
  "inputField": true,
  "inputType": "date",
  "placeholder": "Select date",
  "promptText": "When is the event?",
  "required": true,
  "dateFormat": "MM-DD-YYYY"
}
```

### Controlling the Popup

The input fields popup is rendered inside the `<Workspace>` component and is **enabled by default**. When `store.loadJSON()` is called with a template that contains input field elements, the popup automatically appears.

```jsx
// Popup enabled (default) — dialog appears after loadJSON if input fields exist
<Workspace store={store} />

// Popup disabled — input fields retain their template placeholder values
<Workspace store={store} showInputFieldsPopup={false} />

// With completion callback
<Workspace
  store={store}
  onInputFieldsComplete={() => {
    console.log('All input fields have been filled!');
    // e.g. navigate, save, show a toast, etc.
  }}
/>
```

When `showInputFieldsPopup` is set to `false`, all input fields are treated as if they were skipped — their original template values are preserved regardless of whether they are marked as required.

### Customizing the Popup Appearance

Use `setInputFieldsConfig` to override the default dialog styles:

```js
import { setInputFieldsConfig } from 'labellife-design-tool';

setInputFieldsConfig({
  // Style overrides (applied via MUI sx prop)
  dialogStyle: { borderRadius: 16 },
  titleStyle: { color: '#1a1a1a', fontFamily: 'Georgia' },
  promptStyle: { fontSize: 15 },
  inputStyle: { borderRadius: 8 },
  submitButtonStyle: { backgroundColor: '#4caf50', '&:hover': { backgroundColor: '#388e3c' } },
  skipButtonStyle: { color: '#757575' },

  // Button text overrides
  submitButtonText: 'Apply',
  skipButtonText: 'Leave as is',
});
```

### Providing Custom Dialog Components

You can replace the built-in popup with your own component — either globally for all types, or per input type.

**Per-type custom dialogs** (override specific types only):

```js
import { setInputFieldsConfig } from 'labellife-design-tool';

setInputFieldsConfig({
  CustomTextDialog: MyTextPopup,       // Used for text fields
  CustomDateDialog: MyDatePopup,       // Used for date fields
  CustomIntegerDialog: MyNumberPopup,  // Used for integer fields
  CustomImageDialog: MyImagePopup,     // Used for image fields
});
```

**Global custom dialog** (catches any type without a per-type override):

```js
setInputFieldsConfig({
  CustomDialog: MyGenericPopup,        // Fallback for all types
});
```

**Mix and match** — per-type takes priority over global:

```js
setInputFieldsConfig({
  CustomImageDialog: MyImagePopup,     // Only image fields use this
  CustomDialog: MyGenericPopup,        // Text, date, and integer use this
});
```

**Resolution order:** `CustomTextDialog` / `CustomDateDialog` / `CustomIntegerDialog` / `CustomImageDialog` > `CustomDialog` > built-in dialog.

> **Note:** When `showInputFieldsPopup={false}` is set on `<Workspace>`, no popup is shown regardless of any custom dialog configuration.

**Props passed to every custom dialog component:**

| Prop | Type | Description |
|---|---|---|
| `field` | object | The current input field element (MST node with `id`, `custom`, etc.) |
| `fields` | array | All pending input field elements |
| `currentIndex` | number | Zero-based index of the current field |
| `totalCount` | number | Total number of fields to process |
| `onSubmit(elementId, value)` | function | Call to set the value and advance to the next field |
| `onSkip(elementId)` | function | Call to skip the field and advance |
| `onComplete()` | function | Call when all fields are processed |

**Example custom dialog:**

```jsx
const MyImagePopup = ({ field, currentIndex, totalCount, onSubmit, onSkip }) => {
  const promptText = field.custom?.promptText || 'Upload an image';

  const handleFile = (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (ev) => onSubmit(field.id, ev.target.result);
    reader.readAsDataURL(file);
  };

  return (
    <div className="my-dialog">
      <h3>{promptText}</h3>
      <p>Field {currentIndex + 1} of {totalCount}</p>
      <input type="file" accept="image/*" onChange={handleFile} />
      {!field.custom?.required && (
        <button onClick={() => onSkip(field.id)}>Skip</button>
      )}
    </div>
  );
};
```

### Accessing Input Fields Programmatically

```js
// Get all input field elements across all pages
const fields = store.inputFields;

// Get pending fields awaiting user input
const pending = store._pendingInputFields;

// Manually trigger the input fields flow (called automatically by loadJSON)
store.requestInputFields();

// Clear all pending fields
store.clearPendingInputFields();
```

---

## Image Effects & Filters

The effects panel is accessible via the **Effects** button in the image toolbar.

### Presets

| Preset | Description |
|---|---|
| Natural | No filters (reset all) |
| Grayscale | Desaturate to black & white |
| Sepia | Warm vintage tone |
| Warm | Boost red/green, reduce blue |
| Cold | Boost blue/green, reduce red |

### Adjustable Effects

| Effect | Property | Range | Description |
|---|---|---|---|
| Blur | `blurEnabled`, `blurRadius` | 0–100 | Gaussian blur |
| Brightness | `brightnessEnabled`, `brightness` | -1 to 1 | Lighten or darken |

### Border & Corner Radius

| Property | Description |
|---|---|
| `borderColor` | Border color (hex string) |
| `borderSize` | Border width in pixels (0–50) |
| `cornerRadius` | Rounded corners in pixels (0–200) |

### Shadow

Configurable shadow with blur, offset X/Y, and color — accessible from the effects panel.

---

## Image Masking

The mask panel is accessible via the **Apply Mask** button in the image toolbar. Masks clip the image to a predefined shape.

**Available mask shapes:** Circle, Triangle, Star, Diamond, Hexagon, Pentagon, Heart, Cloud, Cross

To remove a mask, click **Remove Mask** in the mask panel.

Programmatically:

```js
element.set({ clipSrc: 'circle' });  // Apply mask
element.set({ clipSrc: '' });        // Remove mask
```

---

## Export & Download

### Image Export

```js
const dataURL = await store.toDataURL({
  mimeType: 'image/png',   // 'image/png' or 'image/jpeg'
  quality: 1,               // 0–1 (JPEG quality)
  pixelRatio: 2,            // Resolution multiplier
});

const blob = await store.toBlob({ mimeType: 'image/png' });

await store.saveAsImage({ fileName: 'my-design.png' });
```

### PDF Export

```js
const pdfDataURL = await store.toPDFDataURL({ pixelRatio: 2, dpi: 300 });

await store.saveAsPDF({ fileName: 'my-design.pdf' });
```

### JSON Export

```js
const json = store.toJSON();
store.saveAsJSON({ fileName: 'my-design.json' });
store.loadJSON(json);
```

---

## Configuration

### Internationalization (i18n)

The library supports full internationalization via a nested translation system with dot-notation key resolution.

```js
import { setTranslations, t } from 'labellife-design-tool/config';

setTranslations({
  toolbar: {
    flip: 'Spiegelen',
    effects: 'Effecten',
    download: 'Downloaden',
    import: 'Importeren',
    undo: 'Ongedaan maken',
    redo: 'Opnieuw',
    // ... see full key reference below
  },
  sidePanel: {
    text: 'Tekst',
    elements: 'Elementen',
    upload: 'Uploaden',
    background: 'Achtergrond',
    layers: 'Lagen',
    inputFields: {
      tab: 'Invoervelden',
      title: 'Invoervelden',
      required: 'Verplicht',
      // ...
    },
  },
  inputFieldsDialog: {
    title: 'Invoer vereist',
    submit: 'Bevestigen',
    skip: 'Overslaan',
    stepIndicator: 'Veld {current} van {total}',
  },
});

// Translations are deep-merged — safe to call multiple times
setTranslations({ toolbar: { flip: 'Flip' } });
setTranslations({ toolbar: { effects: 'Effects' } }); // Does not overwrite toolbar.flip

// Use the translation function anywhere
t('toolbar.flip');                // Returns translated value
t('toolbar.flip', 'Flip');       // Returns default if key not found
```

<details>
<summary><strong>Full translation key reference</strong></summary>

```
toolbar.flip, toolbar.flipHorizontally, toolbar.flipVertically, toolbar.effects,
toolbar.fitToBackground, toolbar.clip, toolbar.removeMask, toolbar.crop, toolbar.fill,
toolbar.textStroke, toolbar.border, toolbar.cornerRadius, toolbar.color, toolbar.opacity,
toolbar.up, toolbar.down, toolbar.lockedDescription, toolbar.unlockedDescription,
toolbar.duplicateElements, toolbar.removeElements, toolbar.download, toolbar.import,
toolbar.fileType, toolbar.quality, toolbar.undo, toolbar.redo, toolbar.blur,
toolbar.brightness, toolbar.shadow, toolbar.offsetX, toolbar.offsetY,
toolbar.group, toolbar.ungroup

sidePanel.text, sidePanel.elements, sidePanel.shapes, sidePanel.lines,
sidePanel.upload, sidePanel.uploadTip, sidePanel.background, sidePanel.layers,
sidePanel.noLayers, sidePanel.headerText, sidePanel.subHeaderText, sidePanel.bodyText,
sidePanel.inputFields.tab, sidePanel.inputFields.title, sidePanel.inputFields.description,
sidePanel.inputFields.textInput, sidePanel.inputFields.dateInput,
sidePanel.inputFields.integerInput, sidePanel.inputFields.imageInput,
sidePanel.inputFields.required, sidePanel.inputFields.promptTextHint,
sidePanel.inputFields.dateFormat

inputFieldsDialog.title, inputFieldsDialog.submit, inputFieldsDialog.skip,
inputFieldsDialog.stepIndicator
```

</details>

### Theming

```js
import { setTheme, getTheme } from 'labellife-design-tool/config';

setTheme('light'); // or 'dark'
```

### Input Fields Dialog Configuration

See [Input Fields — Customizing the Popup Appearance](#customizing-the-popup-appearance) for full details.

```js
import { setInputFieldsConfig, getInputFieldsConfig } from 'labellife-design-tool';

setInputFieldsConfig({
  dialogStyle: {},          // MUI sx overrides for the Dialog paper
  titleStyle: {},           // MUI sx overrides for the Dialog title
  promptStyle: {},          // MUI sx overrides for the prompt text
  inputStyle: {},           // MUI sx overrides for the input field
  submitButtonStyle: {},    // MUI sx overrides for the Confirm button
  skipButtonStyle: {},      // MUI sx overrides for the Skip button
  submitButtonText: '',     // Override Confirm button label
  skipButtonText: '',       // Override Skip button label
  CustomDialog: null,       // Custom dialog for all types (global fallback)
  CustomTextDialog: null,   // Custom dialog for text fields only
  CustomDateDialog: null,   // Custom dialog for date fields only
  CustomIntegerDialog: null,// Custom dialog for integer fields only
  CustomImageDialog: null,  // Custom dialog for image fields only
});
```

---

## Keyboard Shortcuts

| Shortcut | Action |
|---|---|
| `Delete` / `Backspace` | Delete selected elements |
| `Ctrl+Z` | Undo |
| `Ctrl+Shift+Z` / `Ctrl+Y` | Redo |
| `Ctrl+D` | Duplicate selected elements |
| `Ctrl+A` | Select all elements on the page |
| `Escape` | Deselect all |
| `Mouse wheel` | Zoom in/out |

---

## JSON Schema

The design JSON follows this structure:

```json
{
  "width": 800,
  "height": 600,
  "unit": "px",
  "dpi": 72,
  "schemaVersion": 1,
  "fonts": [
    { "fontFamily": "Open Sans", "url": "...", "styles": [] }
  ],
  "pages": [
    {
      "id": "page-1",
      "background": "#ffffff",
      "children": [
        {
          "id": "el-1",
          "type": "text",
          "x": 100,
          "y": 100,
          "width": 300,
          "height": 60,
          "text": "Hello World",
          "fontSize": 32,
          "fontFamily": "Roboto",
          "fill": "#000000",
          "custom": {}
        },
        {
          "id": "el-2",
          "type": "text",
          "x": 200,
          "y": 200,
          "width": 250,
          "height": 40,
          "text": "Sample Text",
          "fontSize": 20,
          "custom": {
            "inputField": true,
            "inputType": "text",
            "placeholder": "Enter text",
            "promptText": "What is your name?",
            "required": true
          }
        }
      ]
    }
  ]
}
```

---

## Dependencies

### Peer Dependencies

These must be installed in your host application:

| Package | Version |
|---|---|
| `react` | ^17.0.0 \|\| ^18.0.0 \|\| ^19.0.0 |
| `react-dom` | ^17.0.0 \|\| ^18.0.0 \|\| ^19.0.0 |
| `@mui/material` | ^5.0.0 \|\| ^6.0.0 |
| `@mui/icons-material` | ^5.0.0 \|\| ^6.0.0 |
| `@emotion/react` | ^11.0.0 |
| `@emotion/styled` | ^11.0.0 |

### Bundled Dependencies

These are included in the package — no need to install separately:

| Package | Purpose |
|---|---|
| `konva` + `react-konva` | Canvas rendering |
| `mobx` + `mobx-state-tree` + `mobx-react-lite` | State management |
| `jspdf` | PDF generation |
| `uuid` | Unique ID generation |

---

## WordPress Integration

A ready-to-use WordPress plugin starter is included at [`examples/wordpress/`](examples/wordpress/). It provides:

- A **pre-configured Vite + React app** that imports the library with all peer dependencies
- A **WordPress plugin PHP file** with shortcode support (`[labellife_design_tool]`)
- **Predictable build output** (no hashed file names) for easy `wp_enqueue_script`
- A **step-by-step README** with setup, customization, and troubleshooting instructions

### Quick Setup

```bash
# 1. Copy examples/wordpress/ into your WP plugins directory
cp -r examples/wordpress/ /path/to/wp-content/plugins/labellife-design-tool/

# 2. Install dependencies and build
cd /path/to/wp-content/plugins/labellife-design-tool/
npm install
npm run build

# 3. Activate the plugin in WP Admin → Plugins
# 4. Add [labellife_design_tool] shortcode to any page
```

Customize the editor by editing `src/App.jsx` — same API as documented above. Rebuild with `npm run build` after changes.

See the full guide: [`examples/wordpress/README.md`](examples/wordpress/README.md)

---

## Roadmap

All four input field types (Text, Date, Integer, Image) are fully implemented. Future enhancements under consideration:

- **Drag-and-drop image upload** in the input fields popup dialog
- **URL-based image input** as an alternative to file upload

---

## License

MIT
