@dmitryvim/form-builder 0.1.29 → 0.1.31
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 +30 -7
- package/dist/demo.js +59 -1
- package/dist/form-builder.js +45 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,10 +39,11 @@ npm install @dmitryvim/form-builder
|
|
|
39
39
|
|
|
40
40
|
- **🎯 Schema-driven forms**: JSON Schema v0.3 → Interactive forms with live preview
|
|
41
41
|
- **📁 Advanced file handling**: Images, videos, documents with drag-and-drop and grid preview
|
|
42
|
-
- **✅ Real-time validation**: Client-side validation with visual feedback and error display
|
|
42
|
+
- **✅ Real-time validation**: Client-side validation with visual feedback and error display
|
|
43
43
|
- **🌍 Internationalization**: Built-in English/Russian support with extensible translation system
|
|
44
44
|
- **🎨 Rich field types**: Text, textarea, number, select, file, files, and nested groups
|
|
45
45
|
- **👁️ Read-only mode**: Display form data without editing capabilities
|
|
46
|
+
- **🔘 Action buttons**: Configurable buttons in readonly mode for custom interactions
|
|
46
47
|
- **💾 Draft saving**: Save incomplete forms without validation
|
|
47
48
|
- **🔧 Framework agnostic**: Works with any web stack (React, Vue, Angular, vanilla JS)
|
|
48
49
|
- **📦 Zero dependencies**: Self-contained HTML/CSS/JavaScript
|
|
@@ -51,6 +52,7 @@ npm install @dmitryvim/form-builder
|
|
|
51
52
|
## Quick Examples
|
|
52
53
|
|
|
53
54
|
### Simple Contact Form
|
|
55
|
+
|
|
54
56
|
```json
|
|
55
57
|
{
|
|
56
58
|
"version": "0.3",
|
|
@@ -87,7 +89,8 @@ npm install @dmitryvim/form-builder
|
|
|
87
89
|
}
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
### Advanced Product Form with
|
|
92
|
+
### Advanced Product Form with Actions
|
|
93
|
+
|
|
91
94
|
```json
|
|
92
95
|
{
|
|
93
96
|
"version": "0.3",
|
|
@@ -100,7 +103,12 @@ npm install @dmitryvim/form-builder
|
|
|
100
103
|
"required": true,
|
|
101
104
|
"accept": {
|
|
102
105
|
"extensions": ["jpg", "png", "webp"]
|
|
103
|
-
}
|
|
106
|
+
},
|
|
107
|
+
"actions": [
|
|
108
|
+
{ "value": "enhance", "label": "Enhance Quality" },
|
|
109
|
+
{ "value": "crop", "label": "Auto Crop" },
|
|
110
|
+
{ "value": "retry", "label": "Try Again" }
|
|
111
|
+
]
|
|
104
112
|
},
|
|
105
113
|
{
|
|
106
114
|
"type": "group",
|
|
@@ -129,24 +137,30 @@ npm install @dmitryvim/form-builder
|
|
|
129
137
|
## Integration Methods
|
|
130
138
|
|
|
131
139
|
### 1. NPM Package (Recommended)
|
|
140
|
+
|
|
132
141
|
```bash
|
|
133
142
|
npm install @dmitryvim/form-builder
|
|
134
143
|
```
|
|
135
144
|
|
|
136
145
|
```javascript
|
|
137
146
|
// ES6 imports
|
|
138
|
-
import FormBuilder from
|
|
147
|
+
import FormBuilder from "@dmitryvim/form-builder";
|
|
139
148
|
|
|
140
149
|
// Configure and use
|
|
141
|
-
FormBuilder.setFormRoot(document.getElementById(
|
|
150
|
+
FormBuilder.setFormRoot(document.getElementById("form-container"));
|
|
142
151
|
FormBuilder.setUploadHandler(async (file) => {
|
|
143
152
|
// Your upload logic - return resource ID
|
|
144
|
-
return
|
|
153
|
+
return "resource-123";
|
|
154
|
+
});
|
|
155
|
+
FormBuilder.setActionHandler((value) => {
|
|
156
|
+
// Handle action button clicks
|
|
157
|
+
console.log("Action clicked:", value);
|
|
145
158
|
});
|
|
146
159
|
FormBuilder.renderForm(schema, prefillData);
|
|
147
160
|
```
|
|
148
161
|
|
|
149
162
|
### 2. CDN Integration
|
|
163
|
+
|
|
150
164
|
```html
|
|
151
165
|
<!-- Direct script include (npm CDN) -->
|
|
152
166
|
<script src="https://cdn.jsdelivr.net/npm/@dmitryvim/form-builder@latest/dist/form-builder.js"></script>
|
|
@@ -160,11 +174,15 @@ FormBuilder.renderForm(schema, prefillData);
|
|
|
160
174
|
<!-- Embed complete demo -->
|
|
161
175
|
<iframe
|
|
162
176
|
src="https://picaz-form-builder.website.yandexcloud.net/form-builder/latest/index.html"
|
|
163
|
-
width="100%"
|
|
177
|
+
width="100%"
|
|
178
|
+
height="600px"
|
|
179
|
+
frameborder="0"
|
|
180
|
+
>
|
|
164
181
|
</iframe>
|
|
165
182
|
```
|
|
166
183
|
|
|
167
184
|
### 3. Self-Hosted Deployment
|
|
185
|
+
|
|
168
186
|
Download and serve the `dist/` folder contents.
|
|
169
187
|
|
|
170
188
|
See [Integration Guide](docs/integration.md) for detailed setup instructions.
|
|
@@ -172,6 +190,7 @@ See [Integration Guide](docs/integration.md) for detailed setup instructions.
|
|
|
172
190
|
## Complete Feature Set
|
|
173
191
|
|
|
174
192
|
### Field Types
|
|
193
|
+
|
|
175
194
|
- **Text**: Single-line with pattern validation, length limits
|
|
176
195
|
- **Textarea**: Multi-line with configurable rows
|
|
177
196
|
- **Number**: Numeric input with min/max/step/decimals
|
|
@@ -181,6 +200,7 @@ See [Integration Guide](docs/integration.md) for detailed setup instructions.
|
|
|
181
200
|
- **Group**: Nested objects with repeatable array support
|
|
182
201
|
|
|
183
202
|
### File Handling
|
|
203
|
+
|
|
184
204
|
- **Supported formats**: Images (jpg, png, gif, webp), Videos (mp4, webm, mov), Documents (pdf, docx, etc.)
|
|
185
205
|
- **Preview system**: Thumbnails for images, video players, document icons
|
|
186
206
|
- **Drag-and-drop**: Visual feedback and multi-file support
|
|
@@ -188,12 +208,14 @@ See [Integration Guide](docs/integration.md) for detailed setup instructions.
|
|
|
188
208
|
- **Resource management**: Metadata tracking and automatic cleanup
|
|
189
209
|
|
|
190
210
|
### Validation & UX
|
|
211
|
+
|
|
191
212
|
- **Real-time validation**: As-you-type with visual feedback
|
|
192
213
|
- **Schema validation**: Comprehensive error reporting
|
|
193
214
|
- **Internationalization**: English/Russian built-in, extensible
|
|
194
215
|
- **Tooltips**: Field descriptions and hints
|
|
195
216
|
- **Responsive design**: Mobile-friendly interface
|
|
196
217
|
- **Read-only mode**: Display data without editing
|
|
218
|
+
- **Action buttons**: Custom buttons in readonly mode with configurable handlers
|
|
197
219
|
|
|
198
220
|
## Documentation
|
|
199
221
|
|
|
@@ -209,6 +231,7 @@ See [Integration Guide](docs/integration.md) for detailed setup instructions.
|
|
|
209
231
|
**Version Index**: [https://picaz-form-builder.website.yandexcloud.net/index.html](https://picaz-form-builder.website.yandexcloud.net/index.html)
|
|
210
232
|
|
|
211
233
|
Features a 3-column interface:
|
|
234
|
+
|
|
212
235
|
- **Schema Editor**: Edit JSON schema with validation
|
|
213
236
|
- **Live Preview**: See form render in real-time
|
|
214
237
|
- **Data Output**: View/export form data as JSON
|
package/dist/demo.js
CHANGED
|
@@ -18,6 +18,11 @@ const EXAMPLE_SCHEMA = {
|
|
|
18
18
|
mime: ["image/png", "image/jpeg", "image/gif"],
|
|
19
19
|
},
|
|
20
20
|
maxSizeMB: 10,
|
|
21
|
+
actions: [
|
|
22
|
+
{ value: "cover1.retry", label: "А давай ещё разок" },
|
|
23
|
+
{ value: "cover1.enhance", label: "Улучшить качество" },
|
|
24
|
+
{ value: "cover1.crop", label: "Обрезать изображение" },
|
|
25
|
+
],
|
|
21
26
|
},
|
|
22
27
|
{
|
|
23
28
|
type: "files",
|
|
@@ -241,6 +246,39 @@ class InMemoryFileStorage {
|
|
|
241
246
|
// Initialize file storage
|
|
242
247
|
const fileStorage = new InMemoryFileStorage();
|
|
243
248
|
|
|
249
|
+
// Cache for action value -> label mapping for efficient lookup
|
|
250
|
+
let actionLabelMap = new Map();
|
|
251
|
+
|
|
252
|
+
// Build action value -> label mapping from schema for efficient lookup
|
|
253
|
+
function buildActionLabelMap(schema) {
|
|
254
|
+
const map = new Map();
|
|
255
|
+
|
|
256
|
+
function processElements(elements) {
|
|
257
|
+
if (!Array.isArray(elements)) return;
|
|
258
|
+
|
|
259
|
+
for (const element of elements) {
|
|
260
|
+
if (element.actions && Array.isArray(element.actions)) {
|
|
261
|
+
for (const action of element.actions) {
|
|
262
|
+
if (action.value && action.label) {
|
|
263
|
+
map.set(action.value, action.label);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Process nested group elements
|
|
269
|
+
if (element.elements && Array.isArray(element.elements)) {
|
|
270
|
+
processElements(element.elements);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (schema && schema.elements) {
|
|
276
|
+
processElements(schema.elements);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return map;
|
|
280
|
+
}
|
|
281
|
+
|
|
244
282
|
// DOM element references
|
|
245
283
|
const el = {
|
|
246
284
|
schemaInput: document.getElementById("schemaInput"),
|
|
@@ -322,7 +360,24 @@ function setupFormBuilder() {
|
|
|
322
360
|
return thumbnailUrl;
|
|
323
361
|
});
|
|
324
362
|
|
|
325
|
-
|
|
363
|
+
// Action handler - display message when action button is clicked
|
|
364
|
+
FormBuilder.setActionHandler((value) => {
|
|
365
|
+
// Use cached action map for O(1) lookup instead of re-parsing schema
|
|
366
|
+
const actionLabel = actionLabelMap.get(value) || value; // fallback to value
|
|
367
|
+
|
|
368
|
+
console.log("Action clicked:", { label: actionLabel, value });
|
|
369
|
+
|
|
370
|
+
// Show message to user (compatible with all environments)
|
|
371
|
+
if (typeof window !== "undefined" && window.alert) {
|
|
372
|
+
window.alert(`${actionLabel} clicked: ${value}`);
|
|
373
|
+
} else {
|
|
374
|
+
console.log(`Demo action: ${actionLabel} clicked: ${value}`);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
console.log(
|
|
379
|
+
"FormBuilder configured with in-memory file handlers and action handler",
|
|
380
|
+
);
|
|
326
381
|
}
|
|
327
382
|
|
|
328
383
|
// Schema management functions
|
|
@@ -342,6 +397,9 @@ function applyCurrentSchema() {
|
|
|
342
397
|
return false;
|
|
343
398
|
}
|
|
344
399
|
|
|
400
|
+
// Build action value -> label map for efficient lookup
|
|
401
|
+
actionLabelMap = buildActionLabelMap(schema);
|
|
402
|
+
|
|
345
403
|
// Set mode based on toggle
|
|
346
404
|
const isReadOnly = el.readOnlyToggle.checked;
|
|
347
405
|
FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
|
package/dist/form-builder.js
CHANGED
|
@@ -11,6 +11,8 @@ const state = {
|
|
|
11
11
|
downloadFile: null,
|
|
12
12
|
getThumbnail: null,
|
|
13
13
|
getDownloadUrl: null,
|
|
14
|
+
// Action handler
|
|
15
|
+
actionHandler: null,
|
|
14
16
|
// Default implementations
|
|
15
17
|
enableFilePreview: true,
|
|
16
18
|
maxPreviewSize: "200px",
|
|
@@ -242,6 +244,43 @@ function renderElement(element, ctx) {
|
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
|
|
247
|
+
// Add action buttons in readonly mode
|
|
248
|
+
if (
|
|
249
|
+
state.config.readonly &&
|
|
250
|
+
element.actions &&
|
|
251
|
+
Array.isArray(element.actions) &&
|
|
252
|
+
element.actions.length > 0
|
|
253
|
+
) {
|
|
254
|
+
const actionsContainer = document.createElement("div");
|
|
255
|
+
actionsContainer.className = "mt-3 flex flex-wrap gap-2";
|
|
256
|
+
|
|
257
|
+
element.actions.forEach((action) => {
|
|
258
|
+
if (action.value && action.label) {
|
|
259
|
+
const actionBtn = document.createElement("button");
|
|
260
|
+
actionBtn.type = "button";
|
|
261
|
+
actionBtn.className =
|
|
262
|
+
"px-3 py-1.5 text-sm bg-blue-50 border border-blue-200 text-blue-700 rounded-lg hover:bg-blue-100 transition-colors";
|
|
263
|
+
actionBtn.textContent = action.label;
|
|
264
|
+
|
|
265
|
+
actionBtn.addEventListener("click", (e) => {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
|
|
269
|
+
if (
|
|
270
|
+
state.config.actionHandler &&
|
|
271
|
+
typeof state.config.actionHandler === "function"
|
|
272
|
+
) {
|
|
273
|
+
state.config.actionHandler(action.value);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
actionsContainer.appendChild(actionBtn);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
wrapper.appendChild(actionsContainer);
|
|
282
|
+
}
|
|
283
|
+
|
|
245
284
|
return wrapper;
|
|
246
285
|
}
|
|
247
286
|
|
|
@@ -1817,6 +1856,10 @@ function setThumbnailHandler(thumbnailFn) {
|
|
|
1817
1856
|
state.config.getThumbnail = thumbnailFn;
|
|
1818
1857
|
}
|
|
1819
1858
|
|
|
1859
|
+
function setActionHandler(actionFn) {
|
|
1860
|
+
state.config.actionHandler = actionFn;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1820
1863
|
function setMode(mode) {
|
|
1821
1864
|
state.config.readonly = mode === "readonly";
|
|
1822
1865
|
}
|
|
@@ -1879,6 +1922,7 @@ const formBuilderAPI = {
|
|
|
1879
1922
|
setUploadHandler,
|
|
1880
1923
|
setDownloadHandler,
|
|
1881
1924
|
setThumbnailHandler,
|
|
1925
|
+
setActionHandler,
|
|
1882
1926
|
setMode,
|
|
1883
1927
|
setLocale,
|
|
1884
1928
|
getFormData,
|
|
@@ -1903,6 +1947,7 @@ export {
|
|
|
1903
1947
|
setUploadHandler,
|
|
1904
1948
|
setDownloadHandler,
|
|
1905
1949
|
setThumbnailHandler,
|
|
1950
|
+
setActionHandler,
|
|
1906
1951
|
setMode,
|
|
1907
1952
|
setLocale,
|
|
1908
1953
|
getFormData,
|