@dmitryvim/form-builder 0.1.33 → 0.1.34

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/dist/elements.js CHANGED
@@ -17,7 +17,7 @@ function initializeFormBuilder() {
17
17
  name: file.name,
18
18
  type: file.type,
19
19
  size: file.size,
20
- uploadDate: new Date().toISOString()
20
+ uploadDate: new Date().toISOString(),
21
21
  });
22
22
 
23
23
  return resourceId;
@@ -27,7 +27,7 @@ function initializeFormBuilder() {
27
27
  FormBuilder.setDownloadHandler((resourceId, fileName) => {
28
28
  const fileData = fileStorage.get(resourceId);
29
29
  if (fileData && fileData.url) {
30
- const a = document.createElement('a');
30
+ const a = document.createElement("a");
31
31
  a.href = fileData.url;
32
32
  a.download = fileName || fileData.name;
33
33
  document.body.appendChild(a);
@@ -53,266 +53,297 @@ const elementSchemas = {
53
53
  text: {
54
54
  schema: {
55
55
  version: "0.3",
56
- elements: [{
57
- type: "text",
58
- key: "username",
59
- label: "Username",
60
- placeholder: "Enter your username",
61
- required: true,
62
- minLength: 3,
63
- maxLength: 20,
64
- pattern: "^[a-zA-Z0-9_]+$",
65
- description: "Username must be 3-20 characters, alphanumeric and underscore only",
66
- default: "",
67
- actions: [{
68
- value: "check_availability",
69
- label: "Check Availability"
70
- }]
71
- }]
56
+ elements: [
57
+ {
58
+ type: "text",
59
+ key: "username",
60
+ label: "Username",
61
+ placeholder: "Enter your username",
62
+ required: true,
63
+ minLength: 3,
64
+ maxLength: 20,
65
+ pattern: "^[a-zA-Z0-9_]+$",
66
+ description:
67
+ "Username must be 3-20 characters, alphanumeric and underscore only",
68
+ default: "",
69
+ actions: [
70
+ {
71
+ value: "check_availability",
72
+ label: "Check Availability",
73
+ },
74
+ ],
75
+ },
76
+ ],
72
77
  },
73
- prefill: { username: "john_doe" }
78
+ prefill: { username: "john_doe" },
74
79
  },
75
80
 
76
- 'text-multiple': {
81
+ "text-multiple": {
77
82
  schema: {
78
83
  version: "0.3",
79
- elements: [{
80
- type: "text",
81
- key: "keywords",
82
- label: "Keywords",
83
- placeholder: "Enter keyword",
84
- required: true,
85
- multiple: true,
86
- minCount: 2,
87
- maxCount: 5,
88
- minLength: 2,
89
- maxLength: 30,
90
- description: "Add multiple keywords, minimum 2 required, maximum 5 allowed",
91
- default: "example"
92
- }]
84
+ elements: [
85
+ {
86
+ type: "text",
87
+ key: "keywords",
88
+ label: "Keywords",
89
+ placeholder: "Enter keyword",
90
+ required: true,
91
+ multiple: true,
92
+ minCount: 2,
93
+ maxCount: 5,
94
+ minLength: 2,
95
+ maxLength: 30,
96
+ description:
97
+ "Add multiple keywords, minimum 2 required, maximum 5 allowed",
98
+ default: "example",
99
+ },
100
+ ],
93
101
  },
94
- prefill: { keywords: ["javascript", "react", "nodejs"] }
102
+ prefill: { keywords: ["javascript", "react", "nodejs"] },
95
103
  },
96
104
 
97
105
  textarea: {
98
106
  schema: {
99
107
  version: "0.3",
100
- elements: [{
101
- type: "textarea",
102
- key: "description",
103
- label: "Description",
104
- placeholder: "Enter detailed description...",
105
- rows: 6,
106
- required: false,
107
- minLength: 10,
108
- maxLength: 500,
109
- description: "Provide a detailed description of your project"
110
- }]
108
+ elements: [
109
+ {
110
+ type: "textarea",
111
+ key: "description",
112
+ label: "Description",
113
+ placeholder: "Enter detailed description...",
114
+ rows: 6,
115
+ required: false,
116
+ minLength: 10,
117
+ maxLength: 500,
118
+ description: "Provide a detailed description of your project",
119
+ },
120
+ ],
121
+ },
122
+ prefill: {
123
+ description:
124
+ "This is a sample project description that demonstrates the textarea field with multiple lines of text.",
111
125
  },
112
- prefill: { description: "This is a sample project description that demonstrates the textarea field with multiple lines of text." }
113
126
  },
114
127
 
115
- 'textarea-multiple': {
128
+ "textarea-multiple": {
116
129
  schema: {
117
130
  version: "0.3",
118
- elements: [{
119
- type: "textarea",
120
- key: "features",
121
- label: "Product Features",
122
- placeholder: "Describe a feature...",
123
- rows: 4,
124
- required: true,
125
- multiple: true,
126
- minCount: 2,
127
- maxCount: 4,
128
- minLength: 20,
129
- maxLength: 200,
130
- description: "List key product features, minimum 2 required"
131
- }]
131
+ elements: [
132
+ {
133
+ type: "textarea",
134
+ key: "features",
135
+ label: "Product Features",
136
+ placeholder: "Describe a feature...",
137
+ rows: 4,
138
+ required: true,
139
+ multiple: true,
140
+ minCount: 2,
141
+ maxCount: 4,
142
+ minLength: 20,
143
+ maxLength: 200,
144
+ description: "List key product features, minimum 2 required",
145
+ },
146
+ ],
132
147
  },
133
148
  prefill: {
134
149
  features: [
135
150
  "High-quality materials ensure durability and long-lasting performance",
136
- "User-friendly interface makes it easy to operate for all skill levels"
137
- ]
138
- }
151
+ "User-friendly interface makes it easy to operate for all skill levels",
152
+ ],
153
+ },
139
154
  },
140
155
 
141
156
  number: {
142
157
  schema: {
143
158
  version: "0.3",
144
- elements: [{
145
- type: "number",
146
- key: "price",
147
- label: "Price (USD)",
148
- placeholder: "0.00",
149
- required: true,
150
- min: 0,
151
- max: 10000,
152
- step: 0.01,
153
- default: 99.99,
154
- description: "Product price in US dollars"
155
- }]
159
+ elements: [
160
+ {
161
+ type: "number",
162
+ key: "price",
163
+ label: "Price (USD)",
164
+ placeholder: "0.00",
165
+ required: true,
166
+ min: 0,
167
+ max: 10000,
168
+ step: 0.01,
169
+ default: 99.99,
170
+ description: "Product price in US dollars",
171
+ },
172
+ ],
156
173
  },
157
- prefill: { price: 149.99 }
174
+ prefill: { price: 149.99 },
158
175
  },
159
176
 
160
- 'number-multiple': {
177
+ "number-multiple": {
161
178
  schema: {
162
179
  version: "0.3",
163
- elements: [{
164
- type: "number",
165
- key: "dimensions",
166
- label: "Dimensions (cm)",
167
- placeholder: "0.0",
168
- required: true,
169
- multiple: true,
170
- minCount: 2,
171
- maxCount: 3,
172
- min: 0.1,
173
- max: 500,
174
- step: 0.1,
175
- decimals: 1,
176
- default: 10.0,
177
- description: "Enter product dimensions: width, height, and optionally depth"
178
- }]
180
+ elements: [
181
+ {
182
+ type: "number",
183
+ key: "dimensions",
184
+ label: "Dimensions (cm)",
185
+ placeholder: "0.0",
186
+ required: true,
187
+ multiple: true,
188
+ minCount: 2,
189
+ maxCount: 3,
190
+ min: 0.1,
191
+ max: 500,
192
+ step: 0.1,
193
+ decimals: 1,
194
+ default: 10.0,
195
+ description:
196
+ "Enter product dimensions: width, height, and optionally depth",
197
+ },
198
+ ],
179
199
  },
180
- prefill: { dimensions: [25.5, 30.0, 15.2] }
200
+ prefill: { dimensions: [25.5, 30.0, 15.2] },
181
201
  },
182
202
 
183
203
  select: {
184
204
  schema: {
185
205
  version: "0.3",
186
- elements: [{
187
- type: "select",
188
- key: "category",
189
- label: "Product Category",
190
- required: true,
191
- options: [
192
- {value: "electronics", label: "Electronics"},
193
- {value: "clothing", label: "Clothing & Apparel"},
194
- {value: "books", label: "Books & Media"},
195
- {value: "home", label: "Home & Garden"},
196
- {value: "sports", label: "Sports & Recreation"}
197
- ],
198
- default: "electronics",
199
- description: "Select the primary category for your product"
200
- }]
206
+ elements: [
207
+ {
208
+ type: "select",
209
+ key: "category",
210
+ label: "Product Category",
211
+ required: true,
212
+ options: [
213
+ { value: "electronics", label: "Electronics" },
214
+ { value: "clothing", label: "Clothing & Apparel" },
215
+ { value: "books", label: "Books & Media" },
216
+ { value: "home", label: "Home & Garden" },
217
+ { value: "sports", label: "Sports & Recreation" },
218
+ ],
219
+ default: "electronics",
220
+ description: "Select the primary category for your product",
221
+ },
222
+ ],
201
223
  },
202
- prefill: { category: "books" }
224
+ prefill: { category: "books" },
203
225
  },
204
226
 
205
- 'select-multiple': {
227
+ "select-multiple": {
206
228
  schema: {
207
229
  version: "0.3",
208
- elements: [{
209
- type: "select",
210
- key: "tags",
211
- label: "Product Tags",
212
- required: true,
213
- multiple: true,
214
- minCount: 1,
215
- maxCount: 3,
216
- options: [
217
- {value: "new", label: "New"},
218
- {value: "popular", label: "Popular"},
219
- {value: "sale", label: "On Sale"},
220
- {value: "featured", label: "Featured"},
221
- {value: "limited", label: "Limited Edition"},
222
- {value: "bestseller", label: "Bestseller"}
223
- ],
224
- default: "new",
225
- description: "Select relevant tags for the product, maximum 3 allowed"
226
- }]
230
+ elements: [
231
+ {
232
+ type: "select",
233
+ key: "tags",
234
+ label: "Product Tags",
235
+ required: true,
236
+ multiple: true,
237
+ minCount: 1,
238
+ maxCount: 3,
239
+ options: [
240
+ { value: "new", label: "New" },
241
+ { value: "popular", label: "Popular" },
242
+ { value: "sale", label: "On Sale" },
243
+ { value: "featured", label: "Featured" },
244
+ { value: "limited", label: "Limited Edition" },
245
+ { value: "bestseller", label: "Bestseller" },
246
+ ],
247
+ default: "new",
248
+ description:
249
+ "Select relevant tags for the product, maximum 3 allowed",
250
+ },
251
+ ],
227
252
  },
228
- prefill: { tags: ["popular", "featured"] }
253
+ prefill: { tags: ["popular", "featured"] },
229
254
  },
230
255
 
231
256
  file: {
232
257
  schema: {
233
258
  version: "0.3",
234
- elements: [{
235
- type: "file",
236
- key: "avatar",
237
- label: "Profile Picture",
238
- required: false,
239
- accept: {
240
- extensions: ["jpg", "jpeg", "png", "gif", "webp"],
241
- maxSize: 5242880
259
+ elements: [
260
+ {
261
+ type: "file",
262
+ key: "avatar",
263
+ label: "Profile Picture",
264
+ required: false,
265
+ accept: {
266
+ extensions: ["jpg", "jpeg", "png", "gif", "webp"],
267
+ maxSize: 5242880,
268
+ },
269
+ description: "Upload your profile picture (max 5MB, images only)",
242
270
  },
243
- description: "Upload your profile picture (max 5MB, images only)"
244
- }]
271
+ ],
245
272
  },
246
- prefill: { avatar: null }
273
+ prefill: { avatar: null },
247
274
  },
248
275
 
249
276
  files: {
250
277
  schema: {
251
278
  version: "0.3",
252
- elements: [{
253
- type: "files",
254
- key: "attachments",
255
- label: "Project Attachments",
256
- required: false,
257
- min: 1,
258
- max: 10,
259
- accept: {
260
- extensions: ["jpg", "png", "pdf", "doc", "docx", "mp4"],
261
- maxSize: 10485760
279
+ elements: [
280
+ {
281
+ type: "files",
282
+ key: "attachments",
283
+ label: "Project Attachments",
284
+ required: false,
285
+ min: 1,
286
+ max: 10,
287
+ accept: {
288
+ extensions: ["jpg", "png", "pdf", "doc", "docx", "mp4"],
289
+ maxSize: 10485760,
290
+ },
291
+ description: "Upload project files (max 10MB each, 1-10 files)",
262
292
  },
263
- description: "Upload project files (max 10MB each, 1-10 files)"
264
- }]
293
+ ],
265
294
  },
266
- prefill: { attachments: [] }
295
+ prefill: { attachments: [] },
267
296
  },
268
297
 
269
298
  group: {
270
299
  schema: {
271
300
  version: "0.3",
272
- elements: [{
273
- type: "group",
274
- key: "contacts",
275
- label: "Contact Information",
276
- repeat: {
277
- min: 1,
278
- max: 5
279
- },
280
- elements: [
281
- {
282
- type: "text",
283
- key: "name",
284
- label: "Contact Name",
285
- required: true
301
+ elements: [
302
+ {
303
+ type: "group",
304
+ key: "contacts",
305
+ label: "Contact Information",
306
+ repeat: {
307
+ min: 1,
308
+ max: 5,
286
309
  },
287
- {
288
- type: "text",
289
- key: "email",
290
- label: "Email Address",
291
- required: true,
292
- pattern: "^[^@]+@[^@]+\\.[^@]+$"
293
- },
294
- {
295
- type: "select",
296
- key: "role",
297
- label: "Role",
298
- options: [
299
- {value: "primary", label: "Primary Contact"},
300
- {value: "secondary", label: "Secondary Contact"},
301
- {value: "technical", label: "Technical Contact"}
302
- ],
303
- default: "primary"
304
- }
305
- ],
306
- description: "Add contact information for your project"
307
- }]
310
+ elements: [
311
+ {
312
+ type: "text",
313
+ key: "name",
314
+ label: "Contact Name",
315
+ required: true,
316
+ },
317
+ {
318
+ type: "text",
319
+ key: "email",
320
+ label: "Email Address",
321
+ required: true,
322
+ pattern: "^[^@]+@[^@]+\\.[^@]+$",
323
+ },
324
+ {
325
+ type: "select",
326
+ key: "role",
327
+ label: "Role",
328
+ options: [
329
+ { value: "primary", label: "Primary Contact" },
330
+ { value: "secondary", label: "Secondary Contact" },
331
+ { value: "technical", label: "Technical Contact" },
332
+ ],
333
+ default: "primary",
334
+ },
335
+ ],
336
+ description: "Add contact information for your project",
337
+ },
338
+ ],
308
339
  },
309
340
  prefill: {
310
341
  contacts: [
311
342
  { name: "John Smith", email: "john@company.com", role: "primary" },
312
- { name: "Jane Doe", email: "jane@company.com", role: "technical" }
313
- ]
314
- }
315
- }
343
+ { name: "Jane Doe", email: "jane@company.com", role: "technical" },
344
+ ],
345
+ },
346
+ },
316
347
  };
317
348
 
318
349
  // Update preview for an element
@@ -321,13 +352,15 @@ function updatePreview(elementType) {
321
352
  if (!elementData) return;
322
353
 
323
354
  const previewContainer = document.getElementById(`${elementType}-preview`);
324
- const readonlyToggle = document.querySelector(`input[data-element="${elementType}"]`);
355
+ const readonlyToggle = document.querySelector(
356
+ `input[data-element="${elementType}"]`,
357
+ );
325
358
 
326
359
  if (!previewContainer) return;
327
360
 
328
361
  // Set form root and mode
329
362
  FormBuilder.setFormRoot(previewContainer);
330
- FormBuilder.setMode(readonlyToggle?.checked ? 'readonly' : 'edit');
363
+ FormBuilder.setMode(readonlyToggle?.checked ? "readonly" : "edit");
331
364
 
332
365
  // Render the form
333
366
  FormBuilder.renderForm(elementData.schema, elementData.prefill);
@@ -353,7 +386,7 @@ function applySchemaChanges(elementType) {
353
386
  elementSchemas[elementType].schema.elements[0] = elementSchema;
354
387
  updatePreview(elementType);
355
388
  } catch (error) {
356
- console.error('Invalid JSON in schema:', error);
389
+ console.error("Invalid JSON in schema:", error);
357
390
  // Reset to original if invalid
358
391
  updateSchemaTextarea(elementType);
359
392
  }
@@ -361,59 +394,62 @@ function applySchemaChanges(elementType) {
361
394
 
362
395
  // Initialize navigation
363
396
  function initializeNavigation() {
364
- const navItems = document.querySelectorAll('.nav-item');
397
+ const navItems = document.querySelectorAll(".nav-item");
365
398
 
366
399
  // Set initial active state
367
400
  if (navItems.length > 0) {
368
- navItems[0].classList.add('active');
401
+ navItems[0].classList.add("active");
369
402
  }
370
403
 
371
- navItems.forEach(item => {
372
- item.addEventListener('click', (e) => {
404
+ navItems.forEach((item) => {
405
+ item.addEventListener("click", (e) => {
373
406
  e.preventDefault();
374
407
 
375
408
  // Remove active class from all items
376
- navItems.forEach(nav => nav.classList.remove('active'));
409
+ navItems.forEach((nav) => nav.classList.remove("active"));
377
410
 
378
411
  // Add active class to clicked item
379
- item.classList.add('active');
412
+ item.classList.add("active");
380
413
 
381
414
  // Scroll to element
382
- const elementId = item.getAttribute('href').substring(1);
415
+ const elementId = item.getAttribute("href").substring(1);
383
416
  const element = document.getElementById(elementId);
384
417
  if (element) {
385
- element.scrollIntoView({ behavior: 'smooth', block: 'start' });
418
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
386
419
  }
387
420
  });
388
421
  });
389
422
 
390
423
  // Handle scroll-based active states
391
- const sections = document.querySelectorAll('.element-section');
392
- const observer = new IntersectionObserver((entries) => {
393
- entries.forEach(entry => {
394
- if (entry.isIntersecting) {
395
- const id = entry.target.id;
396
- navItems.forEach(nav => {
397
- nav.classList.remove('active');
398
- if (nav.getAttribute('href') === `#${id}`) {
399
- nav.classList.add('active');
400
- }
401
- });
402
- }
403
- });
404
- }, {
405
- rootMargin: '-20% 0% -20% 0%',
406
- threshold: 0.1
407
- });
424
+ const sections = document.querySelectorAll(".element-section");
425
+ const observer = new IntersectionObserver(
426
+ (entries) => {
427
+ entries.forEach((entry) => {
428
+ if (entry.isIntersecting) {
429
+ const id = entry.target.id;
430
+ navItems.forEach((nav) => {
431
+ nav.classList.remove("active");
432
+ if (nav.getAttribute("href") === `#${id}`) {
433
+ nav.classList.add("active");
434
+ }
435
+ });
436
+ }
437
+ });
438
+ },
439
+ {
440
+ rootMargin: "-20% 0% -20% 0%",
441
+ threshold: 0.1,
442
+ },
443
+ );
408
444
 
409
- sections.forEach(section => {
445
+ sections.forEach((section) => {
410
446
  observer.observe(section);
411
447
  });
412
448
  }
413
449
 
414
450
  // Initialize all elements
415
451
  function initializeElements() {
416
- Object.keys(elementSchemas).forEach(elementType => {
452
+ Object.keys(elementSchemas).forEach((elementType) => {
417
453
  // Update textarea with schema
418
454
  updateSchemaTextarea(elementType);
419
455
 
@@ -421,7 +457,7 @@ function initializeElements() {
421
457
  const textarea = document.getElementById(`${elementType}-schema`);
422
458
  if (textarea) {
423
459
  let debounceTimer;
424
- textarea.addEventListener('input', () => {
460
+ textarea.addEventListener("input", () => {
425
461
  clearTimeout(debounceTimer);
426
462
  debounceTimer = setTimeout(() => {
427
463
  applySchemaChanges(elementType);
@@ -430,9 +466,11 @@ function initializeElements() {
430
466
  }
431
467
 
432
468
  // Setup readonly toggle
433
- const readonlyToggle = document.querySelector(`input[data-element="${elementType}"]`);
469
+ const readonlyToggle = document.querySelector(
470
+ `input[data-element="${elementType}"]`,
471
+ );
434
472
  if (readonlyToggle) {
435
- readonlyToggle.addEventListener('change', () => {
473
+ readonlyToggle.addEventListener("change", () => {
436
474
  updatePreview(elementType);
437
475
  });
438
476
  }
@@ -443,8 +481,8 @@ function initializeElements() {
443
481
  }
444
482
 
445
483
  // Initialize everything when DOM is loaded
446
- document.addEventListener('DOMContentLoaded', () => {
484
+ document.addEventListener("DOMContentLoaded", () => {
447
485
  initializeFormBuilder();
448
486
  initializeNavigation();
449
487
  initializeElements();
450
- });
488
+ });