@enerjisaformlibrary/formbuilder-react 1.0.8 → 1.0.22
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 +486 -27
- package/index.cjs +469 -240
- package/index.cjs.map +1 -1
- package/index.js +469 -240
- package/index.js.map +1 -1
- package/package.json +14 -19
- package/styles.css +1 -1
- package/types/client/src/components/form-builder/CanvasField.d.ts.map +1 -1
- package/types/client/src/components/form-builder/PropertiesPanel.d.ts.map +1 -1
- package/types/client/src/components/form-builder/Toolbar.d.ts.map +1 -1
- package/types/client/src/store/formStore.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# @enerjisaformlibrary/formbuilder-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Professional drag-and-drop form builder React component with 20+ field types, multi-step wizard support, conditional logic, undo/redo history, and JSON export/import.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **20+ Field Types**: Text, textarea, number, email, phone, URL, password, date, time, date range, dropdown, checkbox, radio, toggle, multi-select, file upload, signature pad, star rating, slider, color picker, rich text editor, autocomplete, QR code, pattern format, and more
|
|
8
|
+
- **Drag & Drop**: Intuitive drag-and-drop interface for building forms
|
|
9
|
+
- **Multi-Step Wizard**: Create multi-step forms with progress indicator
|
|
10
|
+
- **Conditional Logic**: Show/hide/enable/disable fields based on conditions
|
|
11
|
+
- **Undo/Redo**: Full undo/redo support with 50-state history
|
|
12
|
+
- **Grid Layout**: 12-column responsive grid system
|
|
13
|
+
- **Containers**: Nested row/column layouts within containers
|
|
14
|
+
- **JSON Export/Import**: Export forms as JSON and import them back
|
|
15
|
+
- **Custom Styling**: Per-field CSS class customization
|
|
16
|
+
- **Form Versioning**: Save and restore form versions
|
|
17
|
+
- **Tooltips**: Help text support for fields
|
|
18
|
+
- **Actions System**: onClick, onChange, onFocus, onBlur event handlers
|
|
19
|
+
- **Tab Index**: Keyboard navigation order for form fields
|
|
4
20
|
|
|
5
21
|
## Installation
|
|
6
22
|
|
|
@@ -8,47 +24,490 @@ Standalone drag-and-drop form builder React component with CSS included. No addi
|
|
|
8
24
|
npm install @enerjisaformlibrary/formbuilder-react
|
|
9
25
|
```
|
|
10
26
|
|
|
11
|
-
##
|
|
27
|
+
## Quick Start
|
|
12
28
|
|
|
13
29
|
```tsx
|
|
14
|
-
import { FormBuilder
|
|
30
|
+
import { FormBuilder } from '@enerjisaformlibrary/formbuilder-react';
|
|
15
31
|
import '@enerjisaformlibrary/formbuilder-react/styles.css';
|
|
16
32
|
|
|
17
33
|
function App() {
|
|
18
|
-
const
|
|
19
|
-
console.log('Form
|
|
34
|
+
const handleSave = (formSchema) => {
|
|
35
|
+
console.log('Form saved:', formSchema);
|
|
36
|
+
// Save to your database
|
|
20
37
|
};
|
|
21
38
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
console.log('Save form:', JSON.stringify(form));
|
|
39
|
+
const handleChange = (formSchema) => {
|
|
40
|
+
console.log('Form changed:', formSchema);
|
|
25
41
|
};
|
|
26
42
|
|
|
27
43
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
theme="light"
|
|
33
|
-
/>
|
|
34
|
-
</div>
|
|
44
|
+
<FormBuilder
|
|
45
|
+
onSave={handleSave}
|
|
46
|
+
onChange={handleChange}
|
|
47
|
+
/>
|
|
35
48
|
);
|
|
36
49
|
}
|
|
37
50
|
```
|
|
38
51
|
|
|
39
|
-
##
|
|
52
|
+
## Props
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Required | Description |
|
|
55
|
+
|------|------|----------|-------------|
|
|
56
|
+
| `initialSchema` | `FormSchema` | No | Initial form schema to load |
|
|
57
|
+
| `onSave` | `(schema: FormSchema) => void` | No | Called when user clicks Save button |
|
|
58
|
+
| `onChange` | `(schema: FormSchema) => void` | No | Called on every form change |
|
|
59
|
+
|
|
60
|
+
## Form Schema Structure
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface FormSchema {
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
isMultiStep: boolean;
|
|
68
|
+
currentVersion: number;
|
|
69
|
+
rows: FormRow[]; // For single-page forms
|
|
70
|
+
steps?: FormStep[]; // For multi-step forms
|
|
71
|
+
settings?: FormSettings;
|
|
72
|
+
submissionConfig?: SubmissionConfig;
|
|
73
|
+
versions?: FormVersion[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface FormRow {
|
|
77
|
+
id: string;
|
|
78
|
+
columns: FormColumn[];
|
|
79
|
+
conditionalLogic?: ConditionalLogic;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface FormColumn {
|
|
83
|
+
id: string;
|
|
84
|
+
width: number; // 1-12 (grid columns)
|
|
85
|
+
fields: FormField[];
|
|
86
|
+
responsiveWidth?: ResponsiveWidth;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface FormField {
|
|
90
|
+
id: string;
|
|
91
|
+
type: FieldType;
|
|
92
|
+
props: FieldProps;
|
|
93
|
+
validation?: FieldValidation;
|
|
94
|
+
conditionalLogic?: ConditionalLogic;
|
|
95
|
+
customStyle?: CustomStyle;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Field Types
|
|
100
|
+
|
|
101
|
+
### Basic Fields
|
|
102
|
+
- `input` - Single line text input
|
|
103
|
+
- `textarea` - Multi-line text area
|
|
104
|
+
- `number` - Numeric input
|
|
105
|
+
- `email` - Email input with validation
|
|
106
|
+
- `password` - Password input
|
|
107
|
+
- `phone` - Phone number input
|
|
108
|
+
- `url` - URL input
|
|
109
|
+
|
|
110
|
+
### Date & Time
|
|
111
|
+
- `date` - Date picker
|
|
112
|
+
- `time` - Time picker
|
|
113
|
+
- `daterange` - Date range picker
|
|
114
|
+
|
|
115
|
+
### Selection Fields
|
|
116
|
+
- `dropdown` - Single select dropdown
|
|
117
|
+
- `checkbox` - Checkbox field
|
|
118
|
+
- `radio` - Radio button group
|
|
119
|
+
- `toggle` - Toggle switch
|
|
120
|
+
- `multiselect` - Multi-select with tags
|
|
121
|
+
|
|
122
|
+
### Advanced Fields
|
|
123
|
+
- `file` - File upload with drag-drop
|
|
124
|
+
- `signature` - Signature pad with canvas drawing
|
|
125
|
+
- `rating` - Star rating (configurable max stars)
|
|
126
|
+
- `slider` - Range slider with min/max
|
|
127
|
+
- `color` - Color picker with hex input
|
|
128
|
+
- `richtext` - Rich text editor
|
|
129
|
+
- `autocomplete` - Autocomplete input
|
|
130
|
+
- `pattern` - Pattern format input (phone, credit card, etc.)
|
|
131
|
+
- `qrcode` - Static QR code display
|
|
132
|
+
|
|
133
|
+
### Static Elements
|
|
134
|
+
- `header` - Section header
|
|
135
|
+
- `label` - Static label text
|
|
136
|
+
- `paragraph` - Paragraph text
|
|
137
|
+
- `divider` - Horizontal divider
|
|
138
|
+
- `spacer` - Vertical spacer
|
|
139
|
+
- `alert` - Alert/notification box
|
|
140
|
+
- `image` - Image placeholder
|
|
141
|
+
- `button` - Submit/reset/custom action buttons
|
|
142
|
+
|
|
143
|
+
### Structure
|
|
144
|
+
- `container` - Grouping element with nested rows/columns
|
|
145
|
+
|
|
146
|
+
## Field Props
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
interface FieldProps {
|
|
150
|
+
key: string; // Unique field key for form data
|
|
151
|
+
label?: string; // Field label
|
|
152
|
+
placeholder?: string; // Placeholder text
|
|
153
|
+
tooltip?: string; // Help tooltip text
|
|
154
|
+
optionsString?: string; // Options for dropdown/radio/checkbox (one per line)
|
|
155
|
+
size?: 'small' | 'medium' | 'large';
|
|
156
|
+
autoFocus?: boolean;
|
|
157
|
+
tabIndex?: number; // Keyboard navigation order (positive = order, -1 = skip, 0 = natural order)
|
|
158
|
+
htmlAttributes?: Record<string, string>;
|
|
159
|
+
|
|
160
|
+
// Button specific
|
|
161
|
+
buttonConfig?: {
|
|
162
|
+
buttonType: 'submit' | 'reset' | 'button';
|
|
163
|
+
variant: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
|
|
164
|
+
actionType?: 'submit' | 'reset' | 'navigate' | 'custom';
|
|
165
|
+
navigateUrl?: string;
|
|
166
|
+
customAction?: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Pattern format specific
|
|
170
|
+
patternConfig?: {
|
|
171
|
+
format: 'phone' | 'creditCard' | 'date' | 'ssn' | 'custom';
|
|
172
|
+
mask?: string;
|
|
173
|
+
customPattern?: string;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// QR Code specific
|
|
177
|
+
qrCodeConfig?: {
|
|
178
|
+
value: string;
|
|
179
|
+
size: number;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Container specific
|
|
183
|
+
containerConfig?: {
|
|
184
|
+
rows: ContainerRow[];
|
|
185
|
+
gap?: number;
|
|
186
|
+
padding?: number;
|
|
187
|
+
borderStyle?: 'none' | 'solid' | 'dashed';
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Rating specific
|
|
191
|
+
maxRating?: number;
|
|
192
|
+
|
|
193
|
+
// Slider specific
|
|
194
|
+
min?: number;
|
|
195
|
+
max?: number;
|
|
196
|
+
step?: number;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Tab Index Usage
|
|
201
|
+
|
|
202
|
+
The `tabIndex` property controls keyboard navigation order when users press Tab to move between form fields:
|
|
203
|
+
|
|
204
|
+
| Value | Behavior |
|
|
205
|
+
|-------|----------|
|
|
206
|
+
| Positive (1, 2, 3...) | Field is focused in order from lowest to highest tabIndex |
|
|
207
|
+
| 0 | Field follows natural DOM order |
|
|
208
|
+
| -1 | Field is skipped during Tab navigation |
|
|
209
|
+
|
|
210
|
+
**Example:** To create a custom tab order:
|
|
211
|
+
- First Name: tabIndex = 1
|
|
212
|
+
- Email: tabIndex = 2
|
|
213
|
+
- Last Name: tabIndex = 3
|
|
214
|
+
- Phone: tabIndex = 4
|
|
215
|
+
|
|
216
|
+
When user presses Tab, they'll move: First Name → Email → Last Name → Phone
|
|
217
|
+
|
|
218
|
+
**Important:** tabIndex only works in **Preview Mode**. In editor mode, fields don't receive focus in the same way.
|
|
219
|
+
|
|
220
|
+
## Validation
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
interface FieldValidation {
|
|
224
|
+
required?: boolean;
|
|
225
|
+
minLength?: number;
|
|
226
|
+
maxLength?: number;
|
|
227
|
+
min?: number;
|
|
228
|
+
max?: number;
|
|
229
|
+
pattern?: string;
|
|
230
|
+
customMessage?: string;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Conditional Logic
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
interface ConditionalLogic {
|
|
238
|
+
enabled: boolean;
|
|
239
|
+
action: 'show' | 'hide' | 'enable' | 'disable' | 'require';
|
|
240
|
+
conditions: Condition[];
|
|
241
|
+
logicOperator: 'and' | 'or';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
interface Condition {
|
|
245
|
+
fieldKey: string;
|
|
246
|
+
operator: 'equals' | 'notEquals' | 'contains' | 'notContains' |
|
|
247
|
+
'greaterThan' | 'lessThan' | 'isEmpty' | 'isNotEmpty';
|
|
248
|
+
value: string;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Custom Styling
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
interface CustomStyle {
|
|
256
|
+
containerClassName?: string; // CSS class for field container
|
|
257
|
+
labelClassName?: string; // CSS class for label
|
|
258
|
+
inputClassName?: string; // CSS class for input element
|
|
259
|
+
css?: string; // Custom CSS (applied inline)
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Actions System
|
|
264
|
+
|
|
265
|
+
Each field can have event handlers:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
interface FieldActions {
|
|
269
|
+
onClick?: FieldAction;
|
|
270
|
+
onChange?: FieldAction;
|
|
271
|
+
onFocus?: FieldAction;
|
|
272
|
+
onBlur?: FieldAction;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
interface FieldAction {
|
|
276
|
+
type: 'showMessage' | 'hideField' | 'showField' | 'clearField' |
|
|
277
|
+
'setFieldValue' | 'focusField' | 'submitForm' | 'custom' | 'code';
|
|
278
|
+
args?: {
|
|
279
|
+
message?: string;
|
|
280
|
+
targetFieldKey?: string;
|
|
281
|
+
value?: string;
|
|
282
|
+
code?: string;
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Multi-Step Forms
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
interface FormStep {
|
|
291
|
+
id: string;
|
|
292
|
+
title: string;
|
|
293
|
+
description?: string;
|
|
294
|
+
order: number;
|
|
295
|
+
rows: FormRow[];
|
|
296
|
+
validation?: {
|
|
297
|
+
validateOnNext?: boolean;
|
|
298
|
+
allowSkip?: boolean;
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Example: Complete Form Schema
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"id": "contact-form",
|
|
308
|
+
"name": "Contact Form",
|
|
309
|
+
"isMultiStep": false,
|
|
310
|
+
"currentVersion": 1,
|
|
311
|
+
"rows": [
|
|
312
|
+
{
|
|
313
|
+
"id": "row-1",
|
|
314
|
+
"columns": [
|
|
315
|
+
{
|
|
316
|
+
"id": "col-1",
|
|
317
|
+
"width": 6,
|
|
318
|
+
"fields": [
|
|
319
|
+
{
|
|
320
|
+
"id": "field-1",
|
|
321
|
+
"type": "input",
|
|
322
|
+
"props": {
|
|
323
|
+
"key": "firstName",
|
|
324
|
+
"label": "First Name",
|
|
325
|
+
"placeholder": "Enter your first name",
|
|
326
|
+
"tabIndex": 1
|
|
327
|
+
},
|
|
328
|
+
"validation": {
|
|
329
|
+
"required": true,
|
|
330
|
+
"minLength": 2
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"id": "col-2",
|
|
337
|
+
"width": 6,
|
|
338
|
+
"fields": [
|
|
339
|
+
{
|
|
340
|
+
"id": "field-2",
|
|
341
|
+
"type": "input",
|
|
342
|
+
"props": {
|
|
343
|
+
"key": "lastName",
|
|
344
|
+
"label": "Last Name",
|
|
345
|
+
"placeholder": "Enter your last name",
|
|
346
|
+
"tabIndex": 2
|
|
347
|
+
},
|
|
348
|
+
"validation": {
|
|
349
|
+
"required": true
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"id": "row-2",
|
|
358
|
+
"columns": [
|
|
359
|
+
{
|
|
360
|
+
"id": "col-3",
|
|
361
|
+
"width": 12,
|
|
362
|
+
"fields": [
|
|
363
|
+
{
|
|
364
|
+
"id": "field-3",
|
|
365
|
+
"type": "email",
|
|
366
|
+
"props": {
|
|
367
|
+
"key": "email",
|
|
368
|
+
"label": "Email Address",
|
|
369
|
+
"placeholder": "you@example.com",
|
|
370
|
+
"tooltip": "We'll never share your email",
|
|
371
|
+
"tabIndex": 3
|
|
372
|
+
},
|
|
373
|
+
"validation": {
|
|
374
|
+
"required": true
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
]
|
|
378
|
+
}
|
|
379
|
+
]
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"id": "row-3",
|
|
383
|
+
"columns": [
|
|
384
|
+
{
|
|
385
|
+
"id": "col-4",
|
|
386
|
+
"width": 12,
|
|
387
|
+
"fields": [
|
|
388
|
+
{
|
|
389
|
+
"id": "field-4",
|
|
390
|
+
"type": "dropdown",
|
|
391
|
+
"props": {
|
|
392
|
+
"key": "country",
|
|
393
|
+
"label": "Country",
|
|
394
|
+
"optionsString": "USA\nCanada\nUK\nGermany\nFrance\nOther",
|
|
395
|
+
"tabIndex": 4
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"id": "row-4",
|
|
404
|
+
"columns": [
|
|
405
|
+
{
|
|
406
|
+
"id": "col-5",
|
|
407
|
+
"width": 12,
|
|
408
|
+
"fields": [
|
|
409
|
+
{
|
|
410
|
+
"id": "field-5",
|
|
411
|
+
"type": "textarea",
|
|
412
|
+
"props": {
|
|
413
|
+
"key": "message",
|
|
414
|
+
"label": "Message",
|
|
415
|
+
"placeholder": "How can we help you?",
|
|
416
|
+
"tabIndex": 5
|
|
417
|
+
},
|
|
418
|
+
"validation": {
|
|
419
|
+
"required": true,
|
|
420
|
+
"minLength": 10,
|
|
421
|
+
"maxLength": 500
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
]
|
|
425
|
+
}
|
|
426
|
+
]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"id": "row-5",
|
|
430
|
+
"columns": [
|
|
431
|
+
{
|
|
432
|
+
"id": "col-6",
|
|
433
|
+
"width": 12,
|
|
434
|
+
"fields": [
|
|
435
|
+
{
|
|
436
|
+
"id": "field-6",
|
|
437
|
+
"type": "button",
|
|
438
|
+
"props": {
|
|
439
|
+
"key": "submit",
|
|
440
|
+
"label": "Submit",
|
|
441
|
+
"buttonConfig": {
|
|
442
|
+
"buttonType": "submit",
|
|
443
|
+
"variant": "primary",
|
|
444
|
+
"actionType": "submit"
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Integration with JetDesk
|
|
457
|
+
|
|
458
|
+
```tsx
|
|
459
|
+
import { FormBuilder } from '@enerjisaformlibrary/formbuilder-react';
|
|
460
|
+
import '@enerjisaformlibrary/formbuilder-react/styles.css';
|
|
461
|
+
|
|
462
|
+
function FormEditor({ formId }) {
|
|
463
|
+
const [initialSchema, setInitialSchema] = useState(null);
|
|
464
|
+
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
// Load existing form from database
|
|
467
|
+
fetch(`/api/forms/${formId}`)
|
|
468
|
+
.then(res => res.json())
|
|
469
|
+
.then(data => setInitialSchema(data.schema));
|
|
470
|
+
}, [formId]);
|
|
471
|
+
|
|
472
|
+
const handleSave = async (schema) => {
|
|
473
|
+
await fetch(`/api/forms/${formId}`, {
|
|
474
|
+
method: 'PUT',
|
|
475
|
+
headers: { 'Content-Type': 'application/json' },
|
|
476
|
+
body: JSON.stringify({ schema })
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
if (!initialSchema) return <div>Loading...</div>;
|
|
481
|
+
|
|
482
|
+
return (
|
|
483
|
+
<FormBuilder
|
|
484
|
+
initialSchema={initialSchema}
|
|
485
|
+
onSave={handleSave}
|
|
486
|
+
/>
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Keyboard Shortcuts
|
|
492
|
+
|
|
493
|
+
- **Ctrl/Cmd + Z**: Undo
|
|
494
|
+
- **Ctrl/Cmd + Shift + Z**: Redo
|
|
495
|
+
|
|
496
|
+
## Browser Support
|
|
497
|
+
|
|
498
|
+
- Chrome (latest)
|
|
499
|
+
- Firefox (latest)
|
|
500
|
+
- Safari (latest)
|
|
501
|
+
- Edge (latest)
|
|
502
|
+
|
|
503
|
+
## Changelog
|
|
504
|
+
|
|
505
|
+
### 1.0.22
|
|
506
|
+
- Fixed: tabIndex now properly applied to all interactive field types (dropdown, checkbox, radio, toggle, slider, autocomplete, daterange)
|
|
40
507
|
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
- Conditional logic (show/hide/enable/disable fields)
|
|
45
|
-
- Custom styling per field
|
|
46
|
-
- Form versioning
|
|
47
|
-
- JSON export/import
|
|
48
|
-
- Responsive grid system (12-column)
|
|
49
|
-
- Dark/Light theme support
|
|
50
|
-
- **Container support**: Drop fields into containers with multiple columns
|
|
51
|
-
- **Column support**: Drop fields into existing columns
|
|
508
|
+
### 1.0.21
|
|
509
|
+
- Added: Comprehensive undo/redo functionality with 50-state history
|
|
510
|
+
- Each form modification creates a separate history entry
|
|
52
511
|
|
|
53
512
|
## License
|
|
54
513
|
|