labellife-design-tool 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +412 -177
- package/dist/cjs/canvas/workspace.js +419 -2
- package/dist/cjs/canvas/workspace.js.map +1 -1
- package/dist/cjs/config.js +16 -0
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/index.js +927 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/model/store.js +35 -1
- package/dist/cjs/model/store.js.map +1 -1
- package/dist/cjs/side-panel/index.js +472 -0
- package/dist/cjs/side-panel/index.js.map +1 -1
- package/dist/cjs/side-panel/side-panel.js +6 -0
- package/dist/cjs/side-panel/side-panel.js.map +1 -1
- package/dist/cjs/toolbar/toolbar.js +6 -0
- package/dist/cjs/toolbar/toolbar.js.map +1 -1
- package/dist/esm/canvas/workspace.js +420 -3
- package/dist/esm/canvas/workspace.js.map +1 -1
- package/dist/esm/config.js +15 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/index.js +925 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model/store.js +35 -1
- package/dist/esm/model/store.js.map +1 -1
- package/dist/esm/side-panel/index.js +473 -2
- package/dist/esm/side-panel/index.js.map +1 -1
- package/dist/esm/side-panel/side-panel.js +6 -0
- package/dist/esm/side-panel/side-panel.js.map +1 -1
- package/dist/esm/toolbar/toolbar.js +6 -0
- package/dist/esm/toolbar/toolbar.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# labellife-design-tool
|
|
2
2
|
|
|
3
|
-
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.
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/labellife-design-tool)
|
|
6
6
|
[](LICENSE)
|
|
@@ -9,33 +9,39 @@ A fully featured, modular React design editor library for building label, print,
|
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
1. [Features](#features)
|
|
13
|
+
2. [Installation](#installation)
|
|
14
|
+
3. [Quick Start](#quick-start)
|
|
15
|
+
4. [Architecture Overview](#architecture-overview)
|
|
16
|
+
5. [Store API](#store-api)
|
|
17
|
+
6. [Element Types](#element-types)
|
|
18
|
+
7. [UI Components](#ui-components)
|
|
19
|
+
8. [Side Panels](#side-panels)
|
|
20
|
+
9. [Toolbar](#toolbar)
|
|
21
|
+
10. [Input Fields](#input-fields)
|
|
22
|
+
11. [Image Effects & Filters](#image-effects--filters)
|
|
23
|
+
12. [Image Masking](#image-masking)
|
|
24
|
+
13. [Export & Download](#export--download)
|
|
25
|
+
14. [Configuration](#configuration)
|
|
26
|
+
15. [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
27
|
+
16. [JSON Schema](#json-schema)
|
|
28
|
+
17. [Dependencies](#dependencies)
|
|
29
|
+
18. [Roadmap](#roadmap)
|
|
30
|
+
19. [License](#license)
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
34
|
## Features
|
|
35
35
|
|
|
36
|
+
### Core Editor
|
|
36
37
|
- **Canvas Editor** — Zoom, pan, drag-and-drop, multi-select, and keyboard shortcuts
|
|
37
38
|
- **Rich Element Types** — Images, text, shapes (60+ presets), lines, SVGs, and groups
|
|
38
39
|
- **Context-Sensitive Toolbar** — Adapts controls to the selected element type
|
|
40
|
+
- **Multi-Page Support** — Add, remove, reorder, and navigate between pages
|
|
41
|
+
- **Undo/Redo** — Full history stack with snapshot-based state management
|
|
42
|
+
- **Responsive Layout** — Flexible container system that adapts to any viewport
|
|
43
|
+
|
|
44
|
+
### Design Capabilities
|
|
39
45
|
- **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)
|
|
40
46
|
- **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
|
|
41
47
|
- **Shapes Library** — 60+ vector shapes including basic geometry, arrows, decorative shapes, organic blobs, and custom SVG paths
|
|
@@ -43,13 +49,21 @@ A fully featured, modular React design editor library for building label, print,
|
|
|
43
49
|
- **Shadow System** — Configurable blur, offset, color, and opacity on any element
|
|
44
50
|
- **Layer Management** — Reorder, lock/unlock, show/hide, delete per element
|
|
45
51
|
- **Grouping** — Group and ungroup elements with preserved relative positioning
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
|
|
53
|
+
### Template & Input System
|
|
54
|
+
- **Input Fields** — Define placeholder elements (Text, Date, Integer, Image) that prompt end-users for values when a template is loaded
|
|
55
|
+
- **Input Field Popup** — Built-in sequential dialog that collects user input for each field, with full style customization
|
|
56
|
+
- **Required Fields** — Mark input fields as required to prevent skipping
|
|
57
|
+
- **Custom Dialog Support** — Replace the default popup with your own component
|
|
58
|
+
|
|
59
|
+
### Import / Export
|
|
48
60
|
- **Export** — PNG, JPEG, PDF (multi-page), and JSON serialization
|
|
49
61
|
- **Import** — Load designs from JSON, import images via drag-and-drop or file picker
|
|
50
|
-
|
|
62
|
+
|
|
63
|
+
### Extensibility
|
|
51
64
|
- **Pluggable Panels** — Extend the side panel with custom sections
|
|
52
|
-
- **
|
|
65
|
+
- **Internationalization** — Deep-merge translation system with dot-notation key resolution
|
|
66
|
+
- **Theming** — Light/dark theme support
|
|
53
67
|
- **Unit Conversion** — px, mm, cm, in, pt, pc with configurable DPI
|
|
54
68
|
|
|
55
69
|
---
|
|
@@ -73,6 +87,7 @@ npm install react react-dom @mui/material @mui/icons-material @emotion/react @em
|
|
|
73
87
|
## Quick Start
|
|
74
88
|
|
|
75
89
|
```jsx
|
|
90
|
+
import React, { useMemo } from 'react';
|
|
76
91
|
import { createStore } from 'labellife-design-tool/model/store';
|
|
77
92
|
import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-tool';
|
|
78
93
|
import { SidePanel } from 'labellife-design-tool/side-panel';
|
|
@@ -80,20 +95,24 @@ import { Toolbar } from 'labellife-design-tool/toolbar/toolbar';
|
|
|
80
95
|
import { ZoomButtons } from 'labellife-design-tool/toolbar/zoom-buttons';
|
|
81
96
|
import { Workspace } from 'labellife-design-tool/canvas/workspace';
|
|
82
97
|
|
|
83
|
-
const store = createStore();
|
|
84
|
-
store.addPage();
|
|
85
|
-
|
|
86
98
|
function App() {
|
|
99
|
+
const store = useMemo(() => {
|
|
100
|
+
const s = createStore();
|
|
101
|
+
s.setSize(800, 600);
|
|
102
|
+
s.addPage();
|
|
103
|
+
return s;
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
87
106
|
return (
|
|
88
107
|
<DesignContainer style={{ width: '100vw', height: '100vh' }}>
|
|
89
|
-
<Toolbar store={store} />
|
|
90
108
|
<SidePanelWrap>
|
|
91
109
|
<SidePanel store={store} />
|
|
92
|
-
<WorkspaceWrap>
|
|
93
|
-
<Workspace store={store} />
|
|
94
|
-
<ZoomButtons store={store} />
|
|
95
|
-
</WorkspaceWrap>
|
|
96
110
|
</SidePanelWrap>
|
|
111
|
+
<WorkspaceWrap>
|
|
112
|
+
<Toolbar store={store} />
|
|
113
|
+
<Workspace store={store} />
|
|
114
|
+
<ZoomButtons store={store} />
|
|
115
|
+
</WorkspaceWrap>
|
|
97
116
|
</DesignContainer>
|
|
98
117
|
);
|
|
99
118
|
}
|
|
@@ -104,22 +123,23 @@ function App() {
|
|
|
104
123
|
## Architecture Overview
|
|
105
124
|
|
|
106
125
|
```
|
|
107
|
-
|
|
108
|
-
│ DesignContainer
|
|
109
|
-
│
|
|
110
|
-
│ │
|
|
111
|
-
│
|
|
112
|
-
│ │
|
|
113
|
-
│ │
|
|
114
|
-
│ │
|
|
115
|
-
│ │
|
|
116
|
-
│ │
|
|
117
|
-
│ │
|
|
118
|
-
│ │
|
|
119
|
-
│ │
|
|
120
|
-
│ │
|
|
121
|
-
│
|
|
122
|
-
|
|
126
|
+
┌──────────────────────────────────────────────────────┐
|
|
127
|
+
│ DesignContainer │
|
|
128
|
+
│ ┌────────────┬─────────────────────────────────────┐│
|
|
129
|
+
│ │ SidePanel │ WorkspaceWrap ││
|
|
130
|
+
│ │ │ ┌────────────────────────────────┐ ││
|
|
131
|
+
│ │ • Text │ │ Toolbar (context-sensitive) │ ││
|
|
132
|
+
│ │ • Shapes │ ├────────────────────────────────┤ ││
|
|
133
|
+
│ │ • Upload │ │ Workspace (Konva Stage) │ ││
|
|
134
|
+
│ │ • Input │ │ │ ││
|
|
135
|
+
│ │ • Bg │ │ Canvas: zoom, pan, select │ ││
|
|
136
|
+
│ │ • Layers │ │ Elements rendered here │ ││
|
|
137
|
+
│ │ │ │ Input Fields Popup (optional) │ ││
|
|
138
|
+
│ │ │ │ │ ││
|
|
139
|
+
│ │ │ ├────────────────────────────────┤ ││
|
|
140
|
+
│ │ │ │ ZoomButtons │ ││
|
|
141
|
+
│ └────────────┴──┴────────────────────────────────┘ ││
|
|
142
|
+
└──────────────────────────────────────────────────────┘
|
|
123
143
|
```
|
|
124
144
|
|
|
125
145
|
**State management** uses MobX-State-Tree. The `store` is the single source of truth — all UI components observe it reactively.
|
|
@@ -139,7 +159,7 @@ const store = createStore();
|
|
|
139
159
|
### Canvas Size
|
|
140
160
|
|
|
141
161
|
```js
|
|
142
|
-
store.setSize(800, 600);
|
|
162
|
+
store.setSize(800, 600); // Set canvas dimensions in pixels
|
|
143
163
|
store.setUnit({ unit: 'mm', dpi: 300 }); // Set working unit and DPI
|
|
144
164
|
```
|
|
145
165
|
|
|
@@ -188,7 +208,7 @@ store.ungroupElements([groupId]); // Ungroup a group
|
|
|
188
208
|
|
|
189
209
|
// Find
|
|
190
210
|
store.getElementById(id); // Find element by ID across all pages
|
|
191
|
-
store.find(callback);
|
|
211
|
+
store.find(callback); // Find first element matching callback
|
|
192
212
|
```
|
|
193
213
|
|
|
194
214
|
### Element Actions
|
|
@@ -228,6 +248,8 @@ store.clear(); // Reset store to empty state
|
|
|
228
248
|
store.validate(json); // Returns array of validation errors
|
|
229
249
|
```
|
|
230
250
|
|
|
251
|
+
> **Note:** `loadJSON` automatically loads referenced Google Fonts and triggers the [Input Fields popup](#input-fields) if the template contains input field elements.
|
|
252
|
+
|
|
231
253
|
### Events
|
|
232
254
|
|
|
233
255
|
```js
|
|
@@ -245,8 +267,6 @@ store.addFont({ fontFamily: 'Custom', url: '...' });
|
|
|
245
267
|
store.removeFont('Custom');
|
|
246
268
|
```
|
|
247
269
|
|
|
248
|
-
> When loading a design via `loadJSON`, all fonts referenced in text elements are **automatically loaded** from Google Fonts.
|
|
249
|
-
|
|
250
270
|
---
|
|
251
271
|
|
|
252
272
|
## Element Types
|
|
@@ -330,7 +350,7 @@ store.removeFont('Custom');
|
|
|
330
350
|
| `selectable` | boolean | `true` | Can be selected |
|
|
331
351
|
| `removable` | boolean | `true` | Can be deleted |
|
|
332
352
|
| `name` | string | `''` | Display name (shown in layers) |
|
|
333
|
-
| `custom` | object | `{}` | Arbitrary custom data |
|
|
353
|
+
| `custom` | object | `{}` | Arbitrary custom data (preserved through serialization) |
|
|
334
354
|
|
|
335
355
|
### Shadow (All Elements)
|
|
336
356
|
|
|
@@ -356,8 +376,8 @@ import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-
|
|
|
356
376
|
| Component | Description |
|
|
357
377
|
|---|---|
|
|
358
378
|
| `DesignContainer` | Root layout container. Accepts `style` prop for dimensions. |
|
|
359
|
-
| `SidePanelWrap` |
|
|
360
|
-
| `WorkspaceWrap` | Flex container for
|
|
379
|
+
| `SidePanelWrap` | Wrapper for the side panel area. |
|
|
380
|
+
| `WorkspaceWrap` | Flex container for toolbar, canvas, and zoom controls. |
|
|
361
381
|
|
|
362
382
|
### Workspace
|
|
363
383
|
|
|
@@ -367,12 +387,20 @@ import { Workspace } from 'labellife-design-tool/canvas/workspace';
|
|
|
367
387
|
<Workspace store={store} />
|
|
368
388
|
```
|
|
369
389
|
|
|
390
|
+
| Prop | Type | Default | Description |
|
|
391
|
+
|---|---|---|---|
|
|
392
|
+
| `store` | Store | *required* | The MobX-State-Tree store instance |
|
|
393
|
+
| `showInputFieldsPopup` | boolean | `true` | Show the input fields popup when a template with input fields is loaded. Set to `false` to disable. |
|
|
394
|
+
| `onInputFieldsComplete` | function | `undefined` | Callback fired after the user has completed (submitted or skipped) all input fields. Only called when the popup is enabled. |
|
|
395
|
+
| `components` | object | `{}` | Override slots (e.g. `{ PageControls: MyComponent }`) |
|
|
396
|
+
|
|
370
397
|
The workspace provides:
|
|
371
398
|
- **Infinite canvas** with zoom and pan (mouse wheel + drag)
|
|
372
399
|
- **Click selection** and **multi-select** (Shift+click)
|
|
373
400
|
- **Transform handles** for resize and rotation
|
|
374
401
|
- **Auto-fit** to container on mount
|
|
375
402
|
- **Background rendering** per page
|
|
403
|
+
- **Input fields popup** (automatically shown when a loaded template contains input fields)
|
|
376
404
|
|
|
377
405
|
### Zoom Buttons
|
|
378
406
|
|
|
@@ -406,13 +434,21 @@ import {
|
|
|
406
434
|
TextSection,
|
|
407
435
|
ElementsSection,
|
|
408
436
|
UploadSection,
|
|
437
|
+
InputFieldsSection,
|
|
409
438
|
BackgroundSection,
|
|
410
439
|
LayersSection,
|
|
411
440
|
} from 'labellife-design-tool/side-panel';
|
|
412
441
|
|
|
413
442
|
<SidePanel
|
|
414
443
|
store={store}
|
|
415
|
-
sections={[
|
|
444
|
+
sections={[
|
|
445
|
+
TextSection,
|
|
446
|
+
ElementsSection,
|
|
447
|
+
UploadSection,
|
|
448
|
+
InputFieldsSection,
|
|
449
|
+
BackgroundSection,
|
|
450
|
+
LayersSection,
|
|
451
|
+
]}
|
|
416
452
|
defaultSection="text"
|
|
417
453
|
/>
|
|
418
454
|
```
|
|
@@ -424,6 +460,7 @@ import {
|
|
|
424
460
|
| `TextSection` | Text | Add heading, subheading, and body text presets |
|
|
425
461
|
| `ElementsSection` | Elements | 60+ shapes and 6 line styles |
|
|
426
462
|
| `UploadSection` | Upload | Drag-and-drop or file picker for images |
|
|
463
|
+
| `InputFieldsSection` | Input Fields | Add placeholder input elements (Text, Date, Integer, Image) for template-based workflows |
|
|
427
464
|
| `BackgroundSection` | Background | Color picker with 22 preset colors |
|
|
428
465
|
| `LayersSection` | Layers | Layer list with visibility, lock, reorder, and delete |
|
|
429
466
|
|
|
@@ -447,7 +484,6 @@ const MyCustomSection = {
|
|
|
447
484
|
),
|
|
448
485
|
};
|
|
449
486
|
|
|
450
|
-
// Use it
|
|
451
487
|
<SidePanel store={store} sections={[MyCustomSection, TextSection, LayersSection]} />
|
|
452
488
|
```
|
|
453
489
|
|
|
@@ -480,23 +516,232 @@ The toolbar is **context-sensitive** — it automatically shows relevant control
|
|
|
480
516
|
| **Line** | Line color |
|
|
481
517
|
| **All** | Opacity, Move up/down, Lock/Unlock, Duplicate, Delete |
|
|
482
518
|
|
|
483
|
-
|
|
519
|
+
The toolbar includes built-in **Import** and **Download** buttons by default.
|
|
520
|
+
|
|
521
|
+
### Customizing Toolbar Buttons
|
|
484
522
|
|
|
485
523
|
```jsx
|
|
486
524
|
<Toolbar
|
|
487
525
|
store={store}
|
|
488
526
|
components={{
|
|
527
|
+
// Add custom action buttons
|
|
489
528
|
ActionControls: ({ store }) => (
|
|
490
|
-
|
|
491
|
-
{/* Custom Download button */}
|
|
492
|
-
<button onClick={() => store.saveAsImage()}>Export</button>
|
|
493
|
-
</>
|
|
529
|
+
<button onClick={() => store.saveAsImage()}>Export</button>
|
|
494
530
|
),
|
|
531
|
+
// Replace or hide the Import button
|
|
532
|
+
ImportButton: null, // Set to null to hide
|
|
533
|
+
// Replace or hide the Download button
|
|
534
|
+
DownloadButton: MyDownload, // Provide a custom component
|
|
535
|
+
}}
|
|
536
|
+
/>
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Input Fields
|
|
542
|
+
|
|
543
|
+
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.
|
|
544
|
+
|
|
545
|
+
### Overview
|
|
546
|
+
|
|
547
|
+
The input fields system consists of three parts:
|
|
548
|
+
|
|
549
|
+
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.
|
|
550
|
+
2. **Metadata** — Each input field element stores its configuration in the `custom` property, which is preserved through JSON serialization.
|
|
551
|
+
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.
|
|
552
|
+
|
|
553
|
+
### Supported Input Types
|
|
554
|
+
|
|
555
|
+
| Type | Description | Input Control |
|
|
556
|
+
|---|---|---|
|
|
557
|
+
| `text` | Free-form text entry | Text input |
|
|
558
|
+
| `date` | Date value with configurable format | Date picker |
|
|
559
|
+
| `integer` | Whole number value | Number input (integers only) |
|
|
560
|
+
| `image` | Image upload placeholder | File picker with preview |
|
|
561
|
+
|
|
562
|
+
### Adding Input Fields (Admin Side)
|
|
563
|
+
|
|
564
|
+
Include `InputFieldsSection` in your side panel sections:
|
|
565
|
+
|
|
566
|
+
```jsx
|
|
567
|
+
import { InputFieldsSection } from 'labellife-design-tool/side-panel';
|
|
568
|
+
|
|
569
|
+
<SidePanel
|
|
570
|
+
store={store}
|
|
571
|
+
sections={[TextSection, ElementsSection, InputFieldsSection, LayersSection]}
|
|
572
|
+
/>
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
For each input type, the admin can configure:
|
|
576
|
+
|
|
577
|
+
- **Prompt Text** — The message displayed to the end-user (e.g. "Enter your name", "Select the event date").
|
|
578
|
+
- **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.
|
|
579
|
+
- **Date Format** *(date type only)* — Choose from `MM-DD-YYYY`, `Month Name-DD-YYYY`, `Month Name-DD`, `YYYY`, or `MM-DD`.
|
|
580
|
+
|
|
581
|
+
### Input Field Metadata
|
|
582
|
+
|
|
583
|
+
Each input field element stores the following in its `custom` property:
|
|
584
|
+
|
|
585
|
+
```json
|
|
586
|
+
{
|
|
587
|
+
"inputField": true,
|
|
588
|
+
"inputType": "text",
|
|
589
|
+
"placeholder": "Enter text",
|
|
590
|
+
"promptText": "Please enter your name",
|
|
591
|
+
"required": false
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
For date fields, an additional `dateFormat` property is included:
|
|
596
|
+
|
|
597
|
+
```json
|
|
598
|
+
{
|
|
599
|
+
"inputField": true,
|
|
600
|
+
"inputType": "date",
|
|
601
|
+
"placeholder": "Select date",
|
|
602
|
+
"promptText": "When is the event?",
|
|
603
|
+
"required": true,
|
|
604
|
+
"dateFormat": "MM-DD-YYYY"
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Controlling the Popup
|
|
609
|
+
|
|
610
|
+
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.
|
|
611
|
+
|
|
612
|
+
```jsx
|
|
613
|
+
// Popup enabled (default) — dialog appears after loadJSON if input fields exist
|
|
614
|
+
<Workspace store={store} />
|
|
615
|
+
|
|
616
|
+
// Popup disabled — input fields retain their template placeholder values
|
|
617
|
+
<Workspace store={store} showInputFieldsPopup={false} />
|
|
618
|
+
|
|
619
|
+
// With completion callback
|
|
620
|
+
<Workspace
|
|
621
|
+
store={store}
|
|
622
|
+
onInputFieldsComplete={() => {
|
|
623
|
+
console.log('All input fields have been filled!');
|
|
624
|
+
// e.g. navigate, save, show a toast, etc.
|
|
495
625
|
}}
|
|
496
626
|
/>
|
|
497
627
|
```
|
|
498
628
|
|
|
499
|
-
|
|
629
|
+
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.
|
|
630
|
+
|
|
631
|
+
### Customizing the Popup Appearance
|
|
632
|
+
|
|
633
|
+
Use `setInputFieldsConfig` to override the default dialog styles:
|
|
634
|
+
|
|
635
|
+
```js
|
|
636
|
+
import { setInputFieldsConfig } from 'labellife-design-tool';
|
|
637
|
+
|
|
638
|
+
setInputFieldsConfig({
|
|
639
|
+
// Style overrides (applied via MUI sx prop)
|
|
640
|
+
dialogStyle: { borderRadius: 16 },
|
|
641
|
+
titleStyle: { color: '#1a1a1a', fontFamily: 'Georgia' },
|
|
642
|
+
promptStyle: { fontSize: 15 },
|
|
643
|
+
inputStyle: { borderRadius: 8 },
|
|
644
|
+
submitButtonStyle: { backgroundColor: '#4caf50', '&:hover': { backgroundColor: '#388e3c' } },
|
|
645
|
+
skipButtonStyle: { color: '#757575' },
|
|
646
|
+
|
|
647
|
+
// Button text overrides
|
|
648
|
+
submitButtonText: 'Apply',
|
|
649
|
+
skipButtonText: 'Leave as is',
|
|
650
|
+
});
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Providing Custom Dialog Components
|
|
654
|
+
|
|
655
|
+
You can replace the built-in popup with your own component — either globally for all types, or per input type.
|
|
656
|
+
|
|
657
|
+
**Per-type custom dialogs** (override specific types only):
|
|
658
|
+
|
|
659
|
+
```js
|
|
660
|
+
import { setInputFieldsConfig } from 'labellife-design-tool';
|
|
661
|
+
|
|
662
|
+
setInputFieldsConfig({
|
|
663
|
+
CustomTextDialog: MyTextPopup, // Used for text fields
|
|
664
|
+
CustomDateDialog: MyDatePopup, // Used for date fields
|
|
665
|
+
CustomIntegerDialog: MyNumberPopup, // Used for integer fields
|
|
666
|
+
CustomImageDialog: MyImagePopup, // Used for image fields
|
|
667
|
+
});
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**Global custom dialog** (catches any type without a per-type override):
|
|
671
|
+
|
|
672
|
+
```js
|
|
673
|
+
setInputFieldsConfig({
|
|
674
|
+
CustomDialog: MyGenericPopup, // Fallback for all types
|
|
675
|
+
});
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Mix and match** — per-type takes priority over global:
|
|
679
|
+
|
|
680
|
+
```js
|
|
681
|
+
setInputFieldsConfig({
|
|
682
|
+
CustomImageDialog: MyImagePopup, // Only image fields use this
|
|
683
|
+
CustomDialog: MyGenericPopup, // Text, date, and integer use this
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
**Resolution order:** `CustomTextDialog` / `CustomDateDialog` / `CustomIntegerDialog` / `CustomImageDialog` > `CustomDialog` > built-in dialog.
|
|
688
|
+
|
|
689
|
+
> **Note:** When `showInputFieldsPopup={false}` is set on `<Workspace>`, no popup is shown regardless of any custom dialog configuration.
|
|
690
|
+
|
|
691
|
+
**Props passed to every custom dialog component:**
|
|
692
|
+
|
|
693
|
+
| Prop | Type | Description |
|
|
694
|
+
|---|---|---|
|
|
695
|
+
| `field` | object | The current input field element (MST node with `id`, `custom`, etc.) |
|
|
696
|
+
| `fields` | array | All pending input field elements |
|
|
697
|
+
| `currentIndex` | number | Zero-based index of the current field |
|
|
698
|
+
| `totalCount` | number | Total number of fields to process |
|
|
699
|
+
| `onSubmit(elementId, value)` | function | Call to set the value and advance to the next field |
|
|
700
|
+
| `onSkip(elementId)` | function | Call to skip the field and advance |
|
|
701
|
+
| `onComplete()` | function | Call when all fields are processed |
|
|
702
|
+
|
|
703
|
+
**Example custom dialog:**
|
|
704
|
+
|
|
705
|
+
```jsx
|
|
706
|
+
const MyImagePopup = ({ field, currentIndex, totalCount, onSubmit, onSkip }) => {
|
|
707
|
+
const promptText = field.custom?.promptText || 'Upload an image';
|
|
708
|
+
|
|
709
|
+
const handleFile = (e) => {
|
|
710
|
+
const file = e.target.files?.[0];
|
|
711
|
+
if (!file) return;
|
|
712
|
+
const reader = new FileReader();
|
|
713
|
+
reader.onload = (ev) => onSubmit(field.id, ev.target.result);
|
|
714
|
+
reader.readAsDataURL(file);
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
return (
|
|
718
|
+
<div className="my-dialog">
|
|
719
|
+
<h3>{promptText}</h3>
|
|
720
|
+
<p>Field {currentIndex + 1} of {totalCount}</p>
|
|
721
|
+
<input type="file" accept="image/*" onChange={handleFile} />
|
|
722
|
+
{!field.custom?.required && (
|
|
723
|
+
<button onClick={() => onSkip(field.id)}>Skip</button>
|
|
724
|
+
)}
|
|
725
|
+
</div>
|
|
726
|
+
);
|
|
727
|
+
};
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Accessing Input Fields Programmatically
|
|
731
|
+
|
|
732
|
+
```js
|
|
733
|
+
// Get all input field elements across all pages
|
|
734
|
+
const fields = store.inputFields;
|
|
735
|
+
|
|
736
|
+
// Get pending fields awaiting user input
|
|
737
|
+
const pending = store._pendingInputFields;
|
|
738
|
+
|
|
739
|
+
// Manually trigger the input fields flow (called automatically by loadJSON)
|
|
740
|
+
store.requestInputFields();
|
|
741
|
+
|
|
742
|
+
// Clear all pending fields
|
|
743
|
+
store.clearPendingInputFields();
|
|
744
|
+
```
|
|
500
745
|
|
|
501
746
|
---
|
|
502
747
|
|
|
@@ -546,11 +791,8 @@ To remove a mask, click **Remove Mask** in the mask panel.
|
|
|
546
791
|
Programmatically:
|
|
547
792
|
|
|
548
793
|
```js
|
|
549
|
-
// Apply mask
|
|
550
|
-
element.set({ clipSrc: '
|
|
551
|
-
|
|
552
|
-
// Remove mask
|
|
553
|
-
element.set({ clipSrc: '' });
|
|
794
|
+
element.set({ clipSrc: 'circle' }); // Apply mask
|
|
795
|
+
element.set({ clipSrc: '' }); // Remove mask
|
|
554
796
|
```
|
|
555
797
|
|
|
556
798
|
---
|
|
@@ -560,130 +802,113 @@ element.set({ clipSrc: '' });
|
|
|
560
802
|
### Image Export
|
|
561
803
|
|
|
562
804
|
```js
|
|
563
|
-
// Export as data URL
|
|
564
805
|
const dataURL = await store.toDataURL({
|
|
565
806
|
mimeType: 'image/png', // 'image/png' or 'image/jpeg'
|
|
566
807
|
quality: 1, // 0–1 (JPEG quality)
|
|
567
808
|
pixelRatio: 2, // Resolution multiplier
|
|
568
809
|
});
|
|
569
810
|
|
|
570
|
-
// Export as Blob
|
|
571
811
|
const blob = await store.toBlob({ mimeType: 'image/png' });
|
|
572
812
|
|
|
573
|
-
// Direct download
|
|
574
813
|
await store.saveAsImage({ fileName: 'my-design.png' });
|
|
575
814
|
```
|
|
576
815
|
|
|
577
816
|
### PDF Export
|
|
578
817
|
|
|
579
818
|
```js
|
|
580
|
-
|
|
581
|
-
const pdfDataURL = await store.toPDFDataURL({
|
|
582
|
-
pixelRatio: 2,
|
|
583
|
-
dpi: 300,
|
|
584
|
-
});
|
|
819
|
+
const pdfDataURL = await store.toPDFDataURL({ pixelRatio: 2, dpi: 300 });
|
|
585
820
|
|
|
586
|
-
// Direct download
|
|
587
821
|
await store.saveAsPDF({ fileName: 'my-design.pdf' });
|
|
588
822
|
```
|
|
589
823
|
|
|
590
824
|
### JSON Export
|
|
591
825
|
|
|
592
826
|
```js
|
|
593
|
-
// Serialize
|
|
594
827
|
const json = store.toJSON();
|
|
595
|
-
|
|
596
|
-
// Download as file
|
|
597
828
|
store.saveAsJSON({ fileName: 'my-design.json' });
|
|
598
|
-
|
|
599
|
-
// Load back
|
|
600
829
|
store.loadJSON(json);
|
|
601
830
|
```
|
|
602
831
|
|
|
603
832
|
---
|
|
604
833
|
|
|
605
|
-
##
|
|
834
|
+
## Configuration
|
|
606
835
|
|
|
607
|
-
|
|
836
|
+
### Internationalization (i18n)
|
|
608
837
|
|
|
609
|
-
|
|
838
|
+
The library supports full internationalization via a nested translation system with dot-notation key resolution.
|
|
610
839
|
|
|
611
840
|
```js
|
|
612
|
-
import { setTranslations } from 'labellife-design-tool/config';
|
|
841
|
+
import { setTranslations, t } from 'labellife-design-tool/config';
|
|
613
842
|
|
|
614
843
|
setTranslations({
|
|
615
844
|
toolbar: {
|
|
616
845
|
flip: 'Spiegelen',
|
|
617
|
-
flipHorizontally: 'Horizontaal spiegelen',
|
|
618
|
-
flipVertically: 'Verticaal spiegelen',
|
|
619
846
|
effects: 'Effecten',
|
|
620
|
-
fitToBackground: 'Passend maken',
|
|
621
|
-
clip: 'Masker toepassen',
|
|
622
|
-
removeMask: 'Masker verwijderen',
|
|
623
|
-
crop: 'Bijsnijden',
|
|
624
|
-
fill: 'Vulkleur',
|
|
625
|
-
textStroke: 'Lijnkleur',
|
|
626
|
-
border: 'Rand',
|
|
627
|
-
cornerRadius: 'Hoekstraal',
|
|
628
|
-
color: 'Kleur',
|
|
629
|
-
opacity: 'Dekking',
|
|
630
|
-
up: 'Omhoog',
|
|
631
|
-
down: 'Omlaag',
|
|
632
|
-
lockedDescription: 'Vergrendelen',
|
|
633
|
-
unlockedDescription: 'Ontgrendelen',
|
|
634
|
-
duplicateElements: 'Dupliceren',
|
|
635
|
-
removeElements: 'Verwijderen',
|
|
636
847
|
download: 'Downloaden',
|
|
637
848
|
import: 'Importeren',
|
|
638
|
-
fileType: 'Bestandstype',
|
|
639
|
-
quality: 'Kwaliteit',
|
|
640
849
|
undo: 'Ongedaan maken',
|
|
641
850
|
redo: 'Opnieuw',
|
|
642
|
-
|
|
643
|
-
brightness: 'Helderheid',
|
|
644
|
-
shadow: 'Schaduw',
|
|
645
|
-
offsetX: 'Verschuiving X',
|
|
646
|
-
offsetY: 'Verschuiving Y',
|
|
851
|
+
// ... see full key reference below
|
|
647
852
|
},
|
|
648
853
|
sidePanel: {
|
|
649
854
|
text: 'Tekst',
|
|
650
855
|
elements: 'Elementen',
|
|
651
|
-
shapes: 'Vormen',
|
|
652
|
-
lines: 'Lijnen',
|
|
653
856
|
upload: 'Uploaden',
|
|
654
|
-
uploadTip: 'Sleep afbeeldingen hierheen of klik om te uploaden',
|
|
655
857
|
background: 'Achtergrond',
|
|
656
858
|
layers: 'Lagen',
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
859
|
+
inputFields: {
|
|
860
|
+
tab: 'Invoervelden',
|
|
861
|
+
title: 'Invoervelden',
|
|
862
|
+
required: 'Verplicht',
|
|
863
|
+
// ...
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
inputFieldsDialog: {
|
|
867
|
+
title: 'Invoer vereist',
|
|
868
|
+
submit: 'Bevestigen',
|
|
869
|
+
skip: 'Overslaan',
|
|
870
|
+
stepIndicator: 'Veld {current} van {total}',
|
|
661
871
|
},
|
|
662
872
|
});
|
|
663
|
-
```
|
|
664
873
|
|
|
665
|
-
Translations are
|
|
666
|
-
|
|
667
|
-
```js
|
|
668
|
-
// First call
|
|
874
|
+
// Translations are deep-merged — safe to call multiple times
|
|
669
875
|
setTranslations({ toolbar: { flip: 'Flip' } });
|
|
876
|
+
setTranslations({ toolbar: { effects: 'Effects' } }); // Does not overwrite toolbar.flip
|
|
670
877
|
|
|
671
|
-
//
|
|
672
|
-
|
|
878
|
+
// Use the translation function anywhere
|
|
879
|
+
t('toolbar.flip'); // Returns translated value
|
|
880
|
+
t('toolbar.flip', 'Flip'); // Returns default if key not found
|
|
673
881
|
```
|
|
674
882
|
|
|
675
|
-
|
|
883
|
+
<details>
|
|
884
|
+
<summary><strong>Full translation key reference</strong></summary>
|
|
676
885
|
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
886
|
+
```
|
|
887
|
+
toolbar.flip, toolbar.flipHorizontally, toolbar.flipVertically, toolbar.effects,
|
|
888
|
+
toolbar.fitToBackground, toolbar.clip, toolbar.removeMask, toolbar.crop, toolbar.fill,
|
|
889
|
+
toolbar.textStroke, toolbar.border, toolbar.cornerRadius, toolbar.color, toolbar.opacity,
|
|
890
|
+
toolbar.up, toolbar.down, toolbar.lockedDescription, toolbar.unlockedDescription,
|
|
891
|
+
toolbar.duplicateElements, toolbar.removeElements, toolbar.download, toolbar.import,
|
|
892
|
+
toolbar.fileType, toolbar.quality, toolbar.undo, toolbar.redo, toolbar.blur,
|
|
893
|
+
toolbar.brightness, toolbar.shadow, toolbar.offsetX, toolbar.offsetY,
|
|
894
|
+
toolbar.group, toolbar.ungroup
|
|
895
|
+
|
|
896
|
+
sidePanel.text, sidePanel.elements, sidePanel.shapes, sidePanel.lines,
|
|
897
|
+
sidePanel.upload, sidePanel.uploadTip, sidePanel.background, sidePanel.layers,
|
|
898
|
+
sidePanel.noLayers, sidePanel.headerText, sidePanel.subHeaderText, sidePanel.bodyText,
|
|
899
|
+
sidePanel.inputFields.tab, sidePanel.inputFields.title, sidePanel.inputFields.description,
|
|
900
|
+
sidePanel.inputFields.textInput, sidePanel.inputFields.dateInput,
|
|
901
|
+
sidePanel.inputFields.integerInput, sidePanel.inputFields.imageInput,
|
|
902
|
+
sidePanel.inputFields.required, sidePanel.inputFields.promptTextHint,
|
|
903
|
+
sidePanel.inputFields.dateFormat
|
|
904
|
+
|
|
905
|
+
inputFieldsDialog.title, inputFieldsDialog.submit, inputFieldsDialog.skip,
|
|
906
|
+
inputFieldsDialog.stepIndicator
|
|
682
907
|
```
|
|
683
908
|
|
|
684
|
-
|
|
909
|
+
</details>
|
|
685
910
|
|
|
686
|
-
|
|
911
|
+
### Theming
|
|
687
912
|
|
|
688
913
|
```js
|
|
689
914
|
import { setTheme, getTheme } from 'labellife-design-tool/config';
|
|
@@ -691,41 +916,28 @@ import { setTheme, getTheme } from 'labellife-design-tool/config';
|
|
|
691
916
|
setTheme('light'); // or 'dark'
|
|
692
917
|
```
|
|
693
918
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
## Utility Functions
|
|
697
|
-
|
|
698
|
-
### Unit Conversion
|
|
699
|
-
|
|
700
|
-
```js
|
|
701
|
-
import { unitToPx, pxToUnit } from 'labellife-design-tool/utils/unit';
|
|
702
|
-
|
|
703
|
-
unitToPx({ unit: 'mm', dpi: 300, unitVal: 50 }); // → pixels
|
|
704
|
-
pxToUnit({ unit: 'mm', dpi: 300, pxVal: 590.55 }); // → millimeters
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
**Supported units:** `px`, `mm`, `cm`, `in`, `pt`, `pc`
|
|
919
|
+
### Input Fields Dialog Configuration
|
|
708
920
|
|
|
709
|
-
|
|
921
|
+
See [Input Fields — Customizing the Popup Appearance](#customizing-the-popup-appearance) for full details.
|
|
710
922
|
|
|
711
923
|
```js
|
|
712
|
-
import {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
924
|
+
import { setInputFieldsConfig, getInputFieldsConfig } from 'labellife-design-tool';
|
|
925
|
+
|
|
926
|
+
setInputFieldsConfig({
|
|
927
|
+
dialogStyle: {}, // MUI sx overrides for the Dialog paper
|
|
928
|
+
titleStyle: {}, // MUI sx overrides for the Dialog title
|
|
929
|
+
promptStyle: {}, // MUI sx overrides for the prompt text
|
|
930
|
+
inputStyle: {}, // MUI sx overrides for the input field
|
|
931
|
+
submitButtonStyle: {}, // MUI sx overrides for the Confirm button
|
|
932
|
+
skipButtonStyle: {}, // MUI sx overrides for the Skip button
|
|
933
|
+
submitButtonText: '', // Override Confirm button label
|
|
934
|
+
skipButtonText: '', // Override Skip button label
|
|
935
|
+
CustomDialog: null, // Custom dialog for all types (global fallback)
|
|
936
|
+
CustomTextDialog: null, // Custom dialog for text fields only
|
|
937
|
+
CustomDateDialog: null, // Custom dialog for date fields only
|
|
938
|
+
CustomIntegerDialog: null,// Custom dialog for integer fields only
|
|
939
|
+
CustomImageDialog: null, // Custom dialog for image fields only
|
|
940
|
+
});
|
|
729
941
|
```
|
|
730
942
|
|
|
731
943
|
---
|
|
@@ -762,7 +974,6 @@ The design JSON follows this structure:
|
|
|
762
974
|
{
|
|
763
975
|
"id": "page-1",
|
|
764
976
|
"background": "#ffffff",
|
|
765
|
-
"duration": 5000,
|
|
766
977
|
"children": [
|
|
767
978
|
{
|
|
768
979
|
"id": "el-1",
|
|
@@ -774,16 +985,25 @@ The design JSON follows this structure:
|
|
|
774
985
|
"text": "Hello World",
|
|
775
986
|
"fontSize": 32,
|
|
776
987
|
"fontFamily": "Roboto",
|
|
777
|
-
"fill": "#000000"
|
|
988
|
+
"fill": "#000000",
|
|
989
|
+
"custom": {}
|
|
778
990
|
},
|
|
779
991
|
{
|
|
780
992
|
"id": "el-2",
|
|
781
|
-
"type": "
|
|
782
|
-
"x":
|
|
993
|
+
"type": "text",
|
|
994
|
+
"x": 200,
|
|
783
995
|
"y": 200,
|
|
784
|
-
"width":
|
|
785
|
-
"height":
|
|
786
|
-
"
|
|
996
|
+
"width": 250,
|
|
997
|
+
"height": 40,
|
|
998
|
+
"text": "Sample Text",
|
|
999
|
+
"fontSize": 20,
|
|
1000
|
+
"custom": {
|
|
1001
|
+
"inputField": true,
|
|
1002
|
+
"inputType": "text",
|
|
1003
|
+
"placeholder": "Enter text",
|
|
1004
|
+
"promptText": "What is your name?",
|
|
1005
|
+
"required": true
|
|
1006
|
+
}
|
|
787
1007
|
}
|
|
788
1008
|
]
|
|
789
1009
|
}
|
|
@@ -793,7 +1013,11 @@ The design JSON follows this structure:
|
|
|
793
1013
|
|
|
794
1014
|
---
|
|
795
1015
|
|
|
796
|
-
##
|
|
1016
|
+
## Dependencies
|
|
1017
|
+
|
|
1018
|
+
### Peer Dependencies
|
|
1019
|
+
|
|
1020
|
+
These must be installed in your host application:
|
|
797
1021
|
|
|
798
1022
|
| Package | Version |
|
|
799
1023
|
|---|---|
|
|
@@ -808,10 +1032,21 @@ The design JSON follows this structure:
|
|
|
808
1032
|
|
|
809
1033
|
These are included in the package — no need to install separately:
|
|
810
1034
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
- `
|
|
1035
|
+
| Package | Purpose |
|
|
1036
|
+
|---|---|
|
|
1037
|
+
| `konva` + `react-konva` | Canvas rendering |
|
|
1038
|
+
| `mobx` + `mobx-state-tree` + `mobx-react-lite` | State management |
|
|
1039
|
+
| `jspdf` | PDF generation |
|
|
1040
|
+
| `uuid` | Unique ID generation |
|
|
1041
|
+
|
|
1042
|
+
---
|
|
1043
|
+
|
|
1044
|
+
## Roadmap
|
|
1045
|
+
|
|
1046
|
+
All four input field types (Text, Date, Integer, Image) are fully implemented. Future enhancements under consideration:
|
|
1047
|
+
|
|
1048
|
+
- **Drag-and-drop image upload** in the input fields popup dialog
|
|
1049
|
+
- **URL-based image input** as an alternative to file upload
|
|
815
1050
|
|
|
816
1051
|
---
|
|
817
1052
|
|